mirror of https://github.com/coder/code-server.git
172 lines
7.3 KiB
Diff
172 lines
7.3 KiB
Diff
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-<version>-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 '../../base/common/event.js';
|
|
import { DisposableStore, toDisposable } from '../../base/common/lifecycle.js';
|
|
import { Schemas } from '../../base/common/network.js';
|
|
@@ -65,6 +66,7 @@ import { IExtensionsScannerService } fro
|
|
import { ExtensionsScannerService } from './extensionsScannerService.js';
|
|
import { IExtensionsProfileScannerService } from '../../platform/extensionManagement/common/extensionsProfileScannerService.js';
|
|
import { IUserDataProfilesService } from '../../platform/userDataProfile/common/userDataProfile.js';
|
|
+import { TelemetryClient } from './telemetryClient.js';
|
|
import { NullPolicyService } from '../../platform/policy/common/policy.js';
|
|
import { OneDataSystemAppender } from '../../platform/telemetry/node/1dsAppender.js';
|
|
import { LoggerService } from '../../platform/log/node/loggerService.js';
|
|
@@ -151,11 +153,23 @@ export async function setupServerService
|
|
const requestService = new RequestService(configurationService, environmentService, logService);
|
|
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<string, number | undefined>;
|
|
+ properties: Record<string, string | boolean | null | undefined>;
|
|
+}
|
|
+
|
|
+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
|
|
@@ -319,6 +319,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<IProductConfiguration>;
|
|
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",
|
|
});
|
|
}
|
|
|