Merge changes I59ff0260,Iae793df8 into main
* changes:
ui: remove unnecessary full redraws
ui: clean up raf_scheduler.ts and perf.ts
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index d9b130e..59b2446 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -2268,6 +2268,22 @@
// family) and thread creation (clone(CLONE_THREAD, ...)).
static const uint32_t kCloneThread = 0x00010000; // From kernel's sched.h.
+ if (PERFETTO_UNLIKELY(new_tid == 0)) {
+ // In the case of boot-time tracing (kernel is started with tracing
+ // enabled), the ftrace buffer will see /bin/init creating swapper/0 tasks:
+ // event {
+ // pid: 1
+ // task_newtask {
+ // pid: 0
+ // comm: "swapper/0"
+ // }
+ // }
+ // Skip these task_newtask events since they are kernel idle tasks.
+ PERFETTO_DCHECK(source_tid == 1);
+ PERFETTO_DCHECK(base::StartsWith(evt.comm().ToStdString(), "swapper"));
+ return;
+ }
+
// If the process is a fork, start a new process.
if ((clone_flags & kCloneThread) == 0) {
// This is a plain-old fork() or equivalent.
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
index 54707c1..8af739d 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -1570,3 +1570,76 @@
5230422153284,0,1306,"[NULL]"
5230425693562,0,10,1
"""))
+
+ # Kernel idle tasks created by /sbin/init should be filtered.
+ def test_task_newtask_swapper_by_init(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ first_packet_on_sequence: true
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 1000000
+ pid: 0
+ task_newtask {
+ pid: 1
+ comm: "swapper/0"
+ clone_flags: 8389376
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 1000000
+ pid: 0
+ task_newtask {
+ pid: 2
+ comm: "swapper/0"
+ clone_flags: 8390400
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 17000000
+ pid: 1
+ task_newtask {
+ pid: 0
+ comm: "swapper/0"
+ clone_flags: 256
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 17000000
+ pid: 1
+ task_newtask {
+ pid: 0
+ comm: "swapper/0"
+ clone_flags: 256
+ oom_score_adj: 0
+ }
+ }
+ event {
+ timestamp: 17000000
+ pid: 1
+ task_newtask {
+ pid: 0
+ comm: "swapper/0"
+ clone_flags: 256
+ oom_score_adj: 0
+ }
+ }
+ }
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 2
+ trusted_pid: 521
+ previous_packet_dropped: true
+ }
+ """),
+ query="""
+ SELECT utid, tid, name from thread where tid = 0
+ """,
+ out=Csv("""
+ "utid","tid","name"
+ 0,0,"swapper"
+ """))
diff --git a/ui/release/channels.json b/ui/release/channels.json
index c2c76c2..5f8615b 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
},
{
"name": "canary",
- "rev": "4817ff8af4289f905c36a8a1ba6a583afc569af4"
+ "rev": "2db61efa59d1e2eecb6975854c14b2a122fbfa8a"
},
{
"name": "autopush",
diff --git a/ui/src/frontend/pivot_table.ts b/ui/src/frontend/pivot_table.ts
index a096140..925cfed 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -34,11 +34,6 @@
sliceAggregationColumns,
tables,
} from '../core/pivot_table_query_generator';
-import {
- PopupMenuButton,
- popupMenuIcon,
- PopupMenuItem,
-} from '../widgets/popup_menu';
import {ReorderableCell, ReorderableCellGroup} from './reorderable_cells';
import {AttributeModalHolder} from './tables/attribute_modal_holder';
import {DurationWidget} from './widgets/duration';
@@ -49,6 +44,9 @@
import {TraceImpl} from '../core/trace_impl';
import {PivotTableManager} from '../core/pivot_table_manager';
import {extensions} from '../public/lib/extensions';
+import {MenuItem, PopupMenu2} from '../widgets/menu';
+import {Button} from '../widgets/button';
+import {popupMenuIcon} from '../widgets/table';
interface PathItem {
tree: PivotTree;
@@ -293,15 +291,14 @@
return m('tr', overallValuesRow);
}
- sortingItem(aggregationIndex: number, order: SortDirection): PopupMenuItem {
+ sortingItem(aggregationIndex: number, order: SortDirection): m.Child {
const pivotMgr = this.pivotMgr;
- return {
- itemType: 'regular',
- text: order === 'DESC' ? 'Highest first' : 'Lowest first',
- callback() {
+ return m(MenuItem, {
+ label: order === 'DESC' ? 'Highest first' : 'Lowest first',
+ onclick: () => {
pivotMgr.setSortColumn(aggregationIndex, order);
},
- };
+ });
}
readableAggregationName(aggregation: Aggregation) {
@@ -317,20 +314,21 @@
aggregation: Aggregation,
index: number,
nameOverride?: string,
- ): PopupMenuItem {
- return {
- itemType: 'regular',
- text: nameOverride ?? readableColumnName(aggregation.column),
- callback: () => this.pivotMgr.addAggregation(aggregation, index),
- };
+ ): m.Child {
+ return m(MenuItem, {
+ label: nameOverride ?? readableColumnName(aggregation.column),
+ onclick: () => {
+ this.pivotMgr.addAggregation(aggregation, index);
+ },
+ });
}
aggregationPopupTableGroup(
table: string,
columns: string[],
index: number,
- ): PopupMenuItem | undefined {
- const items = [];
+ ): m.Child | undefined {
+ const items: m.Child[] = [];
for (const column of columns) {
const tableColumn: TableColumn = {kind: 'regular', table, column};
items.push(
@@ -345,12 +343,7 @@
return undefined;
}
- return {
- itemType: 'group',
- itemId: `aggregations-${table}`,
- text: `Add ${table} aggregation`,
- children: items,
- };
+ return m(MenuItem, {label: `Add ${table} aggregation`}, items);
}
renderAggregationHeaderCell(
@@ -358,7 +351,7 @@
index: number,
removeItem: boolean,
): ReorderableCell {
- const popupItems: PopupMenuItem[] = [];
+ const popupItems: m.Child[] = [];
if (aggregation.sortDirection === undefined) {
popupItems.push(
this.sortingItem(index, 'DESC'),
@@ -381,22 +374,26 @@
continue;
}
const pivotMgr = this.pivotMgr;
- popupItems.push({
- itemType: 'regular',
- text: otherAgg,
- callback() {
- pivotMgr.setAggregationFunction(index, otherAgg);
- },
- });
+ popupItems.push(
+ m(MenuItem, {
+ label: otherAgg,
+ onclick: () => {
+ pivotMgr.setAggregationFunction(index, otherAgg);
+ },
+ }),
+ );
}
}
if (removeItem) {
- popupItems.push({
- itemType: 'regular',
- text: 'Remove',
- callback: () => this.pivotMgr.removeAggregation(index),
- });
+ popupItems.push(
+ m(MenuItem, {
+ label: 'Remove',
+ onclick: () => {
+ this.pivotMgr.removeAggregation(index);
+ },
+ }),
+ );
}
let hasCount = false;
@@ -429,10 +426,15 @@
extraClass: '.aggregation' + markFirst(index),
content: [
this.readableAggregationName(aggregation),
- m(PopupMenuButton, {
- icon: popupMenuIcon(aggregation.sortDirection),
- items: popupItems,
- }),
+ m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {
+ icon: popupMenuIcon(aggregation.sortDirection),
+ }),
+ },
+ popupItems,
+ ),
],
};
}
@@ -445,27 +447,27 @@
selectedPivots: Set<string>,
): ReorderableCell {
const pivotMgr = this.pivotMgr;
- const items: PopupMenuItem[] = [
- {
- itemType: 'regular',
- text: 'Add argument pivot',
- callback: () => {
+ const items: m.Child[] = [
+ m(MenuItem, {
+ label: 'Add argument pivot',
+ onclick: () => {
this.attributeModalHolder.start();
},
- },
+ }),
];
if (queryResult.metadata.pivotColumns.length > 1) {
- items.push({
- itemType: 'regular',
- text: 'Remove',
- callback() {
- pivotMgr.setPivotSelected({column: pivot, selected: false});
- },
- });
+ items.push(
+ m(MenuItem, {
+ label: 'Remove',
+ onclick: () => {
+ pivotMgr.setPivotSelected({column: pivot, selected: false});
+ },
+ }),
+ );
}
for (const table of tables) {
- const group: PopupMenuItem[] = [];
+ const group: m.Child[] = [];
for (const columnName of table.columns) {
const column: TableColumn = {
kind: 'regular',
@@ -475,26 +477,30 @@
if (selectedPivots.has(columnKey(column))) {
continue;
}
- group.push({
- itemType: 'regular',
- text: columnName,
- callback() {
- pivotMgr.setPivotSelected({column, selected: true});
- },
- });
+ group.push(
+ m(MenuItem, {
+ label: columnName,
+ onclick: () => {
+ pivotMgr.setPivotSelected({column, selected: true});
+ },
+ }),
+ );
}
- items.push({
- itemType: 'group',
- itemId: `pivot-${table.name}`,
- text: `Add ${table.displayName} pivot`,
- children: group,
- });
+ items.push(
+ m(
+ MenuItem,
+ {
+ label: `Add ${table.displayName} pivot`,
+ },
+ group,
+ ),
+ );
}
return {
content: [
readableColumnName(pivot),
- m(PopupMenuButton, {icon: 'more_horiz', items}),
+ m(PopupMenu2, {trigger: m(Button, {icon: 'more_horiz'})}, items),
],
};
}
@@ -551,20 +557,20 @@
}),
m(
'td.menu',
- m(PopupMenuButton, {
- icon: 'menu',
- items: [
- {
- itemType: 'regular',
- text: state.constrainToArea
- ? 'Query data for the whole timeline'
- : 'Constrain to selected area',
- callback: () => {
- this.pivotMgr.setConstrainedToArea(!state.constrainToArea);
- },
+ m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {icon: 'menu'}),
+ },
+ m(MenuItem, {
+ label: state.constrainToArea
+ ? 'Query data for the whole timeline'
+ : 'Constrain to selected area',
+ onclick: () => {
+ this.pivotMgr.setConstrainedToArea(!state.constrainToArea);
},
- ],
- }),
+ }),
+ ),
),
),
),
diff --git a/ui/src/frontend/value.ts b/ui/src/frontend/value.ts
index 40ad1f4..a57f2ea 100644
--- a/ui/src/frontend/value.ts
+++ b/ui/src/frontend/value.ts
@@ -14,7 +14,8 @@
import m from 'mithril';
import {Tree, TreeNode} from '../widgets/tree';
-import {PopupMenuButton, PopupMenuItem} from '../widgets/popup_menu';
+import {PopupMenu2} from '../widgets/menu';
+import {Button} from '../widgets/button';
// This file implements a component for rendering JSON-like values (with
// customisation options like context menu and action buttons).
@@ -109,7 +110,7 @@
// Customisation parameters which apply to any Value (e.g. context menu).
interface ValueParams {
- contextMenu?: PopupMenuItem[];
+ contextMenu?: m.Child[];
}
// Customisation parameters which apply for a primitive value (e.g. showing
@@ -137,10 +138,15 @@
const left = [
name,
value.contextMenu
- ? m(PopupMenuButton, {
- icon: 'arrow_drop_down',
- items: value.contextMenu,
- })
+ ? m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {
+ icon: 'arrow_drop_down',
+ }),
+ },
+ value.contextMenu,
+ )
: null,
];
if (isArray(value)) {
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
index 7bbcace..327a179 100644
--- a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
+++ b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
@@ -43,7 +43,6 @@
import {LazyTreeNode, Tree, TreeNode} from '../../widgets/tree';
import {VegaView} from '../../widgets/vega_view';
import {PageAttrs} from '../../public/page';
-import {PopupMenuButton} from '../../widgets/popup_menu';
import {TableShowcase} from './table_showcase';
import {TreeTable, TreeTableAttrs} from '../../frontend/widgets/treetable';
import {Intent} from '../../widgets/common';
@@ -904,30 +903,6 @@
},
}),
m(WidgetShowcase, {
- label: 'PopupMenu',
- renderWidget: () => {
- return m(PopupMenuButton, {
- icon: 'description',
- items: [
- {itemType: 'regular', text: 'New', callback: () => {}},
- {itemType: 'regular', text: 'Open', callback: () => {}},
- {itemType: 'regular', text: 'Save', callback: () => {}},
- {itemType: 'regular', text: 'Delete', callback: () => {}},
- {
- itemType: 'group',
- text: 'Share',
- itemId: 'foo',
- children: [
- {itemType: 'regular', text: 'Friends', callback: () => {}},
- {itemType: 'regular', text: 'Family', callback: () => {}},
- {itemType: 'regular', text: 'Everyone', callback: () => {}},
- ],
- },
- ],
- });
- },
- }),
- m(WidgetShowcase, {
label: 'Menu',
renderWidget: () =>
m(
diff --git a/ui/src/widgets/popup_menu.ts b/ui/src/widgets/popup_menu.ts
deleted file mode 100644
index 737815c..0000000
--- a/ui/src/widgets/popup_menu.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright (C) 2022 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 m from 'mithril';
-import {SortDirection} from '../base/comparison_utils';
-import {scheduleFullRedraw} from './raf';
-
-export interface RegularPopupMenuItem {
- itemType: 'regular';
- // Display text
- text: string;
- // Action on menu item click
- callback: () => void;
-}
-
-// Helper function for simplifying defining menus.
-export function menuItem(
- text: string,
- action: () => void,
-): RegularPopupMenuItem {
- return {
- itemType: 'regular',
- text,
- callback: action,
- };
-}
-
-export interface GroupPopupMenuItem {
- itemType: 'group';
- text: string;
- itemId: string;
- children: PopupMenuItem[];
-}
-
-export type PopupMenuItem = RegularPopupMenuItem | GroupPopupMenuItem;
-
-export interface PopupMenuButtonAttrs {
- // Icon for button opening a menu
- icon: string;
- // List of popup menu items
- items: PopupMenuItem[];
-}
-
-// To ensure having at most one popup menu on the screen at a time, we need to
-// listen to click events on the whole page and close currently opened popup, if
-// there's any. This class, used as a singleton, does exactly that.
-class PopupHolder {
- // Invariant: global listener should be register if and only if this.popup is
- // not undefined.
- popup: PopupMenuButton | undefined = undefined;
- initialized = false;
- listener: (e: MouseEvent) => void;
-
- constructor() {
- this.listener = (e: MouseEvent) => {
- // Only handle those events that are not part of dropdown menu themselves.
- const hasDropdown =
- e.composedPath().find(PopupHolder.isDropdownElement) !== undefined;
- if (!hasDropdown) {
- this.ensureHidden();
- }
- };
- }
-
- static isDropdownElement(target: EventTarget) {
- if (target instanceof HTMLElement) {
- return target.tagName === 'DIV' && target.classList.contains('dropdown');
- }
- return false;
- }
-
- ensureHidden() {
- if (this.popup !== undefined) {
- this.popup.setVisible(false);
- }
- }
-
- clear() {
- if (this.popup !== undefined) {
- this.popup = undefined;
- window.removeEventListener('click', this.listener);
- }
- }
-
- showPopup(popup: PopupMenuButton) {
- this.ensureHidden();
- this.popup = popup;
- window.addEventListener('click', this.listener);
- }
-}
-
-// Singleton instance of PopupHolder
-const popupHolder = new PopupHolder();
-
-// For a table column that can be sorted; the standard popup icon should
-// reflect the current sorting direction. This function returns an icon
-// corresponding to optional SortDirection according to which the column is
-// sorted. (Optional because column might be unsorted)
-export function popupMenuIcon(sortDirection?: SortDirection) {
- switch (sortDirection) {
- case undefined:
- return 'more_horiz';
- case 'DESC':
- return 'arrow_drop_down';
- case 'ASC':
- return 'arrow_drop_up';
- }
-}
-
-// Component that displays a button that shows a popup menu on click.
-export class PopupMenuButton implements m.ClassComponent<PopupMenuButtonAttrs> {
- popupShown = false;
- expandedGroups: Set<string> = new Set();
-
- setVisible(visible: boolean) {
- this.popupShown = visible;
- if (this.popupShown) {
- popupHolder.showPopup(this);
- } else {
- popupHolder.clear();
- }
- scheduleFullRedraw();
- }
-
- renderItem(item: PopupMenuItem): m.Child {
- switch (item.itemType) {
- case 'regular':
- return m(
- 'button.open-menu',
- {
- onclick: () => {
- item.callback();
- // Hide the menu item after the action has been invoked
- this.setVisible(false);
- },
- },
- item.text,
- );
- case 'group':
- const isExpanded = this.expandedGroups.has(item.itemId);
- return m(
- 'div',
- m(
- 'button.open-menu.disallow-selection',
- {
- onclick: () => {
- if (this.expandedGroups.has(item.itemId)) {
- this.expandedGroups.delete(item.itemId);
- } else {
- this.expandedGroups.add(item.itemId);
- }
- scheduleFullRedraw();
- },
- },
- // Show text with up/down arrow, depending on expanded state.
- item.text + (isExpanded ? ' \u25B2' : ' \u25BC'),
- ),
- isExpanded
- ? m(
- 'div.nested-menu',
- item.children.map((item) => this.renderItem(item)),
- )
- : null,
- );
- }
- }
-
- view(vnode: m.Vnode<PopupMenuButtonAttrs, this>) {
- return m(
- '.dropdown',
- m(
- '.dropdown-button',
- {
- onclick: () => {
- this.setVisible(!this.popupShown);
- },
- },
- vnode.children,
- m('i.material-icons', vnode.attrs.icon),
- ),
- m(
- this.popupShown ? '.popup-menu.opened' : '.popup-menu.closed',
- vnode.attrs.items.map((item) => this.renderItem(item)),
- ),
- );
- }
-}
diff --git a/ui/src/widgets/table.ts b/ui/src/widgets/table.ts
index ee195d0..1389907 100644
--- a/ui/src/widgets/table.ts
+++ b/ui/src/widgets/table.ts
@@ -22,17 +22,28 @@
SortDirection,
withDirection,
} from '../base/comparison_utils';
-import {
- menuItem,
- PopupMenuButton,
- popupMenuIcon,
- PopupMenuItem,
-} from './popup_menu';
import {scheduleFullRedraw} from './raf';
+import {MenuItem, PopupMenu2} from './menu';
+import {Button} from './button';
+
+// For a table column that can be sorted; the standard popup icon should
+// reflect the current sorting direction. This function returns an icon
+// corresponding to optional SortDirection according to which the column is
+// sorted. (Optional because column might be unsorted)
+export function popupMenuIcon(sortDirection?: SortDirection) {
+ switch (sortDirection) {
+ case undefined:
+ return 'more_horiz';
+ case 'DESC':
+ return 'arrow_drop_down';
+ case 'ASC':
+ return 'arrow_drop_up';
+ }
+}
export interface ColumnDescriptorAttrs<T> {
// Context menu items displayed on the column header.
- contextMenu?: PopupMenuItem[];
+ contextMenu?: m.Child[];
// Unique column ID, used to identify which column is currently sorted.
columnId?: string;
@@ -49,7 +60,7 @@
name: string;
render: (row: T) => m.Child;
id: string;
- contextMenu?: PopupMenuItem[];
+ contextMenu?: m.Child[];
ordering?: ComparisonFn<T>;
constructor(
@@ -81,7 +92,7 @@
export function numberColumn<T>(
name: string,
getter: (t: T) => number,
- contextMenu?: PopupMenuItem[],
+ contextMenu?: m.Child[],
): ColumnDescriptor<T> {
return new ColumnDescriptor<T>(name, getter, {contextMenu, sortKey: getter});
}
@@ -89,7 +100,7 @@
export function stringColumn<T>(
name: string,
getter: (t: T) => string,
- contextMenu?: PopupMenuItem[],
+ contextMenu?: m.Child[],
): ColumnDescriptor<T> {
return new ColumnDescriptor<T>(name, getter, {contextMenu, sortKey: getter});
}
@@ -191,33 +202,42 @@
if (column.ordering !== undefined) {
const ordering = column.ordering;
currDirection = directionOnIndex(column.id, vnode.attrs.data.sortingInfo);
- const newItems: PopupMenuItem[] = [];
+ const newItems: m.Child[] = [];
if (currDirection !== 'ASC') {
newItems.push(
- menuItem('Sort ascending', () => {
- vnode.attrs.data.reorder({
- columnId: column.id,
- direction: 'ASC',
- ordering,
- });
+ m(MenuItem, {
+ label: 'Sort ascending',
+ onclick: () => {
+ vnode.attrs.data.reorder({
+ columnId: column.id,
+ direction: 'ASC',
+ ordering,
+ });
+ },
}),
);
}
if (currDirection !== 'DESC') {
newItems.push(
- menuItem('Sort descending', () => {
- vnode.attrs.data.reorder({
- columnId: column.id,
- direction: 'DESC',
- ordering,
- });
+ m(MenuItem, {
+ label: 'Sort descending',
+ onclick: () => {
+ vnode.attrs.data.reorder({
+ columnId: column.id,
+ direction: 'DESC',
+ ordering,
+ });
+ },
}),
);
}
if (currDirection !== undefined) {
newItems.push(
- menuItem('Restore original order', () => {
- vnode.attrs.data.resetOrder();
+ m(MenuItem, {
+ label: 'Restore original order',
+ onclick: () => {
+ vnode.attrs.data.resetOrder();
+ },
}),
);
}
@@ -227,12 +247,14 @@
return m(
'td',
column.name,
- items === undefined
- ? null
- : m(PopupMenuButton, {
- icon: popupMenuIcon(currDirection),
- items,
- }),
+ items &&
+ m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {icon: popupMenuIcon(currDirection)}),
+ },
+ items,
+ ),
);
}