using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Remoting.Messaging;
using Developpez.Dotnet.ComponentModel;
namespace Developpez.Dotnet.Diagnostics
{
///
/// Permet d'obtenir le nombre d'exécutions et le temps d'exécutions des méthodes
/// d'un objet
///
/// Type de l'objet à surveiller
public class MethodTimeMonitor where T:MarshalByRefObject
{
/* Clairement possible d'obtenir des cas asynchrones */
///
/// Objet de synchronisation
///
protected object sync = new object();
///
/// Crée une nouvelle instance du chronomètre d'exécution de méthode
///
public MethodTimeMonitor()
{
Monitored = new Dictionary.TimeMonitorEntry>>();
RunningMethods = new Dictionary.GenericPoolItem>();
PerfCounterPool = new GenericPool(1, 32); /* pool de 32 objets au maximum, ça devrais suffire */
}
#region Not Thread Safe
///
/// Entrée du rapport d'utilisation d'une méthode
///
public class TimeMonitorEntry
{
///
/// Construit une nouvelle instance d'une entrée du rapport d'appels
///
public TimeMonitorEntry()
{
CallCount = 0;
TotalTime = TimeSpan.Zero; /* secondes */
}
///
/// Nom de la méthode
///
public virtual string MethodName{get;set;}
///
/// Nombre d'appels effectués à cette méthode
///
public virtual int CallCount { get; set; }
///
/// Temps total passé dans cette méthode (incluant également les sous-méthodes appelées)
///
public virtual TimeSpan TotalTime { get; set; }
///
/// Crée une copie de l'objet
///
/// Copie
public virtual TimeMonitorEntry Clone()
{
TimeMonitorEntry res = new MethodTimeMonitor.TimeMonitorEntry();
res.CallCount = CallCount;
res.TotalTime = TotalTime;
res.MethodName = MethodName;
return res;
}
}
///
/// Ensemble des méthodes actuellement surverillées
///
protected virtual Dictionary> Monitored { get; private set; }
#endregion
#region Thread Safe - Transition Level
///
/// Pool de chronomètres à utiliser pour l'observation des méthodes
///
protected virtual GenericPool PerfCounterPool { get; private set; }
/* C'est ici qu'on va pouvoir calculer les temps d'exécution.
* Pour ne pas bloquer inutilement (probable), un autre sync
* */
///
/// Objet de synchronisation pour les transitions entre moniteur (proxy) et code (implémentation)
///
protected object transition_sync = new object();
///
/// Méthodes actuellement en cours d'exécution, avec un pool pour le perf counter
/// afin d'éviter d'en créer un nombre infini
///
protected virtual Dictionary.GenericPoolItem> RunningMethods { get; private set; }
#endregion
#region Thread Safe - Sync Level
///
/// Début d'appel à une méthode d'un objet
///
/// Objet sur lequel la méthode est appelée
/// Paramètres de l'évènement
protected virtual void MethodBegin(T obj, BeforeMethodCallEventArgs args)
{
lock (transition_sync)
{
GenericPool.GenericPoolItem item = PerfCounterPool.ObtainItem();
RunningMethods[args.Msg] = item;
item.Value.Reset();
item.Value.Start();
}
}
///
/// Fin d'appel à une méthode d'un objet : cas où une exception est survenue
///
/// Objet sur lequel la méthode est appelée
/// Paramètres de l'évènement
protected virtual void MethodExceptionEnd(T obj, MethodCallExceptionEventArgs args)
{
GenericPool.GenericPoolItem item = null;
try
{
lock (transition_sync)
{
/* on ne fait que retirer le message ... une exception ne compte pas comme "temps" */
if (!RunningMethods.TryGetValue(args.Msg, out item))
{
/* oops ? */
Debug.Fail(String.Format("L'appel de fin de méthode pour {0} a été appelé, sans qu'il y ait eu de début !",
args.MethodName));
return;
}
RunningMethods.Remove(args.Msg);
}
}
finally
{
if (item != null)
item.Dispose();
}
}
///
/// Fin d'appel à une méthode d'un objet : cas normal
///
/// Objet sur lequel la méthode est appelée
/// Paramètres de l'évènement
protected virtual void MethodEnd(T obj, AfterMethodCallEventArgs args)
{
GenericPool.GenericPoolItem item = null;
lock (transition_sync)
{
if (!RunningMethods.TryGetValue(args.Msg, out item))
{
/* oops ? */
Debug.Fail(String.Format("L'appel de fin de méthode pour {0} a été appelé, sans qu'il y ait eu de début !",
args.MethodName));
return;
}
else
item.Value.Stop();
}
try
{
lock (sync)
{
/* rajout du résultat */
Dictionary target_dictionary;
if (!Monitored.TryGetValue(obj, out target_dictionary))
Monitored[obj] = target_dictionary = new Dictionary.TimeMonitorEntry>();
TimeMonitorEntry entry = null;
if (!target_dictionary.TryGetValue(args.MethodName, out entry))
{
target_dictionary[args.MethodName] = entry = new MethodTimeMonitor.TimeMonitorEntry();
entry.MethodName = args.MethodName;
}
entry.CallCount++;
entry.TotalTime += item.Value.Elapsed;
}
}
finally
{
item.Dispose();
}
}
///
/// Retourne un objet équivalent à obj, mais dont les appels
/// de méthodes seront surveillés
///
/// objet à surveiller
/// Un proxy vers obj pour lequel les appels de méthodes sont surveillés
public virtual T GetAsMonitored(T obj)
{
return GetAsMonitored(obj, null);
}
///
/// Retourne un objet équivalent à obj, mais dont les appels
/// de méthodes seront surveillés
///
/// objet à surveiller
/// Noms des méthodes à surveiller, ou null pour tout surveiller
/// Un proxy vers obj pour lequel les appels de méthodes sont surveillés
private T GetAsMonitored(T obj, string[] methodsToMonitor)
{
MethodCallMonitor m;
T result = MethodCallMonitor.Create(obj, out m);
m.MonitoredMethods = methodsToMonitor;
m.BeforeMethodCall += delegate(object sender, BeforeMethodCallEventArgs e)
{
MethodBegin(obj, e);
};
m.AfterMethodCall += delegate(object sender, AfterMethodCallEventArgs e)
{
MethodEnd(obj, e);
};
m.MethodCallException += delegate(object sender, MethodCallExceptionEventArgs e)
{
MethodExceptionEnd(obj, e);
};
return result;
}
///
/// Annule toute surveillance de l'objet obj
///
/// L'objet pour lequel annuler la surveillance
public virtual void DiscardMonitoring(T obj)
{
lock (sync)
{
Monitored.Remove(obj);
}
}
///
/// Genère un rapport contenant pour chaque objet monitoré, l'ensemble des informations
/// d'appel et d'exécution des méthode surveillées
///
/// Rapport
public virtual KeyValuePair[] Report()
{
List> result = new List.TimeMonitorEntry[]>>();
lock (sync)
{
foreach (var section in Monitored)
{
List sub = new List.TimeMonitorEntry>();
foreach (var item in section.Value)
{
sub.Add(item.Value.Clone());
}
result.Add(new KeyValuePair(section.Key, sub.ToArray()));
}
}
return result.ToArray();
}
#endregion
}
}