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 {slowlyCountRows} from '../common/query_iterator'; 17import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state'; 18import {CpuProfileDetails} from '../frontend/globals'; 19 20import {Controller} from './controller'; 21import {globals} from './globals'; 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 globals.publish('CpuProfileDetails', {}); 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 globals.publish('CpuProfileDetails', 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, 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 spf.name 118 ) AS frame_name, 119 spm.name AS mapping_name 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 (slowlyCountRows(callsites) < 1) { 147 return undefined; 148 } 149 150 const sampleData: CallsiteInfo[] = new Array(); 151 for (let i = 0; i < slowlyCountRows(callsites); i++) { 152 const id = +callsites.columns[0].longValues![i]; 153 const name = callsites.columns[1].stringValues![i]; 154 const mapping = callsites.columns[2].stringValues![i]; 155 156 sampleData.push({ 157 id, 158 totalSize: 0, 159 depth: 0, 160 parentId: 0, 161 name, 162 selfSize: 0, 163 mapping, 164 merged: false, 165 highlighted: false 166 }); 167 } 168 169 return sampleData; 170 } 171} 172