// Copyright 2018 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 testutil import ( "bytes" "fmt" "io" "reflect" "github.com/prometheus/common/expfmt" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/internal" ) // CollectAndCompare registers the provided Collector with a newly created // pedantic Registry. It then does the same as GatherAndCompare, gathering the // metrics from the pedantic Registry. func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames ...string) error { reg := prometheus.NewPedanticRegistry() if err := reg.Register(c); err != nil { return fmt.Errorf("registering collector failed: %s", err) } return GatherAndCompare(reg, expected, metricNames...) } // GatherAndCompare gathers all metrics from the provided Gatherer and compares // it to an expected output read from the provided Reader in the Prometheus text // exposition format. If any metricNames are provided, only metrics with those // names are compared. func GatherAndCompare(g prometheus.Gatherer, expected io.Reader, metricNames ...string) error { metrics, err := g.Gather() if err != nil { return fmt.Errorf("gathering metrics failed: %s", err) } if metricNames != nil { metrics = filterMetrics(metrics, metricNames) } var tp expfmt.TextParser expectedMetrics, err := tp.TextToMetricFamilies(expected) if err != nil { return fmt.Errorf("parsing expected metrics failed: %s", err) } if !reflect.DeepEqual(metrics, internal.NormalizeMetricFamilies(expectedMetrics)) { // Encode the gathered output to the readable text format for comparison. var buf1 bytes.Buffer enc := expfmt.NewEncoder(&buf1, expfmt.FmtText) for _, mf := range metrics { if err := enc.Encode(mf); err != nil { return fmt.Errorf("encoding result failed: %s", err) } } // Encode normalized expected metrics again to generate them in the same ordering // the registry does to spot differences more easily. var buf2 bytes.Buffer enc = expfmt.NewEncoder(&buf2, expfmt.FmtText) for _, mf := range internal.NormalizeMetricFamilies(expectedMetrics) { if err := enc.Encode(mf); err != nil { return fmt.Errorf("encoding result failed: %s", err) } } return fmt.Errorf(` metric output does not match expectation; want: %s got: %s `, buf2.String(), buf1.String()) } return nil } func filterMetrics(metrics []*dto.MetricFamily, names []string) []*dto.MetricFamily { var filtered []*dto.MetricFamily for _, m := range metrics { for _, name := range names { if m.GetName() == name { filtered = append(filtered, m) break } } } return filtered }