common properties in shared proc telemetry service

This commit is contained in:
Joao Moreno 2016-06-14 15:54:39 +02:00
parent e129cd0bb8
commit 165c6be0ac
9 changed files with 191 additions and 156 deletions

View file

@ -5,6 +5,8 @@
import * as fs from 'fs';
import * as platform from 'vs/base/common/platform';
import product from 'vs/platform/product';
import pkg from 'vs/platform/package';
import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@ -19,9 +21,8 @@ import { IExtensionManagementService } from 'vs/platform/extensionManagement/com
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NodeConfigurationService } from 'vs/platform/configuration/node/nodeConfigurationService';
import product from 'vs/platform/product';
import { ITelemetryService, combinedAppender } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
@ -77,20 +78,24 @@ function main(server: Server): void {
const appender = combinedAppender(...appenders);
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appender));
const { appRoot, extensionsPath } = accessor.get(IEnvironmentService);
const config: ITelemetryServiceConfig = {
appender,
commonProperties: TPromise.as({}),
piiPaths: [appRoot, extensionsPath]
};
const services = new ServiceCollection();
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
const { appRoot, extensionsPath, extensionDevelopmentPath, isBuilt } = accessor.get(IEnvironmentService);
if (isBuilt && !extensionDevelopmentPath && product.enableTelemetry) {
const config: ITelemetryServiceConfig = {
appender,
commonProperties: resolveCommonProperties(product.commit, pkg.version),
piiPaths: [appRoot, extensionsPath]
};
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
const instantiationService2 = instantiationService.createChild(services);
instantiationService2.invokeFunction(accessor => {
// const telemetryService = accessor.get(ITelemetryService);
const extensionManagementService = accessor.get(IExtensionManagementService);
const channel = new ExtensionManagementChannel(extensionManagementService);
server.registerChannel('extensions', channel);

View file

@ -13,4 +13,6 @@ export interface IEnvironmentService {
appRoot: string;
userDataPath: string;
extensionsPath: string;
extensionDevelopmentPath: string;
isBuilt: boolean;
}

View file

@ -25,6 +25,11 @@ export class EnvironmentService implements IEnvironmentService {
private _extensionsPath: string;
get extensionsPath(): string { return this._extensionsPath; }
private _extensionDevelopmentPath: string;
get extensionDevelopmentPath(): string { return this._extensionDevelopmentPath; }
get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }
constructor() {
const argv = parseArgs(process.argv);
@ -34,5 +39,7 @@ export class EnvironmentService implements IEnvironmentService {
const userHome = path.join(os.homedir(), product.dataFolderName);
this._extensionsPath = argv.extensionHomePath || path.join(userHome, 'extensions');
this._extensionsPath = path.normalize(this._extensionsPath);
this._extensionDevelopmentPath = argv.extensionDevelopmentPath;
}
}

View file

@ -3,49 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as getmac from 'getmac';
import * as crypto from 'crypto';
import * as winreg from 'winreg';
import * as Platform from 'vs/base/common/platform';
import * as os from 'os';
import {TPromise} from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import * as uuid from 'vs/base/common/uuid';
import {IStorageService} from 'vs/platform/storage/common/storage';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
namespace StorageKeys {
export const MachineId = 'telemetry.machineId';
export const InstanceId = 'telemetry.instanceId';
export const sqmUserId: string = 'telemetry.sqm.userId';
export const sqmMachineId: string = 'telemetry.sqm.machineId';
export const lastSessionDate: string = 'telemetry.lastSessionDate';
export const firstSessionDate: string = 'telemetry.firstSessionDate';
export const SQM_KEY: string = '\\Software\\Microsoft\\SQMClient';
}
export function resolveCommonProperties(storageService: IStorageService, contextService: IWorkspaceContextService): TPromise<{ [name: string]: string }> {
let result: { [name: string]: any } = Object.create(null);
export function resolveCommonProperties(commit: string, version: string): TPromise<{ [name: string]: string; }> {
const result: { [name: string]: string; } = Object.create(null);
result['sessionID'] = uuid.generateUuid() + Date.now();
result['commitHash'] = contextService.getConfiguration().env.commitHash;
// add shell & render version
result['version'] = contextService.getConfiguration().env.version;
result['common.version.shell'] = process.versions && (<any>process).versions['electron'];
result['common.version.renderer'] = process.versions && (<any>process).versions['chrome'];
result['commitHash'] = commit;
result['version'] = version;
result['common.osVersion'] = os.release();
// session dates, first, last, isNewSession
const lastSessionDate = storageService.get(StorageKeys.lastSessionDate);
const firstSessionDate = storageService.get(StorageKeys.firstSessionDate) || new Date().toUTCString();
storageService.store(StorageKeys.firstSessionDate, firstSessionDate);
storageService.store(StorageKeys.lastSessionDate, new Date().toUTCString());
result['common.firstSessionDate'] = firstSessionDate;
result['common.lastSessionDate'] = lastSessionDate;
result['common.isNewSession'] = !lastSessionDate ? 1 : 0;
result['common.platform'] = Platform.Platform[Platform.platform];
// dynamic properties which value differs on each call
let seq = 0;
@ -59,108 +29,11 @@ export function resolveCommonProperties(storageService: IStorageService, context
get: () => Date.now() - startTime,
enumerable: true
},
'common.platform': {
get: () => Platform.Platform[Platform.platform],
enumerable: true
},
'common.sequence': {
get: () => seq++,
enumerable: true
}
});
// promise based properties
let promises: TPromise<any>[] = [];
promises.push(getOrCreateInstanceId(storageService).then(value => result['common.instanceId'] = value));
promises.push(getOrCreateMachineId(storageService).then(value => result['common.machineId'] = value));
if (process.platform === 'win32') {
promises.push(getSqmUserId(storageService).then(value => result['common.sqm.userid']= value));
promises.push(getSqmMachineId(storageService).then(value => result['common.sqm.machineid']= value));
}
return TPromise.join(promises).then(_ => result);
}
function getOrCreateInstanceId( storageService: IStorageService): TPromise<string> {
let result = storageService.get(StorageKeys.InstanceId) || uuid.generateUuid();
storageService.store(StorageKeys.InstanceId, result);
return TPromise.as(result);
}
function getOrCreateMachineId(storageService: IStorageService): TPromise<string> {
return new TPromise<string>(resolve => {
let result = storageService.get(StorageKeys.MachineId);
if (result) {
return resolve(result);
}
// add a unique machine id as a hash of the macAddress
try {
getmac.getMac((error, macAddress) => {
if (!error) {
// crypt machine id
result = crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
} else {
result = uuid.generateUuid(); // fallback, generate a UUID
}
resolve(result);
});
} catch (err) {
errors.onUnexpectedError(err);
resolve(uuid.generateUuid()); // fallback, generate a UUID
}
}).then(result => {
storageService.store(StorageKeys.MachineId, result);
return result;
});
}
function getSqmUserId(storageService: IStorageService): TPromise<string> {
var sqmUserId = storageService.get(StorageKeys.sqmUserId);
if (sqmUserId) {
return TPromise.as(sqmUserId);
}
return getWinRegKeyData(StorageKeys.SQM_KEY, 'UserId', winreg.HKCU).then(result => {
if (result) {
storageService.store(StorageKeys.sqmUserId, result);
return result;
}
});
}
function getSqmMachineId(storageService: IStorageService): TPromise<string> {
let sqmMachineId = storageService.get(StorageKeys.sqmMachineId);
if (sqmMachineId) {
return TPromise.as(sqmMachineId);
}
return getWinRegKeyData(StorageKeys.SQM_KEY, 'MachineId', winreg.HKLM).then(result => {
if (result) {
storageService.store(StorageKeys.sqmMachineId, result);
return result;
}
});
}
function getWinRegKeyData(key: string, name: string, hive: string): TPromise<string> {
return new TPromise<string>((resolve, reject) => {
if (process.platform === 'win32') {
try {
var reg = new winreg({ hive, key });
reg.get(name, (e, result) => {
if (e || !result) {
reject(null);
} else {
resolve(result.value);
}
});
} catch (err) {
errors.onUnexpectedError(err);
reject(err);
}
} else {
resolve(null);
}
}).then(undefined, err => {
// we only want success
return undefined;
});
}
}

View file

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as getmac from 'getmac';
import * as crypto from 'crypto';
import {TPromise} from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import * as uuid from 'vs/base/common/uuid';
export function getMachineId(): TPromise<string> {
return new TPromise<string>(resolve => {
try {
getmac.getMac((error, macAddress) => {
if (!error) {
resolve(crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'));
} else {
resolve(uuid.generateUuid()); // fallback, generate a UUID
}
});
} catch (err) {
errors.onUnexpectedError(err);
resolve(uuid.generateUuid()); // fallback, generate a UUID
}
});
}

View file

@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as winreg from 'winreg';
import * as os from 'os';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import * as uuid from 'vs/base/common/uuid';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { getMachineId } from '../node/machineId';
import { resolveCommonProperties } from '../node/commonProperties';
const SQM_KEY: string = '\\Software\\Microsoft\\SQMClient';
export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string): TPromise<{ [name: string]: string }> {
return resolveCommonProperties(commit, version).then(result => {
result['common.version.shell'] = process.versions && (<any>process).versions['electron'];
result['common.version.renderer'] = process.versions && (<any>process).versions['chrome'];
result['common.osVersion'] = os.release();
const lastSessionDate = storageService.get('telemetry.lastSessionDate');
const firstSessionDate = storageService.get('telemetry.firstSessionDate') || new Date().toUTCString();
storageService.store('telemetry.firstSessionDate', firstSessionDate);
storageService.store('telemetry.lastSessionDate', new Date().toUTCString());
result['common.firstSessionDate'] = firstSessionDate;
result['common.lastSessionDate'] = lastSessionDate;
result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
const promises: TPromise<any>[] = [];
promises.push(getOrCreateInstanceId(storageService).then(value => result['common.instanceId'] = value));
promises.push(getOrCreateMachineId(storageService).then(value => result['common.machineId'] = value));
if (process.platform === 'win32') {
promises.push(getSqmUserId(storageService).then(value => result['common.sqm.userid']= value));
promises.push(getSqmMachineId(storageService).then(value => result['common.sqm.machineid']= value));
}
return TPromise.join(promises).then(() => result);
});
}
function getOrCreateInstanceId(storageService: IStorageService): TPromise<string> {
let result = storageService.get('telemetry.instanceId') || uuid.generateUuid();
storageService.store('telemetry.instanceId', result);
return TPromise.as(result);
}
function getOrCreateMachineId(storageService: IStorageService): TPromise<string> {
const key = 'telemetry.machineId';
let result = storageService.get(key);
if (result) {
return TPromise.as(result);
}
return getMachineId().then(result => {
storageService.store(key, result);
return result;
});
}
function getSqmUserId(storageService: IStorageService): TPromise<string> {
var sqmUserId = storageService.get('telemetry.sqm.userId');
if (sqmUserId) {
return TPromise.as(sqmUserId);
}
return getWinRegKeyData(SQM_KEY, 'UserId', winreg.HKCU).then(result => {
if (result) {
storageService.store('telemetry.sqm.userId', result);
return result;
}
});
}
function getSqmMachineId(storageService: IStorageService): TPromise<string> {
let sqmMachineId = storageService.get('telemetry.sqm.machineId');
if (sqmMachineId) {
return TPromise.as(sqmMachineId);
}
return getWinRegKeyData(SQM_KEY, 'MachineId', winreg.HKLM).then(result => {
if (result) {
storageService.store('telemetry.sqm.machineId', result);
return result;
}
});
}
function getWinRegKeyData(key: string, name: string, hive: string): TPromise<string> {
return new TPromise<string>((resolve, reject) => {
if (process.platform === 'win32') {
try {
var reg = new winreg({ hive, key });
reg.get(name, (e, result) => {
if (e || !result) {
reject(null);
} else {
resolve(result.value);
}
});
} catch (err) {
errors.onUnexpectedError(err);
reject(err);
}
} else {
resolve(null);
}
}).then(undefined, err => {
// we only want success
return undefined;
});
}

View file

@ -6,16 +6,18 @@
import * as assert from 'assert';
import {TPromise} from 'vs/base/common/winjs.base';
import {resolveCommonProperties} from 'vs/platform/telemetry/node/commonProperties';
import {resolveWorkbenchCommonProperties} from 'vs/platform/telemetry/node/workbenchCommonProperties';
import {TestStorageService, TestContextService} from 'vs/workbench/test/common/servicesTestUtils';
suite('Telemetry - common properties', function () {
const contextService = new TestContextService();
const commit = contextService.getConfiguration().env.commitHash;
const version = contextService.getConfiguration().env.version;
test('default', function () {
return resolveCommonProperties(new TestStorageService(), new TestContextService()).then(props => {
assert.equal(Object.keys(props).length, process.platform === 'win32' ? 17 : 15);
return resolveWorkbenchCommonProperties(new TestStorageService(), commit, version).then(props => {
assert.ok('commitHash' in props);
assert.ok('sessionID' in props);
@ -40,6 +42,8 @@ suite('Telemetry - common properties', function () {
assert.ok('common.sqm.userid' in props, 'userid');
assert.ok('common.sqm.machineid' in props, 'machineid');
}
assert.equal(Object.keys(props).length, process.platform === 'win32' ? 17 : 15);
});
});
@ -48,7 +52,7 @@ suite('Telemetry - common properties', function () {
let service = new TestStorageService();
service.store('telemetry.lastSessionDate', new Date().toUTCString());
return resolveCommonProperties(service, new TestContextService()).then(props => {
return resolveWorkbenchCommonProperties(service, commit, version).then(props => {
assert.ok('common.lastSessionDate' in props); // conditional, see below
assert.ok('common.isNewSession' in props);
@ -57,7 +61,7 @@ suite('Telemetry - common properties', function () {
});
test('values chance on ask', function () {
return resolveCommonProperties(new TestStorageService(), new TestContextService()).then(props => {
return resolveWorkbenchCommonProperties(new TestStorageService(), commit, version).then(props => {
let value1 = props['common.sequence'];
let value2 = props['common.sequence'];
assert.ok(value1 !== value2, 'seq');

View file

@ -24,7 +24,7 @@ import {ITelemetryAppenderChannel,TelemetryAppenderClient} from 'vs/platform/tel
import {TelemetryService, ITelemetryServiceConfig} from 'vs/platform/telemetry/common/telemetryService';
import {IdleMonitor, UserStatus} from 'vs/platform/telemetry/browser/idleMonitor';
import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
import {resolveCommonProperties} from 'vs/platform/telemetry/node/commonProperties';
import {resolveWorkbenchCommonProperties} from 'vs/platform/telemetry/node/workbenchCommonProperties';
import {ElectronIntegration} from 'vs/workbench/electron-browser/integration';
import {Update} from 'vs/workbench/electron-browser/update';
import {WorkspaceStats} from 'vs/platform/telemetry/common/workspaceStats';
@ -225,9 +225,12 @@ export class WorkbenchShell {
// Telemetry
if (this.configuration.env.isBuilt && !this.configuration.env.extensionDevelopmentPath && !!this.configuration.env.enableTelemetry) {
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
const commit = this.contextService.getConfiguration().env.commitHash;
const version = this.contextService.getConfiguration().env.version;
const config: ITelemetryServiceConfig = {
appender: new TelemetryAppenderClient(channel),
commonProperties: resolveCommonProperties(this.storageService, this.contextService),
commonProperties: resolveWorkbenchCommonProperties(this.storageService, commit, version),
piiPaths: [this.configuration.env.appRoot, this.configuration.env.userExtensionsHome]
};

View file

@ -54,7 +54,7 @@ export class RawGitService implements IRawGitService {
if (!this.repo) {
return TPromise.as(0);
}
return this.status().then(r => r ? r.status.length : 0);
}