Skip to content

Commit 65b0148

Browse files
feat(telemetry): Crash Monitoring (#5684)
## Problem We want to know when the extension crashes. Having telemetry will be useful. The problem is that when the extension crashes it is not running anymore so it cannot report that it crashed. ## Solution Create a Crash Monitoring mechanism that has all the instances of an extension (Q/Toolkit) work together to monitor eachother for crashes, reporting of a crash if it is detected. To review start with the main file `packages/core/src/shared/crashMonitoring/crashMonitoring.ts`, specifically the `CrashMonitoring.start()` method. There is more information in the docstring of the class. --- <!--- REMINDER: Ensure that your PR meets the guidelines in CONTRIBUTING.md --> License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Signed-off-by: nkomonen-amazon <[email protected]> Co-authored-by: Justin M. Keyes <[email protected]>
1 parent 30601ba commit 65b0148

20 files changed

+1023
-78
lines changed

docs/vscode_behaviors.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# VS Code Behaviors
2+
3+
Many VS Code behavoirs for certain APIs or user interactions with the IDE are not clearly documented,
4+
or documented at all. Please add any findings to this document.
5+
6+
## `deactivate()` - extension shutdown
7+
8+
This method is defined as part of the VS Code extension API, and is run on a **graceful** shutdown
9+
for each extension.
10+
11+
- Our extension and its `deactivate()` function are in the Extension Host process [1]
12+
- The Extension Host process has at most 5 seconds to shut down, after which it will exit. [1]
13+
- The vscode API will be unreliable at deactivation time. So certain VSC APIs like the filesystem may not work. [1]
14+
- The VSC Filesystem API has been confirmed to not work
15+
- In `Run & Debug` mode, closing the Debug IDE instance behaves differently depending on how it is closed
16+
- The regular close button in the Debug IDE instance results in a graceful shutdown
17+
- The red square in the root IDE instance to stop the debugging session results on a non-graceful shutdown, meaning `deactivate()` is not run.
18+
19+
Sources:
20+
21+
- [[1]](https://github.com/Microsoft/vscode/issues/47881#issuecomment-381910587)
22+
- [[2]](https://github.com/microsoft/vscode/issues/122825#issuecomment-814218149)

packages/amazonq/src/extensionNode.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as vscode from 'vscode'
77
import { activateAmazonQCommon, amazonQContextPrefix, deactivateCommon } from './extension'
88
import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq'
99
import { activate as activateQGumby } from 'aws-core-vscode/amazonqGumby'
10-
import { ExtContext, globals } from 'aws-core-vscode/shared'
10+
import { ExtContext, globals, CrashMonitoring } from 'aws-core-vscode/shared'
1111
import { filetypes, SchemaService } from 'aws-core-vscode/sharedNode'
1212
import { updateDevMode } from 'aws-core-vscode/dev'
1313
import { CommonAuthViewProvider } from 'aws-core-vscode/login'
@@ -32,6 +32,8 @@ export async function activate(context: vscode.ExtensionContext) {
3232
* the code compatible with web and move it to {@link activateAmazonQCommon}.
3333
*/
3434
async function activateAmazonQNode(context: vscode.ExtensionContext) {
35+
await (await CrashMonitoring.instance()).start()
36+
3537
const extContext = {
3638
extensionContext: context,
3739
}
@@ -77,6 +79,7 @@ async function setupDevMode(context: vscode.ExtensionContext) {
7779
'deleteSsoConnections',
7880
'expireSsoConnections',
7981
'editAuthConnections',
82+
'forceIdeCrash',
8083
],
8184
}
8285

@@ -92,5 +95,6 @@ async function setupDevMode(context: vscode.ExtensionContext) {
9295
}
9396

9497
export async function deactivate() {
95-
await deactivateCommon()
98+
// Run concurrently to speed up execution. stop() does not throw so it is safe
99+
await Promise.all([(await CrashMonitoring.instance()).stop(), deactivateCommon()])
96100
}

packages/core/src/dev/activation.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { entries } from '../shared/utilities/tsUtils'
2424
import { getEnvironmentSpecificMemento } from '../shared/utilities/mementos'
2525
import { setContext } from '../shared'
2626
import { telemetry } from '../shared/telemetry'
27+
import { getSessionId } from '../shared/telemetry/util'
2728

2829
interface MenuOption {
2930
readonly label: string
@@ -41,6 +42,7 @@ export type DevFunction =
4142
| 'deleteSsoConnections'
4243
| 'expireSsoConnections'
4344
| 'editAuthConnections'
45+
| 'forceIdeCrash'
4446

4547
export type DevOptions = {
4648
context: vscode.ExtensionContext
@@ -105,6 +107,11 @@ const menuOptions: Record<DevFunction, MenuOption> = {
105107
detail: 'Opens editor to all Auth Connections the extension is using.',
106108
executor: editSsoConnections,
107109
},
110+
forceIdeCrash: {
111+
label: 'Crash: Force IDE ExtHost Crash',
112+
detail: `Will SIGKILL ExtHost, { pid: ${process.pid}, sessionId: '${getSessionId().slice(0, 8)}-...' }, but the IDE itself will not crash.`,
113+
executor: forceQuitIde,
114+
},
108115
}
109116

110117
/**
@@ -216,6 +223,7 @@ function isSecrets(obj: vscode.Memento | vscode.SecretStorage): obj is vscode.Se
216223

217224
class VirtualObjectFile implements FileProvider {
218225
private mTime = 0
226+
private size = 0
219227
private readonly onDidChangeEmitter = new vscode.EventEmitter<void>()
220228
public readonly onDidChange = this.onDidChangeEmitter.event
221229

@@ -227,22 +235,24 @@ class VirtualObjectFile implements FileProvider {
227235
/** Emits an event indicating this file's content has changed */
228236
public refresh() {
229237
/**
230-
* Per {@link vscode.FileSystemProvider.onDidChangeFile}, if the mTime does not change, new file content may
231-
* not be retrieved. Without this, when we emit a change the text editor did not update.
238+
* Per {@link vscode.FileSystemProvider.onDidChangeFile}, if the mTime and/or size does not change, new file content may
239+
* not be retrieved due to optimizations. Without this, when we emit a change the text editor did not update.
232240
*/
233241
this.mTime++
234242
this.onDidChangeEmitter.fire()
235243
}
236244

237245
public stat(): { ctime: number; mtime: number; size: number } {
238246
// This would need to be filled out to track conflicts
239-
return { ctime: 0, mtime: this.mTime, size: 0 }
247+
return { ctime: 0, mtime: this.mTime, size: this.size }
240248
}
241249

242250
public async read(): Promise<Uint8Array> {
243251
const encoder = new TextEncoder()
244252

245-
return encoder.encode(await this.readStore(this.key))
253+
const data = encoder.encode(await this.readStore(this.key))
254+
this.size = data.length
255+
return data
246256
}
247257

248258
public async write(content: Uint8Array): Promise<void> {
@@ -435,6 +445,15 @@ async function expireSsoConnections() {
435445
)
436446
}
437447

448+
export function forceQuitIde() {
449+
// This current process is the ExtensionHost. Killing it will cause all the extensions to crash
450+
// for the current ExtensionHost (unless using "extensions.experimental.affinity").
451+
// The IDE instance itself will remaing running, but a new ExtHost will spawn within it.
452+
// The PPID (parent process) is vscode itself, killing it crashes all vscode instances.
453+
const vsCodePid = process.pid
454+
process.kill(vsCodePid, 'SIGKILL') // SIGTERM would be the graceful shutdown
455+
}
456+
438457
async function showState(path: string) {
439458
const uri = vscode.Uri.parse(`aws-dev2://state/${path}-${targetContext.extension.id}`)
440459
const doc = await vscode.workspace.openTextDocument(uri)

packages/core/src/extensionNode.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import { ExtensionUse } from './auth/utils'
5959
import { ExtStartUpSources } from './shared/telemetry'
6060
import { activate as activateThreatComposerEditor } from './threatComposer/activation'
6161
import { isSsoConnection, hasScopes } from './auth/connection'
62-
import { setContext } from './shared'
62+
import { CrashMonitoring, setContext } from './shared'
6363

6464
let localize: nls.LocalizeFunc
6565

@@ -78,6 +78,8 @@ export async function activate(context: vscode.ExtensionContext) {
7878
// IMPORTANT: If you are doing setup that should also work in web mode (browser), it should be done in the function below
7979
const extContext = await activateCommon(context, contextPrefix, false)
8080

81+
await (await CrashMonitoring.instance()).start()
82+
8183
initializeCredentialsProviderManager()
8284

8385
const toolkitEnvDetails = getExtEnvironmentDetails()
@@ -251,7 +253,8 @@ export async function activate(context: vscode.ExtensionContext) {
251253
}
252254

253255
export async function deactivate() {
254-
await deactivateCommon()
256+
// Run concurrently to speed up execution. stop() does not throw so it is safe
257+
await Promise.all([await (await CrashMonitoring.instance()).stop(), deactivateCommon()])
255258
await globals.resourceManager.dispose()
256259
}
257260

packages/core/src/shared/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,14 @@ export const amazonQHelpUrl = 'https://aws.amazon.com/q/'
157157
// URL for Amazon Q VS Code
158158
export const amazonQVscodeMarketplace =
159159
'https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.amazon-q-vscode'
160+
161+
/**
162+
* Names of directories relevant to the crash reporting functionality.
163+
*
164+
* Moved here to resolve circular dependency issues.
165+
*/
166+
export const crashMonitoringDirNames = {
167+
root: 'crashMonitoring',
168+
running: 'running',
169+
shutdown: 'shutdown',
170+
} as const

0 commit comments

Comments
 (0)