Leaked source code of windows server 2003
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.

1170 lines
43 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 UNALIGNED* pWinStationDataDefinition;
  548. WINSTATION_COUNTER_DATA *pWSC;
  549. TERMSERVER_DATA_DEFINITION *pTermServerDataDefinition;
  550. TERMSERVER_COUNTER_DATA *pTSC;
  551. ULONG SessionId;
  552. WinStationInfo *pWSI = NULL;
  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. LPCTSTR pState = NULL;
  562. #ifdef COLLECT_TIME
  563. DWORD StartTick = GetTickCount();
  564. #endif
  565. // DBGPRINT(("PerfTS: Collect() called\n"));
  566. pWinStationDataDefinition = (WINSTATION_DATA_DEFINITION UNALIGNED*)*ppData;
  567. // Check for sufficient space for base WinStation object info and
  568. // as many instances as we currently have in our WinStation database.
  569. // Add in DWORD sizes for potential QWORD alignments.
  570. TotalLen = sizeof(WINSTATION_DATA_DEFINITION) + sizeof(DWORD) +
  571. sizeof(TERMSERVER_DATA_DEFINITION) +
  572. sizeof(TERMSERVER_COUNTER_DATA) + sizeof(DWORD) +
  573. NumCachedWinStations * WinStationInstanceSize;
  574. if (*pTotalBytes >= TotalLen) {
  575. // Grab the latest system process information.
  576. Result = GetSystemProcessData();
  577. if (Result == ERROR_SUCCESS) {
  578. // Copy WinStation counter definitions.
  579. memcpy(pWinStationDataDefinition, &WinStationDataDefinition,
  580. sizeof(WINSTATION_DATA_DEFINITION));
  581. pWinStationDataDefinition->WinStationObjectType.PerfTime =
  582. SysTimeInfo.CurrentTime;
  583. }
  584. else {
  585. DBGPRINT(("PerfTS: Failed to get process data\n"));
  586. goto ErrorExit;
  587. }
  588. }
  589. else {
  590. DBGPRINT(("PerfTS: Not enough space for base WinStation information\n"));
  591. Result = ERROR_MORE_DATA;
  592. goto ErrorExit;
  593. }
  594. // Before we start, we have to transfer each WinStationInfo in the
  595. // cache from the used list into an unused list to detect closed
  596. // WinStations. Also, we need to zero each WSI's pInstanceInfo to detect
  597. // whether we have retrieved the current I/O data for the WinStation.
  598. pEntry = UsedList.Blink;
  599. (UsedList.Flink)->Blink = &UnusedList; // Patch up head links to UnusedList.
  600. pEntry->Flink = &UnusedList;
  601. UnusedList = UsedList;
  602. InitializeListHead(&UsedList);
  603. pEntry = UnusedList.Flink;
  604. while (pEntry != &UnusedList) {
  605. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList);
  606. pWSI->pInstanceInfo = NULL;
  607. pEntry = pEntry->Flink;
  608. }
  609. // Now collect data for each process, summing it for each unique SessionId.
  610. ActiveWS = InactiveWS = 0;
  611. NumWinStationInstances = 0;
  612. ProcessBufferOffset = 0;
  613. pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)pProcessBuffer;
  614. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
  615. &pWinStationDataDefinition[1];
  616. while (TRUE) {
  617. // Check for live process (having a name, one or more threads, or
  618. // not the Idle process (PID==0)). For WinStations we don't want to
  619. // charge the Idle process to the console (session ID == 0).
  620. if (pProcessInfo->ImageName.Buffer != NULL &&
  621. pProcessInfo->NumberOfThreads > 0 &&
  622. pProcessInfo->UniqueProcessId != 0) {
  623. // Get the session ID from the process. This is the same as the
  624. // LogonID in TS4.
  625. SessionId = pProcessInfo->SessionId;
  626. // Find the session ID in the cache.
  627. // We sum all processes seen for a given SessionId into the
  628. // same WinStation instance data block.
  629. pEntry = pWinStationHashBuckets[SessionId & WinStationHashMask].
  630. Flink;
  631. while (pEntry != &pWinStationHashBuckets[SessionId &
  632. WinStationHashMask]) {
  633. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo,
  634. HashBucketList);
  635. if (pWSI->SessionID == SessionId) {
  636. // Found it. Now check that we've retrieved the WS info.
  637. if (pWSI->pInstanceInfo != NULL) {
  638. // Context is the WINSTATION_COUNTER_DATA entry
  639. // for this SessionId.
  640. pWSC = (WINSTATION_COUNTER_DATA *)pWSI->pInstanceInfo;
  641. // Now add the values to the existing counter block.
  642. UpdateWSProcessCounterBlock(pWSC, pProcessInfo);
  643. goto NextProcess;
  644. }
  645. break;
  646. }
  647. else {
  648. pEntry = pEntry->Flink;
  649. }
  650. }
  651. // We have a new entry or one for which we have not gathered
  652. // current information. First grab the info.
  653. if (WinStationQueryInformationW(SERVERNAME_CURRENT, SessionId,
  654. WinStationInformation, &QueryBuffer,
  655. sizeof(QueryBuffer), &AmountRet)) {
  656. if (QueryBuffer.ConnectState == State_Active)
  657. ActiveWS++;
  658. else
  659. InactiveWS++;
  660. // Check for a pre-cached WSI with no stats.
  661. if (pEntry != &pWinStationHashBuckets[SessionId &
  662. WinStationHashMask]) {
  663. // Verify the cached state (and thereby the name).
  664. if (pWSI->LastState != QueryBuffer.ConnectState) {
  665. pWSI->LastState = QueryBuffer.ConnectState;
  666. ConstructSessionName(pWSI, &QueryBuffer);
  667. }
  668. // Remove the entry from the unused list, place on the
  669. // used list.
  670. RemoveEntryList(&pWSI->UsedList);
  671. InsertHeadList(&UsedList, &pWSI->UsedList);
  672. }
  673. else {
  674. // Alloc a new entry.
  675. pWSI = ALLOCMEM(hLibHeap, 0, sizeof(WinStationInfo));
  676. if (pWSI != NULL) {
  677. pWSI->SessionID = SessionId;
  678. pWSI->LastState = QueryBuffer.ConnectState;
  679. pWSI->pInstanceInfo = NULL;
  680. ConstructSessionName(pWSI, &QueryBuffer);
  681. // Add to the used list.
  682. InsertHeadList(&UsedList, &pWSI->UsedList);
  683. // Add new entry. We may have to increase the
  684. // number of hash buckets.
  685. AddWinStationInfoToCache(pWSI);
  686. }
  687. else {
  688. DBGPRINT(("PerfTS: Could not alloc new "
  689. "WinStationInfo\n"));
  690. goto NextProcess;
  691. }
  692. }
  693. InstanceName = pWSI->WinStationName;
  694. pPassedQueryBuf = &QueryBuffer;
  695. }
  696. else {
  697. // We have a WinStation Query problem.
  698. DBGPRINT(("PerfTS: Failed WSQueryInfo(SessID=%u), error=%u\n",
  699. SessionId, GetLastError()));
  700. // We could not open this WinStation, so we will identify
  701. // it as "ID Unknown" using -1 to StrConnectState.
  702. _ltow(SessionId, StringBuf, 10);
  703. wcsncat(StringBuf, L" ", 1);
  704. pState = StrConnectState(-1, TRUE);
  705. if (pState) {
  706. wcsncat(StringBuf, (const wchar_t *)pState,
  707. (MAX_SESSION_NAME_LENGTH - 1) - wcslen(StringBuf));
  708. }
  709. // Alloc a new entry.
  710. pWSI = ALLOCMEM(hLibHeap, 0, sizeof(WinStationInfo));
  711. if (pWSI != NULL) {
  712. pWSI->SessionID = SessionId;
  713. // We don't know the last state so we'll use -1
  714. pWSI->LastState = -1;
  715. pWSI->pInstanceInfo = NULL;
  716. // Since we don't know the WinstationName, we'll use the
  717. // one we generated above
  718. wcsncpy(pWSI->WinStationName,
  719. StringBuf,
  720. WINSTATIONNAME_LENGTH);
  721. // Add to the used list.
  722. InsertHeadList(&UsedList, &pWSI->UsedList);
  723. // Add new entry. We may have to increase the
  724. // number of hash buckets.
  725. AddWinStationInfoToCache(pWSI);
  726. }
  727. else {
  728. DBGPRINT(("PerfTS: Could not alloc new "
  729. "WinStationInfo\n"));
  730. goto NextProcess;
  731. }
  732. InstanceName = StringBuf;
  733. pPassedQueryBuf = NULL;
  734. }
  735. // Add space for new instance header, name, and set of counters
  736. // to TotalLen and see if this instance will fit.
  737. TotalLen += WinStationInstanceSize;
  738. if (*pTotalBytes >= TotalLen) {
  739. NumWinStationInstances++;
  740. }
  741. else {
  742. DBGPRINT(("PerfTS: Not enough space for a new instance "
  743. "(cur inst = %u)\n", NumWinStationInstances));
  744. Result = ERROR_MORE_DATA;
  745. goto ErrorExitFixupUsedList;
  746. }
  747. // MonBuildInstanceDefinition will create an instance of
  748. // the given supplied name inside of the callers buffer
  749. // supplied in pPerfInstanceDefinition. Our counter location
  750. // (the next memory after the instance header and name) is
  751. // returned in pWSC.
  752. // By remembering this pointer, and its counter size, we
  753. // can revisit it to add to the counters.
  754. MonBuildInstanceDefinition(pPerfInstanceDefinition,
  755. (PVOID *)&pWSC, 0, 0, (DWORD)-1, InstanceName);
  756. // Initialize the new counter block.
  757. SetupWinStationCounterBlock(pWSC, pPassedQueryBuf);
  758. // Now set the Context to this counter block so if we
  759. // see any more processes with this SessionId we
  760. // can add to the existing counter block.
  761. pWSI->pInstanceInfo = pWSC;
  762. // Now load the values into the counter block
  763. UpdateWSProcessCounterBlock(pWSC, pProcessInfo);
  764. // set perfdata pointer to next byte if its a new entry
  765. pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)(pWSC + 1);
  766. }
  767. NextProcess:
  768. // Exit if this was the last process in list
  769. if (pProcessInfo->NextEntryOffset != 0) {
  770. // point to next buffer in list
  771. ProcessBufferOffset += pProcessInfo->NextEntryOffset;
  772. pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)
  773. &pProcessBuffer[ProcessBufferOffset];
  774. }
  775. else {
  776. break;
  777. }
  778. }
  779. // Check for unused WinStations and remove.
  780. while (!IsListEmpty(&UnusedList)) {
  781. pEntry = RemoveHeadList(&UnusedList);
  782. pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList);
  783. RemoveWinStationInfoFromCache(pWSI);
  784. FREEMEM(hLibHeap, 0, pWSI);
  785. }
  786. // Note number of WinStation instances.
  787. pWinStationDataDefinition->WinStationObjectType.NumInstances =
  788. NumWinStationInstances;
  789. // Now we know how large an area we used for the
  790. // WinStation definition, so we can update the offset
  791. // to the next object definition. Align size on QWORD.
  792. pTermServerDataDefinition = (TERMSERVER_DATA_DEFINITION *)(
  793. ALIGN_ON_QWORD(pPerfInstanceDefinition));
  794. pWinStationDataDefinition->WinStationObjectType.TotalByteLength =
  795. (DWORD)((PCHAR)pTermServerDataDefinition -
  796. (PCHAR)pWinStationDataDefinition);
  797. // Now we set up and fill in the data for the TermServer object,
  798. // starting at the end of the WinStation instances.
  799. // No instances here, just fill in headers.
  800. memcpy(pTermServerDataDefinition, &TermServerDataDefinition,
  801. sizeof(TERMSERVER_DATA_DEFINITION));
  802. pTermServerDataDefinition->TermServerObjectType.PerfTime =
  803. SysTimeInfo.CurrentTime;
  804. pTSC = (TERMSERVER_COUNTER_DATA *)(pTermServerDataDefinition + 1);
  805. pTSC->CounterBlock.ByteLength = sizeof(TERMSERVER_COUNTER_DATA);
  806. pTSC->NumActiveSessions = ActiveWS;
  807. pTSC->NumInactiveSessions = InactiveWS;
  808. pTSC->NumSessions = ActiveWS + InactiveWS;
  809. // Return final sizes. Align final address on a QWORD size.
  810. *ppData = ALIGN_ON_QWORD((LPVOID)(pTSC + 1));
  811. pTermServerDataDefinition->TermServerObjectType.TotalByteLength =
  812. (DWORD)((PBYTE)*ppData - (PBYTE)pTermServerDataDefinition);
  813. *pTotalBytes = (DWORD)((PBYTE)*ppData - (PBYTE)pWinStationDataDefinition);
  814. *pNumObjectTypes = 2;
  815. #if DBG
  816. if (*pTotalBytes > TotalLen)
  817. DbgPrint ("PerfTS: Perf ctr. instance size underestimated: "
  818. "Est.=%u, Actual=%u", TotalLen, *pTotalBytes);
  819. #endif
  820. #ifdef COLLECT_TIME
  821. DbgPrint("*** Elapsed msec=%u\n", GetTickCount() - StartTick);
  822. #endif
  823. return ERROR_SUCCESS;
  824. // Error handling.
  825. ErrorExitFixupUsedList:
  826. // We have to return the UnusedList entries to the used list and exit the
  827. // cache lock.
  828. while (!IsListEmpty(&UnusedList)) {
  829. pEntry = RemoveHeadList(&UnusedList);
  830. InsertHeadList(&UsedList, pEntry);
  831. }
  832. ErrorExit:
  833. *pNumObjectTypes = 0;
  834. *pTotalBytes = 0;
  835. return Result;
  836. }
  837. #define CalculatePercent(count, hits) ((count) ? (hits) * 100 / (count) : 0)
  838. /****************************************************************************/
  839. // SetupWinStationCounterBlock
  840. //
  841. // Initializes a new WinStation counter block.
  842. //
  843. // Args:
  844. // pCounters (input)
  845. // pointer to WinStation performance counter block
  846. //
  847. // pInfo (input)
  848. // Pointer to WINSTATIONINFORMATION structure to extract counters from
  849. //
  850. // pNextByte (output)
  851. // Returns the pointer to the byte beyound the end of the buffer.
  852. /****************************************************************************/
  853. void SetupWinStationCounterBlock(
  854. WINSTATION_COUNTER_DATA *pCounters,
  855. PWINSTATIONINFORMATIONW pInfo)
  856. {
  857. // Fill in the WinStation information if available.
  858. if (pInfo != NULL) {
  859. PPROTOCOLCOUNTERS pi, po;
  860. PTHINWIRECACHE p;
  861. ULONG TotalReads = 0, TotalHits = 0;
  862. int i;
  863. // Set all members of pCounters->pcd to zero since we are not going to
  864. // init at this time. Then set the included PERF_COUNTER_BLOCK
  865. // byte length.
  866. memset(&pCounters->pcd, 0, sizeof(pCounters->pcd));
  867. pCounters->pcd.CounterBlock.ByteLength = sizeof(
  868. WINSTATION_COUNTER_DATA);
  869. pi = &pInfo->Status.Input;
  870. po = &pInfo->Status.Output;
  871. // Copy input and output counters.
  872. memcpy(&pCounters->Input, pi, sizeof(PROTOCOLCOUNTERS));
  873. memcpy(&pCounters->Output, po, sizeof(PROTOCOLCOUNTERS));
  874. // Calculate I/O totals.
  875. pCounters->Total.WdBytes = pi->WdBytes + po->WdBytes;
  876. pCounters->Total.WdFrames = pi->WdFrames + po->WdFrames;
  877. pCounters->Total.Frames = pi->Frames + po->Frames;
  878. pCounters->Total.Bytes = pi->Bytes + po->Bytes;
  879. pCounters->Total.CompressedBytes = pi->CompressedBytes +
  880. po->CompressedBytes;
  881. pCounters->Total.CompressFlushes = pi->CompressFlushes +
  882. po->CompressFlushes;
  883. pCounters->Total.Errors = pi->Errors + po->Errors;
  884. pCounters->Total.Timeouts = pi->Timeouts + po->Timeouts;
  885. pCounters->Total.AsyncFramingError = pi->AsyncFramingError +
  886. po->AsyncFramingError;
  887. pCounters->Total.AsyncOverrunError = pi->AsyncOverrunError +
  888. po->AsyncOverrunError;
  889. pCounters->Total.AsyncOverflowError = pi->AsyncOverflowError +
  890. po->AsyncOverflowError;
  891. pCounters->Total.AsyncParityError = pi->AsyncParityError +
  892. po->AsyncParityError;
  893. pCounters->Total.TdErrors = pi->TdErrors + po->TdErrors;
  894. // Display driver cache info.
  895. // Bitmap cache.
  896. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[0];
  897. pCounters->DDBitmap.CacheReads = p->CacheReads;
  898. pCounters->DDBitmap.CacheHits = p->CacheHits;
  899. pCounters->DDBitmap.HitRatio = CalculatePercent(p->CacheReads,
  900. p->CacheHits);
  901. TotalReads += p->CacheReads;
  902. TotalHits += p->CacheHits;
  903. // Glyph cache.
  904. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[1];
  905. pCounters->DDGlyph.CacheReads = p->CacheReads;
  906. pCounters->DDGlyph.CacheHits = p->CacheHits;
  907. pCounters->DDGlyph.HitRatio = CalculatePercent(p->CacheReads,
  908. p->CacheHits);
  909. TotalReads += p->CacheReads;
  910. TotalHits += p->CacheHits;
  911. // Brush cache.
  912. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[2];
  913. pCounters->DDBrush.CacheReads = p->CacheReads;
  914. pCounters->DDBrush.CacheHits = p->CacheHits;
  915. pCounters->DDBrush.HitRatio = CalculatePercent(p->CacheReads,
  916. p->CacheHits);
  917. TotalReads += p->CacheReads;
  918. TotalHits += p->CacheHits;
  919. // Save screen bitmap cache.
  920. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[3];
  921. pCounters->DDSaveScr.CacheReads = p->CacheReads;
  922. pCounters->DDSaveScr.CacheHits = p->CacheHits;
  923. pCounters->DDSaveScr.HitRatio = CalculatePercent(p->CacheReads,
  924. p->CacheHits);
  925. TotalReads += p->CacheReads;
  926. TotalHits += p->CacheHits;
  927. // Cache totals.
  928. pCounters->DDTotal.CacheReads = TotalReads;
  929. pCounters->DDTotal.CacheHits = TotalHits;
  930. pCounters->DDTotal.HitRatio = CalculatePercent(TotalReads,
  931. TotalHits);
  932. // Compression PD ratios
  933. pCounters->InputCompressionRatio = CalculatePercent(
  934. pi->CompressedBytes, pi->Bytes);
  935. pCounters->OutputCompressionRatio = CalculatePercent(
  936. po->CompressedBytes, po->Bytes);
  937. pCounters->TotalCompressionRatio = CalculatePercent(
  938. pi->CompressedBytes + po->CompressedBytes,
  939. pi->Bytes + po->Bytes);
  940. }
  941. else {
  942. // Set all the counters to zero and then the perf block length.
  943. memset(pCounters, 0, sizeof(*pCounters));
  944. pCounters->pcd.CounterBlock.ByteLength = sizeof(
  945. WINSTATION_COUNTER_DATA);
  946. }
  947. }
  948. /****************************************************************************/
  949. // UpdateWSProcessCounterBlock
  950. //
  951. // Add the entries for the given process to the supplied counter block
  952. //
  953. // Args:
  954. // pCounters (input)
  955. // pointer to WS performance counter block
  956. //
  957. // ProcessInfo (input)
  958. // pointer to an NT SYSTEM_PROCESS_INFORMATION block
  959. /****************************************************************************/
  960. void UpdateWSProcessCounterBlock(
  961. WINSTATION_COUNTER_DATA *pCounters,
  962. PSYSTEM_PROCESS_INFORMATION pProcessInfo)
  963. {
  964. pCounters->pcd.PageFaults += pProcessInfo->PageFaultCount;
  965. // User, Kernel and Processor Time counters need to be scaled by the
  966. // number of processors.
  967. pCounters->pcd.ProcessorTime += (pProcessInfo->KernelTime.QuadPart +
  968. pProcessInfo->UserTime.QuadPart) / NumberOfCPUs;
  969. pCounters->pcd.UserTime += pProcessInfo->UserTime.QuadPart /
  970. NumberOfCPUs;
  971. pCounters->pcd.KernelTime += pProcessInfo->KernelTime.QuadPart /
  972. NumberOfCPUs;
  973. pCounters->pcd.PeakVirtualSize += pProcessInfo->PeakVirtualSize;
  974. pCounters->pcd.VirtualSize += pProcessInfo->VirtualSize;
  975. pCounters->pcd.PeakWorkingSet += pProcessInfo->PeakWorkingSetSize;
  976. pCounters->pcd.TotalWorkingSet += pProcessInfo->WorkingSetSize;
  977. pCounters->pcd.PeakPageFile += pProcessInfo->PeakPagefileUsage;
  978. pCounters->pcd.PageFile += pProcessInfo->PagefileUsage;
  979. pCounters->pcd.PrivatePages += pProcessInfo->PrivatePageCount;
  980. pCounters->pcd.ThreadCount += pProcessInfo->NumberOfThreads;
  981. // BasePriority, ElapsedTime, ProcessId, CreatorProcessId not totaled.
  982. pCounters->pcd.PagedPool += (DWORD)pProcessInfo->QuotaPagedPoolUsage;
  983. pCounters->pcd.NonPagedPool += (DWORD)pProcessInfo->QuotaNonPagedPoolUsage;
  984. pCounters->pcd.HandleCount += (DWORD)pProcessInfo->HandleCount;
  985. // I/O counts not totaled at this time.
  986. }