Jump to content

Performance Counters in Windows - Measure Process, CPU, Network, Disk Usage


Ascend4nt
 Share

Recommended Posts

New MASSIVE Update. Working with Performance Counters just got a whole heckuva lot easier! Processes especially - no more worrying about 'drifting' Counters - with the new 'ObjectBase' interface, EVERYTHING is handled for you. You can be a productive coder again! :blink:

On with the updates!!

  • 7/2/2010 (MASSIVE Update):

    _PDH_PerformanceCounters core module:

    - Fixed: PCRE "% Processor Usage" detection in 'Update' functions to work with nonlocalized counter strings. NOTE: will not work on localized non-English Counters (forcing it with a 'path' of -2 works here)

    - Fixed: _PDH_GetCounterInfo now works correctly on Windows 2000 (return message discrepancy)

    - Changed: _PDH_UpdateCounterArray now retrieves null-term strings the (better) way.

    - Changed: _PDH_FreeQueryHandle now returns False when there is an error condition returned,

    and also if successful now invalidates the passed Query Handle.

    NOTE _PDH_UnInit() will *not* invalidate any (optional) passed Query handles - they will retain their values

    - Changed: _PDH_RemoveCounter also now invalidates the passed Counter Handle

    - Changed: _PDH_UpdateCounterArray => renamed to => _PDH_UpdateWildcardCounter

    - Changed: __PDH_LocalizeCounter() now works with non-localized counters ending in slash but with no PC name

    - Removed: Comments and error codes extracted and put in two separate text files:

    _PDH_Error_Codes.txt (the PDH codes you'll see in the console window and in @extended returns)

    _PDH_PerformanceCounter_Notes.txt - Some personal notes/references about the PDH process project

    - Removed extraneous Wrapper functions (if you need these, see the older version):

    _PDH_AddCountersTo2DArray() ; needless wrapper function

    _PDH_GetCounterValuesByPathArrays() ; needless wrapper function

    TestPDH_PerformanceCounters GUI:

    - Changed default 'convert to all instances' behavior to instead work only on what is returned from the Browse Counter Dialoag box. If you still want *all* instances, just select 'All instances' from the Dialog box.

    _PDH_ProcessStats module removed. The 3 'ObjectBase' modules can be used in place of this.

    TestPDH_Multi => renamed to => TestPDH_ProcessLoop (also utilizes new interface)

    _PDH_TaskMgrPerfStats => renamed to => _PDH_TaskMgrSysStats (with some changes)

    _PDH_ProcessGetRelatives:

    - Changed: utilizes <_PDH_ObjectBaseCounters.au3> now

    - Added: _PDH_ProcessList() -> simple list that should report on ANY PC (but mind the '.exe' issue)

    (see TestPDH_ProcessLoop on how to get the *local* PC Processes info)

    - Return style changed for both _PDH_ProcessGetParent() and _PDH_ProcessGetChildren(), to keep consistency in returns. ([0]=name,[1]=PID#)

    _PDH_AddProcessCounter module removed. Instead use <_PDH_ProcessCounters.au3>

    Addition of 3 new modules (plus a simple example) to make adding Counters with the same Base Object easier:

    _PDH_ObjectBaseCounters -> for Counters that have the same base and use an Instance name or Wildcard

    _PDH_ProcessCounters -> for single processes, 1 or more Counters [Local PC only]

    These functions will monitor 'Instance' changes and readjust Counters as needed.

    (MUCH easier and safer implementation than previous _PDH_AddProcessCounter UDF)

    _PDH_ProcessAllCounters -> for ALL processes, 1 or more Counters [Local PC only]

    + minor misc. changes, alterations in some UDF's to work with new interfaces.

Link to comment
Share on other sites

Since its hard to visualize how easy things are, I'll put an example or two in the thread.

Example 1:
% CPU Usage of a Process
(from <TestPDH_ProcessCounter.au3>, which might be added back to the bundle..):
 

#include <_PDH_ProcessCounters.au3>

;   --------------------    HOTKEY FUNCTION & VARIABLE --------------------

Global $bHotKeyPressed=False

Func _EscPressed()
    $bHotKeyPressed=True
EndFunc

;   --------------------    MAIN PROGRAM CODE   --------------------

HotKeySet("{Esc}", "_EscPressed")

_PDH_Init()
Local $poCounter,$hSplash,$sProcess,$iProcessID
Local $iCounterValue,$sSplashText,$iTemp

$sProcess="autoit3.exe"     ; change to "autoit3_x64.exe" for 64-bit process :)
$iProcessID=@AutoItPID

$poCounter=_PDH_ProcessObjectCreate($sProcess,$iProcessID)
_PDH_ProcessObjectAddCounters($poCounter,6) ; "% Processor Time"

; successful? Then enter loop
If @error=0 And IsArray($poCounter) Then
    $iCounterValue=Round(_PDH_ProcessObjectUpdateCounters($poCounter,0)/$_PDH_iCPUCount)
    $sSplashText="Process '"&$sProcess&"' (PID #"&$iProcessID&")"&@CRLF&"CPU Usage:"&$iCounterValue&" %"&@CRLF&"[Esc] exits"
    $hSplash=SplashTextOn("Process Info",$sSplashText,360,100,Default,Default,16)
    Do
        Sleep(200)
        $iCounterValue=_PDH_ProcessObjectUpdateCounters($poCounter,0)
        If @error=32 Then ExitLoop      ; Process can no longer be found
        $iCounterValue=Round($iCounterValue/$_PDH_iCPUCount)
        $sSplashText="Process '"&$sProcess&"' (PID #"&$iProcessID&")"&@CRLF&"CPU Usage:"&$iCounterValue&" %"&@CRLF&"[Esc] exits"
        ControlSetText($hSplash,"","[CLASS:Static; INSTANCE:1]",$sSplashText)
    Until $bHotKeyPressed
EndIf
_PDH_ProcessObjectDestroy($poCounter)
_PDH_UnInit()

Some things you can monitor which you'll find difficult to do anywhere else (set the variables as indicated):

"Idle" process ("System Idle Process" in Task Manager):
$sProcess="Idle"
$iProcessID=0

"Total' (accumulation of Counter value(s) in the ProcessObject):
$sProcess="_Total"
$iProcessID=0

"System" Process:
$sProcess="System"
$iProcessID=ProcessExists($sProcess)

*edit: oops, error check was placed after Round()
*also added note on 64-bit "autoit3_x64.exe"

*edit: Alternative now available: >Process CPU Usage Trackers

Edited by Ascend4nt
Link to comment
Share on other sites

Nice :blink:, though it does not seem to work on 64bit OS.

Change "autoit3.exe" to "autoit3_x64.exe" for 64-bit process ;)

