diff --git a/lib/ai.go b/lib/ai.go index b36815d..0b9e256 100644 --- a/lib/ai.go +++ b/lib/ai.go @@ -3,6 +3,9 @@ package lib import ( "bytes" "fmt" + "os" + "path/filepath" + "strings" "sync" "sync/atomic" "time" @@ -12,24 +15,27 @@ import ( ) type myAI struct { - robots []*Robot - scenes []*scene - iscenes []IScene - tickets Tickets //票池 - useCount uint32 //计数(压入的用户数) - lock sync.Mutex // - config *storage.Config //配置 + robots []*Robot + scenes []*scene + iscenes []IScene + tickets Tickets //票池 + useCount uint32 //计数(压入的用户数) + useCountTotal uint32 //总数 + lock sync.Mutex //合并数据锁 + config *storage.Config //配置 + ReportMap map[string]map[string]*Statistics //测试报告 key1:场景 key2:协议 } func NewAI(aip AIParam) (*myAI, error) { - if err := aip.Check(); err != nil { - return nil, err - } + // if err := aip.Check(); err != nil { + // return nil, err + // } ai := &myAI{ - scenes: make([]*scene, 0), - config: aip.Config, - iscenes: aip.Scenes, + scenes: make([]*scene, 0), + config: aip.Config, + iscenes: aip.Scenes, + ReportMap: make(map[string]map[string]*Statistics), } if err := ai.init(); err != nil { @@ -56,7 +62,7 @@ func (m *myAI) init() error { return nil } -func (m *myAI) appendRobot(robot *Robot) { +func (m *myAI) AppendRobot(robot *Robot) { defer m.lock.Unlock() m.lock.Lock() m.robots = append(m.robots, robot) @@ -79,12 +85,28 @@ func (m *myAI) Start() bool { atomic.AddUint32(&m.useCount, 1) robot := NewRobot(m.config) robot.SetScenes(m.iscenes) - m.appendRobot(robot) + m.AppendRobot(robot) robot.Start() + atomic.AddUint32(&m.useCountTotal, 1) + // m.genReport(robot) }() } }() + go func() { + for { + total := atomic.LoadUint32(&m.useCountTotal) + if total == m.config.Global.UserCountTotal { + logrus.Debug("开始合并报告") + m.MergeResult() + logrus.Debug("结束合并报告") + //打印报告 + m.genReport() + break + } + } + }() + return true } @@ -92,6 +114,87 @@ func (m *myAI) Stop() { } -func (m *myAI) ShowResult() { +func (m *myAI) MergeResult() { + m.lock.Lock() + defer m.lock.Unlock() + n := make(map[string]map[string]*Statistics) + for _, r := range m.robots { + if len(m.ReportMap) == 0 { + m.ReportMap = r.ReportMap + } else { + x := m.ReportMap //已存在的 + y := r.ReportMap //将要合并的 + for i, v := range x { + for j, w := range y { + //场景相同 + if i == j { + for k, a := range v { + for l, b := range w { + //判断协议是否相同 + if k == l { + statis := &Statistics{} + statis.ElapseTotal = (a.ElapseTotal + b.ElapseTotal) / 2 + statis.AvgElapse = (a.AvgElapse + b.AvgElapse) / 2 + statis.CallCount = a.CallCount + b.CallCount + statis.MaxElapse = (a.MaxElapse + b.MaxElapse) / 2 + statis.MinElapse = (a.MinElapse + b.MinElapse) / 2 + statis.Route = a.Route + statis.SceneName = i + if n[i][k] == nil { + n[i] = make(map[string]*Statistics) + } + n[i][k] = statis + } else { + if _, ok := n[i][k]; !ok { + n[i][k] = a + } + if _, ok := n[i][l]; !ok { + n[i][l] = b + } + } + } + } + + } else { + if _, ok := n[i]; !ok { + n[i] = v + } + if _, ok := n[j]; !ok { + n[j] = w + } + } + } + } + } + } + + m.ReportMap = n +} + +func (m *myAI) genReport() { + var buf bytes.Buffer + buf.WriteString("测试报告\n") + buf.WriteString(fmt.Sprintf("用户总数:%d \n", + m.config.Global.UserCountTotal)) + var msgs []string + for k, routes := range m.ReportMap { + for r, d := range routes { + msgs = append(msgs, fmt.Sprintf("【%s】协议:%s 调用次数:%d 总耗时:%v 平均耗时:%v 最大耗时:%v 最小耗时:%v", + k, r, d.CallCount, d.ElapseTotal.String(), d.AvgElapse.String(), d.MaxElapse.String(), d.MinElapse.String())) + } + } + record := strings.Join(msgs, "\n") + buf.WriteString(record) + buf.WriteString("\n------------------------------------------------------------------------------------------------------\n") + // logrus.WithField("res", buf.String()).Debug("报告内容") + file, err := os.OpenFile(filepath.Join("./", "report.log"), os.O_APPEND|os.O_CREATE, os.ModePerm) + if err != nil { + logrus.Error(err) + } + defer file.Close() + + if _, err := file.WriteString(buf.String()); err != nil { + logrus.Error(err) + } } diff --git a/lib/base.go b/lib/base.go index 2dd6eb5..7a13e0f 100644 --- a/lib/base.go +++ b/lib/base.go @@ -26,10 +26,11 @@ type SceneInfo struct { } type CallResult struct { - ID int64 // ID。 - MainType string - SubType string - Elapse time.Duration // 耗时。 + ID int64 // ID + SceneName string + MainType string + SubType string + Elapse time.Duration // 耗时。 } type Statistics struct { @@ -39,6 +40,7 @@ type Statistics struct { AvgElapse time.Duration //平均耗时 CallCount int64 //调用次数 Route string //协议名称 + SceneName string //场景名称 } const ( diff --git a/lib/robot.go b/lib/robot.go index 758542b..53b3f49 100644 --- a/lib/robot.go +++ b/lib/robot.go @@ -32,16 +32,19 @@ type IRobot interface { type Robot struct { IStore + scene IScene //场景 account string sid string conn *websocket.Conn - data map[string]interface{} //机器人缓存数据 - status uint32 //状态 - lock sync.Mutex // - sceneQueue *Queue[IScene] //场景队列 - config *storage.Config //配置 - resultCh chan *CallResult //请求结果通道 - sceneResultCh chan *CallResult //场景结果通道 + data map[string]interface{} //机器人缓存数据 + status uint32 //状态 + lock sync.Mutex //数据缓存锁 + sceneQueue *Queue[IScene] //场景队列 + config *storage.Config //配置 + resultCh chan *CallResult //请求结果通道 + sceneResultCh chan *CallResult //场景结果通道 + ReportMap map[string]map[string]*Statistics //测试报告 key1:场景 key2:协议 + elipseTotal time.Duration } func NewRobot(config *storage.Config) *Robot { @@ -51,6 +54,7 @@ func NewRobot(config *storage.Config) *Robot { config: config, resultCh: make(chan *CallResult, 100), sceneResultCh: make(chan *CallResult, 50), + ReportMap: make(map[string]map[string]*Statistics), } robot.Store("sid", config.Global.SId) return robot @@ -76,11 +80,16 @@ func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.M defer func() { t := time.Since(start) logrus.WithFields(logrus.Fields{"MainType": mainType, "SubType": subType, "rsp": rsp, "since": t.String()}).Debug("接收消息") + var name string + if r.scene != nil { + name = r.scene.Info().Name + } // 发送请求结果 r.SendResult(&CallResult{ - MainType: mainType, - SubType: subType, - Elapse: t, + SceneName: name, + MainType: mainType, + SubType: subType, + Elapse: t, }) }() @@ -182,13 +191,17 @@ func (m *Robot) Stop() bool { } func (m *Robot) syncCall() { + // var sceneElipseTotal time.Duration // 场景总耗时 for { scene, err := m.sceneQueue.Pop() if err != nil { logrus.WithField("err", err).Warn("所有场景执行结束") - m.prepareToStop() + // m.prepareToStop() + // m.genReport(sceneElipseTotal) return } + m.scene = scene + info := m.scene.Info() start := time.Now() //这里执行会花很长时间 @@ -197,12 +210,11 @@ func (m *Robot) syncCall() { break } elapsedTime := time.Since(start) - info := scene.Info() + + m.elipseTotal += elapsedTime logrus.WithField("t", elapsedTime.String()).Debug("场景【" + info.Name + "】执行完毕耗时统计") - //结束 - // m.prepareToStop() //显示场景结果 - m.ShowResult() + m.processResult() } } @@ -228,27 +240,14 @@ func (m *Robot) SendResult(result *CallResult) bool { } } -func (m *Robot) ShowResult() { +func (m *Robot) processResult() { go func() { - logrus.Debug("开始生成测试报告") - defer logrus.Debug("测试报告生成完成") - routes := make(map[string]*Statistics) + defer m.lock.Unlock() + m.lock.Lock() for r := range m.resultCh { head := fmt.Sprintf("%s.%s", r.MainType, r.SubType) - logrus.WithField("head", head).Debug("读结果") - if len(routes) == 0 { - statis := &Statistics{ - Route: head, - ElapseTotal: r.Elapse, - MaxElapse: r.Elapse, - MinElapse: r.Elapse, - CallCount: 1, - } - avg := (statis.MaxElapse + statis.MinElapse) / 2 - statis.AvgElapse = avg - routes[head] = statis - } else { - if route, ok := routes[head]; ok { + if routes, ok := m.ReportMap[r.SceneName]; ok { + if route, y := routes[head]; y { max := route.MaxElapse min := route.MinElapse if r.Elapse > max { @@ -260,6 +259,7 @@ func (m *Robot) ShowResult() { } statis := &Statistics{ Route: head, + SceneName: r.SceneName, ElapseTotal: route.ElapseTotal + r.Elapse, MaxElapse: max, MinElapse: min, @@ -271,6 +271,7 @@ func (m *Robot) ShowResult() { } else { statis := &Statistics{ Route: head, + SceneName: r.SceneName, ElapseTotal: r.Elapse, MaxElapse: r.Elapse, MinElapse: r.Elapse, @@ -280,26 +281,52 @@ func (m *Robot) ShowResult() { statis.AvgElapse = avg routes[head] = statis } + m.ReportMap[r.SceneName] = routes + } else { + route := make(map[string]*Statistics) + statis := &Statistics{ + Route: head, + SceneName: r.SceneName, + ElapseTotal: r.Elapse, + MaxElapse: r.Elapse, + MinElapse: r.Elapse, + CallCount: 1, + } + avg := (statis.MaxElapse + statis.MinElapse) / 2 + statis.AvgElapse = avg + route[head] = statis + m.ReportMap[r.SceneName] = route } } - // 将统计结果写入文件 - var msgs []string - for k, v := range routes { - msgs = append(msgs, fmt.Sprintf("协议:%s 调用次数:%d 总耗时:%v 平均耗时:%v 最大耗时:%v 最小耗时:%v", - k, v.CallCount, v.ElapseTotal.String(), v.AvgElapse.String(), v.MaxElapse.String(), v.MinElapse.String())) - } - record := strings.Join(msgs, "\n") - var buf bytes.Buffer - buf.WriteString("测试报告\n") - buf.WriteString(record) - logrus.WithField("res", buf.String()).Debug("报告内容") - if err := ioutil.WriteFile(filepath.Join("./", "report.log"), buf.Bytes(), fs.ModePerm); err != nil { - logrus.WithField("err", err).Error("测试报告") - } }() } +// 将统计结果写入文件 +// Deprecated +func (m *Robot) genReport(e time.Duration) { + var buf bytes.Buffer + buf.WriteString("测试报告\n") + buf.WriteString(fmt.Sprintf("用户总数:%d 场景总耗时:%s\n", + m.config.Global.UserCountTotal, e.String())) + var msgs []string + for k, routes := range m.ReportMap { + // buf.WriteString(fmt.Sprintf("【%s】\n", k)) + for r, d := range routes { + msgs = append(msgs, fmt.Sprintf("【%s】协议:%s 调用次数:%d 总耗时:%v 平均耗时:%v 最大耗时:%v 最小耗时:%v", + k, r, d.CallCount, d.ElapseTotal.String(), d.AvgElapse.String(), d.MaxElapse.String(), d.MinElapse.String())) + } + + } + record := strings.Join(msgs, "\n") + buf.WriteString(record) + buf.WriteString("\n------------------------------------------------------------------------------\n") + // logrus.WithField("res", buf.String()).Debug("报告内容") + if err := ioutil.WriteFile(filepath.Join("./", "report.log"), buf.Bytes(), fs.ModePerm); err != nil { + logrus.WithField("err", err).Error("测试报告") + } +} + func (m *Robot) printIgnoredResult(result *CallResult, cause string) { resultMsg := fmt.Sprintf( "MainType=%s, SubType=%s, Elapse=%v", diff --git a/test/robot_test.go b/test/robot_test.go index b439092..6623215 100644 --- a/test/robot_test.go +++ b/test/robot_test.go @@ -6,6 +6,7 @@ import ( "legu.airobot/busi/friend" "legu.airobot/lib" + "legu.airobot/storage" ) func TestAction(t *testing.T) { @@ -47,3 +48,48 @@ func TestA(t *testing.T) { fmt.Println(i) } } + +func TestMerge(t *testing.T) { + config := &storage.Config{ + Global: &storage.Global{UserCountTotal: 2, SId: "dfz"}, + } + ma, _ := lib.NewAI(lib.AIParam{Config: config}) + + robot1 := lib.NewRobot(config) + robot1.ReportMap = make(map[string]map[string]*lib.Statistics) + robot1.ReportMap["登录"] = make(map[string]*lib.Statistics) + robot1.ReportMap["登录"]["user.login"] = &lib.Statistics{ + ElapseTotal: 5, + CallCount: 1, + AvgElapse: 1, + MaxElapse: 4, + MinElapse: 1, + Route: "user.login", + SceneName: "登录", + } + ma.AppendRobot(robot1) + /////////////// + robot2 := lib.NewRobot(config) + robot2.ReportMap = make(map[string]map[string]*lib.Statistics) + robot2.ReportMap["登录"] = make(map[string]*lib.Statistics) + robot2.ReportMap["登录"]["user.login"] = &lib.Statistics{ + ElapseTotal: 7, + CallCount: 1, + AvgElapse: 1, + MaxElapse: 5, + MinElapse: 2, + Route: "user.login", + SceneName: "登录", + } + ma.AppendRobot(robot2) + + ma.MergeResult() + + fmt.Println(ma.ReportMap) + for i, v := range ma.ReportMap { + fmt.Println(i) + for j, m := range v { + fmt.Printf("【%s】 调用次数:%v 总耗时:%v 平均:%v 最大:%v 最小:%v\n", j, m.CallCount, m.ElapseTotal, m.AvgElapse, m.MaxElapse, m.MinElapse) + } + } +} diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 7cf17aa..76ee27b 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -650,7 +650,7 @@ func (mw *MainWindow) newSceneContainer() { container.NewBorder( container.NewBorder(nil, nil, container.NewHBox(addSceneBtn), nil, searchEntry), nil, nil, nil, registerSceneList.ListWidget), dynamic) - split.Offset = 0.4 + split.Offset = 0.5 mw.changeContent(split) } contentRender()