xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.Counter/trace_processor_counter_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 {Time} from '../../base/time';
16import {
17  BaseCounterTrack,
18  CounterOptions,
19} from '../../components/tracks/base_counter_track';
20import {TrackEventDetails} from '../../public/selection';
21import {Trace} from '../../public/trace';
22import {TrackMouseEvent} from '../../public/track';
23import {LONG, LONG_NULL, NUM} from '../../trace_processor/query_result';
24import {CounterDetailsPanel} from './counter_details_panel';
25
26export class TraceProcessorCounterTrack extends BaseCounterTrack {
27  constructor(
28    trace: Trace,
29    uri: string,
30    options: Partial<CounterOptions>,
31    private readonly trackId: number,
32    private readonly trackName: string,
33    private readonly rootTable: string = 'counter',
34  ) {
35    super(trace, uri, options);
36  }
37
38  getSqlSource() {
39    return `
40      select
41        id,
42        ts,
43        value
44      from ${this.rootTable}
45      where track_id = ${this.trackId}
46    `;
47  }
48
49  onMouseClick({x, timescale}: TrackMouseEvent): boolean {
50    const time = timescale.pxToHpTime(x).toTime('floor');
51
52    const query = `
53      select
54        id
55      from ${this.rootTable}
56      where
57        track_id = ${this.trackId}
58        and ts < ${time}
59      order by ts DESC
60      limit 1
61    `;
62
63    this.engine.query(query).then((result) => {
64      const it = result.iter({
65        id: NUM,
66      });
67      if (!it.valid()) {
68        return;
69      }
70      const id = it.id;
71      this.trace.selection.selectTrackEvent(this.uri, id);
72    });
73
74    return true;
75  }
76
77  // We must define this here instead of in `BaseCounterTrack` because
78  // `BaseCounterTrack` does not require the query to have an id column. Here,
79  // however, we make the assumption that `rootTable` has an id column, as we
80  // need it ot make selections in `onMouseClick` above. Whether or not we
81  // SHOULD assume `rootTable` has an id column is another matter...
82  async getSelectionDetails(id: number): Promise<TrackEventDetails> {
83    const query = `
84      WITH
85        CTE AS (
86          SELECT
87            id,
88            ts as leftTs,
89            LEAD(ts) OVER (ORDER BY ts) AS rightTs
90          FROM ${this.rootTable}
91        )
92      SELECT * FROM CTE WHERE id = ${id}
93    `;
94
95    const counter = await this.engine.query(query);
96    const row = counter.iter({
97      leftTs: LONG,
98      rightTs: LONG_NULL,
99    });
100    const leftTs = Time.fromRaw(row.leftTs);
101    const rightTs = row.rightTs !== null ? Time.fromRaw(row.rightTs) : leftTs;
102    const duration = rightTs - leftTs;
103    return {ts: leftTs, dur: duration};
104  }
105
106  detailsPanel() {
107    return new CounterDetailsPanel(this.trace, this.trackId, this.trackName);
108  }
109}
110