diff --git a/src/utils/monitor.go b/src/utils/monitor.go new file mode 100644 index 0000000000000000000000000000000000000000..eefef11661112450023984b2b47b2fa6bf026d2e --- /dev/null +++ b/src/utils/monitor.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "gitee.com/scottq/go-framework/src/v1/log" + "time" +) + +//CostPoint 耗时点位信息 +type CostPoint struct { + name string + t time.Time +} + +func NewCostPoint(name string, ts ...time.Time) *CostPoint { + t := time.Now() + if len(ts) > 0 { + t = ts[0] + } + return &CostPoint{ + name, t, + } +} + +//BatchPointCostPrinter 批量点位耗时打印 +type BatchPointCostPrinter struct { + log.InvokeLog + arr []*CostPoint +} + +func NewBatchPointCostPrinter() *BatchPointCostPrinter { + return &BatchPointCostPrinter{ + arr: []*CostPoint{NewCostPoint("start")}, + } +} + +func (printer *BatchPointCostPrinter) Print(id string, ts ...*CostPoint) { + if id == "" { + id = fmt.Sprint(time.Now().UnixNano()) + } + if len(ts) == 0 { + ts = printer.arr + } + for i, p := range ts { + cost := time.Duration(0) + if i > 0 { + cost = ts[i].t.Sub(ts[i-1].t) + } + printer.Info("%s point-%s cost %s, %s", id, p.name, cost, p.t) + } +} + +func (printer *BatchPointCostPrinter) AddPoint(name string) { + printer.arr = append(printer.arr, NewCostPoint(name, time.Now())) +} diff --git a/src/utils/monitor_test.go b/src/utils/monitor_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d68bbeefa4a5d9b0e517b468d9b3efbf19104dcf --- /dev/null +++ b/src/utils/monitor_test.go @@ -0,0 +1,20 @@ +package utils + +import ( + "fmt" + v1log "gitee.com/scottq/go-framework/src/v1/log" + "os" + "testing" + "time" +) + +func TestCostPointPrinter(t *testing.T) { + printer := NewBatchPointCostPrinter() + printer.AddLogger(v1log.NewWriterLog(os.Stdout, v1log.DebugLog)) + + for i := 1; i <= 10; i++ { + time.Sleep(time.Millisecond * 100 * time.Duration(i)) + printer.AddPoint(fmt.Sprintf("step%d", i)) + } + printer.Print(fmt.Sprint(time.Now().UnixMilli())) +} diff --git a/src/utils/str.go b/src/utils/str.go new file mode 100644 index 0000000000000000000000000000000000000000..c87218f8f3beae9be3b7f614c672bd46670e4f09 --- /dev/null +++ b/src/utils/str.go @@ -0,0 +1,19 @@ +package utils + +import "strings" + +// FindStrBetweenStr 查找left和right字符串中间的字符串 +func FindStrBetweenStr(s string, left, right string) string { + i1 := strings.Index(s, left) + if left == "" { + i1 = 0 + } + i2 := strings.Index(s, right) + if right == "" { + i2 = len(s) + } + if i1 < 0 || i2 < 0 { + return "" + } + return s[i1+len(left) : i2] +} diff --git a/src/v1/log/log.go b/src/v1/log/log.go index e64f78f9592d4b47ed6fef3a482c16d4a824d94c..a22a93c61d24582666f0493e237ba6917163a494 100644 --- a/src/v1/log/log.go +++ b/src/v1/log/log.go @@ -33,6 +33,7 @@ type ILog interface { Error(log string, params ...interface{}) Panic(log string, params ...interface{}) Fatal(log string, params ...interface{}) + Write(p []byte) (n int, err error) Ctl(bool) ILog //控制是否打印,如 Ctl(true).Warn(...) } diff --git a/src/v1/log/log_default.go b/src/v1/log/log_default.go index a900088cfdc6b4502a731d6b25ca961bac3ec3c3..a8db6faafd62c02808f53758b7b8a577bc79a41e 100644 --- a/src/v1/log/log_default.go +++ b/src/v1/log/log_default.go @@ -13,7 +13,7 @@ import ( "time" ) -//日志 +//MyLogger 日志 type MyLogger struct { name string logger *log.Logger @@ -79,15 +79,23 @@ func (logger *MyLogger) checkLogPath() error { return nil } -//最终日志path +//LogPath 最终日志path func (logger *MyLogger) LogPath() string { p := logger.cnf.LogPath + if p == "" { + p = "default.log" + } var parseT time.Time parseT = time.Now() t := parseT.Format(miscs.DateLayout) - return fmt.Sprintf("%s-%s.log", p[:len(p)-len(".log")], t) + + pos := len(p) + if strings.HasSuffix(p, ".log") { + pos = len(p) - len(".log") + } + return fmt.Sprintf("%s-%s.log", p[:pos], t) } func (logger *MyLogger) Debug(format string, params ...interface{}) { diff --git a/src/v1/log/log_null.go b/src/v1/log/log_null.go index a8c19f0144d6dd4b037d37b46b9ba1b5abd15497..d4a8fd4f1118b54309e5db5a2a6c12fa3539531e 100644 --- a/src/v1/log/log_null.go +++ b/src/v1/log/log_null.go @@ -1,35 +1,73 @@ package log -type nullLog struct{} +import ( + "fmt" + "io" + "time" +) +//空日志 var _nullLog = NewNullLog() -func NewNullLog() *nullLog { - return &nullLog{} +func NewNullLog() *writerLog { + return NewWriterLog(nil, DebugLog) } -func (l nullLog) Debug(format string, params ...interface{}) { - return +//指定writer的日志打印 +type writerLog struct { + w io.Writer + level int } -func (l nullLog) Info(log string, params ...interface{}) { - return + +func NewWriterLog(w io.Writer, level int) *writerLog { + return &writerLog{ + w: w, + level: level, + } +} + +func (l writerLog) Debug(format string, params ...interface{}) { + l.logItem(DebugLog, format, params...) } -func (l nullLog) Warn(log string, params ...interface{}) { - return +func (l writerLog) Info(format string, params ...interface{}) { + l.logItem(InfoLog, format, params...) } -func (l nullLog) Error(log string, params ...interface{}) { - return +func (l writerLog) Warn(format string, params ...interface{}) { + l.logItem(WarnLog, format, params...) } -func (l nullLog) Panic(log string, params ...interface{}) { - return +func (l writerLog) Error(format string, params ...interface{}) { + l.logItem(ErrorLog, format, params...) } -func (l nullLog) Fatal(log string, params ...interface{}) { - return +func (l writerLog) Panic(format string, params ...interface{}) { + l.logItem(PanicLog, format, params...) +} +func (l writerLog) Fatal(format string, params ...interface{}) { + l.logItem(FatalLog, format, params...) } -func (l nullLog) Ctl(t bool) ILog { +func (l writerLog) logItem(level int, format string, params ...interface{}) { + if level < l.level { + return + } + if l.w == nil { + return + } + if len(params) <= 0 { + s := LogLevelMap[level] + a := fmt.Sprintf("[%s]%s: %s\n", s, time.Now().Format(time.RFC3339), format) + l.w.Write([]byte(a)) + return + } + l.logItem(level, fmt.Sprintf(format, params...)) +} + +func (l writerLog) Write(p []byte) (n int, err error){ + return l.w.Write(p) +} + +func (l writerLog) Ctl(t bool) ILog { if !t { return _nullLog } return l -} \ No newline at end of file +} diff --git a/src/v1/log/log_zap.go b/src/v1/log/log_zap.go index e1912b6f113105bef74bc252c0405bd4915619ad..d5be230c423a8534736edafffaa9429103b07117 100644 --- a/src/v1/log/log_zap.go +++ b/src/v1/log/log_zap.go @@ -108,6 +108,11 @@ func levelToZapLevel(level string) zapcore.Level { return zapcore.DebugLevel } +func (l DefaultZapLog) Write(p []byte) (n int, err error) { + l.zapLog.Info(string(p)) + return len(p), nil +} + func (l *DefaultZapLog) Caller(offset int) *DefaultZapLog { return &DefaultZapLog{ l.cnf.LogPath, diff --git a/src/v1/wsserver/ws/feature_clientmgr.go b/src/v1/wsserver/ws/feature_clientmgr.go index 025ef990d99bc916d21a1a098381325398553705..c73de0f534253e69840227c28d26051f00c1acb2 100644 --- a/src/v1/wsserver/ws/feature_clientmgr.go +++ b/src/v1/wsserver/ws/feature_clientmgr.go @@ -28,23 +28,19 @@ type ClientsMgr struct { func (w *ClientsMgr) AddClient(c *Client) { w.mu.Lock() - w.clients[c] = true - w.conn += int64(1) + if _, exist := w.clients[c]; !exist { + w.clients[c] = true + w.conn += int64(1) + } w.mu.Unlock() } func (w *ClientsMgr) DeleteClient(c *Client) { - w.mu.RLock() - _, exist := w.clients[c] - w.mu.RUnlock() - - if !exist { - return - } - w.mu.Lock() - delete(w.clients, c) - w.conn -= int64(1) + if _, exist := w.clients[c]; exist { + delete(w.clients, c) + w.conn -= int64(1) + } w.mu.Unlock() } diff --git a/src/v1/wsserver/ws/feature_subscribe.go b/src/v1/wsserver/ws/feature_subscribe.go index da4939b3ea202139e90ee922101cb3dcd4225d43..cdf7af58f4b55c7dcf712c1a26563d45902f96f9 100644 --- a/src/v1/wsserver/ws/feature_subscribe.go +++ b/src/v1/wsserver/ws/feature_subscribe.go @@ -56,12 +56,12 @@ func (sub *FeatureSubscribeMgr) Parse(data []byte) (ISubscribeGroup, bool) { } //ParseJoinGroup 解析并自动加入group,返回: -// 对应group -// true:订阅加入,false:订阅退出 +// group, 对应group名称, group为nil表示group不存在 +// join, true:订阅加入,false:订阅退出 func (sub *FeatureSubscribeMgr) ParseJoinGroup(client *Client, data []byte) (ISubscribeGroup, bool) { group, join := sub.Parse(data) if group == nil { - return nil, join + return nil, false } if join { sub.JoinGroup(client, group.Name()) @@ -79,19 +79,23 @@ func (sub *FeatureSubscribeMgr) QuitAllGroup(client *Client) { } //JoinGroup 加入指定group -func (sub *FeatureSubscribeMgr) JoinGroup(client *Client, name string) { +func (sub *FeatureSubscribeMgr) JoinGroup(client *Client, name string) bool { if g, exist := sub.groups[name]; exist { logger.Info("subscribe group [%s]", g.Name()) g.AddClient(client) + return true } + return false } //QuitGroup 退出指定group -func (sub *FeatureSubscribeMgr) QuitGroup(client *Client, name string) { +func (sub *FeatureSubscribeMgr) QuitGroup(client *Client, name string) bool { if g, exist := sub.groups[name]; exist { logger.Info("unsubscribe group [%s]", g.Name()) g.DeleteClient(client) + return true } + return false } //Groups 所有groups diff --git a/src/v1/wsserver/ws/feature_subscribe_test.go b/src/v1/wsserver/ws/feature_subscribe_test.go index 25202f6fde081e4072bcd292c85cc57ffe24d746..730e43bee8773d8debacc0714111fc426b1a1c94 100644 --- a/src/v1/wsserver/ws/feature_subscribe_test.go +++ b/src/v1/wsserver/ws/feature_subscribe_test.go @@ -2,7 +2,11 @@ package ws import ( "fmt" + "gitee.com/scottq/go-framework/src/utils" + "sort" + "strings" "testing" + "time" ) func TestWsSubscribe_ParseJoin(t *testing.T) { @@ -18,8 +22,105 @@ func TestWsSubscribe_ParseJoin(t *testing.T) { "G1", "C1", "G2", "DD", } for _, name := range testGroups { - g, join := sub.ParseJoinGroup(clients[0], subscribeData(name)) - t.Logf("join %s %t", g, join) + for _, c := range clients { + for i := 1; i <= 3; i++ { + mockData := subscribeData(name) + g, join := sub.ParseJoinGroup(c, mockData) + if g == nil { + t.Logf("group not exist %s", name) + } else { + t.Logf("join %s %t", g.Name(), join) + } + } + } + } + t.Logf("join %t", sub.JoinGroup(&Client{}, "A1")) + t.Logf("quit %t", sub.QuitGroup(clients[0], "G2")) + t.Logf("quit %t", sub.QuitGroup(clients[0], "A2")) + + t.Logf(strings.Repeat("=", 40)) + for _, g := range sub.Groups() { + t.Logf("group: %s, clients: %d", g.Name(), g.Conns()) + } +} + +func TestWsSubscribe_QuitJoin(t *testing.T) { + clients := []*Client{ + &Client{}, + &Client{}, + &Client{}, + &Client{}, + } + sub := getSubscribeMgr() + + testGroups := []string{ + "G1", "C1", "G2", "DD", + } + for _, name := range testGroups { + for _, c := range clients { + sub.JoinGroup(c, name) + + for i := 1; i <= 3; i++ { + mockData := unsubscribeData(name) + g, join := sub.ParseJoinGroup(c, mockData) + if g == nil { + t.Logf("group not exist %s", name) + } else { + t.Logf("join %s %t", g.Name(), join) + } + } + } + } + + t.Logf(strings.Repeat("=", 40)) + for _, g := range sub.Groups() { + t.Logf("group: %s, clients: %d", g.Name(), g.Conns()) + } +} + +//并发加入和退出 +func TestWsSubscribe_CoJoinQuit(t *testing.T) { + clients := mockClients() + testGroups := []string{ + "G1", "C1", "G2", "DD", + "A1", "A2", "A3", + } + + sub := getSubscribeMgr() + t0 := time.Now() + keep := time.Second * 10 + for i := 1; i <= 5; i++ { + index := i + go func() { + for time.Since(t0) < keep { + c := clients[utils.TrueScopeRand(0, int64(len(clients)))] + name := testGroups[utils.TrueScopeRand(0, int64(len(testGroups)))] + join := utils.TrueScopeRand(0, 10) > 5 + if join { + sub.JoinGroup(c, name) + } else { + sub.QuitGroup(c, name) + } + t.Logf("client-%s join-%t %s", c.ID(), join, name) + time.Sleep(time.Millisecond * 100 * time.Duration(index)) + } + }() + } + + for time.Since(t0) < keep+time.Second*5 { + var s []string + var gs []ISubscribeGroup + for _, g := range sub.Groups() { + gs = append(gs, g) + } + sort.Slice(gs, func(i, j int) bool { + return gs[i].Name() > gs[j].Name() + }) + for _, g := range gs { + s = append(s, fmt.Sprintf(" %s: %d", g.Name(), g.Conns())) + } + t.Logf("=== %s", strings.Join(s, ";")) + time.Sleep(time.Second) } } @@ -48,3 +149,12 @@ func getSubscribeMgr() *FeatureSubscribeMgr { return NewFeatureSubscribeMgr(groups...) } + +func mockClients() []*Client { + return []*Client{ + &Client{uuid: "1"}, + &Client{uuid: "2"}, + &Client{uuid: "3"}, + &Client{uuid: "4"}, + } +}