1730f473a63a9930f5f48daafcdc16baccdd80cc
[public/netxms.git] / src / agent / subagents / winperf / winperf.cpp
1 /*
2 ** Windows Performance NetXMS subagent
3 ** Copyright (C) 2004-2012 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: winperf.cpp
20 **
21 **/
22
23 #include "winperf.h"
24
25
26 //
27 // Constants
28 //
29
30 #define MAX_CPU_COUNT 64
31
32 #define WPF_ENABLE_DEFAULT_COUNTERS 0x0001
33
34
35 //
36 // Global variables
37 //
38
39 HANDLE g_hCondShutdown = NULL;
40
41
42 //
43 // Static variables
44 //
45
46 static DWORD m_dwFlags = WPF_ENABLE_DEFAULT_COUNTERS;
47 static DWORD m_dwNumCPU = 1;
48 static WINPERF_COUNTER *m_pProcessorCounters[MAX_CPU_COUNT];
49 static WINPERF_COUNTER *m_pProcessorCounters5[MAX_CPU_COUNT];
50 static WINPERF_COUNTER *m_pProcessorCounters15[MAX_CPU_COUNT];
51
52
53 //
54 // List of predefined performance counters
55 //
56 static struct
57 {
58 TCHAR *pszParamName;
59 TCHAR *pszCounterName;
60 int iClass;
61 int iNumSamples;
62 int iDataType;
63 TCHAR *pszDescription;
64 int iDCIDataType;
65 } m_counterList[] =
66 {
67 { _T("System.CPU.LoadAvg"), _T("\\System\\Processor Queue Length"), 0,
68 60, COUNTER_TYPE_FLOAT, _T("Average CPU load for last minute"), DCI_DT_FLOAT },
69 { _T("System.CPU.LoadAvg5"), _T("\\System\\Processor Queue Length"), 0,
70 300, COUNTER_TYPE_FLOAT, _T("Average CPU load for last 5 minutes"), DCI_DT_FLOAT },
71 { _T("System.CPU.LoadAvg15"), _T("\\System\\Processor Queue Length"), 0,
72 900, COUNTER_TYPE_FLOAT, _T("Average CPU load for last 15 minutes"), DCI_DT_FLOAT },
73 { _T("System.CPU.Usage"), _T("\\Processor(_Total)\\% Processor Time"), 0,
74 60, COUNTER_TYPE_INT32, _T("Average CPU utilization for last minute"), DCI_DT_INT },
75 { _T("System.CPU.Usage5"), _T("\\Processor(_Total)\\% Processor Time"), 0,
76 300, COUNTER_TYPE_INT32, _T("Average CPU utilization for last 5 minutes"), DCI_DT_INT },
77 { _T("System.CPU.Usage15"), _T("\\Processor(_Total)\\% Processor Time"), 0,
78 900, COUNTER_TYPE_INT32, _T("Average CPU utilization for last 15 minutes"), DCI_DT_INT },
79 { _T("System.IO.DiskQueue"), _T("\\PhysicalDisk(_Total)\\Avg. Disk Queue Length"), 0,
80 60, COUNTER_TYPE_FLOAT, _T("Average disk queue length for last minute"), DCI_DT_FLOAT },
81 { _T("System.IO.DiskTime"), _T("\\PhysicalDisk(_Total)\\% Disk Time"), 0,
82 60, COUNTER_TYPE_FLOAT, _T("Average disk busy time for last minute"), DCI_DT_FLOAT },
83 { NULL, NULL, 0, 0, 0, NULL, 0 }
84 };
85
86
87 //
88 // Value of given performance counter
89 //
90
91 static LONG H_PdhVersion(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
92 {
93 DWORD dwVersion;
94
95 if (PdhGetDllVersion(&dwVersion) != ERROR_SUCCESS)
96 return SYSINFO_RC_ERROR;
97 ret_uint(pValue, dwVersion);
98 return SYSINFO_RC_SUCCESS;
99 }
100
101
102 //
103 // Value of CPU utilization counter
104 //
105
106 static LONG H_CPUUsage(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
107 {
108 LONG nProcessor, nRet = SYSINFO_RC_SUCCESS;
109 TCHAR *pEnd, szBuffer[16];
110
111 if (!AgentGetParameterArg(pszParam, 1, szBuffer, 16))
112 return SYSINFO_RC_UNSUPPORTED;
113
114 nProcessor = _tcstol(szBuffer, &pEnd, 0);
115 if ((*pEnd != 0) || (nProcessor < 0) || (nProcessor >= (LONG)m_dwNumCPU))
116 return SYSINFO_RC_UNSUPPORTED;
117
118 switch(pArg[0])
119 {
120 case _T('1'): // System.CPU.Usage(*)
121 if (m_pProcessorCounters[nProcessor] != NULL)
122 ret_int(pValue, m_pProcessorCounters[nProcessor]->value.iLong);
123 else
124 nRet = SYSINFO_RC_ERROR;
125 break;
126 case _T('2'): // System.CPU.Usage5(*)
127 if (m_pProcessorCounters5[nProcessor] != NULL)
128 ret_int(pValue, m_pProcessorCounters5[nProcessor]->value.iLong);
129 else
130 nRet = SYSINFO_RC_ERROR;
131 break;
132 case _T('3'): // System.CPU.Usage15(*)
133 if (m_pProcessorCounters15[nProcessor] != NULL)
134 ret_int(pValue, m_pProcessorCounters15[nProcessor]->value.iLong);
135 else
136 nRet = SYSINFO_RC_ERROR;
137 break;
138 default:
139 nRet = SYSINFO_RC_UNSUPPORTED;
140 break;
141 }
142
143 return nRet;
144 }
145
146 /**
147 * Value of given counter collected by one of the collector threads
148 */
149 LONG H_CollectedCounterData(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
150 {
151 switch(((WINPERF_COUNTER *)pArg)->wType)
152 {
153 case COUNTER_TYPE_INT32:
154 ret_int(pValue, ((WINPERF_COUNTER *)pArg)->value.iLong);
155 break;
156 case COUNTER_TYPE_INT64:
157 ret_int64(pValue, ((WINPERF_COUNTER *)pArg)->value.iLarge);
158 break;
159 case COUNTER_TYPE_FLOAT:
160 ret_double(pValue, ((WINPERF_COUNTER *)pArg)->value.dFloat);
161 break;
162 }
163 return SYSINFO_RC_SUCCESS;
164 }
165
166 /**
167 * Value of given performance counter
168 */
169 static LONG H_PdhCounterValue(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
170 {
171 HQUERY hQuery;
172 HCOUNTER hCounter;
173 PDH_RAW_COUNTER rawData1, rawData2;
174 PDH_FMT_COUNTERVALUE counterValue;
175 PDH_STATUS rc;
176 TCHAR szCounter[MAX_PATH];
177 DWORD dwType;
178 BOOL bUseTwoSamples = FALSE;
179 static TCHAR szFName[] = _T("H_PdhCounterValue");
180
181 if (pArg == NULL) // Normal call
182 {
183 if (!AgentGetParameterArg(pszParam, 1, szCounter, MAX_PATH))
184 return SYSINFO_RC_UNSUPPORTED;
185 }
186 else // Call from H_CounterAlias
187 {
188 nx_strncpy(szCounter, pArg, MAX_PATH);
189 }
190
191 if ((rc = PdhOpenQuery(NULL, 0, &hQuery)) != ERROR_SUCCESS)
192 {
193 ReportPdhError(szFName, _T("PdhOpenQuery"), rc);
194 return SYSINFO_RC_ERROR;
195 }
196
197 if ((rc = PdhAddCounter(hQuery, szCounter, 0, &hCounter)) != ERROR_SUCCESS)
198 {
199 // Attempt to translate counter name
200 if ((rc == PDH_CSTATUS_NO_COUNTER) || (rc == PDH_CSTATUS_NO_OBJECT))
201 {
202 TCHAR *newName = (TCHAR *)malloc(_tcslen(szCounter) * sizeof(TCHAR) * 4);
203 if (TranslateCounterName(szCounter, newName))
204 {
205 AgentWriteDebugLog(2, _T("WINPERF: Counter translated: %s ==> %s"), szCounter, newName);
206 rc = PdhAddCounter(hQuery, newName, 0, &hCounter);
207 }
208 free(newName);
209 }
210 if (rc != ERROR_SUCCESS)
211 {
212 ReportPdhError(szFName, _T("PdhAddCounter"), rc);
213 PdhCloseQuery(hQuery);
214 return SYSINFO_RC_UNSUPPORTED;
215 }
216 }
217
218 // Get first sample
219 if ((rc = PdhCollectQueryData(hQuery)) != ERROR_SUCCESS)
220 {
221 ReportPdhError(szFName, _T("PdhCollectQueryData"), rc);
222 PdhCloseQuery(hQuery);
223 return SYSINFO_RC_ERROR;
224 }
225 PdhGetRawCounterValue(hCounter, &dwType, &rawData1);
226
227 // Get second sample if required
228 if ((dwType & 0x00000C00) == PERF_TYPE_COUNTER)
229 {
230 DWORD subType = dwType & 0x000F0000;
231 if ((subType == PERF_COUNTER_RATE) || (subType == PERF_COUNTER_PRECISION))
232 {
233 Sleep(1000); // We will take second sample after one second
234 if ((rc = PdhCollectQueryData(hQuery)) != ERROR_SUCCESS)
235 {
236 ReportPdhError(szFName, _T("PdhCollectQueryData"), rc);
237 PdhCloseQuery(hQuery);
238 return SYSINFO_RC_ERROR;
239 }
240 PdhGetRawCounterValue(hCounter, NULL, &rawData2);
241 bUseTwoSamples = TRUE;
242 }
243 }
244
245 if ((dwType & 0x00000300) == PERF_SIZE_LARGE)
246 {
247 if (bUseTwoSamples)
248 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LARGE,
249 &rawData2, &rawData1, &counterValue);
250 else
251 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LARGE,
252 &rawData1, NULL, &counterValue);
253 ret_int64(pValue, counterValue.largeValue);
254 }
255 else
256 {
257 if (bUseTwoSamples)
258 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LONG,
259 &rawData2, &rawData1, &counterValue);
260 else
261 PdhCalculateCounterFromRawValue(hCounter, PDH_FMT_LONG,
262 &rawData1, NULL, &counterValue);
263 ret_int(pValue, counterValue.longValue);
264 }
265 PdhCloseQuery(hQuery);
266 return SYSINFO_RC_SUCCESS;
267 }
268
269 /**
270 * List of available performance objects
271 */
272 static LONG H_PdhObjects(const TCHAR *pszParam, const TCHAR *pArg, StringList *value)
273 {
274 TCHAR *pszObject, *pszObjList, szHostName[256];
275 LONG iResult = SYSINFO_RC_ERROR;
276 PDH_STATUS rc;
277 DWORD dwSize;
278
279 dwSize = 256;
280 if (GetComputerName(szHostName, &dwSize))
281 {
282 dwSize = 256000;
283 pszObjList = (TCHAR *)malloc(sizeof(TCHAR) * dwSize);
284 if ((rc = PdhEnumObjects(NULL, szHostName, pszObjList, &dwSize,
285 PERF_DETAIL_WIZARD, TRUE)) == ERROR_SUCCESS)
286 {
287 for(pszObject = pszObjList; *pszObject != 0; pszObject += _tcslen(pszObject) + 1)
288 value->add(pszObject);
289 iResult = SYSINFO_RC_SUCCESS;
290 }
291 else
292 {
293 ReportPdhError(_T("H_PdhObjects"), _T("PdhEnumObjects"), rc);
294 }
295 free(pszObjList);
296 }
297 return iResult;
298 }
299
300 /**
301 * List of available performance items for given object
302 */
303 static LONG H_PdhObjectItems(const TCHAR *pszParam, const TCHAR *pArg, StringList *value)
304 {
305 TCHAR *pszElement, *pszCounterList, *pszInstanceList, szHostName[256], szObject[256];
306 LONG iResult = SYSINFO_RC_ERROR;
307 DWORD dwSize1, dwSize2;
308 PDH_STATUS rc;
309
310 AgentGetParameterArg(pszParam, 1, szObject, 256);
311 if (szObject[0] != 0)
312 {
313 dwSize1 = 256;
314 if (GetComputerName(szHostName, &dwSize1))
315 {
316 dwSize1 = dwSize2 = 256000;
317 pszCounterList = (TCHAR *)malloc(sizeof(TCHAR) * dwSize1);
318 pszInstanceList = (TCHAR *)malloc(sizeof(TCHAR) * dwSize2);
319 rc = PdhEnumObjectItems(NULL, szHostName, szObject,
320 pszCounterList, &dwSize1, pszInstanceList, &dwSize2,
321 PERF_DETAIL_WIZARD, 0);
322 if ((rc == ERROR_SUCCESS) || (rc == PDH_MORE_DATA))
323 {
324 for(pszElement = (pArg[0] == _T('C')) ? pszCounterList : pszInstanceList;
325 *pszElement != 0; pszElement += _tcslen(pszElement) + 1)
326 value->add(pszElement);
327 iResult = SYSINFO_RC_SUCCESS;
328 }
329 else
330 {
331 ReportPdhError(_T("H_PdhObjectItems"), _T("PdhEnumObjectItems"), rc);
332 }
333 free(pszCounterList);
334 free(pszInstanceList);
335 }
336 }
337 else
338 {
339 iResult = SYSINFO_RC_UNSUPPORTED;
340 }
341 return iResult;
342 }
343
344
345 //
346 // Value of specific performance parameter, which is mapped one-to-one to
347 // performance counter. Actually, it's an alias for PDH.CounterValue(xxx) parameter.
348 //
349
350 static LONG H_CounterAlias(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue)
351 {
352 return H_PdhCounterValue(NULL, pArg, pValue);
353 }
354
355
356 //
357 // Initialize subagent
358 //
359
360 static BOOL SubAgentInit(Config *config)
361 {
362 // Create shutdown condition object
363 g_hCondShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
364 StartCollectorThreads();
365 return TRUE;
366 }
367
368 /**
369 * Handler for subagent unload
370 */
371 static void SubAgentShutdown()
372 {
373 if (g_hCondShutdown != NULL)
374 SetEvent(g_hCondShutdown);
375 Sleep(500);
376 }
377
378 /**
379 * Subagent information
380 */
381 static NETXMS_SUBAGENT_PARAM m_parameters[] =
382 {
383 { _T("PDH.CounterValue(*)"), H_PdhCounterValue, NULL, DCI_DT_INT, _T("") },
384 { _T("PDH.Version"), H_PdhVersion, NULL, DCI_DT_UINT, _T("Version of PDH.DLL") },
385 { _T("System.CPU.Usage(*)"), H_CPUUsage, _T("1"), DCI_DT_INT, DCIDESC_SYSTEM_CPU_USAGE_EX },
386 { _T("System.CPU.Usage5(*)"), H_CPUUsage, _T("2"), DCI_DT_INT, DCIDESC_SYSTEM_CPU_USAGE5_EX },
387 { _T("System.CPU.Usage15(*)"), H_CPUUsage, _T("3"), DCI_DT_INT, DCIDESC_SYSTEM_CPU_USAGE15_EX },
388 { _T("System.ThreadCount"), H_CounterAlias, _T("\\System\\Threads"), DCI_DT_UINT, DCIDESC_SYSTEM_THREADCOUNT },
389 { _T("System.Uptime"), H_CounterAlias, _T("\\System\\System Up Time"), DCI_DT_UINT, DCIDESC_SYSTEM_UPTIME }
390 };
391 static NETXMS_SUBAGENT_LIST m_enums[] =
392 {
393 { _T("PDH.ObjectCounters(*)"), H_PdhObjectItems, _T("C") },
394 { _T("PDH.ObjectInstances(*)"), H_PdhObjectItems, _T("I") },
395 { _T("PDH.Objects"), H_PdhObjects, NULL }
396 };
397
398 static NETXMS_SUBAGENT_INFO m_info =
399 {
400 NETXMS_SUBAGENT_INFO_MAGIC,
401 _T("WinPerf"), NETXMS_VERSION_STRING,
402 SubAgentInit, SubAgentShutdown, NULL, // handlers
403 0, NULL, // parameters
404 sizeof(m_enums) / sizeof(NETXMS_SUBAGENT_LIST),
405 m_enums,
406 0, NULL, // tables
407 0, NULL, // actions
408 0, NULL // push parameters
409 };
410
411
412 //
413 // Add new parameter to list
414 //
415
416 BOOL AddParameter(TCHAR *pszName, LONG (* fpHandler)(const TCHAR *, const TCHAR *, TCHAR *),
417 TCHAR *pArg, int iDataType, TCHAR *pszDescription)
418 {
419 DWORD i;
420
421 for(i = 0; i < m_info.numParameters; i++)
422 if (!_tcsicmp(pszName, m_info.parameters[i].name))
423 break;
424
425 if (i == m_info.numParameters)
426 {
427 // Extend list
428 m_info.numParameters++;
429 m_info.parameters =
430 (NETXMS_SUBAGENT_PARAM *)realloc(m_info.parameters,
431 sizeof(NETXMS_SUBAGENT_PARAM) * m_info.numParameters);
432 }
433
434 nx_strncpy(m_info.parameters[i].name, pszName, MAX_PARAM_NAME);
435 m_info.parameters[i].handler = fpHandler;
436 m_info.parameters[i].arg = pArg;
437 m_info.parameters[i].dataType = iDataType;
438 nx_strncpy(m_info.parameters[i].description, pszDescription, MAX_DB_STRING);
439
440 return TRUE;
441 }
442
443 /**
444 * Add predefined counters
445 */
446 static void AddPredefinedCounters()
447 {
448 DWORD i;
449 WINPERF_COUNTER *pCnt;
450 SYSTEM_INFO sysInfo;
451 TCHAR szBuffer[MAX_PATH];
452
453 for(i = 0; m_counterList[i].pszParamName != NULL; i++)
454 {
455 pCnt = AddCounter(m_counterList[i].pszCounterName, m_counterList[i].iClass,
456 m_counterList[i].iNumSamples, m_counterList[i].iDataType);
457 if (pCnt != NULL)
458 AddParameter(m_counterList[i].pszParamName, H_CollectedCounterData,
459 (TCHAR *)pCnt, m_counterList[i].iDCIDataType,
460 m_counterList[i].pszDescription);
461 }
462
463 // Add CPU utilization counters
464 GetSystemInfo(&sysInfo);
465 m_dwNumCPU = sysInfo.dwNumberOfProcessors;
466 for(i = 0; i < m_dwNumCPU; i++)
467 {
468 _sntprintf(szBuffer, MAX_PATH, _T("\\Processor(%d)\\%% Processor Time"), i);
469 m_pProcessorCounters[i] = AddCounter(szBuffer, 0, 60, COUNTER_TYPE_INT32);
470 m_pProcessorCounters5[i] = AddCounter(szBuffer, 0, 300, COUNTER_TYPE_INT32);
471 m_pProcessorCounters15[i] = AddCounter(szBuffer, 0, 900, COUNTER_TYPE_INT32);
472 }
473 }
474
475 /**
476 * Configuration file template
477 */
478 static TCHAR *m_pszCounterList = NULL;
479 static NX_CFG_TEMPLATE m_cfgTemplate[] =
480 {
481 { _T("Counter"), CT_STRING_LIST, _T('\n'), 0, 0, 0, &m_pszCounterList },
482 { _T("EnableDefaultCounters"), CT_BOOLEAN, 0, 0, WPF_ENABLE_DEFAULT_COUNTERS, 0, &m_dwFlags },
483 { _T(""), CT_END_OF_LIST, 0, 0, 0, 0, NULL }
484 };
485
486 /**
487 * Entry point for NetXMS agent
488 */
489 DECLARE_SUBAGENT_ENTRY_POINT(WINPERF)
490 {
491 DWORD i, dwBufferSize, dwBytes, dwType, dwStatus;
492 TCHAR *pBuffer, *newName;
493
494 if (m_info.parameters != NULL)
495 return FALSE; // Most likely another instance of WINPERF subagent already loaded
496
497 // Read performance counter indexes
498 pBuffer = NULL;
499 dwBufferSize = 0;
500 do
501 {
502 dwBufferSize += 8192;
503 pBuffer = (TCHAR *)realloc(pBuffer, dwBufferSize);
504 dwBytes = dwBufferSize;
505 dwStatus = RegQueryValueEx(HKEY_PERFORMANCE_DATA, _T("Counter 009"), NULL, &dwType, (BYTE *)pBuffer, &dwBytes);
506 } while(dwStatus == ERROR_MORE_DATA);
507 if (dwStatus == ERROR_SUCCESS)
508 {
509 CreateCounterIndex(pBuffer);
510 }
511 safe_free(pBuffer);
512
513 // Init parameters list
514 m_info.numParameters = sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM);
515 m_info.parameters = (NETXMS_SUBAGENT_PARAM *)nx_memdup(m_parameters, sizeof(m_parameters));
516
517 // Check counter names for H_CounterAlias
518 for(i = 0; i < m_info.numParameters; i++)
519 {
520 if (m_info.parameters[i].handler == H_CounterAlias)
521 {
522 CheckCounter(m_info.parameters[i].arg, &newName);
523 if (newName != NULL)
524 m_info.parameters[i].arg = newName;
525 }
526 }
527
528 // Load configuration
529 bool success = config->parseTemplate(_T("WinPerf"), m_cfgTemplate);
530 if (success)
531 {
532 TCHAR *pItem, *pEnd;
533
534 // Parse counter list
535 if (m_pszCounterList != NULL)
536 {
537 for(pItem = m_pszCounterList; *pItem != 0; pItem = pEnd + 1)
538 {
539 pEnd = _tcschr(pItem, _T('\n'));
540 if (pEnd != NULL)
541 *pEnd = 0;
542 StrStrip(pItem);
543 if (!AddCounterFromConfig(pItem))
544 AgentWriteLog(EVENTLOG_WARNING_TYPE,
545 _T("Unable to add counter from configuration file. ")
546 _T("Original configuration record: %s"), pItem);
547 }
548 free(m_pszCounterList);
549 }
550
551 if (m_dwFlags & WPF_ENABLE_DEFAULT_COUNTERS)
552 AddPredefinedCounters();
553 }
554 else
555 {
556 safe_free(m_pszCounterList);
557 }
558 *ppInfo = &m_info;
559 return success;
560 }
561
562 /**
563 * DLL entry point
564 */
565 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
566 {
567 if (dwReason == DLL_PROCESS_ATTACH)
568 DisableThreadLibraryCalls(hInstance);
569 return TRUE;
570 }