github.com/matrixorigin/matrixone@v1.2.0/pkg/util/trace/impl/motrace/buffer_pipe.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 motrace
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"reflect"
    22  	"strconv"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/matrixorigin/matrixone/pkg/util/trace"
    28  
    29  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    30  	"github.com/matrixorigin/matrixone/pkg/common/mpool"
    31  	"github.com/matrixorigin/matrixone/pkg/logutil"
    32  	bp "github.com/matrixorigin/matrixone/pkg/util/batchpipe"
    33  	"github.com/matrixorigin/matrixone/pkg/util/export/table"
    34  )
    35  
    36  const defaultClock = 15 * time.Second
    37  
    38  var errorFormatter atomic.Value
    39  var logStackFormatter atomic.Value
    40  
    41  func init() {
    42  	errorFormatter.Store("%+v")
    43  	logStackFormatter.Store("%+v")
    44  }
    45  
    46  type IBuffer2SqlItem interface {
    47  	bp.HasName
    48  	Size() int64
    49  	Free()
    50  }
    51  
    52  var _ PipeImpl = (*batchETLHandler)(nil)
    53  
    54  type batchETLHandler struct {
    55  	defaultOpts []BufferOption
    56  }
    57  
    58  func NewBufferPipe2CSVWorker(opt ...BufferOption) bp.PipeImpl[bp.HasName, any] {
    59  	return &batchETLHandler{opt}
    60  }
    61  
    62  // NewItemBuffer implement batchpipe.PipeImpl
    63  func (t batchETLHandler) NewItemBuffer(name string) bp.ItemBuffer[bp.HasName, any] {
    64  	var opts []BufferOption
    65  	var f genBatchFunc = genETLData
    66  	logutil.Debugf("NewItemBuffer name: %s", name)
    67  	switch name {
    68  	case MOStatementType, SingleStatementTable.GetName():
    69  	case MOErrorType:
    70  	case MOSpanType:
    71  	case MOLogType:
    72  	case MORawLogType:
    73  	default:
    74  		logutil.Warnf("batchETLHandler handle new type: %s", name)
    75  	}
    76  	opts = append(opts, BufferWithGenBatchFunc(f), BufferWithType(name))
    77  	opts = append(opts, t.defaultOpts...)
    78  	return NewItemBuffer(opts...)
    79  }
    80  
    81  // NewItemBatchHandler implement batchpipe.PipeImpl
    82  func (t batchETLHandler) NewItemBatchHandler(ctx context.Context) func(b any) {
    83  
    84  	handle := func(b any) {
    85  		req, ok := b.(table.WriteRequest) // see genETLData
    86  		if !ok {
    87  			panic(moerr.NewInternalError(ctx, "batchETLHandler meet unknown type: %v", reflect.ValueOf(b).Type()))
    88  		}
    89  		if _, err := req.Handle(); err != nil {
    90  			logutil.Error(fmt.Sprintf("[Trace] failed to write. err: %v", err), logutil.NoReportFiled())
    91  		}
    92  	}
    93  
    94  	var f = func(batch any) {
    95  		_, span := trace.Start(DefaultContext(), "batchETLHandler")
    96  		defer span.End()
    97  		switch b := batch.(type) {
    98  		case table.WriteRequest:
    99  			handle(b)
   100  		case table.ExportRequests:
   101  			for _, req := range b {
   102  				handle(req)
   103  			}
   104  		default:
   105  			panic(moerr.NewNotSupported(ctx, "unknown batch type: %v", reflect.ValueOf(b).Type()))
   106  		}
   107  	}
   108  	return f
   109  }
   110  
   111  type WriteFactoryConfig struct {
   112  	Account     string
   113  	Ts          time.Time
   114  	PathBuilder table.PathBuilder
   115  }
   116  
   117  func genETLData(ctx context.Context, in []IBuffer2SqlItem, buf *bytes.Buffer, factory table.WriterFactory) any {
   118  	buf.Reset()
   119  	if len(in) == 0 {
   120  		return table.NewRowRequest(nil)
   121  	}
   122  
   123  	// Initialize aggregator
   124  	var aggregator *Aggregator
   125  	if !GetTracerProvider().disableStmtAggregation {
   126  		aggregator = NewAggregator(
   127  			ctx,
   128  			GetTracerProvider().aggregationWindow,
   129  			StatementInfoNew,
   130  			StatementInfoUpdate,
   131  			StatementInfoFilter,
   132  		)
   133  	}
   134  
   135  	ts := time.Now()
   136  	writerMap := make(map[string]table.RowWriter, 2)
   137  	writeValues := func(item table.RowField) {
   138  		row := item.GetTable().GetRow(ctx)
   139  		item.FillRow(ctx, row)
   140  		account := row.GetAccount()
   141  		w, exist := writerMap[account]
   142  		if !exist {
   143  			if factory == nil {
   144  				factory = GetTracerProvider().writerFactory
   145  			}
   146  			w = factory.GetRowWriter(ctx, account, row.Table, ts)
   147  			writerMap[row.GetAccount()] = w
   148  		}
   149  		w.WriteRow(row)
   150  		if check, is := item.(table.NeedCheckWrite); is && check.NeedCheckWrite() {
   151  			if writer, support := w.(table.AfterWrite); support {
   152  				writer.AddAfter(check.GetCheckWriteHook())
   153  			}
   154  		}
   155  		row.Free()
   156  	}
   157  
   158  	for _, i := range in {
   159  		// Check if the item is a StatementInfo
   160  		if statementInfo, ok := i.(*StatementInfo); ok && aggregator != nil {
   161  			// if stmt aggregate, then add it to the aggregator
   162  			_, err := aggregator.AddItem(statementInfo)
   163  			if err != nil {
   164  				item, ok := i.(table.RowField)
   165  				if !ok {
   166  					panic("not MalCsv, dont support output CSV")
   167  				}
   168  				writeValues(item)
   169  			}
   170  		} else {
   171  			// If not, process as before
   172  			item, ok := i.(table.RowField)
   173  			if !ok {
   174  				panic("not MalCsv, dont support output CSV")
   175  			}
   176  			writeValues(item)
   177  		}
   178  	}
   179  
   180  	// Get the aggregated results
   181  	if aggregator != nil {
   182  		groupedResults := aggregator.GetResults()
   183  
   184  		for _, i := range groupedResults {
   185  
   186  			// If not, process as before
   187  			item, ok := i.(table.RowField)
   188  			if !ok {
   189  				panic("not MalCsv, dont support output CSV")
   190  			}
   191  
   192  			stmt, stmt_ok := i.(*StatementInfo)
   193  			if stmt_ok {
   194  				if stmt.AggrCount > 1 {
   195  					stmt.Statement = "/* " + strconv.FormatInt(stmt.AggrCount, 10) + " queries */ \n" + stmt.StmtBuilder.String()
   196  				}
   197  				stmt.StmtBuilder.Reset()
   198  			}
   199  
   200  			writeValues(item)
   201  		}
   202  		aggregator.Close()
   203  	}
   204  
   205  	reqs := make(table.ExportRequests, 0, len(writerMap))
   206  	for _, ww := range writerMap {
   207  		reqs = append(reqs, table.NewRowRequest(ww))
   208  	}
   209  
   210  	return reqs
   211  }
   212  
   213  var _ bp.ItemBuffer[bp.HasName, any] = &itemBuffer{}
   214  
   215  // buffer catch item, like trace/log/error, buffer
   216  type itemBuffer struct {
   217  	bp.Reminder   // see BufferWithReminder
   218  	buf           []IBuffer2SqlItem
   219  	mux           sync.Mutex
   220  	bufferType    string // see BufferWithType
   221  	size          int64  // default: 1 MB
   222  	sizeThreshold int64  // see BufferWithSizeThreshold
   223  
   224  	filterItemFunc filterItemFunc
   225  	genBatchFunc   genBatchFunc
   226  }
   227  
   228  type filterItemFunc func(IBuffer2SqlItem)
   229  type genBatchFunc func(context.Context, []IBuffer2SqlItem, *bytes.Buffer, table.WriterFactory) any
   230  
   231  var noopFilterItemFunc = func(IBuffer2SqlItem) {}
   232  var noopGenBatchSQL = genBatchFunc(func(context.Context, []IBuffer2SqlItem, *bytes.Buffer, table.WriterFactory) any { return "" })
   233  
   234  func NewItemBuffer(opts ...BufferOption) *itemBuffer {
   235  	b := &itemBuffer{
   236  		Reminder:       bp.NewConstantClock(defaultClock),
   237  		buf:            make([]IBuffer2SqlItem, 0, 10240),
   238  		sizeThreshold:  10 * mpool.MB,
   239  		filterItemFunc: noopFilterItemFunc,
   240  		genBatchFunc:   noopGenBatchSQL,
   241  	}
   242  	for _, opt := range opts {
   243  		opt.apply(b)
   244  	}
   245  	logutil.Debugf("NewItemBuffer, Reminder next: %v", b.Reminder.RemindNextAfter())
   246  	if b.genBatchFunc == nil || b.filterItemFunc == nil || b.Reminder == nil {
   247  		logutil.Debug("NewItemBuffer meet nil elem")
   248  		return nil
   249  	}
   250  	return b
   251  }
   252  
   253  func (b *itemBuffer) Add(i bp.HasName) {
   254  	b.mux.Lock()
   255  	defer b.mux.Unlock()
   256  	if item, ok := i.(IBuffer2SqlItem); !ok {
   257  		panic("not implement interface IBuffer2SqlItem")
   258  	} else {
   259  		b.filterItemFunc(item)
   260  		b.buf = append(b.buf, item)
   261  		atomic.AddInt64(&b.size, item.Size())
   262  	}
   263  }
   264  
   265  func (b *itemBuffer) Reset() {
   266  	b.mux.Lock()
   267  	defer b.mux.Unlock()
   268  	for idx, i := range b.buf {
   269  		i.Free()
   270  		b.buf[idx] = nil
   271  	}
   272  	b.buf = b.buf[0:0]
   273  	b.size = 0
   274  }
   275  
   276  func (b *itemBuffer) IsEmpty() bool {
   277  	b.mux.Lock()
   278  	defer b.mux.Unlock()
   279  	return b.isEmpty()
   280  }
   281  
   282  func (b *itemBuffer) isEmpty() bool {
   283  	return len(b.buf) == 0
   284  }
   285  
   286  func (b *itemBuffer) ShouldFlush() bool {
   287  	b.mux.Lock()
   288  	defer b.mux.Unlock()
   289  	return b.size > b.sizeThreshold
   290  }
   291  
   292  func (b *itemBuffer) Size() int64 {
   293  	b.mux.Lock()
   294  	defer b.mux.Unlock()
   295  	return b.size
   296  }
   297  
   298  func (b *itemBuffer) GetBufferType() string {
   299  	return b.bufferType
   300  }
   301  
   302  func (b *itemBuffer) GetBatch(ctx context.Context, buf *bytes.Buffer) any {
   303  	ctx, span := trace.Start(ctx, "GenBatch")
   304  	defer span.End()
   305  	b.mux.Lock()
   306  	defer b.mux.Unlock()
   307  
   308  	if b.isEmpty() {
   309  		return nil
   310  	}
   311  	return b.genBatchFunc(ctx, b.buf, buf, nil)
   312  }
   313  
   314  type BufferOption interface {
   315  	apply(*itemBuffer)
   316  }
   317  
   318  type buffer2SqlOptionFunc func(*itemBuffer)
   319  
   320  func (f buffer2SqlOptionFunc) apply(b *itemBuffer) {
   321  	f(b)
   322  }
   323  
   324  func BufferWithReminder(reminder bp.Reminder) BufferOption {
   325  	return buffer2SqlOptionFunc(func(b *itemBuffer) {
   326  		b.Reminder = reminder
   327  	})
   328  }
   329  
   330  func BufferWithType(name string) BufferOption {
   331  	return buffer2SqlOptionFunc(func(b *itemBuffer) {
   332  		b.bufferType = name
   333  	})
   334  }
   335  
   336  func BufferWithSizeThreshold(size int64) BufferOption {
   337  	return buffer2SqlOptionFunc(func(b *itemBuffer) {
   338  		b.sizeThreshold = size
   339  	})
   340  }
   341  
   342  func BufferWithFilterItemFunc(f filterItemFunc) BufferOption {
   343  	return buffer2SqlOptionFunc(func(b *itemBuffer) {
   344  		b.filterItemFunc = f
   345  	})
   346  }
   347  
   348  func BufferWithGenBatchFunc(f genBatchFunc) BufferOption {
   349  	return buffer2SqlOptionFunc(func(b *itemBuffer) {
   350  		b.genBatchFunc = f
   351  	})
   352  }