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.
1 Kommentar:
hört sich gut an :)
Kommentar veröffentlichen