hanayo/vendor/github.com/DataDog/datadog-go/statsd/statsd_test.go
2019-02-23 13:29:15 +00:00

621 lines
18 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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)
}
}