@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
572 lines • 19.8 kB
JavaScript
/**
* Request timing and memory usage tracking
* Provides comprehensive monitoring for HTTP requests, AI calls, and MCP operations
*/
import { randomBytes } from 'node:crypto';
import { MAX_COMPLETED_REQUESTS } from '../../constants.js';
import { generateCorrelationId, getLogger } from './index.js';
import { calculateMemoryDelta, formatBytes, trackPerformance, } from './performance.js';
import { getSafeMemory } from './safe-process.js';
// Get logger instance directly to avoid circular dependencies
const logger = getLogger();
/**
* Request tracker class for monitoring HTTP requests and operations
*/
export class RequestTracker {
activeRequests = new Map();
completedRequests = [];
maxCompletedRequests;
correlationId;
constructor(maxCompletedRequests = MAX_COMPLETED_REQUESTS) {
this.maxCompletedRequests = maxCompletedRequests;
this.correlationId = generateCorrelationId();
}
/**
* Start tracking a new request
*/
startRequest(metadata) {
const id = this.generateRequestId();
const request = {
...metadata,
id,
startTime: Date.now(),
status: 'pending',
correlationId: generateCorrelationId(),
memoryStart: getSafeMemory(),
};
this.activeRequests.set(id, request);
logger.debug('Request tracking started', {
requestId: id,
requestType: metadata.type,
method: metadata.method,
endpoint: metadata.url || metadata.endpoint,
correlationId: request.correlationId,
source: 'request-tracker',
});
return id;
}
/**
* Complete a request successfully
*/
completeRequest(requestId, responseMetadata) {
const request = this.activeRequests.get(requestId);
if (!request) {
logger.warn('Attempted to complete unknown request', {
requestId,
source: 'request-tracker',
});
return null;
}
const endTime = Date.now();
const memoryEnd = getSafeMemory();
const duration = endTime - request.startTime;
let memoryDelta;
if (request.memoryStart) {
try {
memoryDelta = calculateMemoryDelta(request.memoryStart, memoryEnd);
}
catch {
// Ignore memory calculation errors
memoryDelta = undefined;
}
}
const completedRequest = {
...request,
endTime,
duration,
status: 'success',
memoryEnd,
memoryDelta,
...responseMetadata,
};
this.activeRequests.delete(requestId);
this.addToCompleted(completedRequest);
// Log successful request completion
logger.info('Request completed successfully', {
requestId: completedRequest.id,
requestType: completedRequest.type,
method: completedRequest.method,
endpoint: completedRequest.url || completedRequest.endpoint,
duration: `${duration}ms`,
statusCode: completedRequest.statusCode,
memoryDelta: memoryDelta
? {
heapUsed: formatBytes(memoryDelta.heapUsedDelta || 0),
heapTotal: formatBytes(memoryDelta.heapTotalDelta || 0),
external: formatBytes(memoryDelta.externalDelta || 0),
rss: formatBytes(memoryDelta.rssDelta || 0),
}
: undefined,
correlationId: completedRequest.correlationId,
source: 'request-tracker',
});
return completedRequest;
}
/**
* Mark a request as failed
*/
failRequest(requestId, error, errorMetadata) {
const request = this.activeRequests.get(requestId);
if (!request) {
logger.warn('Attempted to fail unknown request', {
requestId,
source: 'request-tracker',
});
return null;
}
const endTime = Date.now();
const memoryEnd = getSafeMemory();
const duration = endTime - request.startTime;
let memoryDelta;
if (request.memoryStart) {
try {
memoryDelta = calculateMemoryDelta(request.memoryStart, memoryEnd);
}
catch {
// Ignore memory calculation errors
memoryDelta = undefined;
}
}
const errorMessage = error instanceof Error ? error.message : error;
const errorType = error instanceof Error
? error.constructor.name
: errorMetadata?.errorType || 'Unknown';
const completedRequest = {
...request,
endTime,
duration,
status: 'error',
memoryEnd,
memoryDelta,
errorType,
errorMessage,
...errorMetadata,
};
this.activeRequests.delete(requestId);
this.addToCompleted(completedRequest);
// Log request failure
logger.error('Request failed', {
requestId: completedRequest.id,
requestType: completedRequest.type,
method: completedRequest.method,
endpoint: completedRequest.url || completedRequest.endpoint,
duration: `${duration}ms`,
errorType,
errorMessage,
statusCode: completedRequest.statusCode,
memoryDelta: memoryDelta
? {
heapUsed: formatBytes(memoryDelta.heapUsedDelta || 0),
heapTotal: formatBytes(memoryDelta.heapTotalDelta || 0),
external: formatBytes(memoryDelta.externalDelta || 0),
rss: formatBytes(memoryDelta.rssDelta || 0),
}
: undefined,
correlationId: completedRequest.correlationId,
source: 'request-tracker',
});
return completedRequest;
}
/**
* Mark a request as timed out
*/
timeoutRequest(requestId, timeoutMs) {
const request = this.activeRequests.get(requestId);
if (!request) {
logger.warn('Attempted to timeout unknown request', {
requestId,
source: 'request-tracker',
});
return null;
}
const endTime = Date.now();
const memoryEnd = getSafeMemory();
const duration = endTime - request.startTime;
let memoryDelta;
if (request.memoryStart) {
try {
memoryDelta = calculateMemoryDelta(request.memoryStart, memoryEnd);
}
catch {
// Ignore memory calculation errors
memoryDelta = undefined;
}
}
const completedRequest = {
...request,
endTime,
duration,
status: 'timeout',
memoryEnd,
memoryDelta,
errorMessage: `Request timed out after ${timeoutMs}ms (actual duration: ${duration}ms)`,
};
this.activeRequests.delete(requestId);
this.addToCompleted(completedRequest);
// Log request timeout
logger.warn('Request timed out', {
requestId: completedRequest.id,
requestType: completedRequest.type,
method: completedRequest.method,
endpoint: completedRequest.url || completedRequest.endpoint,
duration: `${duration}ms`,
timeoutMs: `${timeoutMs}ms`,
memoryDelta: memoryDelta
? {
heapUsed: formatBytes(memoryDelta.heapUsedDelta || 0),
heapTotal: formatBytes(memoryDelta.heapTotalDelta || 0),
external: formatBytes(memoryDelta.externalDelta || 0),
rss: formatBytes(memoryDelta.rssDelta || 0),
}
: undefined,
correlationId: completedRequest.correlationId,
source: 'request-tracker',
});
return completedRequest;
}
/**
* Cancel a request
*/
cancelRequest(requestId, reason) {
const request = this.activeRequests.get(requestId);
if (!request) {
logger.warn('Attempted to cancel unknown request', {
requestId,
source: 'request-tracker',
});
return null;
}
const endTime = Date.now();
const memoryEnd = getSafeMemory();
const duration = endTime - request.startTime;
let memoryDelta;
if (request.memoryStart) {
try {
memoryDelta = calculateMemoryDelta(request.memoryStart, memoryEnd);
}
catch {
// Ignore memory calculation errors
memoryDelta = undefined;
}
}
const completedRequest = {
...request,
endTime,
duration,
status: 'cancelled',
memoryEnd,
memoryDelta,
errorMessage: reason || 'Request was cancelled',
};
this.activeRequests.delete(requestId);
this.addToCompleted(completedRequest);
// Log request cancellation
logger.info('Request cancelled', {
requestId: completedRequest.id,
requestType: completedRequest.type,
method: completedRequest.method,
endpoint: completedRequest.url || completedRequest.endpoint,
duration: `${duration}ms`,
reason,
memoryDelta: memoryDelta
? {
heapUsed: formatBytes(memoryDelta.heapUsedDelta || 0),
heapTotal: formatBytes(memoryDelta.heapTotalDelta || 0),
external: formatBytes(memoryDelta.externalDelta || 0),
rss: formatBytes(memoryDelta.rssDelta || 0),
}
: undefined,
correlationId: completedRequest.correlationId,
source: 'request-tracker',
});
return completedRequest;
}
/**
* Get request statistics
*/
getStats() {
const now = Date.now();
const oneHourAgo = now - 60 * 60 * 1000;
const oneDayAgo = now - 24 * 60 * 60 * 1000;
const requestsInLastHour = this.completedRequests.filter(r => r.endTime !== undefined && r.endTime > oneHourAgo).length;
const requestsInLastDay = this.completedRequests.filter(r => r.endTime !== undefined && r.endTime > oneDayAgo).length;
const requestsByType = {};
const requestsByStatus = {};
const endpointStats = {};
let totalDuration = 0;
let minDuration = Infinity;
let maxDuration = 0;
const memoryDeltaSum = { heapUsed: 0, heapTotal: 0, external: 0, rss: 0 };
let memoryDeltaCount = 0;
for (const request of this.completedRequests) {
// Count by type
requestsByType[request.type] = (requestsByType[request.type] || 0) + 1;
// Count by status
const status = request.status || 'unknown';
requestsByStatus[status] = (requestsByStatus[status] || 0) + 1;
// Duration statistics
if (request.duration) {
totalDuration += request.duration;
minDuration = Math.min(minDuration, request.duration);
maxDuration = Math.max(maxDuration, request.duration);
}
// Memory statistics
if (request.memoryDelta) {
memoryDeltaSum.heapUsed += request.memoryDelta.heapUsedDelta;
memoryDeltaSum.heapTotal += request.memoryDelta.heapTotalDelta;
memoryDeltaSum.external += request.memoryDelta.externalDelta;
memoryDeltaSum.rss += request.memoryDelta.rssDelta;
memoryDeltaCount++;
}
// Endpoint statistics
const endpoint = request.url || request.endpoint || request.toolName || 'unknown';
if (!endpointStats[endpoint]) {
endpointStats[endpoint] = { count: 0, totalDuration: 0, errors: 0 };
}
endpointStats[endpoint].count++;
if (request.duration) {
endpointStats[endpoint].totalDuration += request.duration;
}
if (request.status === 'error') {
endpointStats[endpoint].errors++;
}
}
// Calculate average duration and memory delta
const averageDuration = this.completedRequests.length > 0
? totalDuration / this.completedRequests.length
: 0;
const averageMemoryDelta = memoryDeltaCount > 0
? {
heapUsed: memoryDeltaSum.heapUsed / memoryDeltaCount,
heapTotal: memoryDeltaSum.heapTotal / memoryDeltaCount,
external: memoryDeltaSum.external / memoryDeltaCount,
rss: memoryDeltaSum.rss / memoryDeltaCount,
}
: { heapUsed: 0, heapTotal: 0, external: 0, rss: 0 };
// Find busiest, slowest, and most error-prone endpoints
let busiestEndpoint;
let slowestEndpoint;
let mostErrorProneEndpoint;
let maxRequests = 0;
let maxAvgDuration = 0;
let maxErrorRate = 0;
for (const [endpoint, stats] of Object.entries(endpointStats)) {
if (stats.count > maxRequests) {
maxRequests = stats.count;
busiestEndpoint = endpoint;
}
const avgDuration = stats.count > 0 ? stats.totalDuration / stats.count : 0;
if (avgDuration > maxAvgDuration) {
maxAvgDuration = avgDuration;
slowestEndpoint = endpoint;
}
const errorRate = stats.count > 0 ? stats.errors / stats.count : 0;
if (errorRate > maxErrorRate) {
maxErrorRate = errorRate;
mostErrorProneEndpoint = endpoint;
}
}
const totalRequests = this.completedRequests.length;
const errorRate = requestsByStatus.error
? requestsByStatus.error / totalRequests
: 0;
const timeoutRate = requestsByStatus.timeout
? requestsByStatus.timeout / totalRequests
: 0;
return {
totalRequests,
requestsByType,
requestsByStatus,
averageDuration,
minDuration: minDuration === Infinity ? 0 : minDuration,
maxDuration,
totalDuration,
averageMemoryDelta,
errorRate,
timeoutRate,
requestsInLastHour,
requestsInLastDay,
busiestHour: new Date().getHours(),
busiestEndpoint,
slowestEndpoint,
mostErrorProneEndpoint,
timestamp: new Date().toISOString(),
};
}
/**
* Get active requests
*/
getActiveRequests() {
return Array.from(this.activeRequests.values());
}
/**
* Get recently completed requests
*/
getRecentRequests(limit = 50) {
return this.completedRequests
.sort((a, b) => (b.endTime || 0) - (a.endTime || 0))
.slice(0, limit);
}
/**
* Clear all tracking data
*/
clear() {
this.activeRequests.clear();
this.completedRequests = [];
}
generateRequestId() {
return `req_${Date.now()}_${randomBytes(8).toString('hex')}`;
}
addToCompleted(request) {
this.completedRequests.push(request);
// Keep only the last N requests
if (this.completedRequests.length > this.maxCompletedRequests) {
this.completedRequests = this.completedRequests.slice(-this.maxCompletedRequests);
}
}
}
/**
* Global request tracker instance
*/
export const globalRequestTracker = new RequestTracker();
/**
* Decorator to automatically track function calls as requests
*/
export function trackRequest(fn, options) {
return trackPerformance(async (...args) => {
const requestId = globalRequestTracker.startRequest({
type: options.type,
method: options.method,
endpoint: options.endpoint,
provider: options.provider,
model: options.model,
tags: options.tags,
});
try {
const result = await fn(...args);
globalRequestTracker.completeRequest(requestId, {
customData: {
arguments: options.trackRequestSize
? {
count: args.length,
types: args.map(arg => typeof arg),
size: JSON.stringify(args).length,
}
: undefined,
},
});
return result;
}
catch (error) {
globalRequestTracker.failRequest(requestId, error, {
errorType: error instanceof Error ? error.constructor.name : 'Unknown',
});
throw error;
}
}, `${options.type}-request-${options.endpoint || 'unknown'}`, {
thresholds: options.thresholds,
trackMemory: options.trackMemory !== false,
trackArgs: false,
});
}
/**
* HTTP request tracking utilities
*/
export const httpTracker = {
/**
* Track an HTTP GET request
*/
get: (url, fn, options) => trackRequest(fn, {
type: 'http',
method: 'GET',
endpoint: url,
...options,
}),
/**
* Track an HTTP POST request
*/
post: (url, fn, options) => trackRequest(fn, {
type: 'http',
method: 'POST',
endpoint: url,
...options,
}),
/**
* Track an HTTP PUT request
*/
put: (url, fn, options) => trackRequest(fn, {
type: 'http',
method: 'PUT',
endpoint: url,
...options,
}),
/**
* Track an HTTP DELETE request
*/
delete: (url, fn, options) => trackRequest(fn, {
type: 'http',
method: 'DELETE',
endpoint: url,
...options,
}),
};
/**
* AI request tracking utilities
*/
export const aiTracker = {
/**
* Track an AI chat request
*/
chat: (provider, model, fn, options) => trackRequest(fn, {
type: 'ai',
provider,
model,
endpoint: 'chat',
...options,
}),
/**
* Track an AI completion request
*/
completion: (provider, model, fn, options) => trackRequest(fn, {
type: 'ai',
provider,
model,
endpoint: 'completion',
...options,
}),
/**
* Track an AI embedding request
*/
embedding: (provider, model, fn, options) => trackRequest(fn, {
type: 'ai',
provider,
model,
endpoint: 'embedding',
...options,
}),
};
/**
* MCP request tracking utilities
*/
export const mcpTracker = {
/**
* Track an MCP tool execution
*/
tool: (_serverName, toolName, fn, options) => trackRequest(fn, {
type: 'mcp',
toolName,
endpoint: `tool:${toolName}`,
...options,
}),
/**
* Track an MCP server connection
*/
connect: (_serverName, fn, options) => trackRequest(fn, {
type: 'mcp',
endpoint: 'connect',
...options,
}),
};
// RequestTracker is only used internally
// export default RequestTracker;
//# sourceMappingURL=request-tracker.js.map