Skip to content

Commit

Permalink
Add phetTimingLog, see phetsims/chipper#1342
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Oct 17, 2022
1 parent 566de03 commit e8bce00
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ node_modules/
build-server.log
lintreport.txt
js/build-server/tmp*
.maintenance.json
.maintenance.json
logs/
115 changes: 115 additions & 0 deletions js/common/phetTimingLog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2022, University of Colorado Boulder

/**
* Outputs timing information for nested structured tasks, so they can be inspected to understand what is
* taking so long. This is optimized for human-readability. Use an XML-like syntax for structuring, though the overall
* format is not xml since there are multiple root elements. Output timing information in a comment after a closing tag.
* Sessions are delimited by a blank line. Un-truncated sessions are XML parseable, but the timing data is in the comments
* so naive XML parsing won't help in analysis.
*
* - Data is streamed as it is generated, and hence may be incomplete if a process is interrupted.
* - This is coded in perennial so it can be used in chipper tasks (via perennial-alias) and also for perennial tasks as needed.
* - Assumes single-threaded access to the interface
*
* You can watch the results stream out live, and get a good sense of where the time is being spent by running
* tail -f /path/to/perennial-alias/logs/phet-timing-log.txt
*
* This task is to help identify bottlenecks and cross-platform differences. It is not intended to account for
* every millisecond in self-time.
*
* The log file is dedicated to timing information and structuring of tasks, and we should not add supplemental metadata
* such as messages or results from tasks.
*
* Assumes that task structuring is all done in one frame--not possible to start an event in one animation frame
* and end it in another.
*
* @author Sam Reid (PhET Interactive Simulations)
*/

const path = require( 'path' );
const fs = require( 'fs' );
const assert = require( 'assert' );

const logDir = path.resolve( __dirname, '../../logs' );
try {
fs.mkdirSync( logDir );
}
catch( e ) {
// already exists
}

// Log to perennial-alias if running a perennial-alias task, or perennial if running a perennial task.
const logPath = path.resolve( logDir, 'phet-timing-log.txt' );

// Create file if it doesn't exist, and append to it
const stream = fs.createWriteStream( logPath, { flags: 'a' } );

// Depth of nesting. -1 means not yet started. 0 means top-level.
let depth = -1;

const indent = depth => ' '.repeat( depth );

const push = taskName => {
depth++;

assert( !taskName.includes( ':' ), 'task name cannot include :, it was ' + taskName );
if ( depth === 0 ) {
const time = new Date().toLocaleString( 'en-US', { timeZone: 'America/Denver' } );
stream.write( `<!-- ${time} -->\n` );
}
stream.write( `${indent( depth )}<${taskName}>\n` );

const startTime = Date.now();
return startTime;
};

const pop = ( taskName, startTime ) => {
const endTime = Date.now();

stream.write( `${indent( depth )}</${taskName}> <!-- ${endTime - startTime}ms -->\n` );

if ( depth === 0 ) {
stream.write( '\n' );
}

depth--;
};

const phetTimingLog = {

/**
* Invoke the task and return the return value of the task.
* @param {string} taskName
* @param {()=>T} task
* @returns {T}
*/
start( taskName, task ) {
const startTime = push( taskName );
const result = task();
pop( taskName, startTime );
return result;
},

/**
* Invoke the task and return the return value of the task.
* @param {string} taskName
* @param {()=>Promise<T>} task
* @returns {Promise<T>}
*/
async startAsync( taskName, task ) {
const startTime = push( taskName );
const result = await task();
pop( taskName, startTime );
return result;
},

/**
* Flush the write stream before exiting node.
* @param {()=>void} [callback]
*/
close( callback = () => {} ) {
stream.close( callback );
}
};

module.exports = phetTimingLog;

0 comments on commit e8bce00

Please sign in to comment.