// Copyright (C) 2020 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {Engine} from '../common/engine'; import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state'; import {CpuProfileDetails} from '../frontend/globals'; import {Controller} from './controller'; import {globals} from './globals'; export interface CpuProfileControllerArgs { engine: Engine; } export class CpuProfileController extends Controller<'main'> { private lastSelectedSample?: CpuProfileSampleSelection; private requestingData = false; private queuedRunRequest = false; constructor(private args: CpuProfileControllerArgs) { super('main'); } run() { const selection = globals.state.currentSelection; if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') { return; } const selectedSample = selection as CpuProfileSampleSelection; if (!this.shouldRequestData(selectedSample)) { return; } if (this.requestingData) { this.queuedRunRequest = true; return; } this.requestingData = true; globals.publish('CpuProfileDetails', {}); this.lastSelectedSample = this.copyCpuProfileSample(selection); this.getSampleData(selectedSample.id) .then(sampleData => { if (sampleData !== undefined && selectedSample && this.lastSelectedSample && this.lastSelectedSample.id === selectedSample.id) { const cpuProfileDetails: CpuProfileDetails = { id: selectedSample.id, ts: selectedSample.ts, utid: selectedSample.utid, stack: sampleData, }; globals.publish('CpuProfileDetails', cpuProfileDetails); } }) .finally(() => { this.requestingData = false; if (this.queuedRunRequest) { this.queuedRunRequest = false; this.run(); } }); } private copyCpuProfileSample(cpuProfileSample: CpuProfileSampleSelection): CpuProfileSampleSelection { return { kind: cpuProfileSample.kind, id: cpuProfileSample.id, utid: cpuProfileSample.utid, ts: cpuProfileSample.ts, }; } private shouldRequestData(selection: CpuProfileSampleSelection) { return this.lastSelectedSample === undefined || (this.lastSelectedSample !== undefined && (this.lastSelectedSample.id !== selection.id)); } async getSampleData(id: number) { const sampleQuery = `SELECT samples.id, frame_name, mapping_name FROM cpu_profile_stack_sample AS samples LEFT JOIN ( SELECT callsite_id, position, spf.name AS frame_name, stack_profile_mapping.name AS mapping_name FROM ( WITH RECURSIVE callsite_parser(callsite_id, current_id, position) AS ( SELECT id, id, 0 FROM stack_profile_callsite UNION SELECT callsite_id, parent_id, position + 1 FROM callsite_parser JOIN stack_profile_callsite ON stack_profile_callsite.id = current_id WHERE stack_profile_callsite.depth > 0 ) SELECT * FROM callsite_parser ) AS flattened_callsite LEFT JOIN stack_profile_callsite AS spc LEFT JOIN ( SELECT spf.id AS id, spf.mapping AS mapping, IFNULL( ( SELECT name FROM stack_profile_symbol symbol WHERE symbol.symbol_set_id = spf.symbol_set_id LIMIT 1 ), spf.name ) AS name FROM stack_profile_frame spf ) AS spf LEFT JOIN stack_profile_mapping WHERE flattened_callsite.current_id = spc.id AND spc.frame_id = spf.id AND spf.mapping = stack_profile_mapping.id ORDER BY callsite_id, position ) AS frames ON samples.callsite_id = frames.callsite_id WHERE samples.id = ${id} ORDER BY samples.id, frames.position DESC;`; const callsites = await this.args.engine.query(sampleQuery); if (callsites.numRecords < 1) { return undefined; } const sampleData: CallsiteInfo[] = new Array(); for (let i = 0; i < callsites.numRecords; i++) { const id = +callsites.columns[0].longValues![i]; const name = callsites.columns[1].stringValues![i]; const mapping = callsites.columns[2].stringValues![i]; sampleData.push({ id, totalSize: 0, depth: 0, parentId: 0, name, selfSize: 0, mapping, merged: false }); } return sampleData; } }