• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2021 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 {CPU_PROFILE_TRACK_KIND} from '../../public/track_kinds';
16import {Trace} from '../../public/trace';
17import {PerfettoPlugin} from '../../public/plugin';
18import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
19import {createCpuProfileTrack} from './cpu_profile_track';
20import {getThreadUriPrefix} from '../../public/utils';
21import {exists} from '../../base/utils';
22import {TrackNode} from '../../public/workspace';
23import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
24import {AreaSelection, areaSelectionsEqual} from '../../public/selection';
25import {
26  metricsFromTableOrSubquery,
27  QueryFlamegraph,
28} from '../../components/query_flamegraph';
29import {Flamegraph} from '../../widgets/flamegraph';
30
31export default class implements PerfettoPlugin {
32  static readonly id = 'dev.perfetto.CpuProfile';
33  static readonly dependencies = [ProcessThreadGroupsPlugin];
34
35  async onTraceLoad(ctx: Trace): Promise<void> {
36    const result = await ctx.engine.query(`
37      with thread_cpu_sample as (
38        select distinct utid
39        from cpu_profile_stack_sample
40      )
41      select
42        utid,
43        tid,
44        upid,
45        thread.name as threadName
46      from thread_cpu_sample
47      join thread using(utid)
48      where not is_idle
49    `);
50
51    const it = result.iter({
52      utid: NUM,
53      upid: NUM_NULL,
54      tid: NUM_NULL,
55      threadName: STR_NULL,
56    });
57    for (; it.valid(); it.next()) {
58      const utid = it.utid;
59      const upid = it.upid;
60      const threadName = it.threadName;
61      const uri = `${getThreadUriPrefix(upid, utid)}_cpu_samples`;
62      const title = `${threadName} (CPU Stack Samples)`;
63      ctx.tracks.registerTrack({
64        uri,
65        title,
66        tags: {
67          kind: CPU_PROFILE_TRACK_KIND,
68          utid,
69          ...(exists(upid) && {upid}),
70        },
71        track: createCpuProfileTrack(ctx, uri, utid),
72      });
73      const group = ctx.plugins
74        .getPlugin(ProcessThreadGroupsPlugin)
75        .getGroupForThread(utid);
76      const track = new TrackNode({uri, title, sortOrder: -40});
77      group?.addChildInOrder(track);
78    }
79
80    ctx.selection.registerAreaSelectionTab(createAreaSelectionTab(ctx));
81  }
82}
83
84function createAreaSelectionTab(trace: Trace) {
85  let previousSelection: undefined | AreaSelection;
86  let flamegraph: undefined | QueryFlamegraph;
87
88  return {
89    id: 'cpu_profile_flamegraph',
90    name: 'CPU Profile Sample Flamegraph',
91    render(selection: AreaSelection) {
92      const changed =
93        previousSelection === undefined ||
94        !areaSelectionsEqual(previousSelection, selection);
95
96      if (changed) {
97        flamegraph = computeCpuProfileFlamegraph(trace, selection);
98        previousSelection = selection;
99      }
100
101      if (flamegraph === undefined) {
102        return undefined;
103      }
104
105      return {isLoading: false, content: flamegraph.render()};
106    },
107  };
108}
109
110function computeCpuProfileFlamegraph(trace: Trace, selection: AreaSelection) {
111  const utids = [];
112  for (const trackInfo of selection.tracks) {
113    if (trackInfo?.tags?.kind === CPU_PROFILE_TRACK_KIND) {
114      utids.push(trackInfo.tags?.utid);
115    }
116  }
117  if (utids.length === 0) {
118    return undefined;
119  }
120  const metrics = metricsFromTableOrSubquery(
121    `
122      (
123        select
124          id,
125          parent_id as parentId,
126          name,
127          mapping_name,
128          source_file,
129          cast(line_number AS text) as line_number,
130          self_count
131        from _callstacks_for_callsites!((
132          select p.callsite_id
133          from cpu_profile_stack_sample p
134          where p.ts >= ${selection.start}
135            and p.ts <= ${selection.end}
136            and p.utid in (${utids.join(',')})
137        ))
138      )
139    `,
140    [
141      {
142        name: 'CPU Profile Samples',
143        unit: '',
144        columnName: 'self_count',
145      },
146    ],
147    'include perfetto module callstacks.stack_profile',
148    [{name: 'mapping_name', displayName: 'Mapping'}],
149    [
150      {
151        name: 'source_file',
152        displayName: 'Source File',
153        mergeAggregation: 'ONE_OR_NULL',
154      },
155      {
156        name: 'line_number',
157        displayName: 'Line Number',
158        mergeAggregation: 'ONE_OR_NULL',
159      },
160    ],
161  );
162  return new QueryFlamegraph(trace, metrics, {
163    state: Flamegraph.createDefaultState(metrics),
164  });
165}
166