Merge "perfetto: extract out lifecycle management from the window operator" into main
diff --git a/include/perfetto/tracing/track.h b/include/perfetto/tracing/track.h
index 0d5a364..c12f231 100644
--- a/include/perfetto/tracing/track.h
+++ b/include/perfetto/tracing/track.h
@@ -204,7 +204,7 @@
using CounterType =
perfetto::protos::gen::CounterDescriptor::BuiltinCounterType;
- // |name| must be a string with static lifetime.
+ // |name| must outlive this object.
constexpr explicit CounterTrack(const char* name,
Track parent = MakeProcessTrack())
: Track(internal::Fnv1a(name) ^ kCounterMagic, parent),
@@ -212,7 +212,7 @@
category_(nullptr) {}
// |unit_name| is a free-form description of the unit used by this counter. It
- // must have static lifetime.
+ // must outlive this object.
constexpr CounterTrack(const char* name,
const char* unit_name,
Track parent = MakeProcessTrack())
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 767e90e..ea728bc 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -39,21 +39,21 @@
TraceRedactor redactor;
// Add all collectors.
- redactor.collectors()->emplace_back(new FindPackageUid());
- redactor.collectors()->emplace_back(new BuildTimeline());
+ redactor.emplace_collect<FindPackageUid>();
+ redactor.emplace_collect<BuildTimeline>();
// Add all builders.
- redactor.builders()->emplace_back(new PopulateAllowlists());
- redactor.builders()->emplace_back(new OptimizeTimeline());
+ redactor.emplace_build<PopulateAllowlists>();
+ redactor.emplace_build<OptimizeTimeline>();
// Add all transforms.
- redactor.transformers()->emplace_back(new PrunePackageList());
- redactor.transformers()->emplace_back(new ScrubTracePacket());
- redactor.transformers()->emplace_back(new ScrubFtraceEvents());
- redactor.transformers()->emplace_back(new ScrubProcessTrees());
- redactor.transformers()->emplace_back(new ScrubTaskRename());
- redactor.transformers()->emplace_back(new RedactSchedSwitch());
- redactor.transformers()->emplace_back(new RedactSchedWaking());
+ redactor.emplace_transform<PrunePackageList>();
+ redactor.emplace_transform<ScrubTracePacket>();
+ redactor.emplace_transform<ScrubFtraceEvents>();
+ redactor.emplace_transform<ScrubProcessTrees>();
+ redactor.emplace_transform<ScrubTaskRename>();
+ redactor.emplace_transform<RedactSchedSwitch>();
+ redactor.emplace_transform<RedactSchedWaking>();
Context context;
context.package_name = package_name;
diff --git a/src/trace_redaction/redact_sched_switch_integrationtest.cc b/src/trace_redaction/redact_sched_switch_integrationtest.cc
index 7cb516b..26dd400 100644
--- a/src/trace_redaction/redact_sched_switch_integrationtest.cc
+++ b/src/trace_redaction/redact_sched_switch_integrationtest.cc
@@ -50,10 +50,10 @@
class RedactSchedSwitchIntegrationTest : public testing::Test {
protected:
void SetUp() override {
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.collectors()->emplace_back(new BuildTimeline());
- redactor_.builders()->emplace_back(new OptimizeTimeline());
- redactor_.transformers()->emplace_back(new RedactSchedSwitch());
+ redactor_.emplace_collect<FindPackageUid>();
+ redactor_.emplace_collect<BuildTimeline>();
+ redactor_.emplace_build<OptimizeTimeline>();
+ redactor_.emplace_transform<RedactSchedSwitch>();
context_.package_name = kPackageName;
diff --git a/src/trace_redaction/redact_sched_waking_integrationtest.cc b/src/trace_redaction/redact_sched_waking_integrationtest.cc
index 6b6a990..b83f7e9 100644
--- a/src/trace_redaction/redact_sched_waking_integrationtest.cc
+++ b/src/trace_redaction/redact_sched_waking_integrationtest.cc
@@ -50,10 +50,10 @@
class RedactSchedWakingIntegrationTest : public testing::Test {
protected:
void SetUp() override {
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.collectors()->emplace_back(new BuildTimeline());
- redactor_.builders()->emplace_back(new OptimizeTimeline());
- redactor_.transformers()->emplace_back(new RedactSchedWaking());
+ redactor_.emplace_collect<FindPackageUid>();
+ redactor_.emplace_collect<BuildTimeline>();
+ redactor_.emplace_build<OptimizeTimeline>();
+ redactor_.emplace_transform<RedactSchedWaking>();
context_.package_name = kPackageName;
diff --git a/src/trace_redaction/scrub_process_trees_integrationtest.cc b/src/trace_redaction/scrub_process_trees_integrationtest.cc
index c1ca7b8..61a7bcd 100644
--- a/src/trace_redaction/scrub_process_trees_integrationtest.cc
+++ b/src/trace_redaction/scrub_process_trees_integrationtest.cc
@@ -58,10 +58,10 @@
// BuildTimeline depends on.... nothing
// FindPackageUid depends on... nothing
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.collectors()->emplace_back(new BuildTimeline());
- redactor_.builders()->emplace_back(new OptimizeTimeline());
- redactor_.transformers()->emplace_back(new ScrubProcessTrees());
+ redactor_.emplace_collect<FindPackageUid>();
+ redactor_.emplace_collect<BuildTimeline>();
+ redactor_.emplace_build<OptimizeTimeline>();
+ redactor_.emplace_transform<ScrubProcessTrees>();
// In this case, the process and package have the same name.
context_.package_name = kProcessName;
diff --git a/src/trace_redaction/scrub_task_rename_integrationtest.cc b/src/trace_redaction/scrub_task_rename_integrationtest.cc
index eb75208..dee77e6 100644
--- a/src/trace_redaction/scrub_task_rename_integrationtest.cc
+++ b/src/trace_redaction/scrub_task_rename_integrationtest.cc
@@ -55,10 +55,10 @@
void SetUp() override {
// In order for ScrubTaskRename to work, it needs the timeline. All
// registered primitives are there to generate the timeline.
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.collectors()->emplace_back(new BuildTimeline());
- redactor_.builders()->emplace_back(new OptimizeTimeline());
- redactor_.transformers()->emplace_back(new ScrubTaskRename());
+ redactor_.emplace_collect<FindPackageUid>();
+ redactor_.emplace_collect<BuildTimeline>();
+ redactor_.emplace_build<OptimizeTimeline>();
+ redactor_.emplace_transform<ScrubTaskRename>();
context_.package_name = kPackageName;
diff --git a/src/trace_redaction/trace_redactor.h b/src/trace_redaction/trace_redactor.h
index 82ec371..7da6b3d 100644
--- a/src/trace_redaction/trace_redactor.h
+++ b/src/trace_redaction/trace_redactor.h
@@ -45,16 +45,31 @@
std::string_view dest_filename,
Context* context) const;
- std::vector<std::unique_ptr<CollectPrimitive>>* collectors() {
- return &collectors_;
+ // T must be derived from trace_redaction::CollectPrimitive.
+ template <typename T>
+ T* emplace_collect() {
+ auto uptr = std::make_unique<T>();
+ auto* ptr = uptr.get();
+ collectors_.push_back(std::move(uptr));
+ return ptr;
}
- std::vector<std::unique_ptr<BuildPrimitive>>* builders() {
- return &builders_;
+ // T must be derived from trace_redaction::BuildPrimitive.
+ template <typename T>
+ T* emplace_build() {
+ auto uptr = std::make_unique<T>();
+ auto* ptr = uptr.get();
+ builders_.push_back(std::move(uptr));
+ return ptr;
}
- std::vector<std::unique_ptr<TransformPrimitive>>* transformers() {
- return &transformers_;
+ // T must be derived from trace_redaction::TransformPrimitive.
+ template <typename T>
+ T* emplace_transform() {
+ auto uptr = std::make_unique<T>();
+ auto* ptr = uptr.get();
+ transformers_.push_back(std::move(uptr));
+ return ptr;
}
private:
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
index 3ee73da..db3e23f 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -89,8 +89,8 @@
void SetUp() override {
TraceRedactorIntegrationTest::SetUp();
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.transformers()->emplace_back(new PrunePackageList());
+ redactor_.emplace_collect<FindPackageUid>();
+ redactor_.emplace_transform<PrunePackageList>();
}
std::vector<protozero::ConstBytes> GetPackageInfos(
@@ -210,10 +210,10 @@
void SetUp() override {
TraceRedactorIntegrationTest::SetUp();
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.builders()->emplace_back(new PopulateAllowlists());
- redactor_.transformers()->emplace_back(new ScrubTracePacket());
- redactor_.transformers()->emplace_back(new ScrubFtraceEvents());
+ redactor_.emplace_collect<FindPackageUid>();
+ redactor_.emplace_build<PopulateAllowlists>();
+ redactor_.emplace_transform<ScrubTracePacket>();
+ redactor_.emplace_transform<ScrubFtraceEvents>();
}
static base::StatusOr<protozero::ConstBytes> FindFirstFtraceEvents(
diff --git a/test/data/chrome/chrome_input_with_frame_view.pftrace.sha256 b/test/data/chrome/chrome_input_with_frame_view.pftrace.sha256
index ea5a606..d0943a8 100644
--- a/test/data/chrome/chrome_input_with_frame_view.pftrace.sha256
+++ b/test/data/chrome/chrome_input_with_frame_view.pftrace.sha256
@@ -1 +1 @@
-1e4e1b7098c3c1b900d31fa6d6791e7b022e85ecebbb560123ce7139b3f82231
\ No newline at end of file
+a93548822e481508c728ccc5da3ad34afcd0aec02ca7a7a4dad84ff340ee5975
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index e21997c..e82ef5c 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-8e2f7043d233187a8e2229e3bfd64c3a21522e52036bf4135641c4b22ff2a40d
\ No newline at end of file
+c40361fe7a34f3506e0b84d585ea15eb07d111bf0fa49d3976f40738e3448c7a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index 22796d2..260ea4f 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-804eb26f54e147bc536d14354cc1694f748e598b7baaaa50cfd41e137781cbac
\ No newline at end of file
+0954283d6fc7beb554ffbaee5afe354aed896a7eeefe2475e14c1ad64327a6f7
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
index 95e467d..880b363 100644
--- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
@@ -1 +1 @@
-8948301117c29da08b8b616501462a3012d3afd58e94bc8fad46eb8ed3199d23
\ No newline at end of file
+8d0cb7b3d4794c4f036fb851dd8a4726e7e6c90f15657b86c06a1fabe2c7984c
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
index 2e11179..e327bd1 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
@@ -1 +1 @@
-cafa4721383e4b0d4425ec5a1b0f738a91d665196957b70e535df0ccc40e54b8
\ No newline at end of file
+9e84ad0881a56d3731066604e973b6297dee7b9a602e6b35013560fd0bc8fd39
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index 8b12476..1d3de51 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-09f18560b7c44f2fb3fde256b6de77cf3e7688dd2f7213a2df73534b29cc6104
\ No newline at end of file
+eb92c26c12d039aac315e3514bc24ebfcf392914309b28bc83a172127fe60250
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index 32ead5f..11dfc47 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-5f4d43c74cc565e94dc9e77c522dbe0d84c14a1f1c7d123a7e1c6b4d338c25cc
\ No newline at end of file
+1d80ba6fc24a1db8252e24e0d4250787bf7da4f6f3f7efe4e71b63a2b42c6e23
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
index f394224..ac18e72 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
@@ -1 +1 @@
-e885a74d6e5eaa497a79f018bb60bbd6ddc664e22ff7c62730032916ff58f610
\ No newline at end of file
+99c00e663442fd3ca7913b5ce7bea350af82f5ec8422d16f49175c91d484b52a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
index 0669589..ff1df49 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
@@ -1 +1 @@
-7e0810f73b905ac2586f579efe0e45f9997da071329a55cabd05fb6e7b9fd26b
\ No newline at end of file
+f345cfd5f9f2c5922d15c691dbe76d5d50ccfecdf35f2fd0033c1462299b0d17
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index 20f9f33..97c82b1 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-c006f2550408db44cf35f8e2b18cae1592568fe7d8a5fcdc1d454eb40a7cf34e
\ No newline at end of file
+46d1d64e852be22cb86a7eb6868aea3007ce04c663e9667ae55b71d5a0fad6b7
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index 6bc94e4..f60a4ff 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-8374cbf36adb6de6371002ca3af76e53186cf6138eed82bdf14902fe874dd7dc
\ No newline at end of file
+cd2fafc00d2f2ab761a7c17a0bdc9e606e3996fe20c17ff7e0b61a0e11c55199
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
index 72a3447..c417604 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
@@ -1 +1 @@
-f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
+049301241729848f47474868766daf513e7f739efddbfc3dc3f96200f9b63bc6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index 20f9f33..97c82b1 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-c006f2550408db44cf35f8e2b18cae1592568fe7d8a5fcdc1d454eb40a7cf34e
\ No newline at end of file
+46d1d64e852be22cb86a7eb6868aea3007ce04c663e9667ae55b71d5a0fad6b7
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
index 72a3447..c417604 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
@@ -1 +1 @@
-f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
+049301241729848f47474868766daf513e7f739efddbfc3dc3f96200f9b63bc6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
index 72a3447..c417604 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
@@ -1 +1 @@
-f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
+049301241729848f47474868766daf513e7f739efddbfc3dc3f96200f9b63bc6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index a8a5277..c69fd14 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-e37a6224d98083ee764f849bc02af11ebbd09574b3ef0dc51837057e0ec9324a
\ No newline at end of file
+4b0c253d17c45c7e787e7b9cc484a9c0af38bcf77a92645266f15879af92d8e2
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index 20f9f33..97c82b1 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-c006f2550408db44cf35f8e2b18cae1592568fe7d8a5fcdc1d454eb40a7cf34e
\ No newline at end of file
+46d1d64e852be22cb86a7eb6868aea3007ce04c663e9667ae55b71d5a0fad6b7
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
index 72a3447..c417604 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
@@ -1 +1 @@
-f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
+049301241729848f47474868766daf513e7f739efddbfc3dc3f96200f9b63bc6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
index 72a3447..c417604 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
@@ -1 +1 @@
-f050c13da6a113631d22b24fcf839026cdb848815119e46f10d2472ce522a3a2
\ No newline at end of file
+049301241729848f47474868766daf513e7f739efddbfc3dc3f96200f9b63bc6
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out
index f3d91cd..15270ab 100644
--- a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out
+++ b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out
@@ -1,4 +1,2 @@
"cause_of_jank","sub_cause_of_jank","delay_since_last_frame","vsync_interval"
-"RendererCompositorQueueingDelay","[NULL]",33.462000,16.368000
-"RendererCompositorFinishedToBeginImplFrame","[NULL]",100.274000,16.368000
-"RendererCompositorQueueingDelay","[NULL]",33.404000,16.368000
+"RendererCompositorQueueingDelay","[NULL]",22.331000,10.483000
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out
index 2081e06..f070675 100644
--- a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out
+++ b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out
@@ -1,2 +1,2 @@
"delayed_frame_percentage"
-1.030928
+0.282486
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
index 80b679c..ae641d4 100755
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -112,7 +112,6 @@
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals;
SELECT
- id,
ts,
dur,
track_id,
@@ -122,14 +121,11 @@
delayed_frame_count,
frame_jank_ts,
frame_jank_dur
- FROM chrome_janky_event_latencies_v3
- ORDER by id;
+ FROM chrome_janky_event_latencies_v3;
""",
out=Csv("""
- "id","ts","dur","track_id","name","cause_of_jank","sub_cause_of_jank","delayed_frame_count","frame_jank_ts","frame_jank_dur"
- 29926,174795897267797,48088000,1431,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,174795928261797,17094000
- 38463,174796315541797,131289000,2163,"EventLatency","RendererCompositorFinishedToBeginImplFrame","[NULL]",5,174796362924797,83906000
- 88876,174799556245797,49856000,4329,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,174799589065797,17036000
+ "ts","dur","track_id","name","cause_of_jank","sub_cause_of_jank","delayed_frame_count","frame_jank_ts","frame_jank_dur"
+ 1035869386651926,60311000,2314,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,1035869435114926,11847999
"""))
def test_chrome_janky_frame_presentation_intervals(self):
@@ -144,16 +140,13 @@
dur,
cause_of_jank,
sub_cause_of_jank,
- delayed_frame_count,
- event_latency_id
+ delayed_frame_count
FROM chrome_janky_frame_presentation_intervals
ORDER by id;
""",
out=Csv("""
- "id","ts","dur","cause_of_jank","sub_cause_of_jank","delayed_frame_count","event_latency_id"
- 1,174795928261797,17094000,"RendererCompositorQueueingDelay","[NULL]",1,29926
- 2,174796362924797,83906000,"RendererCompositorFinishedToBeginImplFrame","[NULL]",5,38463
- 3,174799589065797,17036000,"RendererCompositorQueueingDelay","[NULL]",1,88876
+ "id","ts","dur","cause_of_jank","sub_cause_of_jank","delayed_frame_count"
+ 1,1035869435114926,11847999,"RendererCompositorQueueingDelay","[NULL]",1
"""))
def test_chrome_scroll_stats(self):
@@ -174,9 +167,10 @@
""",
out=Csv("""
"scroll_id","missed_vsyncs","frame_count","presented_frame_count","janky_frame_count","janky_frame_percent"
- 1186,6,110,105,2,1.900000
- 1889,"[NULL]",101,102,0,0.000000
- 2506,1,84,84,1,1.190000
+ 4328,"[NULL]",109,110,0,0.000000
+ 4471,"[NULL]",117,118,0,0.000000
+ 4620,"[NULL]",5,4,0,0.000000
+ 4652,1,122,122,1,0.820000
"""))
def test_chrome_scroll_jank_intervals_v3(self):
@@ -194,9 +188,7 @@
""",
out=Csv("""
"id","ts","dur"
- 1,174795928261797,17094000
- 2,174796362924797,83906000
- 3,174799589065797,17036000
+ 1,1035869435114926,11847999
"""))
def test_chrome_presented_scroll_offsets(self):
return DiffTestBlueprint(
diff --git a/tools/install-build-deps b/tools/install-build-deps
index ab15251..a3befb0 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -165,13 +165,13 @@
# tools/clang/scripts/update.py.
Dependency(
'buildtools/linux64/clang.tgz',
- 'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
- '7b33138d8592199f97d132242d7b3e10f460c5c9655d49a3ad3767218fba7a77',
+ 'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tgz',
+ '6741cc1083f935795330b6e04617ac891a7b5d2b5647b664c5b0fccc354adb43',
'linux', 'x64'),
Dependency(
'buildtools/win/clang.tgz',
- 'https://commondatastorage.googleapis.com/chromium-browser-clang/Win/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
- 'c8e1c41eb36aef6e63d65755d4746f68688c2fcefca44777a205d412c83d25a1',
+ 'https://commondatastorage.googleapis.com/chromium-browser-clang/Win/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tgz',
+ 'f627080ed53d4c156f089323e04fa3690c8bb459110b62cd1952b0e1f0755987',
'windows', 'x64'),
]
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 5d29857..5f4dd4d 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
},
{
"name": "canary",
- "rev": "5457e2afae21f167ef490c7e76a5e3fc6c53e04a"
+ "rev": "58439240cbad83689a1fbafd8234a379a56be7c1"
},
{
"name": "autopush",
diff --git a/ui/src/assets/panel_container.scss b/ui/src/assets/panel_container.scss
index 6163353..2356ff8 100644
--- a/ui/src/assets/panel_container.scss
+++ b/ui/src/assets/panel_container.scss
@@ -20,24 +20,13 @@
// instead.
user-select: none;
- .pf-panels {
- // Make this a positioned element so .pf-scroll-limiter is positioned
- // relative to this element.
- position: relative;
+ .pf-panel-stack {
+ position: relative; // Position overlay relative to this element
- // In the scrolling case, since the canvas is overdrawn and continuously
- // repositioned, we need the canvas to be in a div with overflow hidden and
- // height equalling the total height of the content to prevent scrolling
- // height from growing.
- .pf-scroll-limiter {
+ .pf-overlay {
position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- overflow: hidden;
-
- // Make this overlay invisible to pointer events.
- pointer-events: none;
+ inset: 0; // Shorthand for [top, left, right, bottom]: 0
+ pointer-events: none; // Make this overlay invisible to pointer events
}
.pf-panel {
diff --git a/ui/src/assets/viewer_page.scss b/ui/src/assets/viewer_page.scss
index 3fdd737..2ccc9eb 100644
--- a/ui/src/assets/viewer_page.scss
+++ b/ui/src/assets/viewer_page.scss
@@ -24,13 +24,15 @@
z-index: 1;
flex-grow: 0;
flex-shrink: 0;
- overflow: hidden;
+ overflow-x: hidden;
overflow-y: auto;
+ scrollbar-gutter: stable;
}
.scrolling-panel-container {
overflow-x: hidden;
overflow-y: auto;
+ scrollbar-gutter: stable;
flex: 1 1 auto;
will-change: transform; // Force layer creation.
}
@@ -40,10 +42,21 @@
overflow: auto;
}
- .header-panel-container {
- overflow: visible;
+ .header {
+ display: flex;
+ flex-direction: row;
box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3);
z-index: 2;
+
+ .header-panel-container {
+ flex-grow: 1;
+ }
+
+ .scrollbar-spacer-vertical {
+ scrollbar-gutter: stable;
+ overflow-y: scroll;
+ visibility: hidden;
+ }
}
.pan-and-zoom-content {
diff --git a/ui/src/base/dom_utils.ts b/ui/src/base/dom_utils.ts
index 9dca373..3c39e9c 100644
--- a/ui/src/base/dom_utils.ts
+++ b/ui/src/base/dom_utils.ts
@@ -82,25 +82,3 @@
return {x: e.offsetX, y: e.offsetY};
}
-
-function calculateScrollbarWidth() {
- const outer = document.createElement('div');
- outer.style.overflowY = 'scroll';
- const inner = document.createElement('div');
- outer.appendChild(inner);
- document.body.appendChild(outer);
- const width =
- outer.getBoundingClientRect().width - inner.getBoundingClientRect().width;
- document.body.removeChild(outer);
- return width;
-}
-
-let cachedScrollBarWidth: number | undefined = undefined;
-
-// Calculate the space a scrollbar takes up.
-export function getScrollbarWidth() {
- if (cachedScrollBarWidth === undefined) {
- cachedScrollBarWidth = calculateScrollbarWidth();
- }
- return cachedScrollBarWidth;
-}
diff --git a/ui/src/base/geom.ts b/ui/src/base/geom.ts
new file mode 100644
index 0000000..f569400
--- /dev/null
+++ b/ui/src/base/geom.ts
@@ -0,0 +1,59 @@
+// 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.
+
+export interface Rect {
+ readonly left: number;
+ readonly top: number;
+ readonly right: number;
+ readonly bottom: number;
+}
+
+export interface Size {
+ readonly width: number;
+ readonly height: number;
+}
+
+export function intersectRects(a: Rect, b: Rect): Rect {
+ return {
+ top: Math.max(a.top, b.top),
+ left: Math.max(a.left, b.left),
+ bottom: Math.min(a.bottom, b.bottom),
+ right: Math.min(a.right, b.right),
+ };
+}
+
+export function expandRect(r: Rect, amount: number): Rect {
+ return {
+ top: r.top - amount,
+ left: r.left - amount,
+ bottom: r.bottom + amount,
+ right: r.right + amount,
+ };
+}
+
+export function rebaseRect(r: Rect, x: number, y: number): Rect {
+ return {
+ left: r.left - x,
+ right: r.right - x,
+ top: r.top - y,
+ bottom: r.bottom - y,
+ };
+}
+
+export function rectSize(r: Rect): Size {
+ return {
+ width: r.right - r.left,
+ height: r.bottom - r.top,
+ };
+}
diff --git a/ui/src/base/geom_unittest.ts b/ui/src/base/geom_unittest.ts
new file mode 100644
index 0000000..628d053
--- /dev/null
+++ b/ui/src/base/geom_unittest.ts
@@ -0,0 +1,52 @@
+// 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 {intersectRects, expandRect, rebaseRect, rectSize, Rect} from './geom';
+
+describe('intersectRects', () => {
+ it('should correctly intersect two overlapping rects', () => {
+ const a: Rect = {left: 1, top: 1, right: 4, bottom: 4};
+ const b: Rect = {left: 2, top: 2, right: 5, bottom: 5};
+ const result = intersectRects(a, b);
+ expect(result).toEqual({left: 2, top: 2, right: 4, bottom: 4});
+ });
+ // Note: Non-overlapping rects are not supported and thus not tested
+});
+
+describe('expandRect', () => {
+ it('should correctly expand a rect by a given amount', () => {
+ const rect: Rect = {left: 1, top: 1, right: 3, bottom: 3};
+ const amount = 1;
+ const result = expandRect(rect, amount);
+ expect(result).toEqual({left: 0, top: 0, right: 4, bottom: 4});
+ });
+});
+
+describe('rebaseRect', () => {
+ it('should correctly rebase a rect', () => {
+ const rect: Rect = {left: 2, top: 2, right: 5, bottom: 5};
+ const x = 1;
+ const y = 1;
+ const result = rebaseRect(rect, x, y);
+ expect(result).toEqual({left: 1, top: 1, right: 4, bottom: 4});
+ });
+});
+
+describe('rectSize', () => {
+ it('should correctly calculate the size of a rect', () => {
+ const rect: Rect = {left: 1, top: 1, right: 4, bottom: 3};
+ const result = rectSize(rect);
+ expect(result).toEqual({width: 3, height: 2});
+ });
+});
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index 46a7f27..98730e0 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -42,7 +42,7 @@
function roundAway(n: number): number {
const exp = Math.ceil(Math.log10(Math.max(Math.abs(n), 1)));
const pow10 = Math.pow(10, exp);
- return Math.sign(n) * (Math.ceil(Math.abs(n) / (pow10 / 4)) * (pow10 / 4));
+ return Math.sign(n) * (Math.ceil(Math.abs(n) / (pow10 / 20)) * (pow10 / 20));
}
function toLabel(n: number): string {
@@ -752,8 +752,13 @@
}
if (options.yRangeRounding === 'human_readable') {
- yMax = roundAway(yMax);
- yMin = roundAway(yMin);
+ if (options.yDisplay === 'log') {
+ yMax = Math.log(roundAway(Math.exp(yMax)));
+ yMin = Math.log(roundAway(Math.exp(yMin)));
+ } else {
+ yMax = roundAway(yMax);
+ yMin = roundAway(yMin);
+ }
}
const sharer = RangeSharer.get();
diff --git a/ui/src/frontend/debug_tracks.ts b/ui/src/frontend/debug_tracks.ts
index 6ff6297..0a39612 100644
--- a/ui/src/frontend/debug_tracks.ts
+++ b/ui/src/frontend/debug_tracks.ts
@@ -23,11 +23,13 @@
export const DEBUG_SLICE_TRACK_URI = 'perfetto.DebugSlices';
export const DEBUG_COUNTER_TRACK_URI = 'perfetto.DebugCounter';
-// Names of the columns of the underlying view to be used as ts / dur / name.
+// Names of the columns of the underlying view to be used as
+// ts / dur / name / pivot.
export interface SliceColumns {
ts: string;
dur: string;
name: string;
+ pivot?: string;
}
export interface DebugTrackV2CreateConfig {
@@ -87,6 +89,43 @@
return actions;
}
+export async function addPivotDebugSliceTracks(
+ engine: EngineProxy,
+ data: SqlDataSource,
+ trackName: string,
+ sliceColumns: SliceColumns,
+ argColumns: string[],
+ config?: DebugTrackV2CreateConfig,
+) {
+ if (sliceColumns.pivot) {
+ // Get distinct values to group by
+ const pivotValues = await engine.query(`
+ with all_vals as (${data.sqlSource})
+ select DISTINCT ${sliceColumns.pivot} from all_vals;`);
+
+ const iter = pivotValues.iter({});
+
+ for (; iter.valid(); iter.next()) {
+ const pivotDataSource: SqlDataSource = {
+ sqlSource: `select * from
+ (${data.sqlSource})
+ where ${sliceColumns.pivot} = '${iter.get(sliceColumns.pivot)}'`,
+ };
+
+ const actions = await createDebugSliceTrackActions(
+ engine,
+ pivotDataSource,
+ `${trackName.trim() || 'Pivot Track'}: ${iter.get(sliceColumns.pivot)}`,
+ sliceColumns,
+ argColumns,
+ config,
+ );
+
+ globals.dispatchMultiple(actions);
+ }
+ }
+}
+
// Adds a debug track immediately. Use createDebugSliceTrackActions() if you
// want to create many tracks at once.
export async function addDebugSliceTrack(
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 0e13781..517ae4b 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -15,21 +15,22 @@
import m from 'mithril';
import {Trash} from '../base/disposable';
-import {findRef, getScrollbarWidth} from '../base/dom_utils';
+import {findRef, toHTMLElement} from '../base/dom_utils';
import {assertExists, assertFalse} from '../base/logging';
-import {SimpleResizeObserver} from '../base/resize_observer';
import {time} from '../base/time';
import {
+ PerfStatsSource,
+ RunningStatistics,
debugNow,
perfDebug,
perfDisplay,
- PerfStatsSource,
- RunningStatistics,
runningStatStr,
} from '../core/perf';
import {raf} from '../core/raf_scheduler';
import {SliceRect} from '../public';
+import {SimpleResizeObserver} from '../base/resize_observer';
+import {canvasClip} from '../common/canvas_utils';
import {
SELECTION_STROKE_COLOR,
TOPBAR_HEIGHT,
@@ -41,11 +42,9 @@
} from './flow_events_renderer';
import {globals} from './globals';
import {PanelSize} from './panel';
-import {canvasClip} from '../common/canvas_utils';
+import {VirtualCanvas} from './virtual_canvas';
-// If the panel container scrolls, the backing canvas height is
-// SCROLLING_CANVAS_OVERDRAW_FACTOR * parent container height.
-const SCROLLING_CANVAS_OVERDRAW_FACTOR = 1.2;
+const CANVAS_OVERDRAW_PX = 100;
export interface Panel {
kind: 'panel';
@@ -62,7 +61,7 @@
kind: 'group';
collapsed: boolean;
header: Panel;
- childTracks: Panel[];
+ childPanels: Panel[];
trackGroupId: string;
}
@@ -70,9 +69,8 @@
export interface PanelContainerAttrs {
panels: PanelOrGroup[];
- doesScroll: boolean;
- kind: 'TRACKS' | 'OVERVIEW';
className?: string;
+ onPanelStackResize?: (width: number, height: number) => void;
}
interface PanelInfo {
@@ -80,25 +78,23 @@
panel: Panel;
height: number;
width: number;
- x: number;
- y: number;
+ clientX: number;
+ clientY: number;
}
export class PanelContainer
implements m.ClassComponent<PanelContainerAttrs>, PerfStatsSource
{
// These values are updated with proper values in oncreate.
- private parentWidth = 0;
- private parentHeight = 0;
- private scrollTop = 0;
- private panelInfos: PanelInfo[] = [];
+ // Y position of the panel container w.r.t. the client
private panelContainerTop = 0;
private panelContainerHeight = 0;
- private panelByKey = new Map<string, Panel>();
- private totalPanelHeight = 0;
- private canvasHeight = 0;
- private flowEventsRenderer: FlowEventsRenderer;
+ // Updated every render cycle in the view() hook
+ private panelByKey = new Map<string, Panel>();
+
+ // Updated every render cycle in the oncreate/onupdate hook
+ private panelInfos: PanelInfo[] = [];
private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
private perfStats = {
@@ -107,22 +103,12 @@
renderStats: new RunningStatistics(10),
};
- // Attrs received in the most recent mithril redraw. We receive a new vnode
- // with new attrs on every redraw, and we cache it here so that resize
- // listeners and canvas redraw callbacks can access it.
- private attrs: PanelContainerAttrs;
-
private ctx?: CanvasRenderingContext2D;
- private trash: Trash;
+ private readonly trash = new Trash();
- private readonly SCROLL_LIMITER_REF = 'scroll-limiter';
- private readonly PANELS_REF = 'panels';
- private readonly OVERLAY_CANVAS_REF = 'canvas';
-
- get canvasOverdrawFactor() {
- return this.attrs.doesScroll ? SCROLLING_CANVAS_OVERDRAW_FACTOR : 1;
- }
+ private readonly OVERLAY_REF = 'overlay';
+ private readonly PANEL_STACK_REF = 'panel-stack';
getPanelsInRegion(
startX: number,
@@ -137,12 +123,12 @@
const panels: Panel[] = [];
for (let i = 0; i < this.panelInfos.length; i++) {
const pos = this.panelInfos[i];
- const realPosX = pos.x - TRACK_SHELL_WIDTH;
+ const realPosX = pos.clientX - TRACK_SHELL_WIDTH;
if (
realPosX + pos.width >= minX &&
realPosX <= maxX &&
- pos.y + pos.height >= minY &&
- pos.y <= maxY &&
+ pos.clientY + pos.height >= minY &&
+ pos.clientY <= maxY &&
pos.panel.selectable
) {
panels.push(pos.panel);
@@ -165,9 +151,9 @@
}
// Only get panels from the current panel container if the selection began
// in this container.
- const panelContainerTop = this.panelInfos[0].y;
+ const panelContainerTop = this.panelInfos[0].clientY;
const panelContainerBottom =
- this.panelInfos[this.panelInfos.length - 1].y +
+ this.panelInfos[this.panelInfos.length - 1].clientY +
this.panelInfos[this.panelInfos.length - 1].height;
if (
globals.timeline.areaY.start + TOPBAR_HEIGHT < panelContainerTop ||
@@ -207,11 +193,7 @@
globals.timeline.selectArea(area.start, area.end, tracks);
}
- constructor(vnode: m.CVnode<PanelContainerAttrs>) {
- this.attrs = vnode.attrs;
- this.flowEventsRenderer = new FlowEventsRenderer();
- this.trash = new Trash();
-
+ constructor() {
const onRedraw = () => this.renderCanvas();
raf.addRedrawCallback(onRedraw);
this.trash.addCallback(() => {
@@ -224,45 +206,52 @@
});
}
- oncreate({dom}: m.CVnodeDOM<PanelContainerAttrs>) {
- // Save the canvas context in the state.
- const canvas = findRef(dom, this.OVERLAY_CANVAS_REF) as HTMLCanvasElement;
- const ctx = canvas.getContext('2d');
+ private virtualCanvas?: VirtualCanvas;
+
+ oncreate(vnode: m.CVnodeDOM<PanelContainerAttrs>) {
+ const {dom, attrs} = vnode;
+
+ const overlayElement = toHTMLElement(
+ assertExists(findRef(dom, this.OVERLAY_REF)),
+ );
+
+ const virtualCanvas = new VirtualCanvas(overlayElement, dom, {
+ overdrawPx: CANVAS_OVERDRAW_PX,
+ });
+ this.trash.add(virtualCanvas);
+ this.virtualCanvas = virtualCanvas;
+
+ const ctx = virtualCanvas.canvasElement.getContext('2d');
if (!ctx) {
throw Error('Cannot create canvas context');
}
this.ctx = ctx;
- this.readParentSizeFromDom(dom);
- this.readPanelHeightsFromDom(dom);
+ virtualCanvas.setCanvasResizeListener((canvas, width, height) => {
+ const dpr = window.devicePixelRatio;
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+ });
- this.updateCanvasDimensions();
- this.repositionCanvas();
+ virtualCanvas.setLayoutShiftListener(() => {
+ this.renderCanvas();
+ });
- const scrollLimiter = assertExists(findRef(dom, this.SCROLL_LIMITER_REF));
- this.trash.add(
- new SimpleResizeObserver(scrollLimiter, () => {
- const parentSizeChanged = this.readParentSizeFromDom(dom);
- if (parentSizeChanged) {
- this.updateCanvasDimensions();
- this.repositionCanvas();
- this.renderCanvas();
- }
- }),
+ this.onupdate(vnode);
+
+ const panelStackElement = toHTMLElement(
+ assertExists(findRef(dom, this.PANEL_STACK_REF)),
);
- // TODO(dproy): Handle change in doesScroll attribute.
- if (this.attrs.doesScroll) {
- const parentOnScroll = () => {
- this.scrollTop = dom.scrollTop;
- this.repositionCanvas();
- raf.scheduleRedraw();
- };
- dom.addEventListener('scroll', parentOnScroll, {passive: true});
- this.trash.addCallback(() => {
- dom.removeEventListener('scroll', parentOnScroll);
- });
- }
+ // Listen for when the panel stack changes size
+ this.trash.add(
+ new SimpleResizeObserver(panelStackElement, () => {
+ attrs.onPanelStackResize?.(
+ panelStackElement.clientWidth,
+ panelStackElement.clientHeight,
+ );
+ }),
+ );
}
onremove() {
@@ -288,7 +277,7 @@
`${path}-header`,
node.collapsed ? '' : '.pf-sticky',
),
- ...node.childTracks.map((child, index) =>
+ ...node.childPanels.map((child, index) =>
this.renderTree(child, `${path}-${index}`),
),
);
@@ -297,7 +286,6 @@
}
view({attrs}: m.CVnode<PanelContainerAttrs>) {
- this.attrs = attrs;
this.panelByKey.clear();
const children = attrs.panels.map((panel, index) =>
this.renderTree(panel, `track-tree-${index}`),
@@ -307,91 +295,22 @@
'.pf-panel-container',
{className: attrs.className},
m(
- '.pf-panels',
- {ref: this.PANELS_REF},
- m(
- '.pf-scroll-limiter',
- {ref: this.SCROLL_LIMITER_REF},
- m('canvas.pf-overlay-canvas', {ref: this.OVERLAY_CANVAS_REF}),
- ),
+ '.pf-panel-stack',
+ {ref: this.PANEL_STACK_REF},
+ m('.pf-overlay', {ref: this.OVERLAY_REF}),
children,
),
);
}
onupdate({dom}: m.CVnodeDOM<PanelContainerAttrs>) {
- const totalPanelHeightChanged = this.readPanelHeightsFromDom(dom);
- const parentSizeChanged = this.readParentSizeFromDom(dom);
- const canvasSizeShouldChange =
- parentSizeChanged || (!this.attrs.doesScroll && totalPanelHeightChanged);
- if (canvasSizeShouldChange) {
- this.updateCanvasDimensions();
- this.repositionCanvas();
- if (this.attrs.kind === 'TRACKS') {
- globals.timeline.updateLocalLimits(
- 0,
- this.parentWidth - TRACK_SHELL_WIDTH,
- );
- }
- this.renderCanvas();
- }
+ this.readPanelRectsFromDom(dom);
}
- private updateCanvasDimensions() {
- this.canvasHeight = Math.floor(
- this.attrs.doesScroll
- ? this.parentHeight * this.canvasOverdrawFactor
- : this.totalPanelHeight,
- );
- const ctx = assertExists(this.ctx);
- const canvas = assertExists(ctx.canvas);
- canvas.style.height = `${this.canvasHeight}px`;
-
- // If're we're non-scrolling canvas and the scroll-limiter should always
- // have the same height. Enforce this by explicitly setting the height.
- if (!this.attrs.doesScroll) {
- const scrollLimiter = canvas.parentElement;
- if (scrollLimiter) {
- scrollLimiter.style.height = `${this.canvasHeight}px`;
- }
- }
-
- const dpr = window.devicePixelRatio;
- ctx.canvas.width = this.parentWidth * dpr;
- ctx.canvas.height = this.canvasHeight * dpr;
- ctx.scale(dpr, dpr);
- }
-
- private repositionCanvas() {
- const canvas = assertExists(assertExists(this.ctx).canvas);
- const canvasYStart = Math.floor(
- this.scrollTop - this.getCanvasOverdrawHeightPerSide(),
- );
- canvas.style.transform = `translateY(${canvasYStart}px)`;
- }
-
- // Reads dimensions of parent node. Returns true if read dimensions are
- // different from what was cached in the state.
- private readParentSizeFromDom(dom: Element): boolean {
- const oldWidth = this.parentWidth;
- const oldHeight = this.parentHeight;
- const clientRect = dom.getBoundingClientRect();
- // On non-MacOS if there is a solid scroll bar it can cover important
- // pixels, reduce the size of the canvas so it doesn't overlap with
- // the scroll bar.
- this.parentWidth = clientRect.width - getScrollbarWidth();
- this.parentHeight = clientRect.height;
- return this.parentHeight !== oldHeight || this.parentWidth !== oldWidth;
- }
-
- // Reads dimensions of panels. Returns true if total panel height is different
- // from what was cached in state.
- private readPanelHeightsFromDom(dom: Element): boolean {
- const prevHeight = this.totalPanelHeight;
+ private readPanelRectsFromDom(dom: Element): void {
this.panelInfos = [];
- this.totalPanelHeight = 0;
- const panels = assertExists(findRef(dom, this.PANELS_REF));
+ const panels = assertExists(findRef(dom, this.PANEL_STACK_REF));
const domRect = panels.getBoundingClientRect();
this.panelContainerTop = domRect.y;
this.panelContainerHeight = domRect.height;
@@ -407,80 +326,105 @@
id,
height: rect.height,
width: rect.width,
- x: rect.x,
- y: rect.y,
+ clientX: rect.x,
+ clientY: rect.y,
panel,
});
- this.totalPanelHeight += rect.height;
});
-
- return this.totalPanelHeight !== prevHeight;
- }
-
- private overlapsCanvas(yStart: number, yEnd: number) {
- return yEnd > 0 && yStart < this.canvasHeight;
}
private renderCanvas() {
- const redrawStart = debugNow();
if (!this.ctx) return;
- this.ctx.clearRect(0, 0, this.parentWidth, this.canvasHeight);
- const canvasYStart = Math.floor(
- this.scrollTop - this.getCanvasOverdrawHeightPerSide(),
- );
+ if (!this.virtualCanvas) return;
+
+ const ctx = this.ctx;
+ const vc = this.virtualCanvas;
+ const redrawStart = debugNow();
+
+ ctx.resetTransform();
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ const dpr = window.devicePixelRatio;
+ ctx.scale(dpr, dpr);
+ ctx.translate(-vc.canvasRect.left, -vc.canvasRect.top);
this.handleAreaSelection();
- let panelYStart = 0;
- let totalOnCanvas = 0;
- const flowEventsRendererArgs = new FlowEventsRendererArgs(
- this.parentWidth,
- this.canvasHeight,
- );
- for (let i = 0; i < this.panelInfos.length; i++) {
- const panel = this.panelInfos[i].panel;
- const panelHeight = this.panelInfos[i].height;
- const yStartOnCanvas = panelYStart - canvasYStart;
+ const totalRenderedPanels = this.renderPanels(ctx, vc);
- flowEventsRendererArgs.registerPanel(panel, yStartOnCanvas, panelHeight);
+ this.drawTopLayerOnCanvas(ctx, vc);
- if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) {
- panelYStart += panelHeight;
- continue;
- }
-
- totalOnCanvas++;
-
- this.ctx.save();
- this.ctx.translate(0, yStartOnCanvas);
- const clipRect = new Path2D();
- const size = {width: this.parentWidth, height: panelHeight};
- clipRect.rect(0, 0, size.width, size.height);
- this.ctx.clip(clipRect);
- const beforeRender = debugNow();
- panel.renderCanvas(this.ctx, size);
- this.updatePanelStats(
- i,
- panel,
- debugNow() - beforeRender,
- this.ctx,
- size,
- );
- this.ctx.restore();
- panelYStart += panelHeight;
- }
-
- this.drawTopLayerOnCanvas();
- this.flowEventsRenderer.render(this.ctx, flowEventsRendererArgs);
// Collect performance as the last thing we do.
const redrawDur = debugNow() - redrawStart;
- this.updatePerfStats(redrawDur, this.panelInfos.length, totalOnCanvas);
+ this.updatePerfStats(
+ redrawDur,
+ this.panelInfos.length,
+ totalRenderedPanels,
+ );
+ }
+
+ private renderPanels(
+ ctx: CanvasRenderingContext2D,
+ vc: VirtualCanvas,
+ ): number {
+ let panelTop = 0;
+ let totalOnCanvas = 0;
+
+ const flowEventsRendererArgs = new FlowEventsRendererArgs(
+ vc.size.width,
+ vc.size.height,
+ );
+
+ for (let i = 0; i < this.panelInfos.length; i++) {
+ const {
+ panel,
+ width: panelWidth,
+ height: panelHeight,
+ } = this.panelInfos[i];
+
+ const panelRect = {
+ left: 0,
+ top: panelTop,
+ bottom: panelTop + panelHeight,
+ right: panelWidth,
+ };
+ const panelSize = {width: panelWidth, height: panelHeight};
+
+ flowEventsRendererArgs.registerPanel(panel, panelTop, panelHeight);
+
+ if (vc.overlapsCanvas(panelRect)) {
+ totalOnCanvas++;
+
+ ctx.save();
+ ctx.translate(0, panelTop);
+ canvasClip(ctx, 0, 0, panelWidth, panelHeight);
+ const beforeRender = debugNow();
+ panel.renderCanvas(ctx, panelSize);
+ this.updatePanelStats(
+ i,
+ panel,
+ debugNow() - beforeRender,
+ ctx,
+ panelSize,
+ );
+ ctx.restore();
+ }
+
+ panelTop += panelHeight;
+ }
+
+ const flowEventsRenderer = new FlowEventsRenderer();
+ flowEventsRenderer.render(ctx, flowEventsRendererArgs);
+
+ return totalOnCanvas;
}
// The panels each draw on the canvas but some details need to be drawn across
// the whole canvas rather than per panel.
- private drawTopLayerOnCanvas() {
- if (!this.ctx) return;
+ private drawTopLayerOnCanvas(
+ ctx: CanvasRenderingContext2D,
+ vc: VirtualCanvas,
+ ): void {
const area = globals.timeline.selectedArea;
if (
area === undefined ||
@@ -498,10 +442,13 @@
for (let i = 0; i < this.panelInfos.length; i++) {
if (area.tracks.includes(this.panelInfos[i].id)) {
trackFromCurrentContainerSelected = true;
- selectedTracksMinY = Math.min(selectedTracksMinY, this.panelInfos[i].y);
+ selectedTracksMinY = Math.min(
+ selectedTracksMinY,
+ this.panelInfos[i].clientY,
+ );
selectedTracksMaxY = Math.max(
selectedTracksMaxY,
- this.panelInfos[i].y + this.panelInfos[i].height,
+ this.panelInfos[i].clientY + this.panelInfos[i].height,
);
}
}
@@ -518,30 +465,22 @@
// To align with where to draw on the canvas subtract the first panel Y.
selectedTracksMinY -= this.panelContainerTop;
selectedTracksMaxY -= this.panelContainerTop;
- this.ctx.save();
- this.ctx.strokeStyle = SELECTION_STROKE_COLOR;
- this.ctx.lineWidth = 1;
- const canvasYStart = Math.floor(
- this.scrollTop - this.getCanvasOverdrawHeightPerSide(),
- );
- this.ctx.translate(TRACK_SHELL_WIDTH, -canvasYStart);
+ ctx.save();
+ ctx.strokeStyle = SELECTION_STROKE_COLOR;
+ ctx.lineWidth = 1;
+
+ ctx.translate(TRACK_SHELL_WIDTH, 0);
// Clip off any drawing happening outside the bounds of the timeline area
- canvasClip(
- this.ctx,
- 0,
- 0,
- this.parentWidth - TRACK_SHELL_WIDTH,
- this.totalPanelHeight,
- );
+ canvasClip(ctx, 0, 0, vc.size.width - TRACK_SHELL_WIDTH, vc.size.height);
- this.ctx.strokeRect(
+ ctx.strokeRect(
startX,
selectedTracksMaxY,
endX - startX,
selectedTracksMinY - selectedTracksMaxY,
);
- this.ctx.restore();
+ ctx.restore();
}
private updatePanelStats(
@@ -599,9 +538,4 @@
m('div', runningStatStr(this.perfStats.renderStats)),
];
}
-
- private getCanvasOverdrawHeightPerSide() {
- const overdrawHeight = (this.canvasOverdrawFactor - 1) * this.parentHeight;
- return overdrawHeight / 2;
- }
}
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 719705e..34d1752 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
-import {findRef, getScrollbarWidth, toHTMLElement} from '../base/dom_utils';
+import {findRef, toHTMLElement} from '../base/dom_utils';
import {clamp} from '../base/math_utils';
import {Time} from '../base/time';
import {Actions} from '../common/actions';
@@ -78,7 +78,6 @@
* panels, and everything else that's part of the main trace viewer page.
*/
class TraceViewer implements m.ClassComponent {
- private onResize: () => void = () => {};
private zoomContent?: PanAndZoomHandler;
// Used to prevent global deselection if a pan/drag select occurred.
private keepCurrentSelection = false;
@@ -93,25 +92,6 @@
oncreate(vnode: m.CVnodeDOM) {
const timeline = globals.timeline;
- const updateDimensions = () => {
- const rect = vnode.dom.getBoundingClientRect();
- timeline.updateLocalLimits(
- 0,
- rect.width - TRACK_SHELL_WIDTH - getScrollbarWidth(),
- );
- };
-
- updateDimensions();
-
- // TODO: Do resize handling better.
- this.onResize = () => {
- updateDimensions();
- raf.scheduleFullRedraw();
- };
-
- // Once ResizeObservers are out, we can stop accessing the window here.
- window.addEventListener('resize', this.onResize);
-
const panZoomElRaw = findRef(vnode.dom, this.PAN_ZOOM_CONTENT_REF);
const panZoomEl = toHTMLElement(assertExists(panZoomElRaw));
@@ -228,7 +208,6 @@
}
onremove() {
- window.removeEventListener('resize', this.onResize);
if (this.zoomContent) this.zoomContent.dispose();
}
@@ -280,7 +259,7 @@
scrollingPanels.push({
kind: 'group',
collapsed: group.collapsed,
- childTracks,
+ childPanels: childTracks,
header: headerPanel,
trackGroupId: group.id,
});
@@ -306,21 +285,22 @@
globals.clearSelection();
},
},
- m(PanelContainer, {
- className: 'header-panel-container',
- doesScroll: false,
- panels: [
- ...overviewPanel,
- this.timeAxisPanel,
- this.timeSelectionPanel,
- this.notesPanel,
- this.tickmarkPanel,
- ],
- kind: 'OVERVIEW',
- }),
+ m(
+ '.header',
+ m(PanelContainer, {
+ className: 'header-panel-container',
+ panels: [
+ ...overviewPanel,
+ this.timeAxisPanel,
+ this.timeSelectionPanel,
+ this.notesPanel,
+ this.tickmarkPanel,
+ ],
+ }),
+ m('.scrollbar-spacer-vertical'),
+ ),
m(PanelContainer, {
className: 'pinned-panel-container',
- doesScroll: true,
panels: globals.state.pinnedTracks.map((key) => {
const trackBundle = this.resolveTrack(key);
return new TrackPanel({
@@ -332,13 +312,14 @@
closeable: trackBundle.closeable,
});
}),
- kind: 'TRACKS',
}),
m(PanelContainer, {
className: 'scrolling-panel-container',
- doesScroll: true,
panels: scrollingPanels,
- kind: 'TRACKS',
+ onPanelStackResize: (width) => {
+ const timelineWidth = width - TRACK_SHELL_WIDTH;
+ globals.timeline.updateLocalLimits(0, timelineWidth);
+ },
}),
),
this.renderTabPanel(),
diff --git a/ui/src/frontend/virtual_canvas.ts b/ui/src/frontend/virtual_canvas.ts
new file mode 100644
index 0000000..4035614
--- /dev/null
+++ b/ui/src/frontend/virtual_canvas.ts
@@ -0,0 +1,269 @@
+// 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.
+
+/**
+ * Canvases have limits on their maximum size (which is determined by the
+ * system). Usually, this limit is fairly large, but can be as small as
+ * 4096x4096px on some machines.
+ *
+ * If we need a super large canvas, we need to use a different approach.
+ *
+ * Unless the user has a huge monitor, most of the time any sufficiently large
+ * canvas will overflow it's container, so we assume this container is set to
+ * scroll so that the user can actually see all of the canvas. We can take
+ * advantage of the fact that users may only see a small portion of the canvas
+ * at a time. So, if we position a small floating canvas element over the
+ * viewport of the scrolling container, we can approximate a huge canvas using a
+ * much smaller one.
+ *
+ * Given a target element and it's scrolling container, VirtualCanvas turns an
+ * empty HTML element into a "virtual" canvas with virtually unlimited size
+ * using the "floating" canvas technique described above.
+ */
+
+import {Disposable, Trash} from '../base/disposable';
+import {
+ Rect,
+ Size,
+ expandRect,
+ intersectRects,
+ rebaseRect,
+ rectSize,
+} from '../base/geom';
+
+export type LayoutShiftListener = (
+ canvas: HTMLCanvasElement,
+ rect: Rect,
+) => void;
+
+export type CanvasResizeListener = (
+ canvas: HTMLCanvasElement,
+ width: number,
+ height: number,
+) => void;
+
+export interface VirtualCanvasOpts {
+ // How much buffer to add above and below the visible window.
+ overdrawPx: number;
+
+ // If true, the canvas will remain within the bounds on the target element at
+ // all times.
+ //
+ // If false, the canvas is allowed to overflow the bounds of the target
+ // element to avoid resizing unnecessarily.
+ avoidOverflowingContainer: boolean;
+}
+
+export class VirtualCanvas implements Disposable {
+ private readonly _trash = new Trash();
+ private readonly _canvasElement: HTMLCanvasElement;
+ private readonly _targetElement: HTMLElement;
+
+ // Describes the offset of the canvas w.r.t. the "target" container
+ private _canvasRect: Rect;
+ private _layoutShiftListener?: LayoutShiftListener;
+ private _canvasResizeListener?: CanvasResizeListener;
+
+ /**
+ * @param targetElement The element to turn into a virtual canvas. The
+ * dimensions of this element are used to size the canvas, so ensure this
+ * element is sized appropriately.
+ * @param containerElement The scrolling container to be used for determining
+ * the size and position of the canvas. The targetElement should be a child of
+ * this element.
+ * @param opts Setup options for the VirtualCanvas.
+ */
+ constructor(
+ targetElement: HTMLElement,
+ containerElement: Element,
+ opts?: Partial<VirtualCanvasOpts>,
+ ) {
+ const {overdrawPx = 100, avoidOverflowingContainer} = opts ?? {};
+
+ // Returns what the canvas rect should look like
+ const getCanvasRect = () => {
+ const containerRect = containerElement.getBoundingClientRect();
+ const targetElementRect = targetElement.getBoundingClientRect();
+
+ // Calculate the intersection of the container's viewport and the target
+ const intersection = intersectRects(containerRect, targetElementRect);
+
+ // Pad the intersection by the overdraw amount
+ const intersectionExpanded = expandRect(intersection, overdrawPx);
+
+ // Intersect with the original target rect unless we want to avoid resizes
+ const canvasTargetRect = avoidOverflowingContainer
+ ? intersectRects(intersectionExpanded, targetElementRect)
+ : intersectionExpanded;
+
+ return rebaseRect(
+ canvasTargetRect,
+ targetElementRect.x,
+ targetElementRect.y,
+ );
+ };
+
+ const updateCanvas = () => {
+ let repaintRequired = false;
+
+ const canvasRect = getCanvasRect();
+ const canvasRectSize = rectSize(canvasRect);
+ const canvasRectPrev = this._canvasRect;
+ const canvasRectPrevSize = rectSize(canvasRectPrev);
+ this._canvasRect = canvasRect;
+
+ if (
+ canvasRectPrevSize.width !== canvasRectSize.width ||
+ canvasRectPrevSize.height !== canvasRectSize.height
+ ) {
+ // Canvas needs to change size, update its size
+ canvas.style.width = `${canvasRectSize.width}px`;
+ canvas.style.height = `${canvasRectSize.height}px`;
+ this._canvasResizeListener?.(
+ canvas,
+ canvasRectSize.width,
+ canvasRectSize.height,
+ );
+ repaintRequired = true;
+ }
+
+ if (
+ canvasRectPrev.left !== canvasRect.left ||
+ canvasRectPrev.top !== canvasRect.top
+ ) {
+ // Canvas needs to move, update the transform
+ canvas.style.transform = `translate(${canvasRect.left}px, ${canvasRect.top}px)`;
+ repaintRequired = true;
+ }
+
+ repaintRequired && this._layoutShiftListener?.(canvas, canvasRect);
+ };
+
+ containerElement.addEventListener('scroll', updateCanvas, {
+ passive: true,
+ });
+ this._trash.addCallback(() =>
+ containerElement.removeEventListener('scroll', updateCanvas),
+ );
+
+ // Resize observer callbacks are called once immediately
+ const resizeObserver = new ResizeObserver(() => {
+ updateCanvas();
+ });
+
+ resizeObserver.observe(containerElement);
+ resizeObserver.observe(targetElement);
+ this._trash.addCallback(() => {
+ resizeObserver.disconnect();
+ });
+
+ // Ensures the canvas doesn't change the size of the target element
+ targetElement.style.overflow = 'hidden';
+
+ const canvas = document.createElement('canvas');
+ canvas.style.position = 'absolute';
+ targetElement.appendChild(canvas);
+ this._trash.addCallback(() => {
+ targetElement.removeChild(canvas);
+ });
+
+ this._canvasElement = canvas;
+ this._targetElement = targetElement;
+ this._canvasRect = {
+ left: 0,
+ top: 0,
+ bottom: 0,
+ right: 0,
+ };
+ }
+
+ /**
+ * Set the callback that gets called when the canvas element is moved or
+ * resized, thus, invalidating the contents, and should be re-painted.
+ *
+ * @param cb The new callback.
+ */
+ setLayoutShiftListener(cb: LayoutShiftListener) {
+ this._layoutShiftListener = cb;
+ }
+
+ /**
+ * Set the callback that gets called when the canvas element is resized. This
+ * might be a good opportunity to update the size of the canvas' draw buffer.
+ *
+ * @param cb The new callback.
+ */
+ setCanvasResizeListener(cb: CanvasResizeListener) {
+ this._canvasResizeListener = cb;
+ }
+
+ /**
+ * The floating canvas element.
+ */
+ get canvasElement(): HTMLCanvasElement {
+ return this._canvasElement;
+ }
+
+ /**
+ * The target element, i.e. the one passed to our constructor.
+ */
+ get targetElement(): HTMLElement {
+ return this._targetElement;
+ }
+
+ /**
+ * The size of the target element, aka the size of the virtual canvas.
+ */
+ get size(): Size {
+ return {
+ width: this._targetElement.clientWidth,
+ height: this._targetElement.clientHeight,
+ };
+ }
+
+ /**
+ * Returns the rect of the floating canvas with respect to the target element.
+ * This will need to be subtracted from any drawing operations to get the
+ * right alignment within the virtual canvas.
+ */
+ get canvasRect(): Rect {
+ return this._canvasRect;
+ }
+
+ /**
+ * The size of the floating canvas.
+ */
+ get canvasSize(): Size {
+ return rectSize(this._canvasRect);
+ }
+
+ /**
+ * Stop listening to DOM events.
+ */
+ dispose(): void {
+ this._trash.dispose();
+ }
+
+ /**
+ * Return true if a rect overlaps the floating canvas.
+ * @param rect The rect to test.
+ * @returns true if rect overlaps, false otherwise.
+ */
+ overlapsCanvas(rect: Rect): boolean {
+ const c = this._canvasRect;
+ const y = rect.top < c.bottom && rect.bottom > c.top;
+ const x = rect.left < c.right && rect.right > c.left;
+ return x && y;
+ }
+}
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index a1a2842..c022dba 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -448,32 +448,21 @@
wakelock_dur
from step2
where wakelock_dur is not null
- and wakelock_dur > 0
- and count >= 0
- ),
- step4 as (
- select
- ts,
- ts_end,
- suspended_dur,
- wakelock_name,
- count,
- 1.0 * wakelock_dur / (ts_end - ts - suspended_dur) as ratio,
- wakelock_dur
- from step3
+ and wakelock_dur >= 0
)
select
ts,
- min(ratio, 1) * (ts_end - ts) as dur,
+ ts_end - ts as dur,
wakelock_name,
- cast (100.0 * ratio as int) || '% (+' || count || ')' as name
- from step4
- where cast (100.0 * wakelock_dur / (ts_end - ts - suspended_dur) as int) > 1`;
+ min(100.0 * wakelock_dur / (ts_end - ts - suspended_dur), 100) as value
+ from step3`;
const KERNEL_WAKELOCKS_SUMMARY = `
- select distinct wakelock_name
+ select wakelock_name, max(value) as max_value
from kernel_wakelocks
where wakelock_name not in ('PowerManager.SuspendLockout', 'PowerManagerService.Display')
+ group by 1
+ having max_value > 1
order by 1;`;
const HIGH_CPU = `
@@ -1079,12 +1068,12 @@
bluetooth_stack_state & 0x00000F00 >> 8 as acl_ble_count,
bluetooth_stack_state & 0x0000F000 >> 12 as advertising_count,
case bluetooth_stack_state & 0x000F0000 >> 16
- when 0 then 'disabled'
- when 1 then '<5%'
- when 2 then '5% to 10%'
- when 3 then '10% to 25%'
- when 4 then '25% to 100%'
- else 'invalid'
+ when 0 then 0
+ when 1 then 5
+ when 2 then 10
+ when 3 then 25
+ when 4 then 100
+ else -1
end as le_scan_duty_cycle,
bluetooth_stack_state & 0x00100000 >> 20 as inquiry_active,
bluetooth_stack_state & 0x00200000 >> 21 as sco_active,
@@ -1436,11 +1425,12 @@
const result = await e.query(KERNEL_WAKELOCKS_SUMMARY);
const it = result.iter({wakelock_name: 'str'});
for (; it.valid(); it.next()) {
- this.addSliceTrack(
+ this.addCounterTrack(
ctx,
it.wakelock_name,
- `select ts, dur, name from kernel_wakelocks where wakelock_name = "${it.wakelock_name}"`,
+ `select ts, dur, value from kernel_wakelocks where wakelock_name = "${it.wakelock_name}"`,
groupName,
+ {yRangeSharingKey: 'kernel_wakelock', unit: '%'},
);
}
}
@@ -1602,11 +1592,12 @@
'select ts, dur, advertising_count as value from bt_activity',
groupName,
);
- this.addSliceTrack(
+ this.addCounterTrack(
ctx,
- 'LE Scan Duty Cycle',
- 'select ts, dur, le_scan_duty_cycle as name from bt_activity',
+ 'LE Scan Duty Cycle Maximum',
+ 'select ts, dur, le_scan_duty_cycle as value from bt_activity',
groupName,
+ {unit: '%'},
);
this.addSliceTrack(
ctx,
diff --git a/ui/src/test/ui_integrationtest.ts b/ui/src/test/ui_integrationtest.ts
index 946f6cd..442c5ee 100644
--- a/ui/src/test/ui_integrationtest.ts
+++ b/ui/src/test/ui_integrationtest.ts
@@ -86,7 +86,7 @@
});
test('expand_camera', async () => {
- await page.click('.pf-overlay-canvas');
+ await page.click('.pf-overlay');
await page.click('h1[title="com.google.android.GoogleCamera 5506"]');
await page.evaluate(() => {
document.querySelector('.scrolling-panel-container')!.scrollTo(0, 400);
@@ -114,7 +114,7 @@
test('expand_browser_proc', async () => {
const page = await getPage();
- await page.click('.pf-overlay-canvas');
+ await page.click('.pf-overlay');
await page.click('h1[title="Browser 12685"]');
await waitForPerfettoIdle(page);
});
diff --git a/ui/src/tracks/debug/add_debug_track_menu.ts b/ui/src/tracks/debug/add_debug_track_menu.ts
index 8f0cd37..e03c9f2 100644
--- a/ui/src/tracks/debug/add_debug_track_menu.ts
+++ b/ui/src/tracks/debug/add_debug_track_menu.ts
@@ -25,6 +25,7 @@
SqlDataSource,
addDebugCounterTrack,
addDebugSliceTrack,
+ addPivotDebugSliceTracks,
} from '../../frontend/debug_tracks';
export const ARG_PREFIX = 'arg_';
@@ -52,7 +53,13 @@
// 'value' for slice and 'name' for counter) and then just don't the values
// which don't match the currently selected track type (so changing track type
// from A to B and back to A is a no-op).
- renderParams: {ts: string; dur: string; name: string; value: string};
+ renderParams: {
+ ts: string;
+ dur: string;
+ name: string;
+ value: string;
+ pivot: string;
+ };
constructor(vnode: m.Vnode<AddDebugTrackMenuAttrs>) {
this.columns = [...vnode.attrs.dataSource.columns];
@@ -77,6 +84,7 @@
dur: chooseDefaultOption('dur'),
name: chooseDefaultOption('name'),
value: chooseDefaultOption('value'),
+ pivot: '',
};
}
@@ -124,15 +132,23 @@
}
view(vnode: m.Vnode<AddDebugTrackMenuAttrs>) {
- const renderSelect = (name: 'ts' | 'dur' | 'name' | 'value') => {
+ const renderSelect = (name: 'ts' | 'dur' | 'name' | 'value' | 'pivot') => {
const options = [];
+
+ if (name === 'pivot') {
+ options.push(
+ m(
+ 'option',
+ {selected: this.renderParams[name] === '' ? true : undefined},
+ m('i', ''),
+ ),
+ );
+ }
for (const column of this.columns) {
options.push(
m(
'option',
- {
- selected: this.renderParams[name] === column ? true : undefined,
- },
+ {selected: this.renderParams[name] === column ? true : undefined},
column,
),
);
@@ -161,23 +177,39 @@
),
];
};
+
return m(
Form,
{
onSubmit: () => {
switch (this.trackType) {
case 'slice':
- addDebugSliceTrack(
- vnode.attrs.engine,
- vnode.attrs.dataSource,
- this.name,
- {
- ts: this.renderParams.ts,
- dur: this.renderParams.dur,
- name: this.renderParams.name,
- },
- this.columns,
- );
+ if (this.renderParams.pivot === '') {
+ addDebugSliceTrack(
+ vnode.attrs.engine,
+ vnode.attrs.dataSource,
+ this.name,
+ {
+ ts: this.renderParams.ts,
+ dur: this.renderParams.dur,
+ name: this.renderParams.name,
+ },
+ this.columns,
+ );
+ } else {
+ addPivotDebugSliceTracks(
+ vnode.attrs.engine,
+ vnode.attrs.dataSource,
+ this.name,
+ {
+ ts: this.renderParams.ts,
+ dur: this.renderParams.dur,
+ name: this.renderParams.name,
+ pivot: this.renderParams.pivot,
+ },
+ this.columns,
+ );
+ }
break;
case 'counter':
addDebugCounterTrack(vnode.attrs.dataSource, this.name, {
@@ -207,6 +239,7 @@
renderSelect('ts'),
this.trackType === 'slice' && renderSelect('dur'),
this.trackType === 'slice' && renderSelect('name'),
+ this.trackType === 'slice' && renderSelect('pivot'),
this.trackType === 'counter' && renderSelect('value'),
);
}