Sign in to follow this  
Followers 0
Decipher

Get/Limit Process CPU Usage - Background Script

10 posts in this topic

#1 ·  Posted (edited)

I took a script written by Beege, optimized the code, and removed the global variables. Its one function, reliable, and very easy to use or modify to your needs. I decided to post it because I found it very hard to find a reliable way to get the cpu percentage of a given process. I know that there are a few different methods but I deem WMI unreliable and everything I tried before optimizing this function was to slow i.e. freezing when other CPU intensive processes were active.

When monitoring a potentially CPU intensive process, I found it necessary to increase the script's priority otherwise the script/system would freeze which is exactly what I was attempting to prevent!

See Beege's updated post: :thumbsup:

#NoTrayIcon
#include <WinAPI.au3>

HotKeySet('{ESC}', '_Exit')
Func _Exit()
    Exit
EndFunc   ;==>_Exit

_LimitProcessCPU("MediaServer.exe", 10, 1, 5)

Func _LimitProcessCPU($vProcess, $nLimit, $nInterval = 1, $nScriptPriority = 2)
    Local $nProcessCpu = -1, $nOverLimit = 0
    While ProcessExists($vProcess)
        $nProcessCpu = _ProcessCPU($vProcess, $nScriptPriority)
        If $nProcessCpu > 50 Then
            $nOverLimit += 1
        Else
            $nOverLimit = 0
        EndIf
        If $nOverLimit > $nLimit Then
            $nOverLimit = 0
            ProcessClose($vProcess) ; < - Replace this with what you need.
        EndIf
        Sleep($nInterval*1000)
    WEnd
EndFunc

Func _ProcessCPU($nPID = @AutoItPID, $nScriptPriority = 2) ; Realtime priority = 5
        ; Original Author: Beege -> http://www.autoitscript.com/forum/user/8949-beege/
    $nPID = ProcessExists($nPID)
    If Not $nPID Then
        Return SetError(1, 0, "")
    EndIf
    Local Const $tagFILETIME = "struct;dword Lo;dword Hi;endstruct", $nStructs = 7
    Local Static $aStruct[$nStructs], $aPointer[$nStructs], $aStat[4], $hProcess  = _WinAPI_OpenProcess(0x1F0FFF, 0, $nPID, True), $bFirstRun = True
    Local Enum $nIDLETIME, $nKERNELTIME, $nUSERTIME, $nPCreationTime, $nPExitTime, $nPKernelTime, $nPUserTime, _
            $nProcStartKern = 0, $nProcStartUser, $nStartKernel, $nStartUser
    If $bFirstRun Then
        For $i = 0 To $nStructs - 1 Step 1
            $aStruct[$i] = DllStructCreate($tagFILETIME)
            $aPointer[$i] = DllStructGetPtr($aStruct[$i])
        Next
    EndIf
    DllCall('Kernel32.dll', "int", "GetSystemTimes", "ptr", $aPointer[$nIDLETIME], "ptr", $aPointer[$nKERNELTIME], "ptr", $aPointer[$nUSERTIME])
    DllCall('Kernel32.dll', "int", "GetProcessTimes", "hwnd", $hProcess, "ptr", $aPointer[$nPCreationTime], "ptr", $aPointer[$nPExitTime], "ptr", _
            $aPointer[$nPKernelTime], "ptr", $aPointer[$nPUserTime])
    Local $aTemp[4] = [DllStructGetData($aStruct[$nPKernelTime], 1), DllStructGetData($aStruct[$nPUserTime], 1), DllStructGetData($aStruct[$nKERNELTIME], 1), _
            DllStructGetData($aStruct[$nUSERTIME], 1)], $tProcess, $tSystem
    If Not $bFirstRun Then
        $tProcess = ($aTemp[$nProcStartKern] - $aStat[$nProcStartKern]) + ($aTemp[$nProcStartUser] - $aStat[$nProcStartUser])
        $tSystem = ($aTemp[$nStartKernel] - $aStat[$nStartKernel]) + ($aTemp[$nStartUser] - $aStat[$nStartUser])
    Else
        ProcessSetPriority(@AutoItPID, $nScriptPriority)
        $bFirstRun = False
    EndIf
    $aStat = $aTemp
    Return Int(Round(($tProcess / $tSystem) * 100))
EndFunc   ;==>_ProcessCPU

Edit - To give credit for source.

Anonymous

Edited by Decipher

