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 {assertTrue} from '../../base/logging';
16import {slowlyCountRows} from '../../common/query_iterator';
17import {fromNs, toNs} from '../../common/time';
18import {
19  TrackController,
20  trackControllerRegistry,
21} from '../../controller/track_controller';
22
23import {ACTUAL_FRAMES_SLICE_TRACK_KIND, Config, Data} from './common';
24
25const BLUE_COLOR = '#03A9F4';    // Blue 500
26const GREEN_COLOR = '#4CAF50';     // Green 500
27const YELLOW_COLOR = '#FFEB3B';  // Yellow 500
28const RED_COLOR = '#FF5722';      // Red 500
29const LIGHT_GREEN_COLOR = '#C0D588'; // Light Green 500
30const PINK_COLOR = '#F515E0';        // Pink 500
31
32class ActualFramesSliceTrackController extends TrackController<Config, Data> {
33  static readonly kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
34  private maxDurNs = 0;
35
36  async onBoundsChange(start: number, end: number, resolution: number):
37      Promise<Data> {
38    const startNs = toNs(start);
39    const endNs = toNs(end);
40
41    const pxSize = this.pxSize();
42
43    // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
44    // be an even number, so we can snap in the middle.
45    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
46
47    if (this.maxDurNs === 0) {
48      const maxDurResult = await this.query(`
49        select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
50        from experimental_slice_layout
51        where filter_track_ids = '${this.config.trackIds.join(',')}'
52      `);
53      if (slowlyCountRows(maxDurResult) === 1) {
54        this.maxDurNs = maxDurResult.columns[0].longValues![0];
55      }
56    }
57
58    const rawResult = await this.query(`
59      SELECT
60        (s.ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
61        s.ts,
62        max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
63            as dur,
64        s.layout_depth,
65        s.name,
66        s.id,
67        s.dur = 0 as is_instant,
68        s.dur = -1 as is_incomplete,
69        CASE afs.jank_tag
70          WHEN 'Self Jank' THEN '${RED_COLOR}'
71          WHEN 'Other Jank' THEN '${YELLOW_COLOR}'
72          WHEN 'Dropped Frame' THEN '${BLUE_COLOR}'
73          WHEN 'Buffer Stuffing' THEN '${LIGHT_GREEN_COLOR}'
74          WHEN 'SurfaceFlinger Stuffing' THEN '${LIGHT_GREEN_COLOR}'
75          WHEN 'No Jank' THEN '${GREEN_COLOR}'
76          ELSE '${PINK_COLOR}'
77        END as color
78      from experimental_slice_layout s
79      join actual_frame_timeline_slice afs using(id)
80      where
81        filter_track_ids = '${this.config.trackIds.join(',')}' and
82        s.ts >= ${startNs - this.maxDurNs} and
83        s.ts <= ${endNs}
84      group by tsq, s.layout_depth
85      order by tsq, s.layout_depth
86    `);
87
88    const numRows = slowlyCountRows(rawResult);
89    const slices: Data = {
90      start,
91      end,
92      resolution,
93      length: numRows,
94      strings: [],
95      sliceIds: new Float64Array(numRows),
96      starts: new Float64Array(numRows),
97      ends: new Float64Array(numRows),
98      depths: new Uint16Array(numRows),
99      titles: new Uint16Array(numRows),
100      colors: new Uint16Array(numRows),
101      isInstant: new Uint16Array(numRows),
102      isIncomplete: new Uint16Array(numRows),
103    };
104
105    const stringIndexes = new Map<string, number>();
106    function internString(str: string) {
107      let idx = stringIndexes.get(str);
108      if (idx !== undefined) return idx;
109      idx = slices.strings.length;
110      slices.strings.push(str);
111      stringIndexes.set(str, idx);
112      return idx;
113    }
114
115    const cols = rawResult.columns;
116    for (let row = 0; row < numRows; row++) {
117      const startNsQ = +cols[0].longValues![row];
118      const startNs = +cols[1].longValues![row];
119      const durNs = +cols[2].longValues![row];
120      const endNs = startNs + durNs;
121
122      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
123      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
124
125      assertTrue(startNsQ !== endNsQ);
126
127      slices.starts[row] = fromNs(startNsQ);
128      slices.ends[row] = fromNs(endNsQ);
129      slices.depths[row] = +cols[3].longValues![row];
130      slices.titles[row] = internString(cols[4].stringValues![row]);
131      slices.colors![row] = internString(cols[8].stringValues![row]);
132      slices.sliceIds[row] = +cols[5].longValues![row];
133      slices.isInstant[row] = +cols[6].longValues![row];
134      slices.isIncomplete[row] = +cols[7].longValues![row];
135    }
136    return slices;
137  }
138}
139
140
141trackControllerRegistry.register(ActualFramesSliceTrackController);
142