←News detail

Tencent Cloud Developer CommunityUpdated Oct 26 00:11

eul, unmaintained for 5 years, is fixed with Workbuddy and restores temperature and GPU monitoring on Apple M6

English text is being prepared.

AI summary

The open-source menu bar monitoring tool eul, unmaintained for 5 years, completed a low-level fix with Workbuddy and once again reads CPU temperature and GPU information on the Mac Mini 6 with the Apple M6 chip. The root cause is that SMCParamStruct changed from 80 bytes to 88 bytes in arm64 user mode, and all 135 temperature keys on the M6 are of the flt type rather than the sp78 type from the Intel era. After the fix, it dynamically enumerates 2,066 SMC keys, retains 126 valid sensors, and remains compatible with the logic for Intel models.

Body

一、 开头:死亡现场

最近入手了一台 Mac Mini 6(Apple M6 芯片),计划用来评测一些小机型大模型的情况。因此,需要一个能直观、方便观察系统性能的工具。综合对比下,开源项目 eul 非常合适——它足够小巧玲珑(App 仅 5MB 左右),且原生常驻在菜单栏。

但在用 brew 安装后,结果让人傻眼:CPU 温度和 GPU 相关信息全部显示 N/A。对于要跑大模型测试的场景来说,这些信息极其关键。

