Add support for telemetry endpoint Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts +++ code-server/lib/vscode/src/vs/server/node/serverServices.ts @@ -68,6 +68,7 @@ import { REMOTE_TERMINAL_CHANNEL_NAME } import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteFileSystemProviderClient'; import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; +import { TelemetryClient } from "vs/server/node/telemetryClient"; const eventPrefix = 'monacoworkbench'; @@ -120,7 +121,11 @@ export async function setupServerService let appInsightsAppender: ITelemetryAppender = NullAppender; const machineId = await getMachineId(); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.asimovKey) { + const telemetryEndpoint = process.env.CS_TELEMETRY_URL || "https://v1.telemetry.coder.com/track"; + if (telemetryEndpoint) { + appInsightsAppender = new AppInsightsAppender(eventPrefix, null, () => new TelemetryClient(telemetryEndpoint) as any); + disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data + } else if (productService.aiConfig && productService.aiConfig.asimovKey) { appInsightsAppender = new AppInsightsAppender(eventPrefix, null, productService.aiConfig.asimovKey); disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } Index: code-server/lib/vscode/src/vs/server/node/telemetryClient.ts =================================================================== --- /dev/null +++ code-server/lib/vscode/src/vs/server/node/telemetryClient.ts @@ -0,0 +1,135 @@ +import * as appInsights from 'applicationinsights'; +import * as https from 'https'; +import * as http from 'http'; +import * as os from 'os'; + +class Channel { + public get _sender() { + throw new Error('unimplemented'); + } + public get _buffer() { + throw new Error('unimplemented'); + } + + public setUseDiskRetryCaching(): void { + throw new Error('unimplemented'); + } + public send(): void { + throw new Error('unimplemented'); + } + public triggerSend(): void { + throw new Error('unimplemented'); + } +} + +// Unable to use implements because TypeScript tells you a private property is +// missing but if you add it then it complains they have different private +// properties. Uncommenting it during development can be helpful though to see +// if anything is missing. +export class TelemetryClient /* implements appInsights.TelemetryClient */ { + private _telemetryProcessors: any = undefined; + public context: any = undefined; + public commonProperties: any = undefined; + public config: any = {}; + public quickPulseClient: any = undefined; + + public channel: any = new Channel(); + + public constructor(private readonly endpoint: string) { + // Nothing to do. + } + + public addTelemetryProcessor(): void { + throw new Error('unimplemented'); + } + + public clearTelemetryProcessors(): void { + if (this._telemetryProcessors) { + this._telemetryProcessors = undefined; + } + } + + public runTelemetryProcessors(): void { + throw new Error('unimplemented'); + } + + public trackTrace(): void { + throw new Error('unimplemented'); + } + + public trackMetric(): void { + throw new Error('unimplemented'); + } + + public trackException(): void { + throw new Error('unimplemented'); + } + + public trackRequest(): void { + throw new Error('unimplemented'); + } + + public trackDependency(): void { + throw new Error('unimplemented'); + } + + public track(): void { + throw new Error('unimplemented'); + } + + public trackNodeHttpRequestSync(): void { + throw new Error('unimplemented'); + } + + public trackNodeHttpRequest(): void { + throw new Error('unimplemented'); + } + + public trackNodeHttpDependency(): void { + throw new Error('unimplemented'); + } + + public trackEvent(options: appInsights.Contracts.EventTelemetry): void { + if (!options.properties) { + options.properties = {}; + } + if (!options.measurements) { + options.measurements = {}; + } + + try { + const cpus = os.cpus(); + options.measurements.cores = cpus.length; + options.properties['common.cpuModel'] = cpus[0].model; + } catch (error) {} + + try { + options.measurements.memoryFree = os.freemem(); + options.measurements.memoryTotal = os.totalmem(); + } catch (error) {} + + try { + options.properties['common.shell'] = os.userInfo().shell; + options.properties['common.release'] = os.release(); + options.properties['common.arch'] = os.arch(); + } catch (error) {} + + try { + const request = (/^http:/.test(this.endpoint) ? http : https).request(this.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + request.on('error', () => { /* We don't care. */ }); + request.write(JSON.stringify(options)); + request.end(); + } catch (error) {} + } + + public flush(options: { callback: (v: string) => void }): void { + if (options.callback) { + options.callback(''); + } + } +} Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts @@ -304,6 +304,7 @@ export class WebClientServer { logoutEndpoint: this._environmentService.args['auth'] ? base + '/logout' : undefined, proxyEndpointTemplate: base + '/proxy/{{port}}', codeServerVersion: this._productService.codeServerVersion, + enableTelemetry: this._productService.enableTelemetry, embedderIdentifier: 'server-distro', serviceWorker: { scope: vscodeBase + '/', Index: code-server/lib/vscode/src/vs/workbench/services/telemetry/browser/telemetryService.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ code-server/lib/vscode/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -119,16 +119,19 @@ export class TelemetryService extends Di ) { super(); - if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.asimovKey) { + if (supportsTelemetry(productService, environmentService)) { // If remote server is present send telemetry through that, else use the client side appender - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey); - const config: ITelemetryServiceConfig = { - appenders: [new WebTelemetryAppender(telemetryProvider), new TelemetryLogAppender(loggerService, environmentService)], - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), - sendErrorTelemetry: this.sendErrorTelemetry, - }; - - this.impl = this._register(new BaseTelemetryService(config, configurationService, productService)); + const telemetryProvider: ITelemetryAppender | undefined = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : productService.aiConfig?.asimovKey ? new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey) : undefined; + if (telemetryProvider) { + const config: ITelemetryServiceConfig = { + appenders: [new WebTelemetryAppender(telemetryProvider), new TelemetryLogAppender(loggerService, environmentService)], + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), + sendErrorTelemetry: this.sendErrorTelemetry, + }; + this.impl = this._register(new BaseTelemetryService(config, configurationService, productService)); + } else { + this.impl = NullTelemetryService; + } } else { this.impl = NullTelemetryService; }