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