Add support for telemetry endpoint Contains some fixes included in https://github.com/microsoft/vscode/commit/b108bc8294ce920fcf2ee8d53f97c3bcf3316e1c To test: 1. Look inside a build of code-server, inside `lib/vscode/vs/server/node/server.main.js` 2. Search for a `JSON.stringify` near `TelemetryClient` 3. throw in a `console.log()` before it and make sure it logs telemetry data 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 @@ -71,6 +71,7 @@ import { IExtensionsScannerService } fro import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { TelemetryClient } from "vs/server/node/telemetryClient"; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; @@ -133,10 +134,13 @@ export async function setupServerService const machineId = await getMachineId(); const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.ariaKey) { + const telemetryEndpoint = process.env.CS_TELEMETRY_URL || "https://v1.telemetry.coder.com/track"; + if (telemetryEndpoint) { + oneDsAppender = new OneDataSystemAppender(false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint)); + } else if (productService.aiConfig && productService.aiConfig.ariaKey) { oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey); - disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } + disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data const config: ITelemetryServiceConfig = { appenders: [oneDsAppender], 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,49 @@ +import { AppInsightsCore, IExtendedTelemetryItem, ITelemetryItem } from '@microsoft/1ds-core-js'; +import * as https from 'https'; +import * as http from 'http'; +import * as os from 'os'; + +export class TelemetryClient extends AppInsightsCore { + public constructor(private readonly endpoint: string) { + super(); + } + + public override track(item: IExtendedTelemetryItem | ITelemetryItem): void { + const options = item.baseData || {} + 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) {} + } +} 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 @@ -15,7 +15,7 @@ import { ClassifiedEvent, IGDPRProperty, import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { ITelemetryServiceConfig, TelemetryService as BaseTelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { isInternalTelemetry, ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getTelemetryLevel, isInternalTelemetry, ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/browser/workbenchCommonProperties'; @@ -24,7 +24,7 @@ export class TelemetryService extends Di declare readonly _serviceBrand: undefined; - private impl: ITelemetryService; + private impl: ITelemetryService = NullTelemetryService; public readonly sendErrorTelemetry = true; constructor( @@ -37,11 +37,7 @@ export class TelemetryService extends Di ) { super(); - if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.ariaKey) { - this.impl = this.initializeService(environmentService, loggerService, configurationService, storageService, productService, remoteAgentService); - } else { - this.impl = NullTelemetryService; - } + this.impl = this.initializeService(environmentService, loggerService, configurationService, storageService, productService, remoteAgentService); // When the level changes it could change from off to on and we want to make sure telemetry is properly intialized this._register(configurationService.onDidChangeConfiguration(e => { @@ -64,23 +60,28 @@ export class TelemetryService extends Di productService: IProductService, remoteAgentService: IRemoteAgentService ) { - const telemetrySupported = supportsTelemetry(productService, environmentService) && productService.aiConfig?.ariaKey; - if (telemetrySupported && this.impl === NullTelemetryService && this.telemetryLevel.value !== TelemetryLevel.NONE) { + const telemetrySupported = supportsTelemetry(productService, environmentService); + if (telemetrySupported && getTelemetryLevel(configurationService) !== TelemetryLevel.NONE && this.impl === NullTelemetryService) { // If remote server is present send telemetry through that, else use the client side appender const appenders = []; const isInternal = isInternalTelemetry(productService, configurationService); - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(isInternal, 'monacoworkbench', null, productService.aiConfig?.ariaKey); - appenders.push(telemetryProvider); - appenders.push(new TelemetryLogAppender(loggerService, environmentService)); - const config: ITelemetryServiceConfig = { - appenders, - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, isInternal, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), - sendErrorTelemetry: this.sendErrorTelemetry, - }; + const telemetryProvider: ITelemetryAppender | undefined = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : productService.aiConfig?.ariaKey ? new OneDataSystemWebAppender(isInternal, 'monacoworkbench', null, productService.aiConfig?.ariaKey) : undefined; + if (telemetryProvider) { + appenders.push(telemetryProvider); + appenders.push(new TelemetryLogAppender(loggerService, environmentService)); + const config: ITelemetryServiceConfig = { + appenders, + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, isInternal, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), + sendErrorTelemetry: this.sendErrorTelemetry, + }; + + return this._register(new BaseTelemetryService(config, configurationService, productService)); + } else { + return this.impl; + } - return this._register(new BaseTelemetryService(config, configurationService, productService)); } - return NullTelemetryService; + return this.impl; } setExperimentProperty(name: string, value: string): void { 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 @@ -324,6 +324,7 @@ export class WebClientServer { scope: vscodeBase + '/', path: base + '/_static/out/browser/serviceWorker.js', }, + enableTelemetry: this._productService.enableTelemetry, embedderIdentifier: 'server-distro', extensionsGallery: this._productService.extensionsGallery, },