Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
75a4d43
define metric within VSC telemetry
Hweinstock Jan 16, 2025
191e0ea
implement telemetry for fetching stage
Hweinstock Jan 16, 2025
0b4cec4
lift telemetry logic to top level
Hweinstock Jan 16, 2025
3727ea9
add telemetry for remaining stages
Hweinstock Jan 16, 2025
5d5a7d8
seperate verification and downloading
Hweinstock Jan 17, 2025
a7b446d
add telemetry for validation step
Hweinstock Jan 17, 2025
3304e99
rename metrics and types
Hweinstock Jan 17, 2025
45b0236
reduce duplication across stage implementations
Hweinstock Jan 17, 2025
c2596cf
undo unnecessary change
Hweinstock Jan 17, 2025
4f2b662
Merge branch 'feature/amazonqLSP' into telemetry/setup
Hweinstock Jan 21, 2025
6ef6633
switch to fallback instead of cache when remote option fails
Hweinstock Jan 21, 2025
3b3b39b
fix telemetry metadata field
Hweinstock Jan 23, 2025
abdb5fd
port metrics down from commons until release
Hweinstock Jan 23, 2025
0aadddf
implement logic for manifest telemetry
Hweinstock Jan 23, 2025
f03bf8d
Merge branch 'feature/amazonqLSP' into telemetry/setup
Hweinstock Jan 24, 2025
7d05c68
telemetry: implement logic for server resolution
Hweinstock Jan 24, 2025
c7b97e2
refactor: move tryFunctions to core and include tests
Hweinstock Jan 24, 2025
5284763
telemtry: implement telemetry for amazonQLSP
Hweinstock Jan 24, 2025
7dfefe6
test: implement tests for manifestResolver
Hweinstock Jan 24, 2025
bad6697
Merge branch 'feature/amazonqLSP' into telemetry/setup
Hweinstock Jan 24, 2025
4b1e90a
refactor: simplify lspResolver implementation
Hweinstock Jan 24, 2025
45901e7
test: add tests for lsp resolver (WIP)
Hweinstock Jan 24, 2025
2ee4792
test: add tests for lspResolver
Hweinstock Jan 27, 2025
3a6bad2
test: remove duplicate data in tests
Hweinstock Jan 27, 2025
9e708fd
refactor: move telemetry work to more general location
Hweinstock Jan 27, 2025
0b4da9d
fix: change outdated reference
Hweinstock Jan 27, 2025
c37ef4a
merge: bring in HttpResourceFetcher changes
Hweinstock Jan 27, 2025
9cee3e7
refactor: rename stage -> setupStage
Hweinstock Jan 27, 2025
05f3a2b
fix: add workaround for windows tests
Hweinstock Jan 27, 2025
80fa5d6
test: disable lspResolver test on nonmac, add techdebt test
Hweinstock Jan 27, 2025
8ebcc97
jscpd: ignore verbose telemetry checks
Hweinstock Jan 27, 2025
3cf254a
refactor: avoid duplicate import line
Hweinstock Jan 27, 2025
2c60608
merge: handle tsUtils conflicts
Hweinstock Jan 28, 2025
9b9443b
deps: bump telemetry version and remove local changes
Hweinstock Jan 28, 2025
077e402
refactor: cleanup minor details
Hweinstock Jan 28, 2025
0ef9f0d
merge: resolve conflicts
Hweinstock Jan 29, 2025
b0a37f3
refactor: fix spelling mistake
Hweinstock Feb 3, 2025
42ed745
fix: remove accidental space
Hweinstock Feb 3, 2025
ea4e35a
Merge branch 'feature/amazonqLSP' into telemetry/setup
Hweinstock Feb 3, 2025
d286070
test: add telemetry to e2e tests
Hweinstock Feb 4, 2025
3888ac5
test: adjust telemetry to mirror CI reality
Hweinstock Feb 4, 2025
4819d49
test: adjust results to mirror ci
Hweinstock Feb 4, 2025
3aa2670
test: delete duplicate coverage test case
Hweinstock Feb 5, 2025
e55dd7f
test: avoid bleedthrough of globalstate
Hweinstock Feb 5, 2025
8bc7ccc
test: ensure consistent global storage state to ensure consistent beh…
Hweinstock Feb 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 89 additions & 84 deletions packages/core/src/amazonq/lsp/lspClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
import { Writable } from 'stream'
import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings'
import { ResourcePaths, fs, getLogger, globals } from '../../shared'
import { telemetry } from '../../shared/telemetry'

const localize = nls.loadMessageBundle()