Spoiler

censored.jpg

 

Share this post


Link to post
Share on other sites



Interesting, I just developed a Process CPU usage tracker not long after my work on the CPU Usage tracker code.  I liked the idea of keeping it more object-oriented, and so far my system works out pretty well.  In the future I might expand it to more than one process per usage tracker, since it'd be a shame to call GetSystemTimes more times than is necessary.

Ah, and the author you should be crediting is probably Beege - see his >Process CPU Usage script.

Share this post


Link to post
Share on other sites

#3 ·  Posted (edited)

Ascend4nt,

I've looked at a lot of your code recently, it looks great! Thanks for the info. ;)

If you like at this line:

DllCall('Kernel32.dll', "int", "GetSystemTimes", "ptr", $aPointer[$nIDLETIME], "ptr", $aPointer[$nKERNELTIME], "ptr", $aPointer[$nUSERTIME])

You will see that the data structures that it populates are resuable for multiple calls to <GetProcessTimes> however I wouldn't know right off wether or not you could get around that function call or not as it requires you pass a process handle.

Anonymous

Edited by Decipher

Spoiler

censored.jpg

 

Share this post


Link to post
Share on other sites

How's it going?  I'm using the _ProcessCPU function here in a script.  However, occasionally I need to change the monitored Process.  I'm  not quite sure how to do this as it seems the Static variables don't allow it.  Any advice on how to do this?  Thank you for your time.

Share this post


Link to post
Share on other sites

#6 ·  Posted (edited)

arandomguy01,

Here is an example that complements JohnOne's answer.

Global $nPID = "MyProcess.exe"

While 1
    ;_ProcessCPU(3) ; 3 = AboveNormal priority
    ConsoleWrite(_ProcessCPU(3) & "%" & @CRLF)
    If "condition" = "condition" Then
        $nPID = "MyOtherProcess.exe"
    EndIf
    Sleep(1000)
WEnd

Func _ProcessCPU($nScriptPriority = 2) ; Realtime priority = 5
        ; Original Author: Beege -> http://www.autoitscript.com/forum/user/8949-beege/
    $nPID = ProcessExists($nPID)
    If Not $nPID Then
        Return SetError(1, 0, "")
    EndIf
    Static $nLastProcess = $nPID
    If $nPID <> $nLastProcess Then
        $nLastProcess = $nPID
        $hProcess  = _WinAPI_OpenProcess(0x1F0FFF, 0, $nPID, True)
    EndIf
    Local Const $tagFILETIME = "struct;dword Lo;dword Hi;endstruct", $nStructs = 7
    Local Static $aStruct[$nStructs], $aPointer[$nStructs], $aStat[4], $hProcess  = _WinAPI_OpenProcess(0x1F0FFF, 0, $nPID, True), $bFirstRun = True
    Local Enum $nIDLETIME, $nKERNELTIME, $nUSERTIME, $nPCreationTime, $nPExitTime, $nPKernelTime, $nPUserTime, _
            $nProcStartKern = 0, $nProcStartUser, $nStartKernel, $nStartUser
    If $bFirstRun Then
        For $i = 0 To $nStructs - 1 Step 1
            $aStruct[$i] = DllStructCreate($tagFILETIME)
            $aPointer[$i] = DllStructGetPtr($aStruct[$i])
        Next
    EndIf
    DllCall('Kernel32.dll', "int", "GetSystemTimes", "ptr", $aPointer[$nIDLETIME], "ptr", $aPointer[$nKERNELTIME], "ptr", $aPointer[$nUSERTIME])
    DllCall('Kernel32.dll', "int", "GetProcessTimes", "hwnd", $hProcess, "ptr", $aPointer[$nPCreationTime], "ptr", $aPointer[$nPExitTime], "ptr", _
            $aPointer[$nPKernelTime], "ptr", $aPointer[$nPUserTime])
    Local $aTemp[4] = [DllStructGetData($aStruct[$nPKernelTime], 1), DllStructGetData($aStruct[$nPUserTime], 1), DllStructGetData($aStruct[$nKERNELTIME], 1), _
            DllStructGetData($aStruct[$nUSERTIME], 1)], $tProcess, $tSystem
    If Not $bFirstRun Then
        $tProcess = ($aTemp[$nProcStartKern] - $aStat[$nProcStartKern]) + ($aTemp[$nProcStartUser] - $aStat[$nProcStartUser])
        $tSystem = ($aTemp[$nStartKernel] - $aStat[$nStartKernel]) + ($aTemp[$nStartUser] - $aStat[$nStartUser])
    Else
        ProcessSetPriority(@AutoItPID, $nScriptPriority)
        $bFirstRun = False
    EndIf
    $aStat = $aTemp
    Return Int(Round(($tProcess / $tSystem) * 100))
