Classes/MemoryManager.ps1
|
#Requires -Version 5.1 class MemoryManager { [int]$MaxMemoryMB [int]$CheckInterval [int]$CheckCounter [System.Diagnostics.Stopwatch]$Timer [long]$PeakMemoryUsage [bool]$Disposed [string]$CorrelationId # Constructor with CorrelationId (new 3-argument version) MemoryManager([int]$maxMemoryMB, [int]$checkInterval, [string]$correlationId) { $this.MaxMemoryMB = $maxMemoryMB $this.CheckInterval = $checkInterval $this.CheckCounter = 0 $this.Timer = [System.Diagnostics.Stopwatch]::StartNew() $this.PeakMemoryUsage = 0 $this.Disposed = $false $this.CorrelationId = if ($correlationId) { $correlationId } else { [System.Guid]::NewGuid().ToString() } } # Legacy constructor for backward compatibility (2-argument version) MemoryManager([int]$maxMemoryMB, [int]$checkInterval) { $this.MaxMemoryMB = $maxMemoryMB $this.CheckInterval = $checkInterval $this.CheckCounter = 0 $this.Timer = [System.Diagnostics.Stopwatch]::StartNew() $this.PeakMemoryUsage = 0 $this.Disposed = $false $this.CorrelationId = [System.Guid]::NewGuid().ToString() } [void]CheckMemoryUsage() { if ($this.Disposed) { return } $this.CheckCounter++ if ($this.CheckCounter -ge $this.CheckInterval) { $this.CheckCounter = 0 try { $currentProcess = Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id) $currentMemoryMB = [Math]::Round($currentProcess.WorkingSet64 / 1MB, 2) if ($currentMemoryMB -gt $this.PeakMemoryUsage) { $this.PeakMemoryUsage = $currentMemoryMB } if ($currentMemoryMB -gt $this.MaxMemoryMB) { Write-Warning "Memory usage threshold exceeded: $currentMemoryMB MB (Limit: $($this.MaxMemoryMB) MB) - CorrelationId: $($this.CorrelationId)" # Force aggressive garbage collection [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() [System.GC]::Collect() # Add memory pressure to encourage more aggressive cleanup [System.GC]::AddMemoryPressure(50MB) [System.GC]::RemoveMemoryPressure(50MB) # Check memory again after GC $postGCProcess = Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id) $postGCMemoryMB = [Math]::Round($postGCProcess.WorkingSet64 / 1MB, 2) Write-Verbose "Memory after garbage collection: $postGCMemoryMB MB (reduced by $([Math]::Round($currentMemoryMB - $postGCMemoryMB, 2)) MB) - CorrelationId: $($this.CorrelationId)" if ($postGCMemoryMB -gt $this.MaxMemoryMB) { Write-Warning "Memory usage still exceeds limit after garbage collection: $postGCMemoryMB MB. Consider reducing batch size or increasing memory limit. - CorrelationId: $($this.CorrelationId)" # Don't throw immediately - allow processing to continue with warning # throw "Memory usage still exceeds limit after garbage collection: $postGCMemoryMB MB" } } Write-Verbose "Current memory usage: $currentMemoryMB MB (Peak: $($this.PeakMemoryUsage) MB) - CorrelationId: $($this.CorrelationId)" } catch { Write-Warning "Failed to check memory usage: $($_.Exception.Message) - CorrelationId: $($this.CorrelationId)" } } } [long] GetPeakMemoryUsage() { return $this.PeakMemoryUsage } [void] Dispose() { if (-not $this.Disposed) { if ($this.Timer) { $this.Timer.Stop() $this.Timer = $null } $this.Disposed = $true } } } |