storage - scaffold a basic global storage service on the main side

This commit is contained in:
Benjamin Pasero
2018-11-27 17:25:58 +01:00
parent 7b20b40161
commit fc4ac2ccb8
7 changed files with 388 additions and 124 deletions
+41 -24
View File
@@ -73,6 +73,7 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteAgentFileSystemChannel';
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
export class CodeApplication extends Disposable {
@@ -425,30 +426,31 @@ export class CodeApplication extends Disposable {
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
// Services
const appInstantiationService = this.initServices(machineId);
return this.initServices(machineId).then(appInstantiationService => {
// Create driver
if (this.environmentService.driverHandle) {
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
this.logService.info('Driver started at:', this.environmentService.driverHandle);
this._register(server);
});
}
// Create driver
if (this.environmentService.driverHandle) {
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
this.logService.info('Driver started at:', this.environmentService.driverHandle);
this._register(server);
});
}
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this._register(authHandler);
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this._register(authHandler);
// Open Windows
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
// Open Windows
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
// Tracing: Stop tracing after windows are ready if enabled
if (this.environmentService.args.trace) {
this.stopTracingEventually(windows);
}
// Tracing: Stop tracing after windows are ready if enabled
if (this.environmentService.args.trace) {
this.stopTracingEventually(windows);
}
});
});
}
@@ -502,7 +504,7 @@ export class CodeApplication extends Disposable {
});
}
private initServices(machineId: string): IInstantiationService {
private initServices(machineId: string): Thenable<IInstantiationService> {
const services = new ServiceCollection();
if (process.platform === 'win32') {
@@ -522,6 +524,7 @@ export class CodeApplication extends Disposable {
services.set(ILaunchService, new SyncDescriptor(LaunchService));
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
services.set(IMenubarService, new SyncDescriptor(MenubarService));
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
@@ -536,7 +539,22 @@ export class CodeApplication extends Disposable {
services.set(ITelemetryService, NullTelemetryService);
}
return this.instantiationService.createChild(services);
const appInstantiationService = this.instantiationService.createChild(services);
return appInstantiationService.invokeFunction(accessor => this.initStorageService(accessor)).then(() => appInstantiationService);
}
private initStorageService(accessor: ServicesAccessor): Thenable<void> {
const storageService = accessor.get(IStorageMainService) as StorageMainService;
// Ensure to close storage on shutdown
this.lifecycleService.onWillShutdown(e => e.join(storageService.close()));
// Initialize storage service
return storageService.initialize().then(void 0, error => {
errors.onUnexpectedError(error);
this.logService.error(error);
});
}
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
@@ -684,8 +702,7 @@ export class CodeApplication extends Disposable {
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
// Start shared process after a while
const sharedProcess = new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000);
sharedProcess.schedule();
this._register(sharedProcess);
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
sharedProcessSpawn.schedule();
}
}
@@ -12,6 +12,7 @@ import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { always } from 'vs/base/common/async';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
@@ -28,6 +29,15 @@ export interface IWindowUnloadEvent {
veto(value: boolean | Thenable<boolean>): void;
}
export interface ShutdownEvent {
/**
* Allows to join the shutdown. The promise can be a long running operation but it
* will block the application from closing.
*/
join(promise: Thenable<void>): void;
}
export interface ILifecycleService {
_serviceBrand: any;
@@ -42,9 +52,8 @@ export interface ILifecycleService {
quitRequested: boolean;
/**
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeShutdown
* handler in this module can be used and it is only called once on a shutdown sequence.
* An event that fires when the application is about to shutdown before any window is closed.
* The shutdown can still be prevented by any window that vetos this event.
*/
onBeforeShutdown: Event<void>;
@@ -53,37 +62,37 @@ export interface ILifecycleService {
* vetoed the shutdown sequence. At this point listeners are ensured that the application will
* quit without veto.
*/
onWillShutdown: Event<void>;
onWillShutdown: Event<ShutdownEvent>;
/**
* We provide our own event when we close a window because the general window.on('close')
* is called even when the window prevents the closing. We want an event that truly fires
* before the window gets closed for real.
* An event that fires before a window closes. This event is fired after any veto has been dealt
* with so that listeners know for sure that the window will close without veto.
*/
onBeforeWindowClose: Event<ICodeWindow>;
/**
* An even that can be vetoed to prevent a window from being unloaded.
* An event that fires before a window is about to unload. Listeners can veto this event to prevent
* the window from unloading.
*/
onBeforeWindowUnload: Event<IWindowUnloadEvent>;
/**
* Close a window for the provided reason. Shutdown handlers are triggered.
* Unload a window for the provided reason. All lifecycle event handlers are triggered.
*/
unload(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */>;
/**
* Restart the application with optional arguments (CLI). Shutdown handlers are triggered.
* Restart the application with optional arguments (CLI). All lifecycle event handlers are triggered.
*/
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void;
/**
* Shutdown the application normally. Shutdown handlers are triggered.
* Shutdown the application normally. All lifecycle event handlers are triggered.
*/
quit(fromUpdate?: boolean): Thenable<boolean /* veto */>;
/**
* Forcefully shutdown the application. No shutdown handlers are triggered.
* Forcefully shutdown the application. No livecycle event handlers are triggered.
*/
kill(code?: number): void;
}
@@ -95,11 +104,14 @@ export class LifecycleService extends Disposable implements ILifecycleService {
private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted
private windowToCloseRequest: { [windowId: string]: boolean } = Object.create(null);
private pendingQuitPromise: Thenable<boolean> | null;
private pendingQuitPromiseResolve: { (veto: boolean): void } | null;
private oneTimeListenerTokenGenerator = 0;
private windowCounter = 0;
private pendingQuitPromise: Thenable<boolean> | null;
private pendingQuitPromiseResolve: { (veto: boolean): void } | null;
private pendingWillShutdownPromise: Thenable<void> | null;
private _quitRequested = false;
get quitRequested(): boolean { return this._quitRequested; }
@@ -109,8 +121,8 @@ export class LifecycleService extends Disposable implements ILifecycleService {
private readonly _onBeforeShutdown = this._register(new Emitter<void>());
readonly onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
private readonly _onWillShutdown = this._register(new Emitter<void>());
readonly onWillShutdown: Event<void> = this._onWillShutdown.event;
private readonly _onWillShutdown = this._register(new Emitter<ShutdownEvent>());
readonly onWillShutdown: Event<ShutdownEvent> = this._onWillShutdown.event;
private readonly _onBeforeWindowClose = this._register(new Emitter<ICodeWindow>());
readonly onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
@@ -141,15 +153,14 @@ export class LifecycleService extends Disposable implements ILifecycleService {
private registerListeners(): void {
// before-quit
app.on('before-quit', e => {
this.logService.trace('Lifecycle#before-quit');
// before-quit: an event that is fired if application quit was
// requested but before any window was closed.
const beforeQuitListener = () => {
if (this._quitRequested) {
this.logService.trace('Lifecycle#before-quit - returning because quit was already requested');
return;
}
this.logService.trace('Lifecycle#app.on(before-quit)');
this._quitRequested = true;
// Emit event to indicate that we are about to shutdown
@@ -157,25 +168,77 @@ export class LifecycleService extends Disposable implements ILifecycleService {
this._onBeforeShutdown.fire();
// macOS: can run without any window open. in that case we fire
// the onShutdown() event directly because there is no veto to be expected.
// the onWillShutdown() event directly because there is no veto
// to be expected.
if (isMacintosh && this.windowCounter === 0) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onWillShutdown.fire();
this.beginOnWillShutdown();
}
});
};
app.addListener('before-quit', beforeQuitListener);
// window-all-closed
app.on('window-all-closed', () => {
this.logService.trace('Lifecycle#window-all-closed');
// window-all-closed: an event that only fires when the last window
// was closed. We override this event to be in charge if app.quit()
// should be called or not.
const windowAllClosedListener = () => {
this.logService.trace('Lifecycle#app.on(window-all-closed)');
// Windows/Linux: we quit when all windows have closed
// Mac: we only quit when quit was requested
if (this._quitRequested || process.platform !== 'darwin') {
if (this._quitRequested || !isMacintosh) {
app.quit();
}
};
app.addListener('window-all-closed', windowAllClosedListener);
// will-quit: an event that is fired after all windows have been
// closed, but before actually quitting.
app.once('will-quit', e => {
this.logService.trace('Lifecycle#app.on(will-quit)');
// Prevent the quit until the shutdown promise was resolved
e.preventDefault();
// Start shutdown sequence
const shutdownPromise = this.beginOnWillShutdown();
// Wait until shutdown is signaled to be complete
always(shutdownPromise, () => {
// Resolve pending quit promise now without veto
this.resolvePendingQuitPromise(false /* no veto */);
// Quit again, this time do not prevent this, since our
// will-quit listener is only installed "once". Also
// remove any listener we have that is no longer needed
app.removeListener('before-quit', beforeQuitListener);
app.removeListener('window-all-closed', windowAllClosedListener);
app.quit();
});
});
}
private beginOnWillShutdown(): Thenable<void> {
if (this.pendingWillShutdownPromise) {
return this.pendingWillShutdownPromise; // shutdown is already running
}
this.logService.trace('Lifecycle#onWillShutdown.fire()');
const joiners: Thenable<void>[] = [];
this._onWillShutdown.fire({
join(promise) {
if (promise) {
joiners.push(promise);
}
}
});
this.pendingWillShutdownPromise = Promise.all(joiners).then(null, err => this.logService.error(err));
return this.pendingWillShutdownPromise;
}
registerWindow(window: ICodeWindow): void {
// track window count
@@ -183,49 +246,48 @@ export class LifecycleService extends Disposable implements ILifecycleService {
// Window Before Closing: Main -> Renderer
window.win.on('close', e => {
const windowId = window.id;
this.logService.trace('Lifecycle#window-before-close', windowId);
// The window already acknowledged to be closed
const windowId = window.id;
if (this.windowToCloseRequest[windowId]) {
this.logService.trace('Lifecycle#window-close', windowId);
delete this.windowToCloseRequest[windowId];
return;
}
this.logService.trace(`Lifecycle#window.on('close') - window ID ${window.id}`);
// Otherwise prevent unload and handle it from window
e.preventDefault();
this.unload(window, UnloadReason.CLOSE).then(veto => {
if (!veto) {
this.windowToCloseRequest[windowId] = true;
this.logService.trace('Lifecycle#onBeforeWindowClose.fire()');
this._onBeforeWindowClose.fire(window);
window.close();
} else {
this._quitRequested = false;
if (veto) {
delete this.windowToCloseRequest[windowId];
return;
}
this.windowToCloseRequest[windowId] = true;
// Fire onBeforeWindowClose before actually closing
this.logService.trace(`Lifecycle#onBeforeWindowClose.fire() - window ID ${windowId}`);
this._onBeforeWindowClose.fire(window);
// No veto, close window now
window.close();
});
});
// Window After Closing
window.win.on('closed', e => {
const windowId = window.id;
this.logService.trace('Lifecycle#window-closed', windowId);
this.logService.trace(`Lifecycle#window.on('closed') - window ID ${window.id}`);
// update window count
this.windowCounter--;
// if there are no more code windows opened, fire the onShutdown event, unless
// if there are no more code windows opened, fire the onWillShutdown event, unless
// we are on macOS where it is perfectly fine to close the last window and
// the application continues running (unless quit was actually requested)
if (this.windowCounter === 0 && (!isMacintosh || this._quitRequested)) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onWillShutdown.fire();
this.beginOnWillShutdown();
}
});
}
@@ -237,44 +299,53 @@ export class LifecycleService extends Disposable implements ILifecycleService {
return Promise.resolve(false);
}
this.logService.trace('Lifecycle#unload()', window.id);
const windowUnloadReason = this._quitRequested ? UnloadReason.QUIT : reason;
this.logService.trace(`Lifecycle#unload() - window ID ${window.id}`);
// first ask the window itself if it vetos the unload
const windowUnloadReason = this._quitRequested ? UnloadReason.QUIT : reason;
return this.onBeforeUnloadWindowInRenderer(window, windowUnloadReason).then(veto => {
if (veto) {
this.logService.trace('Lifecycle#unload(): veto in renderer', window.id);
this.logService.trace(`Lifecycle#unload() - veto in renderer (window ID ${window.id})`);
return this.handleVeto(veto);
return this.handleWindowUnloadVeto(veto);
}
// then check for vetos in the main side
return this.onBeforeUnloadWindowInMain(window, windowUnloadReason).then(veto => {
if (veto) {
this.logService.trace('Lifecycle#unload(): veto in main', window.id);
this.logService.trace(`Lifecycle#unload() - veto in main (window ID ${window.id})`);
return this.handleVeto(veto);
} else {
this.logService.trace('Lifecycle#unload(): unload continues without veto', window.id);
return this.handleWindowUnloadVeto(veto);
}
this.logService.trace(`Lifecycle#unload() - no veto (window ID ${window.id})`);
// finally if there are no vetos, unload the renderer
return this.onWillUnloadWindowInRenderer(window, windowUnloadReason).then(() => false);
});
});
}
private handleVeto(veto: boolean): boolean {
private handleWindowUnloadVeto(veto: boolean): boolean {
if (!veto) {
return false; // no veto
}
// Any cancellation also cancels a pending quit if present
if (veto && this.pendingQuitPromiseResolve) {
this.pendingQuitPromiseResolve(true /* veto */);
// a veto resolves any pending quit with veto
this.resolvePendingQuitPromise(true /* veto */);
// a veto resets the pending quit request flag
this._quitRequested = false;
return true; // veto
}
private resolvePendingQuitPromise(veto: boolean): void {
if (this.pendingQuitPromiseResolve) {
this.pendingQuitPromiseResolve(veto);
this.pendingQuitPromiseResolve = null;
this.pendingQuitPromise = null;
}
return veto;
}
private onBeforeUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */> {
@@ -325,38 +396,28 @@ export class LifecycleService extends Disposable implements ILifecycleService {
* by the user or not.
*/
quit(fromUpdate?: boolean): Thenable<boolean /* veto */> {
this.logService.trace('Lifecycle#quit()');
if (!this.pendingQuitPromise) {
this.pendingQuitPromise = new Promise(resolve => {
// Store as field to access it from a window cancellation
this.pendingQuitPromiseResolve = resolve;
// The will-quit event is fired when all windows have closed without veto
app.once('will-quit', () => {
this.logService.trace('Lifecycle#will-quit');
if (this.pendingQuitPromiseResolve) {
if (fromUpdate) {
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
}
this.pendingQuitPromiseResolve(false /* no veto */);
this.pendingQuitPromiseResolve = null;
this.pendingQuitPromise = null;
}
});
// Calling app.quit() will trigger the close handlers of each opened window
// and only if no window vetoed the shutdown, we will get the will-quit event
this.logService.trace('Lifecycle#quit() - calling app.quit()');
app.quit();
});
} else {
this.logService.trace('Lifecycle#quit() - a pending quit was found');
if (this.pendingQuitPromise) {
return this.pendingQuitPromise;
}
this.logService.trace(`Lifecycle#quit() - from update: ${fromUpdate}`);
// Remember the reason for quit was to restart
if (fromUpdate) {
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
}
this.pendingQuitPromise = new Promise(resolve => {
// Store as field to access it from a window cancellation
this.pendingQuitPromiseResolve = resolve;
// Calling app.quit() will trigger the close handlers of each opened window
// and only if no window vetoed the shutdown, we will get the will-quit event
this.logService.trace('Lifecycle#quit() - calling app.quit()');
app.quit();
});
return this.pendingQuitPromise;
}
@@ -400,6 +461,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
}
// relaunch after we are sure there is no veto
this.logService.trace('Lifecycle#relaunch() - calling app.relaunch()');
app.relaunch({ args });
}
});
@@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorage, Storage, IStorageLoggingOptions, NullStorage } from 'vs/base/node/storage';
import { join } from 'path';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { mark } from 'vs/base/common/performance';
export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');
export interface IStorageMainService {
_serviceBrand: any;
/**
* Emitted whenever data is updated or deleted.
*/
readonly onDidChangeStorage: Event<IStorageChangeEvent>;
/**
* Emitted when the storage is about to persist. This is the right time
* to persist data to ensure it is stored before the application shuts
* down.
*/
readonly onWillSaveState: Event<void>;
/**
* Retrieve an element stored with the given key from storage. Use
* the provided defaultValue if the element is null or undefined.
*/
get(key: string, fallbackValue: string): string;
/**
* Retrieve an element stored with the given key from storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a boolean.
*/
getBoolean(key: string, fallbackValue: boolean): boolean;
/**
* Retrieve an element stored with the given key from storage. Use
* the provided defaultValue if the element is null or undefined. The element
* will be converted to a number using parseInt with a base of 10.
*/
getInteger(key: string, fallbackValue: number): number;
/**
* Store a string value under the given key to storage. The value will
* be converted to a string.
*/
store(key: string, value: any): void;
/**
* Delete an element stored under the provided key from storage.
*/
remove(key: string): void;
}
export interface IStorageChangeEvent {
key: string;
}
export class StorageMainService extends Disposable implements IStorageMainService {
_serviceBrand: any;
private static STORAGE_NAME = 'temp.vscdb';
private _onDidChangeStorage: Emitter<IStorageChangeEvent> = this._register(new Emitter<IStorageChangeEvent>());
get onDidChangeStorage(): Event<IStorageChangeEvent> { return this._onDidChangeStorage.event; }
private _onWillSaveState: Emitter<void> = this._register(new Emitter<void>());
get onWillSaveState(): Event<void> { return this._onWillSaveState.event; }
private storage: IStorage;
constructor(
@ILogService private logService: ILogService,
@IEnvironmentService environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService
) {
super();
const useInMemoryStorage = !!environmentService.extensionTestsPath; // no storage during extension tests!
this.storage = new NullStorage() || new Storage({
path: useInMemoryStorage ? Storage.IN_MEMORY_PATH : join(environmentService.globalStorageHome, StorageMainService.STORAGE_NAME),
logging: this.createLogginOptions()
});
this.registerListeners();
}
private createLogginOptions(): IStorageLoggingOptions {
const loggedStorageErrors = new Set<string>();
return {
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : void 0,
logError: error => {
this.logService.error(error);
const errorStr = `${error}`;
if (!loggedStorageErrors.has(errorStr)) {
loggedStorageErrors.add(errorStr);
/* __GDPR__
"sqliteMainStorageError" : {
"storageError": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('sqliteMainStorageError', {
'storageError': errorStr
});
}
}
} as IStorageLoggingOptions;
}
private registerListeners(): void {
this._register(this.storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key })));
}
initialize(): Thenable<void> {
mark('main:willInitGlobalStorage');
return this.storage.init().then(() => {
mark('main:didInitGlobalStorage');
}, error => {
mark('main:didInitGlobalStorage');
return Promise.reject(error);
});
}
get(key: string, fallbackValue: string): string {
return this.storage.get(key, fallbackValue);
}
getBoolean(key: string, fallbackValue: boolean): boolean {
return this.storage.getBoolean(key, fallbackValue);
}
getInteger(key: string, fallbackValue: number): number {
return this.storage.getInteger(key, fallbackValue);
}
store(key: string, value: any): Thenable<void> {
return this.storage.set(key, value);
}
remove(key: string): Thenable<void> {
return this.storage.delete(key);
}
close(): Thenable<void> {
this.logService.trace('StorageMainService#close() - begin');
// Signal as event so that clients can still store data
this._onWillSaveState.fire();
// Do it
return this.storage.close().then(() => this.logService.trace('StorageMainService#close() - finished'));
}
}
@@ -147,10 +147,7 @@ export class StorageService extends Disposable implements IStorageService {
}
return migrationPromise.then(() => {
mark('willInitGlobalStorage');
return this.globalStorage.init().then(() => {
mark('didInitGlobalStorage');
});
return this.globalStorage.init();
});
});
});
@@ -415,7 +412,7 @@ export class StorageService extends Disposable implements IStorageService {
workspaceItemsParsed.set(key, safeParse(value));
});
console.group(`Storage: Global (integrity: ${result[2]}, load: ${getDuration('willInitGlobalStorage', 'didInitGlobalStorage')}, path: ${this.globalStorageWorkspacePath})`);
console.group(`Storage: Global (integrity: ${result[2]}, load: ${getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage')}, path: ${this.globalStorageWorkspacePath})`);
let globalValues: { key: string, value: string }[] = [];
globalItems.forEach((value, key) => {
globalValues.push({ key, value });
@@ -292,6 +292,7 @@ export class WorkbenchShell extends Disposable {
const workbenchReadyDuration = perf.getDuration(initialStartup ? 'main:started' : 'main:loadWindow', 'didStartWorkbench');
const workspaceStorageRequireDuration = perf.getDuration('willRequireSQLite', 'didRequireSQLite');
const workspaceStorageSchemaDuration = perf.getDuration('willSetupSQLiteSchema', 'didSetupSQLiteSchema');
const globalStorageInitDuration = perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage');
const workspaceStorageInitDuration = perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage');
const workspaceStorageFileExistsDuration = perf.getDuration('willCheckWorkspaceStorageExists', 'didCheckWorkspaceStorageExists');
const workspaceStorageMigrationDuration = perf.getDuration('willMigrateWorkspaceStorageKeys', 'didMigrateWorkspaceStorageKeys');
@@ -314,6 +315,7 @@ export class WorkbenchShell extends Disposable {
"workspaceMigrationTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"globalReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"localStorageTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
@@ -329,6 +331,7 @@ export class WorkbenchShell extends Disposable {
'workspaceMigrationTime': workspaceStorageMigrationDuration,
'workspaceRequireTime': workspaceStorageRequireDuration,
'workspaceSchemaTime': workspaceStorageSchemaDuration,
'globalReadTime': globalStorageInitDuration,
'workspaceReadTime': workspaceStorageInitDuration,
'localStorageTime': localStorageDuration,
'workbenchRequireTime': workbenchLoadDuration,
@@ -356,6 +359,7 @@ export class WorkbenchShell extends Disposable {
"workspaceMigrationTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"globalReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"localStorageTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
@@ -370,6 +374,7 @@ export class WorkbenchShell extends Disposable {
'workspaceMigrationTime': workspaceStorageMigrationDuration,
'workspaceRequireTime': workspaceStorageRequireDuration,
'workspaceSchemaTime': workspaceStorageSchemaDuration,
'globalReadTime': globalStorageInitDuration,
'workspaceReadTime': workspaceStorageInitDuration,
'localStorageTime': localStorageDuration,
'workbenchRequireTime': workbenchLoadDuration,
@@ -26,9 +26,11 @@ class Info {
static getTimerInfo(metrics: IStartupMetrics, nodeModuleLoadTime?: number): { [name: string]: Info } {
const table: { [name: string]: Info } = Object.create(null);
table['start => app.isReady'] = new Info(metrics.timers.ellapsedAppReady, '[main]', metrics.initialStartup);
table['nls:start => nls:end'] = new Info(metrics.timers.ellapsedNlsGeneration, '[main]', metrics.initialStartup);
table['app.isReady => window.loadUrl()'] = new Info(metrics.timers.ellapsedWindowLoad, '[main]', metrics.initialStartup);
table['start => app.isReady'] = new Info(metrics.timers.ellapsedAppReady, '[main]', `initial startup: ${metrics.initialStartup}`);
table['nls:start => nls:end'] = new Info(metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`);
table['app.isReady => window.loadUrl()'] = new Info(metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`);
table['init global storage'] = new Info(metrics.timers.ellapsedGlobalStorageInit, '[main]', `initial startup: ${metrics.initialStartup}`);
table['window.loadUrl() => begin to require(workbench.main.js)'] = new Info(metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind));
table['require(workbench.main.js)'] = new Info(metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${nodeModuleLoadTime ? `, node_modules took ${nodeModuleLoadTime}ms` : ''}`);
@@ -53,6 +53,7 @@ export interface IMemoryInfo {
"timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedGlobalStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkspaceStorageRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
@@ -194,6 +195,15 @@ export interface IStartupMetrics {
*/
ellapsedWindowLoadToRequire: number;
/**
* The time it took to require the global storage DB, connect to it
* and load the initial set of values.
*
* * Happens in the main-process
* * Measured with the `main:willInitGlobalStorage` and `main:didInitGlobalStorage` performance marks.
*/
ellapsedGlobalStorageInit: number;
/**
* The time it took to require the workspace storage DB.
*
@@ -388,6 +398,7 @@ class TimerService implements ITimerService {
ellapsedWindowLoad: initialStartup ? perf.getDuration('main:appReady', 'main:loadWindow') : undefined,
ellapsedWindowLoadToRequire: perf.getDuration('main:loadWindow', 'willLoadWorkbenchMain'),
ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'),
ellapsedGlobalStorageInit: perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage'),
ellapsedWorkspaceStorageRequire: perf.getDuration('willRequireSQLite', 'didRequireSQLite'),
ellapsedWorkspaceStorageInit: perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'),
ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'),