blob: c938f36d9411468d080ef7a2f4f5fe34a786fec7 [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.
import m from 'mithril';
import {Icons} from '../../base/semantic_icons';
import {sqliteString} from '../../base/string_utils';
import {Duration, Time} from '../../base/time';
import {CounterTrack} from '../../components/tracks/counter_track';
import {DataGrid} from '../../components/widgets/datagrid/datagrid';
import {Timestamp} from '../../components/widgets/timestamp';
import type {TrackEventDetailsPanel} from '../../public/details_panel';
import type {TrackEventSelection} from '../../public/selection';
import type {Trace} from '../../public/trace';
import type {TrackMouseEvent} from '../../public/track';
import {LONG, NUM, type Row} from '../../trace_processor/query_result';
import {Anchor} from '../../widgets/anchor';
import {DetailsShell} from '../../widgets/details_shell';
import {durationCellRenderer, processTrackUri} from './aggregators';
/** Concurrency counter for one state; clicking it lists the processes in it. */
export class ProcessStateCountTrack extends CounterTrack {
constructor(
trace: Trace,
uri: string,
private readonly state: string,
) {
super({
trace,
uri,
sqlSource: `
SELECT ts, concurrency AS value
FROM _android_process_state_concurrency
WHERE state = ${sqliteString(state)}
`,
});
}
onMouseClick({x, timescale}: TrackMouseEvent): boolean {
const ts = timescale.pxToHpTime(x).toTime('floor');
this.engine
.query(
`
SELECT id FROM _android_process_state_concurrency
WHERE state = ${sqliteString(this.state)} AND ts <= ${ts}
ORDER BY ts DESC LIMIT 1
`,
)
.then((result) => {
const it = result.iter({id: NUM});
if (it.valid()) this.trace.selection.selectTrackEvent(this.uri, it.id);
});
return true;
}
async getSelectionDetails(id: number) {
const row = (
await this.engine.query(`
SELECT ts, dur FROM _android_process_state_concurrency WHERE id = ${id}
`)
).firstRow({ts: LONG, dur: LONG});
return {ts: Time.fromRaw(row.ts), dur: Duration.fromRaw(row.dur)};
}
detailsPanel(): TrackEventDetailsPanel {
return new ProcessesInStatePanel(this.trace, this.state);
}
}
class ProcessesInStatePanel implements TrackEventDetailsPanel {
private rows: Row[] = [];
constructor(
private readonly trace: Trace,
private readonly state: string,
) {}
async load(sel: TrackEventSelection) {
const result = await this.trace.engine.query(`
SELECT id, upid, process_name, pid, ts, iif(dur < 0, trace_end() - ts, dur) AS dur
FROM _android_process_state_intervals
WHERE state = ${sqliteString(this.state)}
AND ts <= ${sel.ts}
AND (dur < 0 OR ts + dur > ${sel.ts})
`);
const cols = result.columns();
const rows: Row[] = [];
for (const it = result.iter({}); it.valid(); it.next()) {
rows.push(Object.fromEntries(cols.map((c) => [c, it.get(c)])));
}
this.rows = rows;
}
render() {
const trace = this.trace;
return m(
DetailsShell,
{
title: `${this.rows.length} processes in ${this.state}`,
fillHeight: true,
},
m(DataGrid, {
fillHeight: true,
data: this.rows,
schema: {
ts: {
title: 'Entered at',
columnType: 'quantitative',
cellRenderer: (ts) =>
typeof ts === 'bigint'
? m(Timestamp, {trace, ts: Time.fromRaw(ts)})
: String(ts ?? ''),
},
id: {
title: 'Slice ID',
columnType: 'identifier',
cellRenderer: (id) => {
const upid = this.rows.find((r) => r.id === id)?.upid;
if (typeof id !== 'bigint' || typeof upid !== 'bigint') {
return String(id ?? '');
}
return m(
Anchor,
{
title: 'Go to process state slice',
icon: Icons.UpdateSelection,
onclick: () =>
trace.selection.selectTrackEvent(
processTrackUri(Number(upid)),
Number(id),
{scrollToSelection: true},
),
},
String(id),
);
},
},
process_name: {title: 'Process', columnType: 'text'},
pid: {title: 'PID', columnType: 'identifier'},
dur: {
title: 'Time in state',
columnType: 'quantitative',
cellRenderer: durationCellRenderer(trace),
},
},
initialColumns: [
{id: 'ts', field: 'ts'},
{id: 'id', field: 'id'},
{id: 'process_name', field: 'process_name'},
{id: 'pid', field: 'pid'},
{id: 'dur', field: 'dur', sort: 'DESC'},
],
}),
);
}
}