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 "fmt" 25 "io/ioutil" 26 "math/rand" 27 "net/http" 28 "net/http/httptest" 29 "os" 30 "sync" 31 "testing" 32 "time" 33 34 dto "github.com/prometheus/client_model/go" 35 36 //lint:ignore SA1019 Need to keep deprecated package for compatibility. 37 "github.com/golang/protobuf/proto" 38 "github.com/prometheus/common/expfmt" 39 40 "github.com/prometheus/client_golang/prometheus" 41 "github.com/prometheus/client_golang/prometheus/promhttp" 42) 43 44// uncheckedCollector wraps a Collector but its Describe method yields no Desc. 45type uncheckedCollector struct { 46 c prometheus.Collector 47} 48 49func (u uncheckedCollector) Describe(_ chan<- *prometheus.Desc) {} 50func (u uncheckedCollector) Collect(c chan<- prometheus.Metric) { 51 u.c.Collect(c) 52} 53 54func testHandler(t testing.TB) { 55 // TODO(beorn7): This test is a bit too "end-to-end". It tests quite a 56 // few moving parts that are not strongly coupled. They could/should be 57 // tested separately. However, the changes planned for v2 will 58 // require a major rework of this test anyway, at which time I will 59 // structure it in a better way. 60 61 metricVec := prometheus.NewCounterVec( 62 prometheus.CounterOpts{ 63 Name: "name", 64 Help: "docstring", 65 ConstLabels: prometheus.Labels{"constname": "constvalue"}, 66 }, 67 []string{"labelname"}, 68 ) 69 70 metricVec.WithLabelValues("val1").Inc() 71 metricVec.WithLabelValues("val2").Inc() 72 73 externalMetricFamily := &dto.MetricFamily{ 74 Name: proto.String("externalname"), 75 Help: proto.String("externaldocstring"), 76 Type: dto.MetricType_COUNTER.Enum(), 77 Metric: []*dto.Metric{ 78 { 79 Label: []*dto.LabelPair{ 80 { 81 Name: proto.String("externalconstname"), 82 Value: proto.String("externalconstvalue"), 83 }, 84 { 85 Name: proto.String("externallabelname"), 86 Value: proto.String("externalval1"), 87 }, 88 }, 89 Counter: &dto.Counter{ 90 Value: proto.Float64(1), 91 }, 92 }, 93 }, 94 } 95 externalBuf := &bytes.Buffer{} 96 enc := expfmt.NewEncoder(externalBuf, expfmt.FmtProtoDelim) 97 if err := enc.Encode(externalMetricFamily); err != nil { 98 t.Fatal(err) 99 } 100 externalMetricFamilyAsBytes := externalBuf.Bytes() 101 externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring 102# TYPE externalname counter 103externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1 104`) 105 externalMetricFamilyAsProtoText := []byte(`name: "externalname" 106help: "externaldocstring" 107type: COUNTER 108metric: < 109 label: < 110 name: "externalconstname" 111 value: "externalconstvalue" 112 > 113 label: < 114 name: "externallabelname" 115 value: "externalval1" 116 > 117 counter: < 118 value: 1 119 > 120> 121 122`) 123 externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externalconstname" value:"externalconstvalue" > label:<name:"externallabelname" value:"externalval1" > counter:<value:1 > > 124`) 125 126 expectedMetricFamily := &dto.MetricFamily{ 127 Name: proto.String("name"), 128 Help: proto.String("docstring"), 129 Type: dto.MetricType_COUNTER.Enum(), 130 Metric: []*dto.Metric{ 131 { 132 Label: []*dto.LabelPair{ 133 { 134 Name: proto.String("constname"), 135 Value: proto.String("constvalue"), 136 }, 137 { 138 Name: proto.String("labelname"), 139 Value: proto.String("val1"), 140 }, 141 }, 142 Counter: &dto.Counter{ 143 Value: proto.Float64(1), 144 }, 145 }, 146 { 147 Label: []*dto.LabelPair{ 148 { 149 Name: proto.String("constname"), 150 Value: proto.String("constvalue"), 151 }, 152 { 153 Name: proto.String("labelname"), 154 Value: proto.String("val2"), 155 }, 156 }, 157 Counter: &dto.Counter{ 158 Value: proto.Float64(1), 159 }, 160 }, 161 }, 162 } 163 buf := &bytes.Buffer{} 164 enc = expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 165 if err := enc.Encode(expectedMetricFamily); err != nil { 166 t.Fatal(err) 167 } 168 expectedMetricFamilyAsBytes := buf.Bytes() 169 expectedMetricFamilyAsText := []byte(`# HELP name docstring 170# TYPE name counter 171name{constname="constvalue",labelname="val1"} 1 172name{constname="constvalue",labelname="val2"} 1 173`) 174 expectedMetricFamilyAsProtoText := []byte(`name: "name" 175help: "docstring" 176type: COUNTER 177metric: < 178 label: < 179 name: "constname" 180 value: "constvalue" 181 > 182 label: < 183 name: "labelname" 184 value: "val1" 185 > 186 counter: < 187 value: 1 188 > 189> 190metric: < 191 label: < 192 name: "constname" 193 value: "constvalue" 194 > 195 label: < 196 name: "labelname" 197 value: "val2" 198 > 199 counter: < 200 value: 1 201 > 202> 203 204`) 205 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 > > 206`) 207 208 externalMetricFamilyWithSameName := &dto.MetricFamily{ 209 Name: proto.String("name"), 210 Help: proto.String("docstring"), 211 Type: dto.MetricType_COUNTER.Enum(), 212 Metric: []*dto.Metric{ 213 { 214 Label: []*dto.LabelPair{ 215 { 216 Name: proto.String("constname"), 217 Value: proto.String("constvalue"), 218 }, 219 { 220 Name: proto.String("labelname"), 221 Value: proto.String("different_val"), 222 }, 223 }, 224 Counter: &dto.Counter{ 225 Value: proto.Float64(42), 226 }, 227 }, 228 }, 229 } 230 231 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 > > 232`) 233 234 externalMetricFamilyWithInvalidLabelValue := &dto.MetricFamily{ 235 Name: proto.String("name"), 236 Help: proto.String("docstring"), 237 Type: dto.MetricType_COUNTER.Enum(), 238 Metric: []*dto.Metric{ 239 { 240 Label: []*dto.LabelPair{ 241 { 242 Name: proto.String("constname"), 243 Value: proto.String("\xFF"), 244 }, 245 { 246 Name: proto.String("labelname"), 247 Value: proto.String("different_val"), 248 }, 249 }, 250 Counter: &dto.Counter{ 251 Value: proto.Float64(42), 252 }, 253 }, 254 }, 255 } 256 257 expectedMetricFamilyInvalidLabelValueAsText := []byte(`An error has occurred while serving metrics: 258 259collected metric "name" { label:<name:"constname" value:"\377" > label:<name:"labelname" value:"different_val" > counter:<value:42 > } has a label named "constname" whose value is not utf8: "\xff" 260`) 261 262 summary := prometheus.NewSummary(prometheus.SummaryOpts{ 263 Name: "complex", 264 Help: "A metric to check collisions with _sum and _count.", 265 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 266 }) 267 summaryAsText := []byte(`# HELP complex A metric to check collisions with _sum and _count. 268# TYPE complex summary 269complex{quantile="0.5"} NaN 270complex{quantile="0.9"} NaN 271complex{quantile="0.99"} NaN 272complex_sum 0 273complex_count 0 274`) 275 histogram := prometheus.NewHistogram(prometheus.HistogramOpts{ 276 Name: "complex", 277 Help: "A metric to check collisions with _sun, _count, and _bucket.", 278 }) 279 externalMetricFamilyWithBucketSuffix := &dto.MetricFamily{ 280 Name: proto.String("complex_bucket"), 281 Help: proto.String("externaldocstring"), 282 Type: dto.MetricType_COUNTER.Enum(), 283 Metric: []*dto.Metric{ 284 { 285 Counter: &dto.Counter{ 286 Value: proto.Float64(1), 287 }, 288 }, 289 }, 290 } 291 externalMetricFamilyWithBucketSuffixAsText := []byte(`# HELP complex_bucket externaldocstring 292# TYPE complex_bucket counter 293complex_bucket 1 294`) 295 externalMetricFamilyWithCountSuffix := &dto.MetricFamily{ 296 Name: proto.String("complex_count"), 297 Help: proto.String("externaldocstring"), 298 Type: dto.MetricType_COUNTER.Enum(), 299 Metric: []*dto.Metric{ 300 { 301 Counter: &dto.Counter{ 302 Value: proto.Float64(1), 303 }, 304 }, 305 }, 306 } 307 bucketCollisionMsg := []byte(`An error has occurred while serving metrics: 308 309collected metric named "complex_bucket" collides with previously collected histogram named "complex" 310`) 311 summaryCountCollisionMsg := []byte(`An error has occurred while serving metrics: 312 313collected metric named "complex_count" collides with previously collected summary named "complex" 314`) 315 histogramCountCollisionMsg := []byte(`An error has occurred while serving metrics: 316 317collected metric named "complex_count" collides with previously collected histogram named "complex" 318`) 319 externalMetricFamilyWithDuplicateLabel := &dto.MetricFamily{ 320 Name: proto.String("broken_metric"), 321 Help: proto.String("The registry should detect the duplicate label."), 322 Type: dto.MetricType_COUNTER.Enum(), 323 Metric: []*dto.Metric{ 324 { 325 Label: []*dto.LabelPair{ 326 { 327 Name: proto.String("foo"), 328 Value: proto.String("bar"), 329 }, 330 { 331 Name: proto.String("foo"), 332 Value: proto.String("baz"), 333 }, 334 }, 335 Counter: &dto.Counter{ 336 Value: proto.Float64(2.7), 337 }, 338 }, 339 }, 340 } 341 duplicateLabelMsg := []byte(`An error has occurred while serving metrics: 342 343collected metric "broken_metric" { label:<name:"foo" value:"bar" > label:<name:"foo" value:"baz" > counter:<value:2.7 > } has two or more labels with the same name: foo 344`) 345 346 type output struct { 347 headers map[string]string 348 body []byte 349 } 350 351 var scenarios = []struct { 352 headers map[string]string 353 out output 354 collector prometheus.Collector 355 externalMF []*dto.MetricFamily 356 }{ 357 { // 0 358 headers: map[string]string{ 359 "Accept": "foo/bar;q=0.2, dings/bums;q=0.8", 360 }, 361 out: output{ 362 headers: map[string]string{ 363 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 364 }, 365 body: []byte{}, 366 }, 367 }, 368 { // 1 369 headers: map[string]string{ 370 "Accept": "foo/bar;q=0.2, application/quark;q=0.8", 371 }, 372 out: output{ 373 headers: map[string]string{ 374 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 375 }, 376 body: []byte{}, 377 }, 378 }, 379 { // 2 380 headers: map[string]string{ 381 "Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.8", 382 }, 383 out: output{ 384 headers: map[string]string{ 385 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 386 }, 387 body: []byte{}, 388 }, 389 }, 390 { // 3 391 headers: map[string]string{ 392 "Accept": "text/plain;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8", 393 }, 394 out: output{ 395 headers: map[string]string{ 396 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 397 }, 398 body: []byte{}, 399 }, 400 }, 401 { // 4 402 headers: map[string]string{ 403 "Accept": "application/json", 404 }, 405 out: output{ 406 headers: map[string]string{ 407 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 408 }, 409 body: expectedMetricFamilyAsText, 410 }, 411 collector: metricVec, 412 }, 413 { // 5 414 headers: map[string]string{ 415 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", 416 }, 417 out: output{ 418 headers: map[string]string{ 419 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 420 }, 421 body: expectedMetricFamilyAsBytes, 422 }, 423 collector: metricVec, 424 }, 425 { // 6 426 headers: map[string]string{ 427 "Accept": "application/json", 428 }, 429 out: output{ 430 headers: map[string]string{ 431 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 432 }, 433 body: externalMetricFamilyAsText, 434 }, 435 externalMF: []*dto.MetricFamily{externalMetricFamily}, 436 }, 437 { // 7 438 headers: map[string]string{ 439 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", 440 }, 441 out: output{ 442 headers: map[string]string{ 443 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 444 }, 445 body: externalMetricFamilyAsBytes, 446 }, 447 externalMF: []*dto.MetricFamily{externalMetricFamily}, 448 }, 449 { // 8 450 headers: map[string]string{ 451 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited", 452 }, 453 out: output{ 454 headers: map[string]string{ 455 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 456 }, 457 body: bytes.Join( 458 [][]byte{ 459 externalMetricFamilyAsBytes, 460 expectedMetricFamilyAsBytes, 461 }, 462 []byte{}, 463 ), 464 }, 465 collector: metricVec, 466 externalMF: []*dto.MetricFamily{externalMetricFamily}, 467 }, 468 { // 9 469 headers: map[string]string{ 470 "Accept": "text/plain", 471 }, 472 out: output{ 473 headers: map[string]string{ 474 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 475 }, 476 body: []byte{}, 477 }, 478 }, 479 { // 10 480 headers: map[string]string{ 481 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5", 482 }, 483 out: output{ 484 headers: map[string]string{ 485 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 486 }, 487 body: expectedMetricFamilyAsText, 488 }, 489 collector: metricVec, 490 }, 491 { // 11 492 headers: map[string]string{ 493 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5;version=0.0.4", 494 }, 495 out: output{ 496 headers: map[string]string{ 497 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 498 }, 499 body: bytes.Join( 500 [][]byte{ 501 externalMetricFamilyAsText, 502 expectedMetricFamilyAsText, 503 }, 504 []byte{}, 505 ), 506 }, 507 collector: metricVec, 508 externalMF: []*dto.MetricFamily{externalMetricFamily}, 509 }, 510 { // 12 511 headers: map[string]string{ 512 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.2, text/plain;q=0.5;version=0.0.2", 513 }, 514 out: output{ 515 headers: map[string]string{ 516 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`, 517 }, 518 body: bytes.Join( 519 [][]byte{ 520 externalMetricFamilyAsBytes, 521 expectedMetricFamilyAsBytes, 522 }, 523 []byte{}, 524 ), 525 }, 526 collector: metricVec, 527 externalMF: []*dto.MetricFamily{externalMetricFamily}, 528 }, 529 { // 13 530 headers: map[string]string{ 531 "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", 532 }, 533 out: output{ 534 headers: map[string]string{ 535 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`, 536 }, 537 body: bytes.Join( 538 [][]byte{ 539 externalMetricFamilyAsProtoText, 540 expectedMetricFamilyAsProtoText, 541 }, 542 []byte{}, 543 ), 544 }, 545 collector: metricVec, 546 externalMF: []*dto.MetricFamily{externalMetricFamily}, 547 }, 548 { // 14 549 headers: map[string]string{ 550 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", 551 }, 552 out: output{ 553 headers: map[string]string{ 554 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, 555 }, 556 body: bytes.Join( 557 [][]byte{ 558 externalMetricFamilyAsProtoCompactText, 559 expectedMetricFamilyAsProtoCompactText, 560 }, 561 []byte{}, 562 ), 563 }, 564 collector: metricVec, 565 externalMF: []*dto.MetricFamily{externalMetricFamily}, 566 }, 567 { // 15 568 headers: map[string]string{ 569 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", 570 }, 571 out: output{ 572 headers: map[string]string{ 573 "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`, 574 }, 575 body: bytes.Join( 576 [][]byte{ 577 externalMetricFamilyAsProtoCompactText, 578 expectedMetricFamilyMergedWithExternalAsProtoCompactText, 579 }, 580 []byte{}, 581 ), 582 }, 583 collector: metricVec, 584 externalMF: []*dto.MetricFamily{ 585 externalMetricFamily, 586 externalMetricFamilyWithSameName, 587 }, 588 }, 589 { // 16 590 headers: map[string]string{ 591 "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text", 592 }, 593 out: output{ 594 headers: map[string]string{ 595 "Content-Type": `text/plain; charset=utf-8`, 596 }, 597 body: expectedMetricFamilyInvalidLabelValueAsText, 598 }, 599 collector: metricVec, 600 externalMF: []*dto.MetricFamily{ 601 externalMetricFamily, 602 externalMetricFamilyWithInvalidLabelValue, 603 }, 604 }, 605 { // 17 606 headers: map[string]string{ 607 "Accept": "text/plain", 608 }, 609 out: output{ 610 headers: map[string]string{ 611 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 612 }, 613 body: expectedMetricFamilyAsText, 614 }, 615 collector: uncheckedCollector{metricVec}, 616 }, 617 { // 18 618 headers: map[string]string{ 619 "Accept": "text/plain", 620 }, 621 out: output{ 622 headers: map[string]string{ 623 "Content-Type": `text/plain; charset=utf-8`, 624 }, 625 body: histogramCountCollisionMsg, 626 }, 627 collector: histogram, 628 externalMF: []*dto.MetricFamily{ 629 externalMetricFamilyWithCountSuffix, 630 }, 631 }, 632 { // 19 633 headers: map[string]string{ 634 "Accept": "text/plain", 635 }, 636 out: output{ 637 headers: map[string]string{ 638 "Content-Type": `text/plain; charset=utf-8`, 639 }, 640 body: bucketCollisionMsg, 641 }, 642 collector: histogram, 643 externalMF: []*dto.MetricFamily{ 644 externalMetricFamilyWithBucketSuffix, 645 }, 646 }, 647 { // 20 648 headers: map[string]string{ 649 "Accept": "text/plain", 650 }, 651 out: output{ 652 headers: map[string]string{ 653 "Content-Type": `text/plain; charset=utf-8`, 654 }, 655 body: summaryCountCollisionMsg, 656 }, 657 collector: summary, 658 externalMF: []*dto.MetricFamily{ 659 externalMetricFamilyWithCountSuffix, 660 }, 661 }, 662 { // 21 663 headers: map[string]string{ 664 "Accept": "text/plain", 665 }, 666 out: output{ 667 headers: map[string]string{ 668 "Content-Type": `text/plain; version=0.0.4; charset=utf-8`, 669 }, 670 body: bytes.Join( 671 [][]byte{ 672 summaryAsText, 673 externalMetricFamilyWithBucketSuffixAsText, 674 }, 675 []byte{}, 676 ), 677 }, 678 collector: summary, 679 externalMF: []*dto.MetricFamily{ 680 externalMetricFamilyWithBucketSuffix, 681 }, 682 }, 683 { // 22 684 headers: map[string]string{ 685 "Accept": "text/plain", 686 }, 687 out: output{ 688 headers: map[string]string{ 689 "Content-Type": `text/plain; charset=utf-8`, 690 }, 691 body: duplicateLabelMsg, 692 }, 693 externalMF: []*dto.MetricFamily{ 694 externalMetricFamilyWithDuplicateLabel, 695 }, 696 }, 697 } 698 for i, scenario := range scenarios { 699 registry := prometheus.NewPedanticRegistry() 700 gatherer := prometheus.Gatherer(registry) 701 if scenario.externalMF != nil { 702 gatherer = prometheus.Gatherers{ 703 registry, 704 prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) { 705 return scenario.externalMF, nil 706 }), 707 } 708 } 709 710 if scenario.collector != nil { 711 registry.MustRegister(scenario.collector) 712 } 713 writer := httptest.NewRecorder() 714 handler := promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}) 715 request, _ := http.NewRequest("GET", "/", nil) 716 for key, value := range scenario.headers { 717 request.Header.Add(key, value) 718 } 719 handler.ServeHTTP(writer, request) 720 721 for key, value := range scenario.out.headers { 722 if writer.Header().Get(key) != value { 723 t.Errorf( 724 "%d. expected %q for header %q, got %q", 725 i, value, key, writer.Header().Get(key), 726 ) 727 } 728 } 729 730 if !bytes.Equal(scenario.out.body, writer.Body.Bytes()) { 731 t.Errorf( 732 "%d. expected body:\n%s\ngot body:\n%s\n", 733 i, scenario.out.body, writer.Body.Bytes(), 734 ) 735 } 736 } 737} 738 739func TestHandler(t *testing.T) { 740 testHandler(t) 741} 742 743func BenchmarkHandler(b *testing.B) { 744 for i := 0; i < b.N; i++ { 745 testHandler(b) 746 } 747} 748 749func TestAlreadyRegistered(t *testing.T) { 750 original := prometheus.NewCounterVec( 751 prometheus.CounterOpts{ 752 Name: "test", 753 Help: "help", 754 ConstLabels: prometheus.Labels{"const": "label"}, 755 }, 756 []string{"foo", "bar"}, 757 ) 758 equalButNotSame := prometheus.NewCounterVec( 759 prometheus.CounterOpts{ 760 Name: "test", 761 Help: "help", 762 ConstLabels: prometheus.Labels{"const": "label"}, 763 }, 764 []string{"foo", "bar"}, 765 ) 766 originalWithoutConstLabel := prometheus.NewCounterVec( 767 prometheus.CounterOpts{ 768 Name: "test", 769 Help: "help", 770 }, 771 []string{"foo", "bar"}, 772 ) 773 equalButNotSameWithoutConstLabel := prometheus.NewCounterVec( 774 prometheus.CounterOpts{ 775 Name: "test", 776 Help: "help", 777 }, 778 []string{"foo", "bar"}, 779 ) 780 781 scenarios := []struct { 782 name string 783 originalCollector prometheus.Collector 784 registerWith func(prometheus.Registerer) prometheus.Registerer 785 newCollector prometheus.Collector 786 reRegisterWith func(prometheus.Registerer) prometheus.Registerer 787 }{ 788 { 789 "RegisterNormallyReregisterNormally", 790 original, 791 func(r prometheus.Registerer) prometheus.Registerer { return r }, 792 equalButNotSame, 793 func(r prometheus.Registerer) prometheus.Registerer { return r }, 794 }, 795 { 796 "RegisterNormallyReregisterWrapped", 797 original, 798 func(r prometheus.Registerer) prometheus.Registerer { return r }, 799 equalButNotSameWithoutConstLabel, 800 func(r prometheus.Registerer) prometheus.Registerer { 801 return prometheus.WrapRegistererWith(prometheus.Labels{"const": "label"}, r) 802 }, 803 }, 804 { 805 "RegisterWrappedReregisterWrapped", 806 originalWithoutConstLabel, 807 func(r prometheus.Registerer) prometheus.Registerer { 808 return prometheus.WrapRegistererWith(prometheus.Labels{"const": "label"}, r) 809 }, 810 equalButNotSameWithoutConstLabel, 811 func(r prometheus.Registerer) prometheus.Registerer { 812 return prometheus.WrapRegistererWith(prometheus.Labels{"const": "label"}, r) 813 }, 814 }, 815 { 816 "RegisterWrappedReregisterNormally", 817 originalWithoutConstLabel, 818 func(r prometheus.Registerer) prometheus.Registerer { 819 return prometheus.WrapRegistererWith(prometheus.Labels{"const": "label"}, r) 820 }, 821 equalButNotSame, 822 func(r prometheus.Registerer) prometheus.Registerer { return r }, 823 }, 824 { 825 "RegisterDoublyWrappedReregisterDoublyWrapped", 826 originalWithoutConstLabel, 827 func(r prometheus.Registerer) prometheus.Registerer { 828 return prometheus.WrapRegistererWithPrefix( 829 "wrap_", 830 prometheus.WrapRegistererWith(prometheus.Labels{"const": "label"}, r), 831 ) 832 }, 833 equalButNotSameWithoutConstLabel, 834 func(r prometheus.Registerer) prometheus.Registerer { 835 return prometheus.WrapRegistererWithPrefix( 836 "wrap_", 837 prometheus.WrapRegistererWith(prometheus.Labels{"const": "label"}, r), 838 ) 839 }, 840 }, 841 } 842 843 for _, s := range scenarios { 844 t.Run(s.name, func(t *testing.T) { 845 var err error 846 reg := prometheus.NewRegistry() 847 if err = s.registerWith(reg).Register(s.originalCollector); err != nil { 848 t.Fatal(err) 849 } 850 if err = s.reRegisterWith(reg).Register(s.newCollector); err == nil { 851 t.Fatal("expected error when registering new collector") 852 } 853 if are, ok := err.(prometheus.AlreadyRegisteredError); ok { 854 if are.ExistingCollector != s.originalCollector { 855 t.Error("expected original collector but got something else") 856 } 857 if are.ExistingCollector == s.newCollector { 858 t.Error("expected original collector but got new one") 859 } 860 } else { 861 t.Error("unexpected error:", err) 862 } 863 }) 864 } 865} 866 867// TestRegisterUnregisterCollector ensures registering and unregistering a 868// collector doesn't leave any dangling metrics. 869// We use NewGoCollector as a nice concrete example of a collector with 870// multiple metrics. 871func TestRegisterUnregisterCollector(t *testing.T) { 872 col := prometheus.NewGoCollector() 873 874 reg := prometheus.NewRegistry() 875 reg.MustRegister(col) 876 reg.Unregister(col) 877 if metrics, err := reg.Gather(); err != nil { 878 t.Error("error gathering sample metric") 879 } else if len(metrics) != 0 { 880 t.Error("should have unregistered metric") 881 } 882} 883 884// TestHistogramVecRegisterGatherConcurrency is an end-to-end test that 885// concurrently calls Observe on random elements of a HistogramVec while the 886// same HistogramVec is registered concurrently and the Gather method of the 887// registry is called concurrently. 888func TestHistogramVecRegisterGatherConcurrency(t *testing.T) { 889 labelNames := make([]string, 16) // Need at least 13 to expose #512. 890 for i := range labelNames { 891 labelNames[i] = fmt.Sprint("label_", i) 892 } 893 894 var ( 895 reg = prometheus.NewPedanticRegistry() 896 hv = prometheus.NewHistogramVec( 897 prometheus.HistogramOpts{ 898 Name: "test_histogram", 899 Help: "This helps testing.", 900 ConstLabels: prometheus.Labels{"foo": "bar"}, 901 }, 902 labelNames, 903 ) 904 labelValues = []string{"a", "b", "c", "alpha", "beta", "gamma", "aleph", "beth", "gimel"} 905 quit = make(chan struct{}) 906 wg sync.WaitGroup 907 ) 908 909 observe := func() { 910 defer wg.Done() 911 for { 912 select { 913 case <-quit: 914 return 915 default: 916 obs := rand.NormFloat64()*.1 + .2 917 values := make([]string, 0, len(labelNames)) 918 for range labelNames { 919 values = append(values, labelValues[rand.Intn(len(labelValues))]) 920 } 921 hv.WithLabelValues(values...).Observe(obs) 922 } 923 } 924 } 925 926 register := func() { 927 defer wg.Done() 928 for { 929 select { 930 case <-quit: 931 return 932 default: 933 if err := reg.Register(hv); err != nil { 934 if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { 935 t.Error("Registering failed:", err) 936 } 937 } 938 time.Sleep(7 * time.Millisecond) 939 } 940 } 941 } 942 943 gather := func() { 944 defer wg.Done() 945 for { 946 select { 947 case <-quit: 948 return 949 default: 950 if g, err := reg.Gather(); err != nil { 951 t.Error("Gathering failed:", err) 952 } else { 953 if len(g) == 0 { 954 continue 955 } 956 if len(g) != 1 { 957 t.Error("Gathered unexpected number of metric families:", len(g)) 958 } 959 if len(g[0].Metric[0].Label) != len(labelNames)+1 { 960 t.Error("Gathered unexpected number of label pairs:", len(g[0].Metric[0].Label)) 961 } 962 } 963 time.Sleep(4 * time.Millisecond) 964 } 965 } 966 } 967 968 wg.Add(10) 969 go observe() 970 go observe() 971 go register() 972 go observe() 973 go gather() 974 go observe() 975 go register() 976 go observe() 977 go gather() 978 go observe() 979 980 time.Sleep(time.Second) 981 close(quit) 982 wg.Wait() 983} 984 985func TestWriteToTextfile(t *testing.T) { 986 expectedOut := `# HELP test_counter test counter 987# TYPE test_counter counter 988test_counter{name="qux"} 1 989# HELP test_gauge test gauge 990# TYPE test_gauge gauge 991test_gauge{name="baz"} 1.1 992# HELP test_hist test histogram 993# TYPE test_hist histogram 994test_hist_bucket{name="bar",le="0.005"} 0 995test_hist_bucket{name="bar",le="0.01"} 0 996test_hist_bucket{name="bar",le="0.025"} 0 997test_hist_bucket{name="bar",le="0.05"} 0 998test_hist_bucket{name="bar",le="0.1"} 0 999test_hist_bucket{name="bar",le="0.25"} 0 1000test_hist_bucket{name="bar",le="0.5"} 0 1001test_hist_bucket{name="bar",le="1"} 1 1002test_hist_bucket{name="bar",le="2.5"} 1 1003test_hist_bucket{name="bar",le="5"} 2 1004test_hist_bucket{name="bar",le="10"} 2 1005test_hist_bucket{name="bar",le="+Inf"} 2 1006test_hist_sum{name="bar"} 3.64 1007test_hist_count{name="bar"} 2 1008# HELP test_summary test summary 1009# TYPE test_summary summary 1010test_summary{name="foo",quantile="0.5"} 10 1011test_summary{name="foo",quantile="0.9"} 20 1012test_summary{name="foo",quantile="0.99"} 20 1013test_summary_sum{name="foo"} 30 1014test_summary_count{name="foo"} 2 1015` 1016 1017 registry := prometheus.NewRegistry() 1018 1019 summary := prometheus.NewSummaryVec( 1020 prometheus.SummaryOpts{ 1021 Name: "test_summary", 1022 Help: "test summary", 1023 Objectives: map[float64]float64{ 1024 0.5: 0.05, 1025 0.9: 0.01, 1026 0.99: 0.001, 1027 }, 1028 }, 1029 []string{"name"}, 1030 ) 1031 1032 histogram := prometheus.NewHistogramVec( 1033 prometheus.HistogramOpts{ 1034 Name: "test_hist", 1035 Help: "test histogram", 1036 }, 1037 []string{"name"}, 1038 ) 1039 1040 gauge := prometheus.NewGaugeVec( 1041 prometheus.GaugeOpts{ 1042 Name: "test_gauge", 1043 Help: "test gauge", 1044 }, 1045 []string{"name"}, 1046 ) 1047 1048 counter := prometheus.NewCounterVec( 1049 prometheus.CounterOpts{ 1050 Name: "test_counter", 1051 Help: "test counter", 1052 }, 1053 []string{"name"}, 1054 ) 1055 1056 registry.MustRegister(summary) 1057 registry.MustRegister(histogram) 1058 registry.MustRegister(gauge) 1059 registry.MustRegister(counter) 1060 1061 summary.With(prometheus.Labels{"name": "foo"}).Observe(10) 1062 summary.With(prometheus.Labels{"name": "foo"}).Observe(20) 1063 histogram.With(prometheus.Labels{"name": "bar"}).Observe(0.93) 1064 histogram.With(prometheus.Labels{"name": "bar"}).Observe(2.71) 1065 gauge.With(prometheus.Labels{"name": "baz"}).Set(1.1) 1066 counter.With(prometheus.Labels{"name": "qux"}).Inc() 1067 1068 tmpfile, err := ioutil.TempFile("", "prom_registry_test") 1069 if err != nil { 1070 t.Fatal(err) 1071 } 1072 defer os.Remove(tmpfile.Name()) 1073 1074 if err := prometheus.WriteToTextfile(tmpfile.Name(), registry); err != nil { 1075 t.Fatal(err) 1076 } 1077 1078 fileBytes, err := ioutil.ReadFile(tmpfile.Name()) 1079 if err != nil { 1080 t.Fatal(err) 1081 } 1082 fileContents := string(fileBytes) 1083 1084 if fileContents != expectedOut { 1085 t.Errorf( 1086 "files don't match, got:\n%s\nwant:\n%s", 1087 fileContents, expectedOut, 1088 ) 1089 } 1090} 1091 1092// collidingCollector is a collection of prometheus.Collectors, 1093// and is itself a prometheus.Collector. 1094type collidingCollector struct { 1095 i int 1096 name string 1097 1098 a, b, c, d prometheus.Collector 1099} 1100 1101// Describe satisifies part of the prometheus.Collector interface. 1102func (m *collidingCollector) Describe(desc chan<- *prometheus.Desc) { 1103 m.a.Describe(desc) 1104 m.b.Describe(desc) 1105 m.c.Describe(desc) 1106 m.d.Describe(desc) 1107} 1108 1109// Collect satisifies part of the prometheus.Collector interface. 1110func (m *collidingCollector) Collect(metric chan<- prometheus.Metric) { 1111 m.a.Collect(metric) 1112 m.b.Collect(metric) 1113 m.c.Collect(metric) 1114 m.d.Collect(metric) 1115} 1116 1117// TestAlreadyRegistered will fail with the old, weaker hash function. It is 1118// taken from https://play.golang.org/p/HpV7YE6LI_4 , authored by @awilliams. 1119func TestAlreadyRegisteredCollision(t *testing.T) { 1120 1121 reg := prometheus.NewRegistry() 1122 1123 for i := 0; i < 10000; i++ { 1124 // A collector should be considered unique if its name and const 1125 // label values are unique. 1126 1127 name := fmt.Sprintf("test-collector-%010d", i) 1128 1129 collector := collidingCollector{ 1130 i: i, 1131 name: name, 1132 1133 a: prometheus.NewCounter(prometheus.CounterOpts{ 1134 Name: "my_collector_a", 1135 ConstLabels: prometheus.Labels{ 1136 "name": name, 1137 "type": "test", 1138 }, 1139 }), 1140 b: prometheus.NewCounter(prometheus.CounterOpts{ 1141 Name: "my_collector_b", 1142 ConstLabels: prometheus.Labels{ 1143 "name": name, 1144 "type": "test", 1145 }, 1146 }), 1147 c: prometheus.NewCounter(prometheus.CounterOpts{ 1148 Name: "my_collector_c", 1149 ConstLabels: prometheus.Labels{ 1150 "name": name, 1151 "type": "test", 1152 }, 1153 }), 1154 d: prometheus.NewCounter(prometheus.CounterOpts{ 1155 Name: "my_collector_d", 1156 ConstLabels: prometheus.Labels{ 1157 "name": name, 1158 "type": "test", 1159 }, 1160 }), 1161 } 1162 1163 // Register should not fail, since each collector has a unique 1164 // set of sub-collectors, determined by their names and const label values. 1165 if err := reg.Register(&collector); err != nil { 1166 alreadyRegErr, ok := err.(prometheus.AlreadyRegisteredError) 1167 if !ok { 1168 t.Fatal(err) 1169 } 1170 1171 previous := alreadyRegErr.ExistingCollector.(*collidingCollector) 1172 current := alreadyRegErr.NewCollector.(*collidingCollector) 1173 1174 t.Errorf("Unexpected registration error: %q\nprevious collector: %s (i=%d)\ncurrent collector %s (i=%d)", alreadyRegErr, previous.name, previous.i, current.name, current.i) 1175 } 1176 } 1177} 1178