Skip to content

feat(registry): add Prometheus protobuf exposition - #860

Open
ethanolchik wants to merge 1 commit into
prometheus:mainfrom
ethanolchik:feat/protobuf-exposition
Open

ethanolchik wants to merge 1 commit into
prometheus:mainfrom
ethanolchik:feat/protobuf-exposition

Conversation

@ethanolchik

Copy link
Copy Markdown

Summary

Add length-delimited Prometheus protobuf exposition for the existing metric types.

This adds:

  • Registry.PROMETHEUS_PROTOBUF_CONTENT_TYPE and the exported content-type constant.
  • Binary Registry.metrics() output for protobuf registries while keeping the AsString methods text-only.
  • Protobuf support through registry merge, cluster/worker collection, and Pushgateway.
  • Checked-in upstream schema/descriptor generation, public TypeScript types, and tests for counters, gauges, classic histograms, summaries, exemplars, labels, and fractional histogram counts.

This intentionally does not add native histogram collection; that is stacked separately in #856.

Testing

  • Full test suite, lint, formatting, and TypeScript checks pass locally.

Signed-off-by: Ethan Olchik <eitan.olchik@gmail.com>

@krajorama krajorama left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looking good, a few comments

Comment thread index.d.ts
| OpenMetricsContentType
| PrometheusProtobufContentType;

export type RegistryMetrics<T extends RegistryContentType> =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing the constuctor seemed suspicious, I asked LLM and LLM says this and the constructor change is breaking the API, suggests this fix and text:

diff --git a/index.d.ts b/index.d.ts
@@ -36,14 +36,19 @@ export type RegistryContentType =
 	| OpenMetricsContentType
 	| PrometheusProtobufContentType;
 
-export type RegistryMetrics<T extends RegistryContentType> =
-	T extends PrometheusProtobufContentType ? Uint8Array : string;
+// Not distributive, so a Registry whose content type is not known statically
+// keeps returning a string, as it did before protobuf was supported.
+export type RegistryMetrics<T extends RegistryContentType> = [T] extends [
+	PrometheusProtobufContentType,
+]
+	? Uint8Array
+	: string;
 
 /**
  * Container for all registered metrics
  */
 export class Registry<
-	BoundRegistryContentType extends RegistryContentType = PrometheusContentType,
+	BoundRegistryContentType extends RegistryContentType = RegistryContentType,
 > {
diff --git a/test/protobufTypes.ts b/test/protobufTypes.ts
@@ -52,7 +52,11 @@ const openMetrics: Promise<string> = new Registry(
 	Registry.OPENMETRICS_CONTENT_TYPE,
 ).metrics();
 const unknownFormat: Registry<RegistryContentType> = registry;
-const unknownBody: Promise<string | Uint8Array> = unknownFormat.metrics();
+const unknownBody: Promise<string> = unknownFormat.metrics();
+const plainOpenMetrics: Registry = new Registry(
+	Registry.OPENMETRICS_CONTENT_TYPE,
+);
+const plainProtobuf: Registry = registry;
 void [
 	binary,
 	oneText,
@@ -63,6 +67,8 @@ void [
 	text,
 	openMetrics,
 	unknownBody,
+	plainOpenMetrics,
+	plainProtobuf,
 ];

Comment thread lib/protobuf.js

// Classic histogram aggregation can produce fractional counts. Protobuf has
// dedicated double fields for them; uint64 encoding would truncate them.
function encodeHistogramCount(message, field) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is technically correct and the parser on Prometheus side can process the output which contains a mix of integer and float (bucket) counts.

There is one subtle problem when it comes to aggregated histograms: if the output flip-flops between integer only and not integer only histograms, the NHCB conversion on the Prometheus side may flip-flop between integer and float native histograms with custom buckets, which would in turn cut new chunk on every flip and flop, which can be quite bad for storage. It would be better if the aggregation decided up front what type to use and for example stick to floats for average. Probably a niche problem, but we should add an issue once this PR lands to keep track.

Note: similarly PromQL always produces float histograms, since it avoids having this problem in recording rules - and makes it simpler for the end-user to process the results.

Comment thread lib/protobuf.js
!Number.isInteger(group.summary.sampleCount)
) {
throw new TypeError(
`Prometheus protobuf requires an integer sample count for summary ${metric.name}`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This fails the whole scrape , right? So using average aggregation is basically useless :(
Well, actually the average is wrong for the quantiles anyway see #225.

Could we consider rounding the result and logging that we did this instead of failing? WDYT @jdmarshall ?

I'll ask the Prometheus team if they would be ok adding a float version of the count field like we have for histograms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment thread package.json
},
"dependencies": {
"@opentelemetry/api": "^1.4.0",
"protobufjs": "^8.8.0",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is plus 3.9MB, but not all of it is used. Is this a problem @jdmarshall ?
One could extract encoding/deconfig from this lib possibly?

Comment thread lib/protobuf.js
Comment on lines +45 to +48
const key = JSON.stringify(labelPairs(labels));
let group = groups.get(key);
if (!group) {
group = { label: labelPairs(labels), [metric.type]: {} };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

labelPairs is called twice, can we reduce?

This branch has not been deployed

No deployments
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.

2 participants