|  | # Pivot Tables | 
|  |  | 
|  | _**Project Plan**: [Perfetto: Pivot tables for slices](https://docs.google.com/document/d/1RuEGQKLgOA8YWjZJHD6CTA3ghRRg6o5Phg3_rFCJEDE/)_ | 
|  | _**How to Use**: [Pivot Table Usage](/docs/visualization/perfetto-ui#pivot-tables)_ | 
|  | _**For Googlers**: [Perfetto: Pivot Table Use Cases](https://docs.google.com/document/d/1_iR-JjD7m19Q9GQtMk1_5NLSYXFicB_gg4S9D-6Q8lU/)_ | 
|  |  | 
|  | ## Objective | 
|  | Pivot tables give a simplified aggregated view of more complex data. They are | 
|  | made up of a number of pivots and aggregations that are grouped around these | 
|  | pivots. You can add more columns/aggregations and drag and drop the columns to | 
|  | explore the underlying data. | 
|  |  | 
|  | ## Motivation | 
|  | Pivot tables are useful in debugging hangs, stalls, and digging into traces | 
|  | which usually have too much data to clearly see the problems. | 
|  | The pivot table allows users to create custom tables to view specific | 
|  | information about traces in a summarized and less complex way. | 
|  |  | 
|  | ## Main Components | 
|  |  | 
|  |  | 
|  |  | 
|  | ### Details Panel (Frontend) | 
|  | The [DetailsPanel](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/frontend/details_panel.ts) | 
|  | searches for active PivotTables to display on screen. It also syncs the | 
|  | PivotTableHelper with data from the State. (PivotTableHelper only syncs when the | 
|  | PivotTableEditor modal is not open). | 
|  |  | 
|  |  | 
|  | ### Pivot Table (Frontend) | 
|  | The [PivotTable](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/frontend/pivot_table.ts) builds | 
|  | the pivot table tab and the table. It also handles user requests (like opening | 
|  | the pivot table editor, drag and drop columns, expand, etc) by calling the | 
|  | PivotTableHelper and updating the table. | 
|  |  | 
|  |  | 
|  | ### PivotTableEditor (Frontend) | 
|  | The [PivotTableEditor](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/frontend/pivot_table_editor.ts) | 
|  | consists of ColumnPicker and ColumnDisplay classes. | 
|  | ColumnPicker allows the user to select column type, name and aggregation. Edits | 
|  | made through the ColumnPicker are saved temporarily in the PivotTableHelper | 
|  | without updating the state. | 
|  | ColumnDisplay displays the selected column from the ColumnPicker, it also allows | 
|  | users to manipulate the columns after selection (like delete, reorder, change | 
|  | the default sorting, etc...). | 
|  | In this stage the user is able to query the selected columns and update the | 
|  | table or discard the changes made and the PivotTableHelper will resync with the | 
|  | data in state. | 
|  |  | 
|  |  | 
|  | ### PivotTableHelper (Frontend) | 
|  | The [PivotTableHelper](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/frontend/pivot_table_helper.ts) | 
|  | is created by every PivotTableController for every PivotTableId. It stores a | 
|  | copy of the selectedPivots and selectedAggregations from state. It also holds | 
|  | the logic for manipulating the data locally, which are used by the PivotTable | 
|  | and PivotTableEditor. | 
|  | It also replaces the data in the State with the changes upon request. | 
|  | The PivotTableHelper also checks for special “stack” columns, called stackPivots | 
|  | (`name (stack)` for [slice table](/docs/analysis/sql-tables.autogen#slice) is | 
|  | currently the only special column), as it sets the column attributes which are | 
|  | then used to identify them by other components. | 
|  |  | 
|  |  | 
|  | ### State (Common) | 
|  | [PivotTableState](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/common/state.ts;l=303) holds the | 
|  | information that needs to be transferred to and from the frontend and the | 
|  | controller for each pivot table instance (PivotTableId). It also includes the | 
|  | global PivotTableConfigs (like the availableColumns and availableAggregations). | 
|  |  | 
|  |  | 
|  | ### PivotTableController (Controller) | 
|  | A new [PivotTableController](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/controller/pivot_table_controller.ts) | 
|  | is created for every PivotTableId. | 
|  | The PivotTableController handles the setup of the pivot table once added, it | 
|  | queries for the columns for all tables and sets the PivotTableConfig. It also | 
|  | creates and initializes a PivotTableHelper for every PivotTableId and publishes | 
|  | it to the frontend. | 
|  | Additionally, the PivotTableController handles the collection and the | 
|  | computation of all data needed by the PivotTableQueryGenerator. | 
|  | It constantly checks if a request has been set in the PivotTableState and acts | 
|  | on it if so. | 
|  | It decides what columns to query, what whereFilters and tables to include and | 
|  | how to reformat the query result into a PivotTableQueryResponse based on the | 
|  | request type. | 
|  |  | 
|  | There are four types of requests implemented in the controller: | 
|  |  | 
|  | **_QUERY:_** | 
|  | Queries the first pivot of the selectedPivots and all the aggregations, | 
|  | including any global or table-wide whereFilters (Like the start and end | 
|  | timestamp and selected track_ids that are set by the pivot table generated | 
|  | through area selection). | 
|  | It also adds a whereFilter (Filter in the where clause of the query) if the | 
|  | pivot is a stackPivot to restrict the result to the top level slices only, since | 
|  | descendants can be generated by expanding the cell and issuing the DESCENDANTS | 
|  | request, and returns the result as a PivotTableQueryResponse. | 
|  |  | 
|  |  | 
|  |  | 
|  | Returned PivotTableQueryResponse: | 
|  |  | 
|  | ```typescript | 
|  | pivotTableQueryResponse = { | 
|  | columns: ['slice type', 'slice category', 'slice name']; | 
|  | rows: [ | 
|  | { | 
|  | row: 'internal_slice', | 
|  | expandableColumns: ['slice type'], | 
|  | expandedRows = [], | 
|  | }, { | 
|  | row: 'thread_slice', | 
|  | expandableColumns: ['slice type'], | 
|  | expandedRows = [], | 
|  | }; | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | **_EXPAND:_** | 
|  | The [PivotTableBody](https://cs.android.com/android/_/android/platform/external/perfetto/+/a9118d769009349da7f264abb392f4207e66602b:ui/src/frontend/pivot_table.ts;l=235;drc=0bc8ff07f372a58ca4d0399d88567a66ef5b591b) generates the nested structure by | 
|  | recursively displaying the rows and checking if the row contains any expanded | 
|  | rows with the isExpanded flag set to true. As it goes through the nested rows, | 
|  | it passes the row index that it's about to expand, along with the column it's | 
|  | expanding for till it reaches a [PivotTableRow](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/frontend/pivot_table.ts;l=192). | 
|  | The PivotTableRow creates a cell for each column. If the cell is at a column | 
|  | that can be expanded, it is created as an [ExpandableCell](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/frontend/pivot_table.ts;l=121). | 
|  | When an 'EXPAND' request is issued on an ExpandableCell, it sets the | 
|  | requestedAction in the PivotTableState and provides it with the SubQueryAttrs. | 
|  |  | 
|  | Given a columnIdx, value, and an array of rowIndicies (SubQueryAttrs) from | 
|  | the requestedAction in the PivotTableState, it finds the exact row that called | 
|  | this request from the main PivotTableQueryResponse, and finds the next pivot | 
|  | to query. It then generates the query similarly to the ‘QUERY’ request, but | 
|  | includes the whereFilter of the previous column (column name = column value). | 
|  | The rows of the query result are then nested into the caller row’s expandedRows, | 
|  | to build a tree view structure while expanding. | 
|  |  | 
|  |  | 
|  |  | 
|  | Passed value: | 
|  |  | 
|  | ```typescript | 
|  | subQueryAttrs = { | 
|  | rowIndices: [0, 3], | 
|  | columnIdx: 1, | 
|  | value: 'blink,benchmark', | 
|  | expandedRowColumns: ['slice category'], | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Returned expanded rows: | 
|  |  | 
|  | ```typescript | 
|  | rows = [ | 
|  | { | 
|  | row: 'LocalFrameView::RunAccessibilityLifecyclePhase', | 
|  | expandableColumns: [], | 
|  | expandedRows: [] | 
|  | }, | 
|  | { | 
|  | row: 'LocalFrameView::RunCompositingInputsLifecyclePhase', | 
|  | expandableColumns: [], | 
|  | expandedRows: [] | 
|  | }, | 
|  | { | 
|  | row: 'LocalFrameView::RunStyleAndLayoutLifecyclePhases', | 
|  | expandableColumns: [], | 
|  | expandedRows: [] | 
|  | }, | 
|  | ... | 
|  | ] | 
|  | ``` | 
|  |  | 
|  | The returned rows are saved inside the caller row expandedRows map. | 
|  |  | 
|  | ```typescript | 
|  | rows = { | 
|  | row: 'blink,benchmark', | 
|  | expandableColumns: ['slice category'], | 
|  | expandedRows: [ | 
|  | 'slice name' => { | 
|  | isExpanded: true, | 
|  | rows | 
|  | } | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | **_UNEXPAND:_** | 
|  | Sets the caller row’s isExpanded flag to false, to hide it from the display but | 
|  | also keeping its expandedRows saved so as to not have to query them again if | 
|  | requested. | 
|  |  | 
|  | **_DESCENDANTS:_** | 
|  | Should only be called for stackPivots, generates a query containing the | 
|  | stackPivot and the next pivot, if it exists, and all the aggregations. It also | 
|  | requests the PivotTableQueryGenerator to order by depth first, which is then | 
|  | used to refactor the resulting rows into the PivotTableQueryResponse tree view | 
|  | structure. | 
|  | The returned format is similar to the EXPAND request. | 
|  |  | 
|  | ### PivotTableQueryGenerator (Common) | 
|  | [PivotTableQueryGenerator](https://cs.android.com/android/_/android/platform/external/perfetto/+/0ae7c36fd528824ee9fdea6cfd4494e9f05183b5:ui/src/common/pivot_table_query_generator.ts) | 
|  | generates an sql query based on the given data, along with any hidden columns | 
|  | that may need to be added. It also creates an alias for each pivot and | 
|  | aggregation that is used to identify the resulting cells in the rows. |