@@ -66,6 +66,8 @@ function buildMetricPlotSvg(samples, options = {}) {
6666 const yMaxOverride = Number ( options . yMax ) ;
6767 const yMaxBaseline = Number ( options . yMaxBaseline ) ;
6868 const yTickInterval = Number ( options . yTickInterval ) ;
69+ const xTickInterval = Number ( options . xTickInterval ) ;
70+ const xGridInterval = Number ( options . xGridInterval ) ;
6971 const rightYMinOverride = Number ( options . rightYMin ) ;
7072 const rightYMaxBaseline = Number ( options . rightYMaxBaseline ) ;
7173 const rightYTickInterval = Number ( options . rightYTickInterval ) ;
@@ -84,6 +86,14 @@ function buildMetricPlotSvg(samples, options = {}) {
8486 if ( ! Number . isFinite ( yTickInterval ) ) {
8587 throw new Error ( 'yTickInterval is required' ) ;
8688 }
89+ if ( options . xTickInterval !== undefined
90+ && ( ! Number . isFinite ( xTickInterval ) || xTickInterval <= 0 ) ) {
91+ throw new Error ( 'xTickInterval must be a positive number' ) ;
92+ }
93+ if ( options . xGridInterval !== undefined
94+ && ( ! Number . isFinite ( xGridInterval ) || xGridInterval <= 0 ) ) {
95+ throw new Error ( 'xGridInterval must be a positive number' ) ;
96+ }
8797 if ( hasRightAxis && ! [
8898 rightYMinOverride ,
8999 rightYMaxBaseline ,
@@ -230,7 +240,18 @@ function buildMetricPlotSvg(samples, options = {}) {
230240 } )
231241 . join ( '' )
232242 : '' ;
233- const tickInterval = maxX <= 10 ? 1 : maxX <= 100 ? 10 : 100 ;
243+ const tickInterval = Number . isFinite ( xTickInterval )
244+ ? xTickInterval
245+ : maxX <= 10 ? 1 : maxX <= 100 ? 10 : 100 ;
246+ const xGrid = Number . isFinite ( xGridInterval )
247+ ? Array . from ( { length : Math . floor ( maxX / xGridInterval ) + 1 } ,
248+ ( _value , index ) => {
249+ const gridValue = index * xGridInterval ;
250+ const x = formatPoint ( xFor ( gridValue ) ) ;
251+ return `<line x1="${ x } " y1="${ margin . top } " x2="${ x } " y2="${ height - margin . bottom } " stroke="#d0d7de" />` ;
252+ } )
253+ . join ( '' )
254+ : '' ;
234255 const xTicks = Array . from ( { length : Math . floor ( maxX / tickInterval ) + 1 } ,
235256 ( _value , index ) => {
236257 const tickValue = index * tickInterval ;
@@ -242,10 +263,13 @@ function buildMetricPlotSvg(samples, options = {}) {
242263 } )
243264 . join ( '' ) ;
244265 const firstLoaded = series [ 0 ] ?. samples . find ( ( { loading } ) => loading === false ) ;
266+ const loadedLabel = firstLoaded
267+ ? escapeSvgText ( `Loaded ${ formatStat ( firstLoaded . x ) } s` )
268+ : '' ;
245269 const loadedMarker = firstLoaded
246270 ? [
247271 `<line x1="${ formatPoint ( xFor ( firstLoaded . x ) ) } " y1="${ margin . top } " x2="${ formatPoint ( xFor ( firstLoaded . x ) ) } " y2="${ height - margin . bottom } " stroke="#f97316" stroke-width="2" stroke-dasharray="5 4" />` ,
248- `<text x="${ formatPoint ( xFor ( firstLoaded . x ) + 6 ) } " y="${ margin . top + 16 } " fill="#c2410c" font-family="Arial, sans-serif" font-size="12" font-weight="700">Loaded </text>` ,
272+ `<text x="${ formatPoint ( xFor ( firstLoaded . x ) + 6 ) } " y="${ margin . top + 16 } " fill="#c2410c" font-family="Arial, sans-serif" font-size="12" font-weight="700">${ loadedLabel } </text>` ,
249273 ] . join ( '' )
250274 : '' ;
251275 const averageValue = Number ( options . averageValue ) ;
@@ -284,6 +308,7 @@ function buildMetricPlotSvg(samples, options = {}) {
284308 <text x="${ margin . left } " y="28" fill="#24292f" font-family="Arial, sans-serif" font-size="20" font-weight="700">${ title } </text>
285309 ${ legend }
286310 ${ grid }
311+ ${ xGrid }
287312 <line x1="${ margin . left } " y1="${ margin . top } " x2="${ margin . left } " y2="${ height - margin . bottom } " stroke="#8c959f" />
288313 ${ hasRightAxis ? `<line x1="${ width - margin . right } " y1="${ margin . top } " x2="${ width - margin . right } " y2="${ height - margin . bottom } " stroke="${ rightAxisColor } " stroke-width="2" />` : '' }
289314 ${ rightAxisTicks }
@@ -413,7 +438,7 @@ const inferCsvPlot = ({ headers, rows }, filename = '') => {
413438 yMin : 0 ,
414439 yMaxBaseline : 200 ,
415440 yTickInterval : 100 ,
416- decimalValues : true ,
441+ decimalValues : false ,
417442 samples : rows . map ( ( row , index ) => sampleFor ( row , index , 'fps' ) ) ,
418443 latestCommitSha : latestCommitShaFor ( rows , [ 'fps' ] ) ,
419444 } ;
@@ -464,6 +489,8 @@ const inferCsvPlot = ({ headers, rows }, filename = '') => {
464489 return {
465490 title : title ( basename , 'FPS samples' ) ,
466491 xLabel : xHeader ? 'Seconds' : 'Samples' ,
492+ xTickInterval : xHeader ? 1 : undefined ,
493+ xGridInterval : xHeader ? 1 : undefined ,
467494 yMin : 0 ,
468495 yMaxBaseline : 200 ,
469496 yTickInterval : 100 ,
@@ -478,9 +505,11 @@ const inferCsvPlot = ({ headers, rows }, filename = '') => {
478505} ;
479506
480507const buildCsvPlotSvg = ( content , options = { } ) => {
481- const inferred = inferCsvPlot ( parseCsv ( content ) , options . filename ) ;
508+ const inferred = options . inferred || inferCsvPlot ( parseCsv ( content ) , options . filename ) ;
482509 const { latestCommitSha, ...plotOptions } = inferred ;
483- const svg = buildMetricPlotSvg ( inferred . samples , { ...plotOptions , ...options } ) ;
510+ const svgOptions = { ...options } ;
511+ delete svgOptions . inferred ;
512+ const svg = buildMetricPlotSvg ( inferred . samples , { ...plotOptions , ...svgOptions } ) ;
484513 return addLatestCommitSha ( svg , latestCommitSha ) ;
485514} ;
486515
@@ -502,6 +531,36 @@ async function saveCsvPlot(browser, csvPath, destination, options = {}) {
502531 }
503532}
504533
534+ async function saveLoadDurationPlot ( browser , csvPath , destination ) {
535+ const content = fs . readFileSync ( csvPath , 'utf8' ) ;
536+ const { headers, rows } = parseCsv ( content ) ;
537+ if (
538+ ! headers . includes ( 'fps' )
539+ || ! headers . includes ( '% change' )
540+ || ! headers . includes ( 'load duration' )
541+ || ! rows . some ( row => Number . isFinite ( Number ( row [ 'load duration' ] ) ) )
542+ ) {
543+ return undefined ;
544+ }
545+
546+ await saveCsvPlot ( browser , csvPath , destination , {
547+ inferred : {
548+ title : 'Load duration' ,
549+ xLabel : 'Runs' ,
550+ yMin : 0 ,
551+ yMaxBaseline : 10 ,
552+ yTickInterval : 1 ,
553+ decimalValues : true ,
554+ samples : rows . map ( ( row , index ) => ( {
555+ x : index ,
556+ value : Number ( row [ 'load duration' ] ) ,
557+ } ) ) ,
558+ latestCommitSha : latestCommitShaFor ( rows , [ 'load duration' ] ) ,
559+ } ,
560+ } ) ;
561+ return destination ;
562+ }
563+
505564function printUsage ( ) {
506565 console . log ( [
507566 'Usage: bun scripts/metric_plot.js <csv_path> [plot_path]' ,
@@ -538,6 +597,13 @@ async function main() {
538597 try {
539598 await saveCsvPlot ( browser , csvPath , destination ) ;
540599 console . log ( `CSV_PLOT=${ destination } ` ) ;
600+ const loadDurationName = process . env . LOAD_DURATION_NAME || 'load_duration' ;
601+ const loadDurationDestination = path . join ( '/tmp' , `${ loadDurationName } .png` ) ;
602+ const loadDurationPlot = await saveLoadDurationPlot (
603+ browser , csvPath , loadDurationDestination ) ;
604+ if ( loadDurationPlot ) {
605+ console . log ( `CSV_PLOT=${ loadDurationPlot } ` ) ;
606+ }
541607 } finally {
542608 await browser . close ( ) ;
543609 }
@@ -550,6 +616,7 @@ module.exports = {
550616 inferCsvPlot,
551617 parseCsv,
552618 saveCsvPlot,
619+ saveLoadDurationPlot,
553620 saveMetricPlot,
554621} ;
555622
0 commit comments