-
-
Notifications
You must be signed in to change notification settings - Fork 50
implement platform info #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fa64b69
4373c47
d9403c1
9ace79d
5423611
931a9d9
58d9927
d8ba4c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| import { GetPlatformMetricsOptions, Machine, OS, PlatformMetrics } from './types.js' | ||
| import { runtime as jsRuntime, type JSRuntime } from './utils.js' | ||
|
|
||
| const loadNodeOS = async (jsRuntime: JSRuntime, g: typeof globalThis = globalThis) => { | ||
| return ['bun', 'deno', 'node'].includes(jsRuntime) | ||
| ? await import('node:os') | ||
| : { | ||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
| cpus: typeof g.navigator?.hardwareConcurrency === 'number' | ||
| ? () => { | ||
| return Array | ||
| .from( | ||
| { length: (g.navigator as unknown as { hardwareConcurrency: number }).hardwareConcurrency }, | ||
| () => ({ model: 'unknown', speed: -1 }) | ||
| ) | ||
| } | ||
| : () => ([]), | ||
| freemem: () => -1, | ||
| getPriority: () => -1, | ||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-deprecated | ||
| machine: typeof g.navigator?.platform === 'string' | ||
| ? () => normalizeMachine(g.navigator.platform.split(' ')[1]) // eslint-disable-line @typescript-eslint/no-deprecated | ||
| : () => 'unknown', | ||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-deprecated | ||
| platform: typeof g.navigator?.platform === 'string' | ||
| ? () => normalizeMachine(g.navigator.platform.split(' ')[0]) // eslint-disable-line @typescript-eslint/no-deprecated | ||
|
||
| : () => 'unknown', | ||
| release: () => 'unknown', | ||
| totalmem: typeof (g as unknown as { navigator?: { deviceMemory: number } }).navigator?.deviceMemory === 'number' | ||
| ? () => (g as unknown as { navigator: { deviceMemory: number } }).navigator.deviceMemory * 2 ** 30 | ||
| : () => -1, | ||
| } | ||
| } | ||
|
|
||
| /* eslint-disable */ | ||
| const machineLookup: { [key: string]: Machine } = { | ||
| // @ts-ignore __proto__ makes the object null-prototyped and sets it in dictionary mode | ||
| __proto__: null, | ||
| ia32: "x32", | ||
| amd64: "x64", | ||
| x86_64: "x64", | ||
| } | ||
| /* eslint-enable */ | ||
|
|
||
| /** | ||
| * @param machine - a value to normalize | ||
| * @returns normalized architecture | ||
| */ | ||
| export function normalizeMachine (machine?: unknown): Machine { | ||
| return typeof machine !== 'string' || machine.length === 0 | ||
| ? 'unknown' | ||
| : ((machine = machine.toLowerCase()) && (machineLookup[machine as Machine] ?? machine)) as Machine | ||
| } | ||
|
|
||
| const osLookup: Record<Lowercase<string>, OS> = { | ||
| // @ts-expect-error __proto__ makes the object null-prototyped and sets it in dictionary mode | ||
| __proto__: null, | ||
| windows: 'win32', | ||
| } | ||
|
|
||
| let cachedPlatformMetrics: null | PlatformMetrics = null | ||
|
|
||
| /** | ||
| * @param opts - Options object | ||
| * @returns platform metrics | ||
| */ | ||
| export async function getPlatformMetrics (opts: GetPlatformMetricsOptions = {}): Promise<PlatformMetrics> { | ||
| const { | ||
| g = globalThis, | ||
| runtime = jsRuntime, | ||
| useCache = true | ||
| } = opts | ||
| if (useCache && cachedPlatformMetrics !== null) { | ||
| return cachedPlatformMetrics | ||
| } | ||
| const userAgent = (g as unknown as { navigator?: { userAgent: string } }).navigator?.userAgent ?? '' | ||
|
|
||
| let cpuCores = -1 | ||
| let cpuModel = 'unknown' | ||
| let cpuSpeed = -1 | ||
| let osKernel = 'unknown' | ||
| let osType: OS = 'unknown' | ||
| let cpuMachine: Machine = 'unknown' | ||
| let priority: null | number = -1 | ||
| let memoryTotal = -1 | ||
| let memoryFree = -1 | ||
|
|
||
| const nodeOs = await loadNodeOS(runtime, g) | ||
|
|
||
| try { | ||
| osType = normalizeOSType(nodeOs.platform()) | ||
| cpuMachine = normalizeMachine(nodeOs.machine()) | ||
| osKernel = nodeOs.release() | ||
| memoryTotal = nodeOs.totalmem() | ||
| memoryFree = nodeOs.freemem() | ||
| priority = nodeOs.getPriority() | ||
|
|
||
| cpuCores = nodeOs.cpus().length | ||
| if (cpuCores > 0) { | ||
| cpuModel = (nodeOs as unknown as { cpus: () => [{ model: string }, ...{ model: string }[]] }).cpus()[0].model | ||
| cpuSpeed = (nodeOs as unknown as { cpus: () => [{ speed: number }, ...{ speed: number }[]] }).cpus()[0].speed | ||
| } | ||
| } catch { /* ignore */ } | ||
|
|
||
| return (cachedPlatformMetrics = { | ||
| cpuCores, | ||
| cpuMachine, | ||
| cpuModel, | ||
| cpuSpeed, | ||
| memoryFree, | ||
| memoryTotal, | ||
| osKernel, | ||
| osType, | ||
| priority, | ||
| runtime, | ||
| userAgent | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * @param os - a value to normalize | ||
| * @returns normalized OS | ||
| */ | ||
| export function normalizeOSType (os?: unknown): OS { | ||
| return typeof os !== 'string' || os.length === 0 | ||
| ? 'unknown' | ||
| : ((os = os.toLowerCase()) && (osLookup[os as OS] ?? os)) as OS | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -232,6 +232,12 @@ export interface FnReturnedObject { | |||||||
| overriddenDuration?: number | ||||||||
| } | ||||||||
|
|
||||||||
| export interface GetPlatformMetricsOptions { | ||||||||
| g?: typeof globalThis; | ||||||||
| runtime?: JSRuntime; | ||||||||
| useCache?: boolean; | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * The hook function signature. | ||||||||
| * If warmup is enabled, the hook will be called twice, once for the warmup and once for the run. | ||||||||
|
|
@@ -243,6 +249,34 @@ export type Hook = ( | |||||||
| mode?: 'run' | 'warmup' | ||||||||
| ) => Promise<void> | void | ||||||||
|
|
||||||||
| export type Machine = (Lowercase<string> & Record<never, never>) | ( | ||||||||
| | 'arm64' | ||||||||
| | 'arm' | ||||||||
| | 'i686' | ||||||||
|
||||||||
| | 'ia32' | ||||||||
|
||||||||
| | 'ia32' |
Copilot
AI
Nov 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The Machine type includes 'ia32' but the normalization logic maps 'ia32' to 'x32'. Consider whether 'ia32' should be in the union type if it's always normalized to 'x32'.
| | 'ia32' |
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Machine type includes 'mips64' but the test file only covers 'mips' and 'mipsel'. While 'mips64' may be a valid architecture, consider adding test coverage for it in test/platform-normalize-arch.test.ts to ensure it's handled correctly.
Copilot
AI
Nov 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The Machine type includes 'mips64' but there's no corresponding entry in the test file test/platform-normalize-arch.test.ts (which tests 'mips' and 'mipsel'). Consider adding test coverage for 'mips64' if it's a supported architecture.
Copilot
AI
Nov 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Machine type includes 'x86_64' as a valid value, but the machineLookup in src/platform.ts maps 'x86_64' to 'x64' (line 39). This means 'x86_64' will never be returned by normalizeMachine(). Consider removing 'x86_64' from the union type since it's normalized to 'x64', or document why it's included.
| | 's390x' | |
| | 'x86_64') | |
| | 's390x') |
Copilot
AI
Nov 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The Machine type includes 'x86_64' but the normalization logic maps it to 'x64'. Consider whether 'x86_64' should be in the union type if it's always normalized to 'x64'.
| | 's390x' | |
| | 'x86_64') | |
| | 's390x') |
Copilot
AI
Nov 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Omit excludes all known runtime values ('browser', 'bun', 'deno', 'node'), which would result in never. This makes PlatformMetricsBase unusable since the runtime field would have no valid values.
| runtime: Omit<JSRuntime, 'browser' | 'bun' | 'deno' | 'node'>; | |
| runtime: JSRuntime; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { expect, test } from 'vitest' | ||
|
|
||
| import { getPlatformMetrics } from '../src/platform' | ||
|
|
||
| test('platform metrics', async () => { | ||
| const metrics = await getPlatformMetrics({ useCache: false }) | ||
| expect(metrics).toHaveProperty('osType') | ||
| expect(metrics).toHaveProperty('cpuMachine') | ||
| }) |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||
| import { expect, test } from 'vitest' | ||||
|
|
||||
| import { normalizeMachine } from '../src/platform' | ||||
|
|
||||
| test('normalizeArch with non string value returns unknown', () => { | ||||
| expect(normalizeMachine(undefined)).toBe('unknown') | ||||
| expect(normalizeMachine(123)).toBe('unknown') | ||||
| expect(normalizeMachine(null)).toBe('unknown') | ||||
| expect(normalizeMachine({})).toBe('unknown') | ||||
| expect(normalizeMachine([])).toBe('unknown') | ||||
| }) | ||||
|
|
||||
| test('normalizeArch', () => { | ||||
| expect(normalizeMachine('arm')).toBe('arm') | ||||
| expect(normalizeMachine('arm64')).toBe('arm64') | ||||
| expect(normalizeMachine('ia32')).toBe('x32') | ||||
| expect(normalizeMachine('loong64')).toBe('loong64') | ||||
| expect(normalizeMachine('mips')).toBe('mips') | ||||
| expect(normalizeMachine('mipsel')).toBe('mipsel') | ||||
| expect(normalizeMachine('ppc64')).toBe('ppc64') | ||||
| expect(normalizeMachine('riscv64')).toBe('riscv64') | ||||
| expect(normalizeMachine('s390x')).toBe('s390x') | ||||
| expect(normalizeMachine('x64')).toBe('x64') | ||||
| }) | ||||
|
|
||||
| test('normalizeArch with alternative values', () => { | ||||
| expect(normalizeMachine('ia32')).toBe('x32') | ||||
|
||||
| expect(normalizeMachine('ia32')).toBe('x32') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { expect, test } from 'vitest' | ||
|
|
||
| import { normalizeOSType } from '../src/platform' | ||
|
|
||
| test('normalizeOS with non string value returns unknown', () => { | ||
| expect(normalizeOSType(undefined)).toBe('unknown') | ||
| expect(normalizeOSType(123)).toBe('unknown') | ||
| expect(normalizeOSType(null)).toBe('unknown') | ||
| expect(normalizeOSType({})).toBe('unknown') | ||
| expect(normalizeOSType([])).toBe('unknown') | ||
| }) | ||
|
|
||
| test('normalizeOS defaults provided by node', () => { | ||
| expect(normalizeOSType('aix')).toBe('aix') | ||
| expect(normalizeOSType('android')).toBe('android') | ||
| expect(normalizeOSType('darwin')).toBe('darwin') | ||
| expect(normalizeOSType('freebsd')).toBe('freebsd') | ||
| expect(normalizeOSType('haiku')).toBe('haiku') | ||
| expect(normalizeOSType('linux')).toBe('linux') | ||
| expect(normalizeOSType('openbsd')).toBe('openbsd') | ||
| expect(normalizeOSType('sunos')).toBe('sunos') | ||
| expect(normalizeOSType('win32')).toBe('win32') | ||
| expect(normalizeOSType('cygwin')).toBe('cygwin') | ||
| expect(normalizeOSType('netbsd')).toBe('netbsd') | ||
| }) | ||
|
|
||
| test('normalizeOS returns lowercase', () => { | ||
| expect(normalizeOSType('Linux')).toBe('linux') | ||
| expect(normalizeOSType('SunOS')).toBe('sunos') | ||
| }) | ||
|
|
||
| test('normalizeOS with alternative Windows values', () => { | ||
| expect(normalizeOSType('Windows')).toBe('win32') | ||
| expect(normalizeOSType('Win16')).toBe('win16') | ||
| expect(normalizeOSType('Win32')).toBe('win32') | ||
| expect(normalizeOSType('WinCE')).toBe('wince') | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.