1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package prometheus_test 16 17import ( 18 "bytes" 19 "context" 20 "io/ioutil" 21 "net/http" 22 "net/http/httptest" 23 "sort" 24 "strings" 25 "testing" 26 27 "github.com/stretchr/testify/require" 28 29 "go.opentelemetry.io/otel/exporters/metric/prometheus" 30 "go.opentelemetry.io/otel/label" 31 "go.opentelemetry.io/otel/metric" 32 "go.opentelemetry.io/otel/sdk/metric/controller/pull" 33 "go.opentelemetry.io/otel/sdk/resource" 34) 35 36func TestPrometheusExporter(t *testing.T) { 37 // #TODO: This test does not adequately verify the type of 38 // prometheus metric exported for all types - for example, 39 // it does not verify that an UpDown- counter is exported 40 // as a gauge. To be improved. 41 exporter, err := prometheus.NewExportPipeline( 42 prometheus.Config{ 43 DefaultHistogramBoundaries: []float64{-0.5, 1}, 44 }, 45 pull.WithCachePeriod(0), 46 pull.WithResource(resource.NewWithAttributes(label.String("R", "V"))), 47 ) 48 require.NoError(t, err) 49 50 meter := exporter.MeterProvider().Meter("test") 51 upDownCounter := metric.Must(meter).NewFloat64UpDownCounter("updowncounter") 52 counter := metric.Must(meter).NewFloat64Counter("counter") 53 valuerecorder := metric.Must(meter).NewFloat64ValueRecorder("valuerecorder") 54 55 labels := []label.KeyValue{ 56 label.Key("A").String("B"), 57 label.Key("C").String("D"), 58 } 59 ctx := context.Background() 60 61 var expected []string 62 63 counter.Add(ctx, 10, labels...) 64 counter.Add(ctx, 5.3, labels...) 65 66 expected = append(expected, `counter{A="B",C="D",R="V"} 15.3`) 67 68 _ = metric.Must(meter).NewInt64ValueObserver("intobserver", func(_ context.Context, result metric.Int64ObserverResult) { 69 result.Observe(1, labels...) 70 }) 71 72 expected = append(expected, `intobserver{A="B",C="D",R="V"} 1`) 73 74 valuerecorder.Record(ctx, -0.6, labels...) 75 valuerecorder.Record(ctx, -0.4, labels...) 76 valuerecorder.Record(ctx, 0.6, labels...) 77 valuerecorder.Record(ctx, 20, labels...) 78 79 expected = append(expected, `valuerecorder_bucket{A="B",C="D",R="V",le="+Inf"} 4`) 80 expected = append(expected, `valuerecorder_bucket{A="B",C="D",R="V",le="-0.5"} 1`) 81 expected = append(expected, `valuerecorder_bucket{A="B",C="D",R="V",le="1"} 3`) 82 expected = append(expected, `valuerecorder_count{A="B",C="D",R="V"} 4`) 83 expected = append(expected, `valuerecorder_sum{A="B",C="D",R="V"} 19.6`) 84 85 upDownCounter.Add(ctx, 10, labels...) 86 upDownCounter.Add(ctx, -3.2, labels...) 87 88 expected = append(expected, `updowncounter{A="B",C="D",R="V"} 6.8`) 89 90 compareExport(t, exporter, expected) 91 compareExport(t, exporter, expected) 92} 93 94func compareExport(t *testing.T, exporter *prometheus.Exporter, expected []string) { 95 rec := httptest.NewRecorder() 96 req := httptest.NewRequest("GET", "/metrics", nil) 97 exporter.ServeHTTP(rec, req) 98 99 output := rec.Body.String() 100 lines := strings.Split(output, "\n") 101 102 var metricsOnly []string 103 for _, line := range lines { 104 if !strings.HasPrefix(line, "#") && line != "" { 105 metricsOnly = append(metricsOnly, line) 106 } 107 } 108 109 sort.Strings(metricsOnly) 110 sort.Strings(expected) 111 112 require.Equal(t, strings.Join(expected, "\n"), strings.Join(metricsOnly, "\n")) 113} 114 115func TestPrometheusStatefulness(t *testing.T) { 116 // Create a meter 117 exporter, err := prometheus.NewExportPipeline( 118 prometheus.Config{}, 119 pull.WithCachePeriod(0), 120 ) 121 require.NoError(t, err) 122 123 meter := exporter.MeterProvider().Meter("test") 124 125 // GET the HTTP endpoint 126 scrape := func() string { 127 var input bytes.Buffer 128 resp := httptest.NewRecorder() 129 req, err := http.NewRequest("GET", "/", &input) 130 require.NoError(t, err) 131 132 exporter.ServeHTTP(resp, req) 133 data, err := ioutil.ReadAll(resp.Result().Body) 134 require.NoError(t, err) 135 136 return string(data) 137 } 138 139 ctx := context.Background() 140 141 counter := metric.Must(meter).NewInt64Counter( 142 "a.counter", 143 metric.WithDescription("Counts things"), 144 ) 145 146 counter.Add(ctx, 100, label.String("key", "value")) 147 148 require.Equal(t, `# HELP a_counter Counts things 149# TYPE a_counter counter 150a_counter{key="value"} 100 151`, scrape()) 152 153 counter.Add(ctx, 100, label.String("key", "value")) 154 155 require.Equal(t, `# HELP a_counter Counts things 156# TYPE a_counter counter 157a_counter{key="value"} 200 158`, scrape()) 159 160} 161