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.

1139 lines
40 KiB

  1. /*++
  2. Copyright (c) 1997-1998 Microsoft Corporation
  3. Module Name:
  4. perfts.c
  5. Description:
  6. DLL entry point and support code for Terminal Server performance counters.
  7. Author:
  8. Erik Mavrinac 24-Nov-1998
  9. Revision History:
  10. --*/
  11. #include <nt.h>
  12. #include <ntrtl.h>
  13. #include <nturtl.h>
  14. #include <windows.h>
  15. #include <winperf.h>
  16. #include <ntprfctr.h>
  17. #include <perfutil.h>
  18. #include <winsta.h>
  19. #include <utildll.h>
  20. #include <stdlib.h>
  21. #include "datats.h"
  22. #if DBG
  23. #define DBGPRINT(x) DbgPrint x
  24. #else
  25. #define DBGPRINT(x)
  26. #endif
  27. #define MAX_SESSION_NAME_LENGTH 50
  28. typedef struct _WinStationInfo
  29. {
  30. LIST_ENTRY HashBucketList;
  31. LIST_ENTRY UsedList;
  32. ULONG SessionID;
  33. WINSTATIONSTATECLASS LastState;
  34. void *pInstanceInfo;
  35. WINSTATIONNAMEW WinStationName;
  36. } WinStationInfo;
  37. /****************************************************************************/
  38. // Globals
  39. /****************************************************************************/
  40. // Default hash bucket list to remove having to check for
  41. // pWinStationHashBuckets == NULL on perf path. This array contains the
  42. // default number for one WinStationInfo.
  43. #define NumDefaultWinStationHashBuckets 4
  44. LIST_ENTRY DefaultWinStationHashBuckets[NumDefaultWinStationHashBuckets];
  45. HANDLE hEventLog = NULL;
  46. HANDLE hLibHeap = NULL;
  47. PBYTE pProcessBuffer = NULL;
  48. static DWORD dwOpenCount = 0;
  49. static DWORD ProcessBufSize = LARGE_BUFFER_SIZE;
  50. static DWORD NumberOfCPUs = 0;
  51. static DWORD FirstCounterIndex = 0;
  52. LIST_ENTRY UsedList;
  53. LIST_ENTRY UnusedList;
  54. LIST_ENTRY *pWinStationHashBuckets = DefaultWinStationHashBuckets;
  55. unsigned NumWinStationHashBuckets = NumDefaultWinStationHashBuckets;
  56. ULONG WinStationHashMask = 0x3;
  57. unsigned NumCachedWinStations = 0;
  58. SYSTEM_TIMEOFDAY_INFORMATION SysTimeInfo = {{0,0},{0,0},{0,0},0,0};
  59. /****************************************************************************/
  60. // Prototypes
  61. /****************************************************************************/
  62. BOOL DllProcessAttach(void);
  63. BOOL DllProcessDetach(void);
  64. DWORD GetNumberOfCPUs(void);
  65. NTSTATUS GetDescriptionOffset(void);
  66. void SetupCounterIndices(void);
  67. DWORD APIENTRY OpenWinStationObject(LPWSTR);
  68. DWORD APIENTRY CloseWinStationObject(void);
  69. DWORD APIENTRY CollectWinStationObjectData(IN LPWSTR, IN OUT LPVOID *,
  70. IN OUT LPDWORD, OUT LPDWORD);
  71. DWORD GetSystemProcessData(void);
  72. void SetupWinStationCounterBlock(WINSTATION_COUNTER_DATA *,
  73. PWINSTATIONINFORMATIONW);
  74. void UpdateWSProcessCounterBlock(WINSTATION_COUNTER_DATA *,
  75. PSYSTEM_PROCESS_INFORMATION);
  76. void CreateNewHashBuckets(unsigned);
  77. // Declares these exported functions as PerfMon entry points.
  78. PM_OPEN_PROC OpenTSObject;
  79. PM_COLLECT_PROC CollectTSObjectData;
  80. PM_CLOSE_PROC CloseTSObject;
  81. /****************************************************************************/
  82. // DLL system load/unload entry point.
  83. /****************************************************************************/
  84. BOOL __stdcall DllInit(
  85. IN HANDLE DLLHandle,
  86. IN DWORD Reason,
  87. IN LPVOID ReservedAndUnused)
  88. {
  89. ReservedAndUnused;
  90. // This will prevent the DLL from getting
  91. // the DLL_THREAD_* messages
  92. DisableThreadLibraryCalls(DLLHandle);
  93. switch(Reason) {
  94. case DLL_PROCESS_ATTACH:
  95. return DllProcessAttach();
  96. case DLL_PROCESS_DETACH:
  97. return DllProcessDetach();
  98. default:
  99. return TRUE;
  100. }
  101. }
  102. /****************************************************************************/
  103. // DLL instance load time initialization.
  104. /****************************************************************************/
  105. BOOL DllProcessAttach(void)
  106. {
  107. unsigned i;
  108. NTSTATUS Status;
  109. // DBGPRINT(("PerfTS: ProcessAttach\n"));
  110. // Create the local heap
  111. hLibHeap = HeapCreate(0, 1, 0);
  112. if (hLibHeap == NULL)
  113. return FALSE;
  114. // Open handle to the event log
  115. if (hEventLog == NULL) {
  116. hEventLog = MonOpenEventLog(L"PerfTS");
  117. if (hEventLog == NULL)
  118. goto PostCreateHeap;
  119. }
  120. // Get the counter index value and init the WinStationDataDefinition
  121. // counter values.
  122. Status = GetDescriptionOffset();
  123. if (!NT_SUCCESS(Status))
  124. goto PostOpenEventLog;
  125. SetupCounterIndices();
  126. // Pre-determine the number of system CPUs.
  127. NumberOfCPUs = GetNumberOfCPUs();
  128. // UsedList is used as a skip-list through all valid entries in the
  129. // hash table to allow us to iterate all entries without having to have
  130. // a second, low-performance loop that looks through each hash bucket
  131. // list.
  132. InitializeListHead(&UsedList);
  133. InitializeListHead(&UnusedList);
  134. for (i = 0; i < NumDefaultWinStationHashBuckets; i++)
  135. InitializeListHead(&DefaultWinStationHashBuckets[i]);
  136. return TRUE;
  137. // Error handling.
  138. PostOpenEventLog:
  139. MonCloseEventLog();
  140. hEventLog = NULL;
  141. PostCreateHeap:
  142. HeapDestroy(hLibHeap);
  143. hLibHeap = NULL;
  144. return FALSE;
  145. }
  146. /****************************************************************************/
  147. // DLL unload cleanup.
  148. /****************************************************************************/
  149. BOOL DllProcessDetach(void)
  150. {
  151. // DBGPRINT(("PerfTS: ProcessDetach\n"));
  152. if (dwOpenCount > 0) {
  153. // the Library is being unloaded before it was
  154. // closed so close it now as this is the last
  155. // chance to do it before the library is tossed.
  156. // if the value of dwOpenCount is > 1, set it to
  157. // one to insure everything will be closed when
  158. // the close function is called.
  159. if (dwOpenCount > 1)
  160. dwOpenCount = 1;
  161. CloseTSObject();
  162. }
  163. if (hEventLog != NULL) {
  164. MonCloseEventLog();
  165. hEventLog = NULL;
  166. }
  167. if (hLibHeap != NULL && HeapDestroy(hLibHeap))
  168. hLibHeap = NULL;
  169. return TRUE;
  170. }
  171. /****************************************************************************/
  172. // Utility function used at startup.
  173. /****************************************************************************/
  174. DWORD GetNumberOfCPUs(void)
  175. {
  176. NTSTATUS Status;
  177. DWORD ReturnLen;
  178. SYSTEM_BASIC_INFORMATION Info;
  179. Status = NtQuerySystemInformation(
  180. SystemBasicInformation,
  181. &Info,
  182. sizeof(Info),
  183. &ReturnLen
  184. );
  185. if (NT_SUCCESS(Status)) {
  186. return Info.NumberOfProcessors;
  187. }
  188. else {
  189. DBGPRINT(("GetNumberOfCPUs Error 0x%x returning CPU count\n",Status));
  190. // Return 1 CPU
  191. return 1;
  192. }
  193. }
  194. /****************************************************************************/
  195. // Gets the offset index of the first text description from the
  196. // TermService\Performance key. This value was created by Lodctr /
  197. // LoadPerfCounterTextStrings() during setup.
  198. /****************************************************************************/
  199. NTSTATUS GetDescriptionOffset(void)
  200. {
  201. HKEY hTermServiceKey;
  202. OBJECT_ATTRIBUTES Obja;
  203. NTSTATUS Status;
  204. UNICODE_STRING TermServiceSubKeyString;
  205. UNICODE_STRING ValueNameString;
  206. LONG ResultLength;
  207. PKEY_VALUE_PARTIAL_INFORMATION pValueInformation;
  208. BYTE ValueInfo[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD) - 1];
  209. // Initialize UNICODE_STRING structures used in this function
  210. RtlInitUnicodeString(&TermServiceSubKeyString,
  211. L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\TermService\\Performance");
  212. RtlInitUnicodeString(&ValueNameString, L"First Counter");
  213. // Initialize the OBJECT_ATTRIBUTES structure and open the key.
  214. InitializeObjectAttributes(&Obja, &TermServiceSubKeyString,
  215. OBJ_CASE_INSENSITIVE, NULL, NULL);
  216. Status = NtOpenKey(&hTermServiceKey, KEY_READ, &Obja);
  217. if (NT_SUCCESS(Status)) {
  218. // read value of desired entry
  219. pValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)ValueInfo;
  220. ResultLength = sizeof(ValueInfo);
  221. Status = NtQueryValueKey(hTermServiceKey, &ValueNameString,
  222. KeyValuePartialInformation, pValueInformation,
  223. sizeof(ValueInfo), &ResultLength);
  224. if (NT_SUCCESS(Status)) {
  225. // Check to see if it's a DWORD.
  226. if (pValueInformation->DataLength == sizeof(DWORD) &&
  227. pValueInformation->Type == REG_DWORD) {
  228. FirstCounterIndex = *((DWORD *)(pValueInformation->Data));
  229. }
  230. else {
  231. DBGPRINT(("PerfTS: Len %u not right or type %u not DWORD\n",
  232. pValueInformation->DataLength,
  233. pValueInformation->Type));
  234. }
  235. }
  236. else {
  237. DBGPRINT(("PerfTS: Could not read counter value (status=%X)\n",
  238. Status));
  239. }
  240. // close the registry key
  241. NtClose(hTermServiceKey);
  242. }
  243. else {
  244. DBGPRINT(("PerfTS: Unable to open TermService\\Performance key "
  245. "(status=%x)\n", Status));
  246. }
  247. return Status;
  248. }
  249. /****************************************************************************/
  250. // Initializes the WinStation and TermServer counter descriptions with the
  251. // loaded counter index offset.
  252. /****************************************************************************/
  253. void SetupCounterIndices(void)
  254. {
  255. unsigned i;
  256. unsigned NumCounterDefs;
  257. PERF_COUNTER_DEFINITION *pCounterDef;
  258. // First index goes to the WinStation object description and help.
  259. WinStationDataDefinition.WinStationObjectType.ObjectNameTitleIndex +=
  260. FirstCounterIndex;
  261. WinStationDataDefinition.WinStationObjectType.ObjectHelpTitleIndex +=
  262. FirstCounterIndex;
  263. // We need to add the FirstCounterIndex value directly into the
  264. // description and help indices in the WinStation counters in
  265. // WinStationDataDefinition.
  266. pCounterDef = &WinStationDataDefinition.InputWdBytes;
  267. NumCounterDefs = (sizeof(WinStationDataDefinition) -
  268. (unsigned)((BYTE *)pCounterDef -
  269. (BYTE *)&WinStationDataDefinition)) /
  270. sizeof(PERF_COUNTER_DEFINITION);
  271. for (i = 0; i < NumCounterDefs; i++) {
  272. pCounterDef->CounterNameTitleIndex += FirstCounterIndex;
  273. pCounterDef->CounterHelpTitleIndex += FirstCounterIndex;
  274. pCounterDef++;
  275. }
  276. // We need to add the FirstCounterIndex value directly into the
  277. // description and help indices in the TermServer counters.
  278. TermServerDataDefinition.TermServerObjectType.ObjectNameTitleIndex +=
  279. FirstCounterIndex;
  280. TermServerDataDefinition.TermServerObjectType.ObjectHelpTitleIndex +=
  281. FirstCounterIndex;
  282. pCounterDef = &TermServerDataDefinition.NumSessions;
  283. NumCounterDefs = (sizeof(TermServerDataDefinition) -
  284. (unsigned)((BYTE *)pCounterDef -
  285. (BYTE *)&TermServerDataDefinition)) /
  286. sizeof(PERF_COUNTER_DEFINITION);
  287. for (i = 0; i < NumCounterDefs; i++) {
  288. pCounterDef->CounterNameTitleIndex += FirstCounterIndex;
  289. pCounterDef->CounterHelpTitleIndex += FirstCounterIndex;
  290. pCounterDef++;
  291. }
  292. }
  293. /****************************************************************************/
  294. // PerfMon open entry point.
  295. // DeviceNames is ptr to object ID of each device to be opened.
  296. /****************************************************************************/
  297. DWORD APIENTRY OpenTSObject(LPWSTR DeviceNames)
  298. {
  299. // DBGPRINT(("PerfTS: Open() called\n"));
  300. dwOpenCount++;
  301. // Allocate the first process info buffer.
  302. if (pProcessBuffer == NULL) {
  303. pProcessBuffer = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, ProcessBufSize);
  304. if (pProcessBuffer == NULL)
  305. return ERROR_OUTOFMEMORY;
  306. }
  307. return ERROR_SUCCESS;
  308. }
  309. /****************************************************************************/
  310. // PerfMon close entry point.
  311. /****************************************************************************/
  312. DWORD APIENTRY CloseTSObject(void)
  313. {
  314. PLIST_ENTRY pEntry;
  315. WinStationInfo *pWSI;
  316. // DBGPRINT(("PerfTS: Close() called\n"));
  317. if (--dwOpenCount == 0) {
  318. if (hLibHeap != NULL) {
  319. // Free the WinStation cache entries.
  320. pEntry = UsedList.Flink;
  321. while (!IsListEmpty(&UsedList)) {
  322. pEntry = RemoveHeadList(&UsedList);
  323. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList);
  324. RemoveEntryList(&pWSI->HashBucketList);
  325. FREEMEM(hLibHeap, 0, pWSI);
  326. }
  327. if (pWinStationHashBuckets != DefaultWinStationHashBuckets) {
  328. FREEMEM(hLibHeap, 0, pWinStationHashBuckets);
  329. pWinStationHashBuckets = DefaultWinStationHashBuckets;
  330. NumWinStationHashBuckets = NumDefaultWinStationHashBuckets;
  331. }
  332. // Free the proc info buffer.
  333. if (pProcessBuffer != NULL) {
  334. FREEMEM(hLibHeap, 0, pProcessBuffer);
  335. pProcessBuffer = NULL;
  336. }
  337. }
  338. }
  339. return ERROR_SUCCESS;
  340. }
  341. /****************************************************************************/
  342. // Grabs system process information into global buffer.
  343. /****************************************************************************/
  344. __inline DWORD GetSystemProcessData(void)
  345. {
  346. DWORD dwReturnedBufferSize;
  347. NTSTATUS Status;
  348. // Get process data from system.
  349. while ((Status = NtQuerySystemInformation(SystemProcessInformation,
  350. pProcessBuffer, ProcessBufSize, &dwReturnedBufferSize)) ==
  351. STATUS_INFO_LENGTH_MISMATCH) {
  352. BYTE *pNewProcessBuffer;
  353. // Expand buffer & retry. ReAlloc does not free the original mem
  354. // on error, so alloc into a temp pointer.
  355. ProcessBufSize += INCREMENT_BUFFER_SIZE;
  356. pNewProcessBuffer = REALLOCMEM(hLibHeap, 0, pProcessBuffer,
  357. ProcessBufSize);
  358. if (pNewProcessBuffer != NULL)
  359. pProcessBuffer = pNewProcessBuffer;
  360. else
  361. return ERROR_OUTOFMEMORY;
  362. }
  363. if (NT_SUCCESS(Status)) {
  364. // Get system time.
  365. Status = NtQuerySystemInformation(SystemTimeOfDayInformation,
  366. &SysTimeInfo, sizeof(SysTimeInfo), &dwReturnedBufferSize);
  367. if (!NT_SUCCESS(Status))
  368. Status = (DWORD)RtlNtStatusToDosError(Status);
  369. }
  370. else {
  371. // Convert to Win32 error.
  372. Status = (DWORD)RtlNtStatusToDosError(Status);
  373. }
  374. return Status;
  375. }
  376. /****************************************************************************/
  377. // Creates a WinStation name based on the WinStation state.
  378. // Assumes the cache lock is held.
  379. /****************************************************************************/
  380. void ConstructSessionName(
  381. WinStationInfo *pWSI,
  382. WINSTATIONINFORMATIONW *pQueryData)
  383. {
  384. WCHAR *SrcName, *DstName;
  385. LPCTSTR pState = NULL;
  386. // Update active/inactive counts and create UI names for sessions.
  387. if (pQueryData->WinStationName[0] != '\0') {
  388. // We have a problem with WinStation names --
  389. // the '#' sign is not allowed. So, replace them
  390. // with spaces during name copy.
  391. SrcName = pQueryData->WinStationName;
  392. DstName = pWSI->WinStationName;
  393. while (*SrcName != L'\0') {
  394. if (*SrcName != L'#')
  395. *DstName = *SrcName;
  396. else
  397. *DstName = L' ';
  398. SrcName++;
  399. DstName++;
  400. }
  401. *DstName = L'\0';
  402. }
  403. else {
  404. // Create a fake session name based on the session ID and
  405. // an indication of the session state.
  406. _ltow(pWSI->SessionID, pWSI->WinStationName, 10);
  407. wcsncat(pWSI->WinStationName, L" ",1);
  408. pState = StrConnectState(pQueryData->ConnectState, TRUE);
  409. if(pState)
  410. {
  411. wcsncat(pWSI->WinStationName, (const wchar_t *)pState,
  412. (MAX_SESSION_NAME_LENGTH - 1) -
  413. wcslen(pWSI->WinStationName));
  414. }
  415. }
  416. }
  417. /****************************************************************************/
  418. // Adds a WinStationInfo block (with session ID already filled out) into
  419. // the cache.
  420. // Assumes the cache lock is held.
  421. /****************************************************************************/
  422. void AddWinStationInfoToCache(WinStationInfo *pWSI)
  423. {
  424. unsigned i;
  425. unsigned Temp, NumHashBuckets;
  426. // Add to the hash table.
  427. InsertHeadList(&pWinStationHashBuckets[pWSI->SessionID &
  428. WinStationHashMask], &pWSI->HashBucketList);
  429. NumCachedWinStations++;
  430. // Check to see if we need to increase the hash table size.
  431. // If so, allocate a new one and populate it.
  432. // Hash table size is the number of entries * 4 rounded down
  433. // to the next lower power of 2, for easy key masking and higher
  434. // probability of having a low hash bucket list count.
  435. Temp = 4 * NumCachedWinStations;
  436. // Find the highest bit set in the hash bucket value.
  437. for (i = 0; Temp > 1; i++)
  438. Temp >>= 1;
  439. NumHashBuckets = 1 << i;
  440. if (NumWinStationHashBuckets < NumHashBuckets)
  441. CreateNewHashBuckets(NumHashBuckets);
  442. }
  443. /****************************************************************************/
  444. // Common code for Add and RemoveWinStationInfo.
  445. // Assumes the cache lock is held.
  446. /****************************************************************************/
  447. void CreateNewHashBuckets(unsigned NumHashBuckets)
  448. {
  449. unsigned i, HashMask;
  450. PLIST_ENTRY pLI, pEntry, pTempEntry;
  451. WinStationInfo *pTempWSI;
  452. if (NumHashBuckets != NumDefaultWinStationHashBuckets)
  453. pLI = ALLOCMEM(hLibHeap, 0, NumHashBuckets * sizeof(LIST_ENTRY));
  454. else
  455. pLI = DefaultWinStationHashBuckets;
  456. if (pLI != NULL) {
  457. for (i = 0; i < NumHashBuckets; i++)
  458. InitializeListHead(&pLI[i]);
  459. HashMask = NumHashBuckets - 1;
  460. // Move the old hash table entries into the new table.
  461. // Have to enumerate all entries in used and unused lists
  462. // since we are likely to have entries scattered in both places.
  463. pEntry = UsedList.Flink;
  464. while (pEntry != &UsedList) {
  465. pTempWSI = CONTAINING_RECORD(pEntry, WinStationInfo,
  466. UsedList);
  467. InsertHeadList(&pLI[pTempWSI->SessionID &
  468. HashMask], &pTempWSI->HashBucketList);
  469. pEntry = pEntry->Flink;
  470. }
  471. pEntry = UnusedList.Flink;
  472. while (pEntry != &UnusedList) {
  473. pTempWSI = CONTAINING_RECORD(pEntry, WinStationInfo,
  474. UsedList);
  475. InsertHeadList(&pLI[pTempWSI->SessionID &
  476. HashMask], &pTempWSI->HashBucketList);
  477. pEntry = pEntry->Flink;
  478. }
  479. if (pWinStationHashBuckets != DefaultWinStationHashBuckets)
  480. FREEMEM(hLibHeap, 0, pWinStationHashBuckets);
  481. NumWinStationHashBuckets = NumHashBuckets;
  482. WinStationHashMask = HashMask;
  483. pWinStationHashBuckets = pLI;
  484. }
  485. else {
  486. // On failure, we just leave the hash table alone until next time.
  487. DBGPRINT(("PerfTS: Could not alloc new hash buckets\n"));
  488. }
  489. }
  490. /****************************************************************************/
  491. // Removes a WSI from the hash table.
  492. // Assumes the cache lock is held.
  493. /****************************************************************************/
  494. void RemoveWinStationInfoFromCache(WinStationInfo *pWSI)
  495. {
  496. unsigned i;
  497. unsigned Temp, NumHashBuckets, HashMask;
  498. // Remove from the hash table.
  499. RemoveEntryList(&pWSI->HashBucketList);
  500. NumCachedWinStations--;
  501. // Check to see if we need to decrease the hash table size.
  502. // If so, allocate a new one and populate it.
  503. // Hash table size is the number of entries * 4 rounded down
  504. // to the next lower power of 2, for easy key masking and higher
  505. // probability of having a low hash bucket list count.
  506. Temp = 4 * NumCachedWinStations;
  507. // Find the highest bit set in the hash bucket value.
  508. for (i = 0; Temp > 1; i++)
  509. Temp >>= 1;
  510. NumHashBuckets = 1 << i;
  511. if (NumWinStationHashBuckets < NumHashBuckets &&
  512. NumWinStationHashBuckets >= 4)
  513. CreateNewHashBuckets(NumHashBuckets);
  514. }
  515. /****************************************************************************/
  516. // PerfMon collect entry point.
  517. // Args:
  518. // ValueName: Registry name.
  519. // ppData: Passes in pointer to the address of the buffer to receive the
  520. // completed PerfDataBlock and subordinate structures. This routine will
  521. // append its data to the buffer starting at the point referenced by
  522. // *ppData. Passes out pointer to the first byte after the data structure
  523. // added by this routine.
  524. // pTotalBytes: Passes in ptr to size in bytes of the buf at *ppdata. Passes
  525. // out number of bytes added if *ppData is changed.
  526. // pNumObjectTypes: Passes out the number of objects added by this routine.
  527. //
  528. // Returns: Win32 error code.
  529. /****************************************************************************/
  530. #define WinStationInstanceSize (sizeof(PERF_INSTANCE_DEFINITION) + \
  531. (MAX_WINSTATION_NAME_LENGTH + 1) * sizeof(WCHAR) + \
  532. 2 * sizeof(DWORD) + /* Allow for QWORD alignment space. */ \
  533. sizeof(WINSTATION_COUNTER_DATA))
  534. DWORD APIENTRY CollectTSObjectData(
  535. IN LPWSTR ValueName,
  536. IN OUT LPVOID *ppData,
  537. IN OUT LPDWORD pTotalBytes,
  538. OUT LPDWORD pNumObjectTypes)
  539. {
  540. DWORD Result;
  541. DWORD TotalLen; // Length of the total return block
  542. PERF_INSTANCE_DEFINITION *pPerfInstanceDefinition;
  543. PSYSTEM_PROCESS_INFORMATION pProcessInfo;
  544. ULONG NumWinStationInstances;
  545. NTSTATUS Status;
  546. ULONG ProcessBufferOffset;
  547. WINSTATION_DATA_DEFINITION *pWinStationDataDefinition;
  548. WINSTATION_COUNTER_DATA *pWSC;
  549. TERMSERVER_DATA_DEFINITION *pTermServerDataDefinition;
  550. TERMSERVER_COUNTER_DATA *pTSC;
  551. ULONG SessionId;
  552. WinStationInfo *pWSI;
  553. LIST_ENTRY *pEntry;
  554. unsigned i;
  555. unsigned ActiveWS, InactiveWS;
  556. WCHAR *InstanceName;
  557. ULONG AmountRet;
  558. WCHAR StringBuf[MAX_SESSION_NAME_LENGTH];
  559. WINSTATIONINFORMATIONW *pPassedQueryBuf;
  560. WINSTATIONINFORMATIONW QueryBuffer;
  561. #ifdef COLLECT_TIME
  562. DWORD StartTick = GetTickCount();
  563. #endif
  564. // DBGPRINT(("PerfTS: Collect() called\n"));
  565. pWinStationDataDefinition = (WINSTATION_DATA_DEFINITION *)*ppData;
  566. // Check for sufficient space for base WinStation object info and
  567. // as many instances as we currently have in our WinStation database.
  568. // Add in DWORD sizes for potential QWORD alignments.
  569. TotalLen = sizeof(WINSTATION_DATA_DEFINITION) + sizeof(DWORD) +
  570. sizeof(TERMSERVER_DATA_DEFINITION) +
  571. sizeof(TERMSERVER_COUNTER_DATA) + sizeof(DWORD) +
  572. NumCachedWinStations * WinStationInstanceSize;
  573. if (*pTotalBytes >= TotalLen) {
  574. // Grab the latest system process information.
  575. Result = GetSystemProcessData();
  576. if (Result == ERROR_SUCCESS) {
  577. // Copy WinStation counter definitions.
  578. memcpy(pWinStationDataDefinition, &WinStationDataDefinition,
  579. sizeof(WINSTATION_DATA_DEFINITION));
  580. pWinStationDataDefinition->WinStationObjectType.PerfTime =
  581. SysTimeInfo.CurrentTime;
  582. }
  583. else {
  584. DBGPRINT(("PerfTS: Failed to get process data\n"));
  585. goto ErrorExit;
  586. }
  587. }
  588. else {
  589. DBGPRINT(("PerfTS: Not enough space for base WinStation information\n"));
  590. Result = ERROR_MORE_DATA;
  591. goto ErrorExit;
  592. }
  593. // Before we start, we have to transfer each WinStationInfo in the
  594. // cache from the used list into an unused list to detect closed
  595. // WinStations. Also, we need to zero each WSI's pInstanceInfo to detect
  596. // whether we have retrieved the current I/O data for the WinStation.
  597. pEntry = UsedList.Blink;
  598. (UsedList.Flink)->Blink = &UnusedList; // Patch up head links to UnusedList.
  599. pEntry->Flink = &UnusedList;
  600. UnusedList = UsedList;
  601. InitializeListHead(&UsedList);
  602. pEntry = UnusedList.Flink;
  603. while (pEntry != &UnusedList) {
  604. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList);
  605. pWSI->pInstanceInfo = NULL;
  606. pEntry = pEntry->Flink;
  607. }
  608. // Now collect data for each process, summing it for each unique SessionId.
  609. ActiveWS = InactiveWS = 0;
  610. NumWinStationInstances = 0;
  611. ProcessBufferOffset = 0;
  612. pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)pProcessBuffer;
  613. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
  614. &pWinStationDataDefinition[1];
  615. while (TRUE) {
  616. // Check for live process (having a name, one or more threads, or
  617. // not the Idle process (PID==0)). For WinStations we don't want to
  618. // charge the Idle process to the console (session ID == 0).
  619. if (pProcessInfo->ImageName.Buffer != NULL &&
  620. pProcessInfo->NumberOfThreads > 0 &&
  621. pProcessInfo->UniqueProcessId != 0) {
  622. // Get the session ID from the process. This is the same as the
  623. // LogonID in TS4.
  624. SessionId = pProcessInfo->SessionId;
  625. // Find the session ID in the cache.
  626. // We sum all processes seen for a given SessionId into the
  627. // same WinStation instance data block.
  628. pEntry = pWinStationHashBuckets[SessionId & WinStationHashMask].
  629. Flink;
  630. while (pEntry != &pWinStationHashBuckets[SessionId &
  631. WinStationHashMask]) {
  632. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo,
  633. HashBucketList);
  634. if (pWSI->SessionID == SessionId) {
  635. // Found it. Now check that we've retrieved the WS info.
  636. if (pWSI->pInstanceInfo != NULL) {
  637. // Context is the WINSTATION_COUNTER_DATA entry
  638. // for this SessionId.
  639. pWSC = (WINSTATION_COUNTER_DATA *)pWSI->pInstanceInfo;
  640. // Now add the values to the existing counter block.
  641. UpdateWSProcessCounterBlock(pWSC, pProcessInfo);
  642. goto NextProcess;
  643. }
  644. break;
  645. }
  646. else {
  647. pEntry = pEntry->Flink;
  648. }
  649. }
  650. // We have a new entry or one for which we have not gathered
  651. // current information. First grab the info.
  652. if (WinStationQueryInformationW(SERVERNAME_CURRENT, SessionId,
  653. WinStationInformation, &QueryBuffer,
  654. sizeof(QueryBuffer), &AmountRet)) {
  655. if (QueryBuffer.ConnectState == State_Active)
  656. ActiveWS++;
  657. else
  658. InactiveWS++;
  659. // Check for a pre-cached WSI with no stats.
  660. if (pEntry != &pWinStationHashBuckets[SessionId &
  661. WinStationHashMask]) {
  662. // Verify the cached state (and thereby the name).
  663. if (pWSI->LastState != QueryBuffer.ConnectState) {
  664. pWSI->LastState = QueryBuffer.ConnectState;
  665. ConstructSessionName(pWSI, &QueryBuffer);
  666. }
  667. // Remove the entry from the unused list, place on the
  668. // used list.
  669. RemoveEntryList(&pWSI->UsedList);
  670. InsertHeadList(&UsedList, &pWSI->UsedList);
  671. }
  672. else {
  673. // Alloc a new entry.
  674. pWSI = ALLOCMEM(hLibHeap, 0, sizeof(WinStationInfo));
  675. if (pWSI != NULL) {
  676. pWSI->SessionID = SessionId;
  677. pWSI->LastState = QueryBuffer.ConnectState;
  678. pWSI->pInstanceInfo = NULL;
  679. ConstructSessionName(pWSI, &QueryBuffer);
  680. // Add to the used list.
  681. InsertHeadList(&UsedList, &pWSI->UsedList);
  682. // Add new entry. We may have to increase the
  683. // number of hash buckets.
  684. AddWinStationInfoToCache(pWSI);
  685. }
  686. else {
  687. DBGPRINT(("PerfTS: Could not alloc new "
  688. "WinStationInfo\n"));
  689. goto NextProcess;
  690. }
  691. }
  692. InstanceName = pWSI->WinStationName;
  693. pPassedQueryBuf = &QueryBuffer;
  694. }
  695. else {
  696. // We have a WinStation Query problem.
  697. DBGPRINT(("PerfTS: Failed WSQueryInfo(SessID=%u), error=%u\n",
  698. SessionId, GetLastError()));
  699. // We could not open this WinStation, so we will identify
  700. // it as "ID Unknown" using -1 to StrConnectState.
  701. _ltow(SessionId, StringBuf, 10);
  702. wcsncat(StringBuf, L" ", 1);
  703. wcsncat(StringBuf,
  704. (const wchar_t *)StrConnectState(-1, TRUE),
  705. (MAX_SESSION_NAME_LENGTH - 1) - wcslen(StringBuf));
  706. InstanceName = StringBuf;
  707. pPassedQueryBuf = NULL;
  708. }
  709. // Add space for new instance header, name, and set of counters
  710. // to TotalLen and see if this instance will fit.
  711. TotalLen += WinStationInstanceSize;
  712. if (*pTotalBytes >= TotalLen) {
  713. NumWinStationInstances++;
  714. }
  715. else {
  716. DBGPRINT(("PerfTS: Not enough space for a new instance "
  717. "(cur inst = %u)\n", NumWinStationInstances));
  718. Result = ERROR_MORE_DATA;
  719. goto ErrorExitFixupUsedList;
  720. }
  721. // MonBuildInstanceDefinition will create an instance of
  722. // the given supplied name inside of the callers buffer
  723. // supplied in pPerfInstanceDefinition. Our counter location
  724. // (the next memory after the instance header and name) is
  725. // returned in pWSC.
  726. // By remembering this pointer, and its counter size, we
  727. // can revisit it to add to the counters.
  728. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  729. (PVOID *)&pWSC, 0, 0, (DWORD)-1, InstanceName);
  730. // Initialize the new counter block.
  731. SetupWinStationCounterBlock(pWSC, pPassedQueryBuf);
  732. // Now set the Context to this counter block so if we
  733. // see any more processes with this SessionId we
  734. // can add to the existing counter block.
  735. pWSI->pInstanceInfo = pWSC;
  736. // Now load the values into the counter block
  737. UpdateWSProcessCounterBlock(pWSC, pProcessInfo);
  738. // set perfdata pointer to next byte if its a new entry
  739. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)(pWSC + 1);
  740. }
  741. NextProcess:
  742. // Exit if this was the last process in list
  743. if (pProcessInfo->NextEntryOffset != 0) {
  744. // point to next buffer in list
  745. ProcessBufferOffset += pProcessInfo->NextEntryOffset;
  746. pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)
  747. &pProcessBuffer[ProcessBufferOffset];
  748. }
  749. else {
  750. break;
  751. }
  752. }
  753. // Check for unused WinStations and remove.
  754. while (!IsListEmpty(&UnusedList)) {
  755. pEntry = RemoveHeadList(&UnusedList);
  756. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList);
  757. RemoveWinStationInfoFromCache(pWSI);
  758. FREEMEM(hLibHeap, 0, pWSI);
  759. }
  760. // Note number of WinStation instances.
  761. pWinStationDataDefinition->WinStationObjectType.NumInstances =
  762. NumWinStationInstances;
  763. // Now we know how large an area we used for the
  764. // WinStation definition, so we can update the offset
  765. // to the next object definition. Align size on QWORD.
  766. pTermServerDataDefinition = (TERMSERVER_DATA_DEFINITION *)(
  767. ALIGN_ON_QWORD(pPerfInstanceDefinition));
  768. pWinStationDataDefinition->WinStationObjectType.TotalByteLength =
  769. (DWORD)((PCHAR)pTermServerDataDefinition -
  770. (PCHAR)pWinStationDataDefinition);
  771. // Now we set up and fill in the data for the TermServer object,
  772. // starting at the end of the WinStation instances.
  773. // No instances here, just fill in headers.
  774. memcpy(pTermServerDataDefinition, &TermServerDataDefinition,
  775. sizeof(TERMSERVER_DATA_DEFINITION));
  776. pTermServerDataDefinition->TermServerObjectType.PerfTime =
  777. SysTimeInfo.CurrentTime;
  778. pTSC = (TERMSERVER_COUNTER_DATA *)(pTermServerDataDefinition + 1);
  779. pTSC->CounterBlock.ByteLength = sizeof(TERMSERVER_COUNTER_DATA);
  780. pTSC->NumActiveSessions = ActiveWS;
  781. pTSC->NumInactiveSessions = InactiveWS;
  782. pTSC->NumSessions = ActiveWS + InactiveWS;
  783. // Return final sizes. Align final address on a QWORD size.
  784. *ppData = ALIGN_ON_QWORD((LPVOID)(pTSC + 1));
  785. pTermServerDataDefinition->TermServerObjectType.TotalByteLength =
  786. (DWORD)((PBYTE)*ppData - (PBYTE)pTermServerDataDefinition);
  787. *pTotalBytes = (DWORD)((PBYTE)*ppData - (PBYTE)pWinStationDataDefinition);
  788. *pNumObjectTypes = 2;
  789. #if DBG
  790. if (*pTotalBytes > TotalLen)
  791. DbgPrint ("PerfTS: Perf ctr. instance size underestimated: "
  792. "Est.=%u, Actual=%u", TotalLen, *pTotalBytes);
  793. #endif
  794. #ifdef COLLECT_TIME
  795. DbgPrint("*** Elapsed msec=%u\n", GetTickCount() - StartTick);
  796. #endif
  797. return ERROR_SUCCESS;
  798. // Error handling.
  799. ErrorExitFixupUsedList:
  800. // We have to return the UnusedList entries to the used list and exit the
  801. // cache lock.
  802. while (!IsListEmpty(&UnusedList)) {
  803. pEntry = RemoveHeadList(&UnusedList);
  804. InsertHeadList(&UsedList, pEntry);
  805. }
  806. ErrorExit:
  807. *pNumObjectTypes = 0;
  808. *pTotalBytes = 0;
  809. return Result;
  810. }
  811. #define CalculatePercent(count, hits) ((count) ? (hits) * 100 / (count) : 0)
  812. /****************************************************************************/
  813. // SetupWinStationCounterBlock
  814. //
  815. // Initializes a new WinStation counter block.
  816. //
  817. // Args:
  818. // pCounters (input)
  819. // pointer to WinStation performance counter block
  820. //
  821. // pInfo (input)
  822. // Pointer to WINSTATIONINFORMATION structure to extract counters from
  823. //
  824. // pNextByte (output)
  825. // Returns the pointer to the byte beyound the end of the buffer.
  826. /****************************************************************************/
  827. void SetupWinStationCounterBlock(
  828. WINSTATION_COUNTER_DATA *pCounters,
  829. PWINSTATIONINFORMATIONW pInfo)
  830. {
  831. // Fill in the WinStation information if available.
  832. if (pInfo != NULL) {
  833. PPROTOCOLCOUNTERS pi, po;
  834. PTHINWIRECACHE p;
  835. ULONG TotalReads = 0, TotalHits = 0;
  836. int i;
  837. // Set all members of pCounters->pcd to zero since we are not going to
  838. // init at this time. Then set the included PERF_COUNTER_BLOCK
  839. // byte length.
  840. memset(&pCounters->pcd, 0, sizeof(pCounters->pcd));
  841. pCounters->pcd.CounterBlock.ByteLength = sizeof(
  842. WINSTATION_COUNTER_DATA);
  843. pi = &pInfo->Status.Input;
  844. po = &pInfo->Status.Output;
  845. // Copy input and output counters.
  846. memcpy(&pCounters->Input, pi, sizeof(PROTOCOLCOUNTERS));
  847. memcpy(&pCounters->Output, po, sizeof(PROTOCOLCOUNTERS));
  848. // Calculate I/O totals.
  849. pCounters->Total.WdBytes = pi->WdBytes + po->WdBytes;
  850. pCounters->Total.WdFrames = pi->WdFrames + po->WdFrames;
  851. pCounters->Total.Frames = pi->Frames + po->Frames;
  852. pCounters->Total.Bytes = pi->Bytes + po->Bytes;
  853. pCounters->Total.CompressedBytes = pi->CompressedBytes +
  854. po->CompressedBytes;
  855. pCounters->Total.CompressFlushes = pi->CompressFlushes +
  856. po->CompressFlushes;
  857. pCounters->Total.Errors = pi->Errors + po->Errors;
  858. pCounters->Total.Timeouts = pi->Timeouts + po->Timeouts;
  859. pCounters->Total.AsyncFramingError = pi->AsyncFramingError +
  860. po->AsyncFramingError;
  861. pCounters->Total.AsyncOverrunError = pi->AsyncOverrunError +
  862. po->AsyncOverrunError;
  863. pCounters->Total.AsyncOverflowError = pi->AsyncOverflowError +
  864. po->AsyncOverflowError;
  865. pCounters->Total.AsyncParityError = pi->AsyncParityError +
  866. po->AsyncParityError;
  867. pCounters->Total.TdErrors = pi->TdErrors + po->TdErrors;
  868. // Display driver cache info.
  869. // Bitmap cache.
  870. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[0];
  871. pCounters->DDBitmap.CacheReads = p->CacheReads;
  872. pCounters->DDBitmap.CacheHits = p->CacheHits;
  873. pCounters->DDBitmap.HitRatio = CalculatePercent(p->CacheReads,
  874. p->CacheHits);
  875. TotalReads += p->CacheReads;
  876. TotalHits += p->CacheHits;
  877. // Glyph cache.
  878. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[1];
  879. pCounters->DDGlyph.CacheReads = p->CacheReads;
  880. pCounters->DDGlyph.CacheHits = p->CacheHits;
  881. pCounters->DDGlyph.HitRatio = CalculatePercent(p->CacheReads,
  882. p->CacheHits);
  883. TotalReads += p->CacheReads;
  884. TotalHits += p->CacheHits;
  885. // Brush cache.
  886. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[2];
  887. pCounters->DDBrush.CacheReads = p->CacheReads;
  888. pCounters->DDBrush.CacheHits = p->CacheHits;
  889. pCounters->DDBrush.HitRatio = CalculatePercent(p->CacheReads,
  890. p->CacheHits);
  891. TotalReads += p->CacheReads;
  892. TotalHits += p->CacheHits;
  893. // Save screen bitmap cache.
  894. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[3];
  895. pCounters->DDSaveScr.CacheReads = p->CacheReads;
  896. pCounters->DDSaveScr.CacheHits = p->CacheHits;
  897. pCounters->DDSaveScr.HitRatio = CalculatePercent(p->CacheReads,
  898. p->CacheHits);
  899. TotalReads += p->CacheReads;
  900. TotalHits += p->CacheHits;
  901. // Cache totals.
  902. pCounters->DDTotal.CacheReads = TotalReads;
  903. pCounters->DDTotal.CacheHits = TotalHits;
  904. pCounters->DDTotal.HitRatio = CalculatePercent(TotalReads,
  905. TotalHits);
  906. // Compression PD ratios
  907. pCounters->InputCompressionRatio = CalculatePercent(
  908. pi->CompressedBytes, pi->Bytes);
  909. pCounters->OutputCompressionRatio = CalculatePercent(
  910. po->CompressedBytes, po->Bytes);
  911. pCounters->TotalCompressionRatio = CalculatePercent(
  912. pi->CompressedBytes + po->CompressedBytes,
  913. pi->Bytes + po->Bytes);
  914. }
  915. else {
  916. // Set all the counters to zero and then the perf block length.
  917. memset(pCounters, 0, sizeof(*pCounters));
  918. pCounters->pcd.CounterBlock.ByteLength = sizeof(
  919. WINSTATION_COUNTER_DATA);
  920. }
  921. }
  922. /****************************************************************************/
  923. // UpdateWSProcessCounterBlock
  924. //
  925. // Add the entries for the given process to the supplied counter block
  926. //
  927. // Args:
  928. // pCounters (input)
  929. // pointer to WS performance counter block
  930. //
  931. // ProcessInfo (input)
  932. // pointer to an NT SYSTEM_PROCESS_INFORMATION block
  933. /****************************************************************************/
  934. void UpdateWSProcessCounterBlock(
  935. WINSTATION_COUNTER_DATA *pCounters,
  936. PSYSTEM_PROCESS_INFORMATION pProcessInfo)
  937. {
  938. pCounters->pcd.PageFaults += pProcessInfo->PageFaultCount;
  939. // User, Kernel and Processor Time counters need to be scaled by the
  940. // number of processors.
  941. pCounters->pcd.ProcessorTime += (pProcessInfo->KernelTime.QuadPart +
  942. pProcessInfo->UserTime.QuadPart) / NumberOfCPUs;
  943. pCounters->pcd.UserTime += pProcessInfo->UserTime.QuadPart /
  944. NumberOfCPUs;
  945. pCounters->pcd.KernelTime += pProcessInfo->KernelTime.QuadPart /
  946. NumberOfCPUs;
  947. pCounters->pcd.PeakVirtualSize += pProcessInfo->PeakVirtualSize;
  948. pCounters->pcd.VirtualSize += pProcessInfo->VirtualSize;
  949. pCounters->pcd.PeakWorkingSet += pProcessInfo->PeakWorkingSetSize;
  950. pCounters->pcd.TotalWorkingSet += pProcessInfo->WorkingSetSize;
  951. pCounters->pcd.PeakPageFile += pProcessInfo->PeakPagefileUsage;
  952. pCounters->pcd.PageFile += pProcessInfo->PagefileUsage;
  953. pCounters->pcd.PrivatePages += pProcessInfo->PrivatePageCount;
  954. pCounters->pcd.ThreadCount += pProcessInfo->NumberOfThreads;
  955. // BasePriority, ElapsedTime, ProcessId, CreatorProcessId not totaled.
  956. pCounters->pcd.PagedPool += (DWORD)pProcessInfo->QuotaPagedPoolUsage;
  957. pCounters->pcd.NonPagedPool += (DWORD)pProcessInfo->QuotaNonPagedPoolUsage;
  958. pCounters->pcd.HandleCount += (DWORD)pProcessInfo->HandleCount;
  959. // I/O counts not totaled at this time.
  960. }