Merge "Add Microsoft press link"
diff --git a/src/perfetto_cmd/BUILD.gn b/src/perfetto_cmd/BUILD.gn
index d402e1f..70c47fc 100644
--- a/src/perfetto_cmd/BUILD.gn
+++ b/src/perfetto_cmd/BUILD.gn
@@ -55,6 +55,7 @@
     "../../protos/perfetto/common:cpp",
     "../../protos/perfetto/config:cpp",
     "../../protos/perfetto/config/ftrace:cpp",
+    "../../protos/perfetto/config/sys_stats:cpp",
     "../android_stats",
     "../base",
     "../base:version",
diff --git a/src/perfetto_cmd/config.cc b/src/perfetto_cmd/config.cc
index 56a8f06..1313dcf 100644
--- a/src/perfetto_cmd/config.cc
+++ b/src/perfetto_cmd/config.cc
@@ -24,6 +24,7 @@
 #include "perfetto/tracing/core/trace_config.h"
 
 #include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
+#include "protos/perfetto/config/sys_stats/sys_stats_config.gen.h"
 
 namespace perfetto {
 namespace {
@@ -132,6 +133,18 @@
       frame_timeline->mutable_config()->set_name(
           "android.surfaceflinger.frametimeline");
     }
+
+    // For the disk category, add the diskstat data source
+    // to figure out disk io statistics.
+    if (category == "disk") {
+      protos::gen::SysStatsConfig cfg;
+      cfg.set_diskstat_period_ms(1000);
+
+      auto* sys_stats_ds = config->add_data_sources();
+      sys_stats_ds->mutable_config()->set_name("linux.sys_stats");
+      sys_stats_ds->mutable_config()->set_sys_stats_config_raw(
+          cfg.SerializeAsString());
+    }
   }
 
   config->set_duration_ms(static_cast<unsigned int>(duration_ms));
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 5470643..ccf4776 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -792,7 +792,7 @@
   user-select: none;
 }
 
