• 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 {CallsiteInfo, CpuProfileSampleSelection} from '../common/state';
17import {CpuProfileDetails} from '../frontend/globals';
18
19import {Controller} from './controller';
20import {globals} from './globals';
21
22export interface CpuProfileControllerArgs {
23  engine: Engine;
24}
25
26export class CpuProfileController extends Controller<'main'> {
27  private lastSelectedSample?: CpuProfileSampleSelection;
28  private requestingData = false;
29  private queuedRunRequest = false;
30
31  constructor(private args: CpuProfileControllerArgs) {
32    super('main');
33  }
34
35  run() {
36    const selection = globals.state.currentSelection;
37    if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') {
38      return;
39    }
40
41    const selectedSample = selection as CpuProfileSampleSelection;
42    if (!this.shouldRequestData(selectedSample)) {
43      return;
44    }
45
46    if (this.requestingData) {
47      this.queuedRunRequest = true;
48      return;
49    }
50
51    this.requestingData = true;
52    globals.publish('CpuProfileDetails', {});
53    this.lastSelectedSample = this.copyCpuProfileSample(selection);
54
55    this.getSampleData(selectedSample.id)
56        .then(sampleData => {
57          if (sampleData !== undefined && selectedSample &&
58              this.lastSelectedSample &&
59              this.lastSelectedSample.id === selectedSample.id) {
60            const cpuProfileDetails: CpuProfileDetails = {
61              id: selectedSample.id,
62              ts: selectedSample.ts,
63              utid: selectedSample.utid,
64              stack: sampleData,
65            };
66
67            globals.publish('CpuProfileDetails', cpuProfileDetails);
68          }
69        })
70        .finally(() => {
71          this.requestingData = false;
72          if (this.queuedRunRequest) {
73            this.queuedRunRequest = false;
74            this.run();
75          }
76        });
77  }
78
79  private copyCpuProfileSample(cpuProfileSample: CpuProfileSampleSelection):
80      CpuProfileSampleSelection {
81    return {
82      kind: cpuProfileSample.kind,
83      id: cpuProfileSample.id,
84      utid: cpuProfileSample.utid,
85      ts: cpuProfileSample.ts,
86    };
87  }
88
89  private shouldRequestData(selection: CpuProfileSampleSelection) {
90    return this.lastSelectedSample === undefined ||
91        (this.lastSelectedSample !== undefined &&
92         (this.lastSelectedSample.id !== selection.id));
93  }
94
95  async getSampleData(id: number) {
96    const sampleQuery = `SELECT samples.id, frame_name, mapping_name
97      FROM cpu_profile_stack_sample AS samples
98      LEFT JOIN
99        (
100          SELECT
101            callsite_id,
102            position,
103            spf.name AS frame_name,
104            stack_profile_mapping.name AS mapping_name
105          FROM
106            (
107              WITH
108                RECURSIVE
109                  callsite_parser(callsite_id, current_id, position)
110                  AS (
111                    SELECT id, id, 0 FROM stack_profile_callsite
112                    UNION
113                      SELECT callsite_id, parent_id, position + 1
114                      FROM callsite_parser
115                      JOIN
116                        stack_profile_callsite
117                        ON stack_profile_callsite.id = current_id
118                      WHERE stack_profile_callsite.depth > 0
119                  )
120              SELECT *
121              FROM callsite_parser
122            ) AS flattened_callsite
123          LEFT JOIN stack_profile_callsite AS spc
124          LEFT JOIN
125            (
126              SELECT
127                spf.id AS id,
128                spf.mapping AS mapping,
129                IFNULL(
130                  (
131                    SELECT name
132                    FROM stack_profile_symbol symbol
133                    WHERE symbol.symbol_set_id = spf.symbol_set_id
134                    LIMIT 1
135                  ),
136                  spf.name
137                ) AS name
138              FROM stack_profile_frame spf
139            ) AS spf
140          LEFT JOIN stack_profile_mapping
141          WHERE
142            flattened_callsite.current_id = spc.id
143            AND spc.frame_id = spf.id
144            AND spf.mapping = stack_profile_mapping.id
145          ORDER BY callsite_id, position
146        ) AS frames
147        ON samples.callsite_id = frames.callsite_id
148      WHERE samples.id = ${id}
149      ORDER BY samples.id, frames.position DESC;`;
150
151    const callsites = await this.args.engine.query(sampleQuery);
152
153    if (callsites.numRecords < 1) {
154      return undefined;
155    }
156
157    const sampleData: CallsiteInfo[] = new Array();
158    for (let i = 0; i < callsites.numRecords; i++) {
159      const id = +callsites.columns[0].longValues![i];
160      const name = callsites.columns[1].stringValues![i];
161      const mapping = callsites.columns[2].stringValues![i];
162
163      sampleData.push({
164        id,
165        totalSize: 0,
166        depth: 0,
167        parentId: 0,
168        name,
169        selfSize: 0,
170        mapping,
171        merged: false
172      });
173    }
174
175    return sampleData;
176  }
177}
178