Skip to content

Commit 3010b5f

Browse files
authored
[geneva] Add support for exporting otlp metrics via user_events on Linux (open-telemetry#2113)
1 parent c290f08 commit 3010b5f

File tree

8 files changed

+483
-12
lines changed

8 files changed

+483
-12
lines changed

src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
* Drop support for .NET 6 as this target is no longer supported.
66
([#2117](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2117))
77

8+
* Added support for exporting metrics via
9+
[user_events](https://docs.kernel.org/trace/user_events.html) on Linux when
10+
OTLP protobuf encoding is enabled via the
11+
`PrivatePreviewEnableOtlpProtobufEncoding=true` connection string switch. With
12+
this, `PrivatePreviewEnableOtlpProtobufEncoding=true` is now supported on both
13+
Widows and Linux. Windows uses ETW as transport, while Linux uses user_events
14+
as transport.
15+
([#2113](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2113))
16+
817
## 1.9.0
918

1019
Released 2024-Jun-21

src/OpenTelemetry.Exporter.Geneva/Internal/ExporterEventSource.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ internal sealed class ExporterEventSource : EventSource
1818
private const int EVENT_ID_ERROR = 4; // Other common exporter exceptions
1919
private const int EVENT_ID_OTLP_PROTOBUF_METRIC = 5; // Failed to serialize metric
2020
private const int EVENT_ID_COMPLETED_EXPORT = 6; // Completed export
21+
private const int EVENT_ID_TRANSPORT_ERROR = 7; // Transport error
22+
private const int EVENT_ID_TRANSPORT_EXCEPTION = 8; // Transport exception
23+
private const int EVENT_ID_TRANSPORT_INFO = 9; // Transport info
2124

2225
[NonEvent]
2326
public void FailedToSendTraceData(Exception ex)
@@ -76,6 +79,16 @@ public void FailedToSerializeMetric(string metricName, Exception ex)
7679
}
7780
}
7881

82+
[NonEvent]
83+
public void TransportException(string transportType, string message, Exception ex)
84+
{
85+
if (Log.IsEnabled(EventLevel.Error, EventKeywords.All))
86+
{
87+
// TODO: Do not hit ETW size limit even for external library exception stack.
88+
this.TransportException(transportType, message, ex.ToInvariantString());
89+
}
90+
}
91+
7992
[Event(EVENT_ID_TRACE, Message = "Exporter failed to send trace data. Exception: {0}", Level = EventLevel.Error)]
8093
public void FailedToSendTraceData(string error)
8194
{
@@ -111,4 +124,22 @@ public void ExportCompleted(string exporterName)
111124
{
112125
this.WriteEvent(EVENT_ID_COMPLETED_EXPORT, exporterName);
113126
}
127+
128+
[Event(EVENT_ID_TRANSPORT_ERROR, Message = "Transport '{0}' error. Message: {1}", Level = EventLevel.Error)]
129+
public void TransportError(string transportType, string error)
130+
{
131+
this.WriteEvent(EVENT_ID_TRANSPORT_ERROR, transportType, error);
132+
}
133+
134+
[Event(EVENT_ID_TRANSPORT_EXCEPTION, Message = "Transport '{0}' error. Message: {1}, Exception: {2}", Level = EventLevel.Error)]
135+
public void TransportException(string transportType, string error, string ex)
136+
{
137+
this.WriteEvent(EVENT_ID_TRANSPORT_EXCEPTION, transportType, error, ex);
138+
}
139+
140+
[Event(EVENT_ID_TRANSPORT_INFO, Message = "Transport '{0}' information. Message: {1}", Level = EventLevel.Informational)]
141+
public void TransportInformation(string transportType, string error)
142+
{
143+
this.WriteEvent(EVENT_ID_TRANSPORT_INFO, transportType, error);
144+
}
114145
}

src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixDomainSocketDataTransport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private bool Connect()
7373
}
7474
catch (Exception ex)
7575
{
76-
ExporterEventSource.Log.ExporterException("UDS Connect failed.", ex);
76+
ExporterEventSource.Log.TransportException(nameof(UnixDomainSocketDataTransport), "Attempt to connect to socket failed. Connection will be retried periodically until established.", ex);
7777

7878
return false;
7979
}

src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufMetricExporter.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Diagnostics;
45
using System.Runtime.InteropServices;
56
using OpenTelemetry.Metrics;
67
using OpenTelemetry.Resources;
@@ -15,17 +16,32 @@ internal sealed class OtlpProtobufMetricExporter : IDisposable
1516

1617
private readonly Func<Resource> getResource;
1718

18-
public OtlpProtobufMetricExporter(Func<Resource> getResource, ConnectionStringBuilder connectionStringBuilder, IReadOnlyDictionary<string, object> prepopulatedMetricDimensions)
19+
public OtlpProtobufMetricExporter(
20+
Func<Resource> getResource,
21+
ConnectionStringBuilder connectionStringBuilder,
22+
IReadOnlyDictionary<string, object> prepopulatedMetricDimensions)
1923
{
24+
Debug.Assert(getResource != null, "getResource was null");
25+
26+
this.getResource = getResource;
27+
28+
#if NET6_0_OR_GREATER
29+
IMetricDataTransport transport = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
30+
? MetricUnixUserEventsDataTransport.Instance
31+
: MetricWindowsEventTracingDataTransport.Instance;
32+
#else
2033
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
2134
{
22-
// Temporary until we add support for user_events.
2335
throw new NotSupportedException("Exporting data in protobuf format is not supported on Linux.");
2436
}
2537

26-
this.getResource = getResource;
38+
var transport = MetricWindowsEventTracingDataTransport.Instance;
39+
#endif
2740

28-
this.otlpProtobufSerializer = new OtlpProtobufSerializer(MetricWindowsEventTracingDataTransport.Instance, connectionStringBuilder, prepopulatedMetricDimensions);
41+
this.otlpProtobufSerializer = new OtlpProtobufSerializer(
42+
transport,
43+
connectionStringBuilder,
44+
prepopulatedMetricDimensions);
2945
}
3046

3147
public ExportResult Export(in Batch<Metric> batch)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#if NET
5+
6+
#nullable enable
7+
8+
using System.Text;
9+
using Microsoft.LinuxTracepoints.Provider;
10+
11+
namespace OpenTelemetry.Exporter.Geneva;
12+
13+
internal sealed class MetricUnixUserEventsDataTransport : IMetricDataTransport
14+
{
15+
public const uint MetricsProtocol = 0U;
16+
public const string MetricsVersion = "v0.19.00";
17+
public const string MetricsTracepointName = "otlp_metrics";
18+
public const string MetricsTracepointNameArgs = $"{MetricsTracepointName} u32 protocol;char[8] version;__rel_loc u8[] buffer";
19+
20+
private static readonly ReadOnlyMemory<byte> MetricsVersionUtf8 = Encoding.UTF8.GetBytes(MetricsVersion);
21+
private readonly PerfTracepoint metricsTracepoint;
22+
23+
private MetricUnixUserEventsDataTransport()
24+
{
25+
this.metricsTracepoint = new PerfTracepoint(MetricsTracepointNameArgs);
26+
if (this.metricsTracepoint.RegisterResult != 0)
27+
{
28+
// ENOENT (2): No such file or directory
29+
if (this.metricsTracepoint.RegisterResult == 2)
30+
{
31+
throw new NotSupportedException(
32+
$"Tracepoint registration for 'otlp_metrics' failed with result: '{this.metricsTracepoint.RegisterResult}'. Verify your distribution/kernel supports user_events: https://docs.kernel.org/trace/user_events.html.");
33+
}
34+
35+
ExporterEventSource.Log.TransportInformation(
36+
nameof(MetricUnixUserEventsDataTransport),
37+
$"Tracepoint registration operation for 'otlp_metrics' returned result '{this.metricsTracepoint.RegisterResult}' which is considered recoverable. Entering running state.");
38+
}
39+
}
40+
41+
public static MetricUnixUserEventsDataTransport Instance { get; } = new();
42+
43+
public void Send(MetricEventType eventType, byte[] body, int size)
44+
{
45+
throw new NotSupportedException();
46+
}
47+
48+
public void SendOtlpProtobufEvent(byte[] body, int size)
49+
{
50+
if (this.metricsTracepoint.IsEnabled)
51+
{
52+
var buffer = new ReadOnlySpan<byte>(body, 0, size);
53+
54+
var bufferRelLoc = 0u | ((uint)buffer.Length << 16);
55+
56+
this.metricsTracepoint.Write(
57+
[MetricsProtocol],
58+
MetricsVersionUtf8.Span,
59+
[bufferRelLoc],
60+
buffer);
61+
}
62+
}
63+
64+
public void Dispose()
65+
{
66+
this.metricsTracepoint.Dispose();
67+
}
68+
}
69+
70+
#endif

src/OpenTelemetry.Exporter.Geneva/README.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,25 @@ On Linux provide an `Endpoint` in addition to the `Account` and `Namespace`.
268268
For example:
269269
`Endpoint=unix:{UDS Path};Account={MetricAccount};Namespace={MetricNamespace}`.
270270

271-
Set `PrivatePreviewEnableOtlpProtobufEncoding=true` to opt-in to the
272-
experimental feature for changing the underlying serialization format to binary
273-
protobuf following the schema defined in [OTLP
271+
##### OtlpProtobufEncoding
272+
273+
On Windows set `PrivatePreviewEnableOtlpProtobufEncoding=true` on the
274+
`ConnectionString` to opt-in to the experimental feature for changing the
275+
underlying serialization format to binary protobuf following the schema defined
276+
in [OTLP
274277
specification](https://github.com/open-telemetry/opentelemetry-proto/blob/v1.1.0/opentelemetry/proto/metrics/v1/metrics.proto).
275278

276-
> [!NOTE]
277-
> `PrivatePreviewEnableOtlpProtobufEncoding` is currently
278-
> only supported in Windows environment.
279+
As of `1.10.0` `PrivatePreviewEnableOtlpProtobufEncoding=true` is also supported
280+
on Linux. On Linux when using `PrivatePreviewEnableOtlpProtobufEncoding=true` an
281+
`Endpoint` is **NOT** required to be provided on `ConnectionString`. For
282+
example: `Endpoint=unix:Account={MetricAccount};Namespace={MetricNamespace}`.
283+
284+
> [!IMPORTANT]
285+
> When `PrivatePreviewEnableOtlpProtobufEncoding` is enabled on Linux metrics
286+
> are written using
287+
> [user_events](https://docs.kernel.org/trace/user_events.html). `user_events`
288+
> are a newer feature of the Linux kernel and require a distro with the feature
289+
> enabled.
279290
280291
#### `MetricExportIntervalMilliseconds` (optional)
281292

0 commit comments

Comments
 (0)