ui: make sure data fetch bounds are aligned to resolution
This CL changes the TimelineFetcher to ensure that the bounds it requests
is a multiple of the resolution. If this is not the case, it can led to
very subtle rendering artifacts: this is especially bad if the no row is
shown when it should be.
Change-Id: I01e088d6cd8d886d6b0b8e8a6285d4e0ff53f67f
Bug: 338126907
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index aa136cd..1375a4a 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-b2c625bbdeb611de9a4bfbc3497353ee91322410bb867eee603bf2635353a21f
\ No newline at end of file
+3194c35d4ec8eaf318fa2edcbf8ba04a9d29b0093fd60248ee0c73e982a1766e
\ No newline at end of file
diff --git a/ui/src/common/track_helper.ts b/ui/src/common/track_helper.ts
index 4adeee6..0b58e01 100644
--- a/ui/src/common/track_helper.ts
+++ b/ui/src/common/track_helper.ts
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {BigintMath} from '../base/bigint_math';
import {Disposable} from '../base/disposable';
import {duration, Time, time, TimeSpan} from '../base/time';
export {Store} from '../base/store';
@@ -62,9 +63,14 @@
async requestData(timespan: TimeSpan, resolution: duration): Promise<void> {
if (this.shouldLoadNewData(timespan, resolution)) {
// Over request data, one page worth to the left and right.
- const start = Time.sub(timespan.start, timespan.duration);
- const end = Time.add(timespan.end, timespan.duration);
- this.latestTimespan = new TimeSpan(start, end);
+ const start = timespan.start - timespan.duration;
+ const end = timespan.end + timespan.duration;
+
+ // Quantize up and down to the bounds of |resolution|.
+ const startQ = Time.fromRaw(BigintMath.quantFloor(start, resolution));
+ const endQ = Time.fromRaw(BigintMath.quantCeil(end, resolution));
+
+ this.latestTimespan = new TimeSpan(startQ, endQ);
this.latestResolution = resolution;
await this.loadData();
}
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 39c38e5..7e013cc 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -687,11 +687,12 @@
);
}
+ const resolution = rawSlicesKey.bucketSize;
const extraCols = this.extraSqlColumns.join(',');
const queryRes = await this.engine.query(`
SELECT
- (z.ts / ${rawSlicesKey.bucketSize}) * ${rawSlicesKey.bucketSize} as tsQ,
- max(z.dur, ${rawSlicesKey.bucketSize}) as durQ,
+ (z.ts / ${resolution}) * ${resolution} as tsQ,
+ ((z.dur / ${resolution}) + 1) * ${resolution} as durQ,
s.ts as ts,
s.dur as dur,
s.id,
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 5c042dc..139bac5 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -47,8 +47,8 @@
export interface Data extends TrackData {
// Slices are stored in a columnar fashion. All fields have the same length.
ids: Float64Array;
- starts: BigInt64Array;
- ends: BigInt64Array;
+ startQs: BigInt64Array;
+ endQs: BigInt64Array;
utids: Uint32Array;
flags: Uint8Array;
lastRowId: number;
@@ -112,8 +112,8 @@
const queryRes = await this.engine.query(`
select
- (z.ts / ${resolution}) * ${resolution} as ts,
- max(z.dur, ${resolution}) as dur,
+ (z.ts / ${resolution}) * ${resolution} as tsQ,
+ (((z.ts + z.dur) / ${resolution}) + 1) * ${resolution} as tsEndQ,
s.utid,
s.id,
s.dur = -1 as isIncomplete,
@@ -130,26 +130,23 @@
length: numRows,
lastRowId: this.lastRowId,
ids: new Float64Array(numRows),
- starts: new BigInt64Array(numRows),
- ends: new BigInt64Array(numRows),
+ startQs: new BigInt64Array(numRows),
+ endQs: new BigInt64Array(numRows),
utids: new Uint32Array(numRows),
flags: new Uint8Array(numRows),
};
const it = queryRes.iter({
- ts: LONG,
- dur: LONG,
+ tsQ: LONG,
+ tsEndQ: LONG,
utid: NUM,
id: NUM,
isIncomplete: NUM,
isRealtime: NUM,
});
for (let row = 0; it.valid(); it.next(), row++) {
- const start = it.ts;
- const dur = it.dur;
-
- slices.starts[row] = start;
- slices.ends[row] = start + dur;
+ slices.startQs[row] = it.tsQ;
+ slices.endQs[row] = it.tsEndQ;
slices.utids[row] = it.utid;
slices.ids[row] = it.id;
@@ -201,8 +198,8 @@
renderSlices(ctx: CanvasRenderingContext2D, data: Data): void {
const {visibleTimeScale, visibleTimeSpan, visibleWindowTime} =
globals.timeline;
- assertTrue(data.starts.length === data.ends.length);
- assertTrue(data.starts.length === data.utids.length);
+ assertTrue(data.startQs.length === data.endQs.length);
+ assertTrue(data.startQs.length === data.utids.length);
const visWindowEndPx = visibleTimeScale.hpTimeToPx(visibleWindowTime.end);
@@ -213,15 +210,15 @@
const startTime = visibleTimeSpan.start;
const endTime = visibleTimeSpan.end;
- const rawStartIdx = data.ends.findIndex((end) => end >= startTime);
+ const rawStartIdx = data.endQs.findIndex((end) => end >= startTime);
const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
- const [, rawEndIdx] = searchSegment(data.starts, endTime);
- const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
+ const [, rawEndIdx] = searchSegment(data.startQs, endTime);
+ const endIdx = rawEndIdx === -1 ? data.startQs.length : rawEndIdx;
for (let i = startIdx; i < endIdx; i++) {
- const tStart = Time.fromRaw(data.starts[i]);
- let tEnd = Time.fromRaw(data.ends[i]);
+ const tStart = Time.fromRaw(data.startQs[i]);
+ let tEnd = Time.fromRaw(data.endQs[i]);
const utid = data.utids[i];
// If the last slice is incomplete, it should end with the end of the
@@ -320,8 +317,8 @@
if (selection !== null && selection.kind === 'SLICE') {
const [startIndex, endIndex] = searchEq(data.ids, selection.id);
if (startIndex !== endIndex) {
- const tStart = Time.fromRaw(data.starts[startIndex]);
- const tEnd = Time.fromRaw(data.ends[startIndex]);
+ const tStart = Time.fromRaw(data.startQs[startIndex]);
+ const tEnd = Time.fromRaw(data.endQs[startIndex]);
const utid = data.utids[startIndex];
const color = colorForThread(globals.threads.get(utid));
const rectStart = visibleTimeScale.timeToPx(tStart);
@@ -413,9 +410,9 @@
const t = visibleTimeScale.pxToHpTime(pos.x);
let hoveredUtid = -1;
- for (let i = 0; i < data.starts.length; i++) {
- const tStart = Time.fromRaw(data.starts[i]);
- const tEnd = Time.fromRaw(data.ends[i]);
+ for (let i = 0; i < data.startQs.length; i++) {
+ const tStart = Time.fromRaw(data.startQs[i]);
+ const tEnd = Time.fromRaw(data.endQs[i]);
const utid = data.utids[i];
if (t.gte(tStart) && t.lt(tEnd)) {
hoveredUtid = utid;
@@ -442,7 +439,7 @@
if (data === undefined) return false;
const {visibleTimeScale} = globals.timeline;
const time = visibleTimeScale.pxToHpTime(x);
- const index = search(data.starts, time.toTime());
+ const index = search(data.startQs, time.toTime());
const id = index === -1 ? undefined : data.ids[index];
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!id || this.utidHoveredInThisTrack === -1) return false;