github.com/google/cloudprober@v0.11.3/metrics/payload/payload_test.go (about)

     1  // Copyright 2017-2019 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 payload
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/golang/protobuf/proto"
    25  	"github.com/google/cloudprober/metrics"
    26  	configpb "github.com/google/cloudprober/metrics/payload/proto"
    27  )
    28  
    29  var (
    30  	testPtype  = "external"
    31  	testProbe  = "testprobe"
    32  	testTarget = "test-target"
    33  )
    34  
    35  func parserForTest(t *testing.T, agg bool) *Parser {
    36  	testConf := `
    37  	  aggregate_in_cloudprober: %v
    38  		dist_metric {
    39  			key: "op_latency"
    40  			value {
    41  				explicit_buckets: "1,10,100"
    42  			}
    43  		}
    44   `
    45  	var c configpb.OutputMetricsOptions
    46  	if err := proto.UnmarshalText(fmt.Sprintf(testConf, agg), &c); err != nil {
    47  		t.Error(err)
    48  	}
    49  	p, err := NewParser(&c, testPtype, testProbe, metrics.CUMULATIVE, nil)
    50  	if err != nil {
    51  		t.Error(err)
    52  	}
    53  
    54  	return p
    55  }
    56  
    57  // testData encapsulates the test data.
    58  type testData struct {
    59  	varA, varB float64
    60  	lat        []float64
    61  	labels     [3][2]string
    62  }
    63  
    64  // aggregatedEM returns an EventMetrics struct corresponding to the provided testData.
    65  func (td *testData) aggregatedEM(ts time.Time) *metrics.EventMetrics {
    66  	d := metrics.NewDistribution([]float64{1, 10, 100})
    67  	for _, sample := range td.lat {
    68  		d.AddSample(sample)
    69  	}
    70  	return metrics.NewEventMetrics(ts).
    71  		AddMetric("op_latency", d).
    72  		AddMetric("time_to_running", metrics.NewFloat(td.varA)).
    73  		AddMetric("time_to_ssh", metrics.NewFloat(td.varB)).
    74  		AddLabel("ptype", testPtype).
    75  		AddLabel("probe", testProbe).
    76  		AddLabel("dst", testTarget)
    77  }
    78  
    79  // aggregatedEM returns an EventMetrics struct corresponding to the provided testData.
    80  func (td *testData) multiEM(ts time.Time) []*metrics.EventMetrics {
    81  	var results []*metrics.EventMetrics
    82  
    83  	d := metrics.NewDistribution([]float64{1, 10, 100})
    84  	for _, sample := range td.lat {
    85  		d.AddSample(sample)
    86  	}
    87  
    88  	results = append(results, []*metrics.EventMetrics{
    89  		metrics.NewEventMetrics(ts).AddMetric("op_latency", d),
    90  		metrics.NewEventMetrics(ts).AddMetric("time_to_running", metrics.NewFloat(td.varA)),
    91  		metrics.NewEventMetrics(ts).AddMetric("time_to_ssh", metrics.NewFloat(td.varB)),
    92  	}...)
    93  
    94  	for i, em := range results {
    95  		em.AddLabel("ptype", testPtype).
    96  			AddLabel("probe", testProbe).
    97  			AddLabel("dst", testTarget).
    98  			AddLabel(td.labels[i][0], td.labels[i][1])
    99  	}
   100  
   101  	return results
   102  }
   103  
   104  func (td *testData) testPayload(quoteLabelValues bool) string {
   105  	var labelStrs [3]string
   106  	for i, kv := range td.labels {
   107  		if kv[0] == "" {
   108  			continue
   109  		}
   110  		if quoteLabelValues {
   111  			labelStrs[i] = fmt.Sprintf("{%s=\"%s\"}", kv[0], kv[1])
   112  		} else {
   113  			labelStrs[i] = fmt.Sprintf("{%s=%s}", kv[0], kv[1])
   114  		}
   115  	}
   116  
   117  	var latencyStrs []string
   118  	for _, f := range td.lat {
   119  		latencyStrs = append(latencyStrs, fmt.Sprintf("%f", f))
   120  	}
   121  	payloadLines := []string{
   122  		fmt.Sprintf("op_latency%s %s", labelStrs[0], strings.Join(latencyStrs, ",")),
   123  		fmt.Sprintf("time_to_running%s %f", labelStrs[1], td.varA),
   124  		fmt.Sprintf("time_to_ssh%s %f", labelStrs[2], td.varB),
   125  	}
   126  	return strings.Join(payloadLines, "\n")
   127  }
   128  
   129  func testAggregatedPayloadMetrics(t *testing.T, em *metrics.EventMetrics, td, etd *testData) {
   130  	t.Helper()
   131  
   132  	expectedEM := etd.aggregatedEM(em.Timestamp)
   133  	if em.String() != expectedEM.String() {
   134  		t.Errorf("Output metrics not aggregated correctly:\nGot:      %s\nExpected: %s", em.String(), expectedEM.String())
   135  	}
   136  }
   137  
   138  func testPayloadMetrics(t *testing.T, p *Parser, etd *testData) {
   139  	t.Helper()
   140  
   141  	ems := p.PayloadMetrics(etd.testPayload(false), testTarget)
   142  	expectedMetrics := etd.multiEM(ems[0].Timestamp)
   143  	for i, em := range ems {
   144  		if em.String() != expectedMetrics[i].String() {
   145  			t.Errorf("Output metrics not aggregated correctly:\nGot:      %s\nExpected: %s", em.String(), expectedMetrics[i].String())
   146  		}
   147  	}
   148  
   149  	// Test with quoted label values
   150  	ems = p.PayloadMetrics(etd.testPayload(true), testTarget)
   151  	expectedMetrics = etd.multiEM(ems[0].Timestamp)
   152  	for i, em := range ems {
   153  		if em.String() != expectedMetrics[i].String() {
   154  			t.Errorf("Output metrics not aggregated correctly:\nGot:      %s\nExpected: %s", em.String(), expectedMetrics[i].String())
   155  		}
   156  	}
   157  }
   158  
   159  func TestAggreagateInCloudprober(t *testing.T) {
   160  	p := parserForTest(t, true)
   161  
   162  	// First payload
   163  	td := &testData{10, 30, []float64{3.1, 4.0, 13}, [3][2]string{}}
   164  	em := p.AggregatedPayloadMetrics(nil, td.testPayload(false), testTarget)
   165  
   166  	testAggregatedPayloadMetrics(t, em, td, td)
   167  
   168  	// Send another payload, cloudprober should aggregate the metrics.
   169  	oldtd := td
   170  	td = &testData{
   171  		varA: 8,
   172  		varB: 45,
   173  		lat:  []float64{6, 14.1, 2.1},
   174  	}
   175  	etd := &testData{
   176  		varA: oldtd.varA + td.varA,
   177  		varB: oldtd.varB + td.varB,
   178  		lat:  append(oldtd.lat, td.lat...),
   179  	}
   180  
   181  	em = p.AggregatedPayloadMetrics(em, td.testPayload(false), testTarget)
   182  	testAggregatedPayloadMetrics(t, em, td, etd)
   183  }
   184  
   185  func TestNoAggregation(t *testing.T) {
   186  	p := parserForTest(t, false)
   187  
   188  	// First payload
   189  	td := &testData{
   190  		varA: 10,
   191  		varB: 30,
   192  		lat:  []float64{3.1, 4.0, 13},
   193  		labels: [3][2]string{
   194  			[2]string{"l1", "v1"},
   195  			[2]string{"l2", "v2"},
   196  			[2]string{"l3", "v3"},
   197  		},
   198  	}
   199  	testPayloadMetrics(t, p, td)
   200  
   201  	// Send another payload, cloudprober should not aggregate the metrics.
   202  	td = &testData{
   203  		varA: 8,
   204  		varB: 45,
   205  		lat:  []float64{6, 14.1, 2.1},
   206  		labels: [3][2]string{
   207  			[2]string{"l1", "v1"},
   208  			[2]string{"l2", "v2"},
   209  			[2]string{"l3", "v3"},
   210  		},
   211  	}
   212  	testPayloadMetrics(t, p, td)
   213  }
   214  
   215  func TestMetricValueLabels(t *testing.T) {
   216  	tests := []struct {
   217  		desc    string
   218  		line    string
   219  		metric  string
   220  		labels  [][2]string
   221  		value   string
   222  		wantErr bool
   223  	}{
   224  		{
   225  			desc:   "metric with no labels",
   226  			line:   "total 56",
   227  			metric: "total",
   228  			value:  "56",
   229  		},
   230  		{
   231  			desc:   "metric with no labels, but more spaces",
   232  			line:   "total   56",
   233  			metric: "total",
   234  			value:  "56",
   235  		},
   236  		{
   237  			desc:   "standard metric with labels",
   238  			line:   "total{service=serviceA,dc=xx} 56",
   239  			metric: "total",
   240  			labels: [][2]string{
   241  				[2]string{"service", "serviceA"},
   242  				[2]string{"dc", "xx"},
   243  			},
   244  			value: "56",
   245  		},
   246  		{
   247  			desc:   "quoted labels, same result",
   248  			line:   "total{service=\"serviceA\",dc=\"xx\"} 56",
   249  			metric: "total",
   250  			labels: [][2]string{
   251  				[2]string{"service", "serviceA"},
   252  				[2]string{"dc", "xx"},
   253  			},
   254  			value: "56",
   255  		},
   256  		{
   257  			desc:   "a label value has space and more spaces",
   258  			line:   "total{service=\"service A\", dc= \"xx\"} 56",
   259  			metric: "total",
   260  			labels: [][2]string{
   261  				[2]string{"service", "service A"},
   262  				[2]string{"dc", "xx"},
   263  			},
   264  			value: "56",
   265  		},
   266  		{
   267  			desc:   "label and value have a space",
   268  			line:   "version{service=\"service A\",dc=xx} \"version 1.5\"",
   269  			metric: "version",
   270  			labels: [][2]string{
   271  				[2]string{"service", "service A"},
   272  				[2]string{"dc", "xx"},
   273  			},
   274  			value: "\"version 1.5\"",
   275  		},
   276  		{
   277  			desc: "only one brace, invalid line",
   278  			line: "total{service=\"service A\",dc=\"xx\" 56",
   279  		},
   280  	}
   281  
   282  	p := &Parser{}
   283  
   284  	for _, test := range tests {
   285  		t.Run(test.desc, func(t *testing.T) {
   286  			m, v, l := p.metricValueLabels(test.line)
   287  			if m != test.metric {
   288  				t.Errorf("Metric name: got=%s, wanted=%s", m, test.metric)
   289  			}
   290  			if v != test.value {
   291  				t.Errorf("Metric value: got=%s, wanted=%s", v, test.value)
   292  			}
   293  			if !reflect.DeepEqual(l, test.labels) {
   294  				t.Errorf("Metric labels: got=%v, wanted=%v", l, test.labels)
   295  			}
   296  		})
   297  	}
   298  }
   299  
   300  func BenchmarkMetricValueLabels(b *testing.B) {
   301  	payload := []string{
   302  		"total 50",
   303  		"total   56",
   304  		"total{service=serviceA,dc=xx} 56",
   305  		"total{service=\"serviceA\",dc=\"xx\"} 56",
   306  		"version{service=\"service A\",dc=xx} \"version 1.5\"",
   307  	}
   308  	// run the em.String() function b.N times
   309  	for n := 0; n < b.N; n++ {
   310  		p := &Parser{}
   311  		for _, s := range payload {
   312  			p.metricValueLabels(s)
   313  		}
   314  	}
   315  }