• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, globals} from '../frontend/globals';
19import {publishCpuProfileDetails} from '../frontend/publish';
20
21import {Controller} from './controller';
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    publishCpuProfileDetails({});
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            publishCpuProfileDetails(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    // The goal of the query is to get all the frames of
98    // the callstack at the callsite given by |id|. To do this, it does
99    // the following:
100    // 1. Gets the leaf callsite id for the sample given by |id|.
101    // 2. For this callsite, get all the frame ids and depths
102    //    for the frame and all ancestors in the callstack.
103    // 3. For each frame, get the mapping name (i.e. library which
104    //    contains the frame).
105    // 4. Symbolize each frame using the symbol table if possible.
106    // 5. Sort the query by the depth of the callstack frames.
107    const sampleQuery = `
108      SELECT
109        samples.id as id,
110        IFNULL(
111          (
112            SELECT name
113            FROM stack_profile_symbol symbol
114            WHERE symbol.symbol_set_id = spf.symbol_set_id
115            LIMIT 1
116          ),
117          COALESCE(spf.deobfuscated_name, spf.name)
118        ) AS name,
119        spm.name AS mapping
120      FROM cpu_profile_stack_sample AS samples
121      LEFT JOIN (
122        SELECT
123          id,
124          frame_id,
125          depth
126        FROM stack_profile_callsite
127        UNION ALL
128        SELECT
129          leaf.id AS id,
130          callsite.frame_id AS frame_id,
131          callsite.depth AS depth
132        FROM stack_profile_callsite leaf
133        JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite
134      ) AS callsites
135        ON samples.callsite_id = callsites.id
136      LEFT JOIN stack_profile_frame AS spf
137        ON callsites.frame_id = spf.id
138      LEFT JOIN stack_profile_mapping AS spm
139        ON spf.mapping = spm.id
140      WHERE samples.id = ${id}
141      ORDER BY callsites.depth;
142    `;
143
144    const callsites = await this.args.engine.query(sampleQuery);
145
146    if (callsites.numRows() === 0) {
147      return undefined;
148    }
149
150    const it = callsites.iter({
151      id: NUM,
152      name: STR,
153      mapping: STR,
154    });
155
156    const sampleData: CallsiteInfo[] = [];
157    for (; it.valid(); it.next()) {
158      sampleData.push({
159        id: it.id,
160        totalSize: 0,
161        depth: 0,
162        parentId: 0,
163        name: it.name,
164        selfSize: 0,
165        mapping: it.mapping,
166        merged: false,
167        highlighted: false,
168      });
169    }
170
171    return sampleData;
172  }
173}
174