@@ -38,6 +38,7 @@ import (
38
38
"io"
39
39
"net/http"
40
40
"strconv"
41
+ "strings"
41
42
"sync"
42
43
"time"
43
44
@@ -185,7 +186,7 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
185
186
}
186
187
187
188
var contentType expfmt.Format
188
- if opts .EnableOpenMetrics {
189
+ if opts .EnableOpenMetrics || opts . OpenMetricsOptions . Enable {
189
190
contentType = expfmt .NegotiateIncludingOpenMetrics (req .Header )
190
191
} else {
191
192
contentType = expfmt .Negotiate (req .Header )
@@ -207,7 +208,13 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
207
208
if encodingHeader != string (Identity ) {
208
209
rsp .Header ().Set (contentEncodingHeader , encodingHeader )
209
210
}
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
+ }
211
218
212
219
// handleError handles the error according to opts.ErrorHandling
213
220
// and returns true if we have to abort after the handling.
@@ -398,16 +405,11 @@ type HandlerOpts struct {
398
405
// away). Until the implementation is improved, it is recommended to
399
406
// implement a separate timeout in potentially slow Collectors.
400
407
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.
410
409
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
411
413
// ProcessStartTime allows setting process start timevalue that will be exposed
412
414
// with "Process-Start-Time-Unix" response header along with the metrics
413
415
// payload. This allow callers to have efficient transformations to cumulative
@@ -418,6 +420,56 @@ type HandlerOpts struct {
418
420
ProcessStartTime time.Time
419
421
}
420
422
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
+
421
473
// httpError removes any content-encoding header and then calls http.Error with
422
474
// the provided error and http.StatusInternalServerError. Error contents is
423
475
// supposed to be uncompressed plain text. Same as with a plain http.Error, this
0 commit comments