perfetto-ui: Add thread state aggregation
Bug:118899922
Change-Id: Ie224e1d2e935e92fe8590de563587729dc8f2ea2
diff --git a/ui/src/common/aggregation_data.ts b/ui/src/common/aggregation_data.ts
index e7438e7..f4f3d74 100644
--- a/ui/src/common/aggregation_data.ts
+++ b/ui/src/common/aggregation_data.ts
@@ -34,4 +34,4 @@
columns: Column[];
// For string interning.
strings: string[];
-}
+}
\ No newline at end of file
diff --git a/ui/src/controller/aggregation_controller.ts b/ui/src/controller/aggregation/aggregation_controller.ts
similarity index 83%
rename from ui/src/controller/aggregation_controller.ts
rename to ui/src/controller/aggregation/aggregation_controller.ts
index 7bb77be..5c41231 100644
--- a/ui/src/controller/aggregation_controller.ts
+++ b/ui/src/controller/aggregation/aggregation_controller.ts
@@ -12,16 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {AggregateData} from 'src/common/aggregation_data';
-import {Engine} from '../common/engine';
-import {TimestampedAreaSelection} from '../common/state';
+import {AggregateData} from '../../common/aggregation_data';
-import {Controller} from './controller';
-import {globals} from './globals';
+import {Engine} from '../../common/engine';
+import {TimestampedAreaSelection} from '../../common/state';
+
+import {Controller} from '../controller';
+import {globals} from '../globals';
export interface AggregationControllerArgs {
engine: Engine;
+ kind: string;
}
export abstract class AggregationController extends Controller<'main'> {
@@ -50,7 +52,9 @@
this.requestingData = true;
Object.assign(this.previousArea, selectedArea);
this.onAreaSelectionChange(this.args.engine, selectedArea)
- .then(data => globals.publish('AggregateData', data))
+ .then(
+ data => globals.publish(
+ 'AggregateData', {data, kind: this.args.kind}))
.catch(reason => {
console.error(reason);
})
diff --git a/ui/src/controller/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
similarity index 94%
rename from ui/src/controller/cpu_aggregation_controller.ts
rename to ui/src/controller/aggregation/cpu_aggregation_controller.ts
index 167e536..6ca135a 100644
--- a/ui/src/controller/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {AggregateData} from '../common/aggregation_data';
-import {Engine} from '../common/engine';
-import {TimestampedAreaSelection} from '../common/state';
-import {toNs} from '../common/time';
-
+import {AggregateData} from '../../common/aggregation_data';
+import {Engine} from '../../common/engine';
+import {TimestampedAreaSelection} from '../../common/state';
+import {toNs} from '../../common/time';
import {AggregationController} from './aggregation_controller';
export class CpuAggregationController extends AggregationController {
diff --git a/ui/src/controller/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
similarity index 70%
copy from ui/src/controller/cpu_aggregation_controller.ts
copy to ui/src/controller/aggregation/thread_aggregation_controller.ts
index 167e536..f522b8c 100644
--- a/ui/src/controller/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -12,37 +12,49 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {AggregateData} from '../common/aggregation_data';
-import {Engine} from '../common/engine';
-import {TimestampedAreaSelection} from '../common/state';
-import {toNs} from '../common/time';
+import {AggregateData} from '../../common/aggregation_data';
+import {Engine} from '../../common/engine';
+import {TimestampedAreaSelection} from '../../common/state';
+import {translateState} from '../../common/thread_state';
+import {toNs} from '../../common/time';
+import {
+ Config,
+ THREAD_STATE_TRACK_KIND
+} from '../../tracks/thread_state/common';
+import {globals} from '../globals';
import {AggregationController} from './aggregation_controller';
-export class CpuAggregationController extends AggregationController {
+
+export class ThreadAggregationController extends AggregationController {
async onAreaSelectionChange(
engine: Engine, selectedArea: TimestampedAreaSelection) {
const area = selectedArea.area;
if (area === undefined) {
return {columns: [], strings: []};
}
+ // TODO(taylori): Thread state tracks should have a real track id in the
+ // trace processor.
+ const utids = [];
+ for (const trackId of area.tracks) {
+ const track = globals.state.tracks[trackId];
+ if (track.kind === THREAD_STATE_TRACK_KIND) {
+ utids.push((track.config as Config).utid);
+ }
+ }
- const cpusInTrace = await engine.getCpus();
- const selectedCpuTracks =
- cpusInTrace.filter(x => area.tracks.includes((x + 1).toString()));
-
- const query =
- `SELECT process.name, pid, thread.name, tid, sum(dur) AS total_dur,
+ const query = `SELECT process.name, pid, thread.name, tid,
+ state,
+ sum(dur) AS total_dur,
sum(dur)/count(1) as avg_dur,
count(1) as occurences
FROM process
JOIN thread USING(upid)
JOIN thread_state USING(utid)
- WHERE cpu IN (${selectedCpuTracks}) AND
- state = "Running" AND
+ WHERE utid IN (${utids}) AND
thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
thread_state.ts < ${toNs(area.endSec)}
- GROUP BY utid ORDER BY total_dur DESC`;
+ GROUP BY utid, state ORDER BY total_dur DESC`;
const result = await engine.query(query);
@@ -53,6 +65,7 @@
{title: 'PID', kind: 'NUMBER', data: new Uint16Array(numRows)},
{title: 'Thread', kind: 'STRING', data: new Uint16Array(numRows)},
{title: 'TID', kind: 'NUMBER', data: new Uint16Array(numRows)},
+ {title: 'State', kind: 'STRING', data: new Uint16Array(numRows)},
{
title: 'Wall duration (ms)',
kind: 'TIMESTAMP_NS',
@@ -86,9 +99,11 @@
aggregateData.columns[2].data[row] =
internString(cols[2].stringValues![row]);
aggregateData.columns[3].data[row] = cols[3].longValues![row] as number;
- aggregateData.columns[4].data[row] = cols[4].longValues![row] as number;
+ aggregateData.columns[4].data[row] =
+ internString(translateState(cols[4].stringValues![row]));
aggregateData.columns[5].data[row] = cols[5].longValues![row] as number;
aggregateData.columns[6].data[row] = cols[6].longValues![row] as number;
+ aggregateData.columns[7].data[row] = cols[7].longValues![row] as number;
}
return aggregateData;
}
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 9865405..5f112d7 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -49,8 +49,13 @@
import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
+import {
+ CpuAggregationController
+} from './aggregation/cpu_aggregation_controller';
+import {
+ ThreadAggregationController
+} from './aggregation/thread_aggregation_controller';
import {Child, Children, Controller} from './controller';
-import {CpuAggregationController} from './cpu_aggregation_controller';
import {globals} from './globals';
import {
HeapProfileController,
@@ -154,8 +159,14 @@
const heapProfileArgs: HeapProfileControllerArgs = {engine};
childControllers.push(
Child('heapProfile', HeapProfileController, heapProfileArgs));
- childControllers.push(
- Child('aggregation', CpuAggregationController, {engine}));
+ childControllers.push(Child(
+ 'cpu_aggregation',
+ CpuAggregationController,
+ {engine, kind: 'cpu'}));
+ childControllers.push(Child(
+ 'thread_aggregation',
+ ThreadAggregationController,
+ {engine, kind: 'thread_state'}));
childControllers.push(Child('search', SearchController, {
engine,
app: globals,
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index f0ea1f2..ba2347d 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -15,25 +15,22 @@
import * as m from 'mithril';
import {AggregateData} from '../common/aggregation_data';
-
-import {globals} from './globals';
import {Panel} from './panel';
export interface AggregationPanelAttrs {
- kind: 'CPU';
+ data: AggregateData;
}
export class AggregationPanel extends Panel<AggregationPanelAttrs> {
- view() {
- // In the future we will get different data based on the kind.
- const data = globals.aggregateCpuData;
+ view({attrs}: m.CVnode<AggregationPanelAttrs>) {
return m(
'.details-panel',
m('.details-panel-heading.aggregation',
- m('table', m('tr', data.columns.map(col => (m('th', col.title)))))),
+ m('table',
+ m('tr', attrs.data.columns.map(col => (m('th', col.title)))))),
m(
'.details-table.aggregation',
- m('table', this.getRows(data)),
+ m('table', this.getRows(attrs.data)),
));
}
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index a9590fe..8501d73 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -51,11 +51,9 @@
interface DragHandleAttrs {
height: number;
resize: (height: number) => void;
- tabs: Tab[];
+ tabs: string[];
}
-export type Tab = 'current_selection'|'cpu_slices'|'android_logs';
-
class DragHandle implements m.ClassComponent<DragHandleAttrs> {
private dragStartHeight = 0;
private height = 0;
@@ -65,10 +63,11 @@
private isFullscreen = false;
// We can't get real fullscreen height until the pan_and_zoom_handler exists.
private fullscreenHeight = DEFAULT_DETAILS_HEIGHT_PX;
- private tabNames = new Map<Tab, string>([
+ private tabNames = new Map<string, string>([
['current_selection', 'Current Selection'],
- ['cpu_slices', 'CPU Slices'],
- ['android_logs', 'Android Logs']
+ ['cpu', 'CPU Slices'],
+ ['android_logs', 'Android Logs'],
+ ['thread_state', 'Thread States']
]);
@@ -109,11 +108,14 @@
view({attrs}: m.CVnode<DragHandleAttrs>) {
const icon = this.isClosed ? UP_ICON : DOWN_ICON;
const title = this.isClosed ? 'Show panel' : 'Hide panel';
- const renderTab = (key: Tab) => {
+ const renderTab = (key: string) => {
if (globals.frontendLocalState.currentTab === key ||
globals.frontendLocalState.currentTab === undefined &&
attrs.tabs[0] === key) {
- return m('.tab[active]', this.tabNames.get(key));
+ return m(
+ '.tab[active]',
+ this.tabNames.get(key) === undefined ? key :
+ this.tabNames.get(key));
}
return m(
'.tab',
@@ -167,7 +169,7 @@
private showDetailsPanel = true;
view() {
- const detailsPanels: Map<Tab, AnyAttrsVnode> = new Map();
+ const detailsPanels: Map<string, AnyAttrsVnode> = new Map();
const curSelection = globals.state.currentSelection;
if (curSelection) {
switch (curSelection.kind) {
@@ -213,9 +215,10 @@
detailsPanels.set('android_logs', m(LogPanel, {}));
}
- if (globals.aggregateCpuData.columns.length > 0 &&
- globals.aggregateCpuData.columns[0].data.length > 0) {
- detailsPanels.set('cpu_slices', m(AggregationPanel, {kind: 'CPU'}));
+ for (const [key, value] of globals.aggregateDataStore.entries()) {
+ if (value.columns.length > 0 && value.columns[0].data.length > 0) {
+ detailsPanels.set(key, m(AggregationPanel, {data: value}));
+ }
}
const wasShowing = this.showDetailsPanel;
@@ -225,7 +228,8 @@
this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
}
- const panel = globals.frontendLocalState.currentTab ?
+ const panel = globals.frontendLocalState.currentTab &&
+ detailsPanels.has(globals.frontendLocalState.currentTab) ?
detailsPanels.get(globals.frontendLocalState.currentTab) :
detailsPanels.values().next().value;
const panels = panel ? [panel] : [];
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index a966a45..f66b500 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -24,7 +24,6 @@
import {TimeSpan} from '../common/time';
import {randomColor} from './colorizer';
-import {Tab} from './details_panel';
import {globals} from './globals';
import {TimeScale} from './time_scale';
@@ -103,7 +102,7 @@
visibleTracks = new Set<string>();
prevVisibleTracks = new Set<string>();
searchIndex = -1;
- currentTab?: Tab;
+ currentTab?: string;
scrollToTrackId?: string|number;
httpRpcState: HttpRpcState = {connected: false};
newVersionAvailable = false;
@@ -234,7 +233,6 @@
lastUpdate: Date.now() / 1000
};
this.selectAreaDebounced();
- globals.frontendLocalState.currentTab = 'cpu_slices';
globals.rafScheduler.scheduleFullRedraw();
}
@@ -284,7 +282,6 @@
}));
}
- globals.frontendLocalState.currentTab = 'cpu_slices';
globals.rafScheduler.scheduleFullRedraw();
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 10db5be..4ac3260 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -25,6 +25,7 @@
type Dispatch = (action: DeferredAction) => void;
type TrackDataStore = Map<string, {}>;
type QueryResultsStore = Map<string, {}>;
+type AggregateDataStore = Map<string, AggregateData>;
type Args = Map<string, string>;
export interface SliceDetails {
ts?: number;
@@ -94,6 +95,7 @@
private _trackDataStore?: TrackDataStore = undefined;
private _queryResults?: QueryResultsStore = undefined;
private _overviewStore?: OverviewStore = undefined;
+ private _aggregateDataStore?: AggregateDataStore = undefined;
private _threadMap?: ThreadMap = undefined;
private _sliceDetails?: SliceDetails = undefined;
private _counterDetails?: CounterDetails = undefined;
@@ -101,10 +103,7 @@
private _numQueriesQueued = 0;
private _bufferUsage?: number = undefined;
private _recordingLog?: string = undefined;
- private _aggregateCpuData: AggregateData = {
- strings: [],
- columns: [],
- };
+
private _currentSearchResults: CurrentSearchResults = {
sliceIds: new Float64Array(0),
tsStarts: new Float64Array(0),
@@ -131,6 +130,7 @@
this._trackDataStore = new Map<string, {}>();
this._queryResults = new Map<string, {}>();
this._overviewStore = new Map<string, QuantizedLoad[]>();
+ this._aggregateDataStore = new Map<string, AggregateData>();
this._threadMap = new Map<number, ThreadDesc>();
this._sliceDetails = {};
this._counterDetails = {};
@@ -194,12 +194,8 @@
this._counterDetails = assertExists(click);
}
- get aggregateCpuData(): AggregateData {
- return assertExists(this._aggregateCpuData);
- }
-
- set aggregateCpuData(value: AggregateData) {
- this._aggregateCpuData = value;
+ get aggregateDataStore(): AggregateDataStore {
+ return assertExists(this._aggregateDataStore);
}
get heapProfileDetails() {
@@ -246,6 +242,10 @@
this._recordingLog = recordingLog;
}
+ setAggregateData(kind: string, data: AggregateData) {
+ this.aggregateDataStore.set(kind, data);
+ }
+
getCurResolution() {
// Truncate the resolution to the closest power of 2.
// This effectively means the resolution changes every 6 zoom levels.
@@ -274,6 +274,7 @@
this._overviewStore = undefined;
this._threadMap = undefined;
this._sliceDetails = undefined;
+ this._aggregateDataStore = undefined;
this._numQueriesQueued = 0;
this._currentSearchResults = {
sliceIds: new Float64Array(0),
@@ -283,10 +284,6 @@
sources: [],
totalResults: 0,
};
- this._aggregateCpuData = {
- strings: [],
- columns: [],
- };
}
// Used when switching to the legacy TraceViewer UI.
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index b8bbe83..7823f28 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -175,8 +175,8 @@
this.redraw();
}
- publishAggregateData(args: AggregateData) {
- globals.aggregateCpuData = args;
+ publishAggregateData(args: {data: AggregateData, kind: string}) {
+ globals.setAggregateData(args.kind, args.data);
this.redraw();
}