perfetto-ui: Group together short thread states
- Select all long thread states and display everything else as 'Busy'.
- When zoomed enough, show all detail.
- When grouping states, show any rect with a width < 1px as 1px width.
https://taylori-dot-perfetto-ui.appspot.com/#!/?s=a85d21e477f26c1d5a9c3a20ef5c1296bcd645f9deac26305aba8f7895d32dc6
Bug:130830681
Change-Id: I13d770cb5daa556dcb15a6719c67ea3c3563d97b
diff --git a/ui/src/common/thread_state.ts b/ui/src/common/thread_state.ts
index 6008cf8..1215f4f 100644
--- a/ui/src/common/thread_state.ts
+++ b/ui/src/common/thread_state.ts
@@ -30,7 +30,9 @@
export function translateState(state: string|undefined) {
if (state === undefined) return '';
- if (state === 'Running' || state === 'Runnable') return state;
+ if (state === 'Running' || state === 'Runnable' || state === 'Busy') {
+ return state;
+ }
let result = states[state[0]];
for (let i = 1; i < state.length; i++) {
result += state[i] === '+' ? ' ' : ' + ';
diff --git a/ui/src/frontend/colorizer.ts b/ui/src/frontend/colorizer.ts
index 7522e7f..e42f42c 100644
--- a/ui/src/frontend/colorizer.ts
+++ b/ui/src/frontend/colorizer.ts
@@ -65,6 +65,7 @@
export function colorForState(state: string): Color {
switch (state) {
case 'Running':
+ case 'Busy':
return {c: 'dark green', h: 120, s: 44, l: 34};
case 'Runnable':
case 'R':
diff --git a/ui/src/tracks/thread_state/common.ts b/ui/src/tracks/thread_state/common.ts
index 32b32c4..fe6def0 100644
--- a/ui/src/tracks/thread_state/common.ts
+++ b/ui/src/tracks/thread_state/common.ts
@@ -26,3 +26,7 @@
}
export interface Config { utid: number; }
+
+export function groupBusyStates(resolution: number) {
+ return resolution >= 0.0001;
+}
diff --git a/ui/src/tracks/thread_state/controller.ts b/ui/src/tracks/thread_state/controller.ts
index 7b3e74e..c936ec5 100644
--- a/ui/src/tracks/thread_state/controller.ts
+++ b/ui/src/tracks/thread_state/controller.ts
@@ -21,6 +21,7 @@
import {
Config,
Data,
+ groupBusyStates,
THREAD_STATE_TRACK_KIND,
} from './common';
@@ -40,17 +41,23 @@
const startNs = Math.round(start * 1e9);
const endNs = Math.round(end * 1e9);
+ let minNs = 0;
+ if (groupBusyStates(resolution)) {
+ // Ns for 20px (the smallest state to display)
+ minNs = Math.round(resolution * 20 * 1e9);
+ }
+
if (this.setup === false) {
await this.query(`create view ${this.tableName('sched_wakeup')} AS
- select
- ts,
- lead(ts, 1, (select end_ts from trace_bounds))
- OVER(order by ts) - ts as dur,
- ref as utid
- from instants
- where name = 'sched_wakeup'
- and utid = ${this.config.utid}`);
+ select
+ ts,
+ lead(ts, 1, (select end_ts from trace_bounds))
+ OVER(order by ts) - ts as dur,
+ ref as utid
+ from instants
+ where name = 'sched_wakeup'
+ and utid = ${this.config.utid}`);
await this.query(
`create virtual table ${this.tableName('window')} using window;`);
@@ -60,14 +67,11 @@
await this.query(`create view ${this.tableName('start')} as
select min(ts) as ts from
(select ts from ${this.tableName('sched_wakeup')} UNION
- select ts from sched where utid = ${this.config.utid})`);
+ select ts from sched where utid = ${this.config.utid})`);
// Create an entry from first ts to either the first sched_wakeup
// or to the end if there are no sched wakeups. This means
// we will show all information we have even with no sched_wakeup events.
- // TODO(taylori): Once span outer join exists I should simplify this
- // by outer joining sched_wakeup and sched and then left joining with
- // window.
await this.query(`create view ${this.tableName('fill')} AS
select
(select ts from ${this.tableName('start')}),
@@ -103,23 +107,56 @@
where utid = ${this.config.utid}
window ${this.tableName('ordered')} as (order by ts)`);
+ await this.query(`create view ${this.tableName('long_states')} as
+ select * from ${this.tableName('span_view')} where dur >= ${minNs}`);
+
+ // Create a slice from the first ts to the end of the trace. To
+ // be span joined with the long states - This effectively combines all
+ // of the short states into a single 'Busy' state.
+ await this.query(`create view ${this.tableName('fill_gaps')} as select
+ (select min(ts) from ${this.tableName('span_view')}) as ts,
+ (select end_ts from trace_bounds) -
+ (select min(ts) from ${this.tableName('span_view')}) as dur,
+ ${this.config.utid} as utid`);
+
+ await this.query(`create virtual table ${this.tableName('summarized')}
+ using span_left_join(${this.tableName('fill_gaps')} partitioned utid,
+ ${this.tableName('long_states')} partitioned utid)`);
+
await this.query(`create virtual table ${this.tableName('current')}
- using span_join(
- ${this.tableName('window')},
- ${this.tableName('span_view')} partitioned utid)`);
+ using span_join(
+ ${this.tableName('window')},
+ ${this.tableName('summarized')} partitioned utid)`);
+
this.setup = true;
}
- const windowDur = Math.max(1, endNs - startNs);
+ const windowDurNs = Math.max(1, endNs - startNs);
this.query(`update ${this.tableName('window')} set
- window_start = ${startNs},
- window_dur = ${windowDur},
- quantum = 0`);
+ window_start=${startNs},
+ window_dur=${windowDurNs},
+ quantum=0`);
+
+ this.query(`drop view if exists ${this.tableName('long_states')}`);
+ this.query(`drop view if exists ${this.tableName('fill_gaps')}`);
+
+ await this.query(`create view ${this.tableName('long_states')} as
+ select * from ${this.tableName('span_view')} where dur > ${minNs}`);
+
+ await this.query(`create view ${this.tableName('fill_gaps')} as select
+ (select min(ts) from ${this.tableName('span_view')}) as ts,
+ (select end_ts from trace_bounds) - (select min(ts) from ${
+ this.tableName(
+ 'span_view')
+ }) as dur,
+ ${this.config.utid} as utid`);
const query = `select ts, cast(dur as double), utid,
- state from ${this.tableName('current')}`;
+ case when state is not null then state else 'Busy' end as state
+ from ${this.tableName('current')}`;
+
const result = await this.query(query);
@@ -171,10 +208,13 @@
this.query(`drop table ${this.tableName('window')}`);
this.query(`drop table ${this.tableName('span')}`);
this.query(`drop table ${this.tableName('current')}`);
+ this.query(`drop table ${this.tableName('summarized')}`);
this.query(`drop view ${this.tableName('sched_wakeup')}`);
this.query(`drop view ${this.tableName('fill')}`);
this.query(`drop view ${this.tableName('full_sched_wakeup')}`);
this.query(`drop view ${this.tableName('span_view')}`);
+ this.query(`drop view ${this.tableName('long_states')}`);
+ this.query(`drop view ${this.tableName('fill_gaps')}`);
this.setup = false;
}
}
diff --git a/ui/src/tracks/thread_state/frontend.ts b/ui/src/tracks/thread_state/frontend.ts
index 78284e4..bed5937 100644
--- a/ui/src/tracks/thread_state/frontend.ts
+++ b/ui/src/tracks/thread_state/frontend.ts
@@ -26,6 +26,7 @@
import {
Config,
Data,
+ groupBusyStates,
THREAD_STATE_TRACK_KIND,
} from './common';
@@ -76,7 +77,10 @@
const rectEnd = timeScale.timeToPx(tEnd);
const color = colorForState(state);
ctx.fillStyle = `hsl(${color.h},${color.s}%,${color.l}%)`;
- const rectWidth = rectEnd - rectStart;
+ let rectWidth = rectEnd - rectStart;
+ if (groupBusyStates(data.resolution) && rectWidth < 1) {
+ rectWidth = 1;
+ }
ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
// Don't render text when we have less than 5px to play with.