Skip to content

Conversation

@vesari
Copy link
Contributor

@vesari vesari commented Nov 26, 2023

This PR adds support for unit for Open Metrics. It is ready for review, but not ready to merge (see my long comment below). Fixes #684.

Signed-off-by: Arianna Vespri <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
@ArthurSens
Copy link
Member

Hey @vesari , just passing by to ask if there is anything we could do to unblock you 👋

@vesari
Copy link
Contributor Author

vesari commented Mar 15, 2024

Hey @vesari , just passing by to ask if there is anything we could do to unblock you 👋

@ArthurSens hello! I think I'm just using a Go version which is too new, I plan on fixing that later today. Thanks a lot for checking on me, much appreciated! :)

Copy link
Member

@ArthurSens ArthurSens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide to support units, let's think of alternative that isn't breaking changes to downstream projects :)

Comment on lines 55 to 56
func NewInfoVec(name, help string, labelNames []string) *InfoVec {
desc := prometheus.NewDesc(name, help, labelNames, nil)
func NewInfoVec(name, help, unit string, labelNames []string) *InfoVec {
desc := prometheus.NewDesc(name, help, unit, labelNames, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar problem here, we can't change Public function's signatures

module github.com/prometheus/client_golang/tutorial

go 1.20
go 1.21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we want to keep 1.20 until the next go release

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Yes, absolutely. Just a leftover from when I reverted the changes I had made before common was fixed in this sense.

@vesari
Copy link
Contributor Author

vesari commented May 11, 2024

If we decide to support units, let's think of alternative that isn't breaking changes to downstream projects :)

Of course! I'll await your decision whether to support them and, if so, I'll be happy to continue working on it the way you suggested :)

@beorn7
Copy link
Member

beorn7 commented May 14, 2024

I realized we already kind of agreed to supporting this... #684 so probably too late to challenge it cc @beorn7 @ArthurSens - do you remember what's the benefit other than "compatibility"?

It's hard for me to give a fair judgement here (because of my difficult history with OM). Having said that, the OM approach to units strikes me as weird.

I understand the historical Prometheus approach of "including the unit in the metric name" as a pragmatic solution to the lack of proper metadata support in Prometheus. It should be noted that it always has been just a convention.

OM added the unit metadata field, presumably because others asked for it (the OTel data model didn't exist back then, but I assume that the presence of a unit field in OTel comes from the same school of thinking). Now you might think, if OM has it as an explicit field, you don't have to include it in the metric name anymore. But OM did the opposite: If there is a unit field, the same string MUST be in the metric name, otherwise it is not valid OM. Personally, I think it should be possible to include the unit in the metric name (for those that like that for all the situation where you do not want to lookup the metadata even if you could), but given that others want to live in a world where metadata is always available at your fingertips, enforcing it in the metric name feels redundant.

Sadly, OTel took its guidance for Prometheus compatibility from OM, so the way the OTel data model is converted to Prometheus is already following the OM requirements, although there was never a Prometheus consensus about it.

Looping back to the question: How should we deal with the situation in client_golang? Given how far unit support has already seeped into various parts of the wider ecosystem, it's tough to say "this is all f*** up, let's just ignore it".
I think we should make it possible to expose the unit field for those that want it, but I would, for now, not enforce that the unit is also in the metric name (therefore accepting that we might generate invalid OM 1.0, but anticipating that this requirement is lifted in OM 2.0). It could be a note in the doc comment, like: "If you want to create valid OM 1.0, include the unit in the metric name, but this is not enforced." (I guess the OM vision was that you specify the name without the unit in instrumentation, then specify the unit separately (but only once), and then the actually exposed metric name includes the metric?)

How "1st class" adding the unit should be, is up to debate. I definitely wouldn't require all users to now add a unit. It should be an option for those that want it.

WRT to NewDesc: I once had an ambitious plan to get rid of (user-visible) Desc, see also #222 for issues with Desc. Not sure how to add unit without making everything even worse. Maybe NewDescWithUnit is OK for now.

@vesari
Copy link
Contributor Author

vesari commented May 16, 2024

Thank you all very much for your considerations and suggestions! In order not to enforce that the unit is also in the metric name when the unit field is populated, the logic in common has to be modified accordingly, by reverting some of the most recent changes that enforce exactly that. Of course, I'll be happy to do so. After that, I can work on the NewDescWithUnit approach for NewDesc. Let me know if I can proceed @ArthurSens @bwplotka @beorn7 :)

@beorn7
Copy link
Member

beorn7 commented May 16, 2024

To be clear: @ArthurSens @bwplotka @kakkoyun should make the call here. I'm just providing my personal context.

@ArthurSens
Copy link
Member

ArthurSens commented May 19, 2024

Hi @vesari!

The team met recently and one of the things we discussed was whether we accept OpenMetrics units or not.
A TL;DR; is: We won't accept this until Prometheus has a clear use case for unit metadata.


Reading the OpenMetrics specification, we can read[1][2]:

A MetricFamily MAY have zero or more Metrics. A MetricFamily MUST have a name, HELP, TYPE, and UNIT metadata. Every Metric within a MetricFamily MUST have a unique LabelSet.
.
.
.
Unit specifies MetricFamily units. If non-empty, it MUST be a suffix of the MetricFamily name separated by an underscore. Be aware that further generation rules might make it an infix in the text format.

We clearly understand that units are required by OpenMetrics 1.0, and we clearly understand that units must be used as metric name suffixes. However, at this point, if we accept this the only benefit would be to say "We're OpenMetrics 1.0 compatible". Being OpenMetrics 1.0 compatible is not a bad thing per se, but Prometheus hasn't used the Unit metadata for anything yet, so it brings Client_golang users little benefit while making our API a bit more complex.

If in the future Prometheus introduces the usage of Unit metadata, e.g. to properly sum metrics with different but compatible units (one in seconds and the other in milliseconds), we'd be happy to resurrect this.

In the name of the team, we apologize for taking this long to make a decision. Your work was quite involved, spreading contributions across multiple repositories and communicating with several stakeholders. We'd be happy to continue working with you if that's also of your interest :)

@ArthurSens
Copy link
Member

Maybe we could start brainstorming how to use the unit metadata in Prometheus? Although this would be a high-complexity project that I would also need a lot of time to understand properly😬

@vesari
Copy link
Contributor Author

vesari commented May 20, 2024

Hi @vesari!

The team met recently and one of the things we discussed was whether we accept OpenMetrics units or not. A TL;DR; is: We won't accept this until Prometheus has a clear use case for unit metadata.

Reading the OpenMetrics specification, we can read[1][2]:

A MetricFamily MAY have zero or more Metrics. A MetricFamily MUST have a name, HELP, TYPE, and UNIT metadata. Every Metric within a MetricFamily MUST have a unique LabelSet.
.
.
.
Unit specifies MetricFamily units. If non-empty, it MUST be a suffix of the MetricFamily name separated by an underscore. Be aware that further generation rules might make it an infix in the text format.

We clearly understand that units are required by OpenMetrics 1.0, and we clearly understand that units must be used as metric name suffixes. However, at this point, if we accept this the only benefit would be to say "We're OpenMetrics 1.0 compatible". Being OpenMetrics 1.0 compatible is not a bad thing per se, but Prometheus hasn't used the Unit metadata for anything yet, so it brings Client_golang users little benefit while making our API a bit more complex.

If in the future Prometheus introduces the usage of Unit metadata, e.g. to properly sum metrics with different but compatible units (one in seconds and the other in milliseconds), we'd be happy to resurrect this.

In the name of the team, we apologize for taking this long to make a decision. Your work was quite involved, spreading contributions across multiple repositories and communicating with several stakeholders. We'd be happy to continue working with you if that's also of your interest :)

