Skip to content

Commit 4e7df1e

Browse files
authored
Add debounce to import tracking (microsoft#4875)
* Add debounce to import tracking Add steps on how to profile Skip having test-results.xml checked in. * Add news entry * Update casing * Move profiling to wiki Use lineAt instead of getText.splitLines * Update news entry * Remove debounce changes Implement per file timeout instead * Use isemptyOrWhitespace Fix timeout to be when finally done typing
1 parent 89299ea commit 4e7df1e

5 files changed

Lines changed: 45 additions & 7 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ obj/**
2828
tmp/**
2929
.python-version
3030
.vs/
31+
test-results.xml
3132
!build/

images/JavascriptProfiler.png

116 KB
Loading

news/3 Code Health/4852.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Hash imports so we can get every import and not just known ones. We have to unhash them on the other side to determine if a specific package or not.
1+
Hash imports so we can get every import and not just known ones. Should happen at most once every 5 seconds.

src/client/telemetry/importTracker.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import * as path from 'path';
88
import { TextDocument } from 'vscode';
99

1010
import { sendTelemetryEvent } from '.';
11-
import { noop, sleep } from '../../test/core';
11+
import { noop } from '../../test/core';
1212
import { IDocumentManager } from '../common/application/types';
13+
import { isTestExecution } from '../common/constants';
1314
import { IHistoryProvider } from '../datascience/types';
1415
import { ICodeExecutionManager } from '../terminals/types';
1516
import { EventName } from './constants';
@@ -18,9 +19,14 @@ import { IImportTracker } from './types';
1819
const ImportRegEx = /^(?!['"#]).*from\s+([a-zA-Z0-9_\.]+)\s+import.*(?!['"])|^(?!['"#]).*import\s+([a-zA-Z0-9_\., ]+).*(?!['"])/;
1920
const MAX_DOCUMENT_LINES = 1000;
2021

22+
// Capture isTestExecution on module load so that a test can turn it off and still
23+
// have this value set.
24+
const testExecution = isTestExecution();
25+
2126
@injectable()
2227
export class ImportTracker implements IImportTracker {
2328

29+
private pendingDocs = new Map<string, NodeJS.Timer>();
2430
private sentMatches: Set<string> = new Set<string>();
2531
// tslint:disable-next-line:no-require-imports
2632
private hashFn = require('hash.js').sha256;
@@ -44,21 +50,51 @@ export class ImportTracker implements IImportTracker {
4450
}
4551

4652
public async activate(): Promise<void> {
47-
// Act like all of our open documents just opened. Don't do this now though. We don't want
48-
// to hold up the activate.
49-
await sleep(1000);
53+
// Act like all of our open documents just opened. Our timeout will make sure this is delayed
5054
this.documentManager.textDocuments.forEach(d => this.onOpenedOrSavedDocument(d));
5155
}
5256

57+
private getDocumentLines(document: TextDocument) : string [] {
58+
return Array.apply(null, {length: Math.min(document.lineCount, MAX_DOCUMENT_LINES)}).map((a, i) => {
59+
const line = document.lineAt(i);
60+
if (line && !line.isEmptyOrWhitespace) {
61+
return line.text;
62+
}
63+
return undefined;
64+
}).filter((f: string | undefined) => f);
65+
}
66+
5367
private onOpenedOrSavedDocument(document: TextDocument) {
5468
// Make sure this is a python file.
5569
if (path.extname(document.fileName) === '.py') {
5670
// Parse the contents of the document, looking for import matches on each line
57-
const lines = document.getText().splitLines({ trim: true, removeEmptyEntries: true });
58-
this.lookForImports(lines.slice(0, Math.min(lines.length, MAX_DOCUMENT_LINES)), EventName.KNOWN_IMPORT_FROM_FILE);
71+
this.scheduleDocument(document);
5972
}
6073
}
6174

75+
private scheduleDocument(document: TextDocument) {
76+
// If already scheduled, cancel.
77+
if (this.pendingDocs.has(document.fileName)) {
78+
clearTimeout(this.pendingDocs.get(document.fileName));
79+
this.pendingDocs.delete(document.fileName);
80+
}
81+
82+
// Now schedule a new one.
83+
if (testExecution) {
84+
// During a test, check right away. It needs to be synchronous.
85+
this.checkDocument(document);
86+
} else {
87+
// Wait five seconds to make sure we don't already have this document pending.
88+
this.pendingDocs.set(document.fileName, setTimeout(() => this.checkDocument(document), 5000));
89+
}
90+
}
91+
92+
private checkDocument(document: TextDocument) {
93+
this.pendingDocs.delete(document.fileName);
94+
const lines = this.getDocumentLines(document);
95+
this.lookForImports(lines, EventName.KNOWN_IMPORT_FROM_FILE);
96+
}
97+
6298
private onExecutedCode(code: string) {
6399
const lines = code.splitLines({ trim: true, removeEmptyEntries: true });
64100
this.lookForImports(lines, EventName.KNOWN_IMPORT_FROM_EXECUTION);

src/test/datascience/editor-integration/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export function createDocument(inputText: string, fileName: string, fileVersion:
2929
const testRange = new Range(index, 0, index, line.length);
3030
textLine.setup(l => l.text).returns(() => line);
3131
textLine.setup(l => l.range).returns(() => testRange);
32+
textLine.setup(l => l.isEmptyOrWhitespace).returns(() => line.trim().length === 0);
3233
return textLine;
3334
});
3435
document.setup(d => d.lineAt(TypeMoq.It.isAnyNumber())).returns((index: number) => textLines[index].object);

0 commit comments

Comments
 (0)