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
+
+
+
+
+
+
+
+
+
+
+
+