633 lines
23 KiB
Go
633 lines
23 KiB
Go
// Copyright 2014 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package prometheus_test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
dto "github.com/prometheus/client_model/go"
|
|
"github.com/prometheus/common/expfmt"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
func ExampleGauge() {
|
|
opsQueued := prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Namespace: "our_company",
|
|
Subsystem: "blob_storage",
|
|
Name: "ops_queued",
|
|
Help: "Number of blob storage operations waiting to be processed.",
|
|
})
|
|
prometheus.MustRegister(opsQueued)
|
|
|
|
// 10 operations queued by the goroutine managing incoming requests.
|
|
opsQueued.Add(10)
|
|
// A worker goroutine has picked up a waiting operation.
|
|
opsQueued.Dec()
|
|
// And once more...
|
|
opsQueued.Dec()
|
|
}
|
|
|
|
func ExampleGaugeVec() {
|
|
opsQueued := prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: "our_company",
|
|
Subsystem: "blob_storage",
|
|
Name: "ops_queued",
|
|
Help: "Number of blob storage operations waiting to be processed, partitioned by user and type.",
|
|
},
|
|
[]string{
|
|
// Which user has requested the operation?
|
|
"user",
|
|
// Of what type is the operation?
|
|
"type",
|
|
},
|
|
)
|
|
prometheus.MustRegister(opsQueued)
|
|
|
|
// Increase a value using compact (but order-sensitive!) WithLabelValues().
|
|
opsQueued.WithLabelValues("bob", "put").Add(4)
|
|
// Increase a value with a map using WithLabels. More verbose, but order
|
|
// doesn't matter anymore.
|
|
opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc()
|
|
}
|
|
|
|
func ExampleGaugeFunc_simple() {
|
|
if err := prometheus.Register(prometheus.NewGaugeFunc(
|
|
prometheus.GaugeOpts{
|
|
Subsystem: "runtime",
|
|
Name: "goroutines_count",
|
|
Help: "Number of goroutines that currently exist.",
|
|
},
|
|
func() float64 { return float64(runtime.NumGoroutine()) },
|
|
)); err == nil {
|
|
fmt.Println("GaugeFunc 'goroutines_count' registered.")
|
|
}
|
|
// Note that the count of goroutines is a gauge (and not a counter) as
|
|
// it can go up and down.
|
|
|
|
// Output:
|
|
// GaugeFunc 'goroutines_count' registered.
|
|
}
|
|
|
|
func ExampleGaugeFunc_constLabels() {
|
|
// primaryDB and secondaryDB represent two example *sql.DB connections we want to instrument.
|
|
var primaryDB, secondaryDB interface {
|
|
Stats() struct{ OpenConnections int }
|
|
}
|
|
|
|
if err := prometheus.Register(prometheus.NewGaugeFunc(
|
|
prometheus.GaugeOpts{
|
|
Namespace: "mysql",
|
|
Name: "connections_open",
|
|
Help: "Number of mysql connections open.",
|
|
ConstLabels: prometheus.Labels{"destination": "primary"},
|
|
},
|
|
func() float64 { return float64(primaryDB.Stats().OpenConnections) },
|
|
)); err == nil {
|
|
fmt.Println(`GaugeFunc 'connections_open' for primary DB connection registered with labels {destination="primary"}`)
|
|
}
|
|
|
|
if err := prometheus.Register(prometheus.NewGaugeFunc(
|
|
prometheus.GaugeOpts{
|
|
Namespace: "mysql",
|
|
Name: "connections_open",
|
|
Help: "Number of mysql connections open.",
|
|
ConstLabels: prometheus.Labels{"destination": "secondary"},
|
|
},
|
|
func() float64 { return float64(secondaryDB.Stats().OpenConnections) },
|
|
)); err == nil {
|
|
fmt.Println(`GaugeFunc 'connections_open' for secondary DB connection registered with labels {destination="secondary"}`)
|
|
}
|
|
|
|
// Note that we can register more than once GaugeFunc with same metric name
|
|
// as long as their const labels are consistent.
|
|
|
|
// Output:
|
|
// GaugeFunc 'connections_open' for primary DB connection registered with labels {destination="primary"}
|
|
// GaugeFunc 'connections_open' for secondary DB connection registered with labels {destination="secondary"}
|
|
}
|
|
|
|
func ExampleCounterVec() {
|
|
httpReqs := prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "http_requests_total",
|
|
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
|
|
},
|
|
[]string{"code", "method"},
|
|
)
|
|
prometheus.MustRegister(httpReqs)
|
|
|
|
httpReqs.WithLabelValues("404", "POST").Add(42)
|
|
|
|
// If you have to access the same set of labels very frequently, it
|
|
// might be good to retrieve the metric only once and keep a handle to
|
|
// it. But beware of deletion of that metric, see below!
|
|
m := httpReqs.WithLabelValues("200", "GET")
|
|
for i := 0; i < 1000000; i++ {
|
|
m.Inc()
|
|
}
|
|
// Delete a metric from the vector. If you have previously kept a handle
|
|
// to that metric (as above), future updates via that handle will go
|
|
// unseen (even if you re-create a metric with the same label set
|
|
// later).
|
|
httpReqs.DeleteLabelValues("200", "GET")
|
|
// Same thing with the more verbose Labels syntax.
|
|
httpReqs.Delete(prometheus.Labels{"method": "GET", "code": "200"})
|
|
}
|
|
|
|
func ExampleRegister() {
|
|
// Imagine you have a worker pool and want to count the tasks completed.
|
|
taskCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
|
Subsystem: "worker_pool",
|
|
Name: "completed_tasks_total",
|
|
Help: "Total number of tasks completed.",
|
|
})
|
|
// This will register fine.
|
|
if err := prometheus.Register(taskCounter); err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Println("taskCounter registered.")
|
|
}
|
|
// Don't forget to tell the HTTP server about the Prometheus handler.
|
|
// (In a real program, you still need to start the HTTP server...)
|
|
http.Handle("/metrics", promhttp.Handler())
|
|
|
|
// Now you can start workers and give every one of them a pointer to
|
|
// taskCounter and let it increment it whenever it completes a task.
|
|
taskCounter.Inc() // This has to happen somewhere in the worker code.
|
|
|
|
// But wait, you want to see how individual workers perform. So you need
|
|
// a vector of counters, with one element for each worker.
|
|
taskCounterVec := prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Subsystem: "worker_pool",
|
|
Name: "completed_tasks_total",
|
|
Help: "Total number of tasks completed.",
|
|
},
|
|
[]string{"worker_id"},
|
|
)
|
|
|
|
// Registering will fail because we already have a metric of that name.
|
|
if err := prometheus.Register(taskCounterVec); err != nil {
|
|
fmt.Println("taskCounterVec not registered:", err)
|
|
} else {
|
|
fmt.Println("taskCounterVec registered.")
|
|
}
|
|
|
|
// To fix, first unregister the old taskCounter.
|
|
if prometheus.Unregister(taskCounter) {
|
|
fmt.Println("taskCounter unregistered.")
|
|
}
|
|
|
|
// Try registering taskCounterVec again.
|
|
if err := prometheus.Register(taskCounterVec); err != nil {
|
|
fmt.Println("taskCounterVec not registered:", err)
|
|
} else {
|
|
fmt.Println("taskCounterVec registered.")
|
|
}
|
|
// Bummer! Still doesn't work.
|
|
|
|
// Prometheus will not allow you to ever export metrics with
|
|
// inconsistent help strings or label names. After unregistering, the
|
|
// unregistered metrics will cease to show up in the /metrics HTTP
|
|
// response, but the registry still remembers that those metrics had
|
|
// been exported before. For this example, we will now choose a
|
|
// different name. (In a real program, you would obviously not export
|
|
// the obsolete metric in the first place.)
|
|
taskCounterVec = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Subsystem: "worker_pool",
|
|
Name: "completed_tasks_by_id",
|
|
Help: "Total number of tasks completed.",
|
|
},
|
|
[]string{"worker_id"},
|
|
)
|
|
if err := prometheus.Register(taskCounterVec); err != nil {
|
|
fmt.Println("taskCounterVec not registered:", err)
|
|
} else {
|
|
fmt.Println("taskCounterVec registered.")
|
|
}
|
|
// Finally it worked!
|
|
|
|
// The workers have to tell taskCounterVec their id to increment the
|
|
// right element in the metric vector.
|
|
taskCounterVec.WithLabelValues("42").Inc() // Code from worker 42.
|
|
|
|
// Each worker could also keep a reference to their own counter element
|
|
// around. Pick the counter at initialization time of the worker.
|
|
myCounter := taskCounterVec.WithLabelValues("42") // From worker 42 initialization code.
|
|
myCounter.Inc() // Somewhere in the code of that worker.
|
|
|
|
// Note that something like WithLabelValues("42", "spurious arg") would
|
|
// panic (because you have provided too many label values). If you want
|
|
// to get an error instead, use GetMetricWithLabelValues(...) instead.
|
|
notMyCounter, err := taskCounterVec.GetMetricWithLabelValues("42", "spurious arg")
|
|
if err != nil {
|
|
fmt.Println("Worker initialization failed:", err)
|
|
}
|
|
if notMyCounter == nil {
|
|
fmt.Println("notMyCounter is nil.")
|
|
}
|
|
|
|
// A different (and somewhat tricky) approach is to use
|
|
// ConstLabels. ConstLabels are pairs of label names and label values
|
|
// that never change. Each worker creates and registers an own Counter
|
|
// instance where the only difference is in the value of the
|
|
// ConstLabels. Those Counters can all be registered because the
|
|
// different ConstLabel values guarantee that each worker will increment
|
|
// a different Counter metric.
|
|
counterOpts := prometheus.CounterOpts{
|
|
Subsystem: "worker_pool",
|
|
Name: "completed_tasks",
|
|
Help: "Total number of tasks completed.",
|
|
ConstLabels: prometheus.Labels{"worker_id": "42"},
|
|
}
|
|
taskCounterForWorker42 := prometheus.NewCounter(counterOpts)
|
|
if err := prometheus.Register(taskCounterForWorker42); err != nil {
|
|
fmt.Println("taskCounterVForWorker42 not registered:", err)
|
|
} else {
|
|
fmt.Println("taskCounterForWorker42 registered.")
|
|
}
|
|
// Obviously, in real code, taskCounterForWorker42 would be a member
|
|
// variable of a worker struct, and the "42" would be retrieved with a
|
|
// GetId() method or something. The Counter would be created and
|
|
// registered in the initialization code of the worker.
|
|
|
|
// For the creation of the next Counter, we can recycle
|
|
// counterOpts. Just change the ConstLabels.
|
|
counterOpts.ConstLabels = prometheus.Labels{"worker_id": "2001"}
|
|
taskCounterForWorker2001 := prometheus.NewCounter(counterOpts)
|
|
if err := prometheus.Register(taskCounterForWorker2001); err != nil {
|
|
fmt.Println("taskCounterVForWorker2001 not registered:", err)
|
|
} else {
|
|
fmt.Println("taskCounterForWorker2001 registered.")
|
|
}
|
|
|
|
taskCounterForWorker2001.Inc()
|
|
taskCounterForWorker42.Inc()
|
|
taskCounterForWorker2001.Inc()
|
|
|
|
// Yet another approach would be to turn the workers themselves into
|
|
// Collectors and register them. See the Collector example for details.
|
|
|
|
// Output:
|
|
// taskCounter registered.
|
|
// 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
|
|
// taskCounter unregistered.
|
|
// 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
|
|
// taskCounterVec registered.
|
|
// Worker initialization failed: inconsistent label cardinality: expected 1 label values but got 2 in []string{"42", "spurious arg"}
|
|
// notMyCounter is nil.
|
|
// taskCounterForWorker42 registered.
|
|
// taskCounterForWorker2001 registered.
|
|
}
|
|
|
|
func ExampleSummary() {
|
|
temps := prometheus.NewSummary(prometheus.SummaryOpts{
|
|
Name: "pond_temperature_celsius",
|
|
Help: "The temperature of the frog pond.",
|
|
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
|
})
|
|
|
|
// Simulate some observations.
|
|
for i := 0; i < 1000; i++ {
|
|
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
|
|
}
|
|
|
|
// Just for demonstration, let's check the state of the summary by
|
|
// (ab)using its Write method (which is usually only used by Prometheus
|
|
// internally).
|
|
metric := &dto.Metric{}
|
|
temps.Write(metric)
|
|
fmt.Println(metric.String())
|
|
|
|
// Output:
|
|
// summary:<sample_count:1000 sample_sum:29969.50000000001 quantile:<quantile:0.5 value:31.1 > quantile:<quantile:0.9 value:41.3 > quantile:<quantile:0.99 value:41.9 > >
|
|
}
|
|
|
|
func ExampleSummaryVec() {
|
|
temps := prometheus.NewSummaryVec(
|
|
prometheus.SummaryOpts{
|
|
Name: "pond_temperature_celsius",
|
|
Help: "The temperature of the frog pond.",
|
|
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
|
},
|
|
[]string{"species"},
|
|
)
|
|
|
|
// Simulate some observations.
|
|
for i := 0; i < 1000; i++ {
|
|
temps.WithLabelValues("litoria-caerulea").Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
|
|
temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10)
|
|
}
|
|
|
|
// Create a Summary without any observations.
|
|
temps.WithLabelValues("leiopelma-hochstetteri")
|
|
|
|
// Just for demonstration, let's check the state of the summary vector
|
|
// by registering it with a custom registry and then let it collect the
|
|
// metrics.
|
|
reg := prometheus.NewRegistry()
|
|
reg.MustRegister(temps)
|
|
|
|
metricFamilies, err := reg.Gather()
|
|
if err != nil || len(metricFamilies) != 1 {
|
|
panic("unexpected behavior of custom test registry")
|
|
}
|
|
fmt.Println(metricFamilies[0].String())
|
|
|
|
// Output:
|
|
// name:"pond_temperature_celsius" help:"The temperature of the frog pond." type:SUMMARY metric:<label:<name:"species" value:"leiopelma-hochstetteri" > summary:<sample_count:0 sample_sum:0 quantile:<quantile:0.5 value:nan > quantile:<quantile:0.9 value:nan > quantile:<quantile:0.99 value:nan > > > metric:<label:<name:"species" value:"lithobates-catesbeianus" > summary:<sample_count:1000 sample_sum:31956.100000000017 quantile:<quantile:0.5 value:32.4 > quantile:<quantile:0.9 value:41.4 > quantile:<quantile:0.99 value:41.9 > > > metric:<label:<name:"species" value:"litoria-caerulea" > summary:<sample_count:1000 sample_sum:29969.50000000001 quantile:<quantile:0.5 value:31.1 > quantile:<quantile:0.9 value:41.3 > quantile:<quantile:0.99 value:41.9 > > >
|
|
}
|
|
|
|
func ExampleNewConstSummary() {
|
|
desc := prometheus.NewDesc(
|
|
"http_request_duration_seconds",
|
|
"A summary of the HTTP request durations.",
|
|
[]string{"code", "method"},
|
|
prometheus.Labels{"owner": "example"},
|
|
)
|
|
|
|
// Create a constant summary from values we got from a 3rd party telemetry system.
|
|
s := prometheus.MustNewConstSummary(
|
|
desc,
|
|
4711, 403.34,
|
|
map[float64]float64{0.5: 42.3, 0.9: 323.3},
|
|
"200", "get",
|
|
)
|
|
|
|
// Just for demonstration, let's check the state of the summary by
|
|
// (ab)using its Write method (which is usually only used by Prometheus
|
|
// internally).
|
|
metric := &dto.Metric{}
|
|
s.Write(metric)
|
|
fmt.Println(metric.String())
|
|
|
|
// Output:
|
|
// label:<name:"code" value:"200" > label:<name:"method" value:"get" > label:<name:"owner" value:"example" > summary:<sample_count:4711 sample_sum:403.34 quantile:<quantile:0.5 value:42.3 > quantile:<quantile:0.9 value:323.3 > >
|
|
}
|
|
|
|
func ExampleHistogram() {
|
|
temps := prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "pond_temperature_celsius",
|
|
Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
|
|
Buckets: prometheus.LinearBuckets(20, 5, 5), // 5 buckets, each 5 centigrade wide.
|
|
})
|
|
|
|
// Simulate some observations.
|
|
for i := 0; i < 1000; i++ {
|
|
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
|
|
}
|
|
|
|
// Just for demonstration, let's check the state of the histogram by
|
|
// (ab)using its Write method (which is usually only used by Prometheus
|
|
// internally).
|
|
metric := &dto.Metric{}
|
|
temps.Write(metric)
|
|
fmt.Println(metric.String())
|
|
|
|
// Output:
|
|
// histogram:<sample_count:1000 sample_sum:29969.50000000001 bucket:<cumulative_count:192 upper_bound:20 > bucket:<cumulative_count:366 upper_bound:25 > bucket:<cumulative_count:501 upper_bound:30 > bucket:<cumulative_count:638 upper_bound:35 > bucket:<cumulative_count:816 upper_bound:40 > >
|
|
}
|
|
|
|
func ExampleNewConstHistogram() {
|
|
desc := prometheus.NewDesc(
|
|
"http_request_duration_seconds",
|
|
"A histogram of the HTTP request durations.",
|
|
[]string{"code", "method"},
|
|
prometheus.Labels{"owner": "example"},
|
|
)
|
|
|
|
// Create a constant histogram from values we got from a 3rd party telemetry system.
|
|
h := prometheus.MustNewConstHistogram(
|
|
desc,
|
|
4711, 403.34,
|
|
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
|
|
"200", "get",
|
|
)
|
|
|
|
// Just for demonstration, let's check the state of the histogram by
|
|
// (ab)using its Write method (which is usually only used by Prometheus
|
|
// internally).
|
|
metric := &dto.Metric{}
|
|
h.Write(metric)
|
|
fmt.Println(metric.String())
|
|
|
|
// Output:
|
|
// label:<name:"code" value:"200" > label:<name:"method" value:"get" > label:<name:"owner" value:"example" > histogram:<sample_count:4711 sample_sum:403.34 bucket:<cumulative_count:121 upper_bound:25 > bucket:<cumulative_count:2403 upper_bound:50 > bucket:<cumulative_count:3221 upper_bound:100 > bucket:<cumulative_count:4233 upper_bound:200 > >
|
|
}
|
|
|
|
func ExampleNewConstHistogram_WithExemplar() {
|
|
desc := prometheus.NewDesc(
|
|
"http_request_duration_seconds",
|
|
"A histogram of the HTTP request durations.",
|
|
[]string{"code", "method"},
|
|
prometheus.Labels{"owner": "example"},
|
|
)
|
|
|
|
// Create a constant histogram from values we got from a 3rd party telemetry system.
|
|
h := prometheus.MustNewConstHistogram(
|
|
desc,
|
|
4711, 403.34,
|
|
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
|
|
"200", "get",
|
|
)
|
|
|
|
// Wrap const histogram with exemplars for each bucket.
|
|
exemplarTs, _ := time.Parse(time.RFC850, "Monday, 02-Jan-06 15:04:05 GMT")
|
|
exemplarLabels := prometheus.Labels{"testName": "testVal"}
|
|
h = prometheus.MustNewMetricWithExemplars(
|
|
h,
|
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 24.0},
|
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 42.0},
|
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 89.0},
|
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 157.0},
|
|
)
|
|
|
|
// Just for demonstration, let's check the state of the histogram by
|
|
// (ab)using its Write method (which is usually only used by Prometheus
|
|
// internally).
|
|
metric := &dto.Metric{}
|
|
h.Write(metric)
|
|
fmt.Println(metric.String())
|
|
|
|
// Output:
|
|
// label:<name:"code" value:"200" > label:<name:"method" value:"get" > label:<name:"owner" value:"example" > histogram:<sample_count:4711 sample_sum:403.34 bucket:<cumulative_count:121 upper_bound:25 exemplar:<label:<name:"testName" value:"testVal" > value:24 timestamp:<seconds:1136214245 > > > bucket:<cumulative_count:2403 upper_bound:50 exemplar:<label:<name:"testName" value:"testVal" > value:42 timestamp:<seconds:1136214245 > > > bucket:<cumulative_count:3221 upper_bound:100 exemplar:<label:<name:"testName" value:"testVal" > value:89 timestamp:<seconds:1136214245 > > > bucket:<cumulative_count:4233 upper_bound:200 exemplar:<label:<name:"testName" value:"testVal" > value:157 timestamp:<seconds:1136214245 > > > >
|
|
}
|
|
|
|
func ExampleAlreadyRegisteredError() {
|
|
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "requests_total",
|
|
Help: "The total number of requests served.",
|
|
})
|
|
if err := prometheus.Register(reqCounter); err != nil {
|
|
are := &prometheus.AlreadyRegisteredError{}
|
|
if errors.As(err, are) {
|
|
// A counter for that metric has been registered before.
|
|
// Use the old counter from now on.
|
|
reqCounter = are.ExistingCollector.(prometheus.Counter)
|
|
} else {
|
|
// Something else went wrong!
|
|
panic(err)
|
|
}
|
|
}
|
|
reqCounter.Inc()
|
|
}
|
|
|
|
func ExampleGatherers() {
|
|
reg := prometheus.NewRegistry()
|
|
temp := prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Name: "temperature_kelvin",
|
|
Help: "Temperature in Kelvin.",
|
|
},
|
|
[]string{"location"},
|
|
)
|
|
reg.MustRegister(temp)
|
|
temp.WithLabelValues("outside").Set(273.14)
|
|
temp.WithLabelValues("inside").Set(298.44)
|
|
|
|
var parser expfmt.TextParser
|
|
|
|
text := `
|
|
# TYPE humidity_percent gauge
|
|
# HELP humidity_percent Humidity in %.
|
|
humidity_percent{location="outside"} 45.4
|
|
humidity_percent{location="inside"} 33.2
|
|
# TYPE temperature_kelvin gauge
|
|
# HELP temperature_kelvin Temperature in Kelvin.
|
|
temperature_kelvin{location="somewhere else"} 4.5
|
|
`
|
|
|
|
parseText := func() ([]*dto.MetricFamily, error) {
|
|
parsed, err := parser.TextToMetricFamilies(strings.NewReader(text))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result []*dto.MetricFamily
|
|
for _, mf := range parsed {
|
|
result = append(result, mf)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
gatherers := prometheus.Gatherers{
|
|
reg,
|
|
prometheus.GathererFunc(parseText),
|
|
}
|
|
|
|
gathering, err := gatherers.Gather()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
out := &bytes.Buffer{}
|
|
for _, mf := range gathering {
|
|
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
fmt.Print(out.String())
|
|
fmt.Println("----------")
|
|
|
|
// Note how the temperature_kelvin metric family has been merged from
|
|
// different sources. Now try
|
|
text = `
|
|
# TYPE humidity_percent gauge
|
|
# HELP humidity_percent Humidity in %.
|
|
humidity_percent{location="outside"} 45.4
|
|
humidity_percent{location="inside"} 33.2
|
|
# TYPE temperature_kelvin gauge
|
|
# HELP temperature_kelvin Temperature in Kelvin.
|
|
# Duplicate metric:
|
|
temperature_kelvin{location="outside"} 265.3
|
|
# Missing location label (note that this is undesirable but valid):
|
|
temperature_kelvin 4.5
|
|
`
|
|
|
|
gathering, err = gatherers.Gather()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
// Note that still as many metrics as possible are returned:
|
|
out.Reset()
|
|
for _, mf := range gathering {
|
|
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
fmt.Print(out.String())
|
|
|
|
// Output:
|
|
// # HELP humidity_percent Humidity in %.
|
|
// # TYPE humidity_percent gauge
|
|
// humidity_percent{location="inside"} 33.2
|
|
// humidity_percent{location="outside"} 45.4
|
|
// # HELP temperature_kelvin Temperature in Kelvin.
|
|
// # TYPE temperature_kelvin gauge
|
|
// temperature_kelvin{location="inside"} 298.44
|
|
// temperature_kelvin{location="outside"} 273.14
|
|
// temperature_kelvin{location="somewhere else"} 4.5
|
|
// ----------
|
|
// collected metric "temperature_kelvin" { label:<name:"location" value:"outside" > gauge:<value:265.3 > } was collected before with the same name and label values
|
|
// # HELP humidity_percent Humidity in %.
|
|
// # TYPE humidity_percent gauge
|
|
// humidity_percent{location="inside"} 33.2
|
|
// humidity_percent{location="outside"} 45.4
|
|
// # HELP temperature_kelvin Temperature in Kelvin.
|
|
// # TYPE temperature_kelvin gauge
|
|
// temperature_kelvin 4.5
|
|
// temperature_kelvin{location="inside"} 298.44
|
|
// temperature_kelvin{location="outside"} 273.14
|
|
}
|
|
|
|
func ExampleNewMetricWithTimestamp() {
|
|
desc := prometheus.NewDesc(
|
|
"temperature_kelvin",
|
|
"Current temperature in Kelvin.",
|
|
nil, nil,
|
|
)
|
|
|
|
// Create a constant gauge from values we got from an external
|
|
// temperature reporting system. Those values are reported with a slight
|
|
// delay, so we want to add the timestamp of the actual measurement.
|
|
temperatureReportedByExternalSystem := 298.15
|
|
timeReportedByExternalSystem := time.Date(2009, time.November, 10, 23, 0, 0, 12345678, time.UTC)
|
|
s := prometheus.NewMetricWithTimestamp(
|
|
timeReportedByExternalSystem,
|
|
prometheus.MustNewConstMetric(
|
|
desc, prometheus.GaugeValue, temperatureReportedByExternalSystem,
|
|
),
|
|
)
|
|
|
|
// Just for demonstration, let's check the state of the gauge by
|
|
// (ab)using its Write method (which is usually only used by Prometheus
|
|
// internally).
|
|
metric := &dto.Metric{}
|
|
s.Write(metric)
|
|
fmt.Println(metric.String())
|
|
|
|
// Output:
|
|
// gauge:<value:298.15 > timestamp_ms:1257894000012
|
|
}
|