CN

Android Perfetto Series 9: CPU Information Interpretation

Word count: 5kReading time: 31 min
2025/11/12
loading

When an app misses a frame deadline, first distinguish Running, Runnable, and sleeping or blocked time. Each leads to a different investigation. This article uses CPU scheduling, frequency, idle, and sampling data to turn “the CPU is slow” into observable behavior.

Verify CPU numbering, frequency limits, and scheduling policy on the target device. Running on a smaller core alone does not establish a scheduling error.

Version note (reviewed October 2026): The original screenshots are retained; their CPU numbering and vendor policies do not apply to every Android device. Table and UI details are checked against Perfetto v58.3, and scheduler/frequency concepts against Linux v6.12. That is not a claim about the screenshot device’s kernel. Performance Hint is covered separately from API 31.

Table of Contents

Perfetto Series Articles

  1. Android Perfetto Series Catalog
  2. Android Perfetto Series 1: Introduction to Perfetto
  3. Android Perfetto Series 2: Capturing Perfetto Traces
  4. Android Perfetto Series 3: Familiarizing with Perfetto View
  5. Android Perfetto Series 4: Opening Large Traces via Command Line
  6. Android Perfetto Series 5: Android App Rendering Flow Based on Choreographer
  7. Android Perfetto Series 6: Why 120Hz? Advantages and Challenges of High Refresh Rates
  8. Android Perfetto Series 7: MainThread and RenderThread Deep Dive
  9. Android Perfetto Series 8: Understanding Vsync Mechanism and Performance Analysis
  10. Android Perfetto Series 9: CPU Information Interpretation
  11. Android Perfetto Series 10: Binder Scheduling and Lock Contention
  12. Android Perfetto Series 11: PerfettoSQL, Trace Processor and Regression Detection
  13. Android Perfetto Series 12: Trace Dataflow and Data Loss
  14. Android Perfetto Series 13: Perfetto SDK, Track Event and App Field Traces
  15. Android Perfetto Series 14: heapprofd and Memory Profiling
  16. Android Perfetto Series 15: Boot Traces and Long-running Field Tracing
  17. Android Perfetto Series 16: GPU, Power Counters and Hardware Bottlenecks
  18. Android Perfetto Series 17: Scenario Automation and Platform Tracing
  19. Android Perfetto Series 18: Input Response Latency
  20. Video (Bilibili) - Android Perfetto Basics and Case Sharing
  21. Video (Bilibili) - Android Perfetto: Trace Graph Types - AOSP, WebView, Flutter + OEM System Optimization

CPU Information Overview in Perfetto

In the Perfetto UI, CPU-related information is usually grouped at the top and is the starting point for performance analysis. Read these three kinds of information together; frequency and idle can share a track:

  • CPU Scheduling: Shows which thread is executing on each CPU core at each time point.
  • CPU Frequency: Shows the frequency changes of each CPU core or core cluster.
  • CPU Idle: Shows the low-power states (C-States) entered by each CPU core.

image-20251112214415666

By analyzing CPU-related information, you can answer the following key performance questions or perform competitive analysis:

  • Why isn’t the application main thread executing? Is it being preempted by other threads?
  • What’s the reason for a task executing slowly? Was it scheduled to a low-performance core?
  • In specific scenarios, is the CPU frequency limited?
  • After the app enters the background, do CPUs enter idle? Device suspend requires separate suspend evidence.

Trace Config Required for Capturing CPU Information

The following is a textproto data-source fragment for scheduling, wakeups, and frequency, based on the Perfetto Android analysis guide. It is not YAML or a complete recording config: combine it with recording settings such as buffers and duration_ms. Kernel events and symbol access depend on the device build and Perfetto version.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
ftrace_events: "sched/sched_switch"
ftrace_events: "power/suspend_resume"
ftrace_events: "sched/sched_blocked_reason"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "sched/sched_wakeup_new"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
symbolize_ksyms: true
disable_generic_events: true
}
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}
data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
cpufreq_period_ms: 250
}
}
}

