bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/collect/queue.go (about) 1 package collect 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "net/http/httptest" 16 17 "bosun.org/metadata" 18 "bosun.org/opentsdb" 19 "bosun.org/slog" 20 "github.com/GROpenSourceDev/go-ntlm-auth/ntlm" 21 ) 22 23 func queuer() { 24 for dp := range tchan { 25 if err := dp.Clean(); err != nil { 26 atomic.AddInt64(&discarded, 1) 27 continue // if anything gets this far that can't be made valid, just drop it silently. 28 } 29 qlock.Lock() 30 for { 31 if len(queue) > MaxQueueLen { 32 atomic.AddInt64(&dropped, 1) 33 break 34 } 35 queue = append(queue, dp) 36 select { 37 case dp = <-tchan: 38 if err := dp.Clean(); err != nil { 39 atomic.AddInt64(&discarded, 1) 40 break // if anything gets this far that can't be made valid, just drop it silently. 41 } 42 continue 43 default: 44 } 45 break 46 } 47 qlock.Unlock() 48 } 49 } 50 51 // Locks the queue and sends all datapoints. Intended to be used as scollector exits. 52 func Flush() { 53 flushData() 54 metadata.FlushMetadata() 55 qlock.Lock() 56 for len(queue) > 0 { 57 i := len(queue) 58 if i > BatchSize { 59 i = BatchSize 60 } 61 sending := queue[:i] 62 queue = queue[i:] 63 if Debug { 64 slog.Infof("sending: %d, remaining: %d", i, len(queue)) 65 } 66 sendBatch(sending) 67 } 68 qlock.Unlock() 69 } 70 71 func send() { 72 for { 73 qlock.Lock() 74 if i := len(queue); i > 0 { 75 if i > BatchSize { 76 i = BatchSize 77 } 78 sending := queue[:i] 79 queue = queue[i:] 80 if Debug { 81 slog.Infof("sending: %d, remaining: %d", i, len(queue)) 82 } 83 qlock.Unlock() 84 if DisableDefaultCollectors == false { 85 Sample("collect.post.batchsize", Tags, float64(len(sending))) 86 } 87 sendBatch(sending) 88 } else { 89 qlock.Unlock() 90 time.Sleep(time.Second) 91 } 92 } 93 } 94 95 func sendBatch(batch []*opentsdb.DataPoint) { 96 if Print { 97 for _, d := range batch { 98 j, err := d.MarshalJSON() 99 if err != nil { 100 slog.Error(err) 101 } 102 slog.Info(string(j)) 103 } 104 recordSent(len(batch)) 105 return 106 } 107 now := time.Now() 108 resp, err := SendDataPoints(batch, tsdbURL) 109 if err == nil { 110 defer resp.Body.Close() 111 } 112 d := time.Since(now).Nanoseconds() / 1e6 113 Sample("collect.post.duration", Tags, float64(d)) 114 Add("collect.post.total_duration", Tags, d) 115 Add("collect.post.count", Tags, 1) 116 // Some problem with connecting to the server; retry later. 117 if err != nil || resp.StatusCode != http.StatusNoContent { 118 if err != nil { 119 Add("collect.post.error", Tags, 1) 120 slog.Error(err) 121 } else if resp.StatusCode != http.StatusNoContent { 122 Add("collect.post.bad_status", Tags, 1) 123 slog.Errorln(resp.Status) 124 body, err := ioutil.ReadAll(resp.Body) 125 if err != nil { 126 slog.Error(err) 127 } 128 if len(body) > 0 { 129 slog.Error(string(body)) 130 } 131 } 132 restored := 0 133 for _, msg := range batch { 134 restored++ 135 tchan <- msg 136 } 137 d := time.Second * 5 138 Add("collect.post.restore", Tags, int64(restored)) 139 slog.Infof("restored %d, sleeping %s", restored, d) 140 time.Sleep(d) 141 return 142 } 143 // sleep on success to avoid overstressing opentsdb 144 time.Sleep(500 * time.Millisecond) 145 // Drain up to 512 bytes so the Transport can reuse the connection when it is closed 146 io.CopyN(ioutil.Discard, resp.Body, 512) 147 recordSent(len(batch)) 148 } 149 150 func recordSent(num int) { 151 if Debug { 152 slog.Infoln("sent", num) 153 } 154 slock.Lock() 155 sent += int64(num) 156 slock.Unlock() 157 } 158 159 var bufferPool = sync.Pool{ 160 New: func() interface{} { return &bytes.Buffer{} }, 161 } 162 163 func SendDataPoints(dps []*opentsdb.DataPoint, tsdb string) (*http.Response, error) { 164 req, err := buildHTTPRequest(dps, tsdb) 165 if err != nil { 166 return nil, err 167 } 168 if DirectHandler != nil { 169 rec := httptest.NewRecorder() 170 DirectHandler.ServeHTTP(rec, req) 171 return rec.Result(), nil 172 } 173 client := DefaultClient 174 175 if UseNtlm { 176 resp, err := ntlm.DoNTLMRequest(client, req) 177 if err != nil { 178 return nil, err 179 } 180 if resp.StatusCode == 401 { 181 slog.Errorf("Scollector unauthorized to post data points to tsdb. Terminating.") 182 os.Exit(1) 183 } 184 return resp, err 185 } 186 resp, err := client.Do(req) 187 return resp, err 188 } 189 190 func buildHTTPRequest(dps []*opentsdb.DataPoint, tsdb string) (*http.Request, error) { 191 buf := bufferPool.Get().(*bytes.Buffer) 192 defer bufferPool.Put(buf) 193 buf.Reset() 194 g := gzip.NewWriter(buf) 195 if err := json.NewEncoder(g).Encode(dps); err != nil { 196 return nil, err 197 } 198 if err := g.Close(); err != nil { 199 return nil, err 200 } 201 req, err := http.NewRequest("POST", tsdb, buf) 202 if err != nil { 203 return nil, err 204 } 205 req.Header.Set("Content-Type", "application/json") 206 req.Header.Set("Content-Encoding", "gzip") 207 if AuthToken != "" { 208 req.Header.Set("X-Access-Token", AuthToken) 209 } 210 Add("collect.post.total_bytes", Tags, int64(buf.Len())) 211 return req, nil 212 }