perfetto-ui: Make aggregation controller a base class
Aggregation controllers now work similarly to the track controllers
so that each type of aggregation can be kept separate and work in
a similar way.
Change-Id: I1350517df372e5a86b95769a2ba4162144171c9e
diff --git a/ui/src/controller/aggregation_controller.ts b/ui/src/controller/aggregation_controller.ts
index 526efda..7bb77be 100644
--- a/ui/src/controller/aggregation_controller.ts
+++ b/ui/src/controller/aggregation_controller.ts
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {AggregateData} from '../common/aggregation_data';
+import {AggregateData} from 'src/common/aggregation_data';
+
import {Engine} from '../common/engine';
import {TimestampedAreaSelection} from '../common/state';
-import {toNs} from '../common/time';
import {Controller} from './controller';
import {globals} from './globals';
@@ -24,20 +24,24 @@
engine: Engine;
}
-export class AggregationController extends Controller<'main'> {
+export abstract class AggregationController extends Controller<'main'> {
private previousArea: TimestampedAreaSelection = {lastUpdate: 0};
private requestingData = false;
private queuedRequest = false;
+
+ // Must be overridden by the aggregation implementation. It is invoked
+ // whenever the selected area is changed and returns data to be published.
+ abstract async onAreaSelectionChange(
+ engine: Engine, area: TimestampedAreaSelection): Promise<AggregateData>;
+
constructor(private args: AggregationControllerArgs) {
super('main');
}
run() {
const selectedArea = globals.state.frontendLocalState.selectedArea;
- const area = selectedArea.area;
- if (!area ||
- this.previousArea &&
- this.previousArea.lastUpdate >= selectedArea.lastUpdate) {
+ if (this.previousArea &&
+ this.previousArea.lastUpdate >= selectedArea.lastUpdate) {
return;
}
if (this.requestingData) {
@@ -45,113 +49,18 @@
} else {
this.requestingData = true;
Object.assign(this.previousArea, selectedArea);
-
- this.args.engine.getCpus().then(cpusInTrace => {
- 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,
- 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
- thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
- thread_state.ts < ${toNs(area.endSec)}
- GROUP BY utid ORDER BY total_dur DESC`;
-
- this.args.engine.query(query)
- .then(result => {
- if (globals.state.frontendLocalState.selectedArea.lastUpdate >
- selectedArea.lastUpdate) {
- return;
- }
-
- const numRows = +result.numRecords;
- const aggregateData: AggregateData = {
- columns: [
- {
- title: 'Process',
- kind: 'STRING',
- data: new Uint16Array(numRows)
- },
- {
- 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: 'Wall duration (ms)',
- kind: 'TIMESTAMP_NS',
- data: new Float64Array(numRows)
- },
- {
- title: 'Avg Wall duration (ms)',
- kind: 'TIMESTAMP_NS',
- data: new Float64Array(numRows)
- },
- {
- title: 'Occurrences',
- kind: 'NUMBER',
- data: new Uint16Array(numRows)
- }
- ],
- strings: [],
- };
-
- const stringIndexes = new Map<string, number>();
- function internString(str: string) {
- let idx = stringIndexes.get(str);
- if (idx !== undefined) return idx;
- idx = aggregateData.strings.length;
- aggregateData.strings.push(str);
- stringIndexes.set(str, idx);
- return idx;
- }
-
- for (let row = 0; row < numRows; row++) {
- const cols = result.columns;
- aggregateData.columns[0].data[row] =
- internString(cols[0].stringValues![row]);
- aggregateData.columns[1].data[row] =
- cols[1].longValues![row] as number;
- 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[5].data[row] =
- cols[5].longValues![row] as number;
- aggregateData.columns[6].data[row] =
- cols[6].longValues![row] as number;
- }
- globals.publish('AggregateCpuData', aggregateData);
- })
- .catch(reason => {
- console.error(reason);
- })
- .finally(() => {
- this.requestingData = false;
- if (this.queuedRequest) {
- this.queuedRequest = false;
- this.run();
- }
- });
- });
+ this.onAreaSelectionChange(this.args.engine, selectedArea)
+ .then(data => globals.publish('AggregateData', data))
+ .catch(reason => {
+ console.error(reason);
+ })
+ .finally(() => {
+ this.requestingData = false;
+ if (this.queuedRequest) {
+ this.queuedRequest = false;
+ this.run();
+ }
+ });
}
}
}
\ No newline at end of file
diff --git a/ui/src/controller/cpu_aggregation_controller.ts b/ui/src/controller/cpu_aggregation_controller.ts
new file mode 100644
index 0000000..167e536
--- /dev/null
+++ b/ui/src/controller/cpu_aggregation_controller.ts
@@ -0,0 +1,95 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// 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 {AggregationController} from './aggregation_controller';
+
+export class CpuAggregationController extends AggregationController {
+ async onAreaSelectionChange(
+ engine: Engine, selectedArea: TimestampedAreaSelection) {
+ const area = selectedArea.area;
+ if (area === undefined) {
+ return {columns: [], strings: []};
+ }
+
+ 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,
+ 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
+ thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
+ thread_state.ts < ${toNs(area.endSec)}
+ GROUP BY utid ORDER BY total_dur DESC`;
+
+ const result = await engine.query(query);
+
+ const numRows = +result.numRecords;
+ const aggregateData: AggregateData = {
+ columns: [
+ {title: 'Process', kind: 'STRING', data: new Uint16Array(numRows)},
+ {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: 'Wall duration (ms)',
+ kind: 'TIMESTAMP_NS',
+ data: new Float64Array(numRows)
+ },
+ {
+ title: 'Avg Wall duration (ms)',
+ kind: 'TIMESTAMP_NS',
+ data: new Float64Array(numRows)
+ },
+ {title: 'Occurrences', kind: 'NUMBER', data: new Uint16Array(numRows)}
+ ],
+ strings: [],
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = aggregateData.strings.length;
+ aggregateData.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ for (let row = 0; row < numRows; row++) {
+ const cols = result.columns;
+ aggregateData.columns[0].data[row] =
+ internString(cols[0].stringValues![row]);
+ aggregateData.columns[1].data[row] = cols[1].longValues![row] as number;
+ 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[5].data[row] = cols[5].longValues![row] as number;
+ aggregateData.columns[6].data[row] = cols[6].longValues![row] as number;
+ }
+ return aggregateData;
+ }
+}
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index e9d3652..7e99ab6 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -23,7 +23,7 @@
type PublishKinds = 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|
'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapProfileDetails'|
'HeapProfileFlamegraph'|'FileDownload'|'Loading'|'Search'|'BufferUsage'|
- 'RecordingLog'|'SearchResult'|'AggregateCpuData';
+ 'RecordingLog'|'SearchResult'|'AggregateData';
export interface App {
state: State;
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 327a411..9865405 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -49,8 +49,8 @@
import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
-import {AggregationController} from './aggregation_controller';
import {Child, Children, Controller} from './controller';
+import {CpuAggregationController} from './cpu_aggregation_controller';
import {globals} from './globals';
import {
HeapProfileController,
@@ -155,7 +155,7 @@
childControllers.push(
Child('heapProfile', HeapProfileController, heapProfileArgs));
childControllers.push(
- Child('aggregation', AggregationController, {engine}));
+ Child('aggregation', CpuAggregationController, {engine}));
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 692f3c0..f0ea1f2 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -24,9 +24,8 @@
}
export class AggregationPanel extends Panel<AggregationPanelAttrs> {
- view({attrs}: m.CVnode<AggregationPanelAttrs>) {
+ view() {
// In the future we will get different data based on the kind.
- if (attrs.kind !== 'CPU') return;
const data = globals.aggregateCpuData;
return m(
'.details-panel',
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 4a283c0..a9590fe 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -213,7 +213,8 @@
detailsPanels.set('android_logs', m(LogPanel, {}));
}
- if (globals.frontendLocalState.selectedArea.area !== undefined) {
+ if (globals.aggregateCpuData.columns.length > 0 &&
+ globals.aggregateCpuData.columns[0].data.length > 0) {
detailsPanels.set('cpu_slices', m(AggregationPanel, {kind: 'CPU'}));
}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 0aac9a3..b8bbe83 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -175,7 +175,7 @@
this.redraw();
}
- publishAggregateCpuData(args: AggregateData) {
+ publishAggregateData(args: AggregateData) {
globals.aggregateCpuData = args;
this.redraw();
}
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 9ba8637..3a252fb 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -110,10 +110,10 @@
this.prevAreaSelection.lastUpdate >= selection.lastUpdate) ||
area === undefined ||
globals.frontendLocalState.areaY.start === undefined ||
- globals.frontendLocalState.areaY.end === undefined) {
+ globals.frontendLocalState.areaY.end === undefined ||
+ this.panelPositions.length === 0) {
return;
}
-
// Only get panels from the current panel container if the selection began
// in this container.
const panelContainerTop = this.panelPositions[0].y;