From b900e89c1ecef3d5f00c0363417cf96141df4ac3 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 23 Jan 2025 18:33:11 -0800 Subject: [PATCH 1/3] Adding user_events support for logs on Linux - user_events data transport utility --- .../Transports/UnixUserEventsDataTransport.cs | 53 +++++++++ .../UnixUserEventsDataTransportTests.cs | 103 +++++++++++++++++- 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs new file mode 100644 index 0000000000..d5ad7cdee6 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET + +using Microsoft.LinuxTracepoints.Provider; + +namespace OpenTelemetry.Exporter.Geneva; + +internal sealed class UnixUserEventsDataTransport : IDisposable +{ + public const string LogsTracepointName = "geneva_logs"; // TODO: find the correct tracepoint name + public const string LogsTracepointNameArgs = $"{LogsTracepointName} u32 protocol;char[8] version;__rel_loc u8[] buffer"; // TODO: find the correct tracepoint args + public const string EventHeaderDynamicProviderName = "MicrosoftOpenTelemetryLogs"; + public const System.Diagnostics.Tracing.EventLevel LogsTracepointEventLevel = System.Diagnostics.Tracing.EventLevel.Informational; // TODO: find the correct event level + public const ulong LogsTracepointKeyword = 1; // TODO: find the correct keyword + + private readonly EventHeaderDynamicProvider eventHeaderDynamicProvider; + + private UnixUserEventsDataTransport() + { + this.eventHeaderDynamicProvider = new EventHeaderDynamicProvider(EventHeaderDynamicProviderName); + } + + public static UnixUserEventsDataTransport Instance { get; } = new(); + + public EventHeaderDynamicTracepoint RegisterUserEventProviderForLogs() + { + var logsTracepoint = this.eventHeaderDynamicProvider.Register(LogsTracepointEventLevel, LogsTracepointKeyword); + if (logsTracepoint.RegisterResult != 0) + { + // ENOENT (2): No such file or directory + if (logsTracepoint.RegisterResult == 2) + { + throw new NotSupportedException( + $"Tracepoint registration for 'geneva_logs' failed with result: '{logsTracepoint.RegisterResult}'. Verify your distribution/kernel supports user_events: https://docs.kernel.org/trace/user_events.html."); + } + + ExporterEventSource.Log.TransportInformation( + nameof(UnixUserEventsDataTransport), + $"Tracepoint registration operation for 'geneva_logs' returned result '{logsTracepoint.RegisterResult}' which is considered recoverable. Entering running state."); + } + + return logsTracepoint; + } + + public void Dispose() + { + this.eventHeaderDynamicProvider.Dispose(); + } +} + +#endif diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs index c2b3eb1c80..9a8dded4bf 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs @@ -143,6 +143,107 @@ public void UserEvents_Disabled_Success_Linux() } } + // [Fact(Skip = "This would fail on Ubuntu. Skipping for now.")] + [Fact] + public void UserEvents_Logs_Success_Linux() + { + EnsureUserEventsEnabled(); + + var listener = new PerfTracepointListener( + "MicrosoftOpenTelemetryLogs_L4K1", + MetricUnixUserEventsDataTransport.MetricsTracepointNameArgs); + + var logsTracepoint = UnixUserEventsDataTransport.Instance.RegisterUserEventProviderForLogs(); + + if (listener.IsEnabled()) + { + throw new NotSupportedException($"{MetricUnixUserEventsDataTransport.MetricsTracepointName} is already enabled"); + } + + try + { + listener.Enable(); + + Console.WriteLine("------------- ready to write events -------------"); + Thread.Sleep(5000); + + if (logsTracepoint.IsEnabled) + { + var eventBuilder = CreateEventHeaderDynamicBuilder(); + + Console.WriteLine("About to write tracepoint:"); + eventBuilder.Write(logsTracepoint); + Console.WriteLine("Written tracepoint."); + } + + Thread.Sleep(5000); + + this.testOutputHelper.WriteLine("Writing events from listener:"); + Console.WriteLine("Writing events from listener:"); + foreach (var e in listener.Events) + { + this.testOutputHelper.WriteLine(string.Join(", ", e.Select(kvp => $"{kvp.Key}={kvp.Value}"))); + Console.WriteLine(string.Join(", ", e.Select(kvp => $"{kvp.Key}={kvp.Value}"))); + } + + this.testOutputHelper.WriteLine("Total events: " + listener.Events.Count); + Console.WriteLine("Total events: " + listener.Events.Count); + } + finally + { + try + { + listener.Disable(); + } + catch + { + } + + listener.Dispose(); + } + } + + private static EventHeaderDynamicBuilder CreateEventHeaderDynamicBuilder() + { + var eb = new EventHeaderDynamicBuilder(); + eb.Reset("_"); + eb.AddUInt16("__csver__", 1024, Microsoft.LinuxTracepoints.EventHeaderFieldFormat.HexInt); + + eb.AddStructWithMetadataPosition("partA", out var partAFieldsCountMetadataPosition); + + string rfc3339String = DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFZ", CultureInfo.InvariantCulture); + eb.AddString16("time", rfc3339String); + + byte partAFieldsCount = 1; + + eb.SetStructFieldCount(partAFieldsCountMetadataPosition, partAFieldsCount); + + // Part B + + byte partBFieldsCount = 4; // We at least have three fields in Part B: _typeName, severityText, severityNumber, name + eb.AddStructWithMetadataPosition("PartB", out var partBFieldsCountMetadataPosition); + eb.AddString16("_typeName", "Log"); + eb.AddUInt8("severityNumber", 21); + + eb.AddString16("severityText", "Critical"); + eb.AddString16("name", "CheckoutFailed"); + + eb.SetStructFieldCount(partBFieldsCountMetadataPosition, partBFieldsCount); + + // Part C + + byte partCFieldsCount = 0; + eb.AddStructWithMetadataPosition("PartC", out var partCFieldsCountMetadataPosition); + + eb.AddString16("book_id", "12345"); + eb.AddString16("book_name", "The Hitchhiker's Guide to the Galaxy"); + partCFieldsCount += 2; + + eb.SetStructFieldCount(partCFieldsCountMetadataPosition, partCFieldsCount); + + return eb; + } + private static void EnsureUserEventsEnabled() { var errors = ConsoleCommand.Run("cat", "/sys/kernel/tracing/user_events_status"); @@ -184,7 +285,7 @@ private ConsoleCommand( if (!string.IsNullOrEmpty(args.Data)) { this.output.Add(args.Data); - Console.WriteLine($"[OUT] {args.Data}"); + Console.WriteLine($"{command} {arguments} [OUT] {args.Data}"); onOutputReceived?.Invoke(args.Data); } From 96f344a97a3eb19d109497bb8bcecc013062c75d Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 23 Jan 2025 19:03:28 -0800 Subject: [PATCH 2/3] Move namespace. Remove metrics for the unit test since it will have user_events tests for logs and traces as well. --- .../Internal/Transports/UnixUserEventsDataTransport.cs | 2 +- .../UnixUserEventsDataTransportTests.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs index d5ad7cdee6..f292848de6 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/UnixUserEventsDataTransport.cs @@ -5,7 +5,7 @@ using Microsoft.LinuxTracepoints.Provider; -namespace OpenTelemetry.Exporter.Geneva; +namespace OpenTelemetry.Exporter.Geneva.Transports; internal sealed class UnixUserEventsDataTransport : IDisposable { diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs index 9a8dded4bf..52e6d5f79b 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs @@ -7,12 +7,13 @@ using System.Globalization; using System.Text.RegularExpressions; using Microsoft.LinuxTracepoints.Provider; +using OpenTelemetry.Exporter.Geneva.Transports; using Xunit; using Xunit.Abstractions; namespace OpenTelemetry.Exporter.Geneva.Tests; -[Trait("CategoryName", "Geneva:user_events:metrics")] +[Trait("CategoryName", "Geneva:user_events")] public class UnixUserEventsDataTransportTests { /* @@ -26,7 +27,7 @@ public class UnixUserEventsDataTransportTests * these tests do). * * Command: - * sudo dotnet test --configuration Debug --framework net8.0 --filter CategoryName=Geneva:user_events:metrics + * sudo dotnet test --configuration Debug --framework net8.0 --filter CategoryName=Geneva:user_events * * How these tests work: * From 1bbfa75581f04ded84301177bd7fb677b0c8b682 Mon Sep 17 00:00:00 2001 From: Sean Li Date: Thu, 23 Jan 2025 19:17:03 -0800 Subject: [PATCH 3/3] Disable the test case for now. The kernel in test runner doesn't support user_events yet. --- .../UnixUserEventsDataTransportTests.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs index 52e6d5f79b..b2e5c6be22 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/UnixUserEventsDataTransportTests.cs @@ -50,7 +50,7 @@ public UnixUserEventsDataTransportTests(ITestOutputHelper testOutputHelper) this.testOutputHelper = testOutputHelper; } - [Fact(Skip = "This would fail on Ubuntu. Skipping for now.")] + [Fact(Skip = "This would fail on Ubuntu. Skipping for now. See issue: #2326.")] public void UserEvents_Enabled_Success_Linux() { EnsureUserEventsEnabled(); @@ -114,7 +114,7 @@ public void UserEvents_Enabled_Success_Linux() } } - [Fact(Skip = "This would fail on Ubuntu. Skipping for now.")] + [Fact(Skip = "This would fail on Ubuntu. Skipping for now. See issue: #2326.")] public void UserEvents_Disabled_Success_Linux() { EnsureUserEventsEnabled(); @@ -144,8 +144,7 @@ public void UserEvents_Disabled_Success_Linux() } } - // [Fact(Skip = "This would fail on Ubuntu. Skipping for now.")] - [Fact] + [Fact(Skip = "This would fail on Ubuntu. Skipping for now. See issue: #2326.")] public void UserEvents_Logs_Success_Linux() { EnsureUserEventsEnabled();