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