This fragment does not enable syscalls, CPU callstack sampling, or vendor Min/Max Frequency Limit events. Extra tracks in the first screenshot are not guaranteed by this config. cpufreq_period_ms: 250 is a polling interval; changes between polls require frequency events to be observed.

CPU Core Architecture: big.LITTLE

Confirm the device topology before interpreting placement. Mobile SoCs may have cores of different capacities without using a traditional LITTLE microarchitecture. “Small” and “big” can be relative labels within one chip; CPU numbers, maximum frequencies, and screenshot annotations do not identify the microarchitecture.

  • LITTLE cores: In traditional heterogeneous designs, cores optimized for lower power. Background threads are not confined to them.
  • Big cores: Higher-performance cores suited to heavier work; energy and speed still depend on the workload.
  • Prime cores: The highest-performance core or group on some chips, not necessarily a single core.

Perfetto usually numbers CPUs from zero, but numbers do not rank performance. The three original screenshots label 4+4, 4+3+1, and 5+2 groups. Treat those as annotations for those captures, not verified chip identities or mappings for another device.

image-20251112214626810

image-20251112215105550

image-20251112214833158

Check device specifications and topology, capacity, and cpufreq policy information under /sys/devices/system/cpu/. A shared frequency policy need not be the same as a microarchitecture group.

CPU Scheduling

The CPU Scheduling track shows where threads run. It shows the resulting execution intervals, rather than the scheduler’s complete decision process. Its data source is the sched/sched_switch event in kernel ftrace.

CPU Scheduling Area

Each CPU core corresponds to an independent track row. Different colored blocks on the track represent specific threads running on that CPU core during that time slice.

  • UI details: Clicking a CPU slice, the details panel will show the cpu, end_state, priority, associated process/thread, etc. for that scheduling; expanding the process downward also shows each thread’s independent track, facilitating tracking of a single thread’s state evolution (refer to official documentation).

image-20251112215540626

Thread State Deep Dive

In Perfetto, selecting a thread-state interval shows its recorded state in the details panel; panel labels vary by UI version. This state information comes from Perfetto’s thread_state table.

Running (Green)

State Definition: Green represents the thread is executing code on the CPU. This is scheduled-on-CPU time reconstructed from scheduler events. It can include user/kernel work, busy waits, CPU stalls, and interrupt handling that has not been subtracted.

image-20251112215705017

Analysis Points:

  • Execution Duration: A long Running interval shows CPU occupancy, which may come from computation, a system call, or busy waiting. Use profiling or trace markers to identify the actual work.
  • Running Core: Analyze in combination with the CPU’s core architecture (e.g., big.LITTLE). Whether a compute-intensive task is scheduled to the expected performance core (big core) is an important basis for evaluating whether the scheduling strategy is reasonable.
  • Running Frequency: The thread’s actual execution speed is also affected by CPU frequency. The effect of a frequency limit depends on the balance of computation, memory accesses, and waits. Therefore, comprehensive analysis is needed in combination with the CPU Frequency track.

R (Runnable)

State Definition: The thread is runnable but not executing. Affinity, cpusets, scheduling class, and bandwidth constraints can still limit where or when it runs. In Perfetto’s thread private track, the Runnable state is usually displayed as light green or white bars.

image-20251112220248837

Analysis Points:

  • Runnable and Jank: A long Runnable interval means the thread was eligible to run but had not received CPU time. Compare it with the target frame deadline before attributing visible jank to this delay.

Three Types of Runnable:
Careful observation reveals that the “predecessor” of threads entering Runnable state varies. Based on the kernel’s scheduling timing, we can divide it into three situations:

  1. Wake-up from Sleep: This is the most common type. A thread can wake from S or D when its wait condition is satisfied. Interruptible waits can also end on signals, and timed waits can expire. It then becomes runnable before being selected to execute.

  2. Still Runnable After Descheduling: A running thread can be switched out while remaining runnable. sched_switch may record prev_state=R; that field alone does not establish the precise scheduling cause or whether the thread was in user mode.

  3. Preempted While Runnable: prev_state=R+ is shown as Runnable (Preempted). It does not prove the thread was executing kernel-mode code or identify the thread that preempted it.

To investigate repeated Runnable (Preempted) periods, compare the next thread on the same CPU, priorities, and system load. Do not infer kernel-mode preemption or change thread priorities from R+ alone.

image-20251112220534899

S (Sleep / Interruptible Sleep)

State Definition: The thread enters sleep while waiting for an event and can be interrupted by signals. This is the most common sleep state and is usually benign because it doesn’t consume CPU resources while waiting.

image-20251112221122170

Analysis Points:

  • Waiting Resources: A long sleep matters when it delays pending critical-path work. An idle thread waiting for new work is not itself a performance problem. Common waiting reasons include:
    • Lock Contention: Waiting on a lock or condition variable, possibly implemented with futexes. Java monitors, native mutexes, and futexes are different abstractions.
    • Binder Communication: Waiting for another process to return results through Binder call.
    • I/O Operations: Waiting for fd readiness, for example in epoll_wait; this can also be an idle Looper rather than network I/O.
    • Explicit Sleep: Code called Thread.sleep() or Object.wait().
  • Dependency Analysis: In Perfetto, selecting a CPU slice can reveal its wakeup source when the relevant events are available. This helps quickly locate inter-thread dependencies. Combined with function call stacks, you can further locate the specific code causing sleep.
    image-20251112221703122

D (Uninterruptible Sleep)

State Definition: D means uninterruptible sleep in a kernel wait path. It can involve I/O, but is not limited to hardware I/O; use the state field and blocked-function evidence rather than track color alone.

image-20251112220738143

Analysis Points:

  • Critical-path waits: A D wait can end when its wait condition is satisfied; it does not mean “unable to respond to any event.” A long critical-path wait can contribute to an ANR timeout, but the roughly 103µs wait shown here does not establish an ANR.
  • Common Causes:
    1. Disk I/O: Frequent or single large file read/write operations.
    2. Memory Pressure: Reclaim or paging may extend a kernel wait; check the events before attributing it to storage I/O.
    3. Kernel Driver Issues: Implementation defects in some kernel drivers may also cause threads to fall into D state.
  • Investigation Direction: An io_wait flag is a useful clue, not proof of a particular file or device. Check blocked_function, I/O events, and the target thread’s call stack.

Wakeup Relationship Analysis

Inter-thread dependencies are a difficult point in performance analysis. For a thread sleeping for a long time, the key is finding out “who it’s waiting for”. Perfetto provides powerful wakeup relationship visualization functionality.

  • UI Operation: In Perfetto’s CPU area, left-click to select a thread Task in Running state. With an attributable wakeup event recorded, Perfetto can draw an arrow from “waker” to “wakee” and highlight the thread slice where the wakeup source is located.
    image-20251112221703122
  • Underlying Principle: In Perfetto v58.3, ParseSchedWaking uses the execution context and target pid in sched_waking to record wakeup relationships. Interrupt context, missing events, and trace boundaries limit attribution; the interrupted current thread need not be the application-level resource producer.

A wakeup arrow shows when one thread made another runnable. A Binder transaction or lock-owner chain requires the corresponding Binder, contention, or call-stack evidence; one wakeup arrow cannot reconstruct the full dependency chain.

Scheduling Wakeup and Latency Analysis

A woken thread may still wait before it starts executing. Java Object.notify() does not map one-to-one to a kernel wakeup: the waiter must reacquire the monitor, may block again, and can also return because of a timeout or spurious wakeup.

image-20251112222240991

The screenshot shows UMSDecisionSing woken from a kworker on CPU6 and later running on CPU3. Its roughly 1.463ms interval is wakeup-to-execution latency, not proof of a particular priority, migration, or bandwidth problem.

  • Count both R and R+ in thread_state, then inspect the CPU where execution resumes.
  • Check allowed CPUs, competing tasks, scheduling class, cgroup quotas, and IRQ/SoftIRQ work. An idle CPU elsewhere may not be available to this thread.
  • sched.end_state=R+ describes the end of the preceding Running interval. It is not an end-state field on the Runnable interval.

Differences Between sched_waking and sched_wakeup and Non-strict Work-conserving

In the Linux v6.12 tracepoint definitions, sched_waking is emitted in the waking context. sched_wakeup is emitted when the target is set to TASK_RUNNING, not necessarily from that same context. It is not restricted to cross-CPU wakeups and does not mean execution has started. sched_wakeup_new covers new tasks.

The Perfetto scheduling guide describes placement and energy trade-offs that can leave tasks queued. An idle CPU alongside Runnable time does not prove such a policy: first check allowed CPUs, quotas, and data loss. Use both event types and cross-CPU signaling when investigating the wakeup path; do not treat sched_waking as proof that enqueueing has finished.

User Mode and Kernel Mode: sys_* Slices Locate Empty Flame Graphs

sys_* slices require raw_syscalls/sys_enter and raw_syscalls/sys_exit, which the base config does not enable. The syscall documentation also specifies Android userdebug builds; kernel support and recording permissions matter.

A long syscall can include Running, Runnable, and sleeping time. Its full wall duration is not kernel CPU time. For a sparse user-space flame graph, first check sampling configuration, scope, permissions, sample counts, losses, and unwinding errors, then correlate syscall slices with thread states.

  • Long syscall overlapping Sleep/D: investigate the specific I/O or synchronization wait.
  • Mostly Running within a syscall: inspect kernel samples and call stacks.
  • No syscall slice: the events may not have been recorded; this does not establish a user-space bottleneck.

CPU Time and Wall Time

image-20251112223105996

  • Wall is elapsed time. Here, CPU means this thread’s Running time, excluding other threads’ work while it waits.
  • With complete, non-overlapping states clipped to the interval, wall time can be partitioned into Running, Runnable (including R+), sleeping/blocked, and other states. Keep missing coverage unknown rather than zero.
  • The selected screenshot slice is RenderThread’s DrawFrames -1: about 127.411ms wall time and 95.485ms Running, with S, D, R, and R+ accounting for the remainder. These are observations, not a frame budget.
  • Apply the same method to a main-thread Choreographer#doFrame slice. Wall≈CPU indicates mostly on-CPU work, not necessarily algorithmic computation; Wall≫CPU calls for separating scheduling delay from dependency waits.

CPU Frequency Deep Dive

Frequency affects execution time and power, but the frequency track is not a power meter. The Linux v6.12 cpufreq documentation explains that scaling_cur_freq often reports the driver’s last requested frequency rather than the hardware’s instantaneous effective frequency. Ftrace frequency events likewise depend on driver reporting.

Core Factors Affecting CPU Frequency

  1. Task utilization: Systems using schedutil derive frequency requests from scheduler utilization signals. Check the target policy’s scaling_governor; Android vendors do not all use one governor. Limits, rate controls, and hardware response also affect the result.
  2. Scenario policy (Power HAL): Devices interpret launch and interaction hints. Implementations may adjust frequency constraints, placement, or other parameters; a hint is not a universal write to scaling_min_freq or scaling_max_freq.
  3. Thermal control: Thermal management can constrain CPU/GPU performance through more than cpufreq limits. Low frequency alone is not evidence of thermal throttling; correlate thermal status and limit data.
  4. Power policy: Battery, supply limits, and system modes can constrain performance in device-specific ways.

For a heavy task at low frequency, examine placement, utilization, policy limits, thermal state, and workload type together. Device constraints and inefficient app work can coexist.

image-20251112223237018

The tooltip reads 2,100,000 kHz, or 2.1GHz; 5GHz on the left is the axis scale. Colored/gray overlays represent recorded non-idle/idle state, not a direct count of running tasks. The overview’s separate Min/Max Freq Limit tracks are different data from current frequency.

Frequency Data Collection and Platform Differences

