mirror of https://github.com/prometheus/prometheus
Julien Pivotto
1 year ago
committed by
GitHub
5 changed files with 633 additions and 0 deletions
@ -0,0 +1,138 @@
|
||||
// Copyright 2023 The Prometheus 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 main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"time" |
||||
|
||||
"github.com/golang/snappy" |
||||
config_util "github.com/prometheus/common/config" |
||||
"github.com/prometheus/common/model" |
||||
|
||||
"github.com/prometheus/prometheus/storage/remote" |
||||
"github.com/prometheus/prometheus/util/fmtutil" |
||||
) |
||||
|
||||
// Push metrics to a prometheus remote write (for testing purpose only).
|
||||
func PushMetrics(url *url.URL, roundTripper http.RoundTripper, headers map[string]string, timeout time.Duration, labels map[string]string, files ...string) int { |
||||
addressURL, err := url.Parse(url.String()) |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, err) |
||||
return failureExitCode |
||||
} |
||||
|
||||
// build remote write client
|
||||
writeClient, err := remote.NewWriteClient("remote-write", &remote.ClientConfig{ |
||||
URL: &config_util.URL{URL: addressURL}, |
||||
Timeout: model.Duration(timeout), |
||||
}) |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, err) |
||||
return failureExitCode |
||||
} |
||||
|
||||
// set custom tls config from httpConfigFilePath
|
||||
// set custom headers to every request
|
||||
client, ok := writeClient.(*remote.Client) |
||||
if !ok { |
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("unexpected type %T", writeClient)) |
||||
return failureExitCode |
||||
} |
||||
client.Client.Transport = &setHeadersTransport{ |
||||
RoundTripper: roundTripper, |
||||
headers: headers, |
||||
} |
||||
|
||||
var data []byte |
||||
var failed bool |
||||
|
||||
if len(files) == 0 { |
||||
data, err = io.ReadAll(os.Stdin) |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, " FAILED:", err) |
||||
return failureExitCode |
||||
} |
||||
fmt.Printf("Parsing standard input\n") |
||||
if parseAndPushMetrics(client, data, labels) { |
||||
fmt.Printf(" SUCCESS: metrics pushed to remote write.\n") |
||||
return successExitCode |
||||
} |
||||
return failureExitCode |
||||
} |
||||
|
||||
for _, file := range files { |
||||
data, err = os.ReadFile(file) |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, " FAILED:", err) |
||||
failed = true |
||||
continue |
||||
} |
||||
|
||||
fmt.Printf("Parsing metrics file %s\n", file) |
||||
if parseAndPushMetrics(client, data, labels) { |
||||
fmt.Printf(" SUCCESS: metrics file %s pushed to remote write.\n", file) |
||||
continue |
||||
} |
||||
failed = true |
||||
} |
||||
|
||||
if failed { |
||||
return failureExitCode |
||||
} |
||||
|
||||
return successExitCode |
||||
} |
||||
|
||||
func parseAndPushMetrics(client *remote.Client, data []byte, labels map[string]string) bool { |
||||
metricsData, err := fmtutil.MetricTextToWriteRequest(bytes.NewReader(data), labels) |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, " FAILED:", err) |
||||
return false |
||||
} |
||||
|
||||
raw, err := metricsData.Marshal() |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, " FAILED:", err) |
||||
return false |
||||
} |
||||
|
||||
// Encode the request body into snappy encoding.
|
||||
compressed := snappy.Encode(nil, raw) |
||||
err = client.Store(context.Background(), compressed) |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, " FAILED:", err) |
||||
return false |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
type setHeadersTransport struct { |
||||
http.RoundTripper |
||||
headers map[string]string |
||||
} |
||||
|
||||
func (s *setHeadersTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
||||
for key, value := range s.headers { |
||||
req.Header.Set(key, value) |
||||
} |
||||
return s.RoundTripper.RoundTrip(req) |
||||
} |
@ -0,0 +1,203 @@
|
||||
// Copyright 2023 The Prometheus 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 fmtutil |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"sort" |
||||
"time" |
||||
|
||||
dto "github.com/prometheus/client_model/go" |
||||
"github.com/prometheus/common/expfmt" |
||||
"github.com/prometheus/common/model" |
||||
|
||||
"github.com/prometheus/prometheus/prompb" |
||||
) |
||||
|
||||
const ( |
||||
sumStr = "_sum" |
||||
countStr = "_count" |
||||
bucketStr = "_bucket" |
||||
) |
||||
|
||||
var MetricMetadataTypeValue = map[string]int32{ |
||||
"UNKNOWN": 0, |
||||
"COUNTER": 1, |
||||
"GAUGE": 2, |
||||
"HISTOGRAM": 3, |
||||
"GAUGEHISTOGRAM": 4, |
||||
"SUMMARY": 5, |
||||
"INFO": 6, |
||||
"STATESET": 7, |
||||
} |
||||
|
||||
// MetricTextToWriteRequest consumes an io.Reader and return the data in write request format.
|
||||
func MetricTextToWriteRequest(input io.Reader, labels map[string]string) (*prompb.WriteRequest, error) { |
||||
var parser expfmt.TextParser |
||||
mf, err := parser.TextToMetricFamilies(input) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return MetricFamiliesToWriteRequest(mf, labels) |
||||
} |
||||
|
||||
// MetricFamiliesToWriteRequest convert metric family to a writerequest.
|
||||
func MetricFamiliesToWriteRequest(mf map[string]*dto.MetricFamily, extraLabels map[string]string) (*prompb.WriteRequest, error) { |
||||
wr := &prompb.WriteRequest{} |
||||
|
||||
// build metric list
|
||||
sortedMetricNames := make([]string, 0, len(mf)) |
||||
for metric := range mf { |
||||
sortedMetricNames = append(sortedMetricNames, metric) |
||||
} |
||||
// sort metrics name in lexicographical order
|
||||
sort.Strings(sortedMetricNames) |
||||
|
||||
for _, metricName := range sortedMetricNames { |
||||
// Set metadata writerequest
|
||||
mtype := MetricMetadataTypeValue[mf[metricName].Type.String()] |
||||
metadata := prompb.MetricMetadata{ |
||||
MetricFamilyName: mf[metricName].GetName(), |
||||
Type: prompb.MetricMetadata_MetricType(mtype), |
||||
Help: mf[metricName].GetHelp(), |
||||
} |
||||
wr.Metadata = append(wr.Metadata, metadata) |
||||
|
||||
for _, metric := range mf[metricName].Metric { |
||||
labels := makeLabelsMap(metric, metricName, extraLabels) |
||||
if err := makeTimeseries(wr, labels, metric); err != nil { |
||||
return wr, err |
||||
} |
||||
} |
||||
} |
||||
return wr, nil |
||||
} |
||||
|
||||
func toTimeseries(wr *prompb.WriteRequest, labels map[string]string, timestamp int64, value float64) { |
||||
var ts prompb.TimeSeries |
||||
ts.Labels = makeLabels(labels) |
||||
ts.Samples = []prompb.Sample{ |
||||
{ |
||||
Timestamp: timestamp, |
||||
Value: value, |
||||
}, |
||||
} |
||||
wr.Timeseries = append(wr.Timeseries, ts) |
||||
} |
||||
|
||||
func makeTimeseries(wr *prompb.WriteRequest, labels map[string]string, m *dto.Metric) error { |
||||
var err error |
||||
|
||||
timestamp := m.GetTimestampMs() |
||||
if timestamp == 0 { |
||||
timestamp = time.Now().UnixNano() / int64(time.Millisecond) |
||||
} |
||||
|
||||
switch { |
||||
case m.Gauge != nil: |
||||
toTimeseries(wr, labels, timestamp, m.GetGauge().GetValue()) |
||||
case m.Counter != nil: |
||||
toTimeseries(wr, labels, timestamp, m.GetCounter().GetValue()) |
||||
case m.Summary != nil: |
||||
metricName := labels[model.MetricNameLabel] |
||||
// Preserve metric name order with first quantile labels timeseries then sum suffix timeserie and finally count suffix timeserie
|
||||
// Add Summary quantile timeseries
|
||||
quantileLabels := make(map[string]string, len(labels)+1) |
||||
for key, value := range labels { |
||||
quantileLabels[key] = value |
||||
} |
||||
|
||||
for _, q := range m.GetSummary().Quantile { |
||||
quantileLabels[model.QuantileLabel] = fmt.Sprint(q.GetQuantile()) |
||||
toTimeseries(wr, quantileLabels, timestamp, q.GetValue()) |
||||
} |
||||
// Overwrite label model.MetricNameLabel for count and sum metrics
|
||||
// Add Summary sum timeserie
|
||||
labels[model.MetricNameLabel] = metricName + sumStr |
||||
toTimeseries(wr, labels, timestamp, m.GetSummary().GetSampleSum()) |
||||
// Add Summary count timeserie
|
||||
labels[model.MetricNameLabel] = metricName + countStr |
||||
toTimeseries(wr, labels, timestamp, float64(m.GetSummary().GetSampleCount())) |
||||
|
||||
case m.Histogram != nil: |
||||
metricName := labels[model.MetricNameLabel] |
||||
// Preserve metric name order with first bucket suffix timeseries then sum suffix timeserie and finally count suffix timeserie
|
||||
// Add Histogram bucket timeseries
|
||||
bucketLabels := make(map[string]string, len(labels)+1) |
||||
for key, value := range labels { |
||||
bucketLabels[key] = value |
||||
} |
||||
for _, b := range m.GetHistogram().Bucket { |
||||
bucketLabels[model.MetricNameLabel] = metricName + bucketStr |
||||
bucketLabels[model.BucketLabel] = fmt.Sprint(b.GetUpperBound()) |
||||
toTimeseries(wr, bucketLabels, timestamp, float64(b.GetCumulativeCount())) |
||||
} |
||||
// Overwrite label model.MetricNameLabel for count and sum metrics
|
||||
// Add Histogram sum timeserie
|
||||
labels[model.MetricNameLabel] = metricName + sumStr |
||||
toTimeseries(wr, labels, timestamp, m.GetHistogram().GetSampleSum()) |
||||
// Add Histogram count timeserie
|
||||
labels[model.MetricNameLabel] = metricName + countStr |
||||
toTimeseries(wr, labels, timestamp, float64(m.GetHistogram().GetSampleCount())) |
||||
|
||||
case m.Untyped != nil: |
||||
toTimeseries(wr, labels, timestamp, m.GetUntyped().GetValue()) |
||||
default: |
||||
err = errors.New("unsupported metric type") |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func makeLabels(labelsMap map[string]string) []prompb.Label { |
||||
// build labels name list
|
||||
sortedLabelNames := make([]string, 0, len(labelsMap)) |
||||
for label := range labelsMap { |
||||
sortedLabelNames = append(sortedLabelNames, label) |
||||
} |
||||
// sort labels name in lexicographical order
|
||||
sort.Strings(sortedLabelNames) |
||||
|
||||
var labels []prompb.Label |
||||
for _, label := range sortedLabelNames { |
||||
labels = append(labels, prompb.Label{ |
||||
Name: label, |
||||
Value: labelsMap[label], |
||||
}) |
||||
} |
||||
return labels |
||||
} |
||||
|
||||
func makeLabelsMap(m *dto.Metric, metricName string, extraLabels map[string]string) map[string]string { |
||||
// build labels map
|
||||
labels := make(map[string]string, len(m.Label)+len(extraLabels)) |
||||
labels[model.MetricNameLabel] = metricName |
||||
|
||||
// add extra labels
|
||||
for key, value := range extraLabels { |
||||
labels[key] = value |
||||
} |
||||
|
||||
// add metric labels
|
||||
for _, label := range m.Label { |
||||
labelname := label.GetName() |
||||
if labelname == model.JobLabel { |
||||
labelname = fmt.Sprintf("%s%s", model.ExportedLabelPrefix, labelname) |
||||
} |
||||
labels[labelname] = label.GetValue() |
||||
} |
||||
|
||||
return labels |
||||
} |
@ -0,0 +1,233 @@
|
||||
// Copyright 2023 The Prometheus 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 fmtutil |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/prometheus/prometheus/prompb" |
||||
) |
||||
|
||||
var writeRequestFixture = &prompb.WriteRequest{ |
||||
Metadata: []prompb.MetricMetadata{ |
||||
{ |
||||
MetricFamilyName: "http_request_duration_seconds", |
||||
Type: 3, |
||||
Help: "A histogram of the request duration.", |
||||
}, |
||||
{ |
||||
MetricFamilyName: "http_requests_total", |
||||
Type: 1, |
||||
Help: "The total number of HTTP requests.", |
||||
}, |
||||
{ |
||||
MetricFamilyName: "rpc_duration_seconds", |
||||
Type: 5, |
||||
Help: "A summary of the RPC duration in seconds.", |
||||
}, |
||||
{ |
||||
MetricFamilyName: "test_metric1", |
||||
Type: 2, |
||||
Help: "This is a test metric.", |
||||
}, |
||||
}, |
||||
Timeseries: []prompb.TimeSeries{ |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_request_duration_seconds_bucket"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "le", Value: "0.1"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 33444, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_request_duration_seconds_bucket"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "le", Value: "0.5"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 129389, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_request_duration_seconds_bucket"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "le", Value: "1"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 133988, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_request_duration_seconds_bucket"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "le", Value: "+Inf"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 144320, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_request_duration_seconds_sum"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 53423, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_request_duration_seconds_count"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 144320, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_requests_total"}, |
||||
{Name: "code", Value: "200"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "method", Value: "post"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 1027, Timestamp: 1395066363000}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "http_requests_total"}, |
||||
{Name: "code", Value: "400"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "method", Value: "post"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 3, Timestamp: 1395066363000}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "rpc_duration_seconds"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "quantile", Value: "0.01"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 3102, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "rpc_duration_seconds"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "quantile", Value: "0.5"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 4773, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "rpc_duration_seconds"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
{Name: "quantile", Value: "0.99"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 76656, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "rpc_duration_seconds_sum"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 1.7560473e+07, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "rpc_duration_seconds_count"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 2693, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "test_metric1"}, |
||||
{Name: "b", Value: "c"}, |
||||
{Name: "baz", Value: "qux"}, |
||||
{Name: "d", Value: "e"}, |
||||
{Name: "foo", Value: "bar"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 1, Timestamp: 1}}, |
||||
}, |
||||
{ |
||||
Labels: []prompb.Label{ |
||||
{Name: "__name__", Value: "test_metric1"}, |
||||
{Name: "b", Value: "c"}, |
||||
{Name: "baz", Value: "qux"}, |
||||
{Name: "d", Value: "e"}, |
||||
{Name: "foo", Value: "bar"}, |
||||
{Name: "job", Value: "promtool"}, |
||||
}, |
||||
Samples: []prompb.Sample{{Value: 2, Timestamp: 1}}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
func TestParseAndPushMetricsTextAndFormat(t *testing.T) { |
||||
input := bytes.NewReader([]byte(` |
||||
# HELP http_request_duration_seconds A histogram of the request duration. |
||||
# TYPE http_request_duration_seconds histogram |
||||
http_request_duration_seconds_bucket{le="0.1"} 33444 1 |
||||
http_request_duration_seconds_bucket{le="0.5"} 129389 1 |
||||
http_request_duration_seconds_bucket{le="1"} 133988 1 |
||||
http_request_duration_seconds_bucket{le="+Inf"} 144320 1 |
||||
http_request_duration_seconds_sum 53423 1 |
||||
http_request_duration_seconds_count 144320 1 |
||||
# HELP http_requests_total The total number of HTTP requests. |
||||
# TYPE http_requests_total counter |
||||
http_requests_total{method="post",code="200"} 1027 1395066363000 |
||||
http_requests_total{method="post",code="400"} 3 1395066363000 |
||||
# HELP rpc_duration_seconds A summary of the RPC duration in seconds. |
||||
# TYPE rpc_duration_seconds summary |
||||
rpc_duration_seconds{quantile="0.01"} 3102 1 |
||||
rpc_duration_seconds{quantile="0.5"} 4773 1 |
||||
rpc_duration_seconds{quantile="0.99"} 76656 1 |
||||
rpc_duration_seconds_sum 1.7560473e+07 1 |
||||
rpc_duration_seconds_count 2693 1 |
||||
# HELP test_metric1 This is a test metric. |
||||
# TYPE test_metric1 gauge |
||||
test_metric1{b="c",baz="qux",d="e",foo="bar"} 1 1 |
||||
test_metric1{b="c",baz="qux",d="e",foo="bar"} 2 1 |
||||
`)) |
||||
labels := map[string]string{"job": "promtool"} |
||||
|
||||
expected, err := MetricTextToWriteRequest(input, labels) |
||||
require.NoError(t, err) |
||||
|
||||
require.Equal(t, writeRequestFixture, expected) |
||||
} |
||||
|
||||
func TestMetricTextToWriteRequestErrorParsingFloatValue(t *testing.T) { |
||||
input := bytes.NewReader([]byte(` |
||||
# HELP http_requests_total The total number of HTTP requests. |
||||
# TYPE http_requests_total counter |
||||
http_requests_total{method="post",code="200"} 1027Error 1395066363000 |
||||
http_requests_total{method="post",code="400"} 3 1395066363000 |
||||
`)) |
||||
labels := map[string]string{"job": "promtool"} |
||||
|
||||
_, err := MetricTextToWriteRequest(input, labels) |
||||
require.Equal(t, err.Error(), "text format parsing error in line 4: expected float as value, got \"1027Error\"") |
||||
} |
||||
|
||||
func TestMetricTextToWriteRequestErrorParsingMetricType(t *testing.T) { |
||||
input := bytes.NewReader([]byte(` |
||||
# HELP node_info node info summary. |
||||
# TYPE node_info info |
||||
node_info{test="summary"} 1 1395066363000 |
||||
`)) |
||||
labels := map[string]string{"job": "promtool"} |
||||
|
||||
_, err := MetricTextToWriteRequest(input, labels) |
||||
require.Equal(t, err.Error(), "text format parsing error in line 3: unknown metric type \"info\"") |
||||
} |
Loading…
Reference in new issue