This is the twelfth article in the Systrace series, primarily providing a brief introduction to the CPU information area (Kernel) in Systrace. It covers how to view CPU-related information output by the Kernel module, including CPU frequency, scheduling, frequency locking, and core locking.
The purpose of this series is to view the overall operation of the Android system from a different perspective using Systrace, while also providing an alternative angle for learning the Framework. Perhaps you’ve read many articles about the Framework but can never remember the code, or you’re unclear about the execution flow. Maybe from Systrace’s graphical perspective, you can gain a deeper understanding.
Version note (reviewed 2026-10-13): This article retains its 2019 Systrace figures. SoC models, CPU numbering, cpuset paths, and power states depend on the device and OS; errors in the old specification table are marked nearby. Framework/tool examples were checked against AOSP
android-10.0.0_r1andandroid-16.0.0_r1, with kernel semantics referenced to Linux 6.6. Device inspection and affinity commands were not executed during this review.
Table of Contents
- Series Article Index
- CPU Area Legend
- Core Architecture
- Core Binding (Pinning)
- Frequency Locking
- CPU States
- Detailed Information in Systrace
- References
- About Me && Blog
Series Article Index
- Introduction to Systrace
- Systrace Basics - Prerequisites for Systrace
- Systrace Basics - Why 60 fps?
- Android Systrace Basics - SystemServer Explained
- Systrace Basics - SurfaceFlinger Explained
- Systrace Basics - Input Explained
- Systrace Basics - Vsync Explained
- Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
- Systrace Basics - MainThread and RenderThread Explained
- Systrace Basics - Binder and Lock Contention Explained
- Systrace Basics - Triple Buffer Explained
- Systrace Basics - CPU Info Explained
- Systrace Smoothness in Action 1: Understanding Jank Principles
- Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
- Systrace Smoothness in Action 3: FAQs During Jank Analysis
- Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
- Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
- Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
- Systrace Thread CPU State Analysis Tips - Runnable
- Systrace Thread CPU State Analysis Tips - Running
- Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep
CPU Area Legend
Read this with Perfetto 9: CPU information. Locate the target thread’s Runnable, Running, and sleep intervals, then correlate actual CPU placement, frequency, idle state, and competing work. CPU/thread tracks show timing; data such as sched, thread_state, and counters supports aggregation. Table details depend on the Trace Processor version.
This is an eight-logical-CPU example from an older Android device. The original text called it a Snapdragon 845, but the image alone does not establish its SoC. The three-tier annotation in a later figure is not independent device-identification evidence either.

Four useful kinds of information are:
- CPU tracks reconstruct which thread occupied each logical CPU from scheduling events. Thread colors do not rank performance.
- Clock Frequency contains recorded events or samples. The selected
2419200, in kHz, means 2.4192 GHz, not utilization. - Clock Frequency Limits describes allowed bounds, not instantaneous frequency or the identity of a boost requester.
- C-State/idle describes CPU idleness. The old viewer can renumber raw states, as explained below.
The top CPU usage view aggregates utilization; inspect its time window and normalization. Low overall utilization can coexist with one saturated CPU or a critical thread waiting Runnable. High frequency does not mean continuous execution, and an idle CPU’s track may retain its last recorded frequency.
When a task is slow, the first questions might be: did it run on a small core? Was the frequency too low? For something like fingerprint unlock, could it run on a big core? Use those questions to start reading the trace, then separate scheduling delay, lock/Binder/I/O waits, CPU execution, and critical-path dependencies. Small-core placement or low frequency is a clue, not proof of a scheduling error. More frequency cannot remove a blocking dependency. Frequency events are often emitted only on changes, so missing initial data is not 0 Hz; supported sysfs polling can supplement the capture. See Perfetto frequency and idle data.
For basic thread-state interpretation, see Systrace Prerequisites.
Core Architecture
This article retains homogeneous, big/little, and big/medium/little as ways to discuss the old figures, not an exhaustive classification of modern SoCs. Microarchitecture, performance tier, shared cache, frequency policy, and Linux CPU numbering are distinct. Cores with the same architecture can belong to different frequency tiers.

