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  }