@ArthurSens, no worries at all, I totally understand. Of course I’d be delighted to continue working with the team, thanks for suggesting that!

@vesari
Copy link
Contributor Author

vesari commented May 20, 2024

Maybe we could start brainstorming how to use the unit metadata in Prometheus? Although this would be a high-complexity project that I would also need a lot of time to understand properly😬

Either this or anything the team deem useful. Whatever you think needs attention, I’ll be happy to work on :) Feel free to DM me on Slack if necessary. Thank you!

@vesari
Copy link
Contributor Author

vesari commented Jul 31, 2024

As agreed with @bwplotka, we want to add units eventually, but this needs more thought on how to avoid adding significant boilerplate to all users for that; help wanted, closing for now.

@ArthurSens
Copy link
Member

We might want to ressurect this PR now that prometheus/proposals#39 adds a purpose to units in Prometheus :)

@vesari
Copy link
Contributor Author

vesari commented Jun 10, 2025

I'll be happy to do so

@vesari vesari reopened this Jun 10, 2025
@vesari vesari requested a review from kakkoyun as a code owner June 10, 2025 11:28
@vesari
Copy link
Contributor Author

vesari commented Jun 10, 2025

@kakkoyun ignore the request for review for now, as it was sent automatically once I reopened the PR. There's still a lot to catch up on here and work to do.

Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is amazing. Solid work, thanks for finding way to not break compatibility.

