package metric import ( "fmt" "math" "regexp" "testing" "time" "github.com/influxdata/telegraf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewMetric(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", "datacenter": "us-east-1", } fields := map[string]interface{}{ "usage_idle": float64(99), "usage_busy": float64(1), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.Equal(t, telegraf.Untyped, m.Type()) assert.Equal(t, tags, m.Tags()) assert.Equal(t, fields, m.Fields()) assert.Equal(t, "cpu", m.Name()) assert.Equal(t, now, m.Time()) assert.Equal(t, now.UnixNano(), m.UnixNano()) } func TestNewErrors(t *testing.T) { // creating a metric with an empty name produces an error: m, err := New( "", map[string]string{ "datacenter": "us-east-1", "mytag": "foo", "another": "tag", }, map[string]interface{}{ "value": float64(1), }, time.Now(), ) assert.Error(t, err) assert.Nil(t, m) // creating a metric with empty fields produces an error: m, err = New( "foobar", map[string]string{ "datacenter": "us-east-1", "mytag": "foo", "another": "tag", }, map[string]interface{}{}, time.Now(), ) assert.Error(t, err) assert.Nil(t, m) } func TestNewMetric_Tags(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", "datacenter": "us-east-1", } fields := map[string]interface{}{ "value": float64(1), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.True(t, m.HasTag("host")) assert.True(t, m.HasTag("datacenter")) m.AddTag("newtag", "foo") assert.True(t, m.HasTag("newtag")) m.RemoveTag("host") assert.False(t, m.HasTag("host")) assert.True(t, m.HasTag("newtag")) assert.True(t, m.HasTag("datacenter")) m.RemoveTag("datacenter") assert.False(t, m.HasTag("datacenter")) assert.True(t, m.HasTag("newtag")) assert.Equal(t, map[string]string{"newtag": "foo"}, m.Tags()) m.RemoveTag("newtag") assert.False(t, m.HasTag("newtag")) assert.Equal(t, map[string]string{}, m.Tags()) assert.Equal(t, "cpu value=1 "+fmt.Sprint(now.UnixNano())+"\n", m.String()) } func TestSerialize(t *testing.T) { now := time.Now() tags := map[string]string{ "datacenter": "us-east-1", } fields := map[string]interface{}{ "value": float64(1), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.Equal(t, []byte("cpu,datacenter=us-east-1 value=1 "+fmt.Sprint(now.UnixNano())+"\n"), m.Serialize()) m.RemoveTag("datacenter") assert.Equal(t, []byte("cpu value=1 "+fmt.Sprint(now.UnixNano())+"\n"), m.Serialize()) } func TestHashID(t *testing.T) { m, _ := New( "cpu", map[string]string{ "datacenter": "us-east-1", "mytag": "foo", "another": "tag", }, map[string]interface{}{ "value": float64(1), }, time.Now(), ) hash := m.HashID() // adding a field doesn't change the hash: m.AddField("foo", int64(100)) assert.Equal(t, hash, m.HashID()) // removing a non-existent tag doesn't change the hash: m.RemoveTag("no-op") assert.Equal(t, hash, m.HashID()) // adding a tag does change it: m.AddTag("foo", "bar") assert.NotEqual(t, hash, m.HashID()) hash = m.HashID() // removing a tag also changes it: m.RemoveTag("mytag") assert.NotEqual(t, hash, m.HashID()) } func TestHashID_Consistency(t *testing.T) { m, _ := New( "cpu", map[string]string{ "datacenter": "us-east-1", "mytag": "foo", "another": "tag", }, map[string]interface{}{ "value": float64(1), }, time.Now(), ) hash := m.HashID() for i := 0; i < 1000; i++ { m2, _ := New( "cpu", map[string]string{ "datacenter": "us-east-1", "mytag": "foo", "another": "tag", }, map[string]interface{}{ "value": float64(1), }, time.Now(), ) assert.Equal(t, hash, m2.HashID()) } } func TestNewMetric_NameModifiers(t *testing.T) { now := time.Now() tags := map[string]string{} fields := map[string]interface{}{ "value": float64(1), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) hash := m.HashID() suffix := fmt.Sprintf(" value=1 %d\n", now.UnixNano()) assert.Equal(t, "cpu"+suffix, m.String()) m.SetPrefix("pre_") assert.NotEqual(t, hash, m.HashID()) hash = m.HashID() assert.Equal(t, "pre_cpu"+suffix, m.String()) m.SetSuffix("_post") assert.NotEqual(t, hash, m.HashID()) hash = m.HashID() assert.Equal(t, "pre_cpu_post"+suffix, m.String()) m.SetName("mem") assert.NotEqual(t, hash, m.HashID()) assert.Equal(t, "mem"+suffix, m.String()) } func TestNewMetric_FieldModifiers(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "value": float64(1), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.True(t, m.HasField("value")) assert.False(t, m.HasField("foo")) m.AddField("newfield", "foo") assert.True(t, m.HasField("newfield")) assert.NoError(t, m.RemoveField("newfield")) assert.False(t, m.HasField("newfield")) // don't allow user to remove all fields: assert.Error(t, m.RemoveField("value")) m.AddField("value2", int64(101)) assert.NoError(t, m.RemoveField("value")) assert.False(t, m.HasField("value")) } func TestNewMetric_Fields(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(1), "int": int64(1), "bool": true, "false": false, "string": "test", "quote_string": `x"y`, "backslash_quote_string": `x\"y`, "backslash": `x\y`, } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.Equal(t, fields, m.Fields()) } func TestNewMetric_Time(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(1), "int": int64(1), "bool": true, "false": false, "string": "test", } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) m = m.Copy() m2 := m.Copy() assert.Equal(t, now.UnixNano(), m.Time().UnixNano()) assert.Equal(t, now.UnixNano(), m2.UnixNano()) } func TestNewMetric_Copy(t *testing.T) { now := time.Now() tags := map[string]string{} fields := map[string]interface{}{ "float": float64(1), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) m2 := m.Copy() assert.Equal(t, fmt.Sprintf("cpu float=1 %d\n", now.UnixNano()), m.String()) m.AddTag("host", "localhost") assert.Equal(t, fmt.Sprintf("cpu,host=localhost float=1 %d\n", now.UnixNano()), m.String()) assert.Equal(t, fmt.Sprintf("cpu float=1 %d\n", now.UnixNano()), m2.String()) } func TestNewMetric_AllTypes(t *testing.T) { now := time.Now() tags := map[string]string{} fields := map[string]interface{}{ "float64": float64(1), "float32": float32(1), "int64": int64(1), "int32": int32(1), "int16": int16(1), "int8": int8(1), "int": int(1), "uint64": uint64(1), "uint32": uint32(1), "uint16": uint16(1), "uint8": uint8(1), "uint": uint(1), "bytes": []byte("foo"), "nil": nil, "maxuint64": uint64(MaxInt) + 10, "maxuint": uint(MaxInt) + 10, "unsupported": []int{1, 2}, } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.Contains(t, m.String(), "float64=1") assert.Contains(t, m.String(), "float32=1") assert.Contains(t, m.String(), "int64=1i") assert.Contains(t, m.String(), "int32=1i") assert.Contains(t, m.String(), "int16=1i") assert.Contains(t, m.String(), "int8=1i") assert.Contains(t, m.String(), "int=1i") assert.Contains(t, m.String(), "uint64=1i") assert.Contains(t, m.String(), "uint32=1i") assert.Contains(t, m.String(), "uint16=1i") assert.Contains(t, m.String(), "uint8=1i") assert.Contains(t, m.String(), "uint=1i") assert.NotContains(t, m.String(), "nil") assert.Contains(t, m.String(), fmt.Sprintf("maxuint64=%di", MaxInt)) assert.Contains(t, m.String(), fmt.Sprintf("maxuint=%di", MaxInt)) } func TestIndexUnescapedByte(t *testing.T) { tests := []struct { in []byte b byte expected int }{ { in: []byte(`foobar`), b: 'b', expected: 3, }, { in: []byte(`foo\bar`), b: 'b', expected: -1, }, { in: []byte(`foo\\bar`), b: 'b', expected: -1, }, { in: []byte(`foobar`), b: 'f', expected: 0, }, { in: []byte(`foobar`), b: 'r', expected: 5, }, { in: []byte(`\foobar`), b: 'f', expected: -1, }, } for _, test := range tests { got := indexUnescapedByte(test.in, test.b) assert.Equal(t, test.expected, got) } } func TestNewGaugeMetric(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", "datacenter": "us-east-1", } fields := map[string]interface{}{ "usage_idle": float64(99), "usage_busy": float64(1), } m, err := New("cpu", tags, fields, now, telegraf.Gauge) assert.NoError(t, err) assert.Equal(t, telegraf.Gauge, m.Type()) assert.Equal(t, tags, m.Tags()) assert.Equal(t, fields, m.Fields()) assert.Equal(t, "cpu", m.Name()) assert.Equal(t, now, m.Time()) assert.Equal(t, now.UnixNano(), m.UnixNano()) } func TestNewCounterMetric(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", "datacenter": "us-east-1", } fields := map[string]interface{}{ "usage_idle": float64(99), "usage_busy": float64(1), } m, err := New("cpu", tags, fields, now, telegraf.Counter) assert.NoError(t, err) assert.Equal(t, telegraf.Counter, m.Type()) assert.Equal(t, tags, m.Tags()) assert.Equal(t, fields, m.Fields()) assert.Equal(t, "cpu", m.Name()) assert.Equal(t, now, m.Time()) assert.Equal(t, now.UnixNano(), m.UnixNano()) } // test splitting metric into various max lengths func TestSplitMetric(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(100001), "int": int64(100001), "bool": true, "false": false, "string": "test", } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) split80 := m.Split(80) assert.Len(t, split80, 2) split70 := m.Split(70) assert.Len(t, split70, 3) split60 := m.Split(60) assert.Len(t, split60, 5) } // test splitting metric into various max lengths // use a simple regex check to verify that the split metrics are valid func TestSplitMetric_RegexVerify(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "foo": float64(98934259085), "bar": float64(19385292), "number": float64(19385292), "another": float64(19385292), "n": float64(19385292), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) // verification regex re := regexp.MustCompile(`cpu,host=localhost \w+=\d+(,\w+=\d+)* 1480940990034083306`) split90 := m.Split(90) assert.Len(t, split90, 2) for _, splitM := range split90 { assert.True(t, re.Match(splitM.Serialize()), splitM.String()) } split70 := m.Split(70) assert.Len(t, split70, 3) for _, splitM := range split70 { assert.True(t, re.Match(splitM.Serialize()), splitM.String()) } split20 := m.Split(20) assert.Len(t, split20, 5) for _, splitM := range split20 { assert.True(t, re.Match(splitM.Serialize()), splitM.String()) } } // test splitting metric even when given length is shorter than // shortest possible length // Split should split metric as short as possible, ie, 1 field per metric func TestSplitMetric_TooShort(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(100001), "int": int64(100001), "bool": true, "false": false, "string": "test", } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) split := m.Split(10) assert.Len(t, split, 5) strings := make([]string, 5) for i, splitM := range split { strings[i] = splitM.String() } assert.Contains(t, strings, "cpu,host=localhost float=100001 1480940990034083306\n") assert.Contains(t, strings, "cpu,host=localhost int=100001i 1480940990034083306\n") assert.Contains(t, strings, "cpu,host=localhost bool=true 1480940990034083306\n") assert.Contains(t, strings, "cpu,host=localhost false=false 1480940990034083306\n") assert.Contains(t, strings, "cpu,host=localhost string=\"test\" 1480940990034083306\n") } func TestSplitMetric_NoOp(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(100001), "int": int64(100001), "bool": true, "false": false, "string": "test", } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) split := m.Split(1000) assert.Len(t, split, 1) assert.Equal(t, m, split[0]) } func TestSplitMetric_OneField(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(100001), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", m.String()) split := m.Split(1000) assert.Len(t, split, 1) assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", split[0].String()) split = m.Split(1) assert.Len(t, split, 1) assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", split[0].String()) split = m.Split(40) assert.Len(t, split, 1) assert.Equal(t, "cpu,host=localhost float=100001 1480940990034083306\n", split[0].String()) } func TestSplitMetric_ExactSize(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(100001), "int": int64(100001), "bool": true, "false": false, "string": "test", } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) actual := m.Split(m.Len()) // check that no copy was made require.Equal(t, &m, &actual[0]) } func TestSplitMetric_NoRoomForNewline(t *testing.T) { now := time.Unix(0, 1480940990034083306) tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "float": float64(100001), "int": int64(100001), "bool": true, "false": false, } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) actual := m.Split(m.Len() - 1) require.Equal(t, 2, len(actual)) } func TestNewMetricAggregate(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "usage_idle": float64(99), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) assert.False(t, m.IsAggregate()) m.SetAggregate(true) assert.True(t, m.IsAggregate()) } func TestNewMetricString(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "usage_idle": float64(99), } m, err := New("cpu", tags, fields, now) assert.NoError(t, err) lineProto := fmt.Sprintf("cpu,host=localhost usage_idle=99 %d\n", now.UnixNano()) assert.Equal(t, lineProto, m.String()) } func TestNewMetricFailNaN(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", } fields := map[string]interface{}{ "usage_idle": math.NaN(), } _, err := New("cpu", tags, fields, now) assert.NoError(t, err) } func TestEmptyTagValueOrKey(t *testing.T) { now := time.Now() tags := map[string]string{ "host": "localhost", "emptytag": "", "": "valuewithoutkey", } fields := map[string]interface{}{ "usage_idle": float64(99), } m, err := New("cpu", tags, fields, now) assert.True(t, m.HasTag("host")) assert.False(t, m.HasTag("emptytag")) assert.Equal(t, fmt.Sprintf("cpu,host=localhost usage_idle=99 %d\n", now.UnixNano()), m.String()) assert.NoError(t, err) } func TestNewMetric_TrailingSlash(t *testing.T) { now := time.Now() tests := []struct { name string tags map[string]string fields map[string]interface{} }{ { name: `cpu\`, fields: map[string]interface{}{ "value": int64(42), }, }, { name: "cpu", fields: map[string]interface{}{ `value\`: "x", }, }, { name: "cpu", tags: map[string]string{ `host\`: "localhost", }, fields: map[string]interface{}{ "value": int64(42), }, }, { name: "cpu", tags: map[string]string{ "host": `localhost\`, }, fields: map[string]interface{}{ "value": int64(42), }, }, } for _, tc := range tests { _, err := New(tc.name, tc.tags, tc.fields, now) assert.Error(t, err) } }