Skip to content

Commit

Permalink
Add histogram exemplar support to Prometheus exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
saul committed Oct 27, 2024
1 parent 9f41ead commit fe78b00
Show file tree
Hide file tree
Showing 6 changed files with 545 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Notes](../../RELEASENOTES.md).

* Added meter-level tags to Prometheus exporter
([#5837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5837))
* Added histogram exemplar support to Prometheus exporter

## 1.9.0-beta.2

Expand Down
8 changes: 0 additions & 8 deletions src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ to scrape.
Grafana](../../docs/metrics/getting-started-prometheus-grafana/README.md)
tutorial for more information.

<!-- This comment is to make sure the two notes above and below are not merged -->

> [!NOTE]
> This exporter does not support Exemplars. For using Exemplars, use the [OTLP
Exporter](../OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md) and use a
component like OTel Collector to expose metrics (with exemplars) to Prometheus.
This [tutorial](../../docs/metrics/exemplars/README.md) shows one way how to do that.

## Prerequisite

* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Notes](../../RELEASENOTES.md).

* Added meter-level tags to Prometheus exporter
([#5837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5837))
* Added histogram exemplar support to Prometheus exporter

## 1.9.0-beta.2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,10 @@ public void NoMetrics()
{
this.WriteEvent(4);
}

[Event(5, Message = "Ignoring exemplar tags that are too long for metric: '{0}'", Level = EventLevel.Warning)]
public void ExemplarTagsTooLong(string metricName)
{
this.WriteEvent(5);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
var tags = metricPoint.Tags;
var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds();

metricPoint.TryGetExemplars(out var exemplarCollection);
var exemplars = exemplarCollection.GetEnumerator();
var hasExemplar = exemplars.MoveNext();

long totalCount = 0;
foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets())
{
Expand Down Expand Up @@ -107,6 +111,19 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe

cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);

if (hasExemplar)
{
if (exemplars.Current.DoubleValue <= histogramMeasurement.ExplicitBound)
{
cursor = WriteExemplar(buffer, cursor, exemplars.Current, openMetricsRequested, metric.Name);
}

while (hasExemplar && exemplars.Current.DoubleValue <= histogramMeasurement.ExplicitBound)
{
hasExemplar = exemplars.MoveNext();
}
}

buffer[cursor++] = ASCII_LINEFEED;
}

Expand Down Expand Up @@ -142,4 +159,55 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe

return cursor;
}

private static int WriteExemplar(byte[] buffer, int cursor, in Exemplar exemplar, bool openMetricsRequested, string metricName)
{
buffer[cursor++] = unchecked((byte)' ');
buffer[cursor++] = unchecked((byte)'#');
buffer[cursor++] = unchecked((byte)' ');

buffer[cursor++] = unchecked((byte)'{');
var labelSetCursorStart = cursor;
cursor = WriteAsciiStringNoEscape(buffer, cursor, "trace_id=\"");
cursor = WriteAsciiStringNoEscape(buffer, cursor, exemplar.TraceId.ToHexString());
cursor = WriteAsciiStringNoEscape(buffer, cursor, "\",span_id=\"");
cursor = WriteAsciiStringNoEscape(buffer, cursor, exemplar.SpanId.ToHexString());
buffer[cursor++] = unchecked((byte)'"');
buffer[cursor++] = unchecked((byte)',');

var labelSetWritten = cursor - labelSetCursorStart - 8;

var tagResetCursor = cursor;

foreach (var tag in exemplar.FilteredTags)
{
var prevCursor = cursor;
cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value);

// From the spec:
// Other characters in the text rendering of an exemplar such as ",= are not included in this limit
// for implementation simplicity and for consistency between the text and proto formats.
labelSetWritten += cursor - prevCursor - 3; // subtract 2 x " and 1 x = character

buffer[cursor++] = unchecked((byte)',');

// From the spec:
// The combined length of the label names and values of an Exemplar's LabelSet MUST NOT exceed 128 UTF-8 character code points.
if (labelSetWritten > 128)
{
cursor = tagResetCursor;
PrometheusExporterEventSource.Log.ExemplarTagsTooLong(metricName);
break;
}
}

buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra.
buffer[cursor++] = unchecked((byte)' ');

cursor = WriteDouble(buffer, cursor, exemplar.DoubleValue);
buffer[cursor++] = unchecked((byte)' ');
cursor = WriteTimestamp(buffer, cursor, exemplar.Timestamp.ToUnixTimeMilliseconds(), openMetricsRequested);

return cursor;
}
}
Loading

0 comments on commit fe78b00

Please sign in to comment.