/**
* Multi-Folder Monitoring Workflow
*
* Extends the monitoring system to handle multiple folders with
* dynamic folder management and event aggregation.
*/
import {
MonitoringWorkflow,
WatchingOptions,
WatchingResult,
WatchingStatus,
WatchingError,
FileWatchEvent,
SystemHealthResult,
HealthStatus
} from './index.js';
import { ILoggingService } from '../../di/interfaces.js';
import { IFolderManager, ResolvedFolderConfig } from '../../domain/folders/index.js';
import { getSupportedExtensions } from '../../domain/files/supported-extensions.js';
/**
* Multi-folder monitoring workflow interface
*/
export interface IMultiFolderMonitoringWorkflow {
/**
* Start monitoring all configured folders
*/
startMonitoringAllFolders(options?: MultiFolderWatchingOptions): Promise<MultiFolderWatchingResult>;
/**
* Start monitoring a specific folder
*/
startMonitoringFolder(folderPath: string, options?: MultiFolderWatchingOptions): Promise<FolderWatchingResult>;
/**
* Stop monitoring all folders
*/
stopMonitoringAllFolders(): Promise<void>;
/**
* Stop monitoring a specific folder
*/
stopMonitoringFolder(folderPath: string): Promise<void>;
/**
* Get monitoring status for all folders
*/
getAllFoldersStatus(): Promise<MultiFolderWatchingStatus>;
/**
* Get monitoring status for a specific folder
*/
getFolderStatus(folderPath: string): Promise<FolderWatchingStatus>;
/**
* Handle configuration changes (add/remove folders)
*/
handleConfigurationChange(): Promise<void>;
/**
* Get aggregated file events across all folders
*/
getAggregatedEvents(since?: Date, limit?: number): Promise<AggregatedFileEvent[]>;
/**
* Get system health across all monitored folders
*/
getMultiFolderHealth(): Promise<MultiFolderHealthResult>;
}
/**
* Multi-folder watching options
*/
export interface MultiFolderWatchingOptions {
/** Base watching options */
baseOptions?: WatchingOptions;
/** Maximum concurrent folder watchers */
maxConcurrentWatchers?: number;
/** Whether to continue on folder errors */
continueOnError?: boolean;
/** Folder paths to include (if not specified, includes all) */
includeFolders?: string[];
/** Folder paths to exclude */
excludeFolders?: string[];
/** Enable event aggregation across folders */
enableEventAggregation?: boolean;
/** Event aggregation window in milliseconds */
eventAggregationWindow?: number;
}
/**
* Result for multi-folder watching operation
*/
export interface MultiFolderWatchingResult {
/** Overall success status */
success: boolean;
/** Results for each folder */
folderResults: FolderWatchingResult[];
/** Total processing time */
totalProcessingTime: number;
/** System-wide errors */
systemErrors: string[];
/** Number of successfully started watchers */
activeWatchers: number;
}
/**
* Result for single folder watching
*/
export interface FolderWatchingResult {
/** Folder name */
folderName: string;
/** Folder path */
folderPath: string;
/** Success status for this folder */
success: boolean;
/** Basic watching result */
result?: WatchingResult;
/** Folder-specific error if failed */
error?: string;
/** Folder-specific settings used */
settingsUsed: FolderWatchingSettings;
}
/**
* Multi-folder watching status
*/
export interface MultiFolderWatchingStatus {
/** Whether any folders are being monitored */
isActive: boolean;
/** Total active watchers */
activeWatchers: number;
/** Status for each folder */
folderStatuses: FolderWatchingStatus[];
/** Aggregated statistics */
aggregatedStats: AggregatedWatchingStats;
/** System-wide monitoring health */
systemHealth: HealthStatus;
}
/**
* Single folder watching status
*/
export interface FolderWatchingStatus {
/** Folder name */
folderName: string;
/** Folder path */
folderPath: string;
/** Whether this folder is being monitored */
isActive: boolean;
/** Basic watching status */
status?: WatchingStatus;
/** Settings applied to this folder */
settings: FolderWatchingSettings;
/** Folder health status */
health: HealthStatus;
}
/**
* Settings applied to folder watching
*/
export interface FolderWatchingSettings {
/** Include file types */
includeFileTypes: string[];
/** Exclude patterns (folder excludes + base excludes) */
excludePatterns: string[];
/** Debounce time in milliseconds */
debounceMs: number;
/** Enable batch processing */
enableBatchProcessing: boolean;
/** Batch size */
batchSize: number;
/** Batch timeout */
batchTimeoutMs: number;
}
/**
* Aggregated watching statistics
*/
export interface AggregatedWatchingStats {
/** Total events processed across all folders */
totalEventsProcessed: number;
/** Events processed in last hour */
eventsLastHour: number;
/** Events processed in last 24 hours */
eventsLast24Hours: number;
/** Average processing time per event */
averageProcessingTime: number;
/** Error rate across all folders */
errorRate: number;
/** Total queued events across all folders */
totalQueuedEvents: number;
/** Most active folder */
mostActiveFolder?: string;
}
/**
* Aggregated file event with folder attribution
*/
export interface AggregatedFileEvent extends FileWatchEvent {
/** Name of the folder this event came from */
folderName: string;
/** Resolved path of the folder */
folderPath: string;
/** Processing time for this event */
processingTime?: number;
/** Event batch ID if part of batch processing */
batchId?: string;
}
/**
* Multi-folder health result
*/
export interface MultiFolderHealthResult {
/** Overall health status */
overallHealth: HealthStatus;
/** Health status per folder */
folderHealth: FolderHealthStatus[];
/** System resource usage */
systemResources: SystemResourceUsage;
/** Monitoring performance metrics */
monitoringMetrics: MonitoringMetrics;
/** Health recommendations */
recommendations: HealthRecommendation[];
/** Timestamp of health check */
timestamp: Date;
}
/**
* Health status for a single folder
*/
export interface FolderHealthStatus {
/** Folder name */
folderName: string;
/** Folder path */
folderPath: string;
/** Health status */
health: HealthStatus;
/** Issues detected */
issues: FolderHealthIssue[];
/** Last health check time */
lastChecked: Date;
}
/**
* Health issue for a folder
*/
export interface FolderHealthIssue {
/** Issue type */
type: 'watcher_inactive' | 'high_error_rate' | 'performance_degraded' | 'queue_backlog' | 'permission_denied';
/** Issue severity */
severity: 'low' | 'medium' | 'high' | 'critical';
/** Issue description */
description: string;
/** Suggested resolution */
resolution?: string;
/** Whether issue can be auto-resolved */
autoResolvable: boolean;
}
/**
* System resource usage
*/
export interface SystemResourceUsage {
/** Memory usage by monitoring system */
memoryUsage: number;
/** CPU usage by monitoring system */
cpuUsage: number;
/** Number of open file handles */
fileHandles: number;
/** Network usage if applicable */
networkUsage?: number;
}
/**
* Monitoring performance metrics
*/
export interface MonitoringMetrics {
/** Average event processing time */
averageEventProcessingTime: number;
/** Peak event processing time */
peakEventProcessingTime: number;
/** Events processed per second */
eventsPerSecond: number;
/** Watcher restart frequency */
watcherRestartFrequency: number;
/** Memory usage trend */
memoryTrend: 'increasing' | 'stable' | 'decreasing';
}
/**
* Health recommendation
*/
export interface HealthRecommendation {
/** Priority level */
priority: 'low' | 'medium' | 'high' | 'critical';
/** Recommendation category */
category: 'performance' | 'reliability' | 'resource_usage' | 'configuration';
/** Recommendation title */
title: string;
/** Detailed description */
description: string;
/** Suggested action */
action: string;
/** Whether action can be automated */
automated: boolean;
/** Affected folders */
affectedFolders?: string[];
}
/**
* Multi-folder monitoring workflow implementation
*/
export class MultiFolderMonitoringWorkflow implements IMultiFolderMonitoringWorkflow {
private folderWatchers: Map<string, string> = new Map(); // folderPath -> watchId
private folderStatuses: Map<string, FolderWatchingStatus> = new Map();
private getFolderName(folderPath: string): string {
return folderPath.split('/').pop() || folderPath;
}
private eventAggregator: FileEventAggregator;
private healthMonitor: FolderHealthMonitor;
constructor(
private folderManager: IFolderManager,
private monitoringWorkflow: MonitoringWorkflow,
private loggingService: ILoggingService
) {
this.eventAggregator = new FileEventAggregator(loggingService);
this.healthMonitor = new FolderHealthMonitor(loggingService);
}
async startMonitoringAllFolders(options: MultiFolderWatchingOptions = {}): Promise<MultiFolderWatchingResult> {
const startTime = Date.now();
this.loggingService.info('Starting multi-folder monitoring');
// Get all folders to monitor
const allFolders = await this.folderManager.getFolders();
const foldersToMonitor = this.filterFolders(allFolders, options);
this.loggingService.info(`Monitoring ${foldersToMonitor.length} folders`, {
folderPaths: foldersToMonitor.map(f => f.path)
});
// Initialize status tracking
this.initializeStatusTracking(foldersToMonitor);
const folderResults: FolderWatchingResult[] = [];
const systemErrors: string[] = [];
let activeWatchers = 0;
try {
// Process folders in batches to respect concurrency limits
const maxConcurrent = options.maxConcurrentWatchers || 5;
for (let i = 0; i < foldersToMonitor.length; i += maxConcurrent) {
const batch = foldersToMonitor.slice(i, i + maxConcurrent);
const batchPromises = batch.map(folder =>
this.startFolderMonitoring(folder, options.baseOptions || {})
);
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach((result, j) => {
const folder = batch[j];
if (!folder) return;
if (result.status === 'fulfilled') {
folderResults.push(result.value);
if (result.value.success) {
activeWatchers++;
}
} else {
const errorResult: FolderWatchingResult = {
folderName: this.getFolderName(folder.path),
folderPath: folder.resolvedPath,
success: false,
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
settingsUsed: this.createFolderSettings(folder, options.baseOptions || {})
};
folderResults.push(errorResult);
if (!options.continueOnError) {
systemErrors.push(`Failed to start monitoring folder ${this.getFolderName(folder.path)}: ${errorResult.error}`);
}
}
});
// Break if we hit an error and shouldn't continue
if (!options.continueOnError && systemErrors.length > 0) {
break;
}
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
systemErrors.push(`Multi-folder monitoring failed: ${errorMessage}`);
this.loggingService.error('Multi-folder monitoring failed', error instanceof Error ? error : new Error(String(error)));
}
const totalProcessingTime = Date.now() - startTime;
const result: MultiFolderWatchingResult = {
success: systemErrors.length === 0 && folderResults.every(r => r.success),
folderResults,
totalProcessingTime,
systemErrors,
activeWatchers
};
this.loggingService.info('Multi-folder monitoring started', {
success: result.success,
activeWatchers,
totalFolders: folderResults.length,
processingTime: totalProcessingTime
});
return result;
}
async startMonitoringFolder(folderPath: string, options: MultiFolderWatchingOptions = {}): Promise<FolderWatchingResult> {
const folder = await this.folderManager.getFolderByPath(folderPath);
if (!folder) {
throw new Error(`Folder not found: ${folderPath}`);
}
this.loggingService.info(`Starting monitoring for folder: ${folderPath}`);
return this.startFolderMonitoring(folder, options.baseOptions || {});
}
async stopMonitoringAllFolders(): Promise<void> {
this.loggingService.info('Stopping all folder monitoring');
const stopPromises = Array.from(this.folderWatchers.keys()).map(folderPath =>
this.stopMonitoringFolder(folderPath).catch(error => {
this.loggingService.warn(`Failed to stop monitoring for folder ${folderPath}`, error);
})
);
await Promise.all(stopPromises);
this.folderWatchers.clear();
this.folderStatuses.clear();
this.loggingService.info('All folder monitoring stopped');
}
async stopMonitoringFolder(folderPath: string): Promise<void> {
this.loggingService.info(`Stopping monitoring for folder: ${folderPath}`);
const status = this.folderStatuses.get(folderPath);
if (status?.status?.folderPath) {
await this.monitoringWorkflow.stopFileWatching(status.status.folderPath);
}
this.folderWatchers.delete(folderPath);
this.folderStatuses.delete(folderPath);
this.loggingService.info(`Stopped monitoring for folder: ${folderPath}`);
}
async getAllFoldersStatus(): Promise<MultiFolderWatchingStatus> {
const folderStatuses = Array.from(this.folderStatuses.values());
const isActive = folderStatuses.some(status => status.isActive);
const activeWatchers = folderStatuses.filter(status => status.isActive).length;
// Calculate aggregated stats
const aggregatedStats = await this.calculateAggregatedStats(folderStatuses);
// Calculate system health
const systemHealth = this.calculateSystemHealth(folderStatuses);
return {
isActive,
activeWatchers,
folderStatuses,
aggregatedStats,
systemHealth
};
}
async getFolderStatus(folderPath: string): Promise<FolderWatchingStatus> {
const status = this.folderStatuses.get(folderPath);
if (!status) {
throw new Error(`No status found for folder: ${folderPath}`);
}
return status;
}
async handleConfigurationChange(): Promise<void> {
this.loggingService.info('Handling configuration change for monitoring');
// Get updated folder list
const currentFolders = await this.folderManager.getFolders();
const currentFolderPaths = new Set(currentFolders.map(f => f.path));
const monitoredFolderPaths = new Set(this.folderWatchers.keys());
// Stop monitoring removed folders
for (const folderPath of monitoredFolderPaths) {
if (!currentFolderPaths.has(folderPath)) {
await this.stopMonitoringFolder(folderPath);
this.loggingService.info(`Stopped monitoring removed folder: ${folderPath}`);
}
}
// Start monitoring new folders
for (const folder of currentFolders) {
if (!monitoredFolderPaths.has(folder.path)) {
try {
await this.startFolderMonitoring(folder, {});
this.loggingService.info(`Started monitoring new folder: ${folder.path}`);
} catch (error) {
this.loggingService.warn(`Failed to start monitoring new folder ${folder.path}`, error);
}
}
}
}
async getAggregatedEvents(since?: Date, limit?: number): Promise<AggregatedFileEvent[]> {
return this.eventAggregator.getEvents(since, limit);
}
async getMultiFolderHealth(): Promise<MultiFolderHealthResult> {
return this.healthMonitor.checkHealth(Array.from(this.folderStatuses.values()));
}
private async startFolderMonitoring(folder: ResolvedFolderConfig, baseOptions: WatchingOptions): Promise<FolderWatchingResult> {
const folderPath = folder.path;
const folderName = this.getFolderName(folderPath);
try {
// Create folder-specific options
const folderOptions = this.createFolderWatchingOptions(folder, baseOptions);
const settings = this.createFolderSettings(folder, baseOptions);
this.loggingService.debug(`Starting monitoring for folder: ${folderName}`, settings);
// Start watching the folder
const result = await this.monitoringWorkflow.startFileWatching(folder.resolvedPath, folderOptions);
// Store watcher mapping
this.folderWatchers.set(folderPath, result.watchId);
// Update status
this.updateFolderStatus(folderPath, {
isActive: result.success,
settings,
health: result.success ? 'healthy' : 'critical'
});
this.loggingService.info(`Started monitoring folder: ${folderPath}`, {
watchId: result.watchId,
success: result.success
});
const folderResult: FolderWatchingResult = {
folderName,
folderPath: folder.resolvedPath,
success: result.success,
result,
settingsUsed: settings
};
if (result.error) {
folderResult.error = result.error;
}
return folderResult;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Update status
this.updateFolderStatus(folderPath, {
isActive: false,
settings: this.createFolderSettings(folder, baseOptions),
health: 'critical'
});
this.loggingService.error(`Failed to start monitoring folder: ${folderPath}`, error instanceof Error ? error : new Error(String(error)));
return {
folderName,
folderPath: folder.resolvedPath,
success: false,
error: errorMessage,
settingsUsed: this.createFolderSettings(folder, baseOptions)
};
}
}
private filterFolders(folders: ResolvedFolderConfig[], options: MultiFolderWatchingOptions): ResolvedFolderConfig[] {
let filtered = folders; // Remove enabled filter since field doesn't exist
if (options.includeFolders && options.includeFolders.length > 0) {
filtered = filtered.filter(folder => options.includeFolders!.includes(folder.path));
}
if (options.excludeFolders && options.excludeFolders.length > 0) {
filtered = filtered.filter(folder => !options.excludeFolders!.includes(folder.path));
}
return filtered;
}
private initializeStatusTracking(folders: ResolvedFolderConfig[]): void {
this.folderStatuses.clear();
this.folderWatchers.clear();
for (const folder of folders) {
const status: FolderWatchingStatus = {
folderName: this.getFolderName(folder.path),
folderPath: folder.resolvedPath,
isActive: false,
settings: this.createFolderSettings(folder, {}),
health: 'unknown'
};
this.folderStatuses.set(folder.path, status);
}
}
private updateFolderStatus(folderPath: string, updates: Partial<FolderWatchingStatus>): void {
const current = this.folderStatuses.get(folderPath);
if (current) {
this.folderStatuses.set(folderPath, { ...current, ...updates });
}
}
private createFolderWatchingOptions(folder: ResolvedFolderConfig, baseOptions: WatchingOptions): WatchingOptions {
return {
...baseOptions,
excludePatterns: [...(baseOptions.excludePatterns || []), ...folder.exclude],
debounceMs: baseOptions.debounceMs || 1000,
enableBatchProcessing: baseOptions.enableBatchProcessing || true,
batchSize: folder.performance?.batchSize || baseOptions.batchSize || 10,
batchTimeoutMs: baseOptions.batchTimeoutMs || 5000
};
}
private createFolderSettings(folder: ResolvedFolderConfig, baseOptions: WatchingOptions): FolderWatchingSettings {
return {
includeFileTypes: baseOptions.includeFileTypes || [...getSupportedExtensions()],
excludePatterns: [...(baseOptions.excludePatterns || []), ...folder.exclude],
debounceMs: baseOptions.debounceMs || 1000,
enableBatchProcessing: baseOptions.enableBatchProcessing || true,
batchSize: folder.performance?.batchSize || baseOptions.batchSize || 10,
batchTimeoutMs: baseOptions.batchTimeoutMs || 5000
};
}
private async calculateAggregatedStats(folderStatuses: FolderWatchingStatus[]): Promise<AggregatedWatchingStats> {
const totalEventsProcessed = folderStatuses.reduce((sum, status) =>
sum + (status.status?.eventsProcessed || 0), 0);
const totalQueuedEvents = folderStatuses.reduce((sum, status) =>
sum + (status.status?.queuedEvents || 0), 0);
const totalErrors = folderStatuses.reduce((sum, status) =>
sum + (status.status?.errors.length || 0), 0);
const errorRate = totalEventsProcessed > 0 ? (totalErrors / totalEventsProcessed) * 100 : 0;
// Find most active folder
const mostActiveFolder = folderStatuses.reduce((max, status) => {
const events = status.status?.eventsProcessed || 0;
const maxEvents = max?.status?.eventsProcessed || 0;
return events > maxEvents ? status : max;
}, folderStatuses[0])?.folderName;
const stats: AggregatedWatchingStats = {
totalEventsProcessed,
eventsLastHour: 0, // Would need event history to calculate
eventsLast24Hours: 0, // Would need event history to calculate
averageProcessingTime: 0, // Would need timing data
errorRate,
totalQueuedEvents
};
if (mostActiveFolder) {
stats.mostActiveFolder = mostActiveFolder;
}
return stats;
}
private calculateSystemHealth(folderStatuses: FolderWatchingStatus[]): HealthStatus {
const healthCounts = {
healthy: 0,
warning: 0,
critical: 0,
unknown: 0
};
folderStatuses.forEach(status => {
healthCounts[status.health]++;
});
if (healthCounts.critical > 0) return 'critical';
if (healthCounts.warning > 0) return 'warning';
if (healthCounts.unknown > 0) return 'unknown';
return 'healthy';
}
}
/**
* File event aggregator for multi-folder events
*/
class FileEventAggregator {
private events: AggregatedFileEvent[] = [];
private readonly maxEvents = 10000; // Keep last 10k events
constructor(private loggingService: ILoggingService) {}
addEvent(event: AggregatedFileEvent): void {
this.events.push(event);
// Maintain size limit
if (this.events.length > this.maxEvents) {
this.events = this.events.slice(-this.maxEvents);
}
}
getEvents(since?: Date, limit?: number): AggregatedFileEvent[] {
let filtered = this.events;
if (since) {
filtered = filtered.filter(event => event.timestamp >= since);
}
if (limit) {
filtered = filtered.slice(-limit);
}
return filtered;
}
clear(): void {
this.events = [];
}
}
/**
* Health monitor for multi-folder system
*/
class FolderHealthMonitor {
constructor(private loggingService: ILoggingService) {}
async checkHealth(folderStatuses: FolderWatchingStatus[]): Promise<MultiFolderHealthResult> {
const folderHealth: FolderHealthStatus[] = [];
for (const status of folderStatuses) {
const health = this.checkFolderHealth(status);
folderHealth.push(health);
}
const overallHealth = this.calculateOverallHealth(folderHealth);
const systemResources = await this.getSystemResources();
const monitoringMetrics = await this.getMonitoringMetrics(folderStatuses);
const recommendations = this.generateRecommendations(folderHealth, systemResources, monitoringMetrics);
return {
overallHealth,
folderHealth,
systemResources,
monitoringMetrics,
recommendations,
timestamp: new Date()
};
}
private checkFolderHealth(status: FolderWatchingStatus): FolderHealthStatus {
const issues: FolderHealthIssue[] = [];
if (!status.isActive) {
issues.push({
type: 'watcher_inactive',
severity: 'critical',
description: 'Folder watcher is not active',
resolution: 'Restart folder monitoring',
autoResolvable: true
});
}
if (status.status?.errors && status.status.errors.length > 0) {
const errorRate = status.status.errors.length / Math.max(status.status.eventsProcessed, 1);
if (errorRate > 0.1) { // More than 10% error rate
issues.push({
type: 'high_error_rate',
severity: 'high',
description: `High error rate: ${(errorRate * 100).toFixed(1)}%`,
resolution: 'Check folder permissions and file system health',
autoResolvable: false
});
}
}
if (status.status?.queuedEvents && status.status.queuedEvents > 100) {
issues.push({
type: 'queue_backlog',
severity: 'medium',
description: `Large queue backlog: ${status.status.queuedEvents} events`,
resolution: 'Increase processing capacity or batch size',
autoResolvable: false
});
}
const health = issues.some(i => i.severity === 'critical') ? 'critical' :
issues.some(i => i.severity === 'high') ? 'warning' :
issues.length > 0 ? 'warning' : 'healthy';
return {
folderName: status.folderName,
folderPath: status.folderPath,
health,
issues,
lastChecked: new Date()
};
}
private calculateOverallHealth(folderHealth: FolderHealthStatus[]): HealthStatus {
if (folderHealth.some(f => f.health === 'critical')) return 'critical';
if (folderHealth.some(f => f.health === 'warning')) return 'warning';
if (folderHealth.some(f => f.health === 'unknown')) return 'unknown';
return 'healthy';
}
private async getSystemResources(): Promise<SystemResourceUsage> {
// In a real implementation, this would gather actual system metrics
return {
memoryUsage: process.memoryUsage().heapUsed,
cpuUsage: 0, // Would need actual CPU monitoring
fileHandles: 0 // Would need actual file handle counting
};
}
private async getMonitoringMetrics(folderStatuses: FolderWatchingStatus[]): Promise<MonitoringMetrics> {
// In a real implementation, this would calculate actual metrics
return {
averageEventProcessingTime: 0,
peakEventProcessingTime: 0,
eventsPerSecond: 0,
watcherRestartFrequency: 0,
memoryTrend: 'stable'
};
}
private generateRecommendations(
folderHealth: FolderHealthStatus[],
systemResources: SystemResourceUsage,
monitoringMetrics: MonitoringMetrics
): HealthRecommendation[] {
const recommendations: HealthRecommendation[] = [];
// Check for inactive watchers
const inactiveFolders = folderHealth.filter(f => f.health === 'critical');
if (inactiveFolders.length > 0) {
recommendations.push({
priority: 'critical',
category: 'reliability',
title: 'Restart inactive folder watchers',
description: `${inactiveFolders.length} folder watchers are inactive`,
action: 'Restart monitoring for affected folders',
automated: true,
affectedFolders: inactiveFolders.map(f => f.folderName)
});
}
// Check memory usage
if (systemResources.memoryUsage > 1024 * 1024 * 1024) { // > 1GB
recommendations.push({
priority: 'medium',
category: 'resource_usage',
title: 'High memory usage detected',
description: 'Monitoring system is using significant memory',
action: 'Consider reducing batch sizes or implementing memory optimization',
automated: false
});
}
return recommendations;
}
}