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 const sampleQuery = `SELECT samples.id, frame_name, mapping_name 98 FROM cpu_profile_stack_sample AS samples 99 LEFT JOIN 100 ( 101 SELECT 102 callsite_id, 103 position, 104 spf.name AS frame_name, 105 stack_profile_mapping.name AS mapping_name 106 FROM 107 ( 108 WITH 109 RECURSIVE 110 callsite_parser(callsite_id, current_id, position) 111 AS ( 112 SELECT id, id, 0 FROM stack_profile_callsite 113 UNION 114 SELECT callsite_id, parent_id, position + 1 115 FROM callsite_parser 116 JOIN 117 stack_profile_callsite 118 ON stack_profile_callsite.id = current_id 119 WHERE stack_profile_callsite.depth > 0 120 ) 121 SELECT * 122 FROM callsite_parser 123 ) AS flattened_callsite 124 LEFT JOIN stack_profile_callsite AS spc 125 LEFT JOIN 126 ( 127 SELECT 128 spf.id AS id, 129 spf.mapping AS mapping, 130 IFNULL( 131 ( 132 SELECT name 133 FROM stack_profile_symbol symbol 134 WHERE symbol.symbol_set_id = spf.symbol_set_id 135 LIMIT 1 136 ), 137 spf.name 138 ) AS name 139 FROM stack_profile_frame spf 140 ) AS spf 141 LEFT JOIN stack_profile_mapping 142 WHERE 143 flattened_callsite.current_id = spc.id 144 AND spc.frame_id = spf.id 145 AND spf.mapping = stack_profile_mapping.id 146 ORDER BY callsite_id, position 147 ) AS frames 148 ON samples.callsite_id = frames.callsite_id 149 WHERE samples.id = ${id} 150 ORDER BY samples.id, frames.position DESC;`; 151 152 const callsites = await this.args.engine.query(sampleQuery); 153 154 if (slowlyCountRows(callsites) < 1) { 155 return undefined; 156 } 157 158 const sampleData: CallsiteInfo[] = new Array(); 159 for (let i = 0; i < slowlyCountRows(callsites); i++) { 160 const id = +callsites.columns[0].longValues![i]; 161 const name = callsites.columns[1].stringValues![i]; 162 const mapping = callsites.columns[2].stringValues![i]; 163 164 sampleData.push({ 165 id, 166 totalSize: 0, 167 depth: 0, 168 parentId: 0, 169 name, 170 selfSize: 0, 171 mapping, 172 merged: false, 173 highlighted: false 174 }); 175 } 176 177 return sampleData; 178 } 179} 180