• 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 {assertExists} from '../../base/logging';
16import {
17  metricsFromTableOrSubquery,
18  QueryFlamegraph,
19} from '../../components/query_flamegraph';
20import {PerfettoPlugin} from '../../public/plugin';
21import {AreaSelection, areaSelectionsEqual} from '../../public/selection';
22import {Trace} from '../../public/trace';
23import {
24  COUNTER_TRACK_KIND,
25  PERF_SAMPLES_PROFILE_TRACK_KIND,
26} from '../../public/track_kinds';
27import {getThreadUriPrefix} from '../../public/utils';
28import {TrackNode} from '../../public/workspace';
29import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
30import {Flamegraph} from '../../widgets/flamegraph';
31import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
32import StandardGroupsPlugin from '../dev.perfetto.StandardGroups';
33import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack';
34import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track';
35import {
36  createProcessPerfSamplesProfileTrack,
37  createThreadPerfSamplesProfileTrack,
38} from './perf_samples_profile_track';
39
40function makeUriForProc(upid: number) {
41  return `/process_${upid}/perf_samples_profile`;
42}
43
44export default class implements PerfettoPlugin {
45  static readonly id = 'dev.perfetto.LinuxPerf';
46  static readonly dependencies = [
47    ProcessThreadGroupsPlugin,
48    StandardGroupsPlugin,
49    TraceProcessorTrackPlugin,
50  ];
51
52  async onTraceLoad(trace: Trace): Promise<void> {
53    await this.addProcessPerfSamplesTracks(trace);
54    await this.addThreadPerfSamplesTracks(trace);
55    await this.addPerfCounterTracks(trace);
56
57    trace.onTraceReady.addListener(async () => {
58      await selectPerfSample(trace);
59    });
60  }
61
62  private async addProcessPerfSamplesTracks(trace: Trace) {
63    const pResult = await trace.engine.query(`
64      select distinct upid
65      from perf_sample
66      join thread using (utid)
67      where callsite_id is not null and upid is not null
68    `);
69    for (const it = pResult.iter({upid: NUM}); it.valid(); it.next()) {
70      const upid = it.upid;
71      const uri = makeUriForProc(upid);
72      const title = `Process Callstacks`;
73      trace.tracks.registerTrack({
74        uri,
75        title,
76        tags: {
77          kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
78          upid,
79        },
80        track: createProcessPerfSamplesProfileTrack(trace, uri, upid),
81      });
82      const group = trace.plugins
83        .getPlugin(ProcessThreadGroupsPlugin)
84        .getGroupForProcess(upid);
85      const track = new TrackNode({uri, title, sortOrder: -40});
86      group?.addChildInOrder(track);
87    }
88  }
89
90  private async addThreadPerfSamplesTracks(trace: Trace) {
91    const tResult = await trace.engine.query(`
92      select distinct
93        utid,
94        tid,
95        thread.name as threadName,
96        upid
97      from perf_sample
98      join thread using (utid)
99      where callsite_id is not null
100    `);
101    for (
102      const it = tResult.iter({
103        utid: NUM,
104        tid: NUM,
105        threadName: STR_NULL,
106        upid: NUM_NULL,
107      });
108      it.valid();
109      it.next()
110    ) {
111      const {threadName, utid, tid, upid} = it;
112      const title =
113        threadName === null
114          ? `Thread Callstacks ${tid}`
115          : `${threadName} Callstacks ${tid}`;
116      const uri = `${getThreadUriPrefix(upid, utid)}_perf_samples_profile`;
117      trace.tracks.registerTrack({
118        uri,
119        title,
120        tags: {
121          kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
122          utid,
123          upid: upid ?? undefined,
124        },
125        track: createThreadPerfSamplesProfileTrack(trace, uri, utid),
126      });
127      const group = trace.plugins
128        .getPlugin(ProcessThreadGroupsPlugin)
129        .getGroupForThread(utid);
130      const track = new TrackNode({uri, title, sortOrder: -50});
131      group?.addChildInOrder(track);
132    }
133  }
134
135  private async addPerfCounterTracks(trace: Trace) {
136    const perfCountersGroup = new TrackNode({
137      title: 'Perf Counters',
138      isSummary: true,
139    });
140
141    const result = await trace.engine.query(`
142      select
143        id,
144        name,
145        unit,
146        extract_arg(dimension_arg_set_id, 'cpu') as cpu
147      from counter_track
148      where type = 'perf_counter'
149      order by name, cpu
150    `);
151
152    const it = result.iter({
153      id: NUM,
154      name: STR_NULL,
155      unit: STR_NULL,
156      cpu: NUM, // Perf counters always have a cpu dimension
157    });
158
159    for (; it.valid(); it.next()) {
160      const {id: trackId, name, unit, cpu} = it;
161      const uri = `/counter_${trackId}`;
162      const title = `Cpu ${cpu} ${name}`;
163
164      trace.tracks.registerTrack({
165        uri,
166        title,
167        tags: {
168          kind: COUNTER_TRACK_KIND,
169          trackIds: [trackId],
170          cpu,
171        },
172        track: new TraceProcessorCounterTrack(
173          trace,
174          uri,
175          {
176            yMode: 'rate', // Default to rate mode
177            unit: unit ?? undefined,
178          },
179          trackId,
180          title,
181        ),
182      });
183      const trackNode = new TrackNode({
184        uri,
185        title,
186      });
187      perfCountersGroup.addChildLast(trackNode);
188    }
189
190    if (perfCountersGroup.hasChildren) {
191      const hardwareGroup = trace.plugins
192        .getPlugin(StandardGroupsPlugin)
193        .getOrCreateStandardGroup(trace.workspace, 'HARDWARE');
194      hardwareGroup.addChildInOrder(perfCountersGroup);
195    }
196
197    trace.selection.registerAreaSelectionTab(createAreaSelectionTab(trace));
198  }
199}
200
201async function selectPerfSample(trace: Trace) {
202  const profile = await assertExists(trace.engine).query(`
203    select upid
204    from perf_sample
205    join thread using (utid)
206    where callsite_id is not null
207    order by ts desc
208    limit 1
209  `);
210  if (profile.numRows() !== 1) return;
211  const row = profile.firstRow({upid: NUM});
212  const upid = row.upid;
213
214  // Create an area selection over the first process with a perf samples track
215  trace.selection.selectArea({
216    start: trace.traceInfo.start,
217    end: trace.traceInfo.end,
218    trackUris: [makeUriForProc(upid)],
219  });
220}
221
222function createAreaSelectionTab(trace: Trace) {
223  let previousSelection: undefined | AreaSelection;
224  let flamegraph: undefined | QueryFlamegraph;
225
226  return {
227    id: 'perf_sample_flamegraph',
228    name: 'Perf Sample Flamegraph',
229    render(selection: AreaSelection) {
230      const changed =
231        previousSelection === undefined ||
232        !areaSelectionsEqual(previousSelection, selection);
233
234      if (changed) {
235        flamegraph = computePerfSampleFlamegraph(trace, selection);
236        previousSelection = selection;
237      }
238
239      if (flamegraph === undefined) {
240        return undefined;
241      }
242
243      return {isLoading: false, content: flamegraph.render()};
244    },
245  };
246}
247
248function getUpidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) {
249  const upids = [];
250  for (const trackInfo of currentSelection.tracks) {
251    if (
252      trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
253      trackInfo.tags?.utid === undefined
254    ) {
255      upids.push(assertExists(trackInfo.tags?.upid));
256    }
257  }
258  return upids;
259}
260
261function getUtidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) {
262  const utids = [];
263  for (const trackInfo of currentSelection.tracks) {
264    if (
265      trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
266      trackInfo.tags?.utid !== undefined
267    ) {
268      utids.push(trackInfo.tags?.utid);
269    }
270  }
271  return utids;
272}
273
274function computePerfSampleFlamegraph(
275  trace: Trace,
276  currentSelection: AreaSelection,
277) {
278  const upids = getUpidsFromPerfSampleAreaSelection(currentSelection);
279  const utids = getUtidsFromPerfSampleAreaSelection(currentSelection);
280  if (utids.length === 0 && upids.length === 0) {
281    return undefined;
282  }
283  const metrics = metricsFromTableOrSubquery(
284    `
285      (
286        select id, parent_id as parentId, name, self_count
287        from _callstacks_for_callsites!((
288          select p.callsite_id
289          from perf_sample p
290          join thread t using (utid)
291          where p.ts >= ${currentSelection.start}
292            and p.ts <= ${currentSelection.end}
293            and (
294              p.utid in (${utids.join(',')})
295              or t.upid in (${upids.join(',')})
296            )
297        ))
298      )
299    `,
300    [
301      {
302        name: 'Perf Samples',
303        unit: '',
304        columnName: 'self_count',
305      },
306    ],
307    'include perfetto module linux.perf.samples',
308  );
309  return new QueryFlamegraph(trace, metrics, {
310    state: Flamegraph.createDefaultState(metrics),
311  });
312}
313