UNPKG

@stacksleuth/core

Version:

Advanced TypeScript-based core profiling engine for StackSleuth - Real-time performance monitoring with flexible profiler, span tracing, and unified agent architecture. Features comprehensive metrics collection, memory optimization, and production-ready i

229 lines 7.61 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TraceCollector = void 0; const events_1 = __importDefault(require("events")); const types_1 = require("./types"); const utils_1 = require("./utils"); class TraceCollector extends events_1.default { constructor(config = {}) { super(); this.traces = new Map(); this.activeSpans = new Map(); this.config = { enabled: true, sampling: { rate: 1.0 }, filters: {}, output: { console: true }, ...config }; this.shouldSample = () => utils_1.SamplingUtils.shouldSample(this.config.sampling.rate); if (this.config.sampling.maxTracesPerSecond) { this.samplingThrottle = utils_1.SamplingUtils.createThrottler(this.config.sampling.maxTracesPerSecond); } } /** * Start a new trace */ startTrace(name, metadata = {}) { if (!this.config.enabled || !this.shouldSample()) { return null; } if (this.samplingThrottle && !this.samplingThrottle()) { return null; } const traceId = utils_1.IdGenerator.traceId(); const rootSpanId = utils_1.IdGenerator.spanId(); const timing = { start: utils_1.Timer.now() }; const trace = { id: traceId, name, timing, status: types_1.TraceStatus.PENDING, spans: [], metadata, rootSpanId }; this.traces.set(traceId, trace); this.emit('trace:started', trace); return trace; } /** * Complete a trace */ completeTrace(traceId, status = types_1.TraceStatus.SUCCESS) { const trace = this.traces.get(traceId); if (!trace) return; trace.timing.end = utils_1.Timer.now(); trace.timing.duration = utils_1.Timer.diff(trace.timing.start, trace.timing.end); trace.status = status; // Filter by minimum duration if configured if (this.config.filters.minDuration && trace.timing.duration < this.config.filters.minDuration) { this.traces.delete(traceId); return; } this.emit('trace:completed', trace); this.analyzeTrace(trace); } /** * Start a new span within a trace */ startSpan(traceId, name, type, parentId, metadata = {}) { const trace = this.traces.get(traceId); if (!trace) return null; const spanId = utils_1.IdGenerator.spanId(); const span = { id: spanId, traceId, parentId, name, type: type, timing: { start: utils_1.Timer.now() }, status: types_1.TraceStatus.PENDING, metadata, tags: [] }; this.activeSpans.set(spanId, span); trace.spans.push(span); this.emit('span:started', span); return span; } /** * Complete a span */ completeSpan(spanId, status = types_1.TraceStatus.SUCCESS, metadata = {}) { const span = this.activeSpans.get(spanId); if (!span) return; span.timing.end = utils_1.Timer.now(); span.timing.duration = utils_1.Timer.diff(span.timing.start, span.timing.end); span.status = status; span.metadata = { ...span.metadata, ...metadata }; this.activeSpans.delete(spanId); this.emit('span:completed', span); } /** * Add an error to a span */ addSpanError(spanId, error) { const span = this.activeSpans.get(spanId); if (!span) return; if (!span.errors) span.errors = []; span.errors.push(error); span.status = types_1.TraceStatus.ERROR; } /** * Get a trace by ID */ getTrace(traceId) { return this.traces.get(traceId); } /** * Get all traces */ getAllTraces() { return Array.from(this.traces.values()); } /** * Get traces within a time range */ getTracesByTimeRange(startTime, endTime) { return this.getAllTraces().filter(trace => trace.timing.start.millis >= startTime && trace.timing.start.millis <= endTime); } /** * Get performance statistics */ getStats() { const traces = this.getAllTraces(); const durations = traces .filter(t => t.timing.duration !== undefined) .map(t => t.timing.duration); const spanDurations = traces .flatMap(t => t.spans) .filter(s => s.timing.duration !== undefined) .map(s => s.timing.duration); return { traces: { total: traces.length, ...utils_1.PerformanceUtils.calculateStats(durations) }, spans: { total: spanDurations.length, ...utils_1.PerformanceUtils.calculateStats(spanDurations) } }; } /** * Clear old traces to prevent memory leaks */ cleanup(maxAge = 300000) { const cutoff = Date.now() - maxAge; for (const [traceId, trace] of this.traces.entries()) { if (trace.timing.start.millis < cutoff) { this.traces.delete(traceId); } } } /** * Analyze a trace for performance issues */ analyzeTrace(trace) { const issues = []; // Detect slow operations if (trace.timing.duration && trace.timing.duration > 1000) { issues.push({ id: utils_1.IdGenerator.spanId(), severity: 'high', type: 'slow_query', message: `Trace "${trace.name}" took ${utils_1.PerformanceUtils.formatDuration(trace.timing.duration)}`, suggestion: 'Consider optimizing the slowest spans in this trace', spanIds: trace.spans.map(s => s.id), traceId: trace.id }); } // Detect N+1 queries const dbSpans = trace.spans.filter(s => s.type === 'db_query'); if (dbSpans.length > 10) { issues.push({ id: utils_1.IdGenerator.spanId(), severity: 'medium', type: 'n_plus_one', message: `Potential N+1 query pattern detected (${dbSpans.length} database queries)`, suggestion: 'Consider using batch queries or eager loading', spanIds: dbSpans.map(s => s.id), traceId: trace.id }); } // Emit performance issues issues.forEach(issue => this.emit('performance:issue', issue)); } /** * Export traces in various formats */ export(format = 'json') { const traces = this.getAllTraces(); if (format === 'json') { return JSON.stringify(traces, null, 2); } // Simple CSV export const headers = ['traceId', 'name', 'duration', 'status', 'spanCount']; const rows = traces.map(trace => [ trace.id, trace.name, trace.timing.duration || 0, trace.status, trace.spans.length ]); return [headers, ...rows].map(row => row.join(',')).join('\n'); } } exports.TraceCollector = TraceCollector; //# sourceMappingURL=collector.js.map