blob: d5e66e273dab8859c82bfa34d020bdde6e4b665d [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Projects the `__intrinsic_android_process_state_*` intrinsic tables — filled
// by the process_state_importer trace_processor plugin from ProcessStateSnapshot
// packets (the data behind `dumpsys activity`) — onto the stable `_ps_*`
// relations the explorer reads. Enum fields (proc_state, capabilities, reason)
// already arrive as resolved names from the importer, so this is a plain rename
// projection with no enum logic of its own.
import type {Engine} from '../../trace_processor/engine';
import {NUM} from '../../trace_processor/query_result';
// Only _ps_snapshot is materialized; the rest are plain views. This split is
// deliberate and measured. The explorer reads one snapshot at a time (select a
// snapshot -> its processes/services/bindings), and the intrinsic tables filter
// on snapshot_id in ~a few ms, so views over them are perfectly fine for a
// single-snapshot read. The one exception is the timeline slice's gap-free `dur`
// (a LEAD() window): a window function blocks predicate push-down, so fetching a
// single slice by id would recompute dur for *every* snapshot. On a long trace
// (tens of thousands of snapshots) that one query alone took ~110s. Precomputing
// it once into a table makes the slice fetch instant; everything else stays a
// cheap view.
const SETUP_SQL: ReadonlyArray<string> = [
// Snapshots, with the gap-free slice span (dur) and display name precomputed
// once. Filtered by id (the table's primary key, already fast), so no index is
// needed; the point is just to evaluate the LEAD() window a single time.
`CREATE PERFETTO TABLE _ps_snapshot AS
SELECT id, ts,
IFNULL(LEAD(ts) OVER (ORDER BY ts) - ts,
(SELECT end_ts FROM trace_bounds) - ts) AS dur,
reason,
UPPER(COALESCE(reason, 'snapshot')) AS name
FROM __intrinsic_android_process_state_snapshot`,
// The track-event reconstruction path has no process name (the
// process_state_changed_event doesn't carry one), so fall back to the name
// from trace_processor's `process` table (process-tree / ftrace) for the
// process live at the snapshot's timestamp. The one-shot dumpsys snapshot
// already has names, so its rows are unaffected. This subquery only ever runs
// for the ~processes of the single selected snapshot, so a view is fine.
`CREATE PERFETTO VIEW _ps_process AS
SELECT pr.snapshot_id, pr.pid, pr.uid,
COALESCE(pr.name, (
SELECT p.name FROM process p
WHERE p.pid = pr.pid
AND (p.start_ts IS NULL OR p.start_ts <= s.ts)
AND (p.end_ts IS NULL OR p.end_ts >= s.ts)
LIMIT 1)) AS name,
pr.oom_score, pr.proc_state, pr.capabilities, pr.persistent
FROM __intrinsic_android_process_state_process pr
JOIN __intrinsic_android_process_state_snapshot s ON s.id = pr.snapshot_id`,
`CREATE PERFETTO VIEW _ps_service AS
SELECT snapshot_id, svc_id AS service_id, owning_pid, name
FROM __intrinsic_android_process_state_service`,
`CREATE PERFETTO VIEW _ps_service_binding AS
SELECT snapshot_id, service_id, client_pid, foreground
FROM __intrinsic_android_process_state_service_binding`,
`CREATE PERFETTO VIEW _ps_provider AS
SELECT snapshot_id, provider_id, owning_pid, authority
FROM __intrinsic_android_process_state_provider`,
`CREATE PERFETTO VIEW _ps_provider_binding AS
SELECT snapshot_id, provider_id, client_pid, stable
FROM __intrinsic_android_process_state_provider_binding`,
];
/**
* Builds the `_ps_*` relations the explorer reads. Returns the number of
* process rows available (0 → no process-state data in this trace; the plugin
* should stay inactive).
*/
export async function buildProcessState(engine: Engine): Promise<number> {
try {
for (const sql of SETUP_SQL) {
await engine.query(sql);
}
} catch {
// The intrinsic tables don't exist (a trace_processor without the
// process_state_importer plugin) — leave the plugin inactive.
return 0;
}
const res = await engine.query(`SELECT count(*) AS n FROM _ps_process`);
return res.iter({n: NUM}).n;
}