Collect hardware details telemetry. (#16119)

This commit is contained in:
Christian Gunderman
2026-01-12 23:59:22 +00:00
committed by GitHub
parent e9c9dd1d67
commit 6ef2a92233
7 changed files with 213 additions and 30 deletions

68
package-lock.json generated
View File

@@ -2501,7 +2501,6 @@
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.2",
@@ -2682,7 +2681,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -2716,7 +2714,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -3085,7 +3082,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -3119,7 +3115,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1"
@@ -3172,7 +3167,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1",
@@ -4410,7 +4404,6 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4688,7 +4681,6 @@
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.35.0",
"@typescript-eslint/types": "8.35.0",
@@ -5700,7 +5692,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6145,7 +6136,8 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/array-includes": {
"version": "3.1.9",
@@ -7433,6 +7425,7 @@
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "5.2.1"
},
@@ -8755,7 +8748,6 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -9360,6 +9352,7 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
@@ -9369,6 +9362,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -9378,6 +9372,7 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
@@ -9631,6 +9626,7 @@
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
@@ -9649,6 +9645,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -9657,13 +9654,15 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/finalhandler/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
@@ -10947,7 +10946,6 @@
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.7.tgz",
"integrity": "sha512-QHyxhNF5VonF5cRmdAJD/UPucB9nRx3FozWMjQrDGfBxfAL9lpyu72/MlFPgloS1TMTGsOt7YN6dTPPA6mh0Aw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.2.1",
"ansi-escapes": "^7.0.0",
@@ -14136,7 +14134,8 @@
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/path-type": {
"version": "3.0.0",
@@ -14716,7 +14715,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -14727,7 +14725,6 @@
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
@@ -16547,6 +16544,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/systeminformation": {
"version": "5.30.2",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.2.tgz",
"integrity": "sha512-Rrt5oFTWluUVuPlbtn3o9ja+nvjdF3Um4DG0KxqfYvpzcx7Q9plZBTjJiJy9mAouua4+OI7IUGBaG9Zyt9NgxA==",
"license": "MIT",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos",
"android"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/table": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz",
@@ -16972,7 +16995,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -17199,8 +17221,7 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"peer": true
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.20.3",
@@ -17208,7 +17229,6 @@
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -17392,7 +17412,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -17555,6 +17574,7 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4.0"
}
@@ -17610,7 +17630,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -17727,7 +17746,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -17741,7 +17759,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -18448,7 +18465,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -18923,6 +18939,7 @@
"shell-quote": "^1.8.3",
"simple-git": "^3.28.0",
"strip-ansi": "^7.1.0",
"systeminformation": "^5.25.11",
"tree-sitter-bash": "^0.25.0",
"undici": "^7.10.0",
"uuid": "^13.0.0",
@@ -19013,7 +19030,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},

View File

@@ -64,6 +64,7 @@
"shell-quote": "^1.8.3",
"simple-git": "^3.28.0",
"strip-ansi": "^7.1.0",
"systeminformation": "^5.25.11",
"tree-sitter-bash": "^0.25.0",
"undici": "^7.10.0",
"uuid": "^13.0.0",

View File

