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  }