feat: implement gauge and counter support for OpenMetrics 2.0 - #894
feat: implement gauge and counter support for OpenMetrics 2.0#894dashpole wants to merge 9 commits into
Conversation
bwplotka
left a comment
There was a problem hiding this comment.
For common, given v0 I would just comment on the method. If we want to be fancy we can generate comment in the exposition format too (:
Looks good so far, good start (see comments) - only question is do we iterate on this on main or some feature branch - no strong opinion, assuming commentary is clear.
Thanks
ywwg
left a comment
There was a problem hiding this comment.
looks good to me so far. My general opinion is that if some scraper asks for openmetrics 2.0, they should get it... Hypothetically I could see requiring the version number "2.0.0rc" or "1.9.9" just in case?
|
Lots of discussion at the OM wg. We will add a NegotiateIncluding() function that takes a list of supported protocols so we don't end up with NegotiateIncludingOpenMetricsAndOpenMetrics2 ... 😆 . Then the client can decide when it wants to add support, and how it wants to gate it (behind |
|
I added a NegotiateIncluding function. I didn't deprecate the old functions (e.g. NegotiateIncludingOpenMetrics), but we can do that in a follow-up if we want to. |
873bb38 to
1bcdef1
Compare
|
I think that's great, the experimental factor at this level should be a commentary. As per discussion on Slack I think we decided to follow:
|
ab106e7 to
2f87680
Compare
46773f6 to
42ca72f
Compare
|
In the spirit of @krajorama's feedback, i've updated the implementation to perform some additional validation. It checks for newline characters, NaN and negative counter values, valid timestamps, empty exemplar label keys. It does not validate UTF-8 for strings, or duplicate label keys or duplicate label sets because those are expensive to validate. This does more validation than OM 1.0, but still leaves some things not validated. I also ended up writing benchmarks as part of that work, which i've split into #943. The benchmark results from #943 run against this PR are: |
Signed-off-by: David Ashpole <dashpole@google.com>
Signed-off-by: David Ashpole <dashpole@google.com>
Signed-off-by: David Ashpole <dashpole@google.com>
…rt timestamps Signed-off-by: David Ashpole <dashpole@google.com>
Signed-off-by: David Ashpole <dashpole@google.com>
Signed-off-by: David Ashpole <dashpole@google.com>
…ion loss Signed-off-by: David Ashpole <dashpole@google.com>
…sion loss Signed-off-by: David Ashpole <dashpole@google.com>
…cision Signed-off-by: David Ashpole <dashpole@google.com>
bwplotka
left a comment
There was a problem hiding this comment.
Looks good, just one last API question
| //nolint:revive // Allow for underscores. | ||
| FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` | ||
| //nolint:revive // Allow for underscores. | ||
| FmtOpenMetrics_2_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_2_0_0 + `; charset=utf-8` |
There was a problem hiding this comment.
The PR introduces FmtOpenMetrics_2_0_0 as an exported constant. Adjacent older formats are explicitly marked as // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.. Introducing a new format-specific exported constant reproduces the very API footprint that maintainers actively deprecated. Consider changing to unexported fmtOpenMetrics_2_0_0.
| } | ||
| } | ||
|
|
||
| func TestCreateOpenMetrics20_Errors(t *testing.T) { |
There was a problem hiding this comment.
Missing test for Counter CreatedTimestamp validation (Edge case coverage). Add a test case in TestCreateOpenMetrics20_Errors where Metric.Counter.CreatedTimestamp is mathematically invalid (e.g., negative seconds or out-of-bounds) to assert it cleanly returns the validation error message.
| return strings.IndexByte(s, '\n') >= 0 || strings.IndexByte(s, '\r') >= 0 | ||
| } | ||
|
|
||
| func validateExemplar20(e *dto.Exemplar) error { |
There was a problem hiding this comment.
Missing test for Exemplar Timestamp validation (Edge case coverage). Add a test case with an invalid Exemplar Timestamp (e.g. invalid protobuf timestamp values) to ensure validateExemplar20 correctly captures and bubbles up the CheckValid() error.
| t.Run(tc.name, func(t *testing.T) { | ||
| var buf bytes.Buffer | ||
| _, err := MetricFamilyToOpenMetrics20(&buf, tc.in) | ||
| if err == nil { |
There was a problem hiding this comment.
Update the err == nil logic to strictly check if the actual err.Error() matches the exact expected error message or a substring representing it. Currently, the test blindly accepts any generic non-nil error, masking regressions.
| for _, scenario := range scenarios { | ||
| t.Run(scenario.name, func(t *testing.T) { | ||
| var buf bytes.Buffer | ||
| _, err := MetricFamilyToOpenMetrics20(&buf, scenario.in) |
There was a problem hiding this comment.
Weak Unit Test Assertions on Written Bytes count. The functions return the number of bytes written, but this test suppresses it with _, err := ... . Verify that the returned byte count equals len(buf.String()) / len(expected) to ensure the returned byte counting arithmetic functions properly.
| // FmtOpenMetrics as an option for the result. Note that this function is | ||
| // temporary and will disappear once FmtOpenMetrics is fully supported and as | ||
| // such may be negotiated by the normal Negotiate function. | ||
| func NegotiateIncludingOpenMetrics(h http.Header) Format { |
There was a problem hiding this comment.
Can we deprecate this in favor of the new function?
| // Additional formats provided in the arguments are also included as options, | ||
| // and are checked in the order they are provided, respecting the preference | ||
| // order in the Accept header. | ||
| func NegotiateIncluding(h http.Header, additionalFormats ...Format) Format { |
There was a problem hiding this comment.
nit: Since we bake new API, what about something more explicit like: NegotiateAccept(h http.Header, accepted ...Format) Format { that would allow us to provide full list vs speculating what's default what's not (especially when default change?)
| return false | ||
| } | ||
|
|
||
| // Default OpenMetrics version to OpenMetricsVersion_0_0_1 |
There was a problem hiding this comment.
| // Default OpenMetrics version to OpenMetricsVersion_0_0_1 | |
| // Default OpenMetrics version to OpenMetricsVersion_0_0_1. |
| acVersion = OpenMetricsVersion_0_0_1 | ||
| } | ||
|
|
||
| // General param matching |
There was a problem hiding this comment.
| // General param matching | |
| // General param matching. |
Part 1 of #893
Histograms and summaries are left for follow-ups.
Open Questions:
@ywwg @bwplotka @krajorama