import debugUtil from "debug"; import ConsoleLogger from "../Util/ConsoleLogger.js"; import isAsyncFunction from "../Util/IsAsyncFunction.js"; import Benchmark from "./Benchmark.js"; const debugBenchmark = debugUtil("Eleventy:Benchmark"); class BenchmarkGroup { constructor() { this.benchmarks = {}; // Warning: aggregate benchmarks automatically default to false via BenchmarkManager->getBenchmarkGroup this.isVerbose = true; this.logger = new ConsoleLogger(); this.minimumThresholdMs = 50; this.minimumThresholdPercent = 8; } setIsVerbose(isVerbose) { this.isVerbose = isVerbose; this.logger.isVerbose = isVerbose; } reset() { for (var type in this.benchmarks) { this.benchmarks[type].reset(); } } // TODO use addAsync everywhere instead add(type, callback) { let benchmark = (this.benchmarks[type] = new Benchmark()); /** @this {any} */ let fn = function (...args) { benchmark.before(); let ret = callback.call(this, ...args); benchmark.after(); return ret; }; Object.defineProperty(fn, "__eleventyInternal", { value: { type: isAsyncFunction(callback) ? "async" : "sync", callback, }, }); return fn; } // callback must return a promise // async addAsync(type, callback) { // let benchmark = (this.benchmarks[type] = new Benchmark()); // benchmark.before(); // // don’t await here. // let promise = callback.call(this); // promise.then(function() { // benchmark.after(); // }); // return promise; // } setMinimumThresholdMs(minimumThresholdMs) { let val = parseInt(minimumThresholdMs, 10); if (isNaN(val)) { throw new Error("`setMinimumThresholdMs` expects a number argument."); } this.minimumThresholdMs = val; } setMinimumThresholdPercent(minimumThresholdPercent) { let val = parseInt(minimumThresholdPercent, 10); if (isNaN(val)) { throw new Error("`setMinimumThresholdPercent` expects a number argument."); } this.minimumThresholdPercent = val; } has(type) { return !!this.benchmarks[type]; } get(type) { if (!this.benchmarks[type]) { this.benchmarks[type] = new Benchmark(); } return this.benchmarks[type]; } padNumber(num, length) { if (("" + num).length >= length) { return num; } let prefix = new Array(length + 1).join(" "); return (prefix + num).slice(-1 * length); } finish(label, totalTimeSpent) { for (var type in this.benchmarks) { let bench = this.benchmarks[type]; let isAbsoluteMinimumComparison = this.minimumThresholdMs > 0; let totalForBenchmark = bench.getTotal(); let percent = Math.round((totalForBenchmark * 100) / totalTimeSpent); let callCount = bench.getTimesCalled(); let output = { ms: this.padNumber(totalForBenchmark.toFixed(0), 6), percent: this.padNumber(percent, 3), calls: this.padNumber(callCount, 5), }; let str = `Benchmark ${output.ms}ms ${output.percent}% ${output.calls}× (${label}) ${type}`; if ( isAbsoluteMinimumComparison && totalForBenchmark >= this.minimumThresholdMs && percent > this.minimumThresholdPercent ) { this.logger.warn(str); } // Opt out of logging if low count (1× or 2×) or 0ms / 1% if ( callCount > 1 || // called more than once Math.round(totalForBenchmark) > 0 // more than 0.5ms ) { debugBenchmark(str); } } } } export default BenchmarkGroup;