The 0–3, 4–6, and 7 groups are annotations for a particular sample. They are not a universal numbering rule. Restricting background work to small cores is a policy choice involving cpusets, affinity, and scheduling, not a hardware rule.
Non-big.LITTLE Architecture
Homogeneous cores share a core design; they need not always run at the same frequency or power, or switch on and off together. For example, Helio A22 uses four Cortex-A53 cores. Shared frequency control and independent idle/online behavior still depend on the platform.
Snapdragon 636 was an incorrect homogeneous example. Its official brief specifies four performance and four efficiency cores: an eight-core Kryo 260 CPU, not the original text’s other description of “four small plus two large.”
big.LITTLE Architecture
Different performance/efficiency tiers let the scheduler place work according to load, allowed CPUs, and power constraints. CPU 0–3 need not be small cores or CPU 4–7 large cores. Clock speed alone is also insufficient for comparing different microarchitectures.
Snapdragon 845’s official specifications describe Kryo 385 with four performance and four efficiency cores, clocked up to 2.8 GHz. The old diagram below is retained as an illustration. Its peak rates and performance-uplift percentages are not measurements made in this review, and a Kryo product name should not be treated as an unchanged stock Cortex implementation.

The ratio varies: Snapdragon 670, for example, uses 2+6 cores. Whether it feels like enough also comes down to how much you’re willing to spend. You get what you pay for; Qualcomm isn’t running a charity. Engineering analysis needs the particular workload and device.
The following old table is outdated and contains errors; it is not a reliable specification reference. Corrections follow the retained image:

| Model | Verified official information | Correction to old image |
|---|---|---|
| Snapdragon 670 | Kryo 360, 2 performance + 6 efficiency; Adreno 615 | Not 4+4 or Adreno 620 |
| Snapdragon 460 | Eight-core Kryo 240; Adreno 610 | Not Kryo 360 Silver or Adreno 605 |
| Snapdragon 640 column | No official specification supporting this column was verified | Do not cite it as a released-product specification |
Sources: 670 brief, 460 brief. Check the actual device’s policies and frequency tables for operating points.
Big-Medium-Small Core Architecture
Historical examples include:
- Snapdragon 855’s Kryo 485 has 1 Prime + 3 performance + 4 efficiency cores. Its official technical presentation lists 2.84, 2.42, and 1.80 GHz, respectively.
- Helio X30 has 2 Cortex-A73 + 4 Cortex-A53 + 4 Cortex-A35 cores.
- Kirin 980 has 2 Cortex-A76 at 2.6 GHz, 2 Cortex-A76 at 1.92 GHz, and 4 Cortex-A55 at 1.8 GHz. The same A76 design can serve different tiers.
“Prime” and “medium” are descriptive tiers, not fixed core counts or Linux scheduling classes. Some numbers in the old comparison, including 855’s 2.41/1.78 GHz, differ from the official nominal figures. Without measurement conditions, they should not be used to compare peaks or infer performance.

Snapdragon 865, introduced in 2019, retained a 1+3+4 arrangement; the diagram labels its Prime core at 2.84 GHz. At the time, I wondered whether the 865 Plus would make it to 3 GHz. The 865 Plus announced in 2020 answered that question with a Prime specification of up to 3.1 GHz.

