xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.Ftrace/ftrace_track.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2024 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 {duration, Time, time} from '../../base/time';
16import {colorForFtrace} from '../../components/colorizer';
17import {LIMIT} from '../../components/tracks/track_data';
18import {Store, TimelineFetcher} from '../../components/tracks/track_helper';
19import {checkerboardExcept} from '../../components/checkerboard';
20import {TrackData} from '../../components/tracks/track_data';
21import {Engine} from '../../trace_processor/engine';
22import {Track} from '../../public/track';
23import {LONG, NUM, STR} from '../../trace_processor/query_result';
24import {FtraceFilter} from './common';
25import {Monitor} from '../../base/monitor';
26import {TrackRenderContext} from '../../public/track';
27import {SourceDataset, Dataset} from '../../trace_processor/dataset';
28
29const MARGIN = 2;
30const RECT_HEIGHT = 18;
31const RECT_WIDTH = 8;
32const TRACK_HEIGHT = RECT_HEIGHT + 2 * MARGIN;
33
34interface Data extends TrackData {
35  events: Array<{
36    timestamp: time;
37    color: string;
38  }>;
39}
40
41export interface Config {
42  cpu?: number;
43}
44
45export class FtraceRawTrack implements Track {
46  private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this));
47  private engine: Engine;
48  private cpu: number;
49  private store: Store<FtraceFilter>;
50  private readonly monitor: Monitor;
51
52  constructor(engine: Engine, cpu: number, store: Store<FtraceFilter>) {
53    this.engine = engine;
54    this.cpu = cpu;
55    this.store = store;
56
57    this.monitor = new Monitor([() => store.state]);
58  }
59
60  getDataset(): Dataset {
61    return new SourceDataset({
62      // 'ftrace_event' doesn't have a dur column, but injecting dur=0 (all
63      // ftrace events are effectively 'instant') allows us to participate in
64      // generic slice aggregations
65      src: 'select id, ts, 0 as dur, name, cpu from ftrace_event',
66      schema: {
67        id: NUM,
68        name: STR,
69        ts: LONG,
70        dur: LONG,
71      },
72      filter: {
73        col: 'cpu',
74        eq: this.cpu,
75      },
76    });
77  }
78
79  async onUpdate({
80    visibleWindow,
81    resolution,
82  }: TrackRenderContext): Promise<void> {
83    this.monitor.ifStateChanged(() => {
84      this.fetcher.invalidate();
85    });
86    await this.fetcher.requestData(visibleWindow.toTimeSpan(), resolution);
87  }
88
89  async onDestroy?(): Promise<void> {
90    this.fetcher[Symbol.dispose]();
91  }
92
93  getHeight(): number {
94    return TRACK_HEIGHT;
95  }
96
97  async onBoundsChange(
98    start: time,
99    end: time,
100    resolution: duration,
101  ): Promise<Data> {
102    const excludeList = Array.from(this.store.state.excludeList);
103    const excludeListSql = excludeList.map((s) => `'${s}'`).join(',');
104    const cpuFilter = this.cpu === undefined ? '' : `and cpu = ${this.cpu}`;
105
106    const queryRes = await this.engine.query(`
107      select
108        cast(ts / ${resolution} as integer) * ${resolution} as tsQuant,
109        name
110      from ftrace_event
111      where
112        name not in (${excludeListSql}) and
113        ts >= ${start} and ts <= ${end} ${cpuFilter}
114      group by tsQuant
115      order by tsQuant limit ${LIMIT};`);
116
117    const rowCount = queryRes.numRows();
118
119    const it = queryRes.iter({tsQuant: LONG, name: STR});
120    const events = [];
121    for (let row = 0; it.valid(); it.next(), row++) {
122      events.push({
123        timestamp: Time.fromRaw(it.tsQuant),
124        color: colorForFtrace(it.name).base.cssString,
125      });
126    }
127    return {
128      start,
129      end,
130      resolution,
131      length: rowCount,
132      events,
133    };
134  }
135
136  render({ctx, size, timescale}: TrackRenderContext): void {
137    const data = this.fetcher.data;
138
139    if (data === undefined) return; // Can't possibly draw anything.
140
141    const dataStartPx = timescale.timeToPx(data.start);
142    const dataEndPx = timescale.timeToPx(data.end);
143
144    checkerboardExcept(
145      ctx,
146      this.getHeight(),
147      0,
148      size.width,
149      dataStartPx,
150      dataEndPx,
151    );
152    for (const e of data.events) {
153      ctx.fillStyle = e.color;
154      const xPos = Math.floor(timescale.timeToPx(e.timestamp));
155      ctx.fillRect(xPos - RECT_WIDTH / 2, MARGIN, RECT_WIDTH, RECT_HEIGHT);
156    }
157  }
158}
159