Jump to content

Recommended Posts

Hi. I'm a teacher and I do a lot of tutorials and other presentations on my computer. I've developed a tool using AutoIt and Adobe AIR to display all the shortcuts I use while I'm presenting. According to the forum rules this would mean that I've developed a keylogger, so I can't show any of the code, but I'm still hoping someone will help me solve an issue I'm having - a memory leak (or at least I think that's it).

I can see the application is taking up more and more memory, but it never goes super crazy. I think it was at 25 MB at one point and that was it. However I see that the longer the application is running less responsive it is. It doesn't capture all the events, or it simply lags. 

I'm using AssocArrays and _MouseOnEvent UDFs, _WinAPI_SetTimer, _WinAPI_SetWindowsHookEx, _Singleton and TCP. I've done some research before posting this and I know there are some issues in special cases, but all solutions were "code specific". Since I can't post any of the code I couldn't respond in those threads. Other than that it really doesn't seem to be the problem with any of the UDFs, so my question is:

Is this a memory leak? If so how can I find it and remove it? What to do to avoid it in the future.

I understand that declaring variables over and over (something in the timer) may be the cause of this, so according to what I've read on the forum I've changed the variables to Global and moved them outside the functions. That way they are only declared once, and then only values are being reassigned. That unfortuantly didn't help. Is there anything else I could do or look for?

BTW - I've used Adobe AIR to create a nice UI. If someone want's to create something similar UEZ was kind enough to share his code of creating such GUI with nice antialiased labels. 
https://www.autoitscript.com/forum/topic/178366-adobe-air-like-window/#comment-1280587

screen.thumb.png.1a10abf83f1f825412b9f61

Share this post


Link to post
Share on other sites

A memory leak is when you permanently requesting resources without releasing these resources and the memory consumption for your exe is increasing constantly. A very good tool is ProcessExplorer from MS to find out for memory leaks.

If this applies to your code you have to check for resource consumption without releasing it.

E.g. if you call the function from the link you have posted (_GDIPlus_CreateCurvedTranslucentPanel) without releasing it

_GDIPlus_BitmapDispose($aReturn[0])

you will cause a memory leak.

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Share this post


Link to post
Share on other sites

Thanks for the answer. I do understand the basic concept and I was very careful when coding this. Even without being aware of memory leaks and what causes them I think it's a good programming habit to release resources - even just for clarity, so I am doing it. I fear that maybe there's something in the UDF's that is causing it (if it in fact is a memory leak). Then I notice something strange happening (like this lagging) I did notice that the app is still sending events over TCP at the exact time they should be send (probably because this is WinAPI hook that executes my function when the event happens), however a timer that is supposed to count down from last event is soooo far behind that it never sends the "clear" event (or whatever it is that it's suppose to be doing). When that happens memory consumption is about 25 MB but it never gets any higher. When I run the app fresh it's about 12MB. 

