github.com/GuanceCloud/cliutils@v1.1.21/pipeline/offload/offload.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 offload 7 8 import ( 9 "context" 10 "fmt" 11 "time" 12 13 "github.com/GuanceCloud/cliutils/logger" 14 "github.com/GuanceCloud/cliutils/point" 15 ) 16 17 const ( 18 maxCustomer = 16 19 chanSize = maxCustomer 20 ptsBuf = 128 21 flushInterval = time.Second * 15 22 ) 23 24 var l = logger.DefaultSLogger("pl-offload") 25 26 func InitLog() { 27 l = logger.SLogger("pl-offload") 28 } 29 30 type OffloadConfig struct { 31 Receiver string `toml:"receiver"` 32 Addresses []string `toml:"addresses"` 33 } 34 35 type Receiver interface { 36 // thread safety 37 Send(s uint64, cat point.Category, data []*point.Point) error 38 } 39 40 type dataChan struct { 41 unknownCategory, 42 dynamicDWCategory, 43 metric, 44 metricDeprecated, 45 network, 46 keyEvent, 47 object, 48 customObject, 49 logging, 50 tracing, 51 rum, 52 security, 53 profiling chan []*point.Point 54 } 55 56 func newDataChan() *dataChan { 57 newChan := func() chan []*point.Point { 58 return make(chan []*point.Point, chanSize) 59 } 60 61 return &dataChan{ 62 unknownCategory: newChan(), 63 dynamicDWCategory: newChan(), 64 metric: newChan(), 65 metricDeprecated: newChan(), 66 network: newChan(), 67 keyEvent: newChan(), 68 object: newChan(), 69 customObject: newChan(), 70 logging: newChan(), 71 tracing: newChan(), 72 rum: newChan(), 73 security: newChan(), 74 profiling: newChan(), 75 } 76 } 77 78 type OffloadWorker struct { 79 ch *dataChan 80 stopChan chan struct{} 81 82 sender Receiver 83 } 84 85 func NewOffloader(cfg *OffloadConfig) (*OffloadWorker, error) { 86 if cfg == nil || len(cfg.Receiver) == 0 { 87 return nil, fmt.Errorf("no config") 88 } 89 90 wrk := &OffloadWorker{ 91 ch: newDataChan(), 92 stopChan: make(chan struct{}), 93 } 94 95 switch cfg.Receiver { 96 case DKRcv: 97 if s, err := NewDKRecver(cfg.Addresses); err != nil { 98 return nil, err 99 } else { 100 wrk.sender = s 101 } 102 default: 103 return nil, fmt.Errorf("unsupported receiver") 104 } 105 106 return wrk, nil 107 } 108 109 func (offload *OffloadWorker) Customer(ctx context.Context, cat point.Category) error { 110 flushTicker := time.NewTicker(flushInterval) 111 var ch chan []*point.Point 112 113 switch cat { //nolint:exhaustive 114 case point.Logging: 115 ch = offload.ch.logging 116 default: 117 return fmt.Errorf("unsupported category") 118 } 119 120 ptsCache := make([]*point.Point, 0, ptsBuf) 121 122 var lbID uint64 = 0 // taking modulus to achieve load balancing 123 124 for { 125 select { 126 case pts := <-ch: 127 ptsCache, lbID = offload.sendOrCache(lbID, cat, ptsCache, pts) 128 129 case <-flushTicker.C: 130 if len(ptsCache) > 0 { 131 if err := offload.sender.Send(lbID, cat, ptsCache); err != nil { 132 l.Errorf("offload send failed: %w", err) 133 } 134 ptsCache = make([]*point.Point, 0, ptsBuf) 135 lbID++ 136 } 137 case <-offload.stopChan: 138 if err := offload.sender.Send(lbID, cat, ptsCache); err != nil { 139 l.Errorf("offload send failed: %w", err) 140 } 141 return nil 142 143 case <-ctx.Done(): 144 if err := offload.sender.Send(lbID, cat, ptsCache); err != nil { 145 l.Errorf("offload send failed: %w", err) 146 } 147 return nil 148 } 149 } 150 } 151 152 func (offload *OffloadWorker) sendOrCache(s uint64, cat point.Category, cache []*point.Point, ptsInput []*point.Point) ([]*point.Point, uint64) { 153 diff := ptsBuf - len(cache) 154 switch { 155 case diff > len(ptsInput): 156 // append 157 cache = append(cache, ptsInput...) 158 159 case diff == len(ptsInput): 160 // append and send 161 cache = append(cache, ptsInput...) 162 if err := offload.sender.Send(s, cat, cache); err != nil { 163 l.Errorf("offload send failed: %w", err) 164 } 165 // new slice 166 cache = make([]*point.Point, 0, ptsBuf) 167 s++ 168 169 case diff < len(ptsInput): 170 cache = append(cache, ptsInput[:diff]...) 171 if err := offload.sender.Send(s, cat, cache); err != nil { 172 l.Errorf("offload send failed: %w", err) 173 } 174 cache = make([]*point.Point, 0, ptsBuf) 175 176 ptsInput = ptsInput[diff:] 177 for i := 0; i < len(ptsInput)/ptsBuf; i++ { 178 cache = append(cache, ptsInput[i*ptsBuf:(i+1)*ptsBuf]...) 179 if err := offload.sender.Send(s, cat, cache); err != nil { 180 l.Errorf("offload send failed: %w", err) 181 } 182 cache = make([]*point.Point, 0, ptsBuf) 183 } 184 185 if i := len(ptsInput) % ptsBuf; i > 0 { 186 cache = append(cache, ptsInput[len(ptsInput)-i:]...) 187 } 188 s++ 189 } 190 return cache, s 191 } 192 193 func (offload *OffloadWorker) Send(cat point.Category, pts []*point.Point) error { 194 if cat != point.Logging { 195 return fmt.Errorf("unsupported category") 196 } 197 198 if offload.ch == nil || offload.ch.logging == nil { 199 return fmt.Errorf("logging data chan not ready") 200 } 201 202 switch cat { //nolint:exhaustive 203 case point.Logging: 204 offload.ch.logging <- pts 205 default: 206 } 207 208 return nil 209 } 210 211 func (offload *OffloadWorker) Stop() { 212 if offload.stopChan != nil { 213 close(offload.stopChan) 214 } 215 }