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 }