Windows2003-3790/termsrv/perfts/perfts.c

1171 lines
43 KiB
C

/*++
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 <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <winperf.h>
#include <ntprfctr.h>
#include <perfutil.h>
#include <winsta.h>
#include <utildll.h>
#include <stdlib.h>
#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.
}