Newer designs need not retain traditional A5x efficiency cores. The 2025 Snapdragon 8 Elite Gen 5 brief lists custom Oryon cores: 2 Prime up to 4.6 GHz and 6 Performance up to 3.62 GHz. Re-establish the SoC, topology, capacity, and frequency policies when reading a new trace instead of reusing the old numbering diagram.
Core Binding (Pinning)
Pinning restricts the CPUs on which a thread may run. It can reduce migration or constrain background work, but a smaller allowed set can also increase queueing. Pinning does not reserve a CPU, prevent preemption, or guarantee faster execution.
Configuring CPUsets
cpusets constrain CPUs and memory nodes for groups of tasks. Older Android systems commonly have top-app, foreground, background, and system-background, but their names do not specify fixed CPU masks. foreground/boost belongs to some older implementations, not every release.
The original 0-7 / 0-3 masks were not a universal Google default. Android 10 init.rc initially copies available sets from the root group and leaves device configuration to select appropriate CPUs. This is an init-language excerpt, not shell syntax:
1 | mkdir /dev/cpuset/top-app |
The old cgroup-v1 path is lowercase /dev/cpuset/.../cpus. On a device exposing these nodes, read-only inspection can look like this:
1 | $ adb shell cat /dev/cpuset/top-app/cpus |
These outputs are historical examples. tasks lists task/thread TIDs, not necessarily four application processes. Membership and allowed CPUs can change. Also inspect /proc/<TID>/cgroup and Cpus_allowed_list in /proc/<TID>/status, substituting a real thread ID.
cgroup v2 uses nodes such as cpuset.cpus and cpuset.cpus.effective; distinguish process membership in cgroup.procs from thread membership in cgroup.threads. Do not append v1’s tasks to arbitrary v2 paths. Android can combine v1/v2 controllers; use the device’s actual mounts and configuration. See Linux 6.6 cgroup v2.
A service can also select a group at startup. This is the complete service declaration from Android 10 lmkd.rc:
1 | service lmkd /system/bin/lmkd |
Android 12 introduced init’s task_profiles replacement for deprecated writepid usage. Android 16 lmkd.rc uses task_profiles ServiceCapacityLow; the default profile joins cpuset/system-background. Device/API/vendor configuration can override profiles, so the name alone does not identify the CPU set. See AOSP task profiles.
App scheduling groups can change with process state. These Android 10 Process.java constants belong to a hidden Framework contract, not a general app pinning API:
1 | /** |
They are not CPU numbers, and not every constant implies a same-named cpuset directory. THREAD_GROUP_RT_APP alone does not select SCHED_FIFO. Android 10 OomAdjuster computes scheduling groups and uses a handler to perform operations such as setProcessGroup. Follow the native implementation and device configuration too; oom_adj is not a CPU priority value.
Configuring Affinity
taskset is a command-line tool using sched_getaffinity / sched_setaffinity. Linux affinity is per thread: changing the main thread does not automatically update every existing thread. Effective placement is further restricted by cpusets and online CPUs, and permission requirements apply. See sched_setaffinity.
Usage of taskset
Android 10/16 Toybox taskset uses hexadecimal masks and supports -p and -a, not util-linux’s -c CPU-list option. These are device-shell command forms; replace TID, PID, and COMMAND. Mutation forms belong in authorized, controlled experiments:
| Purpose | Android Toybox form |
|---|---|
| Inspect one thread’s allowed CPUs | taskset -p TID |
| Inspect a process’s current threads | taskset -ap PID |
| Restrict one thread to CPU 3 | taskset -p 8 TID |
| Start a command with CPU 3 affinity | taskset 8 COMMAND |
The least significant bit is CPU 0. Hexadecimal 5, binary 0101, therefore selects CPU 0 and CPU 2. CPU 3 uses hexadecimal mask 8. This is an allowed set, not the thread’s current executing CPU; use scheduling tracks for actual placement. Desktop util-linux versions supporting -c can use taskset -pc 3 PID, but that syntax does not transfer to these Android Toybox versions.
Scheduling Algorithms
The allowed set determines where a task may run; the scheduler chooses where and when it runs. Linux 6.6 EAS can use CPU capacity, load, and an energy model, while vendors may apply other policies. Seeing a thread remain on one core does not prove affinity pinning. Check affinity/cpusets and the available choices before judging placement.
Frequency Locking
Frequency scaling, task placement, and performance hints interact, but there is no universal sequence of maximizing a small core before trying medium and then large cores. Latency-sensitive work such as app launch may request a boost. Whether that raises a frequency floor, adjusts uclamp, changes placement, and for how long depends on OS, driver, and vendor policy.
Launch, installation, rotation, window animation, list flinging, and games are possible optimization scenarios, not a promise that Android maximizes every CPU/GPU/I/O/BUS resource in each case. Thermal, power-mode, and platform limits still apply. Linux 6.6 uclamp changes a performance-demand signal and can influence schedutil frequency selection; it does not promise a fixed MHz value or exclusive use of a large core.
Here is an older Qualcomm frequency-limit example:

The selected bounds are 768000–2016000 kHz: 768 MHz–2.016 GHz. Since the minimum and maximum differ, this is an allowed range rather than a single locked frequency. It also does not identify the requester, temperature, or actual execution rate.
Linux CPUFreq often manages multiple CPUs through a shared policy. related_cpus helps identify its membership. For many drivers, scaling_cur_freq reflects the most recent requested frequency rather than precise instantaneous hardware frequency. Correlate frequency events, idle, workload, and thermal evidence; see CPUFreq.
From Android 12/API 31, apps on supported devices can use PerformanceHintManager to report work-duration targets and completion data for associated threads. This is a performance-hint mechanism, not an app-controlled core or frequency lock.
CPU States
These are the old viewer’s C-State tracks. Values 0, 1, 2, and 3 belong to this sample, not a fixed four-state model for every Android CPU:

Separate raw events from viewer transformations. The Catapult power_parser already present before the 2019 article displays raw cpu_idle value 4294967295 (0xffffffff, idle exit) as 0 and adds 1 to other raw states. On that parsing path, displayed 1 means raw state 0, and displayed 3 means raw state 2.
Perfetto SQL’s cpuidle counter retains raw values: 0 means entry into idle state 0; 4294967295 means idle exit. Do not reuse the old viewer’s meaning of zero. Missing entry/exit events leave boundary state unknown.
These are indices into the platform’s cpuidle driver states. Where permissions allow, inspect name, desc, latency, and residency under /sys/devices/system/cpu/cpuN/cpuidle/stateN/ for the relevant CPU/state and units; latency and residency are normally expressed in microseconds. Deeper states often trade wakeup delay for savings, but a recorded request does not always establish the state that hardware actually reached. See Linux 6.6 CPUIdle.
Idle and offline/hotplug are different mechanisms. Modern kernels still support CPU hotplug; availability and policy depend on the device. CPU idle is also distinct from whole-device suspend. The original cross-platform HLT description, fixed 10 ns/100 ns wakeup claims, and “70% savings” do not explain these ARM tracks and have been removed from this interpretation.
Detailed Information in Systrace
Old Systrace HTML embeds trace data that can be inspected as text; compatibility of the old interactive viewer with current Chrome needs separate verification. Perfetto protobuf data requires appropriate decoding and is not the same plain-text format.
Here is the original scheduling record:
1 | appEventThread-8193 [001] d..2 1638545.400415: sched_switch: prev_comm=appEventThread prev_pid=8193 prev_prio=97 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 |
| Field | Meaning in this record |
|---|---|
appEventThread-8193 |
Current task name and Linux TID; 8193 need not be the process TGID |
[001] |
The event occurred on logical CPU 1, not CPU 0 |
d..2 |
Four character fields, not four bits: IRQs disabled, no resched flag shown, ordinary context, preempt depth 2 |
1638545.400415 |
Trace timestamp in seconds, in the selected trace-clock/format domain; not scheduling latency or calendar time |
prev_* / next_* |
Outgoing and incoming tasks; this switches to CPU 1’s idle task swapper/1, TID 0 |
prev_state=S |
The outgoing thread enters interruptible sleep; this is not evidence that it remained Runnable and was preempted |
prev_prio=97 / next_prio=120 |
Kernel scheduling-priority values, not direct nice values or enough information to distinguish every policy |
d..2 describes context at event time, not an entire app interval spent with interrupts or preemption disabled. Other kernel versions can add flag fields. See the Linux ftrace format.
Other original samples and their interpretation:
sched_waking: comm=kworker/u16:4 pid=17373 prio=120 target_cpu=003: waking TID 17373 toward CPU 3; it is not already Running and can migrate later.sched_blocked_reason: pid=17373 iowait=0 caller=rpmh_write_batch+0x638/0x7d0: this extension reports a blocking location and iowait flag. Zero does not exclude all I/O/hardware waits, and symbol offsets are not source lines. Not every kernel emits this event.cpu_idle: state=0 cpu_id=3: CPU 3 enters raw idle state 0, not an active C0 state.softirq_raise: vec=6 [action=TASKLET]: raises the softirq; inspect entry/exit for execution rather than treating the raise as completion.cpu_frequency_limits: min=1555200 max=1785600 cpu_id=0cpu_frequency_limits: min=710400 max=2419200 cpu_id=4cpu_frequency_limits: min=825600 max=2841600 cpu_id=7
The last three events report frequency limits in kHz. cpu_id can identify a policy’s representative CPU, so the limit need not affect only one core. These records do not replace actual frequency samples. The visual trace, raw events, and matching-version source should explain the same behavior.
About Me && Blog
Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”
- Blogger Intro: Includes personal WeChat and WeChat group links.
- Blog Content Navigation: A guide for my blog content.
- Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
One walks faster alone, but a group walks further together.

References
- Linux sched_setaffinity: thread affinity
- AOSP cgroups and task profiles
- Linux 6.6 CPUFreq
- Linux 6.6 CPUIdle
- Perfetto CPU frequency and idle
Spring Bamboo Shoots
I took a photo and thought it was quite nice, sharing it with everyone.