@@ -259,6 +259,12 @@ function RunDetailPage(): React.JSX.Element {
259259 const residentSeries = findSeries ( m , 'reth_jemalloc_resident' )
260260 const allocatedSeries = findSeries ( m , 'reth_jemalloc_allocated' )
261261
262+ // Gas limit breakdown (derived from block gas limit)
263+ const refGasLimit = blocks ?. [ 0 ] ?. gasLimit ?? 0
264+ const sharedGasLimit = Math . floor ( refGasLimit / 10 )
265+ const generalGasLimit = 30_000_000
266+ const paymentGasLimit = refGasLimit - sharedGasLimit - generalGasLimit
267+
262268 return (
263269 < div >
264270 < div className = "mb-4" >
@@ -380,6 +386,99 @@ function RunDetailPage(): React.JSX.Element {
380386 ] }
381387 formatValue = { ( v ) => formatGas ( v , false ) }
382388 xFormat = "block"
389+ referenceBands = {
390+ refGasLimit > 0
391+ ? [
392+ {
393+ label : `General (${ formatGas ( generalGasLimit , false ) } )` ,
394+ from : 0 ,
395+ to : generalGasLimit ,
396+ color : COLORS . blue ,
397+ } ,
398+ {
399+ label : `Payment (${ formatGas ( paymentGasLimit , false ) } )` ,
400+ from : generalGasLimit ,
401+ to : generalGasLimit + paymentGasLimit ,
402+ color : COLORS . orange ,
403+ } ,
404+ {
405+ label : `Shared (${ formatGas ( sharedGasLimit , false ) } )` ,
406+ from : generalGasLimit + paymentGasLimit ,
407+ to : refGasLimit ,
408+ color : COLORS . purple ,
409+ } ,
410+ ]
411+ : undefined
412+ }
413+ />
414+ < TimeSeriesChart
415+ title = "Gas Fill %"
416+ tooltip = "Percentage of the block gas limit that was used."
417+ showMean
418+ series = { [
419+ {
420+ label : 'Fill %' ,
421+ color : COLORS . blue ,
422+ data : blocks
423+ . filter ( ( b ) => b . gasLimit > 0 )
424+ . map ( ( b ) => ( {
425+ x : b . index ,
426+ y : ( b . gasUsed / b . gasLimit ) * 100 ,
427+ } ) ) ,
428+ } ,
429+ ] }
430+ formatValue = { ( v ) => `${ v . toFixed ( 1 ) } %` }
431+ yMax = { 100 }
432+ xFormat = "block"
433+ referenceBands = {
434+ refGasLimit > 0
435+ ? [
436+ {
437+ label : `General (${ ( ( generalGasLimit / refGasLimit ) * 100 ) . toFixed ( 0 ) } %)` ,
438+ from : 0 ,
439+ to : ( generalGasLimit / refGasLimit ) * 100 ,
440+ color : COLORS . blue ,
441+ } ,
442+ {
443+ label : `Payment (${ ( ( paymentGasLimit / refGasLimit ) * 100 ) . toFixed ( 0 ) } %)` ,
444+ from : ( generalGasLimit / refGasLimit ) * 100 ,
445+ to :
446+ ( ( generalGasLimit + paymentGasLimit ) / refGasLimit ) *
447+ 100 ,
448+ color : COLORS . orange ,
449+ } ,
450+ {
451+ label : `Shared (${ ( ( sharedGasLimit / refGasLimit ) * 100 ) . toFixed ( 0 ) } %)` ,
452+ from :
453+ ( ( generalGasLimit + paymentGasLimit ) / refGasLimit ) *
454+ 100 ,
455+ to : 100 ,
456+ color : COLORS . purple ,
457+ } ,
458+ ]
459+ : undefined
460+ }
461+ />
462+ < TimeSeriesChart
463+ title = "RLP Block Size"
464+ tooltip = "RLP-encoded size of each block in kilobytes."
465+ showMean
466+ series = { [
467+ {
468+ label : 'Size' ,
469+ color : COLORS . green ,
470+ data : transformSamples ( rlpSizeSeries , ( v ) => v / 1024 ) ,
471+ } ,
472+ ] }
473+ formatValue = { ( v ) => `${ v . toFixed ( 0 ) } KB` }
474+ referenceBands = { [
475+ {
476+ label : 'RLPx hard cap (16 MiB)' ,
477+ from : 16 * 1024 ,
478+ to : 16 * 1024 * 1.05 ,
479+ color : COLORS . red ,
480+ } ,
481+ ] }
383482 />
384483 </ div >
385484 </ section >
@@ -521,48 +620,6 @@ function RunDetailPage(): React.JSX.Element {
521620 </ div >
522621 </ section >
523622
524- < section className = "mb-10" >
525- < SectionHeader
526- title = "Block Headroom"
527- tooltip = "How close blocks are to their gas limit and size constraints."
528- />
529- < div className = "grid grid-cols-1 gap-3 md:grid-cols-2" >
530- < TimeSeriesChart
531- title = "Gas Fill %"
532- tooltip = "Percentage of the block gas limit that was used."
533- showMean
534- series = { [
535- {
536- label : 'Fill %' ,
537- color : COLORS . blue ,
538- data : ( blocks ?? [ ] )
539- . filter ( ( b ) => b . gasLimit > 0 )
540- . map ( ( b ) => ( {
541- x : b . index ,
542- y : ( b . gasUsed / b . gasLimit ) * 100 ,
543- } ) ) ,
544- } ,
545- ] }
546- formatValue = { ( v ) => `${ v . toFixed ( 1 ) } %` }
547- yMax = { 100 }
548- xFormat = "block"
549- />
550- < TimeSeriesChart
551- title = "RLP Block Size"
552- tooltip = "RLP-encoded size of each block in kilobytes."
553- showMean
554- series = { [
555- {
556- label : 'Size' ,
557- color : COLORS . green ,
558- data : transformSamples ( rlpSizeSeries , ( v ) => v / 1024 ) ,
559- } ,
560- ] }
561- formatValue = { ( v ) => `${ v . toFixed ( 0 ) } KB` }
562- />
563- </ div >
564- </ section >
565-
566623 < section className = "mb-10" >
567624 < SectionHeader
568625 title = "Txgen"
@@ -860,6 +917,13 @@ function seriesMean(data: Array<ChartPoint>): number {
860917 return data . reduce ( ( sum , p ) => sum + p . y , 0 ) / data . length
861918}
862919
920+ type ReferenceBand = {
921+ label : string
922+ from : number
923+ to : number
924+ color : string
925+ }
926+
863927function TimeSeriesChart ( props : {
864928 series : Array < ChartSeries >
865929 title ?: string | undefined
@@ -869,6 +933,7 @@ function TimeSeriesChart(props: {
869933 formatValue ?: ( ( v : number ) => string ) | undefined
870934 xFormat ?: 'time' | 'block' | undefined
871935 yMax ?: number | undefined
936+ referenceBands ?: Array < ReferenceBand > | undefined
872937} ) : React . JSX . Element {
873938 const plotRef = React . useRef < HTMLDivElement > ( null )
874939 const [ hoverX , setHoverX ] = React . useState < number | null > ( null )
@@ -897,6 +962,10 @@ function TimeSeriesChart(props: {
897962 const xMax = refSeries . data . at ( - 1 ) ?. x ?? 1
898963 const xRange = xMax - xMin || 1
899964
965+ const refBandMax = props . referenceBands ?. length
966+ ? Math . max ( ...props . referenceBands . map ( ( rb ) => rb . to ) )
967+ : 0
968+
900969 let yMax : number
901970 if ( props . yMax != null ) {
902971 yMax = props . yMax
@@ -907,9 +976,12 @@ function TimeSeriesChart(props: {
907976 for ( const s of props . series ) sum += s . data [ i ] ?. y ?? 0
908977 sums . push ( sum )
909978 }
910- yMax = ( Math . max ( ...sums ) || 1 ) * 1.1
979+ yMax = Math . max ( ( Math . max ( ...sums ) || 1 ) * 1.1 , refBandMax * 1.05 )
911980 } else {
912- yMax = ( Math . max ( ...allPoints . map ( ( p ) => p . y ) ) || 1 ) * 1.1
981+ yMax = Math . max (
982+ ( Math . max ( ...allPoints . map ( ( p ) => p . y ) ) || 1 ) * 1.1 ,
983+ refBandMax * 1.05 ,
984+ )
913985 }
914986 const yMin = 0
915987 const yRange = yMax - yMin || 1
@@ -1082,6 +1154,46 @@ function TimeSeriesChart(props: {
10821154 ) )
10831155 ) ) }
10841156
1157+ { /* Reference bands */ }
1158+ { props . referenceBands ?. map ( ( rb ) => {
1159+ const y1 = sy ( rb . to )
1160+ const y2 = sy ( rb . from )
1161+ const h = y2 - y1
1162+ const midY = y1 + h / 2
1163+ return (
1164+ < React . Fragment key = { rb . label } >
1165+ < rect
1166+ x = { 0 }
1167+ y = { y1 }
1168+ width = { SVG_W }
1169+ height = { h }
1170+ fill = { rb . color }
1171+ opacity = { 0.1 }
1172+ />
1173+ < line
1174+ x1 = { 0 }
1175+ y1 = { y1 }
1176+ x2 = { SVG_W }
1177+ y2 = { y1 }
1178+ stroke = { rb . color }
1179+ vectorEffect = "non-scaling-stroke"
1180+ strokeWidth = { 0.5 }
1181+ opacity = { 0.3 }
1182+ />
1183+ < text
1184+ x = { SVG_W - 4 }
1185+ y = { h > 12 ? midY + 3 : y1 + 11 }
1186+ textAnchor = "end"
1187+ fill = { rb . color }
1188+ fontSize = { 8 }
1189+ opacity = { 0.8 }
1190+ >
1191+ { rb . label }
1192+ </ text >
1193+ </ React . Fragment >
1194+ )
1195+ } ) }
1196+
10851197 { /* Stacked area fills */ }
10861198 { props . stacked &&
10871199 stackedAreas . map ( ( a ) => (
@@ -1141,36 +1253,36 @@ function TimeSeriesChart(props: {
11411253 strokeDasharray = "3 3"
11421254 />
11431255 ) }
1256+ </ svg >
11441257
1145- { /* Hover dots */ }
1146- { hoverX !== null &&
1147- props . series . map ( ( s ) => {
1148- const pt = closestPoint ( s . data , hoverX )
1149- if ( ! pt ) return null
1150- let y = pt . y
1151- if ( props . stacked ) {
1152- const si = props . series . indexOf ( s )
1153- y = 0
1154- for ( let j = 0 ; j <= si ; j ++ ) {
1155- const cp = closestPoint ( props . series [ j ] . data , hoverX )
1156- y += cp ?. y ?? 0
1157- }
1258+ { /* Hover dots (HTML to avoid SVG stretch distortion) */ }
1259+ { hoverX !== null &&
1260+ props . series . map ( ( s ) => {
1261+ const pt = closestPoint ( s . data , hoverX )
1262+ if ( ! pt ) return null
1263+ let y = pt . y
1264+ if ( props . stacked ) {
1265+ const si = props . series . indexOf ( s )
1266+ y = 0
1267+ for ( let j = 0 ; j <= si ; j ++ ) {
1268+ const cp = closestPoint ( props . series [ j ] . data , hoverX )
1269+ y += cp ?. y ?? 0
11581270 }
1159- return (
1160- < circle
1161- key = { s . label }
1162- cx = { sx ( pt . x ) }
1163- cy = { sy ( y ) }
1164- r = { 3 }
1165- fill = { s . color }
1166- stroke = "currentColor"
1167- className = "text-surface"
1168- vectorEffect = "non-scaling-stroke"
1169- strokeWidth = { 1.5 }
1170- />
1171- )
1172- } ) }
1173- </ svg >
1271+ }
1272+ const xPct = ( ( pt . x - xMin ) / xRange ) * 100
1273+ const yPct = ( 1 - ( y - yMin ) / yRange ) * 100
1274+ return (
1275+ < div
1276+ key = { s . label }
1277+ className = "pointer-events-none absolute h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full border-[1.5px] border-surface"
1278+ style = { {
1279+ left : ` ${ xPct } %` ,
1280+ top : ` ${ yPct } %` ,
1281+ backgroundColor : s . color ,
1282+ } }
1283+ />
1284+ )
1285+ } ) }
11741286
11751287 { /* Hover tooltip */ }
11761288 { hoverX !== null && (
0 commit comments