The Perfetto frequency and idle guide describes two collection paths:

  • power/cpu_frequency records driver-reported changes. No event means no observation; unknown initial frequency is not zero.
  • linux.sys_stats with cpufreq_period_ms polls readable sysfs values. It adds snapshots but cannot recover transitions between polls. Missing paths, permissions, and offline CPUs can limit coverage.

CPUs sharing a cpufreq policy often change frequency together, but that grouping need not match “small/medium/big” labels. In v58.3, the CPU Frequency plugin LEFT JOINs optional idle tracks, so missing idle data does not itself remove frequency tracks.

Reading CPU Idle: In the raw power/cpu_idle counter, 4294967295 (0xffffffff) means exiting idle. Zero can be the first idle-state index; it does not mean “running.” State numbering and exit latency are SoC-specific, and CPU idle is not full-device suspend. A frequency shown during idle may be the last reported value before idle and cannot directly establish power use.

big.LITTLE: Same Frequency Doesn’t Equal Same Performance or Power Consumption

  • On heterogeneous CPUs, both at 2.0GHz, LITTLE cores and big cores’ actual computing power and energy consumption are not equivalent. Frequency must be understood in combination with core type.
  • Core capacity and IPC: Big cores often complete the same work faster at the same frequency, but their total energy use is not guaranteed to be lower; voltage, duration, and idle behavior also matter.
  • Cluster-level DVFS: Mobile SoCs mostly scale frequency by “cluster”. LITTLE core cluster’s high frequency doesn’t equal big core cluster’s medium frequency performance output; different clusters have different voltage-frequency-energy curves, same frequency point cannot be horizontally compared for “performance per watt”.
  • Memory/cache/interconnect bottleneck: If hotspot is constrained by memory bandwidth, LLC hit rate, or system interconnect (NoC), simply raising LITTLE core frequency has limited benefit; larger caches or stronger prefetching may help, depending on the access pattern and shared bottlenecks.
  • Energy efficiency curve is non-linear: LITTLE cores approaching maximum frequency often enter energy efficiency steep decline zone (voltage elevation causes marginal energy consumption surge), while big cores at medium frequency points may achieve “higher unit energy efficiency”. Same frequency comparison ignores “voltage-frequency-energy” three-dimensional trade-off.

P-States and governor

P-States describe performance states. A driver may expose a discrete table or a performance range with autonomous hardware control; not every graph transition is schedutil selecting a discrete P-State.

  1. Read reported frequency from CPU Frequency. Determine limits from policy data or separately recorded limit tracks, not from the current-frequency curve alone.
  2. Inspect the CPUs used by the critical thread and map them to verified topology.
  3. “High frequency + smaller core + still slow” is a clue. Examine code, memory/cache behavior, and scheduling constraints before changing code or policy.

Energy-efficiency comparisons require comparable work, thermal conditions, and available power/energy measurements. GHz is not a substitute.

Reading ADPF / Performance Hint

PerformanceHintManager is available from Android 12 / API 31. A session groups related threads and reports target and actual work durations so the system can attempt to adjust placement or performance. Creation can return null on unsupported devices. It neither guarantees a frequency boost nor pins work to big cores.

  1. Identify the periodic workload and its budget. A 16.6/11.1/8.3ms refresh interval is not automatically the app’s entire work budget; pipeline timing and reserved time matter.
  2. Compare Running, Runnable, waits, and samples over the same window. Hints can influence scheduling but do not remove lock dependencies or replace code optimization.
  3. Compare actual duration, placement, and frequency under comparable workload, thermal state, and background load. One API call or migration does not demonstrate an effect.
  4. Check thermal and power constraints. Hints do not guarantee bypassing them.

The target duration should describe this thread group’s work budget, rather than blindly copying the display period or the previous iteration’s duration.

CPU Profiling, Flame Graphs, and Symbolization

CPU states show whether a thread received CPU time. On-CPU sampling helps identify what it executed. Define the problem window and target thread before examining Running time, samples, and stacks. If the target mostly waits in R/S/D, profiling competitors or dependency threads may still help; its own on-CPU flame graph cannot explain the complete wait chain.

