blob: 100c13001b4bc24f0ad6e5563bf2105ce7607a30 [file] [edit]
// 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 {Time} from '../../base/time';
import {materialColorScheme} from '../../components/colorizer';
import {SliceTrack} from '../../components/tracks/slice_track';
import type {PerfettoPlugin} from '../../public/plugin';
import {SMAPS_TRACK_KIND} from '../../public/track_kinds';
import type {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
import {SourceDataset} from '../../trace_processor/dataset';
import {LONG, NUM} from '../../trace_processor/query_result';
import {createPerfettoTable} from '../../trace_processor/sql_utils';
import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
import {computeInitialColumns, SmapsDetailsPanel} from './smaps_details_panel';
// One row per smaps snapshot (a group of rows from process_memory_mappings),
// keyed by {upid, ts}.
const SNAPSHOTS_TABLE = '_smaps_snapshots';
function trackUri(upid: number): string {
return `/process_${upid}/smaps`;
}
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.Smaps';
static readonly dependencies = [ProcessThreadGroupsPlugin];
// Typically not all memory metrics are recorded in a trace, so we might not
// show all of the columns. Decide on the set once, lazily when the details
// panel is first opened.
private initialColumns?: ReturnType<typeof computeInitialColumns>;
async onTraceLoad(trace: Trace): Promise<void> {
await createPerfettoTable({
engine: trace.engine,
name: SNAPSHOTS_TABLE,
as: `
SELECT MIN(id) AS id, upid, ts
FROM process_memory_mappings
GROUP BY upid, ts
`,
});
const upids = await this.getUpids(trace);
const groupsPlugin = trace.plugins.getPlugin(ProcessThreadGroupsPlugin);
// Lazily compute the initial set of columns.
const getInitialColumns = () => {
if (this.initialColumns === undefined) {
this.initialColumns = computeInitialColumns(trace.engine);
}
return this.initialColumns;
};
for (const upid of upids) {
const group = groupsPlugin.getGroupForProcess(upid);
if (!group) continue;
const uri = trackUri(upid);
const renderer = SliceTrack.create({
trace,
uri,
dataset: new SourceDataset({
src: SNAPSHOTS_TABLE,
schema: {id: NUM, ts: LONG},
filter: {col: 'upid', eq: upid},
}),
colorizer: () => materialColorScheme('smaps'), // single colour
tooltip: () => 'Memory mapping snapshot',
detailsPanel: (row) =>
new SmapsDetailsPanel(
trace,
upid,
Time.fromRaw(row.ts),
getInitialColumns,
),
});
trace.tracks.registerTrack({
uri,
renderer,
tags: {kinds: [SMAPS_TRACK_KIND], upid},
});
group.addChildInOrder(
new TrackNode({
uri,
name: 'Memory mapping snapshots',
sortOrder: -25,
}),
);
}
}
private async getUpids(trace: Trace): Promise<ReadonlyArray<number>> {
const result = await trace.engine.query(
`SELECT DISTINCT upid FROM ${SNAPSHOTS_TABLE} ORDER BY upid`,
);
const upids: number[] = [];
for (const it = result.iter({upid: NUM}); it.valid(); it.next()) {
upids.push(it.upid);
}
return upids;
}
}