https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-sms-notifications-on-application-performance-using-c/send-notifications_app-proformance_1200x600.png

Envoyer des notifications par SMS sur les performances de l'application en utilisant C#

Publié le August 2, 2021

Temps de lecture : 7 minutes

Lorsque vous travaillez sur des applications, vous souhaitez souvent enregistrer des données significatives, telles que les défaillances de votre application, l'utilisation du processeur et de la mémoire. Vous obtiendrez ainsi des informations vitales qui donneront un coup de pouce à votre entreprise et vous aideront à détecter rapidement les bogues. Pour vous aider à réagir rapidement, vous pouvez utiliser des notifications qui contiennent un résumé des statistiques de performance ou des détails sur une erreur dans la production.

Cet article explique comment récupérer les statistiques de performance d'une application web ASP.NET Core 5 à l'aide de C#, puis tirer parti de l'API SMS de Vonage pour envoyer des notifications textuelles à des intervalles de temps préconfigurés.

Conditions préalables

  • Visual Studio 2019.

  • Système d'exploitation Windows

Vous devrez également installer les paquets NuGet suivants dans votre projet :

  • NLog

  • Quartz

  • Vonage

Vous pouvez les installer via le gestionnaire de paquets NuGet lorsque vous êtes dans Visual Studio.

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Le projet Starter

Vous pouvez trouver un projet de démarrage sur GitHub dans le dossier starter. Ce projet contient un projet ASP.NET Core MVC 5 capable de lire les informations relatives à l'utilisation du processeur et de la mémoire. Dans ce tutoriel, vous allez ajouter la fonctionnalité de notification par SMS.

Utilisation de Quartz.NET pour les tâches d'arrière-plan

Quartz.NET est une version .NET gratuite et open-source du célèbre framework Java de planification de tâches Quartz. Il offre une excellente prise en charge des expressions Cron et est largement recommandé pour la création d'applications nécessitant une fonctionnalité de planification des tâches.

Les composants de base de Quartz.NET

Quartz.NET repose sur trois concepts fondamentaux :

Un job - Il représente les tâches d'arrière-plan que vous souhaitez exécuter pendant que vous faites autre chose. Une classe de travail étend l'interface IJob et met en œuvre ses membres.

Un déclencheur - Un déclencheur détermine le moment où une tâche est exécutée, et il est généralement configuré pour s'exécuter selon un calendrier prédéterminé. En d'autres termes, vous pouvez utiliser un déclencheur pour spécifier la programmation d'un travail.

Un planificateur - Il s'agit d'un composant responsable de la coordination des tâches et des déclencheurs et de l'exécution des tâches en fonction de calendriers prédéfinis.

Nous reviendrons prochainement sur ces éléments.

Les composants de la demande d'envoi d'une notification

Dans l'exemple d'application, vous utiliserez l'API SMS de Vonage pour envoyer des notifications dès que l'utilisation du processeur ou de la mémoire dépasse un seuil prédéfini. Par conséquent, notre application ASP.NET Core MVC 5 comprendrait les composants suivants :

  • Planificateur personnalisé - Vous utiliserez Quartz pour créer un planificateur personnalisé qui gardera un œil sur la consommation de mémoire et de CPU dans le système informatique. Il vérifiera ces valeurs à des intervalles de temps prédéfinis en arrière-plan pendant que vous travaillez avec l'application dans le front-end.

  • Logger personnalisé - Dans cette application, vous utiliserez également NLog pour consigner les données relatives aux performances de l'application. Alors que les notifications ne seront envoyées que si la consommation de CPU ou de mémoire dépasse le seuil prédéfini, le journal contiendra des données sur la consommation de CPU et de mémoire collectées toutes les 5 ou 10 secondes (en fonction de l'intervalle prédéfini) et stockées dans un fichier texte.

Pour ce projet, voici les classes que vous allez créer dans l'application :

  • PerformanceHelper - Cette classe est utilisée pour récupérer l'utilisation de l'unité centrale et de la mémoire.

  • NotificationManager - cette classe encapsule la fonctionnalité d'envoi de notifications.

  • NotificationJob - Il s'agit de la classe de travail qui tire parti de la classe NotificationManager pour envoyer des notifications, comme nous l'avons vu précédemment.

  • CustomJobFactory - Cette classe est responsable de la création d'emplois.

  • CustomJobScheduler - Cette classe est responsable de la programmation des travaux.

  • QuartzExtensions - Cette classe contient une méthode d'extension appelée AddQuartz pour simplifier et organiser le code.

project architecture diagramproject architecture diagram

Utiliser WMI pour récupérer l'utilisation de l'unité centrale et de la mémoire