Expand Down Expand Up @@ -173,104 +174,108 @@ export class LspClient {
* This function assumes the LSP server has already been downloaded.
*/
export async function activate(extensionContext: ExtensionContext, resourcePaths: ResourcePaths) {
LspClient.instance
const toDispose = extensionContext.subscriptions
return await telemetry.lsp_setup.run(async (span) => {
span.record({ lspSetupStage: 'activate' })
const startTime = performance.now()
LspClient.instance
const toDispose = extensionContext.subscriptions

let rangeFormatting: Disposable | undefined
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
const debugOptions = { execArgv: ['--nolazy', '--preserve-symlinks', '--stdio'] }
let rangeFormatting: Disposable | undefined
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
const debugOptions = { execArgv: ['--nolazy', '--preserve-symlinks', '--stdio'] }

const workerThreads = CodeWhispererSettings.instance.getIndexWorkerThreads()
const gpu = CodeWhispererSettings.instance.isLocalIndexGPUEnabled()
const workerThreads = CodeWhispererSettings.instance.getIndexWorkerThreads()
const gpu = CodeWhispererSettings.instance.isLocalIndexGPUEnabled()

if (gpu) {
process.env.Q_ENABLE_GPU = 'true'
} else {
delete process.env.Q_ENABLE_GPU
}
if (workerThreads > 0 && workerThreads < 100) {
process.env.Q_WORKER_THREADS = workerThreads.toString()
} else {
delete process.env.Q_WORKER_THREADS
}
if (gpu) {
process.env.Q_ENABLE_GPU = 'true'
} else {
delete process.env.Q_ENABLE_GPU
}
if (workerThreads > 0 && workerThreads < 100) {
process.env.Q_WORKER_THREADS = workerThreads.toString()
} else {
delete process.env.Q_WORKER_THREADS
}

const serverModule = resourcePaths.lsp
const serverModule = resourcePaths.lsp

const child = spawn(resourcePaths.node, [serverModule, ...debugOptions.execArgv])
// share an encryption key using stdin
// follow same practice of DEXP LSP server
writeEncryptionInit(child.stdin)
const child = spawn(resourcePaths.node, [serverModule, ...debugOptions.execArgv])
// share an encryption key using stdin
// follow same practice of DEXP LSP server
writeEncryptionInit(child.stdin)

// If the extension is launch in debug mode the debug server options are use
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
}
// If the extension is launch in debug mode the debug server options are use
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
}

serverOptions = () => Promise.resolve(child!)
serverOptions = () => Promise.resolve(child!)

const documentSelector = [{ scheme: 'file', language: '*' }]
const documentSelector = [{ scheme: 'file', language: '*' }]

// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
documentSelector,
initializationOptions: {
handledSchemaProtocols: ['file', 'untitled'], // language server only loads file-URI. Fetching schemas with other protocols ('http'...) are made on the client.
provideFormatter: false, // tell the server to not provide formatting capability and ignore the `aws.stepfunctions.asl.format.enable` setting.
// this is used by LSP to determine index cache path, move to this folder so that when extension updates index is not deleted.
extensionPath: path.join(fs.getUserHomeDir(), '.aws', 'amazonq', 'cache'),
},
// Log to the Amazon Q Logs so everything is in a single channel
// TODO: Add prefix to the language server logs so it is easier to search
outputChannel: globals.logOutputChannel,
}
// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
documentSelector,
initializationOptions: {
handledSchemaProtocols: ['file', 'untitled'], // language server only loads file-URI. Fetching schemas with other protocols ('http'...) are made on the client.
provideFormatter: false, // tell the server to not provide formatting capability and ignore the `aws.stepfunctions.asl.format.enable` setting.
// this is used by LSP to determine index cache path, move to this folder so that when extension updates index is not deleted.
extensionPath: path.join(fs.getUserHomeDir(), '.aws', 'amazonq', 'cache'),
},
// Log to the Amazon Q Logs so everything is in a single channel
// TODO: Add prefix to the language server logs so it is easier to search
outputChannel: globals.logOutputChannel,
}

// Create the language client and start the client.
LspClient.instance.client = new LanguageClient(
'amazonq',
localize('amazonq.server.name', 'Amazon Q Language Server'),
serverOptions,
clientOptions
)
LspClient.instance.client.registerProposedFeatures()
// Create the language client and start the client.
LspClient.instance.client = new LanguageClient(
'amazonq',
localize('amazonq.server.name', 'Amazon Q Language Server'),
serverOptions,
clientOptions
)
LspClient.instance.client.registerProposedFeatures()

const disposable = LspClient.instance.client.start()
toDispose.push(disposable)
const disposable = LspClient.instance.client.start()
toDispose.push(disposable)

let savedDocument: vscode.Uri | undefined = undefined
let savedDocument: vscode.Uri | undefined = undefined

