- 指标监控
- 开发工具
【免费下载链接】gotop
A terminal based graphical activity monitor inspired by gtop and vtop
gotop 是一款用 Go 编写的终端图形化系统监控工具,其电池状态组件(Battery Status widget)正是建立在第三方库 distatus/battery 之上的。本篇文章以该库的官方 README 为主线,结合 gotop 仓库中 vendored 的完整源码,深入讲解 battery 库的 API 设计、单位归一化规则、错误处理机制、各操作系统底层实现原理,以及它在 gotop 中的实际调用方式,帮助读者掌握"一行代码拿到系统级电池数据"的跨平台开发能力,并理解终端监控类应用读取硬件信息的通用思路。
一、库的定位:跨平台、归一化的电池信息库
github.com/distatus/battery是一个跨平台的电池信息读取库。官方 README 开宗明义:它向调用方提供与操作系统无关的、类型化的电池状态(state)、容量(capacity)、电量(charge)和电压(voltage)数据,并且所有数值都会按需换算为统一的单位——功率以 mW(毫瓦)返回,容量以 mWh(毫瓦时)返回,电压以 V(伏特)返回。
这一点正是该库最核心的价值:不同操作系统暴露的电池数据在格式、单位和语义上差异极大(有的给能量值、有的给电流值、有的给原始设备树字节),battery 库在底层把这一切"消化"掉,向上层暴露一套稳定的 Go 结构体。gotop 之所以能同时运行在 Linux、FreeBSD、macOS 等系统上而电池组件代码无需分叉,正是得益于这一层抽象。
从 go.mod 可以看到,gotop 锁定的是github.com/distatus/battery v0.9.0,源码完整保留在 vendor/github.com/distatus/battery/ 目录下,可随时查阅。
支持的操作系统
README 明确列出当前支持的平台及版本下限:
| 操作系统 | 版本要求 |
|---|---|
| Linux | 2.6.39+ |
| OS X / macOS | 10.10+ |
| Windows | XP+ |
| FreeBSD | 无特殊要求 |
| DragonFlyBSD | 无特殊要求 |
| NetBSD | 无特殊要求 |
| OpenBSD | 无特殊要求 |
| Solaris | 无特殊要求 |
在仓库的 vendor 目录中,每个平台都有对应的独立实现文件,与上述列表一一对应:battery_linux.go、battery_darwin.go、battery_windows.go、battery_freebsd_dragonfly_.go(通过// +build freebsd dragonfly构建标签同时覆盖 FreeBSD 与 DragonFlyBSD)、battery_netbsd.go、battery_openbsd.go、battery_solaris.go。这种"按文件名后缀 + 构建标签组织平台代码"的做法,是 Go 跨平台开发的经典范式。
二、安装与引入
官方 README 给出的安装方式与标准 Go 库一致:
$ go get -u github.com/distatus/battery由于 gotop 采用 vendor 目录管理依赖,在其源码中直接以"github.com/distatus/battery"导入即可使用,例如 src/widgets/battery.go:
import "github.com/distatus/battery"对于采用 Go Modules 的新项目,等价的做法是在go.mod中声明依赖后执行go mod tidy(本仓库的 go.sum 中已记录了 v0.9.0 的校验和)。
三、核心 API 与数据模型
battery 库对外只暴露极简的两个入口函数,全部定义在 battery.go 中:
Get(idx int) (*Battery, error):按索引读取某一块电池的信息;GetAll() ([]*Battery, error):读取系统中所有电池的信息。
值得注意的语义细节:Get中的索引是库内部归一化后的索引,满足GetAll()[idx] == Get(idx),它不一定对应操作系统给电池起的名字或物理槽位编号。README 与源码注释都明确强调了这一点,调用方不应假设索引与系统命名(如 Linux 的BAT0)有映射关系。
1. Battery 结构体
Battery结构体是数据模型的核心,共 7 个字段,注释中直接给出了单位约定:
| 字段 | 类型 | 含义 | 单位 |
|---|---|---|---|
State | State | 当前电池状态(枚举) | — |
Current | float64 | 当前(瞬时)容量 | mWh |
Full | float64 | 最近一次已知的满电容量 | mWh |
Design | float64 | 设计容量(出厂标称) | mWh |
ChargeRate | float64 | 当前充放电速率 | mW |
Voltage | float64 | 当前电压 | V |
DesignVoltage | float64 | 设计电压 | V |
需要特别留意ChargeRate与State的配合关系:源码注释明确指出ChargeRate 恒为非负,判断当前是在充电还是在放电必须看State字段,而不能看 ChargeRate 的正负号。这是一个容易踩坑的设计决策——不同平台底层返回的电流/功率符号语义不一致(例如 macOS 的Amperage在放电时为负),battery 库选择在上层统一取绝对值,把方向语义交给State表达。
另一个贴心的兼容细节是DesignVoltage:某些系统(如 macOS)并不单独提供设计电压。此时若DesignVoltage读取失败而Voltage读取成功,库会自动用当前电压填充设计电压,方便调用方直接使用。这一逻辑在 Linux、FreeBSD、NetBSD、OpenBSD 的多个后端中均有体现(如 battery_linux.go)。
2. State 枚举
电池状态由State类型表达,其取值定义在 battery.go:
const ( Unknown State = iota Empty Full Charging Discharging )其中Unknown有两种含义:要么是底层控制器本身返回了"未知",要么是读取状态时发生了错误。库内部通过newState(name string)将各平台返回的状态字符串(大小写不敏感)映射为枚举,例如 Linux 的status文件内容"Full\n"会被去掉换行后映射为Full。
四、完整代码示例
以下是 README 给出的开箱即用的示例代码,展示了全部 7 个字段的读取方式:
import ( "fmt" "github.com/distatus/battery" ) func main() { batteries, err := battery.GetAll() if err != nil { fmt.Println("Could not get battery info!") return } for i, battery := range batteries { fmt.Printf("Bat%d: ", i) fmt.Printf("state: %f, ", battery.State) fmt.Printf("current capacity: %f mWh, ", battery.Current) fmt.Printf("last full capacity: %f mWh, ", battery.Full) fmt.Printf("design capacity: %f mWh, ", battery.Design) fmt.Printf("charge rate: %f mW, ", battery.ChargeRate) fmt.Printf("voltage: %f V, ", battery.Voltage) fmt.Printf("design voltage: %f V\n", battery.DesignVoltage) } }有两点需要提醒:
- 状态值建议使用
%v而非%f输出:State是枚举类型而非浮点数,State.String()会返回"Full"、"Charging"等可读字符串(见 battery.go),用%f打印枚举只是 README 示例代码的历史写法。 - 错误并非总是"全有或全无":
GetAll返回的错误可能是部分字段级的,详见下一节。因此更健壮的写法是先判断err是否为nil,再用返回的batteries数据(即使伴随部分错误,仍可能有可用的字段值)。
计算电池剩余百分比
一个最常用的派生计算——剩余电量百分比——可以直接由Current与Full得出。gotop 正是这样做的(见 src/widgets/battery.go):
percentFull := math.Abs(battery.Current/battery.Full) * 100.0之所以使用math.Abs,是因为个别平台底层在特殊情况下可能返回带符号的值,取绝对值可保证百分比始终落在合理区间。
五、错误处理机制:ErrFatal 与 ErrPartial
这是该库设计中最有特色的部分,全部实现集中在 errors.go。跨平台读取电池数据时,"某几个字段读不到、其余字段正常"是常态,库为此设计了两级错误模型:
1. ErrFatal(致命错误)
表示完全无法获取任何有效数据,调用方应当认为返回的数据不可用。可能触发场景包括:无法执行关键的系统调用、所有字段级读取同时失败等。ErrFatal内部包装一个原始错误Err,其Error()输出形如Could not retrieve battery info: ...。
库中还预定义了两个仅以ErrFatal形式抛出的哨兵错误:
ErrNotFound:指定索引处不存在电池;ErrAllNotNil:后端返回的ErrPartial中所有字段都有错误(等价于全部失败),因此被提升为致命错误。
2. ErrPartial(部分错误)
这是库的"精细粒"错误:ErrPartial结构体中包含与Battery字段一一对应的 7 个error字段(State、Current、Full、Design、ChargeRate、Voltage、DesignVoltage)。某个字段为nil表示该字段读取成功,非nil表示该字段失败。
在 battery.go 的getAll聚合逻辑中,错误会被归一化:
- 若所有字段级错误均为
nil→ 返回nil错误; - 若部分字段成功、部分失败 → 返回
ErrPartial,同时仍返回可用的电池数据; - 若所有字段均失败 → 提升为
ErrFatal{ErrAllNotNil}。
3. Errors(批量错误)
GetAll面对多块电池时,使用Errors类型(即[]error)逐块报告。源码注释给出了重要保证:返回的电池切片与错误切片长度相同,第 i 个错误对应第 i 块电池。因此在处理多电池系统时,可以逐块判断哪块电池的数据完整、哪块只有部分字段可用。
六、各平台底层实现原理
battery 库的价值体现在对不同系统差异的"翻译"上。以下逐一剖析各个后端的真实数据来源,全部证据来自 vendor 目录中的平台源码。
1. Linux:读取 sysfs(battery_linux.go)
Linux 后端直接读取内核暴露的 sysfs 路径/sys/class/power_supply:
- 枚举目录:遍历该目录,读取每个条目下的
type文件,值为"Battery"的才视为电池(isBattery函数); - 读取字段:
energy_now(当前能量)、energy_full(满电能量)、energy_full_design(设计能量)、power_now(功率)、voltage_now(当前电压)、voltage_max_design/voltage_min_design(设计电压)、status(状态字符串); - 单位换算:sysfs 中能量和功率以微瓦时/微瓦(µWh/µW)为单位,
readFloat函数将原始值num / 1000转换为毫瓦时/毫瓦(mWh/mW),电压则再额外/= 1000从毫伏(mV)转为伏特(V); - 电流到能量的兜底换算:部分设备只提供电流数据(
charge_now、charge_full、current_now)而不提供能量数据,此时后端用readAmp将电流(mA)× 当前电压(V)换算为功率/容量,保证统一返回 mW/mWh 单位。
2. macOS:ioreg + plist 解析(battery_darwin.go)
macOS 后端通过执行系统命令读取注册表:
out, err := exec.Command("ioreg", "-n", "AppleSmartBattery", "-r", "-a").Output()ioreg输出的 plist(属性列表)数据经howett.net/plist反序列化为内部结构体,包含Voltage(mV)、CurrentCapacity、MaxCapacity、DesignCapacity、Amperage(mA)、FullyCharged、IsCharging、ExternalConnected等字段,随后在convertBattery中完成换算:容量字段 × 当前电压得到 mWh,Amperage取绝对值 × 电压得到 mW。状态则由ExternalConnected/IsCharging/FullyCharged组合推导。如前所述,macOS 不提供独立设计电压,DesignVoltage直接复用当前电压。
3. Windows:SetupAPI + IOCTL(battery_windows.go)
Windows 后端是最复杂的,它通过setupapi.dll枚举电池设备接口(GUID{72631E54-78A4-11D0-BCF7-00AAB7B3B2A}),再对设备句柄发起三层DeviceIoControl调用:
IOCTL_BATTERY_QUERY_TAG(2703424):获取BatteryTag;IOCTL_BATTERY_QUERY_INFORMATION(2703428):读取batteryInformation结构,得到FullChargedCapacity与DesignedCapacity;IOCTL_BATTERY_QUERY_STATUS(2703436):读取batteryStatus结构,得到Capacity、Voltage(mV)、Rate(有符号,BATTERY_UNKNOWN_RATE 为-0x80000000)、PowerState(位标志,按位映射到 Charging/Empty/Discharging/Full)。
值得注意的是 Windows 的Rate字段是有符号的(正数充电、负数放电),后端通过int32ToFloat64取绝对值统一为非负,与前述ChargeRate恒非负的约定一致。多个设备接口的遍历通过SetupDiEnumDeviceInterfaces循环递增索引、直到返回ERROR_NO_MORE_ITEMS(259)为止。
4. FreeBSD / DragonFlyBSD:ACPI ioctl(battery_freebsd_dragonfly_.go)
BSD 后端打开/dev/acpi设备,通过 ioctl 读取 ACPI 电池信息:
ACPIIO_BATT_GET_BIF(0x10):静态信息,含设计容量、满电容量、设计电压、单位标志(units是否为 mW/mWh);APCIIO_BATT_GET_BST(0x11):动态状态,含state(位值映射状态)、rate、cap、volt。
若units表明底层返回的是电流单位(mAh/mA),后端同样用电压相乘换算为 mWh/mW。由于 FreeBSD 无法枚举电池数量,systemGetAll的策略是"从 0 开始递增索引,遇到全部字段错误或ENOENT即停止"。ioctl 的构造逻辑封装在 ioctl.go 中(通过// +build freebsd dragonfly netbsd构建标签共享)。
5. NetBSD:sysmon 设备 + plist(battery_netbsd.go)
NetBSD 后端打开/dev/sysmon,通过 ioctl(命令码0、类型'E')获取系统监控属性列表,反序列化为 plist 后按acpibat*前缀过滤出电池属性组。每个属性携带description、cur-value、max-value、state、type字段,后端按描述符(voltage、design cap、charge、charge rate等)逐项映射。对于type前缀为Amp的容量/功率值,同样乘以电压完成单位换算;状态则通过"存在 charge rate 而无 discharge rate → Charging,反之 → Discharging"的互补关系推导。
6. OpenBSD:sysctl 传感器(battery_openbsd.go)
OpenBSD 后端通过sysctl读取hw.sensors下的传感器树,遍历名字以acpibat开头的传感器设备。它按传感器类型(SENSOR_VOLTS_DCµV、SENSOR_WATTSµW、SENSOR_WATTHOURµWh、SENSOR_AMPSµA、SENSOR_AMPHOURµAh)读取rate、design capacity、last full capacity、remaining capacity、current voltage等描述符;传感器status == unknown的值被拒绝。若功率/容量传感器缺失,则回退到电流传感器再乘电压换算,确保字段尽量齐全。
7. Solaris:kstat 命令(battery_solaris.go)
Solaris 后端执行kstat -p -m acpi_drv -n 'battery B*'并逐行解析输出,读取bif_design_cap、bif_last_cap、bif_unit、bif_voltage、bst_voltage、bst_rem_cap、bst_rate、bst_state等键值。bst_state是位标志(bit0 放电、bit1 充电、bit2 空),按位解析映射状态;同样根据bif_unit判断是否需要从电流单位乘电压换算。
平台实现小结
纵观全部后端,可以提炼出三条贯穿始终的设计准则:
- 单位归一化:无论底层返回 µWh/µW、mAh/mA、还是原始位标志,最终一律换算为 mWh/mW/V;
- 电流→能量换算兜底:当能量传感器缺失时,用"电流 × 电压"推导,保证上层拿到统一维度的数据;
- 字段级错误传播:单个字段读取失败不影响其余字段,通过
ErrPartial逐字段报告。
七、命令行工具 battery
除库本身外,README 还附带了一个独立的小型 CLI 工具,其功能"或多或少"地模拟了 GNU/Linux 的acpi -b命令:
$ go get -u github.com/distatus/battery/cmd/battery安装后直接运行:
$ battery BAT0: Full, 95.61% [Voltage: 12.15V (design: 12.15V)]输出包含电池编号、状态(Full)、剩余百分比(95.61%)、当前电压与设计电压。该命令底层就是调用库的GetAll()并格式化输出,非常适合在脚本中快速查看电池状态、或作为库用法的参考实现。
八、在 gotop 中的实际应用
battery 库在 gotop 中的使用位置是 src/widgets/battery.go,这是理解"库如何被真实消费"的最佳案例:
func (self *BatteryWidget) update() { batteries, err := battery.GetAll() if err != nil { log.Printf("failed to get battery info: %v", err) return } for i, battery := range batteries { id := makeId(i) percentFull := math.Abs(battery.Current/battery.Full) * 100.0 self.Data[id] = append(self.Data[id], percentFull) self.Labels[id] = fmt.Sprintf("%3.0f%% %.0f/%.0f", percentFull, math.Abs(battery.Current), math.Abs(battery.Full)) } }关键实现细节:
- 调用入口:直接使用
battery.GetAll(),无需关心运行平台——这正是跨平台抽象的收益; - 数据加工:用
Current / Full计算百分比作为折线图数据点,标签显示百分比 当前容量/满电容量; - 多电池支持:按索引生成
Batt0、Batt1等系列名,每块电池一条独立曲线; - 刷新策略:
NewBatteryWidget设置了 1 分钟的updateInterval,通过time.NewTicker周期性刷新(见 src/widgets/battery.go),并在构造时故意调用两次update()以填充两个数据点,否则单点曲线难以显示; - 错误容忍:获取失败时仅记录日志并跳过本轮更新,不会导致整个 gotop 崩溃——这是对
GetAll返回错误的合理降级处理。
gotop 的电池组件由启动参数控制:gotop -b(--battery)显示电池组件(minimal模式下自动关闭),官方 README 给出了实际运行预览 assets/screenshots/battery.png,其中每块电池以折线图形式绘制剩余电量百分比随时间的变化,直观展示充电/放电趋势。
九、使用限制与注意事项
- 平台条件:battery 库依赖各操作系统的特定子系统(Linux sysfs、macOS ioreg、Windows ACPI 驱动等)。在无电池的机器(如台式机、虚拟机)上,
GetAll会返回错误或空列表,调用方需像 gotop 那样做好降级处理。 - 单位约定:容量恒为 mWh、功率恒为 mW、电压恒为 V。若业务上需要 mAh 或百分比,需自行基于
Current/Full计算。 - ChargeRate 符号:永远是非负值,充/放电方向必须看
State。 - 索引语义:
Get(idx)的索引是库内归一化索引,不代表系统命名;多电池场景下与第 i 个错误一一对应的是Errors切片的第 i 项。 - 兼容版本:README 标注 Linux 需要 2.6.39+、macOS 需要 10.10+、Windows 需要 XP+,低于这些版本的内核/系统可能无法提供所需接口;gotop 使用的 v0.9.0 版本行为以本仓库 vendor 目录中的源码为准。
十、结语
从 README 的十余行 API 说明出发,深入 vendor 源码后可以看到:battery 库用一套Battery结构体 +Get/GetAll接口,屏蔽了 8 个操作系统在电池数据读取上的巨大差异,并通过ErrPartial字段级错误机制实现了"能读多少给多少"的优雅降级。而 gotop 的电池组件则以极少的代码量接入了这一能力,为终端用户呈现了实时的电量曲线。对于任何需要在 Go 程序中读取电池信息的开发者,这套"库 + 各平台实现 + 真实消费案例"的组合都是一份可复制、可借鉴的完整范本——相关源码均可直接在 vendor/github.com/distatus/battery/ 与 src/widgets/battery.go 中查阅。
- 指标监控
- 开发工具
【免费下载链接】gotop
A terminal based graphical activity monitor inspired by gtop and vtop
相关推荐
oh-my-zsh battery 插件实战:在终端提示符中显示跨平台电池状态
oh my zsh battery 插件实战:在终端提示符中显示跨平台电池状态 oh my zsh 的 battery 插件为 zsh 提示符提供了一组跨平台(
CLI开发工具插件系统基于 chrome.bluetoothLowEnergy 实现 BLE 电池电量读取:Chrome Apps Battery Service 示例全解析
基于 chrome.bluetoothLowEnergy 实现 BLE 电池电量读取:Chrome Apps Battery Service 示例全解析 本文围
示例工程SimBatt 模拟电池驱动样本深度解析:基于 KMDF 的 Windows 电池 miniclass 接口实现指南
SimBatt 模拟电池驱动样本深度解析:基于 KMDF 的 Windows 电池 miniclass 接口实现指南 SimBatt(Simulated Bat
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考