Windows Management Instrumentation (WMI) est un cadre orienté objet, extensible et basé sur des normes qui étend le modèle de pilote Windows et offre une interface de système d'exploitation via laquelle les composants instrumentés fournissent des informations et des notifications. L'objectif premier de WMI est de créer un ensemble propriétaire de spécifications indépendantes de l'environnement qui permet l'échange d'informations de gestion entre les applications.

Pour récupérer l'utilisation du CPU et de la mémoire en pourcentage, vous utiliserez la classe ManagementObjectSearcher de l'espace de noms System Management. Cette classe peut énumérer tous les lecteurs de disques, adaptateurs réseau, processus et autres objets de gestion d'un système et récupérer une collection d'objets de gestion sur la base de la requête que vous avez spécifiée. Lorsque vous instanciez cette classe, vous deviez lui transmettre une requête WMI représentée par une balise ObjectQuery et, éventuellement, un ManagementScope représentant l'espace de noms WMI pour exécuter la requête.

Créez une classe nommée PerformanceHelper.cs dans le projet ASP.NET Core MVC 5 que vous avez cloné précédemment et écrivez le code suivant :

public static class PerformanceHelper
{
  public static double GetCPUUsageInPercentage()
  {
    ObjectQuery objQuery = new ObjectQuery("SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name=\"_Total\"");
    ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(objQuery);
    ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
    double cpu_usage = 0;

    if (managementObjectCollection.Count > 0)
    {
      foreach (ManagementObject managementObject in managementObjectCollection)
      {
        try
        {
          cpu_usage = 100 - Convert.ToUInt32(managementObject["PercentIdleTime"]);
          break;
        }
        catch (Exception ex)
        {
          break;
        }
      }
    }

    return cpu_usage;
  }

  public static Dictionary<string, double> GetMemoryUsageInfo()
  {
    Dictionary<string, double> keyValuePairs = new Dictionary<string, double>();

    ObjectQuery objQuery = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(objQuery);
    ManagementObjectCollection results = searcher.Get();
    Dictionary<string, double> resourceConsumptionResults = new Dictionary<string, double>();

    foreach (ManagementObject result in results)
    {
      var totalVisibleMemory = Math.Round(double.Parse(result["TotalVisibleMemorySize"].ToString()), 2);
      var totalFreeMemory = Math.Round(double.Parse(result["FreePhysicalMemory"].ToString()), 2);

      resourceConsumptionResults.Add("Total_Visible_Memory", totalVisibleMemory);
      resourceConsumptionResults.Add("Free_Physical_Memory", totalFreeMemory);
    }

    double totalVisibleMemorySize = Math.Round(resourceConsumptionResults["Total_Visible_Memory"] / (1024 * 1024), 2);
    double freePhysicalMemory = Math.Round(resourceConsumptionResults["Free_Physical_Memory"] / (1024 * 1024), 2);
    double totalUsedMemory = totalVisibleMemorySize - freePhysicalMemory;
    double memory_usage = 0;

    try
    {
      memory_usage = ((totalVisibleMemorySize - freePhysicalMemory) / totalVisibleMemorySize) * 100;
      keyValuePairs.Add("Total_Visible_Memory", totalVisibleMemorySize);
      keyValuePairs.Add("Total_Free_Memory", freePhysicalMemory);
      keyValuePairs.Add("Total_Used_Memory", totalUsedMemory);
      keyValuePairs.Add("Total_Used_Memory_Percentage", memory_usage);
    }
    catch
    {
      throw;
    }

    return keyValuePairs;
  }
}

Créer une classe d'emploi

Ensuite, vous allez créer la classe de travail. Une classe de travail dans Quartz étend l'interface IJob et implémente ses membres. Créez une classe nommée NotificationJob dans un fichier appelé NotificationJob.cs avec le contenu suivant à l'intérieur :

public class NotificationJob: IJob 
{
  private readonly NotificationManager _notificationManager;
  public NotificationJob(NotificationManager notificationManager)
  {
    _notificationManager = notificationManager;
  }
  public async Task Execute(IJobExecutionContext context)
  {
    await _notificationManager.SendNotification();
  }
}

La méthode Execute de la classe NotificationJob appelle la méthode SendNotification de la classe NotificationManager de la classe. Créez une nouvelle classe nommée NotificationManager dans un fichier appelé NotificationManager.cs avec le code suivant :

public class NotificationManager
{
  private ILoggerFactory LoggerFactory { get; }
  private readonly ILogger Logger;
  public IConfiguration Configuration { get; set; }

  public NotificationManager(IConfiguration config, ILoggerFactory loggerFactory)
  {
    Configuration = config;
    LoggerFactory = loggerFactory;

    if (loggerFactory != null) {
      Logger = loggerFactory.CreateLogger("NotificationManager");
    }
  }
    
  public async Task SendNotification()
  {
    await SendResourceUsageInfo();
    await Task.CompletedTask;
  }

