Jump to content

Memory leak script stopper ( or not )


argumentum
 Share

Recommended Posts

I was asking around on how to detect memory leaks and came up with a logger ( or just stop execution, or restart ).
Here is the code. ( should have done a UDF but maybe there is no need for such )

#AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 6 -w 7
#include <SQLite.au3>
#include <WinAPISys.au3>
#include <WinAPIProc.au3>
#include <Date.au3>

#include <Array.au3>; For _ArrayDisplay()

Example()
Func Example()

    ConsoleWrite(MemLeakTooMuch() & @CRLF) ; call once at first to initialize the baseline.

    Local $a = MemLeakLog(1) ; ..you could call this on the fly and get an array
    _ArrayDisplay($a)

    ; call on AdlibRegister() or other means
    ; Ex: AdlibRegister("OnMemLeakRestart", 3600000) ; once an hour
    ; While Sleep(50)
    ;   ... ...
    ; WEnd

EndFunc   ;==>Example

#Region MemLeak

Func OnMemLeakRestart() ;
    Local Static $iTriggered = 0
    If MemLeakTooMuch() Then
        If Not $iTriggered Then
            $iTriggered = 1
            AdlibRegister("OnMemLeakRestart", 60000) ; since it triggered, check more often
        EndIf
        If _WinAPI_GetIdleTime() > 600000 Then ; over 10 min. maybe ?, to not bother the user ?

            ShellExecute(@ScriptFullPath) ; restart itself if it must, but if you do,
            ;                                          keep that in mind while coding.

            Exit 3 ; use the exit code you please other than 1 or 2,
        EndIf ;                     as AutoIt may use it for itself.
    EndIf
EndFunc   ;==>OnMemLeakRestart

Func MemLeakLog($ReturnAnArray = 0)
    Local $aMethod[5] = ["EXITCLOSE_NORMAL", "EXITCLOSE_BYEXIT", "EXITCLOSE_BYCLICK", "EXITCLOSE_BYLOGOFF", "EXITCLOSE_BYSHUTDOWN"]
    Local $iMethod = Execute("@exitMethod")
    Local $aDesc[14] = ["number of page faults", "peak working set size, in bytes", "current working set size, in bytes", _
            "peak paged pool usage, in bytes", "current paged pool usage, in bytes", "peak nonpaged pool usage, in bytes", _
            "current nonpaged pool usage, in bytes", "current space allocated for the pagefile, in bytes", _
            "peak space allocated for the pagefile, in bytes", "current amount of memory that cannot be shared with other processes, in bytes", _
            "ProcessHandleCount", "ProcessThreadsCount", "GDI objects", "USER objects"]
    Local $s = @YEAR & "." & @MON & "." & @MDAY & "_" & @HOUR & ":" & @MIN & ":" & @SEC
    $s &= "  -  RunTime: " & _Convert(gTimer()) & "  -  PID: " & @AutoItPID
    If IsInt($iMethod) Then
        $s &= "  -  ExitCode: " & @exitCode
        $s &= "  -  ExitMethod: " & $iMethod & " (" & $aMethod[$iMethod] & ")"
    EndIf
    Local $a[4][15]
    $a[1][0] = "Starting:"
    $a[2][0] = " Exiting:"
    $a[3][0] = "   Diff.:"
    MemLeakTooMuch()
    Local $b = MemLeakTooMuch(-2), $c = MemLeakTooMuch(-3)
    For $n = 0 To UBound($c) - 1
        $a[0][$n + 1] = $aDesc[$n]
        $a[1][$n + 1] = $b[$n]
        $a[2][$n + 1] = $c[$n]
        $a[3][$n + 1] = $c[$n] - $b[$n]
        Switch $n
            Case 0, 10, 11, 12, 13, 14
                ; nothing, is a count number
            Case Else
                $a[3][$n + 1] = ByteSuffix($a[3][$n + 1])
        EndSwitch
        $a[3][$n + 1] &= "  ( x" & (($b[$n] > 0 And $b[$n] > 0) ? Round($c[$n] / $b[$n], 1) : "1") & " )"
    Next
    If Int(Eval("ReturnAnArray")) Then
        $a[0][0] = _Convert(gTimer())
        Return $a
    Else
        FileWriteLine(StringTrimRight(@ScriptFullPath, 4) & ".ProcessInfo.log", $s & @CRLF & _
                _SQLite_Display2DResult($a, 0, True) & Eval("sErrorHandler") & @CRLF)
        ; I save the data from ObjEvent("AutoIt.Error", "_ErrFunc") to $sErrorHandler
    EndIf
