// Copyright (C) 2021 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 {HEAP_PROFILE_TRACK_KIND} from '../../public/track_kinds'; import {Trace} from '../../public/trace'; import {PerfettoPlugin} from '../../public/plugin'; import {LONG, NUM, STR} from '../../trace_processor/query_result'; import {HeapProfileTrack} from './heap_profile_track'; import {TrackNode} from '../../public/workspace'; import {createPerfettoTable} from '../../trace_processor/sql_utils'; import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups'; function getUriForTrack(upid: number): string { return `/process_${upid}/heap_profile`; } export default class implements PerfettoPlugin { static readonly id = 'dev.perfetto.HeapProfile'; static readonly dependencies = [ProcessThreadGroupsPlugin]; async onTraceLoad(ctx: Trace): Promise { const it = await ctx.engine.query(` select value from stats where name = 'heap_graph_non_finalized_graph' `); const incomplete = it.firstRow({value: NUM}).value > 0; const result = await ctx.engine.query(` select distinct upid from heap_profile_allocation union select distinct upid from heap_graph_object `); for (const it = result.iter({upid: NUM}); it.valid(); it.next()) { const upid = it.upid; const uri = getUriForTrack(upid); const title = 'Heap Profile'; const tableName = `_heap_profile_${upid}`; createPerfettoTable( ctx.engine, tableName, ` with heaps as (select group_concat(distinct heap_name) h from heap_profile_allocation where upid = ${upid}), allocation_tses as (select distinct ts from heap_profile_allocation where upid = ${upid}), graph_tses as (select distinct graph_sample_ts from heap_graph_object where upid = ${upid}) select *, 0 AS dur, 0 AS depth from ( select ( select a.id from heap_profile_allocation a where a.ts = t.ts order by a.id limit 1 ) as id, ts, 'heap_profile:' || (select h from heaps) AS type from allocation_tses t union all select ( select o.id from heap_graph_object o where o.graph_sample_ts = g.graph_sample_ts order by o.id limit 1 ) as id, graph_sample_ts AS ts, 'graph' AS type from graph_tses g ) `, ); ctx.tracks.registerTrack({ uri, title, tags: { kind: HEAP_PROFILE_TRACK_KIND, upid, }, track: new HeapProfileTrack(ctx, uri, tableName, upid, incomplete), }); const group = ctx.plugins .getPlugin(ProcessThreadGroupsPlugin) .getGroupForProcess(upid); const track = new TrackNode({uri, title, sortOrder: -30}); group?.addChildInOrder(track); } ctx.onTraceReady.addListener(async () => { await selectFirstHeapProfile(ctx); }); } } async function selectFirstHeapProfile(ctx: Trace) { const query = ` select * from ( select min(ts) AS ts, 'heap_profile:' || group_concat(distinct heap_name) AS type, upid from heap_profile_allocation group by upid union select distinct graph_sample_ts as ts, 'graph' as type, upid from heap_graph_object ) order by ts limit 1 `; const profile = await ctx.engine.query(query); if (profile.numRows() !== 1) return; const row = profile.firstRow({ts: LONG, type: STR, upid: NUM}); const upid = row.upid; ctx.selection.selectTrackEvent(getUriForTrack(upid), 0); }