diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI.sln b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI.sln new file mode 100644 index 000000000..2ca423706 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35521.163 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsAPI", "MetricsAPI\MetricsAPI.csproj", "{D81FB454-FDD5-439A-A996-D309484BECBF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{EA4AF403-AE9B-4064-ABB9-69868DE6D34D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D81FB454-FDD5-439A-A996-D309484BECBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D81FB454-FDD5-439A-A996-D309484BECBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D81FB454-FDD5-439A-A996-D309484BECBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D81FB454-FDD5-439A-A996-D309484BECBF}.Release|Any CPU.Build.0 = Release|Any CPU + {EA4AF403-AE9B-4064-ABB9-69868DE6D34D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA4AF403-AE9B-4064-ABB9-69868DE6D34D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA4AF403-AE9B-4064-ABB9-69868DE6D34D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA4AF403-AE9B-4064-ABB9-69868DE6D34D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Controllers/MetricsController.cs b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Controllers/MetricsController.cs new file mode 100644 index 000000000..a08c7ef51 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Controllers/MetricsController.cs @@ -0,0 +1,36 @@ +using MetricsAPI.Services; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; + +namespace MetricsAPI.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class MetricsController(IMetricsService metricsService) : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + var random = Random.Shared; + + metricsService.RecordUserClick(); + + for (int i = 0; i < 100; i++) + { + metricsService.RecordResponseTime(random.NextDouble()); + } + + metricsService.RecordRequest(); + + metricsService.RecordMemoryConsumption(GC.GetAllocatedBytesForCurrentThread() / (1024 * 1024)); + + metricsService.RecordUserClickDetailed("US", "checkout"); + + metricsService.RecordResourceUsage( + Utilities.GetCpuUsagePercentage(), + GC.GetTotalAllocatedBytes() / (1024 * 1024), + Process.GetCurrentProcess().Threads.Count); + + return Ok(); + } +} diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/MetricsAPI.csproj b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/MetricsAPI.csproj new file mode 100644 index 000000000..e379db3e3 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/MetricsAPI.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Program.cs b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Program.cs new file mode 100644 index 000000000..901e94e6b --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Program.cs @@ -0,0 +1,20 @@ +using MetricsAPI.Services; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); +app.Run(); diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Properties/launchSettings.json b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Properties/launchSettings.json new file mode 100644 index 000000000..fb263129f --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7052;http://localhost:5095", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/IMetricsService.cs b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/IMetricsService.cs new file mode 100644 index 000000000..f6055d096 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/IMetricsService.cs @@ -0,0 +1,11 @@ +namespace MetricsAPI.Services; + +public interface IMetricsService +{ + void RecordUserClick(); + void RecordResponseTime(double value); + void RecordRequest(); + void RecordMemoryConsumption(double value); + void RecordUserClickDetailed(string region, string feature); + void RecordResourceUsage(double currentCpuUsage, double currentMemoryUsage, double currentThreadCount); +} diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/MetricsService.cs b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/MetricsService.cs new file mode 100644 index 000000000..c7ed4de42 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/MetricsService.cs @@ -0,0 +1,82 @@ +using System.Diagnostics.Metrics; + +namespace MetricsAPI.Services; + +public class MetricsService : IMetricsService +{ + private readonly Counter _userClicks; + private readonly Histogram _responseTime; + + private int _requests; + private double _memoryConsumption; + private double _cpu; + private double _memory; + private double _threadCount; + + public MetricsService(IMeterFactory meterFactory) + { + var meter = meterFactory.Create("Metrics.Service"); + + _userClicks = meter.CreateCounter("metrics.service.user_clicks"); + + _responseTime = meter.CreateHistogram(name: "metrics.service.response_time", + unit: "Seconds", + description: "This metric measures the time taken for the application to respond to user requests."); + + meter.CreateObservableCounter("metrics.service.requests", () => _requests); + + meter.CreateObservableGauge(name: "metrics.service.memory_consumption", + () => _memoryConsumption, + unit: "Megabytes", + description: "This metric measures the amount of memory used by the application."); + + meter.CreateObservableGauge(name: "metrics.service.resource_consumption", + GetResourceConsumption); + } + public void RecordUserClick() + { + _userClicks.Add(1); + } + + public void RecordResponseTime(double value) + { + _responseTime.Record(value); + } + + public void RecordRequest() + { + Interlocked.Increment(ref _requests); + } + + public void RecordMemoryConsumption(double value) + { + _memoryConsumption = value; + } + + public void RecordUserClickDetailed(string region, string feature) + { + _userClicks.Add(1, + new KeyValuePair("user.region", region), + new KeyValuePair("user.feature", feature)); + } + + public void RecordResourceUsage(double currentCpuUsage, double currentMemoryUsage, double currentThreadCount) + { + _cpu = currentCpuUsage; + _memory = currentMemoryUsage; + _threadCount = currentThreadCount; + } + + private IEnumerable> GetResourceConsumption() + { + return + [ + new Measurement(_cpu, new KeyValuePair + ("resource_usage", "cpu")), + new Measurement(_memory, new KeyValuePair + ("resource_usage", "memory")), + new Measurement(_threadCount, new KeyValuePair + ("resource_usage", "thread_count")), + ]; + } +} diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/Utilities.cs b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/Utilities.cs new file mode 100644 index 000000000..08fda5f05 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/Services/Utilities.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; + +namespace MetricsAPI.Services; + +public static class Utilities +{ + public static double GetCpuUsagePercentage() + { + var process = Process.GetCurrentProcess(); + + var startTime = DateTime.UtcNow; + var initialCpuTime = process.TotalProcessorTime; + + Thread.Sleep(1000); + + var endTime = DateTime.UtcNow; + var finalCpuTime = process.TotalProcessorTime; + + var totalCpuTimeUsed = (finalCpuTime - initialCpuTime).TotalMilliseconds; + var totalTimeElapsed = (endTime - startTime).TotalMilliseconds; + + var cpuUsage = (totalCpuTimeUsed / (Environment.ProcessorCount * totalTimeElapsed)) * 100; + + return cpuUsage; + } +} \ No newline at end of file diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/appsettings.Development.json b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/appsettings.json b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/MetricsAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/Tests/MetricsTests.cs b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/Tests/MetricsTests.cs new file mode 100644 index 000000000..342ecd7b8 --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/Tests/MetricsTests.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Diagnostics.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using MetricsAPI.Services; + +namespace Tests; + +public class MetricsTests +{ + [Fact] + public void GivenMetricsConfigured_WhenUserClickRecorded_ThenCounterCaptured() + { + // Arrange + using var services = CreateServiceProvider(); + var metrics = services.GetRequiredService(); + var meterFactory = services.GetRequiredService(); + var collector = new MetricCollector(meterFactory, "Metrics.Service", "metrics.service.user_clicks"); + + // Act + metrics.RecordUserClick(); + + // Assert + var measurements = collector.GetMeasurementSnapshot(); + Assert.Single(measurements); + Assert.Equal(1, measurements[0].Value); + } + + [Fact] + public void GivenMetricsConfigured_WhenRequestRecorded_ThenObservableCounterCaptured() + { + // Arrange + using var services = CreateServiceProvider(); + var metrics = services.GetRequiredService(); + var meterFactory = services.GetRequiredService(); + var collector = new MetricCollector(meterFactory, "Metrics.Service", "metrics.service.requests"); + + // Act + metrics.RecordRequest(); + + // Assert + collector.RecordObservableInstruments(); + var measurements = collector.GetMeasurementSnapshot(); + Assert.Single(measurements); + Assert.Equal(1, measurements[0].Value); + } + + private static ServiceProvider CreateServiceProvider() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddMetrics(); + serviceCollection.AddSingleton(); + + return serviceCollection.BuildServiceProvider(); + } +} \ No newline at end of file diff --git a/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/Tests/Tests.csproj b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/Tests/Tests.csproj new file mode 100644 index 000000000..73b55320e --- /dev/null +++ b/aspnetcore-features/PerformanceMonitoringWithIMeterFactory/Tests/Tests.csproj @@ -0,0 +1,35 @@ + + + + net9.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + +