进程 CPU 在概览里改为占整机的比例,悬停显示单核口径
用户反馈 CPU 占用高:CPU 弹窗“按应用汇总”与仪表盘“高占用进程”按单核满载为 100% 计(活动监视器的算法),多核 Mac 上一个应用常显示 99%、75%,又放在整机总占用下面,容易误读。 - Format.machineShare:单核口径 ÷ 逻辑核心数,全部核心跑满为 100%;小于 0.1% 且不为 0 写 “<0.1%”。Format.coreShare 保留单核口径写法 - CPU 弹窗 / CPU 页的按应用汇总、仪表盘高占用进程改用整机占比,数值悬停显示单核口径;仪表盘着色阈值改为整机占比 25% / 50% - 进程页保持单核口径,与活动监视器一致 - 单元测试覆盖两种写法;译文与更新日志同步
This commit is contained in:
1 parent
165963de84
commit
8f7691d290
9 files changed
+58
-14
No files matched your search
@@ -11,6 +11,10 @@
|
||||
|
||||
- 网络弹窗偶尔差一点放不下、露出一小截滚动条:弹窗打开时按当时的内容算高度,之后公网 IP、归属地等数据陆续到达,内容变高了窗口却不跟着变。现在内容一变高或变矮,窗口高度随之调整到正好放下,只有屏幕放不下时才出现滚动条;展开、收起区块时同样适用。
|
||||
|
||||
#### 调整
|
||||
|
||||
- CPU 弹窗与主窗口 CPU 页的“按应用汇总”、仪表盘的“高占用进程”,CPU 改为占整机的比例:全部核心跑满为 100%,和顶部的总占用是同一把尺子。以前按单核满载为 100% 计(活动监视器的算法),多核 Mac 上一个应用动不动显示 99%、75%,容易误以为很吃 CPU;鼠标悬停在数值上仍可看到单核口径的数值。进程页保持单核口径,与活动监视器一致。
|
||||
|
||||
#### 样式
|
||||
|
||||
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
|
||||
|
||||
@@ -274,6 +274,7 @@ npm 缓存 npm Cache
|
||||
协议版本 Protocol version
|
||||
单行 Inline
|
||||
单行文字 Inline Text
|
||||
占整机 CPU 的比例:{} 个核心全部跑满为 100% Share of total CPU: all {} cores fully busy is 100%
|
||||
占用 Usage
|
||||
占用超过 85% 时数值与图形显示为红色 Show values and graphs in red above 85%
|
||||
占用超过 85%(电池电量低于 20%)时数值与图形显示为红色 Values and graphics turn red above 85% usage (below 20% battery)
|
||||
@@ -537,6 +538,7 @@ npm 缓存 npm Cache
|
||||
拷贝 PID {} Copy PID {}
|
||||
持续时间 Duration
|
||||
按下快捷键… Press shortcut…
|
||||
按单核满载为 100% 计:{} Per core (one full core = 100%): {}
|
||||
按应用 By App
|
||||
按应用汇总 By App
|
||||
按文件夹 By folder
|
||||
@@ -700,7 +702,7 @@ npm 缓存 npm Cache
|
||||
正常 Normal
|
||||
此刻各核心的占用;超过 60% 变橙色、85% 变红色 Current load of each core; orange above 60%, red above 85%
|
||||
此设备没有可调节的风扇 This Mac has no adjustable fans
|
||||
每个应用按一个核心满载为 100% 计算,与活动监视器一致,所以各应用相加可以超过顶部的总占用 Each app is measured with one full core as 100%, like Activity Monitor, so the apps can add up to more than the total above
|
||||
每个应用占整机 CPU 的比例:{} 个核心全部跑满为 100%,与顶部的总占用是同一把尺子。鼠标悬停在数值上可以看按单核计的占用(活动监视器的算法) Each app's share of total CPU: all {} cores fully busy is 100%, the same scale as the total above. Hover a value to see it per core (the Activity Monitor way)
|
||||
每个指标一个图标,点击弹出该项详情 One icon per metric; click for its details
|
||||
每分钟把主要指标的平均值与峰值写入本机数据库,保留 7 天,不上传 Writes averages and peaks of key metrics to a local database every minute. Kept for 7 days, never uploaded.
|
||||
每核 {} {} per core
|
||||
|
||||
@@ -24,6 +24,22 @@ public enum Format {
|
||||
|
||||
private static let units = ["B", "KB", "MB", "GB", "TB", "PB"]
|
||||
|
||||
/// 逻辑核心数(性能核与能效核合计),进程 CPU 换算成整机占比时用
|
||||
public static let logicalCores = max(1, ProcessInfo.processInfo.activeProcessorCount)
|
||||
|
||||
/// 进程 CPU 按单核满载为 100% 的写法(活动监视器、top 的算法),多线程进程可以超过 100%。例:99.5%
|
||||
public static func coreShare(_ value: Double) -> String {
|
||||
"\((max(0, value) * 100).formatted(.number.precision(.fractionLength(1))))%"
|
||||
}
|
||||
|
||||
/// 进程 CPU 占整机的比例:全部核心跑满为 100%,和系统总占用是同一把尺子。`value` 是单核口径。
|
||||
/// 小于 0.1% 但不为 0 时写 “<0.1%”,不把正在跑的进程显示成 0
|
||||
public static func machineShare(_ value: Double) -> String {
|
||||
let share = max(0, value) / Double(logicalCores) * 100
|
||||
if share > 0, share < 0.05 { return "<0.1%" }
|
||||
return "\(share.formatted(.number.precision(.fractionLength(1))))%"
|
||||
}
|
||||
|
||||
public static func bytes(_ value: UInt64, base: ByteBase = .binary) -> String {
|
||||
bytes(Double(value), base: base)
|
||||
}
|
||||
|
||||
@@ -425,6 +425,7 @@ private struct TopProcessesCard: View {
|
||||
Text(tr("高占用进程")).dsFont(.xs, weight: .semibold)
|
||||
Spacer()
|
||||
Text("CPU").dsFont(.xs, weight: .medium).frame(width: DS.Size.valueColumn, alignment: .trailing)
|
||||
.help(tr("占整机 CPU 的比例:\(Format.logicalCores) 个核心全部跑满为 100%"))
|
||||
Text(tr("内存")).dsFont(.xs, weight: .medium).frame(width: DS.Size.valueColumn, alignment: .trailing)
|
||||
Color.clear.frame(width: DS.Size.iconInline)
|
||||
}
|
||||
@@ -445,9 +446,10 @@ private struct TopProcessesCard: View {
|
||||
.foregroundStyle(DS.Palette.textPrimary)
|
||||
.lineLimit(1)
|
||||
Spacer(minLength: DS.Space.s2)
|
||||
Text(verbatim: cpuText(process.cpu))
|
||||
Text(verbatim: Format.machineShare(process.cpu))
|
||||
.dsFont(.sm, weight: .medium)
|
||||
.foregroundStyle(cpuTone(process.cpu).color)
|
||||
.help(tr("按单核满载为 100% 计:\(Format.coreShare(process.cpu))"))
|
||||
.frame(width: DS.Size.valueColumn, alignment: .trailing)
|
||||
Text(verbatim: Format.bytes(process.memory))
|
||||
.dsFont(.sm)
|
||||
@@ -493,12 +495,10 @@ private struct TopProcessesCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func cpuText(_ value: Double) -> String {
|
||||
"\((value * 100).formatted(.number.precision(.fractionLength(1))))%"
|
||||
}
|
||||
|
||||
/// 按占整机的比例着色:一个进程吃掉整机一半算很高,四分之一算偏高(`value` 是单核口径)
|
||||
private func cpuTone(_ value: Double) -> Tone {
|
||||
value >= 0.8 ? .error : value >= 0.5 ? .warning : .neutral
|
||||
let share = value / Double(Format.logicalCores)
|
||||
return share >= 0.5 ? .error : share >= 0.25 ? .warning : .neutral
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ struct CPUPopover: View {
|
||||
}
|
||||
case .cpuApps:
|
||||
SectionCard(title: section.title,
|
||||
hint: tr("每个应用按一个核心满载为 100% 计算,与活动监视器一致,所以各应用相加可以超过顶部的总占用")) {
|
||||
hint: tr("每个应用占整机 CPU 的比例:\(Format.logicalCores) 个核心全部跑满为 100%,与顶部的总占用是同一把尺子。鼠标悬停在数值上可以看按单核计的占用(活动监视器的算法)")) {
|
||||
AppUsageList(apps: AppUsage.group(store.processes).sorted { $0.cpu > $1.cpu },
|
||||
rowCount: isDetailPage ? 10 : 6, metric: .cpu)
|
||||
}
|
||||
@@ -536,6 +536,7 @@ private struct AppUsageList: View {
|
||||
.dsFont(.xs, weight: .medium)
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(DS.Palette.textSecondary)
|
||||
.help(optional: metric == .cpu ? tr("按单核满载为 100% 计:\(Format.coreShare(app.cpu))") : nil)
|
||||
}
|
||||
ProgressTrack(fraction: peak > 0 ? value / peak : 0, color: DS.Palette.primary, height: DS.Space.s1, showsTrack: false)
|
||||
.padding(.leading, DS.Size.iconInline + DS.Space.s2)
|
||||
@@ -547,7 +548,8 @@ private struct AppUsageList: View {
|
||||
private func valueText(_ app: AppUsage) -> String {
|
||||
switch metric {
|
||||
case .cpu:
|
||||
return "\((app.cpu * 100).formatted(.number.precision(.fractionLength(1))))%"
|
||||
// 占整机的比例;单核口径的数值放在悬停提示里
|
||||
return Format.machineShare(app.cpu)
|
||||
case .memory:
|
||||
guard let total, total > 0 else { return Format.bytes(app.memory) }
|
||||
return "\(Format.bytes(app.memory)) · \(Format.percent(Double(app.memory) / Double(total)))"
|
||||
|
||||
@@ -52,6 +52,18 @@ struct FormatTests {
|
||||
#expect(Format.bytes(UInt64(412_000_000_000), base: .decimal) == "412 GB")
|
||||
}
|
||||
|
||||
@Test func formatsProcessCPU() {
|
||||
// 单核口径:一个核跑满是 100%,多线程可以超过
|
||||
#expect(Format.coreShare(0.995) == "99.5%")
|
||||
#expect(Format.coreShare(2.5) == "250.0%")
|
||||
// 整机口径:全部核心跑满是 100%
|
||||
let cores = Double(Format.logicalCores)
|
||||
#expect(Format.machineShare(cores) == "100.0%")
|
||||
#expect(Format.machineShare(cores / 2) == "50.0%")
|
||||
#expect(Format.machineShare(0) == "0.0%")
|
||||
#expect(Format.machineShare(0.0001) == "<0.1%")
|
||||
}
|
||||
|
||||
@Test func menuBarRate() {
|
||||
#expect(Format.menuBarRate(0) == "0 B/s")
|
||||
#expect(Format.menuBarRate(512) == "512 B/s")
|
||||
|
||||
@@ -59,15 +59,19 @@ system language by default; switch it in Settings → General → Language.
|
||||
<!-- changelog:start -->
|
||||
<!-- Generated from CHANGELOG.md by Scripts/sync_changelog.py. Do not edit by hand. -->
|
||||
|
||||
Latest release **0.3.2** (2026-09-16) · **7** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
|
||||
Latest release **0.3.2** (2026-09-16) · **8** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
|
||||
|
||||
<details open>
|
||||
<summary><b>2026-09-16</b> · Unreleased · 1 fixed · 5 style · 1 added</summary>
|
||||
<summary><b>2026-09-16</b> · Unreleased · 1 fixed · 1 changed · 5 style · 1 added</summary>
|
||||
|
||||
**Fixed**
|
||||
|
||||
- 网络弹窗偶尔差一点放不下、露出一小截滚动条:弹窗打开时按当时的内容算高度,之后公网 IP、归属地等数据陆续到达,内容变高了窗口却不跟着变。现在内容一变高或变矮,窗口高度随之调整到正好放下,只有屏幕放不下时才出现滚动条;展开、收起区块时同样适用。
|
||||
|
||||
**Changed**
|
||||
|
||||
- CPU 弹窗与主窗口 CPU 页的“按应用汇总”、仪表盘的“高占用进程”,CPU 改为占整机的比例:全部核心跑满为 100%,和顶部的总占用是同一把尺子。以前按单核满载为 100% 计(活动监视器的算法),多核 Mac 上一个应用动不动显示 99%、75%,容易误以为很吃 CPU;鼠标悬停在数值上仍可看到单核口径的数值。进程页保持单核口径,与活动监视器一致。
|
||||
|
||||
**Style**
|
||||
|
||||
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
|
||||
|
||||
+6
-2
@@ -55,15 +55,19 @@ CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
|
||||
<!-- changelog:start -->
|
||||
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
|
||||
|
||||
最新版本 **0.3.2**(2026-09-16) · 开发中 **7** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
|
||||
最新版本 **0.3.2**(2026-09-16) · 开发中 **8** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
|
||||
|
||||
<details open>
|
||||
<summary><b>2026-09-16</b> · 未发布 · 修复 1 · 样式 5 · 新增 1</summary>
|
||||
<summary><b>2026-09-16</b> · 未发布 · 修复 1 · 调整 1 · 样式 5 · 新增 1</summary>
|
||||
|
||||
**修复**
|
||||
|
||||
- 网络弹窗偶尔差一点放不下、露出一小截滚动条:弹窗打开时按当时的内容算高度,之后公网 IP、归属地等数据陆续到达,内容变高了窗口却不跟着变。现在内容一变高或变矮,窗口高度随之调整到正好放下,只有屏幕放不下时才出现滚动条;展开、收起区块时同样适用。
|
||||
|
||||
**调整**
|
||||
|
||||
- CPU 弹窗与主窗口 CPU 页的“按应用汇总”、仪表盘的“高占用进程”,CPU 改为占整机的比例:全部核心跑满为 100%,和顶部的总占用是同一把尺子。以前按单核满载为 100% 计(活动监视器的算法),多核 Mac 上一个应用动不动显示 99%、75%,容易误以为很吃 CPU;鼠标悬停在数值上仍可看到单核口径的数值。进程页保持单核口径,与活动监视器一致。
|
||||
|
||||
**样式**
|
||||
|
||||
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ settings:
|
||||
SWIFT_VERSION: "6.0"
|
||||
MARKETING_VERSION: "0.3.2"
|
||||
# 构建号四位数字,每次 make build 加一;营销版本只在发布时用 Scripts/version.sh patch / minor 修改
|
||||
CURRENT_PROJECT_VERSION: "0081"
|
||||
CURRENT_PROJECT_VERSION: "0082"
|
||||
MACOSX_DEPLOYMENT_TARGET: "14.0"
|
||||
# 本地开发使用 ad-hoc 签名;发布时改为 Developer ID(见 docs/PLAN.md)
|
||||
CODE_SIGN_STYLE: Manual
|
||||
|
||||
Reference in new issue
Block a user