diff --git a/dashboard/dashboard.py b/dashboard/dashboard.py
index ecda580..5e9fdfa 100644
--- a/dashboard/dashboard.py
+++ b/dashboard/dashboard.py
@@ -101,7 +101,19 @@ body { background: #0b0f17; color: #bcc3cd; font-family: -apple-system, BlinkMac
-
+
+ 📊 Performance Analytics
+
+
+
+
+
+ Latency — P50 / P95 / P99 (ms)
+ Throughput — Tokens / sec
+ Routing Effectiveness — by Reason
+
+
+
Live Stream
| Time | Agent | Model | Reason | Tier |
@@ -188,8 +200,47 @@ var grid='';for(var g=0;g<=4;g++){var y=(g/4)*H;grid+=''+grid+paths+'';
lg.innerHTML=mn.map(function(m){return''+(ML[m]||m)+'';}).join('');
}
+var perfWindow='1';
+function switchPerfWindow(w){perfWindow=w;document.querySelectorAll('.btn-sm-period').forEach(function(b,i){if(i>=4)b.classList.toggle('active',b.textContent.trim().replace('h','')===w)});loadPerf();}
+function loadPerf(){fetch('/api/performance?window='+perfWindow).then(function(r){return r.json()}).then(renderPerf).catch(function(){})}
+function renderPerf(d){
+var models=d.models||[],reasons=d.reasons||[],agents=d.agents||[],sum=d.summary||{};
+// Latency bars: p50/p95/p99 per model
+var mlab={'qwen3.6-35B-A3B':'35B MoE','qwen3.6-27B-code':'27B Dense','qwen3.5-9b-vlm':'9B VLM'};
+var mcol={'qwen3.6-35B-A3B':'#a78bfa','qwen3.6-27B-code':'#f59e0b','qwen3.5-9b-vlm':'#22c55e'};
+if(!models.length){$('perf-latency').innerHTML='Accumulating data...
';return;}
+var maxLat=Math.max(...models.map(function(m){return m.latency.p99||0}),1);
+var latHTML=models.map(function(m){
+var l=m.latency||{},p50=l.p50||0,p95=l.p95||0,p99=l.p99||0,c=mcol[m.model]||'#38bdf8';
+return''+mlab[m.model]+''+m.count+' reqs
'+
+'
'+
+'
p50: '+p50+'msp95: '+p95+'msp99: '+p99+'ms
';
+}).join('');
+$('perf-latency').innerHTML=latHTML;
+// Throughput comparison
+var maxTps=Math.max(...models.map(function(m){return m.throughput.avg_tokens_per_sec||0}),1);
+var tpsHTML=models.map(function(m){
+var t=m.throughput||{},avg=t.avg_tokens_per_sec||0,p50=t.p50||0,c=mcol[m.model]||'#38bdf8';
+return''+mlab[m.model]+''+avg+' tok/s
'+
+'
'+
+'
';
+}).join('');
+$('perf-throughput').innerHTML=tpsHTML;
+// Routing reasons table
+if(reasons.length){
+var rHTML='| Reason | Count | Avg Lat | P95 Lat |
';
+reasons.forEach(function(r){rHTML+='| '+r.reason+' | '+r.count+' | '+r.avg_total_ms+'ms | '+r.p95_total_ms+'ms |
';});
+rHTML+='
';$('perf-reasons').innerHTML=rHTML;
+}else{$('perf-reasons').innerHTML='-
';}
+// Agent performance
+if(agents.length){
+var maxAc=Math.max(...agents.map(function(a){return a.count||0}),1);
+var aHTML=agents.map(function(a){return''+a.agent+''+a.count+' reqs
';}).join('');
+$('perf-agents').innerHTML=aHTML;
+}else{$('perf-agents').innerHTML='-
';}
+}
function poll(){fetch('/api/state').then(function(r){return r.json()}).then(function(data){render(data);$('connection-status').textContent='live';}).catch(function(){$('connection-status').textContent='reconnecting';});}
-poll();setInterval(poll,3000);loadTS();
+poll();setInterval(poll,3000);loadTS();loadPerf();setInterval(loadPerf,15000);