github.com/GuanceCloud/cliutils@v1.1.21/point/encode.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package point
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"strings"
    13  	sync "sync"
    14  )
    15  
    16  // EncodeFn used to iterate on []*Point payload, if error returned, the iterate terminated.
    17  type EncodeFn func(batchSize int, payload []byte) error
    18  
    19  type EncoderOption func(e *Encoder)
    20  
    21  func WithEncEncoding(enc Encoding) EncoderOption {
    22  	return func(e *Encoder) { e.enc = enc }
    23  }
    24  
    25  func WithEncFn(fn EncodeFn) EncoderOption {
    26  	return func(enc *Encoder) { enc.fn = fn }
    27  }
    28  
    29  func WithEncBatchSize(size int) EncoderOption {
    30  	return func(e *Encoder) { e.batchSize = size }
    31  }
    32  
    33  func WithEncBatchBytes(bytes int) EncoderOption {
    34  	return func(e *Encoder) { e.bytesSize = bytes }
    35  }
    36  
    37  type Encoder struct {
    38  	bytesSize,
    39  	batchSize int
    40  
    41  	pts []*Point
    42  	lastPtsIdx,
    43  	trimmed,
    44  	parts int
    45  	lastErr error
    46  
    47  	lpPointBuf []byte
    48  	pbpts      *PBPoints
    49  
    50  	fn  EncodeFn
    51  	enc Encoding
    52  }
    53  
    54  var encPool sync.Pool
    55  
    56  func GetEncoder(opts ...EncoderOption) *Encoder {
    57  	v := encPool.Get()
    58  	if v == nil {
    59  		v = newEncoder()
    60  	}
    61  
    62  	x := v.(*Encoder)
    63  	for _, opt := range opts {
    64  		if opt != nil {
    65  			opt(x)
    66  		}
    67  	}
    68  
    69  	return x
    70  }
    71  
    72  func PutEncoder(e *Encoder) {
    73  	e.reset()
    74  	encPool.Put(e)
    75  }
    76  
    77  func newEncoder() *Encoder {
    78  	return &Encoder{
    79  		enc:   DefaultEncoding,
    80  		pbpts: &PBPoints{},
    81  	}
    82  }
    83  
    84  func (e *Encoder) reset() {
    85  	e.batchSize = 0
    86  	e.bytesSize = 0
    87  	e.fn = nil
    88  	e.pts = nil
    89  	e.enc = DefaultEncoding
    90  	e.lastPtsIdx = 0
    91  	e.lastErr = nil
    92  	e.parts = 0
    93  	e.trimmed = 0
    94  	e.pbpts.Arr = e.pbpts.Arr[:0]
    95  	e.lpPointBuf = e.lpPointBuf[:0]
    96  }
    97  
    98  func (e *Encoder) getPayload(pts []*Point) ([]byte, error) {
    99  	if len(pts) == 0 {
   100  		return nil, nil
   101  	}
   102  
   103  	var (
   104  		payload []byte
   105  		err     error
   106  	)
   107  
   108  	switch e.enc {
   109  	case Protobuf:
   110  		pbpts := e.pbpts
   111  
   112  		defer func() {
   113  			// Reset e.pbpts buffer: getPayload maybe called multiple times
   114  			// during a single Encode().
   115  			e.pbpts.Arr = e.pbpts.Arr[:0]
   116  		}()
   117  
   118  		for _, pt := range pts {
   119  			pbpts.Arr = append(pbpts.Arr, pt.PBPoint())
   120  		}
   121  
   122  		if payload, err = pbpts.Marshal(); err != nil {
   123  			return nil, err
   124  		}
   125  
   126  	case LineProtocol:
   127  		lppart := []string{}
   128  		for _, pt := range pts {
   129  			if x := pt.LineProto(); x == "" {
   130  				continue
   131  			} else {
   132  				lppart = append(lppart, x)
   133  			}
   134  		}
   135  
   136  		payload = []byte(strings.Join(lppart, "\n"))
   137  
   138  	case JSON:
   139  		payload, err = json.Marshal(pts)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  	}
   144  
   145  	if e.fn != nil {
   146  		return payload, e.fn(len(pts), payload)
   147  	}
   148  	return payload, nil
   149  }
   150  
   151  func (e *Encoder) doEncode(pts []*Point) ([][]byte, error) {
   152  	if len(pts) == 0 {
   153  		return nil, nil
   154  	}
   155  
   156  	var (
   157  		batches [][]byte
   158  		batch   []*Point
   159  	)
   160  
   161  	// nolint: gocritic
   162  	if e.bytesSize > 0 { // prefer byte size
   163  		curBytesBatchSize := 0
   164  		for _, pt := range pts {
   165  			batch = append(batch, pt)
   166  			curBytesBatchSize += pt.Size()
   167  
   168  			if curBytesBatchSize >= e.bytesSize {
   169  				payload, err := e.getPayload(batch)
   170  				if err != nil {
   171  					return nil, err
   172  				}
   173  				batches = append(batches, payload)
   174  
   175  				// reset
   176  				batch = batch[:0]
   177  				curBytesBatchSize = 0
   178  			}
   179  		}
   180  
   181  		if len(batch) > 0 { // tail
   182  			payload, err := e.getPayload(batch)
   183  			if err != nil {
   184  				return nil, err
   185  			}
   186  			batches = append(batches, payload)
   187  		}
   188  	} else if e.batchSize > 0 { // then point count
   189  		for _, pt := range pts {
   190  			batch = append(batch, pt)
   191  			if len(batch)%e.batchSize == 0 { // switch next batch
   192  				payload, err := e.getPayload(batch)
   193  				if err != nil {
   194  					return nil, err
   195  				}
   196  				batches = append(batches, payload)
   197  				batch = batch[:0]
   198  			}
   199  		}
   200  
   201  		if len(batch) > 0 { // tail
   202  			payload, err := e.getPayload(batch)
   203  			if err != nil {
   204  				return nil, err
   205  			}
   206  			batches = append(batches, payload)
   207  		}
   208  	} else {
   209  		payload, err := e.getPayload(pts)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		batches = append(batches, payload)
   214  	}
   215  
   216  	return batches, nil
   217  }
   218  
   219  // Encode get bytes form of multiple Points, often used to Write to somewhere(file/network/...),
   220  // batchSize used to split huge points into multiple part. Set batchSize to 0 to disable the split.
   221  func (e *Encoder) Encode(pts []*Point) ([][]byte, error) {
   222  	return e.doEncode(pts)
   223  }
   224  
   225  var errTooSmallBuffer = errors.New("too small buffer")
   226  
   227  func (e *Encoder) LastErr() error {
   228  	return e.lastErr
   229  }
   230  
   231  func (e *Encoder) String() string {
   232  	return fmt.Sprintf("encoding: %s, parts: %d, byte size: %d, e.batchSize: %d, lastPtsIdx: %d, trimmed: %d",
   233  		e.enc, e.parts, e.bytesSize, e.batchSize, e.lastPtsIdx, e.trimmed,
   234  	)
   235  }
   236  
   237  // PB2LP convert protobuf Point to line-protocol Point.
   238  func PB2LP(pb []byte) (lp []byte, err error) {
   239  	dec := GetDecoder(WithDecEncoding(Protobuf))
   240  	defer PutDecoder(dec)
   241  
   242  	pts, err := dec.Decode(pb)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	enc := GetEncoder(WithEncEncoding(LineProtocol))
   248  	defer PutEncoder(enc)
   249  
   250  	arr, err := enc.Encode(pts)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	return arr[0], nil
   255  }