Merge "Remove running duration check and update blocked by IO threshold." into main
diff --git a/examples/shared_lib/example_shlib_data_source.c b/examples/shared_lib/example_shlib_data_source.c
index d8aa1f7..e57ec1a 100644
--- a/examples/shared_lib/example_shlib_data_source.c
+++ b/examples/shared_lib/example_shlib_data_source.c
@@ -24,7 +24,7 @@
static struct PerfettoDs custom = PERFETTO_DS_INIT();
int main(void) {
- struct PerfettoProducerInitArgs args = {0};
+ struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
args.backends = PERFETTO_BACKEND_SYSTEM;
PerfettoProducerInit(args);
diff --git a/include/perfetto/protozero/gen_field_helpers.h b/include/perfetto/protozero/gen_field_helpers.h
index ad3dee8..b2957f1 100644
--- a/include/perfetto/protozero/gen_field_helpers.h
+++ b/include/perfetto/protozero/gen_field_helpers.h
@@ -145,6 +145,17 @@
HeapBuffered<Message> msg_;
};
+// Wrapper about operator==() which reduces the binary size of generated protos.
+// This is needed because std::string's operator== is inlined aggressively (even
+// when optimizing for size). Having this layer of indirection with removes the
+// overhead.
+template <typename T>
+bool EqualsField(const T& a, const T& b) {
+ return a == b;
+}
+extern template bool EqualsField<std::string>(const std::string&,
+ const std::string&);
+
} // namespace gen_helpers
} // namespace internal
} // namespace protozero
diff --git a/include/perfetto/public/producer.h b/include/perfetto/public/producer.h
index 56692c6..8fa1351 100644
--- a/include/perfetto/public/producer.h
+++ b/include/perfetto/public/producer.h
@@ -30,6 +30,10 @@
PerfettoBackendTypes backends;
};
+// Initializes a PerfettoProducerInitArgs struct.
+#define PERFETTO_PRODUCER_INIT_ARGS_INIT() \
+ { 0 }
+
// Initializes the global perfetto producer.
static inline void PerfettoProducerInit(struct PerfettoProducerInitArgs args) {
if (args.backends & PERFETTO_BACKEND_IN_PROCESS) {
diff --git a/src/protozero/gen_field_helpers.cc b/src/protozero/gen_field_helpers.cc
index 85a41f7..af5949e 100644
--- a/src/protozero/gen_field_helpers.cc
+++ b/src/protozero/gen_field_helpers.cc
@@ -98,6 +98,8 @@
return msg_.SerializeAsString();
}
+template bool EqualsField<std::string>(const std::string&, const std::string&);
+
} // namespace gen_helpers
} // namespace internal
} // namespace protozero
diff --git a/src/protozero/protoc_plugin/cppgen_plugin.cc b/src/protozero/protoc_plugin/cppgen_plugin.cc
index eadc23a..a33803f 100644
--- a/src/protozero/protoc_plugin/cppgen_plugin.cc
+++ b/src/protozero/protoc_plugin/cppgen_plugin.cc
@@ -702,9 +702,14 @@
p->Print("bool $n$::operator==(const $n$& other) const {\n", "n", full_name);
p->Indent();
- p->Print("return unknown_fields_ == other.unknown_fields_");
+ p->Print(
+ "return ::protozero::internal::gen_helpers::EqualsField(unknown_fields_, "
+ "other.unknown_fields_)");
for (int i = 0; i < msg->field_count(); i++)
- p->Print("\n && $n$_ == other.$n$_", "n", msg->field(i)->lowercase_name());
+ p->Print(
+ "\n && ::protozero::internal::gen_helpers::EqualsField($n$_, "
+ "other.$n$_)",
+ "n", msg->field(i)->lowercase_name());
p->Print(";");
p->Outdent();
p->Print("\n}\n\n");
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 35b1c19..03735fa 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -190,7 +190,7 @@
class SharedLibDataSourceTest : public testing::Test {
protected:
void SetUp() override {
- struct PerfettoProducerInitArgs args = {0};
+ struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
args.backends = PERFETTO_BACKEND_IN_PROCESS;
PerfettoProducerInit(args);
PerfettoDsRegister(&data_source_1, kDataSourceName1,
diff --git a/src/shared_lib/test/benchmark.cc b/src/shared_lib/test/benchmark.cc
index 4861a31..5b2a70f 100644
--- a/src/shared_lib/test/benchmark.cc
+++ b/src/shared_lib/test/benchmark.cc
@@ -42,7 +42,7 @@
constexpr char kDataSourceName[] = "com.example.custom_data_source";
bool Initialize() {
- struct PerfettoProducerInitArgs args = {0};
+ struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
args.backends = PERFETTO_BACKEND_IN_PROCESS;
PerfettoProducerInit(args);
PerfettoDsRegister(&custom, kDataSourceName, PerfettoDsParamsDefault());
diff --git a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
index b954447..3a9d203 100644
--- a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
+++ b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
@@ -68,7 +68,15 @@
AND slice.name not GLOB '*resynced*'
AND slice.dur > 0
AND (vsync >= begin_vsync OR begin_vsync is NULL)
- AND (vsync <= end_vsync OR end_vsync is NULL);
+ AND (vsync <= end_vsync OR end_vsync is NULL)
+ -- In some malformed traces we see nested doFrame slices.
+ -- If that is the case, we ignore all parent doFrames and only keep the one
+ -- the lowest in the hierarchy.
+ AND NOT EXISTS (
+ SELECT 1 FROM descendant_slice(slice.id) child
+ WHERE child.name GLOB 'Choreographer#doFrame*'
+ AND child.name NOT GLOB '*resynced*'
+ );
-- Store render thread DrawFrames by matching in the vsync IDs extracted from
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
index d40b345..faea9e4 100644
--- a/ui/src/assets/topbar.scss
+++ b/ui/src/assets/topbar.scss
@@ -255,26 +255,38 @@
}
}
-ul.pf-omnibox-dropdown {
+.pf-omnibox-dropdown {
margin: 2px;
max-height: 300px;
overflow-y: auto;
- padding-left: 0;
-}
-li.pf-omnibox-option {
- font-family: $pf-font;
- cursor: pointer;
- list-style-type: none;
- padding: 4px 8px;
- border-radius: $pf-border-radius;
- align-items: center;
- font-weight: lighter;
- &:hover {
- background-color: $pf-minimal-background-hover;
+ ul {
+ padding-left: 0;
}
- &.pf-highlighted {
- background-color: $pf-primary-background;
- color: white;
+
+ .pf-omnibox-section-header {
+ font-family: $pf-font;
+ font-size: smaller;
+ margin: 4px 0;
+ border-bottom: solid 1px $pf-colour-thin-border;
+ position: sticky;
+ top: 0;
+ }
+
+ li {
+ font-family: $pf-font;
+ cursor: pointer;
+ list-style-type: none;
+ padding: 4px 8px;
+ border-radius: $pf-border-radius;
+ align-items: center;
+ font-weight: lighter;
+ &:hover {
+ background-color: $pf-minimal-background-hover;
+ }
+ &.pf-highlighted {
+ background-color: $pf-primary-background;
+ color: white;
+ }
}
}
diff --git a/ui/src/assets/widgets/form.scss b/ui/src/assets/widgets/form.scss
index 6aadc68..13b0e77 100644
--- a/ui/src/assets/widgets/form.scss
+++ b/ui/src/assets/widgets/form.scss
@@ -38,6 +38,8 @@
// Add some spacing around forms when placed inside a menu
.pf-menu {
.pf-form {
- margin: 4px;
+ // Compensate for the uneven |4px 0| padding in pf-menu, resulting in an
+ // even 4px padding all around the form.
+ margin: 0 4px;
}
}
diff --git a/ui/src/assets/widgets/menu.scss b/ui/src/assets/widgets/menu.scss
index 83cb9c4..523ca9f 100644
--- a/ui/src/assets/widgets/menu.scss
+++ b/ui/src/assets/widgets/menu.scss
@@ -18,6 +18,7 @@
display: flex;
flex-direction: column;
align-items: stretch;
+ margin: 4px 0;
.pf-menu-item {
font-family: $pf-font;
diff --git a/ui/src/assets/widgets/popup.scss b/ui/src/assets/widgets/popup.scss
index 0b9e666..5b3c0bd 100644
--- a/ui/src/assets/widgets/popup.scss
+++ b/ui/src/assets/widgets/popup.scss
@@ -24,7 +24,7 @@
border: solid 1px $pf-colour-thin-border;
border-radius: $pf-border-radius;
box-shadow: 2px 2px 16px rgba(0, 0, 0, 0.2);
- padding: 4px;
+
.pf-popup-content {
// Ensures all content is rendered above the arrow
position: relative;
diff --git a/ui/src/common/viewer.ts b/ui/src/common/viewer.ts
index 49f1203..9169214 100644
--- a/ui/src/common/viewer.ts
+++ b/ui/src/common/viewer.ts
@@ -22,13 +22,13 @@
sidebar = {
hide: () => {
globals.dispatch(Actions.setSidebar({
- visible: true,
+ visible: false,
}));
},
show:
() => {
globals.dispatch(Actions.setSidebar({
- visible: false,
+ visible: true,
}));
},
isVisible: () => globals.state.sidebarVisible,
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index c3ad90e..15c01f2 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -20,8 +20,10 @@
import {assertExists} from '../base/logging';
import {undoCommonChatAppReplacements} from '../base/string_utils';
import {Actions} from '../common/actions';
+import {CommandWithMatchInfo} from '../common/commands';
import {setTimestampFormat, TimestampFormat} from '../common/time';
import {raf} from '../core/raf_scheduler';
+import {Command} from '../public';
import {addTab} from './bottom_tab';
import {onClickCopy} from './clipboard';
@@ -90,6 +92,7 @@
private pendingPrompt?: Prompt;
static readonly OMNIBOX_INPUT_REF = 'omnibox';
private omniboxInputEl?: HTMLInputElement;
+ private recentCommands: string[] = [];
private enterCommandMode(): void {
this.omniboxMode = OmniboxMode.Command;
@@ -280,6 +283,38 @@
name: 'Search',
callback: () => this.enterSearchMode(true),
},
+ {
+ id: 'perfetto.binder.system_server.Incoming',
+ name: 'Run query: system_server incoming binder graph',
+ callback: () => runQueryInNewTab(
+ `SELECT IMPORT('android.binder');
+ SELECT * FROM android_binder_incoming_graph((SELECT utid FROM thread WHERE name = 'system_server'))`,
+ 'system_server incoming binder graph'),
+ },
+ {
+ id: 'perfetto.binder.system_server.Outgoing',
+ name: 'Run query: system_server outgoing binder graph',
+ callback: () => runQueryInNewTab(
+ `SELECT IMPORT('android.binder');
+ SELECT * FROM android_binder_outgoing_graph((SELECT utid FROM thread WHERE name = 'system_server'))`,
+ 'system_server outgoing binder graph'),
+ },
+ {
+ id: 'perfetto.monitor_contention.system_server',
+ name: 'Run query: system_server monitor_contention graph',
+ callback: () => runQueryInNewTab(
+ `SELECT IMPORT('android.monitor_contention');
+ SELECT * FROM android_monitor_contention_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
+ 'system_server monitor_contention graph'),
+ },
+ {
+ id: 'perfetto.binder.all',
+ name: 'Run query: all process binder graph',
+ callback: () => runQueryInNewTab(
+ `SELECT IMPORT('android.binder');
+ SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
+ 'all process binder graph'),
+ },
];
commands() {
@@ -351,7 +386,7 @@
inputRef: App.OMNIBOX_INPUT_REF,
extraClasses: 'prompt-mode',
closeOnOutsideClick: true,
- options,
+ options: options && [{options}],
selectedOptionIndex: this.omniboxSelectionIndex,
onSelectedOptionChanged: (index) => {
this.omniboxSelectionIndex = index;
@@ -359,6 +394,7 @@
},
onInput: (value) => {
this.omniboxText = value;
+ this.omniboxSelectionIndex = 0;
raf.scheduleFullRedraw();
},
onSubmit: (value, _alt) => {
@@ -372,20 +408,42 @@
renderCommandOmnibox(): m.Children {
const cmdMgr = globals.commandManager;
+
+ // Fuzzy-filter commands by the filter string.
const filteredCmds = cmdMgr.fuzzyFilterCommands(this.omniboxText);
- const options: OmniboxOption[] = filteredCmds.map(({segments, id}) => {
+
+ // Sort commands alphabetically
+ const sortedFilteredCommands =
+ filteredCmds.sort((a, b) => a.name.localeCompare(b.name));
+
+ // Look up recent comands
+ const recents = this.findRecentCommands(filteredCmds);
+
+ const cmdToOpt = ({segments, id}: CommandWithMatchInfo): OmniboxOption => {
return {
key: id,
displayName: segments,
};
- });
+ };
+
+ const recentOptions = recents.map(cmdToOpt);
+ const allOptions = sortedFilteredCommands.map(cmdToOpt);
+
+ const optionCategories = recentOptions.length > 0 ?
+ [
+ {name: 'Recent Commands', options: recentOptions},
+ {name: 'All Commands', options: allOptions},
+ ] :
+ [
+ {name: 'All Commands', options: allOptions},
+ ];
return m(Omnibox, {
value: this.omniboxText,
- placeholder: 'Start typing a command...',
+ placeholder: 'Filter commands...',
inputRef: App.OMNIBOX_INPUT_REF,
extraClasses: 'command-mode',
- options,
+ options: optionCategories,
closeOnSubmit: true,
closeOnOutsideClick: true,
selectedOptionIndex: this.omniboxSelectionIndex,
@@ -395,6 +453,7 @@
},
onInput: (value) => {
this.omniboxText = value;
+ this.omniboxSelectionIndex = 0;
raf.scheduleFullRedraw();
},
onClose: () => {
@@ -405,11 +464,30 @@
raf.scheduleFullRedraw();
},
onSubmit: (key: string) => {
+ this.addRecentCommand(key);
cmdMgr.runCommand(key);
},
});
}
+ // Locates all recent commands within a given list of commands.
+ private findRecentCommands<T extends Command>(cmds: T[]): T[] {
+ const recents: T[] = [];
+ for (const recentCmdId of this.recentCommands) {
+ const cmd = cmds.find(({id}) => id === recentCmdId);
+ if (cmd) {
+ recents.push(cmd);
+ }
+ }
+ return recents;
+ }
+
+ private addRecentCommand(id: string): void {
+ this.recentCommands = this.recentCommands.filter((x) => x !== id);
+ this.recentCommands.unshift(id);
+ this.recentCommands.length = Math.min(this.recentCommands.length, 6);
+ }
+
renderQueryOmnibox(): m.Children {
const ph = 'e.g. select * from sched left join thread using(utid) limit 10';
return m(Omnibox, {
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 2bf320f..b70eb25 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -65,10 +65,6 @@
return slice.process?.pid;
}
-function getUpidFromSlice(slice: SliceDetails): number|undefined {
- return slice.process?.upid;
-}
-
function getProcessNameFromSlice(slice: SliceDetails): string|undefined {
return slice.process?.name;
}
@@ -77,10 +73,6 @@
return slice.name !== undefined;
}
-function hasId(slice: SliceDetails): boolean {
- return slice.id !== undefined;
-}
-
function hasTid(slice: SliceDetails): boolean {
return getTidFromSlice(slice) !== undefined;
}
@@ -95,7 +87,7 @@
const ITEMS: ContextMenuItem[] = [
{
- name: 'Average duration',
+ name: 'Average duration of slice name',
shouldDisplay: (slice: SliceDetails) => hasName(slice),
run: (slice: SliceDetails) => runQueryInNewTab(
`SELECT AVG(dur) / 1e9 FROM slice WHERE name = '${slice.name!}'`,
@@ -103,19 +95,7 @@
),
},
{
- name: 'Binder by TXN',
- shouldDisplay: () => true,
- run: () => runQueryInNewTab(
- `SELECT IMPORT('android.binder');
-
- SELECT *
- FROM android_sync_binder_metrics_by_txn
- ORDER BY client_dur DESC`,
- 'Binder by TXN',
- ),
- },
- {
- name: 'Binder call names',
+ name: 'Binder call names on thread',
shouldDisplay: (slice) =>
hasProcessName(slice) && hasTid(slice) && hasPid(slice),
run: (slice: SliceDetails) => {
@@ -139,18 +119,6 @@
));
},
},
- {
- name: 'Lock graph',
- shouldDisplay: (slice: SliceDetails) => hasId(slice),
- run: (slice: SliceDetails) => runQueryInNewTab(
- `
- SELECT IMPORT('android.monitor_contention');
-
- SELECT * FROM android_monitor_contention_graph(${getUpidFromSlice(slice)});
- `,
- 'Lock graph',
- ),
- },
];
function getSliceContextMenuItems(slice: SliceDetails) {
diff --git a/ui/src/frontend/omnibox.ts b/ui/src/frontend/omnibox.ts
index 4013910..f2d7279 100644
--- a/ui/src/frontend/omnibox.ts
+++ b/ui/src/frontend/omnibox.ts
@@ -41,7 +41,7 @@
view({attrs}: m.Vnode<OmniboxOptionRowAttrs>): void|m.Children {
const {displayName, highlighted, ...htmlAttrs} = attrs;
return m(
- 'li.pf-omnibox-option',
+ 'li',
{
class: classNames(highlighted && 'pf-highlighted'),
...htmlAttrs,
@@ -70,10 +70,24 @@
}
}
+// Omnibox option.
export interface OmniboxOption {
- // The value to place into the omnibox.
+ // The value to place into the omnibox. This is what's returned in onSubmit.
key: string;
- displayName: FuzzySegment[];
+
+ // Display name provided as a string or a list of fuzzy segments to enable
+ // fuzzy match highlighting.
+ displayName: FuzzySegment[]|string;
+}
+
+// A category of omnibox options.
+export interface OmniboxOptionsCategory {
+ // Optional name of the category. If omitted, the divider for this category
+ // shall be omitted.
+ name?: string;
+
+ // List of options in this category.
+ options: OmniboxOption[];
}
export interface OmniboxAttrs {
@@ -95,7 +109,10 @@
// Dropdown items to show. If none are supplied, the omnibox runs in free text
// mode, where anyt text can be input. Otherwise, onSubmit will always be
// called with one of the options.
- options?: OmniboxOption[];
+ // Options are provided in groups called categories. If the category has a
+ // name the name will be listed at the top of the group rendered with a little
+ // divider as well.
+ options?: OmniboxOptionsCategory[];
// Called when the user expresses the intent to "execute" the thing.
onSubmit?: (value: string, mod: boolean, shift: boolean) => void;
@@ -144,10 +161,12 @@
selectedOptionIndex = 0,
} = attrs;
+ const flatOptions = options?.flatMap(({options}) => options);
+
return m(
Popup,
{
- className: 'pf-popup-padded',
+ className: 'pf-omnibox-dropdown',
onPopupMount: (dom: HTMLElement) => this.popupElement = dom,
onPopupUnMount: (_dom: HTMLElement) => this.popupElement = undefined,
isOpen: exists(options),
@@ -174,7 +193,7 @@
this.close(attrs);
}
- if (options) {
+ if (flatOptions) {
if (e.key === 'ArrowUp') {
e.preventDefault();
this.highlightPreviousOption(attrs);
@@ -184,7 +203,7 @@
} else if (e.key === 'Enter') {
e.preventDefault();
- const option = options[selectedOptionIndex];
+ const option = flatOptions[selectedOptionIndex];
if (option) {
closeOnSubmit && this.close(attrs);
@@ -223,23 +242,28 @@
if (!options) return null;
+ let index = 0;
+
if (options.length === 0) {
return m(EmptyState, {header: 'No matching options...'});
} else {
- return m(
- 'ul.pf-omnibox-dropdown',
- options.map(({displayName, key}, index) => {
- return m(OmniboxOptionRow, {
- key,
- displayName: displayName,
- highlighted: index === selectedOptionIndex,
- onclick: () => {
- closeOnSubmit && onClose();
- onSubmit(key, false, false);
- },
- });
- }),
- );
+ return options.map(({name, options}) => {
+ return m(
+ 'ul.pf-omnibox-dropdown',
+ name && m('.pf-omnibox-section-header', name),
+ options.map(({displayName, key}) => {
+ return m(OmniboxOptionRow, {
+ key,
+ displayName: displayName,
+ highlighted: index++ === selectedOptionIndex,
+ onclick: () => {
+ closeOnSubmit && onClose();
+ onSubmit(key, false, false);
+ },
+ });
+ }),
+ );
+ });
}
}
@@ -304,7 +328,7 @@
options = [],
} = attrs;
- const max = options.length - 1;
+ const max = options.reduce((sum, {options}) => sum + options.length, 0) - 1;
onSelectedOptionChanged(Math.min(max, selectedOptionIndex + 1));
}
}