BigDaddyO Posted October 16, 2018 Share Posted October 16, 2018 (edited) I automate an older client-server application a lot, and I know from experience that if I try to process a lot of records one after the other, the app/system starts to slow down and eventually stops responding. To get around that, my scripts pause every 200th record (before it normally starts slowing down), logs out of, and closes the application. Then relaunches, logs in, and picks up where it left off. That process takes a bit of extra time so I wanted to find a way to identify when the app is about to experience slowdown and only then perform the close/open. These functions can be used to watch a specific process and warn you when it has exceeded a set usage% of the initial baseline value. Start by launching the application and get the PID for the .exe You then run the Init function to get an initial memory usage of the specified applications PID. You need to then run the Diff function typically at the end or beginning of a main loop to get the % difference between the current memory usage and the initial values. You can set the Init function so a .txt file will be generated by the functions. this is useful so you can watch the normal usage of the app so you know what to set your % thresholds at. The App I was using while testing this out was Chrome since it shows pretty good Mem usage change depending on the site you're in. Looking at that, I set the default Max% at 150 for each. The actual app I'm using this for needs the Max% set to 450, possibly higher as I have additional testing to do. This is my first attempt at Performance stuff and I'm not totally sure if I'm on the right track, so if any of you see any big issues with this please let me know. expandcollapse popup$iPID = 10816 ;Using a Chrome PID from task manager to test with since its resources change a lot $aMemLeak = _ProcessMemLeakInit($iPID, True) ;Pass in the PID and True to create baseline for testing If @error Then ConsoleWrite("Error pulling INIT" & @CRLF) Exit EndIf ConsoleWrite("Initial Function Completed" & @CRLF) For $i = 1 to 10 sleep(3000) ;give it a few seconds to allow you to make some changes in chrome $aMemDiff = _ProcessMemLeakGetDiff($aMemLeak, "Diff record #" & $i) If $aMemDiff[1] > 300 or $aMemDiff[2] > 300 or $aMemDiff[3] > 300 Then ;If any of the Memory starts using > 300% of the initial Mem, then restart it ;Func to Restart the app EndIf Next ;========================================================================== ;=== _ProcessMemLeakGetDiff() ;=== Return an array containing the differences between the _ProcessMemLeakInit() and current values ;=== The array returned from _ProcessMemLeakInit() must be passed into this function as $aRet ;=== Optional Value = $sLog, this value will be added to the Consolewrite, and the log file if you are running a baseline ; ;=== Returns an Array[4] ;=== $aDiff[0] = The same PID that was passed into as $aRet[0] ;=== $aDiff[1] = The % difference between the init $aRet[1] and the current value PoolNonpagedBytes ;=== $aDiff[2] = The % difference between the init $aRet[2] and the current value PoolPagedBytes ;=== $aDiff[3] = The % difference between the init $aRet[3] and the current value PageFileBytes ;========================================================================== Func _ProcessMemLeakGetDiff($aRet, $sLog = "") Local $aDiff[4] ;Using 4 so I can keep the [#] the same for ease of use Local $spacer = " " If IsObj($aRet[4] ) Then If IsObj($aRet[5]) Then $colProcs = $aRet[5].AddEnum($aRet[4] , "Win32_PerfFormattedData_PerfProc_Process" ).objectSet $aRet[5].Refresh For $oProc In $colProcs If $oProc.IDProcess = $aRet[0] Then $iNewVal1 = $oProc.PoolNonpagedBytes $iNewVal2 = $oProc.PoolPagedBytes $iNewVal3 = $oProc.PageFileBytes $aDiff[0] = $aRet[0] $aDiff[1] = Round(($iNewVal1 / $aRet[1]) * 100, 2) $aDiff[2] = Round(($iNewVal2 / $aRet[2]) * 100, 2) $aDiff[3] = Round(($iNewVal3 / $aRet[3]) * 100, 2) If $isMemLeakBaseline = True Then ;If = True then write this info to a log file to identify a good max value later FileWriteLine(@ScriptDir & "\MemLeakBaseline.txt", StringLeft($iNewVal1 & $spacer, 20) & StringLeft($aDiff[1] & "%" & $spacer, 10) & StringLeft($iNewVal2 & $spacer, 20) & StringLeft($aDiff[2] & "%" & $spacer, 10) & StringLeft($iNewVal3 & $spacer, 20) & StringLeft($aDiff[3] & "%" & $spacer, 10) & $sLog) EndIf ConsoleWrite("DIFF: PoolNonpagedBytes = (" & $aDiff[1] & "%), PoolPagedBytes = (" & $aDiff[2] & "%), PageFileBytes = (" & $aDiff[3] & "%) " & $sLog & @CRLF) Return $aDiff EndIf Next Else SetError(1) ;Error connecting to SWbemRefresher EndIf Else SetError(1) ; Error connecting to WMI EndIf SetError(1) ;If it got this far, then the ProcessID wasn't found EndFunc ;========================================================================== ;=== _ProcessMemLeakInit() ;=== This function will take an initial snapshot of those items microsoft suggest to watch to find resources with a memory leak ;=== $iPID is the PID that would normally be returned from a Run or ShellExecute ;=== $isBaseline, If True, then this will create a log file in the @ScriptDir to save all of the diff values and log text so you can better understand when issues may occur ;=== Default = False, no log will be created ;=== Return an array that needs to be passed to the _ProcessMemLeakGetDiff() ;=== [0] = ProcessID ;=== [1] = Pool Nonpaged Bytes ;=== [2] = Pool Paged Bytes ;=== [3] = Paging file in use ;=== [4] = PrivateBytes ;=== [5] = VirtualBytes ;=== [6] = wmi object ;=== [7] = Wbem object ;=== ;=== On Error @error returns one of the following ;=== 2 = Error connecting into the SWbemRefresher ;=== 3 = Error connecting to WMI ;=== 4 = The specified PID could not be found ;=== ;=== https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/determining-whether-a-leak-exists ;========================================================================== Func _ProcessMemLeakInit($iPID, $isBaseline = False) Local $aRet[8] if Not IsDeclared("isMemLeakBaseline") Then Global $isMemLeakBaseline = False ;if not declared in the global scope, then default to False to not create the baseline output file. EndIf $isMemLeakBaseline = $isBaseline ;If this is declared as a baseline run, then update the global variable to the same $oWMI = ObjGet("winmgmts:{impersonationLevel=impersonate,authenticationLevel=pktPrivacy, (Debug)}!\\.\root\cimv2") If IsObj($oWMI) Then Local $oRefresher = ObjCreate("WbemScripting.SWbemRefresher") If IsObj($oRefresher) Then $colProcs = $oRefresher.AddEnum($oWMI, "Win32_PerfFormattedData_PerfProc_Process" ).objectSet $oRefresher.Refresh For $oProc In $colProcs If $oProc.IDProcess = $iPID Then $aRet[0] = $iPID $aRet[1] = $oProc.PoolNonpagedBytes $aRet[2] = $oProc.PoolPagedBytes $aRet[3] = $oProc.PageFileBytes $aRet[4] = $oProc.PrivateBytes ; indicates the total amount of memory that a process has allocated, not including memory shared with other processes. $aRet[5] = $oProc.VirtualBytes ; indicates the current size of the virtual address space that the process is using. $aRet[6] = $oWMI $aRet[7] = $oRefresher ConsoleWrite("Process Name = (" & $oProc.name & ")" & @CRLF) ConsoleWrite(@TAB & "PoolNonpagedBytes = (" & $aRet[1] & "), PoolPagedBytes = (" & $aRet[2] & "), PageFileBytes = (" & $aRet[3] & "), PrivateBytes = (" & $aRet[4] & "), VirtualBytes = (" & $aRet[5] & ")" & @CRLF) If $isMemLeakBaseline = True Then Local $spacer = " " If FileExists(@ScriptDir & "\MemLeakBaseline.txt") Then FileDelete(@ScriptDir & "\MemLeakBaseline.txt") ;Remove the existing log file FileWriteLine(@ScriptDir & "\MemLeakBaseline.txt", "PoolNonpagedBytes PoolPagedBytes PageFileBytes PrivateBytes VirtualBytes Record Description") FileWriteLine(@ScriptDir & "\MemLeakBaseline.txt", StringLeft($aRet[1] & $spacer, 20) & StringLeft($aRet[2] & $spacer, 20) & StringLeft($aRet[3] & $spacer, 20) & StringLeft($aRet[4] & $spacer, 20) & StringLeft($aRet[5] & $spacer, 20) & "Initial value row! All rows below this are the Diff's from these initial values.") ;Write the Diff amounts and the log string (Should show loop# or what you are doing) to the file EndIf $colProcs = "" Return $aRet EndIf Next Else Return SetError(2) ;Error connecting to SWbemRefresher EndIf Else Return SetError(3) ; Error connecting to WMI EndIf Return SetError(4) ;If it got this far, then the ProcessID wasn't found EndFunc edit: Updated the Diff function to return the % diff value of the 3 mem items it looks at. This was working well for me, but i'm no longer using it as the added time the Diff function takes far exceeds the time it would take to restart my app every 200 records. This would be better used just to identify when a problem starts to occur. Edited October 31, 2018 by BigDaddyO Updated Func Xandy and Skeletor 2 Link to comment Share on other sites More sharing options...
faustf Posted October 17, 2018 Share Posted October 17, 2018 what an application it is Link to comment Share on other sites More sharing options...
BigDaddyO Posted October 17, 2018 Author Share Posted October 17, 2018 12 hours ago, faustf said: what an application it is Facets Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now