Production-ready analytics: real telemetry (recent executions, hourly traffic, category/server rollups), kill all fake data

This commit is contained in:
drjones
2026-08-14 01:30:28 +00:00
parent 5e924bb6e9
commit 6993e81221
4 changed files with 256 additions and 134 deletions

View File

@@ -1,15 +1,29 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { BarChart3, Activity, Clock, Cpu, Server, CheckCircle, Zap, RefreshCw, Layers, Compass, ArrowUpRight } from 'lucide-react'; import { BarChart3, Activity, Clock, Zap, Server, CheckCircle, XCircle, RefreshCw, Layers, ArrowUpRight, TrendingUp, Gauge } from 'lucide-react';
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar, PieChart, Pie, Cell } from 'recharts'; import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar, PieChart, Pie, Cell, CartesianGrid } from 'recharts';
import { ServerStats } from '../types'; import { ServerStats } from '../types';
interface AnalyticsTabProps { interface AnalyticsTabProps {
stats: ServerStats | null; stats: ServerStats | null;
} }
const CATEGORY_COLORS: Record<string, string> = {
web: '#4f46e5',
code: '#0ea5e9',
data: '#10b981',
media: '#f59e0b',
memory: '#a855f7',
network: '#ec4899',
dev: '#6366f1',
custom: '#94a3b8',
};
const PIE_COLORS = ['#4f46e5', '#0ea5e9', '#10b981', '#f59e0b', '#a855f7', '#ec4899', '#6366f1', '#94a3b8'];
export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => { export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
const [routerStats, setRouterStats] = useState<any>(null); const [routerStats, setRouterStats] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [lastUpdated, setLastUpdated] = useState<string>('');
const fetchRouterStats = async () => { const fetchRouterStats = async () => {
try { try {
@@ -18,6 +32,7 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
setRouterStats(data); setRouterStats(data);
setLastUpdated(new Date().toLocaleTimeString());
} }
} catch (err) { } catch (err) {
console.error('Failed to fetch router stats:', err); console.error('Failed to fetch router stats:', err);
@@ -32,179 +47,224 @@ export const AnalyticsTab: React.FC<AnalyticsTabProps> = ({ stats }) => {
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
const totalToolCalls = routerStats?.totalToolCalls || stats?.mcpRequests || 142; const totalCalls = routerStats?.totalRoutedCalls ?? 0;
const avgLatency = routerStats?.avgLatencyMs || stats?.avgLatencyMs || 4; const totalSuccess = routerStats?.totalSuccess ?? 0;
const totalErrors = routerStats?.totalErrors ?? 0;
const successRate = totalCalls > 0 ? Math.round((totalSuccess / totalCalls) * 100) : 0;
const errorRate = totalCalls > 0 ? Math.round((totalErrors / totalCalls) * 100) : 0;
const avgLatency = routerStats?.avgLatencyMs ?? stats?.avgLatencyMs ?? 0;
const topToolsData = routerStats?.topTools && routerStats.topTools.length > 0 const toolMetrics = routerStats?.toolMetrics || [];
? routerStats.topTools.map((t: any) => ({ name: t.toolName, calls: t.callCount })) const topTools = toolMetrics.slice(0, 8).map((t: any) => ({ name: t.toolName, calls: t.totalCalls }));
: [ const categoryData = (routerStats?.categoryBreakdown || []).map((c: any) => ({ name: c.category, value: c.totalCalls }));
{ name: 'verify_and_refine_loop', calls: 38 }, const serverData = (routerStats?.serverBreakdown || []).slice(0, 10);
{ name: 'diff_patch_validator', calls: 31 }, const recentExecutions = routerStats?.recentExecutions || [];
{ name: 'tree_of_thought_brancher', calls: 27 }, const trafficData = (routerStats?.trafficByHour || []).map((b: any) => ({
{ name: 'code_ast_inspector', calls: 24 }, hour: b.hour.slice(11, 16),
{ name: 'web_scrape_markdown', calls: 19 }, calls: b.count,
]; success: b.success,
errors: b.errors,
latency: b.avgLatencyMs,
}));
const trafficData = [ const fmtHour = (iso: string) => new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
{ time: '10:00', requests: 12, mcp: 8, rest: 4, latency: 5 },
{ time: '10:05', requests: 19, mcp: 14, rest: 5, latency: 4 }, const EmptyState = ({ message }: { message: string }) => (
{ time: '10:10', requests: 34, mcp: 28, rest: 6, latency: 6 }, <div className="h-full flex flex-col items-center justify-center text-center text-slate-400 py-10">
{ time: '10:15', requests: 27, mcp: 20, rest: 7, latency: 4 }, <Activity className="h-8 w-8 mb-2 opacity-40" />
{ time: '10:20', requests: 45, mcp: 36, rest: 9, latency: 5 }, <p className="text-xs font-medium">{message}</p>
{ time: '10:25', requests: 52, mcp: 42, rest: 10, latency: 3 }, </div>
{ time: '10:30', requests: stats?.totalRequests || 68, mcp: stats?.mcpRequests || 54, rest: stats?.restRequests || 14, latency: avgLatency }, );
];
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Header Banner */} {/* Header */}
<div className="bg-gradient-to-r from-indigo-950 via-slate-900 to-indigo-950 rounded-3xl p-6 sm:p-8 text-white shadow-xl border border-slate-800 relative overflow-hidden flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="bg-gradient-to-r from-indigo-950 via-slate-900 to-indigo-950 rounded-3xl p-6 sm:p-8 text-white shadow-xl border border-slate-800 relative overflow-hidden flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="relative z-10 max-w-3xl space-y-2"> <div className="relative z-10 max-w-3xl space-y-2">
<span className="inline-flex items-center space-x-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30"> <span className="inline-flex items-center space-x-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
<BarChart3 className="h-3.5 w-3.5" /> <BarChart3 className="h-3.5 w-3.5" />
<span>Live MCP Router & Telemetry Engine</span> <span>Live MCP Router & Telemetry Engine</span>
</span> </span>
<h2 className="text-2xl sm:text-3xl font-black tracking-tight">Real-Time Router Performance & Tool Audit</h2> <h2 className="text-2xl sm:text-3xl font-black tracking-tight">Router Performance & Tool Audit</h2>
<p className="text-slate-300 text-xs sm:text-sm leading-relaxed"> <p className="text-slate-300 text-xs sm:text-sm leading-relaxed">
Monitor zero-key algorithmic tool loops, remote Road Sign dispatch latencies, and agent invocation trends across the universal MCP Source of Truth. Real invocation traffic, latency, and success rates across {stats?.totalMCPServersCount ?? 0} registered MCP servers. {lastUpdated && <span className="text-indigo-300 font-mono">Last sync {lastUpdated}</span>}
</p> </p>
</div> </div>
<button <button
onClick={fetchRouterStats} onClick={fetchRouterStats}
disabled={loading} disabled={loading}
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-bold rounded-xl border border-slate-700 transition flex items-center space-x-2 shrink-0" className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-bold rounded-xl border border-slate-700 transition flex items-center space-x-2 shrink-0"
> >
<RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} /> <RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} />
<span>Refresh Metrics</span> <span>Refresh</span>
</button> </button>
</div> </div>
{/* Top Metrics Cards */} {/* Metric Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-2 lg:grid-cols-6 gap-4">
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2"> <div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
<div className="flex items-center justify-between text-slate-500"> <div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Total Calls</span><Activity className="h-4 w-4 text-indigo-600" /></div>
<span className="text-xs font-bold uppercase tracking-wider">Total MCP Requests</span> <div className="text-2xl font-black text-slate-900 font-mono">{totalCalls}</div>
<Activity className="h-4 w-4 text-indigo-600" /> <div className="text-[10px] text-slate-400">routed tool invocations</div>
</div> </div>
<div className="text-2xl font-black text-slate-900 font-mono"> <div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
{stats?.totalRequests || totalToolCalls} <div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Success</span><CheckCircle className="h-4 w-4 text-emerald-600" /></div>
<div className="text-2xl font-black text-emerald-600 font-mono">{successRate}%</div>
<div className="text-[10px] text-slate-400">{totalSuccess} succeeded</div>
</div> </div>
<div className="text-[11px] text-slate-500"> <div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
JSON-RPC & SSE Protocol Traffic <div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Errors</span><XCircle className="h-4 w-4 text-rose-600" /></div>
<div className="text-2xl font-black text-rose-600 font-mono">{errorRate}%</div>
<div className="text-[10px] text-slate-400">{totalErrors} failed</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Avg Latency</span><Clock className="h-4 w-4 text-amber-500" /></div>
<div className="text-2xl font-black text-slate-900 font-mono">{avgLatency}<span className="text-xs font-normal text-slate-400">ms</span></div>
<div className="text-[10px] text-slate-400">per routed call</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Active Tools</span><Zap className="h-4 w-4 text-amber-500" /></div>
<div className="text-2xl font-black text-slate-900 font-mono">{stats?.registeredToolsCount ?? 0}</div>
<div className="text-[10px] text-slate-400">across active servers</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-4 shadow-sm space-y-1">
<div className="flex items-center justify-between text-slate-400"><span className="text-[11px] font-bold uppercase tracking-wider">Servers</span><Server className="h-4 w-4 text-purple-600" /></div>
<div className="text-2xl font-black text-slate-900 font-mono">{stats?.activeMCPServersCount ?? 0}<span className="text-xs font-normal text-slate-400">/{stats?.totalMCPServersCount ?? 0}</span></div>
<div className="text-[10px] text-slate-400">active / registered</div>
</div> </div>
</div> </div>
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2"> {/* Traffic + Category */}
<div className="flex items-center justify-between text-slate-500">
<span className="text-xs font-bold uppercase tracking-wider">Aggregated Tools</span>
<Zap className="h-4 w-4 text-amber-500" />
</div>
<div className="text-2xl font-black text-slate-900 font-mono">
{stats?.registeredToolsCount || 27}
</div>
<div className="text-[11px] text-emerald-600 font-medium">
Across {stats?.activeMCPServersCount || 4} Servers & Road Signs
</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2">
<div className="flex items-center justify-between text-slate-500">
<span className="text-xs font-bold uppercase tracking-wider">Router Dispatch Latency</span>
<Clock className="h-4 w-4 text-emerald-600" />
</div>
<div className="text-2xl font-black text-slate-900 font-mono">
{avgLatency} <span className="text-xs font-normal text-slate-500">ms</span>
</div>
<div className="text-[11px] text-emerald-600 font-semibold">
Sub-millisecond local in-memory routing
</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-5 shadow-sm space-y-2">
<div className="flex items-center justify-between text-slate-500">
<span className="text-xs font-bold uppercase tracking-wider">Server Memory</span>
<Server className="h-4 w-4 text-purple-600" />
</div>
<div className="text-2xl font-black text-slate-900 font-mono">
{stats?.memoryUsageMb || 34} <span className="text-xs font-normal text-slate-500">MB</span>
</div>
<div className="text-[11px] text-slate-500">
Node.js Runtime Container Heap
</div>
</div>
</div>
{/* Traffic Charts */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Request Time Series */}
<div className="lg:col-span-2 bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4"> <div className="lg:col-span-2 bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
<h3 className="text-sm sm:text-base font-bold text-slate-900 flex items-center space-x-2"> <h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
<Activity className="h-5 w-5 text-indigo-600" /> <TrendingUp className="h-5 w-5 text-indigo-600" />
<span>MCP Request Volume & Routing Latency Trend</span> <span>Request Volume & Latency (per hour)</span>
</h3> </h3>
{trafficData.length === 0 ? <EmptyState message="No traffic recorded yet — run a tool or workflow to populate this chart." /> : (
<div className="h-64 w-full"> <div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<AreaChart data={trafficData}> <AreaChart data={trafficData}>
<defs> <defs>
<linearGradient id="colorMcp" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="colorCalls" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#4f46e5" stopOpacity={0.8}/> <stop offset="5%" stopColor="#4f46e5" stopOpacity={0.85}/>
<stop offset="95%" stopColor="#4f46e5" stopOpacity={0}/> <stop offset="95%" stopColor="#4f46e5" stopOpacity={0}/>
</linearGradient> </linearGradient>
</defs> </defs>
<XAxis dataKey="time" stroke="#94a3b8" fontSize={11} /> <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="hour" stroke="#94a3b8" fontSize={11} />
<YAxis stroke="#94a3b8" fontSize={11} /> <YAxis stroke="#94a3b8" fontSize={11} />
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} /> <Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
<Area type="monotone" dataKey="mcp" name="MCP JSON-RPC Calls" stroke="#4f46e5" fillOpacity={1} fill="url(#colorMcp)" /> <Area type="monotone" dataKey="calls" name="Calls" stroke="#4f46e5" fillOpacity={1} fill="url(#colorCalls)" />
<Area type="monotone" dataKey="errors" name="Errors" stroke="#f43f5e" fill="none" />
</AreaChart> </AreaChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
)}
</div> </div>
{/* Top Tools Bar Chart */}
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4"> <div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
<h3 className="text-sm sm:text-base font-bold text-slate-900 flex items-center space-x-2"> <h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
<Layers className="h-5 w-5 text-emerald-600" />
<span>Traffic by Category</span>
</h3>
{categoryData.length === 0 ? <EmptyState message="No category traffic yet." /> : (
<div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie data={categoryData} dataKey="value" nameKey="name" cx="50%" cy="50%" innerRadius={50} outerRadius={80} paddingAngle={2}>
{categoryData.map((_: any, i: number) => <Cell key={i} fill={PIE_COLORS[i % PIE_COLORS.length]} />)}
</Pie>
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
</PieChart>
</ResponsiveContainer>
</div>
)}
<div className="flex flex-wrap gap-2">
{categoryData.map((c: any, i: number) => (
<span key={i} className="inline-flex items-center space-x-1 text-[10px] text-slate-600">
<span className="w-2 h-2 rounded-full" style={{ background: PIE_COLORS[i % PIE_COLORS.length] }} />
<span className="font-medium">{c.name}</span>
</span>
))}
</div>
</div>
</div>
{/* Top Tools + Server Breakdown */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
<h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
<Zap className="h-5 w-5 text-emerald-600" /> <Zap className="h-5 w-5 text-emerald-600" />
<span>Most Invoked Tools</span> <span>Most Invoked Tools</span>
</h3> </h3>
{topTools.length === 0 ? <EmptyState message="No tool invocations recorded yet." /> : (
<div className="h-64 w-full"> <div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<BarChart data={topToolsData} layout="vertical"> <BarChart data={topTools} layout="vertical">
<XAxis type="number" stroke="#94a3b8" fontSize={10} /> <XAxis type="number" stroke="#94a3b8" fontSize={10} allowDecimals={false} />
<YAxis dataKey="name" type="category" stroke="#94a3b8" fontSize={10} width={120} /> <YAxis dataKey="name" type="category" stroke="#94a3b8" fontSize={10} width={150} />
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} /> <Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', color: '#f8fafc', borderRadius: '12px' }} />
<Bar dataKey="calls" name="Tool Invocations" fill="#10b981" radius={[0, 6, 6, 0]} /> <Bar dataKey="calls" name="Invocations" fill="#10b981" radius={[0, 6, 6, 0]} />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
)}
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-4">
<h3 className="text-sm font-bold text-slate-900 flex items-center space-x-2">
<Server className="h-5 w-5 text-purple-600" />
<span>Traffic by Server</span>
</h3>
{serverData.length === 0 ? <EmptyState message="No server traffic yet." /> : (
<div className="space-y-3 max-h-64 overflow-y-auto pr-1">
{serverData.map((s: any) => {
const pct = totalCalls > 0 ? (s.totalCalls / totalCalls) * 100 : 0;
return (
<div key={s.serverName}>
<div className="flex items-center justify-between text-xs mb-1">
<span className="font-semibold text-slate-700 truncate mr-2">{s.serverName}</span>
<span className="text-slate-400 font-mono shrink-0">{s.totalCalls} · {s.avgLatencyMs}ms</span>
</div>
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
<div className="h-full rounded-full" style={{ width: `${pct}%`, background: '#6366f1' }} />
</div>
</div>
);
})}
</div>
)}
</div> </div>
</div> </div>
{/* Recent Telemetry Executions */} {/* Live Execution Stream */}
{routerStats?.recentExecutions && routerStats.recentExecutions.length > 0 && (
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-3"> <div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-3">
<h3 className="text-sm font-bold text-slate-900 uppercase tracking-wider"> <h3 className="text-sm font-bold text-slate-900 uppercase tracking-wider flex items-center space-x-2">
Live Tool Call Stream Log ({routerStats.recentExecutions.length}) <Gauge className="h-4 w-4 text-indigo-600" />
<span>Live Tool Call Stream</span>
{recentExecutions.length > 0 && <span className="text-slate-400 font-mono text-xs normal-case">({recentExecutions.length} recent)</span>}
</h3> </h3>
<div className="divide-y divide-slate-100 overflow-hidden"> {recentExecutions.length === 0 ? (
{routerStats.recentExecutions.map((log: any, idx: number) => ( <p className="text-xs text-slate-400 py-4 text-center">No executions recorded yet traffic will stream here in real time.</p>
) : (
<div className="divide-y divide-slate-100 max-h-80 overflow-y-auto">
{recentExecutions.map((log: any, idx: number) => (
<div key={idx} className="py-2.5 flex items-center justify-between text-xs font-mono"> <div key={idx} className="py-2.5 flex items-center justify-between text-xs font-mono">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2 min-w-0">
<span className={`w-2 h-2 rounded-full ${log.success ? 'bg-emerald-500' : 'bg-rose-500'}`} /> <span className={`w-2 h-2 rounded-full shrink-0 ${log.success ? 'bg-emerald-500' : 'bg-rose-500'}`} />
<span className="font-bold text-slate-900">{log.toolName}</span> <span className="font-bold text-slate-900 truncate">{log.toolName}</span>
<span className="text-slate-400 truncate hidden sm:inline">{log.serverName}</span>
</div> </div>
<div className="flex items-center space-x-4 text-slate-500"> <div className="flex items-center space-x-4 text-slate-500 shrink-0">
<span>{log.durationMs}ms</span> <span className={log.durationMs > 5000 ? 'text-amber-600' : ''}>{log.durationMs}ms</span>
<span>{new Date(log.timestamp).toLocaleTimeString()}</span> <span>{fmtHour(log.timestamp)}</span>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div>
)} )}
</div> </div>
</div>
); );
}; };

View File

@@ -758,7 +758,7 @@ export const MCPHubTab: React.FC<MCPHubTabProps> = ({
<div> <div>
<span className="text-slate-400 block text-xs">Router Latency</span> <span className="text-slate-400 block text-xs">Router Latency</span>
<span className="text-xl font-black text-emerald-400"> <span className="text-xl font-black text-emerald-400">
{stats?.avgLatencyMs || 3}ms {stats?.avgLatencyMs || 0}ms
</span> </span>
</div> </div>
</div> </div>

View File

@@ -101,7 +101,7 @@ export const OverviewTab: React.FC<OverviewTabProps> = ({ stats, setActiveTab })
<Wrench className="h-4 w-4 text-sky-500" /> <Wrench className="h-4 w-4 text-sky-500" />
</div> </div>
<div className="flex items-baseline space-x-2"> <div className="flex items-baseline space-x-2">
<span className="text-xl sm:text-2xl font-bold text-slate-900">{stats?.registeredToolsCount || 19}</span> <span className="text-xl sm:text-2xl font-bold text-slate-900">{stats?.registeredToolsCount || 0}</span>
<span className="text-xs text-slate-500 font-mono">Active Tools</span> <span className="text-xs text-slate-500 font-mono">Active Tools</span>
</div> </div>
<p className="text-xs text-slate-500">Web, Code, Data, Media, Memory</p> <p className="text-xs text-slate-500">Web, Code, Data, Media, Memory</p>

View File

@@ -1872,6 +1872,20 @@ class MCPRegistryManager {
lastError?: string; lastError?: string;
}> = new Map(); }> = new Map();
// Recent execution stream (ring buffer, capped)
private recentExecutions: Array<{
toolName: string;
serverName: string;
category: string;
durationMs: number;
success: boolean;
timestamp: string;
error?: string;
}> = [];
// Traffic bucketed by hour for time-series charting
private trafficByHour: Map<string, { hour: string; count: number; success: number; errors: number; totalLatencyMs: number }> = new Map();
// Record tool execution in the MCP router stats // Record tool execution in the MCP router stats
public recordUsage(toolName: string, serverId: string, serverName: string, category: string, durationMs: number, success: boolean, error?: string) { public recordUsage(toolName: string, serverId: string, serverName: string, category: string, durationMs: number, success: boolean, error?: string) {
const existing = this.toolMetrics.get(toolName) || { const existing = this.toolMetrics.get(toolName) || {
@@ -1895,6 +1909,26 @@ class MCPRegistryManager {
existing.totalDurationMs += durationMs; existing.totalDurationMs += durationMs;
existing.lastExecutedAt = new Date().toISOString(); existing.lastExecutedAt = new Date().toISOString();
this.toolMetrics.set(toolName, existing); this.toolMetrics.set(toolName, existing);
// Recent execution stream (ring buffer, cap 100)
this.recentExecutions.unshift({
toolName,
serverName,
category,
durationMs,
success,
timestamp: new Date().toISOString(),
error,
});
if (this.recentExecutions.length > 100) this.recentExecutions.pop();
// Traffic bucketed by hour
const hour = new Date().toISOString().slice(0, 13) + ':00';
const bucket = this.trafficByHour.get(hour) || { hour, count: 0, success: 0, errors: 0, totalLatencyMs: 0 };
bucket.count++;
if (success) bucket.success++; else bucket.errors++;
bucket.totalLatencyMs += durationMs;
this.trafficByHour.set(hour, bucket);
} }
// Get complete Router Usage Report // Get complete Router Usage Report
@@ -1928,12 +1962,40 @@ class MCPRegistryManager {
const totalDuration = list.reduce((acc, m) => acc + m.totalDurationMs, 0); const totalDuration = list.reduce((acc, m) => acc + m.totalDurationMs, 0);
const avgLatencyMs = totalRoutedCalls > 0 ? Math.round(totalDuration / totalRoutedCalls) : 0; const avgLatencyMs = totalRoutedCalls > 0 ? Math.round(totalDuration / totalRoutedCalls) : 0;
// Category rollup
const catMap = new Map<string, { category: string; totalCalls: number; successCount: number; errorCount: number }>();
for (const m of list) {
const c = catMap.get(m.category) || { category: m.category, totalCalls: 0, successCount: 0, errorCount: 0 };
c.totalCalls += m.totalCalls; c.successCount += m.successCount; c.errorCount += m.errorCount;
catMap.set(m.category, c);
}
// Server rollup
const srvMap = new Map<string, { serverName: string; totalCalls: number; successCount: number; errorCount: number; totalDurationMs: number }>();
for (const m of list) {
const s = srvMap.get(m.serverName) || { serverName: m.serverName, totalCalls: 0, successCount: 0, errorCount: 0, totalDurationMs: 0 };
s.totalCalls += m.totalCalls; s.successCount += m.successCount; s.errorCount += m.errorCount; s.totalDurationMs += m.totalDurationMs;
srvMap.set(m.serverName, s);
}
const serverBreakdown = Array.from(srvMap.values())
.map(s => ({ ...s, avgLatencyMs: s.totalCalls > 0 ? Math.round(s.totalDurationMs / s.totalCalls) : 0 }))
.sort((a, b) => b.totalCalls - a.totalCalls);
const trafficByHour = Array.from(this.trafficByHour.values())
.sort((a, b) => a.hour.localeCompare(b.hour))
.map(b => ({ ...b, avgLatencyMs: b.count > 0 ? Math.round(b.totalLatencyMs / b.count) : 0 }));
return { return {
totalRoutedCalls, totalRoutedCalls,
totalSuccess, totalSuccess,
totalErrors, totalErrors,
avgLatencyMs, avgLatencyMs,
toolMetrics: list.sort((a, b) => b.totalCalls - a.totalCalls), toolMetrics: list.sort((a, b) => b.totalCalls - a.totalCalls),
recentExecutions: this.recentExecutions.slice(0, 50),
trafficByHour,
categoryBreakdown: Array.from(catMap.values()).sort((a, b) => b.totalCalls - a.totalCalls),
serverBreakdown,
}; };
} }