News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

CPU usage for multi-processor / multi-core

Started by GregL, June 09, 2008, 11:39:13 PM

Previous topic - Next topic

GregL

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


hutch--

Greg,

It builds and runs fine but I don't know what its supposed to do.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

GregL

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?


sinsi

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.
Light travels faster than sound, that's why some people seem bright until you hear them.

GregL

sinsi,

Yeah, it says that here:

Collecting Performance Data

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


sinsi

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.
Light travels faster than sound, that's why some people seem bright until you hear them.