
本文介绍如何在 Go 程序中实时获取已启动子进程的内存占用(单位 KB),基于 Linux /proc/[pid]/smaps 文件解析 PSS(Proportional Set Size),精度可控、无需进程退出即可持续采样。
本文介绍如何在 go 程序中实时获取已启动子进程的内存占用(单位 kb),基于 linux `/proc/[pid]/smaps` 文件解析 pss(proportional set size),精度可控、无需进程退出即可持续采样。
在 Go 中通过 os/exec 启动外部命令后,若需动态监控其内存消耗(例如实现内存阈值告警、自适应限流或资源画像),标准库并不提供 cmd.Memory() 这类接口。但得益于 Linux 内核暴露的 /proc 文件系统,我们可直接读取目标进程的 smaps 文件,提取 PSS(Proportional Set Size) ——该指标按共享内存比例分摊计算,比 RSS 更真实反映单个进程的内存“贡献”,且误差通常远低于 10 MB,完全满足一般监控场景需求。
以下是一个生产可用的 calculateMemory 函数实现:
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"strconv"
"strings"
)
// calculateMemory 返回指定 PID 进程的 PSS 总和(单位:KB)
func calculateMemory(pid int) (uint64, error) {
path := fmt.Sprintf("/proc/%d/smaps", pid)
f, err := os.Open(path)
if err != nil {
return 0, fmt.Errorf("failed to open %s: %w", path, err)
}
defer f.Close()
var totalPss uint64
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Bytes()
if bytes.HasPrefix(line, []byte("Pss:")) {
// 提取 Pss: 后的数值(格式如 "Pss: 1234 kB")
rest := strings.TrimSpace(string(line[4:]))
fields := strings.Fields(rest)
if len(fields) == 0 {
continue
}
kbStr := fields[0]
kb, err := strconv.ParseUint(kbStr, 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse Pss value '%s': %w", kbStr, err)
}
totalPss += kb
}
}
if err := scanner.Err(); err != nil {
return 0, fmt.Errorf("scan error in %s: %w", path, err)
}
return totalPss, nil
}✅ 使用示例:结合 exec.Cmd 实时采样
cmd := exec.Command("stress", "--vm", "1", "--vm-bytes", "512M", "--timeout", "30s")
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
defer cmd.Process.Kill() // 确保清理
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
memKB, err := calculateMemory(cmd.Process.Pid)
if err != nil {
log.Printf("failed to get memory for PID %d: %v", cmd.Process.Pid, err)
continue
}
memMB := float64(memKB) / 1024.0
fmt.Printf("PID %d memory usage: %.1f MB\n", cmd.Process.Pid, memMB)
if memMB > 50.0 {
fmt.Println("⚠️ Oh my god, this process is hungry for memory!")
// 可在此触发终止、降级或告警逻辑
}
case <-time.After(35 * time.Second): // 超时退出
return
}
}⚠️ 注意事项与最佳实践:
- 权限要求:调用进程需对 /proc/[pid]/smaps 具有读取权限(通常同用户启动即可;若为 root 启动的子进程,非 root 程序无法读取其 smaps);
- 内核兼容性:Pss: 字段自 Linux 2.6.25+ 稳定支持,现代发行版(CentOS 7+/Ubuntu 16.04+)均适用;
- 性能开销:单次 smaps 解析耗时约 0.1–1 ms,高频采样(如
- 精度说明:PSS 是内核统计值,非瞬时快照,但更新频率高(毫秒级),实际误差通常
- 跨平台限制:此方案仅适用于 Linux;macOS/Windows 需依赖 ps, tasklist 等外部命令(精度低、开销大、不可靠),不推荐用于实时监控。
总结:通过直接解析 /proc/[pid]/smaps 中的 Pss: 行,我们以零依赖、高精度、低开销的方式实现了 Go 子进程内存的实时观测能力。该方法简洁、稳定、符合 Linux 哲学,是构建资源感知型 Go 工具与服务的核心基础能力之一。










