Skip to content

Instantly share code, notes, and snippets.

@paxan
Created December 5, 2014 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paxan/aeee3db334c9a4a0daf5 to your computer and use it in GitHub Desktop.
Save paxan/aeee3db334c9a4a0daf5 to your computer and use it in GitHub Desktop.
HACK: Librato measurement sender
package main
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"regexp"
"strings"
"time"
)
type librato struct {
http.Client
prefix string
user, token string
counters chan libratoCounter
gauges chan interface{}
}
func libratoTelemetry(prefix, user, token string) *librato {
p := new(librato)
p.prefix = prefix
p.user = user
p.token = token
p.counters = make(chan libratoCounter, 1000)
p.gauges = make(chan interface{}, 1000)
go p.processTelemetry()
return p
}
func (p *librato) processTelemetry() {
const dt = 5 * time.Second
timer := time.NewTimer(dt)
m := new(libratoMeasurements)
for {
if m.size() >= 300 {
go p.post(m)
m = new(libratoMeasurements)
timer.Reset(dt)
}
select {
case c := <-p.counters:
m.Counters = append(m.Counters, c)
case g := <-p.gauges:
m.Gauges = append(m.Gauges, g)
case <-timer.C:
if m.size() > 0 {
go p.post(m)
m = new(libratoMeasurements)
}
timer.Reset(dt)
}
}
}
func (p *librato) post(m *libratoMeasurements) {
b, err := json.Marshal(m)
if err != nil {
log.Print(err)
return
}
req, err := http.NewRequest("POST", "https://metrics-api.librato.com/v1/metrics", bytes.NewBuffer(b))
if err != nil {
log.Print(err)
}
req.SetBasicAuth(p.user, p.token)
req.Header.Set("Content-Type", "application/json")
resp, err := p.Client.Do(req)
if err != nil {
log.Print(err)
}
if resp != nil {
if resp.StatusCode >= 400 {
log.Printf("Librato response: %s", resp.Status)
}
io.Copy(ioutil.Discard, resp.Body) // discard the response
resp.Body.Close()
}
}
func replacer(re, repl string) func(string) string {
pattern := regexp.MustCompile(re)
return func(s string) string {
return pattern.ReplaceAllString(s, repl)
}
}
var namePreps = []func(string) string{
strings.ToLower,
replacer(`[:=]\s*`, "."), // "request: total" -> "request.total" or "foo=bar" -> "foo.bar"
replacer(`\s+`, "-"), // "foo bar qaz" -> "foo-bar-qaz"
replacer(`-(\d+[a-z\-_]*)`, ".$1"), // "load-avg-15m" -> "load-avg.15m"
replacer(`\.+`, "."), // "oops...what" -> "oops.what"
replacer(`[^a-z0-9_\-\.]`, "_"), // replace any weird chars with _
}
func (p *librato) prepName(s string) string {
for _, f := range namePreps {
s = f(s)
}
if p.prefix == "" {
return s
}
return p.prefix + "." + s
}
type libratoCounter struct {
Name string `json:"name"`
Value int `json:"value"`
Time int64 `json:"measure_time,omitempty"`
Source string `json:"source,omitempty"`
}
type libratoGauge struct {
Name string `json:"name"`
Value interface{} `json:"value"`
Time int64 `json:"measure_time,omitempty"`
Source string `json:"source,omitempty"`
}
type libratoGaugeSummary struct {
Name string `json:"name"`
Count int `json:"count"`
Sum float64 `json:"sum"`
Max float64 `json:"max"`
Min float64 `json:"min"`
SumSquares float64 `json:"sum_squares"`
Time int64 `json:"measure_time,omitempty"`
Source string `json:"source,omitempty"`
}
type libratoMeasurements struct {
Time int64 `json:"measure_time,omitempty"`
Source string `json:"source,omitempty"`
Counters []libratoCounter `json:"counters,omitempty"`
Gauges []interface{} `json:"gauges,omitempty"`
}
func (p *libratoMeasurements) size() int { return len(p.Counters) + len(p.Gauges) }
func (p *librato) put(measurement interface{}) {
switch m := measurement.(type) {
case libratoCounter:
p.counters <- m
case libratoGauge, libratoGaugeSummary:
p.gauges <- m
}
}
func (p *librato) Value(value interface{}, name string) {
switch value.(type) {
case float32, float64,
int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64:
default:
return
}
p.put(libratoGauge{Name: p.prepName(name), Value: value, Time: time.Now().Unix()})
}
func (p *librato) Count(value int, name string) {
p.put(libratoCounter{Name: p.prepName(name), Value: value, Time: time.Now().Unix()})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment