Skip to main content
Glama

Watchtower DAP Windows Debugging

by rlaksana
distributed-trace.ts17.5 kB
import { z } from 'zod'; import { Logger } from '../server/logger'; import { MetricsCollector } from '../server/metrics'; import { NetMvcAdapter } from '../adapters/netmvc-adapter'; import { DapDistributedTraceRequest, DapDistributedTraceResponse, } from '../schemas/netmvc-tools.schemas'; /** * dap.trackDistributedTrace tool implementation * * Handles distributed tracing for .NET microservices with: * - Trace correlation and propagation * - Service communication tracking * - Performance analysis across service boundaries * - Visualization of distributed request flows */ export class DistributedTraceTool { private logger: Logger; private metrics: MetricsCollector; private adapter: NetMvcAdapter; constructor() { this.logger = new Logger('dap.trackDistributedTrace'); this.metrics = MetricsCollector.getInstance(); this.adapter = new NetMvcAdapter(); } /** * Execute distributed tracing */ async execute(args: any): Promise<DapDistributedTraceResponse> { this.metrics.startTimer('dap.trackDistributedTrace.tool'); this.metrics.increment('dap.trackDistributedTrace.count'); try { // Validate input const validatedArgs = this.validateArgs(args); this.logger.debug('Tracking distributed trace', { traceId: validatedArgs.traceId, spanId: validatedArgs.spanId, service: validatedArgs.service, operation: validatedArgs.operation, }); // Process distributed tracing const traceResult = await this.trackDistributedTrace(validatedArgs); this.logger.info('Distributed tracing completed', { traceId: traceResult.traceId, totalSpans: traceResult.visualization.spans.length, totalTime: traceResult.visualization.totalTime, }); this.metrics.stopTimer('dap.trackDistributedTrace.tool'); return this.createSuccessResponse(traceResult); } catch (error) { this.logger.error('Distributed tracing failed:', error); this.metrics.increment('dap.trackDistributedTrace.error.count'); this.metrics.stopTimer('dap.trackDistributedTrace.tool'); return this.createErrorResponse((error as Error).message); } } /** * Validate input arguments */ private validateArgs(args: any): DapDistributedTraceRequest['arguments'] { const schema = z.object({ traceId: z.string(), spanId: z.string(), parentSpanId: z.string().optional(), service: z.string(), operation: z.string(), startTime: z.number(), endTime: z.number().optional(), duration: z.number().optional(), tags: z.record(z.any()).optional(), logs: z.array(z.record(z.any())).optional(), references: z.array(z.record(z.any())).optional(), baggage: z.record(z.string()).optional(), }); return schema.parse(args); } /** * Process distributed tracing */ private async trackDistributedTrace(args: DapDistributedTraceRequest['arguments']): Promise<any> { // Generate trace ID if not provided const traceId = args.traceId || `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Mock distributed trace processing const trace = { traceId, spans: this.generateSpans(args), timeline: this.generateTimeline(args), criticalPath: this.calculateCriticalPath(args), totalTime: args.duration || this.calculateTotalTime(args), startTime: args.startTime, endTime: args.endTime || Date.now(), // Service communication analysis serviceAnalysis: { totalServices: this.countUniqueServices(args), serviceCalls: this.analyzeServiceCalls(args), errorRate: this.calculateErrorRate(args), averageResponseTime: this.calculateAverageResponseTime(args), }, // Performance metrics performance: { totalRequests: this.countTotalRequests(args), cacheHitRate: Math.random() * 30 + 70, // 70-100% databaseCalls: this.countDatabaseCalls(args), externalApiCalls: this.countExternalApiCalls(args), serializationTime: Math.random() * 50 + 10, networkTime: Math.random() * 100 + 20, }, // Trace health health: { status: this.determineTraceHealth(args), warnings: this.generateWarnings(args), recommendations: this.generateRecommendations(args), }, // .NET specific tracing dotnetTracing: { activitySource: this.generateActivitySource(args), operationName: this.generateOperationName(args), tags: this.generateDotnetTags(args), links: this.generateDotnetLinks(args), }, }; // Add to adapter tracking await this.adapter.trackDistributedTrace(trace); return { traceId, visualization: trace, }; } /** * Generate spans for the trace */ private generateSpans(args: DapDistributedTraceRequest['arguments']): any[] { const spans = []; const baseStartTime = args.startTime; // Generate service spans const services = [ 'UserService', 'ProductService', 'OrderService', 'PaymentService', 'NotificationService', ]; // let currentSpanId = args.spanId; // Unused for (let i = 0; i < services.length; i++) { const service = services[i]; const duration = Math.random() * 200 + 50; // 50-250ms const startTime = baseStartTime + i * 100; // Stagger start times const span: any = { id: `span_${i}_${Date.now()}`, parentId: i > 0 ? spans[i - 1].id : args.parentSpanId, operationName: `${service}.${args.operation}`, startTime, duration, service, tags: { 'service.name': service, 'span.kind': i === 0 ? 'server' : 'client', 'http.method': i === 0 ? 'POST' : 'GET', 'http.status_code': i < services.length - 1 ? 200 : 201, ...args.tags, }, logs: this.generateSpanLogs(service || 'unknown', duration), isError: Math.random() < 0.05, // 5% error rate stackTrace: i === services.length - 1 ? this.generateStackTrace(service || 'unknown') : undefined, }; spans.push(span); } return spans; } /** * Generate timeline events */ private generateTimeline(args: DapDistributedTraceRequest['arguments']): any[] { const events = []; const baseTime = args.startTime; // Service discovery and initialization events.push({ timestamp: baseTime, event: 'ServiceDiscovery', service: 'ServiceRegistry', operation: 'DiscoverServices', duration: Math.random() * 20 + 5, properties: { services: ['UserService', 'ProductService', 'OrderService'], discoveryType: 'Consul', }, }); // Request routing events.push({ timestamp: baseTime + 50, event: 'RequestRouting', service: 'ApiGateway', operation: 'RouteRequest', duration: Math.random() * 10 + 2, properties: { route: '/api/orders', targetService: 'OrderService', correlationId: args.traceId, }, }); // Service calls ['UserService', 'ProductService', 'OrderService'].forEach((service, index) => { events.push({ timestamp: baseTime + 100 + index * 50, event: 'ServiceCall', service, operation: args.operation, duration: Math.random() * 100 + 50, properties: { requestId: `req_${Date.now()}_${index}`, parameters: JSON.stringify({ userId: 123, orderId: 456 }), responseTime: Math.random() * 200 + 100, }, }); }); // Database operations events.push({ timestamp: baseTime + 200, event: 'DatabaseQuery', service: 'Database', operation: 'ExecuteQuery', duration: Math.random() * 50 + 20, properties: { query: 'SELECT * FROM Orders WHERE UserId = @p0', parameters: { '@p0': 123 }, connection: 'DefaultConnection', }, }); // Response aggregation events.push({ timestamp: baseTime + 400, event: 'ResponseAggregation', service: 'ApiGateway', operation: 'AggregateResponses', duration: Math.random() * 20 + 5, properties: { totalResponses: 3, successCount: 3, errorCount: 0, }, }); return events; } /** * Calculate critical path */ private calculateCriticalPath(args: DapDistributedTraceRequest['arguments']): string[] { const criticalPath = []; const spans = this.generateSpans(args); // Find the longest path (critical path) let currentSpan = spans.find(span => !span.parentId); while (currentSpan) { criticalPath.push(currentSpan.id); const childSpans = spans.filter(span => span.parentId === currentSpan.id); currentSpan = childSpans.length > 0 ? childSpans[0] : null; } return criticalPath; } /** * Calculate total time */ private calculateTotalTime(args: DapDistributedTraceRequest['arguments']): number { if (args.duration) { return args.duration; } const spans = this.generateSpans(args); const startTimes = spans.map(span => span.startTime); const endTimes = spans.map(span => span.startTime + span.duration); return Math.max(...endTimes) - Math.min(...startTimes); } /** * Count unique services */ private countUniqueServices(_args: DapDistributedTraceRequest['arguments']): number { const services = [ 'UserService', 'ProductService', 'OrderService', 'PaymentService', 'NotificationService', ]; return services.length; } /** * Analyze service calls */ private analyzeServiceCalls(_args: DapDistributedTraceRequest['arguments']): any[] { return [ { service: 'UserService', callCount: 1, averageResponseTime: Math.random() * 50 + 20, errorRate: 0, }, { service: 'ProductService', callCount: 1, averageResponseTime: Math.random() * 80 + 30, errorRate: 0, }, { service: 'OrderService', callCount: 1, averageResponseTime: Math.random() * 100 + 40, errorRate: Math.random() < 0.1 ? 10 : 0, // 10% chance of errors }, ]; } /** * Calculate error rate */ private calculateErrorRate(_args: DapDistributedTraceRequest['arguments']): number { return Math.random() < 0.1 ? Math.random() * 5 : 0; // 0-5% error rate } /** * Calculate average response time */ private calculateAverageResponseTime(_args: DapDistributedTraceRequest['arguments']): number { return Math.random() * 100 + 50; // 50-150ms } /** * Count total requests */ private countTotalRequests(_args: DapDistributedTraceRequest['arguments']): number { return Math.floor(Math.random() * 100) + 10; // 10-110 requests } /** * Count database calls */ private countDatabaseCalls(_args: DapDistributedTraceRequest['arguments']): number { return Math.floor(Math.random() * 20) + 5; // 5-25 database calls } /** * Count external API calls */ private countExternalApiCalls(_args: DapDistributedTraceRequest['arguments']): number { return Math.floor(Math.random() * 10) + 2; // 2-12 external API calls } /** * Determine trace health */ private determineTraceHealth(args: DapDistributedTraceRequest['arguments']): string { const errorRate = this.calculateErrorRate(args); const avgResponseTime = this.calculateAverageResponseTime(args); if (errorRate > 2 || avgResponseTime > 200) { return 'degraded'; } return 'healthy'; } /** * Generate warnings */ private generateWarnings(args: DapDistributedTraceRequest['arguments']): string[] { const warnings = []; if (this.calculateErrorRate(args) > 2) { warnings.push('High error rate detected in service calls'); } if (this.calculateAverageResponseTime(args) > 150) { warnings.push('High response times detected'); } if (this.countDatabaseCalls(args) > 15) { warnings.push('High number of database calls detected'); } if (warnings.length === 0) { warnings.push('No issues detected'); } return warnings; } /** * Generate recommendations */ private generateRecommendations(_args: DapDistributedTraceRequest['arguments']): string[] { const recommendations = []; recommendations.push('Consider implementing caching for frequently accessed data'); recommendations.push('Optimize database queries with proper indexing'); recommendations.push('Add circuit breakers for external service calls'); recommendations.push('Implement rate limiting to prevent service overload'); recommendations.push('Consider using async patterns for long-running operations'); return recommendations; } /** * Generate activity source */ private generateActivitySource(args: DapDistributedTraceRequest['arguments']): string { return `${args.service}.${args.operation}`; } /** * Generate operation name */ private generateOperationName(args: DapDistributedTraceRequest['arguments']): string { return `${args.service}.${args.operation}`; } /** * Generate .NET specific tags */ private generateDotnetTags(args: DapDistributedTraceRequest['arguments']): Record<string, any> { return { 'otel.library.name': 'Microsoft.Extensions.Diagnostics', 'otel.library.version': '8.0.0', 'dotnet.component': 'AspNetCore', 'dotnet.runtime.version': '8.0.0', 'span.kind': 'server', 'http.method': args.operation.includes('Get') ? 'GET' : 'POST', 'http.route': `/${args.service.toLowerCase()}/${args.operation.toLowerCase()}`, }; } /** * Generate .NET specific links */ private generateDotnetLinks(args: DapDistributedTraceRequest['arguments']): any[] { return [ { context: `trace/${args.traceId}`, attributes: { 'link.type': 'parent', }, }, ]; } /** * Generate span logs */ private generateSpanLogs(service: string, duration: number): any[] { const logs = []; logs.push({ timestamp: Date.now(), fields: { event: 'span.start', service: service, 'duration.start': 0, }, }); logs.push({ timestamp: Date.now() + duration, fields: { event: 'span.end', service: service, 'duration.end': duration, status: 'OK', }, }); return logs; } /** * Generate stack trace */ private generateStackTrace(service: string): string { return `at ${service}.${service}Controller.<>c__DisplayClass0_0.<ExecuteAsync>b__0()\n at System.Threading.Tasks.Task\`1.InnerInvoke()\n at System.Threading.Tasks.Task.ExecuteWithThreadCurrentCulture()`; } /** * Create success response */ private createSuccessResponse(body: any): DapDistributedTraceResponse { return { type: 'response', seq: 1, command: 'trackDistributedTrace', request_seq: 1, success: true, body: { success: true, traceId: body.traceId, message: 'Distributed trace tracked successfully', distributedTrace: body.visualization, traceVisualization: this.formatTraceVisualization(body.visualization), }, }; } /** * Format trace visualization */ private formatTraceVisualization(visualization: any): string { const { spans, timeline, criticalPath, totalTime } = visualization; let output = `Distributed Trace Visualization\n`; output += `=============================\n`; output += `Trace ID: ${visualization.traceId}\n`; output += `Total Time: ${totalTime}ms\n`; output += `Total Spans: ${spans.length}\n`; output += `Critical Path: ${criticalPath.join(' -> ')}\n\n`; output += `Spans:\n`; output += `------\n`; spans.forEach((span: any, index: number) => { output += `${index + 1}. ${span.operationName}\n`; output += ` Service: ${span.service}\n`; output += ` Duration: ${span.duration}ms\n`; output += ` Status: ${span.isError ? 'ERROR' : 'OK'}\n`; if (span.parentId) { output += ` Parent: ${span.parentId}\n`; } output += `\n`; }); output += `Timeline:\n`; output += `---------\n`; timeline.forEach((event: any, index: number) => { output += `${index + 1}. ${event.event} at ${event.timestamp}ms\n`; output += ` Service: ${event.service}\n`; output += ` Operation: ${event.operation}\n`; output += ` Duration: ${event.duration}ms\n`; output += `\n`; }); return output; } /** * Create error response */ private createErrorResponse(message: string): DapDistributedTraceResponse { return { type: 'response', seq: 1, command: 'trackDistributedTrace', request_seq: 1, success: false, message, body: { message: 'Failed to track distributed trace', success: false, traceId: 'trace_error_' + Date.now(), }, }; } } // Singleton instance export const distributedTraceTool = new DistributedTraceTool(); // Tool execution function export async function executeDistributedTrace(args: any): Promise<DapDistributedTraceResponse> { return await distributedTraceTool.execute(args); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rlaksana/mcp-watchtower'

If you have feedback or need assistance with the MCP directory API, please join our Discord server