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 {CallsiteInfo, CpuProfileSampleSelection} from '../common/state'; 17import {CpuProfileDetails} from '../frontend/globals'; 18 19import {Controller} from './controller'; 20import {globals} from './globals'; 21 22export interface CpuProfileControllerArgs { 23 engine: Engine; 24} 25 26export class CpuProfileController extends Controller<'main'> { 27 private lastSelectedSample?: CpuProfileSampleSelection; 28 private requestingData = false; 29 private queuedRunRequest = false; 30 31 constructor(private args: CpuProfileControllerArgs) { 32 super('main'); 33 } 34 35 run() { 36 const selection = globals.state.currentSelection; 37 if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') { 38 return; 39 } 40 41 const selectedSample = selection as CpuProfileSampleSelection; 42 if (!this.shouldRequestData(selectedSample)) { 43 return; 44 } 45 46 if (this.requestingData) { 47 this.queuedRunRequest = true; 48 return; 49 } 50 51 this.requestingData = true; 52 globals.publish('CpuProfileDetails', {}); 53 this.lastSelectedSample = this.copyCpuProfileSample(selection); 54 55 this.getSampleData(selectedSample.id) 56 .then(sampleData => { 57 if (sampleData !== undefined && selectedSample && 58 this.lastSelectedSample && 59 this.lastSelectedSample.id === selectedSample.id) { 60 const cpuProfileDetails: CpuProfileDetails = { 61 id: selectedSample.id, 62 ts: selectedSample.ts, 63 utid: selectedSample.utid, 64 stack: sampleData, 65 }; 66 67 globals.publish('CpuProfileDetails', cpuProfileDetails); 68 } 69 }) 70 .finally(() => { 71 this.requestingData = false; 72 if (this.queuedRunRequest) { 73 this.queuedRunRequest = false; 74 this.run(); 75 } 76 }); 77 } 78 79 private copyCpuProfileSample(cpuProfileSample: CpuProfileSampleSelection): 80 CpuProfileSampleSelection { 81 return { 82 kind: cpuProfileSample.kind, 83 id: cpuProfileSample.id, 84 utid: cpuProfileSample.utid, 85 ts: cpuProfileSample.ts, 86 }; 87 } 88 89 private shouldRequestData(selection: CpuProfileSampleSelection) { 90 return this.lastSelectedSample === undefined || 91 (this.lastSelectedSample !== undefined && 92 (this.lastSelectedSample.id !== selection.id)); 93 } 94 95 async getSampleData(id: number) { 96 const sampleQuery = `SELECT samples.id, frame_name, mapping_name 97 FROM cpu_profile_stack_sample AS samples 98 LEFT JOIN 99 ( 100 SELECT 101 callsite_id, 102 position, 103 spf.name AS frame_name, 104 stack_profile_mapping.name AS mapping_name 105 FROM 106 ( 107 WITH 108 RECURSIVE 109 callsite_parser(callsite_id, current_id, position) 110 AS ( 111 SELECT id, id, 0 FROM stack_profile_callsite 112 UNION 113 SELECT callsite_id, parent_id, position + 1 114 FROM callsite_parser 115 JOIN 116 stack_profile_callsite 117 ON stack_profile_callsite.id = current_id 118 WHERE stack_profile_callsite.depth > 0 119 ) 120 SELECT * 121 FROM callsite_parser 122 ) AS flattened_callsite 123 LEFT JOIN stack_profile_callsite AS spc 124 LEFT JOIN 125 ( 126 SELECT 127 spf.id AS id, 128 spf.mapping AS mapping, 129 IFNULL( 130 ( 131 SELECT name 132 FROM stack_profile_symbol symbol 133 WHERE symbol.symbol_set_id = spf.symbol_set_id 134 LIMIT 1 135 ), 136 spf.name 137 ) AS name 138 FROM stack_profile_frame spf 139 ) AS spf 140 LEFT JOIN stack_profile_mapping 141 WHERE 142 flattened_callsite.current_id = spc.id 143 AND spc.frame_id = spf.id 144 AND spf.mapping = stack_profile_mapping.id 145 ORDER BY callsite_id, position 146 ) AS frames 147 ON samples.callsite_id = frames.callsite_id 148 WHERE samples.id = ${id} 149 ORDER BY samples.id, frames.position DESC;`; 150 151 const callsites = await this.args.engine.query(sampleQuery); 152 153 if (callsites.numRecords < 1) { 154 return undefined; 155 } 156 157 const sampleData: CallsiteInfo[] = new Array(); 158 for (let i = 0; i < callsites.numRecords; i++) { 159 const id = +callsites.columns[0].longValues![i]; 160 const name = callsites.columns[1].stringValues![i]; 161 const mapping = callsites.columns[2].stringValues![i]; 162 163 sampleData.push({ 164 id, 165 totalSize: 0, 166 depth: 0, 167 parentId: 0, 168 name, 169 selfSize: 0, 170 mapping, 171 merged: false 172 }); 173 } 174 175 return sampleData; 176 } 177} 178