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