UNPKG

hikma-engine

Version:

Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents

404 lines (403 loc) 11.8 kB
"use strict"; /** * @file Response timing and metadata injection utilities. * Provides comprehensive timing and performance monitoring for API responses. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.responseHeaders = exports.PerformanceMetrics = exports.RequestTimingManager = exports.TimingUtil = void 0; exports.initializeTimingMiddleware = initializeTimingMiddleware; exports.performanceLoggingMiddleware = performanceLoggingMiddleware; exports.withTiming = withTiming; exports.metricsCollectionMiddleware = metricsCollectionMiddleware; const correlation_1 = require("../middleware/correlation"); /** * Timing measurement utility class. */ class TimingUtil { constructor() { this.timings = new Map(); } /** * Starts a new timing measurement. */ start(name) { const timing = { startTime: Date.now(), markers: {}, measures: {}, }; this.timings.set(name, timing); } /** * Ends a timing measurement. */ end(name) { const timing = this.timings.get(name); if (!timing) { throw new Error(`Timing '${name}' not found`); } timing.endTime = Date.now(); timing.duration = timing.endTime - timing.startTime; return timing.duration; } /** * Adds a marker to a timing measurement. */ mark(timingName, markerName) { const timing = this.timings.get(timingName); if (timing) { timing.markers[markerName] = Date.now() - timing.startTime; } } /** * Measures time between two markers. */ measure(timingName, measureName, startMarker, endMarker) { const timing = this.timings.get(timingName); if (timing && timing.markers[startMarker] && timing.markers[endMarker]) { timing.measures[measureName] = timing.markers[endMarker] - timing.markers[startMarker]; } } /** * Gets timing information. */ getTiming(name) { return this.timings.get(name); } /** * Gets all timings. */ getAllTimings() { const result = {}; this.timings.forEach((timing, name) => { result[name] = timing; }); return result; } /** * Clears all timings. */ clear() { this.timings.clear(); } /** * Gets duration for a completed timing. */ getDuration(name) { const timing = this.timings.get(name); return timing?.duration; } } exports.TimingUtil = TimingUtil; /** * Request timing manager that attaches to request context. */ class RequestTimingManager { constructor(requestId) { this.requestId = requestId; this.timingUtil = new TimingUtil(); } /** * Starts request timing. */ startRequest() { this.timingUtil.start('request'); } /** * Ends request timing. */ endRequest() { return this.timingUtil.end('request'); } /** * Times a database operation. */ async timeDatabase(operation) { this.timingUtil.start('database'); try { const result = await operation(); this.timingUtil.end('database'); return result; } catch (error) { this.timingUtil.end('database'); throw error; } } /** * Times a cache operation. */ async timeCache(operation) { this.timingUtil.start('cache'); try { const result = await operation(); this.timingUtil.end('cache'); return result; } catch (error) { this.timingUtil.end('cache'); throw error; } } /** * Times a search operation. */ async timeSearch(operation) { this.timingUtil.start('search'); try { const result = await operation(); this.timingUtil.end('search'); return result; } catch (error) { this.timingUtil.end('search'); throw error; } } /** * Times a processing operation. */ async timeProcessing(operation) { this.timingUtil.start('processing'); try { const result = await operation(); this.timingUtil.end('processing'); return result; } catch (error) { this.timingUtil.end('processing'); throw error; } } /** * Adds a custom marker. */ mark(marker) { this.timingUtil.mark('request', marker); } /** * Gets performance summary. */ getPerformanceSummary() { const summary = {}; const timings = this.timingUtil.getAllTimings(); Object.entries(timings).forEach(([name, timing]) => { if (timing.duration !== undefined) { summary[`${name}Time`] = timing.duration; } }); return summary; } /** * Gets detailed timing information. */ getDetailedTiming() { const timings = this.timingUtil.getAllTimings(); return { request: timings.request || { startTime: Date.now(), markers: {}, measures: {} }, database: timings.database, cache: timings.cache, search: timings.search, processing: timings.processing, }; } } exports.RequestTimingManager = RequestTimingManager; /** * Middleware to initialize request timing. */ function initializeTimingMiddleware(req, res, next) { const requestId = req.context?.requestId || 'unknown'; req.timing = new RequestTimingManager(requestId); req.timing.startRequest(); // Override response methods to end timing const originalJson = res.json; res.json = function (data) { if (req.timing) { const duration = req.timing.endRequest(); // Inject timing into response if it's a standard API response if (data && typeof data === 'object' && 'meta' in data) { if (!data.meta.processingTime) { data.meta.processingTime = duration; } // Add performance data in development if (process.env.NODE_ENV === 'development') { data.meta.performance = req.timing.getPerformanceSummary(); } } } return originalJson.call(this, data); }; next(); } /** * Middleware to log performance metrics. */ function performanceLoggingMiddleware(req, res, next) { res.on('finish', () => { if (req.timing) { const performance = req.timing.getPerformanceSummary(); const logger = (0, correlation_1.getRequestLogger)(req); // Log slow requests const totalTime = performance.requestTime || 0; const slowThreshold = parseInt(process.env.SLOW_REQUEST_THRESHOLD || '1000', 10); if (totalTime > slowThreshold) { logger.warn('Slow request detected', { method: req.method, url: req.originalUrl, statusCode: res.statusCode, performance, threshold: slowThreshold, }); } else { logger.debug('Request performance', { method: req.method, url: req.originalUrl, statusCode: res.statusCode, performance, }); } } }); next(); } /** * Utility function to create timing decorator for async functions. */ function withTiming(name, fn) { return async (...args) => { const timing = new TimingUtil(); timing.start(name); try { const result = await fn(...args); timing.end(name); return result; } catch (error) { timing.end(name); throw error; } }; } /** * Server-side performance metrics collector. */ class PerformanceMetrics { /** * Records a metric value. */ static record(metric, value) { if (!this.metrics.has(metric)) { this.metrics.set(metric, []); } const values = this.metrics.get(metric); values.push(value); // Keep only last 1000 values if (values.length > 1000) { values.shift(); } } /** * Gets metric statistics. */ static getStats(metric) { const values = this.metrics.get(metric); if (!values || values.length === 0) { return null; } const sorted = [...values].sort((a, b) => a - b); const count = sorted.length; const sum = sorted.reduce((a, b) => a + b, 0); return { count, min: sorted[0], max: sorted[count - 1], avg: sum / count, p50: sorted[Math.floor(count * 0.5)], p95: sorted[Math.floor(count * 0.95)], p99: sorted[Math.floor(count * 0.99)], }; } /** * Gets all metrics. */ static getAllStats() { const stats = {}; this.metrics.forEach((_, metric) => { stats[metric] = this.getStats(metric); }); return stats; } /** * Clears all metrics. */ static clear() { this.metrics.clear(); } } exports.PerformanceMetrics = PerformanceMetrics; PerformanceMetrics.metrics = new Map(); /** * Middleware to collect performance metrics. */ function metricsCollectionMiddleware(req, res, next) { const startTime = Date.now(); res.on('finish', () => { const duration = Date.now() - startTime; const route = req.route?.path || req.path; const method = req.method; const statusCode = res.statusCode; // Record metrics PerformanceMetrics.record(`request.${method}.duration`, duration); PerformanceMetrics.record(`request.${method}.${route}.duration`, duration); PerformanceMetrics.record(`response.${statusCode}.count`, 1); if (req.timing) { const performance = req.timing.getPerformanceSummary(); Object.entries(performance).forEach(([key, value]) => { PerformanceMetrics.record(`timing.${key}`, value); }); } }); next(); } /** * Response header injection utilities. */ exports.responseHeaders = { /** * Adds performance headers to response. */ addPerformanceHeaders(res, timing) { const performance = timing.getPerformanceSummary(); if (performance.requestTime) { res.setHeader('X-Response-Time', `${performance.requestTime}ms`); } if (performance.databaseTime) { res.setHeader('X-Database-Time', `${performance.databaseTime}ms`); } if (performance.cacheTime) { res.setHeader('X-Cache-Time', `${performance.cacheTime}ms`); } if (performance.searchTime) { res.setHeader('X-Search-Time', `${performance.searchTime}ms`); } }, /** * Adds cache headers to response. */ addCacheHeaders(res, hit, ttl) { res.setHeader('X-Cache', hit ? 'HIT' : 'MISS'); if (ttl) { res.setHeader('X-Cache-TTL', ttl.toString()); } }, /** * Adds security headers to response. */ addSecurityHeaders(res) { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); }, };