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 }