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 {CallsiteInfo} from '../common/flamegraph_util'; 16import {CpuProfileSampleSelection, getLegacySelection} from '../common/state'; 17import {CpuProfileDetails, globals} from '../frontend/globals'; 18import {publishCpuProfileDetails} from '../frontend/publish'; 19import {Engine} from '../trace_processor/engine'; 20import {NUM, STR} from '../trace_processor/query_result'; 21 22import {Controller} from './controller'; 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 = getLegacySelection(globals.state); 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 /* eslint-disable @typescript-eslint/strict-boolean-expressions */ 60 if ( 61 sampleData !== undefined && 62 selectedSample && 63 /* eslint-enable */ 64 this.lastSelectedSample && 65 this.lastSelectedSample.id === selectedSample.id 66 ) { 67 const cpuProfileDetails: CpuProfileDetails = { 68 id: selectedSample.id, 69 ts: selectedSample.ts, 70 utid: selectedSample.utid, 71 stack: sampleData, 72 }; 73 74 publishCpuProfileDetails(cpuProfileDetails); 75 } 76 }) 77 .finally(() => { 78 this.requestingData = false; 79 if (this.queuedRunRequest) { 80 this.queuedRunRequest = false; 81 this.run(); 82 } 83 }); 84 } 85 86 private copyCpuProfileSample( 87 cpuProfileSample: CpuProfileSampleSelection, 88 ): CpuProfileSampleSelection { 89 return { 90 kind: cpuProfileSample.kind, 91 id: cpuProfileSample.id, 92 utid: cpuProfileSample.utid, 93 ts: cpuProfileSample.ts, 94 }; 95 } 96 97 private shouldRequestData(selection: CpuProfileSampleSelection) { 98 return ( 99 this.lastSelectedSample === undefined || 100 (this.lastSelectedSample !== undefined && 101 this.lastSelectedSample.id !== selection.id) 102 ); 103 } 104 105 async getSampleData(id: number) { 106 // The goal of the query is to get all the frames of 107 // the callstack at the callsite given by |id|. To do this, it does 108 // the following: 109 // 1. Gets the leaf callsite id for the sample given by |id|. 110 // 2. For this callsite, get all the frame ids and depths 111 // for the frame and all ancestors in the callstack. 112 // 3. For each frame, get the mapping name (i.e. library which 113 // contains the frame). 114 // 4. Symbolize each frame using the symbol table if possible. 115 // 5. Sort the query by the depth of the callstack frames. 116 const sampleQuery = ` 117 SELECT 118 samples.id as id, 119 IFNULL( 120 ( 121 SELECT name 122 FROM stack_profile_symbol symbol 123 WHERE symbol.symbol_set_id = spf.symbol_set_id 124 LIMIT 1 125 ), 126 COALESCE(spf.deobfuscated_name, spf.name, "") 127 ) AS name, 128 spm.name AS mapping 129 FROM cpu_profile_stack_sample AS samples 130 LEFT JOIN ( 131 SELECT 132 id, 133 frame_id, 134 depth 135 FROM stack_profile_callsite 136 UNION ALL 137 SELECT 138 leaf.id AS id, 139 callsite.frame_id AS frame_id, 140 callsite.depth AS depth 141 FROM stack_profile_callsite leaf 142 JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite 143 ) AS callsites 144 ON samples.callsite_id = callsites.id 145 LEFT JOIN stack_profile_frame AS spf 146 ON callsites.frame_id = spf.id 147 LEFT JOIN stack_profile_mapping AS spm 148 ON spf.mapping = spm.id 149 WHERE samples.id = ${id} 150 ORDER BY callsites.depth; 151 `; 152 153 const callsites = await this.args.engine.query(sampleQuery); 154 155 if (callsites.numRows() === 0) { 156 return undefined; 157 } 158 159 const it = callsites.iter({ 160 id: NUM, 161 name: STR, 162 mapping: STR, 163 }); 164 165 const sampleData: CallsiteInfo[] = []; 166 for (; it.valid(); it.next()) { 167 sampleData.push({ 168 id: it.id, 169 totalSize: 0, 170 depth: 0, 171 parentId: 0, 172 name: it.name, 173 selfSize: 0, 174 mapping: it.mapping, 175 merged: false, 176 highlighted: false, 177 }); 178 } 179 180 return sampleData; 181 } 182} 183