Skip to content

Commit 2cd6fb5

Browse files
chore: add a new “Startup Time” event for sessions MONGOSH-1653 (#1761)
* chore: refactor and add categorisation to marks * chore: connect startup with session started event * chore: remove .only * chore: fix names and duration calculation * chore: apply suggestions from PR * chore: add a jsdoc to toSnakeCase * chore: move non-interactive start session a bit earlier, before evaluating scripts * chore: Update packages/logging/src/setup-logger-and-telemetry.ts Co-authored-by: Bailey Pearson <[email protected]> * chore: fix package-lock.json * chore: make clearer that timingData can contain TimingCategory or any other string --------- Co-authored-by: Bailey Pearson <[email protected]>
1 parent 87f1eb6 commit 2cd6fb5

File tree

11 files changed

+299
-75
lines changed

11 files changed

+299
-75
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli-repl/src/cli-repl.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,17 @@ import {
3737
SampledAnalytics,
3838
} from '@mongosh/logging';
3939
import type { MongoshBus } from '@mongosh/types';
40-
import { CliUserConfig, CliUserConfigValidator } from '@mongosh/types';
40+
import {
41+
CliUserConfig,
42+
CliUserConfigValidator,
43+
TimingCategories,
44+
} from '@mongosh/types';
4145
import { promises as fs } from 'fs';
4246
import path from 'path';
4347
import { promisify } from 'util';
4448
import { getOsInfo } from './get-os-info';
4549
import { UpdateNotificationManager } from './update-notification-manager';
46-
import { markTime } from './startup-timing';
50+
import { getTimingData, markTime, summariseTimingData } from './startup-timing';
4751

4852
/**
4953
* Connecting text key.
@@ -228,11 +232,14 @@ export class CliRepl implements MongoshIOProvider {
228232
): Promise<void> {
229233
const { version } = require('../package.json');
230234
await this.verifyNodeVersion();
231-
markTime('verified node version');
235+
markTime(TimingCategories.REPLInstantiation, 'verified node version');
232236

233237
this.isContainerizedEnvironment =
234238
await this.getIsContainerizedEnvironment();
235-
markTime('checked containerized environment');
239+
markTime(
240+
TimingCategories.REPLInstantiation,
241+
'checked containerized environment'
242+
);
236243

237244
if (!this.cliOptions.nodb) {
238245
const cs = new ConnectionString(driverUri);
@@ -253,23 +260,23 @@ export class CliRepl implements MongoshIOProvider {
253260
} catch (err: any) {
254261
this.warnAboutInaccessibleFile(err);
255262
}
256-
markTime('ensured shell homedir');
263+
markTime(TimingCategories.REPLInstantiation, 'ensured shell homedir');
257264

258265
await this.logManager.cleanupOldLogfiles();
259-
markTime('cleaned up log files');
266+
markTime(TimingCategories.Logging, 'cleaned up log files');
260267
const logger = await this.logManager.createLogWriter();
261268
if (!this.cliOptions.quiet) {
262269
this.output.write(`Current Mongosh Log ID:\t${logger.logId}\n`);
263270
}
264271
this.logWriter = logger;
265-
markTime('instantiated log writer');
272+
markTime(TimingCategories.Logging, 'instantiated log writer');
266273

267274
logger.info('MONGOSH', mongoLogId(1_000_000_000), 'log', 'Starting log', {
268275
execPath: process.execPath,
269276
envInfo: redactSensitiveData(this.getLoggedEnvironmentVariables()),
270277
...(await buildInfo()),
271278
});
272-
markTime('logged initial message');
279+
markTime(TimingCategories.Logging, 'logged initial message');
273280

274281
let analyticsSetupError: Error | null = null;
275282
try {
@@ -280,7 +287,7 @@ export class CliRepl implements MongoshIOProvider {
280287
analyticsSetupError = err;
281288
}
282289

283-
markTime('created analytics instance');
290+
markTime(TimingCategories.Telemetry, 'created analytics instance');
284291
setupLoggerAndTelemetry(
285292
this.bus,
286293
logger,
@@ -293,7 +300,7 @@ export class CliRepl implements MongoshIOProvider {
293300
},
294301
version
295302
);
296-
markTime('completed telemetry setup');
303+
markTime(TimingCategories.Telemetry, 'completed telemetry setup');
297304

298305
if (analyticsSetupError) {
299306
this.bus.emit('mongosh:error', analyticsSetupError, 'analytics');
@@ -308,7 +315,7 @@ export class CliRepl implements MongoshIOProvider {
308315
}
309316

310317
this.globalConfig = await this.loadGlobalConfigFile();
311-
markTime('read global config files');
318+
markTime(TimingCategories.UserConfigLoading, 'read global config files');
312319

313320
// Needs to happen after loading the mongosh config file(s)
314321
void this.fetchMongoshUpdateUrl();
@@ -342,7 +349,7 @@ export class CliRepl implements MongoshIOProvider {
342349
}
343350

344351
driverOptions = await this.prepareOIDCOptions(driverOptions);
345-
markTime('prepared OIDC options');
352+
markTime(TimingCategories.DriverSetup, 'prepared OIDC options');
346353

347354
let initialServiceProvider;
348355
try {
@@ -358,12 +365,12 @@ export class CliRepl implements MongoshIOProvider {
358365
}
359366
throw err;
360367
}
361-
markTime('completed SP setup');
368+
markTime(TimingCategories.DriverSetup, 'completed SP setup');
362369
const initialized = await this.mongoshRepl.initialize(
363370
initialServiceProvider,
364371
await this.getMoreRecentMongoshVersion()
365372
);
366-
markTime('initialized mongosh repl');
373+
markTime(TimingCategories.REPLInstantiation, 'initialized mongosh repl');
367374
this.injectReplFunctions();
368375

369376
const commandLineLoadFiles = this.cliOptions.fileNames ?? [];
@@ -402,17 +409,25 @@ export class CliRepl implements MongoshIOProvider {
402409
this.mongoshRepl
403410
),
404411
});
405-
markTime('set up editor');
412+
markTime(TimingCategories.REPLInstantiation, 'set up editor');
406413

407414
if (willExecuteCommandLineScripts) {
408415
this.mongoshRepl.setIsInteractive(willEnterInteractiveMode);
416+
417+
this.bus.emit('mongosh:start-session', {
418+
isInteractive: false,
419+
timings: summariseTimingData(getTimingData()),
420+
});
421+
409422
this.bus.emit('mongosh:start-loading-cli-scripts', {
410423
usesShellOption: !!this.cliOptions.shell,
411424
});
425+
412426
const exitCode = await this.loadCommandLineFilesAndEval(
413427
commandLineLoadFiles,
414428
evalScripts
415429
);
430+
416431
if (exitCode !== 0) {
417432
await this.exit(exitCode);
418433
return;
@@ -440,21 +455,27 @@ export class CliRepl implements MongoshIOProvider {
440455
* - Programmatic users who really want a dependency on a snippet can use
441456
* snippet('load-all') to explicitly load snippets
442457
*/
443-
markTime('start load snippets');
458+
markTime(TimingCategories.SnippetLoading, 'start load snippets');
444459
await snippetManager?.loadAllSnippets();
445-
markTime('done load snippets');
460+
markTime(TimingCategories.SnippetLoading, 'done load snippets');
446461
}
462+
463+
markTime(TimingCategories.ResourceFileLoading, 'loading rc files');
447464
await this.loadRcFiles();
448-
markTime('loaded rc files');
465+
markTime(TimingCategories.ResourceFileLoading, 'loaded rc files');
449466

