github.com/GuanceCloud/cliutils@v1.1.21/point/decode.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 sync "sync" 11 "time" 12 ) 13 14 var decPool sync.Pool 15 16 // DecodeFn used to iterate on []*Point payload, if error returned, the iterate terminated. 17 type DecodeFn func([]*Point) error 18 19 type DecoderOption func(e *Decoder) 20 21 func WithDecEncoding(enc Encoding) DecoderOption { 22 return func(d *Decoder) { d.enc = enc } 23 } 24 25 func WithDecFn(fn DecodeFn) DecoderOption { 26 return func(d *Decoder) { d.fn = fn } 27 } 28 29 func WithDecEasyproto(on bool) DecoderOption { 30 return func(d *Decoder) { d.easyproto = on } 31 } 32 33 type Decoder struct { 34 enc Encoding 35 fn DecodeFn 36 37 easyproto bool 38 39 // For line-protocol parsing, keep original error. 40 detailedError error 41 } 42 43 func GetDecoder(opts ...DecoderOption) *Decoder { 44 v := decPool.Get() 45 if v == nil { 46 v = newDecoder() 47 } 48 49 x := v.(*Decoder) 50 51 for _, opt := range opts { 52 if opt != nil { 53 opt(x) 54 } 55 } 56 57 return x 58 } 59 60 func PutDecoder(d *Decoder) { 61 d.reset() 62 decPool.Put(d) 63 } 64 65 func newDecoder() *Decoder { 66 return &Decoder{} 67 } 68 69 func (d *Decoder) reset() { 70 d.enc = 0 71 d.fn = nil 72 d.detailedError = nil 73 d.easyproto = false 74 } 75 76 func detectTimestampPrecision(ts int64) int64 { 77 if ts/1e9 < 10 { // sec 78 return ts * int64(time.Second) 79 } else if ts/1e12 < 10 { // milli-sec 80 return ts * int64(time.Millisecond) 81 } else if ts/1e15 < 10 { // micro-sec 82 return ts * int64(time.Microsecond) 83 } else { // nano-sec 84 return ts 85 } 86 } 87 88 func (d *Decoder) doDecode(data []byte, c *cfg) ([]*Point, error) { 89 var ( 90 pts []*Point 91 err error 92 ) 93 94 switch d.enc { 95 case JSON: 96 if err := json.Unmarshal(data, &pts); err != nil { 97 return nil, err 98 } 99 100 case Protobuf: 101 if d.easyproto { 102 pts, err = unmarshalPoints(data) 103 if err != nil { 104 return nil, err 105 } 106 } else { 107 var pbpts PBPoints 108 if err = pbpts.Unmarshal(data); err != nil { 109 return nil, err 110 } 111 112 for _, pbpt := range pbpts.Arr { 113 // NOTE: under gogo Unmarshal, nothing comes from point pool, so 114 // we create Point without NewPointV2(), and make the point escaped 115 // from point pool(if defaultPTPool set). 116 // 117 // Although put back points that not originally from the pool is 118 // possible, we still distinguish this behavior for better 119 // observability of actions(these escaped point counter are export 120 // by metrics). 121 pt := &Point{ 122 pt: pbpt, 123 } 124 pt.SetFlag(Ppb) 125 pts = append(pts, pt) 126 } 127 } 128 129 case LineProtocol: 130 pts, err = parseLPPoints(data, c) 131 if err != nil { 132 d.detailedError = err 133 return nil, simplifyLPError(err) 134 } 135 } 136 137 return pts, err 138 } 139 140 func decodeAdjustPoints(pts []*Point, c *cfg) ([]*Point, error) { 141 var ( 142 chk *checker 143 newPoints []*Point 144 145 // set point's default timestamp 146 nowNano = c.timestamp 147 ) 148 149 if nowNano == 0 { // not set 150 nowNano = time.Now().UnixNano() 151 } 152 153 if c.precheck { 154 chk = &checker{cfg: c} 155 } 156 157 // adjust and check the point. 158 for idx, pt := range pts { 159 // use current time 160 if pt.pt.Time == 0 { 161 pt.pt.Time = nowNano 162 } else { // adjust point's timestamp 163 switch c.precision { 164 case PrecDyn: 165 pt.pt.Time = detectTimestampPrecision(pt.pt.Time) 166 case PrecUS: 167 pt.pt.Time *= int64(time.Microsecond) 168 case PrecMS: 169 pt.pt.Time *= int64(time.Millisecond) 170 case PrecS: 171 pt.pt.Time *= int64(time.Second) 172 case PrecM: 173 pt.pt.Time *= int64(time.Minute) 174 case PrecH: 175 pt.pt.Time *= int64(time.Hour) 176 case PrecNS: // pass 177 case PrecW, PrecD: // not used 178 default: // pass 179 } 180 } 181 182 if c.precheck { 183 pts[idx] = chk.check(pts[idx]) 184 chk.reset() 185 } 186 187 // Applied the callback on each point, the callback used to check if the 188 // point is valid for usage, for example: 189 // - Is the measurement name are expected? 190 // - Is point's key or value are expected? 191 // - Is any warning on the point? 192 // - ... 193 if c.callback != nil { 194 if x, err := c.callback(pts[idx]); err != nil { 195 return nil, err 196 } else if x != nil { 197 newPoints = append(newPoints, x) 198 } 199 } 200 } 201 202 if len(newPoints) > 0 { 203 pts = newPoints 204 } 205 206 return pts, nil 207 } 208 209 func (d *Decoder) Decode(data []byte, opts ...Option) ([]*Point, error) { 210 // point options 211 c := GetCfg(opts...) 212 defer PutCfg(c) 213 214 pts, err := d.doDecode(data, c) 215 if err != nil { 216 return nil, err 217 } 218 219 pts, err = decodeAdjustPoints(pts, c) 220 if err != nil { 221 return nil, err 222 } 223 224 if d.fn != nil { 225 return pts, d.fn(pts) 226 } 227 return pts, nil 228 } 229 230 func (d *Decoder) DetailedError() error { 231 return d.detailedError 232 }