Tutoriel : Mesurer les performances à l’aide d’EventCounters dans .NET Core

Cet article s’applique à : ✔️ SDK .NET Core 3.0 et versions ultérieures

Dans ce tutoriel, vous allez découvrir comment un EventCounter peut être utilisé pour mesurer les performances avec une fréquence élevée d’événements. Vous pouvez utiliser les compteurs disponibles publiés par différents packages .NET Core officiels, fournisseurs tiers, ou créer vos propres métriques pour la surveillance.

Durant ce tutoriel, vous allez effectuer les opérations suivantes :

Prérequis

Le didacticiel utilise :

Obtenir la source

L’exemple d’application sera utilisé comme base pour la surveillance. L’exemple ASP.NET Core référentiel est disponible à partir du navigateur d’exemples. Vous téléchargez le fichier zip, extrayez-le une fois téléchargé et ouvrez-le dans votre IDE favori. Générez et exécutez l’application pour vous assurer qu’elle fonctionne correctement, puis arrêtez l’application.

Implémenter un EventSource

Pour les événements qui se produisent toutes les quelques millisecondes, vous souhaitez que la surcharge par événement soit faible (moins d’une milliseconde). Sinon, l’impact sur les performances sera important. La journalisation d’un événement signifie que vous allez écrire quelque chose sur le disque. Si le disque n’est pas assez rapide, vous perdrez des événements. Vous avez besoin d’une solution autre que la journalisation de l’événement lui-même.

Lorsque vous traitez un grand nombre d’événements, il n’est pas non plus utile de connaître la mesure par événement. La plupart du temps, tout ce dont vous avez besoin est juste quelques statistiques à partir de celui-ci. Ainsi, vous pouvez obtenir les statistiques dans le processus lui-même, puis écrire un événement de temps en temps pour signaler les statistiques, c’est ce que EventCounter va faire.

Voici un exemple d’implémentation d’un System.Diagnostics.Tracing.EventSource. Créez un fichier nommé MinimalEventCounterSource.cs et utilisez l’extrait de code comme source :

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, long elapsedMilliseconds)
    {
        WriteEvent(1, url, elapsedMilliseconds);
        _requestCounter?.WriteMetric(elapsedMilliseconds);
    }

    protected override void Dispose(bool disposing)
    {
        _requestCounter?.Dispose();
        _requestCounter = null;

        base.Dispose(disposing);
    }
}

La EventSource.WriteEvent ligne est la EventSource partie et ne fait pas partie de EventCounter, elle a été écrite pour montrer que vous pouvez enregistrer un message avec le compteur d’événements.

Ajouter un filtre d’action

L’exemple de code source est un projet ASP.NET Core. Vous pouvez ajouter un filtre d’action globalement qui journalisera la durée totale de la requête. Créez un fichier nommé LogRequestTimeFilterAttribute.cs et utilisez le code suivant :

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);
        }
    }
}

Le filtre d’action lance Stopwatch au début de la requête et s’arrête une fois celle-ci terminée, capturant ainsi la durée calendaire. Le nombre total de millisecondes est enregistré dans l’instance singleton MinimalEventCounterSource. Pour que ce filtre soit appliqué, vous devez l’ajouter à la collection de filtres. Dans le fichier Startup.cs, mettez à jour la ConfigureServices méthode dans inclure ce filtre.

public void ConfigureServices(IServiceCollection services) =>
    services.AddControllers(options => options.Filters.Add<LogRequestTimeFilterAttribute>())
            .AddNewtonsoftJson();

Surveiller le compteur d’événements

Avec l’implémentation sur un EventSource et le filtre d’action personnalisé, générez et lancez l’application. Vous avez enregistré la métrique dans EventCounter, mais à moins d’accéder aux statistiques à partir de celle-ci, elle n’est pas utile. Pour obtenir les statistiques, vous devez activer le EventCounter en créant un minuteur qui se déclenche aussi fréquemment que vous le souhaitez, ainsi qu’un écouteur pour capturer les événements. Pour ce faire, vous pouvez utiliser dotnet-counters.

Utilisez la commande dotnet-counters ps pour afficher la liste des processus .NET pouvant être surveillés.

dotnet-counters ps

À l’aide de l’identificateur de processus de la sortie de la dotnet-counters ps commande, vous pouvez commencer à surveiller le compteur d’événements avec la commande suivante dotnet-counters monitor :

dotnet-counters monitor --process-id 2196 --counters Sample.EventCounter.Minimal,Microsoft.AspNetCore.Hosting[total-requests,requests-per-second],System.Runtime[cpu-usage]

Pendant l’exécution de la dotnet-counters monitor commande, maintenez F5 sur le navigateur pour commencer à émettre des demandes continues au point de https://localhost:5001/api/values terminaison. Après quelques secondes, arrêtez en appuyant sur q

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

La dotnet-counters monitor commande est idéale pour la surveillance active. Toutefois, vous souhaiterez peut-être collecter ces métriques de diagnostic à des fins de post-traitement et d’analyse. Pour cela, utilisez la dotnet-counters collect commande. La collect commande switch est similaire à la monitor commande , mais accepte quelques paramètres supplémentaires. Vous pouvez spécifier le nom et le format de fichier de sortie souhaités. Pour un fichier JSON nommé diagnostics.json, utilisez la commande suivante :

dotnet-counters collect --process-id 2196 --format json -o diagnostics.json --counters Sample.EventCounter.Minimal,Microsoft.AspNetCore.Hosting[total-requests,requests-per-second],System.Runtime[cpu-usage]

Là encore, pendant l’exécution de la commande, maintenez F5 sur le navigateur pour commencer à émettre des demandes continues au point de https://localhost:5001/api/values terminaison. Après quelques secondes, arrêtez en appuyant sur q. Le fichier diagnostics.json est écrit. Toutefois, le fichier JSON écrit n’est pas mis en retrait ; pour plus de lisibilité, il est mis en retrait ici.

{
  "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
    }
  ]
}

Étapes suivantes