Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

629 lines
18 KiB

  1. // TODO: Move this to utility library.
  2. #include "stdafx.h"
  3. #include "perfmon.h"
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <spdebug.h>
  7. #define QUERY_GLOBAL 1
  8. #define QUERY_ITEMS 2
  9. #define QUERY_FOREIGN 3
  10. #define QUERY_COSTLY 4
  11. DWORD GetQueryType (IN LPWSTR);
  12. BOOL IsNumberInUnicodeList (DWORD, LPWSTR);
  13. DWORD CPerfCounterManager::Init(char * mapname, __int32 cCountersPerObject, __int32 cObjectsMax)
  14. {
  15. if (m_pbSharedMem)
  16. ERROR_SUCCESS; // already inited.
  17. m_cCountersPerObject = cCountersPerObject;
  18. m_cObjectsMax = cObjectsMax;
  19. m_cbPerCounterBlock = sizeof(PERF_COUNTER_BLOCK) + m_cCountersPerObject * sizeof(ULONG);
  20. m_cbPerInstance = sizeof(PerfInstanceHeader) + m_cCountersPerObject * sizeof(ULONG);
  21. // open/create shared memory used by the application to pass performance values
  22. DWORD MemStatus;
  23. SetLastError (ERROR_SUCCESS); // just to clear it out
  24. m_hSharedMem = CreateFileMapping(
  25. (HANDLE)0xFFFFFFFF, // to create a temp file
  26. NULL, // no security
  27. PAGE_READWRITE, // to allow read & write access
  28. 0, // file size (high dword)
  29. cObjectsMax * m_cbPerInstance, // file size (low dword)
  30. mapname); // object name
  31. // return error if unsuccessful
  32. if (m_hSharedMem == NULL)
  33. return GetLastError();
  34. MemStatus = GetLastError(); // to see if this is the first opening
  35. m_pbSharedMem = (unsigned __int8 *) MapViewOfFile(
  36. m_hSharedMem, // shared mem handle
  37. FILE_MAP_WRITE, // access desired
  38. 0, // starting offset
  39. 0,
  40. 0); // map the entire object
  41. if (m_pbSharedMem == NULL)
  42. return GetLastError();
  43. if (MemStatus != ERROR_ALREADY_EXISTS)
  44. {
  45. // New file. Clear it out.
  46. // BUGBUG: Should really protect this with mutex, I guess.
  47. memset (m_pbSharedMem, 0, m_cObjectsMax * m_cbPerInstance);
  48. }
  49. return ERROR_SUCCESS;
  50. }
  51. DWORD CPerfCounterManager::Open(LPWSTR lpDeviceNames, char * appname, PerfObject * ppo, PerfCounter * apc)
  52. {
  53. SPDBG_ASSERT(m_pbSharedMem); // Caller should have already called Init()
  54. HKEY hKeyDriverPerf;
  55. DWORD dwFirstCounter;
  56. DWORD dwFirstHelp;
  57. char ach[256];
  58. LONG status;
  59. DWORD size;
  60. DWORD type;
  61. sprintf(ach, "SYSTEM\\CurrentControlSet\\Services\\%s\\Performance", appname);
  62. status = RegOpenKeyEx (
  63. HKEY_LOCAL_MACHINE,
  64. ach,
  65. 0L,
  66. KEY_ALL_ACCESS,
  67. &hKeyDriverPerf);
  68. if (status != ERROR_SUCCESS)
  69. return status;
  70. size = sizeof (DWORD);
  71. status = RegQueryValueEx(
  72. hKeyDriverPerf,
  73. "First Counter",
  74. 0L,
  75. &type,
  76. (LPBYTE)&dwFirstCounter,
  77. &size);
  78. if (status != ERROR_SUCCESS)
  79. return status;
  80. size = sizeof (DWORD);
  81. status = RegQueryValueEx(
  82. hKeyDriverPerf,
  83. "First Help",
  84. 0L,
  85. &type,
  86. (LPBYTE)&dwFirstHelp,
  87. &size);
  88. if (status != ERROR_SUCCESS)
  89. return status;
  90. PERF_OBJECT_TYPE pot;
  91. PERF_COUNTER_DEFINITION pcd;
  92. pot.TotalByteLength = 0; // Filled in later by Collect()
  93. pot.DefinitionLength = sizeof(pot) + m_cCountersPerObject * sizeof(pcd);
  94. pot.HeaderLength = sizeof(pot);
  95. pot.ObjectNameTitleIndex = ppo->ObjectNameTitleIndex + dwFirstCounter;
  96. pot.ObjectNameTitle = NULL;
  97. pot.ObjectHelpTitleIndex = ppo->ObjectNameTitleIndex + dwFirstHelp; // For now assume name/help same
  98. pot.ObjectHelpTitle = NULL;
  99. pot.DetailLevel = ppo->DetailLevel;
  100. pot.NumCounters = m_cCountersPerObject;
  101. pot.DefaultCounter = ppo->DefaultCounter;
  102. pot.NumInstances = -1; // Filled in later by Collect()
  103. pot.CodePage = 0; // Instance names are in Unicode (they don't exist, yet)
  104. pot.PerfTime = ppo->PerfTime;
  105. pot.PerfFreq = ppo->PerfFreq;
  106. if (!m_accumHeader.Accumulate(&pot, sizeof(pot)))
  107. return ERROR_OUTOFMEMORY;
  108. pcd.ByteLength = sizeof(pcd);
  109. pcd.CounterNameTitle = NULL;
  110. pcd.CounterHelpTitle = NULL;
  111. pcd.CounterSize = sizeof(DWORD);
  112. for (unsigned __int32 i = 0; i < m_cCountersPerObject; ++i)
  113. {
  114. SPDBG_ASSERT(apc[i].CounterNameTitleIndex == i * 2 + 2);
  115. pcd.CounterNameTitleIndex = apc[i].CounterNameTitleIndex + dwFirstCounter;
  116. pcd.CounterHelpTitleIndex = apc[i].CounterNameTitleIndex + dwFirstHelp; // For now assume same as name.
  117. pcd.DefaultScale = apc[i].DefaultScale;
  118. pcd.DetailLevel = apc[i].DetailLevel;
  119. pcd.CounterType = apc[i].CounterType;
  120. pcd.CounterOffset = sizeof(PERF_COUNTER_BLOCK) + i * sizeof(DWORD);
  121. if (!m_accumHeader.Accumulate(&pcd, sizeof(pcd)))
  122. return ERROR_OUTOFMEMORY;
  123. }
  124. // BUGBUG Check Last Counter in registry to make sure lodctr's been run on the most recent file.
  125. m_ppot = (PERF_OBJECT_TYPE *) m_accumHeader.Buffer();
  126. return ERROR_SUCCESS;
  127. }
  128. DWORD CPerfCounterManager::Collect(LPWSTR lpwszValue, LPVOID *lppData, LPDWORD lpcbBytes, LPDWORD lpcObjectTypes)
  129. {
  130. ULONG SpaceNeeded;
  131. DWORD dwQueryType;
  132. unsigned __int8 * pbOutput;
  133. int cInstances = 0;
  134. // before doing anything else, see if Open went OK
  135. if (m_pbSharedMem == NULL || m_ppot == NULL) {
  136. *lpcbBytes = (DWORD) 0;
  137. *lpcObjectTypes = (DWORD) 0;
  138. return ERROR_SUCCESS; // yes, this is a successful exit
  139. }
  140. // see if this is a foreign (i.e. non-NT) computer data request
  141. dwQueryType = GetQueryType(lpwszValue);
  142. if (dwQueryType == QUERY_FOREIGN) {
  143. // this routine does not service requests for data from
  144. // Non-NT computers
  145. *lpcbBytes = (DWORD) 0;
  146. *lpcObjectTypes = (DWORD) 0;
  147. return ERROR_SUCCESS;
  148. }
  149. if (dwQueryType == QUERY_ITEMS) {
  150. if ( !(IsNumberInUnicodeList(m_ppot->ObjectNameTitleIndex, lpwszValue))) {
  151. // request received for data object not provided by this routine
  152. *lpcbBytes = (DWORD) 0;
  153. *lpcObjectTypes = (DWORD) 0;
  154. return ERROR_SUCCESS;
  155. }
  156. }
  157. pbOutput = (unsigned __int8 *) *lppData;
  158. SpaceNeeded = m_accumHeader.Size();
  159. if (*lpcbBytes < SpaceNeeded) {
  160. ErrorMoreData:
  161. // not enough room so return nothing.
  162. *lpcbBytes = (DWORD) 0;
  163. *lpcObjectTypes = (DWORD) 0;
  164. return ERROR_MORE_DATA;
  165. }
  166. // copy the object & counter definition information
  167. memmove(pbOutput, m_accumHeader.Buffer(), m_accumHeader.Size());
  168. pbOutput += m_accumHeader.Size();
  169. // Run through the shared memory and accumulate counters for active object instances.
  170. for (unsigned __int32 i = 0; i < m_cObjectsMax; ++i)
  171. {
  172. PerfInstanceHeader * ppih = (PerfInstanceHeader *) (m_pbSharedMem + i * m_cbPerInstance);
  173. // Active instances have non-zero ByteLength.
  174. if (ppih->fInUse)
  175. {
  176. if (GetProcessVersion(ppih->dwPID) == 0)
  177. {
  178. // This means the owning process of this instance has died.
  179. FreeInstance(i);
  180. continue;
  181. }
  182. PERF_INSTANCE_DEFINITION pid;
  183. SpaceNeeded += sizeof(pid) + 4 + m_cbPerCounterBlock;
  184. if (*lpcbBytes < SpaceNeeded)
  185. goto ErrorMoreData;
  186. // Build object instance block for this instance.
  187. pid.ByteLength = sizeof(pid) + 4;
  188. pid.ParentObjectTitleIndex = 0;
  189. pid.ParentObjectInstance = 0;
  190. pid.UniqueID = i + 1;
  191. pid.NameOffset = sizeof(pid);
  192. pid.NameLength = 4;
  193. memcpy(pbOutput, &pid, sizeof(pid));
  194. pbOutput += sizeof(pid);
  195. // Null terminate instance name.
  196. // TODO: Should use wsprintf or something for number.
  197. *pbOutput++ = '0' + i;
  198. *pbOutput++ = 0;
  199. *pbOutput++ = 0;
  200. *pbOutput++ = 0;
  201. PERF_COUNTER_BLOCK pcb;
  202. pcb.ByteLength = m_cbPerCounterBlock;
  203. memcpy(pbOutput, &pcb, sizeof(PERF_COUNTER_BLOCK));
  204. pbOutput += sizeof(PERF_COUNTER_BLOCK);
  205. memcpy(pbOutput, (BYTE *) ppih + sizeof(PerfInstanceHeader), m_cCountersPerObject * sizeof(ULONG));
  206. pbOutput += m_cCountersPerObject * sizeof(ULONG);
  207. // setup for the next instance
  208. ++cInstances;
  209. }
  210. }
  211. if (cInstances == 0)
  212. {
  213. // always return an "instance sized" buffer after the definition blocks
  214. // to prevent perfmon from reading bogus data. This is strictly a hack
  215. // to accomodate how PERFMON handles the "0" instance case.
  216. // By doing this, perfmon won't choke when there are no instances
  217. // and the counter object & counters will be displayed in the
  218. // list boxes, even though no instances will be listed.
  219. SpaceNeeded += sizeof(PERF_INSTANCE_DEFINITION) + m_cbPerCounterBlock;
  220. if (*lpcbBytes < SpaceNeeded)
  221. goto ErrorMoreData;
  222. memset(pbOutput, 0, sizeof(PERF_INSTANCE_DEFINITION) + m_cbPerCounterBlock);
  223. pbOutput += sizeof(PERF_INSTANCE_DEFINITION) + m_cbPerCounterBlock;
  224. }
  225. // Fill in lengths in output buffer.
  226. PERF_OBJECT_TYPE * ppot = (PERF_OBJECT_TYPE *) *lppData;
  227. ppot->NumInstances = cInstances;
  228. ppot->TotalByteLength = SpaceNeeded;
  229. // update arguments for return
  230. SPDBG_ASSERT(pbOutput == (unsigned __int8 *) *lppData + SpaceNeeded);
  231. *lppData = pbOutput;
  232. *lpcObjectTypes = 1;
  233. *lpcbBytes = SpaceNeeded;
  234. return ERROR_SUCCESS;
  235. }
  236. DWORD CPerfCounterManager::Close()
  237. {
  238. return ERROR_SUCCESS;
  239. }
  240. CPerfCounterManager::~CPerfCounterManager()
  241. {
  242. if (m_pbSharedMem)
  243. {
  244. UnmapViewOfFile(m_pbSharedMem);
  245. }
  246. if (m_hSharedMem)
  247. {
  248. CloseHandle(m_hSharedMem);
  249. }
  250. }
  251. CPerfCounterManager::AllocInstance()
  252. {
  253. // BUGBUG: Oughta be a mutex or something here...
  254. for (unsigned __int32 i = 0; i < m_cObjectsMax; ++i)
  255. {
  256. PerfInstanceHeader * ppih = (PerfInstanceHeader *) (m_pbSharedMem + i * m_cbPerInstance);
  257. if (!ppih->fInUse)
  258. {
  259. memset((unsigned __int8 *) ppih + sizeof(*ppih), 0, m_cCountersPerObject * sizeof(DWORD));
  260. ppih->dwPID = GetCurrentProcessId();
  261. ppih->fInUse = true;
  262. return i;
  263. }
  264. }
  265. // TODO: Attempt to grow file, remove compile-time limit.
  266. return -1;
  267. }
  268. void
  269. CPerfCounterManager::FreeInstance(__int32 iInstance)
  270. {
  271. PerfInstanceHeader * ppih = (PerfInstanceHeader *) (m_pbSharedMem + iInstance * m_cbPerInstance);
  272. // Active instances have non-zero ByteLength.
  273. SPDBG_ASSERT(ppih->fInUse);
  274. ppih->fInUse = false;
  275. }
  276. void
  277. CPerfCounterObject::IncrementCounter(PERFC perfc)
  278. {
  279. if (m_ppcm)
  280. {
  281. // For now, use the symbol offset from the perfsym.h file as the perfc type.
  282. // Since those grow by twos, we need to divide to get index into array.
  283. ++ * (DWORD *) (m_ppcm->m_pbSharedMem + m_iInstance * m_ppcm->m_cbPerInstance + sizeof(PerfInstanceHeader) + ((perfc / 2) - 1) * sizeof(DWORD));
  284. }
  285. }
  286. void
  287. CPerfCounterObject::SetCounter(PERFC perfc, __int32 value)
  288. {
  289. if (m_ppcm)
  290. {
  291. * (DWORD *) (m_ppcm->m_pbSharedMem + m_iInstance * m_ppcm->m_cbPerInstance + sizeof(PerfInstanceHeader) + ((perfc / 2) - 1) * sizeof(DWORD)) = value;
  292. }
  293. }
  294. bool
  295. CPerfCounterObject::Init(CPerfCounterManager * ppcm)
  296. {
  297. SPDBG_ASSERT(m_ppcm == NULL);
  298. if ((m_iInstance = ppcm->AllocInstance()) >= 0)
  299. {
  300. m_ppcm = ppcm;
  301. return true;
  302. }
  303. return false;
  304. }
  305. CPerfCounterObject::~CPerfCounterObject()
  306. {
  307. if (m_ppcm)
  308. m_ppcm->FreeInstance(m_iInstance);
  309. }
  310. // Grody utility fns from SDk sample.
  311. WCHAR GLOBAL_STRING[] = L"Global";
  312. WCHAR FOREIGN_STRING[] = L"Foreign";
  313. WCHAR COSTLY_STRING[] = L"Costly";
  314. WCHAR NULL_STRING[] = L"\0"; // pointer to null string
  315. // test for delimiter, end of line and non-digit characters
  316. // used by IsNumberInUnicodeList routine
  317. //
  318. #define DIGIT 1
  319. #define DELIMITER 2
  320. #define INVALID 3
  321. #define EvalThisChar(c,d) ( \
  322. (c == d) ? DELIMITER : \
  323. (c == 0) ? DELIMITER : \
  324. (c < (WCHAR)'0') ? INVALID : \
  325. (c > (WCHAR)'9') ? INVALID : \
  326. DIGIT)
  327. DWORD
  328. GetQueryType (
  329. IN LPWSTR lpValue
  330. )
  331. /*++
  332. GetQueryType
  333. returns the type of query described in the lpValue string so that
  334. the appropriate processing method may be used
  335. Arguments
  336. IN lpValue
  337. string passed to PerfRegQuery Value for processing
  338. Return Value
  339. QUERY_GLOBAL
  340. if lpValue == 0 (null pointer)
  341. lpValue == pointer to Null string
  342. lpValue == pointer to "Global" string
  343. QUERY_FOREIGN
  344. if lpValue == pointer to "Foriegn" string
  345. QUERY_COSTLY
  346. if lpValue == pointer to "Costly" string
  347. QUERY_ITEMS
  348. otherwise
  349. --*/
  350. {
  351. WCHAR *pwcArgChar, *pwcTypeChar;
  352. BOOL bFound;
  353. if (lpValue == 0) {
  354. return QUERY_GLOBAL;
  355. } else if (*lpValue == 0) {
  356. return QUERY_GLOBAL;
  357. }
  358. // check for "Global" request
  359. pwcArgChar = lpValue;
  360. pwcTypeChar = GLOBAL_STRING;
  361. bFound = TRUE; // assume found until contradicted
  362. // check to the length of the shortest string
  363. while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
  364. if (*pwcArgChar++ != *pwcTypeChar++) {
  365. bFound = FALSE; // no match
  366. break; // bail out now
  367. }
  368. }
  369. if (bFound) return QUERY_GLOBAL;
  370. // check for "Foreign" request
  371. pwcArgChar = lpValue;
  372. pwcTypeChar = FOREIGN_STRING;
  373. bFound = TRUE; // assume found until contradicted
  374. // check to the length of the shortest string
  375. while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
  376. if (*pwcArgChar++ != *pwcTypeChar++) {
  377. bFound = FALSE; // no match
  378. break; // bail out now
  379. }
  380. }
  381. if (bFound) return QUERY_FOREIGN;
  382. // check for "Costly" request
  383. pwcArgChar = lpValue;
  384. pwcTypeChar = COSTLY_STRING;
  385. bFound = TRUE; // assume found until contradicted
  386. // check to the length of the shortest string
  387. while ((*pwcArgChar != 0) && (*pwcTypeChar != 0)) {
  388. if (*pwcArgChar++ != *pwcTypeChar++) {
  389. bFound = FALSE; // no match
  390. break; // bail out now
  391. }
  392. }
  393. if (bFound) return QUERY_COSTLY;
  394. // if not Global and not Foreign and not Costly,
  395. // then it must be an item list
  396. return QUERY_ITEMS;
  397. }
  398. BOOL
  399. IsNumberInUnicodeList (
  400. IN DWORD dwNumber,
  401. IN LPWSTR lpwszUnicodeList
  402. )
  403. /*++
  404. IsNumberInUnicodeList
  405. Arguments:
  406. IN dwNumber
  407. DWORD number to find in list
  408. IN lpwszUnicodeList
  409. Null terminated, Space delimited list of decimal numbers
  410. Return Value:
  411. TRUE:
  412. dwNumber was found in the list of unicode number strings
  413. FALSE:
  414. dwNumber was not found in the list.
  415. --*/
  416. {
  417. DWORD dwThisNumber;
  418. WCHAR *pwcThisChar;
  419. BOOL bValidNumber;
  420. BOOL bNewItem;
  421. WCHAR wcDelimiter; // could be an argument to be more flexible
  422. if (lpwszUnicodeList == 0) return FALSE; // null pointer, # not founde
  423. pwcThisChar = lpwszUnicodeList;
  424. dwThisNumber = 0;
  425. wcDelimiter = (WCHAR)' ';
  426. bValidNumber = FALSE;
  427. bNewItem = TRUE;
  428. while (TRUE) {
  429. switch (EvalThisChar (*pwcThisChar, wcDelimiter)) {
  430. case DIGIT:
  431. // if this is the first digit after a delimiter, then
  432. // set flags to start computing the new number
  433. if (bNewItem) {
  434. bNewItem = FALSE;
  435. bValidNumber = TRUE;
  436. }
  437. if (bValidNumber) {
  438. dwThisNumber *= 10;
  439. dwThisNumber += (*pwcThisChar - (WCHAR)'0');
  440. }
  441. break;
  442. case DELIMITER:
  443. // a delimter is either the delimiter character or the
  444. // end of the string ('\0') if when the delimiter has been
  445. // reached a valid number was found, then compare it to the
  446. // number from the argument list. if this is the end of the
  447. // string and no match was found, then return.
  448. //
  449. if (bValidNumber) {
  450. if (dwThisNumber == dwNumber) return TRUE;
  451. bValidNumber = FALSE;
  452. }
  453. if (*pwcThisChar == 0) {
  454. return FALSE;
  455. } else {
  456. bNewItem = TRUE;
  457. dwThisNumber = 0;
  458. }
  459. break;
  460. case INVALID:
  461. // if an invalid character was encountered, ignore all
  462. // characters up to the next delimiter and then start fresh.
  463. // the invalid number is not compared.
  464. bValidNumber = FALSE;
  465. break;
  466. default:
  467. break;
  468. }
  469. pwcThisChar++;
  470. }
  471. } // IsNumberInUnicodeList
  472. // CAccumulator
  473. bool CAccumulator::Accumulate(void * pb, DWORD cb)
  474. {
  475. if (m_cbCur + cb > m_cbAlloc)
  476. {
  477. // Fairly random heuristic growth pattern.
  478. DWORD cbAllocNew = (m_cbCur + cb + 16) * 5 / 4;
  479. BYTE * pbNew = (BYTE *) realloc(m_pb, cbAllocNew);
  480. if (pbNew == NULL)
  481. return false;
  482. m_pb = pbNew;
  483. m_cbAlloc = cbAllocNew;
  484. }
  485. if (pb)
  486. memcpy(m_pb + m_cbCur, (BYTE *) pb, cb);
  487. m_cbCur += cb;
  488. return true;
  489. }