| # 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 |
| |
| ![Pivot table design](/docs/images/pivot-tables/pivot-table-design.png) |
| |
| ### 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. |
| |
| ![Pivot table query](/docs/images/pivot-tables/pivot-table-query.png) |
| |
| 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. |
| |
| ![Pivot table expanded cell](/docs/images/pivot-tables/pivot-table-expanded-cell.png) |
| |
| 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. |