Skip to content

Commit 6bdb662

Browse files
committed
Report correct memory use on Linux & macOS
1 parent 169b332 commit 6bdb662

File tree

6 files changed

+105
-15
lines changed

6 files changed

+105
-15
lines changed

src/backend/utils/os/processes/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface SpawnWrapperReturn {
1414

1515
async function genericSpawnWrapper(
1616
command: string,
17-
args: string[],
17+
args: string[] = [],
1818
options: SpawnWrapperOptions = {}
1919
): Promise<SpawnWrapperReturn> {
2020
const child = spawn(command, args)

src/backend/utils/systeminfo/index.ts

+6-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import process from 'process'
77
import { filesize } from 'filesize'
88

99
import { getGpuInfo } from './gpu'
10+
import { getMemoryInfo } from './memory'
1011
import { getOsInfo } from './osInfo'
1112
import { isSteamDeck } from './steamDeck'
1213
import { getHeroicVersion } from './heroicVersion'
@@ -70,16 +71,7 @@ async function getSystemInfo(cache = true): Promise<SystemInformation> {
7071
if (cache && cachedSystemInfo) return cachedSystemInfo
7172

7273
const cpus = os.cpus()
73-
const memory = process.getSystemMemoryInfo()
74-
// NOTE: Electron's memory statistics seem to be in kibibytes, not in
75-
// kilobytes (like the docs say)
76-
const totalMemory = memory.total * 1024
77-
// FIXME: This is inaccurate, since "free" RAM is not the same as "available"
78-
// RAM on Linux ("free" often only being a couple hundred MB, while
79-
// "available" can be tens of GBs)
80-
// We're probably going to have to write our own function for this that
81-
// interprets the output of the `free` command
82-
const usedMemory = (memory.total - memory.free) * 1024
74+
const memory = await getMemoryInfo()
8375
const gpus = await getGpuInfo()
8476
const detailedOsInfo = await getOsInfo()
8577
const isDeck = isSteamDeck(cpus, gpus)
@@ -97,10 +89,10 @@ async function getSystemInfo(cache = true): Promise<SystemInformation> {
9789
cores: cpus.length
9890
},
9991
memory: {
100-
total: totalMemory,
101-
used: usedMemory,
102-
totalFormatted: filesize(totalMemory, { base: 2 }) as string,
103-
usedFormatted: filesize(usedMemory, { base: 2 }) as string
92+
total: memory.total,
93+
used: memory.used,
94+
totalFormatted: filesize(memory.total, { base: 2 }) as string,
95+
usedFormatted: filesize(memory.used, { base: 2 }) as string
10496
},
10597
GPUs: gpus,
10698
OS: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { SystemInformation } from '../index'
2+
3+
type PartialMemoryInfo = Omit<
4+
SystemInformation['memory'],
5+
'totalFormatted' | 'usedFormatted'
6+
>
7+
8+
async function getMemoryInfo(): Promise<PartialMemoryInfo> {
9+
switch (process.platform) {
10+
case 'linux': {
11+
const { getMemoryInfo_linux } = await import('./linux')
12+
return getMemoryInfo_linux()
13+
}
14+
case 'win32': {
15+
const { getMemoryInfo_windows } = await import('./windows')
16+
return getMemoryInfo_windows()
17+
}
18+
case 'darwin': {
19+
const { getMemoryInfo_macos } = await import('./macos')
20+
return getMemoryInfo_macos()
21+
}
22+
default:
23+
return { total: 0, used: 0 }
24+
}
25+
}
26+
27+
export { getMemoryInfo }
28+
export type { PartialMemoryInfo }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { PartialMemoryInfo } from './index'
2+
import { genericSpawnWrapper } from '../../os/processes'
3+
4+
async function getMemoryInfo_linux(): Promise<PartialMemoryInfo> {
5+
const { stdout } = await genericSpawnWrapper('free', ['-b'])
6+
const match = stdout.match(/^\w*:\s*(\d*)\s*(\d*)/m)
7+
const totalString = match?.[1]
8+
const usedString = match?.[2]
9+
return {
10+
total: Number(totalString),
11+
used: Number(usedString)
12+
}
13+
}
14+
15+
export { getMemoryInfo_linux }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os from 'os'
2+
3+
import { genericSpawnWrapper } from '../../os/processes'
4+
5+
import type { PartialMemoryInfo } from './index'
6+
7+
const DEFAULT_PAGE_SIZE = 4096
8+
async function getPageSize(): Promise<number> {
9+
const { stdout } = await genericSpawnWrapper('sysctl', ['-n', 'vm.pagesize'])
10+
const pageSize = Number(stdout.trim())
11+
return Number.isNaN(pageSize) ? DEFAULT_PAGE_SIZE : pageSize
12+
}
13+
14+
async function getActiveAndWiredPages(): Promise<{
15+
activePages: number
16+
wiredPages: number
17+
}> {
18+
const { stdout } = await genericSpawnWrapper('vm_stat')
19+
const match = stdout.match(
20+
/^Pages active:\s*(\d*)[\s\S]*^Pages wired down:\s*(\d*)/m
21+
)
22+
const activePagesString = match?.[1]
23+
const wiredPagesString = match?.[2]
24+
if (!activePagesString || !wiredPagesString)
25+
return { activePages: 0, wiredPages: 0 }
26+
return {
27+
activePages: Number(activePagesString),
28+
wiredPages: Number(wiredPagesString)
29+
}
30+
}
31+
32+
async function getMemoryInfo_macos(): Promise<PartialMemoryInfo> {
33+
const total = os.totalmem()
34+
35+
const pageSize = await getPageSize()
36+
const { activePages, wiredPages } = await getActiveAndWiredPages()
37+
const used = activePages * pageSize + wiredPages * pageSize
38+
39+
return { total, used }
40+
}
41+
42+
export { getMemoryInfo_macos }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import process from 'process'
2+
3+
import type { PartialMemoryInfo } from './index'
4+
5+
function getMemoryInfo_windows(): PartialMemoryInfo {
6+
const { total, free } = process.getSystemMemoryInfo()
7+
return {
8+
total: total * 1024,
9+
used: (total - free) * 1024
10+
}
11+
}
12+
13+
export { getMemoryInfo_windows }

0 commit comments

Comments
 (0)