github.com/matrixorigin/matrixone@v1.2.0/pkg/util/metric/mometric/metric_collector_test.go (about) 1 // Copyright 2022 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 "regexp" 20 "runtime" 21 "testing" 22 "time" 23 24 pb "github.com/matrixorigin/matrixone/pkg/pb/metric" 25 "github.com/matrixorigin/matrixone/pkg/util/export/etl" 26 "github.com/matrixorigin/matrixone/pkg/util/export/table" 27 ie "github.com/matrixorigin/matrixone/pkg/util/internalExecutor" 28 "github.com/matrixorigin/matrixone/pkg/util/metric" 29 ) 30 31 func init() { 32 metric.RegisterSubSystem(&metric.SubSystem{Name: "m1", Comment: "m1 test metric", SupportUserAccess: false}) 33 metric.RegisterSubSystem(&metric.SubSystem{Name: "m2", Comment: "m2 test metric", SupportUserAccess: false}) 34 } 35 36 type dummySqlExecutor struct { 37 opts ie.SessionOverrideOptions 38 ch chan<- string 39 } 40 41 func (e *dummySqlExecutor) ApplySessionOverride(opts ie.SessionOverrideOptions) { 42 e.opts = opts 43 } 44 45 func (e *dummySqlExecutor) Exec(ctx context.Context, sql string, opts ie.SessionOverrideOptions) error { 46 select { 47 case e.ch <- sql: 48 default: 49 } 50 return nil 51 } 52 53 func (e *dummySqlExecutor) Query(ctx context.Context, sql string, opts ie.SessionOverrideOptions) ie.InternalExecResult { 54 return nil 55 } 56 57 func newExecutorFactory(sqlch chan string) func() ie.InternalExecutor { 58 return func() ie.InternalExecutor { 59 return &dummySqlExecutor{ 60 opts: ie.NewOptsBuilder().Finish(), 61 ch: sqlch, 62 } 63 } 64 } 65 66 func TestCollectorOpts(t *testing.T) { 67 c := newMetricCollector( 68 nil, // this nil pointer won't be touched when SqlWorkerNum is set to 0 69 WithFlushInterval(time.Second), 70 WithMetricThreshold(3), 71 WithSampleThreshold(10), 72 WithSqlWorkerNum(0), 73 ).(*metricCollector) 74 o := c.opts 75 if o.flushInterval != time.Second || o.metricThreshold != 3 || o.sampleThreshold != 10 { 76 t.Errorf("collectorOpts doesn't apply correctly") 77 } 78 } 79 80 func TestCollector(t *testing.T) { 81 if runtime.NumCPU() < 4 { 82 t.Skip("machine's performance too low to handle time sensitive case") 83 return 84 } 85 t.Logf("runtime.NumCPU: %d", runtime.NumCPU()) 86 sqlch := make(chan string, 100) 87 factory := newExecutorFactory(sqlch) 88 collector := newMetricCollector(factory, WithFlushInterval(200*time.Millisecond), WithMetricThreshold(2), 89 WithSqlWorkerNum(runtime.NumCPU())) 90 collector.Start(context.TODO()) 91 defer collector.Stop(false) 92 names := []string{"m1", "m2"} 93 nodes := []string{"e669d136-24f3-11ed-ba8c-d6aee46d73fa", "e9b89520-24f3-11ed-ba8c-d6aee46d73fa"} 94 roles := []string{"ping", "pong"} 95 ts := time.Now().UnixMicro() 96 go func() { 97 _ = collector.SendMetrics(context.TODO(), []*pb.MetricFamily{ 98 {Name: names[0], Type: pb.MetricType_COUNTER, Node: nodes[0], Role: roles[0], Metric: []*pb.Metric{ 99 { 100 Counter: &pb.Counter{Value: 12.0}, Collecttime: ts, 101 }, 102 }}, 103 {Name: names[1], Type: pb.MetricType_RAWHIST, Metric: []*pb.Metric{ 104 { 105 Label: []*pb.LabelPair{{Name: "type", Value: "select"}, {Name: "account", Value: "user"}}, 106 RawHist: &pb.RawHist{Samples: []*pb.Sample{{Datetime: ts, Value: 12.0}, {Datetime: ts, Value: 12.0}}}, 107 }, 108 }}, 109 }) 110 111 _ = collector.SendMetrics(context.TODO(), []*pb.MetricFamily{ 112 {Name: names[0], Type: pb.MetricType_COUNTER, Node: nodes[1], Role: roles[1], Metric: []*pb.Metric{ 113 { 114 Counter: &pb.Counter{Value: 21.0}, Collecttime: ts, 115 }, 116 { 117 Counter: &pb.Counter{Value: 66.0}, Collecttime: ts, 118 }, 119 }}, 120 }) 121 }() 122 instant := time.Now() 123 valuesRe := regexp.MustCompile(`\([^)]*\),?\s?`) // find pattern like (1,2,3) 124 nameRe := regexp.MustCompile(`\.(\w+)\svalues`) // find table name 125 nameAndValueCnt := func(s string) (name string, cnt int) { 126 cnt = len(valuesRe.FindAllString(s, -1)) 127 matches := nameRe.FindStringSubmatch(s) 128 if len(matches) > 1 { 129 name = matches[1] 130 } else { 131 name = "<nil>" 132 } 133 return name, cnt 134 } 135 136 name, cnt := nameAndValueCnt(<-sqlch) 137 if name != names[0] || cnt != 3 { 138 t.Errorf("m1 metric should be flushed first with 3 rows, got %s with %d rows", name, cnt) 139 } 140 141 sql := <-sqlch 142 if time.Since(instant) < 200*time.Millisecond { 143 t.Errorf("m2 should be flushed after a period") 144 } 145 name, cnt = nameAndValueCnt(sql) 146 if name != names[1] || cnt != 2 { 147 t.Errorf("m2 metric should be flushed first with 2 rows, got %s with %d rows", name, cnt) 148 } 149 } 150 151 type dummyStringWriter struct { 152 name string 153 ch chan string 154 // csvWriter 155 writer table.RowWriter 156 } 157 158 func (w *dummyStringWriter) WriteString(s string) (n int, err error) { 159 n = len(s) 160 w.ch <- w.name 161 w.ch <- s 162 return n, nil 163 } 164 165 func (w *dummyStringWriter) WriteRow(row *table.Row) error { 166 return w.writer.WriteRow(row) 167 } 168 169 func (w *dummyStringWriter) FlushAndClose() (int, error) { 170 return w.writer.FlushAndClose() 171 } 172 173 func (w *dummyStringWriter) GetContent() string { return "" } 174 175 func newDummyFSWriterFactory(csvCh chan string) table.WriterFactory { 176 return table.NewWriterFactoryGetter( 177 func(_ context.Context, account string, tbl *table.Table, ts time.Time) table.RowWriter { 178 w := &dummyStringWriter{name: tbl.Table, ch: csvCh} 179 w.writer = etl.NewCSVWriter(context.TODO(), w) 180 return w 181 }, 182 nil, 183 ) 184 } 185 186 func dummyInitView(ctx context.Context, tbls []string) { 187 for _, tbl := range tbls { 188 GetMetricViewWithLabels(ctx, tbl, []string{metricTypeColumn.Name, metricAccountColumn.Name}) 189 } 190 } 191 192 func TestFSCollector(t *testing.T) { 193 ctx := context.Background() 194 csvCh := make(chan string, 100) 195 factory := newDummyFSWriterFactory(csvCh) 196 collector := newMetricFSCollector(factory, WithFlushInterval(3*time.Second), WithMetricThreshold(4)) 197 collector.Start(context.TODO()) 198 defer collector.Stop(false) 199 names := []string{"m1", "m2"} 200 nodes := []string{"e669d136-24f3-11ed-ba8c-d6aee46d73fa", "e9b89520-24f3-11ed-ba8c-d6aee46d73fa"} 201 roles := []string{"ping", "pong"} 202 ts := time.Now().UnixMicro() 203 dummyInitView(ctx, names) 204 go func() { 205 _ = collector.SendMetrics(context.TODO(), []*pb.MetricFamily{ 206 {Name: names[0], Type: pb.MetricType_COUNTER, Node: nodes[0], Role: roles[0], Metric: []*pb.Metric{ 207 { 208 Label: []*pb.LabelPair{{Name: "account", Value: "user"}}, 209 Counter: &pb.Counter{Value: 12.0}, Collecttime: ts, 210 }, 211 }}, 212 {Name: names[1], Type: pb.MetricType_RAWHIST, Metric: []*pb.Metric{ 213 { 214 Label: []*pb.LabelPair{{Name: "type", Value: "select"}, {Name: "account", Value: "user"}}, 215 RawHist: &pb.RawHist{Samples: []*pb.Sample{{Datetime: ts, Value: 12.0}, {Datetime: ts, Value: 12.0}}}, 216 }, 217 }}, 218 }) 219 220 _ = collector.SendMetrics(context.TODO(), []*pb.MetricFamily{ 221 {Name: names[0], Type: pb.MetricType_COUNTER, Node: nodes[1], Role: roles[1], Metric: []*pb.Metric{ 222 { 223 Label: []*pb.LabelPair{{Name: "account", Value: "user"}}, 224 Counter: &pb.Counter{Value: 21.0}, Collecttime: ts, 225 }, 226 { 227 Label: []*pb.LabelPair{{Name: "account", Value: "user"}}, 228 Counter: &pb.Counter{Value: 66.0}, Collecttime: ts, 229 }, 230 }}, 231 }) 232 }() 233 M1ValuesRe := regexp.MustCompile(`m1,(.*[,]?)+\n`) // find pattern like m1,...,...,...\n 234 M2ValuesRe := regexp.MustCompile(`m2,(.*[,]?)+\n`) // find pattern like m2,...,...,...\n 235 nameAndValueCnt := func(n, s string, re *regexp.Regexp) (name string, cnt int) { 236 t.Logf("name: %s, csv: %s", n, s) 237 cnt = len(re.FindAllString(s, -1)) 238 if cnt > 0 { 239 name = n 240 } else { 241 name = "<nil>" 242 } 243 return name, cnt 244 } 245 246 n, s := <-csvCh, <-csvCh 247 name, cnt := nameAndValueCnt(n, s, M1ValuesRe) 248 if name != SingleMetricTable.GetName() || cnt != 3 { 249 t.Errorf("m1 metric should be flushed first with 3 rows, got %s with %d rows", name, cnt) 250 } 251 252 name, cnt = nameAndValueCnt(n, s, M2ValuesRe) 253 if name != SingleMetricTable.GetName() || cnt != 2 { 254 t.Errorf("m2 metric should be flushed first with 2 rows, got %s with %d rows", name, cnt) 255 } 256 }