Metrics to Main (#2174)
This commit is contained in:
parent
146dd73ed2
commit
7c611c8537
|
@ -3,6 +3,9 @@
|
|||
<packageSources>
|
||||
<clear />
|
||||
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
|
||||
<!--
|
||||
<add key="dotnet6" value="https://dnceng.pkgs.visualstudio.com/public/_packaging/dotnet6/nuget/v3/index.json" />
|
||||
-->
|
||||
<add key=".Net Core Tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
<disabledPackageSources />
|
||||
|
|
|
@ -158,6 +158,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "trace", "trace", "{5B7FB835
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started", "docs\trace\getting-started\getting-started.csproj", "{BE60E3D5-DE30-4BAB-8E7A-63B21D0E80D7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "metrics", "metrics", "{3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
docs\metrics\building-your-own-exporter.md = docs\metrics\building-your-own-exporter.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logs", "logs", "{3862190B-E2C5-418E-AFDC-DB281FB5C705}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroserviceExample", "MicroserviceExample", "{4D492D62-5150-45F9-817F-C99562E364E2}"
|
||||
|
@ -200,6 +205,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "exception-reporting", "docs
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs\trace\customizing-the-sdk\customizing-the-sdk.csproj", "{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started", "docs\metrics\getting-started\getting-started.csproj", "{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Prometheus", "src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj", "{52158A12-E7EF-45A1-859F-06F9B17410CB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -394,6 +403,14 @@ Global
|
|||
{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -411,6 +428,7 @@ Global
|
|||
{2C7DD1DA-C229-4D9E-9AF0-BCD5CD3E4948} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
|
||||
{5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
|
||||
{BE60E3D5-DE30-4BAB-8E7A-63B21D0E80D7} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
|
||||
{3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
|
||||
{3862190B-E2C5-418E-AFDC-DB281FB5C705} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
|
||||
{4D492D62-5150-45F9-817F-C99562E364E2} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
|
||||
{07336602-860B-4975-95DD-405D19C00901} = {4D492D62-5150-45F9-817F-C99562E364E2}
|
||||
|
@ -424,6 +442,7 @@ Global
|
|||
{972396A8-E35B-499C-9BA1-765E9B8822E1} = {77C7929A-2EED-4AA6-8705-B5C443C8AA0F}
|
||||
{08D29501-F0A3-468F-B18D-BD1821A72383} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
|
||||
{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
|
||||
{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
<Content Include="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenTelemetry.sln'))\build\xunit.runner.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<!--Temp workaround to allow using daily dotnet 6 builds-->
|
||||
<!--<PackageReference Include="System.Runtime.CompilerServices.Unsafe" PrivateAssets="All" NoWarn="NU1605" Version="6.0.0-preview.4.21253.7" />-->
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="Package versions used by test and example projects">
|
||||
<!--
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="$(MicrosoftCodeAnalysisAnalyzersPkgVer)" Condition=" $(OS) == 'Windows_NT'">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(MinVerTagPrefix)' == 'core-' AND '$(CheckAPICompatibility)' == 'true'">
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<StackExchangeRedisPkgVer>[2.1.58,3.0)</StackExchangeRedisPkgVer>
|
||||
<StyleCopAnalyzersPkgVer>[1.1.118,2.0)</StyleCopAnalyzersPkgVer>
|
||||
<SystemCollectionsImmutablePkgVer>1.4.0</SystemCollectionsImmutablePkgVer>
|
||||
<SystemDiagnosticSourcePkgVer>5.0.1</SystemDiagnosticSourcePkgVer>
|
||||
<SystemDiagnosticSourcePkgVer>6.0.0-preview.6.21352.12</SystemDiagnosticSourcePkgVer>
|
||||
<SystemReflectionEmitLightweightPkgVer>4.7.0</SystemReflectionEmitLightweightPkgVer>
|
||||
<SystemTextJsonPkgVer>4.7.0</SystemTextJsonPkgVer>
|
||||
<SystemThreadingTasksExtensionsPkgVer>4.5.3</SystemThreadingTasksExtensionsPkgVer>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// <copyright file="Program.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static readonly Meter MyMeter = new Meter("TestMeter", "0.0.1");
|
||||
private static readonly Counter<long> Counter = MyMeter.CreateCounter<long>("counter");
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource("TestMeter")
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
|
||||
using var token = new CancellationTokenSource();
|
||||
Task writeMetricTask = new Task(() =>
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
Counter.Add(
|
||||
10,
|
||||
new KeyValuePair<string, object>("tag1", "value1"),
|
||||
new KeyValuePair<string, object>("tag2", "value2"));
|
||||
Task.Delay(10).Wait();
|
||||
}
|
||||
});
|
||||
writeMetricTask.Start();
|
||||
|
||||
token.CancelAfter(10000);
|
||||
await writeMetricTask;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
# Getting Started with OpenTelemetry .NET in 5 Minutes
|
||||
|
||||
First, download and install the [.NET Core
|
||||
SDK](https://dotnet.microsoft.com/download) on your computer.
|
||||
|
||||
Create a new console application and run it:
|
||||
|
||||
```sh
|
||||
dotnet new console --output getting-started
|
||||
cd getting-started
|
||||
dotnet run
|
||||
```
|
||||
|
||||
You should see the following output:
|
||||
|
||||
```text
|
||||
Hello World!
|
||||
```
|
||||
|
||||
Install the
|
||||
[OpenTelemetry.Exporter.Console](../../../src/OpenTelemetry.Exporter.Console/README.md)
|
||||
package:
|
||||
|
||||
```sh
|
||||
dotnet add package OpenTelemetry.Exporter.Console
|
||||
```
|
||||
|
||||
Update the `Program.cs` file with the code from [Program.cs](./Program.cs):
|
||||
|
||||
Run the application again (using `dotnet run`) and you should see the metric
|
||||
output from the console, similar to shown below:
|
||||
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
```text
|
||||
Export[] 16:38:36.241 16:38:37.233 TestMeter:counter [tag1=value1;tag2=value2] SumMetricAggregator Value: 590, Details: Delta=True,Mon=True,Count=59,Sum=590
|
||||
Export[] 16:38:37.233 16:38:38.258 TestMeter:counter [tag1=value1;tag2=value2] SumMetricAggregator Value: 640, Details: Delta=True,Mon=True,Count=64,Sum=640
|
||||
Export[] 16:38:38.258 16:38:39.261 TestMeter:counter [tag1=value1;tag2=value2] SumMetricAggregator Value: 640, Details: Delta=True,Mon=True,Count=64,Sum=640
|
||||
Export[] 16:38:39.261 16:38:40.266 TestMeter:counter [tag1=value1;tag2=value2] SumMetricAggregator Value: 630, Details: Delta=True,Mon=True,Count=63,Sum=630
|
||||
Export[] 16:38:40.266 16:38:41.271 TestMeter:counter [tag1=value1;tag2=value2] SumMetricAggregator Value: 640, Details: Delta=True,Mon=True,Count=64,Sum=640
|
||||
```
|
||||
<!-- markdownlint-enable MD013 -->
|
||||
|
||||
Congratulations! You are now collecting metrics using OpenTelemetry.
|
||||
|
||||
What does the above program do?
|
||||
|
||||
The program creates a
|
||||
[Meter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meter)
|
||||
instance named "TestMeter" and then creates a
|
||||
[Counter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#counter)
|
||||
instrument from it. This counter is used to repeatedly report metric
|
||||
measurements until exited after 10 seconds.
|
||||
|
||||
An OpenTelemetry
|
||||
[MeterProvider](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meterprovider)
|
||||
is configured to subscribe to instruments from the Meter `TestMeter`, and
|
||||
aggregate the measurements in-memory. The pre-aggregated metrics are exported
|
||||
every 1 second to a `ConsoleExporter`. `ConsoleExporter` simply displays it on
|
||||
the console.
|
|
@ -0,0 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -23,8 +23,10 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Instrumentation.AspNetCore;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
|
@ -32,6 +34,8 @@ namespace Examples.AspNetCore
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
private MeterProvider meterProvider;
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
this.Configuration = configuration;
|
||||
|
@ -112,6 +116,16 @@ namespace Examples.AspNetCore
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Add IServiceCollection.AddOpenTelemetryMetrics extension method
|
||||
var providerBuilder = Sdk.CreateMeterProviderBuilder()
|
||||
.AddAspNetCoreInstrumentation();
|
||||
|
||||
// TODO: Add configuration switch for Prometheus and OTLP export
|
||||
providerBuilder
|
||||
.AddConsoleExporter();
|
||||
|
||||
this.meterProvider = providerBuilder.Build();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
|
|
|
@ -36,5 +36,6 @@
|
|||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Zipkin\OpenTelemetry.Exporter.Zipkin.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.InMemory\OpenTelemetry.Exporter.InMemory.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace Examples.Console
|
|||
/// dotnet run -p Examples.Console.csproj prometheus -i 15 -p 9184 -d 2
|
||||
/// dotnet run -p Examples.Console.csproj otlp -e "http://localhost:4317"
|
||||
/// dotnet run -p Examples.Console.csproj zpages
|
||||
/// dotnet run -p Examples.Console.csproj metrics --help
|
||||
///
|
||||
/// The above must be run from the project root folder
|
||||
/// (eg: C:\repos\opentelemetry-dotnet\examples\Console\).
|
||||
|
@ -41,10 +42,12 @@ namespace Examples.Console
|
|||
/// <param name="args">Arguments from command line.</param>
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Parser.Default.ParseArguments<JaegerOptions, ZipkinOptions, GrpcNetClientOptions, HttpClientOptions, RedisOptions, ZPagesOptions, ConsoleOptions, OpenTelemetryShimOptions, OpenTracingShimOptions, OtlpOptions, InMemoryOptions>(args)
|
||||
Parser.Default.ParseArguments<JaegerOptions, ZipkinOptions, PrometheusOptions, MetricsOptions, GrpcNetClientOptions, HttpClientOptions, RedisOptions, ZPagesOptions, ConsoleOptions, OpenTelemetryShimOptions, OpenTracingShimOptions, OtlpOptions, InMemoryOptions>(args)
|
||||
.MapResult(
|
||||
(JaegerOptions options) => TestJaegerExporter.Run(options.Host, options.Port),
|
||||
(ZipkinOptions options) => TestZipkinExporter.Run(options.Uri),
|
||||
(PrometheusOptions options) => TestPrometheusExporter.Run(options.Port, options.DurationInMins),
|
||||
(MetricsOptions options) => TestMetrics.Run(options),
|
||||
(GrpcNetClientOptions options) => TestGrpcNetClient.Run(),
|
||||
(HttpClientOptions options) => TestHttpClient.Run(),
|
||||
(RedisOptions options) => TestRedis.Run(options.Uri),
|
||||
|
@ -55,8 +58,6 @@ namespace Examples.Console
|
|||
(OtlpOptions options) => TestOtlpExporter.Run(options.Endpoint),
|
||||
(InMemoryOptions options) => TestInMemoryExporter.Run(options),
|
||||
errs => 1);
|
||||
|
||||
System.Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,6 +80,50 @@ namespace Examples.Console
|
|||
public string Uri { get; set; }
|
||||
}
|
||||
|
||||
[Verb("prometheus", HelpText = "Specify the options required to test Prometheus")]
|
||||
internal class PrometheusOptions
|
||||
{
|
||||
[Option('p', "port", Default = 9184, HelpText = "The port to expose metrics. The endpoint will be http://localhost:port/metrics (This is the port from which your Prometheus server scraps metrics from.)", Required = false)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[Option('d', "duration", Default = 2, HelpText = "Total duration in minutes to run the demo.", Required = false)]
|
||||
public int DurationInMins { get; set; }
|
||||
}
|
||||
|
||||
[Verb("metrics", HelpText = "Specify the options required to test Metrics")]
|
||||
internal class MetricsOptions
|
||||
{
|
||||
[Option('d', "IsDelta", HelpText = "Export Delta metrics", Required = false, Default = true)]
|
||||
public bool IsDelta { get; set; }
|
||||
|
||||
[Option('g', "Gauge", HelpText = "Include Observable Gauge.", Required = false)]
|
||||
public bool? FlagGauge { get; set; }
|
||||
|
||||
[Option('u', "UpDownCounter", HelpText = "Include Observable Up/Down Counter.", Required = false)]
|
||||
public bool? FlagUpDownCounter { get; set; }
|
||||
|
||||
[Option('c', "Counter", HelpText = "Include Counter.", Required = false)]
|
||||
public bool? FlagCounter { get; set; }
|
||||
|
||||
[Option('h', "Histogram", HelpText = "Include Histogram.", Required = false)]
|
||||
public bool? FlagHistogram { get; set; }
|
||||
|
||||
[Option("defaultCollection", Default = 500, HelpText = "Default collection period in milliseconds.", Required = false)]
|
||||
public int DefaultCollectionPeriodMilliseconds { get; set; }
|
||||
|
||||
[Option("runtime", Default = 5000, HelpText = "Run time in milliseconds.", Required = false)]
|
||||
public int RunTime { get; set; }
|
||||
|
||||
[Option("tasks", Default = 1, HelpText = "Run # of concurrent tasks.", Required = false)]
|
||||
public int NumTasks { get; set; }
|
||||
|
||||
[Option("maxLoops", Default = 0, HelpText = "Maximum number of loops/iterations per task. (0 = No Limit)", Required = false)]
|
||||
public int MaxLoops { get; set; }
|
||||
|
||||
[Option("useExporter", Default = "console", HelpText = "Options include otlp or console.", Required = false)]
|
||||
public string UseExporter { get; set; }
|
||||
}
|
||||
|
||||
[Verb("grpc", HelpText = "Specify the options required to test Grpc.Net.Client")]
|
||||
internal class GrpcNetClientOptions
|
||||
{
|
||||
|
|
|
@ -66,6 +66,7 @@ namespace Examples.Console
|
|||
}
|
||||
|
||||
System.Console.WriteLine("Press Enter key to exit.");
|
||||
System.Console.ReadLine();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace Examples.Console
|
|||
}
|
||||
|
||||
System.Console.WriteLine("Press Enter key to exit.");
|
||||
System.Console.ReadLine();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
// <copyright file="TestMetrics.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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
namespace Examples.Console
|
||||
{
|
||||
internal class TestMetrics
|
||||
{
|
||||
internal static object Run(MetricsOptions options)
|
||||
{
|
||||
var providerBuilder = Sdk.CreateMeterProviderBuilder()
|
||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("myservice"))
|
||||
.AddSource("TestMeter"); // All instruments from this meter are enabled.
|
||||
|
||||
if (options.UseExporter.ToLower() == "otlp")
|
||||
{
|
||||
/*
|
||||
* Prerequisite to run this example:
|
||||
* Set up an OpenTelemetry Collector to run on local docker.
|
||||
*
|
||||
* Open a terminal window at the examples/Console/ directory and
|
||||
* launch the OpenTelemetry Collector with an OTLP receiver, by running:
|
||||
*
|
||||
* - On Unix based systems use:
|
||||
* docker run --rm -it -p 4317:4317 -v $(pwd):/cfg otel/opentelemetry-collector:0.28.0 --config=/cfg/otlp-collector-example/config.yaml
|
||||
*
|
||||
* - On Windows use:
|
||||
* docker run --rm -it -p 4317:4317 -v "%cd%":/cfg otel/opentelemetry-collector:0.28.0 --config=/cfg/otlp-collector-example/config.yaml
|
||||
*
|
||||
* Open another terminal window at the examples/Console/ directory and
|
||||
* launch the OTLP example by running:
|
||||
*
|
||||
* dotnet run metrics --useExporter otlp
|
||||
*
|
||||
* The OpenTelemetry Collector will output all received metrics to the stdout of its terminal.
|
||||
*
|
||||
*/
|
||||
|
||||
// Adding the OtlpExporter creates a GrpcChannel.
|
||||
// This switch must be set before creating a GrpcChannel/HttpClient when calling an insecure gRPC service.
|
||||
// See: https://docs.microsoft.com/aspnet/core/grpc/troubleshoot#call-insecure-grpc-services-with-net-core-client
|
||||
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
|
||||
|
||||
providerBuilder
|
||||
.AddOtlpExporter(o =>
|
||||
{
|
||||
o.MetricExportInterval = options.DefaultCollectionPeriodMilliseconds;
|
||||
o.IsDelta = options.IsDelta;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
providerBuilder
|
||||
.AddConsoleExporter(o =>
|
||||
{
|
||||
o.MetricExportInterval = options.DefaultCollectionPeriodMilliseconds;
|
||||
o.IsDelta = options.IsDelta;
|
||||
});
|
||||
}
|
||||
|
||||
using var provider = providerBuilder.Build();
|
||||
|
||||
using var meter = new Meter("TestMeter", "0.0.1");
|
||||
|
||||
Counter<int> counter = null;
|
||||
if (options.FlagCounter ?? true)
|
||||
{
|
||||
counter = meter.CreateCounter<int>("counter", "things", "A count of things");
|
||||
}
|
||||
|
||||
Histogram<int> histogram = null;
|
||||
if (options.FlagHistogram ?? false)
|
||||
{
|
||||
histogram = meter.CreateHistogram<int>("histogram");
|
||||
}
|
||||
|
||||
if (options.FlagGauge ?? false)
|
||||
{
|
||||
var observableCounter = meter.CreateObservableGauge<int>("gauge", () =>
|
||||
{
|
||||
return new List<Measurement<int>>()
|
||||
{
|
||||
new Measurement<int>(
|
||||
(int)Process.GetCurrentProcess().PrivateMemorySize64,
|
||||
new KeyValuePair<string, object>("tag1", "value1")),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (options.FlagUpDownCounter ?? true)
|
||||
{
|
||||
var observableCounter = meter.CreateObservableCounter<int>("updown", () =>
|
||||
{
|
||||
return new List<Measurement<int>>()
|
||||
{
|
||||
new Measurement<int>(
|
||||
(int)Process.GetCurrentProcess().PrivateMemorySize64,
|
||||
new KeyValuePair<string, object>("tag1", "value1")),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
for (int i = 0; i < options.NumTasks; i++)
|
||||
{
|
||||
var taskno = i;
|
||||
|
||||
tasks.Add(Task.Run(() =>
|
||||
{
|
||||
System.Console.WriteLine($"Task started {taskno + 1}/{options.NumTasks}.");
|
||||
|
||||
var loops = 0;
|
||||
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
if (options.MaxLoops > 0 && loops >= options.MaxLoops)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
histogram?.Record(10);
|
||||
|
||||
histogram?.Record(
|
||||
100,
|
||||
new KeyValuePair<string, object>("tag1", "value1"));
|
||||
|
||||
histogram?.Record(
|
||||
200,
|
||||
new KeyValuePair<string, object>("tag1", "value2"),
|
||||
new KeyValuePair<string, object>("tag2", "value2"));
|
||||
|
||||
histogram?.Record(
|
||||
100,
|
||||
new KeyValuePair<string, object>("tag1", "value1"));
|
||||
|
||||
histogram?.Record(
|
||||
200,
|
||||
new KeyValuePair<string, object>("tag2", "value2"),
|
||||
new KeyValuePair<string, object>("tag1", "value2"));
|
||||
|
||||
counter?.Add(10);
|
||||
|
||||
counter?.Add(
|
||||
100,
|
||||
new KeyValuePair<string, object>("tag1", "value1"));
|
||||
|
||||
counter?.Add(
|
||||
200,
|
||||
new KeyValuePair<string, object>("tag1", "value2"),
|
||||
new KeyValuePair<string, object>("tag2", "value2"));
|
||||
|
||||
counter?.Add(
|
||||
100,
|
||||
new KeyValuePair<string, object>("tag1", "value1"));
|
||||
|
||||
counter?.Add(
|
||||
200,
|
||||
new KeyValuePair<string, object>("tag2", "value2"),
|
||||
new KeyValuePair<string, object>("tag1", "value2"));
|
||||
|
||||
loops++;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
cts.CancelAfter(options.RunTime);
|
||||
System.Console.WriteLine($"Wait for {options.RunTime} milliseconds.");
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ namespace Examples.Console
|
|||
}
|
||||
|
||||
System.Console.WriteLine("Press Enter key to exit.");
|
||||
System.Console.ReadLine();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace Examples.Console
|
|||
}
|
||||
|
||||
System.Console.WriteLine("Press Enter key to exit.");
|
||||
System.Console.ReadLine();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// <copyright file="TestPrometheusExporter.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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace Examples.Console
|
||||
{
|
||||
internal class TestPrometheusExporter
|
||||
{
|
||||
private static readonly Meter MyMeter = new Meter("TestMeter", "0.0.1");
|
||||
private static readonly Counter<long> Counter = MyMeter.CreateCounter<long>("counter");
|
||||
|
||||
internal static object Run(int port, int totalDurationInMins)
|
||||
{
|
||||
/*
|
||||
Following is sample prometheus.yml config. Adjust port,interval as needed.
|
||||
|
||||
scrape_configs:
|
||||
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
|
||||
- job_name: 'OpenTelemetryTest'
|
||||
|
||||
# metrics_path defaults to '/metrics'
|
||||
# scheme defaults to 'http'.
|
||||
|
||||
static_configs:
|
||||
- targets: ['localhost:9184']
|
||||
*/
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource("TestMeter")
|
||||
.AddPrometheusExporter(opt => opt.Url = $"http://localhost:{port}/metrics/")
|
||||
.Build();
|
||||
|
||||
using var token = new CancellationTokenSource();
|
||||
Task writeMetricTask = new Task(() =>
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
Counter.Add(
|
||||
10,
|
||||
new KeyValuePair<string, object>("tag1", "value1"),
|
||||
new KeyValuePair<string, object>("tag2", "value2"));
|
||||
|
||||
Counter.Add(
|
||||
100,
|
||||
new KeyValuePair<string, object>("tag1", "anothervalue"),
|
||||
new KeyValuePair<string, object>("tag2", "somethingelse"));
|
||||
Task.Delay(10).Wait();
|
||||
}
|
||||
});
|
||||
writeMetricTask.Start();
|
||||
|
||||
token.CancelAfter(totalDurationInMins * 60 * 1000);
|
||||
|
||||
System.Console.WriteLine($"OpenTelemetry Prometheus Exporter is making metrics available at http://localhost:{port}/metrics/");
|
||||
System.Console.WriteLine($"Press Enter key to exit now or will exit automatically after {totalDurationInMins} minutes.");
|
||||
System.Console.ReadLine();
|
||||
token.Cancel();
|
||||
System.Console.WriteLine("Exiting...");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -70,6 +70,9 @@ namespace Examples.Console
|
|||
}
|
||||
}
|
||||
|
||||
System.Console.Write("Press ENTER to stop.");
|
||||
System.Console.ReadLine();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ branch](https://github.com/open-telemetry/opentelemetry-dotnet/tree/metrics),
|
|||
please check the latest changes
|
||||
[here](https://github.com/open-telemetry/opentelemetry-dotnet/blob/metrics/src/OpenTelemetry.Api/CHANGELOG.md#experimental---metrics).
|
||||
|
||||
* Removed existing Metrics code as the spec is completely being re-written.
|
||||
([#2030](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2030))
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Removes .NET Framework 4.5.2 support. The minimum .NET Framework
|
||||
|
@ -81,9 +84,6 @@ Released 2021-Jan-29
|
|||
* `Status.WithDescription` will now ignore the provided description if the
|
||||
`Status.StatusCode` is anything other than `ERROR`.
|
||||
([#1655](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1655))
|
||||
* Metrics removed as it is not part 1.0.0 release. See issue
|
||||
[#1501](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1655) for
|
||||
details on Metric release plans.
|
||||
* Relax System.Diagnostics.DiagnosticSource version requirement to allow
|
||||
versions >=5.0. Previously only versions up to 6.0 (excluding 6.0) was
|
||||
allowed.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// <copyright file="MeterProvider.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.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// MeterProvider base class.
|
||||
/// </summary>
|
||||
public class MeterProvider : BaseProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MeterProvider"/> class.
|
||||
/// </summary>
|
||||
protected MeterProvider()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// <copyright file="MeterProviderBuilder.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;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// TracerProviderBuilder base class.
|
||||
/// </summary>
|
||||
public abstract class MeterProviderBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MeterProviderBuilder"/> class.
|
||||
/// </summary>
|
||||
protected MeterProviderBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds instrumentation to the provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInstrumentation">Type of instrumentation class.</typeparam>
|
||||
/// <param name="instrumentationFactory">Function that builds instrumentation.</param>
|
||||
/// <returns>Returns <see cref="MeterProviderBuilder"/> for chaining.</returns>
|
||||
public abstract MeterProviderBuilder AddInstrumentation<TInstrumentation>(
|
||||
Func<TInstrumentation> instrumentationFactory)
|
||||
where TInstrumentation : class;
|
||||
|
||||
/// <summary>
|
||||
/// Adds given Meter names to the list of subscribed sources.
|
||||
/// </summary>
|
||||
/// <param name="names">Meter source names.</param>
|
||||
/// <returns>Returns <see cref="MeterProviderBuilder"/> for chaining.</returns>
|
||||
public abstract MeterProviderBuilder AddSource(params string[] names);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// <copyright file="ConsoleExporterMetricHelperExtensions.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 OpenTelemetry.Exporter;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public static class ConsoleExporterMetricHelperExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Console exporter to the TracerProvider.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="MeterProviderBuilder"/> builder to use.</param>
|
||||
/// <param name="configure">Exporter configuration options.</param>
|
||||
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
|
||||
public static MeterProviderBuilder AddConsoleExporter(this MeterProviderBuilder builder, Action<ConsoleExporterOptions> configure = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
var options = new ConsoleExporterOptions();
|
||||
configure?.Invoke(options);
|
||||
return builder.AddMetricProcessor(new PushMetricProcessor(new ConsoleMetricExporter(options), options.MetricExportInterval, options.IsDelta));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,5 +22,16 @@ namespace OpenTelemetry.Exporter
|
|||
/// Gets or sets the output targets for the console exporter.
|
||||
/// </summary>
|
||||
public ConsoleExporterOutputTargets Targets { get; set; } = ConsoleExporterOutputTargets.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the metric export interval.
|
||||
/// </summary>
|
||||
public int MetricExportInterval { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to export Delta
|
||||
/// values or not (Cumulative).
|
||||
/// </summary>
|
||||
public bool IsDelta { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
// <copyright file="ConsoleMetricExporter.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 System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
public class ConsoleMetricExporter : ConsoleExporter<MetricItem>
|
||||
{
|
||||
private Resource resource;
|
||||
|
||||
public ConsoleMetricExporter(ConsoleExporterOptions options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public override ExportResult Export(in Batch<MetricItem> batch)
|
||||
{
|
||||
if (this.resource == null)
|
||||
{
|
||||
this.resource = this.ParentProvider.GetResource();
|
||||
if (this.resource != Resource.Empty)
|
||||
{
|
||||
foreach (var resourceAttribute in this.resource.Attributes)
|
||||
{
|
||||
if (resourceAttribute.Key.Equals("service.name"))
|
||||
{
|
||||
Console.WriteLine("Service.Name" + resourceAttribute.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var metricItem in batch)
|
||||
{
|
||||
foreach (var metric in metricItem.Metrics)
|
||||
{
|
||||
var tags = metric.Attributes.ToArray().Select(k => $"{k.Key}={k.Value?.ToString()}");
|
||||
|
||||
string valueDisplay = string.Empty;
|
||||
if (metric is ISumMetric sumMetric)
|
||||
{
|
||||
if (sumMetric.Sum.Value is double doubleSum)
|
||||
{
|
||||
valueDisplay = ((double)doubleSum).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (sumMetric.Sum.Value is long longSum)
|
||||
{
|
||||
valueDisplay = ((long)longSum).ToString();
|
||||
}
|
||||
}
|
||||
else if (metric is IGaugeMetric gaugeMetric)
|
||||
{
|
||||
if (gaugeMetric.LastValue.Value is double doubleValue)
|
||||
{
|
||||
valueDisplay = ((double)doubleValue).ToString();
|
||||
}
|
||||
else if (gaugeMetric.LastValue.Value is long longValue)
|
||||
{
|
||||
valueDisplay = ((long)longValue).ToString();
|
||||
}
|
||||
|
||||
// Qn: tags again ? gaugeMetric.LastValue.Tags
|
||||
}
|
||||
else if (metric is ISummaryMetric summaryMetric)
|
||||
{
|
||||
valueDisplay = string.Format("Sum: {0} Count: {1}", summaryMetric.PopulationSum, summaryMetric.PopulationCount);
|
||||
}
|
||||
else if (metric is IHistogramMetric histogramMetric)
|
||||
{
|
||||
valueDisplay = string.Format("Sum: {0} Count: {1}", histogramMetric.PopulationSum, histogramMetric.PopulationCount);
|
||||
}
|
||||
|
||||
var kind = metric.GetType().Name;
|
||||
|
||||
string time = $"{metric.StartTimeExclusive.ToLocalTime().ToString("HH:mm:ss.fff")} {metric.EndTimeInclusive.ToLocalTime().ToString("HH:mm:ss.fff")}";
|
||||
|
||||
var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {kind} Value: {valueDisplay}");
|
||||
|
||||
if (!string.IsNullOrEmpty(metric.Description))
|
||||
{
|
||||
msg.Append($", Description: {metric.Description}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(metric.Unit))
|
||||
{
|
||||
msg.Append($", Unit: {metric.Unit}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(metric.Meter.Name))
|
||||
{
|
||||
msg.Append($", Meter: {metric.Meter.Name}");
|
||||
|
||||
if (!string.IsNullOrEmpty(metric.Meter.Version))
|
||||
{
|
||||
msg.Append($"/{metric.Meter.Version}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return ExportResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
// <copyright file="MetricItemExtensions.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 System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Collections;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1;
|
||||
using OtlpCommon = Opentelemetry.Proto.Common.V1;
|
||||
using OtlpMetrics = Opentelemetry.Proto.Metrics.V1;
|
||||
using OtlpResource = Opentelemetry.Proto.Resource.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
|
||||
{
|
||||
internal static class MetricItemExtensions
|
||||
{
|
||||
private static readonly ConcurrentBag<OtlpMetrics.InstrumentationLibraryMetrics> MetricListPool = new ConcurrentBag<OtlpMetrics.InstrumentationLibraryMetrics>();
|
||||
private static readonly Action<RepeatedField<OtlpMetrics.Metric>, int> RepeatedFieldOfMetricSetCountAction = CreateRepeatedFieldOfMetricSetCountAction();
|
||||
|
||||
internal static void AddBatch(
|
||||
this OtlpCollector.ExportMetricsServiceRequest request,
|
||||
OtlpResource.Resource processResource,
|
||||
in Batch<MetricItem> batch)
|
||||
{
|
||||
var metricsByLibrary = new Dictionary<string, OtlpMetrics.InstrumentationLibraryMetrics>();
|
||||
var resourceMetrics = new OtlpMetrics.ResourceMetrics
|
||||
{
|
||||
Resource = processResource,
|
||||
};
|
||||
request.ResourceMetrics.Add(resourceMetrics);
|
||||
|
||||
foreach (var metricItem in batch)
|
||||
{
|
||||
foreach (var metric in metricItem.Metrics)
|
||||
{
|
||||
var otlpMetric = metric.ToOtlpMetric();
|
||||
if (otlpMetric == null)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.CouldNotTranslateMetric(
|
||||
nameof(MetricItemExtensions),
|
||||
nameof(AddBatch));
|
||||
continue;
|
||||
}
|
||||
|
||||
var meterName = metric.Meter.Name;
|
||||
if (!metricsByLibrary.TryGetValue(meterName, out var metrics))
|
||||
{
|
||||
metrics = GetMetricListFromPool(meterName, metric.Meter.Version);
|
||||
|
||||
metricsByLibrary.Add(meterName, metrics);
|
||||
resourceMetrics.InstrumentationLibraryMetrics.Add(metrics);
|
||||
}
|
||||
|
||||
metrics.Metrics.Add(otlpMetric);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Return(this OtlpCollector.ExportMetricsServiceRequest request)
|
||||
{
|
||||
var resourceMetrics = request.ResourceMetrics.FirstOrDefault();
|
||||
if (resourceMetrics == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var libraryMetrics in resourceMetrics.InstrumentationLibraryMetrics)
|
||||
{
|
||||
RepeatedFieldOfMetricSetCountAction(libraryMetrics.Metrics, 0);
|
||||
MetricListPool.Add(libraryMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static OtlpMetrics.InstrumentationLibraryMetrics GetMetricListFromPool(string name, string version)
|
||||
{
|
||||
if (!MetricListPool.TryTake(out var metrics))
|
||||
{
|
||||
metrics = new OtlpMetrics.InstrumentationLibraryMetrics
|
||||
{
|
||||
InstrumentationLibrary = new OtlpCommon.InstrumentationLibrary
|
||||
{
|
||||
Name = name, // Name is enforced to not be null, but it can be empty.
|
||||
Version = version ?? string.Empty, // NRE throw by proto
|
||||
},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
metrics.InstrumentationLibrary.Name = name;
|
||||
metrics.InstrumentationLibrary.Version = version ?? string.Empty;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static OtlpMetrics.Metric ToOtlpMetric(this IMetric metric)
|
||||
{
|
||||
var otlpMetric = new OtlpMetrics.Metric
|
||||
{
|
||||
Name = metric.Name,
|
||||
Description = metric.Description,
|
||||
Unit = metric.Unit,
|
||||
};
|
||||
|
||||
if (metric is ISumMetric sumMetric)
|
||||
{
|
||||
var sum = new OtlpMetrics.Sum
|
||||
{
|
||||
IsMonotonic = sumMetric.IsMonotonic,
|
||||
AggregationTemporality = sumMetric.IsDeltaTemporality
|
||||
? OtlpMetrics.AggregationTemporality.Delta
|
||||
: OtlpMetrics.AggregationTemporality.Cumulative,
|
||||
};
|
||||
var dataPoint = metric.ToNumberDataPoint(sumMetric.Sum, sumMetric.Exemplars);
|
||||
sum.DataPoints.Add(dataPoint);
|
||||
otlpMetric.Sum = sum;
|
||||
}
|
||||
else if (metric is IGaugeMetric gaugeMetric)
|
||||
{
|
||||
var gauge = new OtlpMetrics.Gauge();
|
||||
var dataPoint = metric.ToNumberDataPoint(gaugeMetric.LastValue.Value, gaugeMetric.Exemplars);
|
||||
gauge.DataPoints.Add(dataPoint);
|
||||
otlpMetric.Gauge = gauge;
|
||||
}
|
||||
else if (metric is ISummaryMetric summaryMetric)
|
||||
{
|
||||
var summary = new OtlpMetrics.Summary();
|
||||
|
||||
var dataPoint = new OtlpMetrics.SummaryDataPoint
|
||||
{
|
||||
StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(),
|
||||
TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(),
|
||||
Count = (ulong)summaryMetric.PopulationCount,
|
||||
Sum = summaryMetric.PopulationSum,
|
||||
};
|
||||
|
||||
// TODO: Do TagEnumerationState thing.
|
||||
foreach (var attribute in metric.Attributes)
|
||||
{
|
||||
dataPoint.Attributes.Add(attribute.ToOtlpAttribute());
|
||||
}
|
||||
|
||||
foreach (var quantile in summaryMetric.Quantiles)
|
||||
{
|
||||
var quantileValue = new OtlpMetrics.SummaryDataPoint.Types.ValueAtQuantile
|
||||
{
|
||||
Quantile = quantile.Quantile,
|
||||
Value = quantile.Value,
|
||||
};
|
||||
dataPoint.QuantileValues.Add(quantileValue);
|
||||
}
|
||||
|
||||
otlpMetric.Summary = summary;
|
||||
}
|
||||
else if (metric is IHistogramMetric histogramMetric)
|
||||
{
|
||||
var histogram = new OtlpMetrics.Histogram
|
||||
{
|
||||
AggregationTemporality = histogramMetric.IsDeltaTemporality
|
||||
? OtlpMetrics.AggregationTemporality.Delta
|
||||
: OtlpMetrics.AggregationTemporality.Cumulative,
|
||||
};
|
||||
|
||||
var dataPoint = new OtlpMetrics.HistogramDataPoint
|
||||
{
|
||||
StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(),
|
||||
TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(),
|
||||
Count = (ulong)histogramMetric.PopulationCount,
|
||||
Sum = histogramMetric.PopulationSum,
|
||||
};
|
||||
|
||||
foreach (var bucket in histogramMetric.Buckets)
|
||||
{
|
||||
dataPoint.BucketCounts.Add((ulong)bucket.Count);
|
||||
|
||||
// TODO: Verify how to handle the bounds. We've modeled things with
|
||||
// a LowBoundary and HighBoundary. OTLP data model has modeled this
|
||||
// differently: https://github.com/open-telemetry/opentelemetry-proto/blob/bacfe08d84e21fb2a779e302d12e8dfeb67e7b86/opentelemetry/proto/metrics/v1/metrics.proto#L554-L568
|
||||
dataPoint.ExplicitBounds.Add(bucket.HighBoundary);
|
||||
}
|
||||
|
||||
// TODO: Do TagEnumerationState thing.
|
||||
foreach (var attribute in metric.Attributes)
|
||||
{
|
||||
dataPoint.Attributes.Add(attribute.ToOtlpAttribute());
|
||||
}
|
||||
|
||||
foreach (var exemplar in histogramMetric.Exemplars)
|
||||
{
|
||||
dataPoint.Exemplars.Add(exemplar.ToOtlpExemplar());
|
||||
}
|
||||
|
||||
otlpMetric.Histogram = histogram;
|
||||
}
|
||||
|
||||
return otlpMetric;
|
||||
}
|
||||
|
||||
private static OtlpMetrics.NumberDataPoint ToNumberDataPoint(this IMetric metric, object value, IEnumerable<IExemplar> exemplars)
|
||||
{
|
||||
var dataPoint = new OtlpMetrics.NumberDataPoint
|
||||
{
|
||||
StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(),
|
||||
TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(),
|
||||
};
|
||||
|
||||
if (value is double doubleValue)
|
||||
{
|
||||
dataPoint.AsDouble = doubleValue;
|
||||
}
|
||||
else if (value is long longValue)
|
||||
{
|
||||
dataPoint.AsInt = longValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Determine how we want to handle exceptions here.
|
||||
// Do we want to just skip this metric and move on?
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
// TODO: Do TagEnumerationState thing.
|
||||
foreach (var attribute in metric.Attributes)
|
||||
{
|
||||
dataPoint.Attributes.Add(attribute.ToOtlpAttribute());
|
||||
}
|
||||
|
||||
foreach (var exemplar in exemplars)
|
||||
{
|
||||
dataPoint.Exemplars.Add(exemplar.ToOtlpExemplar());
|
||||
}
|
||||
|
||||
return dataPoint;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static OtlpMetrics.Exemplar ToOtlpExemplar(this IExemplar exemplar)
|
||||
{
|
||||
var otlpExemplar = new OtlpMetrics.Exemplar();
|
||||
|
||||
if (exemplar.Value is double doubleValue)
|
||||
{
|
||||
otlpExemplar.AsDouble = doubleValue;
|
||||
}
|
||||
else if (exemplar.Value is long longValue)
|
||||
{
|
||||
otlpExemplar.AsInt = longValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Determine how we want to handle exceptions here.
|
||||
// Do we want to just skip this exemplar and move on?
|
||||
// Should we skip recording the whole metric?
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
otlpExemplar.TimeUnixNano = (ulong)exemplar.Timestamp.ToUnixTimeNanoseconds();
|
||||
|
||||
// TODO: Do the TagEnumerationState thing.
|
||||
foreach (var tag in exemplar.FilteredTags)
|
||||
{
|
||||
otlpExemplar.FilteredAttributes.Add(tag.ToOtlpAttribute());
|
||||
}
|
||||
|
||||
if (exemplar.TraceId != default)
|
||||
{
|
||||
byte[] traceIdBytes = new byte[16];
|
||||
exemplar.TraceId.CopyTo(traceIdBytes);
|
||||
otlpExemplar.TraceId = UnsafeByteOperations.UnsafeWrap(traceIdBytes);
|
||||
}
|
||||
|
||||
if (exemplar.SpanId != default)
|
||||
{
|
||||
byte[] spanIdBytes = new byte[8];
|
||||
exemplar.SpanId.CopyTo(spanIdBytes);
|
||||
otlpExemplar.SpanId = UnsafeByteOperations.UnsafeWrap(spanIdBytes);
|
||||
}
|
||||
|
||||
return otlpExemplar;
|
||||
}
|
||||
|
||||
private static Action<RepeatedField<OtlpMetrics.Metric>, int> CreateRepeatedFieldOfMetricSetCountAction()
|
||||
{
|
||||
FieldInfo repeatedFieldOfMetricCountField = typeof(RepeatedField<OtlpMetrics.Metric>).GetField("count", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
DynamicMethod dynamicMethod = new DynamicMethod(
|
||||
"CreateSetCountAction",
|
||||
null,
|
||||
new[] { typeof(RepeatedField<OtlpMetrics.Metric>), typeof(int) },
|
||||
typeof(MetricItemExtensions).Module,
|
||||
skipVisibility: true);
|
||||
|
||||
var generator = dynamicMethod.GetILGenerator();
|
||||
|
||||
generator.Emit(OpCodes.Ldarg_0);
|
||||
generator.Emit(OpCodes.Ldarg_1);
|
||||
generator.Emit(OpCodes.Stfld, repeatedFieldOfMetricCountField);
|
||||
generator.Emit(OpCodes.Ret);
|
||||
|
||||
return (Action<RepeatedField<OtlpMetrics.Metric>, int>)dynamicMethod.CreateDelegate(typeof(Action<RepeatedField<OtlpMetrics.Metric>, int>));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
|
|||
this.WriteEvent(1, ex);
|
||||
}
|
||||
|
||||
[Event(2, Message = "Exporter failed send spans to collector. Data will not be sent. Exception: {0}", Level = EventLevel.Error)]
|
||||
[Event(2, Message = "Exporter failed send data to collector. Data will not be sent. Exception: {0}", Level = EventLevel.Error)]
|
||||
public void FailedToReachCollector(string ex)
|
||||
{
|
||||
this.WriteEvent(2, ex);
|
||||
|
@ -75,5 +75,11 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
|
|||
{
|
||||
this.WriteEvent(4, ex);
|
||||
}
|
||||
|
||||
[Event(5, Message = "Could not translate metric from class '{0}' and method '{1}', metric will not be recorded.", Level = EventLevel.Informational)]
|
||||
public void CouldNotTranslateMetric(string className, string methodName)
|
||||
{
|
||||
this.WriteEvent(5, className, methodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,10 +32,14 @@
|
|||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\StatusHelper.cs" Link="Includes\StatusHelper.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\IActivityEnumerator.cs" Link="Includes\IActivityEnumerator.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\EnumerationHelper.cs" Link="Includes\EnumerationHelper.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\PooledList.cs" Link="Includes\PooledList.cs" />
|
||||
|
||||
<!-- TODO: Much of the metrics SDK is currently internal.
|
||||
In the meantime the OpenTelemetry SDK project has set InternalsVisibleTo for the OpenTelemetry.Exporter.OpenTelemetryProtocol project.
|
||||
These include conflict with the exposed internal types, so they need to be commented out for now. -->
|
||||
<!-- <Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\PooledList.cs" Link="Includes\PooledList.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\PeerServiceResolver.cs" Link="Includes\PeerServiceResolver.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\ResourceSemanticConventions.cs" Link="Includes\ResourceSemanticConventions.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\ServiceProviderExtensions.cs" Link="Includes\ServiceProviderExtensions.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\ServiceProviderExtensions.cs" Link="Includes\ServiceProviderExtensions.cs" /> -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -52,5 +52,16 @@ namespace OpenTelemetry.Exporter
|
|||
/// Gets or sets the BatchExportProcessor options. Ignored unless ExportProcessorType is Batch.
|
||||
/// </summary>
|
||||
public BatchExportProcessorOptions<Activity> BatchExportProcessorOptions { get; set; } = new BatchExportProcessorOptions<Activity>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the metric export interval.
|
||||
/// </summary>
|
||||
public int MetricExportInterval { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to export Delta
|
||||
/// values or not (Cumulative).
|
||||
/// </summary>
|
||||
public bool IsDelta { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// <copyright file="OtlpMetricExporterHelperExtensions.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 OpenTelemetry.Exporter;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods to simplify registering of the OpenTelemetry Protocol (OTLP) exporter.
|
||||
/// </summary>
|
||||
public static class OtlpMetricExporterHelperExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds OpenTelemetry Protocol (OTLP) exporter to the MeterProvider.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="MeterProviderBuilder"/> builder to use.</param>
|
||||
/// <param name="configure">Exporter configuration options.</param>
|
||||
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
|
||||
public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder builder, Action<OtlpExporterOptions> configure = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
var options = new OtlpExporterOptions();
|
||||
configure?.Invoke(options);
|
||||
return builder.AddMetricProcessor(new PushMetricProcessor(new OtlpMetricsExporter(options), options.MetricExportInterval, options.IsDelta));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
// <copyright file="OtlpMetricsExporter.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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
#if NETSTANDARD2_1
|
||||
using Grpc.Net.Client;
|
||||
#endif
|
||||
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1;
|
||||
using OtlpCommon = Opentelemetry.Proto.Common.V1;
|
||||
using OtlpResource = Opentelemetry.Proto.Resource.V1;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exporter consuming <see cref="MetricItem"/> and exporting the data using
|
||||
/// the OpenTelemetry protocol (OTLP).
|
||||
/// </summary>
|
||||
public class OtlpMetricsExporter : BaseExporter<MetricItem>
|
||||
{
|
||||
private readonly OtlpExporterOptions options;
|
||||
#if NETSTANDARD2_1
|
||||
private readonly GrpcChannel channel;
|
||||
#else
|
||||
private readonly Channel channel;
|
||||
#endif
|
||||
private readonly OtlpCollector.MetricsService.MetricsServiceClient metricsClient;
|
||||
private readonly Metadata headers;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpMetricsExporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Configuration options for the exporter.</param>
|
||||
public OtlpMetricsExporter(OtlpExporterOptions options)
|
||||
: this(options, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OtlpMetricsExporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Configuration options for the exporter.</param>
|
||||
/// <param name="metricsServiceClient"><see cref="OtlpCollector.MetricsService.MetricsServiceClient"/>.</param>
|
||||
internal OtlpMetricsExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.MetricsServiceClient metricsServiceClient = null)
|
||||
{
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
this.headers = GetMetadataFromHeaders(options.Headers);
|
||||
if (this.options.TimeoutMilliseconds <= 0)
|
||||
{
|
||||
throw new ArgumentException("Timeout value provided is not a positive number.", nameof(this.options.TimeoutMilliseconds));
|
||||
}
|
||||
|
||||
if (metricsServiceClient != null)
|
||||
{
|
||||
this.metricsClient = metricsServiceClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps)
|
||||
{
|
||||
throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported.");
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
this.channel = GrpcChannel.ForAddress(options.Endpoint);
|
||||
#else
|
||||
ChannelCredentials channelCredentials;
|
||||
if (options.Endpoint.Scheme == Uri.UriSchemeHttps)
|
||||
{
|
||||
channelCredentials = new SslCredentials();
|
||||
}
|
||||
else
|
||||
{
|
||||
channelCredentials = ChannelCredentials.Insecure;
|
||||
}
|
||||
|
||||
this.channel = new Channel(options.Endpoint.Authority, channelCredentials);
|
||||
#endif
|
||||
this.metricsClient = new OtlpCollector.MetricsService.MetricsServiceClient(this.channel);
|
||||
}
|
||||
}
|
||||
|
||||
internal OtlpResource.Resource ProcessResource { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ExportResult Export(in Batch<MetricItem> batch)
|
||||
{
|
||||
if (this.ProcessResource == null)
|
||||
{
|
||||
this.SetResource(this.ParentProvider.GetResource());
|
||||
}
|
||||
|
||||
// Prevents the exporter's gRPC and HTTP operations from being instrumented.
|
||||
using var scope = SuppressInstrumentationScope.Begin();
|
||||
|
||||
var request = new OtlpCollector.ExportMetricsServiceRequest();
|
||||
|
||||
request.AddBatch(this.ProcessResource, batch);
|
||||
var deadline = DateTime.UtcNow.AddMilliseconds(this.options.TimeoutMilliseconds);
|
||||
|
||||
try
|
||||
{
|
||||
this.metricsClient.Export(request, headers: this.headers, deadline: deadline);
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.FailedToReachCollector(ex);
|
||||
return ExportResult.Failure;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OpenTelemetryProtocolExporterEventSource.Log.ExportMethodException(ex);
|
||||
return ExportResult.Failure;
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.Return();
|
||||
}
|
||||
|
||||
return ExportResult.Success;
|
||||
}
|
||||
|
||||
internal void SetResource(Resource resource)
|
||||
{
|
||||
OtlpResource.Resource processResource = new OtlpResource.Resource();
|
||||
|
||||
foreach (KeyValuePair<string, object> attribute in resource.Attributes)
|
||||
{
|
||||
var oltpAttribute = attribute.ToOtlpAttribute();
|
||||
if (oltpAttribute != null)
|
||||
{
|
||||
processResource.Attributes.Add(oltpAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
if (!processResource.Attributes.Any(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName))
|
||||
{
|
||||
var serviceName = (string)this.ParentProvider.GetDefaultResource().Attributes.Where(
|
||||
kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName).FirstOrDefault().Value;
|
||||
processResource.Attributes.Add(new OtlpCommon.KeyValue
|
||||
{
|
||||
Key = ResourceSemanticConventions.AttributeServiceName,
|
||||
Value = new OtlpCommon.AnyValue { StringValue = serviceName },
|
||||
});
|
||||
}
|
||||
|
||||
this.ProcessResource = processResource;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnShutdown(int timeoutMilliseconds)
|
||||
{
|
||||
if (this.channel == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Task.WaitAny(new Task[] { this.channel.ShutdownAsync(), Task.Delay(timeoutMilliseconds) }) == 0;
|
||||
}
|
||||
|
||||
private static Metadata GetMetadataFromHeaders(string headers)
|
||||
{
|
||||
var metadata = new Metadata();
|
||||
if (!string.IsNullOrEmpty(headers))
|
||||
{
|
||||
Array.ForEach(
|
||||
headers.Split(','),
|
||||
(pair) =>
|
||||
{
|
||||
// Specify the maximum number of substrings to return to 2
|
||||
// This treats everything that follows the first `=` in the string as the value to be added for the metadata key
|
||||
var keyValueData = pair.Split(new char[] { '=' }, 2);
|
||||
if (keyValueData.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Headers provided in an invalid format.");
|
||||
}
|
||||
|
||||
var key = keyValueData[0].Trim();
|
||||
var value = keyValueData[1].Trim();
|
||||
metadata.Add(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// <copyright file="PrometheusExporterEventSource.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 System.Diagnostics.Tracing;
|
||||
using OpenTelemetry.Internal;
|
||||
|
||||
namespace OpenTelemetry.Exporter.Prometheus.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// EventSource events emitted from the project.
|
||||
/// </summary>
|
||||
[EventSource(Name = "OpenTelemetry-Exporter-Prometheus")]
|
||||
internal class PrometheusExporterEventSource : EventSource
|
||||
{
|
||||
public static PrometheusExporterEventSource Log = new PrometheusExporterEventSource();
|
||||
|
||||
[NonEvent]
|
||||
public void FailedExport(Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
|
||||
{
|
||||
this.FailedExport(ex.ToInvariantString());
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void FailedShutdown(Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
|
||||
{
|
||||
this.FailedShutdown(ex.ToInvariantString());
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void CanceledExport(Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
|
||||
{
|
||||
this.CanceledExport(ex.ToInvariantString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(1, Message = "Failed to export metrics: '{0}'", Level = EventLevel.Error)]
|
||||
public void FailedExport(string exception)
|
||||
{
|
||||
this.WriteEvent(1, exception);
|
||||
}
|
||||
|
||||
[Event(2, Message = "Canceled to export metrics: '{0}'", Level = EventLevel.Error)]
|
||||
public void CanceledExport(string exception)
|
||||
{
|
||||
this.WriteEvent(2, exception);
|
||||
}
|
||||
|
||||
[Event(3, Message = "Failed to shutdown Metrics server '{0}'", Level = EventLevel.Error)]
|
||||
public void FailedShutdown(string exception)
|
||||
{
|
||||
this.WriteEvent(3, exception);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
// <copyright file="PrometheusMetricBuilder.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 System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
#if NET452
|
||||
using OpenTelemetry.Internal;
|
||||
#endif
|
||||
|
||||
namespace OpenTelemetry.Exporter.Prometheus.Implementation
|
||||
{
|
||||
internal class PrometheusMetricBuilder
|
||||
{
|
||||
public const string ContentType = "text/plain; version = 0.0.4";
|
||||
|
||||
private static readonly char[] FirstCharacterNameCharset =
|
||||
{
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'_', ':',
|
||||
};
|
||||
|
||||
private static readonly char[] NameCharset =
|
||||
{
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'_', ':',
|
||||
};
|
||||
|
||||
private static readonly char[] FirstCharacterLabelCharset =
|
||||
{
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'_',
|
||||
};
|
||||
|
||||
private static readonly char[] LabelCharset =
|
||||
{
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'_',
|
||||
};
|
||||
|
||||
private readonly ICollection<PrometheusMetricValueBuilder> values = new List<PrometheusMetricValueBuilder>();
|
||||
|
||||
private string name;
|
||||
private string description;
|
||||
private string type;
|
||||
|
||||
public PrometheusMetricBuilder WithName(string name)
|
||||
{
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PrometheusMetricBuilder WithDescription(string description)
|
||||
{
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PrometheusMetricBuilder WithType(string type)
|
||||
{
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PrometheusMetricValueBuilder AddValue()
|
||||
{
|
||||
var val = new PrometheusMetricValueBuilder();
|
||||
|
||||
this.values.Add(val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void Write(StreamWriter writer)
|
||||
{
|
||||
// https://prometheus.io/docs/instrumenting/exposition_formats/
|
||||
|
||||
if (string.IsNullOrEmpty(this.name))
|
||||
{
|
||||
throw new InvalidOperationException("Metric name should not be empty");
|
||||
}
|
||||
|
||||
this.name = GetSafeMetricName(this.name);
|
||||
|
||||
if (!string.IsNullOrEmpty(this.description))
|
||||
{
|
||||
// Lines with a # as the first non-whitespace character are comments.
|
||||
// They are ignored unless the first token after # is either HELP or TYPE.
|
||||
// Those lines are treated as follows: If the token is HELP, at least one
|
||||
// more token is expected, which is the metric name. All remaining tokens
|
||||
// are considered the docstring for that metric name. HELP lines may contain
|
||||
// any sequence of UTF-8 characters (after the metric name), but the backslash
|
||||
// and the line feed characters have to be escaped as \\ and \n, respectively.
|
||||
// Only one HELP line may exist for any given metric name.
|
||||
|
||||
writer.Write("# HELP ");
|
||||
writer.Write(this.name);
|
||||
writer.Write(GetSafeMetricDescription(this.description));
|
||||
writer.Write("\n");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(this.type))
|
||||
{
|
||||
// If the token is TYPE, exactly two more tokens are expected. The first is the
|
||||
// metric name, and the second is either counter, gauge, histogram, summary, or
|
||||
// untyped, defining the type for the metric of that name. Only one TYPE line
|
||||
// may exist for a given metric name. The TYPE line for a metric name must appear
|
||||
// before the first sample is reported for that metric name. If there is no TYPE
|
||||
// line for a metric name, the type is set to untyped.
|
||||
|
||||
writer.Write("# TYPE ");
|
||||
writer.Write(this.name);
|
||||
writer.Write(" ");
|
||||
writer.Write(this.type);
|
||||
writer.Write("\n");
|
||||
}
|
||||
|
||||
// The remaining lines describe samples (one per line) using the following syntax (EBNF):
|
||||
// metric_name [
|
||||
// "{" label_name "=" `"` label_value `"` { "," label_name "=" `"` label_value `"` } [ "," ] "}"
|
||||
// ] value [ timestamp ]
|
||||
// In the sample syntax:
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
foreach (var m in this.values)
|
||||
{
|
||||
// metric_name and label_name carry the usual Prometheus expression language restrictions.
|
||||
writer.Write(m.Name != null ? GetSafeMetricName(m.Name) : this.name);
|
||||
|
||||
// label_value can be any sequence of UTF-8 characters, but the backslash
|
||||
// (\, double-quote ("}, and line feed (\n) characters have to be escaped
|
||||
// as \\, \", and \n, respectively.
|
||||
|
||||
if (m.Labels.Count > 0)
|
||||
{
|
||||
writer.Write(@"{");
|
||||
writer.Write(string.Join(",", m.Labels.Select(x => GetLabelAndValue(x.Item1, x.Item2))));
|
||||
writer.Write(@"}");
|
||||
}
|
||||
|
||||
// value is a float represented as required by Go's ParseFloat() function. In addition to
|
||||
// standard numerical values, Nan, +Inf, and -Inf are valid values representing not a number,
|
||||
// positive infinity, and negative infinity, respectively.
|
||||
writer.Write(" ");
|
||||
writer.Write(m.Value.ToString(CultureInfo.InvariantCulture));
|
||||
writer.Write(" ");
|
||||
|
||||
// The timestamp is an int64 (milliseconds since epoch, i.e. 1970-01-01 00:00:00 UTC, excluding
|
||||
// leap seconds), represented as required by Go's ParseInt() function.
|
||||
writer.Write(now);
|
||||
|
||||
// Prometheus' text-based format is line oriented. Lines are separated
|
||||
// by a line feed character (\n). The last line must end with a line
|
||||
// feed character. Empty lines are ignored.
|
||||
writer.Write("\n");
|
||||
}
|
||||
|
||||
static string GetLabelAndValue(string label, string value)
|
||||
{
|
||||
var safeKey = GetSafeLabelName(label);
|
||||
var safeValue = GetSafeLabelValue(value);
|
||||
return $"{safeKey}=\"{safeValue}\"";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSafeName(string name, char[] firstCharNameCharset, char[] charNameCharset)
|
||||
{
|
||||
// https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
||||
//
|
||||
// Metric names and labels
|
||||
// Every time series is uniquely identified by its metric name and a set of key-value pairs, also known as labels.
|
||||
// The metric name specifies the general feature of a system that is measured (e.g. http_requests_total - the total number of HTTP requests received). It may contain ASCII letters and digits, as well as underscores and colons. It must match the regex [a-zA-Z_:][a-zA-Z0-9_:]*.
|
||||
// Note: The colons are reserved for user defined recording rules. They should not be used by exporters or direct instrumentation.
|
||||
// Labels enable Prometheus's dimensional data model: any given combination of labels for the same metric name identifies a particular dimensional instantiation of that metric (for example: all HTTP requests that used the method POST to the /api/tracks handler). The query language allows filtering and aggregation based on these dimensions. Changing any label value, including adding or removing a label, will create a new time series.
|
||||
// Label names may contain ASCII letters, numbers, as well as underscores. They must match the regex [a-zA-Z_][a-zA-Z0-9_]*. Label names beginning with __ are reserved for internal use.
|
||||
// Label values may contain any Unicode characters.
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var firstChar = name[0];
|
||||
|
||||
sb.Append(firstCharNameCharset.Contains(firstChar)
|
||||
? firstChar
|
||||
: GetSafeChar(char.ToLowerInvariant(firstChar), firstCharNameCharset));
|
||||
|
||||
for (var i = 1; i < name.Length; ++i)
|
||||
{
|
||||
sb.Append(GetSafeChar(name[i], charNameCharset));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
static char GetSafeChar(char c, char[] charset) => charset.Contains(c) ? c : '_';
|
||||
}
|
||||
|
||||
private static string GetSafeMetricName(string name) => GetSafeName(name, FirstCharacterNameCharset, NameCharset);
|
||||
|
||||
private static string GetSafeLabelName(string name) => GetSafeName(name, FirstCharacterLabelCharset, LabelCharset);
|
||||
|
||||
private static string GetSafeLabelValue(string value)
|
||||
{
|
||||
// label_value can be any sequence of UTF-8 characters, but the backslash
|
||||
// (\), double-quote ("), and line feed (\n) characters have to be escaped
|
||||
// as \\, \", and \n, respectively.
|
||||
|
||||
var result = value.Replace("\\", "\\\\");
|
||||
result = result.Replace("\n", "\\n");
|
||||
result = result.Replace("\"", "\\\"");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetSafeMetricDescription(string description)
|
||||
{
|
||||
// HELP lines may contain any sequence of UTF-8 characters(after the metric name), but the backslash
|
||||
// and the line feed characters have to be escaped as \\ and \n, respectively.Only one HELP line may
|
||||
// exist for any given metric name.
|
||||
var result = description.Replace(@"\", @"\\");
|
||||
result = result.Replace("\n", @"\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal class PrometheusMetricValueBuilder
|
||||
{
|
||||
public readonly ICollection<Tuple<string, string>> Labels = new List<Tuple<string, string>>();
|
||||
public double Value;
|
||||
public string Name;
|
||||
|
||||
public PrometheusMetricValueBuilder WithLabel(string name, string value)
|
||||
{
|
||||
this.Labels.Add(new Tuple<string, string>(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PrometheusMetricValueBuilder WithValue(long metricValue)
|
||||
{
|
||||
this.Value = metricValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PrometheusMetricValueBuilder WithValue(double metricValue)
|
||||
{
|
||||
this.Value = metricValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PrometheusMetricValueBuilder WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// <copyright file="MeterProviderBuilderExtensions.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 OpenTelemetry.Exporter;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public static class MeterProviderBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Console exporter to the TracerProvider.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="MeterProviderBuilder"/> builder to use.</param>
|
||||
/// <param name="configure">Exporter configuration options.</param>
|
||||
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
|
||||
public static MeterProviderBuilder AddPrometheusExporter(this MeterProviderBuilder builder, Action<PrometheusExporterOptions> configure = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
var options = new PrometheusExporterOptions();
|
||||
configure?.Invoke(options);
|
||||
var exporter = new PrometheusExporter(options);
|
||||
var pullMetricProcessor = new PullMetricProcessor(exporter, false);
|
||||
exporter.MakePullRequest = pullMetricProcessor.PullRequest;
|
||||
|
||||
var metricsHttpServer = new PrometheusExporterMetricsHttpServer(exporter);
|
||||
metricsHttpServer.Start();
|
||||
return builder.AddMetricProcessor(pullMetricProcessor);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
|
||||
<Description>Console exporter for OpenTelemetry .NET</Description>
|
||||
<PackageTags>$(PackageTags);prometheus;metrics</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn),1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\ServiceProviderExtensions.cs" Link="Includes\ServiceProviderExtensions.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPkgVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,50 @@
|
|||
// <copyright file="PrometheusExporter.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 System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exporter of OpenTelemetry metrics to Prometheus.
|
||||
/// </summary>
|
||||
public class PrometheusExporter : BaseExporter<MetricItem>
|
||||
{
|
||||
internal readonly PrometheusExporterOptions Options;
|
||||
internal Batch<MetricItem> Batch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrometheusExporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Options for the exporter.</param>
|
||||
public PrometheusExporter(PrometheusExporterOptions options)
|
||||
{
|
||||
this.Options = options;
|
||||
}
|
||||
|
||||
internal Action MakePullRequest { get; set; }
|
||||
|
||||
public override ExportResult Export(in Batch<MetricItem> batch)
|
||||
{
|
||||
this.Batch = batch;
|
||||
return ExportResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
// <copyright file="PrometheusExporterExtensions.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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OpenTelemetry.Exporter.Prometheus.Implementation;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to write metrics collection from exporter in Prometheus format.
|
||||
/// </summary>
|
||||
public static class PrometheusExporterExtensions
|
||||
{
|
||||
private const string PrometheusCounterType = "counter";
|
||||
private const string PrometheusSummaryType = "summary";
|
||||
private const string PrometheusSummarySumPostFix = "_sum";
|
||||
private const string PrometheusSummaryCountPostFix = "_count";
|
||||
private const string PrometheusSummaryQuantileLabelName = "quantile";
|
||||
private const string PrometheusSummaryQuantileLabelValueForMin = "0";
|
||||
private const string PrometheusSummaryQuantileLabelValueForMax = "1";
|
||||
|
||||
/// <summary>
|
||||
/// Serialize to Prometheus Format.
|
||||
/// </summary>
|
||||
/// <param name="exporter">Prometheus Exporter.</param>
|
||||
/// <param name="writer">StreamWriter to write to.</param>
|
||||
public static void WriteMetricsCollection(this PrometheusExporter exporter, StreamWriter writer)
|
||||
{
|
||||
foreach (var metricItem in exporter.Batch)
|
||||
{
|
||||
foreach (var metric in metricItem.Metrics)
|
||||
{
|
||||
var builder = new PrometheusMetricBuilder()
|
||||
.WithName(metric.Name)
|
||||
.WithDescription(metric.Name);
|
||||
|
||||
if (metric is ISumMetric sumMetric)
|
||||
{
|
||||
if (sumMetric.Sum.Value is double doubleSum)
|
||||
{
|
||||
WriteSum(writer, builder, metric.Attributes, doubleSum);
|
||||
}
|
||||
else if (sumMetric.Sum.Value is long longSum)
|
||||
{
|
||||
WriteSum(writer, builder, metric.Attributes, longSum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Metrics Collection as a string.
|
||||
/// </summary>
|
||||
/// <param name="exporter"> Prometheus Exporter. </param>
|
||||
/// <returns>Metrics serialized to string in Prometheus format.</returns>
|
||||
public static string GetMetricsCollection(this PrometheusExporter exporter)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new StreamWriter(stream);
|
||||
WriteMetricsCollection(exporter, writer);
|
||||
writer.Flush();
|
||||
|
||||
return Encoding.UTF8.GetString(stream.ToArray(), 0, (int)stream.Length);
|
||||
}
|
||||
|
||||
private static void WriteSum(StreamWriter writer, PrometheusMetricBuilder builder, IEnumerable<KeyValuePair<string, object>> labels, double doubleValue)
|
||||
{
|
||||
builder = builder.WithType(PrometheusCounterType);
|
||||
|
||||
var metricValueBuilder = builder.AddValue();
|
||||
metricValueBuilder = metricValueBuilder.WithValue(doubleValue);
|
||||
|
||||
foreach (var label in labels)
|
||||
{
|
||||
metricValueBuilder.WithLabel(label.Key, label.Value.ToString());
|
||||
}
|
||||
|
||||
builder.Write(writer);
|
||||
}
|
||||
|
||||
private static void WriteSummary(
|
||||
StreamWriter writer,
|
||||
PrometheusMetricBuilder builder,
|
||||
IEnumerable<KeyValuePair<string, string>> labels,
|
||||
string metricName,
|
||||
double sum,
|
||||
long count,
|
||||
double min,
|
||||
double max)
|
||||
{
|
||||
builder = builder.WithType(PrometheusSummaryType);
|
||||
|
||||
foreach (var label in labels)
|
||||
{
|
||||
/* For Summary we emit one row for Sum, Count, Min, Max.
|
||||
Min,Max exports as quantile 0 and 1.
|
||||
In future, when OpenTelemetry implements more aggregation
|
||||
algorithms, this section will need to be revisited.
|
||||
Sample output:
|
||||
MyMeasure_sum{dim1="value1"} 750 1587013352982
|
||||
MyMeasure_count{dim1="value1"} 5 1587013352982
|
||||
MyMeasure{dim1="value2",quantile="0"} 150 1587013352982
|
||||
MyMeasure{dim1="value2",quantile="1"} 150 1587013352982
|
||||
*/
|
||||
builder.AddValue()
|
||||
.WithName(metricName + PrometheusSummarySumPostFix)
|
||||
.WithLabel(label.Key, label.Value)
|
||||
.WithValue(sum);
|
||||
builder.AddValue()
|
||||
.WithName(metricName + PrometheusSummaryCountPostFix)
|
||||
.WithLabel(label.Key, label.Value)
|
||||
.WithValue(count);
|
||||
builder.AddValue()
|
||||
.WithName(metricName)
|
||||
.WithLabel(label.Key, label.Value)
|
||||
.WithLabel(PrometheusSummaryQuantileLabelName, PrometheusSummaryQuantileLabelValueForMin)
|
||||
.WithValue(min);
|
||||
builder.AddValue()
|
||||
.WithName(metricName)
|
||||
.WithLabel(label.Key, label.Value)
|
||||
.WithLabel(PrometheusSummaryQuantileLabelName, PrometheusSummaryQuantileLabelValueForMax)
|
||||
.WithValue(max);
|
||||
}
|
||||
|
||||
builder.Write(writer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// <copyright file="PrometheusExporterMetricsHttpServer.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 System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry.Exporter.Prometheus.Implementation;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// A HTTP listener used to expose Prometheus metrics.
|
||||
/// </summary>
|
||||
public class PrometheusExporterMetricsHttpServer : IDisposable
|
||||
{
|
||||
private readonly PrometheusExporter exporter;
|
||||
private readonly HttpListener httpListener = new HttpListener();
|
||||
private readonly object syncObject = new object();
|
||||
|
||||
private CancellationTokenSource tokenSource;
|
||||
private Task workerThread;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrometheusExporterMetricsHttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="exporter">The <see cref="PrometheusExporter"/> instance.</param>
|
||||
public PrometheusExporterMetricsHttpServer(PrometheusExporter exporter)
|
||||
{
|
||||
this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
|
||||
this.httpListener.Prefixes.Add(exporter.Options.Url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start exporter.
|
||||
/// </summary>
|
||||
/// <param name="token">An optional <see cref="CancellationToken"/> that can be used to stop the htto server.</param>
|
||||
public void Start(CancellationToken token = default)
|
||||
{
|
||||
lock (this.syncObject)
|
||||
{
|
||||
if (this.tokenSource != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// link the passed in token if not null
|
||||
this.tokenSource = token == default ?
|
||||
new CancellationTokenSource() :
|
||||
CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||
|
||||
this.workerThread = Task.Factory.StartNew(this.WorkerThread, default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop exporter.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
lock (this.syncObject)
|
||||
{
|
||||
if (this.tokenSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.tokenSource.Cancel();
|
||||
this.workerThread.Wait();
|
||||
this.tokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by this class and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (this.httpListener != null && this.httpListener.IsListening)
|
||||
{
|
||||
this.Stop();
|
||||
this.httpListener.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void WorkerThread()
|
||||
{
|
||||
this.httpListener.Start();
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = SuppressInstrumentationScope.Begin();
|
||||
while (!this.tokenSource.IsCancellationRequested)
|
||||
{
|
||||
var ctxTask = this.httpListener.GetContextAsync();
|
||||
ctxTask.Wait(this.tokenSource.Token);
|
||||
|
||||
var ctx = ctxTask.Result;
|
||||
|
||||
ctx.Response.StatusCode = 200;
|
||||
ctx.Response.ContentType = PrometheusMetricBuilder.ContentType;
|
||||
|
||||
using var output = ctx.Response.OutputStream;
|
||||
using var writer = new StreamWriter(output);
|
||||
this.exporter.MakePullRequest();
|
||||
this.exporter.WriteMetricsCollection(writer);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
PrometheusExporterEventSource.Log.CanceledExport(ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PrometheusExporterEventSource.Log.FailedExport(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
this.httpListener.Stop();
|
||||
this.httpListener.Close();
|
||||
}
|
||||
catch (Exception exFromFinally)
|
||||
{
|
||||
PrometheusExporterEventSource.Log.FailedShutdown(exFromFinally);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
// <copyright file="PrometheusExporterMiddleware.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 System.Threading.Tasks;
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace OpenTelemetry.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// A middleware used to expose Prometheus metrics.
|
||||
/// </summary>
|
||||
public class PrometheusExporterMiddleware
|
||||
{
|
||||
private readonly PrometheusExporter exporter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrometheusExporterMiddleware"/> class.
|
||||
/// </summary>
|
||||
/// <param name="next">The next middleware in the pipeline.</param>
|
||||
/// <param name="exporter">The <see cref="PrometheusExporter"/> instance.</param>
|
||||
public PrometheusExporterMiddleware(RequestDelegate next, PrometheusExporter exporter)
|
||||
{
|
||||
this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke.
|
||||
/// </summary>
|
||||
/// <param name="httpContext"> context. </param>
|
||||
/// <returns>Task. </returns>
|
||||
public Task InvokeAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var result = this.exporter.GetMetricsCollection();
|
||||
|
||||
return httpContext.Response.WriteAsync(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,29 @@
|
|||
// <copyright file="PrometheusExporterOptions.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Options to run prometheus exporter.
|
||||
/// </summary>
|
||||
public class PrometheusExporterOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the port to listen to. Typically it ends with /metrics like http://localhost:9184/metrics/.
|
||||
/// </summary>
|
||||
public string Url { get; set; } = "http://localhost:9184/metrics/";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// <copyright file="PrometheusRouteBuilderExtensions.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>
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace OpenTelemetry.Exporter.Prometheus
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IApplicationBuilder"/> to add Prometheus Scraper Endpoint.
|
||||
/// </summary>
|
||||
public static class PrometheusRouteBuilderExtensions
|
||||
{
|
||||
private const string DefaultPath = "/metrics";
|
||||
|
||||
/// <summary>
|
||||
/// Use prometheus extension.
|
||||
/// </summary>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/> to add middleware to.</param>
|
||||
/// <returns>A reference to the <see cref="IApplicationBuilder"/> instance after the operation has completed.</returns>
|
||||
public static IApplicationBuilder UsePrometheus(this IApplicationBuilder app)
|
||||
{
|
||||
var options = app.ApplicationServices.GetService(typeof(PrometheusExporterOptions)) as PrometheusExporterOptions;
|
||||
var path = new PathString(options?.Url ?? DefaultPath);
|
||||
return app.Map(
|
||||
new PathString(path),
|
||||
builder => builder.UseMiddleware<PrometheusExporterMiddleware>());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,29 @@
|
|||
# Prometheus Exporter for OpenTelemetry .NET
|
||||
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus)
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus)
|
||||
|
||||
## Prerequisite
|
||||
|
||||
* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/)
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
dotnet add package OpenTelemetry.Exporter.Prometheus
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure the `PrometheusExporter` by following the directions below:
|
||||
|
||||
* `Url`: The url to listen to. Typically it ends with `/metrics` like `http://localhost:9184/metrics/`.
|
||||
|
||||
See
|
||||
[`TestPrometheusExporter.cs`](../../examples/Console/TestPrometheusExporter.cs)
|
||||
for example use.
|
||||
|
||||
## References
|
||||
|
||||
* [OpenTelemetry Project](https://opentelemetry.io/)
|
||||
* [Prometheus](https://prometheus.io)
|
|
@ -0,0 +1,53 @@
|
|||
// <copyright file="AspNetCoreMetrics.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 System.Diagnostics.Metrics;
|
||||
using System.Reflection;
|
||||
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Asp.Net Core Requests instrumentation.
|
||||
/// </summary>
|
||||
internal class AspNetCoreMetrics : IDisposable
|
||||
{
|
||||
internal static readonly AssemblyName AssemblyName = typeof(HttpInListener).Assembly.GetName();
|
||||
internal static readonly string InstrumentationName = AssemblyName.Name;
|
||||
internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString();
|
||||
|
||||
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
|
||||
private readonly Meter meter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AspNetCoreMetrics"/> class.
|
||||
/// </summary>
|
||||
public AspNetCoreMetrics()
|
||||
{
|
||||
this.meter = new Meter(InstrumentationName, InstrumentationVersion);
|
||||
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpInMetricsListener("Microsoft.AspNetCore", this.meter), null);
|
||||
this.diagnosticSourceSubscriber.Subscribe();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.diagnosticSourceSubscriber?.Dispose();
|
||||
this.meter?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// <copyright file="HttpInMetricsListener.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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
|
||||
{
|
||||
internal class HttpInMetricsListener : ListenerHandler
|
||||
{
|
||||
private readonly PropertyFetcher<HttpContext> stopContextFetcher = new PropertyFetcher<HttpContext>("HttpContext");
|
||||
private readonly Meter meter;
|
||||
|
||||
private Counter<long> httpServerRequestCount;
|
||||
|
||||
public HttpInMetricsListener(string name, Meter meter)
|
||||
: base(name)
|
||||
{
|
||||
this.meter = meter;
|
||||
|
||||
// TODO:
|
||||
// In the future, this instrumentation should produce the http.server.duration metric which will likely be represented as a histogram.
|
||||
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server
|
||||
//
|
||||
// Histograms are not yet supported by the SDK.
|
||||
//
|
||||
// For now we produce a count metric called http.server.request_count just for demonstration purposes.
|
||||
// This metric is not defined by the in the semantic conventions.
|
||||
this.httpServerRequestCount = meter.CreateCounter<long>("http.server.request_count", null, "The number of HTTP requests processed.");
|
||||
}
|
||||
|
||||
public override void OnStopActivity(Activity activity, object payload)
|
||||
{
|
||||
HttpContext context = this.stopContextFetcher.Fetch(payload);
|
||||
if (context == null)
|
||||
{
|
||||
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), nameof(this.OnStopActivity));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
|
||||
// Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
|
||||
// If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
|
||||
if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This is just a minimal set of attributes. See the spec for additional attributes:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server
|
||||
var tags = new KeyValuePair<string, object>[]
|
||||
{
|
||||
new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, context.Request.Method),
|
||||
new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, context.Request.Scheme),
|
||||
new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, context.Response.StatusCode),
|
||||
new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, context.Request.Protocol),
|
||||
};
|
||||
|
||||
this.httpServerRequestCount.Add(1, tags);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// <copyright file="MeterProviderBuilderExtensions.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 OpenTelemetry.Instrumentation.AspNetCore;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods to simplify registering of ASP.NET Core request instrumentation.
|
||||
/// </summary>
|
||||
public static class MeterProviderBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables the incoming requests automatic data collection for ASP.NET Core.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
|
||||
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
|
||||
public static MeterProviderBuilder AddAspNetCoreInstrumentation(
|
||||
this MeterProviderBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
// TODO: Implement an IDeferredMeterProviderBuilder
|
||||
|
||||
// TODO: Handle AspNetCoreInstrumentationOptions
|
||||
// Filter - makes sense for metric instrumentation
|
||||
// Enrich - do we want a similar kind of functionality for metrics?
|
||||
// RecordException - probably doesn't make sense for metric instrumentation
|
||||
// EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests
|
||||
|
||||
var instrumentation = new AspNetCoreMetrics();
|
||||
builder.AddSource(AspNetCoreMetrics.InstrumentationName);
|
||||
return builder.AddInstrumentation(() => instrumentation);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,3 +20,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)]
|
||||
[assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)]
|
||||
|
||||
// TODO: Much of the metrics SDK is currently internal. These should be removed once the public API surface area for metrics is defined.
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)]
|
||||
|
|
|
@ -7,6 +7,9 @@ branch](https://github.com/open-telemetry/opentelemetry-dotnet/tree/metrics),
|
|||
please check the latest changes
|
||||
[here](https://github.com/open-telemetry/opentelemetry-dotnet/blob/metrics/src/OpenTelemetry/CHANGELOG.md#experimental---metrics).
|
||||
|
||||
* Removed existing Metrics code as the spec is completely being re-written.
|
||||
([#2030](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2030))
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Removes .NET Framework 4.5.2, .NET 4.6 support. The minimum .NET Framework
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
// <copyright file="AggregatorStore.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class AggregatorStore
|
||||
{
|
||||
private static readonly string[] EmptySeqKey = new string[0];
|
||||
private static readonly object[] EmptySeqValue = new object[0];
|
||||
private readonly Instrument instrument;
|
||||
private readonly object lockKeyValue2MetricAggs = new object();
|
||||
|
||||
// Two-Level lookup. TagKeys x [ TagValues x Metrics ]
|
||||
private readonly Dictionary<string[], Dictionary<object[], IAggregator[]>> keyValue2MetricAggs =
|
||||
new Dictionary<string[], Dictionary<object[], IAggregator[]>>(new StringArrayEqualityComparer());
|
||||
|
||||
private IAggregator[] tag0Metrics = null;
|
||||
|
||||
internal AggregatorStore(Instrument instrument)
|
||||
{
|
||||
this.instrument = instrument;
|
||||
}
|
||||
|
||||
internal IAggregator[] MapToMetrics(string[] seqKey, object[] seqVal)
|
||||
{
|
||||
var aggregators = new List<IAggregator>();
|
||||
|
||||
var name = $"{this.instrument.Meter.Name}:{this.instrument.Name}";
|
||||
|
||||
var tags = new KeyValuePair<string, object>[seqKey.Length];
|
||||
for (int i = 0; i < seqKey.Length; i++)
|
||||
{
|
||||
tags[i] = new KeyValuePair<string, object>(seqKey[i], seqVal[i]);
|
||||
}
|
||||
|
||||
var dt = DateTimeOffset.UtcNow;
|
||||
|
||||
// TODO: Need to map each instrument to metrics (based on View API)
|
||||
if (this.instrument.GetType().Name.Contains("Counter"))
|
||||
{
|
||||
aggregators.Add(new SumMetricAggregator(name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags));
|
||||
}
|
||||
else if (this.instrument.GetType().Name.Contains("Gauge"))
|
||||
{
|
||||
aggregators.Add(new GaugeMetricAggregator(name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags));
|
||||
}
|
||||
else if (this.instrument.GetType().Name.Contains("Histogram"))
|
||||
{
|
||||
aggregators.Add(new HistogramMetricAggregator(name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags));
|
||||
}
|
||||
else
|
||||
{
|
||||
aggregators.Add(new SummaryMetricAggregator(name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags, false));
|
||||
}
|
||||
|
||||
return aggregators.ToArray();
|
||||
}
|
||||
|
||||
internal IAggregator[] FindMetricAggregators(ReadOnlySpan<KeyValuePair<string, object>> tags)
|
||||
{
|
||||
int len = tags.Length;
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
if (this.tag0Metrics == null)
|
||||
{
|
||||
this.tag0Metrics = this.MapToMetrics(AggregatorStore.EmptySeqKey, AggregatorStore.EmptySeqValue);
|
||||
}
|
||||
|
||||
return this.tag0Metrics;
|
||||
}
|
||||
|
||||
var storage = ThreadStaticStorage.GetStorage();
|
||||
|
||||
storage.SplitToKeysAndValues(tags, out var tagKey, out var tagValue);
|
||||
|
||||
if (len > 1)
|
||||
{
|
||||
Array.Sort<string, object>(tagKey, tagValue);
|
||||
}
|
||||
|
||||
IAggregator[] metrics;
|
||||
|
||||
lock (this.lockKeyValue2MetricAggs)
|
||||
{
|
||||
string[] seqKey = null;
|
||||
|
||||
// GetOrAdd by TagKey at 1st Level of 2-level dictionary structure.
|
||||
// Get back a Dictionary of [ Values x Metrics[] ].
|
||||
if (!this.keyValue2MetricAggs.TryGetValue(tagKey, out var value2metrics))
|
||||
{
|
||||
// Note: We are using storage from ThreadStatic, so need to make a deep copy for Dictionary storage.
|
||||
|
||||
seqKey = new string[len];
|
||||
tagKey.CopyTo(seqKey, 0);
|
||||
|
||||
value2metrics = new Dictionary<object[], IAggregator[]>(new ObjectArrayEqualityComparer());
|
||||
this.keyValue2MetricAggs.Add(seqKey, value2metrics);
|
||||
}
|
||||
|
||||
// GetOrAdd by TagValue at 2st Level of 2-level dictionary structure.
|
||||
// Get back Metrics[].
|
||||
if (!value2metrics.TryGetValue(tagValue, out metrics))
|
||||
{
|
||||
// Note: We are using storage from ThreadStatic, so need to make a deep copy for Dictionary storage.
|
||||
|
||||
if (seqKey == null)
|
||||
{
|
||||
seqKey = new string[len];
|
||||
tagKey.CopyTo(seqKey, 0);
|
||||
}
|
||||
|
||||
var seqVal = new object[len];
|
||||
tagValue.CopyTo(seqVal, 0);
|
||||
|
||||
metrics = this.MapToMetrics(seqKey, seqVal);
|
||||
|
||||
value2metrics.Add(seqVal, metrics);
|
||||
}
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
internal void Update<T>(T value, ReadOnlySpan<KeyValuePair<string, object>> tags)
|
||||
where T : struct
|
||||
{
|
||||
// TODO: We can isolate the cost of each user-added aggregator in
|
||||
// the hot path by queuing the DataPoint, and doing the Update as
|
||||
// part of the Collect() instead. Thus, we only pay for the price
|
||||
// of queueing a DataPoint in the Hot Path
|
||||
|
||||
var metricAggregators = this.FindMetricAggregators(tags);
|
||||
|
||||
foreach (var metricAggregator in metricAggregators)
|
||||
{
|
||||
metricAggregator.Update(value);
|
||||
}
|
||||
}
|
||||
|
||||
internal List<IMetric> Collect(bool isDelta)
|
||||
{
|
||||
var collectedMetrics = new List<IMetric>();
|
||||
|
||||
var dt = DateTimeOffset.UtcNow;
|
||||
|
||||
foreach (var keys in this.keyValue2MetricAggs)
|
||||
{
|
||||
foreach (var values in keys.Value)
|
||||
{
|
||||
foreach (var metric in values.Value)
|
||||
{
|
||||
var m = metric.Collect(dt, isDelta);
|
||||
if (m != null)
|
||||
{
|
||||
collectedMetrics.Add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collectedMetrics;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// <copyright file="DataPoint.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 System.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal readonly struct DataPoint : IDataPoint
|
||||
{
|
||||
private static readonly KeyValuePair<string, object>[] EmptyTag = new KeyValuePair<string, object>[0];
|
||||
|
||||
private readonly IDataValue value;
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, long value, KeyValuePair<string, object>[] tags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.Tags = tags;
|
||||
this.value = new DataValue<long>(value);
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, double value, KeyValuePair<string, object>[] tags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.Tags = tags;
|
||||
this.value = new DataValue<double>(value);
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, IDataValue value, KeyValuePair<string, object>[] tags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.Tags = tags;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, long value)
|
||||
: this(timestamp, value, DataPoint.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, double value)
|
||||
: this(timestamp, value, DataPoint.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, IDataValue value)
|
||||
: this(timestamp, value, DataPoint.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
public DateTimeOffset Timestamp { get; }
|
||||
|
||||
public readonly KeyValuePair<string, object>[] Tags { get; }
|
||||
|
||||
public object Value => this.value.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// <copyright file="DataPoint{T}.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 System.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal readonly struct DataPoint<T> : IDataPoint
|
||||
where T : struct
|
||||
{
|
||||
private static readonly KeyValuePair<string, object>[] EmptyTag = new KeyValuePair<string, object>[0];
|
||||
|
||||
private readonly IDataValue value;
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, T value, KeyValuePair<string, object>[] tags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.Tags = tags;
|
||||
this.value = new DataValue<T>(value);
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, IDataValue value, KeyValuePair<string, object>[] tags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.Tags = tags;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, T value)
|
||||
: this(timestamp, value, DataPoint<T>.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
internal DataPoint(DateTimeOffset timestamp, IDataValue value)
|
||||
: this(timestamp, value, DataPoint<T>.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
public DateTimeOffset Timestamp { get; }
|
||||
|
||||
public readonly KeyValuePair<string, object>[] Tags { get; }
|
||||
|
||||
public object Value => this.value.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// <copyright file="DataValue.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;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public readonly struct DataValue : IDataValue
|
||||
{
|
||||
private readonly IDataValue value;
|
||||
|
||||
internal DataValue(int value)
|
||||
{
|
||||
// Promote to long
|
||||
this.value = new DataValue<long>(value);
|
||||
}
|
||||
|
||||
internal DataValue(long value)
|
||||
{
|
||||
this.value = new DataValue<long>(value);
|
||||
}
|
||||
|
||||
internal DataValue(double value)
|
||||
{
|
||||
this.value = new DataValue<double>(value);
|
||||
}
|
||||
|
||||
internal DataValue(IDataValue value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public object Value => this.value.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// <copyright file="DataValue{T}.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.Metrics
|
||||
{
|
||||
internal readonly struct DataValue<T> : IDataValue
|
||||
where T : struct
|
||||
{
|
||||
private readonly T value;
|
||||
|
||||
internal DataValue(T value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public object Value => (object)this.value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// <copyright file="Exemplar.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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal readonly struct Exemplar : IExemplar
|
||||
{
|
||||
private static readonly KeyValuePair<string, object>[] EmptyTag = new KeyValuePair<string, object>[0];
|
||||
|
||||
private readonly IDataValue value;
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, long value, ActivityTraceId traceId, ActivitySpanId spanId, KeyValuePair<string, object>[] filteredTags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.FilteredTags = filteredTags;
|
||||
this.SpanId = spanId;
|
||||
this.TraceId = traceId;
|
||||
this.value = new DataValue<long>(value);
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, double value, ActivityTraceId traceId, ActivitySpanId spanId, KeyValuePair<string, object>[] filteredTags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.FilteredTags = filteredTags;
|
||||
this.SpanId = spanId;
|
||||
this.TraceId = traceId;
|
||||
this.value = new DataValue<double>(value);
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, IDataValue value, ActivityTraceId traceId, ActivitySpanId spanId, KeyValuePair<string, object>[] filteredTags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.FilteredTags = filteredTags;
|
||||
this.SpanId = spanId;
|
||||
this.TraceId = traceId;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, long value)
|
||||
: this(timestamp, value, default, default, Exemplar.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, double value)
|
||||
: this(timestamp, value, default, default, Exemplar.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, IDataValue value)
|
||||
: this(timestamp, value, default, default, Exemplar.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
public DateTimeOffset Timestamp { get; }
|
||||
|
||||
public readonly KeyValuePair<string, object>[] FilteredTags { get; }
|
||||
|
||||
public readonly ActivityTraceId TraceId { get; }
|
||||
|
||||
public readonly ActivitySpanId SpanId { get; }
|
||||
|
||||
public object Value => this.value.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// <copyright file="Exemplar{T}.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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal readonly struct Exemplar<T> : IExemplar
|
||||
where T : struct
|
||||
{
|
||||
private static readonly KeyValuePair<string, object>[] EmptyTag = new KeyValuePair<string, object>[0];
|
||||
|
||||
private readonly IDataValue value;
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, T value, ActivityTraceId traceId, ActivitySpanId spanId, KeyValuePair<string, object>[] filteredTags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.FilteredTags = filteredTags;
|
||||
this.SpanId = spanId;
|
||||
this.TraceId = traceId;
|
||||
this.value = new DataValue<T>(value);
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, IDataValue value, ActivityTraceId traceId, ActivitySpanId spanId, KeyValuePair<string, object>[] filteredTags)
|
||||
{
|
||||
this.Timestamp = timestamp;
|
||||
this.FilteredTags = filteredTags;
|
||||
this.SpanId = spanId;
|
||||
this.TraceId = traceId;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, T value)
|
||||
: this(timestamp, value, default, default, Exemplar<T>.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
internal Exemplar(DateTimeOffset timestamp, IDataValue value)
|
||||
: this(timestamp, value, default, default, Exemplar<T>.EmptyTag)
|
||||
{
|
||||
}
|
||||
|
||||
public DateTimeOffset Timestamp { get; }
|
||||
|
||||
public readonly KeyValuePair<string, object>[] FilteredTags { get; }
|
||||
|
||||
public readonly ActivityTraceId TraceId { get; }
|
||||
|
||||
public readonly ActivitySpanId SpanId { get; }
|
||||
|
||||
public object Value => this.value.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// <copyright file="IDataPoint.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 System.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface IDataPoint : IDataValue
|
||||
{
|
||||
DateTimeOffset Timestamp { get; }
|
||||
|
||||
KeyValuePair<string, object>[] Tags { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// <copyright file="IDataValue.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.Metrics
|
||||
{
|
||||
public interface IDataValue
|
||||
{
|
||||
object Value { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// <copyright file="IExemplar.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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface IExemplar : IDataValue
|
||||
{
|
||||
DateTimeOffset Timestamp { get; }
|
||||
|
||||
KeyValuePair<string, object>[] FilteredTags { get; }
|
||||
|
||||
ActivityTraceId TraceId { get; }
|
||||
|
||||
ActivitySpanId SpanId { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// <copyright file="InstrumentState.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 System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class InstrumentState
|
||||
{
|
||||
private readonly AggregatorStore store;
|
||||
|
||||
internal InstrumentState(MeterProviderSdk sdk, Instrument instrument)
|
||||
{
|
||||
this.store = new AggregatorStore(instrument);
|
||||
sdk.AggregatorStores.TryAdd(this.store, true);
|
||||
}
|
||||
|
||||
internal void Update<T>(T value, ReadOnlySpan<KeyValuePair<string, object>> tags)
|
||||
where T : struct
|
||||
{
|
||||
this.store.Update(value, tags);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// <copyright file="MeterProviderBuilderExtensions.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.Resources;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="MeterProviderBuilder"/> class.
|
||||
/// </summary>
|
||||
public static class MeterProviderBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Add measurement processor.
|
||||
/// </summary>
|
||||
/// <param name="meterProviderBuilder"><see cref="MeterProviderBuilder"/>.</param>
|
||||
/// <param name="processor">Measurement Processor.</param>
|
||||
/// <returns><see cref="MeterProvider"/>.</returns>
|
||||
public static MeterProviderBuilder AddMeasurementProcessor(this MeterProviderBuilder meterProviderBuilder, MeasurementProcessor processor)
|
||||
{
|
||||
if (meterProviderBuilder is MeterProviderBuilderSdk meterProviderBuilderSdk)
|
||||
{
|
||||
return meterProviderBuilderSdk.AddMeasurementProcessor(processor);
|
||||
}
|
||||
|
||||
return meterProviderBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add metric processor.
|
||||
/// </summary>
|
||||
/// <param name="meterProviderBuilder"><see cref="MeterProviderBuilder"/>.</param>
|
||||
/// <param name="processor">Measurement Processors.</param>
|
||||
/// <returns><see cref="MeterProvider"/>.</returns>
|
||||
public static MeterProviderBuilder AddMetricProcessor(this MeterProviderBuilder meterProviderBuilder, MetricProcessor processor)
|
||||
{
|
||||
if (meterProviderBuilder is MeterProviderBuilderSdk meterProviderBuilderSdk)
|
||||
{
|
||||
return meterProviderBuilderSdk.AddMetricProcessor(processor);
|
||||
}
|
||||
|
||||
return meterProviderBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="ResourceBuilder"/> from which the Resource associated with
|
||||
/// this provider is built from. Overwrites currently set ResourceBuilder.
|
||||
/// </summary>
|
||||
/// <param name="meterProviderBuilder">MeterProviderBuilder instance.</param>
|
||||
/// <param name="resourceBuilder"><see cref="ResourceBuilder"/> from which Resource will be built.</param>
|
||||
/// <returns>Returns <see cref="MeterProviderBuilder"/> for chaining.</returns>
|
||||
public static MeterProviderBuilder SetResourceBuilder(this MeterProviderBuilder meterProviderBuilder, ResourceBuilder resourceBuilder)
|
||||
{
|
||||
if (meterProviderBuilder is MeterProviderBuilderSdk meterProviderBuilderSdk)
|
||||
{
|
||||
meterProviderBuilderSdk.SetResourceBuilder(resourceBuilder);
|
||||
}
|
||||
|
||||
return meterProviderBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the given actions to initialize the <see cref="MeterProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="meterProviderBuilder"><see cref="MeterProviderBuilder"/>.</param>
|
||||
/// <returns><see cref="MeterProvider"/>.</returns>
|
||||
public static MeterProvider Build(this MeterProviderBuilder meterProviderBuilder)
|
||||
{
|
||||
if (meterProviderBuilder is MeterProviderBuilderSdk meterProviderBuilderSdk)
|
||||
{
|
||||
return meterProviderBuilderSdk.Build();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// <copyright file="MeterProviderBuilderSdk.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 System.Collections.Generic;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class MeterProviderBuilderSdk : MeterProviderBuilder
|
||||
{
|
||||
private readonly List<InstrumentationFactory> instrumentationFactories = new List<InstrumentationFactory>();
|
||||
private readonly List<string> meterSources = new List<string>();
|
||||
private ResourceBuilder resourceBuilder = ResourceBuilder.CreateDefault();
|
||||
|
||||
internal MeterProviderBuilderSdk()
|
||||
{
|
||||
}
|
||||
|
||||
internal List<MeasurementProcessor> MeasurementProcessors { get; } = new List<MeasurementProcessor>();
|
||||
|
||||
internal List<MetricProcessor> MetricProcessors { get; } = new List<MetricProcessor>();
|
||||
|
||||
public override MeterProviderBuilder AddInstrumentation<TInstrumentation>(Func<TInstrumentation> instrumentationFactory)
|
||||
{
|
||||
if (instrumentationFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instrumentationFactory));
|
||||
}
|
||||
|
||||
this.instrumentationFactories.Add(
|
||||
new InstrumentationFactory(
|
||||
typeof(TInstrumentation).Name,
|
||||
"semver:" + typeof(TInstrumentation).Assembly.GetName().Version,
|
||||
instrumentationFactory));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override MeterProviderBuilder AddSource(params string[] names)
|
||||
{
|
||||
if (names == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(names));
|
||||
}
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(names)} contains null or whitespace string.");
|
||||
}
|
||||
|
||||
this.meterSources.Add(name);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
internal MeterProviderBuilderSdk AddMeasurementProcessor(MeasurementProcessor processor)
|
||||
{
|
||||
this.MeasurementProcessors.Add(processor);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal MeterProviderBuilderSdk AddMetricProcessor(MetricProcessor processor)
|
||||
{
|
||||
this.MetricProcessors.Add(processor);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal MeterProviderBuilderSdk SetResourceBuilder(ResourceBuilder resourceBuilder)
|
||||
{
|
||||
this.resourceBuilder = resourceBuilder ?? throw new ArgumentNullException(nameof(resourceBuilder));
|
||||
return this;
|
||||
}
|
||||
|
||||
internal MeterProvider Build()
|
||||
{
|
||||
return new MeterProviderSdk(
|
||||
this.resourceBuilder.Build(),
|
||||
this.meterSources,
|
||||
this.instrumentationFactories,
|
||||
this.MeasurementProcessors.ToArray(),
|
||||
this.MetricProcessors.ToArray());
|
||||
}
|
||||
|
||||
// TODO: This is copied from TracerProviderBuilderSdk. Move to common location.
|
||||
internal readonly struct InstrumentationFactory
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Version;
|
||||
public readonly Func<object> Factory;
|
||||
|
||||
internal InstrumentationFactory(string name, string version, Func<object> factory)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Version = version;
|
||||
this.Factory = factory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
// <copyright file="MeterProviderSdk.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 System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public class MeterProviderSdk
|
||||
: MeterProvider
|
||||
{
|
||||
internal readonly ConcurrentDictionary<AggregatorStore, bool> AggregatorStores = new ConcurrentDictionary<AggregatorStore, bool>();
|
||||
|
||||
private readonly List<object> instrumentations = new List<object>();
|
||||
private readonly object collectLock = new object();
|
||||
private readonly MeterListener listener;
|
||||
private readonly List<MeasurementProcessor> measurementProcessors = new List<MeasurementProcessor>();
|
||||
private readonly List<MetricProcessor> metricProcessors = new List<MetricProcessor>();
|
||||
|
||||
internal MeterProviderSdk(
|
||||
Resource resource,
|
||||
IEnumerable<string> meterSources,
|
||||
List<MeterProviderBuilderSdk.InstrumentationFactory> instrumentationFactories,
|
||||
MeasurementProcessor[] measurementProcessors,
|
||||
MetricProcessor[] metricProcessors)
|
||||
{
|
||||
this.Resource = resource;
|
||||
|
||||
// TODO: Replace with single CompositeProcessor.
|
||||
this.measurementProcessors.AddRange(measurementProcessors);
|
||||
this.metricProcessors.AddRange(metricProcessors);
|
||||
|
||||
foreach (var processor in this.metricProcessors)
|
||||
{
|
||||
processor.SetGetMetricFunction(this.Collect);
|
||||
processor.SetParentProvider(this);
|
||||
}
|
||||
|
||||
if (instrumentationFactories.Any())
|
||||
{
|
||||
foreach (var instrumentationFactory in instrumentationFactories)
|
||||
{
|
||||
this.instrumentations.Add(instrumentationFactory.Factory());
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Listener
|
||||
var meterSourcesToSubscribe = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var name in meterSources)
|
||||
{
|
||||
meterSourcesToSubscribe[name] = true;
|
||||
}
|
||||
|
||||
this.listener = new MeterListener()
|
||||
{
|
||||
InstrumentPublished = (instrument, listener) =>
|
||||
{
|
||||
if (meterSourcesToSubscribe.ContainsKey(instrument.Meter.Name))
|
||||
{
|
||||
var instrumentState = new InstrumentState(this, instrument);
|
||||
listener.EnableMeasurementEvents(instrument, instrumentState);
|
||||
}
|
||||
},
|
||||
MeasurementsCompleted = (instrument, state) => this.MeasurementsCompleted(instrument, state),
|
||||
};
|
||||
|
||||
// Everything double
|
||||
this.listener.SetMeasurementEventCallback<double>((i, m, l, c) => this.MeasurementRecorded(i, m, l, c));
|
||||
this.listener.SetMeasurementEventCallback<float>((i, m, l, c) => this.MeasurementRecorded(i, (double)m, l, c));
|
||||
|
||||
// Everything long
|
||||
this.listener.SetMeasurementEventCallback<long>((i, m, l, c) => this.MeasurementRecorded(i, m, l, c));
|
||||
this.listener.SetMeasurementEventCallback<int>((i, m, l, c) => this.MeasurementRecorded(i, (long)m, l, c));
|
||||
this.listener.SetMeasurementEventCallback<short>((i, m, l, c) => this.MeasurementRecorded(i, (long)m, l, c));
|
||||
this.listener.SetMeasurementEventCallback<byte>((i, m, l, c) => this.MeasurementRecorded(i, (long)m, l, c));
|
||||
|
||||
this.listener.Start();
|
||||
}
|
||||
|
||||
internal Resource Resource { get; }
|
||||
|
||||
internal void MeasurementsCompleted(Instrument instrument, object state)
|
||||
{
|
||||
Console.WriteLine($"Instrument {instrument.Meter.Name}:{instrument.Name} completed.");
|
||||
}
|
||||
|
||||
internal void MeasurementRecorded<T>(Instrument instrument, T value, ReadOnlySpan<KeyValuePair<string, object>> tagsRos, object state)
|
||||
where T : struct
|
||||
{
|
||||
// Get Instrument State
|
||||
var instrumentState = state as InstrumentState;
|
||||
|
||||
if (instrument == null || instrumentState == null)
|
||||
{
|
||||
// TODO: log
|
||||
return;
|
||||
}
|
||||
|
||||
var measurementItem = new MeasurementItem(instrument, instrumentState);
|
||||
var tags = tagsRos;
|
||||
var val = value;
|
||||
|
||||
// Run measurement Processors
|
||||
foreach (var processor in this.measurementProcessors)
|
||||
{
|
||||
processor.OnEnd(measurementItem, ref val, ref tags);
|
||||
}
|
||||
|
||||
// TODO: Replace the following with a built-in MeasurementProcessor
|
||||
// that knows how to aggregate and produce Metrics.
|
||||
instrumentState.Update(val, tags);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (this.instrumentations != null)
|
||||
{
|
||||
foreach (var item in this.instrumentations)
|
||||
{
|
||||
(item as IDisposable)?.Dispose();
|
||||
}
|
||||
|
||||
this.instrumentations.Clear();
|
||||
}
|
||||
|
||||
foreach (var processor in this.metricProcessors)
|
||||
{
|
||||
processor.Dispose();
|
||||
}
|
||||
|
||||
foreach (var processor in this.measurementProcessors)
|
||||
{
|
||||
processor.Dispose();
|
||||
}
|
||||
|
||||
this.listener.Dispose();
|
||||
}
|
||||
|
||||
private MetricItem Collect(bool isDelta)
|
||||
{
|
||||
lock (this.collectLock)
|
||||
{
|
||||
// Record all observable instruments
|
||||
this.listener.RecordObservableInstruments();
|
||||
var metricItem = new MetricItem();
|
||||
|
||||
foreach (var kv in this.AggregatorStores)
|
||||
{
|
||||
var metrics = kv.Key.Collect(isDelta);
|
||||
metricItem.Metrics.AddRange(metrics);
|
||||
}
|
||||
|
||||
return metricItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// <copyright file="GaugeMetricAggregator.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class GaugeMetricAggregator : IGaugeMetric, IAggregator
|
||||
{
|
||||
private readonly object lockUpdate = new object();
|
||||
private IDataValue value;
|
||||
|
||||
internal GaugeMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Unit = unit;
|
||||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string Unit { get; private set; }
|
||||
|
||||
public Meter Meter { get; private set; }
|
||||
|
||||
public DateTimeOffset StartTimeExclusive { get; private set; }
|
||||
|
||||
public DateTimeOffset EndTimeInclusive { get; private set; }
|
||||
|
||||
public KeyValuePair<string, object>[] Attributes { get; private set; }
|
||||
|
||||
public IEnumerable<IExemplar> Exemplars { get; private set; } = new List<IExemplar>();
|
||||
|
||||
public IDataValue LastValue => this.value;
|
||||
|
||||
public void Update<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
this.value = new DataValue<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public IMetric Collect(DateTimeOffset dt, bool isDelta)
|
||||
{
|
||||
var cloneItem = new GaugeMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes);
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
cloneItem.Exemplars = this.Exemplars;
|
||||
cloneItem.EndTimeInclusive = dt;
|
||||
cloneItem.value = this.LastValue;
|
||||
}
|
||||
|
||||
return cloneItem;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return $"Last={this.LastValue.Value}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// <copyright file="HistogramBucket.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.Metrics
|
||||
{
|
||||
public struct HistogramBucket
|
||||
{
|
||||
internal double LowBoundary;
|
||||
internal double HighBoundary;
|
||||
internal long Count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// <copyright file="HistogramMetricAggregator.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class HistogramMetricAggregator : IHistogramMetric, IAggregator
|
||||
{
|
||||
private readonly object lockUpdate = new object();
|
||||
private List<HistogramBucket> buckets = new List<HistogramBucket>();
|
||||
|
||||
internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Unit = unit;
|
||||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string Unit { get; private set; }
|
||||
|
||||
public Meter Meter { get; private set; }
|
||||
|
||||
public DateTimeOffset StartTimeExclusive { get; private set; }
|
||||
|
||||
public DateTimeOffset EndTimeInclusive { get; private set; }
|
||||
|
||||
public KeyValuePair<string, object>[] Attributes { get; private set; }
|
||||
|
||||
public bool IsDeltaTemporality { get; private set; }
|
||||
|
||||
public IEnumerable<IExemplar> Exemplars { get; private set; } = new List<IExemplar>();
|
||||
|
||||
public long PopulationCount { get; private set; }
|
||||
|
||||
public double PopulationSum { get; private set; }
|
||||
|
||||
public IEnumerable<HistogramBucket> Buckets => this.buckets;
|
||||
|
||||
public void Update<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
// TODO: Implement Histogram!
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
this.PopulationCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public IMetric Collect(DateTimeOffset dt, bool isDelta)
|
||||
{
|
||||
if (this.PopulationCount == 0)
|
||||
{
|
||||
// TODO: Output stale markers
|
||||
return null;
|
||||
}
|
||||
|
||||
var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes);
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
cloneItem.Exemplars = this.Exemplars;
|
||||
cloneItem.EndTimeInclusive = dt;
|
||||
cloneItem.PopulationCount = this.PopulationCount;
|
||||
cloneItem.PopulationSum = this.PopulationSum;
|
||||
cloneItem.buckets = this.buckets;
|
||||
cloneItem.IsDeltaTemporality = isDelta;
|
||||
|
||||
if (isDelta)
|
||||
{
|
||||
this.StartTimeExclusive = dt;
|
||||
this.PopulationCount = 0;
|
||||
this.PopulationSum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return cloneItem;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return $"Count={this.PopulationCount},Sum={this.PopulationSum}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// <copyright file="IAggregator.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;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal interface IAggregator
|
||||
{
|
||||
void Update<T>(T value)
|
||||
where T : struct;
|
||||
|
||||
IMetric Collect(DateTimeOffset dt, bool isDelta);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// <copyright file="IGaugeMetric.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface IGaugeMetric : IMetric
|
||||
{
|
||||
IEnumerable<IExemplar> Exemplars { get; }
|
||||
|
||||
IDataValue LastValue { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// <copyright file="IHistogramMetric.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface IHistogramMetric : IMetric
|
||||
{
|
||||
bool IsDeltaTemporality { get; }
|
||||
|
||||
IEnumerable<IExemplar> Exemplars { get; }
|
||||
|
||||
long PopulationCount { get; }
|
||||
|
||||
double PopulationSum { get; }
|
||||
|
||||
IEnumerable<HistogramBucket> Buckets { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// <copyright file="IMetric.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface IMetric
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string Description { get; }
|
||||
|
||||
string Unit { get; }
|
||||
|
||||
Meter Meter { get; }
|
||||
|
||||
DateTimeOffset StartTimeExclusive { get; }
|
||||
|
||||
DateTimeOffset EndTimeInclusive { get; }
|
||||
|
||||
KeyValuePair<string, object>[] Attributes { get; }
|
||||
|
||||
string ToDisplayString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// <copyright file="ISumMetric.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface ISumMetric : IMetric
|
||||
{
|
||||
bool IsDeltaTemporality { get; }
|
||||
|
||||
bool IsMonotonic { get; }
|
||||
|
||||
IEnumerable<IExemplar> Exemplars { get; }
|
||||
|
||||
IDataValue Sum { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// <copyright file="ISummaryMetric.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public interface ISummaryMetric : IMetric
|
||||
{
|
||||
long PopulationCount { get; }
|
||||
|
||||
double PopulationSum { get; }
|
||||
|
||||
IEnumerable<ValueAtQuantile> Quantiles { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
// <copyright file="SumMetricAggregator.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class SumMetricAggregator : ISumMetric, IAggregator
|
||||
{
|
||||
private readonly object lockUpdate = new object();
|
||||
private Type valueType;
|
||||
private long sumLong = 0;
|
||||
private double sumDouble = 0;
|
||||
|
||||
internal SumMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Unit = unit;
|
||||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
this.IsMonotonic = true;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string Unit { get; private set; }
|
||||
|
||||
public Meter Meter { get; private set; }
|
||||
|
||||
public DateTimeOffset StartTimeExclusive { get; private set; }
|
||||
|
||||
public DateTimeOffset EndTimeInclusive { get; private set; }
|
||||
|
||||
public KeyValuePair<string, object>[] Attributes { get; private set; }
|
||||
|
||||
public bool IsDeltaTemporality { get; private set; }
|
||||
|
||||
public bool IsMonotonic { get; }
|
||||
|
||||
public IEnumerable<IExemplar> Exemplars { get; private set; } = new List<IExemplar>();
|
||||
|
||||
public IDataValue Sum
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.valueType == typeof(long))
|
||||
{
|
||||
return new DataValue(this.sumLong);
|
||||
}
|
||||
else if (this.valueType == typeof(double))
|
||||
{
|
||||
return new DataValue(this.sumDouble);
|
||||
}
|
||||
|
||||
throw new Exception("Unsupported Type");
|
||||
}
|
||||
}
|
||||
|
||||
public void Update<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
this.valueType = typeof(T);
|
||||
var val = (long)(object)value;
|
||||
if (val < 0)
|
||||
{
|
||||
// TODO: log?
|
||||
// Also, this validation can be done in earlier stage.
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sumLong += val;
|
||||
}
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
this.valueType = typeof(T);
|
||||
var val = (double)(object)value;
|
||||
if (val < 0)
|
||||
{
|
||||
// TODO: log?
|
||||
// Also, this validation can be done in earlier stage.
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sumDouble += val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unsupported Type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMetric Collect(DateTimeOffset dt, bool isDelta)
|
||||
{
|
||||
var cloneItem = new SumMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes);
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
cloneItem.Exemplars = this.Exemplars;
|
||||
cloneItem.EndTimeInclusive = dt;
|
||||
cloneItem.valueType = this.valueType;
|
||||
cloneItem.sumLong = this.sumLong;
|
||||
cloneItem.sumDouble = this.sumDouble;
|
||||
cloneItem.IsDeltaTemporality = isDelta;
|
||||
|
||||
if (isDelta)
|
||||
{
|
||||
this.StartTimeExclusive = dt;
|
||||
this.sumLong = 0;
|
||||
this.sumDouble = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return cloneItem;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return $"Sum={this.Sum.Value}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// <copyright file="SummaryMetricAggregator.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class SummaryMetricAggregator : ISummaryMetric, IAggregator
|
||||
{
|
||||
private readonly object lockUpdate = new object();
|
||||
|
||||
private List<ValueAtQuantile> quantiles = new List<ValueAtQuantile>();
|
||||
|
||||
internal SummaryMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes, bool isMonotonic)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Unit = unit;
|
||||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
this.IsMonotonic = isMonotonic;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string Unit { get; private set; }
|
||||
|
||||
public Meter Meter { get; private set; }
|
||||
|
||||
public DateTimeOffset StartTimeExclusive { get; private set; }
|
||||
|
||||
public DateTimeOffset EndTimeInclusive { get; private set; }
|
||||
|
||||
public KeyValuePair<string, object>[] Attributes { get; private set; }
|
||||
|
||||
public bool IsMonotonic { get; }
|
||||
|
||||
public long PopulationCount { get; private set; }
|
||||
|
||||
public double PopulationSum { get; private set; }
|
||||
|
||||
public IEnumerable<ValueAtQuantile> Quantiles => this.quantiles;
|
||||
|
||||
public void Update<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
// TODO: Implement Summary!
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
var val = (long)(object)value;
|
||||
if (val > 0 || !this.IsMonotonic)
|
||||
{
|
||||
this.PopulationSum += (double)val;
|
||||
this.PopulationCount++;
|
||||
}
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var val = (double)(object)value;
|
||||
if (val > 0 || !this.IsMonotonic)
|
||||
{
|
||||
this.PopulationSum += (double)val;
|
||||
this.PopulationCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMetric Collect(DateTimeOffset dt, bool isDelta)
|
||||
{
|
||||
if (this.PopulationCount == 0)
|
||||
{
|
||||
// TODO: Output stale markers
|
||||
return null;
|
||||
}
|
||||
|
||||
var cloneItem = new SummaryMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes, this.IsMonotonic);
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
cloneItem.EndTimeInclusive = dt;
|
||||
cloneItem.PopulationCount = this.PopulationCount;
|
||||
cloneItem.PopulationSum = this.PopulationSum;
|
||||
cloneItem.quantiles = this.quantiles;
|
||||
|
||||
this.StartTimeExclusive = dt;
|
||||
this.PopulationCount = 0;
|
||||
this.PopulationSum = 0;
|
||||
}
|
||||
|
||||
return cloneItem;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return $"Count={this.PopulationCount},Sum={this.PopulationSum}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// <copyright file="ValueAtQuantile.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.Metrics
|
||||
{
|
||||
public struct ValueAtQuantile
|
||||
{
|
||||
internal double Quantile;
|
||||
internal double Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// <copyright file="ObjectArrayEqualityComparer.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class ObjectArrayEqualityComparer : IEqualityComparer<object[]>
|
||||
{
|
||||
public bool Equals(object[] obj1, object[] obj2)
|
||||
{
|
||||
if (ReferenceEquals(obj1, obj2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(obj1, null) || ReferenceEquals(obj2, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var len1 = obj1.Length;
|
||||
|
||||
if (len1 != obj2.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len1; i++)
|
||||
{
|
||||
if (obj1[i] != obj2[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetHashCode(object[] objs)
|
||||
{
|
||||
int hash = 17;
|
||||
|
||||
unchecked
|
||||
{
|
||||
for (int i = 0; i < objs.Length; i++)
|
||||
{
|
||||
hash = (hash * 31) + objs[i].GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// <copyright file="MeasurementItem.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.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public readonly struct MeasurementItem
|
||||
{
|
||||
internal readonly Instrument Instrument;
|
||||
internal readonly InstrumentState State;
|
||||
|
||||
internal MeasurementItem(Instrument instrument, InstrumentState state)
|
||||
{
|
||||
this.Instrument = instrument;
|
||||
this.State = state;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// <copyright file="MeasurementProcessor.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public abstract class MeasurementProcessor : BaseProcessor<MeasurementItem>
|
||||
{
|
||||
internal abstract void OnEnd<T>(MeasurementItem measurementItem, ref T value, ref ReadOnlySpan<KeyValuePair<string, object>> tags)
|
||||
where T : struct;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// <copyright file="MetricItem.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public class MetricItem
|
||||
{
|
||||
public List<IMetric> Metrics = new List<IMetric>();
|
||||
|
||||
internal MetricItem()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// <copyright file="MetricProcessor.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;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public abstract class MetricProcessor : BaseProcessor<MetricItem>
|
||||
{
|
||||
protected readonly BaseExporter<MetricItem> exporter;
|
||||
|
||||
protected MetricProcessor(BaseExporter<MetricItem> exporter)
|
||||
{
|
||||
this.exporter = exporter ?? throw new ArgumentNullException(nameof(exporter));
|
||||
}
|
||||
|
||||
// GetMetric or GetMemoryState or GetAggregatedMetrics..
|
||||
// ...or some other names
|
||||
public abstract void SetGetMetricFunction(Func<bool, MetricItem> getMetrics);
|
||||
|
||||
internal override void SetParentProvider(BaseProvider parentProvider)
|
||||
{
|
||||
base.SetParentProvider(parentProvider);
|
||||
|
||||
this.exporter.ParentProvider = parentProvider;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// <copyright file="PullMetricProcessor.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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public class PullMetricProcessor : MetricProcessor, IDisposable
|
||||
{
|
||||
private Func<bool, MetricItem> getMetrics;
|
||||
private bool disposed;
|
||||
private bool isDelta;
|
||||
|
||||
public PullMetricProcessor(BaseExporter<MetricItem> exporter, bool isDelta)
|
||||
: base(exporter)
|
||||
{
|
||||
this.isDelta = isDelta;
|
||||
}
|
||||
|
||||
public override void SetGetMetricFunction(Func<bool, MetricItem> getMetrics)
|
||||
{
|
||||
this.getMetrics = getMetrics;
|
||||
}
|
||||
|
||||
public void PullRequest()
|
||||
{
|
||||
if (this.getMetrics != null)
|
||||
{
|
||||
var metricsToExport = this.getMetrics(this.isDelta);
|
||||
Batch<MetricItem> batch = new Batch<MetricItem>(metricsToExport);
|
||||
this.exporter.Export(batch);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing && !this.disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.exporter.Dispose();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// <copyright file="PushMetricProcessor.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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
public class PushMetricProcessor : MetricProcessor, IDisposable
|
||||
{
|
||||
private Task exportTask;
|
||||
private CancellationTokenSource token;
|
||||
private int exportIntervalMs;
|
||||
private Func<bool, MetricItem> getMetrics;
|
||||
private bool disposed;
|
||||
|
||||
public PushMetricProcessor(BaseExporter<MetricItem> exporter, int exportIntervalMs, bool isDelta)
|
||||
: base(exporter)
|
||||
{
|
||||
this.exportIntervalMs = exportIntervalMs;
|
||||
this.token = new CancellationTokenSource();
|
||||
this.exportTask = new Task(() =>
|
||||
{
|
||||
while (!this.token.IsCancellationRequested)
|
||||
{
|
||||
Task.Delay(this.exportIntervalMs).Wait();
|
||||
this.Export(isDelta);
|
||||
}
|
||||
});
|
||||
|
||||
this.exportTask.Start();
|
||||
}
|
||||
|
||||
public override void SetGetMetricFunction(Func<bool, MetricItem> getMetrics)
|
||||
{
|
||||
this.getMetrics = getMetrics;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing && !this.disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.token.Cancel();
|
||||
this.exporter.Dispose();
|
||||
this.exportTask.Wait();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Export(bool isDelta)
|
||||
{
|
||||
if (this.getMetrics != null)
|
||||
{
|
||||
var metricsToExport = this.getMetrics(isDelta);
|
||||
Batch<MetricItem> batch = new Batch<MetricItem>(metricsToExport);
|
||||
this.exporter.Export(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// <copyright file="TagEnrichmentProcessor.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Example of a MeasurmentProcessor that adds a new attribute to all measurements.
|
||||
/// </summary>
|
||||
public class TagEnrichmentProcessor : MeasurementProcessor
|
||||
{
|
||||
private KeyValuePair<string, object> extraAttrib;
|
||||
|
||||
public TagEnrichmentProcessor(string name, string value)
|
||||
{
|
||||
this.extraAttrib = new KeyValuePair<string, object>(name, value);
|
||||
}
|
||||
|
||||
internal override void OnEnd<T>(MeasurementItem measurementItem, ref T value, ref ReadOnlySpan<KeyValuePair<string, object>> tags)
|
||||
where T : struct
|
||||
{
|
||||
var list = new List<KeyValuePair<string, object>>(tags.ToArray());
|
||||
list.Add(this.extraAttrib);
|
||||
|
||||
tags = new ReadOnlySpan<KeyValuePair<string, object>>(list.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// <copyright file="StringArrayEqualityComparer.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 System.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class StringArrayEqualityComparer : IEqualityComparer<string[]>
|
||||
{
|
||||
public bool Equals(string[] strings1, string[] strings2)
|
||||
{
|
||||
if (ReferenceEquals(strings1, strings2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(strings1, null) || ReferenceEquals(strings2, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var len1 = strings1.Length;
|
||||
|
||||
if (len1 != strings2.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len1; i++)
|
||||
{
|
||||
if (!strings1[i].Equals(strings2[i], StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetHashCode(string[] strings)
|
||||
{
|
||||
int hash = 17;
|
||||
|
||||
unchecked
|
||||
{
|
||||
for (int i = 0; i < strings.Length; i++)
|
||||
{
|
||||
hash = (hash * 31) + strings[i].GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// <copyright file="ThreadStaticStorage.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 System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class ThreadStaticStorage
|
||||
{
|
||||
private const int MaxTagCacheSize = 3;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ThreadStaticStorage storage;
|
||||
|
||||
private readonly TagStorage[] tagStorage = new TagStorage[MaxTagCacheSize + 1];
|
||||
|
||||
private ThreadStaticStorage()
|
||||
{
|
||||
for (int i = 0; i <= MaxTagCacheSize; i++)
|
||||
{
|
||||
this.tagStorage[i] = new TagStorage(i);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ThreadStaticStorage GetStorage()
|
||||
{
|
||||
if (ThreadStaticStorage.storage == null)
|
||||
{
|
||||
ThreadStaticStorage.storage = new ThreadStaticStorage();
|
||||
}
|
||||
|
||||
return ThreadStaticStorage.storage;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void SplitToKeysAndValues(ReadOnlySpan<KeyValuePair<string, object>> tags, out string[] tagKeys, out object[] tagValues)
|
||||
{
|
||||
var len = tags.Length;
|
||||
|
||||
if (len <= MaxTagCacheSize)
|
||||
{
|
||||
tagKeys = this.tagStorage[len].TagKey;
|
||||
tagValues = this.tagStorage[len].TagValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
tagKeys = new string[len];
|
||||
tagValues = new object[len];
|
||||
}
|
||||
|
||||
for (var n = 0; n < len; n++)
|
||||
{
|
||||
tagKeys[n] = tags[n].Key;
|
||||
tagValues[n] = tags[n].Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TagStorage
|
||||
{
|
||||
// Used to copy ReadOnlySpan from API
|
||||
internal readonly KeyValuePair<string, object>[] Tags;
|
||||
|
||||
// Used to split into Key sequence, Value sequence, and KVPs for Aggregator Processor
|
||||
internal readonly string[] TagKey;
|
||||
internal readonly object[] TagValue;
|
||||
|
||||
internal TagStorage(int n)
|
||||
{
|
||||
this.Tags = new KeyValuePair<string, object>[n];
|
||||
|
||||
this.TagKey = new string[n];
|
||||
this.TagValue = new object[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
// </copyright>
|
||||
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
|
@ -40,6 +41,10 @@ namespace OpenTelemetry
|
|||
{
|
||||
return otelLoggerProvider.Resource;
|
||||
}
|
||||
else if (baseProvider is MeterProviderSdk meterProviderSdk)
|
||||
{
|
||||
return meterProviderSdk.Resource;
|
||||
}
|
||||
|
||||
return Resource.Empty;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
using System.Diagnostics;
|
||||
using OpenTelemetry.Context.Propagation;
|
||||
using OpenTelemetry.Internal;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry
|
||||
|
@ -54,7 +55,17 @@ namespace OpenTelemetry
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates TracerProviderBuilder which should be used to build TracerProvider.
|
||||
/// Creates MeterProviderBuilder which should be used to build MeterProvider.
|
||||
/// </summary>
|
||||
/// <returns>MeterProviderBuilder instance, which should be used to build MeterProvider.</returns>
|
||||
public static MeterProviderBuilder CreateMeterProviderBuilder()
|
||||
{
|
||||
return new MeterProviderBuilderSdk();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates TracerProviderBuilder which should be used to build
|
||||
/// TracerProvider.
|
||||
/// </summary>
|
||||
/// <returns>TracerProviderBuilder instance, which should be used to build TracerProvider.</returns>
|
||||
public static TracerProviderBuilder CreateTracerProviderBuilder()
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
// <copyright file="MetricsBenchmarks.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.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
/*
|
||||
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19043
|
||||
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
|
||||
.NET Core SDK=5.0.202
|
||||
[Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
|
||||
DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
|
||||
|
||||
|
||||
| Method | WithSDK | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|
||||
|-------------------------- |-------- |-----------:|-----------:|----------:|-------:|------:|------:|----------:|
|
||||
| CounterHotPath | False | 15.126 ns | 0.3228 ns | 0.3965 ns | - | - | - | - |
|
||||
| CounterWith1LabelsHotPath | False | 9.766 ns | 0.2268 ns | 0.3530 ns | - | - | - | - |
|
||||
| CounterWith3LabelsHotPath | False | 25.240 ns | 0.2876 ns | 0.2690 ns | - | - | - | - |
|
||||
| CounterWith5LabelsHotPath | False | 37.929 ns | 0.7512 ns | 0.5865 ns | 0.0249 | - | - | 104 B |
|
||||
| CounterHotPath | True | 44.790 ns | 0.9101 ns | 1.3621 ns | - | - | - | - |
|
||||
| CounterWith1LabelsHotPath | True | 115.023 ns | 2.1001 ns | 1.9644 ns | - | - | - | - |
|
||||
| CounterWith3LabelsHotPath | True | 436.527 ns | 6.5121 ns | 5.7728 ns | - | - | - | - |
|
||||
| CounterWith5LabelsHotPath | True | 586.498 ns | 11.4783 ns | 9.5849 ns | 0.0553 | - | - | 232 B |
|
||||
|
||||
|
||||
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19043
|
||||
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
|
||||
[Host] : .NET Framework 4.8 (4.8.4360.0), X64 RyuJIT
|
||||
DefaultJob : .NET Framework 4.8 (4.8.4360.0), X64 RyuJIT
|
||||
|
||||
|
||||
| Method | WithSDK | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|
||||
|-------------------------- |-------- |------------:|----------:|----------:|-------:|------:|------:|----------:|
|
||||
| CounterHotPath | False | 23.53 ns | 0.480 ns | 0.401 ns | - | - | - | - |
|
||||
| CounterWith1LabelsHotPath | False | 28.70 ns | 0.592 ns | 0.770 ns | - | - | - | - |
|
||||
| CounterWith3LabelsHotPath | False | 46.27 ns | 0.942 ns | 1.157 ns | - | - | - | - |
|
||||
| CounterWith5LabelsHotPath | False | 51.66 ns | 1.060 ns | 1.857 ns | 0.0249 | - | - | 104 B |
|
||||
| CounterHotPath | True | 70.44 ns | 1.029 ns | 0.912 ns | - | - | - | - |
|
||||
| CounterWith1LabelsHotPath | True | 151.92 ns | 3.067 ns | 3.651 ns | - | - | - | - |
|
||||
| CounterWith3LabelsHotPath | True | 876.20 ns | 15.920 ns | 14.892 ns | - | - | - | - |
|
||||
| CounterWith5LabelsHotPath | True | 1,973.64 ns | 38.393 ns | 45.705 ns | 0.0534 | - | - | 233 B |
|
||||
*/
|
||||
|
||||
namespace Benchmarks.Metrics
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class MetricsBenchmarks
|
||||
{
|
||||
private readonly KeyValuePair<string, object> tag1 = new KeyValuePair<string, object>("attrib1", "value1");
|
||||
private readonly KeyValuePair<string, object> tag2 = new KeyValuePair<string, object>("attrib2", "value2");
|
||||
private readonly KeyValuePair<string, object> tag3 = new KeyValuePair<string, object>("attrib3", "value3");
|
||||
private readonly KeyValuePair<string, object> tag4 = new KeyValuePair<string, object>("attrib4", "value4");
|
||||
private readonly KeyValuePair<string, object> tag5 = new KeyValuePair<string, object>("attrib5", "value5");
|
||||
|
||||
private Counter<int> counter;
|
||||
private MeterProvider provider;
|
||||
private Meter meter;
|
||||
|
||||
[Params(false, true)]
|
||||
public bool WithSDK { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
if (this.WithSDK)
|
||||
{
|
||||
this.provider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource("TestMeter") // All instruments from this meter are enabled.
|
||||
.Build();
|
||||
}
|
||||
|
||||
this.meter = new Meter("TestMeter");
|
||||
this.counter = this.meter.CreateCounter<int>("counter");
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
this.meter?.Dispose();
|
||||
this.provider?.Dispose();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CounterHotPath()
|
||||
{
|
||||
this.counter?.Add(100);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CounterWith1LabelsHotPath()
|
||||
{
|
||||
this.counter?.Add(100, this.tag1);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CounterWith3LabelsHotPath()
|
||||
{
|
||||
this.counter?.Add(100, this.tag1, this.tag2, this.tag3);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CounterWith5LabelsHotPath()
|
||||
{
|
||||
this.counter?.Add(100, this.tag1, this.tag2, this.tag3, this.tag4, this.tag5);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// <copyright file="MetricAPITest.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.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace OpenTelemetry.Metrics.Tests
|
||||
{
|
||||
public class MetricApiTest
|
||||
{
|
||||
[Fact]
|
||||
public void SimpleTest()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ namespace OpenTelemetry.Trace.Tests
|
|||
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "Get around GitHub failure")]
|
||||
public void TracerProviderSdkInvokesSamplingWithCorrectParameters()
|
||||
{
|
||||
var testSampler = new TestSampler();
|
||||
|
|
Loading…
Reference in New Issue