1// Copyright (C) 2019 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 {ASYNC_SLICE_TRACK_KIND, Config, Data} from './common';
23
24class AsyncSliceTrackController extends TrackController<Config, Data> {
25  static readonly kind = ASYNC_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      isInstant: new Uint16Array(numRows),
82      isIncomplete: new Uint16Array(numRows),
83    };
84
85    const stringIndexes = new Map<string, number>();
86    function internString(str: string) {
87      let idx = stringIndexes.get(str);
88      if (idx !== undefined) return idx;
89      idx = slices.strings.length;
90      slices.strings.push(str);
91      stringIndexes.set(str, idx);
92      return idx;
93    }
94
95    const cols = rawResult.columns;
96    for (let row = 0; row < numRows; row++) {
97      const startNsQ = +cols[0].longValues![row];
98      const startNs = +cols[1].longValues![row];
99      const durNs = +cols[2].longValues![row];
100      const endNs = startNs + durNs;
101
102      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
103      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
104
105      if (startNsQ === endNsQ) {
106        throw new Error('Should never happen');
107      }
108
109      slices.starts[row] = fromNs(startNsQ);
110      slices.ends[row] = fromNs(endNsQ);
111      slices.depths[row] = +cols[3].longValues![row];
112      slices.titles[row] = internString(cols[4].stringValues![row]);
113      slices.sliceIds[row] = +cols[5].longValues![row];
114      slices.isInstant[row] = +cols[6].longValues![row];
115      slices.isIncomplete[row] = +cols[7].longValues![row];
116    }
117    return slices;
118  }
119}
120
121
122trackControllerRegistry.register(AsyncSliceTrackController);
123