windows performance counters with average value for n samples can be configured direc...
[public/netxms.git] / src / agent / subagents / winperf / collect.cpp
1 /*
2 ** Windows Performance NetXMS subagent
3 ** Copyright (C) 2004-2013 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: collect.cpp
20 **
21 **/
22
23 #include "winperf.h"
24
25 /**
26 * Counter sets
27 */
28 WinPerfCounterSet *m_cntSet[3] =
29 {
30 new WinPerfCounterSet(1000, _T('A')),
31 new WinPerfCounterSet(5000, _T('B')),
32 new WinPerfCounterSet(6000, _T('C'))
33 };
34
35 /**
36 * Counter set constructor
37 */
38 WinPerfCounterSet::WinPerfCounterSet(DWORD interval, TCHAR cls)
39 {
40 m_mutex = MutexCreate();
41 m_changeCondition = ConditionCreate(TRUE);
42 m_interval = interval;
43 m_class = cls;
44 m_counters = new ObjectArray<WINPERF_COUNTER>(32, 32, true);
45 }
46
47 /**
48 * Counter set destructor
49 */
50 WinPerfCounterSet::~WinPerfCounterSet()
51 {
52 MutexDestroy(m_mutex);
53 ConditionDestroy(m_changeCondition);
54 delete m_counters;
55 }
56
57 /**
58 * Add counter to set
59 */
60 void WinPerfCounterSet::addCounter(WINPERF_COUNTER *c)
61 {
62 MutexLock(m_mutex);
63 m_counters->add(c);
64 MutexUnlock(m_mutex);
65 ConditionSet(m_changeCondition);
66 }
67
68 /**
69 * Collector thread
70 */
71 void WinPerfCounterSet::collectorThread()
72 {
73 HQUERY hQuery;
74 PDH_STATUS rc;
75 PDH_STATISTICS statData;
76 TCHAR szFName[] = _T("CollectorThread_X");
77
78 szFName[16] = m_class;
79
80 // Outer loop - rebuild query after set changes
81 while(1)
82 {
83 AgentWriteDebugLog(2, _T("WINPERF: %s waiting for set change"), szFName);
84
85 HANDLE handles[2];
86 handles[0] = g_hCondShutdown;
87 handles[1] = m_changeCondition;
88 DWORD waitStatus = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
89 if (waitStatus == WAIT_OBJECT_0)
90 break; // Shutdown condition
91 if (waitStatus != WAIT_OBJECT_0 + 1)
92 continue; // set not changed, continue waiting
93
94 ConditionReset(m_changeCondition);
95
96 AgentWriteDebugLog(2, _T("WINPERF: %s: set changed"), szFName);
97 if ((rc = PdhOpenQuery(NULL, 0, &hQuery)) != ERROR_SUCCESS)
98 {
99 ReportPdhError(szFName, _T("PdhOpenQuery"), rc);
100 continue;
101 }
102
103 // Add counters to query
104 DWORD dwOKCounters = 0;
105 MutexLock(m_mutex);
106 for(int i = 0; i < m_counters->size(); i++)
107 {
108 WINPERF_COUNTER *counter = m_counters->get(i);
109 if ((rc = PdhAddCounter(hQuery, counter->pszName, 0, &counter->handle)) != ERROR_SUCCESS)
110 {
111 TCHAR szBuffer[1024];
112
113 AgentWriteLog(EVENTLOG_WARNING_TYPE, _T("WINPERF: %s: Unable to add counter \"%s\" to query (%s)"),
114 szFName, counter->pszName, GetPdhErrorText(rc, szBuffer, 1024));
115 counter->handle = NULL;
116 }
117 else
118 {
119 dwOKCounters++;
120 }
121 }
122 MutexUnlock(m_mutex);
123
124 // Check if we was able to add at least one counter
125 if (dwOKCounters == 0)
126 {
127 PdhCloseQuery(hQuery);
128 AgentWriteLog(EVENTLOG_WARNING_TYPE, _T("WINPERF: Failed to add any counter to query for counter set %c"), m_class);
129 continue;
130 }
131
132 // Collection loop (until shutdown or set change)
133 AgentWriteDebugLog(2, _T("WINPERF: %s entered data collection loop"), szFName);
134 while(1)
135 {
136 HANDLE handles[2];
137 handles[0] = g_hCondShutdown;
138 handles[1] = m_changeCondition;
139 waitStatus = WaitForMultipleObjects(2, handles, FALSE, m_interval);
140 if (waitStatus == WAIT_OBJECT_0)
141 goto stop; // shutdown
142 if (waitStatus == WAIT_OBJECT_0 + 1)
143 break; // Set change, break collection loop
144
145 // Collect data
146 if ((rc = PdhCollectQueryData(hQuery)) != ERROR_SUCCESS)
147 ReportPdhError(szFName, _T("PdhCollectQueryData"), rc);
148
149 // Get raw values for each counter and compute average value
150 for(int i = 0; i < m_counters->size(); i++)
151 {
152 WINPERF_COUNTER *counter = m_counters->get(i);
153 if (counter->handle != NULL)
154 {
155 // Get raw value into buffer
156 PdhGetRawCounterValue(counter->handle, NULL, &counter->pRawValues[counter->dwBufferPos]);
157 counter->dwBufferPos++;
158 if (counter->dwBufferPos == (DWORD)counter->wNumSamples)
159 counter->dwBufferPos = 0;
160
161 // Calculate mean value
162 if ((rc = PdhComputeCounterStatistics(counter->handle,
163 counter->dwFormat,
164 counter->dwBufferPos,
165 counter->wNumSamples,
166 counter->pRawValues,
167 &statData)) != ERROR_SUCCESS)
168 {
169 ReportPdhError(szFName, _T("PdhComputeCounterStatistics"), rc);
170 }
171
172 // Update mean value in counter set
173 switch(counter->wType)
174 {
175 case COUNTER_TYPE_INT32:
176 counter->value.iLong = statData.mean.longValue;
177 break;
178 case COUNTER_TYPE_INT64:
179 counter->value.iLarge = statData.mean.largeValue;
180 break;
181 case COUNTER_TYPE_FLOAT:
182 counter->value.dFloat = statData.mean.doubleValue;
183 break;
184 default:
185 break;
186 }
187 }
188 }
189 }
190
191 AgentWriteDebugLog(2, _T("WINPERF: %s: data collection stopped"), szFName);
192 PdhCloseQuery(hQuery);
193 }
194
195 stop:
196 AgentWriteLog(EVENTLOG_INFORMATION_TYPE, _T("WINPERF: Collector thread for counter set %c terminated"), m_class);
197 }
198
199 /**
200 * Starter for collector thread
201 */
202 THREAD_RESULT THREAD_CALL WinPerfCounterSet::collectorThreadStarter(void *arg)
203 {
204 ((WinPerfCounterSet *)arg)->collectorThread();
205 return THREAD_OK;
206 }
207
208 /**
209 * Start collector thread
210 */
211 void WinPerfCounterSet::startCollectorThread()
212 {
213 ThreadCreate(collectorThreadStarter, 0, this);
214 }
215
216 /**
217 * Check that counter's name is valid and determine counter's type
218 */
219 int CheckCounter(const TCHAR *pszName, TCHAR **ppszNewName)
220 {
221 HQUERY hQuery;
222 HCOUNTER hCounter;
223 PDH_STATUS rc;
224 PDH_COUNTER_INFO ci;
225 DWORD dwSize;
226 static TCHAR szFName[] = _T("CheckCounter");
227
228 *ppszNewName = NULL;
229
230 if ((rc = PdhOpenQuery(NULL, 0, &hQuery)) != ERROR_SUCCESS)
231 {
232 ReportPdhError(szFName, _T("PdhOpenQuery"), rc);
233 return -1;
234 }
235
236 rc = PdhAddCounter(hQuery, pszName, 0, &hCounter);
237 if (rc != ERROR_SUCCESS)
238 {
239 // Attempt to translate counter name
240 if ((rc == PDH_CSTATUS_NO_COUNTER) || (rc == PDH_CSTATUS_NO_OBJECT))
241 {
242 *ppszNewName = (TCHAR *)malloc(_tcslen(pszName) * sizeof(TCHAR) * 4);
243 if (TranslateCounterName(pszName, *ppszNewName))
244 {
245 AgentWriteDebugLog(2, _T("WINPERF: Counter translated: %s ==> %s"), pszName, *ppszNewName);
246 rc = PdhAddCounter(hQuery, *ppszNewName, 0, &hCounter);
247 if (rc != ERROR_SUCCESS)
248 {
249 free(*ppszNewName);
250 *ppszNewName = NULL;
251 }
252 }
253 }
254 if (rc != ERROR_SUCCESS)
255 {
256 ReportPdhError(szFName, _T("PdhAddCounter"), rc);
257 PdhCloseQuery(hQuery);
258 return -1;
259 }
260 }
261
262 dwSize = sizeof(ci);
263 if ((rc = PdhGetCounterInfo(hCounter, FALSE, &dwSize, &ci)) != ERROR_SUCCESS)
264 {
265 ReportPdhError(szFName, _T("PdhGetCounterInfo"), rc);
266 PdhCloseQuery(hQuery);
267 return -1;
268 }
269
270 PdhCloseQuery(hQuery);
271 return (ci.dwType & PERF_SIZE_LARGE) ? COUNTER_TYPE_INT64 : COUNTER_TYPE_INT32;
272 }
273
274 /**
275 * Add counter to set
276 */
277 WINPERF_COUNTER *AddCounter(TCHAR *pszName, int iClass, int iNumSamples, int iDataType)
278 {
279 WINPERF_COUNTER *pCnt;
280 TCHAR *pszNewName;
281 int iType;
282
283 // Check for valid class
284 if ((iClass < 0) || (iClass > 2))
285 return NULL;
286
287 // Check counter's name and get it's type
288 iType = CheckCounter(pszName, &pszNewName);
289 if (iType == -1)
290 {
291 safe_free(pszNewName);
292 return NULL;
293 }
294
295 // Create new counter
296 pCnt = new WINPERF_COUNTER;
297 memset(pCnt, 0, sizeof(WINPERF_COUNTER));
298 if (pszNewName != NULL)
299 {
300 pCnt->pszName = (TCHAR *)realloc(pszNewName, (_tcslen(pszNewName) + 1) * sizeof(TCHAR));
301 }
302 else
303 {
304 pCnt->pszName = _tcsdup(pszName);
305 }
306 pCnt->wType = (iDataType == COUNTER_TYPE_AUTO) ? iType : iDataType;
307 pCnt->wNumSamples = iNumSamples;
308 pCnt->pRawValues = (PDH_RAW_COUNTER *)malloc(sizeof(PDH_RAW_COUNTER) * iNumSamples);
309 memset(pCnt->pRawValues, 0, sizeof(PDH_RAW_COUNTER) * iNumSamples);
310 switch(pCnt->wType)
311 {
312 case COUNTER_TYPE_INT32:
313 pCnt->dwFormat = PDH_FMT_LONG;
314 break;
315 case COUNTER_TYPE_INT64:
316 pCnt->dwFormat = PDH_FMT_LARGE;
317 break;
318 case COUNTER_TYPE_FLOAT:
319 pCnt->dwFormat = PDH_FMT_DOUBLE;
320 break;
321 }
322
323 // Add counter to set
324 m_cntSet[iClass]->addCounter(pCnt);
325 return pCnt;
326 }
327
328 /**
329 * Add custom counter from configuration file
330 * Should be in form
331 * <parameter name>:<counter path>:<number of samples>:<class>:<data type>:<description>
332 * Class can be A (poll every second), B (poll every 5 seconds) or C (poll every 30 seconds)
333 */
334 BOOL AddCounterFromConfig(TCHAR *pszStr)
335 {
336 TCHAR *ptr, *eptr, *pszCurrField;
337 TCHAR szParamName[MAX_PARAM_NAME], szCounterPath[MAX_PATH];
338 TCHAR szDescription[MAX_DB_STRING] = _T("");
339 int iState, iField, iNumSamples, iClass, iPos, iDataType = DCI_DT_INT;
340 WINPERF_COUNTER *pCnt;
341
342 // Parse line
343 pszCurrField = (TCHAR *)malloc(sizeof(TCHAR) * (_tcslen(pszStr) + 1));
344 for(ptr = pszStr, iState = 0, iField = 0, iPos = 0; (iState != -1) && (iState != 255); ptr++)
345 {
346 switch(iState)
347 {
348 case 0: // Normal text
349 switch(*ptr)
350 {
351 case '\'':
352 iState = 1;
353 break;
354 case '"':
355 iState = 2;
356 break;
357 case ':': // New field
358 case 0:
359 pszCurrField[iPos] = 0;
360 switch(iField)
361 {
362 case 0: // Parameter name
363 nx_strncpy(szParamName, pszCurrField, MAX_PARAM_NAME);
364 break;
365 case 1: // Counter path
366 nx_strncpy(szCounterPath, pszCurrField, MAX_PATH);
367 break;
368 case 2: // Number of samples
369 iNumSamples = _tcstol(pszCurrField, &eptr, 0);
370 if (*eptr != 0)
371 iState = 255; // Error
372 break;
373 case 3: // Class
374 _tcsupr(pszCurrField);
375 iClass = *pszCurrField - _T('A');
376 if ((iClass < 0) || (iClass > 2))
377 iState = 255; // Error
378 break;
379 case 4: // Data type
380 iDataType = NxDCIDataTypeFromText(pszCurrField);
381 if (iDataType == -1) // Invalid data type specified
382 iState = 255;
383 break;
384 case 5: // Description
385 nx_strncpy(szDescription, pszCurrField, MAX_DB_STRING);
386 break;
387 default:
388 iState = 255; // Error
389 break;
390 }
391 iField++;
392 iPos = 0;
393 if ((iState != 255) && (*ptr == 0))
394 iState = -1; // Finish
395 break;
396 default:
397 pszCurrField[iPos++] = *ptr;
398 break;
399 }
400 break;
401 case 1: // Single quotes
402 switch(*ptr)
403 {
404 case '\'':
405 iState = 0;
406 break;
407 case 0: // Unexpected end of line
408 iState = 255;
409 break;
410 default:
411 pszCurrField[iPos++] = *ptr;
412 break;
413 }
414 break;
415 case 2: // Double quotes
416 switch(*ptr)
417 {
418 case '"':
419 iState = 0;
420 break;
421 case 0: // Unexpected end of line
422 iState = 255;
423 break;
424 default:
425 pszCurrField[iPos++] = *ptr;
426 break;
427 }
428 break;
429 default:
430 break;
431 }
432 }
433 free(pszCurrField);
434
435 // Add new counter if parsing was successful
436 if ((iState == -1) && (iField >= 4) && (iField <= 6))
437 {
438 pCnt = AddCounter(szCounterPath, iClass, iNumSamples, (iDataType == DCI_DT_FLOAT) ? COUNTER_TYPE_FLOAT : COUNTER_TYPE_AUTO);
439 if (pCnt != NULL)
440 AddParameter(szParamName, H_CollectedCounterData, (TCHAR *)pCnt,
441 iDataType, szDescription);
442 }
443
444 return ((iState == -1) && (iField >= 4) && (iField <= 6));
445 }
446
447 /**
448 * Start collector threads
449 */
450 void StartCollectorThreads()
451 {
452 for(int i = 0; i < 3; i++)
453 m_cntSet[i]->startCollectorThread();
454 }