-
Notifications
You must be signed in to change notification settings - Fork 306
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
[Exporter.Geneva] Adding user_events support for logs on Linux - user_events data transport utility #2479
base: main
Are you sure you want to change the base?
[Exporter.Geneva] Adding user_events support for logs on Linux - user_events data transport utility #2479
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#if NET | ||
|
||
using Microsoft.LinuxTracepoints.Provider; | ||
|
||
namespace OpenTelemetry.Exporter.Geneva.Transports; | ||
|
||
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reasoning behind the name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now it's a placeholder. I need to work with the agent to find out the endpoint. The agent is also working in progress so it might be a while to finalize. |
||
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 |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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: | ||||||||||
* | ||||||||||
|
@@ -49,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(); | ||||||||||
|
@@ -113,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(); | ||||||||||
|
@@ -143,6 +144,106 @@ public void UserEvents_Disabled_Success_Linux() | |||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
[Fact(Skip = "This would fail on Ubuntu. Skipping for now. See issue: #2326.")] | ||||||||||
public void UserEvents_Logs_Success_Linux() | ||||||||||
{ | ||||||||||
EnsureUserEventsEnabled(); | ||||||||||
|
||||||||||
var listener = new PerfTracepointListener( | ||||||||||
"MicrosoftOpenTelemetryLogs_L4K1", | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is "L4" hardcoded? or its based on severity? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both numbers can change. Description: Lines 13 to 15 in c2eb2e5
Constructor: Line 62 in c2eb2e5
For now I've hard-coded all these values, but at least EventLevel will be dynamic as I make code changes later. |
||||||||||
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"); | ||||||||||
|
||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eventid and event name too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was supposed to be a made-up event for testing data transport works, so I didn't make it a conforming event. |
||||||||||
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); | ||||||||||
} | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a plan to implement
IDataTransport
later?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not likely, the API to send
user_events
looks like this:eventBuilder.Write(logsTracepoint)
. Not like the other data transports which send a byte array.