Skip to content
This repository was archived by the owner on Sep 17, 2021. It is now read-only.

Commit 6508429

Browse files
committed
Adding telemetry
1 parent c6ce493 commit 6508429

File tree

4 files changed

+124
-27
lines changed

4 files changed

+124
-27
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"license": "SEE LICENSE IN LICENSE",
88
"publisher": "codemooseus",
99
"icon": "icon.png",
10+
"aiKey": "3ec4c1b4-542f-4adf-830a-a4b04370fa3f",
1011
"homepage": "https://github.com/CodeMooseUS/vscode-devtools/blob/master/README.md",
1112
"repository": {
1213
"type": "git",
@@ -118,12 +119,12 @@
118119
"dependencies": {
119120
"chrome-devtools-frontend": "^1.0.602557",
120121
"vscode": "^1.1.18",
122+
"vscode-extension-telemetry": "^0.1.0",
121123
"ws": "^6.1.0"
122124
},
123125
"devDependencies": {
124126
"@types/node": "^10.5.2",
125127
"@types/shelljs": "^0.8.0",
126-
"@types/ws": "^6.0.1",
127128
"shelljs": "^0.8.2",
128129
"ts-node": "^7.0.1",
129130
"tslint": "^5.11.0",

src/extension.ts

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
import * as path from 'path';
22
import * as vscode from 'vscode';
3+
import TelemetryReporter from 'vscode-extension-telemetry';
34
import WebSocket from 'ws';
45
import QuickPickItem = vscode.QuickPickItem;
56
import * as utils from './utils';
67

7-
let settings: vscode.WorkspaceConfiguration;
8-
let hostname: string;
9-
let port: number;
8+
interface IPackageInfo {
9+
name: string;
10+
version: string;
11+
aiKey: string;
12+
}
13+
1014
const debuggerType: string = 'devtools-for-chrome';
15+
let telemetryReporter: TelemetryReporter;
1116

1217
export function activate(context: vscode.ExtensionContext) {
13-
settings = vscode.workspace.getConfiguration('vscode-devtools-for-chrome');
14-
hostname = settings.get('hostname') as string || 'localhost';
15-
port = settings.get('port') as number || 9222;
18+
19+
const packageInfo = getPackageInfo(context);
20+
telemetryReporter = packageInfo && new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
21+
context.subscriptions.push(telemetryReporter);
1622

1723
context.subscriptions.push(vscode.commands.registerCommand('devtools-for-chrome.launch', async () => {
1824
launch(context);
1925
}));
2026

2127
context.subscriptions.push(vscode.commands.registerCommand('devtools-for-chrome.attach', async () => {
22-
attach(context);
28+
attach(context, /* viaConfig= */ false);
2329
}));
2430

2531
vscode.debug.registerDebugConfigurationProvider(debuggerType, {
@@ -35,7 +41,7 @@ export function activate(context: vscode.ExtensionContext) {
3541
resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
3642
if (config && config.type == debuggerType) {
3743
if (config.request && config.request.localeCompare('attach', 'en', { sensitivity: 'base' }) == 0) {
38-
attach(context);
44+
attach(context, /* viaConfig= */ true);
3945
} else {
4046
let launchUri: string = '';
4147
if (folder.uri.scheme == 'file') {
@@ -56,13 +62,19 @@ export function activate(context: vscode.ExtensionContext) {
5662
}
5763

5864
async function launch(context: vscode.ExtensionContext, launchUrl?: string, chromePathFromLaunchConfig?: string) {
59-
const portFree = await utils.isPortFree(hostname, port);
65+
const viaConfig = !!(launchUrl || chromePathFromLaunchConfig);
66+
const telemetryProps = { viaConfig: `${viaConfig}` };
67+
telemetryReporter.sendTelemetryEvent('launch', telemetryProps);
6068

69+
const { hostname, port } = getSettings();
70+
const portFree = await utils.isPortFree(hostname, port);
6171
if (portFree) {
72+
const settings = vscode.workspace.getConfiguration('vscode-devtools-for-chrome');
6273
const pathToChrome = settings.get('chromePath') as string || chromePathFromLaunchConfig || utils.getPathToChrome();
6374

6475
if (!pathToChrome || !utils.existsSync(pathToChrome)) {
6576
vscode.window.showErrorMessage('Chrome was not found. Chrome must be installed for this extension to function. If you have Chrome installed at a custom location you can specify it in the \'chromePath\' setting.');
77+
telemetryReporter.sendTelemetryEvent('launch/error/chrome_not_found', telemetryProps);
6678
return;
6779
}
6880

@@ -73,16 +85,22 @@ async function launch(context: vscode.ExtensionContext, launchUrl?: string, chro
7385

7486
if (!target || !target.webSocketDebuggerUrl || target.webSocketDebuggerUrl == '') {
7587
vscode.window.showErrorMessage(`Could not find the launched Chrome tab: (${launchUrl}).`);
76-
attach(context);
88+
telemetryReporter.sendTelemetryEvent('launch/error/tab_not_found', telemetryProps);
89+
attach(context, viaConfig);
7790
} else {
7891
DevToolsPanel.createOrShow(context.extensionPath, target.webSocketDebuggerUrl);
7992
}
8093
}
8194

82-
async function attach(context: vscode.ExtensionContext) {
83-
const responseArray = await getListOfTargets();
95+
async function attach(context: vscode.ExtensionContext, viaConfig: boolean) {
96+
const telemetryProps = { viaConfig: `${viaConfig}` };
97+
telemetryReporter.sendTelemetryEvent('attach', telemetryProps);
8498

99+
const { hostname, port } = getSettings();
100+
const responseArray = await getListOfTargets(hostname, port);
85101
if (Array.isArray(responseArray)) {
102+
telemetryReporter.sendTelemetryEvent('attach/list', telemetryProps, { targetCount: responseArray.length });
103+
86104
const items: QuickPickItem[] = [];
87105

88106
responseArray.forEach(i => {
@@ -95,18 +113,46 @@ async function attach(context: vscode.ExtensionContext) {
95113
DevToolsPanel.createOrShow(context.extensionPath, selection.detail as string);
96114
}
97115
});
116+
} else {
117+
telemetryReporter.sendTelemetryEvent('attach/error/no_json_array', telemetryProps);
98118
}
99119
}
100120

101-
async function getListOfTargets(): Promise<Array<any>> {
121+
function getSettings(): { hostname: string, port: number } {
122+
const settings = vscode.workspace.getConfiguration('vscode-devtools-for-chrome');
123+
const hostname = settings.get('hostname') as string || 'localhost';
124+
const port = settings.get('port') as number || 9222;
125+
126+
return { hostname, port };
127+
}
128+
129+
function getPackageInfo(context: vscode.ExtensionContext): IPackageInfo {
130+
const extensionPackage = require(context.asAbsolutePath('./package.json'));
131+
if (extensionPackage) {
132+
return {
133+
name: extensionPackage.name,
134+
version: extensionPackage.version,
135+
aiKey: extensionPackage.aiKey
136+
};
137+
}
138+
return undefined;
139+
}
140+
141+
async function getListOfTargets(hostname: string, port: number): Promise<Array<any>> {
102142
const checkDiscoveryEndpoint = (url: string) => {
103143
return utils.getURL(url, { headers: { Host: 'localhost' } });
104144
};
105145

106146
const jsonResponse = await checkDiscoveryEndpoint(`http://${hostname}:${port}/json/list`)
107147
.catch(() => checkDiscoveryEndpoint(`http://${hostname}:${port}/json`));
108148

109-
return JSON.parse(jsonResponse);
149+
let result: Array<string>;
150+
try {
151+
result = JSON.parse(jsonResponse);
152+
} catch (ex) {
153+
result = undefined;
154+
}
155+
return result;
110156
}
111157

112158
class DevToolsPanel {
@@ -181,18 +227,24 @@ class DevToolsPanel {
181227
private _disposeSocket() {
182228
if (this._socket) {
183229
// Reset the socket since the devtools have been reloaded
184-
this._socket.onerror = undefined;
230+
telemetryReporter.sendTelemetryEvent('websocket/dispose');
185231
this._socket.onopen = undefined;
186-
this._socket.onclose = undefined;
187232
this._socket.onmessage = undefined;
233+
this._socket.onerror = undefined;
234+
this._socket.onclose = undefined;
188235
this._socket.close();
189236
this._socket = undefined;
190237
}
191238
}
192239

193240
private _onMessageFromWebview(message: string) {
194241
if (message === 'ready') {
242+
if (this._socket) {
243+
telemetryReporter.sendTelemetryEvent('websocket/reconnect');
244+
}
195245
this._disposeSocket();
246+
} else if (message.substr(0, 10) === 'telemetry:') {
247+
return this._sendTelemetryMessage(message.substr(10));
196248
}
197249

198250
if (!this._socket) {
@@ -212,22 +264,16 @@ class DevToolsPanel {
212264

213265
// Create the websocket
214266
this._socket = new WebSocket(url);
215-
this._socket.onerror = this._onError.bind(this);
216267
this._socket.onopen = this._onOpen.bind(this);
217268
this._socket.onmessage = this._onMessage.bind(this);
269+
this._socket.onerror = this._onError.bind(this);
218270
this._socket.onclose = this._onClose.bind(this);
219271
}
220272

221-
private _onError() {
222-
if (this._isConnected) {
223-
// Tell the devtools that there was a connection error
224-
this._panel.webview.postMessage('error');
225-
}
226-
}
227-
228273
private _onOpen() {
229274
this._isConnected = true;
230275
// Tell the devtools that the real websocket was opened
276+
telemetryReporter.sendTelemetryEvent('websocket/open');
231277
this._panel.webview.postMessage('open');
232278

233279
if (this._socket) {
@@ -246,14 +292,28 @@ class DevToolsPanel {
246292
}
247293
}
248294

295+
private _onError() {
296+
if (this._isConnected) {
297+
// Tell the devtools that there was a connection error
298+
telemetryReporter.sendTelemetryEvent('websocket/error');
299+
this._panel.webview.postMessage('error');
300+
}
301+
}
302+
249303
private _onClose() {
250304
if (this._isConnected) {
251305
// Tell the devtools that the real websocket was closed
306+
telemetryReporter.sendTelemetryEvent('websocket/close');
252307
this._panel.webview.postMessage('close');
253308
}
254309
this._isConnected = false;
255310
}
256311

312+
private _sendTelemetryMessage(message: string) {
313+
const telemetry = JSON.parse(message);
314+
telemetryReporter.sendTelemetryEvent(telemetry.name, telemetry.properties, telemetry.metrics);
315+
}
316+
257317
private _update() {
258318
this._panel.webview.html = this._getHtmlForWebview();
259319
}
@@ -271,9 +331,10 @@ class DevToolsPanel {
271331
<head>
272332
<meta http-equiv="content-type" content="text/html; charset=utf-8">
273333
<style>
274-
html, body {
334+
html, body, iframe {
275335
height: 100%;
276336
width: 100%;
337+
position: absolute;
277338
padding: 0;
278339
margin: 0;
279340
overflow: hidden;

src/host/devtools.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
<head>
44
<meta http-equiv="content-type" content="text/html; charset=utf-8">
55
<style>
6-
iframe, html, body {
6+
html, body, iframe {
77
height: 100%;
88
width: 100%;
9+
position: absolute;
910
padding: 0;
1011
margin: 0;
1112
overflow: hidden;

src/host/storage.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ class ToolsHost {
1313
setPreference(name: string, value: string) {
1414
// TODO: save the preference via the extension and global/workspaceState
1515
}
16+
17+
recordEnumeratedHistogram(actionName: string, actionCode: number, bucketSize: number) {
18+
// Inform the extension of the chrome telemetry event
19+
const telemetry = {
20+
name: `devtools/${actionName}`,
21+
properties: {},
22+
metrics: {}
23+
};
24+
if (actionName === 'DevTools.InspectElement') {
25+
(telemetry.metrics as any)[`${actionName}.duration`] = actionCode;
26+
} else {
27+
(telemetry.properties as any)[`${actionName}.actionCode`] = actionCode;
28+
}
29+
window.parent.postMessage(`telemetry:${JSON.stringify(telemetry)}`, '*');
30+
}
1631
}
1732

1833
class ToolsWebSocket {
@@ -62,4 +77,23 @@ devToolsFrame.onload = () => {
6277
get: function () { return undefined; },
6378
set: function () { }
6479
});
80+
81+
const reportError = function (name: string, stack: string) {
82+
const telemetry = {
83+
name: `devtools/${name}`,
84+
properties: { stack: stack.substr(0, 30) },
85+
metrics: {}
86+
};
87+
dtWindow.parent.postMessage(`telemetry:${JSON.stringify(telemetry)}`, '*');
88+
};
89+
90+
// Add unhandled exception listeners for telemetry
91+
(dtWindow as any).addEventListener('error', (event: ErrorEvent) => {
92+
const stack = (event && event.error && event.error.stack ? event.error.stack : event.message);
93+
reportError('error', stack);
94+
});
95+
(dtWindow as any).addEventListener('unhandledrejection', (reject: PromiseRejectionEvent) => {
96+
const stack = (reject && reject.reason && reject.reason.stack ? reject.reason.stack : reject.type);
97+
reportError('unhandledrejection', stack);
98+
});
6599
};

0 commit comments

Comments
 (0)