Sonntag, August 03, 2008

Windows PerformanceCounter Komponente

In der letzten Woche habe ich mich gleich in 2 Projekten mit Windows Performance Countern beschäftigt. Das war nicht das erste mal, so dass mir dabei auch aufgefallen ist, dass in einer früheren Implementierung von mir eine Reihe von Fehlern waren.

Allgemeines

Mittels Performance Countern kann man sehr gut Leistungsdaten eines Server/Anwendung erfassen und protokollieren. Man kann damit zum Beispiel die Anzahl der Aufrufe auf einen Dienst/Webservice/Layer verfolgen oder die Laufzeit. Es gibt wahnsinnig viele interessante Indikatoren in einer Anwendung. Performance Counter sind ebenso extrem wichtig, wenn mit Monitoring Software eine Anwendung überwacht wird. Sehr interessant für die Verwaltung und Administration von Servern ist auch die Möglichkeit Alarme zu definieren. Einen sehr netten Artikel zum Thema Andrew Z. Tabona Windows 2003 Performance Counter.

Coding

Ich wollte eine Komponente erstellen, die von jeder beliebigen Klasse und große Kenntnisse genutzt werden kann. Leider ist es nicht möglich einfach zu sagen, dass man jetzt in Ziel X etwas ausgeben will. Performance Counter müssen vor der Benutzung angelegt werden, zu dem sind für erhöhte Rechte notwendig. Aber Schritt für Schritt:

Prinzipiell ist das Schreiben von Performance-Counter-Werten sehr einfach.

    1 PerformanceCounter c = new PerformanceCounter("myCategory", "CountCalls",false)

    2 c.Increment();

    3 c.Decrement();

    4 c.IncrementBy(5);

Allerdings muss vorher sichergestellt sein, dass der Counter existiert und zugegriffen werden kann. Da Counter immer auch einer Category zugeordnet sind, muss diese ebenso angegeben werden.

    1 CounterCreationDataCollection creation = new CounterCreationDataCollection();

    2 creation.Add(new CounterCreationData(c.CounterName, c.CounterHelp, c.CounterType));

    3 PerformanceCounterCategory.Create(category, "", PerformanceCounterCategoryType.Unknown, creation);

Beim Anlegen eines Counters hat man immer die Wahl, ob ein Multi-Instanz Counter oder ein Single-Instanz Counter angelegt wird. Die Idee dahinter ist relativ simpel. Jede Applikation/Komponente kann bei einem Multi-Instanz Counter verfolgt werden, entsprechend werden für alle Einträge erzeugt. Bei einem Single Instanz Counter werden alle Log-Informationen für den gesamten Server protokolliert. Eine Differenzierung wäre in dem Fall nicht möglich, aber auch nicht immer sinnvoll.

Wird ein Counter vom Typ "PerformanceCounterType.AverageTimer32" angelegt, so muss zusätzlich auch der BaseCounter "PerformanceCounterType.AverageBase" angelegt werden, da es sonst zu Fehler kommt. Bei der Verwendung des Counters soll man die StartZeit in Ticks - der Endzeit der Aktion angeben, ehrlich gesagt, ich habe es noch nicht 100% verifiziert, ob der Counter richtig funktioniert. Ich nutze am meisten den einfachen Zähler oder Aufrufe pro Sekunde.

ABER, ist eine Category angelegt, kann man keine Counter hinzufügen, zumindest nicht mit Managed Code, ich kenne auch keinen anderen Weg, evtl. ist dies über Registry-Manipulation möglich. Aus diesem Grund muss die Category immer gelöscht werden, wenn ein Counter zu der Category hinzugefügt wird. Anschließend werden alle Counter erneut hinzugefügt.

