Page 1 of 2

Memory leak Detector (work Complete) Sergey Wins!

Posted: Sat Feb 07, 2026 11:54 pm
by Randy Walker
[EDIT 02/12/26] JUmp to this post to get the latest revision. Its specacular!: viewtopic.php?p=651513#p651513

I was going to share this anyway after getting it to work but hit a road-block here somewhere and couldn't figure out why it never triggered an event as intended. I promise 5 stars to anyone that can get it to work. It should post offending process to the output window as well as log the event. HINT: Triggering should occur at line 242 (I think).
Full disclosure... This is the product of 2 days working with ChatGPT to develop the following code:

Code: Select all

; ==================================================
; Memory Leak Detector Settings Window (PureBasic)
; ==================================================

; --- Globals for detector configuration ---
Global SnapshotInterval = 3000       ; ms
Global MinGrowthMB = 3                ; MB
Global NumSnapshotsToCheck     = 3          ; consecutive snapshots to check
Global GrowthCountThreshold = 3       ; hits before logging
Global StartupGraceSnapshots = 3      ; ignore first N snapshots
Global NoGrowthResetSamples = 3       ; reset hits if no growth over this many snapshots
;--- Global Declarations ---
Global NewList CurrentProcs.s()    ; List of current process snapshots
Global NewMap ProcHistory.s()      ; Map: process name → snapshot history
Global NewMap PreviousMemory.q()   ; process → last observed memory in bytes
Global NewMap GrowthHits.i()        ; process → hit counter
Global NewMap NoGrowthCount.i()     ; process → no-growth counter

; ; --- Gadget IDs ---
#GID_Text_SnapshotInterval       = 11
#GID_Text_MinGrowthMB            = 12
#GID_Text_GrowthCountThreshold   = 13
#GID_Text_StartupGraceSnapshots  = 14
#GID_Text_NoGrowthResetSamples   = 15
#GID_Text_NumSnapshotsToCheck    = 16

#GID_Spin_SnapshotInterval       = 101
#GID_Spin_MinGrowthMB            = 102
#GID_Spin_GrowthCountThreshold   = 103
#GID_Spin_StartupGraceSnapshots  = 104
#GID_Spin_NoGrowthResetSamples   = 105
#GID_Spin_NumSnapshotsToCheck    = 110
#GID_ApplyButton                 = 201
#GID_CancelButton                = 202
#GID_HelpButton                  = 203