  private async Task SendResourceUsageInfo()
  {
    bool isMemoryUsageHigh = false;
    bool isCPUUsageHigh = false;

    var keyValuePairs = PerformanceHelper.GetMemoryUsageInfo();

    var memoryUsage = Math.Round(keyValuePairs["Total_Used_Memory_Percentage"], 2);
    var cpuUsage = Math.Round(PerformanceHelper.GetCPUUsageInPercentage(), 2);

    var memoryThreshold = Configuration["Memory_Threshold"];
    var cpuThreshold = Configuration["CPU_Threshold"];

    if (memoryUsage > double.Parse(memoryThreshold))
    {
      isMemoryUsageHigh = true;
    }
    if (cpuUsage > double.Parse(cpuThreshold))
    {
      isCPUUsageHigh = true;
    }

    if (isMemoryUsageHigh)
    {
      string memoryConsumptionAlertMessage = string.Format($"Alert!!! Memory Usage: " +
      $ "{keyValuePairs["
      Total_Used_Memory "]} GB " +
      $ "/ {keyValuePairs["
      Total_Visible_Memory "]} GB");
      SendTextMessage(memoryConsumptionAlertMessage);
    }

    if (isCPUUsageHigh)
    {
      string cpuConsumptionAlertMessage = string.Format("Alert!!! CPU Usage: {0}", cpuUsage);
      SendTextMessage(cpuConsumptionAlertMessage);
    }

    await Task.CompletedTask;
  }

    private void SendTextMessage(string message)
    {
      try
      {
        var apiKey = Configuration["API_KEY"];
        var apiSecret = Configuration["API_SECRET"];
        var credentials = Credentials.FromApiKeyAndSecret(apiKey, apiSecret);
        var client = new SmsClient(credentials);

        var request = new SendSmsRequest
        {
          To = Configuration["TO"],
          From = Configuration["FROM"],
          Text = message
        };

        var response = client.SendAnSms(request);
        Logger?.LogInformation(response.MessageCount);
      }
      catch (VonageSmsResponseException ex)
      {
        Logger?.LogError(ex.Message);
        throw;
      }
  }
}

La fonction SendTextMessage utilise le progiciel Vonage pour envoyer un SMS. SendTextMessage est appelée par SendResourceUsageInfo si l'utilisation du processeur ou de la mémoire a dépassé le seuil. Vous définirez les seuils et les variables de configuration plus tard.

Créer une classe JobFactory personnalisée

Ensuite, vous devez créer notre classe JobFactory personnalisée. Cette classe est nécessaire pour créer de nouvelles instances de travail et les éliminer lorsqu'elles ne sont plus nécessaires. Vous avez besoin d'une implémentation personnalisée de Quartz IJobFactory car l'implémentation par défaut ne vous permet pas d'utiliser l'injection de dépendance (DI) lors de la création de jobs. Créez une classe nommée CustomJobFactory dans un fichier portant le même nom et contenant le code suivant :

public class CustomJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;
    public CustomJobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var jobDetail = bundle.JobDetail;
        return (IJob)_serviceProvider.GetService(jobDetail.JobType);
    }
    public void ReturnJob(IJob job)
    {
        var disposableJobInstance = job as IDisposable;

        if (disposableJobInstance != null)
        {
            disposableJobInstance.Dispose();
        }
    }
}

Créer la classe CustomJobScheduler

Créez maintenant une nouvelle classe nommée CustomJobScheduler dans un fichier appelé CustomJobScheduler.cs dans le dossier racine du projet et remplacez le code source par défaut de la classe CustomJobScheduler que vous venez de créer par le code suivant :

public class CustomJobScheduler
{
    public static void ScheduleJob(IScheduler scheduler)
    {
      var jobName = "NotificationJob";

      var job = JobBuilder.Create<NotificationJob>()
      .WithIdentity(jobName)
      .Build();

      var trigger = TriggerBuilder.Create()
      .WithIdentity($"{jobName}.trigger")
      .StartNow()
      .WithSimpleSchedule(scheduleBuilder =>
          scheduleBuilder
              .WithInterval(TimeSpan.FromSeconds(5))
              .RepeatForever())
      .Build();

      scheduler.ScheduleJob(job, trigger);
    }
}

La méthode ScheduleJob de la classe CustomJobScheduler accepte une instance de type IScheduler et l'utilise pour planifier un travail en fonction d'un déclencheur donné, qui dans ce cas est une instance de type TimeSpan.

Mise en place des dépendances

Maintenant, créez une classe nommée QuartzExtensions avec le code suivant :