@@ -26,6 +26,7 @@ import { makeFakeConfig } from '../../test-utils/config.js';
import { http, HttpResponse } from 'msw';
import { server } from '../../mocks/msw.js';
import {
StartSessionEvent,
UserPromptEvent,
makeChatCompressionEvent,
ModelRoutingEvent,
@@ -40,6 +41,9 @@ import { GIT_COMMIT_INFO, CLI_VERSION } from '../../generated/git-commit.js';
import { UserAccountManager } from '../../utils/userAccountManager.js';
import { InstallationManager } from '../../utils/installationManager.js';
import si from 'systeminformation';
import type { Systeminformation } from 'systeminformation';
interface CustomMatchers<R = unknown> {
toHaveMetadataValue: ([key, value]: [EventMetadataKey, string]) => R;
toHaveEventName: (name: EventNames) => R;
@@ -111,8 +115,24 @@ expect.extend({
},
});
vi.mock('node:os', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:os')>();
return {
...actual,
cpus: vi.fn(() => [{ model: 'Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz' }]),
totalmem: vi.fn(() => 32 * 1024 * 1024 * 1024),
};
});
vi.mock('../../utils/userAccountManager.js');
vi.mock('../../utils/installationManager.js');
vi.mock('systeminformation', () => ({
default: {
graphics: vi.fn().mockResolvedValue({
controllers: [{ model: 'Mock GPU' }],
}),
},
}));
const mockUserAccount = vi.mocked(UserAccountManager.prototype);
const mockInstallMgr = vi.mocked(InstallationManager.prototype);
@@ -204,6 +224,7 @@ describe('ClearcutLogger', () => {
afterEach(() => {
ClearcutLogger.clearInstance();
TEST_ONLY.resetCachedGpuInfoForTesting();
vi.useRealTimers();
vi.restoreAllMocks();
});
@@ -238,7 +259,7 @@ describe('ClearcutLogger', () => {
});
describe('createLogEvent', () => {
it('logs the total number of google accounts', () => {
it('logs the total number of google accounts', async () => {
const { logger } = setup({
lifetimeGoogleAccounts: 9001,
});
@@ -346,6 +367,73 @@ describe('ClearcutLogger', () => {
});
});
it('logs the GPU information (single GPU)', async () => {
vi.mocked(si.graphics).mockResolvedValueOnce({
controllers: [{ model: 'Single GPU' }],
} as unknown as Systeminformation.GraphicsData);
const { logger, loggerConfig } = setup({});
await logger?.logStartSessionEvent(new StartSessionEvent(loggerConfig));
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
const gpuInfoEntry = event?.event_metadata[0].find(
(item) => item.gemini_cli_key === EventMetadataKey.GEMINI_CLI_GPU_INFO,
);
expect(gpuInfoEntry).toBeDefined();
expect(gpuInfoEntry?.value).toBe('Single GPU');
});
it('logs multiple GPUs', async () => {
vi.mocked(si.graphics).mockResolvedValueOnce({
controllers: [{ model: 'GPU 1' }, { model: 'GPU 2' }],
} as unknown as Systeminformation.GraphicsData);
const { logger, loggerConfig } = setup({});
await logger?.logStartSessionEvent(new StartSessionEvent(loggerConfig));
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
const metadata = event?.event_metadata[0];
const gpuInfoEntry = metadata?.find(
(m) => m.gemini_cli_key === EventMetadataKey.GEMINI_CLI_GPU_INFO,
);
expect(gpuInfoEntry?.value).toBe('GPU 1, GPU 2');
});
it('logs NA when no GPUs are found', async () => {
vi.mocked(si.graphics).mockResolvedValueOnce({
controllers: [],
} as unknown as Systeminformation.GraphicsData);
const { logger, loggerConfig } = setup({});
await logger?.logStartSessionEvent(new StartSessionEvent(loggerConfig));
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
const metadata = event?.event_metadata[0];
const gpuInfoEntry = metadata?.find(
(m) => m.gemini_cli_key === EventMetadataKey.GEMINI_CLI_GPU_INFO,
);
expect(gpuInfoEntry?.value).toBe('NA');
});
it('logs FAILED when GPU detection fails', async () => {
vi.mocked(si.graphics).mockRejectedValueOnce(
new Error('Detection failed'),
);
const { logger, loggerConfig } = setup({});
await logger?.logStartSessionEvent(new StartSessionEvent(loggerConfig));
const event = logger?.createLogEvent(EventNames.API_ERROR, []);
expect(event?.event_metadata[0]).toContainEqual({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GPU_INFO,
value: 'FAILED',
});
});
type SurfaceDetectionTestCase = {
name: string;
env: Record<string, string | undefined>;

View File

@@ -5,6 +5,8 @@
*/
import { createHash } from 'node:crypto';
import * as os from 'node:os';
import si from 'systeminformation';
import { HttpsProxyAgent } from 'https-proxy-agent';
import type {
StartSessionEvent,
@@ -57,6 +59,7 @@ import {
isCloudShell,
} from '../../ide/detect-ide.js';
import { debugLogger } from '../../utils/debugLogger.js';
import { getErrorMessage } from '../../utils/errors.js';
export enum EventNames {
START_SESSION = 'start_session',
@@ -190,6 +193,35 @@ const MAX_EVENTS = 1000;
*/
const MAX_RETRY_EVENTS = 100;
const NO_GPU = 'NA';
let cachedGpuInfo: string | undefined;
async function refreshGpuInfo(): Promise<void> {
try {
const graphics = await si.graphics();
if (graphics.controllers && graphics.controllers.length > 0) {
cachedGpuInfo = graphics.controllers.map((c) => c.model).join(', ');
} else {
cachedGpuInfo = NO_GPU;
}
} catch (error) {
cachedGpuInfo = 'FAILED';
debugLogger.error(
'Failed to get GPU information for telemetry',
getErrorMessage(error),
);
}
}
async function getGpuInfo(): Promise<string> {
if (!cachedGpuInfo) {
await refreshGpuInfo();
}
return cachedGpuInfo ?? NO_GPU;
}
// Singleton class for batch posting log events to Clearcut. When a new event comes in, the elapsed time
// is checked and events are flushed to Clearcut if at least a minute has passed since the last flush.
export class ClearcutLogger {
@@ -321,7 +353,6 @@ export class ClearcutLogger {
const email = this.userAccountManager.getCachedGoogleAccount();
const surface = determineSurface();
const ghWorkflowName = determineGHWorkflowName();
const baseMetadata: EventValue[] = [
...data,
{
@@ -475,7 +506,7 @@ export class ClearcutLogger {
return result;
}
logStartSessionEvent(event: StartSessionEvent): void {
async logStartSessionEvent(event: StartSessionEvent): Promise<void> {
const data: EventValue[] = [
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MODEL,
@@ -564,6 +595,29 @@ export class ClearcutLogger {
value: event.extension_ids.toString(),
},
];
// Add hardware information only to the start session event
const cpus = os.cpus();
data.push(
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_CPU_INFO,
value: cpus[0].model,
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_CPU_CORES,
value: cpus.length.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_RAM_TOTAL_GB,
value: (os.totalmem() / 1024 ** 3).toFixed(2).toString(),
},
);
const gpuInfo = await getGpuInfo();
data.push({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GPU_INFO,
value: gpuInfo,
});
this.sessionData = data;
// Flush after experiments finish loading from CCPA server
@@ -1533,4 +1587,8 @@ export class ClearcutLogger {
export const TEST_ONLY = {
MAX_RETRY_EVENTS,
MAX_EVENTS,
refreshGpuInfo,
resetCachedGpuInfoForTesting: () => {
cachedGpuInfo = undefined;
},
};

View File

@@ -517,4 +517,16 @@ export enum EventMetadataKey {
// Logs the exit code of the hook script (if applicable).
GEMINI_CLI_HOOK_EXIT_CODE = 136,
// Logs CPU information of user machine.
GEMINI_CLI_CPU_INFO = 137,
// Logs number of CPU cores of user machine.
GEMINI_CLI_CPU_CORES = 138,
// Logs GPU information of user machine.
GEMINI_CLI_GPU_INFO = 139,
// Logs total RAM in GB of user machine.
GEMINI_CLI_RAM_TOTAL_GB = 140,
}

View File

@@ -111,6 +111,14 @@ import { UserAccountManager } from '../utils/userAccountManager.js';
import { InstallationManager } from '../utils/installationManager.js';
import { AgentTerminateMode } from '../agents/types.js';
vi.mock('systeminformation', () => ({
default: {
graphics: vi.fn().mockResolvedValue({
controllers: [{ model: 'Mock GPU' }],
}),
},
}));
describe('loggers', () => {
const mockLogger = {
emit: vi.fn(),

View File

@@ -79,7 +79,7 @@ export function logCliConfiguration(
config: Config,
event: StartSessionEvent,
): void {
ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
void ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
bufferTelemetryEvent(() => {
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {