github.com/matrixorigin/matrixone@v1.2.0/pkg/util/metric/mometric/stats_log_writer_test.go (about) 1 // Copyright 2023 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mometric 16 17 import ( 18 "context" 19 "fmt" 20 "math/rand" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/matrixorigin/matrixone/pkg/common/moerr" 26 "github.com/matrixorigin/matrixone/pkg/common/runtime" 27 "github.com/matrixorigin/matrixone/pkg/logutil" 28 "github.com/matrixorigin/matrixone/pkg/objectio" 29 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 30 "github.com/matrixorigin/matrixone/pkg/util/metric/stats" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 "go.uber.org/zap" 34 "go.uber.org/zap/zapcore" 35 ) 36 37 // MockService for testing stats Registry 38 type MockService struct { 39 stats *MockStats 40 } 41 42 type MockStats struct { 43 reads stats.Counter 44 hits stats.Counter 45 } 46 47 func NewMockService() *MockService { 48 return &MockService{ 49 stats: &MockStats{}, 50 } 51 } 52 53 func (d *MockService) Do() { 54 d.stats.reads.Add(2) 55 d.stats.hits.Add(1) 56 } 57 58 func (d *MockService) Stats() *MockStats { 59 return d.stats 60 } 61 62 // LogExporter for the Mock Service declared above 63 type MockServiceLogExporter struct { 64 service *MockService 65 } 66 67 func NewMockServiceLogExporter(service *MockService) stats.LogExporter { 68 return &MockServiceLogExporter{ 69 service: service, 70 } 71 } 72 73 func (c *MockServiceLogExporter) Export() []zap.Field { 74 var fields []zap.Field 75 76 stats := c.service.Stats() 77 78 reads := stats.reads.SwapW(0) 79 hits := stats.hits.SwapW(0) 80 81 fields = append(fields, zap.Any("reads", reads)) 82 fields = append(fields, zap.Any("hits", hits)) 83 84 return fields 85 } 86 87 func TestStatsLogWriter(t *testing.T) { 88 // 1. Register Dev Stats 89 service := NewMockService() 90 serviceLogExporter := NewMockServiceLogExporter(service) 91 stats.Register("MockServiceStats", stats.WithLogExporter(serviceLogExporter)) 92 93 //2.1 Setup a Runtime 94 runtime.SetupProcessLevelRuntime(runtime.NewRuntime(metadata.ServiceType_CN, "test", logutil.GetGlobalLogger())) 95 96 //2.2 Create custom Hook logger 97 type threadSafeWrittenLog struct { 98 content []zapcore.Entry 99 // This is because, BusyLoop and StatsLogger read (ie len) and write (content) to the same field. 100 sync.Mutex 101 } 102 103 writtenLogs := threadSafeWrittenLog{} 104 customLogger := runtime.ProcessLevelRuntime().Logger().WithOptions(zap.Hooks(func(entry zapcore.Entry) error { 105 writtenLogs.Lock() 106 defer writtenLogs.Unlock() 107 writtenLogs.content = append(writtenLogs.content, entry) 108 return nil 109 })) 110 111 // 2.3 Start the LogWriter 112 c := newStatsLogWriter(stats.DefaultRegistry, customLogger, 100*time.Millisecond) 113 serviceCtx := context.WithValue(context.Background(), ServiceTypeKey, "DN") 114 assert.True(t, c.Start(serviceCtx)) 115 116 // 3. Perform operations on Dev Stats 117 service.Do() 118 119 // 4. Wait for log to print in console. (Busy Loop) 120 err := waitUtil(60*time.Second, 100*time.Millisecond, func() bool { 121 writtenLogs.Lock() 122 defer writtenLogs.Unlock() 123 return len(writtenLogs.content) >= 10 124 }) 125 require.NoError(t, err) 126 127 // 5. Stop the LogWriter 128 if ch, effect := c.Stop(true); effect { 129 <-ch 130 } 131 println("StatsLogWriter has stopped gracefully.") 132 133 //6. Validate the log printed. 134 writtenLogs.Lock() 135 defer writtenLogs.Unlock() 136 assert.True(t, len(writtenLogs.content) >= 10) 137 for _, log := range writtenLogs.content { 138 assert.Contains(t, log.Message, "Stats") 139 } 140 141 // 7. (Optional) Read from the console and validate the log. Example log: 142 // 2023/03/15 02:37:31.767463 -0500 INFO cn-service mometric/stats_log_writer.go:86 MockServiceStats stats {"uuid": "test", "reads": 2, "hits": 1} 143 // 2023/03/15 02:37:33.767659 -0500 INFO cn-service mometric/stats_log_writer.go:86 MockServiceStats stats {"uuid": "test", "reads": 0, "hits": 0} 144 // 2023/03/15 02:37:35.767608 -0500 INFO cn-service mometric/stats_log_writer.go:86 MockServiceStats stats {"uuid": "test", "reads": 0, "hits": 0} 145 // StatsLogWriter has stopped gracefully. 146 147 } 148 149 // waitUtil Busy Loop to wait until check() is true. 150 func waitUtil(timeout, checkInterval time.Duration, check func() bool) error { 151 timeoutTimer := time.After(timeout) 152 for { 153 select { 154 case <-timeoutTimer: 155 return moerr.NewInternalError(context.TODO(), "timeout") 156 default: 157 if check() { 158 return nil 159 } 160 time.Sleep(checkInterval) 161 } 162 } 163 } 164 165 func TestWriteBlkReadStats(t *testing.T) { 166 h1, t1 := rand.Int(), rand.Int()+1 167 objectio.BlkReadStats.BlksByReaderStats.Record(h1, t1) 168 169 h2, t2 := rand.Int(), rand.Int()+1 170 objectio.BlkReadStats.EntryCacheHitStats.Record(h2, t2) 171 172 h3, t3 := rand.Int(), rand.Int()+1 173 objectio.BlkReadStats.BlkCacheHitStats.Record(h3, t3) 174 175 s := new(StatsLogWriter) 176 type threadSafeWrittenLog struct { 177 content []zapcore.Entry 178 // This is because, BusyLoop and StatsLogger read (ie len) and write (content) to the same field. 179 sync.Mutex 180 } 181 182 writtenLogs := threadSafeWrittenLog{} 183 runtime.SetupProcessLevelRuntime(runtime.NewRuntime(metadata.ServiceType_CN, "test", logutil.GetGlobalLogger())) 184 s.logger = runtime.ProcessLevelRuntime().Logger().WithOptions(zap.Hooks(func(entry zapcore.Entry) error { 185 writtenLogs.Lock() 186 defer writtenLogs.Unlock() 187 writtenLogs.content = append(writtenLogs.content, entry) 188 return nil 189 })) 190 s.writeBlkReadStats() 191 192 expected := fmt.Sprintf("duration: %d, "+ 193 "blk hit rate: %d/%d=%.4f, entry hit rate: %d/%d=%.4f, (average) blks in each reader: %d/%d=%.4f", 194 s.gatherInterval, 195 h3, t3, float32(h3)/float32(t3), 196 h2, t2, float32(h2)/float32(t2), 197 t1, h1, float32(t1)/float32(h1), 198 ) 199 200 require.Equal(t, writtenLogs.content[0].Level, zapcore.InfoLevel) 201 require.Equal(t, writtenLogs.content[0].Message, expected) 202 }