• 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} 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