1// Copyright 2015 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package expfmt 15 16import ( 17 "io" 18 "net/http" 19 "reflect" 20 "sort" 21 "strings" 22 "testing" 23 24 "github.com/golang/protobuf/proto" 25 dto "github.com/prometheus/client_model/go" 26 27 "github.com/prometheus/common/model" 28) 29 30func TestTextDecoder(t *testing.T) { 31 var ( 32 ts = model.Now() 33 in = ` 34# Only a quite simple scenario with two metric families. 35# More complicated tests of the parser itself can be found in the text package. 36# TYPE mf2 counter 37mf2 3 38mf1{label="value1"} -3.14 123456 39mf1{label="value2"} 42 40mf2 4 41` 42 out = model.Vector{ 43 &model.Sample{ 44 Metric: model.Metric{ 45 model.MetricNameLabel: "mf1", 46 "label": "value1", 47 }, 48 Value: -3.14, 49 Timestamp: 123456, 50 }, 51 &model.Sample{ 52 Metric: model.Metric{ 53 model.MetricNameLabel: "mf1", 54 "label": "value2", 55 }, 56 Value: 42, 57 Timestamp: ts, 58 }, 59 &model.Sample{ 60 Metric: model.Metric{ 61 model.MetricNameLabel: "mf2", 62 }, 63 Value: 3, 64 Timestamp: ts, 65 }, 66 &model.Sample{ 67 Metric: model.Metric{ 68 model.MetricNameLabel: "mf2", 69 }, 70 Value: 4, 71 Timestamp: ts, 72 }, 73 } 74 ) 75 76 dec := &SampleDecoder{ 77 Dec: &textDecoder{r: strings.NewReader(in)}, 78 Opts: &DecodeOptions{ 79 Timestamp: ts, 80 }, 81 } 82 var all model.Vector 83 for { 84 var smpls model.Vector 85 err := dec.Decode(&smpls) 86 if err == io.EOF { 87 break 88 } 89 if err != nil { 90 t.Fatal(err) 91 } 92 all = append(all, smpls...) 93 } 94 sort.Sort(all) 95 sort.Sort(out) 96 if !reflect.DeepEqual(all, out) { 97 t.Fatalf("output does not match") 98 } 99} 100 101func TestProtoDecoder(t *testing.T) { 102 103 var testTime = model.Now() 104 105 scenarios := []struct { 106 in string 107 expected model.Vector 108 fail bool 109 }{ 110 { 111 in: "", 112 }, 113 { 114 in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_!abel_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@", 115 fail: true, 116 }, 117 { 118 in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@", 119 expected: model.Vector{ 120 &model.Sample{ 121 Metric: model.Metric{ 122 model.MetricNameLabel: "request_count", 123 "some_label_name": "some_label_value", 124 }, 125 Value: -42, 126 Timestamp: testTime, 127 }, 128 &model.Sample{ 129 Metric: model.Metric{ 130 model.MetricNameLabel: "request_count", 131 "another_label_name": "another_label_value", 132 }, 133 Value: 84, 134 Timestamp: testTime, 135 }, 136 }, 137 }, 138 { 139 in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@", 140 expected: model.Vector{ 141 &model.Sample{ 142 Metric: model.Metric{ 143 model.MetricNameLabel: "request_count_count", 144 "some_label_name": "some_label_value", 145 }, 146 Value: 0, 147 Timestamp: testTime, 148 }, 149 &model.Sample{ 150 Metric: model.Metric{ 151 model.MetricNameLabel: "request_count_sum", 152 "some_label_name": "some_label_value", 153 }, 154 Value: 0, 155 Timestamp: testTime, 156 }, 157 &model.Sample{ 158 Metric: model.Metric{ 159 model.MetricNameLabel: "request_count", 160 "some_label_name": "some_label_value", 161 "quantile": "0.99", 162 }, 163 Value: -42, 164 Timestamp: testTime, 165 }, 166 &model.Sample{ 167 Metric: model.Metric{ 168 model.MetricNameLabel: "request_count", 169 "some_label_name": "some_label_value", 170 "quantile": "0.999", 171 }, 172 Value: -84, 173 Timestamp: testTime, 174 }, 175 &model.Sample{ 176 Metric: model.Metric{ 177 model.MetricNameLabel: "request_count_count", 178 "another_label_name": "another_label_value", 179 }, 180 Value: 0, 181 Timestamp: testTime, 182 }, 183 &model.Sample{ 184 Metric: model.Metric{ 185 model.MetricNameLabel: "request_count_sum", 186 "another_label_name": "another_label_value", 187 }, 188 Value: 0, 189 Timestamp: testTime, 190 }, 191 &model.Sample{ 192 Metric: model.Metric{ 193 model.MetricNameLabel: "request_count", 194 "another_label_name": "another_label_value", 195 "quantile": "0.5", 196 }, 197 Value: 10, 198 Timestamp: testTime, 199 }, 200 }, 201 }, 202 { 203 in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f", 204 expected: model.Vector{ 205 &model.Sample{ 206 Metric: model.Metric{ 207 model.MetricNameLabel: "request_duration_microseconds_bucket", 208 "le": "100", 209 }, 210 Value: 123, 211 Timestamp: testTime, 212 }, 213 &model.Sample{ 214 Metric: model.Metric{ 215 model.MetricNameLabel: "request_duration_microseconds_bucket", 216 "le": "120", 217 }, 218 Value: 412, 219 Timestamp: testTime, 220 }, 221 &model.Sample{ 222 Metric: model.Metric{ 223 model.MetricNameLabel: "request_duration_microseconds_bucket", 224 "le": "144", 225 }, 226 Value: 592, 227 Timestamp: testTime, 228 }, 229 &model.Sample{ 230 Metric: model.Metric{ 231 model.MetricNameLabel: "request_duration_microseconds_bucket", 232 "le": "172.8", 233 }, 234 Value: 1524, 235 Timestamp: testTime, 236 }, 237 &model.Sample{ 238 Metric: model.Metric{ 239 model.MetricNameLabel: "request_duration_microseconds_bucket", 240 "le": "+Inf", 241 }, 242 Value: 2693, 243 Timestamp: testTime, 244 }, 245 &model.Sample{ 246 Metric: model.Metric{ 247 model.MetricNameLabel: "request_duration_microseconds_sum", 248 }, 249 Value: 1756047.3, 250 Timestamp: testTime, 251 }, 252 &model.Sample{ 253 Metric: model.Metric{ 254 model.MetricNameLabel: "request_duration_microseconds_count", 255 }, 256 Value: 2693, 257 Timestamp: testTime, 258 }, 259 }, 260 }, 261 { 262 // The metric type is unset in this protobuf, which needs to be handled 263 // correctly by the decoder. 264 in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?", 265 expected: model.Vector{ 266 &model.Sample{ 267 Metric: model.Metric{ 268 model.MetricNameLabel: "request_count", 269 }, 270 Value: 1, 271 Timestamp: testTime, 272 }, 273 }, 274 }, 275 } 276 277 for i, scenario := range scenarios { 278 dec := &SampleDecoder{ 279 Dec: &protoDecoder{r: strings.NewReader(scenario.in)}, 280 Opts: &DecodeOptions{ 281 Timestamp: testTime, 282 }, 283 } 284 285 var all model.Vector 286 for { 287 var smpls model.Vector 288 err := dec.Decode(&smpls) 289 if err == io.EOF { 290 break 291 } 292 if scenario.fail { 293 if err == nil { 294 t.Fatal("Expected error but got none") 295 } 296 break 297 } 298 if err != nil { 299 t.Fatal(err) 300 } 301 all = append(all, smpls...) 302 } 303 sort.Sort(all) 304 sort.Sort(scenario.expected) 305 if !reflect.DeepEqual(all, scenario.expected) { 306 t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all) 307 } 308 } 309} 310 311func testDiscriminatorHTTPHeader(t testing.TB) { 312 var scenarios = []struct { 313 input map[string]string 314 output Format 315 err error 316 }{ 317 { 318 input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`}, 319 output: FmtProtoDelim, 320 }, 321 { 322 input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`}, 323 output: FmtUnknown, 324 }, 325 { 326 input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`}, 327 output: FmtUnknown, 328 }, 329 { 330 input: map[string]string{"Content-Type": `text/plain; version=0.0.4`}, 331 output: FmtText, 332 }, 333 { 334 input: map[string]string{"Content-Type": `text/plain`}, 335 output: FmtText, 336 }, 337 { 338 input: map[string]string{"Content-Type": `text/plain; version=0.0.3`}, 339 output: FmtUnknown, 340 }, 341 } 342 343 for i, scenario := range scenarios { 344 var header http.Header 345 346 if len(scenario.input) > 0 { 347 header = http.Header{} 348 } 349 350 for key, value := range scenario.input { 351 header.Add(key, value) 352 } 353 354 actual := ResponseFormat(header) 355 356 if scenario.output != actual { 357 t.Errorf("%d. expected %s, got %s", i, scenario.output, actual) 358 } 359 } 360} 361 362func TestDiscriminatorHTTPHeader(t *testing.T) { 363 testDiscriminatorHTTPHeader(t) 364} 365 366func BenchmarkDiscriminatorHTTPHeader(b *testing.B) { 367 for i := 0; i < b.N; i++ { 368 testDiscriminatorHTTPHeader(b) 369 } 370} 371 372func TestExtractSamples(t *testing.T) { 373 var ( 374 goodMetricFamily1 = &dto.MetricFamily{ 375 Name: proto.String("foo"), 376 Help: proto.String("Help for foo."), 377 Type: dto.MetricType_COUNTER.Enum(), 378 Metric: []*dto.Metric{ 379 &dto.Metric{ 380 Counter: &dto.Counter{ 381 Value: proto.Float64(4711), 382 }, 383 }, 384 }, 385 } 386 goodMetricFamily2 = &dto.MetricFamily{ 387 Name: proto.String("bar"), 388 Help: proto.String("Help for bar."), 389 Type: dto.MetricType_GAUGE.Enum(), 390 Metric: []*dto.Metric{ 391 &dto.Metric{ 392 Gauge: &dto.Gauge{ 393 Value: proto.Float64(3.14), 394 }, 395 }, 396 }, 397 } 398 badMetricFamily = &dto.MetricFamily{ 399 Name: proto.String("bad"), 400 Help: proto.String("Help for bad."), 401 Type: dto.MetricType(42).Enum(), 402 Metric: []*dto.Metric{ 403 &dto.Metric{ 404 Gauge: &dto.Gauge{ 405 Value: proto.Float64(2.7), 406 }, 407 }, 408 }, 409 } 410 411 opts = &DecodeOptions{ 412 Timestamp: 42, 413 } 414 ) 415 416 got, err := ExtractSamples(opts, goodMetricFamily1, goodMetricFamily2) 417 if err != nil { 418 t.Error("Unexpected error from ExtractSamples:", err) 419 } 420 want := model.Vector{ 421 &model.Sample{Metric: model.Metric{model.MetricNameLabel: "foo"}, Value: 4711, Timestamp: 42}, 422 &model.Sample{Metric: model.Metric{model.MetricNameLabel: "bar"}, Value: 3.14, Timestamp: 42}, 423 } 424 if !reflect.DeepEqual(got, want) { 425 t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want) 426 } 427 428 got, err = ExtractSamples(opts, goodMetricFamily1, badMetricFamily, goodMetricFamily2) 429 if err == nil { 430 t.Error("Expected error from ExtractSamples") 431 } 432 if !reflect.DeepEqual(got, want) { 433 t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want) 434 } 435} 436