replace zxq.co/ripple/hanayo

This commit is contained in:
Alicia
2019-02-23 13:29:15 +00:00
commit c3d206c173
5871 changed files with 1353715 additions and 0 deletions

52
vendor/github.com/DataDog/datadog-go/statsd/README.md generated vendored Normal file
View File

@@ -0,0 +1,52 @@
## Overview
Package `statsd` provides a Go [dogstatsd](http://docs.datadoghq.com/guides/dogstatsd/) client. Dogstatsd extends Statsd, adding tags
and histograms.
## Get the code
$ go get github.com/DataDog/datadog-go/statsd
## Usage
```go
// Create the client
c, err := statsd.New("127.0.0.1:8125")
if err != nil {
log.Fatal(err)
}
// Prefix every metric with the app name
c.Namespace = "flubber."
// Send the EC2 availability zone as a tag with every metric
c.Tags = append(c.Tags, "us-east-1a")
// Do some metrics!
err = c.Gauge("request.queue_depth", 12, nil, 1)
err = c.Timing("request.duration", duration, nil, 1) // Uses a time.Duration!
err = c.TimeInMilliseconds("request", 12, nil, 1)
err = c.Incr("request.count_total", nil, 1)
err = c.Decr("request.count_total", nil, 1)
err = c.Count("request.count_total", 2, nil, 1)
```
## Buffering Client
DogStatsD accepts packets with multiple statsd payloads in them. Using the BufferingClient via `NewBufferingClient` will buffer up commands and send them when the buffer is reached or after 100msec.
## Development
Run the tests with:
$ go test
## Documentation
Please see: http://godoc.org/github.com/DataDog/datadog-go/statsd
## License
go-dogstatsd is released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
## Credits
Original code by [ooyala](https://github.com/ooyala/go-dogstatsd).

580
vendor/github.com/DataDog/datadog-go/statsd/statsd.go generated vendored Normal file
View File

@@ -0,0 +1,580 @@
// Copyright 2013 Ooyala, Inc.
/*
Package statsd provides a Go dogstatsd client. Dogstatsd extends the popular statsd,
adding tags and histograms and pushing upstream to Datadog.
Refer to http://docs.datadoghq.com/guides/dogstatsd/ for information about DogStatsD.
Example Usage:
// Create the client
c, err := statsd.New("127.0.0.1:8125")
if err != nil {
log.Fatal(err)
}
// Prefix every metric with the app name
c.Namespace = "flubber."
// Send the EC2 availability zone as a tag with every metric
c.Tags = append(c.Tags, "us-east-1a")
err = c.Gauge("request.duration", 1.2, nil, 1)
statsd is based on go-statsd-client.
*/
package statsd
import (
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"
)
/*
OptimalPayloadSize defines the optimal payload size for a UDP datagram, 1432 bytes
is optimal for regular networks with an MTU of 1500 so datagrams don't get
fragmented. It's generally recommended not to fragment UDP datagrams as losing
a single fragment will cause the entire datagram to be lost.
This can be increased if your network has a greater MTU or you don't mind UDP
datagrams getting fragmented. The practical limit is MaxUDPPayloadSize
*/
const OptimalPayloadSize = 1432
/*
MaxUDPPayloadSize defines the maximum payload size for a UDP datagram.
Its value comes from the calculation: 65535 bytes Max UDP datagram size -
8byte UDP header - 60byte max IP headers
any number greater than that will see frames being cut out.
*/
const MaxUDPPayloadSize = 65467
// A Client is a handle for sending udp messages to dogstatsd. It is safe to
// use one Client from multiple goroutines simultaneously.
type Client struct {
conn net.Conn
// Namespace to prepend to all statsd calls
Namespace string
// Tags are global tags to be added to every statsd call
Tags []string
// BufferLength is the length of the buffer in commands.
bufferLength int
flushTime time.Duration
commands []string
buffer bytes.Buffer
stop bool
sync.Mutex
}
// New returns a pointer to a new Client given an addr in the format "hostname:port".
func New(addr string) (*Client, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", nil, udpAddr)
if err != nil {
return nil, err
}
client := &Client{conn: conn}
return client, nil
}
// NewBuffered returns a Client that buffers its output and sends it in chunks.
// Buflen is the length of the buffer in number of commands.
func NewBuffered(addr string, buflen int) (*Client, error) {
client, err := New(addr)
if err != nil {
return nil, err
}
client.bufferLength = buflen
client.commands = make([]string, 0, buflen)
client.flushTime = time.Millisecond * 100
go client.watch()
return client, nil
}
// format a message from its name, value, tags and rate. Also adds global
// namespace and tags.
func (c *Client) format(name, value string, tags []string, rate float64) string {
var buf bytes.Buffer
if c.Namespace != "" {
buf.WriteString(c.Namespace)
}
buf.WriteString(name)
buf.WriteString(":")
buf.WriteString(value)
if rate < 1 {
buf.WriteString(`|@`)
buf.WriteString(strconv.FormatFloat(rate, 'f', -1, 64))
}
writeTagString(&buf, c.Tags, tags)
return buf.String()
}
func (c *Client) watch() {
for _ = range time.Tick(c.flushTime) {
if c.stop {
return
}
c.Lock()
if len(c.commands) > 0 {
// FIXME: eating error here
c.flush()
}
c.Unlock()
}
}
func (c *Client) append(cmd string) error {
c.Lock()
defer c.Unlock()
c.commands = append(c.commands, cmd)
// if we should flush, lets do it
if len(c.commands) == c.bufferLength {
if err := c.flush(); err != nil {
return err
}
}
return nil
}
func (c *Client) joinMaxSize(cmds []string, sep string, maxSize int) ([][]byte, []int) {
c.buffer.Reset() //clear buffer
var frames [][]byte
var ncmds []int
sepBytes := []byte(sep)
sepLen := len(sep)
elem := 0
for _, cmd := range cmds {
needed := len(cmd)
if elem != 0 {
needed = needed + sepLen
}
if c.buffer.Len()+needed <= maxSize {
if elem != 0 {
c.buffer.Write(sepBytes)
}
c.buffer.WriteString(cmd)
elem++
} else {
frames = append(frames, copyAndResetBuffer(&c.buffer))
ncmds = append(ncmds, elem)
// if cmd is bigger than maxSize it will get flushed on next loop
c.buffer.WriteString(cmd)
elem = 1
}
}
//add whatever is left! if there's actually something
if c.buffer.Len() > 0 {
frames = append(frames, copyAndResetBuffer(&c.buffer))
ncmds = append(ncmds, elem)
}
return frames, ncmds
}
func copyAndResetBuffer(buf *bytes.Buffer) []byte {
tmpBuf := make([]byte, buf.Len())
copy(tmpBuf, buf.Bytes())
buf.Reset()
return tmpBuf
}
// flush the commands in the buffer. Lock must be held by caller.
func (c *Client) flush() error {
frames, flushable := c.joinMaxSize(c.commands, "\n", OptimalPayloadSize)
var err error
cmdsFlushed := 0
for i, data := range frames {
_, e := c.conn.Write(data)
if e != nil {
err = e
break
}
cmdsFlushed += flushable[i]
}
// clear the slice with a slice op, doesn't realloc
if cmdsFlushed == len(c.commands) {
c.commands = c.commands[:0]
} else {
//this case will cause a future realloc...
// drop problematic command though (sorry).
c.commands = c.commands[cmdsFlushed+1:]
}
return err
}
func (c *Client) sendMsg(msg string) error {
// return an error if message is bigger than MaxUDPPayloadSize
if len(msg) > MaxUDPPayloadSize {
return errors.New("message size exceeds MaxUDPPayloadSize")
}
// if this client is buffered, then we'll just append this
if c.bufferLength > 0 {
return c.append(msg)
}
_, err := c.conn.Write([]byte(msg))
return err
}
// send handles sampling and sends the message over UDP. It also adds global namespace prefixes and tags.
func (c *Client) send(name, value string, tags []string, rate float64) error {
if c == nil {
return nil
}
if rate < 1 && rand.Float64() > rate {
return nil
}
data := c.format(name, value, tags, rate)
return c.sendMsg(data)
}
// Gauge measures the value of a metric at a particular time.
func (c *Client) Gauge(name string, value float64, tags []string, rate float64) error {
stat := fmt.Sprintf("%f|g", value)
return c.send(name, stat, tags, rate)
}
// Count tracks how many times something happened per second.
func (c *Client) Count(name string, value int64, tags []string, rate float64) error {
stat := fmt.Sprintf("%d|c", value)
return c.send(name, stat, tags, rate)
}
// Histogram tracks the statistical distribution of a set of values.
func (c *Client) Histogram(name string, value float64, tags []string, rate float64) error {
stat := fmt.Sprintf("%f|h", value)
return c.send(name, stat, tags, rate)
}
// Decr is just Count of -1
func (c *Client) Decr(name string, tags []string, rate float64) error {
return c.send(name, "-1|c", tags, rate)
}
// Incr is just Count of 1
func (c *Client) Incr(name string, tags []string, rate float64) error {
return c.send(name, "1|c", tags, rate)
}
// Set counts the number of unique elements in a group.
func (c *Client) Set(name string, value string, tags []string, rate float64) error {
stat := fmt.Sprintf("%s|s", value)
return c.send(name, stat, tags, rate)
}
// Timing sends timing information, it is an alias for TimeInMilliseconds
func (c *Client) Timing(name string, value time.Duration, tags []string, rate float64) error {
return c.TimeInMilliseconds(name, value.Seconds()*1000, tags, rate)
}
// TimeInMilliseconds sends timing information in milliseconds.
// It is flushed by statsd with percentiles, mean and other info (https://github.com/etsy/statsd/blob/master/docs/metric_types.md#timing)
func (c *Client) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error {
stat := fmt.Sprintf("%f|ms", value)
return c.send(name, stat, tags, rate)
}
// Event sends the provided Event.
func (c *Client) Event(e *Event) error {
if c == nil {
return nil
}
stat, err := e.Encode(c.Tags...)
if err != nil {
return err
}
return c.sendMsg(stat)
}
// SimpleEvent sends an event with the provided title and text.
func (c *Client) SimpleEvent(title, text string) error {
e := NewEvent(title, text)
return c.Event(e)
}
// ServiceCheck sends the provided ServiceCheck.
func (c *Client) ServiceCheck(sc *ServiceCheck) error {
stat, err := sc.Encode(c.Tags...)
if err != nil {
return err
}
return c.sendMsg(stat)
}
// SimpleServiceCheck sends an serviceCheck with the provided name and status.
func (c *Client) SimpleServiceCheck(name string, status ServiceCheckStatus) error {
sc := NewServiceCheck(name, status)
return c.ServiceCheck(sc)
}
// Close the client connection.
func (c *Client) Close() error {
if c == nil {
return nil
}
c.stop = true
return c.conn.Close()
}
// Events support
type eventAlertType string
const (
// Info is the "info" AlertType for events
Info eventAlertType = "info"
// Error is the "error" AlertType for events
Error eventAlertType = "error"
// Warning is the "warning" AlertType for events
Warning eventAlertType = "warning"
// Success is the "success" AlertType for events
Success eventAlertType = "success"
)
type eventPriority string
const (
// Normal is the "normal" Priority for events
Normal eventPriority = "normal"
// Low is the "low" Priority for events
Low eventPriority = "low"
)
// An Event is an object that can be posted to your DataDog event stream.
type Event struct {
// Title of the event. Required.
Title string
// Text is the description of the event. Required.
Text string
// Timestamp is a timestamp for the event. If not provided, the dogstatsd
// server will set this to the current time.
Timestamp time.Time
// Hostname for the event.
Hostname string
// AggregationKey groups this event with others of the same key.
AggregationKey string
// Priority of the event. Can be statsd.Low or statsd.Normal.
Priority eventPriority
// SourceTypeName is a source type for the event.
SourceTypeName string
// AlertType can be statsd.Info, statsd.Error, statsd.Warning, or statsd.Success.
// If absent, the default value applied by the dogstatsd server is Info.
AlertType eventAlertType
// Tags for the event.
Tags []string
}
// NewEvent creates a new event with the given title and text. Error checking
// against these values is done at send-time, or upon running e.Check.
func NewEvent(title, text string) *Event {
return &Event{
Title: title,
Text: text,
}
}
// Check verifies that an event is valid.
func (e Event) Check() error {
if len(e.Title) == 0 {
return fmt.Errorf("statsd.Event title is required")
}
if len(e.Text) == 0 {
return fmt.Errorf("statsd.Event text is required")
}
return nil
}
// Encode returns the dogstatsd wire protocol representation for an event.
// Tags may be passed which will be added to the encoded output but not to
// the Event's list of tags, eg. for default tags.
func (e Event) Encode(tags ...string) (string, error) {
err := e.Check()
if err != nil {
return "", err
}
text := e.escapedText()
var buffer bytes.Buffer
buffer.WriteString("_e{")
buffer.WriteString(strconv.FormatInt(int64(len(e.Title)), 10))
buffer.WriteRune(',')
buffer.WriteString(strconv.FormatInt(int64(len(text)), 10))
buffer.WriteString("}:")
buffer.WriteString(e.Title)
buffer.WriteRune('|')
buffer.WriteString(text)
if !e.Timestamp.IsZero() {
buffer.WriteString("|d:")
buffer.WriteString(strconv.FormatInt(int64(e.Timestamp.Unix()), 10))
}
if len(e.Hostname) != 0 {
buffer.WriteString("|h:")
buffer.WriteString(e.Hostname)
}
if len(e.AggregationKey) != 0 {
buffer.WriteString("|k:")
buffer.WriteString(e.AggregationKey)
}
if len(e.Priority) != 0 {
buffer.WriteString("|p:")
buffer.WriteString(string(e.Priority))
}
if len(e.SourceTypeName) != 0 {
buffer.WriteString("|s:")
buffer.WriteString(e.SourceTypeName)
}
if len(e.AlertType) != 0 {
buffer.WriteString("|t:")
buffer.WriteString(string(e.AlertType))
}
writeTagString(&buffer, tags, e.Tags)
return buffer.String(), nil
}
// ServiceCheck support
type ServiceCheckStatus byte
const (
// Ok is the "ok" ServiceCheck status
Ok ServiceCheckStatus = 0
// Warn is the "warning" ServiceCheck status
Warn ServiceCheckStatus = 1
// Critical is the "critical" ServiceCheck status
Critical ServiceCheckStatus = 2
// Unknown is the "unknown" ServiceCheck status
Unknown ServiceCheckStatus = 3
)
// An ServiceCheck is an object that contains status of DataDog service check.
type ServiceCheck struct {
// Name of the service check. Required.
Name string
// Status of service check. Required.
Status ServiceCheckStatus
// Timestamp is a timestamp for the serviceCheck. If not provided, the dogstatsd
// server will set this to the current time.
Timestamp time.Time
// Hostname for the serviceCheck.
Hostname string
// A message describing the current state of the serviceCheck.
Message string
// Tags for the serviceCheck.
Tags []string
}
// NewServiceCheck creates a new serviceCheck with the given name and status. Error checking
// against these values is done at send-time, or upon running sc.Check.
func NewServiceCheck(name string, status ServiceCheckStatus) *ServiceCheck {
return &ServiceCheck{
Name: name,
Status: status,
}
}
// Check verifies that an event is valid.
func (sc ServiceCheck) Check() error {
if len(sc.Name) == 0 {
return fmt.Errorf("statsd.ServiceCheck name is required")
}
if byte(sc.Status) < 0 || byte(sc.Status) > 3 {
return fmt.Errorf("statsd.ServiceCheck status has invalid value")
}
return nil
}
// Encode returns the dogstatsd wire protocol representation for an serviceCheck.
// Tags may be passed which will be added to the encoded output but not to
// the Event's list of tags, eg. for default tags.
func (sc ServiceCheck) Encode(tags ...string) (string, error) {
err := sc.Check()
if err != nil {
return "", err
}
message := sc.escapedMessage()
var buffer bytes.Buffer
buffer.WriteString("_sc|")
buffer.WriteString(sc.Name)
buffer.WriteRune('|')
buffer.WriteString(strconv.FormatInt(int64(sc.Status), 10))
if !sc.Timestamp.IsZero() {
buffer.WriteString("|d:")
buffer.WriteString(strconv.FormatInt(int64(sc.Timestamp.Unix()), 10))
}
if len(sc.Hostname) != 0 {
buffer.WriteString("|h:")
buffer.WriteString(sc.Hostname)
}
writeTagString(&buffer, tags, sc.Tags)
if len(message) != 0 {
buffer.WriteString("|m:")
buffer.WriteString(message)
}
return buffer.String(), nil
}
func (e Event) escapedText() string {
return strings.Replace(e.Text, "\n", "\\n", -1)
}
func (sc ServiceCheck) escapedMessage() string {
msg := strings.Replace(sc.Message, "\n", "\\n", -1)
return strings.Replace(msg, "m:", `m\:`, -1)
}
func removeNewlines(str string) string {
return strings.Replace(str, "\n", "", -1)
}
func writeTagString(w io.Writer, tagList1, tagList2 []string) {
// the tag lists may be shared with other callers, so we cannot modify
// them in any way (which means we cannot append to them either)
// therefore we must make an entirely separate copy just for this call
totalLen := len(tagList1) + len(tagList2)
if totalLen == 0 {
return
}
tags := make([]string, 0, totalLen)
tags = append(tags, tagList1...)
tags = append(tags, tagList2...)
io.WriteString(w, "|#")
io.WriteString(w, removeNewlines(tags[0]))
for _, tag := range tags[1:] {
io.WriteString(w, ",")
io.WriteString(w, removeNewlines(tag))
}
}

View File

@@ -0,0 +1,620 @@
// Copyright 2013 Ooyala, Inc.
package statsd
import (
"fmt"
"io"
"net"
"reflect"
"strings"
"testing"
"time"
)
var dogstatsdTests = []struct {
GlobalNamespace string
GlobalTags []string
Method string
Metric string
Value interface{}
Tags []string
Rate float64
Expected string
}{
{"", nil, "Gauge", "test.gauge", 1.0, nil, 1.0, "test.gauge:1.000000|g"},
{"", nil, "Gauge", "test.gauge", 1.0, nil, 0.999999, "test.gauge:1.000000|g|@0.999999"},
{"", nil, "Gauge", "test.gauge", 1.0, []string{"tagA"}, 1.0, "test.gauge:1.000000|g|#tagA"},
{"", nil, "Gauge", "test.gauge", 1.0, []string{"tagA", "tagB"}, 1.0, "test.gauge:1.000000|g|#tagA,tagB"},
{"", nil, "Gauge", "test.gauge", 1.0, []string{"tagA"}, 0.999999, "test.gauge:1.000000|g|@0.999999|#tagA"},
{"", nil, "Count", "test.count", int64(1), []string{"tagA"}, 1.0, "test.count:1|c|#tagA"},
{"", nil, "Count", "test.count", int64(-1), []string{"tagA"}, 1.0, "test.count:-1|c|#tagA"},
{"", nil, "Histogram", "test.histogram", 2.3, []string{"tagA"}, 1.0, "test.histogram:2.300000|h|#tagA"},
{"", nil, "Set", "test.set", "uuid", []string{"tagA"}, 1.0, "test.set:uuid|s|#tagA"},
{"flubber.", nil, "Set", "test.set", "uuid", []string{"tagA"}, 1.0, "flubber.test.set:uuid|s|#tagA"},
{"", []string{"tagC"}, "Set", "test.set", "uuid", []string{"tagA"}, 1.0, "test.set:uuid|s|#tagC,tagA"},
{"", nil, "Count", "test.count", int64(1), []string{"hello\nworld"}, 1.0, "test.count:1|c|#helloworld"},
}
func assertNotPanics(t *testing.T, f func()) {
defer func() {
if r := recover(); r != nil {
t.Fatal(r)
}
}()
f()
}
func TestClient(t *testing.T) {
addr := "localhost:1201"
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
t.Fatal(err)
}
server, err := net.ListenUDP("udp", udpAddr)
if err != nil {
t.Fatal(err)
}
defer server.Close()
client, err := New(addr)
if err != nil {
t.Fatal(err)
}
for _, tt := range dogstatsdTests {
client.Namespace = tt.GlobalNamespace
client.Tags = tt.GlobalTags
method := reflect.ValueOf(client).MethodByName(tt.Method)
e := method.Call([]reflect.Value{
reflect.ValueOf(tt.Metric),
reflect.ValueOf(tt.Value),
reflect.ValueOf(tt.Tags),
reflect.ValueOf(tt.Rate)})[0]
errInter := e.Interface()
if errInter != nil {
t.Fatal(errInter.(error))
}
bytes := make([]byte, 1024)
n, err := server.Read(bytes)
if err != nil {
t.Fatal(err)
}
message := bytes[:n]
if string(message) != tt.Expected {
t.Errorf("Expected: %s. Actual: %s", tt.Expected, string(message))
}
}
}
func TestBufferedClient(t *testing.T) {
addr := "localhost:1201"
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
t.Fatal(err)
}
server, err := net.ListenUDP("udp", udpAddr)
if err != nil {
t.Fatal(err)
}
defer server.Close()
conn, err := net.DialUDP("udp", nil, udpAddr)
if err != nil {
t.Fatal(err)
}
bufferLength := 8
client := &Client{
conn: conn,
commands: make([]string, 0, bufferLength),
bufferLength: bufferLength,
}
client.Namespace = "foo."
client.Tags = []string{"dd:2"}
dur, _ := time.ParseDuration("123us")
client.Incr("ic", nil, 1)
client.Decr("dc", nil, 1)
client.Count("cc", 1, nil, 1)
client.Gauge("gg", 10, nil, 1)
client.Histogram("hh", 1, nil, 1)
client.Timing("tt", dur, nil, 1)
client.Set("ss", "ss", nil, 1)
if len(client.commands) != 7 {
t.Errorf("Expected client to have buffered 7 commands, but found %d\n", len(client.commands))
}
client.Set("ss", "xx", nil, 1)
err = client.flush()
if err != nil {
t.Errorf("Error sending: %s", err)
}
if len(client.commands) != 0 {
t.Errorf("Expecting send to flush commands, but found %d\n", len(client.commands))
}
buffer := make([]byte, 4096)
n, err := io.ReadAtLeast(server, buffer, 1)
result := string(buffer[:n])
if err != nil {
t.Error(err)
}
expected := []string{
`foo.ic:1|c|#dd:2`,
`foo.dc:-1|c|#dd:2`,
`foo.cc:1|c|#dd:2`,
`foo.gg:10.000000|g|#dd:2`,
`foo.hh:1.000000|h|#dd:2`,
`foo.tt:0.123000|ms|#dd:2`,
`foo.ss:ss|s|#dd:2`,
`foo.ss:xx|s|#dd:2`,
}
for i, res := range strings.Split(result, "\n") {
if res != expected[i] {
t.Errorf("Got `%s`, expected `%s`", res, expected[i])
}
}
client.Event(&Event{Title: "title1", Text: "text1", Priority: Normal, AlertType: Success, Tags: []string{"tagg"}})
client.SimpleEvent("event1", "text1")
if len(client.commands) != 2 {
t.Errorf("Expected to find %d commands, but found %d\n", 2, len(client.commands))
}
err = client.flush()
if err != nil {
t.Errorf("Error sending: %s", err)
}
if len(client.commands) != 0 {
t.Errorf("Expecting send to flush commands, but found %d\n", len(client.commands))
}
buffer = make([]byte, 1024)
n, err = io.ReadAtLeast(server, buffer, 1)
result = string(buffer[:n])
if err != nil {
t.Error(err)
}
if n == 0 {
t.Errorf("Read 0 bytes but expected more.")
}
expected = []string{
`_e{6,5}:title1|text1|p:normal|t:success|#dd:2,tagg`,
`_e{6,5}:event1|text1|#dd:2`,
}
for i, res := range strings.Split(result, "\n") {
if res != expected[i] {
t.Errorf("Got `%s`, expected `%s`", res, expected[i])
}
}
}
func TestJoinMaxSize(t *testing.T) {
c := Client{}
elements := []string{"abc", "abcd", "ab", "xyz", "foobaz", "x", "wwxxyyzz"}
res, n := c.joinMaxSize(elements, " ", 8)
if len(res) != len(n) && len(res) != 4 {
t.Errorf("Was expecting 4 frames to flush but got: %v - %v", n, res)
}
if n[0] != 2 {
t.Errorf("Was expecting 2 elements in first frame but got: %v", n[0])
}
if string(res[0]) != "abc abcd" {
t.Errorf("Join should have returned \"abc abcd\" in frame, but found: %s", res[0])
}
if n[1] != 2 {
t.Errorf("Was expecting 2 elements in second frame but got: %v - %v", n[1], n)
}
if string(res[1]) != "ab xyz" {
t.Errorf("Join should have returned \"ab xyz\" in frame, but found: %s", res[1])
}
if n[2] != 2 {
t.Errorf("Was expecting 2 elements in third frame but got: %v - %v", n[2], n)
}
if string(res[2]) != "foobaz x" {
t.Errorf("Join should have returned \"foobaz x\" in frame, but found: %s", res[2])
}
if n[3] != 1 {
t.Errorf("Was expecting 1 element in fourth frame but got: %v - %v", n[3], n)
}
if string(res[3]) != "wwxxyyzz" {
t.Errorf("Join should have returned \"wwxxyyzz\" in frame, but found: %s", res[3])
}
res, n = c.joinMaxSize(elements, " ", 11)
if len(res) != len(n) && len(res) != 3 {
t.Errorf("Was expecting 3 frames to flush but got: %v - %v", n, res)
}
if n[0] != 3 {
t.Errorf("Was expecting 3 elements in first frame but got: %v", n[0])
}
if string(res[0]) != "abc abcd ab" {
t.Errorf("Join should have returned \"abc abcd ab\" in frame, but got: %s", res[0])
}
if n[1] != 2 {
t.Errorf("Was expecting 2 elements in second frame but got: %v", n[1])
}
if string(res[1]) != "xyz foobaz" {
t.Errorf("Join should have returned \"xyz foobaz\" in frame, but got: %s", res[1])
}
if n[2] != 2 {
t.Errorf("Was expecting 2 elements in third frame but got: %v", n[2])
}
if string(res[2]) != "x wwxxyyzz" {
t.Errorf("Join should have returned \"x wwxxyyzz\" in frame, but got: %s", res[2])
}
res, n = c.joinMaxSize(elements, " ", 8)
if len(res) != len(n) && len(res) != 7 {
t.Errorf("Was expecting 7 frames to flush but got: %v - %v", n, res)
}
if n[0] != 1 {
t.Errorf("Separator is long, expected a single element in frame but got: %d - %v", n[0], res)
}
if string(res[0]) != "abc" {
t.Errorf("Join should have returned \"abc\" in first frame, but got: %s", res)
}
if n[1] != 1 {
t.Errorf("Separator is long, expected a single element in frame but got: %d - %v", n[1], res)
}
if string(res[1]) != "abcd" {
t.Errorf("Join should have returned \"abcd\" in second frame, but got: %s", res[1])
}
if n[2] != 1 {
t.Errorf("Separator is long, expected a single element in third frame but got: %d - %v", n[2], res)
}
if string(res[2]) != "ab" {
t.Errorf("Join should have returned \"ab\" in third frame, but got: %s", res[2])
}
if n[3] != 1 {
t.Errorf("Separator is long, expected a single element in fourth frame but got: %d - %v", n[3], res)
}
if string(res[3]) != "xyz" {
t.Errorf("Join should have returned \"xyz\" in fourth frame, but got: %s", res[3])
}
if n[4] != 1 {
t.Errorf("Separator is long, expected a single element in fifth frame but got: %d - %v", n[4], res)
}
if string(res[4]) != "foobaz" {
t.Errorf("Join should have returned \"foobaz\" in fifth frame, but got: %s", res[4])
}
if n[5] != 1 {
t.Errorf("Separator is long, expected a single element in sixth frame but got: %d - %v", n[5], res)
}
if string(res[5]) != "x" {
t.Errorf("Join should have returned \"x\" in sixth frame, but got: %s", res[5])
}
if n[6] != 1 {
t.Errorf("Separator is long, expected a single element in seventh frame but got: %d - %v", n[6], res)
}
if string(res[6]) != "wwxxyyzz" {
t.Errorf("Join should have returned \"wwxxyyzz\" in seventh frame, but got: %s", res[6])
}
res, n = c.joinMaxSize(elements[4:], " ", 6)
if len(res) != len(n) && len(res) != 3 {
t.Errorf("Was expecting 3 frames to flush but got: %v - %v", n, res)
}
if n[0] != 1 {
t.Errorf("Element should just fit in frame - expected single element in frame: %d - %v", n[0], res)
}
if string(res[0]) != "foobaz" {
t.Errorf("Join should have returned \"foobaz\" in first frame, but got: %s", res[0])
}
if n[1] != 1 {
t.Errorf("Single element expected in frame, but got. %d - %v", n[1], res)
}
if string(res[1]) != "x" {
t.Errorf("Join should' have returned \"x\" in second frame, but got: %s", res[1])
}
if n[2] != 1 {
t.Errorf("Even though element is greater then max size we still try to send it. %d - %v", n[2], res)
}
if string(res[2]) != "wwxxyyzz" {
t.Errorf("Join should have returned \"wwxxyyzz\" in third frame, but got: %s", res[2])
}
}
func TestSendMsg(t *testing.T) {
addr := "localhost:1201"
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
t.Fatal(err)
}
server, err := net.ListenUDP("udp", udpAddr)
if err != nil {
t.Fatal(err)
}
defer server.Close()
conn, err := net.DialUDP("udp", nil, udpAddr)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
client := &Client{
conn: conn,
bufferLength: 0,
}
err = client.sendMsg(strings.Repeat("x", MaxUDPPayloadSize+1))
if err == nil {
t.Error("Expected error to be returned if message size is bigger than MaxUDPPayloadSize")
}
longMsg := strings.Repeat("x", MaxUDPPayloadSize)
err = client.sendMsg(longMsg)
if err != nil {
t.Errorf("Expected no error to be returned if message size is smaller or equal to MaxUDPPayloadSize, got: %s", err.Error())
}
buffer := make([]byte, MaxUDPPayloadSize+1)
n, err := io.ReadAtLeast(server, buffer, 1)
if err != nil {
t.Fatalf("Expected no error to be returned reading the buffer, got: %s", err.Error())
}
if n != MaxUDPPayloadSize {
t.Fatalf("Failed to read full message from buffer. Got size `%d` expected `%d`", n, MaxUDPPayloadSize)
}
if string(buffer[:n]) != longMsg {
t.Fatalf("The received message did not match what we expect.")
}
client = &Client{
conn: conn,
commands: make([]string, 0, 1),
bufferLength: 1,
}
err = client.sendMsg(strings.Repeat("x", MaxUDPPayloadSize+1))
if err == nil {
t.Error("Expected error to be returned if message size is bigger than MaxUDPPayloadSize")
}
err = client.sendMsg(longMsg)
if err != nil {
t.Errorf("Expected no error to be returned if message size is smaller or equal to MaxUDPPayloadSize, got: %s", err.Error())
}
client.Lock()
err = client.flush()
client.Unlock()
if err != nil {
t.Fatalf("Expected no error to be returned flushing the client, got: %s", err.Error())
}
buffer = make([]byte, MaxUDPPayloadSize+1)
n, err = io.ReadAtLeast(server, buffer, 1)
if err != nil {
t.Fatalf("Expected no error to be returned reading the buffer, got: %s", err.Error())
}
if n != MaxUDPPayloadSize {
t.Fatalf("Failed to read full message from buffer. Got size `%d` expected `%d`", n, MaxUDPPayloadSize)
}
if string(buffer[:n]) != longMsg {
t.Fatalf("The received message did not match what we expect.")
}
}
func TestNilSafe(t *testing.T) {
var c *Client
assertNotPanics(t, func() { c.Close() })
assertNotPanics(t, func() { c.Count("", 0, nil, 1) })
assertNotPanics(t, func() { c.Histogram("", 0, nil, 1) })
assertNotPanics(t, func() { c.Gauge("", 0, nil, 1) })
assertNotPanics(t, func() { c.Set("", "", nil, 1) })
assertNotPanics(t, func() { c.send("", "", nil, 1) })
assertNotPanics(t, func() { c.SimpleEvent("", "") })
}
func TestEvents(t *testing.T) {
matrix := []struct {
event *Event
encoded string
}{
{
NewEvent("Hello", "Something happened to my event"),
`_e{5,30}:Hello|Something happened to my event`,
}, {
&Event{Title: "hi", Text: "okay", AggregationKey: "foo"},
`_e{2,4}:hi|okay|k:foo`,
}, {
&Event{Title: "hi", Text: "okay", AggregationKey: "foo", AlertType: Info},
`_e{2,4}:hi|okay|k:foo|t:info`,
}, {
&Event{Title: "hi", Text: "w/e", AlertType: Error, Priority: Normal},
`_e{2,3}:hi|w/e|p:normal|t:error`,
}, {
&Event{Title: "hi", Text: "uh", Tags: []string{"host:foo", "app:bar"}},
`_e{2,2}:hi|uh|#host:foo,app:bar`,
}, {
&Event{Title: "hi", Text: "line1\nline2", Tags: []string{"hello\nworld"}},
`_e{2,12}:hi|line1\nline2|#helloworld`,
},
}
for _, m := range matrix {
r, err := m.event.Encode()
if err != nil {
t.Errorf("Error encoding: %s\n", err)
continue
}
if r != m.encoded {
t.Errorf("Expected `%s`, got `%s`\n", m.encoded, r)
}
}
e := NewEvent("", "hi")
if _, err := e.Encode(); err == nil {
t.Errorf("Expected error on empty Title.")
}
e = NewEvent("hi", "")
if _, err := e.Encode(); err == nil {
t.Errorf("Expected error on empty Text.")
}
e = NewEvent("hello", "world")
s, err := e.Encode("tag1", "tag2")
if err != nil {
t.Error(err)
}
expected := "_e{5,5}:hello|world|#tag1,tag2"
if s != expected {
t.Errorf("Expected %s, got %s", expected, s)
}
if len(e.Tags) != 0 {
t.Errorf("Modified event in place illegally.")
}
}
func TestServiceChecks(t *testing.T) {
matrix := []struct {
serviceCheck *ServiceCheck
encoded string
}{
{
NewServiceCheck("DataCatService", Ok),
`_sc|DataCatService|0`,
}, {
NewServiceCheck("DataCatService", Warn),
`_sc|DataCatService|1`,
}, {
NewServiceCheck("DataCatService", Critical),
`_sc|DataCatService|2`,
}, {
NewServiceCheck("DataCatService", Unknown),
`_sc|DataCatService|3`,
}, {
&ServiceCheck{Name: "DataCatService", Status: Ok, Hostname: "DataStation.Cat"},
`_sc|DataCatService|0|h:DataStation.Cat`,
}, {
&ServiceCheck{Name: "DataCatService", Status: Ok, Hostname: "DataStation.Cat", Message: "Here goes valuable message"},
`_sc|DataCatService|0|h:DataStation.Cat|m:Here goes valuable message`,
}, {
&ServiceCheck{Name: "DataCatService", Status: Ok, Hostname: "DataStation.Cat", Message: "Here are some cyrillic chars: к л м н о п р с т у ф х ц ч ш"},
`_sc|DataCatService|0|h:DataStation.Cat|m:Here are some cyrillic chars: к л м н о п р с т у ф х ц ч ш`,
}, {
&ServiceCheck{Name: "DataCatService", Status: Ok, Hostname: "DataStation.Cat", Message: "Here goes valuable message", Tags: []string{"host:foo", "app:bar"}},
`_sc|DataCatService|0|h:DataStation.Cat|#host:foo,app:bar|m:Here goes valuable message`,
}, {
&ServiceCheck{Name: "DataCatService", Status: Ok, Hostname: "DataStation.Cat", Message: "Here goes \n that should be escaped", Tags: []string{"host:foo", "app:b\nar"}},
`_sc|DataCatService|0|h:DataStation.Cat|#host:foo,app:bar|m:Here goes \n that should be escaped`,
}, {
&ServiceCheck{Name: "DataCatService", Status: Ok, Hostname: "DataStation.Cat", Message: "Here goes m: that should be escaped", Tags: []string{"host:foo", "app:bar"}},
`_sc|DataCatService|0|h:DataStation.Cat|#host:foo,app:bar|m:Here goes m\: that should be escaped`,
},
}
for _, m := range matrix {
r, err := m.serviceCheck.Encode()
if err != nil {
t.Errorf("Error encoding: %s\n", err)
continue
}
if r != m.encoded {
t.Errorf("Expected `%s`, got `%s`\n", m.encoded, r)
}
}
sc := NewServiceCheck("", Ok)
if _, err := sc.Encode(); err == nil {
t.Errorf("Expected error on empty Name.")
}
sc = NewServiceCheck("sc", ServiceCheckStatus(5))
if _, err := sc.Encode(); err == nil {
t.Errorf("Expected error on invalid status value.")
}
sc = NewServiceCheck("hello", Warn)
s, err := sc.Encode("tag1", "tag2")
if err != nil {
t.Error(err)
}
expected := "_sc|hello|1|#tag1,tag2"
if s != expected {
t.Errorf("Expected %s, got %s", expected, s)
}
if len(sc.Tags) != 0 {
t.Errorf("Modified serviceCheck in place illegally.")
}
}
// These benchmarks show that using a buffer instead of sprintf-ing together
// a bunch of intermediate strings is 4-5x faster
func BenchmarkFormatNew(b *testing.B) {
b.StopTimer()
c := &Client{}
c.Namespace = "foo.bar."
c.Tags = []string{"app:foo", "host:bar"}
b.StartTimer()
for i := 0; i < b.N; i++ {
c.format("system.cpu.idle", "10", []string{"foo"}, 1)
c.format("system.cpu.load", "0.1", nil, 0.9)
}
}
// Old formatting function, added to client for tests
func (c *Client) formatOld(name, value string, tags []string, rate float64) string {
if rate < 1 {
value = fmt.Sprintf("%s|@%f", value, rate)
}
if c.Namespace != "" {
name = fmt.Sprintf("%s%s", c.Namespace, name)
}
tags = append(c.Tags, tags...)
if len(tags) > 0 {
value = fmt.Sprintf("%s|#%s", value, strings.Join(tags, ","))
}
return fmt.Sprintf("%s:%s", name, value)
}
func BenchmarkFormatOld(b *testing.B) {
b.StopTimer()
c := &Client{}
c.Namespace = "foo.bar."
c.Tags = []string{"app:foo", "host:bar"}
b.StartTimer()
for i := 0; i < b.N; i++ {
c.formatOld("system.cpu.idle", "10", []string{"foo"}, 1)
c.formatOld("system.cpu.load", "0.1", nil, 0.9)
}
}