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.
1719 lines
49 KiB
1719 lines
49 KiB
/*===================================================================
|
|
Microsoft Denali
|
|
|
|
Microsoft Confidential.
|
|
Copyright 1996 Microsoft Corporation. All Rights Reserved.
|
|
|
|
Component: Main
|
|
|
|
File: perfdef.h
|
|
|
|
Owner: DmitryR
|
|
|
|
Data definitions shared between asp.dll and aspperf.dll
|
|
===================================================================*/
|
|
|
|
#ifndef _ASP_PERFDEF_H
|
|
#define _ASP_PERFDEF_H
|
|
|
|
#include <winbase.h>
|
|
|
|
#include <pudebug.h>
|
|
|
|
#ifndef ErrInitCriticalSection
|
|
#define ErrInitCriticalSection( cs, hr ) \
|
|
do { \
|
|
hr = S_OK; \
|
|
__try \
|
|
{ \
|
|
InitializeCriticalSection (cs); \
|
|
} \
|
|
__except(1) \
|
|
{ \
|
|
hr = E_UNEXPECTED; \
|
|
} \
|
|
} while (0)
|
|
|
|
#endif
|
|
/*===================================================================
|
|
PerfData indices
|
|
===================================================================*/
|
|
// Counter offsets in the array
|
|
|
|
#define ID_DEBUGDOCREQ 0
|
|
#define ID_REQERRRUNTIME 1
|
|
#define ID_REQERRPREPROC 2
|
|
#define ID_REQERRCOMPILE 3
|
|
#define ID_REQERRORPERSEC 4
|
|
#define ID_REQTOTALBYTEIN 5
|
|
#define ID_REQTOTALBYTEOUT 6
|
|
#define ID_REQEXECTIME 7
|
|
#define ID_REQWAITTIME 8
|
|
#define ID_REQCOMFAILED 9
|
|
#define ID_REQBROWSEREXEC 10
|
|
#define ID_REQFAILED 11
|
|
#define ID_REQNOTAUTH 12
|
|
#define ID_REQNOTFOUND 13
|
|
#define ID_REQCURRENT 14
|
|
#define ID_REQREJECTED 15
|
|
#define ID_REQSUCCEEDED 16
|
|
#define ID_REQTIMEOUT 17
|
|
#define ID_REQTOTAL 18
|
|
#define ID_REQPERSEC 19
|
|
#define ID_SCRIPTFREEENG 20
|
|
#define ID_SESSIONLIFETIME 21
|
|
#define ID_SESSIONCURRENT 22
|
|
#define ID_SESSIONTIMEOUT 23
|
|
#define ID_SESSIONSTOTAL 24
|
|
#define ID_TEMPLCACHE 25
|
|
#define ID_TEMPLCACHEHITS 26
|
|
#define ID_TEMPLCACHETRYS 27
|
|
#define ID_TEMPLFLUSHES 28
|
|
#define ID_TRANSABORTED 29
|
|
#define ID_TRANSCOMMIT 30
|
|
#define ID_TRANSPENDING 31
|
|
#define ID_TRANSTOTAL 32
|
|
#define ID_TRANSPERSEC 33
|
|
#define ID_MEMORYTEMPLCACHE 34
|
|
#define ID_MEMORYTEMPLCACHEHITS 35
|
|
#define ID_MEMORYTEMPLCACHETRYS 36
|
|
#define ID_ENGINECACHEHITS 37
|
|
#define ID_ENGINECACHETRYS 38
|
|
#define ID_ENGINEFLUSHES 39
|
|
|
|
// Number of counters in per-process file map
|
|
#define C_PERF_PROC_COUNTERS 40
|
|
|
|
/*===================================================================
|
|
Definitions of names, sizes and mapped data block structures
|
|
===================================================================*/
|
|
|
|
// Mutex name to access the main file map
|
|
#define SZ_PERF_MUTEX "Global\\ASP_PERFMON_MUTEX"
|
|
|
|
// WaitForSingleObject arg (how long to wait for mutext before failing)
|
|
#define PERM_MUTEX_WAIT 1000
|
|
|
|
// event signaled by ASP processes when a procId is added
|
|
#define SZ_PERF_ADD_EVENT "Global\\ASP_PERFMON_ADD_EVENT"
|
|
|
|
// Main shared file map name
|
|
#define SZ_PERF_MAIN_FILEMAP "Global\\ASP_PERFMON_MAIN_BLOCK"
|
|
|
|
// Max number of registered (ASP) processes in main file map
|
|
#define C_PERF_PROC_MAX 2048
|
|
|
|
// Structure that defines main file map
|
|
struct CPerfMainBlockData
|
|
{
|
|
DWORD m_dwTimestamp; // time (GetTickCount()) of the last change
|
|
DWORD m_cItems; // number of registred processes
|
|
|
|
// array of process IDs
|
|
DWORD m_dwProcIds[C_PERF_PROC_MAX];
|
|
|
|
// array of accumulated counters for dead processes
|
|
DWORD m_rgdwCounters[C_PERF_PROC_COUNTERS];
|
|
|
|
DWORD m_dwWASPid;
|
|
};
|
|
|
|
#define CB_PERF_MAIN_BLOCK (sizeof(struct CPerfMainBlockData))
|
|
|
|
// Name for per-process file map
|
|
#define SZ_PERF_PROC_FILEMAP_PREFIX "Global\\ASP_PERFMON_BLOCK_"
|
|
#define CCH_PERF_PROC_FILEMAP_PREFIX 25
|
|
|
|
struct CPerfProcBlockData
|
|
{
|
|
DWORD m_dwProcId; // process CLS ID
|
|
DWORD m_rgdwCounters[C_PERF_PROC_COUNTERS]; // array counters
|
|
};
|
|
|
|
#define CB_PERF_PROC_BLOCK (sizeof(struct CPerfProcBlockData))
|
|
#define CB_COUNTERS (sizeof(DWORD) * C_PERF_PROC_COUNTERS)
|
|
|
|
class CASPPerfManager;
|
|
|
|
/*===================================================================
|
|
CSharedMemBlock -- generic shared memory block
|
|
===================================================================*/
|
|
|
|
class CSharedMemBlock
|
|
{
|
|
public:
|
|
HANDLE m_hMemory;
|
|
void *m_pMemory;
|
|
|
|
SECURITY_ATTRIBUTES m_sa;
|
|
|
|
inline CSharedMemBlock() : m_hMemory(NULL), m_pMemory(NULL) {
|
|
m_sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
m_sa.lpSecurityDescriptor = NULL;
|
|
m_sa.bInheritHandle = FALSE;
|
|
}
|
|
inline ~CSharedMemBlock() {
|
|
UnInitMap();
|
|
if (m_sa.lpSecurityDescriptor)
|
|
free(m_sa.lpSecurityDescriptor);
|
|
}
|
|
|
|
inline void *PMemory() { return m_pMemory; }
|
|
|
|
HRESULT InitSD();
|
|
HRESULT InitMap(LPCSTR szName, DWORD dwSize, BOOL bCreate = TRUE);
|
|
HRESULT UnInitMap();
|
|
|
|
SECURITY_ATTRIBUTES *PGetSA() { return &m_sa; }
|
|
private:
|
|
HRESULT CreateSids( PSID *ppBuiltInAdministrators,
|
|
PSID *ppPowerUsers,
|
|
PSID *ppAuthenticatedUsers,
|
|
PSID *ppPerfMonUsers,
|
|
PSID *ppPerfLogUsers);
|
|
};
|
|
|
|
//
|
|
// CreateSids
|
|
//
|
|
// Create 3 Security IDs
|
|
//
|
|
// Caller must free memory allocated to SIDs on success.
|
|
//
|
|
// Returns: HRESULT indicating SUCCESS or FAILURE
|
|
//
|
|
inline HRESULT CSharedMemBlock::CreateSids(
|
|
PSID *ppBuiltInAdministrators,
|
|
PSID *ppPowerUsers,
|
|
PSID *ppAuthenticatedUsers,
|
|
PSID *ppPerfMonUsers,
|
|
PSID *ppPerfLogUsers
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
*ppBuiltInAdministrators = NULL;
|
|
*ppPowerUsers = NULL;
|
|
*ppAuthenticatedUsers = NULL;
|
|
*ppPerfMonUsers = NULL;
|
|
*ppPerfLogUsers = NULL;
|
|
|
|
//
|
|
// An SID is built from an Identifier Authority and a set of Relative IDs
|
|
// (RIDs). The Authority of interest to us SECURITY_NT_AUTHORITY.
|
|
//
|
|
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
|
|
//
|
|
// Each RID represents a sub-unit of the authority. Two of the SIDs we
|
|
// want to build, Local Administrators, and Power Users, are in the "built
|
|
// in" domain. The other SID, for Authenticated users, is based directly
|
|
// off of the authority.
|
|
//
|
|
// For examples of other useful SIDs consult the list in
|
|
// \nt\public\sdk\inc\ntseapi.h.
|
|
//
|
|
|
|
if (!AllocateAndInitializeSid(&NtAuthority,
|
|
2, // 2 sub-authorities
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0,0,0,0,0,0,
|
|
ppBuiltInAdministrators)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AllocateAndInitializeSid(&NtAuthority,
|
|
2, // 2 sub-authorities
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_POWER_USERS,
|
|
0,0,0,0,0,0,
|
|
ppPowerUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AllocateAndInitializeSid(&NtAuthority,
|
|
1, // 1 sub-authority
|
|
SECURITY_AUTHENTICATED_USER_RID,
|
|
0,0,0,0,0,0,0,
|
|
ppAuthenticatedUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AllocateAndInitializeSid(&NtAuthority,
|
|
2, // 1 sub-authority
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_MONITORING_USERS,
|
|
0,0,0,0,0,0,
|
|
ppPerfMonUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AllocateAndInitializeSid(&NtAuthority,
|
|
2, // 1 sub-authority
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_LOGGING_USERS,
|
|
0,0,0,0,0,0,
|
|
ppPerfLogUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
}
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
if (*ppBuiltInAdministrators) {
|
|
FreeSid(*ppBuiltInAdministrators);
|
|
*ppBuiltInAdministrators = NULL;
|
|
}
|
|
|
|
if (*ppPowerUsers) {
|
|
FreeSid(*ppPowerUsers);
|
|
*ppPowerUsers = NULL;
|
|
}
|
|
|
|
if (*ppAuthenticatedUsers) {
|
|
FreeSid(*ppAuthenticatedUsers);
|
|
*ppAuthenticatedUsers = NULL;
|
|
}
|
|
|
|
if (*ppPerfMonUsers) {
|
|
FreeSid(*ppPerfMonUsers);
|
|
*ppPerfMonUsers = NULL;
|
|
}
|
|
|
|
if (*ppPerfLogUsers) {
|
|
FreeSid(*ppPerfLogUsers);
|
|
*ppPerfLogUsers = NULL;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// InitSD
|
|
//
|
|
// Creates a SECURITY_DESCRIPTOR with specific DACLs.
|
|
//
|
|
|
|
inline HRESULT CSharedMemBlock::InitSD()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
PSID pAuthenticatedUsers = NULL;
|
|
PSID pBuiltInAdministrators = NULL;
|
|
PSID pPowerUsers = NULL;
|
|
PSID pMonUsers = NULL;
|
|
PSID pLogUsers = NULL;
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
|
|
if (m_sa.lpSecurityDescriptor != NULL) {
|
|
return S_OK;
|
|
}
|
|
|
|
if (FAILED(hr = CreateSids(&pBuiltInAdministrators,
|
|
&pPowerUsers,
|
|
&pAuthenticatedUsers,
|
|
&pMonUsers,
|
|
&pLogUsers)));
|
|
|
|
|
|
else {
|
|
|
|
//
|
|
// Calculate the size of and allocate a buffer for the DACL, we need
|
|
// this value independently of the total alloc size for ACL init.
|
|
//
|
|
|
|
ULONG AclSize;
|
|
|
|
//
|
|
// "- sizeof (ULONG)" represents the SidStart field of the
|
|
// ACCESS_ALLOWED_ACE. Since we're adding the entire length of the
|
|
// SID, this field is counted twice.
|
|
//
|
|
|
|
AclSize = sizeof (ACL) +
|
|
(5 * (sizeof (ACCESS_ALLOWED_ACE) - sizeof (ULONG))) +
|
|
GetLengthSid(pAuthenticatedUsers) +
|
|
GetLengthSid(pBuiltInAdministrators) +
|
|
GetLengthSid(pPowerUsers) +
|
|
GetLengthSid(pMonUsers) +
|
|
GetLengthSid(pLogUsers);
|
|
|
|
pSD = malloc(SECURITY_DESCRIPTOR_MIN_LENGTH + AclSize);
|
|
|
|
if (!pSD) {
|
|
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
} else {
|
|
|
|
ACL *Acl;
|
|
|
|
Acl = (ACL *)((BYTE *)pSD + SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
|
|
if (!InitializeAcl(Acl,
|
|
AclSize,
|
|
ACL_REVISION)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
|
pAuthenticatedUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
|
pPowerUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
|
pBuiltInAdministrators)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
|
pMonUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!AddAccessAllowedAce(Acl,
|
|
ACL_REVISION,
|
|
SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
|
pLogUsers)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!InitializeSecurityDescriptor(pSD,
|
|
SECURITY_DESCRIPTOR_REVISION)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
} else if (!SetSecurityDescriptorDacl(pSD,
|
|
TRUE,
|
|
Acl,
|
|
FALSE)) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pAuthenticatedUsers)
|
|
FreeSid(pAuthenticatedUsers);
|
|
|
|
if (pBuiltInAdministrators)
|
|
FreeSid(pBuiltInAdministrators);
|
|
|
|
if (pPowerUsers)
|
|
FreeSid(pPowerUsers);
|
|
|
|
if (pMonUsers)
|
|
FreeSid(pMonUsers);
|
|
|
|
if (pLogUsers)
|
|
FreeSid(pLogUsers);
|
|
|
|
if (FAILED(hr) && pSD) {
|
|
free(pSD);
|
|
pSD = NULL;
|
|
}
|
|
|
|
m_sa.lpSecurityDescriptor = pSD;
|
|
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CSharedMemBlock::InitMap
|
|
(
|
|
LPCSTR szName,
|
|
DWORD dwSize,
|
|
BOOL bCreate /* = TRUE */
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (FAILED(hr = InitSD())) {
|
|
return hr;
|
|
}
|
|
|
|
// If we are suppose to be the one's creating the memory,
|
|
// then make sure that we are.
|
|
if ( bCreate )
|
|
{
|
|
m_hMemory = CreateFileMappingA
|
|
(
|
|
INVALID_HANDLE_VALUE,
|
|
&m_sa,
|
|
PAGE_READWRITE,
|
|
0,
|
|
dwSize,
|
|
szName
|
|
);
|
|
|
|
if ( m_hMemory == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError () );
|
|
}
|
|
else
|
|
{
|
|
if ( GetLastError() == ERROR_ALREADY_EXISTS )
|
|
{
|
|
CloseHandle ( m_hMemory );
|
|
m_hMemory = NULL;
|
|
|
|
return HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS );
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Try to open existing
|
|
m_hMemory = OpenFileMappingA
|
|
(
|
|
FILE_MAP_WRITE | FILE_MAP_READ,
|
|
FALSE,
|
|
szName
|
|
);
|
|
|
|
if (!m_hMemory)
|
|
return E_FAIL;
|
|
}
|
|
|
|
m_pMemory = MapViewOfFile
|
|
(
|
|
m_hMemory,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0
|
|
);
|
|
if (!m_pMemory)
|
|
{
|
|
UnInitMap();
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (bCreate)
|
|
memset(m_pMemory, 0, dwSize);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline HRESULT CSharedMemBlock::UnInitMap()
|
|
{
|
|
if (m_pMemory)
|
|
{
|
|
UnmapViewOfFile(m_pMemory);
|
|
m_pMemory = NULL;
|
|
}
|
|
if (m_hMemory)
|
|
{
|
|
CloseHandle(m_hMemory);
|
|
m_hMemory = NULL;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
CPerfProcBlock - class representing pref data for a single process
|
|
===================================================================*/
|
|
|
|
class CPerfProcBlock : public CSharedMemBlock
|
|
{
|
|
|
|
friend class CPerfMainBlock;
|
|
friend class CASPPerfManager;
|
|
|
|
#ifndef _PERF_CMD
|
|
protected:
|
|
#else
|
|
public:
|
|
#endif
|
|
DWORD m_fInited : 1;
|
|
DWORD m_fMemCSInited : 1;
|
|
DWORD m_fReqCSInited : 1;
|
|
DWORD m_fProcessDead : 1;
|
|
|
|
HANDLE m_hWaitHandle;
|
|
|
|
// critical sections (only used in ASP.DLL)
|
|
CRITICAL_SECTION m_csMemLock; // CS for memory counters
|
|
CRITICAL_SECTION m_csReqLock; // CS for per-request counters
|
|
|
|
// block of counters
|
|
CPerfProcBlockData *m_pData;
|
|
|
|
// next process data (used in ASPPERF.DLL/WAS)
|
|
CPerfProcBlock *m_pNext;
|
|
|
|
// access shared memory
|
|
HRESULT MapMemory(DWORD procId, BOOL bCreate = TRUE);
|
|
|
|
static VOID CALLBACK WaitCallback(PVOID pArg, BOOLEAN fReason);
|
|
|
|
public:
|
|
inline CPerfProcBlock()
|
|
: m_fInited(FALSE),
|
|
m_fMemCSInited(FALSE), m_fReqCSInited(FALSE),
|
|
m_fProcessDead(FALSE),
|
|
m_hWaitHandle(NULL),
|
|
m_pData(NULL), m_pNext(NULL)
|
|
{}
|
|
|
|
inline ~CPerfProcBlock() { UnInit(); }
|
|
|
|
HRESULT InitCriticalSections();
|
|
HRESULT UnInitCriticalSections();
|
|
|
|
HRESULT InitExternal(DWORD procId); // from ASPPERF.DLL
|
|
|
|
HRESULT InitForThisProcess // from ASP.DLL
|
|
(
|
|
DWORD procId,
|
|
DWORD *pdwInitCounters = NULL
|
|
);
|
|
|
|
HRESULT UnInit();
|
|
};
|
|
|
|
inline HRESULT CPerfProcBlock::MapMemory
|
|
(
|
|
DWORD procId,
|
|
BOOL bCreate /* = TRUE */
|
|
)
|
|
{
|
|
// Construct unique map name with CLSID
|
|
char szMapName[CCH_PERF_PROC_FILEMAP_PREFIX+32+1];
|
|
strcpy(szMapName, SZ_PERF_PROC_FILEMAP_PREFIX);
|
|
|
|
char *pszHex = szMapName + CCH_PERF_PROC_FILEMAP_PREFIX;
|
|
sprintf(pszHex, "%08x", procId);
|
|
|
|
// create or open the map
|
|
HRESULT hr = InitMap(szMapName, CB_PERF_PROC_BLOCK, bCreate);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pData = (CPerfProcBlockData *)PMemory();
|
|
|
|
if (m_pData->m_dwProcId == 0)
|
|
m_pData->m_dwProcId = procId;
|
|
else if (m_pData->m_dwProcId != procId)
|
|
hr = E_FAIL; // cls id mismatch
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CPerfProcBlock::InitCriticalSections()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!m_fMemCSInited)
|
|
{
|
|
__try { INITIALIZE_CRITICAL_SECTION(&m_csMemLock); }
|
|
__except(1) { hr = E_UNEXPECTED; }
|
|
if (SUCCEEDED(hr))
|
|
m_fMemCSInited = TRUE;
|
|
else
|
|
return hr;
|
|
}
|
|
|
|
if (!m_fReqCSInited)
|
|
{
|
|
__try { INITIALIZE_CRITICAL_SECTION(&m_csReqLock); }
|
|
__except(1) { hr = E_UNEXPECTED; }
|
|
if (SUCCEEDED(hr))
|
|
m_fReqCSInited = TRUE;
|
|
else
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline HRESULT CPerfProcBlock::UnInitCriticalSections()
|
|
{
|
|
if (m_fMemCSInited)
|
|
{
|
|
DeleteCriticalSection(&m_csMemLock);
|
|
m_fMemCSInited = FALSE;
|
|
}
|
|
if (m_fReqCSInited)
|
|
{
|
|
DeleteCriticalSection(&m_csReqLock);
|
|
m_fReqCSInited = FALSE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline HRESULT CPerfProcBlock::InitExternal
|
|
(
|
|
DWORD procId
|
|
)
|
|
{
|
|
HRESULT hr = MapMemory(procId, FALSE);
|
|
|
|
if (SUCCEEDED(hr))
|
|
m_fInited = TRUE;
|
|
else
|
|
UnInit();
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CPerfProcBlock::InitForThisProcess
|
|
(
|
|
DWORD procId,
|
|
DWORD *pdwInitCounters
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Map the shared memory
|
|
if (SUCCEEDED(hr))
|
|
hr = MapMemory(procId, TRUE);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// init the counters
|
|
if (pdwInitCounters)
|
|
memcpy(m_pData->m_rgdwCounters, pdwInitCounters, CB_COUNTERS);
|
|
else
|
|
memset(m_pData->m_rgdwCounters, 0, CB_COUNTERS);
|
|
|
|
m_fInited = TRUE;
|
|
}
|
|
else
|
|
{
|
|
UnInit();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CPerfProcBlock::UnInit()
|
|
{
|
|
UnInitMap();
|
|
|
|
m_pData = NULL;
|
|
m_pNext = NULL;
|
|
m_fInited = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
inline VOID CALLBACK CPerfProcBlock::WaitCallback(PVOID pArg, BOOLEAN fReason)
|
|
{
|
|
CPerfProcBlock *pPerfBlock = (CPerfProcBlock *)pArg;
|
|
|
|
pPerfBlock->m_fProcessDead = TRUE;
|
|
}
|
|
|
|
/*===================================================================
|
|
CPerfMainBlock - class representing the main perf data
|
|
===================================================================*/
|
|
|
|
class CPerfMainBlock : public CSharedMemBlock
|
|
{
|
|
|
|
friend CASPPerfManager;
|
|
|
|
#ifndef _PERF_CMD
|
|
private:
|
|
#else
|
|
public:
|
|
#endif
|
|
DWORD m_fInited : 1;
|
|
|
|
// the process block directory
|
|
CPerfMainBlockData *m_pData;
|
|
|
|
// mutex to access the process block directory
|
|
HANDLE m_hMutex;
|
|
|
|
HANDLE m_hChangeEvent;
|
|
|
|
HANDLE m_WASProcessHandle;
|
|
|
|
// first process data (used in ASPPERF.DLL)
|
|
CPerfProcBlock *m_pProcBlock;
|
|
|
|
// timestamp of main block when the list of process blocks
|
|
// last loaded -- to make decide to reload (ASPPREF.DLL only)
|
|
DWORD m_dwTimestamp;
|
|
|
|
public:
|
|
inline CPerfMainBlock()
|
|
: m_fInited(FALSE),
|
|
m_hChangeEvent(NULL),
|
|
m_pData(NULL),
|
|
m_hMutex(NULL),
|
|
m_pProcBlock(NULL),
|
|
m_dwTimestamp(NULL),
|
|
m_WASProcessHandle(NULL)
|
|
{}
|
|
|
|
inline ~CPerfMainBlock() { UnInit(); }
|
|
|
|
HRESULT Init(BOOL bWASInit = FALSE);
|
|
HRESULT UnInit();
|
|
|
|
// lock / unlock using mutex
|
|
HRESULT Lock();
|
|
HRESULT UnLock();
|
|
|
|
// add/remove process record to the main block (used from ASP.DLL)
|
|
HRESULT AddProcess(DWORD procId);
|
|
|
|
// load CPerfProcBlock blocks from the main block into
|
|
// objects (used from APPPREF.DLL)
|
|
HRESULT Load();
|
|
|
|
// gather (sum-up) the statistics from each proc block
|
|
HRESULT GetStats(DWORD *pdwCounters);
|
|
|
|
// copies the counters from a process that is going away into
|
|
// the shared array of the accumulated counters from dead processes.
|
|
// Used from WAS.
|
|
VOID AggregateDeadProcCounters(CPerfProcBlock *pBlock);
|
|
|
|
HRESULT CreateChangeEvent(BOOL bMustCreate);
|
|
|
|
VOID SetChangeEvent() { SetEvent(m_hChangeEvent); }
|
|
|
|
HANDLE GetWASProcessHandle();
|
|
|
|
|
|
};
|
|
|
|
inline
|
|
HRESULT CPerfMainBlock::Init(BOOL bWASInit)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (FAILED(hr = InitSD()))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
// only WAS can create the Mutex, others have to just open it
|
|
|
|
if (bWASInit)
|
|
{
|
|
m_hMutex = CreateMutexA(&m_sa, FALSE, SZ_PERF_MUTEX);
|
|
|
|
if (m_hMutex == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError () );
|
|
}
|
|
// If we got it, but we didn't create it then throw it
|
|
// back. Only WAS can create this.
|
|
else if ( GetLastError() == ERROR_ALREADY_EXISTS )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError () );
|
|
|
|
CloseHandle( m_hMutex );
|
|
m_hMutex = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_hMutex = OpenMutexA(SYNCHRONIZE, FALSE, SZ_PERF_MUTEX);
|
|
}
|
|
|
|
if (!m_hMutex)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = InitMap(SZ_PERF_MAIN_FILEMAP, CB_PERF_MAIN_BLOCK, bWASInit);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pData = (CPerfMainBlockData *)PMemory();
|
|
|
|
// We got the memory mapped, so set the WAS PID into it
|
|
// if we are setting up the WAS side, otherwise read
|
|
// the value in for others to use.
|
|
if ( bWASInit )
|
|
{
|
|
|
|
m_pData->m_dwWASPid = GetCurrentProcessId();
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CreateChangeEvent(bWASInit);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fInited = TRUE;
|
|
}
|
|
else
|
|
{
|
|
UnInit();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CPerfMainBlock::UnInit()
|
|
{
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Cleaning up ProcBlocks\n"));
|
|
|
|
while (m_pProcBlock)
|
|
{
|
|
CPerfProcBlock *pNext = m_pProcBlock->m_pNext;
|
|
m_pProcBlock->UnInit();
|
|
delete m_pProcBlock;
|
|
m_pProcBlock = pNext;
|
|
}
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Cleaning up mutex\n"));
|
|
|
|
if (m_hMutex)
|
|
{
|
|
CloseHandle(m_hMutex);
|
|
m_hMutex = NULL;
|
|
}
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Uninit'ing map\n"));
|
|
|
|
|
|
UnInitMap();
|
|
|
|
m_dwTimestamp = 0;
|
|
m_pData = NULL;
|
|
m_pProcBlock = NULL;
|
|
m_fInited = FALSE;
|
|
|
|
// close its handle
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Closing ChangeEvent Handle\n"));
|
|
|
|
if (m_hChangeEvent != NULL)
|
|
CloseHandle(m_hChangeEvent);
|
|
|
|
m_hChangeEvent = NULL;
|
|
|
|
if ( m_WASProcessHandle != NULL )
|
|
{
|
|
CloseHandle ( m_WASProcessHandle );
|
|
m_WASProcessHandle = NULL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline HRESULT CPerfMainBlock::CreateChangeEvent(BOOL bMustCreate)
|
|
{
|
|
// Create the changed event using the standard SD. Make the
|
|
// reset Automatic and initial state to unsignalled.
|
|
|
|
if (bMustCreate) {
|
|
m_hChangeEvent = CreateEventA(&m_sa,
|
|
FALSE,
|
|
FALSE,
|
|
SZ_PERF_ADD_EVENT);
|
|
|
|
// if GetLastError indicates that the handle already exists, this
|
|
// is bad. Return an error. This process should always be the creator
|
|
// of the event.
|
|
if ((GetLastError() == ERROR_ALREADY_EXISTS)) {
|
|
CloseHandle(m_hChangeEvent);
|
|
m_hChangeEvent = NULL;
|
|
return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
|
|
}
|
|
|
|
}
|
|
else {
|
|
m_hChangeEvent = OpenEventA(EVENT_MODIFY_STATE,
|
|
FALSE,
|
|
SZ_PERF_ADD_EVENT);
|
|
}
|
|
|
|
if (m_hChangeEvent == NULL)
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline
|
|
HANDLE CPerfMainBlock::GetWASProcessHandle()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( m_WASProcessHandle == NULL )
|
|
{
|
|
m_WASProcessHandle = OpenProcess ( SYNCHRONIZE, // security
|
|
FALSE, // not inheritable
|
|
m_pData->m_dwWASPid);
|
|
|
|
// If we failed to open the process the handle will be null.
|
|
// This will be checked for by the caller.
|
|
}
|
|
|
|
return m_WASProcessHandle;
|
|
}
|
|
|
|
|
|
inline HRESULT CPerfMainBlock::Lock()
|
|
{
|
|
if (!m_hMutex)
|
|
return E_FAIL;
|
|
if (WaitForSingleObject(m_hMutex, PERM_MUTEX_WAIT) == WAIT_TIMEOUT)
|
|
return E_FAIL;
|
|
return S_OK;
|
|
}
|
|
|
|
inline HRESULT CPerfMainBlock::UnLock()
|
|
{
|
|
if (m_hMutex)
|
|
ReleaseMutex(m_hMutex);
|
|
return S_OK;
|
|
}
|
|
|
|
inline HRESULT CPerfMainBlock::AddProcess
|
|
(
|
|
DWORD procId
|
|
)
|
|
{
|
|
if (!m_fInited)
|
|
return E_FAIL;
|
|
|
|
if (FAILED(Lock())) // lock mutex
|
|
return E_FAIL;
|
|
HRESULT hr = S_OK;
|
|
|
|
BOOL fFound = FALSE;
|
|
|
|
DWORD idx = 0;
|
|
// find
|
|
for (DWORD cnt = min(m_pData->m_cItems,C_PERF_PROC_MAX); idx < cnt; idx++)
|
|
{
|
|
if (m_pData->m_dwProcIds[idx] == procId)
|
|
{
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// add only if not already there
|
|
if (!fFound)
|
|
{
|
|
if (idx < C_PERF_PROC_MAX)
|
|
{
|
|
m_pData->m_dwProcIds[idx] = procId;
|
|
m_pData->m_cItems = idx + 1;
|
|
m_pData->m_dwTimestamp = GetTickCount();
|
|
|
|
SetChangeEvent();
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
UnLock(); // unlock mutex
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CPerfMainBlock::Load()
|
|
{
|
|
if (!m_fInited)
|
|
return E_FAIL;
|
|
|
|
if (m_dwTimestamp == m_pData->m_dwTimestamp)
|
|
return S_OK; // already up-to-date
|
|
|
|
// clear out what we have
|
|
while (m_pProcBlock)
|
|
{
|
|
CPerfProcBlock *pNext = m_pProcBlock->m_pNext;
|
|
m_pProcBlock->UnInit();
|
|
delete m_pProcBlock;
|
|
m_pProcBlock = pNext;
|
|
}
|
|
|
|
if (FAILED(Lock())) // lock mutex
|
|
return E_FAIL;
|
|
HRESULT hr = S_OK;
|
|
|
|
// populate new objects for blocks
|
|
for (DWORD i = 0, cnt = min(m_pData->m_cItems,C_PERF_PROC_MAX); i < cnt;)
|
|
{
|
|
CPerfProcBlock *pBlock = new CPerfProcBlock;
|
|
if (!pBlock)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
|
|
hr = pBlock->InitExternal(m_pData->m_dwProcIds[i]);
|
|
if (FAILED(hr))
|
|
{
|
|
delete pBlock;
|
|
hr = S_OK;
|
|
cnt--;
|
|
m_pData->m_cItems--;
|
|
for (DWORD j = i; j < min(m_pData->m_cItems,C_PERF_PROC_MAX); j++) {
|
|
m_pData->m_dwProcIds[j] = m_pData->m_dwProcIds[j+1];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
pBlock->m_pNext = m_pProcBlock;
|
|
m_pProcBlock = pBlock;
|
|
i++;
|
|
}
|
|
|
|
// remember timestamp
|
|
m_dwTimestamp = SUCCEEDED(hr) ? m_pData->m_dwTimestamp : 0;
|
|
|
|
UnLock(); // unlock mutex
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CPerfMainBlock::GetStats
|
|
(
|
|
DWORD *pdwCounters
|
|
)
|
|
{
|
|
if (!m_fInited)
|
|
return E_FAIL;
|
|
|
|
// reload if needed
|
|
if (FAILED(Load()))
|
|
return E_FAIL;
|
|
|
|
// first add in the accumulated stats from the dead procs...
|
|
|
|
for (int i = 0; i < C_PERF_PROC_COUNTERS; i++)
|
|
pdwCounters[i] = m_pData->m_rgdwCounters[i];
|
|
|
|
// gather
|
|
CPerfProcBlock *pBlock = m_pProcBlock;
|
|
while (pBlock) {
|
|
if (pBlock->m_fProcessDead) {
|
|
m_dwTimestamp = 0;
|
|
pBlock = pBlock->m_pNext;
|
|
continue;
|
|
}
|
|
for (int i = 0; i < C_PERF_PROC_COUNTERS; i++)
|
|
pdwCounters[i] += pBlock->m_pData->m_rgdwCounters[i];
|
|
pBlock = pBlock->m_pNext;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline VOID CPerfMainBlock::AggregateDeadProcCounters(CPerfProcBlock *pBlock)
|
|
{
|
|
|
|
DWORD *pOut = m_pData->m_rgdwCounters;
|
|
DWORD *pIn = pBlock->m_pData->m_rgdwCounters;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Aggregating Dead Proc Counters\n"));
|
|
|
|
pOut[ID_DEBUGDOCREQ] += pIn[ID_DEBUGDOCREQ];
|
|
pOut[ID_REQERRRUNTIME] += pIn[ID_REQERRRUNTIME];
|
|
pOut[ID_REQERRPREPROC] += pIn[ID_REQERRPREPROC];
|
|
pOut[ID_REQERRCOMPILE] += pIn[ID_REQERRCOMPILE];
|
|
pOut[ID_REQERRORPERSEC] += pIn[ID_REQERRORPERSEC];
|
|
pOut[ID_REQTOTALBYTEIN] += pIn[ID_REQTOTALBYTEIN];
|
|
pOut[ID_REQTOTALBYTEOUT] += pIn[ID_REQTOTALBYTEOUT];
|
|
pOut[ID_REQCOMFAILED] += pIn[ID_REQCOMFAILED];
|
|
pOut[ID_REQFAILED] += pIn[ID_REQFAILED];
|
|
pOut[ID_REQNOTAUTH] += pIn[ID_REQNOTAUTH];
|
|
pOut[ID_REQREJECTED] += pIn[ID_REQREJECTED];
|
|
pOut[ID_REQSUCCEEDED] += pIn[ID_REQSUCCEEDED];
|
|
pOut[ID_REQTIMEOUT] += pIn[ID_REQTIMEOUT];
|
|
pOut[ID_REQTOTAL] += pIn[ID_REQTOTAL];
|
|
pOut[ID_REQPERSEC] += pIn[ID_REQPERSEC];
|
|
pOut[ID_SESSIONTIMEOUT] += pIn[ID_SESSIONTIMEOUT];
|
|
pOut[ID_TEMPLFLUSHES] += pIn[ID_TEMPLFLUSHES];
|
|
pOut[ID_TRANSABORTED] += pIn[ID_TRANSABORTED];
|
|
pOut[ID_TRANSCOMMIT] += pIn[ID_TRANSCOMMIT];
|
|
pOut[ID_TRANSTOTAL] += pIn[ID_TRANSTOTAL];
|
|
pOut[ID_ENGINEFLUSHES] += pIn[ID_ENGINEFLUSHES];
|
|
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
Class Definition and support structures for the centralized Global Perf
|
|
Counter structures. A single CASPPerfManager object will be declared and
|
|
initialized in WAS. If in new mode, WAS will call the public ProcessDied()
|
|
method to inform when a worker process should no longer be considered alive.
|
|
If not in new mode, WAS does not know about the various ASP host processes
|
|
and so RegisterWaitForSingleObject will be used to monitor the lifetime of
|
|
the ASP host process.
|
|
|
|
When a process is declared dead through one of these mechanism, the ASP
|
|
counters associated with that process are moved into a global table to
|
|
accummulate counter counter perf counter types - e.g. Total ASP Requests.
|
|
|
|
A named event is used by the ASP host process to single that a new
|
|
host process is up.
|
|
|
|
****************************************************************************/
|
|
|
|
typedef struct {
|
|
HANDLE hWaitHandle;
|
|
HANDLE hProcHandle;
|
|
CPerfProcBlock *pBlock;
|
|
CASPPerfManager *pPerfGlobal;
|
|
} sWaitInfo;
|
|
|
|
class CASPPerfManager
|
|
{
|
|
public:
|
|
CASPPerfManager() :
|
|
m_hChangeWaitHandle(NULL),
|
|
m_dwCntProcsDied(0),
|
|
m_fcsProcsDiedInited(0),
|
|
m_fCompatMode(0),
|
|
m_fInited(0)
|
|
{
|
|
ZeroMemory(m_dwProcIdInWaitState, sizeof(m_dwProcIdInWaitState));
|
|
ZeroMemory(m_aWaitInfos, sizeof(m_aWaitInfos));
|
|
}
|
|
|
|
HRESULT Init(BOOL bCompatMode);
|
|
HRESULT UnInit();
|
|
|
|
VOID ProcessDied(DWORD procId);
|
|
|
|
private:
|
|
|
|
CPerfMainBlock m_MainBlock;
|
|
HANDLE m_hChangeWaitHandle;
|
|
DWORD m_fcsProcsDiedInited : 1;
|
|
DWORD m_fCompatMode : 1;
|
|
DWORD m_fInited : 1;
|
|
|
|
// Booleans to track status of Process IDs in above array
|
|
BOOL m_dwProcIdInWaitState[C_PERF_PROC_MAX];
|
|
|
|
// array of structures tracking the WaitInfo data
|
|
sWaitInfo *m_aWaitInfos[C_PERF_PROC_MAX];
|
|
|
|
// array of proc IDs, protected by CritSec, of dead procs
|
|
CRITICAL_SECTION m_csProcsDied;
|
|
DWORD m_dwProcsDied[C_PERF_PROC_MAX];
|
|
DWORD m_dwCntProcsDied;
|
|
|
|
static VOID CALLBACK ChangeEventWaitCallback(PVOID pArg, BOOLEAN fReason);
|
|
|
|
static VOID CALLBACK ProcDiedCallback(PVOID pArg, BOOLEAN fReason);
|
|
|
|
VOID ScanForNewProcIDs();
|
|
|
|
VOID HandleDeadProcIDs();
|
|
|
|
HRESULT RegisterWaitOnProcess(sWaitInfo *pWaitInfo);
|
|
|
|
VOID AddProcDiedToList(DWORD procID);
|
|
|
|
};
|
|
|
|
inline HRESULT CASPPerfManager::Init(BOOL bCompatMode)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Initializing CASPPerfManager\n"));
|
|
|
|
m_fCompatMode = bCompatMode;
|
|
|
|
// initialize the MainBlock. TRUE here indicates that this is
|
|
// a WAS init and the expectation is that the global shared
|
|
// memory is created by this process.
|
|
|
|
hr = m_MainBlock.Init(TRUE);
|
|
|
|
if (FAILED(hr)) {
|
|
DBGPRINTF((DBG_CONTEXT, "Initializing CASPPerfManager FAILED (%x)\n",hr));
|
|
return hr;
|
|
}
|
|
|
|
// Use the RegisterWaitForSingleObject() API to handle the event
|
|
// firing. Relieves us of the burden of managing a thread.
|
|
|
|
if (SUCCEEDED(hr)
|
|
&& RegisterWaitForSingleObject(&m_hChangeWaitHandle,
|
|
m_MainBlock.m_hChangeEvent,
|
|
ChangeEventWaitCallback,
|
|
this,
|
|
INFINITE,
|
|
WT_EXECUTEINIOTHREAD) == FALSE) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
// Initialize the CriticalSection used to added dead proc ids to
|
|
// the deadprocids array
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
ErrInitCriticalSection(&m_csProcsDied, hr);
|
|
if (SUCCEEDED(hr))
|
|
m_fcsProcsDiedInited = TRUE;
|
|
}
|
|
|
|
if (FAILED(hr)) {
|
|
DBGPRINTF((DBG_CONTEXT, "Initializing CASPPerfManager FAILED (%x)\n", hr));
|
|
m_MainBlock.UnInit();
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
m_fInited = TRUE;
|
|
|
|
return hr;
|
|
}
|
|
|
|
inline HRESULT CASPPerfManager::UnInit()
|
|
{
|
|
|
|
m_fInited = FALSE;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Uninitializing CASPPerfManager\n"));
|
|
|
|
// unregister the ChangeEvent wait, if we pass INVALID_HANDLE_VALUE
|
|
// the routine will block until all callbacks have completed before
|
|
// returning
|
|
// Note: We intentionally don't check the return value for the
|
|
// UnregisterWaitEx, because there is nothing we can do if it fails
|
|
if (m_hChangeWaitHandle != NULL) {
|
|
UnregisterWaitEx(m_hChangeWaitHandle, INVALID_HANDLE_VALUE);
|
|
}
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Unregistered ChangeWait\n"));
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "WaitForSingleObject on unregister completed\n"));
|
|
|
|
|
|
// clean up the WaitInfo array
|
|
for (DWORD i=0; m_aWaitInfos[i]; i++) {
|
|
|
|
//
|
|
// UnregisterWaitEx will wait for all callback routines to complete
|
|
// before returning if the INVALID_HANDLE_VALUE is passed in.
|
|
// Note: We intentionally don't check the return value for the
|
|
// UnregisterWaitEx, because there is nothing we can do if it fails
|
|
UnregisterWaitEx(m_aWaitInfos[i]->hWaitHandle, INVALID_HANDLE_VALUE);
|
|
|
|
CloseHandle(m_aWaitInfos[i]->hProcHandle);
|
|
|
|
delete m_aWaitInfos[i];
|
|
}
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Cleaned up WaitInfos\n"));
|
|
|
|
// if successfully created the ProcsDiedCS, clean it up
|
|
if (m_fcsProcsDiedInited == TRUE)
|
|
DeleteCriticalSection(&m_csProcsDied);
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Calling m_MainBlock.UnInit\n"));
|
|
|
|
return m_MainBlock.UnInit();
|
|
}
|
|
|
|
|
|
inline VOID CALLBACK CASPPerfManager::ChangeEventWaitCallback(PVOID pArg, BOOLEAN fReason)
|
|
{
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "ChangeEventWaitCallback called\n"));
|
|
|
|
CASPPerfManager *pPerfGlob = (CASPPerfManager *)pArg;
|
|
|
|
// when the ChangeEvent fires, check for new ProcIDs in the global
|
|
// array and then check for DeadProcIDs.
|
|
|
|
pPerfGlob->ScanForNewProcIDs();
|
|
|
|
pPerfGlob->HandleDeadProcIDs();
|
|
}
|
|
|
|
inline VOID CASPPerfManager::ScanForNewProcIDs()
|
|
{
|
|
|
|
HRESULT hr;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Scanning for New Proc IDS\n"));
|
|
|
|
// We'll need to hold the lock the entire time we're
|
|
// looking thru the list
|
|
|
|
m_MainBlock.Lock();
|
|
|
|
// start from the back looking for entries that haven't had
|
|
// their WaitState set. Again note that there is always the
|
|
// key assumption that the three arrays, m_dwProcIdInWaitState
|
|
// m_aWaitInfo and the m_dwProcIDs array in the global array
|
|
// track each other with respect to position in array.
|
|
|
|
for (LONG i = m_MainBlock.m_pData->m_cItems - 1; i >= 0; i--) {
|
|
|
|
// as soon as we hit one that is in the Wait state,
|
|
// we're done.
|
|
|
|
if (m_dwProcIdInWaitState[i] == TRUE) {
|
|
// DBGPRINTF((DBG_CONTEXT, "Done Scanning for New Proc IDS\n"));
|
|
break;
|
|
}
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Found a new Proc ID at idx - %d\n", i));
|
|
|
|
// found one that is not waiting. Build up the necessary
|
|
// structures and objects
|
|
|
|
// we'll need another CPerfProcBlock for the list
|
|
|
|
CPerfProcBlock *pBlock = new CPerfProcBlock;
|
|
|
|
if (!pBlock) {
|
|
break;
|
|
}
|
|
|
|
// we'll also need a new WaitInfo, if in backwards
|
|
// compat mode. Remember, in backwards compat mode,
|
|
// this object will do all the register for waits, but
|
|
// in new mode, WAS will tell us when a process has died.
|
|
|
|
sWaitInfo *pWaitInfo = NULL;
|
|
|
|
if (m_fCompatMode) {
|
|
|
|
pWaitInfo = new sWaitInfo;
|
|
|
|
if (!pWaitInfo) {
|
|
delete pBlock;
|
|
break;
|
|
}
|
|
|
|
pWaitInfo->pPerfGlobal = this;
|
|
pWaitInfo->pBlock = pBlock;
|
|
}
|
|
|
|
// call InitExternal to hook up to the ASP host processes
|
|
// shared memory. This is where we will get that needed handle
|
|
// to the shared memory and will allow us to access the shared
|
|
// memory even after the process itself has terminated.
|
|
|
|
if (FAILED(hr = pBlock->InitExternal(m_MainBlock.m_pData->m_dwProcIds[i]))) {
|
|
// DBGPRINTF((DBG_CONTEXT, "InitExternal failed in ScanForNewProcIDS (%x)\n", hr));
|
|
delete pWaitInfo;
|
|
delete pBlock;
|
|
continue;
|
|
}
|
|
|
|
// Register the Wait if in compatmode
|
|
if (m_fCompatMode && FAILED(hr = RegisterWaitOnProcess(pWaitInfo))) {
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "RegisterWait failed in ScanForNewProcIDs (%x)\n", hr));
|
|
pBlock->UnInit();
|
|
delete pWaitInfo;
|
|
delete pBlock;
|
|
continue;
|
|
}
|
|
|
|
// Since it's a single linked list, just put the new block
|
|
// at the head of the list.
|
|
|
|
pBlock->m_pNext = m_MainBlock.m_pProcBlock;
|
|
m_MainBlock.m_pProcBlock = pBlock;
|
|
|
|
// note that this procID is now in a wait state
|
|
|
|
m_dwProcIdInWaitState[i] = TRUE;
|
|
|
|
// Add the WaitInfo to the array.
|
|
|
|
m_aWaitInfos[i] = pWaitInfo;
|
|
}
|
|
|
|
m_MainBlock.UnLock();
|
|
|
|
return;
|
|
}
|
|
|
|
inline HRESULT CASPPerfManager::RegisterWaitOnProcess(sWaitInfo *pWaitInfo)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// get a handle to the process to wait on
|
|
|
|
pWaitInfo->hProcHandle = OpenProcess(SYNCHRONIZE,
|
|
FALSE,
|
|
pWaitInfo->pBlock->m_pData->m_dwProcId);
|
|
|
|
if (!pWaitInfo->hProcHandle) {
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
}
|
|
else {
|
|
|
|
// register the wait.
|
|
|
|
if (!RegisterWaitForSingleObject(&pWaitInfo->hWaitHandle, // wait handle
|
|
pWaitInfo->hProcHandle, // handle to object
|
|
CASPPerfManager::ProcDiedCallback, // timer callback function
|
|
pWaitInfo, // callback function parameter
|
|
INFINITE, // time-out interval
|
|
WT_EXECUTEONLYONCE)) { // options
|
|
|
|
CloseHandle(pWaitInfo->hProcHandle);
|
|
pWaitInfo->hProcHandle = NULL;
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
inline VOID CALLBACK CASPPerfManager::ProcDiedCallback(PVOID pArg, BOOLEAN fReason)
|
|
{
|
|
sWaitInfo *pWaitInfo = (sWaitInfo *)pArg;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "ProcDiedCallback enterred\n"));
|
|
|
|
|
|
// The callback will simply call the public ProcessDied method on the
|
|
// PerfGlobal object. This is for simplicity. There is no reason why the
|
|
// process couldn't be cleaned up on this thread.
|
|
|
|
pWaitInfo->pPerfGlobal->ProcessDied(pWaitInfo->pBlock->m_pData->m_dwProcId);
|
|
}
|
|
|
|
inline VOID CASPPerfManager::ProcessDied(DWORD procID)
|
|
{
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "CASPPerfManager::ProcessDied enterred for %d\n", procID));
|
|
|
|
if (m_fInited == FALSE)
|
|
return;
|
|
|
|
// Add the ProcID to the list of dead proc IDS and wakeup the
|
|
// change callback
|
|
|
|
AddProcDiedToList(procID);
|
|
|
|
m_MainBlock.SetChangeEvent();
|
|
|
|
return;
|
|
}
|
|
|
|
inline VOID CASPPerfManager::AddProcDiedToList(DWORD procID)
|
|
{
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Adding Process (%d) to proc died list\n", procID));
|
|
|
|
// take the critical section, add the process to the list
|
|
// and leave the critical section.
|
|
|
|
EnterCriticalSection(&m_csProcsDied);
|
|
|
|
m_dwProcsDied[m_dwCntProcsDied++] = procID;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "New count of ProcsDied list is %d\n",m_dwCntProcsDied));
|
|
|
|
LeaveCriticalSection(&m_csProcsDied);
|
|
}
|
|
|
|
inline VOID CASPPerfManager::HandleDeadProcIDs()
|
|
{
|
|
DWORD procID;
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "HandleDeadProcIDs Enterred\n"));
|
|
|
|
sWaitInfo *pWaitInfo = NULL;
|
|
CPerfProcBlock *pLast = NULL;
|
|
CPerfProcBlock *pBlock = NULL;
|
|
|
|
// Ok, this is the critical routine. It's here where
|
|
// we will handle the dead processes. Cleanup will occur on
|
|
// the various structures around this process as well as the
|
|
// aggregation of it's counters into the global shared memory.
|
|
|
|
// enter the critsec to check for dead procs to process
|
|
|
|
EnterCriticalSection(&m_csProcsDied);
|
|
|
|
// Enter a while loop to process all of the dead procs. Will also
|
|
// bail if we were uninited.
|
|
|
|
while(m_dwCntProcsDied && m_fInited) {
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "current m_dwCntProcsDies is %d\n", m_dwCntProcsDied));
|
|
|
|
// get a proc id from the list. Note that we start at
|
|
// the back so that we can release the critical section.
|
|
// The alternative would be to take it off the front, and
|
|
// then move all the remaining items forward. This seems
|
|
// unnecessary. There should be no issue with LIFO
|
|
// processing that I can see.
|
|
|
|
procID = m_dwProcsDied[--m_dwCntProcsDied];
|
|
|
|
// we can safely leave the critsec now that we've popped
|
|
// an entry of the end of the list
|
|
|
|
LeaveCriticalSection(&m_csProcsDied);
|
|
|
|
// now that we have the entry, we need to find it's position
|
|
// in the MainBlock. Need to hold the lock to do so.
|
|
|
|
m_MainBlock.Lock();
|
|
|
|
int iFound = -1;
|
|
DWORD idx, cnt;
|
|
|
|
// the search begins in the main shared array of procIDs
|
|
|
|
for (idx = 0, cnt = min(m_MainBlock.m_pData->m_cItems,C_PERF_PROC_MAX); idx < cnt; idx++) {
|
|
|
|
// break if we found it
|
|
|
|
if (m_MainBlock.m_pData->m_dwProcIds[idx] == procID) {
|
|
iFound = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we didn't find it, oh well, move to the next item.
|
|
|
|
if (iFound == -1) {
|
|
// DBGPRINTF((DBG_CONTEXT, "Didn't find DeadProcID (%d) in global array\n", procID));
|
|
goto NextItem;
|
|
}
|
|
|
|
pWaitInfo = m_aWaitInfos[iFound];
|
|
|
|
// DBGPRINTF((DBG_CONTEXT, "Found DeadProcID (%d) in global array at idx\n", procID,iFound));
|
|
|
|
m_aWaitInfos[iFound] = NULL;
|
|
m_dwProcIdInWaitState[iFound] = FALSE;
|
|
|
|
// This for loop will compact the various arrays to effective remove
|
|
// this entry from the arrays. I could care about not moving the aWaitInfo
|
|
// when not in compat mode, but it doesn't seem like a big deal.
|
|
|
|
for (idx = iFound, cnt = min(m_MainBlock.m_pData->m_cItems,C_PERF_PROC_MAX)-1; idx < cnt; idx++) {
|
|
m_MainBlock.m_pData->m_dwProcIds[idx] = m_MainBlock.m_pData->m_dwProcIds[idx+1];
|
|
m_aWaitInfos[idx] = m_aWaitInfos[idx+1];
|
|
m_dwProcIdInWaitState[idx] = m_dwProcIdInWaitState[idx+1];
|
|
}
|
|
|
|
// Reset the last value of the list to NULL / FALSE to make sure they
|
|
// all are initialzied correctly when we add the next.
|
|
m_aWaitInfos[m_MainBlock.m_pData->m_cItems-1] = NULL;
|
|
m_dwProcIdInWaitState[m_MainBlock.m_pData->m_cItems-1] = FALSE;
|
|
|
|
// note that there is one less item and that the global array has changed.
|
|
// changing the timestamp will notify ASPPERF.DLL to reload its perfprocblocks
|
|
|
|
m_MainBlock.m_pData->m_cItems--;
|
|
m_MainBlock.m_pData->m_dwTimestamp = GetTickCount();
|
|
|
|
// Now we have to find the PerfProcBlock in the single linked list of
|
|
// PerfBlocks. There is an obvious optimization to make the PerfProcBlocks
|
|
// double linked lists to avoid the scan. Skipping this for now in favor
|
|
// of simplicity.
|
|
|
|
pLast = NULL;
|
|
pBlock = m_MainBlock.m_pProcBlock;
|
|
|
|
// search for the block, maintaining pBlock and pLast variables to allow
|
|
// for the removal of the block.
|
|
|
|
while (pBlock && (pBlock->m_pData->m_dwProcId != procID)) {
|
|
pLast = pBlock;
|
|
pBlock = pBlock->m_pNext;
|
|
}
|
|
|
|
// if we didn't find it, we'll move on, but Assert.
|
|
|
|
if (!pBlock) {
|
|
// DBGPRINTF((DBG_CONTEXT, "Didn't find pBlock (%d) in list\n", procID));
|
|
goto NextItem;
|
|
}
|
|
|
|
// now do the removal. Two cases to handle. 1) the block was the first
|
|
// in the list or 2) it was in the middle. If not the first, set the previous
|
|
// next to the removed block's next, else set the head list in the mainblock
|
|
// to point to the removed block's next.
|
|
|
|
if (pLast)
|
|
pLast->m_pNext = pBlock->m_pNext;
|
|
else
|
|
m_MainBlock.m_pProcBlock = pBlock->m_pNext;
|
|
|
|
// we'll only have wait info in compat mode, which means that this
|
|
// pointer could be NULL.
|
|
|
|
if (pWaitInfo) {
|
|
|
|
UnregisterWait(pWaitInfo->hWaitHandle);
|
|
CloseHandle(pWaitInfo->hProcHandle);
|
|
|
|
delete pWaitInfo;
|
|
}
|
|
|
|
// ahh.... the moment we've all been waiting for - actually saving
|
|
// the accumulated counters!!!
|
|
|
|
m_MainBlock.AggregateDeadProcCounters(pBlock);
|
|
|
|
// UnInit() the block, which will release our handle on the shared
|
|
// memory, and delete it
|
|
|
|
pBlock->UnInit();
|
|
|
|
delete pBlock;
|
|
|
|
NextItem:
|
|
|
|
// get ready for the next item
|
|
|
|
m_MainBlock.UnLock();
|
|
|
|
EnterCriticalSection(&m_csProcsDied);
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csProcsDied);
|
|
|
|
// if we're no longer inited, then set m_dwCntProcsDied to 0
|
|
// to signal to the UnInit routine that we're done processing.
|
|
|
|
if (m_fInited == FALSE)
|
|
m_dwCntProcsDied = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
#endif // _ASP_PERFDEF_H
|