diff --git a/packages/core/src/amazonq/lsp/lspController.ts b/packages/core/src/amazonq/lsp/lspController.ts index 74d13aec882..7356399b304 100644 --- a/packages/core/src/amazonq/lsp/lspController.ts +++ b/packages/core/src/amazonq/lsp/lspController.ts @@ -161,11 +161,7 @@ export class LspController { } setImmediate(async () => { try { - await lspSetupStage('final', async () => { - const installResult = await new WorkspaceLSPResolver().resolve() - await lspSetupStage('launch', async () => activateLsp(context, installResult.resourcePaths)) - getLogger().info('LspController: LSP activated') - }) + await this.setupLsp(context) void LspController.instance.buildIndex(buildIndexConfig) // log the LSP server CPU and Memory usage per 30 minutes. globals.clock.setInterval( @@ -186,4 +182,12 @@ export class LspController { } }) } + + private async setupLsp(context: vscode.ExtensionContext) { + await lspSetupStage('all', async () => { + const installResult = await new WorkspaceLSPResolver().resolve() + await lspSetupStage('launch', async () => activateLsp(context, installResult.resourcePaths)) + getLogger().info('LspController: LSP activated') + }) + } } diff --git a/packages/core/src/amazonq/lsp/util.ts b/packages/core/src/amazonq/lsp/util.ts index bfc24827aec..3829f643e79 100644 --- a/packages/core/src/amazonq/lsp/util.ts +++ b/packages/core/src/amazonq/lsp/util.ts @@ -3,14 +3,43 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { LanguageServerSetupStage, telemetry } from '../../shared/telemetry' +import { LanguageServerSetup, LanguageServerSetupStage, telemetry } from '../../shared/telemetry' -export async function lspSetupStage(stageName: LanguageServerSetupStage, stage: () => Promise) { +/** + * Runs the designated stage within a telemetry span and optionally uses the getMetadata extractor to record metadata from the result of the stage. + * @param stageName name of stage for telemetry. + * @param runStage stage to be run. + * @param getMetadata metadata extracter to be applied to result. + * @returns result of stage + */ +export async function lspSetupStage( + stageName: LanguageServerSetupStage, + runStage: () => Promise, + getMetadata?: (result: T) => Partial +) { return await telemetry.languageServer_setup.run(async (span) => { - const startTime = performance.now() - const result = await stage() + const result = await runStage() span.record({ languageServerSetupStage: stageName }) - span.record({ duration: performance.now() - startTime }) + if (getMetadata) { + span.record(getMetadata(result)) + } return result }) } + +/** + * Try Functions in the order presented and return the first returned result. If none return, throw the final error. + * @param functions non-empty list of functions to try. + * @returns + */ +export async function tryFunctions(functions: (() => Promise)[]): Promise { + let currentError: Error = new Error('No functions provided') + for (const func of functions) { + try { + return await func() + } catch (e) { + currentError = e as Error + } + } + throw currentError +} diff --git a/packages/core/src/amazonq/lsp/workspaceInstaller.ts b/packages/core/src/amazonq/lsp/workspaceInstaller.ts index 9cca1603551..6c5d869a70a 100644 --- a/packages/core/src/amazonq/lsp/workspaceInstaller.ts +++ b/packages/core/src/amazonq/lsp/workspaceInstaller.ts @@ -10,8 +10,6 @@ import { LanguageServerResolver } from '../../shared/lsp/lspResolver' import { Range } from 'semver' import { getNodeExecutableName } from '../../shared/lsp/utils/platform' import { fs } from '../../shared/fs/fs' -import { telemetry } from '../../shared/telemetry' -import { lspSetupStage } from './util' const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json' // this LSP client in Q extension is only going to work with these LSP server versions @@ -20,28 +18,12 @@ const supportedLspServerVersions = '0.1.32' export class WorkspaceLSPResolver implements LspResolver { async resolve(): Promise { const name = 'AmazonQ-Workspace' - const manifest = await lspSetupStage('getManifest', async () => { - const result = await new ManifestResolver(manifestUrl, name).resolve() - telemetry.record({ - manifestVersion: result.manifestSchemaVersion, - languageServerResourceLocation: result.location ?? 'unknown', - }) - return result - }) - telemetry.record({ - manifestVersion: manifest.manifestSchemaVersion, - }) - const installationResult = await lspSetupStage('getServer', async () => { - const result = await new LanguageServerResolver( - manifest, - name, - new Range(supportedLspServerVersions) - ).resolve() - telemetry.record({ - languageServerResourceLocation: result.location ?? 'unknown', - }) - return result - }) + const manifest = await new ManifestResolver(manifestUrl, name).resolve() + const installationResult = await new LanguageServerResolver( + manifest, + name, + new Range(supportedLspServerVersions) + ).resolve() const nodeName = process.platform === 'win32' ? getNodeExecutableName() : `node-${process.platform}-${process.arch}` diff --git a/packages/core/src/shared/lsp/manifestResolver.ts b/packages/core/src/shared/lsp/manifestResolver.ts index 07ea3b9ace2..f3a0a3cfcda 100644 --- a/packages/core/src/shared/lsp/manifestResolver.ts +++ b/packages/core/src/shared/lsp/manifestResolver.ts @@ -9,6 +9,7 @@ import { RetryableResourceFetcher } from '../resourcefetcher/httpResourceFetcher import { Timeout } from '../utilities/timeoutUtils' import globals from '../extensionGlobals' import { Manifest } from './types' +import { lspSetupStage, tryFunctions } from '../../amazonq/lsp/util' const logger = getLogger('lsp') @@ -32,10 +33,17 @@ export class ManifestResolver { * Fetches the latest manifest, falling back to local cache on failure */ async resolve(): Promise { - try { - return await this.fetchRemoteManifest() - } catch (error) { - return await this.getLocalManifest() + return await tryFunctions([ + async () => await resolveManifestWith(async () => await this.fetchRemoteManifest()), + async () => await resolveManifestWith(async () => await this.getLocalManifest()), + ]) + + async function resolveManifestWith(resolver: () => Promise) { + return await lspSetupStage('getManifest', async () => await resolver(), extractVersionFromResult) + } + + function extractVersionFromResult(r: Manifest) { + return { manifestSchemaVersion: r.manifestSchemaVersion } } }