Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export OpenMetrics format for prometheus exporters #5107

Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1c02405
Export openmetrics format for prometheus exporters
robertcoltheart Dec 1, 2023
5a98791
Update changelog
robertcoltheart Dec 1, 2023
36eb712
Add serializer test
robertcoltheart Dec 1, 2023
3c39d4c
Merge branch 'main' into feature/export-prometheus-openmetrics
robertcoltheart Dec 1, 2023
438ce9c
Merge branch 'main' into feature/export-prometheus-openmetrics
robertcoltheart Dec 1, 2023
945834a
Decide content type based on accept header
robertcoltheart Dec 2, 2023
97a1511
Merge branch 'feature/export-prometheus-openmetrics' of https://githu…
robertcoltheart Dec 2, 2023
a8374af
Tidy test variable naming
robertcoltheart Dec 2, 2023
ee44164
Merge branch 'main' into feature/export-prometheus-openmetrics
robertcoltheart Dec 2, 2023
8b9e72e
Use fewer allocations for media type parsing
robertcoltheart Dec 5, 2023
85ec4a6
Merge branch 'main' into feature/export-prometheus-openmetrics
robertcoltheart Dec 5, 2023
6b4b86f
Update src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExp…
robertcoltheart Dec 6, 2023
82d1921
Fix build
robertcoltheart Dec 6, 2023
547782f
Update src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExp…
robertcoltheart Dec 6, 2023
3130372
Fix build
robertcoltheart Dec 6, 2023
36df8f8
Update src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusH…
robertcoltheart Dec 6, 2023
6558ca0
Refactor and share more code, fix flaky tests
robertcoltheart Dec 6, 2023
5a939cd
Merge branch 'main' into feature/export-prometheus-openmetrics
robertcoltheart Dec 6, 2023
75e0bbb
Tidy usings
robertcoltheart Dec 6, 2023
75df04a
Refactor tests
robertcoltheart Dec 6, 2023
bff3601
Fix build
robertcoltheart Dec 6, 2023
5ec4852
Merge branch 'main' into feature/export-prometheus-openmetrics
robertcoltheart Dec 6, 2023
8dd0cbf
Make static and remove redundant set
robertcoltheart Dec 6, 2023
188d59d
Oops fix build
robertcoltheart Dec 6, 2023
015ad3b
Use foreach instead of linq
robertcoltheart Dec 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107))

## 1.7.0-rc.1

Released 2023-Nov-29
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\Internal\PrometheusSerializerExt.cs" Link="Includes/PrometheusSerializerExt.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\Internal\PrometheusType.cs" Link="Includes/PrometheusType.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\Internal\PrometheusMetric.cs" Link="Includes/PrometheusMetric.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\Internal\PrometheusHeadersParser.cs" Link="Includes/PrometheusHeadersParser.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using OpenTelemetry.Exporter.Prometheus;
using OpenTelemetry.Internal;
using OpenTelemetry.Metrics;
Expand Down Expand Up @@ -64,7 +65,9 @@ public async Task InvokeAsync(HttpContext httpContext)

try
{
var collectionResponse = await this.exporter.CollectionManager.EnterCollect().ConfigureAwait(false);
var openMetricsRequested = AcceptsOpenMetrics(httpContext.Request);
var collectionResponse = await this.exporter.CollectionManager.EnterCollect(openMetricsRequested).ConfigureAwait(false);

try
{
if (collectionResponse.View.Count > 0)
Expand All @@ -75,7 +78,9 @@ public async Task InvokeAsync(HttpContext httpContext)
#else
response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R"));
#endif
response.ContentType = "text/plain; charset=utf-8; version=0.0.4";
response.ContentType = openMetricsRequested
? "application/openmetrics-text; version=1.0.0; charset=utf-8"
: "text/plain; charset=utf-8; version=0.0.4";

await response.Body.WriteAsync(collectionResponse.View.Array, 0, collectionResponse.View.Count).ConfigureAwait(false);
}
Expand All @@ -102,4 +107,16 @@ public async Task InvokeAsync(HttpContext httpContext)

this.exporter.OnExport = null;
}

private static bool AcceptsOpenMetrics(HttpRequest request)
{
var acceptHeader = request.Headers.Accept;

if (StringValues.IsNullOrEmpty(acceptHeader))
{
return false;
}

return acceptHeader.Any(PrometheusHeadersParser.AcceptsOpenMetrics);
robertcoltheart marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107))

## 1.7.0-rc.1

Released 2023-Nov-29
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ public PrometheusCollectionManager(PrometheusExporter exporter)
}