EndFunc   ;==>MemLeakLog

Func MemLeakTooMuch($iPid = 0, $iMutliplier = 4) ; in case this is for some other PID, but only one  =/
    Local Static $aInitProcessMemoryInfo = 99, $aLastProcessMemoryInfo
    Local $ret = False, $a, $n, $aTemp
    If $iPid = -2 Then Return $aInitProcessMemoryInfo ; returns the initial readings
    If $iPid = -3 Then Return $aLastProcessMemoryInfo ; returns the last readings
    If $aInitProcessMemoryInfo = 99 Then
        $aTemp = _WinAPI_GetProcessMemoryInfo($iPid)
        If Not (UBound($aTemp) = 10) Then Return SetError(1, 0, $ret)
        $aLastProcessMemoryInfo = $aTemp
    Else
        $aTemp = _WinAPI_GetProcessMemoryInfo($iPid)
        If Not (UBound($aTemp) = 10) Then Return SetError(1, 0, $ret)
        $aLastProcessMemoryInfo = $aTemp
        For $n = 1 To UBound($aTemp) - 1 ; I don't think "page faults" is leakage, so it's omited.
            If $aTemp[$n] > $aInitProcessMemoryInfo[$n] * $iMutliplier Then $ret = True
        Next
    EndIf
    ReDim $aLastProcessMemoryInfo[14] ; to hold the next values
    $aLastProcessMemoryInfo[10] = _WinAPI_GetProcessHandleCount($iPid)
    If $aLastProcessMemoryInfo[10] > 2000 Then $ret = True ; dropping handles ?
    $a = _WinAPI_EnumProcessThreads($iPid)
    $aLastProcessMemoryInfo[11] = UBound($a) - 1
    If $aLastProcessMemoryInfo[11] > 200 Then $ret = True ; dropping threads ?
    If Not $iPid Then $iPid = -1
    $aLastProcessMemoryInfo[12] = _WinAPI_GetGuiResources(0, $iPid) ; count of GDI objects.
    $aLastProcessMemoryInfo[13] = _WinAPI_GetGuiResources(1, $iPid) ; count of USER objects
    If $aLastProcessMemoryInfo[12] > 1000 Then $ret = True ; dropping GDI objects ?
    If $aLastProcessMemoryInfo[13] > 2000 Then $ret = True ; dropping USER objects ?
    If $aInitProcessMemoryInfo = 99 Then
        gTimer() ; init. the timer now, if was not at the top of the script
        $aInitProcessMemoryInfo = $aLastProcessMemoryInfo
        OnAutoItExitRegister("MemLeakLog") ; ..if you wanna keep a log
    EndIf
    Return $ret
EndFunc   ;==>MemLeakTooMuch

Func _Convert($ms) ; https://www.autoitscript.com/forum/topic/163621-convert-ms-to-dayhourminsec/?do=findComment&comment=1192334
    Local $day, $hour, $min, $sec
    _TicksToTime($ms, $hour, $min, $sec)
    If $hour > 24 Then
        $day = $hour / 24
        $hour = Mod($hour, 24)
    EndIf
    Return StringReplace(StringFormat("%03i %02i:%02i:%02i", $day, $hour, $min, $sec), "000 ", "")
EndFunc   ;==>_Convert

Func gTimer()
    Local Static $Timer = TimerInit()
    Return TimerDiff($Timer)
EndFunc   ;==>gTimer

Func ByteSuffix($iBytes) ; https://www.autoitscript.com/autoit3/docs/functions/FileGetSize.htm
    Local $iIndex = 0, $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']
    While $iBytes > 1023
        $iIndex += 1
        $iBytes /= 1024
    WEnd
    Return Round($iBytes) & $aArray[$iIndex]
EndFunc   ;==>ByteSuffix

#EndRegion MemLeak

I'm gonna use it for a pain in the neck script.
If you find ways to improve it, or something I missed, let me know.

2019.01.05 update: Added GDI objects and USER objects. Added return array. Fixed boo-boo. Added Diff. multiplier.

Edited by argumentum
better code

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

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...