Also, as far as that 'C0000BC6' error code, you can ignore it - it happens alot on the 1st call to gather Counter information, which is probably a good reason to call it once before a main loop :P

Link to comment
Share on other sites

  • 2 weeks later...

One way would be to do ProcessList($sProcess) to get an array of processes fitting a process name, then create a bunch of ProcessObject's for each PID that's returned. You can do the logic to figure out when new processes of the same name come or go, its not that hard.

Link to comment
Share on other sites

Sorry maybe I'm stupid or not so smart but...

#include <Array.au3>
#include <_PDH_ProcessCounters.au3>
#include <_PDH_ProcessAllCounters.au3>

_PDH_Init()
Local $poCounter,$sProcess,$aProcess,$TotalCount=0
Local $CounterValue,$ProcessCount

_PDH_ProcessAlInit($_PDH_iCPUCount)
;If @error Then Exit _PDH_UnInit()

$sProcess="chrome.exe"
$ProcessCount=ProcessList($sProcess)
$TotalCount=$ProcessCount [0][0]
MsgBox(0, "Process count",$TotalCount,1)
for $i = 1 to $TotalCount
    $poCounter=_PDH_ProcessObjectCreate($ProcessCount[$i][0],$ProcessCount[$i][1])
    _PDH_ProcessObjectAddCounters($poCounter,6) ; "% Processor Time"
;~  _ArrayDisplay ($poCounter,  "CPU Usage of processes")
Next
$poCounter=_PDH_ProcessAllUpdateCounters()
$poCounter=_PDH_ProcessObjectUpdateCounters($poCounter)
for $i = 1 to $TotalCount
    $CounterValue=Round(_PDH_ProcessObjectUpdateCounters($poCounter [$i][2],0)/$_PDH_iCPUCount)
    MsgBox(0, "Stats", "Process name "&$poCounter[$i][0]&" PID "&$poCounter[$i][1]&" %CPU "&$CounterValue,1)
Next
_PDH_ProcessObjectDestroy($poCounter)
_PDH_UnInit()

With

_ArrayDisplay ($poCounter,  "CPU Usage of processes")
script show me that counter %CPU is added in array

But the

MsgBox(0, "Stats", "Process name "&$poCounter[$i][0]&" PID "&$poCounter[$i][1]&" %CPU "&$CounterValue,1)

give me a error:

D:\AutoIt Work\PDHPerfCounters\CPU usage.au3 (24) : ==> Error parsing function call.:

$CounterValue=Round(_PDH_ProcessObjectUpdateCounters($poCounter [$i][2],0)/$_PDH_iCPUCount)