::: center 图 1.1 CPU 温度和 GPU 相关信息全部显示 N/A ::: 于是找到了 eul 的源码。GitHub 上,master 分支的最新 commit 是 b5c3bd0,提交信息写着 Create Localizable.strings (#238)。日期栏赫然显示:5 years ago。

::: center 图 1.2 eul项目源码更新情况 ::: 右上角 10k Star 似乎在展示着过去的辉煌,就这样静静地躺在 GitHub 上。Issues 区里,有人在两年前问:“M1 还有救吗?”没有人回答。

在 M6 上它还有救么?我的 macOS 应用开发经验不多,但在如今的 AI 时代,或许也可一试。

--- 二、 问题:调研现场 拿到 5 年前的源码后,发现 eul 的温度读取实现源自 Intel 时代的 SMCKit。SMCKit 的 README 明确写着它「for Intel based Macs」,其内置的传感器清单全部是 Intel 机型的 key(TC0P/TG0P/TM0S 等),显然对于 Apple Silicon 毫无办法。

使用 Debug 构建,断言直接触发,App 启动即崩溃: eul/SMC.swift:390: Assertion failed: SMCParamStruct size is != 80

显然,原有的一些数据结构已经不能适用于如今 M6 的运行环境了。

查找一番资料后,发现一个更伤感的事情:Apple 从未公开 AppleSMC 的 API 和 key 命名规范。难道要凭我这有限的 macOS 研发经验去盲猜?

是时候用下这个时代的工具了:Workbuddy

--- 三、 复活吧,eul:先写探针,再动手术

由于没有官方文档支持,Workbuddy 首先写了一个 Swift 探针(smcprobe.swift),直接与底层驱动对话。探针做两件事: 读取 #KEY 得到本机 key 总数,逐个枚举,筛出所有 T 前缀的温度 key,并解码当前值。 遍历 IOAccelerator 服务,打印 PerformanceStatistics 的全部统计项。

最戏剧性的一幕发生了:用来排查故障的探针,自己先崩了。

探针在读取数据时,底层的 IOKit 调用直接抛出了错误:IOKit call error: 0x-1ffffd39。为了查明原因,让探针打印出它发给驱动的数据结构尺寸。结果发现,尺寸变成了 88 字节。

这恰好解释了前文 eul 启动崩溃时的那个断言报错:SMCParamStruct size is != 80,显然之前面对 88 字节的驱动数据包,程序底层直接拒绝了沟通。

这意味着,用来排查问题的探针,自己先完美复现了 eul 的故障。这直接让根因定位从“盲人摸象”变成了“精准实测”。

探针跑通后,拿到了 M6 的一手数据: SMC 总 key 数量:2066 Tx 温度类 key 数量:135 温度类型:135 / 135 均为 flt 类型,没有 sp78

Workbuddy 顺藤摸瓜,制定了“四刀”修复方案。 第一刀:缝合 ABI 裂缝(88 字节 vs 80 字节) 结构体 SMCParamStruct 出自 Apple 早期的开源项目,驱动侧固定按 80 字节解析。但结构体中的一个字段 IOByteCount,在 arm64 用户态下变成了 64 位(8 字节)。这多出来的 4 字节加上对齐填充,让结构体变成了 88 字节。 Debug 构建直接崩溃,Release 构建调用静默失败。 修复: 不依赖平台定义,Workbuddy 建议直接把 dataSize 的类型固定为 UInt32。这样既保证了二进制布局稳定,又去掉了三处多余的类型转换。 第二刀:自适应数据类型(sp78 vs flt) 原代码把读取逻辑写死为 Intel 时代的 sp78(2 字节定点)。而 M6 全是 flt(4 字节 IEEE 754 浮点)。按 sp78 去读,只能读出垃圾值。 修复: 由“假设类型”改为“查询类型”。先查 keyInformation 拿真实类型,再按实际类型选解码器。Workbuddy 顺手补齐了原有 fromSP78 丢失小数位的 Bug,并新增了 fromFLT 解码器,一举覆盖 T2 和 Apple Silicon 芯片。 第三刀:放弃静态清单,拥抱动态发现 原代码只遍历硬编码的 28 个 Intel key。在 M6 上,这些 key 一个都不存在,导致传感器列表为空。 修复: 新增 allTemperatureSensors() 函数,直接枚举 2066 个 key,按“T 前缀 + 温度类型”筛选出 135 个候选,再剔除未初始化的热区占位符(如 Tz11 恒为 0.0 °C),最后保留 126 个有效传感器。 纠错插曲: 聚合温度时,第一版代码是按“显示名”匹配前缀的。但显示名早已被改写(如改为“CPU P-Core”),导致匹配不上。Workbuddy 提醒道:标识符(key code)与显示名必须严格分离。改为按 Tp0、Tg0 等代码前缀匹配后,温度终于出来了。 第四刀:GPU 识别脱虚向实 原代码要求 GPU 必须有 PCI 标识符(device-id / IOPCIMatch)。但在 Apple Silicon 上,集成 GPU 根本没有这玩意儿,导致 GPU 列表被直接丢弃。 修复: 让 device-id 从“必需”变成“可选”,缺失时用型号名(如 Apple M6)兜底。GPU 的温度也从“必须有 registry 字段”改为“registry 优先,SMC Tg0x 兜底”。

--- 四、 跑起来了,但这不是终点

经过编译和构建参数的微调(调整 SDKROOT 等),App 终于成功跑起来了。

::: center 图 4.1 CPU 温度和 GPU 相关信息全部正常显示 :::

但这不仅仅是几个数字的胜利。修复前后的代码对比,本质上是把 eul 的底层采集架构,从“面向 Intel 机型的死胡同”拉回了“面向底层协议”的正轨。 修复后的整体架构演进

::: center 图 4.2 整体架构演进 ::: 从上图可以看出,系统的数据流向没有变,依然是自下而上(硬件 -> 接口层 -> 采集层 -> 状态层 -> 展示层)。真正的变化,发生在那几根最底层的箭头上。

以前,底层接口层(SMCKit / IOHelper)与硬件之间是“硬连线”的,键名、类型、标识符全写死;现在,它们变成了一条带有“翻译器”和“过滤器”的通用管道。 温度读取链路:从“死胡同”到“自适应”

::: center 图 4.3 温度读取链路 ::: 左侧是原来的路径:按 28 个 Intel 硬编码键名去查 -> 查不到 -> 列表为空 -> 显示 N/A。 右侧是修复后的路径:枚举全量 Key -> 筛选 flt/sp78 类型 -> 合理性过滤(剔除 Tz11 这类恒为 0 的无效热区)-> 按 Key Code 前缀聚类取最大值 -> 输出真实温度。

架构级变化: 彻底解耦了“显示名”与“标识符”。原来的代码按 "CPU0DIE" 去查传感器,一旦硬件改了名,代码就崩。现在程序只认底层的 Tp0、TPD 等代码前缀,显示层用 friendlyName 随时可以包装成漂亮的“CPU P-Core”。这就是“面向协议”带来的韧性。 GPU 读取链路:脱虚向实后

::: center 图 4.4 GPU 读取链路 ::: GPU 链路的修复异曲同工。原代码在 systemprofiler 和 IOAccelerator 里死找 PCI 标识符(device-id / IOPCIMatch),找不到就丢弃整条记录。

修复后,标识符变成了“双轨制”:有 PCI 就走 PCI,没有 PCI(如 M6 集成 GPU)就用型号名(如 Apple M6)兜底匹配。同时,GPU 温度不再强求从 registry 里拿,而是回退到 SMC 的 Tg0x 传感器集群。 修改规模与兼容性回归

这次“手术”看似动了底层,其实切口极小,并未伤筋动骨:

|文件|改动量|核心使命| |:-:|:-:|:-:| |SMC.swift|+81 / -10|修复 ABI 裂缝、自适应解码、动态发现传感器| |SmcControl.swift|+51 / -12|传感器过滤、按前缀聚合最大温度| |GPU.swift|+23 / -8|移除 PCI 强依赖,兼容型号名兜底| |GpuStore.swift|+11 / -3|重构 GPU 统计匹配逻辑| |project.pbxproj|+1 / -1|修复 SwiftPM 的 SDKROOT 脚本报错| 更重要的是兼容性。通过回归验证,Intel 机型的逻辑不仅没被破坏,反而因为修复了 sp78 的小数位精度而变得更好。命名传感器(如 CPU0DIE)在 Intel 机器上依然是第一优先级,回退分支根本不会触发。这才是一个合格的开源重构:不砸别人的饭碗,只接自己的断腿。

回顾这次修复,Workbuddy 扮演的角色远不止一个“代码补全工具”。在这个过程中,它深刻展示了 AI 时代解决底层系统问题的新范式。

1. AI 填平了晦涩的“黑盒鸿沟”

如果是传统开发模式,面对“未公开 API”和“报错 0x-1ffffd39”,缺乏 OS 开发经验的工程师大概率会卡在查阅无数个过时的 StackOverflow 帖子中。但 Workbuddy 能迅速理解崩溃日志的底层逻辑,通过生成探针代码“反过来”测出结构体大小,从而推导出 88 字节的 ABI 裂缝。AI 把黑盒变成了灰盒,把试错从“盲人摸象”变成了“按图索骥”。

2. 从“我怎么写”变成了“我怎么设计”

在这个项目中,核心的困难不在于写出 Swift 的语法,而在于“传感器怎么聚合更合理”、“标识符与显示名分离”这样的架构决策。Workbuddy 负责生成 80% 的样板代码、适配和修复草稿;而人负责验证、测试、重写那 20% 的关键逻辑。AI 负责往前冲,人负责拉缰绳。 这种协作模式,极大拉高了个人能力的上限。

3. AI 无法替你承担“最后一步”

AI 可以写出 fromFLT 解码器,但它没法替你处理 Xcode 的签名证书;它可以帮你把 pciMatch 改为可选,但无法替你承担代码合并到主干后的开源维护责任。“写代码”的门槛在变低,但“为结果负责”的门槛永远在。

--- 六、 结尾:停在 5 年前的代码,也有权利跑在新 M6 上

10k Star,380 Fork,35 Contributors,48 Releases。这些数字曾经代表一个活跃的项目,后来变成了一座数字纪念碑。

下一个 5 年,也许它还会再次停更。但至少现在,它跑在了这台 Mac Mini 6 上,读出了 32 °C 的 CPU 温度,伴随继续折腾大模型评测。

---

喜欢本文的朋友 欢迎关注、点赞、收藏 一键三连,大家的支持是写文章最大的动力

Source

Tencent Cloud Developer Community