Loki 仓库中的 prometheus/procfs:从 /proc 与 /sys 读取系统指标的核心 Go 库解析
【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki
导读
procfs 是 Prometheus 生态中一个重要的 Go 基础库,它的全部职责只有一个:从 Linux 内核暴露的伪文件系统/proc与/sys中读取系统、内核与进程指标。在 Loki 仓库中,它以v0.22.0版本被间接依赖(go.mod中的// indirect标注)并随 vendor 目录完整固化,为运行时指标采集、容器环境探测等能力提供底层支撑。读完本文,你将掌握 procfs 的包组织方式、FS类型与挂载点初始化原理、进程与块设备指标读取的完整调用链,以及其测试夹具(ttar)的更新流程。
项目定位:这不是 Loki 的私有实现,而是 vendored 的公共基础库
procfs 以 Go 模块github.com/prometheus/procfs的形态存在于本仓库中。它在 go.mod 中被声明为v0.22.0 // indirect——也就是说,Loki 并不直接调用它,而是通过 dskit、weaveworks/common 等间接依赖链引入,用于采集系统运行指标(例如内存、CPU、网络统计),最终暴露为 Prometheus 格式的指标。由于 Loki 采用 vendor 模式构建,其完整源码被固化在 vendor/github.com/prometheus/procfs 目录下,同时 vendor/modules.txt 中记录了它及其两个内部子包(internal/fs、internal/parsers)的 vendored 路径。
因此,本文讨论的"procfs"是 Loki 构建链中的一个受信任的第三方依赖。理解它的用法,等价于理解 Loki 乃至整个 Prometheus 技术栈中"如何从内核伪文件系统拿数据"的标准答案。
设计骨架:按数据来源划分的三个层次
README 明确指出,procfs 库按"数据来自/proc、/sys还是两者"来组织包结构。这与内核伪文件系统的分工一致:
/proc:进程相关的运行时信息(进程列表、CPU 统计、内存统计等),大多数信息由根procfs包提供;/sys:设备与内核子系统的结构化信息(块设备、PCI 等);- 两者都需要:部分子包,例如
blockdevice,需要同时挂载两个伪文件系统。
在 vendor/github.com/prometheus/procfs/fs.go 中可以看到根包的核心抽象:
// FS represents the pseudo-filesystem sys, which provides an interface to // kernel data structures. type FS struct { proc fs.FS isReal bool }注意其中的isReal字段:它用于标记当前挂载点是否指向真实的 proc 文件系统。在 fs.go 的 NewFS 实现 中,库会先调用internal/fs的NewFS校验目录可读,再通过isRealProc做真实性探测——这保证了在容器环境或测试环境下(例如以目录为根的"伪 /proc")不会误判数据来源。
快速上手:初始化挂载点并读取统计信息
README 给出的最小用法示例,是初始化/proc挂载点后读取 CPU 统计:
fs, err := procfs.NewFS("/proc") stats, err := fs.Stat()如果希望使用默认挂载点,可以更简单地调用procfs.NewDefaultFS()。从源码看,DefaultMountPoint常量定义于 fs.go,实际值来自internal/fs的DefaultProcMountPoint(即标准的/proc路径)。
fs.Stat()返回的Stat结构体以 stat.go 中的CPUStat为核心,其中每个字段对应/proc/stat中的一列:
type CPUStat struct { User float64 Nice float64 System float64 Idle float64 Iowait float64 IRQ float64 SoftIRQ float64 Steal float64 Guest float64 GuestNice float64 }这些字段与/proc/stat中cpu行的各列(user、nice、system、idle、iowait、irq、softirq、steal、guest、guest_nice)一一对应,单位为"USER_HZ"(通常为百分之一秒)。Loki 这类常驻服务在计算 CPU 使用率指标时,正是通过对两次采样差值做归一化来得到百分比。
块设备与双伪文件系统:blockdevice 子包的用法
部分指标横跨/proc与/sys,README 以块设备为例给出调用方式:
fs, err := blockdevice.NewFS("/proc", "/sys") stats, err := fs.ProcDiskstats()之所以需要同时传入两个挂载点,是因为ProcDiskstats需要解析/proc/diskstats(各磁盘的 I/O 累计计数),而设备号与设备名的对应关系、分区信息等细节需要借助/sys下的block目录来补全。这种"以/proc取计数、以/sys取拓扑"的组合方式,是 procfs 内部常见的配套策略。
根包同样提供磁盘统计入口:fs.Diskstats()直接对应 [proc_diskstats 相关实现],并经由internal/parsers包完成行解析与数值转换。
进程信息读取:Procs 与 Proc 类型
除了系统级统计,procfs 还提供了完整的进程级 API。在 proc.go 中:
// Proc provides information about a running process. type Proc struct { // The process ID. PID int fs FS } // Procs represents a list of Proc structs. type Procs []ProcProc将进程 ID 与挂载点FS绑定,通过方法族读取该进程在/proc/<pid>/下的各类文件。当前 vendor 目录下与进程相关的读取器包括:
| 文件 | 对应 /proc 路径 | 指标内容 |
|---|---|---|
| proc_status.go | /proc/<pid>/status | 进程状态、内存(VmRSS、VmSize)、线程数等 |
| proc_stat.go | /proc/<pid>/stat | CPU 时间片、启动时间等 |
| proc_io.go | /proc/<pid>/io | 读写字节数与 I/O 次数 |
| proc_limits.go | /proc/<pid>/limits | 资源软硬限制(RLIMIT) |
| proc_fdinfo.go | /proc/<pid>/fdinfo | 文件描述符详情 |
| proc_cgroup.go | /proc/<pid>/cgroup | cgroup 归属,容器环境下尤其关键 |
| proc_environ.go | /proc/<pid>/environ | 进程环境变量 |
| proc_maps.go | /proc/<pid>/maps | 内存映射区段 |
典型使用模式是先通过fs.AllProcs()或fs.Procs()枚举全部 PID,再对每个Proc调用上述方法。这也是node_exporter等采集器实现process_*系列指标的标准路径。
系统级指标:覆盖内核各子系统的读取器
除进程外,根包还提供大量系统级读取器,全部以"一个源文件对应一个 Go 文件/结构体"的方式组织,便于对照内核文档阅读:
- 内存:meminfo.go 对应
/proc/meminfo,Meminfo结构体中的MemTotal、MemAvailable、Buffers、Cached、SwapTotal等字段均以*uint64指针类型暴露——指针语义用于表达"该字段在当前内核版本上不存在"的情况,读取时需判空。该结构体还包含Zswap/Zswapped等较新内核字段; - CPU 与调度:
stat.go对应/proc/stat,另有 schedstat.go 对应/proc/<pid>/schedstat、softirqs.go 对应/proc/softirqs、[interrupts 相关] 对应/proc/interrupts; - 网络:net_dev.go(网卡流量)、net_tcp.go、net_udp.go、net_unix.go、netstat.go(
/proc/net/netstat扩展统计)、net_sockstat.go(socket 内存占用)等; - 内核信息:loadavg.go(系统负载)、vm.go(
/proc/vmstat)、swaps.go、mdstat.go(软件 RAID 状态)、mountinfo.go(挂载表)、zoneinfo.go(NUMA zone 详情)。
这些读取器普遍遵循同一实现模式:用bufio逐行扫描伪文件 → 交给internal/parsers中的解析函数 → 填充对应结构体。解析失败时统一返回包级错误ErrFileParse(定义于 proc.go),调用方可用errors.Is判断。
内部基础设施:internal/fs 与 internal/parsers
vendored 版本中包含两个内部包,是理解实现细节的关键:
- vendor/github.com/prometheus/procfs/internal/fs 提供
fs.FS类型与DefaultProcMountPoint、DefaultSysMountPoint两个默认挂载点常量。所有公共NewFS最终都委托到这里完成目录校验; - vendor/github.com/prometheus/procfs/internal/parsers 集中了数值解析、文件读取(
readfile.go、sysreadfile.go)与键值解析(valueparser.go)等通用逻辑。由于/proc与/sys中的文件是文本伪文件,解析器需要在"容忍注释行/空行"与"严格校验格式"之间做取舍,这一层正是策略落点。
构建与测试:make test与 ttar 测试夹具
README 强调:procfs 是设计为嵌入其他应用的库,不产出独立二进制,因此构建总是作为宿主项目(如 Loki)的一部分发生。其自带测试则可通过make test运行。
测试体系的独特之处在于测试夹具以 ttar 归档形式存储:目录下的ttar文件打包了大量从真实/proc、/sys采样而来的示例文件,测试运行时自动解包,从而在不依赖真实内核的情况下完整覆盖解析逻辑。更新夹具的标准流程(摘自 README):
rm -rf testdata/fixtures make test修改解包后的testdata/fixtures目录内容,然后重新打包:
make update_fixtures最后用git diff testdata/fixtures.ttar校验变更。这套"真实采样 + 归档固化"的夹具机制,保证了新增解析逻辑能拿到贴近生产的输入数据,是 procfs 长期保持解析鲁棒性的关键工程实践。
版本与适用前提
本仓库固化的是github.com/prometheus/procfs v0.22.0。README 开篇明确警告该库仍处于 work in progress 状态,API 可能以不兼容方式变动——这正是 Loki 采用 vendor 模式锁定版本的直接原因:上游依赖的构建可复现性优先于跟随上游最新 API。
适用前提需注意:procfs 面向 Linux 内核的/proc与/sys设计,非 Linux 平台无法提供等价语义;容器场景下/proc可能被挂载为宿主或容器命名空间视角,读取结果取决于挂载配置(这也解释了isReal探测的存在意义)。若要在自己的 Go 工程中使用,只需go get github.com/prometheus/procfs并遵循本文的初始化与调用模式;在 Loki 仓库内则直接阅读 vendor/github.com/prometheus/procfs 下的实现即可。
【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考