Skip to content

Commit ab46760

Browse files
authored
telemetry: ide_editCodeFile is noisy aws#5491
## Problem: - Pseudo-documents like `foo.git`, `output://…`, and `~/.vscode/argv.json` are included in `ide_editCodeFile` metrics even though they were not actually opened by the user. - `onDidOpenTextDocument` doesn't correctly represent user activity: it fires for *all* documents opened with `onDidOpenTextDocument()`, which includes all documents/buffers opened for programmatic purposes by application or library code. ## Solution: - Listen to `onDidChangeActiveTextEditor` instead of `onDidOpenTextDocument`. This represents when a user actually opens a file in the IDE. - Ignore various "noise" documents.
1 parent 7054fb9 commit ab46760

File tree

6 files changed

+89
-52
lines changed

6 files changed

+89
-52
lines changed

packages/core/src/shared/filetypes.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,17 @@ export function isAwsFiletype(doc: vscode.TextDocument): boolean | undefined {
6262

6363
export function activate(): void {
6464
globals.context.subscriptions.push(
65-
// TODO: onDidChangeTextDocument ?
66-
vscode.workspace.onDidOpenTextDocument(async (doc: vscode.TextDocument) => {
65+
vscode.window.onDidChangeActiveTextEditor(async (editor) => {
66+
const doc = editor?.document
67+
// Ignore output:// files.
68+
// Ignore *.git files (from the builtin git extension).
69+
// Ignore ~/.vscode/argv.json (vscode internal file).
70+
const isNoise =
71+
!doc || doc.uri.scheme === 'git' || doc.uri.scheme === 'output' || doc.fileName.endsWith('argv.json')
72+
if (isNoise) {
73+
return
74+
}
75+
6776
const basename = path.basename(doc.fileName)
6877
let fileExt: string | undefined = path.extname(doc.fileName).trim()
6978
fileExt = fileExt !== '' ? fileExt : undefined // Telemetry client will fail on empty string.
@@ -77,11 +86,10 @@ export function activate(): void {
7786
const isSameMetricPending = await globals.telemetry.findPendingMetric('ide_editCodeFile', metric, [
7887
'filenameExt',
7988
])
80-
if (isSameMetricPending) {
81-
return // Avoid redundant/duplicate metrics.
89+
if (!isSameMetricPending) {
90+
// Avoid redundant/duplicate metrics.
91+
telemetry.ide_editCodeFile.emit(metric)
8292
}
83-
84-
telemetry.ide_editCodeFile.emit(metric)
8593
}
8694

8795
const isAwsFileExt = isAwsFiletype(doc)

packages/core/src/shared/fs/watchedFiles.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import * as pathutils from '../utilities/pathUtils'
99
import * as path from 'path'
1010
import { globDirPatterns, isUntitledScheme, normalizeVSCodeUri } from '../utilities/vsCodeUtils'
1111
import { Settings } from '../settings'
12-
import { once } from '../utilities/functionUtils'
1312
import { Timeout } from '../utilities/timeoutUtils'
1413

1514
/**
@@ -60,7 +59,6 @@ export function getExcludePattern() {
6059
const excludePattern = `**/{${excludePatternsStr}}/`
6160
return excludePattern
6261
}
63-
const getExcludePatternOnce = once(getExcludePattern)
6462

6563
/**
6664
* WatchedFiles lets us watch files on the filesystem. It is used
@@ -180,6 +178,9 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
180178

181179
/**
182180
* Adds a regex pattern to ignore paths containing the pattern
181+
*
182+
* TODO: this does NOT prevent addWatchPatterns() from creating a massive, expensive, recursive
183+
* filewatcher for these patterns 🤦, it merely skips processing at event-time.
183184
*/
184185
public addExcludedPattern(pattern: RegExp) {
185186
if (this._isDisposed) {
@@ -309,7 +310,7 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
309310
let skips = 0
310311
const found: vscode.Uri[] = []
311312

312-
const exclude = getExcludePatternOnce()
313+
const exclude = getExcludePattern()
313314
getLogger().info(`${this.name}: building with: ${this.outputPatterns()}`)
314315

315316
for (let i = 0; i < this.globs.length && !cancel?.completed; i++) {

packages/core/src/test/shared/filetypes.test.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,23 @@ describe('ide_editCodeFile telemetry', function () {
3232
let jsonUri1: vscode.Uri | undefined
3333
let jsonUri2: vscode.Uri | undefined
3434
let tsUri: vscode.Uri | undefined
35+
let noiseUri1!: vscode.Uri
36+
let noiseUri2!: vscode.Uri
3537

3638
beforeEach(async function () {
3739
await testUtil.closeAllEditors()
38-
39-
const jsonFile1 = workspaceUtils.tryGetAbsolutePath(
40-
vscode.workspace.workspaceFolders?.[0],
41-
'ts-plain-sam-app/tsconfig.json'
42-
)
43-
jsonUri1 = vscode.Uri.file(jsonFile1)
44-
45-
const jsonFile2 = workspaceUtils.tryGetAbsolutePath(
46-
vscode.workspace.workspaceFolders?.[0],
47-
'ts-plain-sam-app/package.json'
48-
)
49-
jsonUri2 = vscode.Uri.file(jsonFile2)
50-
51-
const tsFile = workspaceUtils.tryGetAbsolutePath(
52-
vscode.workspace.workspaceFolders?.[0],
53-
'ts-plain-sam-app/src/app.ts'
54-
)
55-
tsUri = vscode.Uri.file(tsFile)
40+
const ws = vscode.workspace.workspaceFolders?.[0]
41+
42+
jsonUri1 = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, 'ts-plain-sam-app/tsconfig.json'))
43+
jsonUri2 = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, 'ts-plain-sam-app/package.json'))
44+
tsUri = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, 'ts-plain-sam-app/src/app.ts'))
45+
// Simulate ~/.vscode/argv.json which is emitted by vscode `onDidOpenTextDocument` event.
46+
// filetypes.ts no longer listens to `onDidOpenTextDocument`, but this test protects against
47+
// regressions.
48+
noiseUri1 = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, '.vscode/argv.json'))
49+
await testUtil.toFile('fake argv.json', noiseUri1)
50+
noiseUri2 = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, 'foo/bar/zub.git'))
51+
await testUtil.toFile('fake *.git file', noiseUri2)
5652
})
5753

5854
after(async function () {
@@ -67,6 +63,14 @@ describe('ide_editCodeFile telemetry', function () {
6763
await vscode.commands.executeCommand('vscode.open', jsonUri1)
6864
// Different file, same extension (.json), thus `ide_editCodeFile` should be skipped/deduped.
6965
await vscode.commands.executeCommand('vscode.open', jsonUri2)
66+
67+
// Open some "noise" documents WITHOUT showing them in a text editor. This triggers
68+
// `onDidOpenTextDocument` but not `onDidChangeActiveTextEditor`. This simulates typical
69+
// vscode activity. Noise documents should NOT trigger `ide_editCodeFile` or any similar
70+
// metric.
71+
await vscode.workspace.openTextDocument(noiseUri1)
72+
await vscode.workspace.openTextDocument(noiseUri2)
73+
7074
// Wait for metrics...
7175
const r1 = await getMetrics(2, 'ide_editCodeFile')
7276
const m1 = r1?.[0]
@@ -92,10 +96,12 @@ describe('ide_editCodeFile telemetry', function () {
9296

9397
describe('file_editAwsFile telemetry', function () {
9498
let awsConfigUri: vscode.Uri | undefined
99+
let ssmJsonUri: vscode.Uri | undefined
95100
let cfnUri: vscode.Uri | undefined
96101

97102
beforeEach(async function () {
98103
await testUtil.closeAllEditors()
104+
const ws = vscode.workspace.workspaceFolders?.[0]
99105

100106
// Create a dummy file in ~/.aws on the system.
101107
// Note: We consider _any_ file in ~/.aws to be an "AWS config" file,
@@ -104,11 +110,8 @@ describe('file_editAwsFile telemetry', function () {
104110
awsConfigUri = vscode.Uri.file(awsConfigFile)
105111
await testUtil.toFile('Test file from the aws-toolkit-vscode test suite.', awsConfigFile)
106112

107-
const cfnFile = workspaceUtils.tryGetAbsolutePath(
108-
vscode.workspace.workspaceFolders?.[0],
109-
'python3.7-plain-sam-app/template.yaml'
110-
)
111-
cfnUri = vscode.Uri.file(cfnFile)
113+
ssmJsonUri = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, 'test.ssm.json'))
114+
cfnUri = vscode.Uri.file(workspaceUtils.tryGetAbsolutePath(ws, 'python3.7-plain-sam-app/template.yaml'))
112115
})
113116

114117
after(async function () {
@@ -118,10 +121,7 @@ describe('file_editAwsFile telemetry', function () {
118121
it('emits when opened by user', async function () {
119122
await vscode.commands.executeCommand('vscode.open', cfnUri)
120123
await vscode.commands.executeCommand('vscode.open', awsConfigUri)
121-
await vscode.workspace.openTextDocument({
122-
content: 'test content for SSM JSON',
123-
language: 'ssm-json',
124-
})
124+
await vscode.commands.executeCommand('vscode.open', ssmJsonUri)
125125

126126
const r = await getMetrics(3, 'file_editAwsFile', 5000)
127127

packages/core/src/test/testUtil.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ import { DeclaredCommand } from '../shared/vscode/commands2'
2020
const testTempDirs: string[] = []
2121

2222
/**
23-
* Writes the string form of `o` to `filePathParts` as UTF-8 text.
23+
* Writes the string form of `o` to `filepath` as UTF-8 text.
2424
*
25-
* Creates parent directories in `filePathParts`, if necessary.
25+
* Creates parent directories in `filepath`, if necessary.
2626
*/
27-
export async function toFile(o: any, ...filePathParts: string[]) {
28-
const text = o ? o.toString() : ''
29-
const filePath = path.join(...filePathParts)
30-
const dir = path.dirname(filePath)
27+
export async function toFile(o: any, filepath: string | vscode.Uri) {
28+
const file = typeof filepath === 'string' ? filepath : filepath.fsPath
29+
const text = o === undefined ? '' : o.toString()
30+
const dir = path.dirname(file)
3131
await fs2.mkdir(dir)
32-
await fs2.writeFile(filePath, text)
32+
await fs2.writeFile(file, text)
3333
}
3434

3535
/**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"description": "Attach IAM Policy to Instance",
3+
"assumeRole": "{{ AutomationAssumeRole }}",
4+
"schemaVersion": "0.3",
5+
"parameters": {
6+
"AutomationAssumeRole": {
7+
"description": "ARN role",
8+
"type": "String"
9+
},
10+
"InstanceId": {
11+
"description": "Instance ID",
12+
"type": "String"
13+
}
14+
},
15+
"mainSteps": [
16+
{
17+
"inputs": {
18+
"FunctionName": "SSMOnboardingLambda",
19+
"InputPayload": {
20+
"instance_id": "{{ InstanceId }}"
21+
}
22+
},
23+
"name": "AttachPoliciesToInstance",
24+
"action": "aws:invokeLambdaFunction",
25+
"onFailure": "Abort"
26+
}
27+
]
28+
}

packages/core/src/testInteg/shared/utilities/workspaceUtils.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ describe('collectFiles', function () {
211211
const workspaceFolder = await createTestWorkspace(fileAmount, { fileNamePrefix, fileContent })
212212

213213
const writeFile = (pathParts: string[], fileContent: string) => {
214-
return toFile(fileContent, workspaceFolder.uri.fsPath, ...pathParts)
214+
return toFile(fileContent, path.join(workspaceFolder.uri.fsPath, ...pathParts))
215215
}
216216

217217
sinon.stub(vscode.workspace, 'workspaceFolders').value([workspaceFolder])
@@ -307,18 +307,18 @@ describe('collectFiles', function () {
307307
const fileContent = ''
308308
for (const fmt of ['txt', 'md']) {
309309
// root license files
310-
await toFile(fileContent, workspace.uri.fsPath, `license.${fmt}`)
311-
await toFile(fileContent, workspace.uri.fsPath, `License.${fmt}`)
312-
await toFile(fileContent, workspace.uri.fsPath, `LICENSE.${fmt}`)
310+
await toFile(fileContent, path.join(workspace.uri.fsPath, `license.${fmt}`))
311+
await toFile(fileContent, path.join(workspace.uri.fsPath, `License.${fmt}`))
312+
await toFile(fileContent, path.join(workspace.uri.fsPath, `LICENSE.${fmt}`))
313313

314314
// nested license files
315-
await toFile(fileContent, workspace.uri.fsPath, 'src', `license.${fmt}`)
316-
await toFile(fileContent, workspace.uri.fsPath, 'src', `License.${fmt}`)
317-
await toFile(fileContent, workspace.uri.fsPath, 'src', `LICENSE.${fmt}`)
315+
await toFile(fileContent, path.join(workspace.uri.fsPath, `src/license.${fmt}`))
316+
await toFile(fileContent, path.join(workspace.uri.fsPath, `src/License.${fmt}`))
317+
await toFile(fileContent, path.join(workspace.uri.fsPath, `src/LICENSE.${fmt}`))
318318
}
319319

320320
// add a non license file too, to make sure it is returned
321-
await toFile(fileContent, workspace.uri.fsPath, 'non-license.md')
321+
await toFile(fileContent, path.join(workspace.uri.fsPath, 'non-license.md'))
322322

323323
const result = await collectFiles([workspace.uri.fsPath], [workspace], true)
324324

@@ -438,7 +438,7 @@ describe('collectFilesForIndex', function () {
438438
const workspaceFolder = await createTestWorkspace(fileAmount, { fileNamePrefix, fileContent })
439439

440440
const writeFile = (pathParts: string[], fileContent: string) => {
441-
return toFile(fileContent, workspaceFolder.uri.fsPath, ...pathParts)
441+
return toFile(fileContent, path.join(workspaceFolder.uri.fsPath, ...pathParts))
442442
}
443443

444444
sinon.stub(vscode.workspace, 'workspaceFolders').value([workspaceFolder])

0 commit comments

Comments
 (0)