@@ -50,13 +50,17 @@ function updateThemeButton(theme) {
5050 }
5151}
5252
53+ let chartGridColor = 'rgba(255,255,255,0.05)' ;
54+
5355function updateChartDefaults ( theme ) {
5456 if ( theme === 'light' ) {
5557 Chart . defaults . color = 'hsl(0, 0%, 45.1%)' ;
5658 Chart . defaults . borderColor = 'hsl(0, 0%, 89.8%)' ;
59+ chartGridColor = 'rgba(0,0,0,0.06)' ;
5760 } else {
5861 Chart . defaults . color = 'hsl(0, 0%, 64%)' ;
5962 Chart . defaults . borderColor = 'hsl(0, 0%, 15%)' ;
63+ chartGridColor = 'rgba(255,255,255,0.05)' ;
6064 }
6165}
6266
@@ -594,7 +598,7 @@ function renderOpThroughputChart(opType, idx) {
594598 } ,
595599 y : {
596600 beginAtZero : true ,
597- grid : { color : 'rgba(255,255,255,0.05)' }
601+ grid : { color : chartGridColor }
598602 }
599603 }
600604 }
@@ -665,7 +669,7 @@ function renderOpLatencyChart(opType, idx) {
665669 y : {
666670 beginAtZero : true ,
667671 title : { display : true , text : 'ms' , font : { size : 10 } } ,
668- grid : { color : 'rgba(255,255,255,0.05)' }
672+ grid : { color : chartGridColor }
669673 }
670674 }
671675 }
@@ -739,7 +743,7 @@ function renderOpTtfbChart(opType, idx) {
739743 y : {
740744 beginAtZero : true ,
741745 title : { display : true , text : 'ms' , font : { size : 10 } } ,
742- grid : { color : 'rgba(255,255,255,0.05)' }
746+ grid : { color : chartGridColor }
743747 }
744748 }
745749 }
@@ -770,6 +774,9 @@ function renderHostsTable() {
770774 <div class="breakdown-chart-container">
771775 <canvas id="host-chart-${ currentChartIdx } "></canvas>
772776 </div>
777+ ${ hasTtfb ( opType ) ? `<div class="breakdown-chart-container">
778+ <canvas id="host-ttfb-chart-${ currentChartIdx } "></canvas>
779+ </div>` : '' }
773780 <table>
774781 <thead>
775782 <tr>
@@ -806,7 +813,9 @@ function renderHostsTable() {
806813 // Render charts for each op type
807814 chartIdx = 0 ;
808815 opTypes . forEach ( ( opType ) => {
809- renderHostThroughputChart ( opType , chartIdx ++ , hosts ) ;
816+ const ci = chartIdx ++ ;
817+ renderHostThroughputChart ( opType , ci , hosts ) ;
818+ renderHostTtfbChart ( opType , ci , hosts ) ;
810819 } ) ;
811820}
812821
@@ -834,6 +843,9 @@ function renderClientsTable() {
834843 <div class="breakdown-chart-container">
835844 <canvas id="client-chart-${ currentChartIdx } "></canvas>
836845 </div>
846+ ${ hasTtfb ( opType ) ? `<div class="breakdown-chart-container">
847+ <canvas id="client-ttfb-chart-${ currentChartIdx } "></canvas>
848+ </div>` : '' }
837849 <table>
838850 <thead>
839851 <tr>
@@ -870,7 +882,9 @@ function renderClientsTable() {
870882 // Render charts for each op type
871883 chartIdx = 0 ;
872884 opTypes . forEach ( ( opType ) => {
873- renderClientThroughputChart ( opType , chartIdx ++ , clients ) ;
885+ const ci = chartIdx ++ ;
886+ renderClientThroughputChart ( opType , ci , clients ) ;
887+ renderClientTtfbChart ( opType , ci , clients ) ;
874888 } ) ;
875889}
876890
@@ -963,7 +977,7 @@ function renderHostThroughputChart(opType, idx, hosts) {
963977 y : {
964978 beginAtZero : true ,
965979 title : { display : true , text : 'MiB/s' , font : { size : 10 } } ,
966- grid : { color : 'rgba(255,255,255,0.05)' }
980+ grid : { color : chartGridColor }
967981 }
968982 }
969983 }
@@ -1043,7 +1057,125 @@ function renderClientThroughputChart(opType, idx, clients) {
10431057 y : {
10441058 beginAtZero : true ,
10451059 title : { display : true , text : 'MiB/s' , font : { size : 10 } } ,
1046- grid : { color : 'rgba(255,255,255,0.05)' }
1060+ grid : { color : chartGridColor }
1061+ }
1062+ }
1063+ }
1064+ } ) ;
1065+ }
1066+
1067+ function renderHostTtfbChart ( opType , idx , hosts ) {
1068+ if ( ! hasTtfb ( opType ) ) return ;
1069+
1070+ const byHostData = data . by_host || { } ;
1071+ const canvas = document . getElementById ( `host-ttfb-chart-${ idx } ` ) ;
1072+ if ( ! canvas ) return ;
1073+ const ctx = canvas . getContext ( '2d' ) ;
1074+
1075+ const datasets = [ ] ;
1076+ hosts . forEach ( ( host , hostIdx ) => {
1077+ const hostAgg = byHostData [ host ] ;
1078+ const ttfbTs = sampleData ( getTtfbTimeSeries ( hostAgg ) ) ;
1079+ if ( ttfbTs && ttfbTs . length > 0 ) {
1080+ datasets . push ( {
1081+ label : host + ' P50' ,
1082+ data : ttfbTs . map ( s => ( { x : s . time , y : s . p50 } ) ) ,
1083+ borderColor : getSeriesColor ( hostIdx ) ,
1084+ backgroundColor : 'transparent' ,
1085+ fill : false ,
1086+ tension : 0.4 ,
1087+ pointRadius : 0 ,
1088+ borderWidth : 2
1089+ } ) ;
1090+ }
1091+ } ) ;
1092+
1093+ if ( datasets . length === 0 ) return ;
1094+
1095+ new Chart ( ctx , {
1096+ type : 'line' ,
1097+ data : { datasets } ,
1098+ options : {
1099+ responsive : true ,
1100+ maintainAspectRatio : false ,
1101+ interaction : { mode : 'index' , intersect : false } ,
1102+ plugins : {
1103+ title : { display : true , text : 'TTFB (P50)' , font : { size : 11 } } ,
1104+ legend : {
1105+ display : datasets . length > 1 ,
1106+ position : 'top' ,
1107+ labels : { boxWidth : 12 , padding : 8 , font : { size : 10 } }
1108+ }
1109+ } ,
1110+ scales : {
1111+ x : {
1112+ type : 'time' ,
1113+ time : { displayFormats : { second : 'HH:mm:ss' } } ,
1114+ grid : { display : false }
1115+ } ,
1116+ y : {
1117+ beginAtZero : true ,
1118+ title : { display : true , text : 'ms' , font : { size : 10 } } ,
1119+ grid : { color : chartGridColor }
1120+ }
1121+ }
1122+ }
1123+ } ) ;
1124+ }
1125+
1126+ function renderClientTtfbChart ( opType , idx , clients ) {
1127+ if ( ! hasTtfb ( opType ) ) return ;
1128+
1129+ const byClientData = data . by_client || { } ;
1130+ const canvas = document . getElementById ( `client-ttfb-chart-${ idx } ` ) ;
1131+ if ( ! canvas ) return ;
1132+ const ctx = canvas . getContext ( '2d' ) ;
1133+
1134+ const datasets = [ ] ;
1135+ clients . forEach ( ( client , clientIdx ) => {
1136+ const clientAgg = byClientData [ client ] ;
1137+ const ttfbTs = sampleData ( getTtfbTimeSeries ( clientAgg ) ) ;
1138+ if ( ttfbTs && ttfbTs . length > 0 ) {
1139+ datasets . push ( {
1140+ label : client + ' P50' ,
1141+ data : ttfbTs . map ( s => ( { x : s . time , y : s . p50 } ) ) ,
1142+ borderColor : getSeriesColor ( clientIdx ) ,
1143+ backgroundColor : 'transparent' ,
1144+ fill : false ,
1145+ tension : 0.4 ,
1146+ pointRadius : 0 ,
1147+ borderWidth : 2
1148+ } ) ;
1149+ }
1150+ } ) ;
1151+
1152+ if ( datasets . length === 0 ) return ;
1153+
1154+ new Chart ( ctx , {
1155+ type : 'line' ,
1156+ data : { datasets } ,
1157+ options : {
1158+ responsive : true ,
1159+ maintainAspectRatio : false ,
1160+ interaction : { mode : 'index' , intersect : false } ,
1161+ plugins : {
1162+ title : { display : true , text : 'TTFB (P50)' , font : { size : 11 } } ,
1163+ legend : {
1164+ display : datasets . length > 1 ,
1165+ position : 'top' ,
1166+ labels : { boxWidth : 12 , padding : 8 , font : { size : 10 } }
1167+ }
1168+ } ,
1169+ scales : {
1170+ x : {
1171+ type : 'time' ,
1172+ time : { displayFormats : { second : 'HH:mm:ss' } } ,
1173+ grid : { display : false }
1174+ } ,
1175+ y : {
1176+ beginAtZero : true ,
1177+ title : { display : true , text : 'ms' , font : { size : 10 } } ,
1178+ grid : { color : chartGridColor }
10471179 }
10481180 }
10491181 }
0 commit comments