ManimCat / src /utils /process-memory.ts
Bin29's picture
Sync from main: c1ef036 chore: document docker persistence volumes
94e1b2f
import { spawn } from 'child_process'
import { promises as fs } from 'fs'
/**
* 获取进程的内存使用情况(MB)
*/
export async function getProcessMemory(pid: number): Promise<number | null> {
const platform = process.platform
if (platform === 'linux') {
return getLinuxProcessTreeMemory(pid)
}
if (platform === 'win32') {
return getWindowsProcessMemory(pid)
}
return getUnixProcessMemory(pid)
}
async function getLinuxProcessTreeMemory(pid: number): Promise<number | null> {
const visited = new Set<number>()
const queue: number[] = [pid]
let totalKb = 0
while (queue.length > 0) {
const current = queue.shift()
if (!current || visited.has(current)) {
continue
}
visited.add(current)
const rssKb = await readLinuxVmRssKb(current)
if (rssKb) {
totalKb += rssKb
}
const children = await readLinuxChildPids(current)
for (const child of children) {
if (!visited.has(child)) {
queue.push(child)
}
}
}
if (!totalKb) {
return null
}
return Math.round(totalKb / 1024)
}
async function readLinuxVmRssKb(pid: number): Promise<number | null> {
try {
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8')
const line = status.split(/\r?\n/).find((entry) => entry.startsWith('VmRSS:'))
if (!line) {
return null
}
const match = line.match(/VmRSS:\s+(\d+)/)
if (!match) {
return null
}
return parseInt(match[1], 10)
} catch {
return null
}
}
async function readLinuxChildPids(pid: number): Promise<number[]> {
try {
const children = await fs.readFile(`/proc/${pid}/task/${pid}/children`, 'utf-8')
if (!children.trim()) {
return []
}
return children
.trim()
.split(/\s+/)
.map((value) => parseInt(value, 10))
.filter((value) => !Number.isNaN(value))
} catch {
return []
}
}
interface WindowsProcessInfo {
ProcessId: number
ParentProcessId: number
WorkingSetSize: number
}
async function getWindowsProcessMemory(pid: number): Promise<number | null> {
const processList = await getWindowsProcessList()
if (processList.length === 0) {
return null
}
const byPid = new Map<number, WindowsProcessInfo>()
const childrenByParent = new Map<number, number[]>()
for (const processInfo of processList) {
byPid.set(processInfo.ProcessId, processInfo)
const children = childrenByParent.get(processInfo.ParentProcessId) || []
children.push(processInfo.ProcessId)
childrenByParent.set(processInfo.ParentProcessId, children)
}
const queue: number[] = [pid]
const visited = new Set<number>()
let totalBytes = 0
while (queue.length > 0) {
const current = queue.shift()
if (!current || visited.has(current)) {
continue
}
visited.add(current)
const processInfo = byPid.get(current)
if (processInfo && Number.isFinite(processInfo.WorkingSetSize)) {
totalBytes += processInfo.WorkingSetSize
}
const children = childrenByParent.get(current)
if (!children) {
continue
}
for (const childPid of children) {
if (!visited.has(childPid)) {
queue.push(childPid)
}
}
}
if (totalBytes <= 0) {
return null
}
return Math.round(totalBytes / 1024 / 1024)
}
async function getWindowsProcessList(): Promise<WindowsProcessInfo[]> {
return new Promise((resolve) => {
const command = spawn('powershell', [
'-NoProfile',
'-Command',
'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,WorkingSetSize | ConvertTo-Json -Compress'
])
let stdout = ''
command.stdout.on('data', (data) => {
stdout += data.toString()
})
command.on('error', () => resolve([]))
command.on('close', (code) => {
if (code !== 0 || !stdout.trim()) {
resolve([])
return
}
try {
const parsed = JSON.parse(stdout) as WindowsProcessInfo | WindowsProcessInfo[]
const list = Array.isArray(parsed) ? parsed : [parsed]
const normalized = list
.map((item) => ({
ProcessId: Number(item.ProcessId),
ParentProcessId: Number(item.ParentProcessId),
WorkingSetSize: Number(item.WorkingSetSize)
}))
.filter((item) => !Number.isNaN(item.ProcessId) && item.ProcessId > 0)
resolve(normalized)
} catch {
resolve([])
}
})
})
}
async function getUnixProcessMemory(pid: number): Promise<number | null> {
return new Promise((resolve) => {
const command = spawn('ps', ['-o', 'rss=', '-p', pid.toString()])
let stdout = ''
command.stdout.on('data', (data) => {
stdout += data.toString()
})
command.on('error', () => resolve(null))
command.on('close', () => {
const output = stdout.trim()
if (!output) {
resolve(null)
return
}
const kb = parseInt(output, 10)
if (Number.isNaN(kb)) {
resolve(null)
return
}
resolve(Math.round(kb / 1024))
})
})
}