Skip to content

Commit

Permalink
Added telemetry (#47)
Browse files Browse the repository at this point in the history
* Added initial framework

* Add handling for each message type

* Fix getting current extension version

* Return a new object for tracking params

* Refactor to use VSCode API for telemetry

* Add popup to ask for consent and update docs

* Update to use better precompile directives

* fix typo in README
  • Loading branch information
michaelshin authored Mar 21, 2023
1 parent 1c87bb1 commit 3b77ba1
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
skyline-vscode/src/version.ts
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ After installation, please make note of the path to the `skyline` binary. To do
2. Press `Ctrl+Shift+P`, then select `Skyline` from the dropdown list.
3. Click on `Begin Analysis`.
## Disabling telemetry
If you do not want to send usage data to CentML, you can set the skyline.isTelemetryEnabled setting to "No".
You can set the value by going to File > Preferences > Settings (On macOS: Code > Preferences > Settings), and search for telemetry. Then set the value in Skyline > Is Telemetry Enabled. This will disable all telemetry events.
As well, DeepView respects VSCode's telemetry levels. IF telemetry.telemetryLevel is set to off, then no telemetry events will be sent to CentML, even if skyline.telemetry.enabled is set to true. If telemetry.telemetryLevel is set to error or crash, only events containing an error or errors property will be sent to CentML.

## Development Environment Setup

### Dependencies
Expand Down
3 changes: 1 addition & 2 deletions skyline-vscode/.vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"script": "debug",
"isBackground": true,
"presentation": {
"reveal": "never"
Expand Down
52 changes: 52 additions & 0 deletions skyline-vscode/esbuild.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as esbuild from 'esbuild';
import ifdefPlugin from 'esbuild-ifdef';

let baseOptions = {
entryPoints: ['./src/extension.js'],
bundle: true,
outfile: 'out/main.js',
external: ['vscode'],
format: 'cjs',
platform: "node"
};

let watchPlugin = [
ifdefPlugin({
variables: {
DEBUG: true
}
})
];

let productionPlugin = [
ifdefPlugin({
variables: {
DEBUG: false
}
})
];

let builds = {
'base': {
...baseOptions,
sourcemap: true
},
'debug': {
...baseOptions,
sourcemap: true,
plugins: watchPlugin
},
'production': {
...baseOptions,
minify: true,
plugins: productionPlugin
}
};

try {


await esbuild.build(builds[process.argv[2]]);
} catch (error) {
process.exit(1);
}
37 changes: 27 additions & 10 deletions skyline-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
"description": "",
"version": "0.1.0",
"engines": {
"vscode": "^1.52.0"
"vscode": "^1.76.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:skyline-vscode.cmd_begin_analyze"
],
"main": "./out/main.js",
"contributes": {
"commands": [
Expand All @@ -34,33 +31,53 @@
"type": "number",
"default": 60120,
"description": "Specifies the port of the profiler."
},
"skyline.isTelemetryEnabled": {
"type": "string",
"default": "Ask me",
"enum": [
"Ask me",
"Yes",
"No"
],
"enumDescriptions": [
"Prompt the user if they consent on collecting their usage data and errors.",
"Allow usage data and errors to be sent to the developer.",
"Do not allow usage data and errors to be sent to the developer."
],
"description": "Controls DeepView telemetry and if data and errors to be sent to the developer. Note that VSCode's telemetry level is respected and takes precedence over this property."
}
}
}
},
"scripts": {
"vscode:prepublish": "npm run esbuild-base -- --minify",
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node",
"esbuild": "npm run esbuild-base -- --sourcemap",
"esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
"test-compile": "tsc -p ./"
"vscode:prepublish": "npm run clean && node esbuild.mjs production",
"esbuild": "npm run clean && node esbuild.mjs base",
"watch": "npm run clean && node esbuild.mjs debug",
"clean": "rm -rf ./out",
"test": "mocha"
},
"devDependencies": {
"@types/glob": "^7.1.4",
"@types/google-protobuf": "^3.15.5",
"@types/mocha": "^9.0.0",
"@types/node": "14.x",
"@types/vscode": "^1.52.0",
"@types/vscode": "^1.76.0",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"@vscode/test-electron": "^1.6.2",
"esbuild": "^0.17.10",
"eslint": "^7.32.0",
"glob": "^7.1.7",
"ifdef-loader": "^2.3.2",
"mocha": "^9.1.1",
"typescript": "^4.4.3"
},
"dependencies": {
"@segment/analytics-node": "^1.0.0-beta.23",
"@types/ws": "^8.2.0",
"bootstrap-fork": "^3.3.6",
"esbuild-ifdef": "^0.2.0",
"google-protobuf": "^3.18.0",
"ts-protoc-gen": "^0.15.0"
}
Expand Down
73 changes: 73 additions & 0 deletions skyline-vscode/src/analytics/AnalyticsManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { SegmentInitializer } from "./SegmentInitializer";
import Analytics from "@segment/analytics-node";
import { filterObjectByKeyName } from "../utils";
export class AnalyticsManager {

analytics: Analytics;
hasIdentifiedUser: boolean;
userId: string;

constructor() {
this.analytics = SegmentInitializer.initialize();
this.hasIdentifiedUser = false;
this.userId = String();
}

sendEventData = (eventName: string, data?: Record<string, any>) => {
this.identifyUser(data);
/// #if DEBUG
console.log("Event!");
console.log({
userId: this.userId,
event: eventName,
timestamp: new Date(),
properties: data
});
/// #else
this.analytics.track({
userId: this.userId,
event: eventName,
timestamp: new Date(),
properties: data
});
/// #endif
};

sendErrorData = (error: Error, data?: Record<string, any>) => {
this.identifyUser(data);
/// #if DEBUG
console.log("Error!");
console.log({
userId: this.userId,
event: "Client Error",
timestamp: new Date(),
properties: {... data, ...error}
});
/// #else
this.analytics.track({
userId: this.userId,
event: "Client Error",
timestamp: new Date(),
properties: {... data, ...error}
});
/// #endif
};

closeAndFlush = () => {
this.analytics.closeAndFlush();
};

identifyUser(data?: Record<string, any>) {
if (!this.hasIdentifiedUser && data) {
this.userId = data["common.vscodemachineid"];
const commonTraits = filterObjectByKeyName(data, "common.");
this.hasIdentifiedUser = true;
/// #if DEBUG
console.log("Identifying!");
console.log({ userId: this.userId, traits:commonTraits });
/// #else
this.analytics.identify({ userId: this.userId, traits:commonTraits });
/// #endif
}
}
}
11 changes: 11 additions & 0 deletions skyline-vscode/src/analytics/SegmentInitializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Analytics, AnalyticsSettings } from '@segment/analytics-node'


