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    const sampleQuery = `SELECT samples.id, frame_name, mapping_name
98      FROM cpu_profile_stack_sample AS samples
99      LEFT JOIN
100        (
101          SELECT
102            callsite_id,
103            position,
104            spf.name AS frame_name,
105            stack_profile_mapping.name AS mapping_name
106          FROM
107            (
108              WITH
109                RECURSIVE
110                  callsite_parser(callsite_id, current_id, position)
111                  AS (
112                    SELECT id, id, 0 FROM stack_profile_callsite
113                    UNION
114                      SELECT callsite_id, parent_id, position + 1
115                      FROM callsite_parser
116                      JOIN
117                        stack_profile_callsite
118                        ON stack_profile_callsite.id = current_id
119                      WHERE stack_profile_callsite.depth > 0
120                  )
121              SELECT *
122              FROM callsite_parser
123            ) AS flattened_callsite
124          LEFT JOIN stack_profile_callsite AS spc
125          LEFT JOIN
126            (
127              SELECT
128                spf.id AS id,
129                spf.mapping AS mapping,
130                IFNULL(
131                  (
132                    SELECT name
133                    FROM stack_profile_symbol symbol
134                    WHERE symbol.symbol_set_id = spf.symbol_set_id
135                    LIMIT 1
136                  ),
137                  spf.name
138                ) AS name
139              FROM stack_profile_frame spf
140            ) AS spf
141          LEFT JOIN stack_profile_mapping
142          WHERE
143            flattened_callsite.current_id = spc.id
144            AND spc.frame_id = spf.id
145            AND spf.mapping = stack_profile_mapping.id
146          ORDER BY callsite_id, position
147        ) AS frames
148        ON samples.callsite_id = frames.callsite_id
149      WHERE samples.id = ${id}
150      ORDER BY samples.id, frames.position DESC;`;
151
152    const callsites = await this.args.engine.query(sampleQuery);
153
154    if (slowlyCountRows(callsites) < 1) {
155      return undefined;
156    }
157
158    const sampleData: CallsiteInfo[] = new Array();
159    for (let i = 0; i < slowlyCountRows(callsites); i++) {
160      const id = +callsites.columns[0].longValues![i];
161      const name = callsites.columns[1].stringValues![i];
162      const mapping = callsites.columns[2].stringValues![i];
163
164      sampleData.push({
165        id,
166        totalSize: 0,
167        depth: 0,
168        parentId: 0,
169        name,
170        selfSize: 0,
171        mapping,
172        merged: false,
173        highlighted: false
174      });
175    }
176
177    return sampleData;
178  }
179}
180