How We Built an iOS Energy Consumption Monitor in Matrix
This article explains how the Matrix team created a low‑overhead iOS/macOS energy‑consumption monitor that captures high‑CPU thread stacks, builds aggregated power‑consume stacks, and helps pinpoint heating scenarios such as image uploads, VoIP calls, and game usage.
Introduction
During WeChat development we noticed devices heating after prolonged use, which could be caused by user behavior or a code issue that spikes CPU usage. Apple’s Energy Log (WWDC 2018) shows thread stacks when CPU exceeds 80%, but it is iOS‑only, costly to obtain, and not customizable.
Inspired by Energy Log, we added a custom energy‑monitoring feature to Matrix that reports an "Energy Log"‑style stack for high‑CPU threads.
Energy Monitoring Implementation
iOS/macOS Mach kernel provides thread_basic_info to retrieve per‑thread CPU usage:
struct thread_basic_info {
time_value_t user_time; /* user run time */
time_value_t system_time; /* system run time */
integer_t cpu_usage; /* scaled cpu usage percentage */
policy_t policy; /* scheduling policy in effect */
integer_t run_state; /* run state */
integer_t flags; /* various flags */
integer_t suspend_count; /* suspend count for thread */
integer_t sleep_time; /* number of seconds that thread has been sleeping */
};Summing cpu_usage across all threads yields the app’s total CPU load. On an iPhone 7 Plus, reading CPU for a ten‑thread app takes about 0.5 ms. When a thread exceeds a CPU threshold, backtrace() captures its stack; Matrix builds an energy stack from these snapshots.
Matrix starts a detection sub‑thread after app launch. It continuously identifies threads whose CPU usage is above 5 % and records their stacks in a circular memory queue. When average CPU over one minute exceeds 80 %, the stored stacks are merged into an energy‑consume stack.
Only threads with CPU usage > 5 % are sampled to avoid meaningless stacks.
Collected stacks are kept in an in‑memory circular queue.
When average CPU > 80 % for a minute, the queued stacks are combined into an energy‑consume stack.
On iPhone 7 Plus, backtrace() for a single thread averages 50 µs, and generating an energy stack takes about 17 ms, which is negligible in practice.
Energy Stack
The energy stack aggregates stacks from high‑CPU threads within a short time window. Numbers next to functions indicate how many times that function appeared in collected stacks; larger numbers imply higher CPU consumption.
Typical heating scenarios discovered via energy stacks include:
Simultaneous upload or download of multiple images.
Bulk download of WeChat collection resources.
VoIP video calls.
Running WeChat mini‑games.
Calculating WeChat’s disk usage (example stack shown below).
Using Xcode Instruments together with the energy stack, we identified the CPU hotspots for the disk‑usage calculation and optimized the caching logic.
Integration
Matrix’s energy monitor reuses the existing lag‑monitor thread. Configuration lives in WCBlockMonitorConfiguration: bGetPowerConsumeStack – enable energy monitoring when set to YES. powerConsumeStackCPULimit – CPU threshold for triggering a stack (default 80 %).
The energy stack is reported as a log type in WCCrashBlockMonitorPlugin: EDumpType_PowerConsume = 2011 Retrieving energy‑stack logs follows the same procedure as foreground lag logs.
Conclusion
The energy‑monitoring feature is now part of Matrix, fully open‑sourced at https://github.com/tencent/matrix . Contributions and issues are welcome.
WeChat Client Technology Team
Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