Also, aside from the timer I have a Do Untill WinActive("appname") loop (so that the app doesn't close) which checks is the Adobe AIR app is running, and if it doesn't the main code continues to run, which calls the _closeApp() (which clears resources).

;Wait for KeySnatcher to close or force stop by user
   Do
      Sleep(100)
   Until GUIGetMsg() = $GUI_EVENT_CLOSE or Not WinExists($hwndGui)
   _closeApp()

$hwndGui is a handle to the other app (the GUI made in Adobe AIR). So that then GUI is not running the app should close. The _closeApp() has a Beep function just for debug purposes. When I notice the lagging the app doesn't beep nor it shuts down. 

My intuition tells me there's something wrong with this loop. But what could it be? Could WinExists be the cause?

Below is everything that I can post without going into details of mouse / keyboard hooks. I'm not including any Global variables or other declarations that are above this code. As you can see I try to be as tidy and elegant with my coding to the best of my ability.

; ===========================================================
; Main Code
; ===========================================================
_Initialize()

Func _Initialize()

   ;Initialize TCP variables
   $sServer = "127.0.0.1"
   $sSocket = 0
   $sPort = 0

   ;Read settings from INI file
   $sPort = IniRead ( @ScriptDir & "\config.ini", "server", "port", "1007" )
   $cHotkey = IniRead ( @ScriptDir & "\config.ini", "control", "hotkey", "Ctrl + Alt + Shift + F12" )
   $hCmd = IniRead ( @ScriptDir & "\config.ini", "server", "hashcmd", "<::cmd>" )
   $iTimeOut = IniRead ( @ScriptDir & "\config.ini", "control", "timeout_ms", "3500" )
   Local $sServerAppTitle = IniRead ( @ScriptDir & "\config.ini", "server", "server_app_title", "KeySnatcher" )

   ;See if there's a KeySnatcher AIR app or another copy of THIS script running
   $hwndGui = WinActivate($sServerAppTitle)
   If $hwndGui = 0 or _Singleton($app, 1) = 0 Then
      Notify()
 ;few beeps
      Exit     ;exit - no resource were set yet, no need to free them
   Endif

   ;Set all variables to their default state (fresh start)
   _FreshStart()

   ;Try to connect to already running KeySnatcher (AIR) app
   ConnectToAirApp()

   ;Execute the core script
   _Run()
EndFunc

; PROCEDURE
; Establish a TCP connection with AIR server (KeySnatcher)
Func ConnectToAirApp()
   TCPStartup()
   Local $sIPAddress = $sServer
   Local $iPort = $sPort

   $sSocket = TCPConnect($sIPAddress, $iPort)

   If @error Then
      ; The server is probably offline/port is not opened on the server.
      Local $iError = @error
      MsgBox(BitOR($MB_SYSTEMMODAL, $MB_ICONHAND), "Connect", "Could not connect, Error code: " & $iError)
      _close()
   Else
      debug("Connection succesful")
   EndIf
EndFunc   ;==>ConnectToAirApp

; PROCEDURE
; Create System Hooks and keep the script alive (infinite loop)
Func _Run()

   ;Creatre system hooks for keyboard and mouse: classes\sysHook.au3
   sysHook()

   ; Create a Timer to clear the display buffer after given time
   $iTimerID = _WinAPI_SetTimer(0, 0, $iTimerDelay, DllCallbackGetPtr($hTimerProc))

   ;System hooks created, timer set, ready to go
   Notify(True)

   ;Wait for KeySnatcher (AIR app) to close or force stop by user
   Do
      Sleep(100)
   Until GUIGetMsg() = $GUI_EVENT_CLOSE or Not WinExists($hwndGui)

   ;Close the script
   _Close()

EndFunc   ;==>_run

 

 

Share this post


Link to post
Share on other sites

How about the loop? Could WinExists cause this? Or maybe it's the fact that I have a Sleep(100) in that loop, and on top of that I have a _WinAPI Timer in my code as well. Maybe they get into some sort of a conflict? 

As for the UDFs - they come from the forum. I've posted what I use. As for _WinAPI functions I basically use examples from the AutoIt manual. All my code is just bunch of snippets working together (as it usually is). It's really hard to pinpoint. When the app is running, but I don't use my computer (so there's nothing to capture, but the timers are working) everything is fine. It can run for 12 hours and everything is fine. But when I'm presenting it takes about an hour for the app to start lagging. 

I've also noticed that when I do something CPU intensive (like presenting image processing or 3D rendering) the app cautpres few events at once, because "it did not manage" to show each action separately and it shows a couple all at once. Which should not happen. I've tripple checked the code. After each captured event, I send it via TCP to display, and after that I'm clearing the variable that holds what the action was, so if the app was unable to follow me in realtime it should at least show the first event that happened or the last, but not all at once. I don't get it. I tried to increase the priority to High with no effect. I am really lost.

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  

  • Similar Content

    • By Arlen
      I think I have memory leak issue...
      Every time I use _IENavigate() my Memory Ram goes up and never down.. I could reach 1gb just using this function. Which is weird..
      #include <IE.au3> #include <GUIConstantsEx.au3> #RequireAdmin Global $oIE = _IECreateEmbedded() Global $Form1 = GUICreate("Form1", 920, 466, 502, 342) GUICtrlCreateObj($oIE, 8, 72, 897, 370) ;IE OBJ Local $Button1 = GUICtrlCreateButton("FETCH", 808, 8, 91, 57) GUISetState(@SW_SHOW) While 1 Local $nMsg = GUIGetMsg() Switch $nMsg Case $GUI_EVENT_CLOSE Exit Case $Button1 Fetch() EndSwitch WEnd Func Fetch() For $i = 0 To 20 _IENavigate($oIE, 'https://www.google.com/search?source=hp&ei=hzprXM_zN-zs_QbExry4CQ&q=sad&btnK=Google+Search&oq=&gs_l=psy-ab.3..35i39j0i67l9.2993.3926..4083...1.0..0.137.749.0j6......0....1..gws-wiz.....6.eVCDaXHQXYA') Next EndFunc  
    • By argumentum
      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.
    • By argumentum
      How do I know there is a memory leak ?, is it "Private Bytes" or "Working Set" too ?
      I have some clues but no definitive knowing. I have no experience looking at these.
      Also, If I can use AutoIt to self-check within the code would be most helpful. 
      Thanks
    • By ur
      I have used _Singleton function in my script to restrict only one instance to run.
      For testing, I have written below code, and triggered multiple instances.
      #include <Misc.au3> MsgBox(0,"",@ScriptName) if _Singleton(@ScriptName, 1) = 0 Then Msgbox(64, @ScriptName, "The program is already running.") Exit Else MsgBox(0,@ScriptName,"No other instances running") EndIf But all are going to else block.
       
      Please suggest.
    • By n3wbie
      i Tried Using Function _single ton
      It Works Like Charm on same computer
      But it is unable to catch up when 2 instance of exe are launched in lan pc
      I would also like to know if there was a way to make particular number of instance work like allow 4-5 instance of same exe to run
×
×
  • Create New...