1// Copyright 2014 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 14// Copyright (c) 2013, The Prometheus Authors 15// All rights reserved. 16// 17// Use of this source code is governed by a BSD-style license that can be found 18// in the LICENSE file. 19 20package prometheus_test 21 22import ( 23 "bytes" 24 "net/http" 25 "net/http/httptest" 26 "testing" 27 28 dto "github.com/prometheus/client_model/go" 29 30 "github.com/golang/protobuf/proto" 31 "github.com/prometheus/common/expfmt" 32 33 "github.com/prometheus/client_golang/prometheus" 34 "github.com/prometheus/client_golang/prometheus/promhttp" 35) 36 37func testHandler(t testing.TB) { 38 39 metricVec := prometheus.NewCounterVec( 40 prometheus.CounterOpts{ 41 Name: "name", 42 Help: "docstring", 43 ConstLabels: prometheus.Labels{"constname": "constvalue"}, 44 }, 45 []string{"labelname"}, 46 ) 47 48 metricVec.WithLabelValues("val1").Inc() 49 metricVec.WithLabelValues("val2").Inc() 50 51 externalMetricFamily := &dto.MetricFamily{ 52 Name: proto.String("externalname"), 53 Help: proto.String("externaldocstring"), 54 Type: dto.MetricType_COUNTER.Enum(), 55 Metric: []*dto.Metric{ 56 { 57 Label: []*dto.LabelPair{ 58 { 59 Name: proto.String("externalconstname"), 60 Value: proto.String("externalconstvalue"), 61 }, 62 { 63 Name: proto.String("externallabelname"), 64 Value: proto.String("externalval1"), 65 }, 66 }, 67 Counter: &dto.Counter{ 68 Value: proto.Float64(1), 69 }, 70 }, 71 }, 72 } 73 externalBuf := &bytes.Buffer{} 74 enc := expfmt.NewEncoder(externalBuf, expfmt.FmtProtoDelim) 75 if err := enc.Encode(externalMetricFamily); err != nil { 76 t.Fatal(err) 77 } 78 externalMetricFamilyAsBytes := externalBuf.Bytes() 79 externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring 80# TYPE externalname counter 81externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1 82`) 83 externalMetricFamilyAsProtoText := []byte(`name: "externalname" 84help: "externaldocstring" 85type: COUNTER 86metric: < 87 label: < 88 name: "externalconstname" 89 value: "externalconstvalue" 90 > 91 label: < 92 name: "externallabelname" 93 value: "externalval1" 94 > 95 counter: < 96 value: 1 97 > 98> 99 100`) 101 externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externalconstname" value:"externalconstvalue" > label:<name:"externallabelname" value:"externalval1" > counter:<value:1 > > 102`) 103 104 expectedMetricFamily := &dto.MetricFamily{ 105 Name: proto.String("name"), 106 Help: proto.String("docstring"), 107 Type: dto.MetricType_COUNTER.Enum(), 108 Metric: []*dto.Metric{ 109 { 110 Label: []*dto.LabelPair{ 111 { 112 Name: proto.String("constname"), 113 Value: proto.String("constvalue"), 114 }, 115 { 116 Name: proto.String("labelname"), 117 Value: proto.String("val1"), 118 }, 119 }, 120 Counter: &dto.Counter{ 121 Value: proto.Float64(1), 122 }, 123 }, 124 { 125 Label: []*dto.LabelPair{ 126 { 127 Name: proto.String("constname"), 128 Value: proto.String("constvalue"), 129 }, 130 { 131 Name: proto.String("labelname"), 132 Value: proto.String("val2"), 133 }, 134 }, 135 Counter: &dto.Counter{ 136 Value: proto.Float64(1), 137 }, 138 }, 139 }, 140 } 141 buf := &bytes.Buffer{} 142 enc = expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 143 if err := enc.Encode(expectedMetricFamily); err != nil { 144 t.Fatal(err) 145 } 146 expectedMetricFamilyAsBytes := buf.Bytes() 147 expectedMetricFamilyAsText := []byte(`# HELP name docstring 148# TYPE name counter 149name{constname="constvalue",labelname="val1"} 1 150name{constname="constvalue",labelname="val2"} 1 151`) 152 expectedMetricFamilyAsProtoText := []byte(`name: "name" 153help: "docstring" 154type: COUNTER 155metric: < 156 label: < 157 name: "constname" 158 value: "constvalue" 159 > 160 label: < 161 name: "labelname" 162 value: "val1" 163 > 164 counter: < 165 value: 1 166 > 167> 168metric: < 169 label: < 170 name: "constname" 171 value: "constvalue" 172 > 173 label: < 174 name: "labelname" 175 value: "val2" 176 > 177 counter: < 178 value: 1 179 > 180> 181 182`) 183 expectedMetricFamilyAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > > 184`) 185 186 externalMetricFamilyWithSameName := &dto.MetricFamily{ 187 Name: proto.String("name"), 188 Help: proto.String("docstring"), 189 Type: dto.MetricType_COUNTER.Enum(), 190 Metric: []*dto.Metric{ 191 { 192 Label: []*dto.LabelPair{ 193 { 194 Name: proto.String("constname"), 195 Value: proto.String("constvalue"), 196 }, 197 { 198 Name: proto.String("labelname"), 199 Value: proto.String("different_val"), 200 }, 201 }, 202 Counter: &dto.Counter{ 203 Value: proto.Float64(42), 204 }, 205 }, 206 }, 207 } 208 209 expectedMetricFamilyMergedWithExternalAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"different_val" > counter:<value:42 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > > 210`) 211 212 externalMetricFamilyWithInvalidLabelValue := &dto.MetricFamily{ 213 Name: proto.String("name"), 214 Help: proto.String("docstring"), 215 Type: dto.MetricType_COUNTER.Enum(), 216 Metric: []*dto.Metric{ 217 { 218 Label: []*dto.LabelPair{ 219 { 220 Name: proto.String("constname"), 221 Value: proto.String("\xFF"), 222 }, 223 { 224 Name: proto.String("labelname"), 225 Value: proto.String("different_val"), 226 }, 227 }, 228 Counter: &dto.Counter{ 229 Value: proto.Float64(42), 230 }, 231 }, 232 }, 233 } 234 235 expectedMetricFamilyInvalidLabelValueAsText := []byte(`An error has occurred during metrics gathering: 236 237collected metric's label constname is not utf8: "\xff" 238`) 239 240 type output struct { 241 headers map[string]string 242 body []byte 243 } 244 245 var scenarios = []struct { 246 headers map[string]string 247 out output 248 collector prometheus.Collector 249 externalMF []*dto.MetricFamily 250 }{ 251 { // 0 252 headers: map[string]string{ 253 "Accept": "foo/bar;q=0.2, dings/bums;q=0.8", 254 }, 255 out: output{ 256 headers: map[string]string{ 257 "Content-Type": `text/plain; version=0.0.4`, 258 }, 259 body: []byte{}, 260 }, 261 }, 262 { // 1 263 headers: map[string]string{ 264 "Accept": "foo/bar;q=0.2, application/quark;q=0.8", 265 }, 266 out: output{ 267 headers: map[string]string{ 268 "Content-Type": `text/plain; version=0.0.4`, 269 }, 270 body: []byte{}, 271 }, 272 }, 273 { // 2 274 headers: map[string]string{ 275 "Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.8", 276 }, 277 out: output{ 278 headers: map[string]string{ 279 "Content-Type": `text/plain; version=0.0.4`, 280 }, 281 body: []byte{}, 282 }, 283 }, 284 { // 3 285 headers: map[string]string{ 286 "Accept": "text/plain;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8", 287 }, 288 out: output{ 289 headers: map[string]string{ 290 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 291 }, 292 body: []byte{}, 293 }, 294 }, 295 { // 4 296 headers: map[string]string{ 297 "Accept": "application/json", 298 }, 299 out: output{ 300 headers: map[string]string{ 301 "Content-Type": `text/plain; version=0.0.4`, 302 }, 303 body: expectedMetricFamilyAsText, 304 }, 305 collector: metricVec, 306 }, 307 { // 5 308 headers: map[string]string{ 309 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", 310 }, 311 out: output{ 312 headers: map[string]string{ 313 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 314 }, 315 body: expectedMetricFamilyAsBytes, 316 }, 317 collector: metricVec, 318 }, 319 { // 6 320 headers: map[string]string{ 321 "Accept": "application/json", 322 }, 323 out: output{ 324 headers: map[string]string{ 325 "Content-Type": `text/plain; version=0.0.4`, 326 }, 327 body: externalMetricFamilyAsText, 328 }, 329 externalMF: []*dto.MetricFamily{externalMetricFamily}, 330 }, 331 { // 7 332 headers: map[string]string{ 333 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", 334 }, 335 out: output{ 336 headers: map[string]string{ 337 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 338 }, 339 body: externalMetricFamilyAsBytes, 340 }, 341 externalMF: []*dto.MetricFamily{externalMetricFamily}, 342 }, 343 { // 8 344 headers: map[string]string{ 345 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", 346 }, 347 out: output{ 348 headers: map[string]string{ 349 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 350 }, 351 body: bytes.Join( 352 [][]byte{ 353 externalMetricFamilyAsBytes, 354 expectedMetricFamilyAsBytes, 355 }, 356 []byte{}, 357 ), 358 }, 359 collector: metricVec, 360 externalMF: []*dto.MetricFamily{externalMetricFamily}, 361 }, 362 { // 9 363 headers: map[string]string{ 364 "Accept": "text/plain", 365 }, 366 out: output{ 367 headers: map[string]string{ 368 "Content-Type": `text/plain; version=0.0.4`, 369 }, 370 body: []byte{}, 371 }, 372 }, 373 { // 10 374 headers: map[string]string{ 375 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5", 376 }, 377 out: output{ 378 headers: map[string]string{ 379 "Content-Type": `text/plain; version=0.0.4`, 380 }, 381 body: expectedMetricFamilyAsText, 382 }, 383 collector: metricVec, 384 }, 385 { // 11 386 headers: map[string]string{ 387 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5;version=0.0.4", 388 }, 389 out: output{ 390 headers: map[string]string{ 391 "Content-Type": `text/plain; version=0.0.4`, 392 }, 393 body: bytes.Join( 394 [][]byte{ 395 externalMetricFamilyAsText, 396 expectedMetricFamilyAsText, 397 }, 398 []byte{}, 399 ), 400 }, 401 collector: metricVec, 402 externalMF: []*dto.MetricFamily{externalMetricFamily}, 403 }, 404 { // 12 405 headers: map[string]string{ 406 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.2, text/plain;q=0.5;version=0.0.2", 407 }, 408 out: output{ 409 headers: map[string]string{ 410 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 411 }, 412 body: bytes.Join( 413 [][]byte{ 414 externalMetricFamilyAsBytes, 415 expectedMetricFamilyAsBytes, 416 }, 417 []byte{}, 418 ), 419 }, 420 collector: metricVec, 421 externalMF: []*dto.MetricFamily{externalMetricFamily}, 422 }, 423 { // 13 424 headers: map[string]string{ 425 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=text;q=0.5, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.4", 426 }, 427 out: output{ 428 headers: map[string]string{ 429 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`, 430 }, 431 body: bytes.Join( 432 [][]byte{ 433 externalMetricFamilyAsProtoText, 434 expectedMetricFamilyAsProtoText, 435 }, 436 []byte{}, 437 ), 438 }, 439 collector: metricVec, 440 externalMF: []*dto.MetricFamily{externalMetricFamily}, 441 }, 442 { // 14 443 headers: map[string]string{ 444 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", 445 }, 446 out: output{ 447 headers: map[string]string{ 448 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, 449 }, 450 body: bytes.Join( 451 [][]byte{ 452 externalMetricFamilyAsProtoCompactText, 453 expectedMetricFamilyAsProtoCompactText, 454 }, 455 []byte{}, 456 ), 457 }, 458 collector: metricVec, 459 externalMF: []*dto.MetricFamily{externalMetricFamily}, 460 }, 461 { // 15 462 headers: map[string]string{ 463 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", 464 }, 465 out: output{ 466 headers: map[string]string{ 467 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, 468 }, 469 body: bytes.Join( 470 [][]byte{ 471 externalMetricFamilyAsProtoCompactText, 472 expectedMetricFamilyMergedWithExternalAsProtoCompactText, 473 }, 474 []byte{}, 475 ), 476 }, 477 collector: metricVec, 478 externalMF: []*dto.MetricFamily{ 479 externalMetricFamily, 480 externalMetricFamilyWithSameName, 481 }, 482 }, 483 { // 16 484 headers: map[string]string{ 485 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", 486 }, 487 out: output{ 488 headers: map[string]string{ 489 "Content-Type": `text/plain; charset=utf-8`, 490 }, 491 body: expectedMetricFamilyInvalidLabelValueAsText, 492 }, 493 collector: metricVec, 494 externalMF: []*dto.MetricFamily{ 495 externalMetricFamily, 496 externalMetricFamilyWithInvalidLabelValue, 497 }, 498 }, 499 } 500 for i, scenario := range scenarios { 501 registry := prometheus.NewPedanticRegistry() 502 gatherer := prometheus.Gatherer(registry) 503 if scenario.externalMF != nil { 504 gatherer = prometheus.Gatherers{ 505 registry, 506 prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) { 507 return scenario.externalMF, nil 508 }), 509 } 510 } 511 512 if scenario.collector != nil { 513 registry.Register(scenario.collector) 514 } 515 writer := httptest.NewRecorder() 516 handler := prometheus.InstrumentHandler("prometheus", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})) 517 request, _ := http.NewRequest("GET", "/", nil) 518 for key, value := range scenario.headers { 519 request.Header.Add(key, value) 520 } 521 handler(writer, request) 522 523 for key, value := range scenario.out.headers { 524 if writer.HeaderMap.Get(key) != value { 525 t.Errorf( 526 "%d. expected %q for header %q, got %q", 527 i, value, key, writer.Header().Get(key), 528 ) 529 } 530 } 531 532 if !bytes.Equal(scenario.out.body, writer.Body.Bytes()) { 533 t.Errorf( 534 "%d. expected body:\n%s\ngot body:\n%s\n", 535 i, scenario.out.body, writer.Body.Bytes(), 536 ) 537 } 538 } 539} 540 541func TestHandler(t *testing.T) { 542 testHandler(t) 543} 544 545func BenchmarkHandler(b *testing.B) { 546 for i := 0; i < b.N; i++ { 547 testHandler(b) 548 } 549} 550 551func TestRegisterWithOrGet(t *testing.T) { 552 // Replace the default registerer just to be sure. This is bad, but this 553 // whole test will go away once RegisterOrGet is removed. 554 oldRegisterer := prometheus.DefaultRegisterer 555 defer func() { 556 prometheus.DefaultRegisterer = oldRegisterer 557 }() 558 prometheus.DefaultRegisterer = prometheus.NewRegistry() 559 original := prometheus.NewCounterVec( 560 prometheus.CounterOpts{ 561 Name: "test", 562 Help: "help", 563 }, 564 []string{"foo", "bar"}, 565 ) 566 equalButNotSame := prometheus.NewCounterVec( 567 prometheus.CounterOpts{ 568 Name: "test", 569 Help: "help", 570 }, 571 []string{"foo", "bar"}, 572 ) 573 var err error 574 if err = prometheus.Register(original); err != nil { 575 t.Fatal(err) 576 } 577 if err = prometheus.Register(equalButNotSame); err == nil { 578 t.Fatal("expected error when registringe equal collector") 579 } 580 if are, ok := err.(prometheus.AlreadyRegisteredError); ok { 581 if are.ExistingCollector != original { 582 t.Error("expected original collector but got something else") 583 } 584 if are.ExistingCollector == equalButNotSame { 585 t.Error("expected original callector but got new one") 586 } 587 } else { 588 t.Error("unexpected error:", err) 589 } 590} 591