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 }