export namespace SegmentInitializer {
export function initialize(): Analytics {
// TODO: make sure the key is inside package.json and validate it
let analyticsSettings: AnalyticsSettings = {writeKey: 'sOQXQfqVkpJxVqKbL0tbwkO6SFnpm5Ef', maxEventsInBatch: 10, flushInterval: 10000}
let analytics: Analytics = new Analytics(analyticsSettings);
return analytics;
}
}
22 changes: 22 additions & 0 deletions skyline-vscode/src/analytics/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Usage data being collected by CentML
Only anonymous data is collected by CentML using VSCode's Telemetry API. All telemetry events are automatically sanitized to anonymize all paths (best effort) and references to the username.

### Common data
The common data sent in all telemetry requests may contain:
- **Extension Name** `common.extname` - The extension name
- **Extension Version** `common.extversion` - The extension version
- **Machine Identifier** `common.vscodemachineid` - A common machine identifier generated by VS Code
- **Session Identifier** `common.vscodesessionid` - A session identifier generated by VS Code
- **VS Code Version** `common.vscodeversion` - The version of VS Code running the extension
- **OS** `common.os` - The OS running VS Code
- **Platform Version** `common.platformversion` - The version of the OS/Platform
- **Product** `common.product` - What Vs code is hosted in, i.e. desktop, github.dev, codespaces.
- **UI Kind** `common.uikind` - Web or Desktop indicating where VS Code is running
- **Remote Name** `common.remotename` - A name to identify the type of remote connection. `other` indicates a remote connection not from the 3 main extensions (ssh, docker, wsl).
- **Architecture** `common.nodeArch` - What architecture of node is running. i.e. arm or x86. On the web it will just say `web`.

