The MASM Forum Archive 2004 to 2012

General Forums => The Laboratory => Topic started by: GregL on June 09, 2008, 11:39:13 PM

Title: CPU usage for multi-processor / multi-core
Post by: GregL on June 09, 2008, 11:39:13 PM
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

Title: Re: CPU Load for multi-processor / multi-core
Post by: hutch-- on June 10, 2008, 03:36:54 AM
Greg,

It builds and runs fine but I don't know what its supposed to do.
Title: Re: CPU Load for multi-processor / multi-core
Post by: GregL on June 10, 2008, 04:26:11 AM
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?

Title: Re: CPU usage for multi-processor / multi-core
Post by: sinsi on June 10, 2008, 04:44:22 AM
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.
Title: Re: CPU usage for multi-processor / multi-core
Post by: GregL on June 10, 2008, 04:56:47 AM
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

Title: Re: CPU usage for multi-processor / multi-core
Post by: sinsi on June 10, 2008, 06:22:43 AM
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.