Add support for telemetry endpoint To test: 1. Create a mock API using [RequestBin](https://requestbin.io/) or [Beeceptor](https://beeceptor.com/) 2. Run code-server with `CS_TELEMETRY_URL` set: i.e. `CS_TELEMETRY_URL="https://requestbin.io/1ebub9z1" ./code-server--macos-amd64/bin/code-server` NOTE: it has to be a production build. 3. Load code-server in browser an do things (i.e. open a file) 4. Refresh RequestBin and you should see logs 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 @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { hostname, release } from 'os'; +import { promises as fs } from 'fs'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -65,6 +66,7 @@ import { IExtensionsScannerService } fro import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IUserDataProfilesService } 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'; import { LoggerService } from 'vs/platform/log/node/loggerService'; @@ -147,11 +149,23 @@ export async function setupServerService const requestService = new RequestService(configurationService, environmentService, logService, loggerService); services.set(IRequestService, requestService); + let isContainer = undefined; + try { + await fs.stat('/run/.containerenv'); + isContainer = true; + } catch (error) { /* Does not exist, probably. */ } + if (!isContainer) { + try { + const content = await fs.readFile('/proc/self/cgroup', 'utf8') + isContainer = content.includes('docker'); + } catch (error) { /* Permission denied, probably. */ } + } + let oneDsAppender: ITelemetryAppender = NullAppender; const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - if (!isLoggingOnly(productService, environmentService) && productService.aiConfig?.ariaKey) { - oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, productService.aiConfig.ariaKey); + if (!isLoggingOnly(productService, environmentService) && productService.telemetryEndpoint) { + oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, () => new TelemetryClient(productService.telemetryEndpoint!, machineId, isContainer)); disposables.add(toDisposable(() => oneDsAppender?.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,71 @@ +import { AppInsightsCore, IExtendedTelemetryItem, ITelemetryItem } from '@microsoft/1ds-core-js'; +import * as https from 'https'; +import * as http from 'http'; +import * as os from 'os'; + +interface SystemInfo { + measurements: Record; + properties: Record; +} + +export class TelemetryClient extends AppInsightsCore { + private readonly systemInfo: SystemInfo = { + measurements: {}, + properties: {}, + }; + + public constructor( + private readonly endpoint: string, + machineId: string, + isContainer: boolean | undefined) { + super(); + + // os.cpus() can take a very long time sometimes (personally I see 1-2 + // seconds in a Coder workspace). This adds up significantly, especially + // when many telemetry requests are sent during startup, which can cause + // connection timeouts. Try to cache as much as we can. + try { + const cpus = os.cpus(); + this.systemInfo.measurements.cores = cpus.length; + this.systemInfo.properties['common.cpuModel'] = cpus[0].model; + } catch (error) {} + + try { + this.systemInfo.properties['common.shell'] = os.userInfo().shell; + this.systemInfo.properties['common.release'] = os.release(); + this.systemInfo.properties['common.arch'] = os.arch(); + } catch (error) {} + + this.systemInfo.properties['common.remoteMachineId'] = machineId; + this.systemInfo.properties['common.isContainer'] = isContainer; + } + + public override track(item: IExtendedTelemetryItem | ITelemetryItem): void { + const options = item.baseData || {} + options.measurements = { + ...(options.measurements || {}), + ...this.systemInfo.measurements, + } + options.properties = { + ...(options.properties || {}), + ...this.systemInfo.properties, + } + + try { + options.measurements.memoryFree = os.freemem(); + options.measurements.memoryTotal = os.totalmem(); + } 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/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 @@ -317,6 +317,8 @@ export class WebClientServer { scope: vscodeBase + '/', path: base + '/_static/out/browser/serviceWorker.js', }, + enableTelemetry: this._productService.enableTelemetry, + telemetryEndpoint: this._productService.telemetryEndpoint, embedderIdentifier: 'server-distro', extensionsGallery: this._productService.extensionsGallery, } satisfies Partial; Index: code-server/lib/vscode/src/vs/base/common/product.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/base/common/product.ts +++ code-server/lib/vscode/src/vs/base/common/product.ts @@ -64,6 +64,7 @@ export interface IProductConfiguration { readonly path: string; readonly scope: string; } + readonly telemetryEndpoint?: string readonly version: string; readonly date?: string; Index: code-server/lib/vscode/src/vs/platform/product/common/product.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/platform/product/common/product.ts +++ code-server/lib/vscode/src/vs/platform/product/common/product.ts @@ -55,7 +55,8 @@ else if (globalThis._VSCODE_PRODUCT_JSON resourceUrlTemplate: "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}", controlUrl: "", recommendationsUrl: "", - }) + }), + telemetryEndpoint: env.CS_TELEMETRY_URL || product.telemetryEndpoint || "https://v1.telemetry.coder.com/track", }); }