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