• 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 {slowlyCountRows} from '../common/query_iterator';
17import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state';
18import {CpuProfileDetails} from '../frontend/globals';
19
20import {Controller} from './controller';
21import {globals} from './globals';
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    globals.publish('CpuProfileDetails', {});
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            globals.publish('CpuProfileDetails', 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,
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          spf.name
118        ) AS frame_name,
119        spm.name AS mapping_name
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 (slowlyCountRows(callsites) < 1) {
147      return undefined;
148    }
149
150    const sampleData: CallsiteInfo[] = new Array();
151    for (let i = 0; i < slowlyCountRows(callsites); i++) {
152      const id = +callsites.columns[0].longValues![i];
153      const name = callsites.columns[1].stringValues![i];
154      const mapping = callsites.columns[2].stringValues![i];
155
156      sampleData.push({
157        id,
158        totalSize: 0,
159        depth: 0,
160        parentId: 0,
161        name,
162        selfSize: 0,
163        mapping,
164        merged: false,
165        highlighted: false
166      });
167    }
168
169    return sampleData;
170  }
171}
172