widgetdc-cortex / apps /backend /src /services /evolution /HarvestResourceController.ts
Kraft102's picture
Initial deployment - WidgeTDC Cortex Backend v2.1.0
529090e
/**
* 🎛️ HARVEST RESOURCE CONTROLLER
*
* Manages resource allocation for OmniHarvester:
* - CPU throttling via delays
* - Memory limits
* - Concurrent file processing limits
* - Batch processing with pauses
* - Real-time resource monitoring
*/
import os from 'os';
// ============================================
// TYPES
// ============================================
export interface ResourceConfig {
throttlePercent: number; // 0-100: Overall resource limit
maxConcurrentFiles: number; // 1-20: Parallel file processing
memoryLimitMB: number; // 128-2048: Max memory for harvester
cpuThrottleMs: number; // 0-100: Delay between operations (ms)
batchSize: number; // 10-500: Files per batch before pause
batchPauseMs: number; // Pause between batches
}
export interface ResourceUsage {
cpuPercent: number;
memoryMB: number;
filesPerSecond: number;
estimatedTimeRemaining: string;
totalFilesProcessed: number;
totalFilesToProcess: number;
currentFile: string;
}
export interface HarvestProgress {
filesScanned: number;
totalFiles: number;
currentFile: string;
bytesProcessed: number;
startTime: number;
strategy: string;
}
// ============================================
// GLOBAL STATE
// ============================================
let globalResourceConfig: ResourceConfig = {
throttlePercent: 50,
maxConcurrentFiles: 5,
memoryLimitMB: 512,
cpuThrottleMs: 10,
batchSize: 100,
batchPauseMs: 100,
};
let harvestProgress: HarvestProgress | null = null;
let filesProcessedSinceStart = 0;
let harvestStartTime = 0;
// ============================================
// CONFIGURATION MANAGEMENT
// ============================================
export function getResourceConfig(): ResourceConfig {
return { ...globalResourceConfig };
}
export function setResourceConfig(config: Partial<ResourceConfig>): ResourceConfig {
// Validate and constrain values
if (config.throttlePercent !== undefined) {
globalResourceConfig.throttlePercent = Math.max(0, Math.min(100, config.throttlePercent));
}
if (config.maxConcurrentFiles !== undefined) {
globalResourceConfig.maxConcurrentFiles = Math.max(1, Math.min(20, config.maxConcurrentFiles));
}
if (config.memoryLimitMB !== undefined) {
globalResourceConfig.memoryLimitMB = Math.max(128, Math.min(2048, config.memoryLimitMB));
}
if (config.cpuThrottleMs !== undefined) {
globalResourceConfig.cpuThrottleMs = Math.max(0, Math.min(100, config.cpuThrottleMs));
}
if (config.batchSize !== undefined) {
globalResourceConfig.batchSize = Math.max(10, Math.min(500, config.batchSize));
}
if (config.batchPauseMs !== undefined) {
globalResourceConfig.batchPauseMs = Math.max(0, Math.min(1000, config.batchPauseMs));
}
console.log(`🎛️ Resource config updated:`, globalResourceConfig);
return getResourceConfig();
}
// Auto-configure based on throttle percent
export function autoConfigureFromThrottle(throttlePercent: number): ResourceConfig {
const config: ResourceConfig = {
throttlePercent,
maxConcurrentFiles: Math.max(1, Math.floor(throttlePercent / 5)),
memoryLimitMB: 256 + Math.floor(throttlePercent * 10),
cpuThrottleMs: Math.floor(100 - throttlePercent),
batchSize: Math.max(10, Math.floor(throttlePercent * 5)),
batchPauseMs: Math.max(0, Math.floor((100 - throttlePercent) * 2)),
};
return setResourceConfig(config);
}
// ============================================
// PROGRESS TRACKING
// ============================================
export function initHarvestProgress(totalFiles: number): void {
harvestStartTime = Date.now();
filesProcessedSinceStart = 0;
harvestProgress = {
filesScanned: 0,
totalFiles,
currentFile: '',
bytesProcessed: 0,
startTime: harvestStartTime,
strategy: 'INITIALIZING',
};
}
export function updateHarvestProgress(update: Partial<HarvestProgress>): void {
if (harvestProgress) {
harvestProgress = { ...harvestProgress, ...update };
if (update.filesScanned !== undefined) {
filesProcessedSinceStart = update.filesScanned;
}
}
}
export function getHarvestProgress(): HarvestProgress | null {
return harvestProgress ? { ...harvestProgress } : null;
}
export function clearHarvestProgress(): void {
harvestProgress = null;
filesProcessedSinceStart = 0;
harvestStartTime = 0;
}
// ============================================
// RESOURCE MONITORING
// ============================================
export function getResourceUsage(): ResourceUsage {
const memUsage = process.memoryUsage();
const memoryMB = memUsage.heapUsed / 1024 / 1024;
// Calculate CPU (rough estimate based on Node.js event loop)
const cpus = os.cpus();
let totalIdle = 0;
let totalTick = 0;
cpus.forEach(cpu => {
for (const type in cpu.times) {
totalTick += cpu.times[type as keyof typeof cpu.times];
}
totalIdle += cpu.times.idle;
});
const cpuPercent = 100 - (totalIdle / totalTick * 100);
// Calculate files per second
const elapsedSeconds = (Date.now() - harvestStartTime) / 1000 || 1;
const filesPerSecond = filesProcessedSinceStart / elapsedSeconds;
// Estimate remaining time
let estimatedTimeRemaining = '--';
if (harvestProgress && filesPerSecond > 0) {
const remaining = harvestProgress.totalFiles - harvestProgress.filesScanned;
const secondsRemaining = remaining / filesPerSecond;
if (secondsRemaining < 60) {
estimatedTimeRemaining = `${Math.ceil(secondsRemaining)}s`;
} else if (secondsRemaining < 3600) {
estimatedTimeRemaining = `${Math.ceil(secondsRemaining / 60)}m`;
} else {
estimatedTimeRemaining = `${(secondsRemaining / 3600).toFixed(1)}h`;
}
}
return {
cpuPercent: Math.round(cpuPercent * 10) / 10,
memoryMB: Math.round(memoryMB),
filesPerSecond: Math.round(filesPerSecond * 10) / 10,
estimatedTimeRemaining,
totalFilesProcessed: filesProcessedSinceStart,
totalFilesToProcess: harvestProgress?.totalFiles || 0,
currentFile: harvestProgress?.currentFile || '',
};
}
// ============================================
// THROTTLING UTILITIES
// ============================================
export async function throttleDelay(): Promise<void> {
if (globalResourceConfig.cpuThrottleMs > 0) {
await new Promise(resolve => setTimeout(resolve, globalResourceConfig.cpuThrottleMs));
}
}
export async function batchPause(): Promise<void> {
if (globalResourceConfig.batchPauseMs > 0) {
await new Promise(resolve => setTimeout(resolve, globalResourceConfig.batchPauseMs));
}
}
export function shouldPauseForBatch(filesProcessed: number): boolean {
return filesProcessed > 0 && filesProcessed % globalResourceConfig.batchSize === 0;
}
export function checkMemoryLimit(): boolean {
const memUsage = process.memoryUsage();
const memoryMB = memUsage.heapUsed / 1024 / 1024;
return memoryMB < globalResourceConfig.memoryLimitMB;
}
// ============================================
// STATUS AGGREGATION
// ============================================
export interface HarvestFullStatus {
isRunning: boolean;
harvestId: string | null;
canAbort: boolean;
progress: HarvestProgress | null;
resourceUsage: ResourceUsage;
resourceConfig: ResourceConfig;
}
// This will be combined with OmniHarvester status
export function getResourceStatus(): {
resourceUsage: ResourceUsage;
resourceConfig: ResourceConfig;
progress: HarvestProgress | null;
} {
return {
resourceUsage: getResourceUsage(),
resourceConfig: getResourceConfig(),
progress: getHarvestProgress(),
};
}