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