add basic analytics

This commit is contained in:
Dave Holoway
2020-07-02 18:20:51 +01:00
parent 4740f6533a
commit b5cc60a516
8 changed files with 182 additions and 0 deletions

View File

@@ -2,6 +2,8 @@
// Import the module and reference it with the alias vscode in your code below
const path = require('path');
const vscode = require('vscode');
const analytics = require('./langserver/analytics');
const package_json = require('./package.json');
const { LanguageClient, TransportKind, } = require('vscode-languageclient');
const { AndroidContentProvider } = require('./src/contentprovider');
const { openLogcatWindow } = require('./src/logcat');
@@ -43,6 +45,8 @@ async function createLanguageClient(context) {
}
const sourceFiles = (await vscode.workspace.findFiles(`${globSearchRoot}**/*.java`, null, 1000, null)).map(uri => uri.toString());
const mpids = analytics.getIDs(context);
// Options to control the language client
/** @type {import('vscode-languageclient').LanguageClientOptions} */
let clientOptions = {
@@ -53,8 +57,11 @@ async function createLanguageClient(context) {
initializationOptions: {
// extensionPath points to the root of the extension (the folder where this file is)
extensionPath: context.extensionPath,
mpuid: mpids.uid,
mpsid: mpids.sid,
initialSettings: config,
sourceFiles,
vscodeVersion: vscode.version,
workspaceFolders: (vscode.workspace.workspaceFolders || []).map(z => z.uri.toString()),
},
synchronize: {
@@ -107,6 +114,9 @@ function activate(context) {
/* Only the logcat stuff is configured here. The debugger is launched from src/debugMain.js */
AndroidContentProvider.register(context, vscode.workspace);
const mpids = analytics.getIDs(context);
analytics.init(undefined, mpids.uid, mpids.sid, package_json, { vscode_version: vscode.version });
createLanguageClient(context).then(client => {
languageClient = client;
refreshLanguageServerEnabledState();

134
langserver/analytics.js Normal file
View File

@@ -0,0 +1,134 @@
let mp;
/** @type {string} */
let uid;
/** @type {string} */
let sid;
/** @type {Map<string,[number,number]>} */
const timeLabels = new Map();
let session_start = Date.now();
/**
* @param {string} t
* @param {string} u
* @param {string} s
* @param {{name:string,version:string}} package_json
*/
function init(t = '0cca95950055c6553804a46ce7e3df18', u, s, package_json, props) {
if (mp) {
return;
}
try {
mp = require('mixpanel').init(t);
}
catch {}
uid = u;
sid = s;
const os = require('os');
event(`${package_json.name}-start`, {
extension: package_json.name,
ext_version: package_json.version,
arch: process.arch,
cpus: os.cpus().length,
mem: (os.totalmem() / 1e6)|0,
platform: process.platform,
node_version: process.version,
release: os.release(),
...props
});
}
/**
*
* @param {string} eventName
* @param {*} [properties]
*/
function event(eventName, properties) {
if (!mp) {
return;
}
try {
if (uid) {
mp.track(eventName, {
distinct_id: uid,
session_id: sid,
session_length: Math.trunc((Date.now() - session_start) / 60e3),
...properties,
});
} else {
mp.track(eventName);
}
} catch {}
}
/**
* @param {string} label
*/
function time(label) {
if (!label || timeLabels.has(label)) {
return;
}
timeLabels.set(label, process.hrtime());
}
/**
* @param {string} label
* @param {'ns'|'us'|'ms'|'s'} time_unit
* @param {*} [additionalProps]
*/
function timeEnd(label, time_unit = 'ms', additionalProps = {}) {
if (!label) {
return;
}
const startTime = timeLabels.get(label);
timeLabels.delete(label);
if (!Array.isArray(startTime)) {
return;
}
const elapsed = process.hrtime(startTime);
const count = time_unit === 's' ? elapsed[0] : ((elapsed[0]*1e9) + elapsed[1]);
const divs = {
ns: 1, us: 1e3, ms: 1e6, s: 1
}
const props = {
[`${label}-elapsed`]: Math.trunc(count / (divs[time_unit] || 1)),
[`${label}-elapsed_unit`]: time_unit,
...additionalProps,
}
event(label, props);
}
/**
* @param {import('vscode').ExtensionContext} context
*/
function getIDs(context) {
if (!context || !context.globalState) {
return {
uid: '', sid: ''
};
}
let uuidv4 = () => {
try {
uuidv4 = require('uuid').v4;
return uuidv4();
} catch {
return '';
}
}
let u = uid || (uid = context.globalState.get('mix-panel-id'));
if (typeof u !== 'string' || u.length > 36) {
u = uid = uuidv4();
context.globalState.update('mix-panel-id', u);
}
let s = sid || (sid = uuidv4());
return {
uid: u,
sid: s,
}
}
exports.init = init;
exports.event = event;
exports.time = time;
exports.timeEnd = timeEnd;
exports.getIDs = getIDs;

View File

@@ -5,6 +5,7 @@ const { SourceType } = require('./java/source-types');
const { indexAt } = require('./document');
const { formatDoc } = require('./doc-formatter');
const { trace } = require('./logging');
const { event } = require('./analytics');
/**
* Case-insensitive sort routines
@@ -276,6 +277,8 @@ let defaultCompletionTypes = null;
/** @type {Map<string,CEIType>} */
let lastCompletionTypeMap = null;
let completionRequestCount = 0;
function initDefaultCompletionTypes(lib) {
defaultCompletionTypes = {
instances: 'this super'.split(' ').map(t => ({
@@ -365,6 +368,11 @@ async function getCompletionItems(params, liveParsers, androidLibrary) {
}
}
completionRequestCount += 1;
if ((completionRequestCount === 1) || (completionRequestCount === 5) || ((completionRequestCount % 25) === 0)) {
event('completion-requests', { comp_req_count: completionRequestCount });
}
let parsed = docinfo.parsed;
// save the typemap associated with this parsed state - we use this when resolving

View File

@@ -1,6 +1,7 @@
const fs = require('fs');
const path = require('path');
const { CEIType, loadJavaLibraryCacheFile } = require('java-mti');
const analytics = require('../analytics');
const { trace, time, timeEnd } = require('../logging');
/**
@@ -9,6 +10,7 @@ const { trace, time, timeEnd } = require('../logging');
* @returns {Promise<Map<string,CEIType>>}
*/
async function loadAndroidSystemLibrary(extensionPath, additional_libs) {
analytics.time('android-library-load');
time('android-library-load');
let library;
try {
@@ -25,6 +27,7 @@ async function loadAndroidSystemLibrary(extensionPath, additional_libs) {
library = typemap;
} finally {
timeEnd('android-library-load');
analytics.timeEnd('android-library-load', 'ms', { libs: additional_libs, typecount: library ? library.size : 0 });
}
return library;
}

View File

@@ -2,6 +2,9 @@ const { Method } = require('java-mti');
const { indexAt } = require('./document');
const { formatDoc } = require('./doc-formatter');
const { trace } = require('./logging');
const { event } = require('./analytics');
let methodsigRequestCount = 0;
/**
* Retrieve method signature information
@@ -36,6 +39,11 @@ async function getSignatureHelp(request, liveParsers) {
// wait for any active edits to complete
await docinfo.reparseWaiter;
methodsigRequestCount += 1;
if ((methodsigRequestCount === 1) || (methodsigRequestCount === 5) || ((methodsigRequestCount % 25) === 0)) {
event('method-sig-requests', { methsig_req_count: methodsigRequestCount });
}
// locate the token at the requested position
const index = indexAt(request.position, docinfo.content);
const token = docinfo.parsed.unit.getTokenAt(index);

View File

@@ -301,6 +301,11 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"uuid": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz",
"integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q=="
},
"vscode-jsonrpc": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz",

View File

@@ -11,6 +11,7 @@
"dependencies": {
"java-mti": "adelphes/java-mti#d0e1e45",
"mixpanel": "^0.11.0",
"uuid": "^8.2.0",
"vscode-languageserver": "6.1.1",
"vscode-languageserver-textdocument": "1.0.1",
"vscode-uri": "2.1.2"

View File

@@ -19,6 +19,10 @@ const { clearDefaultCompletionEntries, getCompletionItems, resolveCompletionItem
const { getSignatureHelp } = require('./method-signatures');
const { FileURIMap, JavaDocInfo, indexAt, reparse } = require('./document');
const { v4: uuidv4 } = require('uuid');
const analytics = require('./analytics');
const package_json = require('./package.json');
/**
* The global map of Android system types
* @typedef {Map<string, CEIType>} AndroidLibrary
@@ -131,6 +135,7 @@ connection.onInitialize((params) => {
}
Settings.set(startupOpts.initialSettings);
analytics.init(undefined, startupOpts.mpuid, uuidv4(), package_json, { vscode_version: startupOpts.vscodeVersion });
loadCodeCompletionLibrary(startupOpts.extensionPath, Settings.codeCompletionLibraries);
@@ -215,6 +220,14 @@ connection.onDidChangeConfiguration(async (change) => {
Settings.set(newSettings);
if (Settings.updateCount > 2) {
analytics.event('ls-settings-changed', {
appSourceRoot: Settings.appSourceRoot,
libs: Settings.codeCompletionLibraries,
trace: Settings.trace,
})
}
const new_ccl = [...new Set(Settings.codeCompletionLibraries)].sort();
if (new_ccl.length !== prev_ccl.length || new_ccl.find((lib,idx) => lib !== prev_ccl[idx])) {
// code-completion libraries have changed - reload the android library