1// Copyright (C) 2020 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 {Engine} from '../common/engine'; 16import {NUM, STR} from '../common/query_result'; 17import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state'; 18import {CpuProfileDetails, globals} from '../frontend/globals'; 19import {publishCpuProfileDetails} from '../frontend/publish'; 20 21import {Controller} from './controller'; 22 23export interface CpuProfileControllerArgs { 24 engine: Engine; 25} 26 27export class CpuProfileController extends Controller<'main'> { 28 private lastSelectedSample?: CpuProfileSampleSelection; 29 private requestingData = false; 30 private queuedRunRequest = false; 31 32 constructor(private args: CpuProfileControllerArgs) { 33 super('main'); 34 } 35 36 run() { 37 const selection = globals.state.currentSelection; 38 if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') { 39 return; 40 } 41 42 const selectedSample = selection as CpuProfileSampleSelection; 43 if (!this.shouldRequestData(selectedSample)) { 44 return; 45 } 46 47 if (this.requestingData) { 48 this.queuedRunRequest = true; 49 return; 50 } 51 52 this.requestingData = true; 53 publishCpuProfileDetails({}); 54 this.lastSelectedSample = this.copyCpuProfileSample(selection); 55 56 this.getSampleData(selectedSample.id) 57 .then((sampleData) => { 58 if (sampleData !== undefined && selectedSample && 59 this.lastSelectedSample && 60 this.lastSelectedSample.id === selectedSample.id) { 61 const cpuProfileDetails: CpuProfileDetails = { 62 id: selectedSample.id, 63 ts: selectedSample.ts, 64 utid: selectedSample.utid, 65 stack: sampleData, 66 }; 67 68 publishCpuProfileDetails(cpuProfileDetails); 69 } 70 }) 71 .finally(() => { 72 this.requestingData = false; 73 if (this.queuedRunRequest) { 74 this.queuedRunRequest = false; 75 this.run(); 76 } 77 }); 78 } 79 80 private copyCpuProfileSample(cpuProfileSample: CpuProfileSampleSelection): 81 CpuProfileSampleSelection { 82 return { 83 kind: cpuProfileSample.kind, 84 id: cpuProfileSample.id, 85 utid: cpuProfileSample.utid, 86 ts: cpuProfileSample.ts, 87 }; 88 } 89 90 private shouldRequestData(selection: CpuProfileSampleSelection) { 91 return this.lastSelectedSample === undefined || 92 (this.lastSelectedSample !== undefined && 93 (this.lastSelectedSample.id !== selection.id)); 94 } 95 96 async getSampleData(id: number) { 97 // The goal of the query is to get all the frames of 98 // the callstack at the callsite given by |id|. To do this, it does 99 // the following: 100 // 1. Gets the leaf callsite id for the sample given by |id|. 101 // 2. For this callsite, get all the frame ids and depths 102 // for the frame and all ancestors in the callstack. 103 // 3. For each frame, get the mapping name (i.e. library which 104 // contains the frame). 105 // 4. Symbolize each frame using the symbol table if possible. 106 // 5. Sort the query by the depth of the callstack frames. 107 const sampleQuery = ` 108 SELECT 109 samples.id as id, 110 IFNULL( 111 ( 112 SELECT name 113 FROM stack_profile_symbol symbol 114 WHERE symbol.symbol_set_id = spf.symbol_set_id 115 LIMIT 1 116 ), 117 COALESCE(spf.deobfuscated_name, spf.name) 118 ) AS name, 119 spm.name AS mapping 120 FROM cpu_profile_stack_sample AS samples 121 LEFT JOIN ( 122 SELECT 123 id, 124 frame_id, 125 depth 126 FROM stack_profile_callsite 127 UNION ALL 128 SELECT 129 leaf.id AS id, 130 callsite.frame_id AS frame_id, 131 callsite.depth AS depth 132 FROM stack_profile_callsite leaf 133 JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite 134 ) AS callsites 135 ON samples.callsite_id = callsites.id 136 LEFT JOIN stack_profile_frame AS spf 137 ON callsites.frame_id = spf.id 138 LEFT JOIN stack_profile_mapping AS spm 139 ON spf.mapping = spm.id 140 WHERE samples.id = ${id} 141 ORDER BY callsites.depth; 142 `; 143 144 const callsites = await this.args.engine.query(sampleQuery); 145 146 if (callsites.numRows() === 0) { 147 return undefined; 148 } 149 150 const it = callsites.iter({ 151 id: NUM, 152 name: STR, 153 mapping: STR, 154 }); 155 156 const sampleData: CallsiteInfo[] = []; 157 for (; it.valid(); it.next()) { 158 sampleData.push({ 159 id: it.id, 160 totalSize: 0, 161 depth: 0, 162 parentId: 0, 163 name: it.name, 164 selfSize: 0, 165 mapping: it.mapping, 166 merged: false, 167 highlighted: false, 168 }); 169 } 170 171 return sampleData; 172 } 173} 174