$CounterValue=Round(_PDH_ProcessObjectUpdateCounters($poCounter ^ ERROR

Link to comment
Share on other sites

I must be very giving :blink:

Here's something that monitors multiple instances, and also monitors for new and killed ones:

Example #2: % CPU Usage for Multiple Instances
 

#include <Array.au3>
#include <_PDH_ProcessCounters.au3>

; --------------------  HOTKEY FUNCTION & VARIABLE --------------------

Global $bHotKeyPressed=False

Func _EscPressed()
    $bHotKeyPressed=True
EndFunc

; --------------------  WRAPPER FUNCTION --------------------

Func _PDH_ProcessObjectGetPID($poCounter)
    If Not IsArray($poCounter) Then Return SetError(1,0,0)
    Return $poCounter[0][3]
EndFunc

; --------------------  MAIN PROGRAM CODE --------------------

HotKeySet("{Esc}", "_EscPressed")

_PDH_Init()
Local $hSplash,$sSplashText,$sProcess,$iBadCounters=0
Local $aProcesses,$aCounters,$iTemp,$iProcIndex,$iTotal,$iLastPID=0,$iCounterValue

$sProcess="chrome.exe"
$aProcesses=ProcessList($sProcess)
$iTotal=$aProcesses[0][0]
If @error Or $iTotal=0 Then Exit _PDH_UnInit()

MsgBox(0, "Process count",$iTotal,1)

Dim $aCounters[$iTotal] ; Hold the counters in the array

; Create all the process counters
For $i = 0 to $iTotal-1
    $aCounters[$i]=_PDH_ProcessObjectCreate($sProcess,$aProcesses[$i+1][1])
    If @error Then $iBadCounters+=1
    _PDH_ProcessObjectAddCounters($aCounters[$i],6) ; "% Processor Time"
Next
$iLastPID=$aProcesses[$i][1]

; at least some good counters? Then enter loop
If $iBadCounters<>$iTotal Then
    $hSplash=SplashTextOn("'"&$sProcess&"' Processes Info","",360,160,Default,Default,16)
    Do
        ; Check for new processes
        $aProcesses=ProcessList($sProcess)
        If @error Or $aProcesses[0][0]=0 Then ExitLoop
        ; If more processess than we currently have, or last PID doesn't match, we better add new ones
        ;   (note: terminated ones are caught elsewhere
        If $aProcesses[0][0]>$iTotal Or $aProcesses[$aProcesses[0][0]][1]<>$iLastPID Then
            $iTemp=$aProcesses[0][0]
            ; Move backwards to find a PID that matches up
            For $iTemp=$aProcesses[0][0] To 1 Step -1
                If $iLastPID=$aProcesses[$iTemp][1] Then
                    $iProcIndex=$iTemp+1    ; set location in ProcessList where to start adding new counters from (1 past match)
                    ; Found a match, now to calculate how many new Counters to add
                    $iTemp=$aProcesses[0][0]-$iTemp
                    If $iTemp<=0 Then ExitLoop
                    ; Resize the array to accomomodate the new counters
                    ReDim $aCounters[$iTotal+$iTemp]
                    ; Add in the new counters
                    For $i=$iTotal To $iTotal+$iTemp-1
                        $aCounters[$i]=_PDH_ProcessObjectCreate($sProcess,$aProcesses[$iProcIndex][1])
                        _PDH_ProcessObjectAddCounters($aCounters[$i],6) ; "% Processor Time"
                        $iProcIndex+=1
                    Next
                    $iTotal+=$iTemp
                    ConsoleWrite("New Counters added, new Total:"&$iTotal&@CRLF)
                    ExitLoop
                EndIf
            Next
        EndIf
        $sSplashText=""
        ; Update all Process counters
        $i=0
        Do
            $iCounterValue=_PDH_ProcessObjectUpdateCounters($aCounters[$i],0)
            If @error=1 Or @error=32 Then
                ConsoleWrite("Process died. removing. New total:"&$iTotal-1&@CRLF)
;~              _PDH_ProcessObjectDestroy($aCounters[$i])   ; it's dead, and already destroyed if @error=1 or =32
                _ArrayDelete($aCounters,$i) ; Shift the array contents around
                $iTotal-=1  ; 1 less!
                $i-=1       ; to compensate for the upcoming increment
                If $iTotal=0 Then ExitLoop 2    ; did they all die!?
            Else
                If Not @error Then $iCounterValue=Round($iCounterValue/$_PDH_iCPUCount)
                $sSplashText&="PID #"&_PDH_ProcessObjectGetPID($aCounters[$i])&" = "&$iCounterValue&"% CPU Usage"&@CRLF
            EndIf
            $i+=1
        Until $i=$iTotal
        $iLastPID=_PDH_ProcessObjectGetPID($aCounters[$iTotal-1])
        $sSplashText&="[Esc] exits"
    ControlSetText($hSplash,"","[CLASS:Static; INSTANCE:1]",$sSplashText)
    #cs
        If 1 Then
            $aProcesses=ProcessList($sProcess)
            Dim $aTemp[$aProcesses[0][0]+1][2]
            $iTemp=0
            $aTemp[0][0]="ProcessList"
            $aTemp[0][1]="CounterList"
            For $i=1 To $aProcesses[0][0]
                $aTemp[$i][0]=$aProcesses[$i][1]
                If $iTemp<$iTotal Then $aTemp[$i][1]=_PDH_ProcessObjectGetPID($aCounters[$iTemp])
                $iTemp+=1
            Next
            _ArrayDisplay($aTemp,"List compare")
        EndIf
#ce
        Sleep(100)
    Until $bHotKeyPressed Or $iTotal=0
    WinClose($hSplash)
EndIf

; Destroy all the Process counters
For $i = 0 to $iTotal-1
    $aCounters[$i]=_PDH_ProcessObjectDestroy($aCounters[$i])
Next

_PDH_UnInit()

*edit 2nd time: wow, major logic errors. I made adjustments where necessary, and left a chunk of 'debug' code in just for the sake of it (I needed something to pause the loop and let me add/remove processes while making sure PID's synched up)

*edit: An alternative to this is now available: see >Process CPU Usage Trackers

Edited by Ascend4nt
Link to comment
Share on other sites

You're very welcome :blink:

There was actually some big logic issues in that code, sorry about that. The code is updated (see above). It should remain in-sync now. Note that you can always keep a 'tracker' of sorts with the counters by adding a 2nd dimension and then monitoring how long over time their CPU usage was over a certain limit. (I believe that was your original goal).

Note however that we divide by the CPU Count - this is actually the total combined physical AND logical processor core counts. This means that on a 4-core CPU, a user that is using 100% of a single CPU will be reported as using 25% usage because that calculation is how I match it up with Task Manager results. However, for your case, it might be better to NOT do the division if you are tracking for a process's given limit.

*edit: clarified wording of what 100% meant (*not* 100% of multi-cpu)

Edited by Ascend4nt
Link to comment
Share on other sites

  • 2 weeks later...

Another PDH Example, inspired by this thread: Monitoring HD read / write activity (thanks Hagaren76 for your update - and making me aware of that thread). (this UDF is named TestPDH_HDDWaitIdle)

Example #3: Waiting for Hard Disk activity to Idle for 'x' ms:

#include <_PDH_PerformanceCounters.au3>

;   -------------------- MAIN FUNCTION --------------------

; ===================================================================================================================
; Func _PDH_WaitHDDIdle($iMinIdle=1000,$iTimeOut=10000)
;
; Function to wait for *all* Physical Hard Drives to become Idle for a certain amount of time.
;
; $iMinIdle = Minimum amount of time (in ms) that *all* HDD's should remain Idle for before returning
; $iTimeOut = Maximum time to wait overall for the Idling period.
;   If 0, this will run forever until $iMinIdle is reached.
;
; Returns:
;   Succes: True
;   Failure: False, with @error set:
;       @error = -1 = Timed Out
;       @error = 1 = invalid parameter
;       @error = 2 = Error adding Counter Handle or getting new Query (@extended contains @error code)
;       @error = 16 = PDH Performance Counters not Initialized (_PDH_Init() must be called)
;
; Author: Ascend4nt
; ===================================================================================================================

Func _PDH_WaitHDDIdle($iMinIdle=1000,$iTimeOut=10000)
    If $iMinIdle<1 Or $iTimeOut<0 Or ($iTimeOut And $iMinIdle>$iTimeOut) Then Return SetError(1,0,False)
    If Not $_PDH_bInit Then Return SetError(16,0,False) ; not initialized?
    Local $hQuery,$hCounter,$iTimer,$iIdleTimer,$bIdlePhaseStarted=False

    $hQuery=_PDH_GetNewQueryHandle()
    $hCounter=_PDH_AddCounter($hQuery,":234\198\(_Total)")  ; "\PhysicalDisk(_Total)\Current Disk Queue Length"
    If @error Then
        _PDH_FreeQueryHandle($hQuery)
        Return SetError(2,@error,False)
    EndIf
    If $iTimeOut Then $iTimer=TimerInit()
    While 1
        If _PDH_UpdateCounter($hQuery,$hCounter)=0 Then
            If Not $bIdlePhaseStarted Then
                $iIdleTimer=TimerInit()
                $bIdlePhaseStarted=True
            ElseIf TimerDiff($iIdleTimer)>=$iMinIdle Then
                _PDH_FreeQueryHandle($hQuery)
                Return True
            EndIf
        Else
            $bIdlePhaseStarted=False
        EndIf
        Sleep(10)
        If $iTimeOut And TimerDiff($iTimer)>=$iTimeOut Then ExitLoop
    WEnd
    _PDH_FreeQueryHandle($hQuery)
    Return SetError(-1,0,False)
EndFunc


;   -------------------- MAIN CODE --------------------


_PDH_Init()
$iTimer=TimerInit()
If _PDH_WaitHDDIdle() Then
    ConsoleWrite("Time elapsed: "&TimerDiff($iTimer)&" ms"&@CRLF)
    MsgBox(0,"Hard Drive Idled","Hard Drive Idle Time Minimum has been reached")
Else
    $iErr=@error
    $iExt=@extended
    ConsoleWrite("Error!: @error="&$iErr&", @extended="&$iExt&", Time elapsed: "&TimerDiff($iTimer)&" ms"&@CRLF)
    MsgBox(0,"Hard Drive Wait Failed","Failure error code:"&$iErr&", @extended="&$iExt&@CRLF)
EndIf
_PDH_UnInit()

*edit: #include'd the wrong file, oops. (2nd edit: renamed UDF)

Edited by Ascend4nt
Link to comment
Share on other sites

  • 3 weeks later...

Another PDH example, this one just displays CPU Usage. Short but sweet. (UDF name will be TestPDH_CPUUsage)

Example #4: % System CPU Usage (by Processor)
 

#include <_PDH_PerformanceCounters.au3>

;   --------------------    HOTKEY FUNCTION & VARIABLE --------------------

Global $bHotKeyPressed=False

Func _EscPressed()
    $bHotKeyPressed=True
EndFunc


; -------------------- WRAPPER FUNCTION --------------------

Func _PDH_GetCPUCounters($hPDHQuery,$sPCName="")
    ; Strip first '\' from PC Name, if passed
    If $sPCName<>"" And StringLeft($sPCName,2)="\\" Then $sPCName=StringTrimLeft($sPCName,1)
    ; CPU Usage (per processor) (":238\6\(*)" or English: "\Processor(*)\% Processor Time")
    Local $aCPUsList=_PDH_GetCounterList(":238\6\(*)"&$sPCName)
    If @error Then Return SetError(@error,@extended,"")
    ; start at element 1 (element 0 countains count), -1 = to-end-of-array
    Local $aCPUCounters=_PDH_AddCountersByArray($hPDHQuery,$aCPUsList,1,-1)
    If @error Then Return SetError(@error,@extended,"")
    Return SetExtended($aCPUsList[0],$aCPUCounters)
EndFunc

;   --------------------    MAIN PROGRAM CODE   --------------------

HotKeySet("{Esc}", "_EscPressed")

If Not _PDH_Init() Then
  Exit MsgBox(48, "PDH Initialization Error", "Error Initializing Performance Counters, error code = "&@error)
EndIf

Local $hPDHQuery,$aCPUCounters,$iTotalCPUs,$hSplash
Local $iCounterValue,$sSplashText,$sPCName=""   ; $sPCName="\\AnotherPC"

$hPDHQuery=_PDH_GetNewQueryHandle()
$aCPUCounters=_PDH_GetCPUCounters($hPDHQuery,$sPCName)
$iTotalCPUs=@extended

; successful? Then enter loop
If @error=0 And IsArray($aCPUCounters) Then
    _PDH_CollectQueryData($hPDHQuery)   ; collect the query information once (1st collection may fail, so we'll discard it)
    Sleep(50)
    $hSplash=SplashTextOn("CPU Usage Information ["&$iTotalCPUs-1&" total CPU's]","",360,150,Default,Default,16)
    ; Start loop
    Do
        _PDH_CollectQueryData($hPDHQuery)
        $sSplashText=""
        For $i=0 To $iTotalCPUs-1
            ; True means do *not* call _PDH_CollectQueryData for each update. Only once per Query handle is needed
            $iCounterValue=_PDH_UpdateCounter($hPDHQuery,$aCPUCounters[$i][1],0,True)
            If $i<>$iTotalCPUs-1 Then
                $sSplashText&="CPU #"&$i+1&" value:"&$iCounterValue&" %"&@CRLF
            Else
                $sSplashText&="Total Overall CPU Usage value:"& $iCounterValue&" %"
            EndIf
        Next
        $sSplashText&=@CRLF&"[Esc] exits"
        ControlSetText($hSplash,"","[CLASS:Static; INSTANCE:1]",$sSplashText)
        Sleep(250)
    Until $bHotKeyPressed
EndIf
_PDH_FreeQueryHandle($hPDHQuery)
_PDH_UnInit()

*edit: didn't use extra column after all, renamed example (not to be confused with Process % CPU Usage)

*update: An alternative to this has been created (by me of course) here: >CPU Multi-Processor Usage w/o Performance Counters

Edited by Ascend4nt
Link to comment
Share on other sites

UPDATED:

8/19/2010:

- Fixed: _PDH_UnInit() was looking for parameters when called On-Exit

- Fixed: _PDH_ObjectBaseCreate -> Logic error if Couldn't get counters from given PCNAME

- Fixed: Extra Registry value found that could disable Performance Counters, changed registry save/restore code

- Updated: _PDH_PerformanceCounter_Notes.txt (requirements)

- Changed: Core Module: _PDH_Init() registers _PDH_UnInit() as an on-exit function (and _PDH_UnInit() unregisters itself)

- Added: Core: _PDH_ConnectMachine() - connects to the given machine (really only addes the name to the dropdown box in BrowseCounters calls)

- Added: _PDH_ProcessCounters Module: two wrapper functions: _PDH_ProcessObjectGetPID() and _PDH_ProcessObjectGetName()

-> More Examples Added <-:

TestPDH_ProcessCounter, TestPDH_ProcessCounterPoll, TestPDH_ProcessMultipleInstanceMonitor, TestPDH_HDDWaitIdle, TestPDH_CPUUsage, TestPDH_NetworkUsage

(I'll be providing the Network Usage example in the next post)

*edit: added On-Exit fix the same day (oops)

Edited by Ascend4nt
Link to comment
Share on other sites

Example #5: Network Usage
(send/receive information for LAN adapters on the PC)


#include <_PDH_PerformanceCounters.au3>

;   --------------------    HOTKEY FUNCTION & VARIABLE --------------------

Global $bHotKeyPressed=False

Func _EscPressed()
    $bHotKeyPressed=True
EndFunc


; -------------------- WRAPPER FUNCTIONS --------------------


Func _PDH_GetNetworkCounters($hPDHQuery,$sPCName="")
    Local $aNetworkSentList,$aNetworkSentCounters,$aNetworkRecvdList,$aNetworkSentCounters
    Local $aCombined,$iTotalAdapters,$aTemp
    ; Strip first '\' from PC Name, if passed
    If $sPCName<>"" And StringLeft($sPCName,2)="\\" Then $sPCName=StringTrimLeft($sPCName,1)

    ; Network - Bytes sent per second (":510\506\(*)") or English: "\Network Interface(*)\Bytes Sent/sec"
    $aNetworkSentList=_PDH_GetCounterList(":510\506\(*)"&$sPCName)
    ; Network - Bytes received per second (":510\264\(*)") or English: "\Network Interface(*)\Bytes Received/sec"
    $aNetworkRecvdList=_PDH_GetCounterList(":510\264\(*)"&$sPCName)
    If @error Or Not IsArray($aNetworkSentList) Then Return SetError(@error,@extended,"")

    ; start at element 1 (element 0 countains count), -1 = to-end-of-array
    $aNetworkSentCounters=_PDH_AddCountersByArray($hPDHQuery,$aNetworkSentList,1,-1)
    $aNetworkRecvdCounters=_PDH_AddCountersByArray($hPDHQuery,$aNetworkRecvdList,1,-1)
    If @error Or Not IsArray($aNetworkSentCounters) Then Return SetError(@error,@extended,"")

    $iTotalAdapters=UBound($aNetworkSentCounters)
    If $iTotalAdapters<>UBound($aNetworkRecvdCounters) Then Return SetError(-1,0,"")

    Dim $aCombined[$iTotalAdapters*2][2]
    For $i=0 To $iTotalAdapters-1
        ; Convert full paths into LAN Adapter Names while copying over data
        $aTemp=StringRegExp($aNetworkSentCounters[$i][0],"\\[^\(]+\(([^\)]+)\)",1)
        If Not @error Then  $aCombined[$i][0]=$aTemp[0]
        $aCombined[$i][1]=$aNetworkSentCounters[$i][1]
        $aCombined[$iTotalAdapters+$i][0]=$aCombined[$i][0] ; Should be the same name (only this is for 'received')
        $aCombined[$iTotalAdapters+$i][1]=$aNetworkRecvdCounters[$i][1]
    Next
    Return SetExtended($iTotalAdapters,$aCombined)
EndFunc

; Add commas, woo..

Func _AddCommas($sString)
    Local $iLen=StringLen($sString)
    If $iLen<=3 Then Return $sString
    Local $iMod=Mod($iLen,3),$sLeft=""
    If Not $iMod Then $iMod=3
    $sLeft=StringLeft($sString,$iMod)
    $sString=StringTrimLeft($sString,$iMod)
    Return $sLeft&StringRegExpReplace($sString,"(...)",",$1")
EndFunc

;   --------------------    MAIN PROGRAM CODE   --------------------

HotKeySet("{Esc}", "_EscPressed")

_PDH_Init()
Local $hPDHQuery,$aSentRecvdCounters,$iTotalAdapters,$hSplash
Local $iCounterValue,$sSplashText,$sPCName=""   ; $sPCName="\\AnotherPC"
Local $iMaxSendValue=0,$iMaxRecvValue=0

$hPDHQuery=_PDH_GetNewQueryHandle()
$aSentRecvdCounters=_PDH_GetNetworkCounters($hPDHQuery,$sPCName)
$iTotalAdapters=@extended

; successful? Then enter loop
If @error=0 And IsArray($aSentRecvdCounters) Then
    _PDH_CollectQueryData($hPDHQuery)   ; collect the query information once (1st collection may fail, so we'll discard it)
    Sleep(50)
    $hSplash=SplashTextOn("Network Usage Information ["&$iTotalAdapters&" total Adapters]","",550,70+$iTotalAdapters*30,Default,Default,16,Default,10)
    ; Start loop
    Do
        _PDH_CollectQueryData($hPDHQuery)
        $sSplashText=""
        For $i=0 To $iTotalAdapters-1
            $sSplashText&=$aSentRecvdCounters[$i][0]&':'&@CRLF
            ; True means do *not* call _PDH_CollectQueryData for each update. Only once per Query handle is needed
            $iCounterValue=_PDH_UpdateCounter($hPDHQuery,$aSentRecvdCounters[$i][1],0,True)
            $sSplashText&="SENT:"&_AddCommas($iCounterValue)&" bytes/sec"
            If $iMaxSendValue<$iCounterValue Then $iMaxSendValue=$iCounterValue
            $iCounterValue=_PDH_UpdateCounter($hPDHQuery,$aSentRecvdCounters[$iTotalAdapters+$i][1],0,True)
            $sSplashText&=", RECVD:"&_AddCommas($iCounterValue)&" bytes/sec"&@CRLF
            If $iMaxRecvValue<$iCounterValue Then $iMaxRecvValue=$iCounterValue
        Next
        $sSplashText&="SEND Maxium:"&_AddCommas($iMaxSendValue)&" bytes/sec, "& _
            "RECEIVE Maximum:"&_AddCommas($iMaxRecvValue)&" bytes/sec"&@CRLF&"[Esc] exits"
        ControlSetText($hSplash,"","[CLASS:Static; INSTANCE:1]",$sSplashText)
        Sleep(500)
    Until $bHotKeyPressed
EndIf
_PDH_FreeQueryHandle($hPDHQuery)
_PDH_UnInit()

Update: non-Performance Counter network statistics: >Network Statistics for TCP and UDP traffic.

Edited by Ascend4nt
Link to comment
Share on other sites

  • 2 months later...

Hi,

I'm trying to integrate your UDF in a script that runs every 5 seconds on a local machine. the script list every process running, and I would add to the list just the all cpus usage.

I'm testing on a script that does only your function, and I very often have errors with report :

Error Calling PdhGetFormattedCounterValue for Handle:0x00000000032824A0, Path [if passed]:0, Return:800007D6

here is the script : I run it 5 times to do an average, to get more consistent report.

It is just the test script that output a msgbox. If working, I would integrate it to my mainscript, running this function avery 5seconds

In such case, should I _pdh_init at the start of the script and _pdh_uninit only when restarting the machine, or every 5 second as script do its stuff ?

#include <Include/_PDH_PerformanceCounters.au3>

_PDH_Init()
Sleep(50)
Local $hPDHQuery,$aCPUCounters,$iTotalCPUs,$hSplash
Local $iCounterValue,$sSplashText,$sPCName=""

$hPDHQuery=_PDH_GetNewQueryHandle()
$aCPUCounters=_PDH_GetCPUCounters($hPDHQuery,$sPCName)
$iTotalCPUs=@extended

_PDH_CollectQueryData($hPDHQuery)
Sleep(50)
_PDH_CollectQueryData($hPDHQuery)

global $cpu[15]
global $avgcpu
$cpu[0] = 0
$avgcpu = 0
$itotal = 5
For $i=1 To $itotal
    $cpu[$i] = instantcpu()
    $avgcpu = $avgcpu + $cpu[$i-1]
    Sleep(50)
Next
$avgcpu = ($avgcpu + $cpu[$itotal]) / $itotal

MsgBox(0,"","Cpu usage : "&$avgcpu&"%")

_PDH_FreeQueryHandle($hPDHQuery)
_PDH_UnInit()

Func _PDH_GetCPUCounters($hPDHQuery,$sPCName="")
    ; CPU Usage (per processor) (":238\6\(*)" or English: "\Processor(*)\% Processor Time")
    Local $aCPUsList=_PDH_GetCounterList(":238\6\(*)"&$sPCName)
    If @error Then Return SetError(@error,@extended,"")
    ; start at element 1 (element 0 countains count), -1 = to-end-of-array
    Local $aCPUCounters=_PDH_AddCountersByArray($hPDHQuery,$aCPUsList,1,-1)
    If @error Then Return SetError(@error,@extended,"")
    Return SetExtended($aCPUsList[0],$aCPUCounters)
EndFunc
Func instantcpu()
    For $i=0 To $iTotalCPUs-1
        $iCounterValue=_PDH_UpdateCounter($hPDHQuery,$aCPUCounters[$i][1],0,True)
    Next
    return $iCounterValue
EndFunc

Could you enlighten me on why I get this error ? I need 100% reliability on this function.

Also, maybe a way to simplify it to get the most performance for this script, which will be allways running on the machine ?

Maybe there is more simple/simplier way to get cpu usage, as I only need that ?

Cheers,

Kib

Win7 pro x64. scripts compiled to x64. - Autoit v3.3.6.1 | Scite 1.79

Link to comment
Share on other sites

First things first - you're going to have to fix the bad programming mistakes you made.

1. You declare Local variables but reference them in the function 'instantcpu'. You can't access local variables inside another function. Either make them Global, or pass them as parameters.

2. instantcpu() doesn't call _PDH_CollectQueryData($hPDHQuery) before retrieving CPU usage for each processor. This is a must for each new update.

3. instantcpu() only returns the LAST counter value, instead of capturing all CPU values into an array - and the LAST value will of course return the _Total CPU Usage. If you need only that, you don't need the loop, nor the CPU collection function.

Fix those mistakes, and you'll have a place to start debugging. The math looks pretty bad too, but I don't get what you're after with it.

By the way, CPU Usage collection should probably use a minimum of 250ms between calls to get more accurate results.

*edit: see my post on your other thread for getting just the *total* CPU usage.

Edited by Ascend4nt
Link to comment
Share on other sites

  • 4 months later...

Example #6: Physical Disk Read/Write Speed
Inspired by this thread: 'Hard Drive Speed (read & write)' ('?do=embed' frameborder='0' data-embedContent>>), the following will display information regarding the current read/write speed (in bytes currently) for every physical disk on the PC. Performance Counters are smart enough to distinguish which physical disk is which, and contains what drive letters:

(This will be included as 'PDH_HardDiskUsageExample.au3' in the next release)


#include <_PDH_PerformanceCounters.au3>
; ===============================================================================================================================
; <PDH_HardDiskUsageExample.au3>
;
; Simple example of using <_PDH_ProcessCounters.au3>
;
; Author: Ascend4nt
; ===============================================================================================================================

; ===================================================================================================================
;   --------------------    HOTKEY FUNCTION & VARIABLE --------------------
; ===================================================================================================================


Global $bHotKeyPressed=False

Func _EscPressed()
    $bHotKeyPressed=True
EndFunc


; ===================================================================================================================
; --------------------  WRAPPER FUNCTIONS --------------------
; ===================================================================================================================

; Physical Disk - Write (bytes) Counters

Func _PDH_GetDiskWriteCounters($hPDHQuery,$sPCName="")
    Local $aDiskWriteList,$aDiskWriteCounters,$iTotalDisks

    ; Strip first '\' from PC Name, if passed
    If $sPCName<>"" And StringLeft($sPCName,2)="\\" Then $sPCName=StringTrimLeft($sPCName,1)

    ; Physical Disk - Disk Write Bytes/sec (":234\222\(*)") or English: "\PhysicalDisk(*)\Disk Write Bytes/sec"
    $aDiskWriteList=_PDH_GetCounterList(":234\222\(*)"&$sPCName)
    If @error Then Return SetError(@error,@extended,"")

    ; start at element 1 (element 0 countains count), -1 = to-end-of-array
    $aDiskWriteCounters=_PDH_AddCountersByArray($hPDHQuery,$aDiskWriteList,1,-1)
    If @error Then Return SetError(@error,@extended,"")

    $iTotalDisks=UBound($aDiskWriteCounters)-1
    Return SetExtended($iTotalDisks,$aDiskWriteCounters)
EndFunc

; Physical Disk - Read (bytes) Counters

Func _PDH_GetDiskReadCounters($hPDHQuery,$sPCName="")
    Local $aDiskReadList,$aDiskReadCounters,$iTotalDisks

    ; Strip first '\' from PC Name, if passed
    If $sPCName<>"" And StringLeft($sPCName,2)="\\" Then $sPCName=StringTrimLeft($sPCName,1)

    ; Physical Disk - Disk Read Bytes/sec (":234\220\(*)") or English: "\PhysicalDisk(*)\Disk Read Bytes/sec"
    $aDiskReadList=_PDH_GetCounterList(":234\220\(*)"&$sPCName)
    If @error Then Return SetError(@error,@extended,"")

    ; start at element 1 (element 0 countains count), -1 = to-end-of-array
    $aDiskReadCounters=_PDH_AddCountersByArray($hPDHQuery,$aDiskReadList,1,-1)
    If @error Then Return SetError(@error,@extended,"")

    $iTotalDisks=UBound($aDiskReadCounters)-1
    Return SetExtended($iTotalDisks,$aDiskReadCounters)
EndFunc


; ===================================================================================================================
; --------------------  MISC FUNCTION --------------------
; ===================================================================================================================


; ====================================================================================================
; Func _AddCommas($sString)
;
; Simple function - does what it says. Adds commas to a number/string and returns the result.
;
; Author: Ascend4nt
; ====================================================================================================

Func _AddCommas($sString)
    Local $iLen=StringLen($sString)
    If $iLen<=3 Then Return $sString
    Local $iMod=Mod($iLen,3),$sLeft
    If Not $iMod Then $iMod=3
    $sLeft=StringLeft($sString,$iMod)
    $sString=StringTrimLeft($sString,$iMod)
    Return $sLeft&StringRegExpReplace($sString,"(...)",",$1")
EndFunc


; ===================================================================================================================
;   --------------------    MAIN PROGRAM CODE   --------------------
; ===================================================================================================================
#include <Array.au3>

HotKeySet("{Esc}", "_EscPressed")

_PDH_Init()
Local $hPDHQuery,$aDiskWriteCounters,$aDiskReadCounters,$iTotalDisks,$hSplash,$aDrivesPerDisk,$aMaxReadWritesPerDisk
Local $iCounterValue,$sSplashText,$sPCName=""   ; $sPCName="\\AnotherPC"
Local $iMaxWriteValue=0,$iMaxReadValue=0

$hPDHQuery=_PDH_GetNewQueryHandle()
$aDiskWriteCounters=_PDH_GetDiskWriteCounters($hPDHQuery,$sPCName)
$iTotalDisks=@extended
$aDiskReadCounters=_PDH_GetDiskReadCounters($hPDHQuery,$sPCName)

; successful? Then enter loop
If @error=0 And @extended=$iTotalDisks Then
    ; Extract the drive letters assigned to each physical disk
    Dim $aDrivesPerDisk[$iTotalDisks]
    Dim $aMaxReadWritesPerDisk[$iTotalDisks][2]
    For $i=0 To $iTotalDisks-1
        ; Initialize Disk info strings
        $aDrivesPerDisk[$i]=StringRegExpReplace($aDiskWriteCounters[$i][0],'[^(]+\((\d+\s+[^)]+)\)\\.*','$1')
        $aDrivesPerDisk[$i]="Physical Drive #"&StringLeft($aDrivesPerDisk[$i],1)&" [Letters: "&StringReplace(StringMid($aDrivesPerDisk[$i],3),' ',', ')&']'
        ; Initialize min/max values per disk
        $aMaxReadWritesPerDisk[$i][0]=0
        $aMaxReadWritesPerDisk[$i][1]=0
    Next

    _PDH_CollectQueryData($hPDHQuery)   ; collect the query information once (1st collection may fail, so we'll discard it)
    Sleep(50)
    $hSplash=SplashTextOn("Disk Usage Information ["&$iTotalDisks&" total Physical Disks]","",550,70+$iTotalDisks*45,Default,Default,16,Default,10)
    ; Start loop
    Do
        _PDH_CollectQueryData($hPDHQuery)
        $sSplashText=""
        For $i=0 To $iTotalDisks-1
            $sSplashText&=$aDrivesPerDisk[$i]&':'&@CRLF
            ; True means do *not* call _PDH_CollectQueryData for each update.  Only once per Query handle is needed
            $iCounterValue=_PDH_UpdateCounter($hPDHQuery,$aDiskReadCounters[$i][1],0,True)
            $sSplashText&="Read:"&_AddCommas($iCounterValue)&" bytes/sec"
            If $aMaxReadWritesPerDisk[$i][0]<$iCounterValue Then $aMaxReadWritesPerDisk[$i][0]=$iCounterValue
            If $iMaxReadValue<$iCounterValue Then $iMaxReadValue=$iCounterValue

            $iCounterValue=_PDH_UpdateCounter($hPDHQuery,$aDiskWriteCounters[$i][1],0,True)
            $sSplashText&=", Written:"&_AddCommas($iCounterValue)&" bytes/sec"&@CRLF
            If $aMaxReadWritesPerDisk[$i][1]<$iCounterValue Then $aMaxReadWritesPerDisk[$i][1]=$iCounterValue
            $sSplashText&="Max Read:"&_AddCommas($aMaxReadWritesPerDisk[$i][0])&" bytes/sec, Max Write:"&_AddCommas($aMaxReadWritesPerDisk[$i][1])&"bytes/sec"&@CRLF
            If $iMaxWriteValue<$iCounterValue Then $iMaxWriteValue=$iCounterValue
        Next
        $sSplashText&="Overall - Read Maximum:"&_AddCommas($iMaxReadValue)&" bytes/sec, "& _
            "Write Maximum:"&_AddCommas($iMaxWriteValue)&" bytes/sec"&@CRLF&"[Esc] exits"
        ControlSetText($hSplash,"","[CLASS:Static; INSTANCE:1]",$sSplashText)
        Sleep(500)
    Until $bHotKeyPressed
EndIf

_PDH_FreeQueryHandle($hPDHQuery)
_PDH_UnInit()

*edit: There's an alternative way to read Disk/Device Read/Write statistics (although not in the same manner): see >Disk and Device Read/Write Statistics UDF

Edited by Ascend4nt
Link to comment
Share on other sites

Thanks for that :) So it is possible!

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

  • 2 months later...

This is some amazing work and way over my head but i can still have a play :unsure:

Im wanting to use the output from the network usage counters and wrote a copy of netmeter. It works great on xp but when i test it on win7 it dont want to work ?

I can only test on a virtual win7 OS so maybe thats part of the problem.

the console output im getting is :-

CPU count result:1
_PDH_GetNewQueryHandle Call succeeded, return:0,param1:0x00000000,param2:0,handle:0x027607D0
_PDH_GetCounterList() call, $sCounterWildcardPath=':510\506\(*)', PDH DLL 'handle' (or just 'pdh.dll'):1
_PDH_GetCounterNameByIndex() call, Index:510 [optional] Machine Name:, PDH DLL 'handle' (or just 'pdh.dll'):1
_PDH_GetCounterNameByIndex() call, Index:506 [optional] Machine Name:, PDH DLL 'handle' (or just 'pdh.dll'):1
Localized *wildcard* counter (from non-localized string):\Network Interface(*)\Bytes Sent/sec
_PDH_GetCounterList: PdhExpandWildCardPathW 2nd Call successful, 1st reported Bufsize (adjusted+1 on Win2K systems):268, 2nd call's Bufsize (should match):268
There are still * wildcards left after a call to PdhExpandWildCardPathW!
Original wildcard path:\Network Interface(*)\Bytes Sent/sec
_PDH_GetCounterList() call, $sCounterWildcardPath=':510\264\(*)', PDH DLL 'handle' (or just 'pdh.dll'):1
_PDH_GetCounterNameByIndex() call, Index:510 [optional] Machine Name:, PDH DLL 'handle' (or just 'pdh.dll'):1
_PDH_GetCounterNameByIndex() call, Index:264 [optional] Machine Name:, PDH DLL 'handle' (or just 'pdh.dll'):1
Localized *wildcard* counter (from non-localized string):\Network Interface(*)\Bytes Received/sec
_PDH_GetCounterList: PdhExpandWildCardPathW 2nd Call successful, 1st reported Bufsize (adjusted+1 on Win2K systems):280, 2nd call's Bufsize (should match):280
There are still * wildcards left after a call to PdhExpandWildCardPathW!
Original wildcard path:\Network Interface(*)\Bytes Received/sec
PdhCloseQuery DLL call successful
+>13:30:24 AutoIT3.exe ended.rc:0
>Exit code: 0   Time: 2.497

This is testing using your TestPDH_NetworkUsage.Au3

Many thx for the excellent work,

Cheers.

Edited by JackDinn

Thx all,Jack Dinn.

 

JD's Auto Internet Speed Tester

JD's Clip Catch (With Screen Shot Helper)

Projects :- AutoIt - My projects

My software never has bugs. It just develops random features. :-D

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...