Created
December 5, 2014 23:55
-
-
Save paxan/aeee3db334c9a4a0daf5 to your computer and use it in GitHub Desktop.
HACK: Librato measurement sender
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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