diff --git a/go/stats/opentsdb/backend_test.go b/go/stats/opentsdb/backend_test.go new file mode 100644 index 00000000000..c70b9ecb88b --- /dev/null +++ b/go/stats/opentsdb/backend_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentsdb + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "vitess.io/vitess/go/stats" +) + +type mockWriter struct { + data []*DataPoint +} + +func (mw *mockWriter) Write(data []*DataPoint) error { + mw.data = data + return nil +} + +func TestPushAll(t *testing.T) { + mw := &mockWriter{} + b := &backend{ + prefix: "testPrefix", + commonTags: map[string]string{"tag1": "value1"}, + writer: mw, + } + + err := b.PushAll() + assert.NoError(t, err) + before := len(mw.data) + + stats.NewGaugeFloat64("test_push_all1", "help") + stats.NewGaugeFloat64("test_push_all2", "help") + + err = b.PushAll() + assert.NoError(t, err) + after := len(mw.data) + + assert.Equalf(t, after-before, 2, "length of writer.data should have been increased by 2") +} + +func TestPushOne(t *testing.T) { + mw := &mockWriter{} + b := &backend{ + prefix: "testPrefix", + commonTags: map[string]string{"tag1": "value1"}, + writer: mw, + } + + s := stats.NewGaugeFloat64("test_push_one", "help") + err := b.PushOne("test_push_one", s) + assert.NoError(t, err) + + assert.Len(t, mw.data, 1) + assert.Equal(t, "testprefix.test_push_one", mw.data[0].Metric) +} diff --git a/go/stats/opentsdb/datapoint_reader_test.go b/go/stats/opentsdb/datapoint_reader_test.go new file mode 100644 index 00000000000..43f99541fa3 --- /dev/null +++ b/go/stats/opentsdb/datapoint_reader_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentsdb + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRead(t *testing.T) { + invalidInputs := []string{ + "testMetric 0.100000 1.100000 key1=val1 key2=val2", + "InvalidMarshalText\n", + } + + for _, in := range invalidInputs { + mockReader := bytes.NewBufferString(in) + dpr := NewDataPointReader(mockReader) + dp, err := dpr.Read() + + assert.Error(t, err) + assert.Nil(t, dp) + } + + mockReader := bytes.NewBufferString("testMetric 0.100000 1.100000 key1=val1 key2=val2\n") + dpr := NewDataPointReader(mockReader) + dp, err := dpr.Read() + + assert.NoError(t, err) + + expectedDataPoint := DataPoint{ + Metric: "testMetric", + Timestamp: 0.1, + Value: 1.1, + Tags: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + } + assert.Equal(t, expectedDataPoint, *dp) +} diff --git a/go/stats/opentsdb/datapoint_test.go b/go/stats/opentsdb/datapoint_test.go new file mode 100644 index 00000000000..4864c94745d --- /dev/null +++ b/go/stats/opentsdb/datapoint_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentsdb + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMarshalText(t *testing.T) { + dp := DataPoint{ + Metric: "testMetric", + Timestamp: 0.1, + Value: 1.1, + Tags: map[string]string{ + "key1": "val1", + }, + } + + str, err := dp.MarshalText() + assert.NoError(t, err) + assert.Equal(t, "testMetric 0.100000 1.100000 key1=val1\n", str) +} + +func TestUnmarshalTextToData(t *testing.T) { + dp := DataPoint{} + + invalidMarshalTestCases := []string{ + "InvalidMarshalText", + "testMetric invalidFloat invalidFloat", + "testMetric 0.100000 invalidFloat", + "testMetric 0.100000 1.100000 invalidKey:ValuePair", + } + + for _, text := range invalidMarshalTestCases { + err := unmarshalTextToData(&dp, []byte(text)) + assert.Error(t, err) + } + + err := unmarshalTextToData(&dp, []byte("testMetric 0.100000 1.100000 key1=val1 key2=val2")) + assert.NoError(t, err) + + expectedDataPoint := DataPoint{ + Metric: "testMetric", + Timestamp: 0.1, + Value: 1.1, + Tags: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + } + assert.Equal(t, expectedDataPoint, dp) +} diff --git a/go/stats/opentsdb/file_writer_test.go b/go/stats/opentsdb/file_writer_test.go new file mode 100644 index 00000000000..8b7b52fb637 --- /dev/null +++ b/go/stats/opentsdb/file_writer_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentsdb + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileWriter(t *testing.T) { + tempFile, err := os.CreateTemp("", "tempfile") + assert.NoError(t, err) + defer os.Remove(tempFile.Name()) + + w, err := newFileWriter(tempFile.Name()) + assert.NoError(t, err) + + dp := []*DataPoint{ + { + Metric: "testMetric", + Timestamp: 1.0, + Value: 2.0, + Tags: map[string]string{ + "key1": "value1", + }, + }, + } + + err = w.Write(dp) + assert.NoError(t, err) + + err = tempFile.Close() + assert.NoError(t, err) + + content, err := os.ReadFile(tempFile.Name()) + assert.NoError(t, err) + assert.Equal(t, "testMetric 1.000000 2.000000 key1=value1\n", string(content)) +} diff --git a/go/stats/opentsdb/flags_test.go b/go/stats/opentsdb/flags_test.go new file mode 100644 index 00000000000..ca9d63e37d9 --- /dev/null +++ b/go/stats/opentsdb/flags_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentsdb + +import ( + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +func TestRegisterFlags(t *testing.T) { + oldOpenTSDBURI := openTSDBURI + defer func() { + openTSDBURI = oldOpenTSDBURI + }() + + fs := pflag.NewFlagSet("test", pflag.ExitOnError) + + registerFlags(fs) + + err := fs.Set("opentsdb_uri", "testURI") + assert.NoError(t, err) + assert.Equal(t, "testURI", openTSDBURI) +} diff --git a/go/stats/opentsdb/http_writer_test.go b/go/stats/opentsdb/http_writer_test.go new file mode 100644 index 00000000000..faba7b000d6 --- /dev/null +++ b/go/stats/opentsdb/http_writer_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentsdb + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWrite(t *testing.T) { + sampleData := []*DataPoint{ + { + Metric: "testMetric", + Timestamp: 1.0, + Value: 2.0, + Tags: map[string]string{ + "tag1": "value1", + }, + }, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + var receivedData []*DataPoint + err := json.NewDecoder(r.Body).Decode(&receivedData) + + assert.NoError(t, err) + assert.Len(t, receivedData, 1) + assert.Equal(t, sampleData[0], receivedData[0]) + + w.WriteHeader(http.StatusOK) + })) + + defer server.Close() + + client := &http.Client{} + hw := newHTTPWriter(client, server.URL) + + err := hw.Write(sampleData) + assert.NoError(t, err) +} diff --git a/go/stats/opentsdb/opentsdb_test.go b/go/stats/opentsdb/opentsdb_test.go index 940ee845ada..78db2616841 100644 --- a/go/stats/opentsdb/opentsdb_test.go +++ b/go/stats/opentsdb/opentsdb_test.go @@ -19,14 +19,36 @@ package opentsdb import ( "encoding/json" "expvar" - "reflect" "sort" "testing" "time" + "github.com/stretchr/testify/assert" + "vitess.io/vitess/go/stats" ) +func TestFloatFunc(t *testing.T) { + name := "float_func_name" + f := stats.FloatFunc(func() float64 { + return 1.2 + }) + + stats.Publish(name, f) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.float_func_name", + "timestamp": 1234, + "value": 1.2, + "tags": { + "host": "localhost" + } + } + ]`) +} + func TestOpenTsdbCounter(t *testing.T) { name := "counter_name" c := stats.NewCounter(name, "counter description") @@ -83,6 +105,405 @@ func TestGaugesWithMultiLabels(t *testing.T) { ]`) } +func TestGaugesFuncWithMultiLabels(t *testing.T) { + name := "gauges_func_with_multi_labels_name" + stats.NewGaugesFuncWithMultiLabels(name, "help", []string{"flavor", "texture"}, func() map[string]int64 { + m := make(map[string]int64) + m["foo.bar"] = 1 + m["bar.baz"] = 2 + return m + }) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.gauges_func_with_multi_labels_name", + "timestamp": 1234, + "value": 2, + "tags": { + "flavor": "bar", + "host": "localhost", + "texture": "baz" + } + }, + { + "metric": "vtgate.gauges_func_with_multi_labels_name", + "timestamp": 1234, + "value": 1, + "tags": { + "flavor": "foo", + "host": "localhost", + "texture": "bar" + } + } + ]`) +} + +func TestGaugesWithSingleLabel(t *testing.T) { + name := "gauges_with_single_label_name" + s := stats.NewGaugesWithSingleLabel(name, "help", "label1") + s.Add("bar", 1) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.gauges_with_single_label_name", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost", + "label1": "bar" + } + } + ]`) +} + +func TestCountersWithSingleLabel(t *testing.T) { + name := "counter_with_single_label_name" + s := stats.NewCountersWithSingleLabel(name, "help", "label", "tag1", "tag2") + s.Add("tag1", 2) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.counter_with_single_label_name", + "timestamp": 1234, + "value": 2, + "tags": { + "host": "localhost", + "label": "tag1" + } + }, + { + "metric": "vtgate.counter_with_single_label_name", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label": "tag2" + } + } + ]`) +} + +func TestCountersWithMultiLabels(t *testing.T) { + name := "counter_with_multiple_label_name" + s := stats.NewCountersWithMultiLabels(name, "help", []string{"label1", "label2"}) + s.Add([]string{"foo", "bar"}, 1) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.counter_with_multiple_label_name", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + } + ]`) +} + +func TestCountersFuncWithMultiLabels(t *testing.T) { + name := "counter_func_with_multiple_labels_name" + stats.NewCountersFuncWithMultiLabels(name, "help", []string{"label1", "label2"}, func() map[string]int64 { + m := make(map[string]int64) + m["foo.bar"] = 1 + m["bar.baz"] = 2 + return m + }) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.counter_func_with_multiple_labels_name", + "timestamp": 1234, + "value": 2, + "tags": { + "host": "localhost", + "label1": "bar", + "label2": "baz" + } + }, + { + "metric": "vtgate.counter_func_with_multiple_labels_name", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + } + ]`) +} + +func TestGaugeFloat64(t *testing.T) { + name := "gauge_float64_name" + s := stats.NewGaugeFloat64(name, "help") + s.Set(3.14) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.gauge_float64_name", + "timestamp": 1234, + "value": 3.14, + "tags": { + "host": "localhost" + } + } + ]`) +} + +func TestGaugeFunc(t *testing.T) { + name := "gauge_func_name" + stats.NewGaugeFunc(name, "help", func() int64 { + return 2 + }) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.gauge_func_name", + "timestamp": 1234, + "value": 2, + "tags": { + "host": "localhost" + } + } + ]`) +} + +func TestCounterDuration(t *testing.T) { + name := "counter_duration_name" + s := stats.NewCounterDuration(name, "help") + s.Add(1 * time.Millisecond) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.counter_duration_name", + "timestamp": 1234, + "value": 1000000, + "tags": { + "host": "localhost" + } + } + ]`) +} + +func TestCounterDurationFunc(t *testing.T) { + name := "counter_duration_func_name" + stats.NewCounterDurationFunc(name, "help", func() time.Duration { + return 1 * time.Millisecond + }) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.counter_duration_func_name", + "timestamp": 1234, + "value": 1000000, + "tags": { + "host": "localhost" + } + } + ]`) +} + +func TestMultiTimings(t *testing.T) { + name := "multi_timings_name" + s := stats.NewMultiTimings(name, "help", []string{"label1", "label2"}) + s.Add([]string{"foo", "bar"}, 1) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.multi_timings_name.1000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.10000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.100000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.1000000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.10000000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.500000", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.5000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.50000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.500000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.5000000000", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.count", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.inf", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + }, + { + "metric": "vtgate.multi_timings_name.time", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost", + "label1": "foo", + "label2": "bar" + } + } + ]`) +} + +func TestHistogram(t *testing.T) { + name := "histogram_name" + s := stats.NewHistogram(name, "help", []int64{1, 2}) + s.Add(2) + + checkOutput(t, name, ` + [ + { + "metric": "vtgate.histogram_name.1", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost" + } + }, + { + "metric": "vtgate.histogram_name.2", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost" + } + }, + { + "metric": "vtgate.histogram_name.count", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "localhost" + } + }, + { + "metric": "vtgate.histogram_name.inf", + "timestamp": 1234, + "value": 0, + "tags": { + "host": "localhost" + } + }, + { + "metric": "vtgate.histogram_name.total", + "timestamp": 1234, + "value": 2, + "tags": { + "host": "localhost" + } + } + ]`) +} + type myVar bool func (mv *myVar) String() string { @@ -351,6 +772,49 @@ func TestOpenTsdbTimings(t *testing.T) { ]`) } +func TestCounterForEmptyCollectorPrefix(t *testing.T) { + name := "counter_for_empty_collector_prefix_name" + c := stats.NewCounter(name, "counter description") + c.Add(1) + + expectedOutput := ` + [ + { + "metric": "counter_for_empty_collector_prefix_name", + "timestamp": 1234, + "value": 1, + "tags": { + "host": "test_localhost" + } + } + ]` + + dc := &collector{ + commonTags: map[string]string{"host": "test localhost"}, + prefix: "", + timestamp: int64(1234), + } + expvar.Do(func(kv expvar.KeyValue) { + if kv.Key == name { + dc.addExpVar(kv) + sort.Sort(byMetric(dc.data)) + + gotBytes, err := json.MarshalIndent(dc.data, "", " ") + assert.NoErrorf(t, err, "failed to marshal json") + + var got any + err = json.Unmarshal(gotBytes, &got) + assert.NoErrorf(t, err, "failed to unmarshal json") + + var want any + err = json.Unmarshal([]byte(expectedOutput), &want) + assert.NoErrorf(t, err, "failed to unmarshal json") + + assert.Equal(t, want, got) + } + }) +} + func checkOutput(t *testing.T, statName string, wantJSON string) { b := &backend{ prefix: "vtgate", @@ -372,30 +836,18 @@ func checkOutput(t *testing.T, statName string, wantJSON string) { sort.Sort(byMetric(dc.data)) gotBytes, err := json.MarshalIndent(dc.data, "", " ") - if err != nil { - t.Errorf("Failed to marshal json: %v", err) - return - } + assert.NoErrorf(t, err, "failed to marshal json") + var got any err = json.Unmarshal(gotBytes, &got) - if err != nil { - t.Errorf("Failed to marshal json: %v", err) - return - } + assert.NoErrorf(t, err, "failed to unmarshal json") var want any err = json.Unmarshal([]byte(wantJSON), &want) - if err != nil { - t.Errorf("Failed to marshal json: %v", err) - return - } + assert.NoErrorf(t, err, "failed to unmarshal json") - if !reflect.DeepEqual(got, want) { - t.Errorf("addExpVar(%#v) = %s, want %s", kv, string(gotBytes), wantJSON) - } + assert.Equal(t, want, got) } }) - if !found { - t.Errorf("Stat %s not found?...", statName) - } + assert.True(t, found, "stat %s not found", statName) }