mirror of
https://github.com/Microsoft/vscode
synced 2024-09-20 19:18:03 +00:00
wip: telemetry service in shared process
This commit is contained in:
parent
1f74e3c545
commit
261f0f1cf6
6
src/bootstrap.js
vendored
6
src/bootstrap.js
vendored
|
@ -97,9 +97,9 @@ var writable = new stream.Writable({
|
||||||
write: function () { /* No OP */ }
|
write: function () { /* No OP */ }
|
||||||
});
|
});
|
||||||
|
|
||||||
process.__defineGetter__('stdout', function() { return writable; });
|
// process.__defineGetter__('stdout', function() { return writable; });
|
||||||
process.__defineGetter__('stderr', function() { return writable; });
|
// process.__defineGetter__('stderr', function() { return writable; });
|
||||||
process.__defineGetter__('stdin', function() { return writable; });
|
// process.__defineGetter__('stdin', function() { return writable; });
|
||||||
|
|
||||||
// Handle uncaught exceptions
|
// Handle uncaught exceptions
|
||||||
process.on('uncaughtException', function (err) {
|
process.on('uncaughtException', function (err) {
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
import {TPromise} from 'vs/base/common/winjs.base';
|
import {TPromise} from 'vs/base/common/winjs.base';
|
||||||
import {IServer} from 'vs/base/parts/ipc/common/ipc';
|
import {IServer} from 'vs/base/parts/ipc/common/ipc';
|
||||||
import {AIAdapter} from './aiAdapter';
|
import {AppInsightsAppender} from 'vs/platform/telemetry/node/aiAdapter';
|
||||||
import {IAIChannel} from './ai.ipc';
|
import {IAIChannel} from './ai.ipc';
|
||||||
|
|
||||||
const adapter: { [handle: number]: AIAdapter } = Object.create(null);
|
const adapter: { [handle: number]: AppInsightsAppender } = Object.create(null);
|
||||||
let idPool = 0;
|
let idPool = 0;
|
||||||
|
|
||||||
export function registerAIChannel(server: IServer) {
|
export function registerAIChannel(server: IServer) {
|
||||||
|
@ -20,7 +20,7 @@ export function registerAIChannel(server: IServer) {
|
||||||
case 'create': {
|
case 'create': {
|
||||||
let handle = idPool++;
|
let handle = idPool++;
|
||||||
let {key, eventPrefix, data} = arg;
|
let {key, eventPrefix, data} = arg;
|
||||||
adapter[handle] = new AIAdapter(eventPrefix, data, key);
|
adapter[handle] = new AppInsightsAppender(eventPrefix, data, key);
|
||||||
return TPromise.as(handle);
|
return TPromise.as(handle);
|
||||||
}
|
}
|
||||||
case 'log': {
|
case 'log': {
|
||||||
|
|
|
@ -19,6 +19,11 @@ import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/comm
|
||||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||||
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
||||||
|
|
||||||
|
import product from 'vs/platform/product';
|
||||||
|
import { ITelemetryAppender, combinedAppender, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||||
|
import { AppInsightsAppender } from 'vs/platform/telemetry/node/aiAdapter';
|
||||||
|
|
||||||
function quit(err?: Error) {
|
function quit(err?: Error) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -40,6 +45,23 @@ function setupPlanB(parentPid: number): void {
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const eventPrefix = 'monacoworkbench';
|
||||||
|
|
||||||
|
function createAppender(): ITelemetryAppender {
|
||||||
|
const result: ITelemetryAppender[] = [];
|
||||||
|
const { key, asimovKey } = product.aiConfig;
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
result.push(new AppInsightsAppender(eventPrefix, null, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asimovKey) {
|
||||||
|
result.push(new AppInsightsAppender(eventPrefix, null, asimovKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
return combinedAppender(...result);
|
||||||
|
}
|
||||||
|
|
||||||
function main(server: Server): void {
|
function main(server: Server): void {
|
||||||
const services = new ServiceCollection();
|
const services = new ServiceCollection();
|
||||||
|
|
||||||
|
@ -50,13 +72,31 @@ function main(server: Server): void {
|
||||||
const instantiationService = new InstantiationService(services);
|
const instantiationService = new InstantiationService(services);
|
||||||
|
|
||||||
instantiationService.invokeFunction(accessor => {
|
instantiationService.invokeFunction(accessor => {
|
||||||
const extensionManagementService = accessor.get(IExtensionManagementService);
|
const { appRoot, extensionsPath } = accessor.get(IEnvironmentService);
|
||||||
const channel = new ExtensionManagementChannel(extensionManagementService);
|
|
||||||
server.registerChannel('extensions', channel);
|
|
||||||
registerAIChannel(server);
|
|
||||||
|
|
||||||
// eventually clean up old extensions
|
const appender = createAppender();
|
||||||
setTimeout(() => (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(), 5000);
|
const config: ITelemetryServiceConfig = {
|
||||||
|
appender,
|
||||||
|
commonProperties: TPromise.as({}), //resolveCommonProperties(storageService, contextService),
|
||||||
|
piiPaths: [appRoot, extensionsPath]
|
||||||
|
};
|
||||||
|
|
||||||
|
const services = new ServiceCollection();
|
||||||
|
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
|
||||||
|
const instantiationService2 = instantiationService.createChild(services);
|
||||||
|
|
||||||
|
instantiationService2.invokeFunction(accessor => {
|
||||||
|
const telemetryService = accessor.get(ITelemetryService);
|
||||||
|
console.log(telemetryService);
|
||||||
|
|
||||||
|
const extensionManagementService = accessor.get(IExtensionManagementService);
|
||||||
|
const channel = new ExtensionManagementChannel(extensionManagementService);
|
||||||
|
server.registerChannel('extensions', channel);
|
||||||
|
registerAIChannel(server);
|
||||||
|
|
||||||
|
// eventually clean up old extensions
|
||||||
|
setTimeout(() => (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(), 5000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const IEnvironmentService = createDecorator<IEnvironmentService>('environ
|
||||||
export interface IEnvironmentService {
|
export interface IEnvironmentService {
|
||||||
serviceId: ServiceIdentifier<any>;
|
serviceId: ServiceIdentifier<any>;
|
||||||
|
|
||||||
|
appRoot: string;
|
||||||
userDataPath: string;
|
userDataPath: string;
|
||||||
extensionsPath: string;
|
extensionsPath: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,15 @@ import pkg from 'vs/platform/package';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { parseArgs } from 'vs/code/node/argv';
|
import { parseArgs } from 'vs/code/node/argv';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
|
||||||
export class EnvironmentService implements IEnvironmentService {
|
export class EnvironmentService implements IEnvironmentService {
|
||||||
|
|
||||||
serviceId = IEnvironmentService;
|
serviceId = IEnvironmentService;
|
||||||
|
|
||||||
|
private _appRoot: string;
|
||||||
|
get appRoot(): string { return this._appRoot; }
|
||||||
|
|
||||||
private _userDataPath: string;
|
private _userDataPath: string;
|
||||||
get userDataPath(): string { return this._userDataPath; }
|
get userDataPath(): string { return this._userDataPath; }
|
||||||
|
|
||||||
|
@ -24,6 +28,7 @@ export class EnvironmentService implements IEnvironmentService {
|
||||||
constructor() {
|
constructor() {
|
||||||
const argv = parseArgs(process.argv);
|
const argv = parseArgs(process.argv);
|
||||||
|
|
||||||
|
this._appRoot = path.dirname(URI.parse(require.toUrl('')).fsPath);
|
||||||
this._userDataPath = paths.getUserDataPath(process.platform, pkg.name, process.argv);
|
this._userDataPath = paths.getUserDataPath(process.platform, pkg.name, process.argv);
|
||||||
|
|
||||||
const userHome = path.join(os.homedir(), product.dataFolderName);
|
const userHome = path.join(os.homedir(), product.dataFolderName);
|
||||||
|
|
42
src/vs/platform/telemetry/common/telemetryIpc.ts
Normal file
42
src/vs/platform/telemetry/common/telemetryIpc.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||||
|
import { ITelemetryAppender } from './telemetry';
|
||||||
|
|
||||||
|
export interface ITelemetryLog {
|
||||||
|
eventName: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITelemetryAppenderChannel extends IChannel {
|
||||||
|
call(command: 'log', data: ITelemetryLog): TPromise<void>;
|
||||||
|
call(command: string, arg: any): TPromise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelemetryAppenderChannel implements ITelemetryAppenderChannel {
|
||||||
|
|
||||||
|
constructor(private appender: ITelemetryAppender) { }
|
||||||
|
|
||||||
|
call(command: string, { eventName, data }: ITelemetryLog): TPromise<any> {
|
||||||
|
return this.appender.log(eventName, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelemetryAppenderClient implements ITelemetryAppender {
|
||||||
|
|
||||||
|
constructor(private channel: ITelemetryAppenderChannel) { }
|
||||||
|
|
||||||
|
log(eventName: string, data?: any): any {
|
||||||
|
return this.channel.call('log', { eventName, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): any {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,36 +8,34 @@ import * as appInsights from 'applicationinsights';
|
||||||
import {isObject} from 'vs/base/common/types';
|
import {isObject} from 'vs/base/common/types';
|
||||||
import {safeStringify, mixin} from 'vs/base/common/objects';
|
import {safeStringify, mixin} from 'vs/base/common/objects';
|
||||||
import {TPromise} from 'vs/base/common/winjs.base';
|
import {TPromise} from 'vs/base/common/winjs.base';
|
||||||
|
import {ITelemetryAppender} from '../common/telemetry';
|
||||||
|
|
||||||
namespace AI {
|
let _initialized = false;
|
||||||
|
|
||||||
let _initialized = false;
|
function ensureAIEngineIsInitialized(): void {
|
||||||
|
if (_initialized === false) {
|
||||||
|
// we need to pass some fake key, otherwise AI throws an exception
|
||||||
|
appInsights.setup('2588e01f-f6c9-4cd6-a348-143741f8d702')
|
||||||
|
.setAutoCollectConsole(false)
|
||||||
|
.setAutoCollectExceptions(false)
|
||||||
|
.setAutoCollectPerformance(false)
|
||||||
|
.setAutoCollectRequests(false);
|
||||||
|
|
||||||
function ensureAIEngineIsInitialized(): void {
|
_initialized = true;
|
||||||
if (_initialized === false) {
|
|
||||||
// we need to pass some fake key, otherwise AI throws an exception
|
|
||||||
appInsights.setup('2588e01f-f6c9-4cd6-a348-143741f8d702')
|
|
||||||
.setAutoCollectConsole(false)
|
|
||||||
.setAutoCollectExceptions(false)
|
|
||||||
.setAutoCollectPerformance(false)
|
|
||||||
.setAutoCollectRequests(false);
|
|
||||||
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getClient(aiKey: string): typeof appInsights.client {
|
function getClient(aiKey: string): typeof appInsights.client {
|
||||||
|
|
||||||
ensureAIEngineIsInitialized();
|
ensureAIEngineIsInitialized();
|
||||||
|
|
||||||
const client = appInsights.getClient(aiKey);
|
const client = appInsights.getClient(aiKey);
|
||||||
client.channel.setOfflineMode(true);
|
client.channel.setOfflineMode(true);
|
||||||
client.context.tags[client.context.keys.deviceMachineName] = ''; //prevent App Insights from reporting machine name
|
client.context.tags[client.context.keys.deviceMachineName] = ''; //prevent App Insights from reporting machine name
|
||||||
if (aiKey.indexOf('AIF-') === 0) {
|
if (aiKey.indexOf('AIF-') === 0) {
|
||||||
client.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1';
|
client.config.endpointUrl = 'https://vortex.data.microsoft.com/collect/v1';
|
||||||
}
|
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Properties {
|
interface Properties {
|
||||||
|
@ -48,7 +46,7 @@ interface Measurements {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AIAdapter {
|
export class AppInsightsAppender implements ITelemetryAppender {
|
||||||
|
|
||||||
private _aiClient: typeof appInsights.client;
|
private _aiClient: typeof appInsights.client;
|
||||||
|
|
||||||
|
@ -62,7 +60,7 @@ export class AIAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof aiKeyOrClientFactory === 'string') {
|
if (typeof aiKeyOrClientFactory === 'string') {
|
||||||
this._aiClient = AI.getClient(aiKeyOrClientFactory);
|
this._aiClient = getClient(aiKeyOrClientFactory);
|
||||||
} else if (typeof aiKeyOrClientFactory === 'function') {
|
} else if (typeof aiKeyOrClientFactory === 'function') {
|
||||||
this._aiClient = aiKeyOrClientFactory();
|
this._aiClient = aiKeyOrClientFactory();
|
||||||
}
|
}
|
||||||
|
@ -74,7 +72,7 @@ export class AIAdapter {
|
||||||
const measurements: Measurements = Object.create(null);
|
const measurements: Measurements = Object.create(null);
|
||||||
|
|
||||||
const flat = Object.create(null);
|
const flat = Object.create(null);
|
||||||
AIAdapter._flaten(data, flat);
|
AppInsightsAppender._flaten(data, flat);
|
||||||
|
|
||||||
for (let prop in flat) {
|
for (let prop in flat) {
|
||||||
// enforce property names less than 150 char, take the last 150 char
|
// enforce property names less than 150 char, take the last 150 char
|
||||||
|
@ -120,7 +118,7 @@ export class AIAdapter {
|
||||||
|
|
||||||
} else if (isObject(value)) {
|
} else if (isObject(value)) {
|
||||||
if (order < 2) {
|
if (order < 2) {
|
||||||
AIAdapter._flaten(value, result, order + 1, index + '.');
|
AppInsightsAppender._flaten(value, result, order + 1, index + '.');
|
||||||
} else {
|
} else {
|
||||||
result[index] = safeStringify(value);
|
result[index] = safeStringify(value);
|
||||||
}
|
}
|
||||||
|
@ -135,7 +133,7 @@ export class AIAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data = mixin(data, this._defaultData);
|
data = mixin(data, this._defaultData);
|
||||||
let {properties, measurements} = AIAdapter._getData(data);
|
let {properties, measurements} = AppInsightsAppender._getData(data);
|
||||||
this._aiClient.trackEvent(this._eventPrefix + '/' + eventName, properties, measurements);
|
this._aiClient.trackEvent(this._eventPrefix + '/' + eventName, properties, measurements);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import {AIAdapter} from 'vs/base/parts/ai/node/aiAdapter';
|
import {AppInsightsAppender} from 'vs/platform/telemetry/node/aiAdapter';
|
||||||
|
|
||||||
interface IAppInsightsEvent {
|
interface IAppInsightsEvent {
|
||||||
eventName: string;
|
eventName: string;
|
||||||
|
@ -41,12 +41,12 @@ class AppInsightsMock {
|
||||||
|
|
||||||
suite('AIAdapter', () => {
|
suite('AIAdapter', () => {
|
||||||
var appInsightsMock: AppInsightsMock;
|
var appInsightsMock: AppInsightsMock;
|
||||||
var adapter: AIAdapter;
|
var adapter: AppInsightsAppender;
|
||||||
var prefix = 'prefix';
|
var prefix = 'prefix';
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
appInsightsMock = new AppInsightsMock();
|
appInsightsMock = new AppInsightsMock();
|
||||||
adapter = new AIAdapter(prefix, undefined, () => appInsightsMock);
|
adapter = new AppInsightsAppender(prefix, undefined, () => appInsightsMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
teardown(() => {
|
teardown(() => {
|
||||||
|
@ -61,7 +61,7 @@ suite('AIAdapter', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addional data', () => {
|
test('addional data', () => {
|
||||||
adapter = new AIAdapter(prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock);
|
adapter = new AppInsightsAppender(prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock);
|
||||||
adapter.log('testEvent');
|
adapter.log('testEvent');
|
||||||
|
|
||||||
assert.equal(appInsightsMock.events.length, 1);
|
assert.equal(appInsightsMock.events.length, 1);
|
Loading…
Reference in a new issue