Skip to content

Commit 633b05b

Browse files
authored
telemetry(lsp): Integrate language server/manifest resolver telemetry (#6385)
## Problem LSP downloading processes does not emit any telemetry. ## Solution ### Refactoring - separate verification from core downloading steps so that we can capture it as its own telemetry event. - Note: this refactor means that if all the downloaded content cannot be verified, no files will be written to disk. - Introduce abstraction of `StageResolver` to make telemetry instrumentation more natural. ### Metric Behavior - metric for each stage of the LSP setup process. - can be emitted multiple times per stage to capture specific error codes. See tests for examples. - Commons repo PR: aws/aws-toolkit-common#961 - bumped commons version to [1.0.296](aws/aws-toolkit-common@8df7a87) to include this change. ### Testing - adds basic tests for `ManifestResolver` in `packages/core/src/test/shared/lsp/manifestResolver.test.ts`. - adds basic tests for `LanguageServerResolver` in `packages/core/src/test/shared/lsp/lspResolver.test.ts`. - Currently `LanguageServerResolver` only supports mac, so its tests skip on non-mac platforms. There is a techdebt test to address this. ## Future Work - Update logging messages to be LSP specific. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 119ed30 commit 633b05b

File tree

14 files changed

+459
-80
lines changed

14 files changed

+459
-80
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"mergeReports": "ts-node ./scripts/mergeReports.ts"
4141
},
4242
"devDependencies": {
43-
"@aws-toolkits/telemetry": "^1.0.295",
43+
"@aws-toolkits/telemetry": "^1.0.296",
4444
"@playwright/browser-chromium": "^1.43.1",
4545
"@stylistic/eslint-plugin": "^2.11.0",
4646
"@types/he": "^1.2.3",

packages/amazonq/src/lsp/activation.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import vscode from 'vscode'
77
import { startLanguageServer } from './client'
88
import { AmazonQLSPResolver } from './lspInstaller'
9-
import { Commands, ToolkitError } from 'aws-core-vscode/shared'
9+
import { Commands, lspSetupStage, ToolkitError } from 'aws-core-vscode/shared'
1010

1111
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
1212
try {
13-
const installResult = await new AmazonQLSPResolver().resolve()
14-
await startLanguageServer(ctx, installResult.resourcePaths)
13+
await lspSetupStage('all', async () => {
14+
const installResult = await new AmazonQLSPResolver().resolve()
15+
await lspSetupStage('launch', async () => await startLanguageServer(ctx, installResult.resourcePaths))
16+
})
1517
ctx.subscriptions.push(
1618
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
1719
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')

packages/amazonq/test/e2e/lsp/lspInstaller.test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ import sinon from 'sinon'
88
import { AmazonQLSPResolver, supportedLspServerVersions } from '../../../src/lsp/lspInstaller'
99
import {
1010
fs,
11+
globals,
1112
LanguageServerResolver,
1213
makeTemporaryToolkitFolder,
1314
ManifestResolver,
15+
manifestStorageKey,
1416
request,
1517
} from 'aws-core-vscode/shared'
1618
import * as semver from 'semver'
19+
import { assertTelemetry } from 'aws-core-vscode/test'
20+
import { LspController } from 'aws-core-vscode/amazonq'
21+
import { LanguageServerSetup } from 'aws-core-vscode/telemetry'
1722

1823
function createVersion(version: string) {
1924
return {
@@ -40,12 +45,22 @@ describe('AmazonQLSPInstaller', () => {
4045
let resolver: AmazonQLSPResolver
4146
let sandbox: sinon.SinonSandbox
4247
let tempDir: string
48+
// If globalState contains an ETag that is up to date with remote, we won't fetch it resulting in inconsistent behavior.
49+
// Therefore, we clear it temporarily for these tests to ensure consistent behavior.
50+
let manifestStorage: { [key: string]: any }
51+
52+
before(async () => {
53+
manifestStorage = globals.globalState.get(manifestStorageKey) || {}
54+
})
4355

4456
beforeEach(async () => {
4557
sandbox = sinon.createSandbox()
4658
resolver = new AmazonQLSPResolver()
4759
tempDir = await makeTemporaryToolkitFolder()
4860
sandbox.stub(LanguageServerResolver.prototype, 'defaultDownloadFolder').returns(tempDir)
61+
// Called on extension activation and can contaminate telemetry.
62+
sandbox.stub(LspController.prototype, 'trySetupLsp')
63+
await globals.globalState.update(manifestStorageKey, {})
4964
})
5065

5166
afterEach(async () => {
@@ -56,6 +71,10 @@ describe('AmazonQLSPInstaller', () => {
5671
})
5772
})
5873

74+
after(async () => {
75+
await globals.globalState.update(manifestStorageKey, manifestStorage)
76+
})
77+
5978
describe('resolve()', () => {
6079
it('uses AWS_LANGUAGE_SERVER_OVERRIDE', async () => {
6180
const overridePath = '/custom/path/to/lsp'
@@ -117,6 +136,94 @@ describe('AmazonQLSPInstaller', () => {
117136
assert.ok(fallback.assetDirectory.startsWith(tempDir))
118137
assert.deepStrictEqual(fallback.location, 'fallback')
119138
assert.ok(semver.satisfies(fallback.version, supportedLspServerVersions))
139+
140+
/* First Try Telemetry
141+
getManifest: remote succeeds
142+
getServer: cache fails then remote succeeds.
143+
validate: succeeds.
144+
*/
145+
const firstTryTelemetry: Partial<LanguageServerSetup>[] = [
146+
{
147+
id: 'AmazonQ',
148+
manifestLocation: 'remote',
149+
languageServerSetupStage: 'getManifest',
150+
result: 'Succeeded',
151+
},
152+
{
153+
id: 'AmazonQ',
154+
languageServerLocation: 'cache',
155+
languageServerSetupStage: 'getServer',
156+
result: 'Failed',
157+
},
158+
{
159+
id: 'AmazonQ',
160+
languageServerLocation: 'remote',
161+
languageServerSetupStage: 'validate',
162+
result: 'Succeeded',
163+
},
164+
{
165+
id: 'AmazonQ',
166+
languageServerLocation: 'remote',
167+
languageServerSetupStage: 'getServer',
168+
result: 'Succeeded',
169+
},
170+
]
171+
172+
/* Second Try Telemetry
173+
getManifest: remote fails, then cache succeeds.
174+
getServer: cache succeeds
175+
validate: doesn't run since its cached.
176+
*/
177+
const secondTryTelemetry: Partial<LanguageServerSetup>[] = [
178+
{
179+
id: 'AmazonQ',
180+
manifestLocation: 'remote',
181+
languageServerSetupStage: 'getManifest',
182+
result: 'Failed',
183+
},
184+
{
185+
id: 'AmazonQ',
186+
manifestLocation: 'cache',
187+
languageServerSetupStage: 'getManifest',
188+
result: 'Succeeded',
189+
},
190+
{
191+
id: 'AmazonQ',
192+
languageServerLocation: 'cache',
193+
languageServerSetupStage: 'getServer',
194+
result: 'Succeeded',
195+
},
196+
]
197+
198+
/* Third Try Telemetry
199+
getManifest: (stubbed to fail, no telemetry)
200+
getServer: remote and cache fail
201+
validate: no validation since not remote.
202+
*/
203+
const thirdTryTelemetry: Partial<LanguageServerSetup>[] = [
204+
{
205+
id: 'AmazonQ',
206+
languageServerLocation: 'cache',
207+
languageServerSetupStage: 'getServer',
208+
result: 'Failed',
209+
},
210+
{
211+
id: 'AmazonQ',
212+
languageServerLocation: 'remote',
213+
languageServerSetupStage: 'getServer',
214+
result: 'Failed',
215+
},
216+
{
217+
id: 'AmazonQ',
218+
languageServerLocation: 'fallback',
219+
languageServerSetupStage: 'getServer',
220+
result: 'Succeeded',
221+
},
222+
]
223+
224+
const expectedTelemetry = firstTryTelemetry.concat(secondTryTelemetry, thirdTryTelemetry)
225+
226+
assertTelemetry('languageServer_setup', expectedTelemetry)
120227
})
121228
})
122229
})

packages/core/package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"AWS.configuration.description.suppressPrompts": "Prompts which ask for confirmation. Checking an item suppresses the prompt.",
2121
"AWS.configuration.enableCodeLenses": "Enable SAM hints in source code and template.yaml files",
2222
"AWS.configuration.description.resources.enabledResources": "AWS resources to display in the 'Resources' portion of the explorer.",
23-
"AWS.configuration.description.featureDevelopment.allowRunningCodeAndTests": "Allow /dev to run code and test commands",
23+
"AWS.configuration.description.featureDevelopment.allowRunningCodeAndTests": "Allow /dev to run code and test commands",
2424
"AWS.configuration.description.experiments": "Try experimental features and give feedback. Note that experimental features may be removed at any time.\n * `jsonResourceModification` - Enables basic create, update, and delete support for cloud resources via the JSON Resources explorer component.",
2525
"AWS.stepFunctions.asl.format.enable.desc": "Enables the default formatter used with Amazon States Language files",
2626
"AWS.stepFunctions.asl.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).",

packages/core/src/amazonq/lsp/lspController.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { isCloud9 } from '../../shared/extensionUtilities'
1515
import globals, { isWeb } from '../../shared/extensionGlobals'
1616
import { isAmazonInternalOs } from '../../shared/vscode/env'
1717
import { WorkspaceLSPResolver } from './workspaceInstaller'
18+
import { lspSetupStage } from '../../shared'
1819

1920
export interface Chunk {
2021
readonly filePath: string
@@ -160,9 +161,7 @@ export class LspController {
160161
}
161162
setImmediate(async () => {
162163
try {
163-
const installResult = await new WorkspaceLSPResolver().resolve()
164-
await activateLsp(context, installResult.resourcePaths)
165-
getLogger().info('LspController: LSP activated')
164+
await this.setupLsp(context)
166165
void LspController.instance.buildIndex(buildIndexConfig)
167166
// log the LSP server CPU and Memory usage per 30 minutes.
168167
globals.clock.setInterval(
@@ -183,4 +182,12 @@ export class LspController {
183182
}
184183
})
185184
}
185+
186+
private async setupLsp(context: vscode.ExtensionContext) {
187+
await lspSetupStage('all', async () => {
188+
const installResult = await new WorkspaceLSPResolver().resolve()
189+
await lspSetupStage('launch', async () => activateLsp(context, installResult.resourcePaths))
190+
getLogger().info('LspController: LSP activated')
191+
})
192+
}
186193
}

packages/core/src/shared/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export { TabTypeDataMap } from '../amazonq/webview/ui/tabs/constants'
6565
export * from './lsp/manifestResolver'
6666
export * from './lsp/lspResolver'
6767
export * from './lsp/types'
68+
export * from './lsp/utils/setupStage'
6869
export * from './lsp/utils/cleanup'
6970
export { default as request } from './request'
7071
export * from './lsp/utils/platform'

0 commit comments

Comments
 (0)