- Notifications
You must be signed in to change notification settings - Fork1.2k
Integer-only Counters#1464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -23,6 +23,9 @@ import ( | ||
| "google.golang.org/protobuf/types/known/timestamppb" | ||
| ) | ||
| // 64-bit float mantissa: https://en.wikipedia.org/wiki/Double-precision_floating-point_format | ||
| var float64Mantissa uint64 = 9007199254740992 | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Shouldn't this really be a const instead of a var? IMHO it would also be clearer and more legible if it were defined as | ||
| // Counter is a Metric that represents a single numerical value that only ever | ||
| // goes up. That implies that it cannot be used to count items whose number can | ||
| // also go down, e.g. the number of currently running goroutines. Those | ||
| @@ -57,8 +60,52 @@ type ExemplarAdder interface { | ||
| AddWithExemplar(value float64, exemplar Labels) | ||
| } | ||
| // CounterOpts bundles the options for creating a Histogram metric. It is | ||
| // mandatory to set Name to a non-empty string. All other fields are optional | ||
| // and can safely be left at their zero value, although it is strongly | ||
| // encouraged to set a Help string. | ||
| type CounterOpts struct { | ||
| // Namespace, Subsystem, and Name are components of the fully-qualified | ||
| // name of the Metric (created by joining these components with | ||
| // "_"). Only Name is mandatory, the others merely help structuring the | ||
| // name. Note that the fully-qualified name of the metric must be a | ||
| // valid Prometheus metric name. | ||
| Namespace string | ||
| Subsystem string | ||
| Name string | ||
| // Help provides information about this metric. | ||
| // | ||
| // Metrics with the same fully-qualified name must have the same Help | ||
| // string. | ||
| Help string | ||
| // ConstLabels are used to attach fixed labels to this metric. Metrics | ||
| // with the same fully-qualified name must have the same label names in | ||
| // their ConstLabels. | ||
| // | ||
| // ConstLabels are only used rarely. In particular, do not use them to | ||
| // attach the same labels to all your metrics. Those use cases are | ||
| // better covered by target labels set by the scraping Prometheus | ||
| // server, or by one specific metric (e.g. a build_info or a | ||
| // machine_role metric). See also | ||
| // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels | ||
| ConstLabels Labels | ||
| // now is for testing purposes, by default it's time.Now. | ||
| now func() time.Time | ||
| // Counters are double (float64) values. At values above 2^53, double loses | ||
| // the ability to represent discrete integer values precisely. At 2^53 the | ||
| // error is just +/-1 and is likely of little consequence. At 2^64 the error | ||
| // is +/-1024 (the next smallest number that can be represented is 2^64-2048). | ||
| // This may be significant error for long-running counters that reach the | ||
| // upper range of uint64. To present large Counter values as integer-only, | ||
| // set the IntegerExposition option. This will wrap the Counter twice, once at the | ||
| // largest safe integer value, and again when the Counter's uint64 value | ||
| // becomes 0. Prometheus will handle this rollover gracefully. | ||
| IntegerExposition bool | ||
| } | ||
| // CounterVecOpts bundles the options to create a CounterVec metric. | ||
| // It is mandatory to set CounterOpts, see there for mandatory fields. VariableLabels | ||
| @@ -94,7 +141,7 @@ func NewCounter(opts CounterOpts) Counter { | ||
| if opts.now == nil { | ||
| opts.now = time.Now | ||
| } | ||
| result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: opts.now, integerExposition: opts.IntegerExposition} | ||
| result.init(result) // Init self-collection. | ||
| result.createdTs = timestamppb.New(opts.now()) | ||
| return result | ||
| @@ -115,6 +162,8 @@ type counter struct { | ||
| labelPairs []*dto.LabelPair | ||
| exemplar atomic.Value // Containing nil or a *dto.Exemplar. | ||
| integerExposition bool | ||
| // now is for testing purposes, by default it's time.Now. | ||
| now func() time.Time | ||
| } | ||
| @@ -132,6 +181,9 @@ func (c *counter) Add(v float64) { | ||
| if float64(ival) == v { | ||
| atomic.AddUint64(&c.valInt, ival) | ||
| return | ||
| } else if c.integerExposition { | ||
| // perhaps there should be an AddInt() method? this is a footgun for callers. | ||
| panic(errors.New("cannot add large value with rounding error to integer counter")) | ||
| } | ||
| for { | ||
| @@ -153,8 +205,11 @@ func (c *counter) Inc() { | ||
| } | ||
| func (c *counter) get() float64 { | ||
| ival := atomic.LoadUint64(&c.valInt) | ||
| if c.integerExposition { | ||
| return float64(ival % float64Mantissa) | ||
| } | ||
| fval := math.Float64frombits(atomic.LoadUint64(&c.valBits)) | ||
| return fval + float64(ival) | ||
| } | ||
| @@ -214,7 +269,7 @@ func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec { | ||
| if len(lvs) != len(desc.variableLabels.names) { | ||
| panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, lvs)) | ||
| } | ||
| result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: opts.now, integerExposition: opts.IntegerExposition} | ||
| result.init(result) // Init self-collection. | ||
| result.createdTs = timestamppb.New(opts.now()) | ||
| return result | ||