The Perfetto v58.3 CPU profiling guide uses linux.perf with callstack_sampling to collect stacks. The base CPU config above does not enable it. That guide’s Android recording workflow requires Android 15+ and a profileable/debuggable target app on user builds. This is not a blanket claim about support in every older version.

Method Useful for Check
Perfetto linux.perf Scheduling and stacks on one timeline Sampling event, scope, permissions, overhead, and loss
simpleperf App/native hotspots and symbolization Preserve perf.data and matching binaries/symbols
Gecko Profile JSON Export from simpleperf to Perfetto UI or Firefox Profiler Verify preserved timestamps and weight semantics

The Android 16 simpleperf script reference documents gecko_profile_generator.py -i perf.data. This is a Gecko export, not generically “Perfetto protobuf.” binary_cache_builder.py -lib can collect binaries from matching symbol directories; native build IDs, ART/JIT information, and device system-library versions must still match.

Sampling is not exact function timing. At roughly 100Hz of CPU-time sampling, a thread running continuously for 10s has about 1000 sampling opportunities; 80ms has only about eight. These are scale estimates, not hard limits or guarantees. Event choice, rate adjustment, migration, and loss affect counts. Hardware-event-period sampling cannot be converted by simply multiplying wall seconds by a frequency.

  • Self: Samples or weight attributed to the leaf function itself, excluding callees.
  • Cumulative: Samples or weight for a node and its descendants. Several nodes can include the same samples, so summing rows does not give a total.

Only suitable time sampling and weights approximate CPU-time shares. Symbolization is easy to get wrong. If much of the stack is unknown, hold off on changing the code: check for missing symbols, unwind failures, and gaps in the capture scope. Do not assign the unknown portion to a function.

The following is an illustrative reporting format, not a measurement from the screenshots:

1
2
3
4
5
6
7
8
Window: 12.300s - 12.860s
Thread: RenderThread (also record its utid)
Wall: 560ms
Running: 492ms; Runnable (including R+): 18ms
Other known wait states: 50ms; unknown coverage: 0ms
Sampling: roughly 100Hz of CPU time, 51 observed samples
Top cumulative: a Skia path, 34 samples
Limit: verify symbols and weights; too few samples for precise shares

Return to the timeline to confirm that the hotspot overlaps the user-visible problem. A whole-recording flame graph is not automatically evidence for one late frame.

Linux Kernel Scheduling Strategy: Core Selection and Migration

Energy Aware Scheduling (EAS) participates in placement for eligible fair-class tasks. It is not the entire Android scheduler, nor a replacement for real-time classes or per-CPU task selection. This section uses the Linux v6.12 EAS documentation; vendor kernels, utilization tracking, and policies may differ.

Core Selection Logic (Task Placement)

When enabled and applicable, wakeup placement combines historical task utilization, allowed CPUs, capacity, and current load with Energy Model estimates.

  1. Estimate demand: Utilization is based on execution history and estimation, not advance knowledge of the next workload.
  2. Select candidates: Capacity and spare capacity matter, subject to affinity and cpuset constraints. This is not a simple scan of every physical CPU for capacity > util.
  3. Compare estimated energy: The model estimates effects across performance domains. It is neither a live whole-device power reading nor a guarantee of global optimality.

EAS is generally suited to light and medium load. Overutilization in a root domain shifts the relevant path toward load balancing; not every migration under heavy load is an EAS choice of the “lowest-power” core.

Core Migration Logic (Task Migration)

A thread can move CPUs during wakeup placement or through load balancing and other paths. Allowed CPUs, scheduling class, and vendor policy constrain the result.

  1. Load balancing: Balance load or capacity mismatches within eligible scheduling domains. An idle big core does not guarantee that the target task can be pulled there.
  2. Wakeup placement: Use retained or decayed utilization estimates and other tasks’ load. Receiving a large download does not instantly increase the historical util of a thread that has not executed that work; new execution or hints may be needed to reflect the change.

