-
Notifications
You must be signed in to change notification settings - Fork 749
telemetry(lsp): Integrate language server/manifest resolver telemetry #6385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 37 commits
75a4d43
191e0ea
0b4cec4
3727ea9
5d5a7d8
a7b446d
3304e99
45b0236
c2596cf
4f2b662
6ef6633
3b3b39b
abdb5fd
0aadddf
f03bf8d
7d05c68
c7b97e2
5284763
7dfefe6
bad6697
4b1e90a
45901e7
2ee4792
3a6bad2
9e708fd
0b4da9d
c37ef4a
9cee3e7
05f3a2b
80fa5d6
8ebcc97
3cf254a
2c60608
9b9443b
077e402
0ef9f0d
b0a37f3
42ed745
ea4e35a
d286070
3888ac5
4819d49
3aa2670
e55dd7f
8bc7ccc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ import { isCloud9 } from '../../shared/extensionUtilities' | |
| import globals, { isWeb } from '../../shared/extensionGlobals' | ||
| import { isAmazonInternalOs } from '../../shared/vscode/env' | ||
| import { WorkspaceLSPResolver } from './workspaceInstaller' | ||
| import { lspSetupStage } from '../../shared' | ||
|
|
||
| export interface Chunk { | ||
| readonly filePath: string | ||
|
|
@@ -160,9 +161,7 @@ export class LspController { | |
| } | ||
| setImmediate(async () => { | ||
| try { | ||
| const installResult = await new WorkspaceLSPResolver().resolve() | ||
| await 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( | ||
|
|
@@ -183,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') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessarily an issue for this PR, but I think we should probably scope these messages to regular codewhisperer language server vs workspace context one
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think we could make use of the logging header here that references which LSP it is. Can do as a follow up. |
||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ import AdmZip from 'adm-zip' | |
| import { TargetContent, logger, LspResult, LspVersion, Manifest } from './types' | ||
| import { getApplicationSupportFolder } from '../vscode/env' | ||
| import { createHash } from '../crypto' | ||
| import { lspSetupStage, StageResolver, tryStageResolvers } from './utils/setupStage' | ||
| import { HttpResourceFetcher } from '../resourcefetcher/httpResourceFetcher' | ||
|
|
||
| export class LanguageServerResolver { | ||
|
|
@@ -30,63 +31,90 @@ export class LanguageServerResolver { | |
| * @throws ToolkitError if no compatible version can be found | ||
| */ | ||
| async resolve() { | ||
| const result: LspResult = { | ||
| location: 'unknown', | ||
| version: '', | ||
| assetDirectory: '', | ||
| } | ||
|
|
||
| const latestVersion = this.latestCompatibleLspVersion() | ||
| const targetContents = this.getLSPTargetContents(latestVersion) | ||
| const cacheDirectory = this.getDownloadDirectory(latestVersion.serverVersion) | ||
|
|
||
| if (await this.hasValidLocalCache(cacheDirectory, targetContents)) { | ||
| result.location = 'cache' | ||
| result.version = latestVersion.serverVersion | ||
| result.assetDirectory = cacheDirectory | ||
| return result | ||
| } else { | ||
| // Delete the cached directory since it's invalid | ||
| if (await fs.existsDir(cacheDirectory)) { | ||
| await fs.delete(cacheDirectory, { | ||
| recursive: true, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| if (await this.downloadRemoteTargetContent(targetContents, latestVersion.serverVersion)) { | ||
| result.location = 'remote' | ||
| result.version = latestVersion.serverVersion | ||
| result.assetDirectory = cacheDirectory | ||
| return result | ||
| } else { | ||
| // clean up any leftover content that may have been downloaded | ||
| if (await fs.existsDir(cacheDirectory)) { | ||
| await fs.delete(cacheDirectory, { | ||
| recursive: true, | ||
| }) | ||
| const serverResolvers: StageResolver<LspResult>[] = [ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not neccessarily a problem but just wanted to call it out for anyone reading -- this is where we will slightly deviate from eclipse/visual studios implementations. We can just do some things a bit simpler
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what ways does this deviate from their implementations? Would it be easier if I modified the implementation to closer mirror those implementations?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just the structure of having resolvers. It's not a big deal. I just copied Visual Studios implementation and basically ported it to typescript. AFAIK Eclipse ported Visual Studio's implementation for this code to java. This is just a slight refactor of their work |
||
| { | ||
| resolve: async () => await this.getLocalServer(cacheDirectory, latestVersion, targetContents), | ||
| telemetryMetadata: { id: this.lsName, languageServerLocation: 'cache' }, | ||
| }, | ||
| { | ||
| resolve: async () => await this.fetchRemoteServer(cacheDirectory, latestVersion, targetContents), | ||
| telemetryMetadata: { id: this.lsName, languageServerLocation: 'remote' }, | ||
| }, | ||
| { | ||
| resolve: async () => await this.getFallbackServer(latestVersion), | ||
| telemetryMetadata: { id: this.lsName, languageServerLocation: 'fallback' }, | ||
| }, | ||
| ] | ||
|
|
||
| return await tryStageResolvers('getServer', serverResolvers, getServerVersion) | ||
|
|
||
| function getServerVersion(result: LspResult) { | ||
| return { | ||
| languageServerVersion: result.version, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| logger.info( | ||
| `Unable to download language server version ${latestVersion.serverVersion}. Attempting to fetch from fallback location` | ||
| ) | ||
|
|
||
| private async getFallbackServer(latestVersion: LspVersion): Promise<LspResult> { | ||
| const fallbackDirectory = await this.getFallbackDir(latestVersion.serverVersion) | ||
| if (!fallbackDirectory) { | ||
| throw new ToolkitError('Unable to find a compatible version of the Language Server') | ||
| throw new ToolkitError('Unable to find a compatible version of the Language Server', { | ||
| code: 'IncompatibleVersion', | ||
| }) | ||
| } | ||
|
|
||
| const version = path.basename(fallbackDirectory) | ||
| logger.info( | ||
| `Unable to install ${this.lsName} language server v${latestVersion.serverVersion}. Launching a previous version from ${fallbackDirectory}` | ||
| ) | ||
|
|
||
| result.location = 'fallback' | ||
| result.version = version | ||
| result.assetDirectory = fallbackDirectory | ||
| return { | ||
| location: 'fallback', | ||
| version: version, | ||
| assetDirectory: fallbackDirectory, | ||
| } | ||
| } | ||
|
|
||
| private async fetchRemoteServer( | ||
| cacheDirectory: string, | ||
| latestVersion: LspVersion, | ||
| targetContents: TargetContent[] | ||
| ): Promise<LspResult> { | ||
| if (await this.downloadRemoteTargetContent(targetContents, latestVersion.serverVersion)) { | ||
| return { | ||
| location: 'remote', | ||
| version: latestVersion.serverVersion, | ||
| assetDirectory: cacheDirectory, | ||
| } | ||
| } else { | ||
| throw new ToolkitError('Failed to download server from remote', { code: 'RemoteDownloadFailed' }) | ||
| } | ||
| } | ||
|
|
||
| return result | ||
| private async getLocalServer( | ||
| cacheDirectory: string, | ||
| latestVersion: LspVersion, | ||
| targetContents: TargetContent[] | ||
| ): Promise<LspResult> { | ||
| if (await this.hasValidLocalCache(cacheDirectory, targetContents)) { | ||
| return { | ||
| location: 'cache', | ||
| version: latestVersion.serverVersion, | ||
| assetDirectory: cacheDirectory, | ||
| } | ||
| } else { | ||
| // Delete the cached directory since it's invalid | ||
| if (await fs.existsDir(cacheDirectory)) { | ||
| await fs.delete(cacheDirectory, { | ||
| recursive: true, | ||
| }) | ||
| } | ||
| throw new ToolkitError('Failed to retrieve server from cache', { code: 'InvalidCache' }) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -164,25 +192,39 @@ export class LanguageServerResolver { | |
| await fs.mkdir(downloadDirectory) | ||
| } | ||
|
|
||
| const downloadTasks = contents.map(async (content) => { | ||
| const res = await new HttpResourceFetcher(content.url, { showUrl: true }).get() | ||
| if (!res || !res.ok || !res.body) { | ||
| return false | ||
| const fetchTasks = contents.map(async (content) => { | ||
| return { | ||
| res: await new HttpResourceFetcher(content.url, { showUrl: true }).get(), | ||
| hash: content.hashes[0], | ||
| filename: content.filename, | ||
| } | ||
| }) | ||
| const fetchResults = await Promise.all(fetchTasks) | ||
| const verifyTasks = fetchResults.flatMap(async (fetchResult) => { | ||
| if (!(fetchResult.res && fetchResult.res.ok && fetchResult.res.body)) { | ||
| return [] | ||
| } | ||
|
|
||
| const arrBuffer = await res.arrayBuffer() | ||
| const arrBuffer = await fetchResult.res.arrayBuffer() | ||
| const data = Buffer.from(arrBuffer) | ||
|
|
||
| const hash = createHash('sha384', data) | ||
| if (hash === content.hashes[0]) { | ||
| await fs.writeFile(`${downloadDirectory}/${content.filename}`, data) | ||
| return true | ||
| if (hash === fetchResult.hash) { | ||
| return [{ filename: fetchResult.filename, data }] | ||
| } | ||
| return false | ||
| return [] | ||
| }) | ||
| const downloadResults = await Promise.all(downloadTasks) | ||
| const downloadResult = downloadResults.every(Boolean) | ||
| return downloadResult && this.extractZipFilesFromRemote(downloadDirectory) | ||
| const filesToDownload = await lspSetupStage('validate', async () => (await Promise.all(verifyTasks)).flat()) | ||
|
|
||
| if (filesToDownload.length !== contents.length) { | ||
| return false | ||
| } | ||
|
|
||
| for (const file of filesToDownload) { | ||
| await fs.writeFile(`${downloadDirectory}/${file.filename}`, file.data) | ||
| } | ||
|
|
||
| return this.extractZipFilesFromRemote(downloadDirectory) | ||
| } | ||
|
|
||
| private async extractZipFilesFromRemote(downloadDirectory: string) { | ||
|
|
@@ -333,7 +375,9 @@ export class LanguageServerResolver { | |
| private getCompatibleLspTarget(version: LspVersion) { | ||
| // TODO make this web friendly | ||
| // TODO make this fully support windows | ||
| const platform = process.platform | ||
|
|
||
| // Workaround: Manifest platform field is `windows`, whereas node returns win32 | ||
| const platform = process.platform === 'win32' ? 'windows' : process.platform | ||
| const arch = process.arch | ||
| return version.targets.find((x) => x.arch === arch && x.platform === platform) | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.