diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts index ded5e2e7270..618861a5bee 100644 --- a/src/typings/require.d.ts +++ b/src/typings/require.d.ts @@ -17,7 +17,12 @@ declare const enum LoaderEventType { NodeEndEvaluatingScript = 32, NodeBeginNativeRequire = 33, - NodeEndNativeRequire = 34 + NodeEndNativeRequire = 34, + + CachedDataFound = 60, + CachedDataMissed = 61, + CachedDataRejected = 62, + CachedDataCreated = 63, } declare class LoaderEvent { diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 28e3fefafcc..4f91d4a5b2e 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -35,13 +35,7 @@ bootstrapWindow.load([ showPartsSplash(windowConfig); }, beforeLoaderConfig: function (windowConfig, loaderConfig) { - loaderConfig.recordStats = !!windowConfig['prof-modules']; - if (loaderConfig.nodeCachedData) { - const onNodeCachedData = window['MonacoEnvironment'].onNodeCachedData = []; - loaderConfig.nodeCachedData.onData = function () { - onNodeCachedData.push(arguments); - }; - } + loaderConfig.recordStats = true; }, beforeRequire: function () { perf.mark('willLoadWorkbenchMain'); diff --git a/src/vs/loader.js b/src/vs/loader.js index 4eddcab3a02..2de484960b1 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -235,6 +235,7 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { + ; var ConfigurationOptionsUtil = (function () { function ConfigurationOptionsUtil() { } @@ -305,22 +306,8 @@ var AMDLoader; if (typeof options.nodeCachedData.writeDelay !== 'number' || options.nodeCachedData.writeDelay < 0) { options.nodeCachedData.writeDelay = 1000 * 7; } - if (typeof options.nodeCachedData.onData !== 'function') { - options.nodeCachedData.onData = function (err) { - if (err && err.errorCode === 'cachedDataRejected') { - console.warn('Rejected cached data from file: ' + err.path); - } - else if (err && err.errorCode) { - console.error('Problems handling cached data file: ' + err.path); - console.error(err.detail); - } - else if (err) { - console.error(err); - } - }; - } if (!options.nodeCachedData.path || typeof options.nodeCachedData.path !== 'string') { - options.nodeCachedData.onData('INVALID cached data configuration, \'path\' MUST be set'); + options.onError('INVALID cached data configuration, \'path\' MUST be set'); options.nodeCachedData = undefined; } } @@ -660,7 +647,6 @@ var AMDLoader; this._env = env; this._didInitialize = false; this._didPatchNodeRequire = false; - this._hasCreateCachedData = false; } NodeScriptLoader.prototype._init = function (nodeRequire) { if (this._didInitialize) { @@ -672,14 +658,17 @@ var AMDLoader; this._vm = nodeRequire('vm'); this._path = nodeRequire('path'); this._crypto = nodeRequire('crypto'); - // check for `createCachedData`-api - this._hasCreateCachedData = typeof (new this._vm.Script('').createCachedData) === 'function'; }; // patch require-function of nodejs such that we can manually create a script // from cached data. this is done by overriding the `Module._compile` function NodeScriptLoader.prototype._initNodeRequire = function (nodeRequire, moduleManager) { + // It is important to check for `nodeCachedData` first and then set `_didPatchNodeRequire`. + // That's because `nodeCachedData` is set _after_ calling this for the first time... var nodeCachedData = moduleManager.getConfig().getOptionsLiteral().nodeCachedData; - if (!nodeCachedData || this._didPatchNodeRequire) { + if (!nodeCachedData) { + return; + } + if (this._didPatchNodeRequire) { return; } this._didPatchNodeRequire = true; @@ -708,13 +697,13 @@ var AMDLoader; content = content.replace(/^#!.*/, ''); // create wrapper function var wrapper = Module.wrap(content); - var cachedDataPath = that._getCachedDataPath(nodeCachedData.seed, nodeCachedData.path, filename); + var cachedDataPath = that._getCachedDataPath(nodeCachedData, filename); var options = { filename: filename }; try { options.cachedData = that._fs.readFileSync(cachedDataPath); } catch (e) { - options.produceCachedData = !that._hasCreateCachedData; + // ignore } var script = new that._vm.Script(wrapper, options); var compileWrapper = script.runInThisContext(options); @@ -722,7 +711,7 @@ var AMDLoader; var require = makeRequireFunction(this); var args = [this.exports, require, this, filename, dirname, process, _commonjsGlobal, Buffer]; var result = compileWrapper.apply(this.exports, args); - that._processCachedData(moduleManager, script, wrapper, cachedDataPath, !options.cachedData); + that._processCachedData(script, cachedDataPath, Boolean(options.cachedData), moduleManager.getConfig(), moduleManager.getRecorder()); return result; }; }; @@ -780,16 +769,16 @@ var AMDLoader; _this._loadAndEvalScript(moduleManager, scriptSrc, vmScriptSrc, contents, { filename: vmScriptSrc }, recorder, callback, errorback); } else { - var cachedDataPath_1 = _this._getCachedDataPath(opts.nodeCachedData.seed, opts.nodeCachedData.path, scriptSrc); + var cachedDataPath_1 = _this._getCachedDataPath(opts.nodeCachedData, scriptSrc); _this._fs.readFile(cachedDataPath_1, function (_err, cachedData) { // create script options var options = { filename: vmScriptSrc, - produceCachedData: !_this._hasCreateCachedData && typeof cachedData === 'undefined', cachedData: cachedData }; + recorder.record(cachedData ? 60 /* CachedDataFound */ : 61 /* CachedDataMissed */, scriptSrc); var script = _this._loadAndEvalScript(moduleManager, scriptSrc, vmScriptSrc, contents, options, recorder, callback, errorback); - _this._processCachedData(moduleManager, script, contents, cachedDataPath_1, !options.cachedData); + _this._processCachedData(script, cachedDataPath_1, Boolean(cachedData), moduleManager.getConfig(), recorder); }); } }); @@ -818,74 +807,56 @@ var AMDLoader; } return script; }; - NodeScriptLoader.prototype._getCachedDataPath = function (seed, basedir, filename) { - var hash = this._crypto.createHash('md5').update(filename, 'utf8').update(seed, 'utf8').digest('hex'); + NodeScriptLoader.prototype._getCachedDataPath = function (config, filename) { + var hash = this._crypto.createHash('md5').update(filename, 'utf8').update(config.seed, 'utf8').digest('hex'); var basename = this._path.basename(filename).replace(/\.js$/, ''); - return this._path.join(basedir, basename + "-" + hash + ".code"); + return this._path.join(config.path, basename + "-" + hash + ".code"); }; - NodeScriptLoader.prototype._processCachedData = function (moduleManager, script, contents, cachedDataPath, createCachedData) { + NodeScriptLoader.prototype._processCachedData = function (script, cachedDataPath, hadCachedData, config, recorder) { var _this = this; if (script.cachedDataRejected) { - // data rejected => delete cache file - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'cachedDataRejected', - path: cachedDataPath + // rejected cached data + // (1) delete data + // (2) create new data + recorder.record(62 /* CachedDataRejected */, cachedDataPath); + this._fs.unlink(cachedDataPath, function (err) { + if (err) { + config.onError(err); + } + _this._createCachedData(script, cachedDataPath, config, recorder); }); - NodeScriptLoader._runSoon(function () { - return _this._fs.unlink(cachedDataPath, function (err) { - if (err) { - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'unlink', - path: cachedDataPath, - detail: err - }); - } - }); - }, moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay / 2); } - else if (script.cachedDataProduced) { - // data produced => tell outside world - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData(undefined, { - path: cachedDataPath - }); - // data produced => write cache file - NodeScriptLoader._runSoon(function () { - return _this._fs.writeFile(cachedDataPath, script.cachedData, function (err) { - if (err) { - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'writeFile', - path: cachedDataPath, - detail: err - }); - } - }); - }, moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay); - } - else if (this._hasCreateCachedData && createCachedData) { - // NEW world - // data produced => tell outside world - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData(undefined, { - path: cachedDataPath - }); - // soon'ish create and save cached data - NodeScriptLoader._runSoon(function () { - var data = script.createCachedData(contents); - _this._fs.writeFile(cachedDataPath, data, function (err) { - if (!err) { - return; - } - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'writeFile', - path: cachedDataPath, - detail: err - }); - }); - }, moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay); + else if (!hadCachedData) { + // create cached data unless we already had + // and accepted cached data + this._createCachedData(script, cachedDataPath, config, recorder); } }; - NodeScriptLoader._runSoon = function (callback, minTimeout) { - var timeout = minTimeout + Math.ceil(Math.random() * minTimeout); - setTimeout(callback, timeout); + NodeScriptLoader.prototype._createCachedData = function (script, cachedDataPath, config, recorder) { + var _this = this; + var timeout = Math.ceil(config.getOptionsLiteral().nodeCachedData.writeDelay * (1 + Math.random())); + var lastSize = -1; + var iteration = 0; + var createLoop = function () { + setTimeout(function () { + var data = script.createCachedData(); + if (data.length === lastSize) { + return; + } + lastSize = data.length; + _this._fs.writeFile(cachedDataPath, data, function (err) { + if (err) { + config.onError(err); + } + recorder.record(63 /* CachedDataCreated */, cachedDataPath); + createLoop(); + }); + }, timeout * (Math.pow(4, iteration++))); + }; + // with some delay (`timeout`) create cached data + // and repeat that (with backoff delay) until the + // data seems to be not changing anymore + createLoop(); }; return NodeScriptLoader; }()); diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index f48e072b5e8..94a50e54731 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -27,7 +27,6 @@ export interface ParsedArgs { 'prof-startup'?: string; 'prof-startup-prefix'?: string; 'prof-append-timers'?: string; - 'prof-modules'?: string; verbose?: boolean; trace?: boolean; 'trace-category-filter'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 33d28e88cfc..b3e99e2b808 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -54,7 +54,6 @@ export const options: Option[] = [ { id: 'verbose', type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") }, { id: 'log', type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") }, { id: 'status', type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") }, - { id: 'prof-modules', type: 'boolean', alias: 'p', cat: 't', description: localize('prof-modules', "Capture performance markers while loading JS modules and print them with 'F1 > Developer: Startup Performance") }, { id: 'prof-startup', type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") }, { id: 'disable-extensions', type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, { id: 'disable-extension', type: 'string', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, @@ -275,4 +274,4 @@ export function createWaitMarkerFile(verbose?: boolean): string | undefined { } return undefined; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index bd40d800b94..ec3f140038b 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -20,7 +20,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { mergeSort } from 'vs/base/common/arrays'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/node/product'; import pkg from 'vs/platform/product/node/package'; @@ -73,7 +72,6 @@ class PerfModelContentProvider implements ITextModelContentProvider { @ICodeEditorService private readonly _editorService: ICodeEditorService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @ITimerService private readonly _timerService: ITimerService, - @IEnvironmentService private readonly _envService: IEnvironmentService, @IExtensionService private readonly _extensionService: IExtensionService, ) { } @@ -107,7 +105,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { ]).then(([metrics]) => { if (this._model && !this._model.isDisposed()) { - let stats = this._envService.args['prof-modules'] ? LoaderStats.get() : undefined; + let stats = LoaderStats.get(); let md = new MarkdownBuilder(); this._addSummary(md, metrics); md.blank(); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 53d317fbec3..287a098c05f 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -12,7 +12,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IUpdateService } from 'vs/platform/update/common/update'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -432,16 +431,16 @@ export function didUseCachedData(): boolean { if (!Boolean((global).require.getConfig().nodeCachedData)) { return false; } - // whenever cached data is produced or rejected a onNodeCachedData-callback is invoked. That callback - // stores data in the `MonacoEnvironment.onNodeCachedData` global. See: - // https://github.com/Microsoft/vscode/blob/efe424dfe76a492eab032343e2fa4cfe639939f0/src/vs/workbench/electron-browser/bootstrap/index.js#L299 - if (isNonEmptyArray(MonacoEnvironment.onNodeCachedData)) { - return false; + // There are loader events that signal if cached data was missing, rejected, + // or used. The former two mean no cached data. + for (const event of require.getStats()) { + switch (event.type) { + case LoaderEventType.CachedDataRejected: + case LoaderEventType.CachedDataMissed: + return false; + } } return true; } -declare type OnNodeCachedDataArgs = [{ errorCode: string, path: string, detail?: string }, { path: string, length: number }]; -declare const MonacoEnvironment: { onNodeCachedData: OnNodeCachedDataArgs[] }; - //#endregion