A thread bouncing back and forth between big and small cores, or spending a long time on a smaller core, merits investigation, but does not prove a scheduler defect. Check the critical path, allowed CPUs, utilization, and deadline impact. Explain vendor behavior using device traces and implementation evidence, not an engine’s marketing name.

Practice and SQL

Using SQL for Quantitative Analysis

These queries follow Perfetto v58.3 scheduling tables and process/thread views, using fields in sched, thread_state, thread, and process, and assume a single-device trace. utid and upid identify instances within the trace; names or reusable OS tids/pids do not.

The queries conservatively exclude dur <= 0, especially open intervals with dur = -1. Results represent observed durations of known complete intervals and can underestimate time after data loss or an unclosed tail. Check the target Trace Processor schema and trace completeness before use.

First list thread/process identities and select a utid using process, tid, name, and lifetime. Do not guess with name = ... LIMIT 1:

1
2
3
4
5
6
7
8
SELECT
t.utid, t.tid, t.name AS thread_name,
p.upid, p.pid, p.name AS process_name,
t.start_ts, t.end_ts
FROM thread AS t
LEFT JOIN process AS p ON p.upid = t.upid
WHERE t.is_idle = 0
ORDER BY p.upid, t.utid;

For a window [start, end), sum the overlap max(0, min(ts + dur, end) - max(ts, start)). Filtering only starting timestamps misses boundary-crossing time. Multi-device traces also require machine/ucpu-aware grouping.

1. Calculate Total CPU Time for Each Process

Group by upid to keep process instances separate, retaining pid/name for readability. Same-named processes are not merged. Only known non-idle threads associated with a process are included.

1
2
3
4
5
6
7
8
9
SELECT
p.upid, p.pid, p.name AS process_name,
SUM(s.dur) / 1e9 AS observed_cpu_time_s
FROM sched AS s
JOIN thread AS t ON s.utid = t.utid
JOIN process AS p ON t.upid = p.upid
WHERE s.dur > 0 AND t.is_idle = 0
GROUP BY p.upid, p.pid, p.name
ORDER BY observed_cpu_time_s DESC;

2. Analyze Single Thread’s Time State Distribution

Select the target utid with the identity query above, then replace NULL. Until replaced, this returns no rows, not a zero-time measurement. Preserve raw state codes so R, R+, and other states remain distinct.

1
2
3
4
5
6
7
8
SELECT
s.state,
SUM(s.dur) / 1e6 AS observed_time_ms
FROM thread_state AS s
WHERE s.utid = /* replace NULL with the chosen utid */ NULL
AND s.dur > 0
GROUP BY s.state
ORDER BY observed_time_ms DESC;

3. Find Threads with Highest CPU Consumption Across the Trace

Group by utid to avoid merging reused tids or same-named threads. This ranks known execution across the whole trace; a windowed query must clip boundary-crossing slices.

1
2
3
4
5
6
7
8
9
SELECT
t.utid, t.tid, t.upid, t.name,
SUM(s.dur) / 1e9 AS observed_cpu_time_s
FROM sched AS s
JOIN thread AS t ON s.utid = t.utid
WHERE s.dur > 0 AND t.is_idle = 0
GROUP BY t.utid, t.tid, t.upid, t.name
ORDER BY observed_cpu_time_s DESC
LIMIT 20;

4. View Thread’s Running Time Distribution on Each CPU Core

Replace NULL with the selected utid. This shows actual placement, not the configured affinity mask: an allowed CPU on which the thread never ran will not appear.

1
2
3
4
5
6
7
8
SELECT
s.cpu,
SUM(s.dur) / 1e6 AS observed_time_on_cpu_ms
FROM sched AS s
WHERE s.utid = /* replace NULL with the chosen utid */ NULL
AND s.dur > 0
GROUP BY s.cpu
ORDER BY s.cpu;

5. Calculate CPU Utilization for Each Process

