mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
common properties in shared proc telemetry service
This commit is contained in:
parent
e129cd0bb8
commit
165c6be0ac
|
@ -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);
|
||||
|
|
|
@ -13,4 +13,6 @@ export interface IEnvironmentService {
|
|||
appRoot: string;
|
||||
userDataPath: string;
|
||||
extensionsPath: string;
|
||||
extensionDevelopmentPath: string;
|
||||
isBuilt: boolean;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
27
src/vs/platform/telemetry/node/machineId.ts
Normal file
27
src/vs/platform/telemetry/node/machineId.ts
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
114
src/vs/platform/telemetry/node/workbenchCommonProperties.ts
Normal file
114
src/vs/platform/telemetry/node/workbenchCommonProperties.ts
Normal 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;
|
||||
});
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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]
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue