UNPKG

@oclif/core

Version:

base library for oclif CLIs

228 lines (227 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Performance = exports.OCLIF_MARKER_OWNER = void 0; const node_perf_hooks_1 = require("node:perf_hooks"); const logger_1 = require("./logger"); const settings_1 = require("./settings"); exports.OCLIF_MARKER_OWNER = '@oclif/core'; class Marker { owner; name; details; method; module; scope; stopped = false; startMarker; stopMarker; constructor(owner, name, details = {}) { this.owner = owner; this.name = name; this.details = details; this.startMarker = `${this.name}-start`; this.stopMarker = `${this.name}-stop`; const [caller, scope] = name.split('#'); const [module, method] = caller.split('.'); this.module = module; this.method = method; this.scope = scope; node_perf_hooks_1.performance.mark(this.startMarker); } addDetails(details) { this.details = { ...this.details, ...details }; } measure() { node_perf_hooks_1.performance.measure(this.name, this.startMarker, this.stopMarker); } stop() { this.stopped = true; node_perf_hooks_1.performance.mark(this.stopMarker); } } class Performance { static _oclifPerf; /* Key: marker.owner */ static _results = new Map(); /* Key: marker.name */ static markers = new Map(); /** * Collect performance results into static Performance.results * * @returns Promise<void> */ static async collect() { if (!Performance.enabled) return; if (Performance._results.size > 0) return; const markers = [...Performance.markers.values()]; if (markers.length === 0) return; for (const marker of markers.filter((m) => !m.stopped)) { marker.stop(); } return new Promise((resolve) => { // eslint-disable-next-line complexity const perfObserver = new node_perf_hooks_1.PerformanceObserver((items) => { for (const entry of items.getEntries()) { const marker = Performance.markers.get(entry.name); if (marker) { const result = { details: marker.details, duration: entry.duration, method: marker.method, module: marker.module, name: entry.name, scope: marker.scope, }; const existing = Performance._results.get(marker.owner) ?? []; Performance._results.set(marker.owner, [...existing, result]); } } const oclifResults = Performance._results.get(exports.OCLIF_MARKER_OWNER) ?? []; const command = oclifResults.find((r) => r.name.startsWith('config.runCommand')); const commandLoadTime = command ? (Performance.getResult(exports.OCLIF_MARKER_OWNER, `plugin.findCommand#${command.details.plugin}.${command.details.command}`)?.duration ?? 0) : 0; const pluginLoadTimes = Object.fromEntries(oclifResults .filter(({ name }) => name.startsWith('plugin.load#')) .sort((a, b) => b.duration - a.duration) .map(({ details, duration, scope }) => [scope, { details, duration }])); const hookRunTimes = oclifResults .filter(({ name }) => name.startsWith('config.runHook#')) .reduce((acc, perfResult) => { const event = perfResult.details.event; if (event) { if (!acc[event]) acc[event] = {}; acc[event][perfResult.scope] = perfResult.duration; } else { const event = perfResult.scope; if (!acc[event]) acc[event] = {}; acc[event].total = perfResult.duration; } return acc; }, {}); const pluginLoadTimeByType = Object.fromEntries(oclifResults .filter(({ name }) => name.startsWith('config.loadPlugins#')) .sort((a, b) => b.duration - a.duration) .map(({ duration, scope }) => [scope, duration])); Performance._oclifPerf = { hookRunTimes, 'oclif.commandLoadMs': commandLoadTime, 'oclif.commandRunMs': oclifResults.find(({ name }) => name.startsWith('config.runCommand#'))?.duration ?? 0, 'oclif.configLoadMs': Performance.getResult(exports.OCLIF_MARKER_OWNER, 'config.load')?.duration ?? 0, 'oclif.corePluginsLoadMs': pluginLoadTimeByType.core ?? 0, 'oclif.initHookMs': hookRunTimes.init?.total ?? 0, 'oclif.initMs': Performance.getResult(exports.OCLIF_MARKER_OWNER, 'main.run#init')?.duration ?? 0, 'oclif.linkedPluginsLoadMs': pluginLoadTimeByType.link ?? 0, 'oclif.postrunHookMs': hookRunTimes.postrun?.total ?? 0, 'oclif.prerunHookMs': hookRunTimes.prerun?.total ?? 0, 'oclif.runMs': Performance.getResult(exports.OCLIF_MARKER_OWNER, 'main.run')?.duration ?? 0, 'oclif.userPluginsLoadMs': pluginLoadTimeByType.user ?? 0, pluginLoadTimes, }; resolve(); }); perfObserver.observe({ buffered: true, entryTypes: ['measure'] }); for (const marker of markers) { try { marker.measure(); } catch { // ignore } } node_perf_hooks_1.performance.clearMarks(); }); } /** * Add debug logs for plugin loading performance */ static debug() { if (!Performance.enabled) return; const oclifDebug = (0, logger_1.makeDebug)('perf'); const processUpTime = (process.uptime() * 1000).toFixed(4); oclifDebug('Process Uptime: %sms', processUpTime); oclifDebug('Oclif Time: %sms', Performance.oclifPerf['oclif.runMs'].toFixed(4)); oclifDebug('Init Time: %sms', Performance.oclifPerf['oclif.initMs'].toFixed(4)); oclifDebug('Config Load Time: %sms', Performance.oclifPerf['oclif.configLoadMs'].toFixed(4)); oclifDebug(' • Root Plugin Load Time: %sms', Performance.getResult(exports.OCLIF_MARKER_OWNER, 'plugin.load#root')?.duration.toFixed(4) ?? 0); oclifDebug(' • Plugins Load Time: %sms', Performance.getResult(exports.OCLIF_MARKER_OWNER, 'config.loadAllPlugins')?.duration.toFixed(4) ?? 0); oclifDebug(' • Commands Load Time: %sms', Performance.getResult(exports.OCLIF_MARKER_OWNER, 'config.loadAllCommands')?.duration.toFixed(4) ?? 0); oclifDebug('Core Plugin Load Time: %sms', Performance.oclifPerf['oclif.corePluginsLoadMs'].toFixed(4)); oclifDebug('User Plugin Load Time: %sms', Performance.oclifPerf['oclif.userPluginsLoadMs'].toFixed(4)); oclifDebug('Linked Plugin Load Time: %sms', Performance.oclifPerf['oclif.linkedPluginsLoadMs'].toFixed(4)); oclifDebug('Plugin Load Times:'); for (const [plugin, result] of Object.entries(Performance.oclifPerf.pluginLoadTimes)) { if (result.details.hasManifest) { oclifDebug(` ${plugin}: ${result.duration.toFixed(4)}ms`); } else { oclifDebug(` ${plugin}: ${result.duration.toFixed(4)}ms (no manifest!)`); } } oclifDebug('Hook Run Times:'); for (const [event, runTimes] of Object.entries(Performance.oclifPerf.hookRunTimes)) { oclifDebug(` ${event}:`); for (const [plugin, duration] of Object.entries(runTimes)) { oclifDebug(` ${plugin}: ${duration.toFixed(4)}ms`); } } oclifDebug('Command Load Time: %sms', Performance.oclifPerf['oclif.commandLoadMs'].toFixed(4)); oclifDebug('Command Run Time: %sms', Performance.oclifPerf['oclif.commandRunMs'].toFixed(4)); if (Performance.oclifPerf['oclif.configLoadMs'] > Performance.oclifPerf['oclif.runMs']) { oclifDebug('! Config load time is greater than total oclif time. This might mean that Config was instantiated before oclif was run.'); } const nonCoreDebug = (0, logger_1.makeDebug)('non-oclif-perf'); const nonCorePerf = Performance.results; if (nonCorePerf.size > 0) { nonCoreDebug('Non-Core Performance Measurements:'); for (const [owner, results] of nonCorePerf) { nonCoreDebug(` ${owner}:`); for (const result of results) { nonCoreDebug(` ${result.name}: ${result.duration.toFixed(4)}ms`); } } } } static getResult(owner, name) { return Performance._results.get(owner)?.find((r) => r.name === name); } /** * Add a new performance marker * * @param owner An npm package like `@oclif/core` or `@salesforce/source-tracking` * @param name Name of the marker. Use `module.method#scope` format * @param details Arbitrary details to attach to the marker * @returns Marker instance */ static mark(owner, name, details = {}) { if (!Performance.enabled) return; const marker = new Marker(owner, name, details); Performance.markers.set(marker.name, marker); return marker; } static get enabled() { return settings_1.settings.performanceEnabled ?? false; } static get oclifPerf() { if (!Performance.enabled) return {}; if (Performance._oclifPerf) return Performance._oclifPerf; throw new Error('Perf results not available. Did you forget to call await Performance.collect()?'); } /** returns a map of owner, PerfResult[]. Excludes oclif PerfResult, which you can get from oclifPerf */ static get results() { if (!Performance.enabled) return new Map(); return new Map([...Performance._results.entries()].filter(([owner]) => owner !== exports.OCLIF_MARKER_OWNER)); } } exports.Performance = Performance;