Added a few suggestions and ideas, hope those help! Overall this goes in a good direction 👍🏽

enc = expfmt.NewEncoder(w, contentType)
encOpts = append(encOpts, expfmt.WithCreatedLines())
}
if opts.EnableOpenMetricsUnit {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this manual flag?

Why can't we simply add UNIT line if someone enabled OM? Shouldn't this be safe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We added flag for EnableOpenMetricsTextCreatedSamples because we believe CT line is potentially breaking to the ecosystem (lot's of problems). But with UNIT metadata, I'd expect OM compatible parsers to either ignore it or parse. Even TEXT formats should work, because it's just a # .. comment.

The only potential risk is the slight increase of payload size, but this is actually limited by the user who need to manually add Unit: <....> fields to their metric definitions, no?

encOpts = append(encOpts, expfmt.WithCreatedLines())
}
if opts.EnableOpenMetricsUnit {
encOpts = append(encOpts, expfmt.WithUnit())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, does this print UNIT line if UNIT is unknown (empty string?)

Can we ensure this does NOT add UNIT if it's unknown? Just to safe space and limit the risks mentioned in https://github.com/prometheus/client_golang/pull/1392/files#r2580622660

EnableOpenMetricsTextCreatedSamples bool
// EnableOpenMetricsUnit enables unit metadata in the OpenMetrics output format.
// This is only applicable when OpenMetrics format is negotiated.
EnableOpenMetricsUnit bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, I wonder if we could skip this (reduce complexity and effort for everyone, users already need to manually add Unit field).

}

wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: {}}: collect error
wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", unit: "", constLabels: {}, variableLabels: {}}: collect error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we ensure everywhere we don't print empty string? (and just skip it?). It does not give a lot of info in an empty string form

// For constLabels, the label values are constant. Therefore, they are fully
// specified in the Desc. See the Collector example for a usage pattern.
func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels) *Desc {
func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels, unit ...string) *Desc {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I wonder if we don't want to break compatibility. 🤔 It's V2 and I wished we marked it as experimental somewhere visibility (it was meant to experiment on a new API). However, I bet downstream ecosystem already started using it.

I'd suggest, (see A, B options in https://github.com/prometheus/client_golang/pull/1392/files#r2580668173)

  • For NewDesc we do (B)
  • For V2 we do (A) ?

// label names.
xxh.Reset()
xxh.WriteString(help)
xxh.WriteString(optionalUnitValue(unit...))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's eval unit as soon as we have it from descV2Opts or something

}
return fmt.Sprintf(
"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: {%s}}",
"Desc{fqName: %q, help: %q, unit: %q, constLabels: {%s}, variableLabels: {%s}}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, optionally print it?

sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]

// Extract unit from the runtime/metrics name (e.g., "/gc/heap/allocs:bytes" -> "bytes")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's nice!

vesari and others added 4 commits December 14, 2025 09:53
Co-authored-by: Bartlomiej Plotka <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
Co-authored-by: Bartlomiej Plotka <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
Signed-off-by: Arianna Vespri <[email protected]>
@vesari
Copy link
Contributor Author

vesari commented Dec 14, 2025

This is amazing. Solid work, thanks for finding way to not break compatibility.

Added a few suggestions and ideas, hope those help! Overall this goes in a good direction 👍🏽

Thank you very much, I'm working on addressing your requests for changes and your observations. I'll get back to you once I'm done :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenMetrics unit support (in v1)

4 participants