This keeps core-count normalization, with a denominator of trace duration × distinct CPUs observed in scheduling records. It measures observed capacity, not total hardware cores, integrated online-CPU time, or heterogeneous capacity weighting. Offline CPUs, incomplete collection, and device suspend make those denominators differ.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
WITH observed_cpus AS (
SELECT COUNT(DISTINCT cpu) AS n
FROM sched
WHERE dur > 0
)
SELECT
p.upid, p.pid, p.name AS process_name,
100.0 * SUM(s.dur) / NULLIF(
(TRACE_END() - TRACE_START()) * 1.0 * c.n, 0
) AS observed_capacity_percent
FROM sched AS s
JOIN thread AS t ON t.utid = s.utid
JOIN process AS p ON p.upid = t.upid
CROSS JOIN observed_cpus AS c
WHERE s.dur > 0 AND t.is_idle = 0
GROUP BY p.upid, p.pid, p.name, c.n
ORDER BY observed_capacity_percent DESC;

Removing c.n gives a one-core reference that may exceed 100% for a parallel process. This percentage does not include frequency or core capacity and cannot directly compare work or energy across SoCs.

Summary

Start with the target window and distinguish Running, Runnable, and sleeping or blocked time. Investigate scheduling constraints for long Runnable periods, samples for on-CPU work, and dependencies for waits. Frequency and topology provide context; a single frequency reading cannot replace thread-state and workload evidence.

About Me && Blog

Below is a personal introduction and related links. I look forward to communicating with you all. When three people walk together, one of them can be my teacher!

  1. Blogger Personal Introduction: Contains personal WeChat and WeChat group links.
  2. Blog Content Navigation: A navigation of personal blog content.
  3. Excellent Blog Articles Collected and Organized by Individuals - Must-Know for Android Performance Optimization: Welcome everyone to recommend yourself and recommend (WeChat private chat is fine)
  4. Android Performance Optimization Knowledge Planet: Historical community introduction; the community closed in 2026.

One person can go faster, a group of people can go further

WeChat QR Code

CATALOG
  1. 1. Table of Contents
  2. 2. Perfetto Series Articles
  3. 3. CPU Information Overview in Perfetto
  4. 4. Trace Config Required for Capturing CPU Information
  5. 5. CPU Core Architecture: big.LITTLE
  6. 6. CPU Scheduling
    1. 6.1. Thread State Deep Dive
      1. 6.1.1. Running (Green)
      2. 6.1.2. R (Runnable)
      3. 6.1.3. S (Sleep / Interruptible Sleep)
      4. 6.1.4. D (Uninterruptible Sleep)
    2. 6.2. Wakeup Relationship Analysis
      1. 6.2.1. Scheduling Wakeup and Latency Analysis
      2. 6.2.2. Differences Between sched_waking and sched_wakeup and Non-strict Work-conserving
    3. 6.3. User Mode and Kernel Mode: sys_* Slices Locate Empty Flame Graphs
    4. 6.4. CPU Time and Wall Time
  7. 7. CPU Frequency Deep Dive
    1. 7.1. Core Factors Affecting CPU Frequency
    2. 7.2. Frequency Data Collection and Platform Differences
    3. 7.3. big.LITTLE: Same Frequency Doesn’t Equal Same Performance or Power Consumption
      1. 7.3.1. P-States and governor
    4. 7.4. Reading ADPF / Performance Hint
  8. 8. CPU Profiling, Flame Graphs, and Symbolization
  9. 9. Linux Kernel Scheduling Strategy: Core Selection and Migration
    1. 9.1. Core Selection Logic (Task Placement)
    2. 9.2. Core Migration Logic (Task Migration)
  10. 10. Practice and SQL
    1. 10.1. Using SQL for Quantitative Analysis
    2. 10.2. 1. Calculate Total CPU Time for Each Process
    3. 10.3. 2. Analyze Single Thread’s Time State Distribution
    4. 10.4. 3. Find Threads with Highest CPU Consumption Across the Trace
    5. 10.5. 4. View Thread’s Running Time Distribution on Each CPU Core
    6. 10.6. 5. Calculate CPU Utilization for Each Process
  11. 11. Summary
  • About Me && Blog