toDispose.push(
vscode.workspace.onDidSaveTextDocument((document) => {
if (document.uri.scheme !== 'file') {
return
}
savedDocument = document.uri
}),
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (savedDocument && editor && editor.document.uri.fsPath !== savedDocument.fsPath) {
void LspClient.instance.updateIndex([savedDocument.fsPath], 'update')
}
}),
vscode.workspace.onDidCreateFiles((e) => {
void LspClient.instance.updateIndex(
e.files.map((f) => f.fsPath),
'add'
)
}),
vscode.workspace.onDidDeleteFiles((e) => {
void LspClient.instance.updateIndex(
e.files.map((f) => f.fsPath),
'remove'
)
toDispose.push(
vscode.workspace.onDidSaveTextDocument((document) => {
if (document.uri.scheme !== 'file') {
return
}
savedDocument = document.uri
}),
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (savedDocument && editor && editor.document.uri.fsPath !== savedDocument.fsPath) {
void LspClient.instance.updateIndex([savedDocument.fsPath], 'update')
}
}),
vscode.workspace.onDidCreateFiles((e) => {
void LspClient.instance.updateIndex(
e.files.map((f) => f.fsPath),
'add'
)
}),
vscode.workspace.onDidDeleteFiles((e) => {
void LspClient.instance.updateIndex(
e.files.map((f) => f.fsPath),
'remove'
)
})
)
void LspClient.instance.client.onReady().then(() => {
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
toDispose.push(disposableFunc)
})
)

return LspClient.instance.client.onReady().then(() => {
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
toDispose.push(disposableFunc)
span.record({ duration: performance.now() - startTime })
})
}

Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/amazonq/lsp/lspController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,14 @@ export class LspController {
}
setImmediate(async () => {
try {
const installResult = await new WorkspaceLSPResolver().resolve()
await activateLsp(context, installResult.resourcePaths)
getLogger().info('LspController: LSP activated')
await telemetry.lsp_setup.run(async (span) => {
const startTime = performance.now()
span.record({ lspSetupStage: 'final' })
const installResult = await new WorkspaceLSPResolver().resolve()
await activateLsp(context, installResult.resourcePaths)
getLogger().info('LspController: LSP activated')
span.record({ duration: performance.now() - startTime })
})
void LspController.instance.buildIndex(buildIndexConfig)
// log the LSP server CPU and Memory usage per 30 minutes.
globals.clock.setInterval(
Expand Down
32 changes: 26 additions & 6 deletions packages/core/src/amazonq/lsp/workspaceInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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'

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
Expand All @@ -18,12 +19,31 @@ const supportedLspServerVersions = '0.1.32'
export class WorkspaceLSPResolver implements LspResolver {
async resolve(): Promise<LspResolution> {
const name = 'AmazonQ-Workspace'
const manifest = await new ManifestResolver(manifestUrl, name).resolve()
const installationResult = await new LanguageServerResolver(
manifest,
name,
new Range(supportedLspServerVersions)
).resolve()
const manifest = await telemetry.lsp_setup.run(async (span) => {
const startTime = performance.now()
span.record({ lspSetupStage: 'fetchManifest' })
const result = await new ManifestResolver(manifestUrl, name).resolve()
span.record({
lspSetupLocation: result.location ?? 'unknown',
duration: performance.now() - startTime,
})
return result
})

const installationResult = await telemetry.lsp_setup.run(async (span) => {
const startTime = performance.now()
span.record({ lspSetupStage: 'serverCall' })
const result = await new LanguageServerResolver(
manifest,
name,
new Range(supportedLspServerVersions)
).resolve()
span.record({
lspSetupLocation: result.location ?? 'unknown',
duration: performance.now() - startTime,
})
return result
})

const nodeName =
process.platform === 'win32' ? getNodeExecutableName() : `node-${process.platform}-${process.arch}`
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/shared/lsp/manifestResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ManifestResolver {
const manifest = this.parseManifest(resp.content)
await this.saveManifest(resp.eTag, resp.content)
this.checkDeprecation(manifest)

manifest.location = 'remote'
return manifest
}

Expand All @@ -70,6 +70,7 @@ export class ManifestResolver {

const manifest = this.parseManifest(manifestData.content)
this.checkDeprecation(manifest)
manifest.location = 'cache'
return manifest
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/shared/lsp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface Manifest {
artifactDescription: string
isManifestDeprecated: boolean
versions: LspVersion[]
location?: Location
}

export interface VersionRange {
Expand Down
34 changes: 34 additions & 0 deletions packages/core/src/shared/telemetry/vscodeTelemetry.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
{
"types": [
{
"name": "lspSetupStage",
"type": "string",
"allowedValues": ["fetchManifest", "serverCall", "validate", "activate", "final"],
"description": "The stage of the LSP setup process"
},
{
"name": "lspSetupLocation",
"type": "string",
"allowedValues": ["cache", "remote", "fallback", "override", "unknown"],
"description": "The location of the LSP server"
},
{
"name": "amazonGenerateApproachLatency",
"type": "double",
Expand Down Expand Up @@ -358,6 +370,28 @@
}
],
"metrics": [
{
"name": "lsp_setup",
"description": "LSP setup event",
"metadata": [
{
"type": "lspSetupStage",
"required": true
},
{
"type": "lspSetupLocation",
"required": false
},
{
"type": "duration",
"required": true
},
{
"type": "result",
"required": true
}
]
},
{
"name": "ide_fileSystem",
"description": "File System event on execution",
Expand Down
Loading