450467
this.verifyPlatformSupport();
451468

452469
// We only enable/disable here, since the rc file/command line scripts
453470
// can disable the telemetry setting.
454471
this.setTelemetryEnabled(await this.getConfig('enableTelemetry'));
455472
this.bus.emit('mongosh:start-mongosh-repl', { version });
456-
markTime('starting repl');
473+
markTime(TimingCategories.REPLInstantiation, 'starting repl');
457474
await this.mongoshRepl.startRepl(initialized);
475+
this.bus.emit('mongosh:start-session', {
476+
isInteractive: true,
477+
timings: summariseTimingData(getTimingData()),
478+
});
458479
}
459480

460481
injectReplFunctions(): void {
@@ -537,15 +558,15 @@ export class CliRepl implements MongoshIOProvider {
537558
let lastEvalResult: any;
538559
let exitCode = 0;
539560
try {
540-
markTime('start eval scripts');
561+
markTime(TimingCategories.Eval, 'start eval scripts');
541562
for (const script of evalScripts) {
542563
this.bus.emit('mongosh:eval-cli-script');
543564
lastEvalResult = await this.mongoshRepl.loadExternalCode(
544565
script,
545566
'@(shell eval)'
546567
);
547568
}
548-
markTime('finished eval scripts');
569+
markTime(TimingCategories.Eval, 'finished eval scripts');
549570
} catch (err) {
550571
// We have two distinct flows of control in the exception case;
551572
// if we are running in --json mode, we treat the error as a
@@ -584,7 +605,8 @@ export class CliRepl implements MongoshIOProvider {
584605
this.output.write(formattedResult + '\n');
585606
}
586607

587-
markTime('wrote eval output, start loading external files');
608+
markTime(TimingCategories.Eval, 'wrote eval output');
609+
markTime(TimingCategories.EvalFile, 'start loading external files');
588610
for (const file of files) {
589611
if (!this.cliOptions.quiet) {
590612
this.output.write(
@@ -593,7 +615,7 @@ export class CliRepl implements MongoshIOProvider {
593615
}
594616
await this.mongoshRepl.loadExternalFile(file);
595617
}
596-
markTime('finished external files');
618+
markTime(TimingCategories.EvalFile, 'finished external files');
597619
return exitCode;
598620
}
599621

@@ -978,7 +1000,7 @@ export class CliRepl implements MongoshIOProvider {
9781000
* Close all open resources held by this REPL instance.
9791001
*/
9801002
async close(): Promise<void> {
981-
markTime('start closing');
1003+
markTime(TimingCategories.REPLInstantiation, 'start closing');
9821004
if (this.closing) {
9831005
return;
9841006
}
@@ -998,7 +1020,7 @@ export class CliRepl implements MongoshIOProvider {
9981020
await new Promise((resolve) => this.output.write('', resolve));
9991021
}
10001022
}
1001-
markTime('output flushed');
1023+
markTime(TimingCategories.REPLInstantiation, 'output flushed');
10021024
this.closing = true;
10031025
const analytics = this.toggleableAnalytics;
10041026
let flushError: string | null = null;
@@ -1007,7 +1029,7 @@ export class CliRepl implements MongoshIOProvider {
10071029
const flushStart = Date.now();
10081030
try {
10091031
await promisify(analytics.flush.bind(analytics))();
1010-
markTime('flushed analytics');
1032+
markTime(TimingCategories.Telemetry, 'flushed analytics');
10111033
} catch (err: any) {
10121034
flushError = err.message;
10131035
} finally {
@@ -1025,7 +1047,7 @@ export class CliRepl implements MongoshIOProvider {
10251047
}
10261048
);
10271049
await this.logWriter?.flush();
1028-
markTime('flushed log writer');
1050+
markTime(TimingCategories.Logging, 'flushed log writer');
10291051
this.bus.emit('mongosh:closed');
10301052
}
10311053

packages/cli-repl/src/mongosh-repl.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import {
1818
import type { ShellResult } from '@mongosh/shell-evaluator';
1919
import { ShellEvaluator } from '@mongosh/shell-evaluator';
2020
import type { ConfigProvider, MongoshBus } from '@mongosh/types';
21-
import { CliUserConfig, CliUserConfigValidator } from '@mongosh/types';
21+
import {
22+
CliUserConfig,
23+
CliUserConfigValidator,
24+
TimingCategories,
25+
} from '@mongosh/types';
2226
import askcharacter from 'askcharacter';
2327
import askpassword from 'askpassword';
2428
import { Console } from 'console';
@@ -174,7 +178,7 @@ class MongoshNodeRepl implements EvaluationListener {
174178
);
175179
instanceState.setEvaluationListener(this);
176180
await instanceState.fetchConnectionInfo();
177-
markTime('fetched connection info');
181+
markTime(TimingCategories.REPLInstantiation, 'fetched connection info');
178182

179183
const { buildInfo, extraInfo } = instanceState.connectionInfo;
180184
let mongodVersion = extraInfo?.is_stream
@@ -188,7 +192,7 @@ class MongoshNodeRepl implements EvaluationListener {
188192
}
189193
await this.greet(mongodVersion, moreRecentMongoshVersion);
190194
await this.printBasicConnectivityWarning(instanceState);
191-
markTime('greeted');
195+
markTime(TimingCategories.REPLInstantiation, 'greeted');
192196

193197
this.inspectCompact =
194198
(await this.getConfig('inspectCompact')) ?? this.inspectCompact;
@@ -198,7 +202,7 @@ class MongoshNodeRepl implements EvaluationListener {
198202
(await this.getConfig('showStackTraces')) ?? this.showStackTraces;
199203
this.redactHistory =
200204
(await this.getConfig('redactHistory')) ?? this.redactHistory;
201-
markTime('fetched config vars');
205+
markTime(TimingCategories.UserConfigLoading, 'fetched config vars');
202206

203207
const repl = asyncRepl.start({
204208
// 'repl' is not supported in startup snapshots yet.
@@ -347,7 +351,7 @@ class MongoshNodeRepl implements EvaluationListener {
347351
// https://github.com/nodejs/node/issues/36773
348352
(repl as Mutable<typeof repl>).line = '';
349353

350-
markTime('created repl object');
354+
markTime(TimingCategories.REPLInstantiation, 'created repl object');
351355
const historyFile = this.ioProvider.getHistoryFilePath();
352356
try {
353357
await promisify(repl.setupHistory).call(repl, historyFile);
@@ -410,7 +414,7 @@ class MongoshNodeRepl implements EvaluationListener {
410414
this.output.write(this.writer(warn) + '\n');
411415
}
412416

413-
markTime('set up history file');
417+
markTime(TimingCategories.UserConfigLoading, 'set up history file');
414418
(repl as any).on(asyncRepl.evalStart, () => {
415419
this.bus.emit('mongosh:evaluate-started');
416420
});
@@ -442,7 +446,7 @@ class MongoshNodeRepl implements EvaluationListener {
442446
}
443447
}
444448

445-
markTime('finished initialization');
449+
markTime(TimingCategories.REPLInstantiation, 'finished initialization');
446450
return { __initialized: 'yes' };
447451
}
448452

@@ -545,7 +549,7 @@ class MongoshNodeRepl implements EvaluationListener {
545549
context: any,
546550
filename: string
547551
): Promise<any> {
548-
markTime('start repl eval');
552+
markTime(TimingCategories.Eval, 'start repl eval');
549553
if (!this.insideAutoCompleteOrGetPrompt) {
550554
this.lineByLineInput.enableBlockOnNewLine();
551555
}
@@ -599,7 +603,7 @@ class MongoshNodeRepl implements EvaluationListener {
599603
}
600604
throw err;
601605
} finally {
602-
markTime('done repl eval');
606+
markTime(TimingCategories.Eval, 'done repl eval');
603607
if (!this.insideAutoCompleteOrGetPrompt && !interrupted) {
604608
// In case of an interrupt, onAsyncSigint will print the prompt when completed
605609
repl.setPrompt(await this.getShellPrompt());
@@ -608,7 +612,7 @@ class MongoshNodeRepl implements EvaluationListener {
608612
if (this.loadNestingLevel <= 1) {
609613
this.bus.emit('mongosh:eval-complete'); // For testing purposes.
610614
}
611-
markTime('re-set prompt');
615+
markTime(TimingCategories.Eval, 're-set prompt');
612616
}
613617
}
614618

packages/cli-repl/src/run.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { PassThrough } from 'stream';
2929
import crypto from 'crypto';
3030
import net from 'net';
3131
import v8 from 'v8';
32+
import { TimingCategories } from '@mongosh/types';
3233

3334
// TS does not yet have type definitions for v8.startupSnapshot
3435
if ((v8 as any)?.startupSnapshot?.isBuildingSnapshot?.()) {
@@ -48,7 +49,7 @@ if ((v8 as any)?.startupSnapshot?.isBuildingSnapshot?.()) {
4849

4950
(v8 as any).startupSnapshot.setDeserializeMainFunction(() => {
5051
enableFipsIfRequested();
51-
markTime('loaded pre-snapshot deps');
52+
markTime(TimingCategories.Snapshot, 'loaded pre-snapshot deps');
5253

5354
void main();
5455
});
@@ -58,7 +59,7 @@ if ((v8 as any)?.startupSnapshot?.isBuildingSnapshot?.()) {
5859

5960
// eslint-disable-next-line complexity
6061
async function main() {
61-
markTime('entered main');
62+
markTime(TimingCategories.Main, 'entered main');
6263
if (process.env.MONGOSH_RUN_NODE_SCRIPT) {
6364
// For uncompiled mongosh: node /path/to/this/file script ... -> node script ...
6465
// FOr compiled mongosh: mongosh mongosh script ... -> mongosh script ...
@@ -217,7 +218,7 @@ async function main() {
217218
shellHomePaths: shellHomePaths,
218219
globalConfigPaths: globalConfigPaths,
219220
});
220-
markTime('entering repl.start()');
221+
markTime(TimingCategories.REPLInstantiation, 'entering repl.start()');
221222
await repl.start(connectionInfo.connectionString, {
222223
productName: 'MongoDB Shell',
223224
productDocsLink: 'https://www.mongodb.com/docs/mongodb-shell/',

0 commit comments

Comments
 (0)