From 360ddb5632d388e76b00325c4ce5a6196e452a0b Mon Sep 17 00:00:00 2001 From: Yun-Ting Date: Thu, 28 Jul 2022 16:21:56 -0700 Subject: [PATCH 1/4] initial commit --- OpenTelemetry.sln | 12 - ...etry.Exporter.Prometheus.AspNetCore.csproj | 12 +- ...sExporterMeterProviderBuilderExtensions.cs | 2 +- .../PrometheusExporterMiddleware.cs | 1 - .../.publicApi/net462/PublicAPI.Unshipped.txt | 14 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 14 +- .../AssemblyInfo.cs | 25 ++ ...ry.Exporter.Prometheus.HttpListener.csproj | 10 - .../PrometheusCollectionManager.cs | 240 +++++++++++ .../PrometheusExporter.cs | 88 ++++ .../PrometheusExporterEventSource.cs | 82 ++++ ...pListenerMeterProviderBuilderExtensions.cs | 2 +- .../PrometheusExporterOptions.cs | 58 +++ .../PrometheusHttpListener.cs | 1 - .../PrometheusSerializer.cs | 305 ++++++++++++++ .../PrometheusSerializerExt.cs | 200 +++++++++ test/Benchmarks/Benchmarks.csproj | 2 +- .../PrometheusSerializerBenchmarks.cs | 2 +- .../PrometheusCollectionManagerTests.cs | 1 - ...orter.Prometheus.HttpListener.Tests.csproj | 1 + .../PrometheusSerializerTests.cs | 387 ++++++++++++++++++ 21 files changed, 1410 insertions(+), 49 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializer.cs create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializerExt.cs create mode 100644 test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index d345ee94283..a16555d39ce 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -239,14 +239,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prom EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.HttpListener", "src\OpenTelemetry.Exporter.Prometheus.HttpListener\OpenTelemetry.Exporter.Prometheus.HttpListener.csproj", "{6B0232B7-5F29-4FB5-B383-1AA02DFE1089}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.Shared", "src\OpenTelemetry.Exporter.Prometheus.Shared\OpenTelemetry.Exporter.Prometheus.Shared.csproj", "{4AD27517-BAFC-413B-A8F0-988C3CEDC662}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests", "test\OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests\OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj", "{FBD12B0B-6731-4DD4-9C13-86F34593E974}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.HttpListener.Tests", "test\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj", "{4EF4364F-6E64-43CE-BED1-E6FE01024899}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.Shared.Tests", "test\OpenTelemetry.Exporter.Prometheus.Shared.Tests\OpenTelemetry.Exporter.Prometheus.Shared.Tests.csproj", "{8E75AEE2-017B-474F-A96D-035DF76A1C9E}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -501,10 +497,6 @@ Global {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Release|Any CPU.Build.0 = Release|Any CPU - {4AD27517-BAFC-413B-A8F0-988C3CEDC662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4AD27517-BAFC-413B-A8F0-988C3CEDC662}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4AD27517-BAFC-413B-A8F0-988C3CEDC662}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4AD27517-BAFC-413B-A8F0-988C3CEDC662}.Release|Any CPU.Build.0 = Release|Any CPU {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Debug|Any CPU.Build.0 = Debug|Any CPU {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -513,10 +505,6 @@ Global {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Release|Any CPU.Build.0 = Release|Any CPU - {8E75AEE2-017B-474F-A96D-035DF76A1C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E75AEE2-017B-474F-A96D-035DF76A1C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E75AEE2-017B-474F-A96D-035DF76A1C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E75AEE2-017B-474F-A96D-035DF76A1C9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj index e7453e0bf52..671cecda430 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -19,12 +19,12 @@ - - - - - - + + + + + + diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs index fd6809d68ee..fd45978a9c5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs @@ -15,8 +15,8 @@ // using System; +using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Exporter.Prometheus.AspNetCore; -using OpenTelemetry.Exporter.Prometheus.Shared; using OpenTelemetry.Internal; namespace OpenTelemetry.Metrics diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index bebf084ce33..99bc08ba44b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -19,7 +19,6 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using OpenTelemetry.Exporter.Prometheus.Shared; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt index a8f01b6d787..b4cf532ea27 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,12 +1,12 @@ -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.set -> void OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.PrometheusExporterOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions -static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index a8f01b6d787..b4cf532ea27 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,12 +1,12 @@ -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.set -> void OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.PrometheusExporterOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions -static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs new file mode 100644 index 00000000000..e05d6714457 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs @@ -0,0 +1,25 @@ +// +// 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. +// + +using System.Runtime.CompilerServices; + +#if SIGNED +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] +[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] +#else +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests")] +[assembly: InternalsVisibleTo("Benchmarks")] +#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj index 7769147c0f6..ff00ef3db79 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj @@ -14,21 +14,11 @@ false - - $(DefineConstants);PROMETHEUS_HTTPLISTENER - - - - - - - - diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs new file mode 100644 index 00000000000..8f672645ba0 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs @@ -0,0 +1,240 @@ +// +// 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. +// + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; + +namespace OpenTelemetry.Exporter.Prometheus +{ + internal sealed class PrometheusCollectionManager + { + private readonly PrometheusExporter exporter; + private readonly int scrapeResponseCacheDurationInMilliseconds; + private readonly Func, ExportResult> onCollectRef; + private byte[] buffer = new byte[85000]; // encourage the object to live in LOH (large object heap) + private int globalLockState; + private ArraySegment previousDataView; + private DateTime? previousDataViewGeneratedAtUtc; + private int readerCount; + private bool collectionRunning; + private TaskCompletionSource collectionTcs; + + public PrometheusCollectionManager(PrometheusExporter exporter) + { + this.exporter = exporter; + this.scrapeResponseCacheDurationInMilliseconds = this.exporter.Options.ScrapeResponseCacheDurationMilliseconds; + this.onCollectRef = this.OnCollect; + } + +#if NETCOREAPP3_1_OR_GREATER + public ValueTask EnterCollect() +#else + public Task EnterCollect() +#endif + { + this.EnterGlobalLock(); + + // If we are within {ScrapeResponseCacheDurationMilliseconds} of the + // last successful collect, return the previous view. + if (this.previousDataViewGeneratedAtUtc.HasValue + && this.scrapeResponseCacheDurationInMilliseconds > 0 + && this.previousDataViewGeneratedAtUtc.Value.AddMilliseconds(this.scrapeResponseCacheDurationInMilliseconds) >= DateTime.UtcNow) + { + Interlocked.Increment(ref this.readerCount); + this.ExitGlobalLock(); +#if NETCOREAPP3_1_OR_GREATER + return new ValueTask(new CollectionResponse(this.previousDataView, this.previousDataViewGeneratedAtUtc.Value, fromCache: true)); +#else + return Task.FromResult(new CollectionResponse(this.previousDataView, this.previousDataViewGeneratedAtUtc.Value, fromCache: true)); +#endif + } + + // If a collection is already running, return a task to wait on the result. + if (this.collectionRunning) + { + if (this.collectionTcs == null) + { + this.collectionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + Interlocked.Increment(ref this.readerCount); + this.ExitGlobalLock(); +#if NETCOREAPP3_1_OR_GREATER + return new ValueTask(this.collectionTcs.Task); +#else + return this.collectionTcs.Task; +#endif + } + + this.WaitForReadersToComplete(); + + // Start a collection on the current thread. + this.collectionRunning = true; + this.previousDataViewGeneratedAtUtc = null; + Interlocked.Increment(ref this.readerCount); + this.ExitGlobalLock(); + + CollectionResponse response; + bool result = this.ExecuteCollect(); + if (result) + { + this.previousDataViewGeneratedAtUtc = DateTime.UtcNow; + response = new CollectionResponse(this.previousDataView, this.previousDataViewGeneratedAtUtc.Value, fromCache: false); + } + else + { + response = default; + } + + this.EnterGlobalLock(); + + this.collectionRunning = false; + + if (this.collectionTcs != null) + { + this.collectionTcs.SetResult(response); + this.collectionTcs = null; + } + + this.ExitGlobalLock(); + +#if NETCOREAPP3_1_OR_GREATER + return new ValueTask(response); +#else + return Task.FromResult(response); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExitCollect() + { + Interlocked.Decrement(ref this.readerCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnterGlobalLock() + { + SpinWait lockWait = default; + while (true) + { + if (Interlocked.CompareExchange(ref this.globalLockState, 1, this.globalLockState) != 0) + { + lockWait.SpinOnce(); + continue; + } + + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ExitGlobalLock() + { + this.globalLockState = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WaitForReadersToComplete() + { + SpinWait readWait = default; + while (true) + { + if (Interlocked.CompareExchange(ref this.readerCount, 0, this.readerCount) != 0) + { + readWait.SpinOnce(); + continue; + } + + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ExecuteCollect() + { + this.exporter.OnExport = this.onCollectRef; + bool result = this.exporter.Collect(Timeout.Infinite); + this.exporter.OnExport = null; + return result; + } + + private ExportResult OnCollect(Batch metrics) + { + int cursor = 0; + + try + { + foreach (var metric in metrics) + { + while (true) + { + try + { + cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric); + break; + } + catch (IndexOutOfRangeException) + { + int bufferSize = this.buffer.Length * 2; + + // there are two cases we might run into the following condition: + // 1. we have many metrics to be exported - in this case we probably want + // to put some upper limit and allow the user to configure it. + // 2. we got an IndexOutOfRangeException which was triggered by some other + // code instead of the buffer[cursor++] - in this case we should give up + // at certain point rather than allocating like crazy. + if (bufferSize > 100 * 1024 * 1024) + { + throw; + } + + var newBuffer = new byte[bufferSize]; + this.buffer.CopyTo(newBuffer, 0); + this.buffer = newBuffer; + } + } + } + + this.previousDataView = new ArraySegment(this.buffer, 0, Math.Max(cursor - 1, 0)); + return ExportResult.Success; + } + catch (Exception) + { + this.previousDataView = new ArraySegment(Array.Empty(), 0, 0); + return ExportResult.Failure; + } + } + + public readonly struct CollectionResponse + { + public CollectionResponse(ArraySegment view, DateTime generatedAtUtc, bool fromCache) + { + this.View = view; + this.GeneratedAtUtc = generatedAtUtc; + this.FromCache = fromCache; + } + + public ArraySegment View { get; } + + public DateTime GeneratedAtUtc { get; } + + public bool FromCache { get; } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs new file mode 100644 index 00000000000..5199b55caf9 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs @@ -0,0 +1,88 @@ +// +// 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. +// + +using System; +#if PROMETHEUS_ASPNETCORE +using OpenTelemetry.Exporter.Prometheus.AspNetCore; +#endif +using OpenTelemetry.Metrics; + +namespace OpenTelemetry.Exporter.Prometheus +{ + /// + /// Exporter of OpenTelemetry metrics to Prometheus. + /// + [ExportModes(ExportModes.Pull)] + internal sealed class PrometheusExporter : BaseExporter, IPullMetricExporter + { + internal const string HttpListenerStartFailureExceptionMessage = "PrometheusExporter http listener could not be started."; + internal readonly PrometheusExporterOptions Options; + private Func funcCollect; + private Func, ExportResult> funcExport; + private bool disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// Options for the exporter. + public PrometheusExporter(PrometheusExporterOptions options) + { + this.Options = options; + this.CollectionManager = new PrometheusCollectionManager(this); + } + + /// + /// Gets or sets the Collect delegate. + /// + public Func Collect + { + get => this.funcCollect; + set => this.funcCollect = value; + } + + internal Func, ExportResult> OnExport + { + get => this.funcExport; + set => this.funcExport = value; + } + + internal Action OnDispose { get; set; } + + internal PrometheusCollectionManager CollectionManager { get; } + + /// + public override ExportResult Export(in Batch metrics) + { + return this.OnExport(metrics); + } + + /// + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.OnDispose?.Invoke(); + } + + this.disposed = true; + } + + base.Dispose(disposing); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs new file mode 100644 index 00000000000..b678cb436e2 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs @@ -0,0 +1,82 @@ +// +// 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. +// + +using System; +using System.Diagnostics.Tracing; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Prometheus +{ + /// + /// EventSource events emitted from the project. + /// + [EventSource(Name = "OpenTelemetry-Exporter-Prometheus")] + internal sealed class PrometheusExporterEventSource : EventSource + { + public static PrometheusExporterEventSource Log = new(); + + [NonEvent] + public void FailedExport(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.FailedExport(ex.ToInvariantString()); + } + } + + [NonEvent] + public void FailedShutdown(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.FailedShutdown(ex.ToInvariantString()); + } + } + + [NonEvent] + public void CanceledExport(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + 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); + } + + [Event(4, Message = "No metrics are available for export.", Level = EventLevel.Warning)] + public void NoMetrics() + { + this.WriteEvent(4); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs index 9ab648b6be0..d8c2672b70f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs @@ -15,8 +15,8 @@ // using System; +using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Exporter.Prometheus.HttpListener; -using OpenTelemetry.Exporter.Prometheus.Shared; using OpenTelemetry.Internal; namespace OpenTelemetry.Metrics diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs new file mode 100644 index 00000000000..d35ebba526e --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs @@ -0,0 +1,58 @@ +// +// 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. +// + +using System; +using OpenTelemetry.Internal; + +#if PROMETHEUS_ASPNETCORE +namespace OpenTelemetry.Exporter.Prometheus.AspNetCore +#else +namespace OpenTelemetry.Exporter.Prometheus +#endif +{ + /// + /// Prometheus exporter options. + /// + public class PrometheusExporterOptions + { + internal const string DefaultScrapeEndpointPath = "/metrics"; + internal Func GetUtcNowDateTimeOffset = () => DateTimeOffset.UtcNow; + + private int scrapeResponseCacheDurationMilliseconds = 10 * 1000; + + /// + /// Gets or sets the path to use for the scraping endpoint. Default value: /metrics. + /// + public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; + + /// + /// Gets or sets the cache duration in milliseconds for scrape responses. Default value: 10,000 (10 seconds). + /// + /// + /// Note: Specify 0 to disable response caching. + /// + public int ScrapeResponseCacheDurationMilliseconds + { + get => this.scrapeResponseCacheDurationMilliseconds; + set + { + Guard.ThrowIfOutOfRange(value, min: 0); + + this.scrapeResponseCacheDurationMilliseconds = value; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 28700603b1b..f092fa89d7d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -18,7 +18,6 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using OpenTelemetry.Exporter.Prometheus.Shared; using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.Prometheus.HttpListener diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializer.cs new file mode 100644 index 00000000000..ac8bd042c62 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializer.cs @@ -0,0 +1,305 @@ +// +// 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. +// + +#if NETCOREAPP3_1_OR_GREATER +using System; +#endif +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Prometheus +{ + /// + /// Basic PrometheusSerializer which has no OpenTelemetry dependency. + /// + internal static partial class PrometheusSerializer + { +#pragma warning disable SA1310 // Field name should not contain an underscore + private const byte ASCII_QUOTATION_MARK = 0x22; // '"' + private const byte ASCII_FULL_STOP = 0x2E; // '.' + private const byte ASCII_HYPHEN_MINUS = 0x2D; // '-' + private const byte ASCII_REVERSE_SOLIDUS = 0x5C; // '\\' + private const byte ASCII_LINEFEED = 0x0A; // `\n` +#pragma warning restore SA1310 // Field name should not contain an underscore + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteDouble(byte[] buffer, int cursor, double value) + { + if (MathHelper.IsFinite(value)) + { +#if NETCOREAPP3_1_OR_GREATER + Span span = stackalloc char[128]; + + var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture); + Debug.Assert(result, $"{nameof(result)} should be true."); + + for (int i = 0; i < cchWritten; i++) + { + buffer[cursor++] = unchecked((byte)span[i]); + } +#else + cursor = WriteAsciiStringNoEscape(buffer, cursor, value.ToString(CultureInfo.InvariantCulture)); +#endif + } + else if (double.IsPositiveInfinity(value)) + { + cursor = WriteAsciiStringNoEscape(buffer, cursor, "+Inf"); + } + else if (double.IsNegativeInfinity(value)) + { + cursor = WriteAsciiStringNoEscape(buffer, cursor, "-Inf"); + } + else + { + Debug.Assert(double.IsNaN(value), $"{nameof(value)} should be NaN."); + cursor = WriteAsciiStringNoEscape(buffer, cursor, "Nan"); + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteLong(byte[] buffer, int cursor, long value) + { +#if NETCOREAPP3_1_OR_GREATER + Span span = stackalloc char[20]; + + var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture); + Debug.Assert(result, $"{nameof(result)} should be true."); + + for (int i = 0; i < cchWritten; i++) + { + buffer[cursor++] = unchecked((byte)span[i]); + } +#else + cursor = WriteAsciiStringNoEscape(buffer, cursor, value.ToString(CultureInfo.InvariantCulture)); +#endif + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteAsciiStringNoEscape(byte[] buffer, int cursor, string value) + { + for (int i = 0; i < value.Length; i++) + { + buffer[cursor++] = unchecked((byte)value[i]); + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteUnicodeNoEscape(byte[] buffer, int cursor, ushort ordinal) + { + if (ordinal <= 0x7F) + { + buffer[cursor++] = unchecked((byte)ordinal); + } + else if (ordinal <= 0x07FF) + { + buffer[cursor++] = unchecked((byte)(0b_1100_0000 | (ordinal >> 6))); + buffer[cursor++] = unchecked((byte)(0b_1000_0000 | (ordinal & 0b_0011_1111))); + } + else if (ordinal <= 0xFFFF) + { + buffer[cursor++] = unchecked((byte)(0b_1110_0000 | (ordinal >> 12))); + buffer[cursor++] = unchecked((byte)(0b_1000_0000 | ((ordinal >> 6) & 0b_0011_1111))); + buffer[cursor++] = unchecked((byte)(0b_1000_0000 | (ordinal & 0b_0011_1111))); + } + else + { + Debug.Assert(ordinal <= 0xFFFF, ".NET string should not go beyond Unicode BMP."); + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteUnicodeString(byte[] buffer, int cursor, string value) + { + for (int i = 0; i < value.Length; i++) + { + var ordinal = (ushort)value[i]; + switch (ordinal) + { + case ASCII_REVERSE_SOLIDUS: + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + break; + case ASCII_LINEFEED: + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + buffer[cursor++] = unchecked((byte)'n'); + break; + default: + cursor = WriteUnicodeNoEscape(buffer, cursor, ordinal); + break; + } + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteLabelKey(byte[] buffer, int cursor, string value) + { + Debug.Assert(!string.IsNullOrEmpty(value), $"{nameof(value)} should not be null or empty."); + + var ordinal = (ushort)value[0]; + + if (ordinal >= (ushort)'0' && ordinal <= (ushort)'9') + { + buffer[cursor++] = unchecked((byte)'_'); + } + + for (int i = 0; i < value.Length; i++) + { + ordinal = (ushort)value[i]; + + if ((ordinal >= (ushort)'A' && ordinal <= (ushort)'Z') || + (ordinal >= (ushort)'a' && ordinal <= (ushort)'z') || + (ordinal >= (ushort)'0' && ordinal <= (ushort)'9')) + { + buffer[cursor++] = unchecked((byte)ordinal); + } + else + { + buffer[cursor++] = unchecked((byte)'_'); + } + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteLabelValue(byte[] buffer, int cursor, string value) + { + Debug.Assert(value != null, $"{nameof(value)} should not be null."); + + for (int i = 0; i < value.Length; i++) + { + var ordinal = (ushort)value[i]; + switch (ordinal) + { + case ASCII_QUOTATION_MARK: + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + buffer[cursor++] = ASCII_QUOTATION_MARK; + break; + case ASCII_REVERSE_SOLIDUS: + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + break; + case ASCII_LINEFEED: + buffer[cursor++] = ASCII_REVERSE_SOLIDUS; + buffer[cursor++] = unchecked((byte)'n'); + break; + default: + cursor = WriteUnicodeNoEscape(buffer, cursor, ordinal); + break; + } + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteLabel(byte[] buffer, int cursor, string labelKey, object labelValue) + { + cursor = WriteLabelKey(buffer, cursor, labelKey); + buffer[cursor++] = unchecked((byte)'='); + buffer[cursor++] = unchecked((byte)'"'); + + // In Prometheus, a label with an empty label value is considered equivalent to a label that does not exist. + cursor = WriteLabelValue(buffer, cursor, labelValue?.ToString() ?? string.Empty); + buffer[cursor++] = unchecked((byte)'"'); + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteMetricName(byte[] buffer, int cursor, string metricName, string metricUnit = null) + { + Debug.Assert(!string.IsNullOrEmpty(metricName), $"{nameof(metricName)} should not be null or empty."); + + for (int i = 0; i < metricName.Length; i++) + { + var ordinal = (ushort)metricName[i]; + buffer[cursor++] = ordinal switch + { + ASCII_FULL_STOP or ASCII_HYPHEN_MINUS => unchecked((byte)'_'), + _ => unchecked((byte)ordinal), + }; + } + + if (!string.IsNullOrEmpty(metricUnit)) + { + buffer[cursor++] = unchecked((byte)'_'); + + for (int i = 0; i < metricUnit.Length; i++) + { + var ordinal = (ushort)metricUnit[i]; + + if ((ordinal >= (ushort)'A' && ordinal <= (ushort)'Z') || + (ordinal >= (ushort)'a' && ordinal <= (ushort)'z') || + (ordinal >= (ushort)'0' && ordinal <= (ushort)'9')) + { + buffer[cursor++] = unchecked((byte)ordinal); + } + else + { + buffer[cursor++] = unchecked((byte)'_'); + } + } + } + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteHelpText(byte[] buffer, int cursor, string metricName, string metricUnit = null, string metricDescription = null) + { + cursor = WriteAsciiStringNoEscape(buffer, cursor, "# HELP "); + cursor = WriteMetricName(buffer, cursor, metricName, metricUnit); + + if (!string.IsNullOrEmpty(metricDescription)) + { + buffer[cursor++] = unchecked((byte)' '); + cursor = WriteUnicodeString(buffer, cursor, metricDescription); + } + + buffer[cursor++] = ASCII_LINEFEED; + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteTypeInfo(byte[] buffer, int cursor, string metricName, string metricUnit, string metricType) + { + Debug.Assert(!string.IsNullOrEmpty(metricType), $"{nameof(metricType)} should not be null or empty."); + + cursor = WriteAsciiStringNoEscape(buffer, cursor, "# TYPE "); + cursor = WriteMetricName(buffer, cursor, metricName, metricUnit); + buffer[cursor++] = unchecked((byte)' '); + cursor = WriteAsciiStringNoEscape(buffer, cursor, metricType); + + buffer[cursor++] = ASCII_LINEFEED; + + return cursor; + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializerExt.cs new file mode 100644 index 00000000000..805a51a2488 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializerExt.cs @@ -0,0 +1,200 @@ +// +// 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. +// + +using OpenTelemetry.Metrics; + +namespace OpenTelemetry.Exporter.Prometheus +{ + /// + /// OpenTelemetry additions to the PrometheusSerializer. + /// + internal static partial class PrometheusSerializer + { + private static readonly string[] MetricTypes = new string[] + { + "untyped", "counter", "gauge", "summary", "histogram", "histogram", "histogram", "histogram", "untyped", + }; + + public static int WriteMetric(byte[] buffer, int cursor, Metric metric) + { + if (!string.IsNullOrWhiteSpace(metric.Description)) + { + cursor = WriteHelpText(buffer, cursor, metric.Name, metric.Unit, metric.Description); + } + + int metricType = (int)metric.MetricType >> 4; + cursor = WriteTypeInfo(buffer, cursor, metric.Name, metric.Unit, MetricTypes[metricType]); + + if (!metric.MetricType.IsHistogram()) + { + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) + { + var tags = metricPoint.Tags; + var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); + + // Counter and Gauge + cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); + + if (tags.Count > 0) + { + buffer[cursor++] = unchecked((byte)'{'); + + foreach (var tag in tags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + + buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. + } + + buffer[cursor++] = unchecked((byte)' '); + + // TODO: MetricType is same for all MetricPoints + // within a given Metric, so this check can avoided + // for each MetricPoint + if (((int)metric.MetricType & 0b_0000_1111) == 0x0a /* I8 */) + { + if (metric.MetricType.IsSum()) + { + cursor = WriteLong(buffer, cursor, metricPoint.GetSumLong()); + } + else + { + cursor = WriteLong(buffer, cursor, metricPoint.GetGaugeLastValueLong()); + } + } + else + { + if (metric.MetricType.IsSum()) + { + cursor = WriteDouble(buffer, cursor, metricPoint.GetSumDouble()); + } + else + { + cursor = WriteDouble(buffer, cursor, metricPoint.GetGaugeLastValueDouble()); + } + } + + buffer[cursor++] = unchecked((byte)' '); + + cursor = WriteLong(buffer, cursor, timestamp); + + buffer[cursor++] = ASCII_LINEFEED; + } + } + else + { + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) + { + var tags = metricPoint.Tags; + var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); + + long totalCount = 0; + foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) + { + totalCount += histogramMeasurement.BucketCount; + + cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); + cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); + + foreach (var tag in tags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + + cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\""); + + if (histogramMeasurement.ExplicitBound != double.PositiveInfinity) + { + cursor = WriteDouble(buffer, cursor, histogramMeasurement.ExplicitBound); + } + else + { + cursor = WriteAsciiStringNoEscape(buffer, cursor, "+Inf"); + } + + cursor = WriteAsciiStringNoEscape(buffer, cursor, "\"} "); + + cursor = WriteLong(buffer, cursor, totalCount); + buffer[cursor++] = unchecked((byte)' '); + + cursor = WriteLong(buffer, cursor, timestamp); + + buffer[cursor++] = ASCII_LINEFEED; + } + + // Histogram sum + cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); + cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); + + if (tags.Count > 0) + { + buffer[cursor++] = unchecked((byte)'{'); + + foreach (var tag in tags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + + buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. + } + + buffer[cursor++] = unchecked((byte)' '); + + cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum()); + buffer[cursor++] = unchecked((byte)' '); + + cursor = WriteLong(buffer, cursor, timestamp); + + buffer[cursor++] = ASCII_LINEFEED; + + // Histogram count + cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); + cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); + + if (tags.Count > 0) + { + buffer[cursor++] = unchecked((byte)'{'); + + foreach (var tag in tags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + + buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. + } + + buffer[cursor++] = unchecked((byte)' '); + + cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount()); + buffer[cursor++] = unchecked((byte)' '); + + cursor = WriteLong(buffer, cursor, timestamp); + + buffer[cursor++] = ASCII_LINEFEED; + } + } + + buffer[cursor++] = ASCII_LINEFEED; + + return cursor; + } + } +} diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index 3a3cc739d91..20ee4fb7456 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -29,6 +29,6 @@ - + diff --git a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs index efd31fa6f86..c08d069bb50 100644 --- a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs +++ b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs @@ -18,7 +18,7 @@ using System.Diagnostics.Metrics; using BenchmarkDotNet.Attributes; using OpenTelemetry; -using OpenTelemetry.Exporter.Prometheus.Shared; +using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Metrics; using OpenTelemetry.Tests; diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs index adeb3ac29c0..bc60596ad9b 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs @@ -21,7 +21,6 @@ #endif using System.Threading; using System.Threading.Tasks; -using OpenTelemetry.Exporter.Prometheus.Shared; using OpenTelemetry.Metrics; using OpenTelemetry.Tests; using Xunit; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj index 22b68c4e9ea..71d5582bcb6 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs new file mode 100644 index 00000000000..e6c0865cc3a --- /dev/null +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -0,0 +1,387 @@ +// +// 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. +// + +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Text; +using OpenTelemetry.Metrics; +using OpenTelemetry.Tests; +using Xunit; + +namespace OpenTelemetry.Exporter.Prometheus +{ + public sealed class PrometheusSerializerTests + { + [Fact] + public void GaugeZeroDimension() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + meter.CreateObservableGauge("test_gauge", () => 123); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_gauge gauge\n" + + "test_gauge 123 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void GaugeZeroDimensionWithDescription() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + meter.CreateObservableGauge("test_gauge", () => 123, description: "Hello, world!"); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# HELP test_gauge Hello, world!\n" + + "# TYPE test_gauge gauge\n" + + "test_gauge 123 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void GaugeZeroDimensionWithUnit() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + meter.CreateObservableGauge("test_gauge", () => 123, unit: "seconds"); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_gauge_seconds gauge\n" + + "test_gauge_seconds 123 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void GaugeOneDimension() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + meter.CreateObservableGauge( + "test_gauge", + () => new Measurement(123, new KeyValuePair("tagKey", "tagValue"))); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_gauge gauge\n" + + "test_gauge{tagKey='tagValue'} 123 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void GaugeDoubleSubnormal() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + meter.CreateObservableGauge("test_gauge", () => new List> + { + new(double.NegativeInfinity, new("x", "1"), new("y", "2")), + new(double.PositiveInfinity, new("x", "3"), new("y", "4")), + new(double.NaN, new("x", "5"), new("y", "6")), + }); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_gauge gauge\n" + + "test_gauge{x='1',y='2'} -Inf \\d+\n" + + "test_gauge{x='3',y='4'} \\+Inf \\d+\n" + + "test_gauge{x='5',y='6'} Nan \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void SumDoubleInfinites() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var counter = meter.CreateCounter("test_counter"); + counter.Add(1.0E308); + counter.Add(1.0E308); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_counter counter\n" + + "test_counter \\+Inf \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramZeroDimension() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18); + histogram.Record(100); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{le='0'} 0 \\d+\n" + + "test_histogram_bucket{le='5'} 0 \\d+\n" + + "test_histogram_bucket{le='10'} 0 \\d+\n" + + "test_histogram_bucket{le='25'} 1 \\d+\n" + + "test_histogram_bucket{le='50'} 1 \\d+\n" + + "test_histogram_bucket{le='75'} 1 \\d+\n" + + "test_histogram_bucket{le='100'} 2 \\d+\n" + + "test_histogram_bucket{le='250'} 2 \\d+\n" + + "test_histogram_bucket{le='500'} 2 \\d+\n" + + "test_histogram_bucket{le='1000'} 2 \\d+\n" + + "test_histogram_bucket{le='\\+Inf'} 2 \\d+\n" + + "test_histogram_sum 118 \\d+\n" + + "test_histogram_count 2 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramOneDimension() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{x='1',le='0'} 0 \\d+\n" + + "test_histogram_bucket{x='1',le='5'} 0 \\d+\n" + + "test_histogram_bucket{x='1',le='10'} 0 \\d+\n" + + "test_histogram_bucket{x='1',le='25'} 1 \\d+\n" + + "test_histogram_bucket{x='1',le='50'} 1 \\d+\n" + + "test_histogram_bucket{x='1',le='75'} 1 \\d+\n" + + "test_histogram_bucket{x='1',le='100'} 2 \\d+\n" + + "test_histogram_bucket{x='1',le='250'} 2 \\d+\n" + + "test_histogram_bucket{x='1',le='500'} 2 \\d+\n" + + "test_histogram_bucket{x='1',le='1000'} 2 \\d+\n" + + "test_histogram_bucket{x='1',le='\\+Inf'} 2 \\d+\n" + + "test_histogram_sum{x='1'} 118 \\d+\n" + + "test_histogram_count{x='1'} 2 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramTwoDimensions() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18, new("x", "1"), new("y", "2")); + histogram.Record(100, new("x", "1"), new("y", "2")); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{x='1',y='2',le='0'} 0 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='5'} 0 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='10'} 0 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='25'} 1 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='50'} 1 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='75'} 1 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='100'} 2 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='250'} 2 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='500'} 2 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='1000'} 2 \\d+\n" + + "test_histogram_bucket{x='1',y='2',le='\\+Inf'} 2 \\d+\n" + + "test_histogram_sum{x='1',y='2'} 118 \\d+\n" + + "test_histogram_count{x='1',y='2'} 2 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramInfinites() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18); + histogram.Record(double.PositiveInfinity); + histogram.Record(double.PositiveInfinity); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{le='0'} 0 \\d+\n" + + "test_histogram_bucket{le='5'} 0 \\d+\n" + + "test_histogram_bucket{le='10'} 0 \\d+\n" + + "test_histogram_bucket{le='25'} 1 \\d+\n" + + "test_histogram_bucket{le='50'} 1 \\d+\n" + + "test_histogram_bucket{le='75'} 1 \\d+\n" + + "test_histogram_bucket{le='100'} 1 \\d+\n" + + "test_histogram_bucket{le='250'} 1 \\d+\n" + + "test_histogram_bucket{le='500'} 1 \\d+\n" + + "test_histogram_bucket{le='1000'} 1 \\d+\n" + + "test_histogram_bucket{le='\\+Inf'} 3 \\d+\n" + + "test_histogram_sum \\+Inf \\d+\n" + + "test_histogram_count 3 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramNaN() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18); + histogram.Record(double.PositiveInfinity); + histogram.Record(double.NaN); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{le='0'} 0 \\d+\n" + + "test_histogram_bucket{le='5'} 0 \\d+\n" + + "test_histogram_bucket{le='10'} 0 \\d+\n" + + "test_histogram_bucket{le='25'} 1 \\d+\n" + + "test_histogram_bucket{le='50'} 1 \\d+\n" + + "test_histogram_bucket{le='75'} 1 \\d+\n" + + "test_histogram_bucket{le='100'} 1 \\d+\n" + + "test_histogram_bucket{le='250'} 1 \\d+\n" + + "test_histogram_bucket{le='500'} 1 \\d+\n" + + "test_histogram_bucket{le='1000'} 1 \\d+\n" + + "test_histogram_bucket{le='\\+Inf'} 3 \\d+\n" + + "test_histogram_sum Nan \\d+\n" + + "test_histogram_count 3 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + } +} From 85a957cec022c43140079131ae79e73336c7d733 Mon Sep 17 00:00:00 2001 From: Yun-Ting Date: Thu, 28 Jul 2022 16:27:56 -0700 Subject: [PATCH 2/4] minor --- .../OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj index 71d5582bcb6..4588d2168b5 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj @@ -20,7 +20,7 @@ - + From 50088cdc13d24f903de1af4e9e29f30014530186 Mon Sep 17 00:00:00 2001 From: Yun-Ting Date: Fri, 29 Jul 2022 13:59:08 -0700 Subject: [PATCH 3/4] removed the shared project --- .../.publicApi/net462/PublicAPI.Shipped.txt | 0 .../.publicApi/net462/PublicAPI.Unshipped.txt | 6 - .../netcoreapp3.1/PublicAPI.Shipped.txt | 0 .../netcoreapp3.1/PublicAPI.Unshipped.txt | 6 - .../AssemblyInfo.cs | 25 -- ...elemetry.Exporter.Prometheus.Shared.csproj | 29 -- .../PrometheusCollectionManager.cs | 240 -------------- .../PrometheusExporter.cs | 90 ------ .../PrometheusExporterEventSource.cs | 82 ----- .../PrometheusExporterOptions.cs | 60 ---- .../PrometheusSerializer.cs | 305 ------------------ .../PrometheusSerializerExt.cs | 200 ------------ 12 files changed, 1043 deletions(-) delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Shipped.txt delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Unshipped.txt delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Shipped.txt delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Unshipped.txt delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/AssemblyInfo.cs delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/OpenTelemetry.Exporter.Prometheus.Shared.csproj delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusCollectionManager.cs delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporter.cs delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterEventSource.cs delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterOptions.cs delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializer.cs delete mode 100644 src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializerExt.cs diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Shipped.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Unshipped.txt deleted file mode 100644 index d61e4fb9ed9..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/net462/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,6 +0,0 @@ -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Shipped.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Unshipped.txt deleted file mode 100644 index 539467ab62d..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/.publicApi/netcoreapp3.1/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,6 +0,0 @@ -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.Prometheus.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/AssemblyInfo.cs deleted file mode 100644 index d4361cf0913..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/AssemblyInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -// 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. -// - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Shared.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("Benchmarks")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Shared.Tests")] -#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/OpenTelemetry.Exporter.Prometheus.Shared.csproj b/src/OpenTelemetry.Exporter.Prometheus.Shared/OpenTelemetry.Exporter.Prometheus.Shared.csproj deleted file mode 100644 index 169e97169c4..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/OpenTelemetry.Exporter.Prometheus.Shared.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - - netcoreapp3.1;net462 - Prometheus exporter shared code for both Prometheus exporter HttpListener and Prometheus exporter AspNetCore - $(PackageTags);prometheus;metrics - - - - - false - - - - - - - - - - - - - - - - diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusCollectionManager.cs deleted file mode 100644 index 87f3d6bff02..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusCollectionManager.cs +++ /dev/null @@ -1,240 +0,0 @@ -// -// 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. -// - -using System; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics; - -namespace OpenTelemetry.Exporter.Prometheus.Shared -{ - internal sealed class PrometheusCollectionManager - { - private readonly PrometheusExporter exporter; - private readonly int scrapeResponseCacheDurationInMilliseconds; - private readonly Func, ExportResult> onCollectRef; - private byte[] buffer = new byte[85000]; // encourage the object to live in LOH (large object heap) - private int globalLockState; - private ArraySegment previousDataView; - private DateTime? previousDataViewGeneratedAtUtc; - private int readerCount; - private bool collectionRunning; - private TaskCompletionSource collectionTcs; - - public PrometheusCollectionManager(PrometheusExporter exporter) - { - this.exporter = exporter; - this.scrapeResponseCacheDurationInMilliseconds = this.exporter.Options.ScrapeResponseCacheDurationMilliseconds; - this.onCollectRef = this.OnCollect; - } - -#if NETCOREAPP3_1_OR_GREATER - public ValueTask EnterCollect() -#else - public Task EnterCollect() -#endif - { - this.EnterGlobalLock(); - - // If we are within {ScrapeResponseCacheDurationMilliseconds} of the - // last successful collect, return the previous view. - if (this.previousDataViewGeneratedAtUtc.HasValue - && this.scrapeResponseCacheDurationInMilliseconds > 0 - && this.previousDataViewGeneratedAtUtc.Value.AddMilliseconds(this.scrapeResponseCacheDurationInMilliseconds) >= DateTime.UtcNow) - { - Interlocked.Increment(ref this.readerCount); - this.ExitGlobalLock(); -#if NETCOREAPP3_1_OR_GREATER - return new ValueTask(new CollectionResponse(this.previousDataView, this.previousDataViewGeneratedAtUtc.Value, fromCache: true)); -#else - return Task.FromResult(new CollectionResponse(this.previousDataView, this.previousDataViewGeneratedAtUtc.Value, fromCache: true)); -#endif - } - - // If a collection is already running, return a task to wait on the result. - if (this.collectionRunning) - { - if (this.collectionTcs == null) - { - this.collectionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - Interlocked.Increment(ref this.readerCount); - this.ExitGlobalLock(); -#if NETCOREAPP3_1_OR_GREATER - return new ValueTask(this.collectionTcs.Task); -#else - return this.collectionTcs.Task; -#endif - } - - this.WaitForReadersToComplete(); - - // Start a collection on the current thread. - this.collectionRunning = true; - this.previousDataViewGeneratedAtUtc = null; - Interlocked.Increment(ref this.readerCount); - this.ExitGlobalLock(); - - CollectionResponse response; - bool result = this.ExecuteCollect(); - if (result) - { - this.previousDataViewGeneratedAtUtc = DateTime.UtcNow; - response = new CollectionResponse(this.previousDataView, this.previousDataViewGeneratedAtUtc.Value, fromCache: false); - } - else - { - response = default; - } - - this.EnterGlobalLock(); - - this.collectionRunning = false; - - if (this.collectionTcs != null) - { - this.collectionTcs.SetResult(response); - this.collectionTcs = null; - } - - this.ExitGlobalLock(); - -#if NETCOREAPP3_1_OR_GREATER - return new ValueTask(response); -#else - return Task.FromResult(response); -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ExitCollect() - { - Interlocked.Decrement(ref this.readerCount); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnterGlobalLock() - { - SpinWait lockWait = default; - while (true) - { - if (Interlocked.CompareExchange(ref this.globalLockState, 1, this.globalLockState) != 0) - { - lockWait.SpinOnce(); - continue; - } - - break; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ExitGlobalLock() - { - this.globalLockState = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WaitForReadersToComplete() - { - SpinWait readWait = default; - while (true) - { - if (Interlocked.CompareExchange(ref this.readerCount, 0, this.readerCount) != 0) - { - readWait.SpinOnce(); - continue; - } - - break; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ExecuteCollect() - { - this.exporter.OnExport = this.onCollectRef; - bool result = this.exporter.Collect(Timeout.Infinite); - this.exporter.OnExport = null; - return result; - } - - private ExportResult OnCollect(Batch metrics) - { - int cursor = 0; - - try - { - foreach (var metric in metrics) - { - while (true) - { - try - { - cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric); - break; - } - catch (IndexOutOfRangeException) - { - int bufferSize = this.buffer.Length * 2; - - // there are two cases we might run into the following condition: - // 1. we have many metrics to be exported - in this case we probably want - // to put some upper limit and allow the user to configure it. - // 2. we got an IndexOutOfRangeException which was triggered by some other - // code instead of the buffer[cursor++] - in this case we should give up - // at certain point rather than allocating like crazy. - if (bufferSize > 100 * 1024 * 1024) - { - throw; - } - - var newBuffer = new byte[bufferSize]; - this.buffer.CopyTo(newBuffer, 0); - this.buffer = newBuffer; - } - } - } - - this.previousDataView = new ArraySegment(this.buffer, 0, Math.Max(cursor - 1, 0)); - return ExportResult.Success; - } - catch (Exception) - { - this.previousDataView = new ArraySegment(Array.Empty(), 0, 0); - return ExportResult.Failure; - } - } - - public readonly struct CollectionResponse - { - public CollectionResponse(ArraySegment view, DateTime generatedAtUtc, bool fromCache) - { - this.View = view; - this.GeneratedAtUtc = generatedAtUtc; - this.FromCache = fromCache; - } - - public ArraySegment View { get; } - - public DateTime GeneratedAtUtc { get; } - - public bool FromCache { get; } - } - } -} diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporter.cs deleted file mode 100644 index 25cd97c7a4b..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporter.cs +++ /dev/null @@ -1,90 +0,0 @@ -// -// 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. -// - -using System; -#if PROMETHEUS_ASPNETCORE -using OpenTelemetry.Exporter.Prometheus.AspNetCore; -#elif PROMETHEUS_HTTPLISTENER -using OpenTelemetry.Exporter.Prometheus.HttpListener; -#endif -using OpenTelemetry.Metrics; - -namespace OpenTelemetry.Exporter.Prometheus.Shared -{ - /// - /// Exporter of OpenTelemetry metrics to Prometheus. - /// - [ExportModes(ExportModes.Pull)] - internal sealed class PrometheusExporter : BaseExporter, IPullMetricExporter - { - internal const string HttpListenerStartFailureExceptionMessage = "PrometheusExporter http listener could not be started."; - internal readonly PrometheusExporterOptions Options; - private Func funcCollect; - private Func, ExportResult> funcExport; - private bool disposed = false; - - /// - /// Initializes a new instance of the class. - /// - /// Options for the exporter. - public PrometheusExporter(PrometheusExporterOptions options) - { - this.Options = options; - this.CollectionManager = new PrometheusCollectionManager(this); - } - - /// - /// Gets or sets the Collect delegate. - /// - public Func Collect - { - get => this.funcCollect; - set => this.funcCollect = value; - } - - internal Func, ExportResult> OnExport - { - get => this.funcExport; - set => this.funcExport = value; - } - - internal Action OnDispose { get; set; } - - internal PrometheusCollectionManager CollectionManager { get; } - - /// - public override ExportResult Export(in Batch metrics) - { - return this.OnExport(metrics); - } - - /// - protected override void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.OnDispose?.Invoke(); - } - - this.disposed = true; - } - - base.Dispose(disposing); - } - } -} diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterEventSource.cs deleted file mode 100644 index 34f296c4451..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterEventSource.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// 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. -// - -using System; -using System.Diagnostics.Tracing; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Exporter.Prometheus.Shared -{ - /// - /// EventSource events emitted from the project. - /// - [EventSource(Name = "OpenTelemetry-Exporter-Prometheus")] - internal sealed class PrometheusExporterEventSource : EventSource - { - public static PrometheusExporterEventSource Log = new(); - - [NonEvent] - public void FailedExport(Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.FailedExport(ex.ToInvariantString()); - } - } - - [NonEvent] - public void FailedShutdown(Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.FailedShutdown(ex.ToInvariantString()); - } - } - - [NonEvent] - public void CanceledExport(Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - 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); - } - - [Event(4, Message = "No metrics are available for export.", Level = EventLevel.Warning)] - public void NoMetrics() - { - this.WriteEvent(4); - } - } -} diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterOptions.cs deleted file mode 100644 index 6c5cbd53db5..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusExporterOptions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// 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. -// - -using System; -using OpenTelemetry.Internal; - -#if PROMETHEUS_ASPNETCORE -namespace OpenTelemetry.Exporter.Prometheus.AspNetCore -#elif PROMETHEUS_HTTPLISTENER -namespace OpenTelemetry.Exporter.Prometheus.HttpListener -#else -namespace OpenTelemetry.Exporter.Prometheus.Shared -#endif -{ - /// - /// Prometheus exporter options. - /// - public class PrometheusExporterOptions - { - internal const string DefaultScrapeEndpointPath = "/metrics"; - internal Func GetUtcNowDateTimeOffset = () => DateTimeOffset.UtcNow; - - private int scrapeResponseCacheDurationMilliseconds = 10 * 1000; - - /// - /// Gets or sets the path to use for the scraping endpoint. Default value: /metrics. - /// - public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; - - /// - /// Gets or sets the cache duration in milliseconds for scrape responses. Default value: 10,000 (10 seconds). - /// - /// - /// Note: Specify 0 to disable response caching. - /// - public int ScrapeResponseCacheDurationMilliseconds - { - get => this.scrapeResponseCacheDurationMilliseconds; - set - { - Guard.ThrowIfOutOfRange(value, min: 0); - - this.scrapeResponseCacheDurationMilliseconds = value; - } - } - } -} diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializer.cs deleted file mode 100644 index a8738d1853d..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializer.cs +++ /dev/null @@ -1,305 +0,0 @@ -// -// 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. -// - -#if NETCOREAPP3_1_OR_GREATER -using System; -#endif -using System.Diagnostics; -using System.Globalization; -using System.Runtime.CompilerServices; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Exporter.Prometheus.Shared -{ - /// - /// Basic PrometheusSerializer which has no OpenTelemetry dependency. - /// - internal static partial class PrometheusSerializer - { -#pragma warning disable SA1310 // Field name should not contain an underscore - private const byte ASCII_QUOTATION_MARK = 0x22; // '"' - private const byte ASCII_FULL_STOP = 0x2E; // '.' - private const byte ASCII_HYPHEN_MINUS = 0x2D; // '-' - private const byte ASCII_REVERSE_SOLIDUS = 0x5C; // '\\' - private const byte ASCII_LINEFEED = 0x0A; // `\n` -#pragma warning restore SA1310 // Field name should not contain an underscore - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteDouble(byte[] buffer, int cursor, double value) - { - if (MathHelper.IsFinite(value)) - { -#if NETCOREAPP3_1_OR_GREATER - Span span = stackalloc char[128]; - - var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture); - Debug.Assert(result, $"{nameof(result)} should be true."); - - for (int i = 0; i < cchWritten; i++) - { - buffer[cursor++] = unchecked((byte)span[i]); - } -#else - cursor = WriteAsciiStringNoEscape(buffer, cursor, value.ToString(CultureInfo.InvariantCulture)); -#endif - } - else if (double.IsPositiveInfinity(value)) - { - cursor = WriteAsciiStringNoEscape(buffer, cursor, "+Inf"); - } - else if (double.IsNegativeInfinity(value)) - { - cursor = WriteAsciiStringNoEscape(buffer, cursor, "-Inf"); - } - else - { - Debug.Assert(double.IsNaN(value), $"{nameof(value)} should be NaN."); - cursor = WriteAsciiStringNoEscape(buffer, cursor, "Nan"); - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteLong(byte[] buffer, int cursor, long value) - { -#if NETCOREAPP3_1_OR_GREATER - Span span = stackalloc char[20]; - - var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture); - Debug.Assert(result, $"{nameof(result)} should be true."); - - for (int i = 0; i < cchWritten; i++) - { - buffer[cursor++] = unchecked((byte)span[i]); - } -#else - cursor = WriteAsciiStringNoEscape(buffer, cursor, value.ToString(CultureInfo.InvariantCulture)); -#endif - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteAsciiStringNoEscape(byte[] buffer, int cursor, string value) - { - for (int i = 0; i < value.Length; i++) - { - buffer[cursor++] = unchecked((byte)value[i]); - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteUnicodeNoEscape(byte[] buffer, int cursor, ushort ordinal) - { - if (ordinal <= 0x7F) - { - buffer[cursor++] = unchecked((byte)ordinal); - } - else if (ordinal <= 0x07FF) - { - buffer[cursor++] = unchecked((byte)(0b_1100_0000 | (ordinal >> 6))); - buffer[cursor++] = unchecked((byte)(0b_1000_0000 | (ordinal & 0b_0011_1111))); - } - else if (ordinal <= 0xFFFF) - { - buffer[cursor++] = unchecked((byte)(0b_1110_0000 | (ordinal >> 12))); - buffer[cursor++] = unchecked((byte)(0b_1000_0000 | ((ordinal >> 6) & 0b_0011_1111))); - buffer[cursor++] = unchecked((byte)(0b_1000_0000 | (ordinal & 0b_0011_1111))); - } - else - { - Debug.Assert(ordinal <= 0xFFFF, ".NET string should not go beyond Unicode BMP."); - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteUnicodeString(byte[] buffer, int cursor, string value) - { - for (int i = 0; i < value.Length; i++) - { - var ordinal = (ushort)value[i]; - switch (ordinal) - { - case ASCII_REVERSE_SOLIDUS: - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - break; - case ASCII_LINEFEED: - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - buffer[cursor++] = unchecked((byte)'n'); - break; - default: - cursor = WriteUnicodeNoEscape(buffer, cursor, ordinal); - break; - } - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteLabelKey(byte[] buffer, int cursor, string value) - { - Debug.Assert(!string.IsNullOrEmpty(value), $"{nameof(value)} should not be null or empty."); - - var ordinal = (ushort)value[0]; - - if (ordinal >= (ushort)'0' && ordinal <= (ushort)'9') - { - buffer[cursor++] = unchecked((byte)'_'); - } - - for (int i = 0; i < value.Length; i++) - { - ordinal = (ushort)value[i]; - - if ((ordinal >= (ushort)'A' && ordinal <= (ushort)'Z') || - (ordinal >= (ushort)'a' && ordinal <= (ushort)'z') || - (ordinal >= (ushort)'0' && ordinal <= (ushort)'9')) - { - buffer[cursor++] = unchecked((byte)ordinal); - } - else - { - buffer[cursor++] = unchecked((byte)'_'); - } - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteLabelValue(byte[] buffer, int cursor, string value) - { - Debug.Assert(value != null, $"{nameof(value)} should not be null."); - - for (int i = 0; i < value.Length; i++) - { - var ordinal = (ushort)value[i]; - switch (ordinal) - { - case ASCII_QUOTATION_MARK: - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - buffer[cursor++] = ASCII_QUOTATION_MARK; - break; - case ASCII_REVERSE_SOLIDUS: - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - break; - case ASCII_LINEFEED: - buffer[cursor++] = ASCII_REVERSE_SOLIDUS; - buffer[cursor++] = unchecked((byte)'n'); - break; - default: - cursor = WriteUnicodeNoEscape(buffer, cursor, ordinal); - break; - } - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteLabel(byte[] buffer, int cursor, string labelKey, object labelValue) - { - cursor = WriteLabelKey(buffer, cursor, labelKey); - buffer[cursor++] = unchecked((byte)'='); - buffer[cursor++] = unchecked((byte)'"'); - - // In Prometheus, a label with an empty label value is considered equivalent to a label that does not exist. - cursor = WriteLabelValue(buffer, cursor, labelValue?.ToString() ?? string.Empty); - buffer[cursor++] = unchecked((byte)'"'); - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteMetricName(byte[] buffer, int cursor, string metricName, string metricUnit = null) - { - Debug.Assert(!string.IsNullOrEmpty(metricName), $"{nameof(metricName)} should not be null or empty."); - - for (int i = 0; i < metricName.Length; i++) - { - var ordinal = (ushort)metricName[i]; - buffer[cursor++] = ordinal switch - { - ASCII_FULL_STOP or ASCII_HYPHEN_MINUS => unchecked((byte)'_'), - _ => unchecked((byte)ordinal), - }; - } - - if (!string.IsNullOrEmpty(metricUnit)) - { - buffer[cursor++] = unchecked((byte)'_'); - - for (int i = 0; i < metricUnit.Length; i++) - { - var ordinal = (ushort)metricUnit[i]; - - if ((ordinal >= (ushort)'A' && ordinal <= (ushort)'Z') || - (ordinal >= (ushort)'a' && ordinal <= (ushort)'z') || - (ordinal >= (ushort)'0' && ordinal <= (ushort)'9')) - { - buffer[cursor++] = unchecked((byte)ordinal); - } - else - { - buffer[cursor++] = unchecked((byte)'_'); - } - } - } - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteHelpText(byte[] buffer, int cursor, string metricName, string metricUnit = null, string metricDescription = null) - { - cursor = WriteAsciiStringNoEscape(buffer, cursor, "# HELP "); - cursor = WriteMetricName(buffer, cursor, metricName, metricUnit); - - if (!string.IsNullOrEmpty(metricDescription)) - { - buffer[cursor++] = unchecked((byte)' '); - cursor = WriteUnicodeString(buffer, cursor, metricDescription); - } - - buffer[cursor++] = ASCII_LINEFEED; - - return cursor; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteTypeInfo(byte[] buffer, int cursor, string metricName, string metricUnit, string metricType) - { - Debug.Assert(!string.IsNullOrEmpty(metricType), $"{nameof(metricType)} should not be null or empty."); - - cursor = WriteAsciiStringNoEscape(buffer, cursor, "# TYPE "); - cursor = WriteMetricName(buffer, cursor, metricName, metricUnit); - buffer[cursor++] = unchecked((byte)' '); - cursor = WriteAsciiStringNoEscape(buffer, cursor, metricType); - - buffer[cursor++] = ASCII_LINEFEED; - - return cursor; - } - } -} diff --git a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializerExt.cs deleted file mode 100644 index 1178c3069b6..00000000000 --- a/src/OpenTelemetry.Exporter.Prometheus.Shared/PrometheusSerializerExt.cs +++ /dev/null @@ -1,200 +0,0 @@ -// -// 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. -// - -using OpenTelemetry.Metrics; - -namespace OpenTelemetry.Exporter.Prometheus.Shared -{ - /// - /// OpenTelemetry additions to the PrometheusSerializer. - /// - internal static partial class PrometheusSerializer - { - private static readonly string[] MetricTypes = new string[] - { - "untyped", "counter", "gauge", "summary", "histogram", "histogram", "histogram", "histogram", "untyped", - }; - - public static int WriteMetric(byte[] buffer, int cursor, Metric metric) - { - if (!string.IsNullOrWhiteSpace(metric.Description)) - { - cursor = WriteHelpText(buffer, cursor, metric.Name, metric.Unit, metric.Description); - } - - int metricType = (int)metric.MetricType >> 4; - cursor = WriteTypeInfo(buffer, cursor, metric.Name, metric.Unit, MetricTypes[metricType]); - - if (!metric.MetricType.IsHistogram()) - { - foreach (ref readonly var metricPoint in metric.GetMetricPoints()) - { - var tags = metricPoint.Tags; - var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); - - // Counter and Gauge - cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); - - if (tags.Count > 0) - { - buffer[cursor++] = unchecked((byte)'{'); - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } - - buffer[cursor++] = unchecked((byte)' '); - - // TODO: MetricType is same for all MetricPoints - // within a given Metric, so this check can avoided - // for each MetricPoint - if (((int)metric.MetricType & 0b_0000_1111) == 0x0a /* I8 */) - { - if (metric.MetricType.IsSum()) - { - cursor = WriteLong(buffer, cursor, metricPoint.GetSumLong()); - } - else - { - cursor = WriteLong(buffer, cursor, metricPoint.GetGaugeLastValueLong()); - } - } - else - { - if (metric.MetricType.IsSum()) - { - cursor = WriteDouble(buffer, cursor, metricPoint.GetSumDouble()); - } - else - { - cursor = WriteDouble(buffer, cursor, metricPoint.GetGaugeLastValueDouble()); - } - } - - buffer[cursor++] = unchecked((byte)' '); - - cursor = WriteLong(buffer, cursor, timestamp); - - buffer[cursor++] = ASCII_LINEFEED; - } - } - else - { - foreach (ref readonly var metricPoint in metric.GetMetricPoints()) - { - var tags = metricPoint.Tags; - var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); - - long totalCount = 0; - foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) - { - totalCount += histogramMeasurement.BucketCount; - - cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); - cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\""); - - if (histogramMeasurement.ExplicitBound != double.PositiveInfinity) - { - cursor = WriteDouble(buffer, cursor, histogramMeasurement.ExplicitBound); - } - else - { - cursor = WriteAsciiStringNoEscape(buffer, cursor, "+Inf"); - } - - cursor = WriteAsciiStringNoEscape(buffer, cursor, "\"} "); - - cursor = WriteLong(buffer, cursor, totalCount); - buffer[cursor++] = unchecked((byte)' '); - - cursor = WriteLong(buffer, cursor, timestamp); - - buffer[cursor++] = ASCII_LINEFEED; - } - - // Histogram sum - cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); - cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); - - if (tags.Count > 0) - { - buffer[cursor++] = unchecked((byte)'{'); - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } - - buffer[cursor++] = unchecked((byte)' '); - - cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum()); - buffer[cursor++] = unchecked((byte)' '); - - cursor = WriteLong(buffer, cursor, timestamp); - - buffer[cursor++] = ASCII_LINEFEED; - - // Histogram count - cursor = WriteMetricName(buffer, cursor, metric.Name, metric.Unit); - cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); - - if (tags.Count > 0) - { - buffer[cursor++] = unchecked((byte)'{'); - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } - - buffer[cursor++] = unchecked((byte)' '); - - cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount()); - buffer[cursor++] = unchecked((byte)' '); - - cursor = WriteLong(buffer, cursor, timestamp); - - buffer[cursor++] = ASCII_LINEFEED; - } - } - - buffer[cursor++] = ASCII_LINEFEED; - - return cursor; - } - } -} From f03a0e7fcc85c8f61c7baca2191bfed1390f5c7f Mon Sep 17 00:00:00 2001 From: Yun-Ting Date: Fri, 29 Jul 2022 16:06:56 -0700 Subject: [PATCH 4/4] comments --- ...Telemetry.Exporter.Prometheus.AspNetCore.csproj | 12 ++++++------ ...etheusExporterMeterProviderBuilderExtensions.cs | 2 +- .../PrometheusExporterMiddleware.cs | 1 + .../.publicApi/net462/PublicAPI.Unshipped.txt | 14 +++++++------- .../netstandard2.0/PublicAPI.Unshipped.txt | 14 +++++++------- ...erHttpListenerMeterProviderBuilderExtensions.cs | 2 +- .../PrometheusHttpListener.cs | 1 + .../{ => Shared}/PrometheusCollectionManager.cs | 10 +++++----- .../{ => Shared}/PrometheusExporter.cs | 2 +- .../{ => Shared}/PrometheusExporterEventSource.cs | 2 +- .../{ => Shared}/PrometheusExporterOptions.cs | 2 +- .../{ => Shared}/PrometheusSerializer.cs | 0 .../{ => Shared}/PrometheusSerializerExt.cs | 0 .../PrometheusCollectionManagerTests.cs | 1 + 14 files changed, 33 insertions(+), 30 deletions(-) rename src/OpenTelemetry.Exporter.Prometheus.HttpListener/{ => Shared}/PrometheusCollectionManager.cs (96%) rename src/OpenTelemetry.Exporter.Prometheus.HttpListener/{ => Shared}/PrometheusExporter.cs (97%) rename src/OpenTelemetry.Exporter.Prometheus.HttpListener/{ => Shared}/PrometheusExporterEventSource.cs (97%) rename src/OpenTelemetry.Exporter.Prometheus.HttpListener/{ => Shared}/PrometheusExporterOptions.cs (96%) rename src/OpenTelemetry.Exporter.Prometheus.HttpListener/{ => Shared}/PrometheusSerializer.cs (100%) rename src/OpenTelemetry.Exporter.Prometheus.HttpListener/{ => Shared}/PrometheusSerializerExt.cs (100%) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj index 671cecda430..33a00e05f1a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -19,12 +19,12 @@ - - - - - - + + + + + + diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs index fd45978a9c5..d230d03d042 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs @@ -15,8 +15,8 @@ // using System; -using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Exporter.Prometheus.AspNetCore; +using OpenTelemetry.Exporter.Prometheus.HttpListener.Shared; using OpenTelemetry.Internal; namespace OpenTelemetry.Metrics diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index 99bc08ba44b..d76a85d44d3 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -19,6 +19,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using OpenTelemetry.Exporter.Prometheus.HttpListener.Shared; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt index b4cf532ea27..5f48f2c7562 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt @@ -2,11 +2,11 @@ OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.set -> void OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.PrometheusExporterOptions() -> void +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions -static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index b4cf532ea27..5f48f2c7562 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -2,11 +2,11 @@ OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.Prefixes.set -> void OpenTelemetry.Exporter.Prometheus.HttpListener.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.PrometheusExporterOptions() -> void +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int +OpenTelemetry.Exporter.Prometheus.HttpListener.Shared.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions -static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Metrics.PrometheusExporterHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configureExporterOptions = null, System.Action configureListenerOptions = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs index d8c2672b70f..8c53f09837d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterHttpListenerMeterProviderBuilderExtensions.cs @@ -15,8 +15,8 @@ // using System; -using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Exporter.Prometheus.HttpListener; +using OpenTelemetry.Exporter.Prometheus.HttpListener.Shared; using OpenTelemetry.Internal; namespace OpenTelemetry.Metrics diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index f092fa89d7d..6badb4d5337 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -18,6 +18,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using OpenTelemetry.Exporter.Prometheus.HttpListener.Shared; using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.Prometheus.HttpListener diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusCollectionManager.cs similarity index 96% rename from src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusCollectionManager.cs index 8f672645ba0..43da102246f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusCollectionManager.cs @@ -20,7 +20,7 @@ using System.Threading.Tasks; using OpenTelemetry.Metrics; -namespace OpenTelemetry.Exporter.Prometheus +namespace OpenTelemetry.Exporter.Prometheus.HttpListener.Shared { internal sealed class PrometheusCollectionManager { @@ -91,7 +91,7 @@ public Task EnterCollect() this.ExitGlobalLock(); CollectionResponse response; - bool result = this.ExecuteCollect(); + var result = this.ExecuteCollect(); if (result) { this.previousDataViewGeneratedAtUtc = DateTime.UtcNow; @@ -169,14 +169,14 @@ private void WaitForReadersToComplete() private bool ExecuteCollect() { this.exporter.OnExport = this.onCollectRef; - bool result = this.exporter.Collect(Timeout.Infinite); + var result = this.exporter.Collect(Timeout.Infinite); this.exporter.OnExport = null; return result; } private ExportResult OnCollect(Batch metrics) { - int cursor = 0; + var cursor = 0; try { @@ -191,7 +191,7 @@ private ExportResult OnCollect(Batch metrics) } catch (IndexOutOfRangeException) { - int bufferSize = this.buffer.Length * 2; + var bufferSize = this.buffer.Length * 2; // there are two cases we might run into the following condition: // 1. we have many metrics to be exported - in this case we probably want diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporter.cs similarity index 97% rename from src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporter.cs index 5199b55caf9..ec2dbf0467b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporter.cs @@ -20,7 +20,7 @@ #endif using OpenTelemetry.Metrics; -namespace OpenTelemetry.Exporter.Prometheus +namespace OpenTelemetry.Exporter.Prometheus.HttpListener.Shared { /// /// Exporter of OpenTelemetry metrics to Prometheus. diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporterEventSource.cs similarity index 97% rename from src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporterEventSource.cs index b678cb436e2..6cdbf9a9024 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporterEventSource.cs @@ -18,7 +18,7 @@ using System.Diagnostics.Tracing; using OpenTelemetry.Internal; -namespace OpenTelemetry.Exporter.Prometheus +namespace OpenTelemetry.Exporter.Prometheus.HttpListener.Shared { /// /// EventSource events emitted from the project. diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporterOptions.cs similarity index 96% rename from src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporterOptions.cs index d35ebba526e..4662ee3e263 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusExporterOptions.cs @@ -20,7 +20,7 @@ #if PROMETHEUS_ASPNETCORE namespace OpenTelemetry.Exporter.Prometheus.AspNetCore #else -namespace OpenTelemetry.Exporter.Prometheus +namespace OpenTelemetry.Exporter.Prometheus.HttpListener.Shared #endif { /// diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusSerializer.cs similarity index 100% rename from src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializer.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusSerializer.cs diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusSerializerExt.cs similarity index 100% rename from src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusSerializerExt.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Shared/PrometheusSerializerExt.cs diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs index bc60596ad9b..aa680d6495c 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs @@ -21,6 +21,7 @@ #endif using System.Threading; using System.Threading.Tasks; +using OpenTelemetry.Exporter.Prometheus.HttpListener.Shared; using OpenTelemetry.Metrics; using OpenTelemetry.Tests; using Xunit;