modules/Core/BackgroundProcessingOptimizer.psm1
|
# Background Processing Optimizer Module # Implements efficient API request batching, caching strategies, and resource management using namespace System.Management.Automation using namespace System.Collections.Generic using namespace System.Collections.Concurrent using namespace System.Threading # Request batch manager for efficient API usage class RequestBatchManager { [ConcurrentQueue[hashtable]] $PendingRequests [Timer] $BatchTimer [int] $BatchSize = 10 [int] $BatchIntervalMs = 2000 # 2 seconds [scriptblock] $BatchProcessor [bool] $IsProcessing = $false [object] $ProcessingLock = [object]::new() [hashtable] $BatchStats = @{ TotalBatches = 0 TotalRequests = 0 AverageBatchSize = 0 LastBatchTime = [DateTime]::MinValue } RequestBatchManager([scriptblock]$batchProcessor, [int]$batchSize = 10, [int]$intervalMs = 2000) { $this.PendingRequests = [ConcurrentQueue[hashtable]]::new() $this.BatchProcessor = $batchProcessor $this.BatchSize = $batchSize $this.BatchIntervalMs = $intervalMs # Create timer for periodic batch processing $this.BatchTimer = [Timer]::new( { $this.ProcessBatch() }, $null, $this.BatchIntervalMs, $this.BatchIntervalMs ) } [void] QueueRequest([hashtable]$request) { $request['QueuedAt'] = [DateTime]::UtcNow $this.PendingRequests.Enqueue($request) # Process immediately if batch is full if ($this.PendingRequests.Count -ge $this.BatchSize) { $this.ProcessBatch() } } [void] ProcessBatch() { lock ($this.ProcessingLock) { if ($this.IsProcessing -or $this.PendingRequests.Count -eq 0) { return } $this.IsProcessing = $true } try { $batch = [List[hashtable]]::new() $request = $null # Collect requests for batch processing $collected = 0 while ($collected -lt $this.BatchSize -and $this.PendingRequests.TryDequeue([ref]$request)) { $batch.Add($request) $collected++ } if ($batch.Count -gt 0) { # Group requests by type for efficient processing $groupedRequests = $this.GroupRequestsByType($batch) # Process each group foreach ($group in $groupedRequests.GetEnumerator()) { try { & $this.BatchProcessor $group.Key $group.Value } catch { Write-Warning "Batch processing error for type $($group.Key): $($_.Exception.Message)" } } # Update statistics $this.BatchStats.TotalBatches++ $this.BatchStats.TotalRequests += $batch.Count $this.BatchStats.AverageBatchSize = $this.BatchStats.TotalRequests / $this.BatchStats.TotalBatches $this.BatchStats.LastBatchTime = [DateTime]::UtcNow } } finally { lock ($this.ProcessingLock) { $this.IsProcessing = $false } } } [hashtable] GroupRequestsByType([List[hashtable]]$requests) { $groups = @{} foreach ($request in $requests) { $type = $request.ContainsKey('Type') ? $request.Type : 'default' if (-not $groups.ContainsKey($type)) { $groups[$type] = [List[hashtable]]::new() } $groups[$type].Add($request) } return $groups } [void] FlushPendingRequests() { if ($this.PendingRequests.Count -gt 0) { $this.ProcessBatch() } } [hashtable] GetStats() { return @{ PendingRequests = $this.PendingRequests.Count BatchSize = $this.BatchSize BatchIntervalMs = $this.BatchIntervalMs IsProcessing = $this.IsProcessing Statistics = $this.BatchStats.Clone() } } [void] Dispose() { if ($this.BatchTimer) { $this.BatchTimer.Dispose() } # Process any remaining requests $this.FlushPendingRequests() } } # Intelligent cache strategy manager class IntelligentCacheStrategy { [hashtable] $CacheStrategies = @{} [hashtable] $AccessPatterns = @{} [hashtable] $CacheMetrics = @{} [Timer] $AnalysisTimer [int] $AnalysisIntervalMs = 300000 # 5 minutes [double] $HitRateThreshold = 0.7 [int] $AccessCountThreshold = 10 IntelligentCacheStrategy() { $this.InitializeCacheStrategies() # Start periodic analysis $this.AnalysisTimer = [Timer]::new( { $this.AnalyzeCachePerformance() }, $null, $this.AnalysisIntervalMs, $this.AnalysisIntervalMs ) } [void] InitializeCacheStrategies() { $this.CacheStrategies = @{ "current_track" = @{ TTL = 30000 # 30 seconds Priority = "High" Strategy = "WriteThrough" MaxSize = 1 } "track_info" = @{ TTL = 3600000 # 1 hour Priority = "Medium" Strategy = "WriteBack" MaxSize = 100 } "lyrics" = @{ TTL = 2592000000 # 30 days Priority = "High" Strategy = "WriteThrough" MaxSize = 500 } "statistics" = @{ TTL = 86400000 # 24 hours Priority = "Medium" Strategy = "WriteBack" MaxSize = 50 } "user_playlists" = @{ TTL = 1800000 # 30 minutes Priority = "Low" Strategy = "WriteBack" MaxSize = 20 } } } [hashtable] GetCacheStrategy([string]$dataType) { if ($this.CacheStrategies.ContainsKey($dataType)) { return $this.CacheStrategies[$dataType] } # Return default strategy for unknown types return @{ TTL = 300000 # 5 minutes Priority = "Low" Strategy = "WriteThrough" MaxSize = 10 } } [void] RecordCacheAccess([string]$dataType, [string]$key, [bool]$hit) { $now = [DateTime]::UtcNow if (-not $this.AccessPatterns.ContainsKey($dataType)) { $this.AccessPatterns[$dataType] = @{} } if (-not $this.AccessPatterns[$dataType].ContainsKey($key)) { $this.AccessPatterns[$dataType][$key] = @{ AccessCount = 0 HitCount = 0 LastAccess = $now FirstAccess = $now } } $pattern = $this.AccessPatterns[$dataType][$key] $pattern.AccessCount++ $pattern.LastAccess = $now if ($hit) { $pattern.HitCount++ } # Update metrics if (-not $this.CacheMetrics.ContainsKey($dataType)) { $this.CacheMetrics[$dataType] = @{ TotalAccesses = 0 TotalHits = 0 HitRate = 0.0 LastUpdated = $now } } $metrics = $this.CacheMetrics[$dataType] $metrics.TotalAccesses++ if ($hit) { $metrics.TotalHits++ } $metrics.HitRate = $metrics.TotalHits / $metrics.TotalAccesses $metrics.LastUpdated = $now } [void] AnalyzeCachePerformance() { try { foreach ($dataType in $this.CacheStrategies.Keys) { $this.OptimizeCacheStrategy($dataType) } } catch { Write-Warning "Cache performance analysis error: $($_.Exception.Message)" } } [void] OptimizeCacheStrategy([string]$dataType) { if (-not $this.CacheMetrics.ContainsKey($dataType)) { return } $metrics = $this.CacheMetrics[$dataType] $strategy = $this.CacheStrategies[$dataType] $changed = $false # Adjust TTL based on hit rate if ($metrics.HitRate -gt 0.9 -and $metrics.TotalAccesses -gt $this.AccessCountThreshold) { # Very high hit rate - increase TTL $newTTL = [Math]::Min($strategy.TTL * 1.5, 86400000) # Max 24 hours if ($newTTL -ne $strategy.TTL) { $strategy.TTL = $newTTL $changed = $true } } elseif ($metrics.HitRate -lt 0.3 -and $metrics.TotalAccesses -gt $this.AccessCountThreshold) { # Low hit rate - decrease TTL $newTTL = [Math]::Max($strategy.TTL * 0.7, 30000) # Min 30 seconds if ($newTTL -ne $strategy.TTL) { $strategy.TTL = $newTTL $changed = $true } } # Adjust cache size based on access patterns if ($this.AccessPatterns.ContainsKey($dataType)) { $uniqueKeys = $this.AccessPatterns[$dataType].Keys.Count $currentMaxSize = $strategy.MaxSize if ($uniqueKeys -gt ($currentMaxSize * 0.8)) { # Increase cache size if we're using most of it $strategy.MaxSize = [Math]::Min($currentMaxSize * 1.2, 1000) $changed = $true } elseif ($uniqueKeys -lt ($currentMaxSize * 0.3)) { # Decrease cache size if we're not using much $strategy.MaxSize = [Math]::Max($currentMaxSize * 0.8, 10) $changed = $true } } if ($changed) { Write-Verbose "Optimized cache strategy for $dataType - TTL: $($strategy.TTL)ms, MaxSize: $($strategy.MaxSize)" } } [bool] ShouldPreload([string]$dataType, [string]$key) { if (-not $this.AccessPatterns.ContainsKey($dataType) -or -not $this.AccessPatterns[$dataType].ContainsKey($key)) { return $false } $pattern = $this.AccessPatterns[$dataType][$key] $timeSinceLastAccess = [DateTime]::UtcNow - $pattern.LastAccess # Preload if frequently accessed and recently used return $pattern.AccessCount -gt 5 -and $timeSinceLastAccess.TotalMinutes -lt 30 } [hashtable] GetCacheAnalytics() { $analytics = @{ Strategies = $this.CacheStrategies.Clone() Metrics = $this.CacheMetrics.Clone() TotalDataTypes = $this.CacheStrategies.Keys.Count OverallHitRate = 0.0 LastAnalysis = [DateTime]::UtcNow } # Calculate overall hit rate $totalAccesses = 0 $totalHits = 0 foreach ($metrics in $this.CacheMetrics.Values) { $totalAccesses += $metrics.TotalAccesses $totalHits += $metrics.TotalHits } if ($totalAccesses -gt 0) { $analytics.OverallHitRate = $totalHits / $totalAccesses } return $analytics } [void] Dispose() { if ($this.AnalysisTimer) { $this.AnalysisTimer.Dispose() } } } # Resource cleanup manager for memory and resource management class ResourceCleanupManager { [Timer] $CleanupTimer [int] $CleanupIntervalMs = 300000 # 5 minutes [hashtable] $ResourceTrackers = @{} [hashtable] $CleanupStrategies = @{} [int] $MemoryThresholdMB = 150 [int] $ForceGCThresholdMB = 200 [DateTime] $LastFullCleanup = [DateTime]::UtcNow [int] $FullCleanupIntervalMs = 1800000 # 30 minutes ResourceCleanupManager([int]$memoryThresholdMB = 150) { $this.MemoryThresholdMB = $memoryThresholdMB $this.ForceGCThresholdMB = $memoryThresholdMB + 50 $this.InitializeCleanupStrategies() # Start cleanup timer $this.CleanupTimer = [Timer]::new( { $this.PerformCleanup() }, $null, $this.CleanupIntervalMs, $this.CleanupIntervalMs ) } [void] InitializeCleanupStrategies() { $this.CleanupStrategies = @{ "cache" = @{ Priority = 1 Action = { param($manager) $manager.CleanupExpiredCache() } Threshold = 0.8 # Cleanup when 80% full } "statistics" = @{ Priority = 2 Action = { param($manager) $manager.CleanupOldStatistics() } Threshold = 0.9 # Cleanup when 90% full } "logs" = @{ Priority = 3 Action = { param($manager) $manager.CleanupOldLogs() } Threshold = 0.7 # Cleanup when 70% full } "temp_files" = @{ Priority = 4 Action = { param($manager) $manager.CleanupTempFiles() } Threshold = 0.6 # Cleanup when 60% full } } } [void] RegisterResourceTracker([string]$name, [scriptblock]$sizeCalculator, [scriptblock]$cleaner) { $this.ResourceTrackers[$name] = @{ SizeCalculator = $sizeCalculator Cleaner = $cleaner LastCleanup = [DateTime]::UtcNow TotalCleanups = 0 BytesCleaned = 0 } } [void] PerformCleanup() { try { $memoryUsage = $this.GetCurrentMemoryUsageMB() $shouldPerformFullCleanup = $this.ShouldPerformFullCleanup() Write-Verbose "Starting resource cleanup - Memory: $memoryUsage MB, Full cleanup: $shouldPerformFullCleanup" if ($memoryUsage -gt $this.MemoryThresholdMB -or $shouldPerformFullCleanup) { $this.PerformResourceCleanup($shouldPerformFullCleanup) } # Force garbage collection if memory usage is very high if ($memoryUsage -gt $this.ForceGCThresholdMB) { $this.ForceGarbageCollection() } } catch { Write-Warning "Resource cleanup error: $($_.Exception.Message)" } } [bool] ShouldPerformFullCleanup() { $timeSinceLastFullCleanup = [DateTime]::UtcNow - $this.LastFullCleanup return $timeSinceLastFullCleanup.TotalMilliseconds -gt $this.FullCleanupIntervalMs } [void] PerformResourceCleanup([bool]$fullCleanup) { $beforeMemory = $this.GetCurrentMemoryUsageMB() # Sort cleanup strategies by priority $sortedStrategies = $this.CleanupStrategies.GetEnumerator() | Sort-Object { $_.Value.Priority } foreach ($strategy in $sortedStrategies) { try { $name = $strategy.Key $config = $strategy.Value if ($fullCleanup -or $this.ShouldCleanupResource($name, $config.Threshold)) { Write-Verbose "Performing cleanup for resource: $name" & $config.Action $this } } catch { Write-Warning "Cleanup failed for $($strategy.Key): $($_.Exception.Message)" } } # Cleanup registered resource trackers foreach ($tracker in $this.ResourceTrackers.GetEnumerator()) { try { $name = $tracker.Key $config = $tracker.Value if ($fullCleanup -or $this.ShouldCleanupTracker($name)) { Write-Verbose "Performing cleanup for tracked resource: $name" $bytesCleaned = & $config.Cleaner $config.LastCleanup = [DateTime]::UtcNow $config.TotalCleanups++ $config.BytesCleaned += $bytesCleaned } } catch { Write-Warning "Tracked resource cleanup failed for $($tracker.Key): $($_.Exception.Message)" } } if ($fullCleanup) { $this.LastFullCleanup = [DateTime]::UtcNow } $afterMemory = $this.GetCurrentMemoryUsageMB() $memoryFreed = $beforeMemory - $afterMemory Write-Verbose "Resource cleanup completed - Memory freed: $memoryFreed MB" } [bool] ShouldCleanupResource([string]$name, [double]$threshold) { # This would integrate with actual resource monitoring # For now, return true if memory usage is above threshold $memoryUsage = $this.GetCurrentMemoryUsageMB() return ($memoryUsage / $this.MemoryThresholdMB) -gt $threshold } [bool] ShouldCleanupTracker([string]$name) { if (-not $this.ResourceTrackers.ContainsKey($name)) { return $false } $tracker = $this.ResourceTrackers[$name] $timeSinceLastCleanup = [DateTime]::UtcNow - $tracker.LastCleanup # Cleanup if it's been more than 10 minutes since last cleanup return $timeSinceLastCleanup.TotalMinutes -gt 10 } [void] CleanupExpiredCache() { # This would integrate with the cache manager Write-Verbose "Cleaning up expired cache entries" # Placeholder implementation [GC]::Collect(0, [GCCollectionMode]::Optimized) } [void] CleanupOldStatistics() { # This would integrate with the statistics engine Write-Verbose "Cleaning up old statistics data" # Placeholder implementation - would clean statistics older than retention period } [void] CleanupOldLogs() { Write-Verbose "Cleaning up old log files" try { $logDir = Join-Path $env:APPDATA "SpotifyCLI\Logs" if (Test-Path $logDir) { $cutoffDate = [DateTime]::UtcNow.AddDays(-7) # Keep logs for 7 days $oldLogs = Get-ChildItem -Path $logDir -Filter "*.log" | Where-Object { $_.LastWriteTime -lt $cutoffDate } foreach ($log in $oldLogs) { Remove-Item $log.FullName -Force -ErrorAction SilentlyContinue } } } catch { Write-Warning "Failed to cleanup old logs: $($_.Exception.Message)" } } [void] CleanupTempFiles() { Write-Verbose "Cleaning up temporary files" try { $tempDir = Join-Path $env:APPDATA "SpotifyCLI\Temp" if (Test-Path $tempDir) { $cutoffDate = [DateTime]::UtcNow.AddHours(-1) # Keep temp files for 1 hour $oldTempFiles = Get-ChildItem -Path $tempDir | Where-Object { $_.LastWriteTime -lt $cutoffDate } foreach ($file in $oldTempFiles) { Remove-Item $file.FullName -Force -Recurse -ErrorAction SilentlyContinue } } } catch { Write-Warning "Failed to cleanup temp files: $($_.Exception.Message)" } } [void] ForceGarbageCollection() { Write-Verbose "Forcing garbage collection due to high memory usage" try { $beforeMemory = $this.GetCurrentMemoryUsageMB() # Force full garbage collection [GC]::Collect() [GC]::WaitForPendingFinalizers() [GC]::Collect() $afterMemory = $this.GetCurrentMemoryUsageMB() $memoryFreed = $beforeMemory - $afterMemory Write-Verbose "Garbage collection completed - Memory freed: $memoryFreed MB" } catch { Write-Warning "Garbage collection failed: $($_.Exception.Message)" } } [double] GetCurrentMemoryUsageMB() { try { $process = Get-Process -Id $PID return [Math]::Round($process.WorkingSet64 / 1MB, 2) } catch { return 0 } } [hashtable] GetResourceStats() { $stats = @{ CurrentMemoryMB = $this.GetCurrentMemoryUsageMB() MemoryThresholdMB = $this.MemoryThresholdMB ForceGCThresholdMB = $this.ForceGCThresholdMB LastFullCleanup = $this.LastFullCleanup CleanupIntervalMs = $this.CleanupIntervalMs TrackedResources = @{} } foreach ($tracker in $this.ResourceTrackers.GetEnumerator()) { $stats.TrackedResources[$tracker.Key] = @{ LastCleanup = $tracker.Value.LastCleanup TotalCleanups = $tracker.Value.TotalCleanups BytesCleaned = $tracker.Value.BytesCleaned } } return $stats } [void] Dispose() { if ($this.CleanupTimer) { $this.CleanupTimer.Dispose() } # Perform final cleanup $this.PerformResourceCleanup($true) } } # Background processing coordinator class BackgroundProcessingCoordinator { [RequestBatchManager] $BatchManager [IntelligentCacheStrategy] $CacheStrategy [ResourceCleanupManager] $CleanupManager [bool] $IsInitialized = $false [hashtable] $Configuration = @{} BackgroundProcessingCoordinator([hashtable]$config) { $this.Configuration = $config $this.Initialize() } [void] Initialize() { try { # Initialize batch manager $batchProcessor = { param([string]$requestType, [List[hashtable]]$requests) $this.ProcessRequestBatch($requestType, $requests) } $batchSize = $this.Configuration.ContainsKey('BatchSize') ? $this.Configuration.BatchSize : 10 $batchInterval = $this.Configuration.ContainsKey('BatchIntervalMs') ? $this.Configuration.BatchIntervalMs : 2000 $this.BatchManager = [RequestBatchManager]::new($batchProcessor, $batchSize, $batchInterval) # Initialize cache strategy $this.CacheStrategy = [IntelligentCacheStrategy]::new() # Initialize cleanup manager $memoryThreshold = $this.Configuration.ContainsKey('MemoryThresholdMB') ? $this.Configuration.MemoryThresholdMB : 150 $this.CleanupManager = [ResourceCleanupManager]::new($memoryThreshold) $this.IsInitialized = $true Write-Verbose "Background processing coordinator initialized successfully" } catch { Write-Error "Failed to initialize background processing coordinator: $($_.Exception.Message)" throw } } [void] ProcessRequestBatch([string]$requestType, [List[hashtable]]$requests) { try { Write-Verbose "Processing batch of $($requests.Count) requests of type: $requestType" switch ($requestType) { "statistics" { $this.ProcessStatisticsBatch($requests) } "lyrics" { $this.ProcessLyricsBatch($requests) } "track_info" { $this.ProcessTrackInfoBatch($requests) } default { Write-Warning "Unknown request type for batch processing: $requestType" } } } catch { Write-Warning "Batch processing error for type $requestType : $($_.Exception.Message)" } } [void] ProcessStatisticsBatch([List[hashtable]]$requests) { # Group statistics requests by operation type $operations = @{} foreach ($request in $requests) { $operation = $request.ContainsKey('Operation') ? $request.Operation : 'record' if (-not $operations.ContainsKey($operation)) { $operations[$operation] = [List[hashtable]]::new() } $operations[$operation].Add($request) } # Process each operation type efficiently foreach ($operation in $operations.GetEnumerator()) { switch ($operation.Key) { "record" { # Batch record multiple playback events $this.BatchRecordPlaybackEvents($operation.Value) } "aggregate" { # Batch aggregate statistics calculations $this.BatchAggregateStatistics($operation.Value) } } } } [void] ProcessLyricsBatch([List[hashtable]]$requests) { # Group lyrics requests by provider to minimize API calls $providers = @{} foreach ($request in $requests) { $provider = $request.ContainsKey('Provider') ? $request.Provider : 'genius' if (-not $providers.ContainsKey($provider)) { $providers[$provider] = [List[hashtable]]::new() } $providers[$provider].Add($request) } # Process each provider's requests in batch foreach ($provider in $providers.GetEnumerator()) { $this.BatchFetchLyrics($provider.Key, $provider.Value) } } [void] ProcessTrackInfoBatch([List[hashtable]]$requests) { # Extract unique track IDs to avoid duplicate API calls $uniqueTrackIds = [HashSet[string]]::new() foreach ($request in $requests) { if ($request.ContainsKey('TrackId')) { $uniqueTrackIds.Add($request.TrackId) | Out-Null } } # Batch fetch track information if ($uniqueTrackIds.Count -gt 0) { $this.BatchFetchTrackInfo($uniqueTrackIds.ToArray()) } } [void] BatchRecordPlaybackEvents([List[hashtable]]$events) { # This would integrate with the statistics engine Write-Verbose "Batch recording $($events.Count) playback events" # Placeholder implementation foreach ($event in $events) { # Record event efficiently } } [void] BatchAggregateStatistics([List[hashtable]]$requests) { # This would integrate with the statistics engine Write-Verbose "Batch aggregating statistics for $($requests.Count) requests" # Placeholder implementation } [void] BatchFetchLyrics([string]$provider, [List[hashtable]]$requests) { # This would integrate with the lyrics engine Write-Verbose "Batch fetching lyrics from $provider for $($requests.Count) tracks" # Placeholder implementation foreach ($request in $requests) { # Fetch lyrics efficiently } } [void] BatchFetchTrackInfo([string[]]$trackIds) { # This would integrate with the Spotify API client Write-Verbose "Batch fetching track info for $($trackIds.Count) tracks" # Spotify API supports fetching up to 50 tracks at once $batchSize = 50 for ($i = 0; $i -lt $trackIds.Count; $i += $batchSize) { $batch = $trackIds[$i..([Math]::Min($i + $batchSize - 1, $trackIds.Count - 1))] # Make batch API call } } [void] QueueRequest([hashtable]$request) { if (-not $this.IsInitialized) { throw [System.InvalidOperationException]::new("Background processing coordinator not initialized") } $this.BatchManager.QueueRequest($request) } [void] RecordCacheAccess([string]$dataType, [string]$key, [bool]$hit) { if ($this.CacheStrategy) { $this.CacheStrategy.RecordCacheAccess($dataType, $key, $hit) } } [hashtable] GetCacheStrategy([string]$dataType) { if ($this.CacheStrategy) { return $this.CacheStrategy.GetCacheStrategy($dataType) } return @{} } [hashtable] GetPerformanceStats() { $stats = @{ IsInitialized = $this.IsInitialized Configuration = $this.Configuration.Clone() } if ($this.BatchManager) { $stats['BatchManager'] = $this.BatchManager.GetStats() } if ($this.CacheStrategy) { $stats['CacheStrategy'] = $this.CacheStrategy.GetCacheAnalytics() } if ($this.CleanupManager) { $stats['ResourceCleanup'] = $this.CleanupManager.GetResourceStats() } return $stats } [void] FlushPendingOperations() { if ($this.BatchManager) { $this.BatchManager.FlushPendingRequests() } } [void] Dispose() { try { if ($this.BatchManager) { $this.BatchManager.Dispose() } if ($this.CacheStrategy) { $this.CacheStrategy.Dispose() } if ($this.CleanupManager) { $this.CleanupManager.Dispose() } Write-Verbose "Background processing coordinator disposed successfully" } catch { Write-Warning "Error disposing background processing coordinator: $($_.Exception.Message)" } } } # Export classes and functions Export-ModuleMember -Function @() -Variable @() |