Skip to content

Commit bb45176

Browse files
authored
Metrics generators sample (#143)
* Metrics code-generators usage sample --------- Authored-by: Oleg Moskalenko <[email protected]>
1 parent efcee42 commit bb45176

File tree

11 files changed

+289
-1
lines changed

11 files changed

+289
-1
lines changed

Samples.sln

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ComplexObjectLogging", "Com
3636
EndProject
3737
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComplexObjectLogging", "src\Telemetry\Logging\ComplexObjectLogging\ComplexObjectLogging.csproj", "{75034993-150B-4940-B633-4FEF73E30401}"
3838
EndProject
39+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Metering", "Metering", "{2AC2A977-1FB9-46C6-8D25-186BA67F3290}"
40+
EndProject
3941
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpClientLogging", "HttpClientLogging", "{01EA865B-B3B0-4A2F-8361-8E59C004653B}"
4042
EndProject
43+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Metrics.Generators", "src\Telemetry\Metering\Metrics.Generators\Metrics.Generators.csproj", "{51040EAD-75AB-4169-A171-D90C25D82EA8}"
44+
EndProject
4145
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpClientLogging", "src\Telemetry\Logging\HttpClientLogging\HttpClientLogging.csproj", "{A8922F1D-0A30-45DA-ADD2-927455CB6549}"
4246
EndProject
4347
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpLogging", "HttpLogging", "{175F98E5-AFE8-4978-A512-EB84AD660208}"
@@ -66,6 +70,10 @@ Global
6670
{75034993-150B-4940-B633-4FEF73E30401}.Debug|Any CPU.Build.0 = Debug|Any CPU
6771
{75034993-150B-4940-B633-4FEF73E30401}.Release|Any CPU.ActiveCfg = Release|Any CPU
6872
{75034993-150B-4940-B633-4FEF73E30401}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{51040EAD-75AB-4169-A171-D90C25D82EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74+
{51040EAD-75AB-4169-A171-D90C25D82EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
75+
{51040EAD-75AB-4169-A171-D90C25D82EA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
76+
{51040EAD-75AB-4169-A171-D90C25D82EA8}.Release|Any CPU.Build.0 = Release|Any CPU
6977
{A8922F1D-0A30-45DA-ADD2-927455CB6549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
7078
{A8922F1D-0A30-45DA-ADD2-927455CB6549}.Debug|Any CPU.Build.0 = Debug|Any CPU
7179
{A8922F1D-0A30-45DA-ADD2-927455CB6549}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -89,6 +97,8 @@ Global
8997
{248CB37C-2412-4231-96C0-092413C10D4B} = {72B462BA-F903-4CA7-8A0A-6C030BE408BC}
9098
{2B4A9009-DB4D-497A-BA1A-23A18EBC32E8} = {248CB37C-2412-4231-96C0-092413C10D4B}
9199
{75034993-150B-4940-B633-4FEF73E30401} = {2B4A9009-DB4D-497A-BA1A-23A18EBC32E8}
100+
{2AC2A977-1FB9-46C6-8D25-186BA67F3290} = {72B462BA-F903-4CA7-8A0A-6C030BE408BC}
101+
{51040EAD-75AB-4169-A171-D90C25D82EA8} = {2AC2A977-1FB9-46C6-8D25-186BA67F3290}
92102
{01EA865B-B3B0-4A2F-8361-8E59C004653B} = {248CB37C-2412-4231-96C0-092413C10D4B}
93103
{A8922F1D-0A30-45DA-ADD2-927455CB6549} = {01EA865B-B3B0-4A2F-8361-8E59C004653B}
94104
{175F98E5-AFE8-4978-A512-EB84AD660208} = {248CB37C-2412-4231-96C0-092413C10D4B}

eng/Version.Details.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
<Uri>https://github.com/dotnet/runtime</Uri>
1010
<Sha>488a8a3521610422e8fbe22d5cc66127f3dce3dc</Sha>
1111
</Dependency>
12+
<Dependency Name="Microsoft.Extensions.Diagnostics" Version="8.0.0">
13+
<Uri>https://github.com/dotnet/runtime</Uri>
14+
<Sha>488a8a3521610422e8fbe22d5cc66127f3dce3dc</Sha>
15+
</Dependency>
1216
<Dependency Name="Microsoft.Extensions.Logging.Console" Version="8.0.0">
1317
<Uri>https://github.com/dotnet/runtime</Uri>
14-
<Sha>c88b3776d0fb3bb33fcd4192b476147c0895bd6f</Sha>
18+
<Sha>488a8a3521610422e8fbe22d5cc66127f3dce3dc</Sha>
1519
</Dependency>
1620
<!-- dotnet/aspnetcore -->
1721
<!-- dotnet/extensions -->

eng/Versions.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<!-- dotnet/runtime -->
3030
<MicrosoftBclTimeProviderVersion>8.0.0</MicrosoftBclTimeProviderVersion>
3131
<MicrosoftExtensionsHostingVersion>8.0.0</MicrosoftExtensionsHostingVersion>
32+
<MicrosoftExtensionsDiagnosticsVersion>8.0.0</MicrosoftExtensionsDiagnosticsVersion>
3233
<MicrosoftExtensionsLoggingConsoleVersion>8.0.0</MicrosoftExtensionsLoggingConsoleVersion>
3334
<!-- dotnet/aspnetcore -->
3435
<!-- dotnet/extensions -->
@@ -40,6 +41,7 @@
4041
<MicrosoftExtensionsTelemetryAbstractionsVersion>8.1.0-preview.23564.2</MicrosoftExtensionsTelemetryAbstractionsVersion>
4142
<MicrosoftExtensionsTelemetryVersion>8.1.0-preview.23564.2</MicrosoftExtensionsTelemetryVersion>
4243
<MicrosoftExtensionsHttpDiagnosticsVersion>8.1.0-preview.23564.2</MicrosoftExtensionsHttpDiagnosticsVersion>
44+
<MicrosoftExtensionsAuditReportsVersion>8.1.0-preview.23564.2</MicrosoftExtensionsAuditReportsVersion>
4345
</PropertyGroup>
4446
<!--
4547
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>Demonstrates how to emit application metrics.</Description>
5+
<OutputType>Exe</OutputType>
6+
<TargetFrameworks>$(NetCoreTargetFrameworks)</TargetFrameworks>
7+
<!-- This activates metrics report generator -->
8+
<GenerateMetricsReport>true</GenerateMetricsReport>
9+
<!-- Specify where the MetricReport.json file should be created -->
10+
<MetricsReportOutputPath>$(OutDir)</MetricsReportOutputPath>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.Extensions.Diagnostics" Version="$(MicrosoftExtensionsDiagnosticsVersion)" />
15+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsHostingVersion)" />
16+
<!-- This package should be referenced for metrics code generator -->
17+
<PackageReference Include="Microsoft.Extensions.Telemetry.Abstractions" Version="$(MicrosoftExtensionsTelemetryAbstractionsVersion)" />
18+
<!-- This package should be referenced for metrics usage report generator -->
19+
<PackageReference Include="Microsoft.Extensions.AuditReports" Version="$(MicrosoftExtensionsAuditReportsVersion)" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using Microsoft.Extensions.Diagnostics.Metrics;
6+
using static Metrics.Generators.Metric;
7+
8+
namespace Metrics.Generators;
9+
10+
// The structure that contains the metering information about the request.
11+
internal struct RequestInfo
12+
{
13+
// This annotated property will be used as a tag for the RequestStats histogram.
14+
[TagName(Tags.Target)]
15+
public RequestTarget Target { get; set; }
16+
17+
// This annotated property will be used as a tag for the RequestStats histogram.
18+
[TagName(Tags.DayOfWeek)]
19+
public DayOfWeek DayOfWeek { get; set; }
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Metrics.Generators;
5+
6+
internal enum RequestTarget
7+
{
8+
MicrosoftDotCom,
9+
GitHubDotCom,
10+
LinkedInDotCom,
11+
Invalid,
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
7+
8+
namespace Metrics.Generators;
9+
10+
internal static class Startup
11+
{
12+
public static async Task Main(string[] args)
13+
{
14+
using var host = Host.CreateDefaultBuilder(args)
15+
.ConfigureServices(services => services.AddHostedService<TelemetryEmitterBackgroundService>())
16+
.Build();
17+
18+
await host.RunAsync().ConfigureAwait(false);
19+
}
20+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Metrics with code generators
2+
3+
This sample shows you how to add metrics to your application with the help of code generators.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Metrics;
7+
using System.Text;
8+
9+
namespace Metrics.Generators;
10+
11+
// The utility class that prints to the console all metrics recorded by the application.
12+
internal sealed class ConsoleMetricWriter : IDisposable
13+
{
14+
private readonly MeterListener _meterListener;
15+
16+
public ConsoleMetricWriter()
17+
{
18+
_meterListener = new()
19+
{
20+
InstrumentPublished = (instrument, listener) =>
21+
{
22+
listener.SetMeasurementEventCallback<long>(PrintMeasurement);
23+
listener.EnableMeasurementEvents(instrument);
24+
}
25+
};
26+
_meterListener.Start();
27+
}
28+
29+
private static void PrintMeasurement<T>(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state)
30+
{
31+
var stringBuilder = new StringBuilder()
32+
.Append("Metric [")
33+
.Append(instrument.Name)
34+
.Append("] value: ")
35+
.Append(measurement);
36+
37+
foreach (var tag in tags)
38+
{
39+
stringBuilder
40+
.Append(' ')
41+
.Append(tag.Key)
42+
.Append('=')
43+
.Append(tag.Value);
44+
}
45+
46+
Console.WriteLine(stringBuilder);
47+
}
48+
49+
public void Dispose()
50+
{
51+
_meterListener.Dispose();
52+
}
53+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Metrics;
7+
using System.Net.Http;
8+
using System.Security.Cryptography;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.Extensions.Hosting;
12+
13+
namespace Metrics.Generators;
14+
15+
internal sealed class TelemetryEmitterBackgroundService : BackgroundService
16+
{
17+
private readonly Meter _meter;
18+
private readonly RequestStatsHistogram _requestsStatsHistogram;
19+
private readonly TotalRequestCounter _totalRequestCounter;
20+
private readonly FailedRequestCounter _failedRequestCounter;
21+
private readonly PeriodicTimer _timer;
22+
23+
public TelemetryEmitterBackgroundService()
24+
{
25+
_timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
26+
_meter = new Meter(nameof(TelemetryEmitterBackgroundService));
27+
28+
// Create metering instruments using the auto-generated code:
29+
_requestsStatsHistogram = Metric.CreateRequestStatsHistogram(_meter);
30+
_totalRequestCounter = Metric.CreateTotalRequestCounter(_meter);
31+
_failedRequestCounter = Metric.CreateFailedRequestCounter(_meter);
32+
}
33+
34+
protected override Task ExecuteAsync(CancellationToken cancellationToken)
35+
{
36+
return Task.Run(async () =>
37+
{
38+
using var consoleMetricWriter = new ConsoleMetricWriter();
39+
40+
var targets = new List<(RequestTarget, string)>
41+
{
42+
new (RequestTarget.MicrosoftDotCom, "https://microsoft.com"),
43+
new (RequestTarget.GitHubDotCom, "https://github.com"),
44+
new (RequestTarget.LinkedInDotCom, "https://linkedin.com"),
45+
new (RequestTarget.Invalid, "invalid_url"),
46+
};
47+
48+
using var httpClient = new HttpClient();
49+
50+
while (!cancellationToken.IsCancellationRequested)
51+
{
52+
var index = RandomNumberGenerator.GetInt32(0, targets.Count);
53+
var target = targets[index].Item1;
54+
var targetUrl = targets[index].Item2;
55+
56+
try
57+
{
58+
Console.WriteLine($"{Environment.NewLine}Sending request to ${targetUrl}...");
59+
60+
// Record the 'sample.total_requests' counter metric.
61+
_totalRequestCounter.Add(1, target);
62+
63+
var response = await httpClient.GetAsync(targetUrl, cancellationToken).ConfigureAwait(false);
64+
65+
if (response.IsSuccessStatusCode)
66+
{
67+
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
68+
69+
// Record the 'sample.request_stats' histogram metric.
70+
_requestsStatsHistogram.Record(
71+
content.Length,
72+
new RequestInfo
73+
{
74+
Target = target,
75+
DayOfWeek = DateTimeOffset.UtcNow.DayOfWeek
76+
});
77+
}
78+
else
79+
{
80+
// Record the 'sample.failed_requests' counter metric.
81+
_failedRequestCounter.Add(1, target, response.StatusCode.ToString());
82+
}
83+
}
84+
catch (Exception ex)
85+
{
86+
// Record the 'sample.failed_requests' counter metric.
87+
_failedRequestCounter.Add(1, target, ex.GetType().Name);
88+
}
89+
90+
await _timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false);
91+
}
92+
}, cancellationToken);
93+
}
94+
95+
public override void Dispose()
96+
{
97+
base.Dispose();
98+
99+
_timer.Dispose();
100+
_meter.Dispose();
101+
}
102+
}

0 commit comments

Comments
 (0)