EndFunc   ;==>_ProcessCPU

Edit - Do notice that I slightly modified the function for use with the global variable.

Anonymous

Edited by Decipher

Spoiler

censored.jpg

 

Share this post


Link to post
Share on other sites

#7 ·  Posted (edited)

Interesting, I just developed a Process CPU usage tracker not long after my work on the CPU Usage tracker code.  I liked the idea of keeping it more object-oriented, and so far my system works out pretty well.  In the future I might expand it to more than one process per usage tracker, since it'd be a shame to call GetSystemTimes more times than is necessary.

 

Ah, and the author you should be crediting is probably Beege - see his >Process CPU Usage script.

 

Thanks for the credit point out Ascend4nt. Ive thought about doing the same type of expansion myself with the code below and passing an array of process handles. My only worry was how far off would the system times be by the time I got to the last call of getprocesstimes. But might be over thinking it  :wacko:

 

 Decipher,

Heres another example you could adapt from thats a little cleaner then I wrote back then with more of the updated syntax that autoit currently has. When I originally wrote it the main thought in my head was everything could be all in one function cause Im only monitoring the process itself. Hense the static var for the process handle. If the handle might change, I would recommend functions like below that except the handle as a parameter. I realize it was just a quick fix, but a global var that changes a local static var is not a good choice. Also note the use of the _ProcessExists function Im using. Its much more efficient than autoits built in one due to how autoit enumerates all processes, then goes throught the list looking for the pid. This returns both total cpu and process cpu. 

#AutoIt3Wrapper_UseX64=n

#include <winapi.au3>
#include <Timers.au3>

;Press ESC to Exit
HotKeySet('{ESC}', '_Exit')

;Press Alt-L to generate a test load
HotKeySet('!l', '_TestLoad')

;Define requested access rights depending on which os is being used
Global $iAccess = 0x0400 ; PROCESS_QUERY_INFORMATION (xp)
If (@OSBuild > 3790) Then $iAccess = 0x1000 ; PROCESS_QUERY_LIMITED_INFORMATION (Vista and up)

;Get Handle to autoit process
Global $g_hAutoItProcess = _WinAPI_OpenProcess($iAccess, 0, @AutoItPID)
If @error Then Exit (ConsoleWrite('Error getting handle' & @LF))

;Call _GetProcessCPU() 1/sec
AdlibRegister('_ProcessCPUExample', 1000)

;Sleep till exit
While Sleep(600000)
WEnd


Func _ProcessCPUExample()

    Static Local $aEnd, $aStart = _GetProcessUsage($g_hAutoItProcess)

    $aEnd = _GetProcessUsage($g_hAutoItProcess)

    ;Get both total and process cpu usage
    Local $aCPU = _CalcUsage($aStart, $aEnd)

    ;Make sure cpu values are valid
    If $aCPU[0] >= 0 And $aCPU[0] <= 100 Then
        ConsoleWrite('Total CPU Usage = ' & $aCPU[0] & '%' & @TAB & @TAB & 'AutoIt CPU Usage = ' & $aCPU[1] & '%' & @LF)
    EndIf

    ;copy last cpu times to start.
    $aStart = $aEnd

EndFunc   ;==>_GetProcessCPU

Func _GetProcessUsage($hProc)

    Static Local $tIdle = DllStructCreate("dword;dword"), $tUser = DllStructCreate("dword;dword"), $tKernel = DllStructCreate("dword;dword")
    Static Local $tCreation = DllStructCreate("dword;dword"), $tExit = DllStructCreate("dword;dword")

    ;Make sure process still exists
    If Not _ProcessExists($hProc) Then Return SetError(1, 0, -1)

    ;Array for holding idle, System and process cpu times
    Local $aTimes[3]

    ;Get system CPU times
    DllCall('Kernel32.dll', "int", "GetSystemTimes", "struct*", $tIdle, "struct*", $tUser, "struct*", $tKernel)
    $aTimes[0] = DllStructGetData($tIdle, 1)
    $aTimes[1] = DllStructGetData($tUser, 1) + DllStructGetData($tKernel, 1)

    ;Get Process cpu times
    DllCall('Kernel32.dll', "int", "GetProcessTimes", "handle", $hProc, "struct*", $tCreation, "struct*", $tExit, "struct*", $tKernel, "struct*", $tUser)
    $aTimes[2] = DllStructGetData($tUser, 1) + DllStructGetData($tKernel, 1)

    Return $aTimes

