• 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 {CallsiteInfo} from '../common/flamegraph_util';
16import {CpuProfileSampleSelection, getLegacySelection} from '../common/state';
17import {CpuProfileDetails, globals} from '../frontend/globals';
18import {publishCpuProfileDetails} from '../frontend/publish';
19import {Engine} from '../trace_processor/engine';
20import {NUM, STR} from '../trace_processor/query_result';
21
22import {Controller} from './controller';
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 = getLegacySelection(globals.state);
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        /* eslint-disable @typescript-eslint/strict-boolean-expressions */
60        if (
61          sampleData !== undefined &&
62          selectedSample &&
63          /* eslint-enable */
64          this.lastSelectedSample &&
65          this.lastSelectedSample.id === selectedSample.id
66        ) {
67          const cpuProfileDetails: CpuProfileDetails = {
68            id: selectedSample.id,
69            ts: selectedSample.ts,
70            utid: selectedSample.utid,
71            stack: sampleData,
72          };
73
74          publishCpuProfileDetails(cpuProfileDetails);
75        }
76      })
77      .finally(() => {
78        this.requestingData = false;
79        if (this.queuedRunRequest) {
80          this.queuedRunRequest = false;
81          this.run();
82        }
83      });
84  }
85
86  private copyCpuProfileSample(
87    cpuProfileSample: CpuProfileSampleSelection,
88  ): CpuProfileSampleSelection {
89    return {
90      kind: cpuProfileSample.kind,
91      id: cpuProfileSample.id,
92      utid: cpuProfileSample.utid,
93      ts: cpuProfileSample.ts,
94    };
95  }
96
97  private shouldRequestData(selection: CpuProfileSampleSelection) {
98    return (
99      this.lastSelectedSample === undefined ||
100      (this.lastSelectedSample !== undefined &&
101        this.lastSelectedSample.id !== selection.id)
102    );
103  }
104
105  async getSampleData(id: number) {
106    // The goal of the query is to get all the frames of
107    // the callstack at the callsite given by |id|. To do this, it does
108    // the following:
109    // 1. Gets the leaf callsite id for the sample given by |id|.
110    // 2. For this callsite, get all the frame ids and depths
111    //    for the frame and all ancestors in the callstack.
112    // 3. For each frame, get the mapping name (i.e. library which
113    //    contains the frame).
114    // 4. Symbolize each frame using the symbol table if possible.
115    // 5. Sort the query by the depth of the callstack frames.
116    const sampleQuery = `
117      SELECT
118        samples.id as id,
119        IFNULL(
120          (
121            SELECT name
122            FROM stack_profile_symbol symbol
123            WHERE symbol.symbol_set_id = spf.symbol_set_id
124            LIMIT 1
125          ),
126          COALESCE(spf.deobfuscated_name, spf.name, "")
127        ) AS name,
128        spm.name AS mapping
129      FROM cpu_profile_stack_sample AS samples
130      LEFT JOIN (
131        SELECT
132          id,
133          frame_id,
134          depth
135        FROM stack_profile_callsite
136        UNION ALL
137        SELECT
138          leaf.id AS id,
139          callsite.frame_id AS frame_id,
140          callsite.depth AS depth
141        FROM stack_profile_callsite leaf
142        JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite
143      ) AS callsites
144        ON samples.callsite_id = callsites.id
145      LEFT JOIN stack_profile_frame AS spf
146        ON callsites.frame_id = spf.id
147      LEFT JOIN stack_profile_mapping AS spm
148        ON spf.mapping = spm.id
149      WHERE samples.id = ${id}
150      ORDER BY callsites.depth;
151    `;
152
153    const callsites = await this.args.engine.query(sampleQuery);
154
155    if (callsites.numRows() === 0) {
156      return undefined;
157    }
158
159    const it = callsites.iter({
160      id: NUM,
161      name: STR,
162      mapping: STR,
163    });
164
165    const sampleData: CallsiteInfo[] = [];
166    for (; it.valid(); it.next()) {
167      sampleData.push({
168        id: it.id,
169        totalSize: 0,
170        depth: 0,
171        parentId: 0,
172        name: it.name,
173        selfSize: 0,
174        mapping: it.mapping,
175        merged: false,
176        highlighted: false,
177      });
178    }
179
180    return sampleData;
181  }
182}
183