Merge changes I8ed3c55e,I021610d7 into main
* changes:
tp: Migrate stdlib to CREATE PERFETTO INDEX
tp: Fix validation
diff --git a/infra/luci/PRESUBMIT.py b/infra/luci/PRESUBMIT.py
index c1564ce..d46a2ba 100644
--- a/infra/luci/PRESUBMIT.py
+++ b/infra/luci/PRESUBMIT.py
@@ -18,12 +18,15 @@
def CommonChecks(input_api, output_api):
- recipes_py = input_api.os_path.join(input_api.PresubmitLocalPath(),
- 'recipes.py')
+ presubmit_dir = input_api.PresubmitLocalPath()
+ recipes_py = input_api.os_path.join(presubmit_dir, 'recipes.py')
+ recipes_cfg = input_api.os_path.join(
+ presubmit_dir, '..', 'config', 'recipes.cfg'
+ )
return input_api.RunTests([
input_api.Command(
'Run recipe tests',
- ['python3', recipes_py, 'test', 'run'],
+ ['python3', recipes_py, '--package', recipes_cfg, 'test', 'run'],
{},
output_api.PresubmitError,
)
diff --git a/src/trace_redaction/collect_frame_cookies_unittest.cc b/src/trace_redaction/collect_frame_cookies_unittest.cc
index 2a38160..dc6d8fd 100644
--- a/src/trace_redaction/collect_frame_cookies_unittest.cc
+++ b/src/trace_redaction/collect_frame_cookies_unittest.cc
@@ -139,8 +139,6 @@
ASSERT_OK(collect_.End(context));
}
-} // namespace
-
class FrameCookieTest : public testing::Test {
protected:
CollectFrameCookies collect_;
@@ -414,4 +412,6 @@
ASSERT_FALSE(redacted.has_frame_timeline_event());
}
+
+} // namespace
} // namespace perfetto::trace_redaction
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 567deae..4f50c43 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -31,29 +31,32 @@
@import "viz_page";
@import "widgets_page";
@import "plugins_page";
+
+// Widgets - keep these sorted (they should NOT have any inter-dependencies)
@import "widgets/anchor";
@import "widgets/button";
+@import "widgets/callout";
@import "widgets/checkbox";
@import "widgets/details_shell";
+@import "widgets/editor";
@import "widgets/empty_state";
@import "widgets/error";
@import "widgets/form";
@import "widgets/grid_layout";
+@import "widgets/hotkey";
@import "widgets/menu";
@import "widgets/multiselect";
@import "widgets/popup";
@import "widgets/section";
-@import "widgets/timestamp";
@import "widgets/select";
@import "widgets/spinner";
@import "widgets/switch";
+@import "widgets/tag_input";
@import "widgets/text_input";
-@import "widgets/tree";
-@import "widgets/virtual_scroll_container";
-@import "widgets/callout";
-@import "widgets/editor";
-@import "widgets/vega_view";
-@import "widgets/hotkey";
@import "widgets/text_paragraph";
+@import "widgets/timestamp";
+@import "widgets/tree";
@import "widgets/treetable";
+@import "widgets/vega_view";
+@import "widgets/virtual_scroll_container";
@import "widgets/virtual_table";
diff --git a/ui/src/assets/widgets/tag_input.scss b/ui/src/assets/widgets/tag_input.scss
new file mode 100644
index 0000000..c91c608
--- /dev/null
+++ b/ui/src/assets/widgets/tag_input.scss
@@ -0,0 +1,67 @@
+// Copyright (C) 2024 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-tag-input {
+ font-family: $pf-font;
+ font-size: inherit;
+ outline: none; // Disable the default outline
+ border: none; // Disable the default border
+ border-bottom: solid 1px $pf-minimal-foreground; // Thin underline
+ background: none;
+ transition: border $pf-anim-timing, box-shadow $pf-anim-timing,
+ background $pf-anim-timing;
+
+ // Round only the top corners to avoid rounding the edges of the underline
+ border-radius: $pf-border-radius $pf-border-radius 0 0;
+
+ input {
+ outline: none;
+ border: none;
+ background: none;
+ font-family: inherit;
+ font-size: inherit;
+ }
+
+ i {
+ cursor: pointer;
+ font-size: smaller;
+ margin-left: 2px;
+ }
+
+ .pf-tag {
+ border-radius: $pf-border-radius;
+ background: $pf-primary-background;
+ margin-right: 2px;
+ color: $pf-primary-foreground;
+ padding-inline: 4px;
+ white-space: nowrap;
+ }
+
+ // The gentle hover effect indicates this component is interactive
+ &:hover {
+ background: $pf-minimal-background-hover;
+ }
+
+ &:focus-within {
+ background: $pf-minimal-background-hover;
+ border-bottom: solid 1px $pf-primary-background;
+
+ // The box-shadow thickens the bottom border, without adding to the height.
+ // This is the same technique used by materializecss:
+ // See https://materializecss.com/text-inputs.html
+ box-shadow: 0 1px 0 $pf-primary-background;
+ }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 98fc400..37a8eac 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -55,6 +55,7 @@
VirtualTableAttrs,
VirtualTableRow,
} from '../widgets/virtual_table';
+import {TagInput} from '../widgets/tag_input';
const DATA_ENGLISH_LETTER_FREQUENCY = {
table: [
@@ -590,6 +591,32 @@
rows: [],
};
+function TagInputDemo() {
+ const tags: string[] = ['foo', 'bar', 'baz'];
+ let tagInputValue: string = '';
+
+ return {
+ view: () => {
+ return m(TagInput, {
+ tags,
+ value: tagInputValue,
+ onTagAdd: (tag) => {
+ tags.push(tag);
+ tagInputValue = '';
+ raf.scheduleFullRedraw();
+ },
+ onChange: (value) => {
+ tagInputValue = value;
+ },
+ onTagRemove: (index) => {
+ tags.splice(index);
+ raf.scheduleFullRedraw();
+ },
+ });
+ },
+ };
+}
+
export const WidgetsPage = createPage({
view() {
return m(
@@ -1205,6 +1232,15 @@
return m(VirtualTable, attrs);
},
}),
+ m(WidgetShowcase, {
+ label: 'Tag Input',
+ description: `
+ TagInput displays Tag elements inside an input, followed by an
+ interactive text input. The container is styled to look like a
+ TextInput, but the actual editable element appears after the last tag.
+ Clicking anywhere on the container will focus the text input.`,
+ renderWidget: () => m(TagInputDemo),
+ }),
);
},
});
diff --git a/ui/src/plugins/org.kernel.Wattson/index.ts b/ui/src/plugins/org.kernel.Wattson/index.ts
index dcc8315..a8fb89b 100644
--- a/ui/src/plugins/org.kernel.Wattson/index.ts
+++ b/ui/src/plugins/org.kernel.Wattson/index.ts
@@ -23,7 +23,6 @@
PluginContextTrace,
PluginDescriptor,
} from '../../public';
-import {NUM} from '../../trace_processor/query_result';
class Wattson implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
@@ -36,44 +35,21 @@
ctx.registerStaticTrack({
uri: `perfetto.CpuSubsystemEstimate#CPU${cpu}`,
displayName: `Cpu${cpu} Estimate`,
- kind: `CpuEstimateTrack`,
+ kind: `CpuSubsystemEstimateTrack`,
trackFactory: ({trackKey}) =>
new CpuSubsystemEstimateTrack(ctx.engine, trackKey, queryKey),
groupName: `Wattson`,
});
}
+
ctx.registerStaticTrack({
- uri: `perfetto.CpuSubsystemEstimate#Static`,
- displayName: `Static Estimate`,
- kind: `CpuEstimateTrack`,
+ uri: `perfetto.CpuSubsystemEstimate#ScuInterconnect`,
+ displayName: `SCU Interconnect Estimate`,
+ kind: `CpuSubsystemEstimateTrack`,
trackFactory: ({trackKey}) =>
- new CpuSubsystemEstimateTrack(ctx.engine, trackKey, `static_curve`),
+ new CpuSubsystemEstimateTrack(ctx.engine, trackKey, `scu`),
groupName: `Wattson`,
});
-
- // Cache estimates for remainder of CPU subsystem
- const L3RowCount = await ctx.engine.query(`
- SELECT
- COUNT(*) as numRows
- FROM _system_state_curves
- WHERE l3_hit_value is NOT NULL AND l3_hit_value != 0
- `);
- const numL3Rows = L3RowCount.firstRow({numRows: NUM}).numRows;
-
- if (numL3Rows > 0) {
- const queryKeys: string[] = [`l3_hit_value`, `l3_miss_value`];
- for (const queryKey of queryKeys) {
- const keyName = queryKey.replace(`_value`, ``).replace(`l3`, `L3`);
- ctx.registerStaticTrack({
- uri: `perfetto.CpuSubsystemEstimate#${keyName}`,
- displayName: `${keyName} Estimate`,
- kind: `CacheEstimateTrack`,
- trackFactory: ({trackKey}) =>
- new CpuSubsystemEstimateTrack(ctx.engine, trackKey, queryKey),
- groupName: `Wattson`,
- });
- }
- }
}
}
@@ -92,26 +68,25 @@
protected getDefaultCounterOptions(): CounterOptions {
const options = super.getDefaultCounterOptions();
+ options.yRangeSharingKey = `CpuSubsystem`;
options.unit = `mW`;
return options;
}
getSqlSource() {
- const isL3 = this.queryKey.startsWith(`l3`);
- return isL3
- ? `
- select
- ts,
- -- scale by 1000 because dividing by ns and LUTs are scaled by 10^6
- ${this.queryKey} * 1000 / dur as value
- from _system_state_curves
- `
- : `
- select
- ts,
- ${this.queryKey} as value
- from _system_state_curves
- `;
+ if (this.queryKey.startsWith(`cpu`)) {
+ return `select ts, ${this.queryKey} as value from _system_state_curves`;
+ } else {
+ return `
+ select
+ ts,
+ -- L3 values are scaled by 1000 because it's divided by ns and L3 LUTs
+ -- are scaled by 10^6. This brings to same units as static_curve (mW)
+ ((IFNULL(l3_hit_value, 0) + IFNULL(l3_miss_value, 0)) * 1000 / dur)
+ + static_curve as value
+ from _system_state_curves
+ `;
+ }
}
}
diff --git a/ui/src/widgets/tag_input.ts b/ui/src/widgets/tag_input.ts
new file mode 100644
index 0000000..ed8a5f3
--- /dev/null
+++ b/ui/src/widgets/tag_input.ts
@@ -0,0 +1,141 @@
+// Copyright (C) 2024 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 {HTMLAttrs} from './common';
+import {Icon} from './icon';
+import {findRef} from '../base/dom_utils';
+
+export interface TagInputAttrs extends HTMLAttrs {
+ value?: string;
+ onChange?: (text: string) => void;
+ tags: string[];
+ onTagAdd: (text: string) => void;
+ onTagRemove: (index: number) => void;
+}
+
+const INPUT_REF = 'input';
+
+/**
+ * TagInput displays Tag elements inside an input, followed by an interactive
+ * text input. The container is styled to look like a TextInput, but the actual
+ * editable element appears after the last tag. Clicking anywhere on the
+ * container will focus the text input.
+ *
+ * To use this widget, the user must provide the tags as a list of strings, and
+ * provide callbacks which are called when the user modifies the list of tags,
+ * either adding a new tag by typing and pressing enter, or removing a tag by
+ * clicking the close button on a tag.
+ *
+ * The text value can be optionally be controlled, which allows access to this
+ * value from outside the widget.
+ *
+ * Uncontrolled example:
+ *
+ * In this example, we only have access to the list of tags from outside.
+ *
+ * ```
+ * const tags = [];
+ *
+ * m(TagInput, {
+ * tags,
+ * onTagAdd: (tag) => tags.push(tag),
+ * onTagRemove: (index) => tags.splice(index),
+ * });
+ * ```
+ *
+ * Controlled example:
+ *
+ * In this example we have complete control over the value in the text field.
+ *
+ * ```
+ * const tags = [];
+ * let value = '';
+ *
+ * m(TagInput, {
+ * tags,
+ * onTagAdd: (tag) => {
+ * tags.push(tag);
+ * value = ''; // The value is controlled so we must manually clear it here
+ * },
+ * onTagRemove: (index) => tags.splice(index),
+ * value,
+ * onChange: (x) => value = x,
+ * });
+ * ```
+ *
+ */
+
+export class TagInput implements m.ClassComponent<TagInputAttrs> {
+ view({attrs}: m.CVnode<TagInputAttrs>) {
+ const {value, onChange, tags, onTagAdd, onTagRemove, ...htmlAttrs} = attrs;
+
+ const valueIsControlled = value !== undefined;
+
+ return m(
+ '.pf-tag-input',
+ {
+ onclick: (ev: PointerEvent) => {
+ const target = ev.currentTarget as HTMLElement;
+ const inputElement = findRef(target, INPUT_REF);
+ if (inputElement) {
+ (inputElement as HTMLInputElement).focus();
+ }
+ },
+ ...htmlAttrs,
+ },
+ tags.map((tag, index) => renderTag(tag, () => onTagRemove(index))),
+ m('input', {
+ ref: INPUT_REF,
+ value,
+ onkeydown: (ev: KeyboardEvent) => {
+ if (ev.key === 'Enter') {
+ const el = ev.target as HTMLInputElement;
+ if (el.value.trim() !== '') {
+ onTagAdd(el.value);
+ if (!valueIsControlled) {
+ el.value = '';
+ }
+ }
+ } else if (ev.key === 'Backspace') {
+ const el = ev.target as HTMLInputElement;
+ if (el.value !== '') return;
+ if (tags.length === 0) return;
+
+ const lastTagIndex = tags.length - 1;
+ onTagRemove(lastTagIndex);
+ }
+ },
+ oninput: (ev: InputEvent) => {
+ const el = ev.target as HTMLInputElement;
+ onChange?.(el.value);
+ },
+ }),
+ );
+ }
+}
+
+function renderTag(text: string, onRemove: () => void): m.Children {
+ return m(
+ 'span.pf-tag',
+ text,
+ m(Icon, {
+ icon: 'close',
+ onclick: () => {
+ onRemove();
+ },
+ }),
+ );
+}