### Usage data
The usage data sent contains the responses given by the DeepView.Profile

### Error data
The error data sent contains all error information thrown by the extension.
42 changes: 41 additions & 1 deletion skyline-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import * as vscode from 'vscode';
import {SkylineEnvironment, SkylineSession, SkylineSessionOptions} from './skyline_session';

import * as path from 'path';
import { AnalyticsManager } from './analytics/AnalyticsManager';

const PRIVACY_STATEMENT_URL = "https://centml.ai/privacy-policy/";
const OPT_OUT_INSTRUCTIONS_URL = "https://github.com/CentML/DeepView.Explore#how-to-disable-telemetry-reporting";
const RETRY_OPTIN_DELAY_IN_MS = 60 * 60 * 1000; // 1h

export function activate(context: vscode.ExtensionContext) {
let sess: SkylineSession;
Expand All @@ -10,6 +15,14 @@ export function activate(context: vscode.ExtensionContext) {
reactProjectRoot: path.join(context.extensionPath, "react-ui")
};

let anayticsManager: AnalyticsManager = new AnalyticsManager();
const telemetrySender: vscode.TelemetrySender = {
sendEventData: anayticsManager.sendEventData,
sendErrorData: anayticsManager.sendErrorData,
flush: anayticsManager.closeAndFlush
};
const logger = vscode.env.createTelemetryLogger(telemetrySender);

let disposable = vscode.commands.registerCommand('skyline-vscode.cmd_begin_analyze', () => {
let vsconfig = vscode.workspace.getConfiguration('skyline');

Expand Down Expand Up @@ -44,7 +57,9 @@ export function activate(context: vscode.ExtensionContext) {
projectRoot: uri[0].fsPath,
addr: vsconfig.address,
port: vsconfig.port,
webviewPanel: panel
isTelemetryEnabled: isTelemetryEnabled,
webviewPanel: panel,
telemetryLogger: logger
};


Expand All @@ -60,6 +75,7 @@ export function activate(context: vscode.ExtensionContext) {
}

startSkyline();
showTelemetryOptInDialogIfNeeded();
});
});

Expand All @@ -69,3 +85,27 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() {

}

async function showTelemetryOptInDialogIfNeeded() {
let vsconfig = vscode.workspace.getConfiguration('skyline');
if (vsconfig.isTelemetryEnabled === "Ask me"){
// Pop up the message then wait
const message: string = `Help CentML improve DeepView by allowing us to collect usage data.
Read our [privacy statement](${PRIVACY_STATEMENT_URL})
and learn how to [opt out](${OPT_OUT_INSTRUCTIONS_URL}).`;

const retryOptin = setTimeout(showTelemetryOptInDialogIfNeeded, RETRY_OPTIN_DELAY_IN_MS);
let selection: string | undefined;
selection = await vscode.window.showInformationMessage(message, 'Yes', 'No');
if (!selection) {
return;
}
clearTimeout(retryOptin);
vsconfig.update("isTelemetryEnabled", selection, true);
}
}

function isTelemetryEnabled(): boolean {
let vsconfig = vscode.workspace.getConfiguration('skyline');
return (vsconfig.isTelemetryEnabled === "Yes");
}
Loading

0 comments on commit 3b77ba1

Please sign in to comment.