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