public static class QuartzExtensions
{
  public static void AddQuartz(this IServiceCollection services, params Type[] jobs)
  {
    services.AddSingleton<IJobFactory, CustomJobFactory>();
    services.AddSingleton<NotificationJob>();

    services.AddSingleton(provider =>
    {
      var schedulerFactory = new StdSchedulerFactory();
      var scheduler = schedulerFactory.GetScheduler().Result;
      scheduler.JobFactory = provider.GetService<IJobFactory>();
      scheduler.Start();
      return scheduler;
    });
  }
}

Comme le montre le diagramme des composants présenté plus haut, la classe QuartzExtensions contient une méthode nommée AddQuartz. Cette méthode crée des instances de JobFactory etNotificationJobqui ont toutes deux une durée de vie singleton. AddQuartz Elle les ajoute au conteneur. Elle assigne également notre classe personnalisée JobFactory à l'instance du planificateur.

Configurer l'Applications

Ajouter le Memory_Threshold, CPU_Threshold, TO, FROM, API_KEY, et API_SECRET dans le fichier appsettings.json fichier. En remplaçant FROM, API_KEY, et API_SECRET par votre numéro Vonage, votre clé API et votre secret API. TO doit être le numéro auquel vous souhaitez envoyer les notifications par SMS :

{

"AllowedHosts": "*",

"Memory_Threshold": 80,

"CPU_Threshold": 80,

"TO": "TO",

"FROM": "FROM",

"API_KEY": "API_KEY",

"API_SECRET": "API_SECRET"

}

Vous devez également configurer NLog.config et y spécifier le nom et le chemin d'accès du fichier journal.

<?xml version="1.0" encoding="utf-8" ?>
<nlog
  xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Trace"
      internalLogFile="C:\Logs\InternalLog.txt">
  <targets>
    <target name="logfile" xsi:type="File"
            fileName="C:/Logs/${shortdate}_AppLog.txt"
            layout="${longdate} ${level:uppercase=true} ${message}"/>
  </targets>
  <rules>
    <logger name="*" minlevel="Debug" writeTo="logfile">
      <filters defaultAction='Log'>
        <when condition="contains('${message}','Quartz scheduler')" action="Ignore" />
      </filters>
    </logger>
  </rules>
</nlog>

Ajoutez la ligne suivante dans la méthode ConfigureServices de la classe Startup pour ajouter une instance de la classe NotificationManager en tant que service transitoire dans le conteneur de services intégrés :

services.AddTransient<NotificationManager>();

Planifier le travail

Enfin, vous devez récupérer une instance de planificateur dans le conteneur de services de l'application et l'utiliser pour planifier un travail. Pour ce faire, vous devez utiliser la méthode Configure de la classe Startup de la classe La méthode ConfigureServices et Configure de la classe Startup sont générées par défaut lors de la création d'un projet web en ASP.NET Core ou ASP.NET Core MVC. La méthode ConfigureServices est utilisée pour ajouter des services au conteneur intégré, et la méthode Configure est utilisée pour configurer le pipeline de requêtes HTTP.

Ecrivez le code suivant dans la méthode Configure de la classe Startup :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())

    {
      app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseAuthorization();

    var scheduler = app.ApplicationServices.GetService<IScheduler>();
    CustomJobScheduler.ScheduleJob(scheduler);

    app.UseEndpoints(endpoints =>
    {
      endpoints.MapControllerRoute(
          name: "default",
          pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Exécuter l'application

Pour exécuter cette application :

  1. Localisez le fichier .sln et double-cliquez dessus pour l'ouvrir dans Visual Studio 2019.

  2. Appuyez sur F5 pour lancer l'application

Une fois l'application lancée, attendez qu'elle génère des journaux. Vous pouvez voir que des messages sont créés toutes les 5 secondes dans le dossier C:\\Logs dans le dossier Vous pouvez modifier cette durée si nécessaire en la rendant configurable dans le fichier appsettings.json pour modifier cette durée. Vous verrez également des notifications envoyées au numéro de mobile préconfiguré à l'aide de l'API SMS de Vonage dès que la consommation de CPU ou de mémoire franchit les valeurs seuils.

a text message generated by the application when the memory usage exceeds 80%a text message generated by the application when the memory usage exceeds 80%

Quelle est la prochaine étape ?

Vous pouvez consulter une version complète du projet sur GitHub sous le dossier completed.

Partager:

https://a.storyblok.com/f/270183/320x319/b39aa07782/joydip-kanjilal.png
Joydip Kanjilal

Joydip is a Microsoft Most Valuable Professional in ASP.NET, Speaker, and Author of several books and articles. He has more than 25 years of experience in IT with more than 18 years in Microsoft .NET and its related technologies. Several times, he was selected as MSDN Featured Developer of the Fortnight (MSDN) and was a Community Credit Winner at http://www.community-credit.com. He has authored more than 500 articles in some of the most reputed sites worldwide, including MSDN, Info World, CodeMag, Tech Beacon, Tech Target, etc.