EndFunc   ;==>_GetProcessUsage

Func _CalcUsage($aCpuStart, $aCpuEnd, $nPercent = True)

    ;Calculate time difference for idle, system, process
    Local $iIdle = ($aCpuEnd[0] - $aCpuStart[0]), $iSystem = ($aCpuEnd[1] - $aCpuStart[1]), $iProcess = ($aCpuEnd[2] - $aCpuStart[2])

    ;Calculate Total and Process CPU values
    Local $aCPUTimes[2]
    $aCPUTimes[0] = ($iSystem - $iIdle) / $iSystem ;    Total CPU Usage
    $aCPUTimes[1] = $iProcess / $iSystem ;              Process CPU Usage

    ;Convert to percent
    If $nPercent Then
        $aCPUTimes[0] = Round($aCPUTimes[0] * 100)
        $aCPUTimes[1] = Round($aCPUTimes[1] * 100)
    EndIf

    Return $aCPUTimes

EndFunc   ;==>_CalcUsage

Func _ProcessExists($hProc)

    Local $aRet = DllCall('kernel32.dll', 'bool', 'GetExitCodeProcess', 'handle', $hProc, 'dword*', 0)
    If @error Or $aRet[0] = 0 Then Return SetError(1, 0, 0)

    Return ($aRet[2] = 259)

EndFunc   ;==>_ProcessExists

Func _TestLoad()
    Local $iA
    For $i = 1 To 500000
        $iA = $iA / 3
    Next
EndFunc   ;==>_TestLoad

Func _Exit()
    _WinAPI_CloseHandle($g_hAutoItProcess)
    Exit
EndFunc   ;==>_Exit
Edit: Changes to OpenProcess. Access rights should be set according to OS version used. Also debug privilages not needed in this case. Edited by Beege
1 person likes this

Share this post


Link to post
Share on other sites

Beege,

Thanks for the awesome(efficient) code again! I'll definitely be updating my scripts. So the new dll call syntax removes the need to call DllStructGetPtr directly, hu? I had no idea that AutoIt's ProcessExists() function behaved that way. I'll be adding your functions to my reference list. ;)

Anonymous


Spoiler

censored.jpg

 

Share this post


Link to post
Share on other sites

Your very welcome. And yes, Struct* gives us the ability to pass a pointer to a struct now so no extra calls to DllStructGetPtr in most cases. 

One other tip, Static is very useful for calling function once, even if your not storing anything in the variable:

Static Local $SetPrioityOnce = ProcessSetPriority(@AutoItPID, $nScriptPriority)

Share this post


Link to post
Share on other sites
 

 

When using the above I got occasional negative values. Found it was due to the idle time value rolling over (only the PCU percentage was checked not the Process Percentage). Also sometimes it would have no time elapse between measurements (Some OS caching the function call?) which resulted in incorrect 0% CPU time for the measured Process.
 
Here is the error checking:
Func _ProcessCPUExample()
   
    Static Local $aEnd, $aStart = _GetProcessUsage($g_hAutoItProcess)
    sleep(2000) ; make sure some time elapses between measurements. 
    $aEnd = _GetProcessUsage($g_hAutoItProcess)
    
    ;Make sure time value did not roll over
    while $aEnd[0] <= $aStart[0] or $aEnd[1] <= $aStart[1] or $aEnd[2] <= $aStart[2] 
      ConsoleWrite('ReTrying'&@CRLF)
      $aStart = _GetProcessUsage($g_hAutoItProcess)
      sleep(2000)
      $aEnd = _GetProcessUsage($g_hAutoItProcess)
    WEnd
    
    ;Get both total and process cpu usage
    Local $aCPU = _CalcUsage($aStart, $aEnd)
    ConsoleWrite('Total CPU Usage = ' & $aCPU[0] & '%' & @TAB & @TAB & 'AutoIt CPU Usage = ' & $aCPU[1] & '%' & @LF)

    ;copy last cpu times to start.
    $aStart = $aEnd

