
Compartir:
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.
Enviar Notificaciones SMS sobre el Rendimiento de la Aplicación Usando C#
Cuando se trabaja en Applications, a menudo se desea registrar datos significativos, como dónde ha estado fallando la aplicación, el uso de CPU y el uso de memoria. Esto le proporcionará información vital que impulsará su negocio y le ayudará a detectar errores rápidamente. Para ayudarle a responder rápidamente, puede utilizar notificaciones que contengan un resumen de las estadísticas de rendimiento o detalles sobre un error en la producción.
Este artículo habla sobre cómo puedes recuperar las estadísticas de rendimiento de una aplicación web ASP.NET Core 5 usando C# y luego aprovechar la API de SMS de Vonage para enviar notificaciones de texto en intervalos de tiempo preconfigurados.
Requisitos previos
Visual Studio 2019.
Sistema operativo Windows
También necesitarás instalar los siguientes paquetes NuGet en tu proyecto:
NLog
Cuarzo
Vonage
Puede instalarlos a través del gestor de paquetes NuGet desde 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.
El proyecto Starter
Puede encontrar un proyecto inicial en GitHub en la carpeta starter. El proyecto de inicio contiene un proyecto ASP.NET Core MVC 5 con capacidades para leer información sobre el uso de la CPU y la memoria. En este tutorial, añadirás la funcionalidad de notificación por SMS.
Utilización de Quartz.NET para trabajos en segundo plano
Quartz.NET es una versión .NET gratuita y de código abierto del popular marco de programación de tareas Quartz de Java. Ofrece una excelente compatibilidad con expresiones Cron y se recomienda ampliamente para crear aplicaciones que necesiten funciones de programación de tareas.
Componentes básicos de Quartz.NET
Quartz.NET se compone de tres conceptos fundamentales:
Un Trabajo - Representa las tareas en segundo plano que quieres ejecutar mientras haces otras cosas. Una clase job extiende la interfaz IJob e implementa sus miembros.
Un desencadenante - Un desencadenante determina cuándo se ejecuta una tarea, y normalmente se configura para que se ejecute en un horario predeterminado. En otras palabras, puede utilizar un desencadenante para especificar la programación de un trabajo.
Un Programador - Este es un componente responsable de coordinar los trabajos y los disparadores y de ejecutar los trabajos basados en programas predefinidos.
El blog tratará más sobre estos componentes en breve.
Componentes de la aplicación Enviar notificación
En la aplicación de muestra, utilizarás la SMS API de Vonage para enviar notificaciones en cuanto el uso de la CPU o de la memoria supere un umbral predefinido. Por lo tanto, nuestra aplicación ASP.NET Core MVC 5 constaría de los siguientes componentes:
Programador personalizado - Utilizarás Quartz para construir un programador personalizado que vigile el consumo de memoria y CPU en el sistema informático. Comprobará estos valores a intervalos de tiempo predefinidos en segundo plano mientras trabaja con la aplicación en el front-end.
Registrador personalizado - En esta aplicación también se utilizará NLog para registrar datos sobre el rendimiento de las aplicaciones. Mientras que las notificaciones se enviarán sólo si el consumo de CPU o memoria cruza el umbral predefinido, el registro contendría datos de consumo de CPU y memoria recogidos cada 5 o 10 segundos (en función del intervalo predefinido) y almacenados en un archivo de texto.
Para este proyecto, estas son las clases que crearás en la aplicación:
PerformanceHelper- Esta clase se utiliza para recuperar el uso de CPU y memoria.NotificationManager- esta clase encapsula la funcionalidad para enviar notificaciones.NotificationJob- Esta es la clase de trabajo que aprovecha la claseNotificationManagerpara enviar notificaciones, como se ha comentado anteriormente.CustomJobFactory- Esta clase es responsable de la creación de empleo.CustomJobScheduler- Esta clase se encarga de programar los trabajos.QuartzExtensions- Esta clase contiene un método de extensión llamado AddQuartz para simplificar y organizar el código.
project architecture diagram
Uso de WMI para recuperar el uso de CPU y memoria
Windows Management Instrumentation (WMI) es un marco orientado a objetos, extensible y basado en estándares que amplía el modelo de controlador de Windows y ofrece una interfaz de sistema operativo a través de la cual los componentes instrumentados proporcionan información y notificaciones. El objetivo principal de WMI es crear un conjunto propio de especificaciones independientes del entorno que permita el intercambio de información de gestión entre aplicaciones.
Para recuperar los usos de CPU y Memoria en porcentaje, usarás la clase ManagementObjectSearcher del espacio de nombres System Management. Esta clase puede enumerar todas las unidades de disco, adaptadores de red, procesos y otros objetos de gestión en un sistema y recuperar una colección de objetos de gestión basados en la consulta que ha especificado. Al instanciar esta clase, deberá pasar una consulta WMI representada en un campo ObjectQuery y, opcionalmente, un ManagementScope que representa el espacio de nombres WMI para ejecutar la consulta.
Cree una clase llamada PerformanceHelper.cs en el proyecto ASP.NET Core MVC 5 que clonó anteriormente y escriba el siguiente código:
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;
}
}
Crear una clase de empleo
A continuación, crearás la clase job. Una clase job en Quartz extiende la interfaz IJob e implementa sus miembros. Crea una clase llamada NotificationJob en un archivo llamado NotificationJob.cs con el siguiente contenido dentro:
public class NotificationJob: IJob
{
private readonly NotificationManager _notificationManager;
public NotificationJob(NotificationManager notificationManager)
{
_notificationManager = notificationManager;
}
public async Task Execute(IJobExecutionContext context)
{
await _notificationManager.SendNotification();
}
}El método Execute de la clase NotificationJob llama al método SendNotification de la clase NotificationManager clase. Crea una nueva clase llamada NotificationManager en un fichero llamado NotificationManager.cs con el siguiente código:
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 función SendTextMessage utiliza el paquete de Vonage para enviar un SMS. SendTextMessage es llamada por SendResourceUsageInfo si el uso de CPU o Memoria ha superado el umbral. Más adelante establecerá los umbrales y las variables de configuración.
Crear una clase JobFactory personalizada
A continuación, debe crear nuestra clase personalizada JobFactory personalizada. Esta clase es necesaria para crear nuevas instancias de trabajo y deshacerse de ellas cuando ya no sean necesarias. Necesitas una implementación personalizada de Quartz IJobFactory ya que la implementación por defecto no permite utilizar Inyección de Dependencias (DI) al crear trabajos. Cree una clase llamada CustomJobFactory en un archivo con el mismo nombre con el siguiente código:
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();
}
}
} Crear la clase CustomJobScheduler
Ahora, cree una nueva clase llamada CustomJobScheduler en un archivo llamado CustomJobScheduler.cs en la carpeta raíz del proyecto y sustituye el código fuente por defecto de la clase CustomJobScheduler que acabas de crear por el siguiente código:
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);
}
}
El método ScheduleJob de la clase CustomJobScheduler acepta una instancia de tipo IScheduler y la utiliza para programar un trabajo basado en un disparador dado, que en este caso es un archivo TimeSpan.
Configuración de las dependencias
Ahora, crea una clase llamada QuartzExtensions con el siguiente código:
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;
});
}
}
Como se muestra en el diagrama de componentes anterior, la clase QuartzExtensions contiene un método llamado AddQuartz. El método crea instancias de JobFactory yNotificationJobque tienen una vida singleton. AddQuartz Los añade al contenedor. También asigna nuestra clase JobFactory a la instancia del planificador.
Configurar la aplicación
Añade el Memory_Threshold, CPU_Threshold, TO, FROM, API_KEYy API_SECRET en el archivo appsettings.json archivo. Sustitución de FROM, API_KEYy API_SECRET con tu número de Vonage, clave API y secreto API. TO debe ser el número al que deseas enviar las notificaciones por SMS:
{
"AllowedHosts": "*",
"Memory_Threshold": 80,
"CPU_Threshold": 80,
"TO": "TO",
"FROM": "FROM",
"API_KEY": "API_KEY",
"API_SECRET": "API_SECRET"
}
También debe configurar NLog.config y especificar allí el nombre y la ruta del archivo de registro.
<?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>
Añade la siguiente línea en el método ConfigureServices de la clase Startup para añadir una instancia de la clase NotificationManager como servicio transitorio al contenedor de servicios incorporados:
services.AddTransient<NotificationManager>();
Programar el trabajo
Por último, debe recuperar una instancia del planificador del contenedor de servicios de la aplicación y utilizarla para programar un trabajo. Para ello, debes utilizar el método Configure de la clase Startup de la clase Los métodos ConfigureServices y Configure de la clase Startup se generan por defecto al crear un proyecto web en ASP.NET Core o ASP.NET Core MVC. El método ConfigureServices se utiliza para añadir servicios al contenedor integrado, y el método Configure se utiliza para configurar el canal de peticiones HTTP.
Escriba el siguiente código en el método Configure de la clase 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?}");
});
}
Ejecutar la aplicación
Para ejecutar esta aplicación:
Localiza el archivo .sln y haz doble clic para abrirlo en Visual Studio 2019
Pulse F5 para ejecutar la aplicación
Una vez que la aplicación se esté ejecutando, espere a que la aplicación genere registros. Podrá ver que se crean mensajes de registro cada 5 segundos en la carpeta C:\\Logs carpeta. Puede cambiar esta duración según sea necesario haciéndola configurable en el archivo appsettings.json archivo. También verás que se envían notificaciones al número de móvil preconfigurado mediante la API de SMS de Vonage en cuanto el consumo de CPU o de memoria supere los valores de umbral.
a text message generated by the application when the memory usage exceeds 80%
¿Y ahora qué?
Puede consultar una versión completa del proyecto en GitHub en la carpeta completed.
Compartir:
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.