#if NET6_0_OR_GREATER
public ValueTask<CollectionResponse> EnterCollect()
public ValueTask<CollectionResponse> EnterCollect(bool openMetricsRequested)
#else
public Task<CollectionResponse> EnterCollect()
public Task<CollectionResponse> EnterCollect(bool openMetricsRequested)
#endif
{
this.EnterGlobalLock();
Expand Down Expand Up @@ -93,7 +93,7 @@ public Task<CollectionResponse> EnterCollect()
this.ExitGlobalLock();

CollectionResponse response;
var result = this.ExecuteCollect();
var result = this.ExecuteCollect(openMetricsRequested);
if (result)
{
this.previousDataViewGeneratedAtUtc = DateTime.UtcNow;
Expand Down Expand Up @@ -168,9 +168,10 @@ private void WaitForReadersToComplete()
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool ExecuteCollect()
private bool ExecuteCollect(bool openMetricsRequested)
{
this.exporter.OnExport = this.onCollectRef;
this.exporter.OpenMetricsRequested = openMetricsRequested;
var result = this.exporter.Collect(Timeout.Infinite);
this.exporter.OnExport = null;
return result;
Expand All @@ -193,7 +194,13 @@ private ExportResult OnCollect(Batch<Metric> metrics)
{
try
{
cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric));
cursor = PrometheusSerializer.WriteMetric(
this.buffer,
cursor,
metric,
this.GetPrometheusMetric(metric),
this.exporter.OpenMetricsRequested);

break;
}
catch (IndexOutOfRangeException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ internal Func<Batch<Metric>, ExportResult> OnExport

internal int ScrapeResponseCacheDurationMilliseconds { get; }

internal bool OpenMetricsRequested { get; set; }

/// <inheritdoc/>
public override ExportResult Export(in Batch<Metric> metrics)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// <copyright file="PrometheusHeadersParser.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

namespace OpenTelemetry.Exporter.Prometheus;

internal static class PrometheusHeadersParser
{
private const string OpenMetricsMediaType = "application/openmetrics-text";

internal static bool AcceptsOpenMetrics(string contentType)
{
var value = contentType.AsSpan();

while (value.Length > 0)
{
var headerValue = SplitNext(ref value, ',');
var mediaType = SplitNext(ref headerValue, ';');

if (mediaType.Equals(OpenMetricsMediaType.AsSpan(), StringComparison.Ordinal))
{
return true;
}
}

return false;
}

private static ReadOnlySpan<char> SplitNext(ref ReadOnlySpan<char> span, char character)
{
var index = span.IndexOf(character);

if (index == -1)
{
var part = span;
span = span.Slice(span.Length);

return part;
}
else
{
var part = span.Slice(0, index);
span = span.Slice(index + 1);

return part;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,32 @@ public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric
return cursor;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool useOpenMetrics)
{
if (useOpenMetrics)
{
cursor = WriteLong(buffer, cursor, value / 1000);
buffer[cursor++] = unchecked((byte)'.');

long millis = value % 1000;

if (millis < 100)
{
buffer[cursor++] = unchecked((byte)'0');
}

if (millis < 10)
{
buffer[cursor++] = unchecked((byte)'0');
}

return WriteLong(buffer, cursor, millis);
}

return WriteLong(buffer, cursor, value);
}

private static string MapPrometheusType(PrometheusType type)
{
return type switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric)
return true;
}

public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric)
public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false)
{
cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric);
cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric);
Expand Down Expand Up @@ -94,7 +94,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe

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

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

buffer[cursor++] = ASCII_LINEFEED;
}
Expand Down Expand Up @@ -136,7 +136,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
cursor = WriteLong(buffer, cursor, totalCount);
buffer[cursor++] = unchecked((byte)' ');

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

buffer[cursor++] = ASCII_LINEFEED;
}
Expand All @@ -163,7 +163,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum());
buffer[cursor++] = unchecked((byte)' ');

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

buffer[cursor++] = ASCII_LINEFEED;

Expand All @@ -189,14 +189,12 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount());
buffer[cursor++] = unchecked((byte)' ');

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

buffer[cursor++] = ASCII_LINEFEED;
}
}

buffer[cursor++] = ASCII_LINEFEED;
robertcoltheart marked this conversation as resolved.
Show resolved Hide resolved

return cursor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ public void Dispose()
}
}

private static bool AcceptsOpenMetrics(HttpListenerRequest request)
{
var acceptHeader = request.Headers["Accept"];

if (string.IsNullOrEmpty(acceptHeader))
{
return false;
}

return PrometheusHeadersParser.AcceptsOpenMetrics(acceptHeader);
}

private void WorkerProc()
{
this.httpListener.Start();
Expand Down Expand Up @@ -148,15 +160,19 @@ private async Task ProcessRequestAsync(HttpListenerContext context)
{
try
{
var collectionResponse = await this.exporter.CollectionManager.EnterCollect().ConfigureAwait(false);
var openMetricsRequested = AcceptsOpenMetrics(context.Request);
var collectionResponse = await this.exporter.CollectionManager.EnterCollect(openMetricsRequested).ConfigureAwait(false);

try
{
context.Response.Headers.Add("Server", string.Empty);
if (collectionResponse.View.Count > 0)
{
context.Response.StatusCode = 200;
context.Response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R"));
context.Response.ContentType = "text/plain; charset=utf-8; version=0.0.4";
context.Response.ContentType = openMetricsRequested
? "application/openmetrics-text; version=1.0.0; charset=utf-8"
: "text/plain; charset=utf-8; version=0.0.4";

await context.Response.OutputStream.WriteAsync(collectionResponse.View.Array, 0, collectionResponse.View.Count).ConfigureAwait(false);
}
Expand Down
Loading