[perf] Reflect the selected point on a trace into the URL.
Also displays the xbar at the location of the click to make it easier
to track which point is being displayed in the 'Details' section.
Bug: skia:13725
Change-Id: I704d11a9d96a3b61088d7c062f8b0274ee7241fe
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/578676
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/modules/explore-sk/explore-sk.ts b/perf/modules/explore-sk/explore-sk.ts
index d1b1186..d4dc1ac 100644
--- a/perf/modules/explore-sk/explore-sk.ts
+++ b/perf/modules/explore-sk/explore-sk.ts
@@ -48,6 +48,7 @@
progress,
pivot,
FrameResponseDisplayMode,
+ ColumnHeader,
} from '../json';
import {
PlotSimpleSk,
@@ -115,6 +116,48 @@
summary: [],
});
+// Stores the trace name and commit number of a single point on a trace.
+export interface PointSelected {
+ commit: number
+ name: string
+}
+
+/** Returns true if the PointSelected is valid. */
+export const isValidSelection = (p: PointSelected): boolean => p.name !== '';
+
+/** Converts a PointSelected into a CustomEvent<PlotSimpleSkTraceEventDetails>,
+ * so that it can be passed into traceSelected().
+ *
+ * Note that we need the _dataframe.header to convert the commit back into an
+ * offset. Also note that might fail, in which case the 'x' value will be set to
+ * -1.
+ */
+export const selectionToEvent = (p: PointSelected, header: (ColumnHeader | null)[] | null): CustomEvent<PlotSimpleSkTraceEventDetails> => {
+ let x = -1;
+ if (header !== null) {
+ // Find the index of the ColumnHeader that matches the commit.
+ x = header.findIndex((h: ColumnHeader | null) => {
+ if (h === null) {
+ return false;
+ }
+ return (h.offset === p.commit);
+ });
+ }
+ return new CustomEvent<PlotSimpleSkTraceEventDetails>('', {
+ detail: {
+ x: x,
+ y: 0,
+ name: p.name,
+ },
+ });
+};
+
+/** Returns a default value for PointSelected. */
+export const defaultPointSelected = (): PointSelected => ({
+ commit: 0,
+ name: '',
+});
+
// State is reflected to the URL via stateReflector.
class State {
begin: number = Math.floor(Date.now() / 1000 - DEFAULT_RANGE_S);
@@ -144,6 +187,8 @@
sort: string = '' // Pivot table sort order.
summary: boolean = false; // Whether to show the zoom/summary area.
+
+ selected: PointSelected = defaultPointSelected(); // The point on a trace that was clicked on.
}
// TODO(jcgregorio) Move to a 'key' module.
@@ -860,18 +905,28 @@
/** Highlight a trace when it is clicked on. */
private traceSelected(e: CustomEvent<PlotSimpleSkTraceEventDetails>) {
this.plot!.highlight = [e.detail.name];
+ this.plot!.xbar = e.detail.x;
this.commits!.details = [];
const x = e.detail.x;
+
+ if (x < 0) {
+ return;
+ }
// loop backwards from x until you get the next
// non MISSING_DATA_SENTINEL point.
- const commits = [this._dataframe.header![x]?.offset];
+ const commit = this._dataframe.header![x]?.offset;
+ if (!commit) {
+ return;
+ }
+
+ const commits = [commit];
const trace = this._dataframe.traceset[e.detail.name];
for (let i = x - 1; i >= 0; i--) {
if (trace![i] !== MISSING_DATA_SENTINEL) {
break;
}
- commits.push(this._dataframe.header![i]?.offset);
+ commits.push(this._dataframe.header![i]!.offset);
}
// Convert the trace id into a paramset to display.
const params: { [key: string]: string } = toObject(e.detail.name);
@@ -882,6 +937,10 @@
this._render();
+ this.state.selected.name = e.detail.name;
+ this.state.selected.commit = commit;
+ this._stateHasChanged();
+
// Request populated commits from the server.
fetch('/_/cid/', {
method: 'POST',
@@ -907,6 +966,16 @@
.catch(errorMessage);
}
+ private clearSelectedState() {
+ // Switch back to the params tab since we are about to hide the details tab.
+ this.detailTab!.selected = PARAMS_TAB_INDEX;
+ this.commitsTab!.disabled = true;
+ this.plot!.highlight = [];
+ this.plot!.xbar = -1;
+ this.state.selected = defaultPointSelected();
+ this._stateHasChanged();
+ }
+
private startStateReflector() {
this._stateHasChanged = stateReflector(
() => (this.state as unknown) as HintableObject,
@@ -1017,6 +1086,16 @@
this.plot!.removeAll();
this.addTraces(json, switchToTab);
this._render();
+ if (isValidSelection(this.state.selected)) {
+ const e = selectionToEvent(this.state.selected, this._dataframe.header);
+ // If the range has moved to no longer include the selected commit then
+ // clear the selection.
+ if (e.detail.x === -1) {
+ this.clearSelectedState();
+ } else {
+ this.traceSelected(e);
+ }
+ }
});
}
@@ -1244,6 +1323,7 @@
this.displayMode = 'display_query_only';
this._render();
if (!skipHistory) {
+ this.clearSelectedState();
this._stateHasChanged();
}
}
@@ -1279,6 +1359,7 @@
.then((json) => {
this.state.keys = json.id;
this.state.queries = [];
+ this.clearSelectedState();
this._stateHasChanged();
this._render();
})
diff --git a/perf/modules/explore-sk/explore-sk_test.ts b/perf/modules/explore-sk/explore-sk_test.ts
index 9045732..8802e66 100644
--- a/perf/modules/explore-sk/explore-sk_test.ts
+++ b/perf/modules/explore-sk/explore-sk_test.ts
@@ -1,8 +1,10 @@
/* eslint-disable dot-notation */
import { assert } from 'chai';
import fetchMock from 'fetch-mock';
-import { FrameRequest, progress } from '../json';
-import { calculateRangeChange, ExploreSk } from './explore-sk';
+import { ColumnHeader, progress } from '../json';
+import {
+ calculateRangeChange, defaultPointSelected, ExploreSk, isValidSelection, PointSelected, selectionToEvent,
+} from './explore-sk';
fetchMock.config.overwriteRoutes = true;
@@ -113,3 +115,69 @@
fetchMock.restore();
});
});
+
+describe('PointSelected', () => {
+ it('defaults to not having a name', () => {
+ const p = defaultPointSelected();
+ assert.isEmpty(p.name);
+ });
+
+ it('defaults to being invalid', () => {
+ const p = defaultPointSelected();
+ assert.isFalse(isValidSelection(p));
+ });
+
+ it('becomes a valid event if the commit appears in the header', () => {
+ const header: ColumnHeader[] = [
+ {
+ offset: 99,
+ timestamp: 0,
+ },
+ {
+ offset: 100,
+ timestamp: 0,
+ },
+ {
+ offset: 101,
+ timestamp: 0,
+ },
+ ];
+
+ const p: PointSelected = {
+ commit: 100,
+ name: 'foo',
+ };
+ // selectionToEvent will look up the commit (aka offset) in header and
+ // should return an event where the 'x' value is the index of the matching
+ // ColumnHeader in 'header', i.e. 1.
+ const e = selectionToEvent(p, header);
+ assert.equal(e.detail.x, 1);
+ });
+
+ it('becomes an invalid event if the commit does not appear in the header', () => {
+ const header: ColumnHeader[] = [
+ {
+ offset: 99,
+ timestamp: 0,
+ },
+ {
+ offset: 100,
+ timestamp: 0,
+ },
+ {
+ offset: 101,
+ timestamp: 0,
+ },
+ ];
+
+ const p: PointSelected = {
+ commit: 102,
+ name: 'foo',
+ };
+ // selectionToEvent will look up the commit (aka offset) in header and
+ // should return an event where the 'x' value is -1 since the matching
+ // ColumnHeader in 'header' doesn't exist.
+ const e = selectionToEvent(p, header);
+ assert.equal(e.detail.x, -1);
+ });
+});
diff --git a/perf/modules/plot-simple-sk/plot-simple-sk.ts b/perf/modules/plot-simple-sk/plot-simple-sk.ts
index a20029a..61b04e4 100644
--- a/perf/modules/plot-simple-sk/plot-simple-sk.ts
+++ b/perf/modules/plot-simple-sk/plot-simple-sk.ts
@@ -960,6 +960,7 @@
removeAll(): void {
this.lineData = [];
this.labels = [];
+ this.highlight = [];
this.hoverPt = {
x: -1,
y: -1,