Skip to content

Commit eb0f4ef

Browse files
Arthur Silva SensArthurSens
authored andcommitted
Optinally print OM created lines
Signed-off-by: Arthur Silva Sens <arthur.sens@coralogix.com>
1 parent fa4f164 commit eb0f4ef

File tree

5 files changed

+136
-16
lines changed

5 files changed

+136
-16
lines changed

examples/createdtimestamps/main.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2022 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+
// A simple example of how to exposed created timestamps in OpenMetrics format.
15+
16+
package main
17+
18+
import (
19+
"log"
20+
"net/http"
21+
"time"
22+
23+
"github.com/prometheus/client_golang/prometheus"
24+
"github.com/prometheus/client_golang/prometheus/promhttp"
25+
)
26+
27+
func main() {
28+
requestDurations := prometheus.NewHistogram(prometheus.HistogramOpts{
29+
Name: "http_request_duration_seconds",
30+
Help: "A histogram of the HTTP request durations in seconds.",
31+
Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5),
32+
})
33+
34+
// Create non-global registry.
35+
registry := prometheus.NewRegistry()
36+
registry.MustRegister(
37+
requestDurations,
38+
)
39+
40+
go func() {
41+
for {
42+
// Record fictional latency.
43+
now := time.Now()
44+
requestDurations.Observe(time.Since(now).Seconds())
45+
time.Sleep(600 * time.Millisecond)
46+
}
47+
}()
48+
49+
// Expose /metrics HTTP endpoint using the created custom registry.
50+
http.Handle(
51+
"/metrics", promhttp.HandlerFor(
52+
registry,
53+
promhttp.HandlerOpts{
54+
OpenMetricsOptions: promhttp.OpenMetricsOptions{
55+
Enable: true,
56+
EnableCreatedTimestamps: true,
57+
},
58+
}),
59+
)
60+
// To test: curl -H 'Accept: application/openmetrics-text' localhost:8080/metrics
61+
log.Fatalln(http.ListenAndServe(":8080", nil))
62+
}

examples/exemplars/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ func main() {
6161
"/metrics", promhttp.HandlerFor(
6262
registry,
6363
promhttp.HandlerOpts{
64-
EnableOpenMetrics: true,
64+
OpenMetricsOptions: promhttp.OpenMetricsOptions{
65+
Enable: true,
66+
},
6567
}),
6668
)
6769
// To test: curl -H 'Accept: application/openmetrics-text' localhost:8080/metrics

