forked from open-telemetry/opentelemetry-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a Geneva Exporter - Exports to local ETW or UDS (open-telemetr…
- Loading branch information
Showing
47 changed files
with
8,281 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
[assembly: CLSCompliant(false)] | ||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Geneva.Benchmark" + AssemblyInfo.PublicKey)] | ||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Geneva.UnitTest" + AssemblyInfo.PublicKey)] | ||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Geneva.Stress" + AssemblyInfo.PublicKey)] | ||
|
||
internal static class AssemblyInfo | ||
{ | ||
#if SIGNED | ||
public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; | ||
#else | ||
public const string PublicKey = ""; | ||
#endif | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Changelog | ||
|
||
## Unreleased |
212 changes: 212 additions & 0 deletions
212
src/OpenTelemetry.Exporter.Geneva/ConnectionStringBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
|
||
namespace OpenTelemetry.Exporter.Geneva | ||
{ | ||
internal enum TransportProtocol | ||
{ | ||
Etw, | ||
Tcp, | ||
Udp, | ||
Unix, | ||
Unspecified, | ||
} | ||
|
||
internal class ConnectionStringBuilder | ||
{ | ||
private readonly Dictionary<string, string> _parts = new Dictionary<string, string>(StringComparer.Ordinal); | ||
|
||
public ConnectionStringBuilder(string connectionString) | ||
{ | ||
if (string.IsNullOrWhiteSpace(connectionString)) | ||
{ | ||
throw new ArgumentNullException(nameof(connectionString), $"{nameof(connectionString)} is invalid."); | ||
} | ||
|
||
const char Semicolon = ';'; | ||
const char EqualSign = '='; | ||
foreach (var token in connectionString.Split(Semicolon)) | ||
{ | ||
if (string.IsNullOrWhiteSpace(token)) | ||
{ | ||
continue; | ||
} | ||
|
||
var index = token.IndexOf(EqualSign); | ||
if (index == -1 || index != token.LastIndexOf(EqualSign)) | ||
{ | ||
continue; | ||
} | ||
|
||
var pair = token.Trim().Split(EqualSign); | ||
|
||
var key = pair[0].Trim(); | ||
var value = pair[1].Trim(); | ||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) | ||
{ | ||
throw new ArgumentException("Connection string cannot contain empty keys or values."); | ||
} | ||
|
||
this._parts[key] = value; | ||
} | ||
|
||
if (this._parts.Count == 0) | ||
{ | ||
throw new ArgumentNullException(nameof(connectionString), $"{nameof(connectionString)} is invalid."); | ||
} | ||
} | ||
|
||
public string EtwSession | ||
{ | ||
get => this.ThrowIfNotExists<string>(nameof(this.EtwSession)); | ||
set => this._parts[nameof(this.EtwSession)] = value; | ||
} | ||
|
||
public string Endpoint | ||
{ | ||
get => this.ThrowIfNotExists<string>(nameof(this.Endpoint)); | ||
set => this._parts[nameof(this.Endpoint)] = value; | ||
} | ||
|
||
public TransportProtocol Protocol | ||
{ | ||
get | ||
{ | ||
try | ||
{ | ||
// Checking Etw first, since it's preferred for Windows and enables fail fast on Linux | ||
if (this._parts.ContainsKey(nameof(this.EtwSession))) | ||
{ | ||
return TransportProtocol.Etw; | ||
} | ||
|
||
if (!this._parts.ContainsKey(nameof(this.Endpoint))) | ||
{ | ||
return TransportProtocol.Unspecified; | ||
} | ||
|
||
var endpoint = new Uri(this.Endpoint); | ||
if (Enum.TryParse(endpoint.Scheme, true, out TransportProtocol protocol)) | ||
{ | ||
return protocol; | ||
} | ||
|
||
throw new ArgumentException("Endpoint scheme is invalid."); | ||
} | ||
catch (UriFormatException ex) | ||
{ | ||
throw new ArgumentException($"{nameof(this.Endpoint)} value is malformed.", ex); | ||
} | ||
} | ||
} | ||
|
||
public string ParseUnixDomainSocketPath() | ||
{ | ||
try | ||
{ | ||
var endpoint = new Uri(this.Endpoint); | ||
return endpoint.AbsolutePath; | ||
} | ||
catch (UriFormatException ex) | ||
{ | ||
throw new ArgumentException($"{nameof(this.Endpoint)} value is malformed.", ex); | ||
} | ||
} | ||
|
||
public int TimeoutMilliseconds | ||
{ | ||
get | ||
{ | ||
if (!this._parts.TryGetValue(nameof(this.TimeoutMilliseconds), out string value)) | ||
{ | ||
return UnixDomainSocketDataTransport.DefaultTimeoutMilliseconds; | ||
} | ||
|
||
try | ||
{ | ||
int timeout = int.Parse(value, CultureInfo.InvariantCulture); | ||
if (timeout <= 0) | ||
{ | ||
throw new ArgumentException( | ||
$"{nameof(this.TimeoutMilliseconds)} should be greater than zero.", | ||
nameof(this.TimeoutMilliseconds)); | ||
} | ||
|
||
return timeout; | ||
} | ||
catch (ArgumentException) | ||
{ | ||
throw; | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new ArgumentException( | ||
$"{nameof(this.TimeoutMilliseconds)} is malformed.", | ||
nameof(this.TimeoutMilliseconds), | ||
ex); | ||
} | ||
} | ||
set => this._parts[nameof(this.TimeoutMilliseconds)] = value.ToString(CultureInfo.InvariantCulture); | ||
} | ||
|
||
public string Host | ||
{ | ||
get | ||
{ | ||
try | ||
{ | ||
var endpoint = new Uri(this.Endpoint); | ||
return endpoint.Host; | ||
} | ||
catch (UriFormatException ex) | ||
{ | ||
throw new ArgumentException($"{nameof(this.Endpoint)} value is malformed.", ex); | ||
} | ||
} | ||
} | ||
|
||
public int Port | ||
{ | ||
get | ||
{ | ||
try | ||
{ | ||
var endpoint = new Uri(this.Endpoint); | ||
if (endpoint.IsDefaultPort) | ||
{ | ||
throw new ArgumentException($"Port should be explicitly set in {nameof(this.Endpoint)} value."); | ||
} | ||
|
||
return endpoint.Port; | ||
} | ||
catch (UriFormatException ex) | ||
{ | ||
throw new ArgumentException($"{nameof(this.Endpoint)} value is malformed.", ex); | ||
} | ||
} | ||
} | ||
|
||
public string Account | ||
{ | ||
get => this.ThrowIfNotExists<string>(nameof(this.Account)); | ||
set => this._parts[nameof(this.Account)] = value; | ||
} | ||
|
||
public string Namespace | ||
{ | ||
get => this.ThrowIfNotExists<string>(nameof(this.Namespace)); | ||
set => this._parts[nameof(this.Namespace)] = value; | ||
} | ||
|
||
private T ThrowIfNotExists<T>(string name) | ||
{ | ||
if (!this._parts.TryGetValue(name, out var value)) | ||
{ | ||
throw new ArgumentException($"'{name}' value is missing in connection string."); | ||
} | ||
|
||
return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
using System; | ||
using System.Diagnostics.Tracing; | ||
|
||
namespace OpenTelemetry.Exporter.Geneva | ||
{ | ||
[EventSource(Name = "OpenTelemetry")] | ||
internal class EtwEventSource : EventSource | ||
{ | ||
public EtwEventSource(string providerName) | ||
: base(providerName, EventSourceSettings.EtwManifestEventFormat) | ||
{ | ||
} | ||
|
||
public enum EtwEventId | ||
{ | ||
TraceEvent = 100, | ||
} | ||
|
||
[Event((int)EtwEventId.TraceEvent, Version = 1, Level = EventLevel.Informational)] | ||
public void InformationalEvent() | ||
{ | ||
} | ||
|
||
[NonEvent] | ||
public unsafe void SendEvent(int eventId, byte[] data, int size) | ||
{ | ||
EventData* dataDesc = stackalloc EventData[1]; | ||
fixed (byte* ptr = data) | ||
{ | ||
dataDesc[0].DataPointer = (IntPtr)ptr; | ||
dataDesc[0].Size = (int)size; | ||
this.WriteEventCore(eventId, 1, dataDesc); | ||
} | ||
} | ||
} | ||
|
||
internal class EtwDataTransport : IDataTransport, IDisposable | ||
{ | ||
public EtwDataTransport(string providerName) | ||
{ | ||
this.m_eventSource = new EtwEventSource(providerName); | ||
} | ||
|
||
public void Send(byte[] data, int size) | ||
{ | ||
this.m_eventSource.SendEvent((int)EtwEventSource.EtwEventId.TraceEvent, data, size); | ||
} | ||
|
||
public bool IsEnabled() | ||
{ | ||
return this.m_eventSource.IsEnabled(); | ||
} | ||
|
||
private EtwEventSource m_eventSource; | ||
private bool m_disposed; | ||
|
||
public void Dispose() | ||
{ | ||
this.Dispose(true); | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (this.m_disposed) | ||
{ | ||
return; | ||
} | ||
|
||
if (disposing) | ||
{ | ||
this.m_eventSource.Dispose(); | ||
} | ||
|
||
this.m_disposed = true; | ||
} | ||
} | ||
} |
Oops, something went wrong.