UNPKG

dnsweeper

Version:

Advanced CLI tool for DNS record risk analysis and cleanup. Features CSV import for Cloudflare/Route53, automated risk assessment, and parallel DNS validation.

261 lines (219 loc) 6.15 kB
/** * メモリ最適化ユーティリティ * 大容量データ処理時のメモリ効率を向上 */ export interface MemoryUsage { used: number; external: number; heapUsed: number; heapTotal: number; rss: number; } export interface StreamProcessorOptions { chunkSize: number; highWaterMark: number; objectMode: boolean; onMemoryWarning?: (usage: MemoryUsage) => void; memoryWarningThreshold?: number; // MB } export class MemoryOptimizer { private static readonly DEFAULT_WARNING_THRESHOLD = 512; // 512MB /** * 現在のメモリ使用量を取得 */ static getMemoryUsage(): MemoryUsage { const usage = process.memoryUsage(); return { used: Math.round(usage.heapUsed / 1024 / 1024), external: Math.round(usage.external / 1024 / 1024), heapUsed: Math.round(usage.heapUsed / 1024 / 1024), heapTotal: Math.round(usage.heapTotal / 1024 / 1024), rss: Math.round(usage.rss / 1024 / 1024), }; } /** * メモリ使用量をログ出力 */ static logMemoryUsage(label = 'Memory usage'): void { const usage = this.getMemoryUsage(); console.log(`${label}:`, { 'Heap Used': `${usage.heapUsed}MB`, 'Heap Total': `${usage.heapTotal}MB`, External: `${usage.external}MB`, RSS: `${usage.rss}MB`, }); } /** * メモリ警告チェック */ static checkMemoryWarning( threshold = this.DEFAULT_WARNING_THRESHOLD, onWarning?: (usage: MemoryUsage) => void, ): boolean { const usage = this.getMemoryUsage(); if (usage.heapUsed > threshold) { onWarning?.(usage); return true; } return false; } /** * ガベージコレクション強制実行 */ static forceGarbageCollection(): void { if (global.gc) { global.gc(); } else { console.warn('Garbage collection is not exposed. Run with --expose-gc flag.'); } } /** * メモリ効率的な配列チャンク処理 */ static async processArrayInChunks<T, R>( array: T[], processor: (chunk: T[]) => Promise<R[]>, chunkSize = 1000, onProgress?: (processed: number, total: number) => void, ): Promise<R[]> { const results: R[] = []; let processed = 0; for (let i = 0; i < array.length; i += chunkSize) { const chunk = array.slice(i, i + chunkSize); const chunkResults = await processor(chunk); results.push(...chunkResults); processed += chunk.length; onProgress?.(processed, array.length); // メモリ警告チェック this.checkMemoryWarning(512, (usage) => { console.warn(`Memory warning: ${usage.heapUsed}MB used`); this.forceGarbageCollection(); }); } return results; } /** * WeakMapベースのキャッシュ(メモリリーク防止) */ static createWeakCache<K extends object, V>(): WeakMap<K, V> { return new WeakMap<K, V>(); } /** * LRUキャッシュの実装 */ static createLRUCache<K, V>(maxSize: number): LRUCache<K, V> { return new LRUCache<K, V>(maxSize); } } /** * LRUキャッシュ実装 */ class LRUCache<K, V> { private cache = new Map<K, V>(); private maxSize: number; constructor(maxSize: number) { this.maxSize = maxSize; } get(key: K): V | undefined { if (this.cache.has(key)) { // LRUのため再挿入 const value = this.cache.get(key)!; this.cache.delete(key); this.cache.set(key, value); return value; } return undefined; } set(key: K, value: V): void { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.maxSize) { // 最古のアイテムを削除 const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); } } this.cache.set(key, value); } delete(key: K): boolean { return this.cache.delete(key); } clear(): void { this.cache.clear(); } size(): number { return this.cache.size; } has(key: K): boolean { return this.cache.has(key); } } /** * ストリーミング処理用のメモリ効率的なプロセッサー */ export class StreamProcessor<T> { private options: StreamProcessorOptions; private processedCount = 0; constructor(options: Partial<StreamProcessorOptions> = {}) { this.options = { chunkSize: 1000, highWaterMark: 64 * 1024, // 64KB objectMode: true, memoryWarningThreshold: 512, ...options, }; } /** * ストリーミング処理 */ async processStream<R>( items: AsyncIterable<T> | Iterable<T>, processor: (item: T) => Promise<R> | R, ): Promise<R[]> { const results: R[] = []; let chunk: T[] = []; for await (const item of items) { chunk.push(item); if (chunk.length >= this.options.chunkSize) { const chunkResults = await this.processChunk(chunk, processor); results.push(...chunkResults); chunk = []; // チャンクをクリア // メモリチェック this.checkMemoryAndCleanup(); } } // 残りのアイテムを処理 if (chunk.length > 0) { const chunkResults = await this.processChunk(chunk, processor); results.push(...chunkResults); } return results; } private async processChunk<R>(chunk: T[], processor: (item: T) => Promise<R> | R): Promise<R[]> { const results: R[] = []; for (const item of chunk) { try { const result = await processor(item); results.push(result); this.processedCount++; } catch (error) { console.error('Error processing item:', error); } } return results; } private checkMemoryAndCleanup(): void { const threshold = this.options.memoryWarningThreshold || 512; MemoryOptimizer.checkMemoryWarning(threshold, (usage) => { this.options.onMemoryWarning?.(usage); MemoryOptimizer.forceGarbageCollection(); }); } getProcessedCount(): number { return this.processedCount; } reset(): void { this.processedCount = 0; } }