diff --git a/Extension/src/Debugger/extension.ts b/Extension/src/Debugger/extension.ts index 81770ce34..f784c1382 100644 --- a/Extension/src/Debugger/extension.ts +++ b/Extension/src/Debugger/extension.ts @@ -15,6 +15,7 @@ import { TargetLeafNode, setActiveSshTarget } from '../SSH/TargetsView/targetNod import { sshCommandToConfig } from '../SSH/sshCommandToConfig'; import { getSshConfiguration, getSshConfigurationFiles, parseFailures, writeSshConfiguration } from '../SSH/sshHosts'; import { pathAccessible } from '../common'; +import { instrument } from '../instrumentation'; import { getSshChannel } from '../logger'; import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess'; import { ConfigurationAssetProviderFactory, ConfigurationSnippetProvider, DebugConfigurationProvider, IConfigurationAssetProvider } from './configurationProvider'; @@ -46,10 +47,10 @@ export async function initialize(context: vscode.ExtensionContext): Promise enableSshTargetsViewAndRun(addSshTargetImpl))); disposables.push(vscode.commands.registerCommand('C_Cpp.removeSshTarget', (node?: BaseNode) => enableSshTargetsViewAndRun(removeSshTargetImpl, node))); disposables.push(vscode.commands.registerCommand(refreshCppSshTargetsViewCmd, (node?: BaseNode) => enableSshTargetsViewAndRun((node?: BaseNode) => sshTargetsProvider.refresh(node), node))); diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 553964d1c..76d464be9 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -38,6 +38,7 @@ import { logAndReturn } from '../Utility/Async/returns'; import { is } from '../Utility/System/guards'; import * as util from '../common'; import { isWindows } from '../constants'; +import { instrument, isInstrumentationEnabled } from '../instrumentation'; import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, logDebugProtocol, logLocalized, showWarning } from '../logger'; import { localizedStringCount, lookupString } from '../nativeStrings'; import { SessionState } from '../sessionState'; @@ -840,6 +841,16 @@ export interface Client { } export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client { + if (isInstrumentationEnabled) { + instrument(vscode.languages, { name: "languages" }); + instrument(vscode.window, { name: "window" }); + instrument(vscode.workspace, { name: "workspace" }); + instrument(vscode.commands, { name: "commands" }); + instrument(vscode.debug, { name: "debug" }); + instrument(vscode.env, { name: "env" }); + instrument(vscode.extensions, { name: "extensions" }); + return instrument(new DefaultClient(workspaceFolder), { ignore: ["enqueue", "onInterval", "logTelemetry"] }); + } return new DefaultClient(workspaceFolder); } @@ -1323,31 +1334,33 @@ export class DefaultClient implements Client { this.currentCopilotHoverEnabled = new PersistentWorkspaceState("cpp.copilotHover", settings.copilotHover); if (settings.copilotHover !== "disabled") { this.copilotHoverProvider = new CopilotHoverProvider(this); - this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, this.copilotHoverProvider)); + this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, instrument(this.copilotHoverProvider))); } + if (settings.copilotHover !== this.currentCopilotHoverEnabled.Value) { this.currentCopilotHoverEnabled.Value = settings.copilotHover; } - this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, this.hoverProvider)); - this.disposables.push(vscode.languages.registerInlayHintsProvider(util.documentSelector, this.inlayHintsProvider)); - this.disposables.push(vscode.languages.registerRenameProvider(util.documentSelector, new RenameProvider(this))); - this.disposables.push(vscode.languages.registerReferenceProvider(util.documentSelector, new FindAllReferencesProvider(this))); - this.disposables.push(vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this))); - this.disposables.push(vscode.languages.registerDocumentSymbolProvider(util.documentSelector, new DocumentSymbolProvider(), undefined)); - this.disposables.push(vscode.languages.registerCodeActionsProvider(util.documentSelector, new CodeActionProvider(this), undefined)); - this.disposables.push(vscode.languages.registerCallHierarchyProvider(util.documentSelector, new CallHierarchyProvider(this))); + this.disposables.push(vscode.languages.registerHoverProvider(util.documentSelector, instrument(this.hoverProvider))); + this.disposables.push(vscode.languages.registerInlayHintsProvider(util.documentSelector, instrument(this.inlayHintsProvider))); + this.disposables.push(vscode.languages.registerRenameProvider(util.documentSelector, instrument(new RenameProvider(this)))); + this.disposables.push(vscode.languages.registerReferenceProvider(util.documentSelector, instrument(new FindAllReferencesProvider(this)))); + this.disposables.push(vscode.languages.registerWorkspaceSymbolProvider(instrument(new WorkspaceSymbolProvider(this)))); + this.disposables.push(vscode.languages.registerDocumentSymbolProvider(util.documentSelector, instrument(new DocumentSymbolProvider()), undefined)); + this.disposables.push(vscode.languages.registerCodeActionsProvider(util.documentSelector, instrument(new CodeActionProvider(this)), undefined)); + this.disposables.push(vscode.languages.registerCallHierarchyProvider(util.documentSelector, instrument(new CallHierarchyProvider(this)))); + // Because formatting and codeFolding can vary per folder, we need to register these providers once // and leave them registered. The decision of whether to provide results needs to be made on a per folder basis, // within the providers themselves. - this.documentFormattingProviderDisposable = vscode.languages.registerDocumentFormattingEditProvider(util.documentSelector, new DocumentFormattingEditProvider(this)); - this.formattingRangeProviderDisposable = vscode.languages.registerDocumentRangeFormattingEditProvider(util.documentSelector, new DocumentRangeFormattingEditProvider(this)); - this.onTypeFormattingProviderDisposable = vscode.languages.registerOnTypeFormattingEditProvider(util.documentSelector, new OnTypeFormattingEditProvider(this), ";", "}", "\n"); + this.documentFormattingProviderDisposable = vscode.languages.registerDocumentFormattingEditProvider(util.documentSelector, instrument(new DocumentFormattingEditProvider(this))); + this.formattingRangeProviderDisposable = vscode.languages.registerDocumentRangeFormattingEditProvider(util.documentSelector, instrument(new DocumentRangeFormattingEditProvider(this))); + this.onTypeFormattingProviderDisposable = vscode.languages.registerOnTypeFormattingEditProvider(util.documentSelector, instrument(new OnTypeFormattingEditProvider(this)), ";", "}", "\n"); this.codeFoldingProvider = new FoldingRangeProvider(this); - this.codeFoldingProviderDisposable = vscode.languages.registerFoldingRangeProvider(util.documentSelector, this.codeFoldingProvider); + this.codeFoldingProviderDisposable = vscode.languages.registerFoldingRangeProvider(util.documentSelector, instrument(this.codeFoldingProvider)); if (settings.isEnhancedColorizationEnabled && semanticTokensLegend) { - this.semanticTokensProvider = new SemanticTokensProvider(); + this.semanticTokensProvider = instrument(new SemanticTokensProvider()); this.semanticTokensProviderDisposable = vscode.languages.registerDocumentSemanticTokensProvider(util.documentSelector, this.semanticTokensProvider, semanticTokensLegend); } @@ -1730,8 +1743,7 @@ export class DefaultClient implements Client { const oldLoggingLevelLogged: boolean = this.loggingLevel > 1; this.loggingLevel = util.getNumericLoggingLevel(changedSettings.loggingLevel); if (oldLoggingLevelLogged || this.loggingLevel > 1) { - const out: Logger = getOutputChannelLogger(); - out.appendLine(localize({ key: "loggingLevel.changed", comment: ["{0} is the setting name 'loggingLevel', {1} is a string value such as 'Debug'"] }, "{0} has changed to: {1}", "loggingLevel", changedSettings.loggingLevel)); + getOutputChannelLogger().appendLine(localize({ key: "loggingLevel.changed", comment: ["{0} is the setting name 'loggingLevel', {1} is a string value such as 'Debug'"] }, "{0} has changed to: {1}", "loggingLevel", changedSettings.loggingLevel)); } } const settings: CppSettings = new CppSettings(); @@ -2746,12 +2758,7 @@ export class DefaultClient implements Client { const status: IntelliSenseStatus = { status: Status.IntelliSenseCompiling }; testHook.updateStatus(status); } else if (message.endsWith("IntelliSense done")) { - const settings: CppSettings = new CppSettings(); - if (util.getNumericLoggingLevel(settings.loggingLevel) >= 6) { - const out: Logger = getOutputChannelLogger(); - const duration: number = Date.now() - timeStamp; - out.appendLine(localize("update.intellisense.time", "Update IntelliSense time (sec): {0}", duration / 1000)); - } + getOutputChannelLogger().appendLineAtLevel(6, localize("update.intellisense.time", "Update IntelliSense time (sec): {0}", (Date.now() - timeStamp) / 1000)); this.model.isUpdatingIntelliSense.Value = false; const status: IntelliSenseStatus = { status: Status.IntelliSenseReady }; testHook.updateStatus(status); @@ -3183,11 +3190,8 @@ export class DefaultClient implements Client { return; } - const settings: CppSettings = new CppSettings(); const out: Logger = getOutputChannelLogger(); - if (util.getNumericLoggingLevel(settings.loggingLevel) >= 6) { - out.appendLine(localize("configurations.received", "Custom configurations received:")); - } + out.appendLineAtLevel(6, localize("configurations.received", "Custom configurations received:")); const sanitized: SourceFileConfigurationItemAdapter[] = []; configs.forEach(item => { if (this.isSourceFileConfigurationItem(item, providerVersion)) { @@ -3199,10 +3203,8 @@ export class DefaultClient implements Client { uri = item.uri.toString(); } this.configurationLogging.set(uri, JSON.stringify(item.configuration, null, 4)); - if (util.getNumericLoggingLevel(settings.loggingLevel) >= 6) { - out.appendLine(` uri: ${uri}`); - out.appendLine(` config: ${JSON.stringify(item.configuration, null, 2)}`); - } + out.appendLineAtLevel(6, ` uri: ${uri}`); + out.appendLineAtLevel(6, ` config: ${JSON.stringify(item.configuration, null, 2)}`); if (item.configuration.includePath.some(path => path.endsWith('**'))) { console.warn("custom include paths should not use recursive includes ('**')"); } @@ -3306,11 +3308,7 @@ export class DefaultClient implements Client { return; } - const settings: CppSettings = new CppSettings(); - if (util.getNumericLoggingLevel(settings.loggingLevel) >= 6) { - const out: Logger = getOutputChannelLogger(); - out.appendLine(localize("browse.configuration.received", "Custom browse configuration received: {0}", JSON.stringify(sanitized, null, 2))); - } + getOutputChannelLogger().appendLineAtLevel(6, localize("browse.configuration.received", "Custom browse configuration received: {0}", JSON.stringify(sanitized, null, 2))); // Separate compiler path and args before sending to language client if (util.isString(sanitized.compilerPath)) { diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 52721a1fc..d6dc3fc0b 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -18,6 +18,7 @@ import * as which from 'which'; import { logAndReturn } from '../Utility/Async/returns'; import * as util from '../common'; import { modelSelector } from '../constants'; +import { instrument } from '../instrumentation'; import { getCrashCallStacksChannel } from '../logger'; import { PlatformInformation } from '../platform'; import * as telemetry from '../telemetry'; @@ -222,7 +223,7 @@ export async function activate(): Promise { { scheme: 'file', language: 'cpp' }, { scheme: 'file', language: 'cuda-cpp' } ]; - codeActionProvider = vscode.languages.registerCodeActionsProvider(selector, { + codeActionProvider = vscode.languages.registerCodeActionsProvider(selector, instrument({ provideCodeActions: async (document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext): Promise => { if (!await clients.ActiveClient.getVcpkgEnabled()) { @@ -248,7 +249,7 @@ export async function activate(): Promise { const actions: vscode.CodeAction[] = ports.map(getVcpkgClipboardInstallAction); return actions; } - }); + })); await vscode.commands.executeCommand('setContext', 'cpptools.msvcEnvironmentFound', util.hasMsvcEnvironment()); @@ -1280,10 +1281,8 @@ async function handleCrashFileRead(crashDirectory: string, crashFile: string, cr if (crashCallStack !== prevCppCrashCallStackData) { prevCppCrashCallStackData = crashCallStack; - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); - if (lines.length >= 6 && util.getNumericLoggingLevel(settings.get("loggingLevel")) >= 1) { - const out: vscode.OutputChannel = getCrashCallStacksChannel(); - out.appendLine(`\n${isCppToolsSrv ? "cpptools-srv" : "cpptools"}\n${crashDate.toLocaleString()}\n${signalType}${crashCallStack}`); + if (lines.length >= 6 && util.getLoggingLevel() >= 1) { + getCrashCallStacksChannel().appendLine(`\n${isCppToolsSrv ? "cpptools-srv" : "cpptools"}\n${crashDate.toLocaleString()}\n${signalType}${crashCallStack}`); } } diff --git a/Extension/src/common.ts b/Extension/src/common.ts index e4ae353b0..8817767b1 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -759,10 +759,7 @@ export interface ProcessReturnType { export async function spawnChildProcess(program: string, args: string[] = [], continueOn?: string, skipLogging?: boolean, cancellationToken?: vscode.CancellationToken): Promise { // Do not use CppSettings to avoid circular require() if (skipLogging === undefined || !skipLogging) { - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); - if (getNumericLoggingLevel(settings.get("loggingLevel")) >= 5) { - getOutputChannelLogger().appendLine(`$ ${program} ${args.join(' ')}`); - } + getOutputChannelLogger().appendLineAtLevel(5, `$ ${program} ${args.join(' ')}`); } const programOutput: ProcessOutput = await spawnChildProcessImpl(program, args, continueOn, skipLogging, cancellationToken); const exitCode: number | NodeJS.Signals | undefined = programOutput.exitCode; @@ -789,10 +786,6 @@ interface ProcessOutput { async function spawnChildProcessImpl(program: string, args: string[], continueOn?: string, skipLogging?: boolean, cancellationToken?: vscode.CancellationToken): Promise { const result = new ManualPromise(); - // Do not use CppSettings to avoid circular require() - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); - const loggingLevel: number = (skipLogging === undefined || !skipLogging) ? getNumericLoggingLevel(settings.get("loggingLevel")) : 0; - let proc: child_process.ChildProcess; if (await isExecutable(program)) { proc = child_process.spawn(`.${isWindows ? '\\' : '/'}${path.basename(program)}`, args, { shell: true, cwd: path.dirname(program) }); @@ -817,8 +810,8 @@ async function spawnChildProcessImpl(program: string, args: string[], continueOn if (proc.stdout) { proc.stdout.on('data', data => { const str: string = data.toString(); - if (loggingLevel > 0) { - getOutputChannelLogger().append(str); + if (skipLogging === undefined || !skipLogging) { + getOutputChannelLogger().appendAtLevel(1, str); } stdout += str; if (continueOn) { @@ -1576,6 +1569,10 @@ function isIntegral(str: string): boolean { return regex.test(str); } +export function getLoggingLevel() { + return getNumericLoggingLevel(vscode.workspace.getConfiguration("C_Cpp", null).get("loggingLevel")); +} + export function getNumericLoggingLevel(loggingLevel: string | undefined): number { if (!loggingLevel) { return 1; diff --git a/Extension/src/cppTools.ts b/Extension/src/cppTools.ts index ca6bb9861..21152f8bc 100644 --- a/Extension/src/cppTools.ts +++ b/Extension/src/cppTools.ts @@ -9,9 +9,7 @@ import { CppToolsTestApi, CppToolsTestHook } from 'vscode-cpptools/out/testApi'; import * as nls from 'vscode-nls'; import { CustomConfigurationProvider1, CustomConfigurationProviderCollection, getCustomConfigProviders } from './LanguageServer/customProviders'; import * as LanguageServer from './LanguageServer/extension'; -import { CppSettings } from './LanguageServer/settings'; -import { getNumericLoggingLevel } from './common'; -import { getOutputChannel } from './logger'; +import { getOutputChannelLogger } from './logger'; import * as test from './testHook'; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); @@ -61,10 +59,7 @@ export class CppTools implements CppToolsTestApi { if (providers.add(provider, this.version)) { const added: CustomConfigurationProvider1 | undefined = providers.get(provider); if (added) { - const settings: CppSettings = new CppSettings(); - if (getNumericLoggingLevel(settings.loggingLevel) >= 5) { - getOutputChannel().appendLine(localize("provider.registered", "Custom configuration provider '{0}' registered", added.name)); - } + getOutputChannelLogger().appendLineAtLevel(5, localize("provider.registered", "Custom configuration provider '{0}' registered", added.name)); this.providers.push(added); LanguageServer.getClients().forEach(client => void client.onRegisterCustomConfigurationProvider(added)); this.addNotifyReadyTimer(added); diff --git a/Extension/src/instrumentation.ts b/Extension/src/instrumentation.ts new file mode 100644 index 000000000..360d903eb --- /dev/null +++ b/Extension/src/instrumentation.ts @@ -0,0 +1,54 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +/* eslint @typescript-eslint/no-var-requires: "off" */ + +export interface PerfMessage | undefined> { + /** this is the 'name' of the event */ + name: string; + + /** this indicates the context or origin of the message */ + context: Record>; + + /** if the message contains complex data, it should be in here */ + data?: TInput; + + /** if the message is just a text message, this is the contents of the message */ + text?: string; + + /** the message can have a numeric value that indicates the 'level' or 'severity' etc */ + level?: number; +} + +const services = { + instrument: >(instance: T, _options?: { ignore?: string[]; name?: string }): T => instance, + message: (_message: PerfMessage) => { }, + init: (_vscode: any) => { } +}; + +/** Adds instrumentation to all the members of an object when instrumentation is enabled */ +export function instrument>(instance: T, options?: { ignore?: string[]; name?: string }): T { + return services.instrument(instance, options); +} + +/** sends a perf message to the monitor */ +export function sendInstrumentation(message: PerfMessage): void { + services.message(message); +} + +/** verifies that the instrumentation is loaded into the global namespace */ +export const isInstrumentationEnabled = !!(global as any).instrumentation; + +// If the instrumentation is enabled, then ensure the functions are wired up. +if (isInstrumentationEnabled) { + // Instrumentation services provided by the tool. + services.instrument = (global as any).instrumentation.instrument; + services.message = (global as any).instrumentation.message; + services.init = (global as any).instrumentation.init; + + // Passes the specific vscode object for this extension to the init routine. + services.init(require('vscode')); +} diff --git a/Extension/src/logger.ts b/Extension/src/logger.ts index 4dba0e1d3..d14bb531f 100644 --- a/Extension/src/logger.ts +++ b/Extension/src/logger.ts @@ -7,7 +7,8 @@ import * as os from 'os'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import { getNumericLoggingLevel } from './common'; +import { getLoggingLevel } from './common'; +import { sendInstrumentation } from './instrumentation'; import { CppSourceStr } from './LanguageServer/extension'; import { getLocalizedString, LocalizeStringParams } from './LanguageServer/localization'; @@ -27,18 +28,26 @@ export class Logger { this.writer = writer; } - public append(message: string): void { - this.writer(message); - if (Subscriber) { - Subscriber(message); + public appendAtLevel(level: number, message: string): void { + if (getLoggingLevel() >= level) { + this.writer(message); + if (Subscriber) { + Subscriber(message); + } } + sendInstrumentation({ name: 'log', text: message, context: { channel: 'log', source: 'extension' }, level }); + } + + public append(message: string): void { + this.appendAtLevel(0, message); + } + + public appendLineAtLevel(level: number, message: string): void { + this.appendAtLevel(level, message + os.EOL); } public appendLine(message: string): void { - this.writer(message + os.EOL); - if (Subscriber) { - Subscriber(message + os.EOL); - } + this.appendAtLevel(0, message + os.EOL); } // We should not await on this function. @@ -83,9 +92,8 @@ export function getOutputChannel(): vscode.OutputChannel { if (!outputChannel) { outputChannel = vscode.window.createOutputChannel(CppSourceStr); // Do not use CppSettings to avoid circular require() - const settings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", null); - const loggingLevel: string | undefined = settings.get("loggingLevel"); - if (getNumericLoggingLevel(loggingLevel) > 1) { + const loggingLevel = getLoggingLevel(); + if (loggingLevel > 1) { outputChannel.appendLine(`loggingLevel: ${loggingLevel}`); } } @@ -130,10 +138,7 @@ export function getOutputChannelLogger(): Logger { } export function log(output: string): void { - if (!outputChannel) { - outputChannel = getOutputChannel(); - } - outputChannel.appendLine(`${output}`); + getOutputChannel().appendLine(`${output}`); } export interface DebugProtocolParams { diff --git a/Extension/src/main.ts b/Extension/src/main.ts index f8d5bcd33..eb577599c 100644 --- a/Extension/src/main.ts +++ b/Extension/src/main.ts @@ -23,6 +23,7 @@ import { CppSettings } from './LanguageServer/settings'; import { logAndReturn, returns } from './Utility/Async/returns'; import { CppTools1 } from './cppTools1'; import { logMachineIdMappings } from './id'; +import { instrument, sendInstrumentation } from './instrumentation'; import { disposeOutputChannels, log } from './logger'; import { PlatformInformation } from './platform'; @@ -35,6 +36,11 @@ let reloadMessageShown: boolean = false; const disposables: vscode.Disposable[] = []; export async function activate(context: vscode.ExtensionContext): Promise { + sendInstrumentation({ + name: "activate", + context: { cpptools: '', start: '' } + }); + util.setExtensionContext(context); Telemetry.activate(); util.setProgress(0); @@ -54,7 +60,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { if (event.execution.task.definition.type === CppBuildTaskProvider.CppBuildScriptType @@ -152,6 +158,11 @@ export async function activate(context: vscode.ExtensionContext): Promise