examples/gocollector/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ func main() {
5454
http.Handle("/metrics", promhttp.HandlerFor(
5555
reg,
5656
promhttp.HandlerOpts{
57-
// Opt into OpenMetrics to support exemplars.
58-
EnableOpenMetrics: true,
57+
OpenMetricsOptions: promhttp.OpenMetricsOptions{
58+
// Opt into OpenMetrics to support exemplars.
59+
Enable: true,
60+
},
5961
},
6062
))
6163
fmt.Println("Hello world from new Go Collector!")

examples/random/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,10 @@ func main() {
134134
http.Handle("/metrics", promhttp.HandlerFor(
135135
reg,
136136
promhttp.HandlerOpts{
137-
// Opt into OpenMetrics to support exemplars.
138-
EnableOpenMetrics: true,
137+
OpenMetricsOptions: promhttp.OpenMetricsOptions{
138+
// Opt into OpenMetrics to support exemplars.
139+
Enable: true,
140+
},
139141
// Pass custom registry
140142
Registry: reg,
141143
},

prometheus/promhttp/http.go

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"io"
3939
"net/http"
4040
"strconv"
41+
"strings"
4142
"sync"
4243
"time"
4344

@@ -185,7 +186,7 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
185186
}
186187

187188
var contentType expfmt.Format
188-
if opts.EnableOpenMetrics {
189+
if opts.EnableOpenMetrics || opts.OpenMetricsOptions.Enable {
189190
contentType = expfmt.NegotiateIncludingOpenMetrics(req.Header)
190191
} else {
191192
contentType = expfmt.Negotiate(req.Header)
@@ -207,7 +208,13 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
207208
if encodingHeader != string(Identity) {
208209
rsp.Header().Set(contentEncodingHeader, encodingHeader)
209210
}
210-
enc := expfmt.NewEncoder(w, contentType)
211+
212+
var enc expfmt.Encoder
213+
if opts.OpenMetricsOptions.EnableCreatedTimestamps {
214+
enc = expfmt.NewEncoder(w, contentType, expfmt.WithCreatedLines())
215+
} else {
216+
enc = expfmt.NewEncoder(w, contentType)
217+
}
211218

212219
// handleError handles the error according to opts.ErrorHandling
213220
// and returns true if we have to abort after the handling.
@@ -398,16 +405,11 @@ type HandlerOpts struct {
398405
// away). Until the implementation is improved, it is recommended to
399406
// implement a separate timeout in potentially slow Collectors.
400407
Timeout time.Duration
401-
// If true, the experimental OpenMetrics encoding is added to the
402-
// possible options during content negotiation. Note that Prometheus
403-
// 2.5.0+ will negotiate OpenMetrics as first priority. OpenMetrics is
404-
// the only way to transmit exemplars. However, the move to OpenMetrics
405-
// is not completely transparent. Most notably, the values of "quantile"
406-
// labels of Summaries and "le" labels of Histograms are formatted with
407-
// a trailing ".0" if they would otherwise look like integer numbers
408-
// (which changes the identity of the resulting series on the Prometheus
409-
// server).
408+
// Deprecated: Use OpenMetricsOptions.Enable instead.
410409
EnableOpenMetrics bool
410+
// OpenMetricsOptions holds settings for the experimental OpenMetrics encoding.
411+
// It can be used to enable OpenMetrics encoding and for setting extra options.
412+
OpenMetricsOptions OpenMetricsOptions
411413
// ProcessStartTime allows setting process start timevalue that will be exposed
412414
// with "Process-Start-Time-Unix" response header along with the metrics
413415
// payload. This allow callers to have efficient transformations to cumulative
@@ -418,6 +420,56 @@ type HandlerOpts struct {
418420
ProcessStartTime time.Time
419421
}
420422

423+
type OpenMetricsOptions struct {
424+
// Enable specifies if the experimental OpenMetrics encoding is added to the
425+
// possible options during content negotiation.
426+
//
427+
// Note that Prometheus 2.5.0+ might negotiate OpenMetrics Text format
428+
// as first priority unless user uses custom scrape protocol prioritization or
429+
// histograms feature is enabled (then Prometheus proto format is prioritized,
430+
// which client_golang supports).
431+
//
432+
// Keep in mind that the move to OpenMetrics is not completely transparent. Most notably,
433+
// the values of "quantile" labels of Summaries and "le" labels of Histograms are
434+
// formatted with a trailing ".0" if they would otherwise look like integer numbers
435+
// (which changes the identity of the resulting series on the Prometheus
436+
// server).
437+
//
438+
// See other options in OpenMetricsOptions to learn how to enable some special
439+
// features e.g. potentially dangerous created timestamp series.
440+
Enable bool
441+
// EnableCreatedTimestamps specifies if this handler should add, extra, synthetic
442+
// Created Timestamps for counters, histograms and summaries, which for the current
443+
// version of OpenMetrics are defined as extra series with the same name and "_created"
444+
// suffix. See also the OpenMetrics specification for more details
445+
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1
446+
//
447+
// Created timestamps are used to improve the accuracy of reset detection,
448+
// but the way it's designed in OpenMetrics 1.0 it also dramatically increases cardinality
449+
// if the scraper does not handle those metrics correctly (converting to created timestamp
450+
// instead of leaving those series as-is). New OpenMetrics versions might improve
451+
// this situation.
452+
//
453+
// Prometheus introduced the feature flag 'created-timestamp-zero-ingestion'
454+
// in version 2.50.0, but only for the Prometheus protobuf format. Starting in
455+
// future Prometheus version, the feature flag will be extended to the OpenMetrics
456+
// text format, thus safe to be enabled to improve accuracy of counters in Prometheus.
457+
EnableCreatedTimestamps bool
458+
}
459+
460+
// gzipAccepted returns whether the client will accept gzip-encoded content.
461+
func gzipAccepted(header http.Header) bool {
462+
a := header.Get(acceptEncodingHeader)
463+
parts := strings.Split(a, ",")
464+
for _, part := range parts {
465+
part = strings.TrimSpace(part)
466+
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
467+
return true
468+
}
469+
}
470+
return false
471+
}
472+
421473
// httpError removes any content-encoding header and then calls http.Error with
422474
// the provided error and http.StatusInternalServerError. Error contents is
423475
// supposed to be uncompressed plain text. Same as with a plain http.Error, this

0 commit comments

Comments
 (0)