Skip to content

Commit 0969437

Browse files
Include resource attributes as target_info for Prometheus exporters (#5407)
1 parent aacbf03 commit 0969437

File tree

7 files changed

+106
-9
lines changed

7 files changed

+106
-9
lines changed

src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Unreleased
44

5+
* Fix serializing scope_info when buffer overflows
6+
([#5407](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5407))
7+
8+
* Add `target_info` to Prometheus exporters when using OpenMetrics
9+
([#5407](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5407))
10+
511
## 1.8.0-beta.1
612

713
Released 2024-Mar-14
@@ -10,6 +16,7 @@ Released 2024-Mar-14
1016
([#5305](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5305))
1117

1218
* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107))
19+
1320
* For requests with OpenMetrics format, scope info is automatically added
1421
([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)
1522
[#5182](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5182))

src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Unreleased
44

5+
* Fix serializing scope_info when buffer overflows
6+
([#5407](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5407))
7+
8+
* Add `target_info` to Prometheus exporters when using OpenMetrics
9+
([#5407](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5407))
10+
511
## 1.8.0-beta.1
612

713
Released 2024-Mar-14

src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs

+35-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ internal sealed class PrometheusCollectionManager
1717
private readonly HashSet<string> scopes;
1818
private int metricsCacheCount;
1919
private byte[] buffer = new byte[85000]; // encourage the object to live in LOH (large object heap)
20+
private int targetInfoBufferLength = -1; // zero or positive when target_info has been written for the first time
2021
private int globalLockState;
2122
private ArraySegment<byte> previousDataView;
2223
private DateTime? previousDataViewGeneratedAtUtc;
@@ -174,13 +175,20 @@ private ExportResult OnCollect(Batch<Metric> metrics)
174175
{
175176
if (this.exporter.OpenMetricsRequested)
176177
{
178+
cursor = this.WriteTargetInfo();
179+
177180
this.scopes.Clear();
178181

179182
foreach (var metric in metrics)
180183
{
181-
if (PrometheusSerializer.CanWriteMetric(metric))
184+
if (!PrometheusSerializer.CanWriteMetric(metric))
185+
{
186+
continue;
187+
}
188+
189+
if (this.scopes.Add(metric.MeterName))
182190
{
183-
if (this.scopes.Add(metric.MeterName))
191+
while (true)
184192
{
185193
try
186194
{
@@ -262,6 +270,31 @@ private ExportResult OnCollect(Batch<Metric> metrics)
262270
}
263271
}
264272

273+
private int WriteTargetInfo()
274+
{
275+
if (this.targetInfoBufferLength < 0)
276+
{
277+
while (true)
278+
{
279+
try
280+
{
281+
this.targetInfoBufferLength = PrometheusSerializer.WriteTargetInfo(this.buffer, 0, this.exporter.Resource);
282+
283+
break;
284+
}
285+
catch (IndexOutOfRangeException)
286+
{
287+
if (!this.IncreaseBufferSize())
288+
{
289+
throw;
290+
}
291+
}
292+
}
293+
}
294+
295+
return this.targetInfoBufferLength;
296+
}
297+
265298
private bool IncreaseBufferSize()
266299
{
267300
var newBufferSize = this.buffer.Length * 2;

src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using OpenTelemetry.Internal;
55
using OpenTelemetry.Metrics;
6+
using OpenTelemetry.Resources;
67

78
namespace OpenTelemetry.Exporter.Prometheus;
89

@@ -14,6 +15,7 @@ internal sealed class PrometheusExporter : BaseExporter<Metric>, IPullMetricExpo
1415
{
1516
private Func<int, bool> funcCollect;
1617
private Func<Batch<Metric>, ExportResult> funcExport;
18+
private Resource resource;
1719
private bool disposed;
1820

1921
/// <summary>
@@ -55,6 +57,8 @@ internal Func<Batch<Metric>, ExportResult> OnExport
5557

5658
internal bool OpenMetricsRequested { get; set; }
5759

60+
internal Resource Resource => this.resource ??= this.ParentProvider.GetResource();
61+
5862
/// <inheritdoc/>
5963
public override ExportResult Export(in Batch<Metric> metrics)
6064
{

src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs

+35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using OpenTelemetry.Internal;
88
using OpenTelemetry.Metrics;
9+
using OpenTelemetry.Resources;
910

1011
namespace OpenTelemetry.Exporter.Prometheus;
1112

@@ -396,6 +397,40 @@ public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTa
396397
return cursor;
397398
}
398399

400+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
401+
public static int WriteTargetInfo(byte[] buffer, int cursor, Resource resource)
402+
{
403+
if (resource == Resource.Empty)
404+
{
405+
return cursor;
406+
}
407+
408+
cursor = WriteAsciiStringNoEscape(buffer, cursor, "# TYPE target info");
409+
buffer[cursor++] = ASCII_LINEFEED;
410+
411+
cursor = WriteAsciiStringNoEscape(buffer, cursor, "# HELP target Target metadata");
412+
buffer[cursor++] = ASCII_LINEFEED;
413+
414+
cursor = WriteAsciiStringNoEscape(buffer, cursor, "target_info");
415+
buffer[cursor++] = unchecked((byte)'{');
416+
417+
foreach (var attribute in resource.Attributes)
418+
{
419+
cursor = WriteLabel(buffer, cursor, attribute.Key, attribute.Value);
420+
421+
buffer[cursor++] = unchecked((byte)',');
422+
}
423+
424+
cursor--; // Write over the last written comma
425+
426+
buffer[cursor++] = unchecked((byte)'}');
427+
buffer[cursor++] = unchecked((byte)' ');
428+
buffer[cursor++] = unchecked((byte)'1');
429+
buffer[cursor++] = ASCII_LINEFEED;
430+
431+
return cursor;
432+
}
433+
399434
private static string MapPrometheusType(PrometheusType type)
400435
{
401436
return type switch

test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs

+13-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Extensions.DependencyInjection;
1313
using Microsoft.Extensions.Hosting;
1414
using OpenTelemetry.Metrics;
15+
using OpenTelemetry.Resources;
1516
using OpenTelemetry.Tests;
1617
using Xunit;
1718

@@ -150,6 +151,7 @@ public async Task PrometheusExporterMiddlewareIntegration_MeterProvider()
150151
{
151152
using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
152153
.AddMeter(MeterName)
154+
.ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1"))
153155
.AddPrometheusExporter()
154156
.Build();
155157

@@ -213,6 +215,7 @@ public async Task PrometheusExporterMiddlewareIntegration_MapEndpoint_WithMeterP
213215
{
214216
using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
215217
.AddMeter(MeterName)
218+
.ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1"))
216219
.AddPrometheusExporter()
217220
.Build();
218221

@@ -265,11 +268,12 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest(
265268
if (registerMeterProvider)
266269
{
267270
services.AddOpenTelemetry().WithMetrics(builder => builder
268-
.AddMeter(MeterName)
269-
.AddPrometheusExporter(o =>
270-
{
271-
configureOptions?.Invoke(o);
272-
}));
271+
.ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1"))
272+
.AddMeter(MeterName)
273+
.AddPrometheusExporter(o =>
274+
{
275+
configureOptions?.Invoke(o);
276+
}));
273277
}
274278

275279
configureServices?.Invoke(services);
@@ -322,7 +326,10 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest(
322326
string content = await response.Content.ReadAsStringAsync();
323327

324328
string expected = requestOpenMetrics
325-
? "# TYPE otel_scope_info info\n"
329+
? "# TYPE target info\n"
330+
+ "# HELP target Target metadata\n"
331+
+ "target_info{service_name='my_service',service_instance_id='id1'} 1\n"
332+
+ "# TYPE otel_scope_info info\n"
326333
+ "# HELP otel_scope_info Scope metadata\n"
327334
+ $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n"
328335
+ "# TYPE counter_double_total counter\n"

test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Net.Http;
88
#endif
99
using OpenTelemetry.Metrics;
10+
using OpenTelemetry.Resources;
1011
using OpenTelemetry.Tests;
1112
using Xunit;
1213

@@ -175,6 +176,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri
175176
{
176177
provider = Sdk.CreateMeterProviderBuilder()
177178
.AddMeter(meter.Name)
179+
.ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1"))
178180
.AddPrometheusHttpListener(options =>
179181
{
180182
options.UriPrefixes = new string[] { address };
@@ -233,7 +235,10 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri
233235
var content = await response.Content.ReadAsStringAsync();
234236

235237
var expected = requestOpenMetrics
236-
? "# TYPE otel_scope_info info\n"
238+
? "# TYPE target info\n"
239+
+ "# HELP target Target metadata\n"
240+
+ "target_info{service_name='my_service',service_instance_id='id1'} 1\n"
241+
+ "# TYPE otel_scope_info info\n"
237242
+ "# HELP otel_scope_info Scope metadata\n"
238243
+ $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n"
239244
+ "# TYPE counter_double_total counter\n"

0 commit comments

Comments
 (0)