diff --git a/docs/core/diagnostics/dotnet-counters.md b/docs/core/diagnostics/dotnet-counters.md index 904efc0b6e743..daac80bb25fa0 100644 --- a/docs/core/diagnostics/dotnet-counters.md +++ b/docs/core/diagnostics/dotnet-counters.md @@ -38,11 +38,11 @@ dotnet-counters [-h|--help] [--version] ## Commands | Command | -| --------------------------------------------------- | +|-----------------------------------------------------| | [dotnet-counters collect](#dotnet-counters-collect) | | [dotnet-counters list](#dotnet-counters-list) | | [dotnet-counters monitor](#dotnet-counters-monitor) | -| [dotnet-counters ps](#dotnet-counters-ps) | +| [dotnet-counters ps](#dotnet-counters-ps) | ## dotnet-counters collect @@ -101,18 +101,39 @@ dotnet-counters list [-h|--help] ```console > dotnet-counters list - - Showing well-known counters only. Specific processes may support additional counters. - System.Runtime - cpu-usage Amount of time the process has utilized the CPU (ms) - working-set Amount of working set used by the process (MB) - gc-heap-size Total heap size reported by the GC (MB) - gen-0-gc-count Number of Gen 0 GCs / sec - gen-1-gc-count Number of Gen 1 GCs / sec - gen-2-gc-count Number of Gen 2 GCs / sec - exception-count Number of Exceptions / sec +Showing well-known counters only. Specific processes may support additional counters. + +System.Runtime + cpu-usage Amount of time the process has utilized the CPU (ms) + working-set Amount of working set used by the process (MB) + gc-heap-size Total heap size reported by the GC (MB) + gen-0-gc-count Number of Gen 0 GCs / min + gen-1-gc-count Number of Gen 1 GCs / min + gen-2-gc-count Number of Gen 2 GCs / min + time-in-gc % time in GC since the last GC + gen-0-size Gen 0 Heap Size + gen-1-size Gen 1 Heap Size + gen-2-size Gen 2 Heap Size + loh-size LOH Heap Size + alloc-rate Allocation Rate + assembly-count Number of Assemblies Loaded + exception-count Number of Exceptions / sec + threadpool-thread-count Number of ThreadPool Threads + monitor-lock-contention-count Monitor Lock Contention Count + threadpool-queue-length ThreadPool Work Items Queue Length + threadpool-completed-items-count ThreadPool Completed Work Items Count + active-timer-count Active Timers Count + +Microsoft.AspNetCore.Hosting + requests-per-second Request rate + total-requests Total number of requests + current-requests Current number of requests + failed-requests Failed number of requests ``` +> [!NOTE] +> The `Microsoft.AspNetCore.Hosting` counters are displayed when there are processes identified that support these counters, for example; when an ASP.NET Core application is running on the host machine. + ## dotnet-counters monitor Displays periodically refreshing values of selected counters. @@ -166,7 +187,7 @@ dotnet-counters monitor [-h|--help] [-p|--process-id] [--refreshInterval] [count GC Heap Size (MB) 811 ``` -- Monitor `EventCounter` values from user-defined `EventSource`. For more information, see [Tutorial: How to measure performance for very frequent events using EventCounters](https://github.com/dotnet/runtime/blob/master/src/libraries/System.Diagnostics.Tracing/documentation/EventCounterTutorial.md). +- Monitor `EventCounter` values from user-defined `EventSource`. For more information, see [Tutorial: Measure performance using EventCounters in .NET Core](event-counter-perf.md). ```console > dotnet-counters monitor --process-id 1902 Samples-EventCounterDemos-Minimal diff --git a/docs/core/diagnostics/event-counter-perf.md b/docs/core/diagnostics/event-counter-perf.md new file mode 100644 index 0000000000000..784012893f059 --- /dev/null +++ b/docs/core/diagnostics/event-counter-perf.md @@ -0,0 +1,217 @@ +--- +title: Measure performance using EventCounters in .NET Core +description: In this tutorial, you'll learn how to measure performance using EventCounters. +ms.date: 08/07/2020 +ms.topic: tutorial +--- + +# Tutorial: Measure performance using EventCounters in .NET Core + +**This article applies to: ✔️** .NET Core 3.0 SDK and later versions + +In this tutorial, you'll learn how an can be used to measure performance with a high frequency of events. You can use the [available counters](event-counters.md#available-counters) published by various official .NET Core packages, third-party providers, or create your own metrics for monitoring. + +In this tutorial, you will: + +> [!div class="checklist"] +> +> - Implement an . +> - Monitor counters with [dotnet-counters](dotnet-counters.md). + +## Prerequisites + +The tutorial uses: + +- [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download/dotnet-core) or a later version. +- [dotnet-counters](dotnet-counters.md) to monitor event counters. +- A [sample debug target](https://docs.microsoft.com/samples/dotnet/samples/diagnostic-scenarios) app to diagnose. + +## Get the source + +The sample application will be used as a basis for monitoring. The [sample ASP.NET Core repository](https://docs.microsoft.com/samples/dotnet/samples/diagnostic-scenarios) is available from the samples browser. You download the zip file, extract it once downloaded, and open it in your favorite IDE. Build and run the application to ensure that it works properly, then stop the application. + +## Implement an EventSource + +For events that happen every few milliseconds, you'll want the overhead per event to be low (less than a millisecond). Otherwise, the impact on performance will be significant. Logging an event means you're going to write something to disk. If the disk is not fast enough, you will lose events. You need a solution other than logging the event itself. + +When dealing with a large number of events, knowing the measure per event is not useful either. Most of the time all you need is just some statistics out of it. So you could get the statistics within the process itself and then write an event once in a while to report the statistics, that's what will do. + +Below is an example of how to implement an . Create a new file named *MinimalEventCounterSource.cs* and use the code snippet as its source: + +:::code language="csharp" source="snippets/EventCounters/MinimalEventCounterSource.cs"::: + +The line is the part and is not part of , it was written to show that you can log a message together with the event counter. + +## Add an action filter + +The sample source code is an ASP.NET Core project. You can add an [action filter](/aspnet/core/mvc/controllers/filters#action-filters) globally that will log the total request time. Create a new file named *LogRequestTimeFilterAttribute.cs*, and use the following code: + +```csharp +using System.Diagnostics; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace DiagnosticScenarios +{ + public class LogRequestTimeFilterAttribute : ActionFilterAttribute + { + readonly Stopwatch _stopwatch = new Stopwatch(); + + public override void OnActionExecuting(ActionExecutingContext context) => _stopwatch.Start(); + + public override void OnActionExecuted(ActionExecutedContext context) + { + _stopwatch.Stop(); + + MinimalEventCounterSource.Log.Request( + context.HttpContext.Request.GetDisplayUrl(), _stopwatch.ElapsedMilliseconds); + } + } +} +``` + +The action filter starts a as the request begins, and stops after it has completed, capturing the elapsed time. The total milliseconds is logged to the `MinimalEventCounterSource` singleton instance. In order for this filter to be applied, you need to add it to the filters collection. In the *Startup.cs* file, update the `ConfigureServices` method in include this filter. + +```csharp +public void ConfigureServices(IServiceCollection services) => + services.AddControllers(options => options.Filters.Add()) + .AddNewtonsoftJson(); +``` + +## Monitor event counter + +With the implementation on an and the custom action filter, build and launch the application. +You logged the metric to the , but unless you access the statistics from of it, it is not useful. To get the statistics, you need to enable the by creating a timer that fires as frequently as you want the events, as well as a listener to capture the events. To do that, you can use [dotnet-counters](dotnet-counters.md). + +Use the [dotnet-counters ps](dotnet-counters.md#dotnet-counters-ps) command to display a list of .NET processes that can be monitored. + +```console +dotnet-counters ps +``` + +Using the process identifier from the output of the `dotnet-counters ps` command, you can start monitoring the event counter with the following `dotnet-counters monitor` command: + +```console +dotnet-counters monitor --process-id 2196 Sample.EventCounter.Minimal Microsoft.AspNetCore.Hosting[total-requests,requests-per-second] System.Runtime[cpu-usage] +``` + +While the `dotnet-counters monitor` command is running, hold F5 on the browser to start issuing continuous requests to the `https://localhost:5001/api/values` endpoint. After a few seconds stop by pressing q + +```console +Press p to pause, r to resume, q to quit. + Status: Running + +[Microsoft.AspNetCore.Hosting] + Request Rate / 1 sec 9 + Total Requests 134 +[System.Runtime] + CPU Usage (%) 13 +[Sample.EventCounter.Minimal] + Request Processing Time (ms) 34.5 +``` + +The `dotnet-counters monitor` command is great for active monitoring. However, you may want to collect these diagnostic metrics for post processing and analysis. For that, use the `dotnet-counters collect` command. The `collect` switch command is similar to the `monitor` command, but accepts a few additional parameters. You can specify the desired output file name and format. For a JSON file named *diagnostics.json* use the following command: + +```console +dotnet-counters collect --process-id 2196 --format json -o diagnostics.json Sample.EventCounter.Minimal Microsoft.AspNetCore.Hosting[total-requests,requests-per-second] System.Runtime[cpu-usage] +``` + +Again, while the command is running, hold F5 on the browser to start issuing continuous requests to the `https://localhost:5001/api/values` endpoint. After a few seconds stop by pressing q. The *diagnostics.json* file is written. The JSON file that is written is not indented, however; for readability it is indented here. + +```json +{ + "TargetProcess": "DiagnosticScenarios", + "StartTime": "8/5/2020 3:02:45 PM", + "Events": [ + { + "timestamp": "2020-08-05 15:02:47Z", + "provider": "System.Runtime", + "name": "CPU Usage (%)", + "counterType": "Metric", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:47Z", + "provider": "Microsoft.AspNetCore.Hosting", + "name": "Request Rate / 1 sec", + "counterType": "Rate", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:47Z", + "provider": "Microsoft.AspNetCore.Hosting", + "name": "Total Requests", + "counterType": "Metric", + "value": 134 + }, + { + "timestamp": "2020-08-05 15:02:47Z", + "provider": "Sample.EventCounter.Minimal", + "name": "Request Processing Time (ms)", + "counterType": "Metric", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:47Z", + "provider": "System.Runtime", + "name": "CPU Usage (%)", + "counterType": "Metric", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:48Z", + "provider": "Microsoft.AspNetCore.Hosting", + "name": "Request Rate / 1 sec", + "counterType": "Rate", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:48Z", + "provider": "Microsoft.AspNetCore.Hosting", + "name": "Total Requests", + "counterType": "Metric", + "value": 134 + }, + { + "timestamp": "2020-08-05 15:02:48Z", + "provider": "Sample.EventCounter.Minimal", + "name": "Request Processing Time (ms)", + "counterType": "Metric", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:48Z", + "provider": "System.Runtime", + "name": "CPU Usage (%)", + "counterType": "Metric", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:50Z", + "provider": "Microsoft.AspNetCore.Hosting", + "name": "Request Rate / 1 sec", + "counterType": "Rate", + "value": 0 + }, + { + "timestamp": "2020-08-05 15:02:50Z", + "provider": "Microsoft.AspNetCore.Hosting", + "name": "Total Requests", + "counterType": "Metric", + "value": 134 + }, + { + "timestamp": "2020-08-05 15:02:50Z", + "provider": "Sample.EventCounter.Minimal", + "name": "Request Processing Time (ms)", + "counterType": "Metric", + "value": 0 + } + ] +} +``` + +## Next steps + +> [!div class="nextstepaction"] +> [EventCounters](event-counters.md) diff --git a/docs/core/diagnostics/event-counters.md b/docs/core/diagnostics/event-counters.md new file mode 100644 index 0000000000000..9029d40143c92 --- /dev/null +++ b/docs/core/diagnostics/event-counters.md @@ -0,0 +1,199 @@ +--- +title: EventCounters in .NET Core +description: In this article, you'll learn what EventCounters are, how to implement them, and how to consume them. +ms.date: 08/07/2020 +--- + +# EventCounters in .NET Core + +**This article applies to: ✔️** .NET Core 3.0 SDK and later versions + +EventCounters are .NET Core APIs used for lightweight, cross-platform, and near real-time performance metric collection. EventCounters were added as a cross-platform alternative to the "performance counters" of the .NET Framework on Windows. In this article you'll learn what EventCounters are, how to implement them, and how to consume them. + +The .NET Core runtime and a few .NET libraries publish basic diagnostics information using EventCounters starting in .NET Core 3.0. Apart from the EventCounters that are provided by the .NET runtime, you may choose to implement your own EventCounters. EventCounters can be used to track various metrics. + +EventCounters live as a part of an , and are automatically pushed to listener tools on a regular basis. Like all other events on an , they can be consumed both in-proc and out-of-proc via and EventPipe. This article focuses on the cross-platform capabilities of EventCounters, and intentionally excludes PerfView and ETW (Event Tracing for Windows) - although both can be used with EventCounters. + +![EventCounters in-proc and out-of-proc diagram image](media/event-counters.svg) + +[!INCLUDE [available-counters](includes/available-counters.md)] + +## EventCounter API overview + +There are two primary categories of counters. Some counters are for "rate" values, such as total number of exceptions, total number of GCs, and total number of requests. Other counters are "snapshot" values, such as heap usage, CPU usage, and working set size. Within each of these categories of counters, there are two types of counters that vary by how they get their value. Polling counters retrieve their value via a callback, and non-polling counters have their values directly set on the counter instance. + +The counters are represented by the following implementations: + +* +* +* +* + +An event listener specifies how long measurement intervals are. At the end of each interval a value is transmitted to the listener for each counter. The implementations of a counter determine what APIs and calculations are used to produce the value each interval. + +1. The records a set of values. The method adds a new value to the set. With each interval, a statistical summary for the set is computed, such as the min, max, and mean. The [dotnet-counters](dotnet-counters.md) tool will always display the mean value. The is useful to describe a discrete set of operations. Common usage may include monitoring the average size in bytes of recent IO operations, or the average monetary value of a set of financial transactions. + +1. The records a running total for each time interval. The method adds to the total. For example, if `Increment()` is called three times during one interval with values `1`, `2`, and `5`, then the running total of `8` will be reported as the counter value for this interval. The [dotnet-counters](dotnet-counters.md) tool will display the rate as the recorded total / time. The is useful to measure how frequently an action is occurring, such as the number of requests processed per second. + +1. The uses a callback to determine the value that is reported. With each time interval, the user provided callback function is invoked and the return value is used as the counter value. A can be used to query a metric from an external source, for example getting the current free bytes on a disk. It can also be used to report custom statistics that can be computed on demand by an application. Examples include reporting the 95th percentile of recent request latencies, or the current hit or miss ratio of a cache. + +1. The uses a callback to determine the reported increment value. With each time interval, the callback is invoked, and then the difference between the current invocation, and the last invocation is the reported value. The [dotnet-counters](dotnet-counters.md) tool will always display the difference as a rate, the reported value / time. This counter is useful when it isn't feasible to call an API on each occurrence, but it's possible to query the total number of occurrences. For example, you could report the number of bytes written to a file per second, even without a notification each time a byte is written. + +## Implement an EventSource + +The following code implements a sample exposed as the named `"Sample.EventCounter.Minimal"` provider. This source contains an representing request processing time. Such a counter has a name (that is, its unique ID in the source) and a display name, both used by listener tools such as [dotnet-counter](dotnet-counters.md). + +:::code language="csharp" source="snippets/EventCounters/MinimalEventCounterSource.cs"::: + +You use `dotnet-counters ps` to display a list of .NET processes that can be monitored: + +```console +dotnet-counters ps + 1398652 dotnet C:\Program Files\dotnet\dotnet.exe + 1399072 dotnet C:\Program Files\dotnet\dotnet.exe + 1399112 dotnet C:\Program Files\dotnet\dotnet.exe + 1401880 dotnet C:\Program Files\dotnet\dotnet.exe + 1400180 sample-counters C:\sample-counters\bin\Debug\netcoreapp3.1\sample-counters.exe +``` + +Pass the name to the `counter_list` switch to start monitoring your counter: + +```console +dotnet-counters monitor --process-id 1400180 Sample.EventCounter.Minimal +``` + +The following example shows monitor output: + +```console +Press p to pause, r to resume, q to quit. + Status: Running + +[Samples-EventCounterDemos-Minimal] + Request Processing Time (ms) 0.445 +``` + +Press q to stop the monitoring command. + +#### Conditional counters + +When implementing an , the containing counters can be conditionally instantiated when the method is called with a value of `EventCommand.Enable`. To safely instantiate a counter instance only if it is `null`, use the [null-coalescing assignment operator](../../csharp/language-reference/operators/null-coalescing-operator.md). Additionally, custom methods can evaluate the method to determine whether or not the current event source is enabled. + +:::code language="csharp" source="snippets/EventCounters/ConditionalEventCounterSource.cs"::: + +> [!TIP] +> Conditional counters are counters that are conditionally instantiated, a micro-optimization. The runtime adopts this pattern for scenarios where counters are normally not used, to save a fraction of a millisecond. + +### .NET Core runtime example counters + +There are many great example implementations in the .NET Core runtime. Here is the runtime implementation for the counter that tracks the working set size of the application. + +```csharp +var workingSetCounter = new PollingCounter( + "working-set", + this, + () => (double)(Environment.WorkingSet / 1_000_000)) +{ + DisplayName = "Working Set", + DisplayUnits = "MB" +}; +``` + +The reports the current amount of physical memory mapped to the process (working set) of the app, since it captures a metric at a moment in time. The callback for polling a value is the provided lambda expression, which is just a call to the API. and are optional properties that can be set to help the consumer side of the counter to display the value more clearly. For example, [dotnet-counters](dotnet-counters.md) uses these properties to display the more display-friendly version of the counter names. + +> [!IMPORTANT] +> The `DisplayName` properties are not localized. + +For the , and the , nothing else needs to be done. They both poll the values themselves at an interval requested by the consumer. + +Here is an example of a runtime counter implemented using . + +```csharp +var monitorContentionCounter = new IncrementingPollingCounter( + "monitor-lock-contention-count", + this, + () => Monitor.LockContentionCount +) +{ + DisplayName = "Monitor Lock Contention Count", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) +}; +``` + +The uses the API to report the increment of the total lock contention count. The property is optional, but when used it can provide a hint for what time interval the counter is best displayed at. For example, the lock contention count is best displayed as _count per second_, so its is set to one second. The display rate can be adjusted for different types of rate counters. + +> [!NOTE] +> The is _not_ used by [dotnet-counters](dotnet-counters.md), and event listeners are not required to use it. + +There are more counter implementations to use as a reference in the [.NET runtime](https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs) repo. + +## Concurrency + +> [!TIP] +> The EventCounters API does not guarantee thread safety. When the delegates passed to or instances are called by multiple threads, it's your responsibility to guarantee the delegates' thread-safety. + +For example, consider the following to keep track of requests. + +:::code language="csharp" source="snippets/EventCounters/RequestEventSource.cs"::: + +The `AddRequest()` method can be called from a request handler, and the `RequestRateCounter` polls the value at the interval specified by the consumer of the counter. However, the `AddRequest()` method can be called by multiple threads at once, putting a race condition on `_requestCount`. A thread-safe alternative way to increment the `_requestCount` is to use . + +```csharp +public void AddRequest() => Interlocked.Increment(ref _requestCount); +``` + +## Consume EventCounters + +There are two primary ways of consuming EventCounters, either in-proc, or out-of-proc. The consumption of EventCounters can be distinguished into three layers of various consuming technologies. + +- Transporting events in a raw stream via ETW or EventPipe: + - ETW APIs come with the Windows OS, and EventPipe is accessible as a [.NET API](https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/diagnostics-client-library.md#1-attaching-to-a-process-and-dumping-out-all-the-runtime-gc-events-in-real-time-to-the-console), or the diagnostic [IPC protocol](https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/ipc-protocol.md). +- Decoding the binary event stream into events: + - The [TraceEvent library](https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent) handles both ETW and EventPipe stream formats. +- Command-line and GUI tools: + - Tools like PerfView (ETW or EventPipe), dotnet-counters (EventPipe only), and dotnet-monitor (EventPipe only). + +### Consume out-of-proc + +Consuming EventCounters out-of-proc is a very common approach. You can use [dotnet-counters](dotnet-counters.md) to consume them in a cross-platform manner via an EventPipe. The `dotnet-counters` tool is a cross-platform dotnet CLI global tool that can be used to monitor the counter values. To find out how to use `dotnet-counters` to monitor your counters, see [dotnet-counters](dotnet-counters.md), or work through the [Measure performance using EventCounters](event-counter-perf.md) tutorial. + +#### dotnet-trace + +The `dotnet-trace` tool can be used to consume the counter data through an EventPipe. Here is an example using `dotnet-trace` to collect counter data. + +```console +dotnet-trace collect --process-id Sample.EventCounter.Minimal:0:0:EventCounterIntervalSec=1 +``` + +For more information on how to collect counter values over time, see the [dotnet-trace](dotnet-trace.md#use-dotnet-trace-to-collect-counter-values-over-time) documentation. + +#### Azure Application Insights + +EventCounters can be consumed by Azure Monitor, specifically Azure Application Insights. Counters can be added and removed, and you're free to specify custom counters, or well-known counters. For more information, see [Customizing counters to be collected](/azure/azure-monitor/app/eventcounters#customizing-counters-to-be-collected). + +#### dotnet-monitor + +The `dotnet-monitor` is an experimental tool that makes it easier to get access to diagnostics information in a .NET process. For more information, see [Introducing dotnet-monitor, an experimental tool](https://devblogs.microsoft.com/dotnet/introducing-dotnet-monitor). + +### Consume in-proc + +You can consume the counter values via the API. An is an in-proc way of consuming any events written by all instances of an in your application. For more information on how to use the `EventListener` API, see . + +First, the that produces the counter value needs to be enabled. Override the method to get a notification when an is created, and if this is the correct with your EventCounters, then you can call on it. Here is an example override: + +:::code language="csharp" source="snippets/EventCounters/SimpleEventListener.cs" range="16-27"::: + +#### Sample code + +Here is a sample class that prints out all the counter names and values from the .NET runtime's , for publishing its internal counters (`System.Runtime`) at some interval. + +:::code language="csharp" source="snippets/EventCounters/SimpleEventListener.cs"::: + +As shown above, you _must_ make sure the `"EventCounterIntervalSec"` argument is set in the `filterPayload` argument when calling . Otherwise the counters will not be able to flush out values since it doesn't know at which interval it should be getting flushed out. + +## See also + +- [dotnet-counters](dotnet-counters.md) +- [dotnet-trace](dotnet-trace.md) +- +- +- diff --git a/docs/core/diagnostics/includes/available-counters.md b/docs/core/diagnostics/includes/available-counters.md new file mode 100644 index 0000000000000..c9a061c1d6bf2 --- /dev/null +++ b/docs/core/diagnostics/includes/available-counters.md @@ -0,0 +1,69 @@ +## Available counters + +Throughout various .NET packages, basic metrics on Garbage Collection (GC), Just-in-Time (JIT), assemblies, exceptions, threading, networking, and web requests are published using EventCounters. + +### "System.Runtime" counters + +The following counters are published as part of .NET runtime, and are maintained in the [`RuntimeEventSource.cs`](https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs). + +| Counter | Description | +|--|--| +| :::no-loc text="% Time in GC since last GC"::: (`time-in-gc`) | The percent of time in GC since the last GC | +| :::no-loc text="Allocation Rate"::: (`alloc-rate`) | The rate of allocation in bytes | +| :::no-loc text="CPU Usage"::: (`cpu-usage`) | The percent of CPU usage | +| :::no-loc text="Exception Count"::: (`exception-count`) | The number of exceptions that have occurred | +| :::no-loc text="GC Heap Size"::: (`gc-heap-size`) | The number of bytes thought to be allocated based on | +| :::no-loc text="Gen 0 GC Count"::: (`gen-0-gc-count`) | The number of times GC has occurred for Gen 0 | +| :::no-loc text="Gen 0 Size"::: (`gen-0-size`) | The number of bytes for Gen 0 GC | +| :::no-loc text="Gen 1 GC Count"::: (`gen-1-gc-count`) | The number of times GC has occurred for Gen 1 | +| :::no-loc text="Gen 1 Size"::: (`gen-1-size`) | The number of bytes for Gen 1 GC | +| :::no-loc text="Gen 2 GC Count"::: (`gen-2-gc-count`) | The number of times GC has occurred for Gen 2 | +| :::no-loc text="Gen 2 Size"::: (`gen-2-size`) | The number of bytes for Gen 2 GC | +| :::no-loc text="LOH Size"::: (`loh-size`) | The number of bytes for Gen 3 GC | +| :::no-loc text="Monitor Lock Contention Count"::: (`monitor-lock-contention-count`) | The number of times there was contention when trying to take the monitor's lock, based on | +| :::no-loc text="Number of Active Timers"::: (`active-timer-count`) | The number of instances that are currently active, based on | +| :::no-loc text="Number of Assemblies Loaded"::: (`assembly-count`) | The number of instances loaded into a process at a point in time | +| :::no-loc text="ThreadPool Completed Work Item Count"::: (`threadpool-completed-items-count`) | The number of work items that have been processed so far in the | +| :::no-loc text="ThreadPool Queue Length"::: (`threadpool-queue-length`) | The number of work items that are currently queued to be processed in the | +| :::no-loc text="ThreadPool Thread Count"::: (`threadpool-thread-count`) | The number of thread pool threads that currently exist in the , based on | +| :::no-loc text="Working Set"::: (`working-set`) | The amount of physical memory mapped to the process context at a point in time base on | + +### "Microsoft.AspNetCore.Hosting" counters + +The following counters are published as part of [ASP.NET Core](/aspnet/core) and are maintained in [`HostingEventSource.cs`](https://github.com/dotnet/aspnetcore/blob/master/src/Hosting/Hosting/src/Internal/HostingEventSource.cs). + +| Counter | Description | +|--|--| +| :::no-loc text="Current Requests"::: (`current-requests`) | The total number of requests the have started, but not yet stopped | +| :::no-loc text="Failed Requests"::: (`failed-requests`) | The total number of failed requests that have occurred for the life of the app | +| :::no-loc text="Request Rate"::: (`requests-per-second`) | The number of requests that occur per second | +| :::no-loc text="Total Requests"::: (`total-requests`) | The total number of requests that have occurred for the life of the app | + +### "Microsoft.AspNetCore.Http.Connections" counters + +The following counters are published as part of [ASP.NET Core SignalR](/aspnet/core/signalr/introduction) and are maintained in [`HttpConnectionsEventSource.cs`](https://github.com/dotnet/aspnetcore/blob/master/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionsEventSource.cs). + +| Counter | Description | +|--|--| +| :::no-loc text="Average Connection Duration"::: (`connections-duration`) | The average duration of a connection in milliseconds | +| :::no-loc text="Current Connections"::: (`current-connections`) | The number of active connections that have started, but not yet stopped | +| :::no-loc text="Total Connections Started"::: (`connections-started`) | The total number of connections that have started | +| :::no-loc text="Total Connections Stopped"::: (`connections-stopped`) | The total number of connections that have stopped | +| :::no-loc text="Total Connections Timed Out"::: (`connections-timed-out`) | The total number of connections that have timed out | + +### "Microsoft-AspNetCore-Server-Kestrel" counters + +The following counters are published as part of the [ASP.NET Core Kestrel web server](/aspnet/core/fundamentals/servers/kestrel) and are maintained in [`KestrelEventSource.cs`](https://github.com/dotnet/aspnetcore/blob/master/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelEventSource.cs). + +| Counter | Description | +|--|--| +| :::no-loc text="Connection Queue Length"::: (`connection-queue-length`) | The current length of the connection queue | +| :::no-loc text="Connection Rate"::: (`connections-per-second`) | The number of connections per second to the web server | +| :::no-loc text="Current Connections"::: (`current-connections`) | The current number of active connections to the web server | +| :::no-loc text="Current TLS Handshakes"::: (`current-tls-handshakes`) | The current number of TLS handshakes | +| :::no-loc text="Current Upgraded Requests (WebSockets)"::: (`current-upgraded-requests`) | The current number of upgraded requests (WebSockets) | +| :::no-loc text="Failed TLS Handshakes"::: (`failed-tls-handshakes`) | The total number of failed TLS handshakes | +| :::no-loc text="Request Queue Length"::: (`request-queue-length`) | The current length of the request queue | +| :::no-loc text="TLS Handshake Rate"::: (`tls-handshakes-per-second`) | The number of TLS handshakes per second | +| :::no-loc text="Total Connections"::: (`total-connections`) | The total number of connections to the web server | +| :::no-loc text="Total TLS Handshakes"::: (`total-tls-handshakes`) | The total number of TLS handshakes with the web server | diff --git a/docs/core/diagnostics/media/event-counters.svg b/docs/core/diagnostics/media/event-counters.svg new file mode 100644 index 0000000000000..f23ddf4c18ebb --- /dev/null +++ b/docs/core/diagnostics/media/event-counters.svg @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + Page-1 + + + Rectangle.1003 + + + + + + + Rounded Rectangle + + + + + + + + + + + + + + + + + + Rounded Rectangle.2 + + + + + + + + + + + + + + + + + + Rounded Rectangle.3 + + + + + + + + + + + + + + + + + + Sheet.4 + EventSource + + + + EventSource + + Rounded Rectangle.5 + EventCounter + + + + + + + + + + + + + + + + + + + + + + EventCounter + + Rounded Rectangle.6 + EventCounter + + + + + + + + + + + + + + + + + + + + + + EventCounter + + Rounded Rectangle.7 + EventCounter + + + + + + + + + + + + + + + + + + + + + + EventCounter + + Sheet.1000 + + Rectangle + + + + + + + Sheet.15 + + Rounded Rectangle.8 + dotnet-counters + + + + + + + + + + + + + + + + + + dotnet-counters + + Rounded Rectangle.9 + dotnet-trace + + + + + + + + + + + + + + + + + + dotnet-trace + + Striped Arrow + EventPipe + + + + + + + EventPipe + + + Sheet.20 + Out-of-proc + + + + Out-of-proc + + + Sheet.1002 + + Rectangle.16 + + + + + + + Sheet.21 + In-proc + + + + In-proc + + Sheet.1001 + + Rounded Rectangle.8 + EventListener + + + + + + + + + + + + + + + + + + EventListener + + Striped Arrow + + + + + + + + + diff --git a/docs/core/diagnostics/media/event-counters.vsdx b/docs/core/diagnostics/media/event-counters.vsdx new file mode 100644 index 0000000000000..3f7760cedcea1 Binary files /dev/null and b/docs/core/diagnostics/media/event-counters.vsdx differ diff --git a/docs/core/diagnostics/media/flamegraph.jpg b/docs/core/diagnostics/media/flamegraph.jpg index f7bfe8a49e919..00a5b06d3310d 100644 Binary files a/docs/core/diagnostics/media/flamegraph.jpg and b/docs/core/diagnostics/media/flamegraph.jpg differ diff --git a/docs/core/diagnostics/media/perfview.jpg b/docs/core/diagnostics/media/perfview.jpg index 82b29d352a855..f31647962cc6e 100644 Binary files a/docs/core/diagnostics/media/perfview.jpg and b/docs/core/diagnostics/media/perfview.jpg differ diff --git a/docs/core/diagnostics/snippets/EventCounters/ConditionalEventCounterSource.cs b/docs/core/diagnostics/snippets/EventCounters/ConditionalEventCounterSource.cs new file mode 100644 index 0000000000000..ab797b26b6880 --- /dev/null +++ b/docs/core/diagnostics/snippets/EventCounters/ConditionalEventCounterSource.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.Tracing; + +[EventSource(Name = "Sample.EventCounter.Conditional")] +public sealed class ConditionalEventCounterSource : EventSource +{ + public static readonly ConditionalEventCounterSource Log = new ConditionalEventCounterSource(); + + private EventCounter _requestCounter; + + private ConditionalEventCounterSource() { } + + protected override void OnEventCommand(EventCommandEventArgs args) + { + if (args.Command == EventCommand.Enable) + { + _requestCounter ??= new EventCounter("request-time", this) + { + DisplayName = "Request Processing Time", + DisplayUnits = "ms" + }; + } + } + + public void Request(string url, float elapsedMilliseconds) + { + if (IsEnabled()) + { + _requestCounter?.WriteMetric(elapsedMilliseconds); + } + } + + protected override void Dispose(bool disposing) + { + _requestCounter?.Dispose(); + _requestCounter = null; + + base.Dispose(disposing); + } +} diff --git a/docs/core/diagnostics/snippets/EventCounters/EventCounters.csproj b/docs/core/diagnostics/snippets/EventCounters/EventCounters.csproj new file mode 100644 index 0000000000000..4388898924936 --- /dev/null +++ b/docs/core/diagnostics/snippets/EventCounters/EventCounters.csproj @@ -0,0 +1,8 @@ + + + + Library + netstandard2.1 + + + diff --git a/docs/core/diagnostics/snippets/EventCounters/MinimalEventCounterSource.cs b/docs/core/diagnostics/snippets/EventCounters/MinimalEventCounterSource.cs new file mode 100644 index 0000000000000..064c69b1facf6 --- /dev/null +++ b/docs/core/diagnostics/snippets/EventCounters/MinimalEventCounterSource.cs @@ -0,0 +1,30 @@ +using System.Diagnostics.Tracing; + +[EventSource(Name = "Sample.EventCounter.Minimal")] +public sealed class MinimalEventCounterSource : EventSource +{ + public static readonly MinimalEventCounterSource Log = new MinimalEventCounterSource(); + + private EventCounter _requestCounter; + + private MinimalEventCounterSource() => + _requestCounter = new EventCounter("request-time", this) + { + DisplayName = "Request Processing Time", + DisplayUnits = "ms" + }; + + public void Request(string url, float elapsedMilliseconds) + { + WriteEvent(1, url, elapsedMilliseconds); + _requestCounter?.WriteMetric(elapsedMilliseconds); + } + + protected override void Dispose(bool disposing) + { + _requestCounter?.Dispose(); + _requestCounter = null; + + base.Dispose(disposing); + } +} diff --git a/docs/core/diagnostics/snippets/EventCounters/RequestEventSource.cs b/docs/core/diagnostics/snippets/EventCounters/RequestEventSource.cs new file mode 100644 index 0000000000000..3506c39770796 --- /dev/null +++ b/docs/core/diagnostics/snippets/EventCounters/RequestEventSource.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.Tracing; + +public class RequestEventSource : EventSource +{ + public static readonly RequestEventSource Log = new RequestEventSource(); + + private IncrementingPollingCounter _requestRateCounter; + private int _requestCount = 0; + + private RequestEventSource() => + _requestRateCounter = new IncrementingPollingCounter("request-rate", this, () => _requestCount) + { + DisplayName = "Request Rate", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + public void AddRequest() => ++ _requestCount; + + protected override void Dispose(bool disposing) + { + _requestRateCounter?.Dispose(); + _requestRateCounter = null; + + base.Dispose(disposing); + } +} diff --git a/docs/core/diagnostics/snippets/EventCounters/SimpleEventListener.cs b/docs/core/diagnostics/snippets/EventCounters/SimpleEventListener.cs new file mode 100644 index 0000000000000..b6c07cb5eda46 --- /dev/null +++ b/docs/core/diagnostics/snippets/EventCounters/SimpleEventListener.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; + +public class SimpleEventListener : EventListener +{ + private readonly int _intervalSec; + + public int EventCount { get; private set; } + + public SimpleEventListener(int intervalSec = 1) => + _intervalSec = intervalSec <= 0 + ? throw new ArgumentException("Interval must be at least 1 second.", nameof(intervalSec)) + : intervalSec; + + protected override void OnEventSourceCreated(EventSource source) + { + if (!source.Name.Equals("System.Runtime")) + { + return; + } + + EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary() + { + ["EventCounterIntervalSec"] = _intervalSec.ToString() + }); + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + if (!eventData.EventName.Equals("EventCounters")) + { + return; + } + + for (int i = 0; i < eventData.Payload.Count; ++ i) + { + if (eventData.Payload[i] is IDictionary eventPayload) + { + var (counterName, counterValue) = GetRelevantMetric(eventPayload); + Console.WriteLine($"{counterName} : {counterValue}"); + } + } + } + + private static (string counterName, string counterValue) GetRelevantMetric( + IDictionary eventPayload) + { + var counterName = ""; + var counterValue = ""; + + if (eventPayload.TryGetValue("DisplayName", out object displayValue)) + { + counterName = displayValue.ToString(); + } + if (eventPayload.TryGetValue("Mean", out object value) || + eventPayload.TryGetValue("Increment", out value)) + { + counterValue = value.ToString(); + } + + return (counterName, counterValue); + } +} diff --git a/docs/core/toc.yml b/docs/core/toc.yml index f4a0b32a8766a..e700939334aed 100644 --- a/docs/core/toc.yml +++ b/docs/core/toc.yml @@ -290,6 +290,8 @@ items: href: diagnostics/index.md - name: Managed debuggers href: diagnostics/managed-debuggers.md + - name: EventCounters + href: diagnostics/event-counters.md - name: Logging and tracing href: diagnostics/logging-tracing.md - name: .NET Core CLI global tools @@ -302,6 +304,8 @@ items: href: diagnostics/dotnet-trace.md - name: .NET Core diagnostics tutorials items: + - name: Get perf metrics with EventCounters + href: diagnostics/event-counter-perf.md - name: Debug a memory leak href: diagnostics/debug-memory-leak.md - name: Debug high CPU usage