-.pivot-table-redux {
+.pivot-table {
   user-select: text;
   padding: 10px;
 
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 880b11d..32568d3 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -29,3 +29,4 @@
 @import "widgets/button";
 @import "widgets/checkbox";
 @import "widgets/text_input";
+@import "widgets/empty_state";
diff --git a/ui/src/assets/widgets/empty_state.scss b/ui/src/assets/widgets/empty_state.scss
new file mode 100644
index 0000000..46ab9bc
--- /dev/null
+++ b/ui/src/assets/widgets/empty_state.scss
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 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 "theme";
+
+.pf-empty-state {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 10px;
+  user-select: none;
+
+  & > i {
+    margin: auto;
+    font-size: 5em; // Size of the icon is relative to the font size
+    color: $pf-minimal-foreground;
+    margin-bottom: 10px;
+  }
+
+  .pf-empty-state-header {
+    margin-bottom: 10px;
+    color: $pf-minimal-foreground;
+  }
+
+  .pf-empty-state-detail {
+    margin: auto;
+  }
+}
diff --git a/ui/src/assets/widgets_page.scss b/ui/src/assets/widgets_page.scss
index 653c6a0..beff40c 100644
--- a/ui/src/assets/widgets_page.scss
+++ b/ui/src/assets/widgets_page.scss
@@ -15,6 +15,7 @@
 .widgets-page {
   padding: 20px;
   font-size: 16px;
+  overflow: auto;
 
   h1 {
     margin: 32px 0 0 0;
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 2909c73..38e9d44 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -23,7 +23,7 @@
   TableColumn,
   tableColumnEquals,
   toggleEnabled,
-} from '../frontend/pivot_table_redux_types';
+} from '../frontend/pivot_table_types';
 
 import {randomColor} from './colorizer';
 import {
@@ -44,7 +44,7 @@
   LogsPagination,
   NewEngineMode,
   OmniboxState,
-  PivotTableReduxResult,
+  PivotTableResult,
   PrimaryTrackSortKey,
   ProfileType,
   RecordingTarget,
@@ -1031,25 +1031,23 @@
     }
   },
 
-  togglePivotTableRedux(state: StateDraft, args: {areaId: string|null}) {
-    state.nonSerializableState.pivotTableRedux.selectionArea =
-        args.areaId === null ?
+  togglePivotTable(state: StateDraft, args: {areaId: string|null}) {
+    state.nonSerializableState.pivotTable.selectionArea = args.areaId === null ?
         undefined :
         {areaId: args.areaId, tracks: globals.state.areas[args.areaId].tracks};
     if (args.areaId !==
-        state.nonSerializableState.pivotTableRedux.selectionArea?.areaId) {
-      state.nonSerializableState.pivotTableRedux.queryResult = null;
+        state.nonSerializableState.pivotTable.selectionArea?.areaId) {
+      state.nonSerializableState.pivotTable.queryResult = null;
     }
   },
 
   setPivotStateQueryResult(
-      state: StateDraft, args: {queryResult: PivotTableReduxResult|null}) {
-    state.nonSerializableState.pivotTableRedux.queryResult = args.queryResult;
+      state: StateDraft, args: {queryResult: PivotTableResult|null}) {
+    state.nonSerializableState.pivotTable.queryResult = args.queryResult;
   },
 
-  setPivotTableReduxConstrainToArea(
-      state: StateDraft, args: {constrain: boolean}) {
-    state.nonSerializableState.pivotTableRedux.constrainToArea = args.constrain;
+  setPivotTableConstrainToArea(state: StateDraft, args: {constrain: boolean}) {
+    state.nonSerializableState.pivotTable.constrainToArea = args.constrain;
   },
 
   dismissFlamegraphModal(state: StateDraft, _: {}) {
@@ -1058,41 +1056,40 @@
 
   addPivotTableAggregation(
       state: StateDraft, args: {aggregation: Aggregation, after: number}) {
-    state.nonSerializableState.pivotTableRedux.selectedAggregations.splice(
+    state.nonSerializableState.pivotTable.selectedAggregations.splice(
         args.after, 0, args.aggregation);
   },
 
   removePivotTableAggregation(state: StateDraft, args: {index: number}) {
-    state.nonSerializableState.pivotTableRedux.selectedAggregations.splice(
+    state.nonSerializableState.pivotTable.selectedAggregations.splice(
         args.index, 1);
   },
 
   setPivotTableQueryRequested(
       state: StateDraft, args: {queryRequested: boolean}) {
-    state.nonSerializableState.pivotTableRedux.queryRequested =
-        args.queryRequested;
+    state.nonSerializableState.pivotTable.queryRequested = args.queryRequested;
   },
 
   setPivotTablePivotSelected(
       state: StateDraft, args: {column: TableColumn, selected: boolean}) {
     toggleEnabled(
         tableColumnEquals,
-        state.nonSerializableState.pivotTableRedux.selectedPivots,
+        state.nonSerializableState.pivotTable.selectedPivots,
         args.column,
         args.selected);
   },
 
   setPivotTableAggregationFunction(
       state: StateDraft, args: {index: number, function: AggregationFunction}) {
-    state.nonSerializableState.pivotTableRedux.selectedAggregations[args.index]
+    state.nonSerializableState.pivotTable.selectedAggregations[args.index]
         .aggregationFunction = args.function;
   },
 
   setPivotTableSortColumn(
       state: StateDraft,
       args: {aggregationIndex: number, order: SortDirection}) {
-    state.nonSerializableState.pivotTableRedux.selectedAggregations =
-        state.nonSerializableState.pivotTableRedux.selectedAggregations.map(
+    state.nonSerializableState.pivotTable.selectedAggregations =
+        state.nonSerializableState.pivotTable.selectedAggregations.map(
             (agg, index) => ({
               column: agg.column,
               aggregationFunction: agg.aggregationFunction,
@@ -1114,26 +1111,24 @@
 
   setPivotTableArgumentNames(
       state: StateDraft, args: {argumentNames: string[]}) {
-    state.nonSerializableState.pivotTableRedux.argumentNames =
-        args.argumentNames;
+    state.nonSerializableState.pivotTable.argumentNames = args.argumentNames;
   },
 
   changePivotTablePivotOrder(
       state: StateDraft,
       args: {from: number, to: number, direction: DropDirection}) {
-    const pivots = state.nonSerializableState.pivotTableRedux.selectedPivots;
-    state.nonSerializableState.pivotTableRedux.selectedPivots =
-        performReordering(
-            computeIntervals(pivots.length, args.from, args.to, args.direction),
-            pivots);
+    const pivots = state.nonSerializableState.pivotTable.selectedPivots;
+    state.nonSerializableState.pivotTable.selectedPivots = performReordering(
+        computeIntervals(pivots.length, args.from, args.to, args.direction),
+        pivots);
   },
 
   changePivotTableAggregationOrder(
       state: StateDraft,
       args: {from: number, to: number, direction: DropDirection}) {
     const aggregations =
-        state.nonSerializableState.pivotTableRedux.selectedAggregations;
-    state.nonSerializableState.pivotTableRedux.selectedAggregations =
+        state.nonSerializableState.pivotTable.selectedAggregations;
+    state.nonSerializableState.pivotTable.selectedAggregations =
         performReordering(
             computeIntervals(
                 aggregations.length, args.from, args.to, args.direction),
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index 72b7f8b..4267be2 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -15,7 +15,7 @@
 import {createEmptyRecordConfig} from '../controller/record_config_types';
 import {
   Aggregation,
-} from '../frontend/pivot_table_redux_types';
+} from '../frontend/pivot_table_types';
 import {
   autosaveConfigStore,
   recordTargetStore,
@@ -57,7 +57,7 @@
 
 export function createEmptyNonSerializableState(): NonSerializableState {
   return {
-    pivotTableRedux: {
+    pivotTable: {
       queryResult: null,
       selectedPivots: [{kind: 'regular', table: 'slice', column: 'name'}],
       selectedAggregations: [
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index b3b9aba..3b978b9 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -18,7 +18,7 @@
   PivotTree,
   RegularColumn,
   TableColumn,
-} from '../frontend/pivot_table_redux_types';
+} from '../frontend/pivot_table_types';
 
 /**
  * A plain js object, holding objects of type |Class| keyed by string id.
@@ -403,41 +403,41 @@
 // Auxiliary metadata needed to parse the query result, as well as to render it
 // correctly. Generated together with the text of query and passed without the
 // change to the query response.
-export interface PivotTableReduxQueryMetadata {
+export interface PivotTableQueryMetadata {
   pivotColumns: TableColumn[];
   aggregationColumns: Aggregation[];
   countIndex: number;
 }
 
 // Everything that's necessary to run the query for pivot table
-export interface PivotTableReduxQuery {
+export interface PivotTableQuery {
   text: string;
-  metadata: PivotTableReduxQueryMetadata;
+  metadata: PivotTableQueryMetadata;
 }
 
 // Pivot table query result
-export interface PivotTableReduxResult {
+export interface PivotTableResult {
   // Hierarchical pivot structure on top of rows
   tree: PivotTree;
   // Copy of the query metadata from the request, bundled up with the query
   // result to ensure the correct rendering.
-  metadata: PivotTableReduxQueryMetadata;
+  metadata: PivotTableQueryMetadata;
 }
 
 // Input parameters to check whether the pivot table needs to be re-queried.
-export interface PivotTableReduxAreaState {
+export interface PivotTableAreaState {
   areaId: string;
   tracks: string[];
 }
 
 export type SortDirection = 'DESC'|'ASC';
 
-export interface PivotTableReduxState {
+export interface PivotTableState {
   // Currently selected area, if null, pivot table is not going to be visible.
-  selectionArea?: PivotTableReduxAreaState;
+  selectionArea?: PivotTableAreaState;
 
   // Query response
-  queryResult: PivotTableReduxResult|null;
+  queryResult: PivotTableResult|null;
 
   // Selected pivots for tables other than slice.
   // Because of the query generation, pivoting happens first on non-slice
@@ -477,7 +477,7 @@
     LoadedConfigNone|LoadedConfigAutomatic|LoadedConfigNamed;
 
 export interface NonSerializableState {
-  pivotTableRedux: PivotTableReduxState;
+  pivotTable: PivotTableState;
 }
 
 export interface LogFilteringCriteria {
diff --git a/ui/src/controller/pivot_table_redux_controller.ts b/ui/src/controller/pivot_table_controller.ts
similarity index 90%
rename from ui/src/controller/pivot_table_redux_controller.ts
rename to ui/src/controller/pivot_table_controller.ts
index 6329e81..cc33e3e 100644
--- a/ui/src/controller/pivot_table_redux_controller.ts
+++ b/ui/src/controller/pivot_table_controller.ts
@@ -21,22 +21,22 @@
 import {ColumnType, STR} from '../common/query_result';
 import {
   AreaSelection,
-  PivotTableReduxQuery,
-  PivotTableReduxQueryMetadata,
-  PivotTableReduxResult,
-  PivotTableReduxState,
+  PivotTableQuery,
+  PivotTableQueryMetadata,
+  PivotTableResult,
+  PivotTableState,
 } from '../common/state';
 import {globals} from '../frontend/globals';
 import {
   aggregationIndex,
   generateQueryFromState,
-} from '../frontend/pivot_table_redux_query_generator';
-import {Aggregation, PivotTree} from '../frontend/pivot_table_redux_types';
+} from '../frontend/pivot_table_query_generator';
+import {Aggregation, PivotTree} from '../frontend/pivot_table_types';
 
 import {Controller} from './controller';
 
 export const PIVOT_TABLE_REDUX_FLAG = featureFlags.register({
-  id: 'pivotTableRedux',
+  id: 'pivotTable',
   name: 'Pivot tables V2',
   description: 'Second version of pivot table',
   // Enabled in canary and autopush by default.
@@ -53,7 +53,7 @@
 // Auxiliary class to build the tree from query response.
 export class PivotTableTreeBuilder {
   private readonly root: PivotTree;
-  queryMetadata: PivotTableReduxQueryMetadata;
+  queryMetadata: PivotTableQueryMetadata;
 
   get pivotColumnsCount(): number {
     return this.queryMetadata.pivotColumns.length;
@@ -63,8 +63,7 @@
     return this.queryMetadata.aggregationColumns;
   }
 
-  constructor(
-      queryMetadata: PivotTableReduxQueryMetadata, firstRow: ColumnType[]) {
+  constructor(queryMetadata: PivotTableQueryMetadata, firstRow: ColumnType[]) {
     this.queryMetadata = queryMetadata;
     this.root = this.createNode(firstRow);
     let tree = this.root;
@@ -163,8 +162,8 @@
   }
 }
 
-function createEmptyQueryResult(metadata: PivotTableReduxQueryMetadata):
-    PivotTableReduxResult {
+function createEmptyQueryResult(metadata: PivotTableQueryMetadata):
+    PivotTableResult {
   return {
     tree: {
       aggregates: [],
@@ -178,7 +177,7 @@
 
 // Controller responsible for showing the panel with pivot table, as well as
 // executing its queries and post-processing query results.
-export class PivotTableReduxController extends Controller<{}> {
+export class PivotTableController extends Controller<{}> {
   static detailsCount = 0;
   engine: Engine;
   lastQueryAreaId = '';
@@ -205,7 +204,7 @@
     return true;
   }
 
-  shouldRerun(state: PivotTableReduxState, selection: AreaSelection) {
+  shouldRerun(state: PivotTableState, selection: AreaSelection) {
     if (state.selectionArea === undefined) {
       return false;
     }
@@ -220,7 +219,7 @@
     return false;
   }
 
-  async processQuery(query: PivotTableReduxQuery) {
+  async processQuery(query: PivotTableQuery) {
     const result = await this.engine.query(query.text);
     try {
       await result.waitAllRows();
@@ -256,7 +255,7 @@
 
     globals.dispatch(Actions.setPivotStateQueryResult(
         {queryResult: {tree: treeBuilder.build(), metadata: query.metadata}}));
-    globals.dispatch(Actions.setCurrentTab({tab: 'pivot_table_redux'}));
+    globals.dispatch(Actions.setCurrentTab({tab: 'pivot_table'}));
   }
 
   async requestArgumentNames() {
@@ -285,7 +284,7 @@
       this.requestArgumentNames();
     }
 
-    const pivotTableState = globals.state.nonSerializableState.pivotTableRedux;
+    const pivotTableState = globals.state.nonSerializableState.pivotTable;
     const selection = globals.state.currentSelection;
 
     if (pivotTableState.queryRequested ||
@@ -301,8 +300,7 @@
     if (selection !== null && selection.kind === 'AREA' &&
         (pivotTableState.selectionArea === undefined ||
          pivotTableState.selectionArea.areaId !== selection.areaId)) {
-      globals.dispatch(
-          Actions.togglePivotTableRedux({areaId: selection.areaId}));
+      globals.dispatch(Actions.togglePivotTable({areaId: selection.areaId}));
     }
   }
 }
diff --git a/ui/src/controller/pivot_table_tree_builder_unittest.ts b/ui/src/controller/pivot_table_tree_builder_unittest.ts
index b2e5c7e..5ab93f9 100644
--- a/ui/src/controller/pivot_table_tree_builder_unittest.ts
+++ b/ui/src/controller/pivot_table_tree_builder_unittest.ts
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-import {PivotTableTreeBuilder} from './pivot_table_redux_controller';
+import {PivotTableTreeBuilder} from './pivot_table_controller';
 
 describe('Pivot Table tree builder', () => {
   test('aggregates averages correctly', () => {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 0e42c8f..7fd41ed 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -82,8 +82,8 @@
 import {MetricsController} from './metrics_controller';
 import {
   PIVOT_TABLE_REDUX_FLAG,
-  PivotTableReduxController,
-} from './pivot_table_redux_controller';
+  PivotTableController,
+} from './pivot_table_controller';
 import {QueryController, QueryControllerArgs} from './query_controller';
 import {SearchController} from './search_controller';
 import {
@@ -311,7 +311,7 @@
           app: globals,
         }));
         childControllers.push(
-          Child('pivot_table_redux', PivotTableReduxController, {engine}));
+            Child('pivot_table', PivotTableController, {engine}));
 
         childControllers.push(Child('logs', LogsController, {
           engine,
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index d195b7e..2c73385 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -36,7 +36,7 @@
 import {LogPanel} from './logs_panel';
 import {NotesEditorTab} from './notes_panel';
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
-import {PivotTableRedux} from './pivot_table_redux';
+import {PivotTable} from './pivot_table';
 import {QueryTable} from './query_table';
 import {SliceDetailsPanel} from './slice_details_panel';
 import {ThreadStateTab} from './thread_state_tab';
@@ -355,15 +355,15 @@
     }
 
 
-    if (globals.state.nonSerializableState.pivotTableRedux.selectionArea !==
+    if (globals.state.nonSerializableState.pivotTable.selectionArea !==
         undefined) {
       detailsPanels.push({
-        key: 'pivot_table_redux',
+        key: 'pivot_table',
         name: 'Pivot Table',
-        vnode: m(PivotTableRedux, {
-          key: 'pivot_table_redux',
+        vnode: m(PivotTable, {
+          key: 'pivot_table',
           selectionArea:
-              globals.state.nonSerializableState.pivotTableRedux.selectionArea,
+              globals.state.nonSerializableState.pivotTable.selectionArea,
         }),
       });
     }
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index d406835..b24f69a 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -74,7 +74,6 @@
   scrollToTrackId?: string|number;
   httpRpcState: HttpRpcState = {connected: false};
   newVersionAvailable = false;
-  showPivotTable = false;
 
   // This is used to calculate the tracks within a Y range for area selection.
   areaY: Range = {};
@@ -126,11 +125,6 @@
     }
   }
 
-  togglePivotTable() {
-    this.showPivotTable = !this.showPivotTable;
-    globals.rafScheduler.scheduleFullRedraw();
-  }
-
   mergeState(state: FrontendState): void {
     // This is unfortunately subtle. This class mutates this._visibleState.
     // Since we may not mutate |state| (in order to make immer's immutable
diff --git a/ui/src/frontend/pivot_table_redux.ts b/ui/src/frontend/pivot_table.ts
similarity index 90%
rename from ui/src/frontend/pivot_table_redux.ts
rename to ui/src/frontend/pivot_table.ts
index 20f1b48..a408b22 100644
--- a/ui/src/frontend/pivot_table_redux.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -23,34 +23,34 @@
 import {ColumnType} from '../common/query_result';
 import {
   Area,
-  PivotTableReduxAreaState,
-  PivotTableReduxResult,
+  PivotTableAreaState,
+  PivotTableResult,
   SortDirection,
 } from '../common/state';
 import {fromNs, timeToCode} from '../common/time';
 import {
-  PivotTableReduxController,
-} from '../controller/pivot_table_redux_controller';
+  PivotTableController,
+} from '../controller/pivot_table_controller';
 
 import {globals} from './globals';
 import {fullscreenModalContainer, ModalDefinition} from './modal';
 import {Panel} from './panel';
 import {AnyAttrsVnode} from './panel_container';
-import {ArgumentPopup} from './pivot_table_redux_argument_popup';
+import {ArgumentPopup} from './pivot_table_argument_popup';
 import {
   aggregationIndex,
   areaFilter,
   extractArgumentExpression,
   sliceAggregationColumns,
   tables,
-} from './pivot_table_redux_query_generator';
+} from './pivot_table_query_generator';
 import {
   Aggregation,
   AggregationFunction,
   columnKey,
   PivotTree,
   TableColumn,
-} from './pivot_table_redux_types';
+} from './pivot_table_types';
 import {PopupMenuButton, PopupMenuItem} from './popup_menu';
 import {ReorderableCell, ReorderableCellGroup} from './reorderable_cells';
 
@@ -60,8 +60,8 @@
   nextKey: ColumnType;
 }
 
-interface PivotTableReduxAttrs {
-  selectionArea: PivotTableReduxAreaState;
+interface PivotTableAttrs {
+  selectionArea: PivotTableAreaState;
 }
 
 interface DrillFilter {
@@ -107,12 +107,12 @@
   return '';
 }
 
-export class PivotTableRedux extends Panel<PivotTableReduxAttrs> {
+export class PivotTable extends Panel<PivotTableAttrs> {
   get pivotState() {
-    return globals.state.nonSerializableState.pivotTableRedux;
+    return globals.state.nonSerializableState.pivotTable;
   }
   get constrainToArea() {
-    return globals.state.nonSerializableState.pivotTableRedux.constrainToArea;
+    return globals.state.nonSerializableState.pivotTable.constrainToArea;
   }
 
   renderCanvas(): void {}
@@ -139,7 +139,7 @@
               // custom query is a temporary one, replace with a proper UI.
               globals.dispatch(Actions.executeQuery({
                 queryId: `pivot_table_details_${
-                    PivotTableReduxController.detailsCount++}`,
+                    PivotTableController.detailsCount++}`,
                 query,
               }));
             },
@@ -149,7 +149,7 @@
 
   renderSectionRow(
       area: Area, path: PathItem[], tree: PivotTree,
-      result: PivotTableReduxResult): m.Vnode {
+      result: PivotTableResult): m.Vnode {
     const renderedCells = [];
     for (let j = 0; j + 1 < path.length; j++) {
       renderedCells.push(m('td', m('span.indent', ' '), `${path[j].nextKey}`));
@@ -200,8 +200,8 @@
   }
 
   renderTree(
-      area: Area, path: PathItem[], tree: PivotTree,
-      result: PivotTableReduxResult, sink: m.Vnode[]) {
+      area: Area, path: PathItem[], tree: PivotTree, result: PivotTableResult,
+      sink: m.Vnode[]) {
     if (tree.isCollapsed) {
       sink.push(this.renderSectionRow(area, path, tree, result));
       return;
@@ -251,7 +251,7 @@
     }
   }
 
-  renderTotalsRow(queryResult: PivotTableReduxResult) {
+  renderTotalsRow(queryResult: PivotTableResult) {
     const overallValuesRow =
         [m('td.total-values',
            {'colspan': queryResult.metadata.pivotColumns.length},
@@ -328,7 +328,7 @@
       aggregation: Aggregation, index: number,
       removeItem: boolean): ReorderableCell {
     const popupItems: PopupMenuItem[] = [];
-    const state = globals.state.nonSerializableState.pivotTableRedux;
+    const state = globals.state.nonSerializableState.pivotTable;
     let icon = 'more_horiz';
     if (aggregation.sortDirection === undefined) {
       popupItems.push(
@@ -409,13 +409,14 @@
   renderModal(): ModalDefinition {
     return {
       title: 'Enter argument name',
-      content: m(ArgumentPopup, {
-                 knownArguments: globals.state.nonSerializableState
-                                     .pivotTableRedux.argumentNames,
-                 onArgumentChange: (arg) => {
-                   this.typedArgument = arg;
-                 },
-               }) as AnyAttrsVnode,
+      content:
+          m(ArgumentPopup, {
+            knownArguments:
+                globals.state.nonSerializableState.pivotTable.argumentNames,
+            onArgumentChange: (arg) => {
+              this.typedArgument = arg;
+            },
+          }) as AnyAttrsVnode,
       buttons: [
         {
           text: 'Add',
@@ -433,7 +434,7 @@
   }
 
   renderPivotColumnHeader(
-      queryResult: PivotTableReduxResult, pivot: TableColumn,
+      queryResult: PivotTableResult, pivot: TableColumn,
       selectedPivots: Set<string>): ReorderableCell {
     const items: PopupMenuItem[] = [{
       itemType: 'regular',
@@ -496,12 +497,12 @@
     };
   }
 
-  renderResultsTable(attrs: PivotTableReduxAttrs) {
-    const state = globals.state.nonSerializableState.pivotTableRedux;
+  renderResultsTable(attrs: PivotTableAttrs) {
+    const state = globals.state.nonSerializableState.pivotTable;
     if (state.queryResult === null) {
       return m('div', 'Loading...');
     }
-    const queryResult: PivotTableReduxResult = state.queryResult;
+    const queryResult: PivotTableResult = state.queryResult;
 
     const renderedRows: m.Vnode[] = [];
     const tree = state.queryResult.tree;
@@ -566,7 +567,7 @@
                       'Query data for the whole timeline' :
                       'Constrain to selected area',
                   callback: () => {
-                    globals.dispatch(Actions.setPivotTableReduxConstrainToArea(
+                    globals.dispatch(Actions.setPivotTableConstrainToArea(
                         {constrain: !state.constrainToArea}));
                     globals.dispatch(Actions.setPivotTableQueryRequested(
                         {queryRequested: true}));
@@ -576,11 +577,11 @@
         m('tbody', this.renderTotalsRow(state.queryResult), renderedRows));
   }
 
-  view({attrs}: m.Vnode<PivotTableReduxAttrs>): m.Children {
+  view({attrs}: m.Vnode<PivotTableAttrs>): m.Children {
     if (this.showModal) {
       fullscreenModalContainer.updateVdom(this.renderModal());
     }
 
-    return m('.pivot-table-redux', this.renderResultsTable(attrs));
+    return m('.pivot-table', this.renderResultsTable(attrs));
   }
 }
diff --git a/ui/src/frontend/pivot_table_redux_argument_popup.ts b/ui/src/frontend/pivot_table_argument_popup.ts
similarity index 100%
rename from ui/src/frontend/pivot_table_redux_argument_popup.ts
rename to ui/src/frontend/pivot_table_argument_popup.ts
diff --git a/ui/src/frontend/pivot_table_redux_query_generator.ts b/ui/src/frontend/pivot_table_query_generator.ts
similarity index 96%
rename from ui/src/frontend/pivot_table_redux_query_generator.ts
rename to ui/src/frontend/pivot_table_query_generator.ts
index 8e819cb..50df919 100644
--- a/ui/src/frontend/pivot_table_redux_query_generator.ts
+++ b/ui/src/frontend/pivot_table_query_generator.ts
@@ -17,8 +17,8 @@
 import {sqliteString} from '../base/string_utils';
 import {
   Area,
-  PivotTableReduxQuery,
-  PivotTableReduxState,
+  PivotTableQuery,
+  PivotTableState,
 } from '../common/state';
 import {toNs} from '../common/time';
 import {
@@ -29,7 +29,7 @@
 import {
   Aggregation,
   TableColumn,
-} from './pivot_table_redux_types';
+} from './pivot_table_types';
 
 export interface Table {
   name: string;
@@ -132,9 +132,8 @@
   return pivotColumns + aggregationNo;
 }
 
-export function generateQueryFromState(
-    state: PivotTableReduxState,
-    ): PivotTableReduxQuery {
+export function generateQueryFromState(state: PivotTableState):
+    PivotTableQuery {
   if (state.selectionArea === undefined) {
     throw new QueryGeneratorError('Should not be called without area');
   }
diff --git a/ui/src/frontend/pivot_table_redux_types.ts b/ui/src/frontend/pivot_table_types.ts
similarity index 100%
rename from ui/src/frontend/pivot_table_redux_types.ts
rename to ui/src/frontend/pivot_table_types.ts
diff --git a/ui/src/frontend/sql_utils.ts b/ui/src/frontend/sql_utils.ts
index ff6e200..422e70f 100644
--- a/ui/src/frontend/sql_utils.ts
+++ b/ui/src/frontend/sql_utils.ts
@@ -21,7 +21,7 @@
 
 // Interface for defining constraints which can be passed to a SQL query.
 export interface SQLConstraints {
-      where?: string[];
+  filters?: string[];
   orderBy?: OrderClause[];
   limit?: number;
 }
@@ -30,8 +30,8 @@
 // SQL query.
 export function constraintsToQueryFragment(c: SQLConstraints): string {
   const result: string[] = [];
-  if (c.where && c.where.length > 0) {
-    result.push(`WHERE ${c.where.join(' and ')}`);
+  if (c.filters && c.filters.length > 0) {
+    result.push(`WHERE ${c.filters.join(' and ')}`);
   }
   if (c.orderBy && c.orderBy.length > 0) {
     const orderBys = c.orderBy.map((clause) => {
diff --git a/ui/src/frontend/sql_utils_unittest.ts b/ui/src/frontend/sql_utils_unittest.ts
index d8e35ba..0e5dc76 100644
--- a/ui/src/frontend/sql_utils_unittest.ts
+++ b/ui/src/frontend/sql_utils_unittest.ts
@@ -21,7 +21,7 @@
 
 test('constraintsToQueryFragment: where', () => {
   expect(normalize(constraintsToQueryFragment({
-    where: ['ts > 1000', 'dur != 0'],
+    filters: ['ts > 1000', 'dur != 0'],
   }))).toEqual('WHERE ts > 1000 and dur != 0');
 });
 
@@ -37,7 +37,7 @@
 
 test('constraintsToQueryFragment: all', () => {
   expect(normalize(constraintsToQueryFragment({
-    where: ['id != 1'],
+    filters: ['id != 1'],
     orderBy: [{fieldName: 'ts'}],
     limit: 1,
   }))).toEqual('WHERE id != 1 ORDER BY ts LIMIT 1');
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index 4513c55..1a4f558 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -125,7 +125,7 @@
 export async function getThreadState(
     engine: EngineProxy, id: number): Promise<ThreadState|undefined> {
   const result = await getThreadStateFromConstraints(engine, {
-    where: [`id=${id}`],
+    filters: [`id=${id}`],
   });
   if (result.length > 1) {
     throw new Error(`thread_state table has more than one row with id ${id}`);
diff --git a/ui/src/frontend/widgets/empty_state.ts b/ui/src/frontend/widgets/empty_state.ts
new file mode 100644
index 0000000..255e9ff
--- /dev/null
+++ b/ui/src/frontend/widgets/empty_state.ts
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 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 * as m from 'mithril';
+
+export interface EmptyStateAttrs {
+  // Which material icon to show.
+  // Defaults to 'search'.
+  icon?: string;
+  // Some text to show under the icon. No text shown if omitted.
+  header?: string;
+}
+
+// Something to show when there's nothing else to show!
+// Features a large icon, followed by some text explaining what went wrong, and
+// some optional content passed as children elements, usually containing common
+// actions for things you might want to do next (e.g. clear a search box).
+export class EmptyState implements m.ClassComponent<EmptyStateAttrs> {
+  view({attrs, children}: m.Vnode<EmptyStateAttrs, this>): void|m.Children {
+    const {
+      icon = 'search',  // Icon defaults to the search symbol
+      header,
+    } = attrs;
+    return m(
+        '.pf-empty-state',
+        m('i.material-icons', icon),
+        header && m('span.pf-empty-state-header', header),
+        m('div.pf-empty-state-content', children),
+    );
+  }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 02e8398..6f667df 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -13,11 +13,12 @@
 // limitations under the License.
 
 import * as m from 'mithril';
-import {globals} from './globals';
 
+import {globals} from './globals';
 import {createPage} from './pages';
 import {Button} from './widgets/button';
 import {Checkbox} from './widgets/checkbox';
+import {EmptyState} from './widgets/empty_state';
 import {TextInput} from './widgets/text_input';
 
 interface WidgetShowcaseAttrs {
@@ -114,6 +115,19 @@
             disabled: false,
           },
         }),
+        m('h2', 'Empty State'),
+        m(WidgetShowcase, {
+          renderWidget: ({header, content}) =>
+              m(EmptyState,
+                {
+                  header: header && 'No search results found...',
+                },
+                content && m(Button, {label: 'Try again'})),
+          initialOpts: {
+            header: true,
+            content: true,
+          },
+        }),
     );
   },
 });