github.com/matrixorigin/matrixone@v1.2.0/pkg/util/export/batch_processor_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 export
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"sort"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    27  	"github.com/matrixorigin/matrixone/pkg/config"
    28  	"github.com/matrixorigin/matrixone/pkg/util/stack"
    29  	"github.com/matrixorigin/matrixone/pkg/util/trace/impl/motrace"
    30  
    31  	"github.com/matrixorigin/matrixone/pkg/logutil"
    32  	"github.com/matrixorigin/matrixone/pkg/util/batchpipe"
    33  	"github.com/matrixorigin/matrixone/pkg/util/errutil"
    34  
    35  	"github.com/google/gops/agent"
    36  	"github.com/prashantv/gostub"
    37  	"github.com/stretchr/testify/require"
    38  	"go.uber.org/zap/zapcore"
    39  )
    40  
    41  func init() {
    42  	time.Local = time.FixedZone("CST", 0) // set time-zone +0000
    43  	logutil.SetupMOLogger(&logutil.LogConfig{
    44  		Level:      zapcore.DebugLevel.String(),
    45  		Format:     "console",
    46  		Filename:   "",
    47  		MaxSize:    512,
    48  		MaxDays:    0,
    49  		MaxBackups: 0,
    50  
    51  		DisableStore: true,
    52  	})
    53  	if err := agent.Listen(agent.Options{}); err != nil {
    54  		logutil.Errorf("listen gops agent failed: %s", err)
    55  		return
    56  	}
    57  }
    58  
    59  const NumType = "Num"
    60  
    61  var _ batchpipe.HasName = (*Num)(nil)
    62  var _ batchpipe.ItemBuffer[batchpipe.HasName, any] = &dummyBuffer{}
    63  var _ batchpipe.PipeImpl[batchpipe.HasName, any] = &dummyPipeImpl{}
    64  
    65  type Num int64
    66  
    67  func newDummy(v int64) *Num {
    68  	n := Num(v)
    69  	return &n
    70  }
    71  
    72  func (n Num) GetName() string { return NumType }
    73  
    74  var signalFunc = func() {}
    75  
    76  type dummyBuffer struct {
    77  	batchpipe.Reminder
    78  	arr    []batchpipe.HasName
    79  	mux    sync.Mutex
    80  	signal func()
    81  	ctx    context.Context
    82  }
    83  
    84  func (s *dummyBuffer) Add(item batchpipe.HasName) {
    85  	s.mux.Lock()
    86  	defer s.mux.Unlock()
    87  	ctx := s.ctx
    88  	s.arr = append(s.arr, item)
    89  	if s.signal != nil {
    90  		val := int(*item.(*Num))
    91  		length := len(s.arr)
    92  		logutil.Debugf("accept: %v, len: %d", *item.(*Num), length)
    93  		if (val <= 3 && val != length) && (val-3) != length {
    94  			panic(moerr.NewInternalError(ctx, "len not rignt, elem: %d, len: %d", val, length))
    95  		}
    96  		s.signal()
    97  	}
    98  }
    99  func (s *dummyBuffer) Reset() {
   100  	s.mux.Lock()
   101  	defer s.mux.Unlock()
   102  	logutil.Debugf("buffer reset, stack: %+v", stack.Callers(0))
   103  	s.arr = s.arr[0:0]
   104  }
   105  func (s *dummyBuffer) IsEmpty() bool {
   106  	s.mux.Lock()
   107  	defer s.mux.Unlock()
   108  	return len(s.arr) == 0
   109  }
   110  func (s *dummyBuffer) ShouldFlush() bool {
   111  	s.mux.Lock()
   112  	defer s.mux.Unlock()
   113  	length := len(s.arr)
   114  	should := length >= 3
   115  	if should {
   116  		logutil.Debugf("buffer shouldFlush: %v", should)
   117  	}
   118  	return should
   119  }
   120  
   121  var waitGetBatchFinish = func() {}
   122  
   123  func (s *dummyBuffer) GetBatch(ctx context.Context, buf *bytes.Buffer) any {
   124  	s.mux.Lock()
   125  	defer s.mux.Unlock()
   126  	if len(s.arr) == 0 {
   127  		return ""
   128  	}
   129  
   130  	logutil.Debugf("GetBatch, len: %d", len(s.arr))
   131  	buf.Reset()
   132  	for _, item := range s.arr {
   133  		s, ok := item.(*Num)
   134  		if !ok {
   135  			panic("Not Num type")
   136  		}
   137  		buf.WriteString("(")
   138  		buf.WriteString(fmt.Sprintf("%d", *s))
   139  		buf.WriteString("),")
   140  	}
   141  	logutil.Debugf("GetBatch: %s", buf.String())
   142  	if waitGetBatchFinish != nil {
   143  		logutil.Debugf("wait BatchFinish")
   144  		waitGetBatchFinish()
   145  		logutil.Debugf("wait BatchFinish, Done")
   146  	}
   147  	return string(buf.Next(buf.Len() - 1))
   148  }
   149  
   150  type dummyPipeImpl struct {
   151  	ch       chan string
   152  	duration time.Duration
   153  }
   154  
   155  func (n *dummyPipeImpl) NewItemBuffer(string) batchpipe.ItemBuffer[batchpipe.HasName, any] {
   156  	return &dummyBuffer{Reminder: batchpipe.NewConstantClock(n.duration), signal: signalFunc, ctx: context.Background()}
   157  }
   158  
   159  func (n *dummyPipeImpl) NewItemBatchHandler(ctx context.Context) func(any) {
   160  	return func(batch any) {
   161  		n.ch <- batch.(string)
   162  	}
   163  }
   164  
   165  var MOCollectorMux sync.Mutex
   166  
   167  func TestNewMOCollector(t *testing.T) {
   168  	MOCollectorMux.Lock()
   169  	defer MOCollectorMux.Unlock()
   170  	// defer leaktest.AfterTest(t)()
   171  	defer agent.Close()
   172  	ctx := context.Background()
   173  	ch := make(chan string, 3)
   174  	errutil.SetErrorReporter(func(ctx context.Context, err error, i int) {
   175  		t.Logf("TestNewMOCollector::ErrorReport: %+v", err)
   176  	})
   177  	var signalC = make(chan struct{}, 16)
   178  	var acceptSignal = func() { <-signalC }
   179  	stub1 := gostub.Stub(&signalFunc, func() { signalC <- struct{}{} })
   180  	defer stub1.Reset()
   181  
   182  	cfg := getDummyOBCollectorConfig()
   183  	collector := NewMOCollector(ctx, WithOBCollectorConfig(cfg))
   184  	collector.Register(newDummy(0), &dummyPipeImpl{ch: ch, duration: time.Hour})
   185  	collector.Start()
   186  
   187  	collector.Collect(ctx, newDummy(1))
   188  	acceptSignal()
   189  	collector.Collect(ctx, newDummy(2))
   190  	acceptSignal()
   191  	collector.Collect(ctx, newDummy(3))
   192  	acceptSignal()
   193  	got123 := <-ch
   194  	collector.Collect(ctx, newDummy(4))
   195  	acceptSignal()
   196  	collector.Collect(ctx, newDummy(5))
   197  	acceptSignal()
   198  	collector.Stop(true)
   199  	logutil.GetGlobalLogger().Sync()
   200  	got45 := <-ch
   201  	for i := len(ch); i > 0; i-- {
   202  		got := <-ch
   203  		t.Logf("left ch: %s", got)
   204  	}
   205  	require.Equal(t, `(1),(2),(3)`, got123)
   206  	require.Equal(t, `(4),(5)`, got45)
   207  }
   208  
   209  func TestNewMOCollector_Stop(t *testing.T) {
   210  	MOCollectorMux.Lock()
   211  	defer MOCollectorMux.Unlock()
   212  	defer agent.Close()
   213  	ctx := context.Background()
   214  	ch := make(chan string, 3)
   215  
   216  	collector := NewMOCollector(ctx)
   217  	collector.Register(newDummy(0), &dummyPipeImpl{ch: ch, duration: time.Hour})
   218  	collector.Start()
   219  	collector.Stop(true)
   220  
   221  	var N int = 1e3
   222  	for i := 0; i < N; i++ {
   223  		collector.Collect(ctx, newDummy(int64(i)))
   224  	}
   225  	length := len(collector.awakeCollect)
   226  	dropCnt := collector.stopDrop.Load()
   227  	t.Logf("channal len: %d, dropCnt: %d, totalElem: %d", length, dropCnt, N)
   228  	require.Equal(t, N, int(dropCnt)+length)
   229  }
   230  
   231  func TestNewMOCollector_BufferCnt(t *testing.T) {
   232  	MOCollectorMux.Lock()
   233  	defer MOCollectorMux.Unlock()
   234  	ctx := context.Background()
   235  	ch := make(chan string, 3)
   236  	errutil.SetErrorReporter(func(ctx context.Context, err error, i int) {
   237  		t.Logf("TestNewMOCollector::ErrorReport: %+v", err)
   238  	})
   239  	var signalC = make(chan struct{}, 16)
   240  	var acceptSignal = func() { <-signalC }
   241  	stub1 := gostub.Stub(&signalFunc, func() { signalC <- struct{}{} })
   242  	defer stub1.Reset()
   243  
   244  	var batchFlowC = make(chan struct{})
   245  	var signalBatchFinishC = make(chan struct{})
   246  	var signalBatchFinish = func() {
   247  		signalBatchFinishC <- struct{}{}
   248  	}
   249  	bhStub := gostub.Stub(&waitGetBatchFinish, func() {
   250  		batchFlowC <- struct{}{}
   251  		<-signalBatchFinishC
   252  	})
   253  	defer bhStub.Reset()
   254  
   255  	cfg := getDummyOBCollectorConfig()
   256  	cfg.ShowStatsInterval.Duration = 5 * time.Second
   257  	cfg.BufferCnt = 2
   258  	collector := NewMOCollector(ctx, WithOBCollectorConfig(cfg))
   259  	collector.Register(newDummy(0), &dummyPipeImpl{ch: ch, duration: time.Hour})
   260  	collector.Start()
   261  
   262  	collector.Collect(ctx, newDummy(1))
   263  	acceptSignal()
   264  	collector.Collect(ctx, newDummy(2))
   265  	acceptSignal()
   266  	collector.Collect(ctx, newDummy(3))
   267  	acceptSignal()
   268  	// make 1/2 buffer hang.
   269  	<-batchFlowC
   270  	collector.Collect(ctx, newDummy(4))
   271  	acceptSignal()
   272  	collector.Collect(ctx, newDummy(5))
   273  	acceptSignal()
   274  	collector.Collect(ctx, newDummy(6))
   275  	acceptSignal()
   276  
   277  	// make 2/2 buffer hang.
   278  	<-batchFlowC
   279  	t.Log("done 2rd buffer fill, then send the last elem")
   280  
   281  	// send 7th elem, it will hang, wait for buffer slot
   282  	go func() {
   283  		t.Log("dummy hung goroutine started.")
   284  		collector.Collect(ctx, newDummy(7))
   285  		t.Log("dummy hung goroutine finished.")
   286  	}()
   287  	// reset
   288  	bhStub.Reset()
   289  	t.Log("done all dummy action, then do check result")
   290  
   291  	select {
   292  	case <-signalC:
   293  		t.Errorf("failed wait buffer released.")
   294  		return
   295  	case <-time.After(5 * time.Second):
   296  		t.Logf("success: hang by buffer alloc: no slot.")
   297  		// reset be normal flow
   298  		signalBatchFinish()
   299  		signalBatchFinish()
   300  		acceptSignal()
   301  		t.Logf("reset normally")
   302  	}
   303  	got123 := <-ch
   304  	got456 := <-ch
   305  	collector.Stop(true)
   306  	got7 := <-ch
   307  	got := []string{got123, got456, got7}
   308  	sort.Strings(got)
   309  	logutil.GetGlobalLogger().Sync()
   310  	for i := len(ch); i > 0; i-- {
   311  		got := <-ch
   312  		t.Logf("left ch: %s", got)
   313  	}
   314  	require.Equal(t, []string{`(1),(2),(3)`, `(4),(5),(6)`, `(7)`}, got)
   315  }
   316  
   317  func Test_newBufferHolder_AddAfterStop(t *testing.T) {
   318  	MOCollectorMux.Lock()
   319  	defer MOCollectorMux.Unlock()
   320  	type args struct {
   321  		ctx    context.Context
   322  		name   batchpipe.HasName
   323  		impl   motrace.PipeImpl
   324  		signal bufferSignalFunc
   325  		c      *MOCollector
   326  	}
   327  
   328  	ch := make(chan string)
   329  	triggerSignalFunc := func(holder *bufferHolder) {}
   330  
   331  	cfg := getDummyOBCollectorConfig()
   332  	collector := NewMOCollector(context.TODO(), WithOBCollectorConfig(cfg))
   333  
   334  	tests := []struct {
   335  		name string
   336  		args args
   337  		want *bufferHolder
   338  	}{
   339  		{
   340  			name: "callAddAfterStop",
   341  			args: args{
   342  				ctx:    context.TODO(),
   343  				name:   newDummy(0),
   344  				impl:   &dummyPipeImpl{ch: ch, duration: time.Hour},
   345  				signal: triggerSignalFunc,
   346  				c:      collector,
   347  			},
   348  		},
   349  	}
   350  	for _, tt := range tests {
   351  		t.Run(tt.name, func(t *testing.T) {
   352  			buf := newBufferHolder(tt.args.ctx, tt.args.name, tt.args.impl, tt.args.signal, tt.args.c)
   353  			buf.Start()
   354  			buf.Add(newDummy(1))
   355  			buf.Stop()
   356  			buf.Add(newDummy(2))
   357  			buf.Add(newDummy(3))
   358  			b, _ := buf.buffer.(*dummyBuffer)
   359  			require.Equal(t, []batchpipe.HasName{newDummy(1)}, b.arr)
   360  		})
   361  	}
   362  }
   363  
   364  func getDummyOBCollectorConfig() *config.OBCollectorConfig {
   365  	cfg := &config.OBCollectorConfig{}
   366  	cfg.SetDefaultValues()
   367  	cfg.ExporterCntPercent = maxPercentValue
   368  	cfg.GeneratorCntPercent = maxPercentValue
   369  	cfg.CollectorCntPercent = maxPercentValue
   370  	return cfg
   371  }
   372  
   373  func TestMOCollector_DiscardableCollect(t *testing.T) {
   374  
   375  	ctx := context.TODO()
   376  	cfg := getDummyOBCollectorConfig()
   377  	collector := NewMOCollector(context.TODO(), WithOBCollectorConfig(cfg))
   378  	elem := newDummy(1)
   379  	for i := 0; i < defaultQueueSize; i++ {
   380  		collector.Collect(ctx, elem)
   381  	}
   382  	require.Equal(t, defaultQueueSize, len(collector.awakeCollect))
   383  
   384  	// check DisableStore will discard
   385  	now := time.Now()
   386  	collector.DiscardableCollect(ctx, elem)
   387  	require.Equal(t, defaultQueueSize, len(collector.awakeCollect))
   388  	require.True(t, time.Since(now) > discardCollectTimeout)
   389  	t.Logf("DiscardableCollect accept")
   390  }
   391  
   392  func TestMOCollector_calculateDefaultWorker(t *testing.T) {
   393  	type fields struct {
   394  		collectorCntP int
   395  		generatorCntP int
   396  		exporterCntP  int
   397  	}
   398  	type args struct {
   399  		numCpu int
   400  	}
   401  	type want struct {
   402  		collectorCnt int
   403  		generatorCnt int
   404  		exporterCnt  int
   405  	}
   406  	tests := []struct {
   407  		name   string
   408  		fields fields
   409  		args   args
   410  		wants  want
   411  	}{
   412  		{
   413  			name:   "normal_8c",
   414  			fields: fields{collectorCntP: 10, generatorCntP: 20, exporterCntP: 80},
   415  			args:   args{numCpu: 8},
   416  			wants:  want{collectorCnt: 1, generatorCnt: 1, exporterCnt: 1},
   417  		},
   418  		{
   419  			name:   "normal_30c",
   420  			fields: fields{collectorCntP: 10, generatorCntP: 20, exporterCntP: 80},
   421  			args:   args{numCpu: 30},
   422  			wants:  want{collectorCnt: 1, generatorCnt: 1, exporterCnt: 2},
   423  		},
   424  		{
   425  			name:   "normal_8c_big",
   426  			fields: fields{collectorCntP: 10, generatorCntP: 800, exporterCntP: 800},
   427  			args:   args{numCpu: 8},
   428  			wants:  want{collectorCnt: 1, generatorCnt: 8, exporterCnt: 8},
   429  		},
   430  		{
   431  			name:   "normal_1c_100p_400p",
   432  			fields: fields{collectorCntP: 10, generatorCntP: 100, exporterCntP: 400},
   433  			args:   args{numCpu: 1},
   434  			wants:  want{collectorCnt: 1, generatorCnt: 1, exporterCnt: 1},
   435  		},
   436  		{
   437  			name:   "normal_7c_80p_400p_1000p",
   438  			fields: fields{collectorCntP: 80, generatorCntP: 400, exporterCntP: 1000},
   439  			args:   args{numCpu: 7},
   440  			wants:  want{collectorCnt: 1, generatorCnt: 4, exporterCnt: 7},
   441  		},
   442  		{
   443  			name:   "normal_16c_80p_400p_1000p",
   444  			fields: fields{collectorCntP: 80, generatorCntP: 400, exporterCntP: 800},
   445  			args:   args{numCpu: 16},
   446  			wants:  want{collectorCnt: 2, generatorCnt: 8, exporterCnt: 16},
   447  		},
   448  	}
   449  	for _, tt := range tests {
   450  		t.Run(tt.name, func(t *testing.T) {
   451  			c := &MOCollector{
   452  				collectorCntP: tt.fields.collectorCntP,
   453  				generatorCntP: tt.fields.generatorCntP,
   454  				exporterCntP:  tt.fields.exporterCntP,
   455  			}
   456  			c.calculateDefaultWorker(tt.args.numCpu)
   457  			require.Equal(t, tt.wants.collectorCnt, c.collectorCnt)
   458  			require.Equal(t, tt.wants.generatorCnt, c.generatorCnt)
   459  			require.Equal(t, tt.wants.exporterCnt, c.exporterCnt)
   460  		})
   461  	}
   462  }