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 {slowlyCountRows} from '../../common/query_iterator';
16import {fromNs, toNs} from '../../common/time';
17import {
18  TrackController,
19  trackControllerRegistry,
20} from '../../controller/track_controller';
21
22import {Config, Data, EXPECTED_FRAMES_SLICE_TRACK_KIND} from './common';
23
24class ExpectedFramesSliceTrackController extends TrackController<Config, Data> {
25  static readonly kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
26  private maxDurNs = 0;
27
28  async onBoundsChange(start: number, end: number, resolution: number):
29      Promise<Data> {
30    const startNs = toNs(start);
31    const endNs = toNs(end);
32
33    const pxSize = this.pxSize();
34
35    // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
36    // be an even number, so we can snap in the middle.
37    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
38
39    if (this.maxDurNs === 0) {
40      const maxDurResult = await this.query(`
41        select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
42        from experimental_slice_layout
43        where filter_track_ids = '${this.config.trackIds.join(',')}'
44      `);
45      if (slowlyCountRows(maxDurResult) === 1) {
46        this.maxDurNs = maxDurResult.columns[0].longValues![0];
47      }
48    }
49
50    const rawResult = await this.query(`
51      SELECT
52        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
53        ts,
54        max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
55        layout_depth,
56        name,
57        id,
58        dur = 0 as is_instant,
59        dur = -1 as is_incomplete
60      from experimental_slice_layout
61      where
62        filter_track_ids = '${this.config.trackIds.join(',')}' and
63        ts >= ${startNs - this.maxDurNs} and
64        ts <= ${endNs}
65      group by tsq, layout_depth
66      order by tsq, layout_depth
67    `);
68
69    const numRows = slowlyCountRows(rawResult);
70    const slices: Data = {
71      start,
72      end,
73      resolution,
74      length: numRows,
75      strings: [],
76      sliceIds: new Float64Array(numRows),
77      starts: new Float64Array(numRows),
78      ends: new Float64Array(numRows),
79      depths: new Uint16Array(numRows),
80      titles: new Uint16Array(numRows),
81      colors: new Uint16Array(numRows),
82      isInstant: new Uint16Array(numRows),
83      isIncomplete: new Uint16Array(numRows),
84    };
85
86    const stringIndexes = new Map<string, number>();
87    function internString(str: string) {
88      let idx = stringIndexes.get(str);
89      if (idx !== undefined) return idx;
90      idx = slices.strings.length;
91      slices.strings.push(str);
92      stringIndexes.set(str, idx);
93      return idx;
94    }
95    const greenIndex = internString('#4CAF50');
96
97    const cols = rawResult.columns;
98    for (let row = 0; row < numRows; row++) {
99      const startNsQ = +cols[0].longValues![row];
100      const startNs = +cols[1].longValues![row];
101      const durNs = +cols[2].longValues![row];
102      const endNs = startNs + durNs;
103
104      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
105      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
106
107      if (startNsQ === endNsQ) {
108        throw new Error('Should never happen');
109      }
110
111      slices.starts[row] = fromNs(startNsQ);
112      slices.ends[row] = fromNs(endNsQ);
113      slices.depths[row] = +cols[3].longValues![row];
114      slices.titles[row] = internString(cols[4].stringValues![row]);
115      slices.sliceIds[row] = +cols[5].longValues![row];
116      slices.isInstant[row] = +cols[6].longValues![row];
117      slices.isIncomplete[row] = +cols[7].longValues![row];
118      slices.colors![row] = greenIndex;
119    }
120    return slices;
121  }
122}
123
124
125trackControllerRegistry.register(ExpectedFramesSliceTrackController);
126