From 68a25e8a0344e231e3740914610ad122f97d3cfc Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 30 Sep 2024 09:17:09 -0700 Subject: [PATCH] [geneva] Vendor code for writing tracepoints on Linux (#2114) --- .editorconfig | 3 + .../External/.editorconfig | 2 + .../LinuxTracepoints-Net/.editorconfig | 6 + .../Provider/DataSegment.cs | 24 + .../Provider/PerfTracepoint.cs | 411 ++++++++++++ .../Provider/RawFileHandle.cs | 165 +++++ .../Provider/TracepointHandle.cs | 631 ++++++++++++++++++ .../LinuxTracepoints-Net/Provider/Utility.cs | 41 ++ .../External/LinuxTracepoints-Net/README.md | 17 + .../LinuxTracepoints-Net/Types/EventHeader.cs | 302 +++++++++ .../Types/EventHeaderExtension.cs | 46 ++ .../Types/EventHeaderExtensionKind.cs | 117 ++++ .../Types/EventHeaderFieldEncoding.cs | 269 ++++++++ .../Types/EventHeaderFieldFormat.cs | 168 +++++ .../Types/EventHeaderFlags.cs | 36 + .../External/TraceLoggingDynamic.cs | 2 - .../OpenTelemetry.Exporter.Geneva.csproj | 6 + .../THIRD-PARTY-NOTICES.TXT | 30 + 18 files changed, 2274 insertions(+), 2 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/.editorconfig create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/.editorconfig create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/DataSegment.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/PerfTracepoint.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/RawFileHandle.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/TracepointHandle.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/Utility.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/README.md create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeader.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtension.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtensionKind.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldEncoding.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldFormat.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFlags.cs create mode 100644 src/OpenTelemetry.Exporter.Geneva/THIRD-PARTY-NOTICES.TXT diff --git a/.editorconfig b/.editorconfig index cdfa4e3246..3b4f1d86da 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,7 @@ # To learn more about .editorconfig see https://aka.ms/editorconfigdocs + +root = true + ############################### # Core EditorConfig Options # ############################### diff --git a/src/OpenTelemetry.Exporter.Geneva/External/.editorconfig b/src/OpenTelemetry.Exporter.Geneva/External/.editorconfig new file mode 100644 index 0000000000..c895abfaeb --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +generated_code = true diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/.editorconfig b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/.editorconfig new file mode 100644 index 0000000000..e0875140a8 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/.editorconfig @@ -0,0 +1,6 @@ +[*.cs] +# Use DefaultDllImportSearchPaths attribute for P/Invokes +dotnet_diagnostic.CA5392.severity = none + +# The ref modifier for argument corresponding to in parameter is equivalent to in. Consider using in instead. +dotnet_diagnostic.CS9191.severity = none diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/DataSegment.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/DataSegment.cs new file mode 100644 index 0000000000..7ff5ce81a6 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/DataSegment.cs @@ -0,0 +1,24 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints.Provider; + +using System.Runtime.InteropServices; + +/// +/// struct iovec, for use with calls to writev. +/// +[StructLayout(LayoutKind.Sequential)] +internal unsafe struct DataSegment +{ + public void* PinnedBase; + public nuint Length; + + public DataSegment(void* pinnedBase, nuint length) + { + this.PinnedBase = pinnedBase; + this.Length = length; + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/PerfTracepoint.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/PerfTracepoint.cs new file mode 100644 index 0000000000..c352eed671 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/PerfTracepoint.cs @@ -0,0 +1,411 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints.Provider; + +using System; + +/// +/// Flags used when registering a user_events tracepoint. +/// From LINUX/include/uapi/linux/user_events.h enum user_reg_flag. +/// +[Flags] +internal enum PerfUserEventReg : UInt16 +{ + /// + /// No flags set (default). + /// + None, + + /// + /// USER_EVENT_REG_PERSIST: + /// Event will not delete upon last reference closing. + /// + Persist = 1 << 0, + + /// + /// USER_EVENT_REG_MULTI_FORMAT: + /// Event will be allowed to have multiple formats. + /// + MultiFormat = 1 << 1, +} + +/// +/// Direct access to a user_events tracepoint -- caller is responsible for the registration +/// string and for packing/marshalling event data. +///
+/// For more information, see user_events. +///
+/// The tracepoint is registered by the constructor. +/// You'll generally construct all of your application's Tracepoint objects at application +/// start or component initialization. +///
+/// You'll use the IsEnabled property to determine +/// whether any sessions are collecting the tracecpoint, and you'll use the Write method +/// to write events. +///
+/// Normal usage: +/// +/// Tracepoint tp = new PerfTracepoint("MyEventName int MyField1; int MyField2"); +/// +/// // To log an event where preparing the data is very simple: +/// tp.Write(data...); +/// +/// // To log an event where preparing the data is expensive: +/// if (tp.IsEnabled) // Skip preparing data and calling Write if the tracepoint is not enabled. +/// { +/// var data = ...; // Prepare data that needs to be logged. +/// tp.Write(data...); +/// } +/// +/// Note that tracepoint registration can fail, and Write operations can also fail. +/// The RegisterResult property and the error code returned by the Write method are provided +/// for debugging and diagnostics, but you'll usually ignore these in normal operation (most +/// applications should continue to work even if tracing isn't working). +///
+internal class PerfTracepoint : IDisposable +{ + private readonly TracepointHandle handle; + + /// + /// As a performance optimization, avoid one level of indirection during calls to IsEnabled + /// by caching the enablement array. The array always has Length == 1. The contents of this + /// array should be considered read-only and MUST NOT be modified (the array data is updated + /// by the Linux kernel when the tracepoint is enabled or disabled). + ///
+ /// When handle.IsInvalid, one array is shared by all invalid handles and is a normal allocation. + /// When !handle.IsInvalid, the array is unique for for each handle and is a pinned allocation. + ///
+ private readonly Int32[] enablementArray; + + /// + /// Given a user_events command string, attempts to register a user_events tracepoint. + ///
+ /// If registration succeeds, the new tracepoint will be valid and active: + /// IsEnabled is dynamic, RegisterResult == 0, Write sends data to the kernel. + ///
+ /// If registration fails, the new tracepoint will be invalid and inactive: + /// IsEnabled == false, RegisterResult != 0, Write does nothing and immediately returns EBADF. + ///
+ /// + /// user_events command string, + /// e.g. "MyEventName int arg1; u32 arg2". String should use only Latin-1 characters (each + /// char in the string should have value 255 or below). + /// + /// + /// user_reg flags, + /// e.g. USER_EVENT_REG_PERSIST or USER_EVENT_REG_MULTI_FORMAT. + /// + public PerfTracepoint(ReadOnlySpan nameArgs, PerfUserEventReg flags = 0) + { + var h = TracepointHandle.Register(nameArgs, flags); + this.handle = h; + this.enablementArray = h.DangerousGetEnablementArray(); + } + + /// + /// Given a NUL-terminated Latin1-encoded user_events command string, attempts to + /// register a user_events tracepoint. + ///
+ /// If registration succeeds, the new tracepoint will be valid and active: + /// IsEnabled is dynamic, RegisterResult == 0, Write sends data to the kernel. + ///
+ /// If registration fails, the new tracepoint will be invalid and inactive: + /// IsEnabled == false, RegisterResult != 0, Write does nothing and immediately returns EBADF. + ///
+ /// + /// user_events command string, + /// Latin-1 encoded, NUL-terminated, e.g. "MyEventName int arg1; u32 arg2\0". + /// + /// + /// user_reg flags, + /// e.g. USER_EVENT_REG_PERSIST or USER_EVENT_REG_MULTI_FORMAT. + /// + /// + /// nulTerminatedNameArgs does not contain any NUL termination (no 0 bytes). + /// + public PerfTracepoint(ReadOnlySpan nulTerminatedNameArgs, PerfUserEventReg flags = 0) + { + if (0 > nulTerminatedNameArgs.LastIndexOf((byte)0)) + { + throw new ArgumentException( + nameof(nulTerminatedNameArgs) + " must be NUL-terminated, i.e. must contain (byte)0.", + nameof(nulTerminatedNameArgs)); + } + + var h = TracepointHandle.Register(nulTerminatedNameArgs, flags); + this.handle = h; + this.enablementArray = h.DangerousGetEnablementArray(); + } + + /// + /// Returns true if this tracepoint is registered and one or more tracepoint collection sessions + /// are collecting this tracepoint. + ///
+ /// Returns false if this tracepoint is unregistered or if there are no tracepoint collection + /// sessions that are collecting this tracepoint. The tracepoint will be unregistered if + /// registration failed or if the tracepoint has been disposed. + ///
+ /// Note that this property is provided to support performance optimization, but use of this + /// property is optional. It's ok to call Write even if IsEnabled returns false. If your + /// tracepoint is not being collected, the Write method will do nothing and will immediately + /// return EBADF. This property is provided so that you can efficiently skip preparing your data + /// and calling the Write method if your tracepoint is not being collected. + ///
+ public bool IsEnabled => this.enablementArray[0] != 0; + + /// + /// If tracepoint registration succeeded, returns 0. + /// Otherwise, returns an errno indicating the error. + ///
+ /// This property is for diagnostic purposes and should usually be ignored for normal + /// operation -- most programs should continue to operate even if trace registration + /// fails. + ///
+ public int RegisterResult => this.handle.RegisterResult; + + /// + /// Unregisters the tracepoint. After calling Dispose(), IsEnabled will return false and + /// Write will do nothing and immediately return EBADF. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// If !IsEnabled, immediately returns EBADF. + /// Otherwise, writes an event with no data. + /// + /// + /// 0 if event was written, errno otherwise. + /// Typically returns EBADF if no data collection sessions are listening for the tracepoint. + /// The return value is for debugging/diagnostic purposes and is usually ignored in normal operation + /// since most programs should continue to function even when tracing is not configured. + /// + public int Write() + { + if (this.enablementArray[0] == 0) + { + return TracepointHandle.DisabledEventError; + } + + return this.handle.Write(stackalloc DataSegment[] { + default, // segment[0] is used for headers. + }); + } + + /// + /// If !IsEnabled, immediately returns EBADF. + /// Otherwise, writes an event with 1 chunk of data. + /// + /// + /// 0 if event was written, errno otherwise. + /// Typically returns EBADF if no data collection sessions are listening for the tracepoint. + /// The return value is for debugging/diagnostic purposes and is usually ignored in normal operation + /// since most programs should continue to function even when tracing is not configured. + /// + public int Write( + ReadOnlySpan v1) + where T1 : unmanaged + { + if (this.enablementArray[0] == 0) + { + return TracepointHandle.DisabledEventError; + } + + unsafe + { + fixed (void* + p1 = v1) + { + return this.handle.Write(stackalloc DataSegment[] { + default, // segment[0] is used for headers. + new DataSegment(p1, (uint)v1.Length * (uint)sizeof(T1)), + }); + } + } + } + + /// + /// If !IsEnabled, immediately returns EBADF. + /// Otherwise, writes an event with 2 chunks of data. + /// + /// + /// 0 if event was written, errno otherwise. + /// Typically returns EBADF if no data collection sessions are listening for the tracepoint. + /// The return value is for debugging/diagnostic purposes and is usually ignored in normal operation + /// since most programs should continue to function even when tracing is not configured. + /// + public int Write( + ReadOnlySpan v1, + ReadOnlySpan v2) + where T1 : unmanaged + where T2 : unmanaged + { + if (this.enablementArray[0] == 0) + { + return TracepointHandle.DisabledEventError; + } + + unsafe + { + fixed (void* + p1 = v1, + p2 = v2) + { + return this.handle.Write(stackalloc DataSegment[] { + default, // segment[0] is used for headers. + new DataSegment(p1, (uint)v1.Length * (uint)sizeof(T1)), + new DataSegment(p2, (uint)v2.Length * (uint)sizeof(T2)), + }); + } + } + } + + /// + /// If !IsEnabled, immediately returns EBADF. + /// Otherwise, writes an event with 3 chunks of data. + /// + /// + /// 0 if event was written, errno otherwise. + /// Typically returns EBADF if no data collection sessions are listening for the tracepoint. + /// The return value is for debugging/diagnostic purposes and is usually ignored in normal operation + /// since most programs should continue to function even when tracing is not configured. + /// + public int Write( + ReadOnlySpan v1, + ReadOnlySpan v2, + ReadOnlySpan v3) + where T1 : unmanaged + where T2 : unmanaged + where T3 : unmanaged + { + if (this.enablementArray[0] == 0) + { + return TracepointHandle.DisabledEventError; + } + + unsafe + { + fixed (void* + p1 = v1, + p2 = v2, + p3 = v3) + { + return this.handle.Write(stackalloc DataSegment[] { + default, // segment[0] is used for headers. + new DataSegment(p1, (uint)v1.Length * (uint)sizeof(T1)), + new DataSegment(p2, (uint)v2.Length * (uint)sizeof(T2)), + new DataSegment(p3, (uint)v3.Length * (uint)sizeof(T3)), + }); + } + } + } + + /// + /// If !IsEnabled, immediately returns EBADF. + /// Otherwise, writes an event with 4 chunks of data. + /// + /// + /// 0 if event was written, errno otherwise. + /// Typically returns EBADF if no data collection sessions are listening for the tracepoint. + /// The return value is for debugging/diagnostic purposes and is usually ignored in normal operation + /// since most programs should continue to function even when tracing is not configured. + /// + public int Write( + ReadOnlySpan v1, + ReadOnlySpan v2, + ReadOnlySpan v3, + ReadOnlySpan v4) + where T1 : unmanaged + where T2 : unmanaged + where T3 : unmanaged + where T4 : unmanaged + { + if (this.enablementArray[0] == 0) + { + return TracepointHandle.DisabledEventError; + } + + unsafe + { + fixed (void* + p1 = v1, + p2 = v2, + p3 = v3, + p4 = v4) + { + return this.handle.Write(stackalloc DataSegment[] { + default, // segment[0] is used for headers. + new DataSegment(p1, (uint)v1.Length * (uint)sizeof(T1)), + new DataSegment(p2, (uint)v2.Length * (uint)sizeof(T2)), + new DataSegment(p3, (uint)v3.Length * (uint)sizeof(T3)), + new DataSegment(p4, (uint)v4.Length * (uint)sizeof(T4)), + }); + } + } + } + + /// + /// If !IsEnabled, immediately returns EBADF. + /// Otherwise, writes an event with 5 chunks of data. + /// + /// + /// 0 if event was written, errno otherwise. + /// Typically returns EBADF if no data collection sessions are listening for the tracepoint. + /// The return value is for debugging/diagnostic purposes and is usually ignored in normal operation + /// since most programs should continue to function even when tracing is not configured. + /// + public int Write( + ReadOnlySpan v1, + ReadOnlySpan v2, + ReadOnlySpan v3, + ReadOnlySpan v4, + ReadOnlySpan v5) + where T1 : unmanaged + where T2 : unmanaged + where T3 : unmanaged + where T4 : unmanaged + where T5 : unmanaged + { + if (this.enablementArray[0] == 0) + { + return TracepointHandle.DisabledEventError; + } + + unsafe + { + fixed (void* + p1 = v1, + p2 = v2, + p3 = v3, + p4 = v4, + p5 = v5) + { + return this.handle.Write(stackalloc DataSegment[] { + default, // segment[0] is used for headers. + new DataSegment(p1, (uint)v1.Length * (uint)sizeof(T1)), + new DataSegment(p2, (uint)v2.Length * (uint)sizeof(T2)), + new DataSegment(p3, (uint)v3.Length * (uint)sizeof(T3)), + new DataSegment(p4, (uint)v4.Length * (uint)sizeof(T4)), + new DataSegment(p5, (uint)v5.Length * (uint)sizeof(T5)), + }); + } + } + } + + /// + /// If disposing is true, calls this.handle.Dispose(). + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.handle.Dispose(); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/RawFileHandle.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/RawFileHandle.cs new file mode 100644 index 0000000000..7d80faa023 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/RawFileHandle.cs @@ -0,0 +1,165 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints.Provider; + +using System; +using System.Runtime.InteropServices; + +/// +/// Wraps a raw Posix file descriptor returned from "open". +/// Non-negative handle is a valid descriptor. +/// Negative handle is a negative errno with the result of the "open" operation. +/// +internal sealed class RawFileHandle + : SafeHandle +{ + private const int UnknownErrno = int.MaxValue; + + /// + /// Initializes a new handle that is invalid. + /// Do not use this constructor. To create a valid handle, call the static Open method. + /// + public RawFileHandle() + : base(new IntPtr(-UnknownErrno), true) + { + return; + } + + /// + /// Returns true if handle is negative (stores a negative errno). + /// + public override bool IsInvalid => (nint)this.handle < 0; + + /// + /// If open succeeded, returns 0. Otherwise returns the errno from open. + /// + public int OpenResult + { + get + { + var h = (nint)this.handle; + return h >= 0 ? 0 : -(int)h; + } + } + + /// + /// Calls "open" with the given path name and with flags = O_WRONLY. + /// On success, returns a valid handle. On failure, returns an invalid handle + /// (check OpenResult for the errno). + /// + public static RawFileHandle OpenWRONLY(ReadOnlySpan nulTerminatedPathName) + { + var result = new RawFileHandle(); + + // Need a finally block to make sure the thread is not interrupted + // between the open and the handle assignment. + try + { + // Nothing. + } + finally + { + unsafe + { + fixed (byte* pathNamePtr = nulTerminatedPathName) + { + const int O_WRONLY = 0x0001; + result.handle = (nint)NativeMethods.open(pathNamePtr, O_WRONLY, 0); + } + } + } + + if (0 > (nint)result.handle) + { + var errno = Marshal.GetLastWin32Error(); + if (errno <= 0) + { + errno = UnknownErrno; + } + + result.handle = new IntPtr(-errno); + } + + return result; + } + + /// + /// Calls "writev" with the given data. + /// On success, returns the number of bytes written. + /// On error, returns -1 (check Marshal.GetLastWin32Error() for the errno). + /// + public nint WriteV(ReadOnlySpan iovecs) + { + var needRelease = false; + try + { + this.DangerousAddRef(ref needRelease); + unsafe + { + fixed (DataSegment* iovecsPtr = iovecs) + { + return NativeMethods.writev((Int32)(nint)this.handle, iovecsPtr, iovecs.Length); + } + } + } + finally + { + if (needRelease) + { + this.DangerousRelease(); + } + } + } + + /// + /// Calls "ioctl" with the given request and data. + /// On error, returns -1 (check Marshal.GetLastWin32Error() for the errno). + /// + public int Ioctl(uint request, ref T data) + where T : unmanaged + { + var needRelease = false; + try + { + this.DangerousAddRef(ref needRelease); + unsafe + { + fixed (void* dataPtr = &data) + { + return NativeMethods.ioctl((Int32)(nint)this.handle, new UIntPtr(request), dataPtr); + } + } + } + finally + { + if (needRelease) + { + this.DangerousRelease(); + } + } + } + + protected override bool ReleaseHandle() + { + var h = unchecked((Int32)(nint)this.handle); + return 0 <= NativeMethods.close(h); + } + + private unsafe static class NativeMethods + { + [DllImport("libc", SetLastError = true)] + public static extern int close(Int32 fd); + + [DllImport("libc", SetLastError = true)] + public static extern int open(byte* pathname, Int32 flags, Int32 mode); + + [DllImport("libc", SetLastError = true)] + public static extern int ioctl(Int32 fd, UIntPtr request, void* data); + + [DllImport("libc", SetLastError = true)] + public static extern IntPtr writev(Int32 fd, DataSegment* iovec, Int32 iovecCount); + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/TracepointHandle.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/TracepointHandle.cs new file mode 100644 index 0000000000..e50d6092c5 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/TracepointHandle.cs @@ -0,0 +1,631 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints.Provider; + +using System; +using System.IO; +using System.Runtime.InteropServices; +using Debug = System.Diagnostics.Debug; +using Interlocked = System.Threading.Interlocked; + +/// +/// Low-level owner of a tracepoint registration. +/// Handle is the tracepoint's write_index. +/// Uses a pinned allocation for the tracepoint's is-enabled buffer. +/// +internal sealed class TracepointHandle : SafeHandle +{ + /// + /// The error to return from Write to a disabled event = EBADF = 9. + /// + public const int DisabledEventError = 9; + + /// + /// The error to return from Write of an event that is too big = E2BIG = 7. + /// + public const int EventTooBigError = 7; + + private const byte EnableSize = sizeof(UInt32); + + private const int EBADF = 9; + private const int UnknownErrno = int.MaxValue; + private const int UnregisteredWriteIndex = -1; + + private const int IOC_NRSHIFT = 0; + private const int IOC_TYPESHIFT = IOC_NRSHIFT + 8; // 8 = IOC_NRBITS + private const int IOC_SIZESHIFT = IOC_TYPESHIFT + 8; // 8 = IOC_TYPEBITS + private const int IOC_DIRSHIFT = IOC_SIZESHIFT + 14; // 14 = IOC_SIZEBITS + private const uint IOC_WRITE = 1; + private const uint IOC_READ = 2; + private const uint DIAG_IOC_MAGIC = '*'; + + private static RawFileHandle? userEventsDataStatic; + private static Int32[]? emptyEnabledPinned; // From normal heap. + + /// + /// When this.IsInvalid, enabledPinned is a shared emptyEnabledPinned from the normal heap. + /// When !this.IsInvalid, enabledPinned is a unique allocation from the pinned object heap. + /// + private Int32[] enabledPinned; + + /// + /// Initializes a new handle that is invalid. + /// +#pragma warning disable CA1419 // Provide a parameterless constructor. (We don't need the runtime to create instances of TracepointHandle.) + private TracepointHandle() +#pragma warning restore CA1419 + : base((nint)UnregisteredWriteIndex, true) + { + var enabledPinned = emptyEnabledPinned ?? + Utility.InterlockedInitSingleton(ref emptyEnabledPinned, new Int32[1]); + Debug.Assert(0 == enabledPinned[0]); + this.enabledPinned = enabledPinned; + } + + /// + /// If registration succeeded, returns 0. + /// If registration failed, returns the errno from the failed open/ioctl. + /// + public int RegisterResult { get; private set; } = UnknownErrno; + + /// + /// Returns true if this tracepoint is enabled, false otherwise. + /// Value may change at any time while the tracepoint is registered. + /// + public bool IsEnabled => 0 != this.enabledPinned[0]; + + /// + /// Returns true if registration was successful. + /// (Remains true even after handle is closed/disposed.) + /// + public override bool IsInvalid => (nint)UnregisteredWriteIndex == this.handle; + + /// + /// Given an all-Latin1 user_events command string (no characters with value > 255), + /// attempts to register it. Returns a TracepointHandle with the result. + /// Syntax for nameArgs is given here: + /// https://docs.kernel.org/trace/user_events.html#command-format, + /// e.g. "MyEventName int arg1; u32 arg2". + ///
+ /// If registration succeeds, the returned handle will be valid and active: + /// IsInvalid == false, IsEnabled is dynamic, RegisterResult == 0, + /// Write is meaningful. + ///
+ /// If registration fails, the returned handle will be invalid and inactive: + /// IsInvalid == true, IsEnabled == false, RegisterResult != 0, + /// Write will always return EBADF. + ///
+ public static TracepointHandle Register(ReadOnlySpan nameArgs, PerfUserEventReg flags = 0) + { + Span nulTerminatedNameArgs = stackalloc byte[nameArgs.Length + 1]; + for (var i = 0; i < nameArgs.Length; i += 1) + { + nulTerminatedNameArgs[i] = unchecked((byte)nameArgs[i]); + } + + nulTerminatedNameArgs[nameArgs.Length] = 0; + return Register(nulTerminatedNameArgs, flags); + } + + /// + /// Given a NUL-terminated Latin1 user_events command string, attempts to register it. + /// Returns a TracepointHandle with the result. + /// Syntax for nameArgs is given here: + /// https://docs.kernel.org/trace/user_events.html#command-format, + /// e.g. "MyEventName int arg1; u32 arg2". + ///
+ /// If registration succeeds, the returned handle will be valid and active: + /// IsInvalid == false, IsEnabled is meaningful, RegisterResult == 0, + /// Write is meaningful. + ///
+ /// If registration fails, the returned handle will be invalid and inactive: + /// IsInvalid == true, IsEnabled == false, RegisterResult != 0, + /// Write will always return EBADF. + ///
+ public static TracepointHandle Register(ReadOnlySpan nulTerminatedNameArgs, PerfUserEventReg flags = 0) + { + Debug.Assert(0 <= nulTerminatedNameArgs.LastIndexOf((byte)0)); + + var tracepoint = new TracepointHandle(); + + var userEventsData = userEventsDataStatic ?? InitUserEventsDataStatic(); + if (userEventsData.OpenResult != 0) + { + tracepoint.InitFailed(userEventsData.OpenResult); + } + else + { + var enabledPinned = GC.AllocateArray(1, pinned: true); + Debug.Assert(0 == enabledPinned[0]); + + int ioctlResult; + unsafe + { + fixed (Int32* enabledPtr = enabledPinned) + { + fixed (byte* nameArgsPtr = nulTerminatedNameArgs) + { + var reg = new user_reg + { + size = user_reg.SizeOfStruct, + enable_size = EnableSize, + flags = flags, + enable_addr = (nuint)enabledPtr, + name_args = (nuint)nameArgsPtr, + }; + + // Need a finally block to make sure the thread is not interrupted + // between the ioctl and the SetHandle. + try + { + // Nothing. + } + finally + { + var DIAG_IOCSREG = + ((IOC_WRITE | IOC_READ) << IOC_DIRSHIFT) | + (DIAG_IOC_MAGIC << IOC_TYPESHIFT) | + (0u << IOC_NRSHIFT) | + ((uint)IntPtr.Size << IOC_SIZESHIFT); + ioctlResult = userEventsData.Ioctl(DIAG_IOCSREG, ref reg); + if (ioctlResult >= 0) + { + tracepoint.enabledPinned = enabledPinned; + tracepoint.SetHandle((nint)reg.write_index); + } + } + } + } + } + + if (ioctlResult >= 0) + { + tracepoint.InitSucceeded(); + } + else + { + var errno = Marshal.GetLastWin32Error(); + if (errno <= 0) + { + errno = UnknownErrno; + } + + tracepoint.InitFailed(errno); + } + } + + // All code paths should have called either InitSucceeded or InitFailed. + return tracepoint; + } + + /// + /// Sends tracepoint data to the user_events_data file. Uses data[0] for headers. + /// Returns EBADF if closed. Does NOT check IsEnabled (caller should do that). + ///
+ /// Requires: data[0].Length == 0 (data[0] will be used for headers). + ///
+ public int Write(Span data) + { + // Precondition: slot for write_index in data[0]. + Debug.Assert(data[0].Length == 0); + + var userEventsData = userEventsDataStatic; + Debug.Assert(userEventsData != null); // Otherwise there would be no TracepointHandle instance. + Debug.Assert(userEventsData.OpenResult == 0); // Otherwise Enabled would be false. + + var writeIndex = new WriteIndexPlus { WriteIndex = (Int32)(nint)this.handle, Padding = 0 }; + unsafe + { + // Workaround: On old kernels, events with 0 bytes of data don't get written. + // If event has 0 bytes of data, add a byte to avoid the problem. + data[0] = new DataSegment(&writeIndex, sizeof(Int32) + (data.Length == 1 ? 1u : 0u)); + } + + if (this.IsClosed) + { + return DisabledEventError; + } + + // Ignore race condition: if we're disposed between checking IsClosed and calling WriteV, + // we will write the data using a write_index that doesn't belong to us. That's not great, + // but it's not fatal and probably not worth degrading performance to avoid it. The + // write_index is unlikely to be recycled during the race condition, and even if it is + // recycled, the worst consequence would be a garbage event in a trace. Could avoid with: + // try { AddRef; WriteV; } catch { return EBADF; } finally { Release; }. + + var writevResult = userEventsData.WriteV(data); + return writevResult >= 0 ? 0 : Marshal.GetLastWin32Error(); + } + + /// + /// Sends tracepoint with EventHeader to the user_events_data file. Uses data[0] for headers. + /// Returns EBADF if closed. Does NOT check IsEnabled (caller should do that). + ///
+ /// Fills in data[0] with writeIndex + eventHeader + activityIdBlock? + metadataHeader?. Sets the extension + /// block's flags based on metaLength. + ///
+ /// Requires: data[0].Length == 0 (data[0] will be used for headers). + ///
+ /// Requires: relatedId cannot be present unless activityId is present. + ///
+ /// Requires: If activityId is present or metaLength != 0 then + /// eventHeader.Flags must equal DefaultWithExtension. + ///
+ /// Requires: If metaLength != 0 then data[1] starts with metadata extension block data. + ///
+ public unsafe int WriteEventHeader( + EventHeader eventHeader, + Guid* activityId, + Guid* relatedId, + ushort metaLength, + Span data) + { + // Precondition: slot for write_index in data[0]. + Debug.Assert(data[0].Length == 0); + + // Precondition: relatedId cannot be present unless activityId is present. + Debug.Assert(relatedId == null || activityId != null); + + // Precondition: eventHeader.Flags must match up with presence of first extension. + Debug.Assert((activityId == null && metaLength == 0) || + eventHeader.Flags == (EventHeader.DefaultFlags | EventHeaderFlags.Extension)); + + // Precondition: metaLength implies metadata extension block data. + Debug.Assert(metaLength == 0 || data.Length > 1); + + var userEventsData = userEventsDataStatic; + Debug.Assert(userEventsData != null); // Otherwise there would be no TracepointHandle instance. + Debug.Assert(userEventsData.OpenResult == 0); // Otherwise Enabled would be false. + + const byte HeadersMax = sizeof(Int32) // writeIndex + + EventHeader.SizeOfStruct // eventHeader + + EventHeaderExtension.SizeOfStruct + 16 + 16 // activityId header + activityId + relatedId + + EventHeaderExtension.SizeOfStruct; // metadata header + var writeIndex = (Int32)(nint)this.handle; + unsafe + { + uint* headersUInt32 = stackalloc UInt32[HeadersMax / sizeof(UInt32)]; // Ensure 4-byte alignment. + byte* headers = (byte*)headersUInt32; + uint pos = 0; + + *(Int32*)&headers[pos] = (Int32)(nint)this.handle; + pos += sizeof(Int32); + + *(EventHeader*)&headers[pos] = eventHeader; + pos += EventHeader.SizeOfStruct; + + if (activityId != null) + { + var kind = EventHeaderExtensionKind.ActivityId | (metaLength == 0 ? 0 : EventHeaderExtensionKind.ChainFlag); + if (relatedId != null) + { + *(EventHeaderExtension*)&headers[pos] = new EventHeaderExtension { Kind = kind, Size = 32 }; + pos += EventHeaderExtension.SizeOfStruct; + Utility.WriteGuidBigEndian(new Span(&headers[pos], 16), *activityId); + pos += 16; + Utility.WriteGuidBigEndian(new Span(&headers[pos], 16), *relatedId); + pos += 16; + } + else + { + *(EventHeaderExtension*)&headers[pos] = new EventHeaderExtension { Kind = kind, Size = 16 }; + pos += EventHeaderExtension.SizeOfStruct; + Utility.WriteGuidBigEndian(new Span(&headers[pos], 16), *activityId); + pos += 16; + } + } + + if (metaLength != 0) + { + *(EventHeaderExtension*)&headers[pos] = new EventHeaderExtension + { + Kind = EventHeaderExtensionKind.Metadata, // Last one, so no chain flag. + Size = metaLength, + }; + pos += EventHeaderExtension.SizeOfStruct; + } + + data[0] = new DataSegment(headers, pos); + } + + if (this.IsClosed) + { + return DisabledEventError; + } + + // Ignore race condition: if we're disposed between checking IsClosed and calling WriteV, + // we will write the data using a write_index that doesn't belong to us. That's not great, + // but it's not fatal and probably not worth degrading performance to avoid it. The + // write_index is unlikely to be recycled during the race condition, and even if it is + // recycled, the worst consequence would be a garbage event in a trace. Could avoid with: + // try { AddRef; WriteV; } catch { return EBADF; } finally { Release; }. + + var writevResult = userEventsData.WriteV(data); + return writevResult >= 0 ? 0 : Marshal.GetLastWin32Error(); + } + + /// + /// Returns an array of length 1 that contains the value that will be updated by the + /// kernel when the tracepoint is enabled or disabled. Caller MUST NOT modify the contents + /// of the array. + ///
+ /// When this.IsInvalid, the array is shared by all other invalid handles and is a normal allocation. + /// When !this.IsInvalid, there is a separate array for each handle and is a pinned allocation. + ///
+ public Int32[] DangerousGetEnablementArray() + { + return this.enabledPinned; + } + + protected override bool ReleaseHandle() + { + var userEventsData = userEventsDataStatic; + Debug.Assert(userEventsData != null); // Otherwise there would be no TracepointHandle instance. + + var enabledPinned = this.enabledPinned; + int ioctlResult; + unsafe + { + Debug.Assert(!ReferenceEquals(enabledPinned, emptyEnabledPinned)); + fixed (Int32* enabledPtr = enabledPinned) + { + var unreg = new user_unreg + { + size = user_unreg.SizeOfStruct, + disable_addr = (nuint)enabledPtr, + }; + + var DIAG_IOCSUNREG = + (IOC_WRITE << IOC_DIRSHIFT) | + (DIAG_IOC_MAGIC << IOC_TYPESHIFT) | + (2u << IOC_NRSHIFT) | + ((uint)IntPtr.Size << IOC_SIZESHIFT); + ioctlResult = userEventsData.Ioctl(DIAG_IOCSUNREG, ref unreg); + } + } + + enabledPinned[0] = 0; // Force IsEnabled = false. + return 0 <= ioctlResult; + } + + /// + /// Locates and opens the user_events_data file. + /// First call to this will update userEventsDataStatic with the result. + /// + /// The new value of userEventsDataStatic (never null). + private static RawFileHandle InitUserEventsDataStatic() + { + RawFileHandle? resultHandle = null; + RawFileHandle? newHandle = null; + try + { + newHandle = RawFileHandle.OpenWRONLY("/sys/kernel/tracing/user_events_data\0"u8); + if (newHandle.OpenResult == 0) + { + // Success. + } + else if (!File.Exists("/proc/mounts")) + { + // Give up. + } + else + { + Span path = stackalloc byte[274]; // 256 + sizeof("/user_events_data\0") + FileStream? mounts = null; + try + { + mounts = File.OpenRead("/proc/mounts"); + + Span line = stackalloc byte[4096]; + bool eof = false; + while (!eof) + { + // ~fgets + int lineEnd; + for (lineEnd = 0; lineEnd < line.Length; lineEnd += 1) + { + var b = mounts.ReadByte(); + if (b < 0) + { + eof = true; + break; + } + else if (b == '\n') + { + break; + } + else + { + line[lineEnd] = (byte)b; + } + } + + // line is "device_name mount_point file_system other_stuff..." + + int linePos = 0; + + // device_name + while (linePos < lineEnd && IsNonspaceByte(line[linePos])) + { + linePos += 1; + } + + // whitespace + while (linePos < lineEnd && IsSpaceByte(line[linePos])) + { + linePos += 1; + } + + // mount_point + var mountBegin = linePos; + while (linePos < lineEnd && IsNonspaceByte(line[linePos])) + { + linePos += 1; + } + + var mountEnd = linePos; + + // whitespace + while (linePos < lineEnd && IsSpaceByte(line[linePos])) + { + linePos += 1; + } + + // file_system + var fsBegin = linePos; + while (linePos < lineEnd && IsNonspaceByte(line[linePos])) + { + linePos += 1; + } + + var fsEnd = linePos; + + if (linePos == lineEnd || !IsSpaceByte(line[linePos])) + { + // Ignore line if no whitespace after file_system. + continue; + } + + bool foundTraceFS; + var fs = line.Slice(fsBegin, fsEnd - fsBegin); + if (fs.SequenceEqual("tracefs"u8)) + { + // "tracefsMountPoint/user_events_data" + foundTraceFS = true; + } + else if (path[0] == 0 && fs.SequenceEqual("debugfs"u8)) + { + // "debugfsMountPoint/tracing/user_events_data" + foundTraceFS = false; + } + else + { + continue; + } + + var pathSuffix0 = foundTraceFS + ? "/user_events_data\0"u8 + : "/tracing/user_events_data\0"u8; + + var mountLen = mountEnd - mountBegin; + var pathLen = mountLen + pathSuffix0.Length; // Includes NUL + if (pathLen > path.Length) + { + continue; + } + + // path = mountpoint + suffix + line.Slice(mountBegin, mountLen).CopyTo(path); + pathSuffix0.CopyTo(path.Slice(mountLen)); // Includes NUL + + if (foundTraceFS) + { + // Found a match, and it's tracefs, so stop looking. + break; + } + else + { + // Found a match, but it's debugfs. We prefer tracefs, so keep looking. + } + } + } + catch (ArgumentException) { } + catch (IOException) { } + catch (NotSupportedException) { } + catch (UnauthorizedAccessException) { } + finally + { + mounts?.Dispose(); + } + + if (path[0] != 0) + { + newHandle.Dispose(); + newHandle = RawFileHandle.OpenWRONLY(path); + } + } + + var oldHandle = Interlocked.CompareExchange(ref userEventsDataStatic, newHandle, null); + if (oldHandle != null) + { + resultHandle = oldHandle; + } + else + { + resultHandle = newHandle; + } + } + finally + { + if (newHandle != null && newHandle != resultHandle) + { + newHandle.Dispose(); + } + } + + return resultHandle; + } + + private static bool IsSpaceByte(byte b) + { + return b == ' ' || b == '\t'; + } + + private static bool IsNonspaceByte(byte b) + { + return b != '\0' && !IsSpaceByte(b); + } + + private void InitSucceeded() + { + this.RegisterResult = 0; + Debug.Assert(1 >= this.enabledPinned[0]); + Debug.Assert(!ReferenceEquals(this.enabledPinned, emptyEnabledPinned)); + Debug.Assert(!this.IsClosed); + Debug.Assert(!this.IsInvalid); + } + + private void InitFailed(int registerResult) + { + this.SetHandleAsInvalid(); + this.RegisterResult = registerResult; + Debug.Assert(0 == this.enabledPinned[0]); + Debug.Assert(ReferenceEquals(this.enabledPinned, emptyEnabledPinned)); + Debug.Assert(this.IsClosed); + Debug.Assert(this.IsInvalid); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct user_reg + { + public const int SizeOfStruct = 28; + public UInt32 size; + public byte enable_bit; + public byte enable_size; + public PerfUserEventReg flags; + public UInt64 enable_addr; + public UInt64 name_args; + public Int32 write_index; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct user_unreg + { + public const int SizeOfStruct = 16; + public UInt32 size; + public byte disable_bit; + public byte reserved; + public UInt16 reserved2; + public UInt64 disable_addr; + } + + [StructLayout(LayoutKind.Sequential)] + private struct WriteIndexPlus + { + public Int32 WriteIndex; + public UInt32 Padding; + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/Utility.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/Utility.cs new file mode 100644 index 0000000000..ef68419d51 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Provider/Utility.cs @@ -0,0 +1,41 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints.Provider; + +using System; +using System.Runtime.InteropServices; +using Interlocked = System.Threading.Interlocked; +using BinaryPrimitives = System.Buffers.Binary.BinaryPrimitives; + +internal static class Utility +{ + /// + /// Atomically: old = location; if (old != null) { return old; } else { location = value; return value; } + /// + public static T InterlockedInitSingleton(ref T? location, T value) + where T : class + { + return Interlocked.CompareExchange(ref location, value, null) ?? value; + } + + public static void WriteGuidBigEndian(Span destination, Guid value) + { + if (BitConverter.IsLittleEndian) + { + unsafe + { + var p = (byte*)&value; + var p0 = (uint*)p; + var p1 = (ushort*)(p + 4); + var p2 = (ushort*)(p + 6); + *p0 = BinaryPrimitives.ReverseEndianness(*p0); + *p1 = BinaryPrimitives.ReverseEndianness(*p1); + *p2 = BinaryPrimitives.ReverseEndianness(*p2); + } + } + MemoryMarshal.Write(destination, ref value); + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/README.md b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/README.md new file mode 100644 index 0000000000..3e4bb7b133 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/README.md @@ -0,0 +1,17 @@ +# LinuxTracepoints-Net + +The code in this folder came from the +[LinuxTracepoints-Net](https://github.com/microsoft/LinuxTracepoints-Net) repo +([commit +974c475](https://github.com/microsoft/LinuxTracepoints-Net/blob/974c47522d053c915009ef5112840026eaf22adb)). + +Only the files required to build +`Microsoft.LinuxTracepoints.Provider.PerfTracepoint` were included. + +The following changes were made: + +* `#nullable enabled` added at the top of all files. This is because + LinuxTracepoints-Net has `enabled` repo-wide but + GenevaExporter has `disabled` at the moment. + +* `public` types made `internal`. diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeader.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeader.cs new file mode 100644 index 0000000000..a8db3a3140 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeader.cs @@ -0,0 +1,302 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/*--EventHeader Events-------------------------------------------------------- + +EventHeader is a tracing convention layered on top of Linux Tracepoints. + +To reduce the number of unique Tracepoint names tracked by the kernel, we +use a small number of Tracepoints to manage a larger number of events. All +events with the same attributes (provider name, severity level, category +keyword, etc.) will share one Tracepoint. + +- This means we cannot enable/disable events individually. Instead, all events + with the same attributes will be enabled/disabled as a group. +- This means we cannot rely on the kernel's Tracepoint metadata for event + identity or event field names/types. Instead, all events contain a common + header that provides event identity, core event attributes, and support for + optional event attributes. The kernel's Tracepoint metadata is used only for + the Tracepoint's name and to determine whether the event follows the + EventHeader conventions. + +We define a naming scheme to be used for the shared Tracepoints: + + TracepointName = ProviderName + '_' + 'L' + EventLevel + 'K' + EventKeyword + + [Options] + +We define a common event layout to be used by all EventHeader events. The +event has a header, optional header extensions, and then the event data: + + Event = eventheader + [HeaderExtensions] + Data + +We define a format to be used for header extensions: + + HeaderExtension = eventheader_extension + ExtensionData + +We define a header extension to be used for activity IDs. + +We define a header extension to be used for event metadata (event name, field +names, field types). + +For use in the event metadata extension, we define a field type system that +supports scalar, string, binary, array, and struct. + +Note that we assume that the Tracepoint name corresponding to the event is +available during event decoding. The event decoder obtains the provider name +and keyword for an event by parsing the event's Tracepoint name. + +--Provider Names-------------------------------------------------------------- + +A provider is a component that generates events. Each event from a provider is +associated with a Provider Name that uniquely identifies the provider. + +The provider name should be short, yet descriptive enough to minimize the +chance of collision and to help developers track down the component generating +the events. Hierarchical namespaces may be useful for provider names, e.g. +"MyCompany_MyOrg_MyComponent". + +Restrictions: + +- ProviderName may not contain ' ' or ':' characters. +- strlen(ProviderName + '_' + Attributes) must be less than + EVENTHEADER_NAME_MAX (256) characters. +- Some event APIs (e.g. tracefs) might impose additional restrictions on + tracepoint names. For best compatibility, use only ASCII identifier characters + [A-Za-z0-9_] in provider names. + +Event attribute semantics should be consistent within a given provider. While +some event attributes have generally-accepted semantics (e.g. level value 3 +is defined below as "warning"), the precise semantics of the attribute values +are defined at the scope of a provider (e.g. different providers will use +different criteria for what constitutes a warning). In addition, some +attributes (tag, keyword) are completely provider-defined. All events with a +particular provider name should use consistent semantics for all attributes +(e.g. keyword bit 0x1 should have a consistent meaning for all events from a +particular provider but will mean something different for other providers). + +--Tracepoint Names------------------------------------------------------------ + +A Tracepoint is registered with the kernel for each unique combination of +ProviderName + Attributes. This allows a larger number of distinct events to +be controlled by a smaller number of kernel Tracepoints while still allowing +events to be enabled/disabled at a reasonable granularity. + +The Tracepoint name for an EventHeader event is defined as: + + ProviderName + '_' + 'L' + eventLevel + 'K' + eventKeyword + [Options] + or printf("%s_L%xK%lx%s", providerName, eventLevel, eventKeyword, options), + e.g. "MyProvider_L3K2a" or "OtherProvider_L5K0Gperf". + +Event level is a uint8 value 1..255 indicating event severity, formatted as +lowercase hexadecimal, e.g. printf("L%x", eventLevel). The defined level values +are: 1 = critical error, 2 = error, 3 = warning, 4 = information, 5 = verbose. + +Event keyword is a uint64 bitmask indicating event category membership, +formatted as lowercase hexadecimal, e.g. printf("K%lx", eventKeyword). Each +bit in the keyword corresponds to a provider-defined category, e.g. a provider +might define 0x2 = networking and 0x4 = I/O so that keyword value of 0x2|0x4 = +0x6 would indicate that an event is in both the networking and I/O categories. + +Options (optional attributes) can be specified after the keyword attribute. +Each option consists of an uppercase ASCII letter (option type) followed by 0 +or more ASCII digits or lowercase ASCII letters (option value). To support +consistent event names, the options must be sorted in alphabetical order, e.g. +"Aoption" should come before "Boption". + +The currently defined options are: + +- 'G' = provider Group name. Defines a group of providers. This can be used by + event analysis tools to find all providers that generate a certain kind of + information. + +Restrictions: + +- ProviderName may not contain ' ' or ':' characters. +- Tracepoint name must be less than EVENTHEADER_NAME_MAX (256) + characters in length. +- Some event APIs (e.g. tracefs) might impose additional restrictions on + tracepoint names. For best compatibility, use only ASCII identifier characters + [A-Za-z0-9_] in provider names. + +--Header----------------------------------------------------------------------- + +Because multiple events may share a single Tracepoint, each event must contain +information needed to distinguish it from other events. To enable this, each +event starts with an EventHeader structure which contains information about +the event: + +- flags: Bits indicating pointer size (32 or 64 bits), byte order + (big-endian or little), and whether any header extensions are present. +- opcode: Indicates special event semantics e.g. "normal event", + "activity start event", "activity end event". +- tag: Provider-defined 16-bit value. Can be used for anything. +- id: 16-bit stable event identifier, or 0 if no identifier is assigned. +- version: 8-bit event version, incremented for e.g. field type changes. +- level: 8-bit event severity level, 1 = critical .. 5 = verbose. + (level value in event header must match the level in the Tracepoint name.) + +If the extension flag is not set, the header is immediately followed by the +event payload. + +If the extension flag is set, the header is immediately followed by one or more +header extensions. Each header extension has a 16-bit size, a 15-bit type code, +and a 1-bit flag indicating whether another header extension follows the +current extension. The final header extension is immediately followed by the +event payload. + +The following header extensions are defined: + +- Activity ID: Contains a 128-bit ID that can be used to correlate events. May + also contain the 128-bit ID of the parent activity (typically used only for + the first event of an activity). +- Metadata: Contains the event's metadata: event name, event attributes, field + names, field attributes, and field types. Both simple (e.g. Int32, HexInt16, + Float64, Char32, Uuid) and complex (e.g. NulTerminatedString8, + CountedString16, Binary, Struct, Array) types are supported. +*/ +namespace Microsoft.LinuxTracepoints +{ + using System; + using System.Runtime.InteropServices; + using Tracing = System.Diagnostics.Tracing; + + /// + /// + /// Core metadata for an EventHeader event. + /// + /// Each EventHeader event starts with an instance of the EventHeader structure. + /// It contains core information recorded for every event to help with event + /// identification, filtering, and decoding. + /// + /// If EventHeader.Flags has the Extension bit set then the EventHeader is followed + /// by one or more EventHeaderExtension blocks. Otherwise the EventHeader is + /// followed by the event payload data. + /// + /// If an EventHeaderExtension.Kind has the Chain flag set then the + /// EventHeaderExtension block is followed immediately (no alignment/padding) by + /// another extension block. Otherwise it is followed immediately (no + /// alignment/padding) by the event payload data. + /// + /// If there is a Metadata extension then it contains the event name, field names, + /// and field types needed to decode the payload data. Otherwise, the payload + /// decoding system is defined externally, i.e. you will use the provider name to + /// find the appropriate decoding manifest, then use the event's id+version to + /// find the decoding information within the manifest, then use that decoding + /// information to decode the event payload data. + /// + /// For a particular event definition (i.e. for a particular event name, or for a + /// particular event id+version), the information in the EventHeader (and in the + /// Metadata extension, if present) should be constant. For example, instead of + /// having a single event with a runtime-variable level, you should have a + /// distinct event definition (with distinct event name and/or distinct event id) + /// for each level. + /// + /// + [StructLayout(LayoutKind.Sequential)] + internal struct EventHeader + { + /// + /// The size of this structure in bytes (8). + /// + public const int SizeOfStruct = 8; + + /// + /// Pointer-size and Endian flags as appropriate for the currently-running process, + /// no extension blocks present. + /// + public static readonly EventHeaderFlags DefaultFlags = + (IntPtr.Size == 8 ? EventHeaderFlags.Pointer64 : EventHeaderFlags.None) | + (BitConverter.IsLittleEndian ? EventHeaderFlags.LittleEndian : EventHeaderFlags.None); + + /// + /// The encoding corresponding to IntPtr. If IntPtr.Size == 8, this is Value64. + /// Otherwise, this is Value32. + /// + public static readonly EventHeaderFieldEncoding IntPtrEncoding = + IntPtr.Size == 8 ? EventHeaderFieldEncoding.Value64 : EventHeaderFieldEncoding.Value32; + + /// + /// Pointer64, LittleEndian, Extension. + /// + public EventHeaderFlags Flags; + + /// + /// Increment Version whenever event layout changes. + /// + public byte Version; + + /// + /// Stable id for this event, or 0 if none. + /// + public ushort Id; + + /// + /// Provider-defined event tag, or 0 if none. + /// + public ushort Tag; + + /// + /// EventOpcode raw value. (Stores the value of the Opcode property.) + /// + public byte OpcodeByte; + + /// + /// EventLevel raw value. (Stores the value of the Level property.) + /// + public byte LevelByte; + + /// + /// EventOpcode: info, start activity, stop activity, etc. + /// Throws OverflowException if set value > 255. + /// + /// + /// Most events set Opcode = Info (0). Other Opcode values add special semantics to + /// an event that help the event analysis tool with grouping related events. The + /// most frequently-used special semantics are ActivityStart and ActivityStop. + /// + /// To record an activity: + /// + /// Generate a new activity id. An activity id is a 128-bit value that must be + /// unique within the trace. This can be a UUID or it can be generated by any + /// other id-generation system that is unlikely to create the same value for any + /// other activity id in the same trace. + /// + /// Write an event with opcode = ActivityStart and with an ActivityId header + /// extension. The ActivityId extension should have the newly-generated activity + /// id, followed by the id of a parent activity (if any). If there is a parent + /// activity, the extension length will be 32; otherwise it will be 16. + /// + /// As appropriate, write any number of normal events (events with opcode set to + /// something other than ActivityStart or ActivityStop, e.g. opcode = Info). To + /// indicate that the events are part of the activity, each of these events + /// should have an ActivityId header extension with the new activity id + /// (extension length will be 16). + /// + /// When the activity ends, write an event with opcode = ActivityStop and with + /// an ActivityId header extension containing the activity id of the activity + /// that is ending (extension length will be 16). + /// + /// + /// Set value > 255 + public Tracing.EventOpcode Opcode + { + readonly get => (Tracing.EventOpcode)OpcodeByte; + set => OpcodeByte = checked((byte)value); + } + + /// + /// EventLevel: critical, error, warning, info, verbose. + /// Throws OverflowException if set value > 255. + /// + /// Set value > 255 + public Tracing.EventLevel Level + { + readonly get => (Tracing.EventLevel)LevelByte; + set => LevelByte = checked((byte)value); + } + + // Followed by: EventHeaderExtension block(s), then event payload. + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtension.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtension.cs new file mode 100644 index 0000000000..807217a5f0 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtension.cs @@ -0,0 +1,46 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints +{ + using System.Runtime.InteropServices; + + /// + /// + /// Additional information for an EventHeader event. + /// + /// If EventHeader.Flags has the Extension bit set then the EventHeader is + /// followed by one or more EventHeaderExtension blocks. Otherwise the EventHeader + /// is followed by the event payload data. + /// + /// If EventHeaderExtension.Kind has the Chain flag set then the + /// EventHeaderExtension block is followed immediately (no alignment/padding) by + /// another extension block. Otherwise it is followed immediately (no + /// alignment/padding) by the event payload data. + /// + /// + [StructLayout(LayoutKind.Sequential)] + internal struct EventHeaderExtension + { + /// + /// The size of this structure in bytes (4). + /// + public const int SizeOfStruct = 4; + + /// + /// The size of the extension data in bytes. + /// The data immediately follows this structure with no padding/alignment. + /// + public ushort Size; + + /// + /// The kind of extension. This determines the format of the extension data. + /// In addition, the Chain flag indicates whether another extension follows. + /// + public EventHeaderExtensionKind Kind; + + // Followed by Size bytes of data. No padding/alignment. + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtensionKind.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtensionKind.cs new file mode 100644 index 0000000000..2d2e845f33 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderExtensionKind.cs @@ -0,0 +1,117 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints +{ + /// + /// Values for EventHeaderExtension.Kind. + /// + internal enum EventHeaderExtensionKind : ushort + { + /// + /// Mask for the base extension kind (low 15 bits). + /// + ValueMask = 0x7fff, + + /// + /// If not set, this is the last extension block (event payload data follows). + /// If set, this is not the last extension block (another extension block follows). + /// + ChainFlag = 0x8000, + + /// + /// Invalid extension kind. + /// + Invalid = 0, + + /// + /// + /// Extension contains an event definition (i.e. event metadata). + /// + /// Event definition format: + /// + /// char EventName[]; // Nul-terminated utf-8 string: "eventName{;attribName=attribValue}" + /// + /// 0 or more field definition blocks. + /// + /// Field definition block: + /// + /// char FieldName[]; // Nul-terminated utf-8 string: "fieldName{;attribName=attribValue}" + /// + /// uint8_t Encoding; // Encoding is 0..31, with 3 flag bits. + /// + /// uint8_t Format; // Present if (Encoding & 128). Format is 0..127, with 1 flag bit. + /// + /// uint16_t Tag; // Present if (Format & 128). Contains provider-defined value. + /// + /// uint16_t ArrayLength; // Present if (Encoding & 32). Contains element count of constant-length array. + /// + /// Notes: + /// + /// eventName and fieldName may not contain any ';' characters. + /// + /// eventName and fieldName may be followed by attribute strings. + /// + /// attribute string is: ';' + attribName + '=' + attribValue. + /// + /// attribName may not contain any ';' or '=' characters. + /// + /// Semicolons in attribValue must be escaped by doubling, e.g. + /// "my;value" is escaped as "my;;value". + /// + /// + Metadata, + + /// + /// + /// Extension contains activity id information. + /// + /// Any event that is part of an activity has an ActivityId extension. + /// + /// Activity is started by an event with opcode = ActivityStart. The + /// ActivityId extension for the start event must contain a newly-generated + /// activity id and may optionally contain the parent activity id. + /// + /// Activity may contain any number of normal events (opcode something other + /// than ActivityStart or ActivityStop). The ActivityId extension for each + /// normal event must contain the id of the associated activity (otherwise + /// it is not considered to be part of the activity). + /// + /// Activity is ended by an event with opcode = ActivityStop. The ActivityId + /// extension for the stop event must contain the id of the activity that is + /// ending. + /// + /// An activity id is a 128-bit value that is unique within this trace + /// session. It may be a UUID. Since UUID generation can be expensive, this + /// may also be a 128-bit LUID (locally-unique id), generated using any method + /// that is unlikely to conflict with other activity ids in the same trace. + /// + /// If extension.Size == 16 then value is a 128-bit activity id. + /// + /// If extension.Size == 32 then value is a 128-bit activity id followed by a + /// 128-bit related (parent) activity id. + /// + /// + ActivityId, + } + + /// + /// Extension methods for . + /// + internal static class EventHeaderExtensionKindExtensions + { + /// + /// Returns the format without any flags (format & ValueMask). + /// + public static EventHeaderExtensionKind BaseKind(this EventHeaderExtensionKind kind) => + kind & EventHeaderExtensionKind.ValueMask; + + /// + /// Returns true if ChainFlag is present (more extensions are present in event). + /// + public static bool HasChainFlag(this EventHeaderExtensionKind kind) => + 0 != (kind & EventHeaderExtensionKind.ChainFlag); + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldEncoding.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldEncoding.cs new file mode 100644 index 0000000000..28cb80ff87 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldEncoding.cs @@ -0,0 +1,269 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.LinuxTracepoints +{ + /// + /// + /// Values for the Encoding byte of a field definition. + /// + /// The low 5 bits of the Encoding byte contain the field's encoding. The encoding + /// indicates the following information about the field: + /// + /// How the decoder should determine the size of the field. For example, + /// Value32 indicates a 4-byte field, Value128 indicates a 16-byte + /// field, ZStringChar8 indicates that the field ends at the first char8 unit + /// with value 0, and BinaryLength16Char8 indicates that the first 16 bits of + /// the field are the uint16 Length and that the subsequent Length + /// char8 units are the field value. + /// + /// How the field should be formatted if the field's format is + /// , unrecognized, or unsupported. For + /// example, a Value32 encoding with Default or unrecognized format + /// should be treated as if it had UnsignedInt format. A + /// StringLength16Char8 encoding with Default or unrecognized format + /// should be treated as if it had StringUtf format. A + /// BinaryLength16Char8 encoding with Default or unrecognized format + /// should be treated as if it had HexBytes format. + /// + /// + /// The StringLength16Char8 and BinaryLength16Char8 are special. These + /// encodings can be used with both variable-length (e.g. HexBytes and + /// String) formats as well as with fixed-length (e.g. UnsignedInt, + /// Float, IPAddress) formats. When used with fixed-length formats, + /// the semantics depend on the field's variable Length (as determined from the first + /// two bytes of the field): + /// + /// If the Length is 0, the field is formatted as null. For example, a field + /// with encoding = BinaryLength16Char8, format = SignedInt, and + /// Length = 0 would be formatted as a null value. + /// + /// If the Length is appropriate for the format, the field is formatted as if it had + /// the Value8, Value16, Value32, Value64, or Value128 encoding corresponding to its + /// size. For example, a field with encoding = BinaryLength16Char8, + /// format = SignedInt, and Length = 4 would be formatted as an Int32 field. + /// + /// If the Length is not appropriate for the format, the field is formatted as if it + /// had the default format for the encoding. For example, a field with + /// encoding = BinaryLength16Char8, format = SignedInt, and Length = 16 + /// would be formatted as a HexBytes field since 16 is not a supported size + /// for the SignedInt format and the default format for + /// BinaryLength16Char8 is HexBytes. + /// + /// + /// The top 3 bits of the field encoding byte are flags: + /// + /// CArrayFlag indicates that this field is a constant-length array, with the + /// element count specified as a 16-bit value in the event metadata (must not be + /// 0). + /// + /// VArrayFlag indicates that this field is a variable-length array, with the + /// element count specified as a 16-bit value in the event payload (immediately + /// before the array elements, may be 0). + /// + /// ChainFlag indicates that a format byte is present after the encoding byte. + /// If Chain is not set, the format byte is omitted and is assumed to be 0. + /// + /// Setting both CArray and VArray is invalid (reserved). + /// + /// + internal enum EventHeaderFieldEncoding : byte + { + /// + /// Mask for the base encoding type (low 5 bits). + /// + ValueMask = 0x1F, + + /// + /// Mask for the encoding flags: CArrayFlag, VArrayFlag, ChainFlag. + /// + FlagMask = 0xE0, + + /// + /// Mask for the array flags: CArrayFlag, VArrayFlag. + /// + ArrayFlagMask = 0x60, + + /// + /// Constant-length array: 16-bit element count in metadata (must not be 0). + /// + CArrayFlag = 0x20, + + /// + /// Variable-length array: 16-bit element count in payload (may be 0). + /// + VArrayFlag = 0x40, + + /// + /// If present in the field, this flag indicates that an EventHeaderFieldFormat + /// byte follows the EventHeaderFieldEncoding byte. + /// + ChainFlag = 0x80, + + /// + /// Invalid encoding value. + /// + Invalid = 0, + + /// + /// 0-byte value, logically groups subsequent N fields, N = format & 0x7F, N must not be 0. + /// + Struct, + + /// + /// 1-byte value, default format UnsignedInt. + /// + Value8, + + /// + /// 2-byte value, default format UnsignedInt. + /// + Value16, + + /// + /// 4-byte value, default format UnsignedInt. + /// + Value32, + + /// + /// 8-byte value, default format UnsignedInt. + /// + Value64, + + /// + /// 16-byte value, default format HexBytes. + /// + Value128, + + /// + /// zero-terminated uint8[], default format StringUtf. + /// + ZStringChar8, + + /// + /// zero-terminated uint16[], default format StringUtf. + /// + ZStringChar16, + + /// + /// zero-terminated uint32[], default format StringUtf. + /// + ZStringChar32, + + /// + /// uint16 Length followed by uint8 Data[Length], default format StringUtf. + /// This should be treated exactly the same as BinaryLength16Char8 except that it + /// has a different default format. + /// + StringLength16Char8, + + /// + /// uint16 Length followed by uint16 Data[Length], default format StringUtf. + /// + StringLength16Char16, + + /// + /// uint16 Length followed by uint32 Data[Length], default format StringUtf. + /// + StringLength16Char32, + + /// + /// uint16 Length followed by uint8 Data[Length], default format HexBytes. + /// This should be treated exactly the same as StringLength16Char8 except that it + /// has a different default format. + /// + BinaryLength16Char8, + + /// + /// Invalid encoding value. Value will change in future versions of this header. + /// + Max, + } + + /// + /// Extension methods for . + /// + internal static class EventHeaderFieldEncodingExtensions + { + /// + /// Returns the encoding without any flags (encoding & ValueMask). + /// + public static EventHeaderFieldEncoding WithoutFlags(this EventHeaderFieldEncoding encoding) => + encoding & EventHeaderFieldEncoding.ValueMask; + + /// + /// Returns the encoding without the chain flag (encoding & ~ChainFlag). + /// + public static EventHeaderFieldEncoding WithoutChainFlag(this EventHeaderFieldEncoding encoding) => + encoding & ~EventHeaderFieldEncoding.ChainFlag; + + /// + /// Returns the array flags of the encoding (VArrayFlag or CArrayFlag, if set). + /// + public static EventHeaderFieldEncoding ArrayFlags(this EventHeaderFieldEncoding encoding) => + encoding & EventHeaderFieldEncoding.ArrayFlagMask; + + /// + /// Returns true if any ArrayFlag is present (constant-length or variable-length array). + /// + public static bool IsArray(this EventHeaderFieldEncoding encoding) => + 0 != (encoding & EventHeaderFieldEncoding.ArrayFlagMask); + + /// + /// Returns true if CArrayFlag is present (constant-length array). + /// + public static bool IsCArray(this EventHeaderFieldEncoding encoding) => + 0 != (encoding & EventHeaderFieldEncoding.CArrayFlag); + + /// + /// Returns true if VArrayFlag is present (variable-length array). + /// + public static bool IsVArray(this EventHeaderFieldEncoding encoding) => + 0 != (encoding & EventHeaderFieldEncoding.VArrayFlag); + + /// + /// Returns true if ChainFlag is present (format byte is present in event). + /// + public static bool HasChainFlag(this EventHeaderFieldEncoding encoding) => + 0 != (encoding & EventHeaderFieldEncoding.ChainFlag); + + /// + /// Gets the default format for the encoding, or EventHeaderFieldFormat.Default if the encoding is invalid. + /// + /// Value8, Value16, Value32, Value64: UnsignedInt. + /// + /// Value128: HexBytes. + /// + /// String: StringUtf. + /// + /// Other: Default. + /// + /// + public static EventHeaderFieldFormat DefaultFormat(this EventHeaderFieldEncoding encoding) + { + switch (encoding & EventHeaderFieldEncoding.ValueMask) + { + case EventHeaderFieldEncoding.Value8: + case EventHeaderFieldEncoding.Value16: + case EventHeaderFieldEncoding.Value32: + case EventHeaderFieldEncoding.Value64: + return EventHeaderFieldFormat.UnsignedInt; + case EventHeaderFieldEncoding.Value128: + return EventHeaderFieldFormat.HexBytes; + case EventHeaderFieldEncoding.ZStringChar8: + case EventHeaderFieldEncoding.ZStringChar16: + case EventHeaderFieldEncoding.ZStringChar32: + case EventHeaderFieldEncoding.StringLength16Char8: + case EventHeaderFieldEncoding.StringLength16Char16: + case EventHeaderFieldEncoding.StringLength16Char32: + return EventHeaderFieldFormat.StringUtf; + case EventHeaderFieldEncoding.BinaryLength16Char8: + return EventHeaderFieldFormat.HexBytes; + default: + return EventHeaderFieldFormat.Default; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldFormat.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldFormat.cs new file mode 100644 index 0000000000..e6fa34e4fb --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFieldFormat.cs @@ -0,0 +1,168 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma warning disable CA1720 // Identifier contains type name + +namespace Microsoft.LinuxTracepoints +{ + /// + /// + /// Values for the Format byte of a field definition. + /// + /// The low 7 bits of the Format byte contain the field's format. + /// In the case of the Struct encoding, the low 7 bits of the Format byte contain + /// the number of logical fields in the struct (which must not be 0). + /// + /// The top bit of the field format byte is the ChainFlag. If set, it indicates + /// that a field tag (uint16) is present after the format byte. If not set, the + /// field tag is not present and is assumed to be 0. + /// + /// + internal enum EventHeaderFieldFormat : byte + { + /// + /// Mask for the base format type (low 7 bits). + /// + ValueMask = 0x7F, + + /// + /// If present in the field, this flag indicates that a uint16 + /// field tag follows the EventHeaderFieldFormat byte. + /// + ChainFlag = 0x80, + + /// + /// Use the default format of the encoding. + /// + Default = 0, + + /// + /// unsigned integer, event byte order. + /// Use with Value8..Value64 encodings. + /// + UnsignedInt, + + /// + /// signed integer, event byte order. + /// Use with Value8..Value64 encodings. + /// + SignedInt, + + /// + /// hex integer, event byte order. + /// Use with Value8..Value64 encodings. + /// + HexInt, + + /// + /// errno, event byte order. + /// Use with Value32 encoding. + /// + Errno, + + /// + /// process id, event byte order. + /// Use with Value32 encoding. + /// + Pid, + + /// + /// signed integer, event byte order, seconds since 1970. + /// Use with Value32 or Value64 encodings. + /// + Time, + + /// + /// 0 = false, 1 = true, event byte order. + /// Use with Value8..Value32 encodings. + /// + Boolean, + + /// + /// floating point, event byte order. + /// Use with Value32..Value64 encodings. + /// + Float, + + /// + /// binary, decoded as hex dump of bytes. + /// Use with any encoding. + /// + HexBytes, + + /// + /// 8-bit char string, unspecified character set (usually treated as ISO-8859-1 or CP-1252). + /// Use with Value8 and Char8 encodings. + /// + String8, + + /// + /// UTF string, event byte order, code unit size based on encoding. + /// Use with Value16..Value32 and Char8..Char32 encodings. + /// + StringUtf, + + /// + /// UTF string, BOM used if present, otherwise behaves like StringUtf. + /// Use with Char8..Char32 encodings. + /// + StringUtfBom, + + /// + /// XML string, otherwise behaves like StringUtfBom. + /// Use with Char8..Char32 encodings. + /// + StringXml, + + /// + /// JSON string, otherwise behaves like StringUtfBom. + /// Use with Char8..Char32 encodings. + /// + StringJson, + + /// + /// UUID, network byte order (RFC 4122 format). + /// Use with Value128 encoding. + /// + Uuid, + + /// + /// IP port, network byte order (in_port_t layout). + /// Use with Value16 encoding. + /// + Port, + + /// + /// IP address, network byte order (in_addr/in6_addr layout). + /// Use with Value32 or Value128 encoding. + /// + IPAddress, + + /// + /// Deprecated: Alternate format for IPAddress. + /// Do not generate events with this format. + /// Decode this format as IPAddress. + /// + IPAddressObsolete, + } + + /// + /// Extension methods for . + /// + internal static class EventHeaderFieldFormatExtensions + { + /// + /// Returns the format without any flags (format & ValueMask). + /// + public static EventHeaderFieldFormat WithoutFlags(this EventHeaderFieldFormat format) => + format & EventHeaderFieldFormat.ValueMask; + + /// + /// Returns true if ChainFlag is present (tag present in event). + /// + public static bool HasChainFlag(this EventHeaderFieldFormat format) => + 0 != (format & EventHeaderFieldFormat.ChainFlag); + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFlags.cs b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFlags.cs new file mode 100644 index 0000000000..af554483c9 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/External/LinuxTracepoints-Net/Types/EventHeaderFlags.cs @@ -0,0 +1,36 @@ +#nullable enable + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix + +namespace Microsoft.LinuxTracepoints +{ + /// + /// Values for EventHeader.Flags. + /// + [System.Flags] + internal enum EventHeaderFlags : byte + { + /// + /// Pointer32, BigEndian, no extensions. + /// + None = 0x00, + + /// + /// Pointer is 64 bits, not 32 bits. + /// + Pointer64 = 0x01, + + /// + /// Event uses little-endian, not big-endian. + /// + LittleEndian = 0x02, + + /// + /// There is at least one EventHeaderExtension block. + /// + Extension = 0x04, + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/External/TraceLoggingDynamic.cs b/src/OpenTelemetry.Exporter.Geneva/External/TraceLoggingDynamic.cs index 481e41c981..e2cea7bb1f 100644 --- a/src/OpenTelemetry.Exporter.Geneva/External/TraceLoggingDynamic.cs +++ b/src/OpenTelemetry.Exporter.Geneva/External/TraceLoggingDynamic.cs @@ -1,5 +1,3 @@ -// (Turns off StyleCop analysis in this file.) - /* Dynamic ETW TraceLogging Provider API for .NET. diff --git a/src/OpenTelemetry.Exporter.Geneva/OpenTelemetry.Exporter.Geneva.csproj b/src/OpenTelemetry.Exporter.Geneva/OpenTelemetry.Exporter.Geneva.csproj index 4605cafce6..ca6ebaf6dc 100644 --- a/src/OpenTelemetry.Exporter.Geneva/OpenTelemetry.Exporter.Geneva.csproj +++ b/src/OpenTelemetry.Exporter.Geneva/OpenTelemetry.Exporter.Geneva.csproj @@ -1,4 +1,5 @@ + @@ -20,6 +21,11 @@ + + + + + diff --git a/src/OpenTelemetry.Exporter.Geneva/THIRD-PARTY-NOTICES.TXT b/src/OpenTelemetry.Exporter.Geneva/THIRD-PARTY-NOTICES.TXT new file mode 100644 index 0000000000..3d74bb0bd8 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/THIRD-PARTY-NOTICES.TXT @@ -0,0 +1,30 @@ +OpenTelemetry.Exporter.Geneva uses third-party libraries or other resources that +may be distributed under licenses different than the +OpenTelemetry.Exporter.Geneva software. + +The attached notices are provided for information only. + +License notice for LinuxTracepoints-Net +------------------------------- + +MIT License + +Copyright (c) 2024 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.