package prometheus_client import ( "fmt" "io" "net/http" "net/http/httptest" "regexp" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" inputs "github.com/influxdata/telegraf/plugins/inputs/prometheus" "github.com/influxdata/telegraf/plugins/serializers/prometheus" "github.com/influxdata/telegraf/testutil" ) func TestMetricVersion1(t *testing.T) { logger := testutil.Logger{Name: "outputs.prometheus_client"} tests := []struct { name string output *PrometheusClient metrics []telegraf.Metric expected []byte }{ { name: "simple", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu", map[string]string{ "host": "example.org", }, map[string]interface{}{ "time_idle": 42.0, }, time.Unix(0, 0), ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle untyped cpu_time_idle{host="example.org"} 42 `), }, { name: "prometheus untyped", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{ "host": "example.org", }, map[string]interface{}{ "value": 42.0, }, time.Unix(0, 0), ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle untyped cpu_time_idle{host="example.org"} 42 `), }, { name: "prometheus counter", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{ "host": "example.org", }, map[string]interface{}{ "counter": 42.0, }, time.Unix(0, 0), telegraf.Counter, ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle counter cpu_time_idle{host="example.org"} 42 `), }, { name: "when export timestamp is true timestamp is present in the metric", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", ExportTimestamp: true, Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{ "host": "example.org", }, map[string]interface{}{ "counter": 42.0, }, time.Unix(1257894000, 0), telegraf.Counter, ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle counter cpu_time_idle{host="example.org"} 42 1257894000000 `), }, { name: "replace characters when using string as label", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", StringAsLabel: true, Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{}, map[string]interface{}{ "host:name": "example.org", "counter": 42.0, }, time.Unix(0, 0), telegraf.Counter, ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle counter cpu_time_idle{host_name="example.org"} 42 `), }, { name: "prometheus gauge", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{ "host": "example.org", }, map[string]interface{}{ "gauge": 42.0, }, time.Unix(0, 0), telegraf.Gauge, ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle gauge cpu_time_idle{host="example.org"} 42 `), }, { name: "prometheus histogram", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "http_request_duration_seconds", map[string]string{}, map[string]interface{}{ "sum": 53423, "0.05": 24054, "0.1": 33444, "0.2": 100392, "0.5": 129389, "1": 133988, "+Inf": 144320, "count": 144320, }, time.Unix(0, 0), telegraf.Histogram, ), }, expected: []byte(` # HELP http_request_duration_seconds Telegraf collected metric # TYPE http_request_duration_seconds histogram http_request_duration_seconds_bucket{le="0.05"} 24054 http_request_duration_seconds_bucket{le="0.1"} 33444 http_request_duration_seconds_bucket{le="0.2"} 100392 http_request_duration_seconds_bucket{le="0.5"} 129389 http_request_duration_seconds_bucket{le="1"} 133988 http_request_duration_seconds_bucket{le="+Inf"} 144320 http_request_duration_seconds_sum 53423 http_request_duration_seconds_count 144320 `), }, { name: "prometheus summary", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "rpc_duration_seconds", map[string]string{}, map[string]interface{}{ "0.01": 3102, "0.05": 3272, "0.5": 4773, "0.9": 9001, "0.99": 76656, "count": 2693, "sum": 17560473, }, time.Unix(0, 0), telegraf.Summary, ), }, expected: []byte(` # HELP rpc_duration_seconds Telegraf collected metric # TYPE rpc_duration_seconds summary rpc_duration_seconds{quantile="0.01"} 3102 rpc_duration_seconds{quantile="0.05"} 3272 rpc_duration_seconds{quantile="0.5"} 4773 rpc_duration_seconds{quantile="0.9"} 9001 rpc_duration_seconds{quantile="0.99"} 76656 rpc_duration_seconds_sum 1.7560473e+07 rpc_duration_seconds_count 2693 `), }, { name: "prometheus untyped forced to counter", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", TypeMappings: prometheus.MetricTypes{Counter: []string{"cpu_time_idle"}}, Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{ "host": "example.org", }, map[string]interface{}{ "value": 42, }, time.Unix(0, 0), ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle counter cpu_time_idle{host="example.org"} 42 `), }, { name: "prometheus untyped forced to gauge", output: &PrometheusClient{ Listen: ":0", MetricVersion: 1, CollectorsExclude: []string{"gocollector", "process"}, Path: "/metrics", TypeMappings: prometheus.MetricTypes{Gauge: []string{"cpu_time_idle"}}, Log: logger, }, metrics: []telegraf.Metric{ testutil.MustMetric( "cpu_time_idle", map[string]string{ "host": "example.org", }, map[string]interface{}{ "value": 42.0, }, time.Unix(0, 0), ), }, expected: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle gauge cpu_time_idle{host="example.org"} 42 `), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.NoError(t, tt.output.Init()) require.NoError(t, tt.output.Connect()) defer func() { require.NoError(t, tt.output.Close()) }() require.NoError(t, tt.output.Write(tt.metrics)) resp, err := http.Get(tt.output.URL()) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, strings.TrimSpace(string(tt.expected)), strings.TrimSpace(string(body))) }) } } func TestRoundTripMetricVersion1(t *testing.T) { logger := testutil.Logger{Name: "outputs.prometheus_client"} regxPattern := regexp.MustCompile(`.*prometheus_request_.*`) tests := []struct { name string data []byte }{ { name: "untyped", data: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle untyped cpu_time_idle{host="example.org"} 42 `), }, { name: "counter", data: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle counter cpu_time_idle{host="example.org"} 42 `), }, { name: "gauge", data: []byte(` # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle gauge cpu_time_idle{host="example.org"} 42 `), }, { name: "multi", data: []byte(` # HELP cpu_time_guest Telegraf collected metric # TYPE cpu_time_guest gauge cpu_time_guest{host="one.example.org"} 42 cpu_time_guest{host="two.example.org"} 42 # HELP cpu_time_idle Telegraf collected metric # TYPE cpu_time_idle gauge cpu_time_idle{host="one.example.org"} 42 cpu_time_idle{host="two.example.org"} 42 `), }, { name: "histogram", data: []byte(` # HELP http_request_duration_seconds Telegraf collected metric # TYPE http_request_duration_seconds histogram http_request_duration_seconds_bucket{le="0.05"} 24054 http_request_duration_seconds_bucket{le="0.1"} 33444 http_request_duration_seconds_bucket{le="0.2"} 100392 http_request_duration_seconds_bucket{le="0.5"} 129389 http_request_duration_seconds_bucket{le="1"} 133988 http_request_duration_seconds_bucket{le="+Inf"} 144320 http_request_duration_seconds_sum 53423 http_request_duration_seconds_count 144320 `), }, { name: "summary", data: []byte(` # HELP rpc_duration_seconds Telegraf collected metric # TYPE rpc_duration_seconds summary rpc_duration_seconds{quantile="0.01"} 3102 rpc_duration_seconds{quantile="0.05"} 3272 rpc_duration_seconds{quantile="0.5"} 4773 rpc_duration_seconds{quantile="0.9"} 9001 rpc_duration_seconds{quantile="0.99"} 76656 rpc_duration_seconds_sum 1.7560473e+07 rpc_duration_seconds_count 2693 `), }, } ts := httptest.NewServer(http.NotFoundHandler()) defer ts.Close() address := fmt.Sprintf("http://%s", ts.Listener.Addr()) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(tt.data); err != nil { w.WriteHeader(http.StatusInternalServerError) t.Error(err) return } }) input := &inputs.Prometheus{ Log: logger, URLs: []string{address}, URLTag: "", MetricVersion: 1, } err := input.Init() require.NoError(t, err) var acc testutil.Accumulator err = input.Start(&acc) require.NoError(t, err) err = input.Gather(&acc) require.NoError(t, err) input.Stop() metrics := acc.GetTelegrafMetrics() output := &PrometheusClient{ Listen: "127.0.0.1:0", Path: defaultPath, MetricVersion: 1, Log: logger, CollectorsExclude: []string{"gocollector", "process"}, } err = output.Init() require.NoError(t, err) err = output.Connect() require.NoError(t, err) defer func() { err = output.Close() require.NoError(t, err) }() err = output.Write(metrics) require.NoError(t, err) resp, err := http.Get(output.URL()) require.NoError(t, err) defer resp.Body.Close() actual, err := io.ReadAll(resp.Body) require.NoError(t, err) current := regxPattern.ReplaceAllLiteralString(string(actual), "") require.Equal(t, strings.TrimSpace(string(tt.data)), strings.TrimSpace(current)) }) } }