How the PC side builds a high-performance environment for HarmonyOS tests and keeps the device awake
Before a test, the PC-side HdcPerfController starts SP_daemon on the device over HDC, opens a high-performance capture session with -lockfreq and manages screen wake: protocol commands, keepalive and stop order, recovery records, plus 16 independent frequency snapshots and a two-hour screen-timeout override measured on the SGU-AL10.
This article has no English version yet; the original Chinese text follows. Chart labels are in Chinese as well.
我们把高性能运行条件放在 PC 端监控软件中管理。测试前,在 HdcPerfController 中选择“高性能 + 监控”,PC 端通过已授权的 HDC 连接启动设备的 SP_daemon,再通过内部采集协议提交高性能请求。测试应用继续负责自己的计算、线程调度和结果校验。
因此,单核测试曲线中的满频或接近满频平台,需要归到 PC 端建立的实验条件。应用内的 QoS、uclamp 和 affinity 用来表达线程需求与运行位置;它们的有效回读与设备的频率曲线分别保存。这样可以同时知道“负载在怎样的核心上执行”和“监控端为设备申请了怎样的性能状态”。
一次高性能采集会话按下面的顺序建立:
PC 端选择目标设备
→ 为会话生成随机 token
→ HDC 启动 SP_daemon -editorServer:<token>
→ 动态本地 TCP 端口转发到设备 tcp:8284
→ TCP init 命令携带 -lockfreq
→ start,接收采样,定期 keepalive
→ stop 确认,核对恢复并清理会话资源
HDC 负责跨设备连接,SP_daemon 负责设备内采集和性能请求。它们有各自的端口:手机的无线 HDC 端口、PC 的 HDC server 端口、设备上的 SmartPerf 8284,以及为当前会话动态分配的 PC 本地转发端口。代码始终绑定选定的 HDC target,避免把另一台手机当作同一次测试。
开始前先检查已有 daemon 和转发。SP_daemon 的 Editor 通道只接受一个客户端;发现 HiSmartPerf 或其他监控会话时,软件会报告冲突,由用户决定是否接管。启动后先核实带本次 token 的进程,再建立转发并使用真正的采集连接。一次性 TCP 探活连接可能占住这个通道,因此就绪检查不另外打开探活 socket。
会话入口的代码形态如下。hdc 是我们的进程调用封装,SmartPerfSession 是 TCP 协议封装;真正改变采集模式的是 lockFrequency: true:
var token = Convert
.ToHexString(RandomNumberGenerator.GetBytes(16))
.ToLowerInvariant();
var stopSignal = new TaskCompletionSource<bool>(
TaskCreationOptions.RunContinuationsAsynchronously);
var daemonHost = hdc.StartLongRunningShell(
target, $"SP_daemon -editorServer:{token}");
var forward = await hdc.CreateForwardAsync(target, localPort, 8284);
if (!forward.Success)
throw new IOException($"HDC 转发失败:{forward.CombinedOutput}");
await using var session = new SmartPerfSession(token);
await session.ConnectAsync(localPort);
try
{
await session.StartAsync(
lockFrequency: true,
intervalMs: 500,
targetPackage: packageName,
cancellationToken: runCancellation);
// UI 停止操作调用 stopSignal.TrySetResult(true)。
await stopSignal.Task.WaitAsync(runCancellation);
}
finally
{
// 清理使用独立预算,不复用可能已经取消的 runCancellation。
using var cleanup = new CancellationTokenSource(TimeSpan.FromSeconds(20));
await session.StopAsync(cleanup.Token);
}
这段代码展示协议会话的启动与停止;daemon 就绪等待、失败启动后的恢复和 HDC 资源回收由外层 Service 负责。DisposeAsync() 只关闭本地连接和保活任务,不能代替显式 StopAsync()。start 已发出但响应丢失时,设备可能已经开始工作,所以 finally 不以“收到启动成功”为条件。stop 未确认时,外层保留恢复记录。
token 在启动 daemon 和后续控制命令中保持一致,每次会话重新生成。它与目标设备、daemon 身份和转发端口一同保存在会话状态中;恢复记录在启动前写入,屏幕原状态也在修改前写入。停止时据此确认资源归属。PC 保持启动 daemon 的 HDC 子进程与 TCP 会话生命周期,采集过程不依赖被测应用维持这条连接。
“仅监控”和“高性能 + 监控”使用同一条采样通道。高性能模式在 init 参数后追加 -lockfreq,完整命令形态是:
init::<token> -SESSIONID 1 -INTERVAL 500 -c -g -r -d -t -p -net -lockfreq
需要应用进程和帧率指标时,追加 -PKG <bundleName> -f。初始化、启动、保活和结束各有明确职责:
| 命令 | 作用 | 响应处理 |
|---|---|---|
init::<token> ... -lockfreq | 配置指标、间隔和高性能请求 | 等待 init::True,识别失败响应 |
start::<token> | 启动当前采集任务 | 等待 start::True,随后检查真实样本 |
keepalive::<token> | 保持当前 daemon 会话 | 每 60 秒发送;该命令没有 ACK |
stop::<token> | 停止采集和性能请求 | 最长等待 12 秒的 stop::True,再核对清理 |
协议控制命令直接写入 UTF-8 字节,不自动追加换行。TCP 的接收边界不等于一条完整消息,所以控制响应与采样数据由持续读取器解析;发送端则串行写入,避免保活和停止命令穿插:
static async Task SendCommandAsync(
NetworkStream stream,
SemaphoreSlim writeGate,
string command,
CancellationToken cancellationToken)
{
await writeGate.WaitAsync(cancellationToken);
try
{
byte[] bytes = Encoding.UTF8.GetBytes(command);
await stream.WriteAsync(bytes, cancellationToken);
}
finally
{
writeGate.Release();
}
}
发送 init 或 start 前先注册相应响应等待器,避免响应很快返回时漏接。等待器同时识别 TOKEN_CHECK_FAILED、init::False、start::False 等失败文本。start::True 表示任务被接受,实际性能状态由接下来的逐核频率读回确认。
随文的 C# 协议辅助代码包含命令构造、参数检查、带超时的串行发送,以及屏幕状态解析和恢复命令选择。它可以作为接入起点;HDC 进程管理、响应等待器、采样解析和持久化恢复仍由完整会话层承担。
采样数据经过持续读取器合并 TCP 拆包、解析完整批次,再送往两个独立出口:原始 CSV 保存队列和界面显示队列。CSV 保留设备时间戳、接收顺序和逐核数值;界面只保留有限时间窗口,避免长时间监控不断占用内存。暂停曲线冻结的是显示视图,完整采样与保存继续进行。这个分工让用户可以查看某一段曲线,而不用中断正在运行的测试。
设备端接到 -lockfreq 后,进入其固件实现的性能管理路径。我们核对的设备二进制中,这条路径解析 PerfCmdHandle 并以约 4 秒周期维护请求。PC 端每 60 秒发送的 TCP keepalive 维持的是采集会话,设备端约 4 秒的周期维护的是性能请求,两者不能混作一个定时器。具体资源由固件解释,PC 的这条命令没有携带任意 MHz 目标参数。
在 SGU-AL10、系统 7.0.0.109 的一次频率验证中,高性能会话期间报告的 CPU 频点为:
| 核心组 | 报告频率 |
|---|---|
| CPU0–3 | 1.72 GHz |
| CPU4–11 | 2.27 GHz |
| CPU12–13 | 2.75 GHz |
我们在开启前、会话保持期间和 stop 后分别做独立读回。会话期间 8/8 次快照中,14 个逻辑 CPU 均达到当轮报告的三个核心组上限;开启前为 0/3,恢复后为 0/5。SmartPerf 原始采样中另有 27/28 帧匹配该组频点。这里报告的是各次快照的命中数,不能据此把有限采样换算为整段时间的频点驻留率。
PC 端高性能会话的独立频率读回
SGU-AL10 · 7.0.0.109 · 14 个逻辑 CPU 均达到当轮报告上限的快照比例
柱形表示有限采样的命中比例,不能换算为整段运行时间的频点驻留率。
数据表
| 阶段 | 命中 / 快照数 | 比例 |
|---|---|---|
| 开启前 | 0 / 3 | 0% |
| 高性能会话期间 | 8 / 8 | 100% |
| stop 后 | 0 / 5 | 0% |
下面把同一组独立快照按采集顺序展开,展示开启、保持和恢复时各核心组的实际读回。每个点是一份快照中的组内频率范围与均值;横轴是快照序号,不把两次读回之间的状态补成连续采样。
高性能会话的频率变化与恢复
SGU-AL10 · 7.0.0.109 · 各核心组报告频率的独立读回
点为组内均值,色带为组内最小至最大值,虚线是当轮报告上限。不跨阶段连线,不对快照间状态补样。
数据frequency-cycle.json·frequency-groups.csv·frequency-cores.csv
数据表
| 快照 | 阶段 | 相对时间 / s | CPU0–3 均值(范围)/ GHz | CPU4–11 均值(范围)/ GHz | CPU12–13 均值(范围)/ GHz |
|---|---|---|---|---|---|
| 1 | 开启前 | 0.0 | 1.53 | 0.619(0.558–0.85) | 1.2 |
| 2 | 开启前 | 3.2 | 1.53 | 0.582(0.558–0.75) | 1.2 |
| 3 | 开启前 | 6.4 | 1.53 | 0.661(0.558–1.38) | 1.2 |
| 4 | 高性能会话期间 | 13.3 | 1.72 | 2.27 | 2.75 |
| 5 | 高性能会话期间 | 16.4 | 1.72 | 2.27 | 2.75 |
| 6 | 高性能会话期间 | 19.5 | 1.72 | 2.27 | 2.75 |
| 7 | 高性能会话期间 | 22.6 | 1.72 | 2.27 | 2.75 |
| 8 | 高性能会话期间 | 25.8 | 1.72 | 2.27 | 2.75 |
| 9 | 高性能会话期间 | 28.9 | 1.72 | 2.27 | 2.75 |
| 10 | 高性能会话期间 | 31.9 | 1.72 | 2.27 | 2.75 |
| 11 | 高性能会话期间 | 35.0 | 1.72 | 2.27 | 2.75 |
| 12 | stop 后 | 42.7 | 1.283(1.04–1.53) | 0.558 | 1.2 |
| 13 | stop 后 | 46.0 | 1.468(1.28–1.53) | 0.568(0.558–0.64) | 1.2 |
| 14 | stop 后 | 49.3 | 1.408(1.04–1.53) | 0.558 | 1.2 |
| 15 | stop 后 | 52.6 | 1.438(1.16–1.53) | 0.558 | 1.2 |
| 16 | stop 后 | 55.9 | 1.378(0.92–1.53) | 0.558 | 1.2 |
频率效果与固件有关。在 7.0.0.111 的另一次同机会话中,协议同样启动成功,但逐核频率仍在变化,软件显示“未命中金标”。因此界面分别报告协议状态与频率验证结果;-lockfreq 被接受并不保证每个固件、每个热状态都得到同一组固定频点。
停止时先取消并等待 keepalive 任务退出,再发送同一 token 的 stop。12 秒是 stop 响应的等待期限,整个清理还包括屏幕恢复、连接释放和资源复查。协议停止、屏幕设置或自有资源清理未确认时,会话恢复记录继续保留,重新连接同一设备后完成清理。直接结束 daemon 进程不能替代正常的停止协议。
频率恢复验证作为独立选项执行:确认 stop 并清理会话后,等待约 3 秒,读取 9 个逐核恢复样本。如果这个后置采样失败,界面报告“恢复未验证”;它不会把已经确认完成的协议清理改写成尚未停止,也不会重新创建已经清除的恢复记录。
屏幕常亮也属于监控会话管理。测试前勾选“监控时屏幕常亮”,PC 修改的是被测手机的临时自动熄屏覆盖值,控制动作经 HDC 发给手机的电源服务。
开启前先读取并保存基础超时与临时覆盖值:
hidumper -s PowerManagerService -a '-s'
随后优先请求无限临时超时:
power-shell timeout -o -1
有的商用固件明确拒绝 -1。只有当设备返回精确的 Override screen off time to -1 failed 时,程序才切换到两小时覆盖值:
power-shell timeout -o 7200000
7200000 的单位是毫秒。采用有限覆盖时,监控期间每 30 分钟重新提交一次;续期失败按一分钟间隔最多重试三次,并在界面显示异常。续期任务与数据接收分别运行,短暂的屏幕控制失败不会把已收到的采样丢掉。
设置超时不会自动点亮已经熄灭的屏幕,因此开始时再发送一次:
power-shell wakeup
程序随后重新读取电源服务,检查 OverrideTimeout 是否等于请求值,并确认 Current State: AWAKE。power-shell 即使打印失败,也可能以退出码 0 结束;因此判断依据是语义响应和读回状态。续期只更新超时,不重复发送 wakeup,从而保留用户手动熄屏的选择。
停止时的顺序同样重要:先取消并等待在途续期真正结束,再恢复原设置。否则,一条迟到的两小时续期可能覆盖刚刚完成的恢复。恢复命令由开启前保存的状态决定:
if (renewal is not null)
await renewal.DisposeAsync(); // 取消续期,并等待在途命令结束。
string restoreCommand = original.OverrideTimeoutMs is int previous
? $"power-shell timeout -o {previous}"
: "power-shell timeout -r";
await ShellAsync(target, restoreCommand);
// 再次读取 PowerManagerService,确认基础值和覆盖值均匹配原状态。
临时覆盖不是一个栈。原来已有覆盖值时,需要写回那个值;原来没有覆盖值时,才使用 timeout -r。原状态在修改设备前写入持久化会话记录,所以 PC 强退、手机离线或恢复失败后,仍能连接原设备继续恢复。
常亮功能也有完整实测。在 SGU-AL10、7.0.0.109 上,原设置为 600000 ms,即 10 分钟,没有临时覆盖。设备明确拒绝无限值后,程序成功设置 7200000 ms,即 2 小时的临时覆盖并读回 AWAKE。该次会话实际完成了 5 次约 30 分钟续期,停止后读回原来的 10 分钟基础设置,临时覆盖消失。
| 实测环节 | 设备读回 |
|---|---|
| 监控开始前 | 基础超时 600000 ms,无临时覆盖 |
| 常亮开启后 | 基础超时 600000 ms,临时覆盖 7200000 ms,AWAKE |
| 5 次定时续期 | 均确认临时覆盖仍为 7200000 ms |
| 停止并恢复 | 基础超时回读 600000 ms,无临时覆盖 |
这次会话只有开始时发送了一次 wakeup。第五次续期时读回的电源状态已经是 SLEEP,超时覆盖仍成功续期,说明用户主动熄屏后不会被续期任务反复唤醒。这验证的是超时管理、续期和恢复,并不表示屏幕连续亮了两个半小时。
三个周期由不同任务承担:设备内部约 4 秒维护性能请求,PC 每 60 秒维持采集会话,有限屏幕覆盖每 30 分钟续期。暂停曲线只冻结显示,后台采样、CSV 保存和这些会话任务继续运行。采样间隔虽然请求为 500 ms,SGU-AL10 返回的新样本实测约为 1 Hz;图表使用设备真实时间戳,不复制样本补成更高频率。
Comments
Discussion welcome, no login needed. Comments show up right away; abuse and ads get removed.
Admin
Comments
Loading…