Skip to content
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

[Do not merge] Add OpenTelemetry ETW metric exporter project to the solution #1161

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions opentelemetry-dotnet-contrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Sampler.AWS.T
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.OneCollector.Benchmarks", "test\OpenTelemetry.Exporter.OneCollector.Benchmarks\OpenTelemetry.Exporter.OneCollector.Benchmarks.csproj", "{C42868C8-968A-473F-AC39-AC97C5D47E84}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw", "src\OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw\OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.csproj", "{A91DF3D3-BE2D-47E0-B600-E2838F47CE1A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Enrichment", "examples\enrichment\Examples.Enrichment\Examples.Enrichment.csproj", "{9B7F7605-ADFF-4A47-9B64-FFF3E2EC9DEA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "enrichment", "enrichment", "{93503FAF-D43D-48C0-818C-92EB90F7606B}"
Expand Down Expand Up @@ -590,6 +592,10 @@ Global
{9B7F7605-ADFF-4A47-9B64-FFF3E2EC9DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B7F7605-ADFF-4A47-9B64-FFF3E2EC9DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B7F7605-ADFF-4A47-9B64-FFF3E2EC9DEA}.Release|Any CPU.Build.0 = Release|Any CPU
{A91DF3D3-BE2D-47E0-B600-E2838F47CE1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A91DF3D3-BE2D-47E0-B600-E2838F47CE1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A91DF3D3-BE2D-47E0-B600-E2838F47CE1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A91DF3D3-BE2D-47E0-B600-E2838F47CE1A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -680,6 +686,7 @@ Global
{9B7F7605-ADFF-4A47-9B64-FFF3E2EC9DEA} = {93503FAF-D43D-48C0-818C-92EB90F7606B}
{93503FAF-D43D-48C0-818C-92EB90F7606B} = {B75EE478-97F7-4E9F-9A5A-DB3D0988EDEA}
{1FCC8EEC-9E75-4FEA-AFCF-363DD33FF0B9} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
{A91DF3D3-BE2D-47E0-B600-E2838F47CE1A} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MaxEtwEventSizeBytes.get -> int
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MaxEtwEventSizeBytes.set -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MetricExportIntervalMilliseconds.get -> int
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MetricExportIntervalMilliseconds.set -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.OtlpEtwExporterOptions() -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpMetricEtwExporterExtensions
static OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpMetricEtwExporterExtensions.AddOtlpEtwExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action<OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions, OpenTelemetry.Metrics.MetricReaderOptions> configureExporterAndMetricReader = null) -> OpenTelemetry.Metrics.MeterProviderBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MaxEtwEventSizeBytes.get -> int
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MaxEtwEventSizeBytes.set -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MetricExportIntervalMilliseconds.get -> int
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MetricExportIntervalMilliseconds.set -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.OtlpEtwExporterOptions() -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpMetricEtwExporterExtensions
static OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpMetricEtwExporterExtensions.AddOtlpEtwExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action<OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions, OpenTelemetry.Metrics.MetricReaderOptions> configureExporterAndMetricReader = null) -> OpenTelemetry.Metrics.MeterProviderBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MaxEtwEventSizeBytes.get -> int
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MaxEtwEventSizeBytes.set -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MetricExportIntervalMilliseconds.get -> int
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.MetricExportIntervalMilliseconds.set -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions.OtlpEtwExporterOptions() -> void
OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpMetricEtwExporterExtensions
static OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpMetricEtwExporterExtensions.AddOtlpEtwExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action<OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.OtlpEtwExporterOptions, OpenTelemetry.Metrics.MetricReaderOptions> configureExporterAndMetricReader = null) -> OpenTelemetry.Metrics.MeterProviderBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// <copyright file="BaseOtlpEtwExportClient.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics.Tracing;
using System.Threading;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.Implementation.ExportClient;