Nach soviel Kleinigkeiten, hier mal ein ganzer Code-Block:

   63 private static readonly IDictionary<string, IDictionary<string, PerformanceCounter>> categoryDict =

   64     new Dictionary<string, IDictionary<string, PerformanceCounter>>();

   65 private PerformanceCounter GetCounter(string category, string counterName, bool useInstance, PerformanceCounterType type)

   66 {

   67     if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(counterName))

   68         return null;

   69     try

   70     {

   71         return EnsureWithCategory(category, counterName, useInstance, type, false);

   72     }

   73     catch (Exception ex)

   74     {

   75         System.Diagnostics.Trace.WriteLine("Get counter failed! " + ex.ToString(), "EnterpriseLoggingService.GetCounter");

   76     }

   77     return null;

   78 }

   79 private static ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

   80 

   81 private PerformanceCounter EnsureWithCategory(string category, string counterName, bool useInstance, PerformanceCounterType type, bool deep)

   82 {

   83     PerformanceCounter counter;

   84     cacheLock.EnterUpgradeableReadLock();

   85     try

   86     {

   87         if (categoryDict.ContainsKey(category))

   88         {

   89             IDictionary<string, PerformanceCounter> perf = categoryDict[category];

   90             return EnsureCounter(category, counterName, useInstance, type, perf);

   91         }

   92         else

   93         {

   94             if (deep)

   95                 return null;

   96             return EnsureCounter(category, counterName, useInstance, type, null);

   97 

   98         }

   99 

  100     }

  101     finally

  102     {

  103         cacheLock.ExitUpgradeableReadLock();

  104     }

  105 }

  106 

  107 private static PerformanceCounter EnsureCounter(string category, string counterName, bool useInstance, PerformanceCounterType type, IDictionary<string, PerformanceCounter> perf)

  108 {

  109     PerformanceCounter counter;

  110     if (perf != null && perf.ContainsKey(counterName))

  111     {

  112         counter = perf[counterName];

  113     }

  114     else

  115     {

  116         cacheLock.EnterWriteLock();

  117         try

  118         {

  119             bool exists = false;

  120             Dictionary<string, PerformanceCounter> counterDict;

  121             CounterCreationDataCollection creation = new CounterCreationDataCollection();

  122             if (PerformanceCounterCategory.Exists(category))

  123             {

  124                 exists = PerformanceCounterCategory.CounterExists(counterName, category);

  125                 PerformanceCounterCategory pc = new PerformanceCounterCategory(category);

  126                 PerformanceCounter[] counters;

  127                 if (pc.CategoryType == PerformanceCounterCategoryType.MultiInstance)

  128                     counters = pc.GetCounters(AppDomain.CurrentDomain.FriendlyName);

  129                 else

  130                     counters = pc.GetCounters();

  131 

  132                 foreach (PerformanceCounter c in counters)

  133                 {

  134                     creation.Add(new CounterCreationData(c.CounterName, c.CounterHelp, c.CounterType));

  135                 }

  136                 if (!exists)

  137                 {

  138                     if (perf != null)

  139                     {

  140                         foreach (PerformanceCounter p in perf.Values)

  141                         {

  142                             p.Dispose();

  143                         }

  144                         perf.Clear();

  145                         perf = null;

  146                     }

  147                     PerformanceCounterCategory.Delete(category);

  148                 }

  149             }

  150             if (!exists)

  151             {

  152                 creation.Add(new CounterCreationData(counterName, "", type));

  153                 if (type == PerformanceCounterType.AverageTimer32)

  154                     creation.Add(new CounterCreationData(counterName + "Base", "", PerformanceCounterType.AverageBase));

  155                 PerformanceCounterCategory.Create(category, "", PerformanceCounterCategoryType.Unknown, creation);

  156             }

  157             counterDict = new Dictionary<string, PerformanceCounter>(creation.Count);

  158             foreach (CounterCreationData ccd in creation)

  159             {

  160                 counter = new PerformanceCounter(category, ccd.CounterName, useInstance ? AppDomain.CurrentDomain.FriendlyName : "", false);

  161                 counterDict.Add(ccd.CounterName, counter);

  162             }

  163             counter = counterDict[counterName];

  164             perf = counterDict;

  165             if (categoryDict.ContainsKey(category))

  166                 categoryDict[category] = counterDict;

  167             else

  168                 categoryDict.Add(category, counterDict);

  169         }

  170         finally

  171         {

  172             cacheLock.ExitWriteLock();

  173         }

  174     }

  175     return counter;

  176 }

  177 public void NumberCounter(string category, string counterName, bool useInstance, int incrementValue)

  178 {

  179     PerformanceCounter c = GetCounter(category, counterName, useInstance, PerformanceCounterType.NumberOfItems32);

  180     if (c != null)

  181     {

  182         if (incrementValue == 1)

  183             c.Increment();

  184         else

  185         {

  186             if (incrementValue == -1)

  187                 c.Decrement();

  188             else

  189                 c.IncrementBy(incrementValue);

  190         }

  191     }

  192 }

  193 

  194 public void RateCounter(string category, string counterName, bool useInstance, int incrementValue)

  195 {

  196     PerformanceCounter c = GetCounter(category, counterName, useInstance, PerformanceCounterType.RateOfCountsPerSecond32);

  197     if (c != null)

  198     {

  199         if (incrementValue == 1)

  200             c.Increment();

  201         else

  202         {

  203             if (incrementValue == -1)

  204                 c.Decrement();

  205             else

  206                 c.IncrementBy(incrementValue);

  207         }

  208     }

  209 }

  210 

  211 public void AvarageCounter(string category, string counterName, bool useInstance, long incrementValue)

  212 {

  213     PerformanceCounter c = GetCounter(category, counterName, useInstance, PerformanceCounterType.AverageTimer32);

  214     if (c != null)

  215     {

  216         PerformanceCounter cBase = GetCounter(category, counterName + "Base", useInstance, PerformanceCounterType.AverageTimer32);

  217         c.IncrementBy(incrementValue);

  218         cBase.Increment();

  219     }

  220 }

  221 

  222 public void RemoveCurrentCounterInstance(string category, string counterName)

  223 {

  224     cacheLock.EnterReadLock();

  225     try

  226     {

  227         if (categoryDict.ContainsKey(category) && categoryDict[category].ContainsKey(counterName))

  228         {

  229             if (PerformanceCounterCategory.CounterExists(counterName, category) && PerformanceCounterCategory.InstanceExists(AppDomain.CurrentDomain.FriendlyName, category))

  230             {

  231                 PerformanceCounter c = categoryDict[category][counterName];

  232                 c.InstanceName = AppDomain.CurrentDomain.FriendlyName;

  233                 c.RemoveInstance();

  234             }

  235 

  236         }

  237     }

  238     finally

  239     {

  240         cacheLock.ExitReadLock();

  241     }

  242 }

  243 

  244 public void SetNumberCounterValue(string category, string counterName, bool useInstance, int newValue)

  245 {

  246     PerformanceCounter c = GetCounter(category, counterName, useInstance, PerformanceCounterType.NumberOfItems32);

  247     if (c != null)

  248     {

  249         if (useInstance)

  250             c.InstanceName = AppDomain.CurrentDomain.FriendlyName;

  251         c.RawValue = newValue;

  252     }

  253 }

  254 public void SetAvarageCounterValue(string category, string counterName, bool useInstance, long newValue)

  255 {

  256     PerformanceCounter c = GetCounter(category, counterName, useInstance, PerformanceCounterType.AverageTimer32);

  257     if (c != null)

  258     {

  259         PerformanceCounter cBase = GetCounter(category, counterName + "Base", useInstance, PerformanceCounterType.AverageTimer32);

  260         if (useInstance)

  261             c.InstanceName = AppDomain.CurrentDomain.FriendlyName;

  262         c.RawValue = newValue;

  263         if (newValue == 0)

  264             c.RawValue = 0;

  265     }

  266 }

  267 public void SetRateCounterValue(string category, string counterName, bool useInstance, int newValue)

  268 {

  269     PerformanceCounter c = GetCounter(category, counterName, useInstance, PerformanceCounterType.RateOfCountsPerSecond32);

  270     if (c != null)

  271     {

  272         if (useInstance)

  273             c.InstanceName = AppDomain.CurrentDomain.FriendlyName;

  274         c.RawValue = newValue;

  275     }

  276 }

Deployment

Beim Deployment sollte man die Empfehlung von MS usw. berücksichtigen und bei der Installation alle Counter anlegen. Counter zu nutzen ist nicht sehr "teuer", allerdings ist es sehr aufwendig und langsam, wenn Counter angelegt werden. Zu mal immer alle Counter inklusive Category gelöscht wird.

Der einfachste Weg bei der Installation ist es eine Installer-Klasse zu erstellen, die alles Counter der Anwendung anlegt. hierfür ist ein bisschen Coding notwendig und anschließend einfach "installutil" aufrufen.

Technorati-Tags: ,,,

1 Kommentar:

Anonym hat gesagt…

hört sich gut an :)