System Monitor is a tool in Windows® 95 that graphically displays a number of system performance statistics. This document describes how other drivers can report data to the System Monitor.
Any VxD can report statistics which will be displayed in the System Monitor user interface. To do so, the VxD must do the following:
1. Register itself as a stat server by calling the PERF_Server_Register (usually done at VxD load time)
2. Register one or more statistics by calling PERF_Server_Add_Stat for each statistic (usually done immediately after calling PERF_Server_Register)
3. Unregister the stat server when the VxD unloads
Both static and dynamic VxDs may use the service; stat servers and statistics may be added and removed at any time.
Statistics are obtained in one of two ways, depending on information passed in the PERF_Server_Add_Stat call. Normally the pst0_pStatFunc contains a ring 0 pointer to a DWORD that contains the data to be displayed. The PERF VxD simply dereferences the pointer and retrieves the data whenever it is requested by the UI client. This places the lowest overhead on the stat server VxD and on the system. If the PSTF_FUNCPTR flag is set, however, the pst0_pStatFunc member should be a ring 0 pointer to a function with no parameters that returns the data value in EAX. This is useful if data is expensive to calculate; then the data is only calculated when requested.
Another useful flag in stat creation is PSTF_RATE, which causes the client to automatically differentiate the reported value by time. For instance, if a driver wishes to report throughput in bytes/sec, it can keep a counter of the bytes it processes in a DWORD and increment the DWORD whenever it processes data. If the PSTF_RATE flag is set then UI clients will divide by elapsed time to arrive at bytes/sec. This is also useful because it allows multiple clients to poll for data at different times and always get correct data.
The perf.h header file and example code example.asm (both attached below) illustrate the details of how to implement this.
/********************************************************************* * (C) Copyright MICROSOFT Corp., 1993-1995 * Title: PERF.H - Include file for perf monitor * Version: 1.00 * Date: * Author: FCF *---------------------------------------------------------------------- * Change log: * DATE REV DESCRIPTION * ----------- --- --------------------------------------------------- */ /* defines */ #define MAXNAMELEN 50 /* maximum number of characters in service name, stat name, or key names */ #define MAXCOMPLEXSUBSTAT 8 /* maximum number of stats making up a complex stat */ /* structures and flags used for the ring-0 interface */ struct perf_server_0 { unsigned long psrv0_Level; /* Must be zero for this level */ unsigned long psrv0_Flags; char *psrv0_pszServerName; char *psrv0_pszServerNodeName; void *psrv0_pControlFunc; }; struct perf_stat_0 { unsigned long pst0_Level; /* Must be zero for this level */ unsigned long pst0_Flags; char *pst0_pszStatName; char *pst0_pszStatNodeName; char *pst0_reserved; // must be NULL char *pst0_pszStatDescription; void *pst0_pStatFunc; unsigned long pst0_ScaleFactor; // set PSTF_FACTOR_xxx flag to use }; /* Values for psrv0_Flags follow */ /* Values for pst0_Flags follow */ /* pst0_pStatFunc points either directly to data (always a DWORD for now) */ /* or, if PSTF_FUNCPTR_BIT is set, to a _cdecl function. This function */ /* accepts a stat handle as it's argument and returns the stat in eax */ #define PSTF_FUNCPTR 0x00000001
The data referenced by this stat is always a counter, e.g. number of bytes read. It is up to the client to differentiate this into a rate. If PSTF_RATE is set, then the text associated with this stat assumes that the stat will be differentiated with respect to time. It's possible that two stats will refer to the same data - one with this bit set and one without, with help text appropriate for each.
#define PSTF_COUNT 0x00000000 #define PSTF_RATE 0x00000002 /* If either of these flags are set, then the output will be scaled (either multiplied or divided) by pst0_ScaleFactor. */ #define PSTF_FACTOR_MULTIPLY 0x00000040 #define PSTF_FACTOR_DIVIDE 0x00000080 /* XLATOFF */ unsigned long PERF_Server_Register( struct perf_server_0 * ); void PERF_Server_Deregister( unsigned long hReg ); unsigned long PERF_Server_Add_Stat( unsigned long hReg, struct perf_stat_0 ); void PERF_Server_Remove_Stat( unsigned long hStat ); /* XLATON */
Control messages sent to perf server's control function. The control function is optional, set it to NULL if you don't want any control messages. Perf servers are free to ignore any messages they want. Control functions take two DWORD parameters; a message (dwMsg) and a DWORD of message-dependent data (dwData).
The following defines are values for dwMsg:
#define PMSG_START_STAT 0x11 #define PMSG_STOP_STAT 0x12
PMSG_START_STAT: Notifies that a perf client is going to start watching this stat. dwData contains the stat handle.
PMSG_STOP_STAT: Notifies that a perf client is no longer watching this stat. dwData contains the stat handle. Stats which are expensive to maintain should only be kept while some- one is watching them. Note that there can be more than one stat client, so don't just stop keeping track of a stat if you receive a PMSG_STOP_STAT. The server should keep a counter of the number of PMSG_START_STAT's it receives for a particular counter, decrement it for each PMSG_STOP_STAT and stop keeping track of the stat when thecounter reaches zero. Most stats are trivial to maintain and just involve incrementing a counter. For stats like these, perf servers should always increment the counter and ignore messages to start and stop.
/* ASM ; Ring-0 macros to aid stat registration Reg_Perf_Srv MACRO level:REQ, flags:REQ, servername:REQ, nodename:REQ, controlfunc:REQ local nothere VxDcall PERF_Get_Version or eax, eax jz nothere IF (OPATTR(controlfunc)) AND 00010000y ;; register push controlfunc ELSE push OFFSET32 controlfunc ENDIF IF (OPATTR(nodename)) AND 00010000y ;; register push nodename ELSE push OFFSET32 nodename ENDIF IF (OPATTR(servername)) AND 00010000y ;; register push servername ELSE push OFFSET32 servername ENDIF push flags push level push esp VxDcall PERF_Server_Register add esp, 6*4 nothere: ENDM Reg_Perf_Stat MACRO srvhandle:REQ, level:REQ, flags:REQ, name:REQ, nodename:REQ, reserved:REQ, desc:REQ, func:REQ IF (OPATTR(func)) AND 00010000y ;; register push func ELSE push OFFSET32 func ENDIF IF (OPATTR(desc)) AND 00010000y ;; register push desc ELSE push OFFSET32 desc ENDIF IF (OPATTR(reserved)) AND 00010000y ;; register push reserved ELSE push OFFSET32 reserved ENDIF IF (OPATTR(nodename)) AND 00010000y ;; register push nodename ELSE push OFFSET32 nodename ENDIF IF (OPATTR(name)) AND 00010000y ;; register push name ELSE push OFFSET32 name ENDIF push flags push level push esp push srvhandle VxDcall PERF_Server_Add_Stat add esp, 9*4 ENDM Begin_Service_Table PERF PERF_Service PERF_Get_Version, LOCAL PERF_Service PERF_Server_Register, LOCAL PERF_Service PERF_Server_Deregister, LOCAL PERF_Service PERF_Server_Add_Stat, LOCAL PERF_Service PERF_Server_Remove_Stat, LOCAL End_Service_Table PERF */ #define HKEY_PERF_ROOT HKEY_LOCAL_MACHINE #define PERF_REG_KEY "System\\CurrentControlSet\\Control\\PerfStats\\Enum" #define PERF_REG_OTHER_KEY "System\\CurrentControlSet\\Control\\PerfStats\\Other" #define PERF_REG_NAME_SRV_NAME "Name" #define PERF_REG_NAME_VXD_NAME "VxDName" #define PERF_REG_NAME_STAT_NAME "Name" #define PERF_REG_NAME_STAT_COMPLEX "Complex" #define PERF_REG_NAME_STAT_DESC "Description" #define PERF_REG_NAME_STAT_DIFF "Differentiate" #define PERF_REG_VAL_STAT_TRUE "TRUE" #define PERF_REG_VAL_STAT_FALSE "FALSE" #define PERF_STAT_PREFIX "STAT" /* complex stat defines */ #define PSTF_COMPLEX 0x00000100
PSTF_COMPLEX specifies that this is a complex stat. Complex stats have no data of their own, they specify a list of handles to other stats. The values of the other stats in the list are retrieved and added together and the resulting sum displayed. The pst0_pStatFunc member points to an array of stat handles, where the handle values were obtained by previous calls to PERF_Server_Add_Stat. The array is terminated by a NULL handle.
PAGE 58,132 ;*************************************************************** TITLE Example.Asm - VxD performance gathering component ;*************************************************************** .386p ;*************************************************************** ; I N C L U D E S ;*************************************************************** .XLIST INCLUDE VMM.Inc INCLUDE Debug.Inc INCLUDE OptTest.Inc Create_FakeStat_Service_Table EQU VMM_TRUE INCLUDE Perf.Inc INCLUDE VWin32.Inc INCLUDE WinError.Inc .LIST ;********************************************************************** ; V I R T U A L D E V I C E D E C L A R A T I O N ;********************************************************************** Begin_Service_Table FAKESTAT FAKESTAT_Service FAKESTAT_Get_Version, LOCAL End_Service_Table FAKESTAT FakeStat_Device_ID EQU 44h FakeStat_Init_Order EQU 06500000h Declare_Virtual_Device FAKESTAT, 4, 00h, FakeStat_Control, FakeStat_Device_ID, \ FakeStat_Init_Order, , ;********************************************************************** ; E Q U A T E S ;********************************************************************** ;********************************************************************** ;********************************************************************** VxD_IDATA_SEG ; NOTE: Strings are not int'l correct for brevity of this example. ; Windows 95 VxDs should use message tables and message macros ; for strings. ; UI names szSrvName db "Fake Performance Data",0 szStatNameRead db "Fake Reads/sec",0 szStatNameWrite db "Fake Writes/sec",0 szStatNameTotal db "Total Fake Stuff/sec",0 ; stat explanations (optional) szReadDesc db "Fake stuff read per second.",0 szWriteDesc db "Fake stuff written per second.",0 szTotalDesc db "Fake stuff read and written per second.",0 ; registry key names szStatSrvKeyname db "FAKE",0 szStatKeynameRead db "FReadSec",0 szStatKeynameWrite db "FWriteSec",0 szStatKeynameTotal db "FTotalSec",0 VxD_IDATA_ENDS VxD_PAGEABLE_DATA_SEG ALIGN 4 hPerfID DWORD 0 ; perf server handle dwReadCounter DWORD 1024 ; perf stat counters dwWriteCounter DWORD 4096 VxD_PAGEABLE_DATA_ENDS VxD_LOCKED_DATA_SEG VxD_LOCKED_DATA_ENDS ;********************************************************************** .SALL PAGE ;********************************************************************** ; R E A L M O D E I N I T C O D E ;********************************************************************** VxD_REAL_INIT_SEG BeginProc FakeStat_Real_Init xor si, si test bx, Duplicate_From_INT2F OR Duplicate_Device_ID jnz SHORT RI_Abort_Load xor bx, bx xor edx, edx mov ax, Device_Load_Ok ret RI_Abort_Load: xor bx, bx mov ax, Abort_Device_Load + No_Fail_Message ret EndProc FakeStat_Real_Init VxD_REAL_INIT_ENDS PAGE ;********************************************************************** ; P R O T E C T E D M O D E I N I T C O D E ;********************************************************************** VxD_ICODE_SEG BeginProc FAKESTAT_Sys_Critical_Init clc ret EndProc FAKESTAT_Sys_Critical_Init BeginProc FAKESTAT_Device_Init LocalVar hStatNull, DWORD LocalVar hStat2, DWORD LocalVar hStat1, DWORD EnterProc ifdef DEBUG Trace_Out "FAKESTAT: Loading" endif ; register us as a perf client Reg_Perf_Srv 0,PSTF_RATE,szSrvName,szStatSrvKeyname,FakeStat_StatControl or eax,eax jz fdi10 mov hPerfId,eax ; register 1st stat (reads/sec) Reg_Perf_Stat eax,0,PSTF_RATE,szStatNameRead,szStatKeynameRead,0,szReadDesc,dwReadCounter mov [hStat1],eax ; register 2nd stat (writes/sec) mov eax,hPerfID Reg_Perf_Stat eax,0,PSTF_RATE,szStatNameWrite,szStatKeynameWrite,0,szWriteDesc,dwWriteCounter mov [hStat2],eax ; register 3rd stat (total/sec) ; this is a complex stat made up of reads/sec + writes/sec-- ; specified by list of stats (hStat1 & hStat2) mov eax,hPerfID mov [hStatNull],0 ; null-terminate list of stats lea edx,hStat1 Reg_Perf_Stat eax,0,PSTF_RATE | PSTF_COMPLEX,szStatNameTotal,szStatKeynameTotal,0,szTotalDesc,edx fdi10: clc LeaveProc Return EndProc FAKESTAT_Device_Init VxD_ICODE_ENDS PAGE ;********************************************************************** ; P R O T E C T E D M O D E R E S I D E N T C O D E ;********************************************************************** VxD_LOCKED_CODE_SEG BeginProc FakeStat_Control Control_Dispatch Sys_Critical_Init, FAKESTAT_Sys_Critical_Init Control_Dispatch Device_Init, FAKESTAT_Device_Init Control_Dispatch Sys_Dynamic_Device_Init, FAKESTAT_Device_Init Control_Dispatch Sys_Dynamic_Device_Exit, FAKESTAT_System_Exit Control_Dispatch System_Exit, FAKESTAT_System_Exit IFDEF DEBUG Control_Dispatch Debug_Query, FAKESTAT_Debug_Query ENDIF clc ret EndProc FakeStat_Control VxD_LOCKED_CODE_ENDS VxD_PAGEABLE_CODE_SEG ;********************************************************************** ; S E R V I C E S ;********************************************************************** BeginProc FAKESTAT_System_Exit ; De-register perf client (this will also de-register stats) ifdef DEBUG Trace_Out "FAKESTAT: Unloading" endif ; make sure perf.386 is still there, if we're loaded dynamically and ; "stuck" (caller died or failed to unload us) we can unload after ; perf VxDcall PERF_Get_Version or eax, eax jz fse40 mov eax,hPerfID cmp eax,0 je fse40 push eax VxDcall PERF_Server_Deregister add esp,4 fse40: clc ret EndProc FAKESTAT_System_Exit BeginProc FAKESTAT_Get_Version, Service mov eax, 400h ; Version 4.0 clc ret EndProc FAKESTAT_Get_Version
Control function for our perf server. Receives stat turn-on and turn-off notification messages when client is interested in looking at stat. If it's cheap and easy to just always update the stat, you can ignore these messages or not register a control function. For stats that are expensive to update, it's best to only update them while someone is looking at them. Since there can be more than one perf client, if you process control functions you should keep a counter for each stat and increment it with every PMSG_START_STAT and decrement it for each PMSG_STOP_STAT that that stat receives (and stop updating the stat when the client count reaches zero).
BeginProc FakeStat_StatControl ArgVar dwMsg, DWORD ; the control message (PMSG_xxx) ArgVar dwData, DWORD ; stat handle EnterProc cmp dwMsg,PMSG_START_STAT jnz fsc10 Trace_Out "FAKESTAT: Got stat start msg" ; do stat-starting stuff here jmp fsc20 fsc10: ; PMSG_STOP_STAT Trace_Out "FAKESTAT: Got stat stop msg" ; do stat-stopping stuff here fsc20: LeaveProc Return EndProc FakeStat_StatControl VxD_PAGEABLE_CODE_ENDS PAGE ;*************************************************************** ; D E B U G G I N G C O D E ;*************************************************************** IFDEF DEBUG VxD_LOCKED_CODE_SEG ;*************************************************************** ; ; FakeStat_Debug_Query ; ; DESCRIPTION: ; ; ENTRY: ; ; EXIT: ; ; USES: ; ;======================================================================= BeginProc FakeStat_Debug_Query PF_DQ_Exit: clc ret EndProc FakeStat_Debug_Query VxD_LOCKED_CODE_ENDS ENDIF END FakeStat_Real_Init