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 {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 as maxDur 43 from experimental_slice_layout 44 where filter_track_ids = '${this.config.trackIds.join(',')}' 45 `); 46 this.maxDurNs = maxDurResult.firstRow({maxDur: NUM_NULL}).maxDur || 0; 47 } 48 49 const queryRes = await this.query(` 50 SELECT 51 (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq, 52 ts, 53 max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur, 54 layout_depth as layoutDepth, 55 name, 56 id, 57 dur = 0 as isInstant, 58 dur = -1 as isIncomplete 59 from experimental_slice_layout 60 where 61 filter_track_ids = '${this.config.trackIds.join(',')}' and 62 ts >= ${startNs - this.maxDurNs} and 63 ts <= ${endNs} 64 group by tsq, layout_depth 65 order by tsq, layout_depth 66 `); 67 68 const numRows = queryRes.numRows(); 69 const slices: Data = { 70 start, 71 end, 72 resolution, 73 length: numRows, 74 strings: [], 75 sliceIds: new Float64Array(numRows), 76 starts: new Float64Array(numRows), 77 ends: new Float64Array(numRows), 78 depths: new Uint16Array(numRows), 79 titles: new Uint16Array(numRows), 80 colors: 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 const greenIndex = internString('#4CAF50'); 95 96 const it = queryRes.iter({ 97 tsq: NUM, 98 ts: NUM, 99 dur: NUM, 100 layoutDepth: NUM, 101 id: NUM, 102 name: STR, 103 isInstant: NUM, 104 isIncomplete: NUM, 105 }); 106 for (let row = 0; it.valid(); it.next(), ++row) { 107 const startNsQ = it.tsq; 108 const startNs = it.ts; 109 const durNs = it.dur; 110 const endNs = startNs + durNs; 111 112 let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs; 113 endNsQ = Math.max(endNsQ, startNsQ + bucketNs); 114 115 slices.starts[row] = fromNs(startNsQ); 116 slices.ends[row] = fromNs(endNsQ); 117 slices.depths[row] = it.layoutDepth; 118 slices.titles[row] = internString(it.name); 119 slices.sliceIds[row] = it.id; 120 slices.isInstant[row] = it.isInstant; 121 slices.isIncomplete[row] = it.isIncomplete; 122 slices.colors![row] = greenIndex; 123 } 124 return slices; 125 } 126} 127 128 129trackControllerRegistry.register(ExpectedFramesSliceTrackController); 130