/*++ Copyright (c) 1997-1998 Microsoft Corporation Module Name: perfts.c Description: DLL entry point and support code for Terminal Server performance counters. Author: Erik Mavrinac 24-Nov-1998 Revision History: --*/ #include #include #include #include #include #include #include #include #include #include #include "datats.h" #if DBG #define DBGPRINT(x) DbgPrint x #else #define DBGPRINT(x) #endif #define MAX_SESSION_NAME_LENGTH 50 typedef struct _WinStationInfo { LIST_ENTRY HashBucketList; LIST_ENTRY UsedList; ULONG SessionID; WINSTATIONSTATECLASS LastState; void *pInstanceInfo; WINSTATIONNAMEW WinStationName; } WinStationInfo; /****************************************************************************/ // Globals /****************************************************************************/ // Default hash bucket list to remove having to check for // pWinStationHashBuckets == NULL on perf path. This array contains the // default number for one WinStationInfo. #define NumDefaultWinStationHashBuckets 4 LIST_ENTRY DefaultWinStationHashBuckets[NumDefaultWinStationHashBuckets]; HANDLE hEventLog = NULL; HANDLE hLibHeap = NULL; PBYTE pProcessBuffer = NULL; static DWORD dwOpenCount = 0; static DWORD ProcessBufSize = LARGE_BUFFER_SIZE; static DWORD NumberOfCPUs = 0; static DWORD FirstCounterIndex = 0; LIST_ENTRY UsedList; LIST_ENTRY UnusedList; LIST_ENTRY *pWinStationHashBuckets = DefaultWinStationHashBuckets; unsigned NumWinStationHashBuckets = NumDefaultWinStationHashBuckets; ULONG WinStationHashMask = 0x3; unsigned NumCachedWinStations = 0; SYSTEM_TIMEOFDAY_INFORMATION SysTimeInfo = {{0,0},{0,0},{0,0},0,0}; /****************************************************************************/ // Prototypes /****************************************************************************/ BOOL DllProcessAttach(void); BOOL DllProcessDetach(void); DWORD GetNumberOfCPUs(void); NTSTATUS GetDescriptionOffset(void); void SetupCounterIndices(void); DWORD APIENTRY OpenWinStationObject(LPWSTR); DWORD APIENTRY CloseWinStationObject(void); DWORD APIENTRY CollectWinStationObjectData(IN LPWSTR, IN OUT LPVOID *, IN OUT LPDWORD, OUT LPDWORD); DWORD GetSystemProcessData(void); void SetupWinStationCounterBlock(WINSTATION_COUNTER_DATA *, PWINSTATIONINFORMATIONW); void UpdateWSProcessCounterBlock(WINSTATION_COUNTER_DATA *, PSYSTEM_PROCESS_INFORMATION); void CreateNewHashBuckets(unsigned); // Declares these exported functions as PerfMon entry points. PM_OPEN_PROC OpenTSObject; PM_COLLECT_PROC CollectTSObjectData; PM_CLOSE_PROC CloseTSObject; /****************************************************************************/ // DLL system load/unload entry point. /****************************************************************************/ BOOL __stdcall DllInit( IN HANDLE DLLHandle, IN DWORD Reason, IN LPVOID ReservedAndUnused) { ReservedAndUnused; // This will prevent the DLL from getting // the DLL_THREAD_* messages DisableThreadLibraryCalls(DLLHandle); switch(Reason) { case DLL_PROCESS_ATTACH: return DllProcessAttach(); case DLL_PROCESS_DETACH: return DllProcessDetach(); default: return TRUE; } } /****************************************************************************/ // DLL instance load time initialization. /****************************************************************************/ BOOL DllProcessAttach(void) { unsigned i; NTSTATUS Status; // DBGPRINT(("PerfTS: ProcessAttach\n")); // Create the local heap hLibHeap = HeapCreate(0, 1, 0); if (hLibHeap == NULL) return FALSE; // Open handle to the event log if (hEventLog == NULL) { hEventLog = MonOpenEventLog(L"PerfTS"); if (hEventLog == NULL) goto PostCreateHeap; } // Get the counter index value and init the WinStationDataDefinition // counter values. Status = GetDescriptionOffset(); if (!NT_SUCCESS(Status)) goto PostOpenEventLog; SetupCounterIndices(); // Pre-determine the number of system CPUs. NumberOfCPUs = GetNumberOfCPUs(); // UsedList is used as a skip-list through all valid entries in the // hash table to allow us to iterate all entries without having to have // a second, low-performance loop that looks through each hash bucket // list. InitializeListHead(&UsedList); InitializeListHead(&UnusedList); for (i = 0; i < NumDefaultWinStationHashBuckets; i++) InitializeListHead(&DefaultWinStationHashBuckets[i]); return TRUE; // Error handling. PostOpenEventLog: MonCloseEventLog(); hEventLog = NULL; PostCreateHeap: HeapDestroy(hLibHeap); hLibHeap = NULL; return FALSE; } /****************************************************************************/ // DLL unload cleanup. /****************************************************************************/ BOOL DllProcessDetach(void) { // DBGPRINT(("PerfTS: ProcessDetach\n")); if (dwOpenCount > 0) { // the Library is being unloaded before it was // closed so close it now as this is the last // chance to do it before the library is tossed. // if the value of dwOpenCount is > 1, set it to // one to insure everything will be closed when // the close function is called. if (dwOpenCount > 1) dwOpenCount = 1; CloseTSObject(); } if (hEventLog != NULL) { MonCloseEventLog(); hEventLog = NULL; } if (hLibHeap != NULL && HeapDestroy(hLibHeap)) hLibHeap = NULL; return TRUE; } /****************************************************************************/ // Utility function used at startup. /****************************************************************************/ DWORD GetNumberOfCPUs(void) { NTSTATUS Status; DWORD ReturnLen; SYSTEM_BASIC_INFORMATION Info; Status = NtQuerySystemInformation( SystemBasicInformation, &Info, sizeof(Info), &ReturnLen ); if (NT_SUCCESS(Status)) { return Info.NumberOfProcessors; } else { DBGPRINT(("GetNumberOfCPUs Error 0x%x returning CPU count\n",Status)); // Return 1 CPU return 1; } } /****************************************************************************/ // Gets the offset index of the first text description from the // TermService\Performance key. This value was created by Lodctr / // LoadPerfCounterTextStrings() during setup. /****************************************************************************/ NTSTATUS GetDescriptionOffset(void) { HKEY hTermServiceKey; OBJECT_ATTRIBUTES Obja; NTSTATUS Status; UNICODE_STRING TermServiceSubKeyString; UNICODE_STRING ValueNameString; LONG ResultLength; PKEY_VALUE_PARTIAL_INFORMATION pValueInformation; BYTE ValueInfo[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD) - 1]; // Initialize UNICODE_STRING structures used in this function RtlInitUnicodeString(&TermServiceSubKeyString, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\TermService\\Performance"); RtlInitUnicodeString(&ValueNameString, L"First Counter"); // Initialize the OBJECT_ATTRIBUTES structure and open the key. InitializeObjectAttributes(&Obja, &TermServiceSubKeyString, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenKey(&hTermServiceKey, KEY_READ, &Obja); if (NT_SUCCESS(Status)) { // read value of desired entry pValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)ValueInfo; ResultLength = sizeof(ValueInfo); Status = NtQueryValueKey(hTermServiceKey, &ValueNameString, KeyValuePartialInformation, pValueInformation, sizeof(ValueInfo), &ResultLength); if (NT_SUCCESS(Status)) { // Check to see if it's a DWORD. if (pValueInformation->DataLength == sizeof(DWORD) && pValueInformation->Type == REG_DWORD) { FirstCounterIndex = *((DWORD *)(pValueInformation->Data)); } else { DBGPRINT(("PerfTS: Len %u not right or type %u not DWORD\n", pValueInformation->DataLength, pValueInformation->Type)); } } else { DBGPRINT(("PerfTS: Could not read counter value (status=%X)\n", Status)); } // close the registry key NtClose(hTermServiceKey); } else { DBGPRINT(("PerfTS: Unable to open TermService\\Performance key " "(status=%x)\n", Status)); } return Status; } /****************************************************************************/ // Initializes the WinStation and TermServer counter descriptions with the // loaded counter index offset. /****************************************************************************/ void SetupCounterIndices(void) { unsigned i; unsigned NumCounterDefs; PERF_COUNTER_DEFINITION *pCounterDef; // First index goes to the WinStation object description and help. WinStationDataDefinition.WinStationObjectType.ObjectNameTitleIndex += FirstCounterIndex; WinStationDataDefinition.WinStationObjectType.ObjectHelpTitleIndex += FirstCounterIndex; // We need to add the FirstCounterIndex value directly into the // description and help indices in the WinStation counters in // WinStationDataDefinition. pCounterDef = &WinStationDataDefinition.InputWdBytes; NumCounterDefs = (sizeof(WinStationDataDefinition) - (unsigned)((BYTE *)pCounterDef - (BYTE *)&WinStationDataDefinition)) / sizeof(PERF_COUNTER_DEFINITION); for (i = 0; i < NumCounterDefs; i++) { pCounterDef->CounterNameTitleIndex += FirstCounterIndex; pCounterDef->CounterHelpTitleIndex += FirstCounterIndex; pCounterDef++; } // We need to add the FirstCounterIndex value directly into the // description and help indices in the TermServer counters. TermServerDataDefinition.TermServerObjectType.ObjectNameTitleIndex += FirstCounterIndex; TermServerDataDefinition.TermServerObjectType.ObjectHelpTitleIndex += FirstCounterIndex; pCounterDef = &TermServerDataDefinition.NumSessions; NumCounterDefs = (sizeof(TermServerDataDefinition) - (unsigned)((BYTE *)pCounterDef - (BYTE *)&TermServerDataDefinition)) / sizeof(PERF_COUNTER_DEFINITION); for (i = 0; i < NumCounterDefs; i++) { pCounterDef->CounterNameTitleIndex += FirstCounterIndex; pCounterDef->CounterHelpTitleIndex += FirstCounterIndex; pCounterDef++; } } /****************************************************************************/ // PerfMon open entry point. // DeviceNames is ptr to object ID of each device to be opened. /****************************************************************************/ DWORD APIENTRY OpenTSObject(LPWSTR DeviceNames) { // DBGPRINT(("PerfTS: Open() called\n")); dwOpenCount++; // Allocate the first process info buffer. if (pProcessBuffer == NULL) { pProcessBuffer = ALLOCMEM(hLibHeap, HEAP_ZERO_MEMORY, ProcessBufSize); if (pProcessBuffer == NULL) return ERROR_OUTOFMEMORY; } return ERROR_SUCCESS; } /****************************************************************************/ // PerfMon close entry point. /****************************************************************************/ DWORD APIENTRY CloseTSObject(void) { PLIST_ENTRY pEntry; WinStationInfo *pWSI; // DBGPRINT(("PerfTS: Close() called\n")); if (--dwOpenCount == 0) { if (hLibHeap != NULL) { // Free the WinStation cache entries. pEntry = UsedList.Flink; while (!IsListEmpty(&UsedList)) { pEntry = RemoveHeadList(&UsedList); pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList); RemoveEntryList(&pWSI->HashBucketList); FREEMEM(hLibHeap, 0, pWSI); } if (pWinStationHashBuckets != DefaultWinStationHashBuckets) { FREEMEM(hLibHeap, 0, pWinStationHashBuckets); pWinStationHashBuckets = DefaultWinStationHashBuckets; NumWinStationHashBuckets = NumDefaultWinStationHashBuckets; } // Free the proc info buffer. if (pProcessBuffer != NULL) { FREEMEM(hLibHeap, 0, pProcessBuffer); pProcessBuffer = NULL; } } } return ERROR_SUCCESS; } /****************************************************************************/ // Grabs system process information into global buffer. /****************************************************************************/ __inline DWORD GetSystemProcessData(void) { DWORD dwReturnedBufferSize; NTSTATUS Status; // Get process data from system. while ((Status = NtQuerySystemInformation(SystemProcessInformation, pProcessBuffer, ProcessBufSize, &dwReturnedBufferSize)) == STATUS_INFO_LENGTH_MISMATCH) { BYTE *pNewProcessBuffer; // Expand buffer & retry. ReAlloc does not free the original mem // on error, so alloc into a temp pointer. ProcessBufSize += INCREMENT_BUFFER_SIZE; pNewProcessBuffer = REALLOCMEM(hLibHeap, 0, pProcessBuffer, ProcessBufSize); if (pNewProcessBuffer != NULL) pProcessBuffer = pNewProcessBuffer; else return ERROR_OUTOFMEMORY; } if (NT_SUCCESS(Status)) { // Get system time. Status = NtQuerySystemInformation(SystemTimeOfDayInformation, &SysTimeInfo, sizeof(SysTimeInfo), &dwReturnedBufferSize); if (!NT_SUCCESS(Status)) Status = (DWORD)RtlNtStatusToDosError(Status); } else { // Convert to Win32 error. Status = (DWORD)RtlNtStatusToDosError(Status); } return Status; } /****************************************************************************/ // Creates a WinStation name based on the WinStation state. // Assumes the cache lock is held. /****************************************************************************/ void ConstructSessionName( WinStationInfo *pWSI, WINSTATIONINFORMATIONW *pQueryData) { WCHAR *SrcName, *DstName; LPCTSTR pState = NULL; // Update active/inactive counts and create UI names for sessions. if (pQueryData->WinStationName[0] != '\0') { // We have a problem with WinStation names -- // the '#' sign is not allowed. So, replace them // with spaces during name copy. SrcName = pQueryData->WinStationName; DstName = pWSI->WinStationName; while (*SrcName != L'\0') { if (*SrcName != L'#') *DstName = *SrcName; else *DstName = L' '; SrcName++; DstName++; } *DstName = L'\0'; } else { // Create a fake session name based on the session ID and // an indication of the session state. _ltow(pWSI->SessionID, pWSI->WinStationName, 10); wcsncat(pWSI->WinStationName, L" ",1); pState = StrConnectState(pQueryData->ConnectState, TRUE); if(pState) { wcsncat(pWSI->WinStationName, (const wchar_t *)pState, (MAX_SESSION_NAME_LENGTH - 1) - wcslen(pWSI->WinStationName)); } } } /****************************************************************************/ // Adds a WinStationInfo block (with session ID already filled out) into // the cache. // Assumes the cache lock is held. /****************************************************************************/ void AddWinStationInfoToCache(WinStationInfo *pWSI) { unsigned i; unsigned Temp, NumHashBuckets; // Add to the hash table. InsertHeadList(&pWinStationHashBuckets[pWSI->SessionID & WinStationHashMask], &pWSI->HashBucketList); NumCachedWinStations++; // Check to see if we need to increase the hash table size. // If so, allocate a new one and populate it. // Hash table size is the number of entries * 4 rounded down // to the next lower power of 2, for easy key masking and higher // probability of having a low hash bucket list count. Temp = 4 * NumCachedWinStations; // Find the highest bit set in the hash bucket value. for (i = 0; Temp > 1; i++) Temp >>= 1; NumHashBuckets = 1 << i; if (NumWinStationHashBuckets < NumHashBuckets) CreateNewHashBuckets(NumHashBuckets); } /****************************************************************************/ // Common code for Add and RemoveWinStationInfo. // Assumes the cache lock is held. /****************************************************************************/ void CreateNewHashBuckets(unsigned NumHashBuckets) { unsigned i, HashMask; PLIST_ENTRY pLI, pEntry, pTempEntry; WinStationInfo *pTempWSI; if (NumHashBuckets != NumDefaultWinStationHashBuckets) pLI = ALLOCMEM(hLibHeap, 0, NumHashBuckets * sizeof(LIST_ENTRY)); else pLI = DefaultWinStationHashBuckets; if (pLI != NULL) { for (i = 0; i < NumHashBuckets; i++) InitializeListHead(&pLI[i]); HashMask = NumHashBuckets - 1; // Move the old hash table entries into the new table. // Have to enumerate all entries in used and unused lists // since we are likely to have entries scattered in both places. pEntry = UsedList.Flink; while (pEntry != &UsedList) { pTempWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList); InsertHeadList(&pLI[pTempWSI->SessionID & HashMask], &pTempWSI->HashBucketList); pEntry = pEntry->Flink; } pEntry = UnusedList.Flink; while (pEntry != &UnusedList) { pTempWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList); InsertHeadList(&pLI[pTempWSI->SessionID & HashMask], &pTempWSI->HashBucketList); pEntry = pEntry->Flink; } if (pWinStationHashBuckets != DefaultWinStationHashBuckets) FREEMEM(hLibHeap, 0, pWinStationHashBuckets); NumWinStationHashBuckets = NumHashBuckets; WinStationHashMask = HashMask; pWinStationHashBuckets = pLI; } else { // On failure, we just leave the hash table alone until next time. DBGPRINT(("PerfTS: Could not alloc new hash buckets\n")); } } /****************************************************************************/ // Removes a WSI from the hash table. // Assumes the cache lock is held. /****************************************************************************/ void RemoveWinStationInfoFromCache(WinStationInfo *pWSI) { unsigned i; unsigned Temp, NumHashBuckets, HashMask; // Remove from the hash table. RemoveEntryList(&pWSI->HashBucketList); NumCachedWinStations--; // Check to see if we need to decrease the hash table size. // If so, allocate a new one and populate it. // Hash table size is the number of entries * 4 rounded down // to the next lower power of 2, for easy key masking and higher // probability of having a low hash bucket list count. Temp = 4 * NumCachedWinStations; // Find the highest bit set in the hash bucket value. for (i = 0; Temp > 1; i++) Temp >>= 1; NumHashBuckets = 1 << i; if (NumWinStationHashBuckets < NumHashBuckets && NumWinStationHashBuckets >= 4) CreateNewHashBuckets(NumHashBuckets); } /****************************************************************************/ // PerfMon collect entry point. // Args: // ValueName: Registry name. // ppData: Passes in pointer to the address of the buffer to receive the // completed PerfDataBlock and subordinate structures. This routine will // append its data to the buffer starting at the point referenced by // *ppData. Passes out pointer to the first byte after the data structure // added by this routine. // pTotalBytes: Passes in ptr to size in bytes of the buf at *ppdata. Passes // out number of bytes added if *ppData is changed. // pNumObjectTypes: Passes out the number of objects added by this routine. // // Returns: Win32 error code. /****************************************************************************/ #define WinStationInstanceSize (sizeof(PERF_INSTANCE_DEFINITION) + \ (MAX_WINSTATION_NAME_LENGTH + 1) * sizeof(WCHAR) + \ 2 * sizeof(DWORD) + /* Allow for QWORD alignment space. */ \ sizeof(WINSTATION_COUNTER_DATA)) DWORD APIENTRY CollectTSObjectData( IN LPWSTR ValueName, IN OUT LPVOID *ppData, IN OUT LPDWORD pTotalBytes, OUT LPDWORD pNumObjectTypes) { DWORD Result; DWORD TotalLen; // Length of the total return block PERF_INSTANCE_DEFINITION *pPerfInstanceDefinition; PSYSTEM_PROCESS_INFORMATION pProcessInfo; ULONG NumWinStationInstances; NTSTATUS Status; ULONG ProcessBufferOffset; WINSTATION_DATA_DEFINITION UNALIGNED* pWinStationDataDefinition; WINSTATION_COUNTER_DATA *pWSC; TERMSERVER_DATA_DEFINITION *pTermServerDataDefinition; TERMSERVER_COUNTER_DATA *pTSC; ULONG SessionId; WinStationInfo *pWSI = NULL; LIST_ENTRY *pEntry; unsigned i; unsigned ActiveWS, InactiveWS; WCHAR *InstanceName; ULONG AmountRet; WCHAR StringBuf[MAX_SESSION_NAME_LENGTH]; WINSTATIONINFORMATIONW *pPassedQueryBuf; WINSTATIONINFORMATIONW QueryBuffer; LPCTSTR pState = NULL; #ifdef COLLECT_TIME DWORD StartTick = GetTickCount(); #endif // DBGPRINT(("PerfTS: Collect() called\n")); pWinStationDataDefinition = (WINSTATION_DATA_DEFINITION UNALIGNED*)*ppData; // Check for sufficient space for base WinStation object info and // as many instances as we currently have in our WinStation database. // Add in DWORD sizes for potential QWORD alignments. TotalLen = sizeof(WINSTATION_DATA_DEFINITION) + sizeof(DWORD) + sizeof(TERMSERVER_DATA_DEFINITION) + sizeof(TERMSERVER_COUNTER_DATA) + sizeof(DWORD) + NumCachedWinStations * WinStationInstanceSize; if (*pTotalBytes >= TotalLen) { // Grab the latest system process information. Result = GetSystemProcessData(); if (Result == ERROR_SUCCESS) { // Copy WinStation counter definitions. memcpy(pWinStationDataDefinition, &WinStationDataDefinition, sizeof(WINSTATION_DATA_DEFINITION)); pWinStationDataDefinition->WinStationObjectType.PerfTime = SysTimeInfo.CurrentTime; } else { DBGPRINT(("PerfTS: Failed to get process data\n")); goto ErrorExit; } } else { DBGPRINT(("PerfTS: Not enough space for base WinStation information\n")); Result = ERROR_MORE_DATA; goto ErrorExit; } // Before we start, we have to transfer each WinStationInfo in the // cache from the used list into an unused list to detect closed // WinStations. Also, we need to zero each WSI's pInstanceInfo to detect // whether we have retrieved the current I/O data for the WinStation. pEntry = UsedList.Blink; (UsedList.Flink)->Blink = &UnusedList; // Patch up head links to UnusedList. pEntry->Flink = &UnusedList; UnusedList = UsedList; InitializeListHead(&UsedList); pEntry = UnusedList.Flink; while (pEntry != &UnusedList) { pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList); pWSI->pInstanceInfo = NULL; pEntry = pEntry->Flink; } // Now collect data for each process, summing it for each unique SessionId. ActiveWS = InactiveWS = 0; NumWinStationInstances = 0; ProcessBufferOffset = 0; pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)pProcessBuffer; pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *) &pWinStationDataDefinition[1]; while (TRUE) { // Check for live process (having a name, one or more threads, or // not the Idle process (PID==0)). For WinStations we don't want to // charge the Idle process to the console (session ID == 0). if (pProcessInfo->ImageName.Buffer != NULL && pProcessInfo->NumberOfThreads > 0 && pProcessInfo->UniqueProcessId != 0) { // Get the session ID from the process. This is the same as the // LogonID in TS4. SessionId = pProcessInfo->SessionId; // Find the session ID in the cache. // We sum all processes seen for a given SessionId into the // same WinStation instance data block. pEntry = pWinStationHashBuckets[SessionId & WinStationHashMask]. Flink; while (pEntry != &pWinStationHashBuckets[SessionId & WinStationHashMask]) { pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, HashBucketList); if (pWSI->SessionID == SessionId) { // Found it. Now check that we've retrieved the WS info. if (pWSI->pInstanceInfo != NULL) { // Context is the WINSTATION_COUNTER_DATA entry // for this SessionId. pWSC = (WINSTATION_COUNTER_DATA *)pWSI->pInstanceInfo; // Now add the values to the existing counter block. UpdateWSProcessCounterBlock(pWSC, pProcessInfo); goto NextProcess; } break; } else { pEntry = pEntry->Flink; } } // We have a new entry or one for which we have not gathered // current information. First grab the info. if (WinStationQueryInformationW(SERVERNAME_CURRENT, SessionId, WinStationInformation, &QueryBuffer, sizeof(QueryBuffer), &AmountRet)) { if (QueryBuffer.ConnectState == State_Active) ActiveWS++; else InactiveWS++; // Check for a pre-cached WSI with no stats. if (pEntry != &pWinStationHashBuckets[SessionId & WinStationHashMask]) { // Verify the cached state (and thereby the name). if (pWSI->LastState != QueryBuffer.ConnectState) { pWSI->LastState = QueryBuffer.ConnectState; ConstructSessionName(pWSI, &QueryBuffer); } // Remove the entry from the unused list, place on the // used list. RemoveEntryList(&pWSI->UsedList); InsertHeadList(&UsedList, &pWSI->UsedList); } else { // Alloc a new entry. pWSI = ALLOCMEM(hLibHeap, 0, sizeof(WinStationInfo)); if (pWSI != NULL) { pWSI->SessionID = SessionId; pWSI->LastState = QueryBuffer.ConnectState; pWSI->pInstanceInfo = NULL; ConstructSessionName(pWSI, &QueryBuffer); // Add to the used list. InsertHeadList(&UsedList, &pWSI->UsedList); // Add new entry. We may have to increase the // number of hash buckets. AddWinStationInfoToCache(pWSI); } else { DBGPRINT(("PerfTS: Could not alloc new " "WinStationInfo\n")); goto NextProcess; } } InstanceName = pWSI->WinStationName; pPassedQueryBuf = &QueryBuffer; } else { // We have a WinStation Query problem. DBGPRINT(("PerfTS: Failed WSQueryInfo(SessID=%u), error=%u\n", SessionId, GetLastError())); // We could not open this WinStation, so we will identify // it as "ID Unknown" using -1 to StrConnectState. _ltow(SessionId, StringBuf, 10); wcsncat(StringBuf, L" ", 1); pState = StrConnectState(-1, TRUE); if (pState) { wcsncat(StringBuf, (const wchar_t *)pState, (MAX_SESSION_NAME_LENGTH - 1) - wcslen(StringBuf)); } // Alloc a new entry. pWSI = ALLOCMEM(hLibHeap, 0, sizeof(WinStationInfo)); if (pWSI != NULL) { pWSI->SessionID = SessionId; // We don't know the last state so we'll use -1 pWSI->LastState = -1; pWSI->pInstanceInfo = NULL; // Since we don't know the WinstationName, we'll use the // one we generated above wcsncpy(pWSI->WinStationName, StringBuf, WINSTATIONNAME_LENGTH); // Add to the used list. InsertHeadList(&UsedList, &pWSI->UsedList); // Add new entry. We may have to increase the // number of hash buckets. AddWinStationInfoToCache(pWSI); } else { DBGPRINT(("PerfTS: Could not alloc new " "WinStationInfo\n")); goto NextProcess; } InstanceName = StringBuf; pPassedQueryBuf = NULL; } // Add space for new instance header, name, and set of counters // to TotalLen and see if this instance will fit. TotalLen += WinStationInstanceSize; if (*pTotalBytes >= TotalLen) { NumWinStationInstances++; } else { DBGPRINT(("PerfTS: Not enough space for a new instance " "(cur inst = %u)\n", NumWinStationInstances)); Result = ERROR_MORE_DATA; goto ErrorExitFixupUsedList; } // MonBuildInstanceDefinition will create an instance of // the given supplied name inside of the callers buffer // supplied in pPerfInstanceDefinition. Our counter location // (the next memory after the instance header and name) is // returned in pWSC. // By remembering this pointer, and its counter size, we // can revisit it to add to the counters. MonBuildInstanceDefinition(pPerfInstanceDefinition, (PVOID *)&pWSC, 0, 0, (DWORD)-1, InstanceName); // Initialize the new counter block. SetupWinStationCounterBlock(pWSC, pPassedQueryBuf); // Now set the Context to this counter block so if we // see any more processes with this SessionId we // can add to the existing counter block. pWSI->pInstanceInfo = pWSC; // Now load the values into the counter block UpdateWSProcessCounterBlock(pWSC, pProcessInfo); // set perfdata pointer to next byte if its a new entry pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)(pWSC + 1); } NextProcess: // Exit if this was the last process in list if (pProcessInfo->NextEntryOffset != 0) { // point to next buffer in list ProcessBufferOffset += pProcessInfo->NextEntryOffset; pProcessInfo = (PSYSTEM_PROCESS_INFORMATION) &pProcessBuffer[ProcessBufferOffset]; } else { break; } } // Check for unused WinStations and remove. while (!IsListEmpty(&UnusedList)) { pEntry = RemoveHeadList(&UnusedList); pWSI = CONTAINING_RECORD(pEntry, WinStationInfo, UsedList); RemoveWinStationInfoFromCache(pWSI); FREEMEM(hLibHeap, 0, pWSI); } // Note number of WinStation instances. pWinStationDataDefinition->WinStationObjectType.NumInstances = NumWinStationInstances; // Now we know how large an area we used for the // WinStation definition, so we can update the offset // to the next object definition. Align size on QWORD. pTermServerDataDefinition = (TERMSERVER_DATA_DEFINITION *)( ALIGN_ON_QWORD(pPerfInstanceDefinition)); pWinStationDataDefinition->WinStationObjectType.TotalByteLength = (DWORD)((PCHAR)pTermServerDataDefinition - (PCHAR)pWinStationDataDefinition); // Now we set up and fill in the data for the TermServer object, // starting at the end of the WinStation instances. // No instances here, just fill in headers. memcpy(pTermServerDataDefinition, &TermServerDataDefinition, sizeof(TERMSERVER_DATA_DEFINITION)); pTermServerDataDefinition->TermServerObjectType.PerfTime = SysTimeInfo.CurrentTime; pTSC = (TERMSERVER_COUNTER_DATA *)(pTermServerDataDefinition + 1); pTSC->CounterBlock.ByteLength = sizeof(TERMSERVER_COUNTER_DATA); pTSC->NumActiveSessions = ActiveWS; pTSC->NumInactiveSessions = InactiveWS; pTSC->NumSessions = ActiveWS + InactiveWS; // Return final sizes. Align final address on a QWORD size. *ppData = ALIGN_ON_QWORD((LPVOID)(pTSC + 1)); pTermServerDataDefinition->TermServerObjectType.TotalByteLength = (DWORD)((PBYTE)*ppData - (PBYTE)pTermServerDataDefinition); *pTotalBytes = (DWORD)((PBYTE)*ppData - (PBYTE)pWinStationDataDefinition); *pNumObjectTypes = 2; #if DBG if (*pTotalBytes > TotalLen) DbgPrint ("PerfTS: Perf ctr. instance size underestimated: " "Est.=%u, Actual=%u", TotalLen, *pTotalBytes); #endif #ifdef COLLECT_TIME DbgPrint("*** Elapsed msec=%u\n", GetTickCount() - StartTick); #endif return ERROR_SUCCESS; // Error handling. ErrorExitFixupUsedList: // We have to return the UnusedList entries to the used list and exit the // cache lock. while (!IsListEmpty(&UnusedList)) { pEntry = RemoveHeadList(&UnusedList); InsertHeadList(&UsedList, pEntry); } ErrorExit: *pNumObjectTypes = 0; *pTotalBytes = 0; return Result; } #define CalculatePercent(count, hits) ((count) ? (hits) * 100 / (count) : 0) /****************************************************************************/ // SetupWinStationCounterBlock // // Initializes a new WinStation counter block. // // Args: // pCounters (input) // pointer to WinStation performance counter block // // pInfo (input) // Pointer to WINSTATIONINFORMATION structure to extract counters from // // pNextByte (output) // Returns the pointer to the byte beyound the end of the buffer. /****************************************************************************/ void SetupWinStationCounterBlock( WINSTATION_COUNTER_DATA *pCounters, PWINSTATIONINFORMATIONW pInfo) { // Fill in the WinStation information if available. if (pInfo != NULL) { PPROTOCOLCOUNTERS pi, po; PTHINWIRECACHE p; ULONG TotalReads = 0, TotalHits = 0; int i; // Set all members of pCounters->pcd to zero since we are not going to // init at this time. Then set the included PERF_COUNTER_BLOCK // byte length. memset(&pCounters->pcd, 0, sizeof(pCounters->pcd)); pCounters->pcd.CounterBlock.ByteLength = sizeof( WINSTATION_COUNTER_DATA); pi = &pInfo->Status.Input; po = &pInfo->Status.Output; // Copy input and output counters. memcpy(&pCounters->Input, pi, sizeof(PROTOCOLCOUNTERS)); memcpy(&pCounters->Output, po, sizeof(PROTOCOLCOUNTERS)); // Calculate I/O totals. pCounters->Total.WdBytes = pi->WdBytes + po->WdBytes; pCounters->Total.WdFrames = pi->WdFrames + po->WdFrames; pCounters->Total.Frames = pi->Frames + po->Frames; pCounters->Total.Bytes = pi->Bytes + po->Bytes; pCounters->Total.CompressedBytes = pi->CompressedBytes + po->CompressedBytes; pCounters->Total.CompressFlushes = pi->CompressFlushes + po->CompressFlushes; pCounters->Total.Errors = pi->Errors + po->Errors; pCounters->Total.Timeouts = pi->Timeouts + po->Timeouts; pCounters->Total.AsyncFramingError = pi->AsyncFramingError + po->AsyncFramingError; pCounters->Total.AsyncOverrunError = pi->AsyncOverrunError + po->AsyncOverrunError; pCounters->Total.AsyncOverflowError = pi->AsyncOverflowError + po->AsyncOverflowError; pCounters->Total.AsyncParityError = pi->AsyncParityError + po->AsyncParityError; pCounters->Total.TdErrors = pi->TdErrors + po->TdErrors; // Display driver cache info. // Bitmap cache. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[0]; pCounters->DDBitmap.CacheReads = p->CacheReads; pCounters->DDBitmap.CacheHits = p->CacheHits; pCounters->DDBitmap.HitRatio = CalculatePercent(p->CacheReads, p->CacheHits); TotalReads += p->CacheReads; TotalHits += p->CacheHits; // Glyph cache. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[1]; pCounters->DDGlyph.CacheReads = p->CacheReads; pCounters->DDGlyph.CacheHits = p->CacheHits; pCounters->DDGlyph.HitRatio = CalculatePercent(p->CacheReads, p->CacheHits); TotalReads += p->CacheReads; TotalHits += p->CacheHits; // Brush cache. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[2]; pCounters->DDBrush.CacheReads = p->CacheReads; pCounters->DDBrush.CacheHits = p->CacheHits; pCounters->DDBrush.HitRatio = CalculatePercent(p->CacheReads, p->CacheHits); TotalReads += p->CacheReads; TotalHits += p->CacheHits; // Save screen bitmap cache. p = &pInfo->Status.Cache.Specific.IcaCacheStats.ThinWireCache[3]; pCounters->DDSaveScr.CacheReads = p->CacheReads; pCounters->DDSaveScr.CacheHits = p->CacheHits; pCounters->DDSaveScr.HitRatio = CalculatePercent(p->CacheReads, p->CacheHits); TotalReads += p->CacheReads; TotalHits += p->CacheHits; // Cache totals. pCounters->DDTotal.CacheReads = TotalReads; pCounters->DDTotal.CacheHits = TotalHits; pCounters->DDTotal.HitRatio = CalculatePercent(TotalReads, TotalHits); // Compression PD ratios pCounters->InputCompressionRatio = CalculatePercent( pi->CompressedBytes, pi->Bytes); pCounters->OutputCompressionRatio = CalculatePercent( po->CompressedBytes, po->Bytes); pCounters->TotalCompressionRatio = CalculatePercent( pi->CompressedBytes + po->CompressedBytes, pi->Bytes + po->Bytes); } else { // Set all the counters to zero and then the perf block length. memset(pCounters, 0, sizeof(*pCounters)); pCounters->pcd.CounterBlock.ByteLength = sizeof( WINSTATION_COUNTER_DATA); } } /****************************************************************************/ // UpdateWSProcessCounterBlock // // Add the entries for the given process to the supplied counter block // // Args: // pCounters (input) // pointer to WS performance counter block // // ProcessInfo (input) // pointer to an NT SYSTEM_PROCESS_INFORMATION block /****************************************************************************/ void UpdateWSProcessCounterBlock( WINSTATION_COUNTER_DATA *pCounters, PSYSTEM_PROCESS_INFORMATION pProcessInfo) { pCounters->pcd.PageFaults += pProcessInfo->PageFaultCount; // User, Kernel and Processor Time counters need to be scaled by the // number of processors. pCounters->pcd.ProcessorTime += (pProcessInfo->KernelTime.QuadPart + pProcessInfo->UserTime.QuadPart) / NumberOfCPUs; pCounters->pcd.UserTime += pProcessInfo->UserTime.QuadPart / NumberOfCPUs; pCounters->pcd.KernelTime += pProcessInfo->KernelTime.QuadPart / NumberOfCPUs; pCounters->pcd.PeakVirtualSize += pProcessInfo->PeakVirtualSize; pCounters->pcd.VirtualSize += pProcessInfo->VirtualSize; pCounters->pcd.PeakWorkingSet += pProcessInfo->PeakWorkingSetSize; pCounters->pcd.TotalWorkingSet += pProcessInfo->WorkingSetSize; pCounters->pcd.PeakPageFile += pProcessInfo->PeakPagefileUsage; pCounters->pcd.PageFile += pProcessInfo->PagefileUsage; pCounters->pcd.PrivatePages += pProcessInfo->PrivatePageCount; pCounters->pcd.ThreadCount += pProcessInfo->NumberOfThreads; // BasePriority, ElapsedTime, ProcessId, CreatorProcessId not totaled. pCounters->pcd.PagedPool += (DWORD)pProcessInfo->QuotaPagedPoolUsage; pCounters->pcd.NonPagedPool += (DWORD)pProcessInfo->QuotaNonPagedPoolUsage; pCounters->pcd.HandleCount += (DWORD)pProcessInfo->HandleCount; // I/O counts not totaled at this time. }