diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 3b705899e..22e4bddf7 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -218,6 +218,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress", "test\OpenTelemetry.Tests.Stress\OpenTelemetry.Tests.Stress.csproj", "{2770158A-D220-414B-ABC6-179371323579}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.Tests", "test\OpenTelemetry.Exporter.Prometheus.Tests\OpenTelemetry.Exporter.Prometheus.Tests.csproj", "{380EE686-91F1-45B3-AEEB-755F0E5B068F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -448,6 +450,10 @@ Global {2770158A-D220-414B-ABC6-179371323579}.Debug|Any CPU.Build.0 = Debug|Any CPU {2770158A-D220-414B-ABC6-179371323579}.Release|Any CPU.ActiveCfg = Release|Any CPU {2770158A-D220-414B-ABC6-179371323579}.Release|Any CPU.Build.0 = Release|Any CPU + {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs index 36f813dca..fd06ffcdf 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs @@ -17,6 +17,8 @@ using System.Runtime.CompilerServices; #if SIGNED [assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] #else [assembly: InternalsVisibleTo("Benchmarks")] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests")] #endif diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs index 74d5c4345..1b1462755 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterExtensions.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System; using System.Globalization; using System.IO; using System.Threading.Tasks; @@ -36,17 +37,20 @@ namespace OpenTelemetry.Exporter.Prometheus private const string PrometheusHistogramBucketLabelPositiveInfinity = "+Inf"; private const string PrometheusHistogramBucketLabelLessThan = "le"; + private static readonly Func DefaultGetUtcNowDateTimeOffset = () => DateTimeOffset.UtcNow; + /// /// Serialize metrics to prometheus format. /// /// . /// StreamWriter to write to. + /// Optional function to resolve the current date & time. /// to await the operation. - public static async Task WriteMetricsCollection(this PrometheusExporter exporter, StreamWriter writer) + public static async Task WriteMetricsCollection(this PrometheusExporter exporter, StreamWriter writer, Func getUtcNowDateTimeOffset = null) { foreach (var metric in exporter.Metrics) { - var builder = new PrometheusMetricBuilder() + var builder = new PrometheusMetricBuilder(getUtcNowDateTimeOffset ?? DefaultGetUtcNowDateTimeOffset) .WithName(metric.Name) .WithDescription(metric.Description); diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs index 751a7be50..d4166a1f5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs @@ -59,11 +59,17 @@ namespace OpenTelemetry.Exporter.Prometheus }; private readonly ICollection values = new List(); + private readonly Func getUtcNowDateTimeOffset; private string name; private string description; private string type; + public PrometheusMetricBuilder(Func getUtcNowDateTimeOffset) + { + this.getUtcNowDateTimeOffset = getUtcNowDateTimeOffset; + } + public PrometheusMetricBuilder WithName(string name) { this.name = name; @@ -141,7 +147,7 @@ namespace OpenTelemetry.Exporter.Prometheus // ] value [ timestamp ] // In the sample syntax: - var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture); + var now = this.getUtcNowDateTimeOffset().ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture); foreach (var m in this.values) { diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/OpenTelemetry.Exporter.Prometheus.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.Tests/OpenTelemetry.Exporter.Prometheus.Tests.csproj new file mode 100644 index 000000000..bed1d487a --- /dev/null +++ b/test/OpenTelemetry.Exporter.Prometheus.Tests/OpenTelemetry.Exporter.Prometheus.Tests.csproj @@ -0,0 +1,29 @@ + + + Unit test project for Prometheus Exporter for OpenTelemetry + netcoreapp3.1;net5.0 + $(TargetFrameworks);net461 + + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterExtensionsTests.cs b/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterExtensionsTests.cs new file mode 100644 index 000000000..3762ddb13 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterExtensionsTests.cs @@ -0,0 +1,77 @@ +// +// 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.Collections.Generic; +using System.Diagnostics.Metrics; +using System.IO; +using System.Text; +using OpenTelemetry.Metrics; +using OpenTelemetry.Tests; +using Xunit; + +namespace OpenTelemetry.Exporter.Prometheus.Tests +{ + public sealed class PrometheusExporterExtensionsTests + { + [Fact] + public void WriteMetricsCollectionTest() + { + var tags = new KeyValuePair[] + { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + }; + + using var provider = Sdk.CreateMeterProviderBuilder() + .AddSource("TestMeter") + .AddReader(new BaseExportingMetricReader(new TestExporter(RunTest))) + .Build(); + + using var meter = new Meter("TestMeter", "0.0.1"); + + var counter = meter.CreateCounter("counter"); + + counter.Add(100, tags); + + var testCompleted = false; + + // Invokes the TestExporter which will invoke RunTest + provider.ForceFlush(3000); + + Assert.True(testCompleted); + + void RunTest(Batch metrics) + { + using PrometheusExporter prometheusExporter = new PrometheusExporter(new PrometheusExporterOptions()); + + prometheusExporter.Metrics = metrics; + + using MemoryStream ms = new MemoryStream(); + using (StreamWriter writer = new StreamWriter(ms)) + { + PrometheusExporterExtensions.WriteMetricsCollection(prometheusExporter, writer, () => new DateTimeOffset(2021, 9, 30, 22, 30, 0, TimeSpan.Zero)).GetAwaiter().GetResult(); + } + + Assert.Equal( + "# TYPE counter counter\ncounter{key1=\"value1\",key2=\"value2\"} 100 1633041000000\n", + Encoding.UTF8.GetString(ms.ToArray())); + + testCompleted = true; + } + } + } +}