1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import { 16 createQueryCounterTrack, 17 SqlDataSource, 18} from '../../components/tracks/query_counter_track'; 19import {PerfettoPlugin} from '../../public/plugin'; 20import {Trace} from '../../public/trace'; 21import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds'; 22import {TrackNode} from '../../public/workspace'; 23import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result'; 24import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups'; 25import StandardGroupsPlugin from '../dev.perfetto.StandardGroups'; 26import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack'; 27import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track'; 28import {createTraceProcessorSliceTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_slice_track'; 29 30async function registerAllocsTrack( 31 ctx: Trace, 32 uri: string, 33 dataSource: SqlDataSource, 34) { 35 const track = await createQueryCounterTrack({ 36 trace: ctx, 37 uri, 38 data: dataSource, 39 }); 40 ctx.tracks.registerTrack({ 41 uri, 42 title: `dmabuf allocs`, 43 track: track, 44 }); 45} 46 47export default class implements PerfettoPlugin { 48 static readonly id = 'dev.perfetto.AndroidDmabuf'; 49 static readonly dependencies = [ 50 ProcessThreadGroupsPlugin, 51 StandardGroupsPlugin, 52 TraceProcessorTrackPlugin, 53 ]; 54 55 async onTraceLoad(ctx: Trace): Promise<void> { 56 const e = ctx.engine; 57 await e.query(`INCLUDE PERFETTO MODULE android.memory.dmabuf`); 58 await e.query(` 59 CREATE PERFETTO TABLE _android_memory_cumulative_dmabuf AS 60 SELECT 61 upid, utid, ts, 62 SUM(buf_size) OVER(PARTITION BY COALESCE(upid, utid) ORDER BY ts) AS value 63 FROM android_dmabuf_allocs;`); 64 65 const pids = await e.query( 66 `SELECT DISTINCT upid, IIF(upid IS NULL, utid, NULL) AS utid FROM _android_memory_cumulative_dmabuf`, 67 ); 68 const it = pids.iter({upid: NUM_NULL, utid: NUM_NULL}); 69 for (; it.valid(); it.next()) { 70 if (it.upid != null) { 71 const uri = `/android_process_dmabuf_upid_${it.upid}`; 72 const config: SqlDataSource = { 73 sqlSource: `SELECT ts, value FROM _android_memory_cumulative_dmabuf 74 WHERE upid = ${it.upid}`, 75 }; 76 await registerAllocsTrack(ctx, uri, config); 77 ctx.plugins 78 .getPlugin(ProcessThreadGroupsPlugin) 79 .getGroupForProcess(it.upid) 80 ?.addChildInOrder(new TrackNode({uri, title: 'dmabuf allocs'})); 81 } else if (it.utid != null) { 82 const uri = `/android_process_dmabuf_utid_${it.utid}`; 83 const config: SqlDataSource = { 84 sqlSource: `SELECT ts, value FROM _android_memory_cumulative_dmabuf 85 WHERE utid = ${it.utid}`, 86 }; 87 await registerAllocsTrack(ctx, uri, config); 88 ctx.plugins 89 .getPlugin(ProcessThreadGroupsPlugin) 90 .getGroupForThread(it.utid) 91 ?.addChildInOrder(new TrackNode({uri, title: 'dmabuf allocs'})); 92 } 93 } 94 const memoryGroupFn = () => { 95 return ctx.plugins 96 .getPlugin(StandardGroupsPlugin) 97 .getOrCreateStandardGroup(ctx.workspace, 'MEMORY'); 98 }; 99 const node = await addGlobalCounter(ctx, memoryGroupFn); 100 await addGlobalAllocs(ctx, () => { 101 return node ?? memoryGroupFn(); 102 }); 103 } 104} 105 106async function addGlobalCounter(ctx: Trace, parent: () => TrackNode) { 107 const track = await ctx.engine.query(` 108 select id, name 109 from track 110 where type = 'android_dma_heap' 111 `); 112 const it = track.maybeFirstRow({id: NUM, name: STR}); 113 if (!it) { 114 return undefined; 115 } 116 const {id, name: title} = it; 117 const uri = `/android_dmabuf_counter`; 118 ctx.tracks.registerTrack({ 119 uri, 120 title, 121 tags: { 122 kind: COUNTER_TRACK_KIND, 123 trackIds: [id], 124 }, 125 track: new TraceProcessorCounterTrack(ctx, uri, {}, id, title), 126 }); 127 const node = new TrackNode({ 128 uri, 129 title, 130 }); 131 parent().addChildInOrder(node); 132 return node; 133} 134 135async function addGlobalAllocs(ctx: Trace, parent: () => TrackNode) { 136 const track = await ctx.engine.query(` 137 select name, group_concat(id) as trackIds 138 from track 139 where type = 'android_dma_allocations' 140 group by name 141 `); 142 const it = track.maybeFirstRow({trackIds: STR, name: STR}); 143 if (!it) { 144 return undefined; 145 } 146 const {trackIds, name: title} = it; 147 const uri = `/android_dmabuf_allocs`; 148 const ids = trackIds.split(',').map((x) => Number(x)); 149 ctx.tracks.registerTrack({ 150 uri, 151 title, 152 tags: { 153 kind: SLICE_TRACK_KIND, 154 trackIds: ids, 155 }, 156 track: createTraceProcessorSliceTrack({trace: ctx, uri, trackIds: ids}), 157 }); 158 const node = new TrackNode({ 159 uri, 160 title, 161 }); 162 parent().addChildInOrder(node); 163} 164