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
JavaScript
/**
* @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');
},
};
;