; --- Procedure: Capture current processes ---
Procedure CaptureProcesses()
  Program$ = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
  param$   = "-NoProfile -Command " + Chr(34) + "Get-Process | ForEach-Object { $_.Name + ' ' + [math]::Round($_.WS/1MB,1) }" + Chr(34)

  ClearList(CurrentProcs.s())

  EXE = RunProgram(Program$, param$, "", #PB_Program_Open | #PB_Program_Read | #PB_Program_Hide)

  If EXE
    While ProgramRunning(EXE)
      If AvailableProgramOutput(EXE)
        OutputLine$ = Trim(ReadProgramString(EXE))
        If OutputLine$ <> ""
          AddElement(CurrentProcs.s())
          CurrentProcs.s() = OutputLine$
     ;     Debug OutputLine$
        EndIf
      EndIf
    Wend
    CloseProgram(EXE)
  EndIf
EndProcedure

; --- Open Settings Window ---
If OpenWindow(0, 100, 100, 320, 250, "Leak Detector Settings", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  ; Labels
  TextGadget(#GID_Text_SnapshotInterval, 40, 10, 180, 20, "Snapshot Interval (MilliSeconds):")
  TextGadget(#GID_Text_MinGrowthMB, 10, 40, 210, 20, "Minimum Growth (MegaBytes) / sample:")
  TextGadget(#GID_Text_StartupGraceSnapshots, 10, 70, 180, 20, "Startup Grace Snapshots:")
  TextGadget(#GID_Text_NoGrowthResetSamples, 10, 100, 180, 20, "No-Growth Reset Samples:")
  TextGadget(#GID_Text_NumSnapshotsToCheck, 10, 130, 200, 20, "Samples to check before loggiong:")
  TextGadget(#GID_Text_GrowthCountThreshold, 10, 160, 180, 20, "Growth Count Threshold:")

  ; SpinGadgets with defaults (all values inside min/max)
  SpinGadget(#GID_Spin_SnapshotInterval, 200, 10, 100, 20, 1000, 60000,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_MinGrowthMB, 200, 40, 100, 20, 1, 100,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_StartupGraceSnapshots, 200, 70, 100, 20, 0, 10,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_NoGrowthResetSamples, 200, 100, 100, 20, 0, 20,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_NumSnapshotsToCheck, 200, 130, 100, 20, 1, 20,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_GrowthCountThreshold, 200, 160, 100, 20, 1, 20,#PB_Spin_Numeric)


  ; Buttons
  ButtonGadget(#GID_ApplyButton, 50, 220, 80, 20, "Apply")
  ButtonGadget(#GID_CancelButton, 150, 220, 80, 20, "Cancel/Close")
  ButtonGadget(#GID_HelpButton, 100, 180, 80, 20, "Help")
  
  SetGadgetText(#GID_Spin_SnapshotInterval,Str(SnapshotInterval))
  SetGadgetText(#GID_Spin_MinGrowthMB,Str(MinGrowthMB))
  SetGadgetText(#GID_Spin_GrowthCountThreshold,Str(GrowthCountThreshold))
  SetGadgetText(#GID_Spin_StartupGraceSnapshots,Str(StartupGraceSnapshots))
  SetGadgetText(#GID_Spin_NoGrowthResetSamples,Str(NoGrowthResetSamples))
  SetGadgetState(#GID_Spin_NumSnapshotsToCheck, 3)
EndIf
Procedure ShowHelp()
  helpText$ =  "Memory Leak Detector Help" + #CRLF$
  helpText$ + "-------------------------" + #CRLF$
  helpText$ + "Snapshot Interval: Time between memory snapshots." + #CRLF$
  helpText$ + "MinGrowth_MB: Minimum growth per snapshot (MB) to consider for a leak." + #CRLF$
  helpText$ + "NumSnapshotsToCheck: Number of consecutive snapshots a process must grow to log as leak." + #CRLF$
  helpText$ + "GrowthCountThreshold: How many times the growth must hit MinGrowthMB before logging." + #CRLF$
  helpText$ + "StartupGraceSnapshots: Ignore the first N snapshots of a process after it appears." + #CRLF$
  helpText$ + "NoGrowthResetSamples: Reset hit count if no growth occurs for this many snapshots." + #CRLF$
  helpText$ + "NumSnapshotsToCheck	- How many snapshots to examine in a row" + #CRLF$
  helpText$ + "GrowthCountThreshold	-How many of those snapshots need a growth ≥ MinGrowthMB To envoke logging" + #CRLF$
  helpText$ + #CRLF$ + "Example:" + #CRLF$
  helpText$ + "A process with memory 100 → 105 → 112 → 120 MB with MinGrowthMB=5 and NumSnapshotsToCheck=3 will be logged as leaking."

  MessageRequester("Memory Leak Detector Help", helpText$, #PB_MessageRequester_Ok)
EndProcedure
        CaptureProcesses()
; ==================================================
; Event Loop (your canonical style)
; ==================================================
Repeat
  Event = WaitWindowEvent()
  
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        
        Case #GID_ApplyButton
          ; Read values from gadgets and update globals
          SnapshotInterval        = GetGadgetState(#GID_Spin_SnapshotInterval)
          MinGrowthMB             = GetGadgetState(#GID_Spin_MinGrowthMB)
          GrowthCountThreshold    = GetGadgetState(#GID_Spin_GrowthCountThreshold)
          StartupGraceSnapshots   = GetGadgetState(#GID_Spin_StartupGraceSnapshots)
          NoGrowthResetSamples    = GetGadgetState(#GID_Spin_NoGrowthResetSamples)
          NumSnapshotsToCheck     = GetGadgetState(#GID_Spin_NumSnapshotsToCheck)

          
          ; Feedback
          Debug "Settings Applied:"
          Debug "SnapshotInterval=" + Str(SnapshotInterval)
          Debug "MinGrowthMB=" + Str(MinGrowthMB)
          Debug "GrowthCountThreshold=" + Str(GrowthCountThreshold)
          Debug "StartupGraceSnapshots=" + Str(StartupGraceSnapshots)
          Debug "NoGrowthResetSamples=" + Str(NoGrowthResetSamples)
          Debug "GID_Spin_NumSnapshotsToCheck=" + Str(NumSnapshotsToCheck)
          Break
        Case #GID_CancelButton
          End
        Case #GID_HelpButton
          ShowHelp()
      EndSelect
  EndSelect
Until event = #PB_Event_CloseWindow
          CloseWindow(0)

;==================================================
;PureBasic Memory Leak Detector (GUI + Logging)
;==================================================



; --- Procedure: Update ProcHistory Map ---
Procedure UpdateHistory()
  ForEach CurrentProcs.s()
    Name$  = StringField(CurrentProcs.s(), 1, " ")
    WS_MB$ = StringField(CurrentProcs.s(), 2, " ")

    If ProcHistory(Name$) = ""
      ProcHistory(Name$) = WS_MB$
    Else
      ProcHistory(Name$) + "_" + WS_MB$ ; append new snapshot
    EndIf
  Next
EndProcedure

; --- Procedure: Continuous logging of growing processes ---
Procedure LogLeak(Name$, Snapshots$)
  logFile$ = "MemoryLeakLog.txt"
  If OpenFile(1, logFile$, #PB_File_Append)
    currentTime$ = FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss", Date())
    WriteStringN(1, currentTime$ + " | Growing leak | " + Name$ + " | History: " + Snapshots$)
    CloseFile(1)
  Else
    Debug "Failed to write to log file!"
  EndIf
EndProcedure
Debug GetCurrentDirectory()
; --- Procedure: Detect memory leaks ---
Procedure DetectLeaks()
  AddGadgetItem(0, -1, "----- Memory Leak Check -----")

  ForEach ProcHistory()
    
    If MapKey(ProcHistory()) = "LeakyApp"
      Debug  MapKey(ProcHistory())+ " "+ProcHistory()
    Debug "Prev=" + StrD(PreviousVal.d) + " Curr=" + StrD(CurrentVal.d) + " Diff=" + StrD(CurrentVal.d - PreviousVal.d)
    EndIf
    Name$ = MapKey(ProcHistory())
    Snapshots$ = ProcHistory()

    NumFields = CountString(Snapshots$, "_") + 1

    ; --- Startup grace + minimum history ---
    If NumFields <= StartupGraceSnapshots
      Continue
    EndIf

    If NumFields < NumSnapshotsToCheck
      Continue
    EndIf

    ; --- Examine last NumSnapshotsToCheck samples ---
    StartIndex = NumFields - NumSnapshotsToCheck + 1
    PreviousVal.d = ValD(StringField(Snapshots$, StartIndex, "_"))

    HitsThisPass = 0
    GrowthSeen   = #False

    i = StartIndex + 1
    While i <= NumFields

      CurrentVal.d  = ValD(StringField(Snapshots$, i, "_"))

      If (CurrentVal.d - PreviousVal.d) >= MinGrowthMB
        HitsThisPass + 1
        GrowthSeen = #True
      EndIf

      PreviousVal.d = CurrentVal.d
      i + 1
    Wend


    ; --- Update counters ---
    If GrowthSeen
      ;     GrowthHits(Name$) + HitsThisPass
      GrowthHits(Name$) + 1

      NoGrowthCount(Name$) = 0
    Else
      NoGrowthCount(Name$) + 1
    EndIf

    ; --- Reset if no growth for too long ---
    If NoGrowthCount(Name$) >= NoGrowthResetSamples
      GrowthHits(Name$) = 0
      NoGrowthCount(Name$) = 0
    EndIf
    
    ; --- Log only after sustained growth ---
    If GrowthHits(Name$) >= GrowthCountThreshold
      Debug "***** TRIGGER REACHED *****"
      
      line$ = "Memory leak detected: " + Name$ + " | History: " + Snapshots$
      AddGadgetItem(0, -1, line$)
      Debug line$
      LogLeak(Name$, Snapshots$)

      ; keep logging if it continues
      GrowthHits(Name$) = 0
    EndIf

  Next

EndProcedure

; --- Open GUI ---
If OpenWindow(0, 0, 0, 400, 400, "Memory Leak Detector", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  EditorGadget(0, 8, 8, 384, 384, #PB_Editor_ReadOnly | #PB_Editor_WordWrap)
  AddWindowTimer(0, 123, SnapshotInterval)
  AddGadgetItem(0, -1, "Memory Leak Detector Started.")
EndIf

; --- Main Event Loop ---
Repeat
  event = WaitWindowEvent()

  Select event
    Case #PB_Event_CloseWindow
      End

    Case #PB_Event_Timer
      If EventTimer() = 123
        CaptureProcesses()
        UpdateHistory()
        DetectLeaks()
      EndIf
  EndSelect
ForEver
Next up is the code compiled separately to simulate an offending process. Well technically it is an offending Process -- deliberately never freeing memory. I called it LeakyApp.exe

Code: Select all

 MessageRequester("Leaking Pending", "Press Enter to begin leaking.", #MB_OK | #MB_SYSTEMMODAL)
Procedure LeakyFunction()
  Static fails
  Protected ptr

  ptr = AllocateMemory(3 * 1024 * 1024) ; exactly MinGrowthMB

  If ptr = 0
    fails + 1
    If fails = 3
MessageRequester("Allocate Failed", "failed to allocate mem 3 times.", #MB_OK | #MB_SYSTEMMODAL)
    EndIf
    ProcedureReturn
  EndIf

  ; Force OS-visible commit
  FillMemory(ptr, 3 * 1024 * 1024, 1)
EndProcedure

; Run longer than minimum to be safe
For i = 1 To 10
  LeakyFunction()
  Delay(3000) ; match SnapshotInterval
Next

Delay(12000)

MessageRequester("Leaking Ceased", "leaking has concluded.", #MB_OK | #MB_SYSTEMMODAL)
End
If you run the detector in debug mode you can clearly see "LeakyApp" is growing as intended.
And remember ... 5 stars to the one who gets it posting and logging offenders. THANKS!!!
[EDIT 02/10/26] Sergey found the Problem so WE fixed it by adding line 8 which I have done above, AND 5 stars go to Sergey!! ⭐⭐⭐⭐⭐

Re: Memory leak Detector (work in progress)

Posted: Mon Feb 09, 2026 2:45 pm
by Sergey
Hmm... leaking has concluded and leak not detected
OutputLine$ shows fine

Re: Memory leak Detector (work in progress)

Posted: Mon Feb 09, 2026 7:37 pm
by Randy Walker
Sergey wrote: Mon Feb 09, 2026 2:45 pm Hmm... leaking has concluded and leak not detected
OutputLine$ shows fine
Yes Sergey.. That is exactly my point. It does not report the leak. :cry:
The big Question here is: "What is wrong in the code?" :idea: :?:
The big Reward if you can answer that question is 5 stars. ⭐
Sorry, I'm low budget so it is best I can offer.
Thanks for reading. :)

Re: Memory leak Detector (work in progress)

Posted: Mon Feb 09, 2026 8:28 pm
by mk-soft
The memory management of Windows is not static, but dynamic.
This means that you do not know if the memory is reorganized by the application. Thus, the memory can grow and fall.

Re: Memory leak Detector (work in progress)

Posted: Mon Feb 09, 2026 9:53 pm
by Randy Walker
mk-soft wrote: Mon Feb 09, 2026 8:28 pm The memory management of Windows is not static, but dynamic.
This means that you do not know if the memory is reorganized by the application. Thus, the memory can grow and fall.
Sorry, don't see the pertinence. The code clearly records growth in the ProcHistory() list. so it is plain to see growth is occuring. Regardless where Windows chooses to move it.

Re: Memory leak Detector (work in progress)

Posted: Tue Feb 10, 2026 2:25 pm
by Sergey
Randy Walker wrote: Mon Feb 09, 2026 7:37 pm Yes Sergey.. That is exactly my point. It does not report the leak. :cry:
The big Question here is: "What is wrong in the code?" :idea: :?:
The big Reward if you can answer that question is 5 stars. ⭐
Sorry, I'm low budget so it is best I can offer.
Thanks for reading. :)
RandyWalker, I read it wrong, I'm ready to get 5 stars
You just need to do Global NumSnapshotsToCheck
because Procedure DetectLeaks() does not see NumSnapshotsToCheck

I advice using EnableExplicit at the beginning of your code.
And then this error would not exist, the code would simply not compile

Re: Memory leak Detector (work in progress)

Posted: Tue Feb 10, 2026 7:49 pm
by Randy Walker
Sergey wrote: Tue Feb 10, 2026 2:25 pm
Randy Walker wrote: Mon Feb 09, 2026 7:37 pm Yes Sergey.. That is exactly my point. It does not report the leak. :cry:
The big Question here is: "What is wrong in the code?" :idea: :?:
The big Reward if you can answer that question is 5 stars. ⭐
Sorry, I'm low budget so it is best I can offer.
Thanks for reading. :)
RandyWalker, I read it wrong, I'm ready to get 5 stars
You just need to do Global NumSnapshotsToCheck
because Procedure DetectLeaks() does not see NumSnapshotsToCheck

I advice using EnableExplicit at the beginning of your code.
And then this error would not exist, the code would simply not compile
Sergey gets five stars!!!!
THANK YOU Sergey... ⭐⭐⭐⭐⭐

Re: Memory leak Detector (work in progress)

Posted: Tue Feb 10, 2026 8:04 pm
by Sergey
Thanks for stars and I was glad to help you

Re: [SOLVED]Memory leak Detector (work Complete) Sergey Wins!

Posted: Wed Feb 11, 2026 1:20 pm
by Caronte3D
As soon as this program started, it detects leaks constantly on many standard programs on my system, like: Chrome, Steam, PowerShell, QtWebEngineProcess...
I don't think all of them have memory leaks, so... maybe false positives?

Re: [SOLVED]Memory leak Detector (work Complete) Sergey Wins!

Posted: Wed Feb 11, 2026 7:31 pm
by Randy Walker
Caronte3D wrote: Wed Feb 11, 2026 1:20 pm As soon as this program started, it detects leaks constantly on many standard programs on my system, like: Chrome, Steam, PowerShell, QtWebEngineProcess...
I don't think all of them have memory leaks, so... maybe false positives?
Thanks Caronte3D,
I'm working to resolve that. I had it stripping those out but ChatGPT broke it for me. It can be so stupid working with code.
90% of the time it injects C code no matter how many times I tell it this is NOT C!!! It tries again and trashes some other piece of the code. Very frusrating.

Re: [SOLVED]Memory leak Detector (work Complete) Sergey Wins!

Posted: Wed Feb 11, 2026 9:09 pm
by infratec
Use Cursor AI instead of ChatGPT.

Re: [SOLVED]Memory leak Detector (work Complete) Sergey Wins!

Posted: Wed Feb 11, 2026 11:14 pm
by Randy Walker
infratec wrote: Wed Feb 11, 2026 9:09 pm Use Cursor AI instead of ChatGPT.
Not ready or desperate enough to trust AI installed on my PC but thanks for sharing the thought.

Re: [SOLVED]Memory leak Detector (work Complete) Sergey Wins!

Posted: Thu Feb 12, 2026 11:49 am
by Caronte3D
For me, Claude is the most powerful AI for coding; check it out if you haven't already.

Re: [SOLVED]Memory leak Detector (work Complete) Sergey Wins!

Posted: Thu Feb 12, 2026 11:46 pm
by Randy Walker
Caronte3D wrote: Thu Feb 12, 2026 11:49 am For me, Claude is the most powerful AI for coding; check it out if you haven't already.
You are correct Caronte3D :!: :!: :!:
BIG thanks to Claude we now have a severely improved memory leak detector.
See my next post :)

Re: [SEVERELY IMPROVED]Memory leak Detector (work Complete)

Posted: Thu Feb 12, 2026 11:49 pm
by Randy Walker
SEVERELY IMPROVED leak detector for all to enjoy, Compliments of ChatGPT and surgically enhanced by Claude to monitor by PID instead of name only:
[EDIT] -- I mistakenly applied #PB_Window_MaximizeGadget instead of #PB_Window_MinimizeGadget on my original post below but corrected that BooBoo below also.
{2nd EDIT] Added About MessageRquester with Creation andLicense Info.
[3rd EDIT] 02/25/26 -- Fixed a Unicode character drawing issue in the About text.

Code: Select all

; ==================================================
; Memory Leak Detector Settings Window (PureBasic)
; ==================================================

; --- Globals for detector configuration ---
Global SnapshotInterval = 3000       ; ms
Global MinGrowthMB = 3                ; MB
Global NumSnapshotsToCheck     = 3          ; consecutive snapshots to check
Global GrowthCountThreshold = 3       ; hits before logging
Global StartupGraceSnapshots = 3      ; ignore first N snapshots
Global NoGrowthResetSamples = 3       ; reset hits if no growth over this many snapshots
Global Omit$
Global SystemProcesses$

;--- Global Declarations ---
Global NewList CurrentProcs.s()    ; List of current process snapshots
Global NewMap ProcHistory.s()      ; Map: ProcID → snapshot history
Global NewMap ProcNames.s()        ; Map: ProcID → Process Name
Global NewMap PreviousMemory.q()   ; ProcID → last observed memory in bytes
Global NewMap GrowthHits.i()       ; ProcID → hit counter
Global NewMap NoGrowthCount.i()    ; ProcID → no-growth counter

; ; --- Gadget IDs ---
#GID_Text_SnapshotInterval       = 11
#GID_Text_MinGrowthMB            = 12
#GID_Text_GrowthCountThreshold   = 13
#GID_Text_StartupGraceSnapshots  = 14
#GID_Text_NoGrowthResetSamples   = 15
#GID_Text_NumSnapshotsToCheck    = 16

#GID_Spin_SnapshotInterval       = 101
#GID_Spin_MinGrowthMB            = 102
#GID_Spin_GrowthCountThreshold   = 103
#GID_Spin_StartupGraceSnapshots  = 104
#GID_Spin_NoGrowthResetSamples   = 105
#GID_Spin_NumSnapshotsToCheck    = 110
#GID_ApplyButton                 = 201
#GID_CancelButton                = 202
#GID_HelpButton                  = 203

; --- Procedure: Capture current processes ---
Procedure CaptureProcesses()
  Program$ = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
  ; Fixed format: ensure single spaces between fields
  param$   = "-NoProfile -Command " + Chr(34) + "Get-Process | ForEach-Object { '{0} {1} {2}' -f $_.Id, $_.Name, [math]::Round($_.WS/1MB,1) }" + Chr(34)

  ClearList(CurrentProcs.s())

  EXE = RunProgram(Program$, param$, "", #PB_Program_Open | #PB_Program_Read | #PB_Program_Hide)

  If EXE
    While ProgramRunning(EXE)
      If AvailableProgramOutput(EXE)
        OutputLine$ = Trim(ReadProgramString(EXE))
        If OutputLine$ <> ""
          AddElement(CurrentProcs.s())
          CurrentProcs.s() = OutputLine$
          ; Debug output to see captured processes
          If FindString(UCase(OutputLine$), "LEAKY")
            Debug "CAPTURED: " + OutputLine$
          EndIf
        EndIf
      EndIf
    Wend
    CloseProgram(EXE)
  EndIf
EndProcedure

Procedure GetOmitProcesses()
  ; Build a list of system processes and low-memory processes to omit from leak detection
  ; These are processes that are unlikely to leak or are critical system components
  
  ; Common system processes to ignore (by name, not ProcID)
  SystemProcesses$ = "System Idle Process|System|Registry|smss|csrss|wininit|services|lsass|svchost|"
  SystemProcesses$ + "dwm|winlogon|fontdrvhost|WUDFHost|spoolsv|RuntimeBroker|SearchIndexer|"
  SystemProcesses$ + "audiodg|conhost|dllhost|taskhostw|explorer|"
  
  Debug "System processes to omit: " + SystemProcesses$
  
  ; You can also add low-memory threshold filtering here if needed
  ; For example, processes under 10MB are unlikely to cause issues
  
EndProcedure

GetOmitProcesses()

; --- Open Settings Window ---
If OpenWindow(0, 100, 100, 320, 250, "Leak Detector Settings", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  ; Labels
  TextGadget(#GID_Text_SnapshotInterval, 40, 10, 180, 20, "Snapshot Interval (MilliSeconds):")
  TextGadget(#GID_Text_MinGrowthMB, 10, 40, 210, 20, "Minimum Growth (MegaBytes) / sample:")
  TextGadget(#GID_Text_StartupGraceSnapshots, 10, 70, 180, 20, "Startup Grace Snapshots:")
  TextGadget(#GID_Text_NoGrowthResetSamples, 10, 100, 180, 20, "No-Growth Reset Samples:")
  TextGadget(#GID_Text_NumSnapshotsToCheck, 10, 130, 200, 20, "Samples to check before loggiong:")
  TextGadget(#GID_Text_GrowthCountThreshold, 10, 160, 180, 20, "Growth Count Threshold:")

  ; SpinGadgets with defaults (all values inside min/max)
  SpinGadget(#GID_Spin_SnapshotInterval, 200, 10, 100, 20, 1000, 60000,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_MinGrowthMB, 200, 40, 100, 20, 1, 100,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_StartupGraceSnapshots, 200, 70, 100, 20, 0, 10,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_NoGrowthResetSamples, 200, 100, 100, 20, 0, 20,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_NumSnapshotsToCheck, 200, 130, 100, 20, 1, 20,#PB_Spin_Numeric)
  SpinGadget(#GID_Spin_GrowthCountThreshold, 200, 160, 100, 20, 1, 20,#PB_Spin_Numeric)


  ; Buttons
  ButtonGadget(#GID_ApplyButton, 50, 220, 80, 20, "Apply")
  ButtonGadget(#GID_CancelButton, 150, 220, 80, 20, "Cancel/Close")
  ButtonGadget(#GID_HelpButton, 100, 180, 80, 20, "Help")
  
  SetGadgetText(#GID_Spin_SnapshotInterval,Str(SnapshotInterval))
  SetGadgetText(#GID_Spin_MinGrowthMB,Str(MinGrowthMB))
  SetGadgetText(#GID_Spin_GrowthCountThreshold,Str(GrowthCountThreshold))
  SetGadgetText(#GID_Spin_StartupGraceSnapshots,Str(StartupGraceSnapshots))
  SetGadgetText(#GID_Spin_NoGrowthResetSamples,Str(NoGrowthResetSamples))
  SetGadgetState(#GID_Spin_NumSnapshotsToCheck, 3)
EndIf

Procedure ShowHelp()
  helpText$ =  "===============================================" + #CRLF$
  helpText$ + "     MEMORY LEAK DETECTOR - SETTINGS HELP" + #CRLF$
  helpText$ + "===============================================" + #CRLF$ + #CRLF$
  
  helpText$ + "SNAPSHOT INTERVAL (milliseconds)" + #CRLF$
  helpText$ + "  How often to check process memory usage." + #CRLF$
  helpText$ + "  Default: 3000 ms (3 seconds)" + #CRLF$ + #CRLF$
  
  helpText$ + "MINIMUM GROWTH (megabytes)" + #CRLF$
  helpText$ + "  Memory increase required per snapshot to count as growth." + #CRLF$
  helpText$ + "  Default: 3 MB" + #CRLF$ + #CRLF$
  
  helpText$ + "SAMPLES TO CHECK" + #CRLF$
  helpText$ + "  How many recent snapshots to examine for leaks." + #CRLF$
  helpText$ + "  Default: 3 snapshots" + #CRLF$ + #CRLF$
  
  helpText$ + "GROWTH COUNT THRESHOLD" + #CRLF$
  helpText$ + "  How many times growth must be detected before logging." + #CRLF$
  helpText$ + "  Prevents false positives from temporary spikes." + #CRLF$
  helpText$ + "  Default: 3 hits" + #CRLF$ + #CRLF$
  
  helpText$ + "STARTUP GRACE SNAPSHOTS" + #CRLF$
  helpText$ + "  Ignore this many initial snapshots after process starts." + #CRLF$
  helpText$ + "  Programs naturally grow during startup." + #CRLF$
  helpText$ + "  Default: 3 snapshots" + #CRLF$ + #CRLF$
  
  helpText$ + "NO-GROWTH RESET SAMPLES" + #CRLF$
  helpText$ + "  Reset hit counter if no growth for this many snapshots." + #CRLF$
  helpText$ + "  Prevents logging when growth has stopped." + #CRLF$
  helpText$ + "  Default: 3 snapshots" + #CRLF$ + #CRLF$
  
  helpText$ + "-----------------------------------------------" + #CRLF$
  helpText$ + "EXAMPLE:" + #CRLF$
  helpText$ + "-----------------------------------------------" + #CRLF$
  helpText$ + "Process memory: 100 -> 105 -> 112 -> 120 MB" + #CRLF$
  helpText$ + "Settings: MinGrowth=5 MB, SamplesToCheck=3" + #CRLF$ + #CRLF$
  helpText$ + "Result: LEAK DETECTED" + #CRLF$
  helpText$ + "  (3 snapshots with >= 5 MB growth each)" + #CRLF$

  MessageRequester("Memory Leak Detector Help", helpText$, #PB_MessageRequester_Ok)
EndProcedure

CaptureProcesses()

; ==================================================
; Event Loop (your canonical style)
; ==================================================
Repeat
  Event = WaitWindowEvent()
  
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        
        Case #GID_ApplyButton
          ; Read values from gadgets and update globals
          SnapshotInterval        = GetGadgetState(#GID_Spin_SnapshotInterval)
          MinGrowthMB             = GetGadgetState(#GID_Spin_MinGrowthMB)
          GrowthCountThreshold    = GetGadgetState(#GID_Spin_GrowthCountThreshold)
          StartupGraceSnapshots   = GetGadgetState(#GID_Spin_StartupGraceSnapshots)
          NoGrowthResetSamples    = GetGadgetState(#GID_Spin_NoGrowthResetSamples)
          NumSnapshotsToCheck     = GetGadgetState(#GID_Spin_NumSnapshotsToCheck)

          
          ; Feedback
          Debug "Settings Applied:"
          Debug "SnapshotInterval=" + Str(SnapshotInterval)
          Debug "MinGrowthMB=" + Str(MinGrowthMB)
          Debug "GrowthCountThreshold=" + Str(GrowthCountThreshold)
          Debug "StartupGraceSnapshots=" + Str(StartupGraceSnapshots)
          Debug "NoGrowthResetSamples=" + Str(NoGrowthResetSamples)
          Debug "GID_Spin_NumSnapshotsToCheck=" + Str(NumSnapshotsToCheck)
          Break
          
        Case #GID_CancelButton
          End
          
        Case #GID_HelpButton
          ShowHelp()
      EndSelect
  EndSelect
Until event = #PB_Event_CloseWindow

CloseWindow(0)

;==================================================
;PureBasic Memory Leak Detector (GUI + Logging)
;==================================================

; --- Procedure: Update ProcHistory Map ---
Procedure UpdateHistory()
  ForEach CurrentProcs.s()
    ProcID$ = StringField(CurrentProcs.s(), 1, " ")
    Name$   = StringField(CurrentProcs.s(), 2, " ")
    WS_MB$  = StringField(CurrentProcs.s(), 3, " ")

    ; Store process name for this ProcID
    ProcNames(ProcID$) = Name$

    If ProcHistory(ProcID$) = ""
      ProcHistory(ProcID$) = WS_MB$
    Else
      ProcHistory(ProcID$) + "_" + WS_MB$ ; append new snapshot
    EndIf
    
    ; Debug LeakyApp tracking
    If FindString(UCase(Name$), "LEAKY")
      Debug "HISTORY UPDATE: " + Name$ + " (PID: " + ProcID$ + ") = " + ProcHistory(ProcID$)
    EndIf
  Next
EndProcedure

; --- Procedure: Continuous logging of growing processes ---
Procedure LogLeak(ProcID$, Name$, Snapshots$)
  logFile$ = "MemoryLeakLog.txt"
  If OpenFile(1, logFile$, #PB_File_Append)
    currentTime$ = FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss", Date())
    line$ = Name$ + " (PID: " + ProcID$ + ") <Memory leak detected> | History: " + Snapshots$
    WriteStringN(1, currentTime$ + " " + line$)
    CloseFile(1)
  Else
    Debug "Failed to write to log file!"
  EndIf
EndProcedure

Debug GetCurrentDirectory()

; --- Procedure: Detect memory leaks ---
Procedure DetectLeaks()
  AddGadgetItem(0, -1, "----- Memory Leak Check -----")

  ForEach ProcHistory()
    ProcID$ = MapKey(ProcHistory())
    Name$ = ProcNames(ProcID$)
    Snapshots$ = ProcHistory()

    NumFields = CountString(Snapshots$, "_") + 1

    ; --- Startup grace + minimum history ---
    If NumFields <= StartupGraceSnapshots
      Continue
    EndIf

    If NumFields < NumSnapshotsToCheck
      Continue
    EndIf

    ; --- Examine last NumSnapshotsToCheck samples ---
    StartIndex = NumFields - NumSnapshotsToCheck + 1
    PreviousVal.d = ValD(StringField(Snapshots$, StartIndex, "_"))

    HitsThisPass = 0
    GrowthSeen   = #False

    i = StartIndex + 1
    While i <= NumFields
      CurrentVal.d  = ValD(StringField(Snapshots$, i, "_"))

      If (CurrentVal.d - PreviousVal.d) >= MinGrowthMB
        HitsThisPass + 1
        GrowthSeen = #True
      EndIf

      PreviousVal.d = CurrentVal.d
      i + 1
    Wend

    ; --- Update counters ---
    If GrowthSeen
      GrowthHits(ProcID$) + 1
      NoGrowthCount(ProcID$) = 0
    Else
      NoGrowthCount(ProcID$) + 1
    EndIf
    
    ; --- Reset if no growth for too long ---
    If NoGrowthCount(ProcID$) >= NoGrowthResetSamples
      GrowthHits(ProcID$) = 0
      NoGrowthCount(ProcID$) = 0
    EndIf
    
    ; --- Log only after sustained growth ---
    If GrowthHits(ProcID$) >= GrowthCountThreshold And (FindString(SystemProcesses$, Name$ + "|") = 0)
      Debug "***** TRIGGER REACHED *****"
      line$ = Name$ + " (PID: " + ProcID$ + ") <Memory leak detected> | History: " + Snapshots$
      AddGadgetItem(0, -1, line$)
      Debug line$
      LogLeak(ProcID$, Name$, Snapshots$)
      ; keep logging if it continues
      GrowthHits(ProcID$) = 0
    EndIf
  Next
EndProcedure

; --- Open GUI ---
If OpenWindow(0, 0, 0, 600, 400, "Memory Leak Detector", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  EditorGadget(0, 8, 8, 584, 384, #PB_Editor_ReadOnly | #PB_Editor_WordWrap)
  AddWindowTimer(0, 123, SnapshotInterval)
  AddGadgetItem(0, -1, "Memory Leak Detector Started - Tracking by Process ID")
EndIf

; --- Main Event Loop ---
Repeat
  event = WaitWindowEvent()
  Select event
    Case #PB_Event_Timer
      If EventTimer() = 123
        CaptureProcesses()
        UpdateHistory()
        DetectLeaks()
      EndIf
  EndSelect
Until Event = #PB_Event_CloseWindow
THis is going up on GitHub for sure.