I took Phoenix's CpuLoad_pdh code and modified it for 1-4 processors/cores. Only tested on 1 and 2 processors/cores. Could easily be extended to more.
.686
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDE \masm32\include\msvcrt.inc
INCLUDELIB \masm32\lib\msvcrt.lib
INCLUDE \masm32\include\masm32.inc
INCLUDELIB \masm32\lib\masm32.lib
INCLUDE \masm32\include\pdh.inc
INCLUDELIB \masm32\lib\pdh.lib
INCLUDE \masm32\macros\macros.asm
; Prototypes -------------------------------------------
main PROTO
InitPdhCounter PROTO :DWORD
CpuCores PROTO
; Constants --------------------------------------------
PDH_MAX_COUNTER_PATH EQU 0800h
PDH_FMT_LONG EQU 0100h
PDH_CSTATUS_VALID_DATA EQU 0h ; The data for the counter was returned successfully, but is unchanged from the last time the counter was read.
PDH_CSTATUS_NEW_DATA EQU 1h ; The data for the counter was returned successfully and is different from the last time the counter was read.
CPU_0 EQU 0
CPU_1 EQU 1
CPU_2 EQU 2
CPU_3 EQU 3
CPU_TOTAL EQU 4
; Structures -------------------------------------------
CPULoad STRUCT
hQuery HANDLE ?
hCounter HANDLE ?
uElapse DWORD ?
TimerID DWORD ?
dwValue DWORD ?
CPULoad ENDS
PDH_COUNTER_PATH_ELEMENTS STRUCT
szMachineName DWORD ? ; optional - Pointer to a null-terminated string that specifies the computer name.
szObjectName DWORD ? ; required - Pointer to a null-terminated string that specifies the object name.
szInstanceName DWORD ? ; optional - Pointer to a null-terminated string that specifies the instance name.
szParentInstance DWORD ? ; optional - Pointer to a null-terminated string that specifies the parent instance name.
dwInstanceIndex DWORD ? ; optional - Index of duplicate instance names.
szCounterName DWORD ? ; required - to a null-terminated string that specifies the counter name.
szFillBytes BYTE PDH_MAX_COUNTER_PATH DUP(?)
PDH_COUNTER_PATH_ELEMENTS ENDS
PDH_FMT_COUNTERVALUE STRUCT
CStatus DWORD ?
unionPadding DWORD ?
longValue DWORD ?
extraBytes DWORD ?
PDH_FMT_COUNTERVALUE ENDS
;-------------------------------------------------------
PCPULOAD TYPEDEF PTR CPULoad
.DATA
cpl_0 CPULoad <0, 0, 0, 0, 0>
cpl_1 CPULoad <0, 0, 0, 0, 0>
cpl_2 CPULoad <0, 0, 0, 0, 0>
cpl_3 CPULoad <0, 0, 0, 0, 0>
cpl_Total CPULoad <0, 0, 0, 0, 0>
pCPULoad PCPULOAD cpl_0, cpl_1, cpl_2, cpl_3, cpl_Total, 0
ALIGN 4
szCPU_0 BYTE "0", 7 DUP(0) ; PDH instance name, first processor/core
szCPU_1 BYTE "1", 7 DUP(0) ; PDH instance name, second processor/core
szCPU_2 BYTE "2", 7 DUP(0) ; PDH instance name, third processor/core
szCPU_3 BYTE "3", 7 DUP(0) ; PDH instance name, fourth processor/core
szCPU_Total BYTE "_Total", 2 DUP(0) ; PDH instance name, average of processors/cores
pszInstName PBYTE szCPU_0, szCPU_1, szCPU_2, szCPU_3, szCPU_Total, 0
dwOffset DWORD 0
dwCores DWORD 0
dwFirstCollection DWORD TRUE
szHeading_0 BYTE "0",9,"_Total",13,10,0
szHeading_1 BYTE "0",9,"1",9,"_Total",13,10,0
szHeading_2 BYTE "0",9,"1",9,"2",9,"_Total",13,10,0
szHeading_3 BYTE "0",9,"1",9,"2",9,"3",9,"_Total",13,10,0
szCrLf BYTE 13,10,0
.CODE
start:
INVOKE main
INVOKE ExitProcess, 0
;-------------------------------------------------------
main PROC
LOCAL pfc_0:PDH_FMT_COUNTERVALUE
LOCAL pfc_1:PDH_FMT_COUNTERVALUE
LOCAL pfc_2:PDH_FMT_COUNTERVALUE
LOCAL pfc_3:PDH_FMT_COUNTERVALUE
LOCAL pfc_Total:PDH_FMT_COUNTERVALUE
LOCAL pcpl_0:PTR CPULoad
LOCAL pcpl_1:PTR CPULoad
LOCAL pcpl_2:PTR CPULoad
LOCAL pcpl_3:PTR CPULoad
LOCAL pcpl_Total:PTR CPULoad
INVOKE CpuCores
mov dwCores, eax
.IF dwCores > 0
; ** CPU 0 - Initialize
INVOKE InitPdhCounter, CPU_0
mov pcpl_0, eax
.ENDIF
.IF dwCores > 1
; ** CPU 1 - Initialize
INVOKE InitPdhCounter, CPU_1
mov pcpl_1, eax
.ENDIF
.IF dwCores > 2
; ** CPU 2 - Initialize
INVOKE InitPdhCounter, CPU_2
mov pcpl_2, eax
.ENDIF
.IF dwCores > 3
; ** CPU 3 - Initialize
INVOKE InitPdhCounter, CPU_3
mov pcpl_3, eax
.ENDIF
; ** _Total - Initialize
INVOKE InitPdhCounter, CPU_TOTAL
mov pcpl_Total, eax
; -- Print the heading
.IF dwCores == 1
INVOKE StdOut, ADDR szHeading_0
.ELSEIF dwCores == 2
INVOKE StdOut, ADDR szHeading_1
.ELSEIF dwCores == 3
INVOKE StdOut, ADDR szHeading_2
.ELSEIF dwCores == 4
INVOKE StdOut, ADDR szHeading_3
.ENDIF
@@:
; Always Sleep at least 1 second between collections
INVOKE Sleep, 1000
.IF dwCores > 0
;** CPU 0 - Read new data
mov edx, pcpl_0
INVOKE PdhCollectQueryData, (CPULoad PTR [edx]).hQuery
mov edx, pcpl_0
INVOKE PdhGetFormattedCounterValue, (CPULoad PTR [edx]).hCounter, PDH_FMT_LONG, NULL, ADDR pfc_0
.ENDIF
.IF dwCores > 1
;** CPU 1 - Read new data
mov edx, pcpl_1
INVOKE PdhCollectQueryData, (CPULoad PTR [edx]).hQuery
mov edx, pcpl_1
INVOKE PdhGetFormattedCounterValue, (CPULoad PTR [edx]).hCounter, PDH_FMT_LONG, NULL, ADDR pfc_1
.ENDIF
.IF dwCores > 2
;** CPU 2 - Read new data
mov edx, pcpl_2
INVOKE PdhCollectQueryData, (CPULoad PTR [edx]).hQuery
mov edx, pcpl_2
INVOKE PdhGetFormattedCounterValue, (CPULoad PTR [edx]).hCounter, PDH_FMT_LONG, NULL, ADDR pfc_2
.ENDIF
.IF dwCores > 3
;** CPU 3 - Read new data
mov edx, pcpl_3
INVOKE PdhCollectQueryData, (CPULoad PTR [edx]).hQuery
mov edx, pcpl_3
INVOKE PdhGetFormattedCounterValue, (CPULoad PTR [edx]).hCounter, PDH_FMT_LONG, NULL, ADDR pfc_3
.ENDIF
;** _Total - Read new data
mov edx, pcpl_Total
INVOKE PdhCollectQueryData, (CPULoad PTR [edx]).hQuery
mov edx, pcpl_Total
INVOKE PdhGetFormattedCounterValue, (CPULoad PTR [edx]).hCounter, PDH_FMT_LONG, NULL, ADDR pfc_Total
; If this is the first collection, do it again
.IF dwFirstCollection == TRUE
mov dwFirstCollection, FALSE
jmp @B
.ELSE
mov dwFirstCollection, TRUE
.ENDIF
.IF dwCores > 0
;** CPU 0 - Check for valid data and display it
.IF (pfc_0.CStatus == PDH_CSTATUS_VALID_DATA) || (pfc_0.CStatus == PDH_CSTATUS_NEW_DATA)
INVOKE StdOut, sdword$(pfc_0.longValue)
INVOKE StdOut, SADD("%",9)
.ENDIF
.ENDIF
.IF dwCores > 1
;** CPU 1 - Check for valid data and display it
.IF (pfc_1.CStatus == PDH_CSTATUS_VALID_DATA) || (pfc_1.CStatus == PDH_CSTATUS_NEW_DATA)
INVOKE StdOut, sdword$(pfc_1.longValue)
INVOKE StdOut, SADD("%",9)
.ENDIF
.ENDIF
.IF dwCores > 2
;** CPU 2 - Check for valid data and display it
.IF (pfc_2.CStatus == PDH_CSTATUS_VALID_DATA) || (pfc_2.CStatus == PDH_CSTATUS_NEW_DATA)
INVOKE StdOut, sdword$(pfc_2.longValue)
INVOKE StdOut, SADD("%",9)
.ENDIF
.ENDIF
.IF dwCores > 3
;** CPU 3 - Check for valid data and display it
.IF (pfc_3.CStatus == PDH_CSTATUS_VALID_DATA) || (pfc_3.CStatus == PDH_CSTATUS_NEW_DATA)
INVOKE StdOut, sdword$(pfc_3.longValue)
INVOKE StdOut, SADD("%",9)
.ENDIF
.ENDIF
;** _Total - Check for valid data and display it
.IF (pfc_Total.CStatus == PDH_CSTATUS_VALID_DATA) || (pfc_Total.CStatus == PDH_CSTATUS_NEW_DATA)
INVOKE StdOut, sdword$(pfc_Total.longValue)
INVOKE StdOut, SADD("%")
INVOKE StdOut, ADDR szCrLf
.ENDIF
; Press any key to exit
call crt__kbhit
test eax, eax
jz @B
.IF dwCores > 0
;** CPU 0 - Clean up
mov edx, pcpl_0
INVOKE PdhRemoveCounter, (CPULoad PTR [edx]).hCounter
mov edx, pcpl_0
INVOKE PdhCloseQuery, (CPULoad PTR [edx]).hQuery
.ENDIF
.IF dwCores > 1
;** CPU 1 - Clean up
mov edx, pcpl_1
INVOKE PdhRemoveCounter, (CPULoad PTR [edx]).hCounter
mov edx, pcpl_1
INVOKE PdhCloseQuery, (CPULoad PTR [edx]).hQuery
.ENDIF
.IF dwCores > 2
;** CPU 2 - Clean up
mov edx, pcpl_2
INVOKE PdhRemoveCounter, (CPULoad PTR [edx]).hCounter
mov edx, pcpl_2
INVOKE PdhCloseQuery, (CPULoad PTR [edx]).hQuery
.ENDIF
.IF dwCores > 3
;** CPU 3 - Clean up
mov edx, pcpl_3
INVOKE PdhRemoveCounter, (CPULoad PTR [edx]).hCounter
mov edx, pcpl_3
INVOKE PdhCloseQuery, (CPULoad PTR [edx]).hQuery
.ENDIF
;** _Total - Clean up
mov edx, pcpl_Total
INVOKE PdhRemoveCounter, (CPULoad PTR [edx]).hCounter
mov edx, pcpl_Total
INVOKE PdhCloseQuery, (CPULoad PTR [edx]).hQuery
ret
main ENDP
;-------------------------------------------------------
InitPdhCounter PROC dwInst:DWORD
LOCAL pcpe:PDH_COUNTER_PATH_ELEMENTS
LOCAL dwBufferSize:DWORD
LOCAL lpszCntPath:PTR BYTE
LOCAL pcpl:PTR CPULoad
INVOKE RtlZeroMemory, ADDR pcpe, SIZEOF PDH_COUNTER_PATH_ELEMENTS
mov eax, dwInst
mov edx, DWORD
mul edx
mov dwOffset, eax
mov eax, OFFSET pCPULoad
add eax, dwOffset
mov edx, [eax]
mov pcpl, edx ; save the pointer
mov (CPULoad PTR [edx]).uElapse, 500
;-- Retrieve the computer name
mov dwBufferSize, MAX_COMPUTERNAME_LENGTH + 1
INVOKE LocalAlloc, LPTR, dwBufferSize
mov pcpe.szMachineName, eax
INVOKE GetComputerName, pcpe.szMachineName, ADDR dwBufferSize
;-- Retrieve performance object name by index: 238 ("Processor")
mov dwBufferSize, MAX_PATH
INVOKE LocalAlloc, LPTR, dwBufferSize
mov pcpe.szObjectName, eax
INVOKE PdhLookupPerfNameByIndex, pcpe.szMachineName, 238, pcpe.szObjectName, ADDR dwBufferSize
;-- Retrieve performance object name by index: 6 ("% Processor Time")
mov dwBufferSize, MAX_PATH
INVOKE LocalAlloc, LPTR, dwBufferSize
mov pcpe.szCounterName, eax
INVOKE PdhLookupPerfNameByIndex, pcpe.szMachineName, 6, pcpe.szCounterName, ADDR dwBufferSize
;-- Initialize other parts of PDH_COUNTER_PATH_ELEMENTS struct
mov eax, OFFSET pszInstName
add eax, dwOffset
mov edx, [eax]
mov pcpe.szInstanceName, edx ; "0", "1", "2", "3" or "_Total"
xor eax, eax
mov pcpe.szParentInstance, eax ; NULL
mov pcpe.dwInstanceIndex, eax ; NULL
;-- Create a full counter path
mov dwBufferSize, PDH_MAX_COUNTER_PATH
INVOKE LocalAlloc, LPTR, dwBufferSize
mov lpszCntPath, eax
INVOKE PdhMakeCounterPath, ADDR pcpe, lpszCntPath, ADDR dwBufferSize, 0
;-- Validate the Counter Path
INVOKE PdhValidatePath, lpszCntPath
test eax,eax
jnz _exit
;-- Print the Counter Path
;INVOKE StdOut, SADD("Counter Path: ")
;INVOKE StdOut, lpszCntPath
;INVOKE StdOut, ADDR szCrLf
;-- Create a PDH Query
mov edx, pcpl
INVOKE PdhOpenQuery, NULL, 0, ADDR (CPULoad PTR [edx]).hQuery
test eax, eax
jnz _exit
;-- Add a counter to the query for the counterPath specified.
mov edx, pcpl
INVOKE PdhAddCounter, (CPULoad PTR [edx]).hQuery, lpszCntPath, CPU_TOTAL, ADDR (CPULoad PTR [edx]).hCounter
test eax, eax
jnz _exit
_exit:
INVOKE LocalFree, pcpe.szMachineName
INVOKE LocalFree, pcpe.szObjectName
INVOKE LocalFree, pcpe.szCounterName
INVOKE LocalFree, lpszCntPath
mov eax, pcpl
ret
InitPdhCounter ENDP
;-------------------------------------------------------
CpuCores PROC
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
cpuid
.IF eax < 4
mov eax, 1
.ELSE
mov eax, 4
xor ebx, ebx
xor ecx, ecx
xor edx, edx
cpuid
and eax, 11111100000000000000000000000000b ; bits 26-31
shr eax, 26
inc eax
.ENDIF
ret
CpuCores ENDP
;-------------------------------------------------------
END start
Greg,
It builds and runs fine but I don't know what its supposed to do.
It displays percent CPU usage for each processor or core. 0 is the first core, 1 is the second core, etc. _Total is the average of all cores. On a single core processor, _Total will be equal to 0. It uses pdh.dll to get the performance counters. They are the same performance counters that you see in Performance Monitor under 'Processor\% Processor Time'. I have only tested it on a Pentium III with XP and a Pentium D with Vista. Is it working correctly on Windows 2000?
Is there a reason for
; Always Sleep at least 1 second between collections
I tried 100, and usage basically agreed with taskman. Nice to see percentages instead of graphs.
sinsi,
Yeah, it says that here:
Collecting Performance Data (http://msdn.microsoft.com/en-us/library/aa371897(VS.85).aspx)
I'm not sure how hard and fast a rule that is.
Glad to hear it works on a quad-core. I kind of like the graphs. :bg
Yeah, the PdhCollectQueryDataEx takes a time parameter in seconds, I wonder if PdhCollectQueryData is a 'wrapper'.
But it creates a thread, which would stuff up our timing things eh?
Funny how quite a few examples on msdn are for vb.