EndFunc   ;==>_GetProcessCPU 

Share this post


Link to post
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
Sign in to follow this  
Followers 0

  • Similar Content

    • VIP
      By VIP
      The simple widget shows the percentage of cpu, ram used and the CPU temperature (WMI).
      - Every 30s check, sync and update time for computer.
      - Drag adn Drop any files on GUI for reset Attribute (-RAHS) and set Full Access to Everyone !
      - Righ Click on x and click Un/Install app to boot with Windows!
      CPU_Widget_TimeSync.exe CRC32: 1956339E MD5: BFC2B596BD1EDA35D6B02B97F8FB57AE SHA-1: 796A663739EA5AC9890D9FA324A98E987F35E7C8
      Function code from:
      CPU: 
      RAM:  MemGetStats ( )
      Memory:   _WinAPI_EmptyWorkingSet ()
       
    • jesus40
      By jesus40
      Hello friends, i have a working curl command that show informations about my account on binance.com, but_it dont work with autoit code without curl.exe.
      I want to do it without curl, because the whole process much Slower_ with StdoutRead (I want get the response in variable.)
      My Curl command in Autoit:
      This 2 are works, but_ i would like to do it without curl.exe
      $apikey="XYZ" sCommand = @ScriptDir & '\curl.exe -k -H "X-MBX-APIKEY: ' & $apikey & '" -X GET "https://api.binance.com/api/v3/account?' & $request the same in .bat  file
      curl.exe -k -H "X-MBX-APIKEY: XYZ" -X GET "https://api.binance.com/api/v3/account?timestamp=1514917812000&signature=85bdee77e53cd521e1d5229fbfb459d53799c42b3fa4596d73f1520fad5f965a" (I use curl with -k option which allows curl to make insecure connections, because there is problem with the sites certificate, (cURL error 60))
       
      I tried many variations, this is the latest... I cant get the same response.
      curl $error message (I changed ): {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
      autoit version $error message (Response code:400): Mandatory parameter 'timestamp' was not sent, was empty/null, or malformed.
       
      $request = $query & '&signature=' & $signature $oHTTP = ObjCreate("winhttp.winhttprequest.5.1") $oHTTP.Open("GET", "https://api.binance.com/api/v3/account", False) $oHTTP.SetRequestHeader("X-MBX-APIKEY", $apikey) $oHTTP.Send($request) $oReceived = $oHTTP.ResponseText $oStatusCode = $oHTTP.Status If $oStatusCode <> 200 then MsgBox(4096, "Response code", $oStatusCode) EndIf  
      thanks
    • ur
      By ur
      When we run our exe from any service or through any background process, it won't interact with the applications running in the foreground, I mean launched when the user is logged in or in GUI mode.
       
      Let's say, the onedrive sync app can't be detected from the exe we created and running it in background.

      I tried both options below.

       
      Any Idea how to interact with foreground processes using AuotIT code.

      Other than telling to launch the exe also in foreground.
    • ur
      By ur
      with the below code, I am keeping waiting for a particular file and waiting in the background.
      While $i <> 10 Sleep(60000) If FileExists(@ScriptDir&"\Binaries.ini") then --some processing-- $i = 10 endif WEnd I thought keeping the Sleep will freeze the process from resources but the CPU being uilized.But it is taking 47%.
      How to free this CPU usage also.?

    • Earthshine
      By Earthshine
      This baby can do almost any language! I found Gleany on this site and set it up on a fresh win10 install. all i would need to do to make it build an exe of the search files app we discovered is the .NET Target Framework download to build 100 % but the exe works and generates! You also need to add the path the the version of csc.exe that you want to run. MSBuild is part of .NET and can read your .csproj file.
      YOU can develop .NET OR C/C++ on this baby!
      Super lightweight (fit for crappy laptops), and you can use any dev compilers you wish, Ming, MS, etc, whatever.
      To use the build, you need to open the csproj file, else, to check your syntax it can compile individual files as well. The warning are because I do not have the .NET 4.6 Targeting Pack installed... LOL, and I could get rid of the Core warning, yes, I did not want to build a .NET Core.dll I wanted a live x64 exe! Told you MSBUILD could handle .CSPROJ, .VBPROJ, and all others Microsoft. Don't let Visual Code or Visual Studio slow you down, although, this works with Visual Code as well but there is only debugging in CORE.DLL projects

      ListFiles Example.zip