How to use EventCounters in .NET Core
How to use EventCounters in .NET Core
When working in .NET or .NET Core applications, you will often want to profile the performance of your application to be aware of any potential bottlenecks before you deploy the application to the production environment. EventCounters provides a collection of performance metrics you can use for this.
This article discusses how we can work with EventCounters in .NET Core, providing code examples to help you get started. To work with the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.
Create a console application project in Visual Studio
First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2022 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Click Next.
- In the “Additional information” window shown next, choose “.NET 7.0 (Standard Term Support)” as the Framework version you would like to use.
- Click Create.
We’ll use this .NET 7 console application project to work with EventCounters in the subsequent sections of this article.
What are EventCounters?
EventCounters are the cross-platform, lightweight, .NET Core counterpart to the performance counters of the .NET Framework for Windows. They are APIs for collecting lightweight performance data in real time in your .NET Core applications.
An EventCounter is a feature of EventSource that is designed for capturing a specific low-level performance metric. Different EventCounters track CPU usage, memory consumption, the number of threads, and many other aspects of .NET Core and ASP.NET Core runtime performance.
To use EventCounters in .NET Core, you must first create a class that extends the System.Diagnostics.Tracing.EventSource class. Then you can implement any EventCounters that provide the performance metrics you wish to track. You can observe the performance counter data published by EventCounters using any number of performance monitoring tools. We’ll use the handy dotnet-counters tool here.
Install dotnet-counters
To get started using the dotnet-counters tool, you must first install it using the following command at the console window.
dotnet tool install --global dotnet-counters
Once dotnet-counters is installed, press F5 to run the console application we created above. Figure 2 below shows the console application in execution.
The dotnet-counters tool allows you to investigate performance counters in your .NET Core applications. To display a list of processes that can be monitored, just run the following command at the command window.
dotnet-counters ps
Figure 3 shows the list of processes running.
Once the console application has started, execute the following command at the command window to monitor the console application.
dotnet-counters monitor -n EventCountersDemo
Implement EventCounters in .NET Core
To publish your own performance counters in .NET Core, you should implement an EventSource class. The reason is that EventCounters are developed on top of .NET Core’s tracing mechanism.
Create a class named CustomEventSource in a file of the same name with a .cs extension. Because all EventCounters must be registered against an EventSource, your CustomEventSource class should extend the System.Diagnostics.Tracing.EventSource class.
Replace the default generated code of the file you just created with the source code given in the listing given below.
[EventSource(Name = "EventCountersDemo.CustomEventSource")] public sealed class CustomEventSource : EventSource { public static readonly CustomEventSource Instance = new(); private readonly IncrementingEventCounter _incrementingEventCounter; private bool _disposedValue; private CustomEventSource() { _incrementingEventCounter = new IncrementingEventCounter("MyCustomCounter", this) { DisplayName = "Incrementing Counter" }; } public void IncreaseCounterValue() { _incrementingEventCounter.Increment(); } public void Dispose() => Dispose(true); protected override void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _incrementingEventCounter?.Dispose(); } _disposedValue = true; } base.Dispose(disposing); } }
We will trigger the event counting using a bit of custom code. To do this, replace the default generated code of the Program.cs file with the following code.
using EventCountersDemo; Task.Run(async () => await CounterIncrementor()); Console.ReadKey(); async Task CounterIncrementor() { while (true) { CustomEventSource.Instance.IncreaseCounterValue(); await Task.Delay(500); } }
To monitor the console application now, run the following command at the command window:
dotnet-counters monitor -n EventCountersDemo --counters EventCountersDemo.CustomEventSource
The –counters parameter enables us to specify the particular event source we are interested in monitoring. In this example, the event source name is EventCountersDemo.CustomEventSource.
Figure 5 shows the output on execution of the preceeding command.
Consume EventCounters in .NET Core
EventCounters can be consumed using EventLKisteners and EventPipes. You can consume EventCounters in two different ways, in-proc and out-of-proc. You can consume a counter in an in-proc way using the EventListener API. To consume a counter in an out-of-proc way you should use an EventPipe.
The following code listing illustrates how you can consume EventCounters in an in-proc way using the EventListener class.
public sealed class CustomEventCounterListener : EventListener { protected override void OnEventSourceCreated(EventSource eventSource) { if (eventSource.Name == "EventCountersDemo.CustomEventSource") { var args = new Dictionary<string, string?> { ["EventCounterIntervalSec"] = "1" }; EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, args); } } protected override void OnEventWritten(EventWrittenEventArgs eventWrittenEventArgs) { if (!eventWrittenEventArgs.EventName.Equals("EventCounters")) { return; } if (eventWrittenEventArgs.Payload.First() is not IDictionary<string, object> payload) { return; } for (int i = 0; i < eventWrittenEventArgs.Payload.Count; ++i) { if (eventWrittenEventArgs.Payload[i] is IDictionary<string, object> eventPayload) { Console.WriteLine($"{payload["DisplayName"]} - {payload["Increment"]}"); } } } }
EventSources are typically used for diagnosing, logging, and tracking application behavior. An EventCounter, on the other hand, is better suited for performance profiling, optimization, and diagnostic purposes. The primary use of EventCounters is as a diagnostic or profiling tool, not as a tool for monitoring health and performance in production. To monitor applications deployed in production, you should use higher-level monitoring and logging systems.