internal abstract class BaseOtlpEtwExportClient<TRequest> : EventSource, IExportClient<TRequest>
{
protected BaseOtlpEtwExportClient(OtlpExporterOptions options)
{
Guard.ThrowIfNull(options);
}

protected BaseOtlpEtwExportClient()
{
}

/// <inheritdoc/>
public abstract bool SendExportRequest(TRequest request, CancellationToken cancellationToken = default);

/// <inheritdoc/>
public bool Shutdown(int timeoutMilliseconds)
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// <copyright file="OtlpEtwMetricsExportClient.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics.Tracing;
using System.Linq;
using System.Threading;
using Google.Protobuf;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
using OtlpCollector = OpenTelemetry.Proto.Collector.Metrics.V1;
using OtlpMetrics = OpenTelemetry.Proto.Metrics.V1;
using OtlpResource = OpenTelemetry.Proto.Resource.V1;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw.Implementation.ExportClient;

/// <summary>Class for sending OTLP metrics export request over ETW.</summary>
[EventSource(Name = "OpenTelemetryOtlpMetricExporter", Guid = "{edc24920-e004-40f6-a8e1-0e6e48f39d84}")]
internal sealed class OtlpEtwMetricsExportClient : BaseOtlpEtwExportClient<OtlpCollector.ExportMetricsServiceRequest>
{
private const int OtlpMetricEventId = 80;
private readonly int _maxEtwEventSize;

public OtlpEtwMetricsExportClient(OtlpEtwExporterOptions options)
{
this._maxEtwEventSize = options.MaxEtwEventSizeBytes;
}

public override bool SendExportRequest(OtlpCollector.ExportMetricsServiceRequest request, CancellationToken cancellationToken = default)
{
if (request.CalculateSize() > this._maxEtwEventSize)
{
var resourceMetrics = request.ResourceMetrics.FirstOrDefault();
var partialRequest = CreateNewRequest(resourceMetrics.Resource);
var partialResourceMetrics = partialRequest.ResourceMetrics.FirstOrDefault();
var partialRequestSize = partialRequest.CalculateSize();

// Separate partial request for each resource metric.
foreach (var scopeMetrics in resourceMetrics.ScopeMetrics)
{
var partialScopeMetrics = AddScopeMetrics(partialResourceMetrics, scopeMetrics);
partialRequestSize += partialScopeMetrics.CalculateSize();

foreach (var otlpMetric in scopeMetrics.Metrics)
{
if (partialRequestSize + otlpMetric.CalculateSize() > this._maxEtwEventSize)
{
this.WriteEvent(OtlpMetricEventId, partialRequest.ToByteArray());

// Reset to continue from an empty object.
partialRequest.Return();
partialResourceMetrics.ScopeMetrics.Clear();
partialScopeMetrics = AddScopeMetrics(partialResourceMetrics, scopeMetrics);
partialRequestSize = partialRequest.CalculateSize();
}

partialScopeMetrics.Metrics.Add(otlpMetric);
partialRequestSize += otlpMetric.CalculateSize();
}
}

this.WriteEvent(OtlpMetricEventId, partialRequest.ToByteArray());
partialRequest.Return();
}
else
{
this.WriteEvent(OtlpMetricEventId, request.ToByteArray());
}

return true;
}

private static OtlpCollector.ExportMetricsServiceRequest CreateNewRequest(OtlpResource.Resource resource)
{
var partialRequest = new OtlpCollector.ExportMetricsServiceRequest();
var partialResourceMetrics = new OtlpMetrics.ResourceMetrics
{
Resource = resource,
};
partialRequest.ResourceMetrics.Add(partialResourceMetrics);
return partialRequest;
}

private static OtlpMetrics.ScopeMetrics AddScopeMetrics(OtlpMetrics.ResourceMetrics partialResourceMetrics, OtlpMetrics.ScopeMetrics scopeMetrics)
{
var partialScopeMetrics = MetricItemExtensions.GetMetricListFromPool(scopeMetrics.Scope.Name, scopeMetrics.Scope.Version);
partialResourceMetrics.ScopeMetrics.Add(partialScopeMetrics);
return partialScopeMetrics;
}

// Though not used, these event definitions are required so event source base has the metadata for the IDs
// that will be generated.
[Event(OtlpMetricEventId)]
private void OtlpMetricEvent()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>OpenTelemetry protocol exporter over ETW for OpenTelemetry .NET</Description>
<Authors>OpenTelemetry Authors</Authors>
<NoWarn>$(NoWarn),CS1591,SA1123,SA1310,CA1810,CA1822,CA2000,CA2208,SA1201,SA1202,SA1308,SA1309,SA1311,SA1402,SA1602,SA1649</NoWarn>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition="$(OS) == 'Windows_NT'">$(TargetFrameworks);net462</TargetFrameworks>
<MinVerTagPrefix>Exporter.OpenTelemetryProtocol.Etw-</MinVerTagPrefix>
<EnableAnalysis>false</EnableAnalysis>
</PropertyGroup>

<!-- Guard.cs from OpenTelemetry.Exporter.OpenTelemetryProtocol.dll clashes with Shared\Guard.cs-->
<!--<ItemGroup>
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
</ItemGroup>-->

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="$(OpenTelemetryCoreLatestPrereleaseVersion)" />
</ItemGroup>

<!-- The following references are temporarily added to compile the project. -->
<ItemGroup>
<Reference Include="Google.Protobuf">
<HintPath>..\..\..\opentelemetry-dotnet\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\bin\Release\net462\Google.Protobuf.dll</HintPath>
</Reference>
<Reference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol">
<HintPath>..\..\..\opentelemetry-dotnet\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\bin\Release\net462\OpenTelemetry.Exporter.OpenTelemetryProtocol.dll</HintPath>
</Reference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// <copyright file="OtlpEtwExporterOptions.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using OpenTelemetry.Internal;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw;
public class OtlpEtwExporterOptions
{
private int _metricExporterIntervalMilliseconds = 60000;
private int _maxEtwEventSizeBytes = 32 * 1024; // Can be max ~64KB

/// <summary>
/// Gets or sets the metric export interval in milliseconds. The default value is 60000.
/// </summary>
public int MetricExportIntervalMilliseconds
{
get
{
return this._metricExporterIntervalMilliseconds;
}

set
{
Guard.ThrowIfOutOfRange(value, min: 1000);

this._metricExporterIntervalMilliseconds = value;
}
}

/// <summary>
/// Gets or sets the max ETW size in bytes. The default value is 32 KiB.
/// </summary>
public int MaxEtwEventSizeBytes
{
get
{
return this._maxEtwEventSizeBytes;
}

set
{
Guard.ThrowIfOutOfRange(value, min: 10/*1000*/, max: 63 * 1024);

this._maxEtwEventSizeBytes = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// <copyright file="OtlpEtwMetricExporter.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw;
internal sealed class OtlpEtwMetricExporter : OtlpMetricExporter
{
public OtlpEtwMetricExporter(OtlpEtwExporterOptions options)
: base(null, new Implementation.ExportClient.OtlpEtwMetricsExportClient(options))
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// <copyright file="OtlpMetricEtwExporterExtensions.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenTelemetry.Internal;
using OpenTelemetry.Metrics;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Etw;
public static class OtlpMetricEtwExporterExtensions
{
public static MeterProviderBuilder AddOtlpEtwExporter(
this MeterProviderBuilder builder,
Action<OtlpEtwExporterOptions, MetricReaderOptions> configureExporterAndMetricReader = null)
{
Guard.ThrowIfNull(builder);

if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
{
return deferredMeterProviderBuilder.Configure((sp, builder) =>
{
var exporterOptions = sp.GetRequiredService<IOptionsMonitor<OtlpEtwExporterOptions>>().Get(null);
var metricReaderOptions = sp.GetRequiredService<IOptionsMonitor<MetricReaderOptions>>().Get(null);

AddOtlpEtwExporter(builder, exporterOptions, metricReaderOptions, configureExporterAndMetricReader);
});
}

return AddOtlpEtwExporter(builder, new OtlpEtwExporterOptions(), new MetricReaderOptions(), configureExporterAndMetricReader);
}

private static MeterProviderBuilder AddOtlpEtwExporter(
MeterProviderBuilder builder,
OtlpEtwExporterOptions exporterOptions,
MetricReaderOptions metricReaderOptions,
Action<OtlpEtwExporterOptions, MetricReaderOptions> configure = null)
{
configure?.Invoke(exporterOptions, metricReaderOptions);

const int defaultExportIntervalMilliseconds = 60000;
const int defaultExportTimeoutMilliseconds = 30000;
var exportInterval = metricReaderOptions.PeriodicExportingMetricReaderOptions?.ExportIntervalMilliseconds ?? defaultExportIntervalMilliseconds;
var exportTimeout = metricReaderOptions.PeriodicExportingMetricReaderOptions?.ExportTimeoutMilliseconds ?? defaultExportTimeoutMilliseconds;

return builder.AddReader(new PeriodicExportingMetricReader(new OtlpEtwMetricExporter(exporterOptions), exportInterval, exportTimeout)
{
TemporalityPreference = metricReaderOptions.TemporalityPreference,
});
}
}