github.com/google/cloudprober@v0.11.3/surfacers/prometheus/prometheus_test.go (about)

     1  // Copyright 2017-2020 The Cloudprober 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  
    15  package prometheus
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"math/rand"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/golang/protobuf/proto"
    27  	"github.com/google/cloudprober/logger"
    28  	"github.com/google/cloudprober/metrics"
    29  	configpb "github.com/google/cloudprober/surfacers/prometheus/proto"
    30  )
    31  
    32  func newEventMetrics(sent, rcvd int64, respCodes map[string]int64, ptype, probe string) *metrics.EventMetrics {
    33  	respCodesVal := metrics.NewMap("code", metrics.NewInt(0))
    34  	for k, v := range respCodes {
    35  		respCodesVal.IncKeyBy(k, metrics.NewInt(v))
    36  	}
    37  	return metrics.NewEventMetrics(time.Now()).
    38  		AddMetric("sent", metrics.NewInt(sent)).
    39  		AddMetric("rcvd", metrics.NewInt(rcvd)).
    40  		AddMetric("resp-code", respCodesVal).
    41  		AddLabel("ptype", ptype).
    42  		AddLabel("probe", probe)
    43  }
    44  
    45  func verify(t *testing.T, ps *PromSurfacer, expectedMetrics map[string]testData) {
    46  	for k, td := range expectedMetrics {
    47  		pm := ps.metrics[td.metricName]
    48  		if pm == nil {
    49  			t.Errorf("Metric %s not found in the prometheus metrics: %v", k, ps.metrics)
    50  			continue
    51  		}
    52  		if pm.data[k] == nil {
    53  			t.Errorf("Data key %s not found in the prometheus metrics: %v", k, pm.data)
    54  			continue
    55  		}
    56  		if pm.data[k].value != td.value {
    57  			t.Errorf("Didn't get expected metrics. Got: %s, Expected: %s", pm.data[k].value, td.value)
    58  		}
    59  	}
    60  	var dataCount int
    61  	for _, pm := range ps.metrics {
    62  		dataCount += len(pm.data)
    63  	}
    64  	if dataCount != len(expectedMetrics) {
    65  		t.Errorf("Prometheus doesn't have expected number of data keys. Got: %d, Expected: %d", dataCount, len(expectedMetrics))
    66  	}
    67  }
    68  
    69  // mergeMap is helper function to build expectedMetrics by merging newly
    70  // added expectedMetrics with the existing ones.
    71  func mergeMap(recv map[string]testData, newmap map[string]testData) {
    72  	for k, v := range newmap {
    73  		recv[k] = v
    74  	}
    75  }
    76  
    77  // testData encapsulates expected value for a metric key and metric name.
    78  type testData struct {
    79  	metricName string // To access data row in a 2-level data structure.
    80  	value      string
    81  }
    82  
    83  func newPromSurfacer(t *testing.T, writeTimestamp bool) *PromSurfacer {
    84  	c := &configpb.SurfacerConf{
    85  		// Attach a random integer to metrics URL so that multiple
    86  		// tests can run in parallel without handlers clashing with
    87  		// each other.
    88  		MetricsUrl:       proto.String(fmt.Sprintf("/metrics_%d", rand.Int())),
    89  		IncludeTimestamp: proto.Bool(writeTimestamp),
    90  	}
    91  	l, _ := logger.New(context.Background(), "promtheus_test")
    92  	ps, err := New(context.Background(), c, nil, l)
    93  	if err != nil {
    94  		t.Fatal("Error while initializing prometheus surfacer", err)
    95  	}
    96  	return ps
    97  }
    98  
    99  func TestRecord(t *testing.T) {
   100  	ps := newPromSurfacer(t, true)
   101  
   102  	// Record first EventMetrics
   103  	ps.record(newEventMetrics(32, 22, map[string]int64{
   104  		"200": 22,
   105  	}, "http", "vm-to-google"))
   106  	expectedMetrics := map[string]testData{
   107  		"sent{ptype=\"http\",probe=\"vm-to-google\"}":                   testData{"sent", "32"},
   108  		"rcvd{ptype=\"http\",probe=\"vm-to-google\"}":                   testData{"rcvd", "22"},
   109  		"resp_code{ptype=\"http\",probe=\"vm-to-google\",code=\"200\"}": testData{"resp_code", "22"},
   110  	}
   111  	verify(t, ps, expectedMetrics)
   112  
   113  	// Record second EventMetrics, no overlap.
   114  	ps.record(newEventMetrics(500, 492, map[string]int64{}, "ping", "vm-to-vm"))
   115  	mergeMap(expectedMetrics, map[string]testData{
   116  		"sent{ptype=\"ping\",probe=\"vm-to-vm\"}": testData{"sent", "500"},
   117  		"rcvd{ptype=\"ping\",probe=\"vm-to-vm\"}": testData{"rcvd", "492"},
   118  	})
   119  	verify(t, ps, expectedMetrics)
   120  
   121  	// Record third EventMetrics, replaces first EventMetrics' metrics.
   122  	ps.record(newEventMetrics(62, 50, map[string]int64{
   123  		"200": 42,
   124  		"204": 8,
   125  	}, "http", "vm-to-google"))
   126  	mergeMap(expectedMetrics, map[string]testData{
   127  		"sent{ptype=\"http\",probe=\"vm-to-google\"}":                   testData{"sent", "62"},
   128  		"rcvd{ptype=\"http\",probe=\"vm-to-google\"}":                   testData{"rcvd", "50"},
   129  		"resp_code{ptype=\"http\",probe=\"vm-to-google\",code=\"200\"}": testData{"resp_code", "42"},
   130  		"resp_code{ptype=\"http\",probe=\"vm-to-google\",code=\"204\"}": testData{"resp_code", "8"},
   131  	})
   132  	verify(t, ps, expectedMetrics)
   133  
   134  	// Test string metrics.
   135  	em := metrics.NewEventMetrics(time.Now()).
   136  		AddMetric("instance_id", metrics.NewString("23152113123131")).
   137  		AddMetric("version", metrics.NewString("cloudradar-20170606-RC00")).
   138  		AddLabel("module", "sysvars")
   139  	em.Kind = metrics.GAUGE
   140  	ps.record(em)
   141  	mergeMap(expectedMetrics, map[string]testData{
   142  		"instance_id{module=\"sysvars\",val=\"23152113123131\"}":       testData{"instance_id", "1"},
   143  		"version{module=\"sysvars\",val=\"cloudradar-20170606-RC00\"}": testData{"version", "1"},
   144  	})
   145  	verify(t, ps, expectedMetrics)
   146  }
   147  
   148  func TestInvalidNames(t *testing.T) {
   149  	ps := newPromSurfacer(t, true)
   150  	respCodesVal := metrics.NewMap("resp-code", metrics.NewInt(0))
   151  	respCodesVal.IncKeyBy("200", metrics.NewInt(19))
   152  	ps.record(metrics.NewEventMetrics(time.Now()).
   153  		AddMetric("sent", metrics.NewInt(32)).
   154  		AddMetric("rcvd/sent", metrics.NewInt(22)).
   155  		AddMetric("resp", respCodesVal).
   156  		AddLabel("probe-type", "http").
   157  		AddLabel("probe/name", "vm-to-google"))
   158  
   159  	// Metric rcvd/sent is dropped
   160  	// Label probe-type is converted to probe_type
   161  	// Label probe/name is dropped
   162  	// Map value key resp-code is converted to resp_code label name
   163  	expectedMetrics := map[string]testData{
   164  		"sent{probe_type=\"http\"}":                   testData{"sent", "32"},
   165  		"resp{probe_type=\"http\",resp_code=\"200\"}": testData{"resp", "19"},
   166  	}
   167  	verify(t, ps, expectedMetrics)
   168  }
   169  
   170  func TestScrapeOutput(t *testing.T) {
   171  	ps := newPromSurfacer(t, true)
   172  	respCodesVal := metrics.NewMap("code", metrics.NewInt(0))
   173  	respCodesVal.IncKeyBy("200", metrics.NewInt(19))
   174  	latencyVal := metrics.NewDistribution([]float64{1, 4})
   175  	latencyVal.AddSample(0.5)
   176  	latencyVal.AddSample(5)
   177  	ts := time.Now()
   178  	promTS := fmt.Sprintf("%d", ts.UnixNano()/(1000*1000))
   179  	ps.record(metrics.NewEventMetrics(ts).
   180  		AddMetric("sent", metrics.NewInt(32)).
   181  		AddMetric("rcvd", metrics.NewInt(22)).
   182  		AddMetric("latency", latencyVal).
   183  		AddMetric("resp_code", respCodesVal).
   184  		AddLabel("ptype", "http"))
   185  	var b bytes.Buffer
   186  	ps.writeData(&b)
   187  	data := b.String()
   188  	for _, d := range []string{
   189  		"#TYPE sent counter",
   190  		"#TYPE rcvd counter",
   191  		"#TYPE resp_code counter",
   192  		"#TYPE latency histogram",
   193  		"sent{ptype=\"http\"} 32 " + promTS,
   194  		"rcvd{ptype=\"http\"} 22 " + promTS,
   195  		"resp_code{ptype=\"http\",code=\"200\"} 19 " + promTS,
   196  		"latency_sum{ptype=\"http\"} 5.5 " + promTS,
   197  		"latency_count{ptype=\"http\"} 2 " + promTS,
   198  		"latency_bucket{ptype=\"http\",le=\"1\"} 1 " + promTS,
   199  		"latency_bucket{ptype=\"http\",le=\"4\"} 1 " + promTS,
   200  		"latency_bucket{ptype=\"http\",le=\"+Inf\"} 2 " + promTS,
   201  	} {
   202  		if strings.Index(data, d) == -1 {
   203  			t.Errorf("String \"%s\" not found in output data: %s", d, data)
   204  		}
   205  	}
   206  }
   207  
   208  func TestScrapeOutputNoTimestamp(t *testing.T) {
   209  	ps := newPromSurfacer(t, false)
   210  	respCodesVal := metrics.NewMap("code", metrics.NewInt(0))
   211  	respCodesVal.IncKeyBy("200", metrics.NewInt(19))
   212  	latencyVal := metrics.NewDistribution([]float64{1, 4})
   213  	latencyVal.AddSample(0.5)
   214  	latencyVal.AddSample(5)
   215  	ps.record(metrics.NewEventMetrics(time.Now()).
   216  		AddMetric("sent", metrics.NewInt(32)).
   217  		AddMetric("rcvd", metrics.NewInt(22)).
   218  		AddMetric("latency", latencyVal).
   219  		AddMetric("resp_code", respCodesVal).
   220  		AddLabel("ptype", "http"))
   221  	var b bytes.Buffer
   222  	ps.writeData(&b)
   223  	data := b.String()
   224  	for _, d := range []string{
   225  		"#TYPE sent counter",
   226  		"#TYPE rcvd counter",
   227  		"#TYPE resp_code counter",
   228  		"#TYPE latency histogram",
   229  		"sent{ptype=\"http\"} 32",
   230  		"rcvd{ptype=\"http\"} 22",
   231  		"resp_code{ptype=\"http\",code=\"200\"} 19",
   232  		"latency_sum{ptype=\"http\"} 5.5",
   233  		"latency_count{ptype=\"http\"} 2",
   234  		"latency_bucket{ptype=\"http\",le=\"1\"} 1",
   235  		"latency_bucket{ptype=\"http\",le=\"4\"} 1",
   236  		"latency_bucket{ptype=\"http\",le=\"+Inf\"} 2",
   237  	} {
   238  		if strings.Index(data, d) == -1 {
   239  			t.Errorf("String \"%s\" not found in output data: %s", d, data)
   240  		}
   241  	}
   242  }