@sap/cds-compiler
Version:
CDS (Core Data Services) compiler and backends
159 lines (144 loc) • 4.3 kB
JavaScript
;
const { CompilerAssertion } = require('../base/error');
function hrtimeToSec( dt ) {
const sec = (dt / BigInt('1000000000')).toString().padStart(2, ' ');
// first, get remaining ns, then convert to ms.
const msec = ((dt % BigInt('1000000000')) / BigInt('1000000')).toString().padStart(3, '0');
return [ sec, msec ];
}
/**
* A single StopWatch encapsulates the runtime of a selected code frame.
*
* @class TimeTrace
*/
class StopWatch {
/**
* Creates an instance of TimeTrace.
* @param {string} id
*
* @memberOf TimeTrace
*/
constructor(id) {
this.id = id;
this.startTime = process.hrtime.bigint();
this.lapTime = this.startTime;
}
/**
* Start watch.
*/
start() {
this.startTime = process.hrtime.bigint();
this.lapTime = this.startTime;
}
/**
* Stop and return delta T in nanoseconds,
* but do not set start time
*/
stop() {
const endTime = process.hrtime.bigint();
return endTime - this.startTime;
}
lap() {
const endTime = process.hrtime.bigint();
const dt = endTime - this.startTime;
this.lapTime = process.hrtime.bigint();
return dt;
}
stopInFloatSecs() {
const dt = this.stop();
return dt / BigInt(1000000000);
}
// lap as sec.ns float
lapInFloatSecs() {
const dt = this.lap();
return dt / BigInt(1000000000);
}
}
/**
* The main class to handle measuring the runtime of code blocks
*
* Results are logged to stderr
*
* To enable time tracing, set CDSC_TRACE_TIME to true in the environment
*
* @class TimeTracer
*/
class TimeTracer {
/**
* Creates an instance of TimeTracer.
*
* @memberOf TimeTracer
*/
constructor() {
this.traceStack = [];
this.lastStop = null;
}
/**
* Reset the time tracer. Use this if an exception is thrown, because then
* start/end won't correctly match.
*
* @param {string} reason
*/
reset(reason) {
// eslint-disable-next-line no-console
console.error(`Reset TimeTrace: Stopping all timers because: ${ reason }`);
while (this.traceStack.length)
this.stop(this.traceStack[this.traceStack.length - 1].id);
}
/**
* Start a new TimeTrace, using the given id for logging etc.
*
* @param {string} id A short description of whats going on
*
* @memberOf TimeTracer
*/
start(id) {
// Get time between last stop and new start: Those sections were not tracked.
const [ sec, msec ] = this.lastStop ? hrtimeToSec(this.lastStop.stop()) : [ ' 0', '000' ];
this.lastStop = null;
let base = `${ ' '.repeat(this.traceStack.length * 2) }${ id } started:`;
base += ' '.repeat(60 - base.length);
if (sec !== ' 0' || msec !== '000')
// eslint-disable-next-line no-console
console.error( `${ base } ${ sec }s ${ msec }ms (since last stop)` );
else
// eslint-disable-next-line no-console
console.error( `${ base }` );
this.traceStack.push(new StopWatch(id));
}
/**
* Stop the current TimeTrace and log the execution time.
*
* @param {string} id
* @memberOf TimeTracer
*/
stop(id) {
if (this.traceStack.length === 0)
throw new CompilerAssertion('TimeTracer mismatch: called stop() too many times');
const current = this.traceStack.pop();
if (current.id !== id)
throw new CompilerAssertion(`TimeTracer mismatch; expected id: “${ id }”, was “${ current.id }”`);
let diff = '';
if (this.lastStop !== null) {
const [ sec, msec ] = hrtimeToSec(this.lastStop.stop());
if ( sec !== ' 0' || msec !== '000')
diff = ` (diff to last stop: ${ sec }s ${ msec }ms)`;
}
const [ sec, msec ] = hrtimeToSec(current.stop());
const base = `${ ' '.repeat(this.traceStack.length * 2) }${ current.id } took:`;
// eslint-disable-next-line no-console
console.error( `${ base }${ ' '.repeat(60 - base.length) } ${ sec }s ${ msec }ms${ diff }` );
this.lastStop = new StopWatch(id);
}
}
const ignoreTimeTrace = {
start: () => { /* ignore */ },
stop: () => { /* ignore */ },
reset: () => { /* ignore */ },
};
const doTimeTrace = process?.env?.CDSC_TRACE_TIME !== undefined;
module.exports = {
timetrace: (doTimeTrace ? new TimeTracer() : ignoreTimeTrace),
TimeTracer,
StopWatch,
};