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
14package prometheus_test
15
16import (
17	"bytes"
18	"fmt"
19	"math"
20	"net/http"
21	"runtime"
22	"strings"
23	"time"
24
25	//lint:ignore SA1019 Need to keep deprecated package for compatibility.
26	"github.com/golang/protobuf/proto"
27	"github.com/prometheus/common/expfmt"
28
29	dto "github.com/prometheus/client_model/go"
30
31	"github.com/prometheus/client_golang/prometheus"
32	"github.com/prometheus/client_golang/prometheus/promhttp"
33)
34
35func ExampleGauge() {
36	opsQueued := prometheus.NewGauge(prometheus.GaugeOpts{
37		Namespace: "our_company",
38		Subsystem: "blob_storage",
39		Name:      "ops_queued",
40		Help:      "Number of blob storage operations waiting to be processed.",
41	})
42	prometheus.MustRegister(opsQueued)
43
44	// 10 operations queued by the goroutine managing incoming requests.
45	opsQueued.Add(10)
46	// A worker goroutine has picked up a waiting operation.
47	opsQueued.Dec()
48	// And once more...
49	opsQueued.Dec()
50}
51
52func ExampleGaugeVec() {
53	opsQueued := prometheus.NewGaugeVec(
54		prometheus.GaugeOpts{
55			Namespace: "our_company",
56			Subsystem: "blob_storage",
57			Name:      "ops_queued",
58			Help:      "Number of blob storage operations waiting to be processed, partitioned by user and type.",
59		},
60		[]string{
61			// Which user has requested the operation?
62			"user",
63			// Of what type is the operation?
64			"type",
65		},
66	)
67	prometheus.MustRegister(opsQueued)
68
69	// Increase a value using compact (but order-sensitive!) WithLabelValues().
70	opsQueued.WithLabelValues("bob", "put").Add(4)
71	// Increase a value with a map using WithLabels. More verbose, but order
72	// doesn't matter anymore.
73	opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc()
74}
75
76func ExampleGaugeFunc_simple() {
77	if err := prometheus.Register(prometheus.NewGaugeFunc(
78		prometheus.GaugeOpts{
79			Subsystem: "runtime",
80			Name:      "goroutines_count",
81			Help:      "Number of goroutines that currently exist.",
82		},
83		func() float64 { return float64(runtime.NumGoroutine()) },
84	)); err == nil {
85		fmt.Println("GaugeFunc 'goroutines_count' registered.")
86	}
87	// Note that the count of goroutines is a gauge (and not a counter) as
88	// it can go up and down.
89
90	// Output:
91	// GaugeFunc 'goroutines_count' registered.
92}
93
94func ExampleGaugeFunc_constLabels() {
95	// primaryDB and secondaryDB represent two example *sql.DB connections we want to instrument.
96	var primaryDB, secondaryDB interface {
97		Stats() struct{ OpenConnections int }
98	}
99
100	if err := prometheus.Register(prometheus.NewGaugeFunc(
101		prometheus.GaugeOpts{
102			Namespace:   "mysql",
103			Name:        "connections_open",
104			Help:        "Number of mysql connections open.",
105			ConstLabels: prometheus.Labels{"destination": "primary"},
106		},
107		func() float64 { return float64(primaryDB.Stats().OpenConnections) },
108	)); err == nil {
109		fmt.Println(`GaugeFunc 'connections_open' for primary DB connection registered with labels {destination="primary"}`)
110	}
111
112	if err := prometheus.Register(prometheus.NewGaugeFunc(
113		prometheus.GaugeOpts{
114			Namespace:   "mysql",
115			Name:        "connections_open",
116			Help:        "Number of mysql connections open.",
117			ConstLabels: prometheus.Labels{"destination": "secondary"},
118		},
119		func() float64 { return float64(secondaryDB.Stats().OpenConnections) },
120	)); err == nil {
121		fmt.Println(`GaugeFunc 'connections_open' for secondary DB connection registered with labels {destination="secondary"}`)
122	}
123
124	// Note that we can register more than once GaugeFunc with same metric name
125	// as long as their const labels are consistent.
126
127	// Output:
128	// GaugeFunc 'connections_open' for primary DB connection registered with labels {destination="primary"}
129	// GaugeFunc 'connections_open' for secondary DB connection registered with labels {destination="secondary"}
130}
131
132func ExampleCounterVec() {
133	httpReqs := prometheus.NewCounterVec(
134		prometheus.CounterOpts{
135			Name: "http_requests_total",
136			Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
137		},
138		[]string{"code", "method"},
139	)
140	prometheus.MustRegister(httpReqs)
141
142	httpReqs.WithLabelValues("404", "POST").Add(42)
143
144	// If you have to access the same set of labels very frequently, it
145	// might be good to retrieve the metric only once and keep a handle to
146	// it. But beware of deletion of that metric, see below!
147	m := httpReqs.WithLabelValues("200", "GET")
148	for i := 0; i < 1000000; i++ {
149		m.Inc()
150	}
151	// Delete a metric from the vector. If you have previously kept a handle
152	// to that metric (as above), future updates via that handle will go
153	// unseen (even if you re-create a metric with the same label set
154	// later).
155	httpReqs.DeleteLabelValues("200", "GET")
156	// Same thing with the more verbose Labels syntax.
157	httpReqs.Delete(prometheus.Labels{"method": "GET", "code": "200"})
158}
159
160func ExampleRegister() {
161	// Imagine you have a worker pool and want to count the tasks completed.
162	taskCounter := prometheus.NewCounter(prometheus.CounterOpts{
163		Subsystem: "worker_pool",
164		Name:      "completed_tasks_total",
165		Help:      "Total number of tasks completed.",
166	})
167	// This will register fine.
168	if err := prometheus.Register(taskCounter); err != nil {
169		fmt.Println(err)
170	} else {
171		fmt.Println("taskCounter registered.")
172	}
173	// Don't forget to tell the HTTP server about the Prometheus handler.
174	// (In a real program, you still need to start the HTTP server...)
175	http.Handle("/metrics", promhttp.Handler())
176
177	// Now you can start workers and give every one of them a pointer to
178	// taskCounter and let it increment it whenever it completes a task.
179	taskCounter.Inc() // This has to happen somewhere in the worker code.
180
181	// But wait, you want to see how individual workers perform. So you need
182	// a vector of counters, with one element for each worker.
183	taskCounterVec := prometheus.NewCounterVec(
184		prometheus.CounterOpts{
185			Subsystem: "worker_pool",
186			Name:      "completed_tasks_total",
187			Help:      "Total number of tasks completed.",
188		},
189		[]string{"worker_id"},
190	)
191
192	// Registering will fail because we already have a metric of that name.
193	if err := prometheus.Register(taskCounterVec); err != nil {
194		fmt.Println("taskCounterVec not registered:", err)
195	} else {
196		fmt.Println("taskCounterVec registered.")
197	}
198
199	// To fix, first unregister the old taskCounter.
200	if prometheus.Unregister(taskCounter) {
201		fmt.Println("taskCounter unregistered.")
202	}
203
204	// Try registering taskCounterVec again.
205	if err := prometheus.Register(taskCounterVec); err != nil {
206		fmt.Println("taskCounterVec not registered:", err)
207	} else {
208		fmt.Println("taskCounterVec registered.")
209	}
210	// Bummer! Still doesn't work.
211
212	// Prometheus will not allow you to ever export metrics with
213	// inconsistent help strings or label names. After unregistering, the
214	// unregistered metrics will cease to show up in the /metrics HTTP
215	// response, but the registry still remembers that those metrics had
216	// been exported before. For this example, we will now choose a
217	// different name. (In a real program, you would obviously not export
218	// the obsolete metric in the first place.)
219	taskCounterVec = prometheus.NewCounterVec(
220		prometheus.CounterOpts{
221			Subsystem: "worker_pool",
222			Name:      "completed_tasks_by_id",
223			Help:      "Total number of tasks completed.",
224		},
225		[]string{"worker_id"},
226	)
227	if err := prometheus.Register(taskCounterVec); err != nil {
228		fmt.Println("taskCounterVec not registered:", err)
229	} else {
230		fmt.Println("taskCounterVec registered.")
231	}
232	// Finally it worked!
233
234	// The workers have to tell taskCounterVec their id to increment the
235	// right element in the metric vector.
236	taskCounterVec.WithLabelValues("42").Inc() // Code from worker 42.
237
238	// Each worker could also keep a reference to their own counter element
239	// around. Pick the counter at initialization time of the worker.
240	myCounter := taskCounterVec.WithLabelValues("42") // From worker 42 initialization code.
241	myCounter.Inc()                                   // Somewhere in the code of that worker.
242
243	// Note that something like WithLabelValues("42", "spurious arg") would
244	// panic (because you have provided too many label values). If you want
245	// to get an error instead, use GetMetricWithLabelValues(...) instead.
246	notMyCounter, err := taskCounterVec.GetMetricWithLabelValues("42", "spurious arg")
247	if err != nil {
248		fmt.Println("Worker initialization failed:", err)
249	}
250	if notMyCounter == nil {
251		fmt.Println("notMyCounter is nil.")
252	}
253
254	// A different (and somewhat tricky) approach is to use
255	// ConstLabels. ConstLabels are pairs of label names and label values
256	// that never change. Each worker creates and registers an own Counter
257	// instance where the only difference is in the value of the
258	// ConstLabels. Those Counters can all be registered because the
259	// different ConstLabel values guarantee that each worker will increment
260	// a different Counter metric.
261	counterOpts := prometheus.CounterOpts{
262		Subsystem:   "worker_pool",
263		Name:        "completed_tasks",
264		Help:        "Total number of tasks completed.",
265		ConstLabels: prometheus.Labels{"worker_id": "42"},
266	}
267	taskCounterForWorker42 := prometheus.NewCounter(counterOpts)
268	if err := prometheus.Register(taskCounterForWorker42); err != nil {
269		fmt.Println("taskCounterVForWorker42 not registered:", err)
270	} else {
271		fmt.Println("taskCounterForWorker42 registered.")
272	}
273	// Obviously, in real code, taskCounterForWorker42 would be a member
274	// variable of a worker struct, and the "42" would be retrieved with a
275	// GetId() method or something. The Counter would be created and
276	// registered in the initialization code of the worker.
277
278	// For the creation of the next Counter, we can recycle
279	// counterOpts. Just change the ConstLabels.
280	counterOpts.ConstLabels = prometheus.Labels{"worker_id": "2001"}
281	taskCounterForWorker2001 := prometheus.NewCounter(counterOpts)
282	if err := prometheus.Register(taskCounterForWorker2001); err != nil {
283		fmt.Println("taskCounterVForWorker2001 not registered:", err)
284	} else {
285		fmt.Println("taskCounterForWorker2001 registered.")
286	}
287
288	taskCounterForWorker2001.Inc()
289	taskCounterForWorker42.Inc()
290	taskCounterForWorker2001.Inc()
291
292	// Yet another approach would be to turn the workers themselves into
293	// Collectors and register them. See the Collector example for details.
294
295	// Output:
296	// taskCounter registered.
297	// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string
298	// taskCounter unregistered.
299	// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string
300	// taskCounterVec registered.
301	// Worker initialization failed: inconsistent label cardinality: expected 1 label values but got 2 in []string{"42", "spurious arg"}
302	// notMyCounter is nil.
303	// taskCounterForWorker42 registered.
304	// taskCounterForWorker2001 registered.
305}
306
307func ExampleSummary() {
308	temps := prometheus.NewSummary(prometheus.SummaryOpts{
309		Name:       "pond_temperature_celsius",
310		Help:       "The temperature of the frog pond.",
311		Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
312	})
313
314	// Simulate some observations.
315	for i := 0; i < 1000; i++ {
316		temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
317	}
318
319	// Just for demonstration, let's check the state of the summary by
320	// (ab)using its Write method (which is usually only used by Prometheus
321	// internally).
322	metric := &dto.Metric{}
323	temps.Write(metric)
324	fmt.Println(proto.MarshalTextString(metric))
325
326	// Output:
327	// summary: <
328	//   sample_count: 1000
329	//   sample_sum: 29969.50000000001
330	//   quantile: <
331	//     quantile: 0.5
332	//     value: 31.1
333	//   >
334	//   quantile: <
335	//     quantile: 0.9
336	//     value: 41.3
337	//   >
338	//   quantile: <
339	//     quantile: 0.99
340	//     value: 41.9
341	//   >
342	// >
343}
344
345func ExampleSummaryVec() {
346	temps := prometheus.NewSummaryVec(
347		prometheus.SummaryOpts{
348			Name:       "pond_temperature_celsius",
349			Help:       "The temperature of the frog pond.",
350			Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
351		},
352		[]string{"species"},
353	)
354
355	// Simulate some observations.
356	for i := 0; i < 1000; i++ {
357		temps.WithLabelValues("litoria-caerulea").Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
358		temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10)
359	}
360
361	// Create a Summary without any observations.
362	temps.WithLabelValues("leiopelma-hochstetteri")
363
364	// Just for demonstration, let's check the state of the summary vector
365	// by registering it with a custom registry and then let it collect the
366	// metrics.
367	reg := prometheus.NewRegistry()
368	reg.MustRegister(temps)
369
370	metricFamilies, err := reg.Gather()
371	if err != nil || len(metricFamilies) != 1 {
372		panic("unexpected behavior of custom test registry")
373	}
374	fmt.Println(proto.MarshalTextString(metricFamilies[0]))
375
376	// Output:
377	// name: "pond_temperature_celsius"
378	// help: "The temperature of the frog pond."
379	// type: SUMMARY
380	// metric: <
381	//   label: <
382	//     name: "species"
383	//     value: "leiopelma-hochstetteri"
384	//   >
385	//   summary: <
386	//     sample_count: 0
387	//     sample_sum: 0
388	//     quantile: <
389	//       quantile: 0.5
390	//       value: nan
391	//     >
392	//     quantile: <
393	//       quantile: 0.9
394	//       value: nan
395	//     >
396	//     quantile: <
397	//       quantile: 0.99
398	//       value: nan
399	//     >
400	//   >
401	// >
402	// metric: <
403	//   label: <
404	//     name: "species"
405	//     value: "lithobates-catesbeianus"
406	//   >
407	//   summary: <
408	//     sample_count: 1000
409	//     sample_sum: 31956.100000000017
410	//     quantile: <
411	//       quantile: 0.5
412	//       value: 32.4
413	//     >
414	//     quantile: <
415	//       quantile: 0.9
416	//       value: 41.4
417	//     >
418	//     quantile: <
419	//       quantile: 0.99
420	//       value: 41.9
421	//     >
422	//   >
423	// >
424	// metric: <
425	//   label: <
426	//     name: "species"
427	//     value: "litoria-caerulea"
428	//   >
429	//   summary: <
430	//     sample_count: 1000
431	//     sample_sum: 29969.50000000001
432	//     quantile: <
433	//       quantile: 0.5
434	//       value: 31.1
435	//     >
436	//     quantile: <
437	//       quantile: 0.9
438	//       value: 41.3
439	//     >
440	//     quantile: <
441	//       quantile: 0.99
442	//       value: 41.9
443	//     >
444	//   >
445	// >
446}
447
448func ExampleNewConstSummary() {
449	desc := prometheus.NewDesc(
450		"http_request_duration_seconds",
451		"A summary of the HTTP request durations.",
452		[]string{"code", "method"},
453		prometheus.Labels{"owner": "example"},
454	)
455
456	// Create a constant summary from values we got from a 3rd party telemetry system.
457	s := prometheus.MustNewConstSummary(
458		desc,
459		4711, 403.34,
460		map[float64]float64{0.5: 42.3, 0.9: 323.3},
461		"200", "get",
462	)
463
464	// Just for demonstration, let's check the state of the summary by
465	// (ab)using its Write method (which is usually only used by Prometheus
466	// internally).
467	metric := &dto.Metric{}
468	s.Write(metric)
469	fmt.Println(proto.MarshalTextString(metric))
470
471	// Output:
472	// label: <
473	//   name: "code"
474	//   value: "200"
475	// >
476	// label: <
477	//   name: "method"
478	//   value: "get"
479	// >
480	// label: <
481	//   name: "owner"
482	//   value: "example"
483	// >
484	// summary: <
485	//   sample_count: 4711
486	//   sample_sum: 403.34
487	//   quantile: <
488	//     quantile: 0.5
489	//     value: 42.3
490	//   >
491	//   quantile: <
492	//     quantile: 0.9
493	//     value: 323.3
494	//   >
495	// >
496}
497
498func ExampleHistogram() {
499	temps := prometheus.NewHistogram(prometheus.HistogramOpts{
500		Name:    "pond_temperature_celsius",
501		Help:    "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
502		Buckets: prometheus.LinearBuckets(20, 5, 5),  // 5 buckets, each 5 centigrade wide.
503	})
504
505	// Simulate some observations.
506	for i := 0; i < 1000; i++ {
507		temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
508	}
509
510	// Just for demonstration, let's check the state of the histogram by
511	// (ab)using its Write method (which is usually only used by Prometheus
512	// internally).
513	metric := &dto.Metric{}
514	temps.Write(metric)
515	fmt.Println(proto.MarshalTextString(metric))
516
517	// Output:
518	// histogram: <
519	//   sample_count: 1000
520	//   sample_sum: 29969.50000000001
521	//   bucket: <
522	//     cumulative_count: 192
523	//     upper_bound: 20
524	//   >
525	//   bucket: <
526	//     cumulative_count: 366
527	//     upper_bound: 25
528	//   >
529	//   bucket: <
530	//     cumulative_count: 501
531	//     upper_bound: 30
532	//   >
533	//   bucket: <
534	//     cumulative_count: 638
535	//     upper_bound: 35
536	//   >
537	//   bucket: <
538	//     cumulative_count: 816
539	//     upper_bound: 40
540	//   >
541	// >
542}
543
544func ExampleNewConstHistogram() {
545	desc := prometheus.NewDesc(
546		"http_request_duration_seconds",
547		"A histogram of the HTTP request durations.",
548		[]string{"code", "method"},
549		prometheus.Labels{"owner": "example"},
550	)
551
552	// Create a constant histogram from values we got from a 3rd party telemetry system.
553	h := prometheus.MustNewConstHistogram(
554		desc,
555		4711, 403.34,
556		map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
557		"200", "get",
558	)
559
560	// Just for demonstration, let's check the state of the histogram by
561	// (ab)using its Write method (which is usually only used by Prometheus
562	// internally).
563	metric := &dto.Metric{}
564	h.Write(metric)
565	fmt.Println(proto.MarshalTextString(metric))
566
567	// Output:
568	// label: <
569	//   name: "code"
570	//   value: "200"
571	// >
572	// label: <
573	//   name: "method"
574	//   value: "get"
575	// >
576	// label: <
577	//   name: "owner"
578	//   value: "example"
579	// >
580	// histogram: <
581	//   sample_count: 4711
582	//   sample_sum: 403.34
583	//   bucket: <
584	//     cumulative_count: 121
585	//     upper_bound: 25
586	//   >
587	//   bucket: <
588	//     cumulative_count: 2403
589	//     upper_bound: 50
590	//   >
591	//   bucket: <
592	//     cumulative_count: 3221
593	//     upper_bound: 100
594	//   >
595	//   bucket: <
596	//     cumulative_count: 4233
597	//     upper_bound: 200
598	//   >
599	// >
600}
601
602func ExampleAlreadyRegisteredError() {
603	reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
604		Name: "requests_total",
605		Help: "The total number of requests served.",
606	})
607	if err := prometheus.Register(reqCounter); err != nil {
608		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
609			// A counter for that metric has been registered before.
610			// Use the old counter from now on.
611			reqCounter = are.ExistingCollector.(prometheus.Counter)
612		} else {
613			// Something else went wrong!
614			panic(err)
615		}
616	}
617	reqCounter.Inc()
618}
619
620func ExampleGatherers() {
621	reg := prometheus.NewRegistry()
622	temp := prometheus.NewGaugeVec(
623		prometheus.GaugeOpts{
624			Name: "temperature_kelvin",
625			Help: "Temperature in Kelvin.",
626		},
627		[]string{"location"},
628	)
629	reg.MustRegister(temp)
630	temp.WithLabelValues("outside").Set(273.14)
631	temp.WithLabelValues("inside").Set(298.44)
632
633	var parser expfmt.TextParser
634
635	text := `
636# TYPE humidity_percent gauge
637# HELP humidity_percent Humidity in %.
638humidity_percent{location="outside"} 45.4
639humidity_percent{location="inside"} 33.2
640# TYPE temperature_kelvin gauge
641# HELP temperature_kelvin Temperature in Kelvin.
642temperature_kelvin{location="somewhere else"} 4.5
643`
644
645	parseText := func() ([]*dto.MetricFamily, error) {
646		parsed, err := parser.TextToMetricFamilies(strings.NewReader(text))
647		if err != nil {
648			return nil, err
649		}
650		var result []*dto.MetricFamily
651		for _, mf := range parsed {
652			result = append(result, mf)
653		}
654		return result, nil
655	}
656
657	gatherers := prometheus.Gatherers{
658		reg,
659		prometheus.GathererFunc(parseText),
660	}
661
662	gathering, err := gatherers.Gather()
663	if err != nil {
664		fmt.Println(err)
665	}
666
667	out := &bytes.Buffer{}
668	for _, mf := range gathering {
669		if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
670			panic(err)
671		}
672	}
673	fmt.Print(out.String())
674	fmt.Println("----------")
675
676	// Note how the temperature_kelvin metric family has been merged from
677	// different sources. Now try
678	text = `
679# TYPE humidity_percent gauge
680# HELP humidity_percent Humidity in %.
681humidity_percent{location="outside"} 45.4
682humidity_percent{location="inside"} 33.2
683# TYPE temperature_kelvin gauge
684# HELP temperature_kelvin Temperature in Kelvin.
685# Duplicate metric:
686temperature_kelvin{location="outside"} 265.3
687 # Missing location label (note that this is undesirable but valid):
688temperature_kelvin 4.5
689`
690
691	gathering, err = gatherers.Gather()
692	if err != nil {
693		fmt.Println(err)
694	}
695	// Note that still as many metrics as possible are returned:
696	out.Reset()
697	for _, mf := range gathering {
698		if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
699			panic(err)
700		}
701	}
702	fmt.Print(out.String())
703
704	// Output:
705	// # HELP humidity_percent Humidity in %.
706	// # TYPE humidity_percent gauge
707	// humidity_percent{location="inside"} 33.2
708	// humidity_percent{location="outside"} 45.4
709	// # HELP temperature_kelvin Temperature in Kelvin.
710	// # TYPE temperature_kelvin gauge
711	// temperature_kelvin{location="inside"} 298.44
712	// temperature_kelvin{location="outside"} 273.14
713	// temperature_kelvin{location="somewhere else"} 4.5
714	// ----------
715	// collected metric "temperature_kelvin" { label:<name:"location" value:"outside" > gauge:<value:265.3 > } was collected before with the same name and label values
716	// # HELP humidity_percent Humidity in %.
717	// # TYPE humidity_percent gauge
718	// humidity_percent{location="inside"} 33.2
719	// humidity_percent{location="outside"} 45.4
720	// # HELP temperature_kelvin Temperature in Kelvin.
721	// # TYPE temperature_kelvin gauge
722	// temperature_kelvin 4.5
723	// temperature_kelvin{location="inside"} 298.44
724	// temperature_kelvin{location="outside"} 273.14
725}
726
727func ExampleNewMetricWithTimestamp() {
728	desc := prometheus.NewDesc(
729		"temperature_kelvin",
730		"Current temperature in Kelvin.",
731		nil, nil,
732	)
733
734	// Create a constant gauge from values we got from an external
735	// temperature reporting system. Those values are reported with a slight
736	// delay, so we want to add the timestamp of the actual measurement.
737	temperatureReportedByExternalSystem := 298.15
738	timeReportedByExternalSystem := time.Date(2009, time.November, 10, 23, 0, 0, 12345678, time.UTC)
739	s := prometheus.NewMetricWithTimestamp(
740		timeReportedByExternalSystem,
741		prometheus.MustNewConstMetric(
742			desc, prometheus.GaugeValue, temperatureReportedByExternalSystem,
743		),
744	)
745
746	// Just for demonstration, let's check the state of the gauge by
747	// (ab)using its Write method (which is usually only used by Prometheus
748	// internally).
749	metric := &dto.Metric{}
750	s.Write(metric)
751	fmt.Println(proto.MarshalTextString(metric))
752
753	// Output:
754	// gauge: <
755	//   value: 298.15
756	// >
757	// timestamp_ms: 1257894000012
758}
759