Merge "[jumbo] Const-correct SharedMemory::start() and AlignedBufferTest::buf()" into main
diff --git a/Android.bp b/Android.bp
index f3b1593..c7412d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2412,6 +2412,8 @@
":perfetto_src_trace_processor_importers_fuchsia_minimal",
":perfetto_src_trace_processor_importers_gzip_full",
":perfetto_src_trace_processor_importers_i2c_full",
+ ":perfetto_src_trace_processor_importers_instruments_instruments",
+ ":perfetto_src_trace_processor_importers_instruments_row",
":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
@@ -2512,6 +2514,7 @@
shared_libs: [
"heapprofd_client_api",
"libbase",
+ "libexpat",
"libicu",
"liblog",
"libprocinfo",
@@ -6648,6 +6651,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -7075,6 +7079,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -7164,6 +7169,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/oom.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/panel.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.gen.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/pixel_mm.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/power.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/printk.gen.cc",
"external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.cc",
@@ -7253,6 +7259,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/oom.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/panel.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.gen.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/pixel_mm.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/power.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/printk.gen.h",
"external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.h",
@@ -7338,6 +7345,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -7426,6 +7434,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/oom.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/panel.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pb.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/pixel_mm.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/power.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/printk.pb.cc",
"external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.cc",
@@ -7514,6 +7523,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/oom.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/panel.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pb.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/pixel_mm.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/power.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/printk.pb.h",
"external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.h",
@@ -7599,6 +7609,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -7688,6 +7699,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/oom.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/panel.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pbzero.cc",
+ "external/perfetto/protos/perfetto/trace/ftrace/pixel_mm.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/power.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.cc",
"external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.cc",
@@ -7777,6 +7789,7 @@
"external/perfetto/protos/perfetto/trace/ftrace/oom.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/panel.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pbzero.h",
+ "external/perfetto/protos/perfetto/trace/ftrace/pixel_mm.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/power.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.h",
"external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h",
@@ -12234,6 +12247,7 @@
"src/trace_processor/importers/common/flow_tracker.cc",
"src/trace_processor/importers/common/global_args_tracker.cc",
"src/trace_processor/importers/common/jit_cache.cc",
+ "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc",
"src/trace_processor/importers/common/machine_tracker.cc",
"src/trace_processor/importers/common/mapping_tracker.cc",
"src/trace_processor/importers/common/metadata_tracker.cc",
@@ -12322,6 +12336,7 @@
"src/trace_processor/importers/ftrace/gpu_work_period_tracker.cc",
"src/trace_processor/importers/ftrace/iostat_tracker.cc",
"src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc",
+ "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc",
"src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc",
"src/trace_processor/importers/ftrace/rss_stat_tracker.cc",
"src/trace_processor/importers/ftrace/thermal_tracker.cc",
@@ -12396,6 +12411,21 @@
],
}
+// GN: //src/trace_processor/importers/instruments:instruments
+filegroup {
+ name: "perfetto_src_trace_processor_importers_instruments_instruments",
+ srcs: [
+ "src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc",
+ "src/trace_processor/importers/instruments/row_data_tracker.cc",
+ "src/trace_processor/importers/instruments/row_parser.cc",
+ ],
+}
+
+// GN: //src/trace_processor/importers/instruments:row
+filegroup {
+ name: "perfetto_src_trace_processor_importers_instruments_row",
+}
+
// GN: //src/trace_processor/importers/json:full
filegroup {
name: "perfetto_src_trace_processor_importers_json_full",
@@ -13319,6 +13349,7 @@
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_stats.sql",
+ "src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/general.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/process.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql",
@@ -14821,6 +14852,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -15297,6 +15329,8 @@
":perfetto_src_trace_processor_importers_fuchsia_unittests",
":perfetto_src_trace_processor_importers_gzip_full",
":perfetto_src_trace_processor_importers_i2c_full",
+ ":perfetto_src_trace_processor_importers_instruments_instruments",
+ ":perfetto_src_trace_processor_importers_instruments_row",
":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
@@ -15436,6 +15470,7 @@
],
shared_libs: [
"libbase",
+ "libexpat",
"libicu",
"liblog",
"libprocinfo",
@@ -16138,6 +16173,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -16270,8 +16306,11 @@
":perfetto_include_perfetto_ext_traced_sys_stats_counters",
":perfetto_include_perfetto_protozero_protozero",
":perfetto_include_perfetto_public_abi_base",
+ ":perfetto_include_perfetto_public_abi_public",
":perfetto_include_perfetto_public_base",
+ ":perfetto_include_perfetto_public_protos_protos",
":perfetto_include_perfetto_public_protozero",
+ ":perfetto_include_perfetto_public_public",
":perfetto_include_perfetto_trace_processor_basic_types",
":perfetto_include_perfetto_trace_processor_storage",
":perfetto_include_perfetto_trace_processor_trace_processor",
@@ -16345,6 +16384,8 @@
":perfetto_src_trace_processor_importers_fuchsia_minimal",
":perfetto_src_trace_processor_importers_gzip_full",
":perfetto_src_trace_processor_importers_i2c_full",
+ ":perfetto_src_trace_processor_importers_instruments_instruments",
+ ":perfetto_src_trace_processor_importers_instruments_row",
":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
@@ -16478,6 +16519,7 @@
target: {
android: {
shared_libs: [
+ "libexpat",
"libicu",
"liblog",
"libprotobuf-cpp-full",
@@ -16491,6 +16533,7 @@
},
host: {
static_libs: [
+ "libexpat",
"libprotobuf-cpp-full",
"libsqlite_static_noicu",
"libz",
@@ -16575,6 +16618,7 @@
":perfetto_src_trace_processor_importers_etw_minimal",
":perfetto_src_trace_processor_importers_ftrace_minimal",
":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
+ ":perfetto_src_trace_processor_importers_instruments_row",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
":perfetto_src_trace_processor_importers_perf_record",
@@ -16675,8 +16719,11 @@
":perfetto_include_perfetto_profiling_pprof_builder",
":perfetto_include_perfetto_protozero_protozero",
":perfetto_include_perfetto_public_abi_base",
+ ":perfetto_include_perfetto_public_abi_public",
":perfetto_include_perfetto_public_base",
+ ":perfetto_include_perfetto_public_protos_protos",
":perfetto_include_perfetto_public_protozero",
+ ":perfetto_include_perfetto_public_public",
":perfetto_include_perfetto_trace_processor_basic_types",
":perfetto_include_perfetto_trace_processor_storage",
":perfetto_include_perfetto_trace_processor_trace_processor",
@@ -16748,6 +16795,8 @@
":perfetto_src_trace_processor_importers_fuchsia_minimal",
":perfetto_src_trace_processor_importers_gzip_full",
":perfetto_src_trace_processor_importers_i2c_full",
+ ":perfetto_src_trace_processor_importers_instruments_instruments",
+ ":perfetto_src_trace_processor_importers_instruments_row",
":perfetto_src_trace_processor_importers_json_full",
":perfetto_src_trace_processor_importers_json_minimal",
":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
@@ -16807,6 +16856,7 @@
":perfetto_src_traceconv_utils",
],
static_libs: [
+ "libexpat",
"libsqlite_static_noicu",
"libz",
"perfetto_src_trace_processor_demangle",
diff --git a/BUILD b/BUILD
index 2f616c6..2f6503b 100644
--- a/BUILD
+++ b/BUILD
@@ -236,6 +236,8 @@
":src_trace_processor_importers_fuchsia_minimal",
":src_trace_processor_importers_gzip_full",
":src_trace_processor_importers_i2c_full",
+ ":src_trace_processor_importers_instruments_instruments",
+ ":src_trace_processor_importers_instruments_row",
":src_trace_processor_importers_json_full",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
@@ -305,8 +307,11 @@
":include_perfetto_ext_traced_sys_stats_counters",
":include_perfetto_protozero_protozero",
":include_perfetto_public_abi_base",
+ ":include_perfetto_public_abi_public",
":include_perfetto_public_base",
+ ":include_perfetto_public_protos_protos",
":include_perfetto_public_protozero",
+ ":include_perfetto_public_public",
":include_perfetto_trace_processor_basic_types",
":include_perfetto_trace_processor_storage",
":include_perfetto_trace_processor_trace_processor",
@@ -367,7 +372,8 @@
":src_trace_processor_metrics_gen_cc_metrics_descriptor",
":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
":src_trace_processor_perfetto_sql_stdlib_stdlib",
- ] + PERFETTO_CONFIG.deps.jsoncpp +
+ ] + PERFETTO_CONFIG.deps.expat +
+ PERFETTO_CONFIG.deps.jsoncpp +
PERFETTO_CONFIG.deps.sqlite +
PERFETTO_CONFIG.deps.sqlite_ext_percentile +
PERFETTO_CONFIG.deps.zlib +
@@ -1530,6 +1536,8 @@
"src/trace_processor/importers/common/global_args_tracker.h",
"src/trace_processor/importers/common/jit_cache.cc",
"src/trace_processor/importers/common/jit_cache.h",
+ "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc",
+ "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h",
"src/trace_processor/importers/common/machine_tracker.cc",
"src/trace_processor/importers/common/machine_tracker.h",
"src/trace_processor/importers/common/mapping_tracker.cc",
@@ -1634,6 +1642,8 @@
"src/trace_processor/importers/ftrace/iostat_tracker.h",
"src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc",
"src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h",
+ "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc",
+ "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h",
"src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.cc",
"src/trace_processor/importers/ftrace/pkvm_hyp_cpu_tracker.h",
"src/trace_processor/importers/ftrace/rss_stat_tracker.cc",
@@ -1705,6 +1715,27 @@
],
)
+# GN target: //src/trace_processor/importers/instruments:instruments
+perfetto_filegroup(
+ name = "src_trace_processor_importers_instruments_instruments",
+ srcs = [
+ "src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc",
+ "src/trace_processor/importers/instruments/instruments_xml_tokenizer.h",
+ "src/trace_processor/importers/instruments/row_data_tracker.cc",
+ "src/trace_processor/importers/instruments/row_data_tracker.h",
+ "src/trace_processor/importers/instruments/row_parser.cc",
+ "src/trace_processor/importers/instruments/row_parser.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/instruments:row
+perfetto_filegroup(
+ name = "src_trace_processor_importers_instruments_row",
+ srcs = [
+ "src/trace_processor/importers/instruments/row.h",
+ ],
+)
+
# GN target: //src/trace_processor/importers/json:full
perfetto_filegroup(
name = "src_trace_processor_importers_json_full",
@@ -2751,6 +2782,7 @@
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_stats.sql",
+ "src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql",
],
)
@@ -5240,6 +5272,7 @@
"protos/perfetto/trace/ftrace/oom.proto",
"protos/perfetto/trace/ftrace/panel.proto",
"protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/pixel_mm.proto",
"protos/perfetto/trace/ftrace/power.proto",
"protos/perfetto/trace/ftrace/printk.proto",
"protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6228,6 +6261,8 @@
":src_trace_processor_importers_fuchsia_minimal",
":src_trace_processor_importers_gzip_full",
":src_trace_processor_importers_i2c_full",
+ ":src_trace_processor_importers_instruments_instruments",
+ ":src_trace_processor_importers_instruments_row",
":src_trace_processor_importers_json_full",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
@@ -6294,8 +6329,11 @@
":include_perfetto_ext_traced_sys_stats_counters",
":include_perfetto_protozero_protozero",
":include_perfetto_public_abi_base",
+ ":include_perfetto_public_abi_public",
":include_perfetto_public_base",
+ ":include_perfetto_public_protos_protos",
":include_perfetto_public_protozero",
+ ":include_perfetto_public_public",
":include_perfetto_trace_processor_basic_types",
":include_perfetto_trace_processor_storage",
":include_perfetto_trace_processor_trace_processor",
@@ -6358,7 +6396,8 @@
":src_trace_processor_metrics_gen_cc_metrics_descriptor",
":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
":src_trace_processor_perfetto_sql_stdlib_stdlib",
- ] + PERFETTO_CONFIG.deps.jsoncpp +
+ ] + PERFETTO_CONFIG.deps.expat +
+ PERFETTO_CONFIG.deps.jsoncpp +
PERFETTO_CONFIG.deps.sqlite +
PERFETTO_CONFIG.deps.sqlite_ext_percentile +
PERFETTO_CONFIG.deps.zlib +
@@ -6380,8 +6419,11 @@
":include_perfetto_ext_traced_sys_stats_counters",
":include_perfetto_protozero_protozero",
":include_perfetto_public_abi_base",
+ ":include_perfetto_public_abi_public",
":include_perfetto_public_base",
+ ":include_perfetto_public_protos_protos",
":include_perfetto_public_protozero",
+ ":include_perfetto_public_public",
":include_perfetto_trace_processor_basic_types",
":include_perfetto_trace_processor_storage",
":include_perfetto_trace_processor_trace_processor",
@@ -6410,6 +6452,8 @@
":src_trace_processor_importers_fuchsia_minimal",
":src_trace_processor_importers_gzip_full",
":src_trace_processor_importers_i2c_full",
+ ":src_trace_processor_importers_instruments_instruments",
+ ":src_trace_processor_importers_instruments_row",
":src_trace_processor_importers_json_full",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
@@ -6531,7 +6575,8 @@
":src_trace_processor_metrics_gen_cc_metrics_descriptor",
":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
":src_trace_processor_perfetto_sql_stdlib_stdlib",
- ] + PERFETTO_CONFIG.deps.jsoncpp +
+ ] + PERFETTO_CONFIG.deps.expat +
+ PERFETTO_CONFIG.deps.jsoncpp +
PERFETTO_CONFIG.deps.linenoise +
PERFETTO_CONFIG.deps.protobuf_full +
PERFETTO_CONFIG.deps.sqlite +
@@ -6622,8 +6667,11 @@
":include_perfetto_profiling_pprof_builder",
":include_perfetto_protozero_protozero",
":include_perfetto_public_abi_base",
+ ":include_perfetto_public_abi_public",
":include_perfetto_public_base",
+ ":include_perfetto_public_protos_protos",
":include_perfetto_public_protozero",
+ ":include_perfetto_public_public",
":include_perfetto_trace_processor_basic_types",
":include_perfetto_trace_processor_storage",
":include_perfetto_trace_processor_trace_processor",
@@ -6652,6 +6700,8 @@
":src_trace_processor_importers_fuchsia_minimal",
":src_trace_processor_importers_gzip_full",
":src_trace_processor_importers_i2c_full",
+ ":src_trace_processor_importers_instruments_instruments",
+ ":src_trace_processor_importers_instruments_row",
":src_trace_processor_importers_json_full",
":src_trace_processor_importers_json_minimal",
":src_trace_processor_importers_memory_tracker_graph_processor",
@@ -6774,7 +6824,8 @@
":src_trace_processor_perfetto_sql_stdlib_stdlib",
":src_traceconv_gen_cc_trace_descriptor",
":src_traceconv_gen_cc_winscope_descriptor",
- ] + PERFETTO_CONFIG.deps.jsoncpp +
+ ] + PERFETTO_CONFIG.deps.expat +
+ PERFETTO_CONFIG.deps.jsoncpp +
PERFETTO_CONFIG.deps.sqlite +
PERFETTO_CONFIG.deps.sqlite_ext_percentile +
PERFETTO_CONFIG.deps.zlib +
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
index 69dbcbb..d97179a 100644
--- a/bazel/deps.bzl
+++ b/bazel/deps.bzl
@@ -67,6 +67,14 @@
)
_add_repo_if_not_existing(
+ new_git_repository,
+ name = "perfetto_dep_expat",
+ remote = "https://github.com/libexpat/libexpat",
+ commit = "fa75b96546c069d17b8f80d91e0f4ef0cde3790d", # R_2_6_2
+ build_file = "//bazel:expat.BUILD",
+ )
+
+ _add_repo_if_not_existing(
http_archive,
name = "perfetto_dep_zlib",
url = "https://storage.googleapis.com/perfetto/zlib-6d3f6aa0f87c9791ca7724c279ef61384f331dfd.tar.gz",
diff --git a/bazel/expat.BUILD b/bazel/expat.BUILD
new file mode 100644
index 0000000..10f88c7
--- /dev/null
+++ b/bazel/expat.BUILD
@@ -0,0 +1,64 @@
+# 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.
+
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+
+cc_library(
+ name = "expat",
+ hdrs = glob(["expat/lib/*.h"]),
+ deps = [
+ ":expat_impl",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+cc_library(
+ name = "expat_impl",
+ srcs = [
+ "expat/lib/xmlparse.c",
+ "expat/lib/xmlrole.c",
+ "expat/lib/xmltok.c",
+ ],
+ hdrs = [
+ "expat/lib/ascii.h",
+ "expat/lib/asciitab.h",
+ "expat/lib/expat.h",
+ "expat/lib/expat_external.h",
+ "expat/lib/iasciitab.h",
+ "expat/lib/internal.h",
+ "expat/lib/latin1tab.h",
+ "expat/lib/nametab.h",
+ "expat/lib/siphash.h",
+ "expat/lib/utf8tab.h",
+ "expat/lib/winconfig.h",
+ "expat/lib/xmlrole.h",
+ "expat/lib/xmltok.h",
+ "expat/lib/xmltok_impl.c",
+ "expat/lib/xmltok_impl.h",
+ "expat/lib/xmltok_ns.c",
+ ],
+ deps = [
+ "@perfetto//buildtools/expat/include:expat_config",
+ ],
+ copts = [
+ "-DHAVE_EXPAT_CONFIG_H",
+ ] + PERFETTO_CONFIG.deps_copts.expat,
+ defines = [
+ "XML_STATIC"
+ ],
+ includes = [
+ "expat",
+ "expat/lib",
+ ],
+)
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index cbabb29..254bcb8 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -45,6 +45,7 @@
base_platform = ["//:perfetto_base_default_platform"],
zlib = ["@perfetto_dep_zlib//:zlib"],
+ expat = ["@perfetto_dep_expat//:expat"],
jsoncpp = ["@perfetto_dep_jsoncpp//:jsoncpp"],
linenoise = ["@perfetto_dep_linenoise//:linenoise"],
sqlite = ["@perfetto_dep_sqlite//:sqlite"],
@@ -83,6 +84,7 @@
# initialized with the Perfetto build files (i.e. via perfetto_deps()).
deps_copts = struct(
zlib = [],
+ expat = [],
jsoncpp = [],
linenoise = [],
sqlite = [],
diff --git a/buildtools/.gitignore b/buildtools/.gitignore
index a3e6376..42137c3 100644
--- a/buildtools/.gitignore
+++ b/buildtools/.gitignore
@@ -11,6 +11,7 @@
/catapult_trace_viewer/
/clang_format/
/clang/
+/expat/src/
/d8/
/debian_sid_arm-sysroot/
/debian_sid_arm64-sysroot/
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index b58b678..b4af804 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -1402,6 +1402,41 @@
deps = [ "//gn:default_deps" ]
}
+config("expat_public_config") {
+ defines = [ "XML_STATIC" ]
+ cflags = [
+ # Using -isystem instead of include_dirs (-I), so we don't need to suppress
+ # warnings coming from third-party headers. Doing so would mask warnings in
+ # our own code.
+ perfetto_isystem_cflag,
+ rebase_path("expat/src/expat/lib", root_build_dir),
+ perfetto_isystem_cflag,
+ rebase_path("expat/include", root_build_dir),
+ ]
+}
+
+config("no_format_warning") {
+ cflags = [ "-Wno-format" ]
+}
+
+static_library("expat") {
+ sources = [
+ "expat/src/expat/lib/expat.h",
+ "expat/src/expat/lib/xmlparse.c",
+ "expat/src/expat/lib/xmlrole.c",
+ "expat/src/expat/lib/xmltok.c",
+ ]
+
+ public_configs = [ ":expat_public_config" ]
+ configs -= [ "//gn/standalone:extra_warnings" ]
+ configs += [ ":no_format_warning" ]
+
+ defines = [
+ "_LIB",
+ "HAVE_EXPAT_CONFIG_H",
+ ]
+}
+
config("linenoise_config") {
visibility = _buildtools_visibility
cflags = [
diff --git a/buildtools/expat/include/BUILD b/buildtools/expat/include/BUILD
new file mode 100644
index 0000000..1c6cb8f
--- /dev/null
+++ b/buildtools/expat/include/BUILD
@@ -0,0 +1,24 @@
+# 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.
+
+cc_library(
+ name = "expat_config",
+ hdrs = [
+ "expat_config.h",
+ ],
+ includes = [
+ ".",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/buildtools/expat/include/expat_config.h b/buildtools/expat/include/expat_config.h
new file mode 100644
index 0000000..8ce2388
--- /dev/null
+++ b/buildtools/expat/include/expat_config.h
@@ -0,0 +1,146 @@
+/* expat_config.h. Generated from expat_config.h.in by configure. */
+/* expat_config.h.in. Generated from configure.ac by autoheader. */
+
+#ifndef EXPAT_CONFIG_H
+#define EXPAT_CONFIG_H 1
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* 1234 = LILENDIAN, 4321 = BIGENDIAN */
+#define BYTEORDER 1234
+
+/* Define to 1 if you have the `arc4random' function. */
+/* #undef HAVE_ARC4RANDOM */
+
+/* Define to 1 if you have the `arc4random_buf' function. */
+/* #define HAVE_ARC4RANDOM_BUF 1 */
+
+/* define if the compiler supports basic C++11 syntax */
+#define HAVE_CXX11 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `getpagesize' function. */
+#define HAVE_GETPAGESIZE 1
+
+/* Define to 1 if you have the `getrandom' function. */
+/* #define HAVE_GETRANDOM 1 */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `bsd' library (-lbsd). */
+/* #undef HAVE_LIBBSD */
+
+/* Define to 1 if you have a working `mmap' system call. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have `syscall' and `SYS_getrandom'. */
+/* #define HAVE_SYSCALL_GETRANDOM 1 */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "expat"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "https://github.com/libexpat/libexpat/issues"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "expat"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "expat 2.6.2"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "expat"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.6.2"
+
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+ required in a freestanding environment). This macro is provided for
+ backward compatibility; new code need not use it. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "2.6.2"
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+#if defined __BIG_ENDIAN__
+#define WORDS_BIGENDIAN 1
+#endif
+#else
+#ifndef WORDS_BIGENDIAN
+/* # undef WORDS_BIGENDIAN */
+#endif
+#endif
+
+/* Define to allow retrieving the byte offsets for attribute names and values.
+ */
+/* #undef XML_ATTR_INFO */
+
+/* Define to specify how much context to retain around the current parse
+ point, 0 to disable. */
+#define XML_CONTEXT_BYTES 1024
+
+/* Define to include code reading entropy from `/dev/urandom'. */
+#define XML_DEV_URANDOM 1
+
+/* Define to make parameter entity parsing functionality available. */
+#define XML_DTD 1
+
+/* Define as 1/0 to enable/disable support for general entities. */
+#define XML_GE 1
+
+/* Define to make XML Namespaces functionality available. */
+#define XML_NS 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+#endif // ndef EXPAT_CONFIG_H
\ No newline at end of file
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index e61fb9f..bfa7a8e 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -363,6 +363,14 @@
}
}
+group("expat") {
+ if (perfetto_root_path == "//") {
+ public_deps = [ "//buildtools:expat" ]
+ } else {
+ public_deps = [ "//third_party/expat:expat" ]
+ }
+}
+
if (enable_perfetto_trace_processor_json) {
group("jsoncpp") {
if (perfetto_root_path == "//") {
diff --git a/protos/perfetto/metrics/android/wattson_in_time_period.proto b/protos/perfetto/metrics/android/wattson_in_time_period.proto
index 2f02e4d..9691b66 100644
--- a/protos/perfetto/metrics/android/wattson_in_time_period.proto
+++ b/protos/perfetto/metrics/android/wattson_in_time_period.proto
@@ -31,7 +31,7 @@
message AndroidWattsonCpuSubsystemEstimate {
// estimates and estimates of subrails
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
optional AndroidWattsonPolicyEstimate policy0 = 2;
optional AndroidWattsonPolicyEstimate policy1 = 3;
optional AndroidWattsonPolicyEstimate policy2 = 4;
@@ -44,7 +44,7 @@
}
message AndroidWattsonPolicyEstimate {
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
optional AndroidWattsonCpuEstimate cpu0 = 2;
optional AndroidWattsonCpuEstimate cpu1 = 3;
optional AndroidWattsonCpuEstimate cpu2 = 4;
@@ -56,9 +56,9 @@
}
message AndroidWattsonCpuEstimate {
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
}
message AndroidWattsonDsuScuEstimate {
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
}
diff --git a/protos/perfetto/metrics/android/wattson_tasks_attribution.proto b/protos/perfetto/metrics/android/wattson_tasks_attribution.proto
index f505c04..43b79df 100644
--- a/protos/perfetto/metrics/android/wattson_tasks_attribution.proto
+++ b/protos/perfetto/metrics/android/wattson_tasks_attribution.proto
@@ -26,9 +26,9 @@
message AndroidWattsonTaskInfo {
// Average estimated power for wall duration in mW
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
// Total energy over wall duration across CPUs in mWs
- optional float estimate_mws = 2;
+ optional float estimated_mws = 2;
optional string thread_name = 3;
optional string process_name = 4;
optional string package_name = 5;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 7ef002f..621316c 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -2895,7 +2895,7 @@
message AndroidWattsonCpuSubsystemEstimate {
// estimates and estimates of subrails
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
optional AndroidWattsonPolicyEstimate policy0 = 2;
optional AndroidWattsonPolicyEstimate policy1 = 3;
optional AndroidWattsonPolicyEstimate policy2 = 4;
@@ -2908,7 +2908,7 @@
}
message AndroidWattsonPolicyEstimate {
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
optional AndroidWattsonCpuEstimate cpu0 = 2;
optional AndroidWattsonCpuEstimate cpu1 = 3;
optional AndroidWattsonCpuEstimate cpu2 = 4;
@@ -2920,11 +2920,11 @@
}
message AndroidWattsonCpuEstimate {
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
}
message AndroidWattsonDsuScuEstimate {
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
}
// End of protos/perfetto/metrics/android/wattson_in_time_period.proto
@@ -2939,9 +2939,9 @@
message AndroidWattsonTaskInfo {
// Average estimated power for wall duration in mW
- optional float estimate_mw = 1;
+ optional float estimated_mw = 1;
// Total energy over wall duration across CPUs in mWs
- optional float estimate_mws = 2;
+ optional float estimated_mws = 2;
optional string thread_name = 3;
optional string process_name = 4;
optional string package_name = 5;
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 1cb6085..5aae5d9 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -63,6 +63,7 @@
"oom.proto",
"panel.proto",
"perf_trace_counters.proto",
+ "pixel_mm.proto",
"power.proto",
"printk.proto",
"raw_syscalls.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index 4a0a589..877618a 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -63,6 +63,7 @@
import "protos/perfetto/trace/ftrace/oom.proto";
import "protos/perfetto/trace/ftrace/panel.proto";
import "protos/perfetto/trace/ftrace/perf_trace_counters.proto";
+import "protos/perfetto/trace/ftrace/pixel_mm.proto";
import "protos/perfetto/trace/ftrace/power.proto";
import "protos/perfetto/trace/ftrace/printk.proto";
import "protos/perfetto/trace/ftrace/raw_syscalls.proto";
@@ -674,5 +675,7 @@
KgslAdrenoCmdbatchSubmittedFtraceEvent kgsl_adreno_cmdbatch_submitted = 535;
KgslAdrenoCmdbatchSyncFtraceEvent kgsl_adreno_cmdbatch_sync = 536;
KgslAdrenoCmdbatchRetiredFtraceEvent kgsl_adreno_cmdbatch_retired = 537;
+ PixelMmKswapdWakeFtraceEvent pixel_mm_kswapd_wake = 538;
+ PixelMmKswapdDoneFtraceEvent pixel_mm_kswapd_done = 539;
}
}
diff --git a/protos/perfetto/trace/ftrace/perf_trace_counters.proto b/protos/perfetto/trace/ftrace/perf_trace_counters.proto
index 0e3531c..337e492 100644
--- a/protos/perfetto/trace/ftrace/perf_trace_counters.proto
+++ b/protos/perfetto/trace/ftrace/perf_trace_counters.proto
@@ -8,19 +8,25 @@
message SchedSwitchWithCtrsFtraceEvent {
optional int32 old_pid = 1;
optional int32 new_pid = 2;
- optional uint32 cctr = 3;
- optional uint32 ctr0 = 4;
- optional uint32 ctr1 = 5;
- optional uint32 ctr2 = 6;
- optional uint32 ctr3 = 7;
+ optional uint64 cctr = 3;
+ optional uint64 ctr0 = 4;
+ optional uint64 ctr1 = 5;
+ optional uint64 ctr2 = 6;
+ optional uint64 ctr3 = 7;
optional uint32 lctr0 = 8;
optional uint32 lctr1 = 9;
- optional uint32 ctr4 = 10;
- optional uint32 ctr5 = 11;
+ optional uint64 ctr4 = 10;
+ optional uint64 ctr5 = 11;
optional string prev_comm = 12;
optional int32 prev_pid = 13;
optional uint32 cyc = 14;
optional uint32 inst = 15;
optional uint32 stallbm = 16;
optional uint32 l3dm = 17;
+ optional int32 next_pid = 18;
+ optional string next_comm = 19;
+ optional int64 prev_state = 20;
+ optional uint64 amu0 = 21;
+ optional uint64 amu1 = 22;
+ optional uint64 amu2 = 23;
}
diff --git a/protos/perfetto/trace/ftrace/pixel_mm.proto b/protos/perfetto/trace/ftrace/pixel_mm.proto
new file mode 100644
index 0000000..5c323e4
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/pixel_mm.proto
@@ -0,0 +1,14 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message PixelMmKswapdWakeFtraceEvent {
+ optional int32 whatever = 1;
+}
+message PixelMmKswapdDoneFtraceEvent {
+ optional uint64 delta_nr_scanned = 1;
+ optional uint64 delta_nr_reclaimed = 2;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index a3822b2..a13fdff 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -9662,25 +9662,43 @@
message SchedSwitchWithCtrsFtraceEvent {
optional int32 old_pid = 1;
optional int32 new_pid = 2;
- optional uint32 cctr = 3;
- optional uint32 ctr0 = 4;
- optional uint32 ctr1 = 5;
- optional uint32 ctr2 = 6;
- optional uint32 ctr3 = 7;
+ optional uint64 cctr = 3;
+ optional uint64 ctr0 = 4;
+ optional uint64 ctr1 = 5;
+ optional uint64 ctr2 = 6;
+ optional uint64 ctr3 = 7;
optional uint32 lctr0 = 8;
optional uint32 lctr1 = 9;
- optional uint32 ctr4 = 10;
- optional uint32 ctr5 = 11;
+ optional uint64 ctr4 = 10;
+ optional uint64 ctr5 = 11;
optional string prev_comm = 12;
optional int32 prev_pid = 13;
optional uint32 cyc = 14;
optional uint32 inst = 15;
optional uint32 stallbm = 16;
optional uint32 l3dm = 17;
+ optional int32 next_pid = 18;
+ optional string next_comm = 19;
+ optional int64 prev_state = 20;
+ optional uint64 amu0 = 21;
+ optional uint64 amu1 = 22;
+ optional uint64 amu2 = 23;
}
// End of protos/perfetto/trace/ftrace/perf_trace_counters.proto
+// Begin of protos/perfetto/trace/ftrace/pixel_mm.proto
+
+message PixelMmKswapdWakeFtraceEvent {
+ optional int32 whatever = 1;
+}
+message PixelMmKswapdDoneFtraceEvent {
+ optional uint64 delta_nr_scanned = 1;
+ optional uint64 delta_nr_reclaimed = 2;
+}
+
+// End of protos/perfetto/trace/ftrace/pixel_mm.proto
+
// Begin of protos/perfetto/trace/ftrace/power.proto
message CpuFrequencyFtraceEvent {
@@ -11092,6 +11110,8 @@
KgslAdrenoCmdbatchSubmittedFtraceEvent kgsl_adreno_cmdbatch_submitted = 535;
KgslAdrenoCmdbatchSyncFtraceEvent kgsl_adreno_cmdbatch_sync = 536;
KgslAdrenoCmdbatchRetiredFtraceEvent kgsl_adreno_cmdbatch_retired = 537;
+ PixelMmKswapdWakeFtraceEvent pixel_mm_kswapd_wake = 538;
+ PixelMmKswapdDoneFtraceEvent pixel_mm_kswapd_done = 539;
}
}
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 7b9052d..7c2cd5c 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index 464683e..c61f3bb 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -242,11 +242,6 @@
r"/core_plugins/.*",
"core code should not depend on plugins.",
),
- #NoDirectDep(
- # r'/tracks/.*',
- # r'/core/.*',
- # 'instead tracks should depend on the API exposed at ui/src/public.',
- #),
NoDep(
r'/core/.*',
r'/plugins/.*',
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index c8ad70f..5271e82 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -116,7 +116,7 @@
}
template <typename E>
-std::optional<uint64_t> GetLoadBias(void* mem, size_t size) {
+std::optional<uint64_t> GetElfLoadBias(void* mem, size_t size) {
const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
PERFETTO_ELOG("Corrupted ELF.");
@@ -136,7 +136,7 @@
}
template <typename E>
-std::optional<std::string> GetBuildId(void* mem, size_t size) {
+std::optional<std::string> GetElfBuildId(void* mem, size_t size) {
const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
PERFETTO_ELOG("Corrupted ELF.");
@@ -202,13 +202,103 @@
mem[EI_MAG2] == ELFMAG2 && mem[EI_MAG3] == ELFMAG3);
}
-struct BuildIdAndLoadBias {
- std::string build_id;
- uint64_t load_bias;
+constexpr uint32_t kMachO64Magic = 0xfeedfacf;
+
+bool IsMachO64(const char* mem, size_t size) {
+ if (size < sizeof(kMachO64Magic))
+ return false;
+ return memcmp(mem, &kMachO64Magic, sizeof(kMachO64Magic)) == 0;
+}
+
+struct mach_header_64 {
+ uint32_t magic; /* mach magic number identifier */
+ int32_t cputype; /* cpu specifier */
+ int32_t cpusubtype; /* machine specifier */
+ uint32_t filetype; /* type of file */
+ uint32_t ncmds; /* number of load commands */
+ uint32_t sizeofcmds; /* the size of all the load commands */
+ uint32_t flags; /* flags */
+ uint32_t reserved; /* reserved */
};
-std::optional<BuildIdAndLoadBias> GetBuildIdAndLoadBias(const char* fname,
- size_t size) {
+struct load_command {
+ uint32_t cmd; /* type of load command */
+ uint32_t cmdsize; /* total size of command in bytes */
+};
+
+struct segment_64_command {
+ uint32_t cmd; /* LC_SEGMENT_64 */
+ uint32_t cmdsize; /* includes sizeof section_64 structs */
+ char segname[16]; /* segment name */
+ uint64_t vmaddr; /* memory address of this segment */
+ uint64_t vmsize; /* memory size of this segment */
+ uint64_t fileoff; /* file offset of this segment */
+ uint64_t filesize; /* amount to map from the file */
+ uint32_t maxprot; /* maximum VM protection */
+ uint32_t initprot; /* initial VM protection */
+ uint32_t nsects; /* number of sections in segment */
+ uint32_t flags; /* flags */
+};
+
+struct BinaryInfo {
+ std::string build_id;
+ uint64_t load_bias;
+ BinaryType type;
+};
+
+std::optional<BinaryInfo> GetMachOBinaryInfo(char* mem, size_t size) {
+ if (size < sizeof(mach_header_64))
+ return {};
+
+ mach_header_64 header;
+ memcpy(&header, mem, sizeof(mach_header_64));
+
+ if (size < sizeof(mach_header_64) + header.sizeofcmds)
+ return {};
+
+ std::optional<std::string> build_id;
+ uint64_t load_bias = 0;
+
+ char* pcmd = mem + sizeof(mach_header_64);
+ char* pcmds_end = pcmd + header.sizeofcmds;
+ while (pcmd < pcmds_end) {
+ load_command cmd_header;
+ memcpy(&cmd_header, pcmd, sizeof(load_command));
+
+ constexpr uint32_t LC_SEGMENT_64 = 0x19;
+ constexpr uint32_t LC_UUID = 0x1b;
+
+ switch (cmd_header.cmd) {
+ case LC_UUID: {
+ build_id = std::string(pcmd + sizeof(load_command),
+ cmd_header.cmdsize - sizeof(load_command));
+ break;
+ }
+ case LC_SEGMENT_64: {
+ segment_64_command seg_cmd;
+ memcpy(&seg_cmd, pcmd, sizeof(segment_64_command));
+ if (strcmp(seg_cmd.segname, "__TEXT") == 0) {
+ load_bias = seg_cmd.vmaddr;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ pcmd += cmd_header.cmdsize;
+ }
+
+ if (build_id) {
+ constexpr uint32_t MH_DSYM = 0xa;
+ BinaryType type = header.filetype == MH_DSYM ? BinaryType::kMachODsym
+ : BinaryType::kMachO;
+ return BinaryInfo{*build_id, load_bias, type};
+ }
+ return {};
+}
+
+std::optional<BinaryInfo> GetBinaryInfo(const char* fname, size_t size) {
static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
if (size <= EI_CLASS)
return std::nullopt;
@@ -219,25 +309,26 @@
}
char* mem = static_cast<char*>(map.data());
- if (!IsElf(mem, size))
- return std::nullopt;
-
std::optional<std::string> build_id;
std::optional<uint64_t> load_bias;
- switch (mem[EI_CLASS]) {
- case ELFCLASS32:
- build_id = GetBuildId<Elf32>(mem, size);
- load_bias = GetLoadBias<Elf32>(mem, size);
- break;
- case ELFCLASS64:
- build_id = GetBuildId<Elf64>(mem, size);
- load_bias = GetLoadBias<Elf64>(mem, size);
- break;
- default:
- return std::nullopt;
- }
- if (build_id && load_bias) {
- return BuildIdAndLoadBias{*build_id, *load_bias};
+ if (IsElf(mem, size)) {
+ switch (mem[EI_CLASS]) {
+ case ELFCLASS32:
+ build_id = GetElfBuildId<Elf32>(mem, size);
+ load_bias = GetElfLoadBias<Elf32>(mem, size);
+ break;
+ case ELFCLASS64:
+ build_id = GetElfBuildId<Elf64>(mem, size);
+ load_bias = GetElfLoadBias<Elf64>(mem, size);
+ break;
+ default:
+ return std::nullopt;
+ }
+ if (build_id && load_bias) {
+ return BinaryInfo{*build_id, *load_bias, BinaryType::kElf};
+ }
+ } else if (IsMachO64(mem, size)) {
+ return GetMachOBinaryInfo(mem, size);
}
return std::nullopt;
}
@@ -245,6 +336,7 @@
std::map<std::string, FoundBinary> BuildIdIndex(std::vector<std::string> dirs) {
std::map<std::string, FoundBinary> result;
WalkDirectories(std::move(dirs), [&result](const char* fname, size_t size) {
+ static_assert(EI_MAG3 + 1 == sizeof(kMachO64Magic));
char magic[EI_MAG3 + 1];
// Scope file access. On windows OpenFile opens an exclusive lock.
// This lock needs to be released before mapping the file.
@@ -259,16 +351,39 @@
PERFETTO_PLOG("Failed to read %s", fname);
return;
}
- if (!IsElf(magic, static_cast<size_t>(rd))) {
- PERFETTO_DLOG("%s not an ELF.", fname);
+ if (!IsElf(magic, static_cast<size_t>(rd)) &&
+ !IsMachO64(magic, static_cast<size_t>(rd))) {
+ PERFETTO_DLOG("%s not an ELF or Mach-O 64.", fname);
return;
}
}
- std::optional<BuildIdAndLoadBias> build_id_and_load_bias =
- GetBuildIdAndLoadBias(fname, size);
- if (build_id_and_load_bias) {
- result.emplace(build_id_and_load_bias->build_id,
- FoundBinary{fname, build_id_and_load_bias->load_bias});
+ std::optional<BinaryInfo> binary_info = GetBinaryInfo(fname, size);
+ if (!binary_info) {
+ PERFETTO_DLOG("Failed to extract build id from %s.", fname);
+ return;
+ }
+ auto it = result.emplace(
+ binary_info->build_id,
+ FoundBinary{fname, binary_info->load_bias, binary_info->type});
+
+ // If there was already an existing FoundBinary, the emplace wouldn't insert
+ // anything. But, for Mac binaries, we prefer dSYM files over the original
+ // binary, so make sure these overwrite the FoundBinary entry.
+ bool has_existing = it.second == false;
+ if (has_existing) {
+ if (it.first->second.type == BinaryType::kMachO &&
+ binary_info->type == BinaryType::kMachODsym) {
+ PERFETTO_LOG("Overwriting index entry for %s to %s.",
+ base::ToHex(binary_info->build_id).c_str(), fname);
+ it.first->second =
+ FoundBinary{fname, binary_info->load_bias, binary_info->type};
+ } else {
+ PERFETTO_DLOG("Ignoring %s, index entry for %s already exists.", fname,
+ base::ToHex(binary_info->build_id).c_str());
+ }
+ } else {
+ PERFETTO_LOG("Indexed: %s (%s)", fname,
+ base::ToHex(binary_info->build_id).c_str());
}
});
return result;
@@ -548,6 +663,13 @@
std::optional<FoundBinary>& cache_entry = p.first->second;
+ // Try the absolute path first.
+ if (base::StartsWith(abspath, "/")) {
+ cache_entry = IsCorrectFile(abspath, build_id);
+ if (cache_entry)
+ return cache_entry;
+ }
+
for (const std::string& root_str : roots_) {
cache_entry = FindBinaryInRoot(root_str, abspath, build_id);
if (cache_entry)
@@ -579,14 +701,14 @@
return std::nullopt;
}
- std::optional<BuildIdAndLoadBias> build_id_and_load_bias =
- GetBuildIdAndLoadBias(symbol_file.c_str(), size);
- if (!build_id_and_load_bias)
+ std::optional<BinaryInfo> binary_info =
+ GetBinaryInfo(symbol_file.c_str(), size);
+ if (!binary_info)
return std::nullopt;
- if (build_id_and_load_bias->build_id != build_id) {
+ if (binary_info->build_id != build_id) {
return std::nullopt;
}
- return FoundBinary{symbol_file, build_id_and_load_bias->load_bias};
+ return FoundBinary{symbol_file, binary_info->load_bias, binary_info->type};
}
std::optional<FoundBinary> LocalBinaryFinder::FindBinaryInRoot(
diff --git a/src/profiling/symbolizer/local_symbolizer.h b/src/profiling/symbolizer/local_symbolizer.h
index d7185ef..36a8718 100644
--- a/src/profiling/symbolizer/local_symbolizer.h
+++ b/src/profiling/symbolizer/local_symbolizer.h
@@ -33,10 +33,16 @@
bool ParseLlvmSymbolizerJsonLine(const std::string& line,
std::vector<SymbolizedFrame>* result);
+enum BinaryType {
+ kElf,
+ kMachO,
+ kMachODsym,
+};
struct FoundBinary {
std::string file_name;
uint64_t load_bias;
+ BinaryType type;
};
class BinaryFinder {
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index 9d5553d..aecdad3 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -531,4 +531,6 @@
kgsl/adreno_cmdbatch_queued
kgsl/adreno_cmdbatch_submitted
kgsl/adreno_cmdbatch_sync
-kgsl/adreno_cmdbatch_retired
\ No newline at end of file
+kgsl/adreno_cmdbatch_retired
+pixel_mm/pixel_mm_kswapd_wake
+pixel_mm/pixel_mm_kswapd_done
\ No newline at end of file
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index ec71357..e063ea6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -174,6 +174,7 @@
"importers/ftrace:full",
"importers/fuchsia:full",
"importers/gzip:full",
+ "importers/instruments",
"importers/json:full",
"importers/json:minimal",
"importers/ninja",
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 70bffd5..b2250fd 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -57,6 +57,7 @@
return std::nullopt;
case kPerfDataTraceType:
+ case kInstrumentsXmlTraceType:
return TraceSorter::SortingMode::kDefault;
case kUnknownTraceType:
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index c2f61da..d128b43 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -41,6 +41,8 @@
"global_args_tracker.h",
"jit_cache.cc",
"jit_cache.h",
+ "legacy_v8_cpu_profile_tracker.cc",
+ "legacy_v8_cpu_profile_tracker.h",
"machine_tracker.cc",
"machine_tracker.h",
"mapping_tracker.cc",
diff --git a/src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc b/src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc
new file mode 100644
index 0000000..9206d87
--- /dev/null
+++ b/src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
+
+#include <cstdint>
+#include <optional>
+#include <utility>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor {
+
+LegacyV8CpuProfileTracker::LegacyV8CpuProfileTracker(
+ TraceProcessorContext* context)
+ : context_(context) {}
+
+void LegacyV8CpuProfileTracker::SetStartTsForSessionAndPid(uint64_t session_id,
+ uint32_t pid,
+ int64_t ts) {
+ auto [it, inserted] = state_by_session_and_pid_.Insert(
+ std::make_pair(session_id, pid),
+ State{ts, base::FlatHashMap<uint32_t, CallsiteId>(), nullptr});
+ it->ts = ts;
+ if (inserted) {
+ it->mapping = &context_->mapping_tracker->CreateDummyMapping("");
+ }
+}
+
+base::Status LegacyV8CpuProfileTracker::AddCallsite(
+ uint64_t session_id,
+ uint32_t pid,
+ uint32_t raw_callsite_id,
+ std::optional<uint32_t> parent_raw_callsite_id,
+ base::StringView script_url,
+ base::StringView function_name) {
+ auto* state = state_by_session_and_pid_.Find(std::make_pair(session_id, pid));
+ if (!state) {
+ return base::ErrStatus(
+ "v8 profile id does not exist: cannot insert callsite");
+ }
+ FrameId frame_id =
+ state->mapping->InternDummyFrame(function_name, script_url);
+ CallsiteId callsite_id;
+ if (parent_raw_callsite_id) {
+ auto* parent_id = state->callsites.Find(*parent_raw_callsite_id);
+ if (!parent_id) {
+ return base::ErrStatus(
+ "v8 profile parent id does not exist: cannot insert callsite");
+ }
+ auto row =
+ context_->storage->stack_profile_callsite_table().FindById(*parent_id);
+ callsite_id = context_->stack_profile_tracker->InternCallsite(
+ *parent_id, frame_id, row->depth() + 1);
+ } else {
+ callsite_id = context_->stack_profile_tracker->InternCallsite(std::nullopt,
+ frame_id, 0);
+ }
+ if (!state->callsites.Insert(raw_callsite_id, callsite_id).second) {
+ return base::ErrStatus("v8 profile: callsite with id already exists");
+ }
+ return base::OkStatus();
+}
+
+base::StatusOr<int64_t> LegacyV8CpuProfileTracker::AddDeltaAndGetTs(
+ uint64_t session_id,
+ uint32_t pid,
+ int64_t delta_ts) {
+ auto* state = state_by_session_and_pid_.Find(std::make_pair(session_id, pid));
+ if (!state) {
+ return base::ErrStatus(
+ "v8 profile id does not exist: cannot compute timestamp from delta");
+ }
+ state->ts += delta_ts;
+ return state->ts;
+}
+
+base::Status LegacyV8CpuProfileTracker::AddSample(int64_t ts,
+ uint64_t session_id,
+ uint32_t pid,
+ uint32_t tid,
+ uint32_t raw_callsite_id) {
+ auto* state = state_by_session_and_pid_.Find(std::make_pair(session_id, pid));
+ if (!state) {
+ return base::ErrStatus("v8 callsite id does not exist: cannot add sample");
+ }
+ auto* id = state->callsites.Find(raw_callsite_id);
+ if (!id) {
+ return base::ErrStatus("v8 callsite id does not exist: cannot add sample");
+ }
+ UniqueTid utid = context_->process_tracker->UpdateThread(tid, pid);
+ auto* samples = context_->storage->mutable_cpu_profile_stack_sample_table();
+ samples->Insert({ts, *id, utid, 0});
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h b/src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h
new file mode 100644
index 0000000..bdb2e78
--- /dev/null
+++ b/src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_LEGACY_V8_CPU_PROFILE_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_LEGACY_V8_CPU_PROFILE_TRACKER_H_
+
+#include <cstdint>
+#include <optional>
+#include <utility>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor {
+
+// Stores interned callsites for given pid for legacy v8 samples.
+class LegacyV8CpuProfileTracker {
+ public:
+ explicit LegacyV8CpuProfileTracker(TraceProcessorContext*);
+
+ // Sets the start timestamp for the given pid.
+ void SetStartTsForSessionAndPid(uint64_t session_id,
+ uint32_t pid,
+ int64_t ts);
+
+ // Adds the callsite with for the given session and pid and given raw callsite
+ // id.
+ base::Status AddCallsite(uint64_t session_id,
+ uint32_t pid,
+ uint32_t raw_callsite_id,
+ std::optional<uint32_t> parent_raw_callsite_id,
+ base::StringView script_url,
+ base::StringView function_name);
+
+ // Increments the current timestamp for the given session and pid by
+ // |delta_ts| and returns the resulting full timestamp.
+ base::StatusOr<int64_t> AddDeltaAndGetTs(uint64_t session_id,
+ uint32_t pid,
+ int64_t delta_ts);
+
+ // Adds the sample with for the given session and pid/tid and given raw
+ // callsite id.
+ base::Status AddSample(int64_t ts,
+ uint64_t session_id,
+ uint32_t pid,
+ uint32_t tid,
+ uint32_t raw_callsite_id);
+
+ private:
+ struct State {
+ int64_t ts;
+ base::FlatHashMap<uint32_t, CallsiteId> callsites;
+ DummyMemoryMapping* mapping;
+ };
+ struct Hasher {
+ uint64_t operator()(const std::pair<uint64_t, uint32_t>& res) {
+ return base::Hasher::Combine(res.first, res.second);
+ }
+ };
+ base::FlatHashMap<std::pair<uint64_t, uint32_t>, State, Hasher>
+ state_by_session_and_pid_;
+
+ TraceProcessorContext* const context_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_LEGACY_V8_CPU_PROFILE_TRACKER_H_
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
index 02976f9..6e6fbf3 100644
--- a/src/trace_processor/importers/common/mapping_tracker.cc
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -25,6 +25,7 @@
#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/address_range.h"
#include "src/trace_processor/importers/common/jit_cache.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/build_id.h"
@@ -161,14 +162,15 @@
});
}
-VirtualMemoryMapping* MappingTracker::GetDummyMapping() {
- if (!dummy_mapping_) {
- CreateMappingParams params;
- params.memory_range =
- AddressRange::FromStartAndSize(0, std::numeric_limits<uint64_t>::max());
- dummy_mapping_ = &InternMemoryMapping(params);
- }
- return dummy_mapping_;
+DummyMemoryMapping& MappingTracker::CreateDummyMapping(std::string name) {
+ CreateMappingParams params;
+ params.name = std::move(name);
+ params.memory_range =
+ AddressRange::FromStartAndSize(0, std::numeric_limits<uint64_t>::max());
+ std::unique_ptr<DummyMemoryMapping> mapping(
+ new DummyMemoryMapping(context_, std::move(params)));
+
+ return AddMapping(std::move(mapping));
}
} // namespace trace_processor
diff --git a/src/trace_processor/importers/common/mapping_tracker.h b/src/trace_processor/importers/common/mapping_tracker.h
index 4791dba..c655d57 100644
--- a/src/trace_processor/importers/common/mapping_tracker.h
+++ b/src/trace_processor/importers/common/mapping_tracker.h
@@ -68,6 +68,10 @@
UserMemoryMapping& CreateUserMemoryMapping(UniquePid upid,
CreateMappingParams params);
+ // Sometimes we just need a mapping and we are lacking trace data to create a
+ // proper one. Use this mapping in those cases.
+ DummyMemoryMapping& CreateDummyMapping(std::string name);
+
// Create an "other" mapping. Returned reference will be valid for the
// duration of this instance.
VirtualMemoryMapping& InternMemoryMapping(CreateMappingParams params);
@@ -91,10 +95,6 @@
// Jitted ranges will only be applied to UserMemoryMappings
void AddJitRange(UniquePid upid, AddressRange range, JitCache* jit_cache);
- // Sometimes we just need a mapping and we are lacking trace data to create a
- // proper one. Use this mapping in those cases.
- VirtualMemoryMapping* GetDummyMapping();
-
private:
template <typename MappingImpl>
MappingImpl& AddMapping(std::unique_ptr<MappingImpl> mapping);
@@ -140,8 +140,6 @@
KernelMemoryMapping* kernel_ = nullptr;
base::FlatHashMap<UniquePid, AddressRangeMap<JitCache*>> jit_caches_;
-
- VirtualMemoryMapping* dummy_mapping_ = nullptr;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/common/parser_types.h b/src/trace_processor/importers/common/parser_types.h
index 7290626..bd32bde 100644
--- a/src/trace_processor/importers/common/parser_types.h
+++ b/src/trace_processor/importers/common/parser_types.h
@@ -17,14 +17,19 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PARSER_TYPES_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PARSER_TYPES_H_
-#include <stdint.h>
+#include <array>
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <string>
+#include <utility>
+#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
struct alignas(8) InlineSchedSwitch {
int64_t prev_state;
@@ -32,6 +37,11 @@
int32_t next_prio;
StringPool::Id next_comm;
};
+static_assert(sizeof(InlineSchedSwitch) == 24);
+
+// We enforce the exact size as it's critical for peak-memory use when sorting
+// data in trace processor that this struct is as small as possible.
+static_assert(sizeof(InlineSchedSwitch) == 24);
struct alignas(8) InlineSchedWaking {
int32_t pid;
@@ -40,18 +50,23 @@
StringPool::Id comm;
uint16_t common_flags;
};
+
+// We enforce the exact size as it's critical for peak-memory use when sorting
+// data in trace processor that this struct is as small as possible.
static_assert(sizeof(InlineSchedWaking) == 16);
struct alignas(8) JsonEvent {
std::string value;
};
+static_assert(sizeof(JsonEvent) % 8 == 0);
-struct TracePacketData {
+struct alignas(8) TracePacketData {
TraceBlobView packet;
RefPtr<PacketSequenceStateGeneration> sequence_state;
};
+static_assert(sizeof(TracePacketData) % 8 == 0);
-struct TrackEventData {
+struct alignas(8) TrackEventData {
TrackEventData(TraceBlobView pv,
RefPtr<PacketSequenceStateGeneration> generation)
: trace_packet_data{std::move(pv), std::move(generation)} {}
@@ -75,8 +90,16 @@
double counter_value = 0;
std::array<double, kMaxNumExtraCounters> extra_counter_values = {};
};
+static_assert(sizeof(TracePacketData) % 8 == 0);
-} // namespace trace_processor
-} // namespace perfetto
+struct alignas(8) LegacyV8CpuProfileEvent {
+ uint64_t session_id;
+ uint32_t pid;
+ uint32_t tid;
+ uint32_t callsite_id;
+};
+static_assert(sizeof(LegacyV8CpuProfileEvent) % 8 == 0);
+
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PARSER_TYPES_H_
diff --git a/src/trace_processor/importers/common/trace_parser.cc b/src/trace_processor/importers/common/trace_parser.cc
index f497278..a667b75 100644
--- a/src/trace_processor/importers/common/trace_parser.cc
+++ b/src/trace_processor/importers/common/trace_parser.cc
@@ -23,6 +23,7 @@
JsonTraceParser::~JsonTraceParser() = default;
FuchsiaRecordParser::~FuchsiaRecordParser() = default;
PerfRecordParser::~PerfRecordParser() = default;
+InstrumentsRowParser::~InstrumentsRowParser() = default;
AndroidLogEventParser::~AndroidLogEventParser() = default;
} // namespace trace_processor
diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h
index 87fd8ff..cf7c84a 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -17,13 +17,16 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_PARSER_H_
-#include <stdint.h>
+#include <cstdint>
#include <string>
namespace perfetto::trace_processor {
namespace perf_importer {
struct Record;
}
+namespace instruments_importer {
+struct Row;
+}
struct AndroidLogEvent;
class PacketSequenceStateGeneration;
@@ -34,6 +37,7 @@
struct InlineSchedWaking;
struct TracePacketData;
struct TrackEventData;
+struct LegacyV8CpuProfileEvent;
class ProtoTraceParser {
public:
@@ -44,6 +48,7 @@
virtual void ParseFtraceEvent(uint32_t, int64_t, TracePacketData) = 0;
virtual void ParseInlineSchedSwitch(uint32_t, int64_t, InlineSchedSwitch) = 0;
virtual void ParseInlineSchedWaking(uint32_t, int64_t, InlineSchedWaking) = 0;
+ virtual void ParseLegacyV8ProfileEvent(int64_t, LegacyV8CpuProfileEvent) = 0;
};
class JsonTraceParser {
@@ -65,6 +70,12 @@
virtual void ParsePerfRecord(int64_t, perf_importer::Record) = 0;
};
+class InstrumentsRowParser {
+ public:
+ virtual ~InstrumentsRowParser();
+ virtual void ParseInstrumentsRow(int64_t, instruments_importer::Row) = 0;
+};
+
class AndroidLogEventParser {
public:
virtual ~AndroidLogEventParser();
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.cc b/src/trace_processor/importers/common/virtual_memory_mapping.cc
index 0485243..562049e 100644
--- a/src/trace_processor/importers/common/virtual_memory_mapping.cc
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.cc
@@ -23,6 +23,7 @@
#include <string>
#include <utility>
+#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/address_range.h"
#include "src/trace_processor/importers/common/jit_cache.h"
@@ -119,5 +120,40 @@
return {frame_id, true};
}
+DummyMemoryMapping::~DummyMemoryMapping() = default;
+
+DummyMemoryMapping::DummyMemoryMapping(TraceProcessorContext* context,
+ CreateMappingParams params)
+ : VirtualMemoryMapping(context, std::move(params)) {}
+
+FrameId DummyMemoryMapping::InternDummyFrame(base::StringView function_name,
+ base::StringView source_file) {
+ DummyFrameKey key{context()->storage->InternString(function_name),
+ context()->storage->InternString(source_file)};
+
+ if (FrameId* id = interned_dummy_frames_.Find(key); id) {
+ return *id;
+ }
+
+ uint32_t symbol_set_id = context()->storage->symbol_table().row_count();
+
+ tables::SymbolTable::Id symbol_id =
+ context()
+ ->storage->mutable_symbol_table()
+ ->Insert({symbol_set_id, key.function_name_id, key.source_file_id})
+ .id;
+
+ PERFETTO_CHECK(symbol_set_id == symbol_id.value);
+
+ const FrameId frame_id =
+ context()
+ ->storage->mutable_stack_profile_frame_table()
+ ->Insert({key.function_name_id, mapping_id(), 0, symbol_set_id})
+ .id;
+ interned_dummy_frames_.Insert(key, frame_id);
+
+ return frame_id;
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.h b/src/trace_processor/importers/common/virtual_memory_mapping.h
index 498a9ef..1676437 100644
--- a/src/trace_processor/importers/common/virtual_memory_mapping.h
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.h
@@ -86,6 +86,8 @@
VirtualMemoryMapping(TraceProcessorContext* context,
CreateMappingParams params);
+ TraceProcessorContext* context() const { return context_; }
+
private:
friend class MappingTracker;
@@ -149,6 +151,42 @@
const UniquePid upid_;
};
+// Dummy mapping to be able to create frames when we have no real pc addresses
+// or real mappings.
+class DummyMemoryMapping : public VirtualMemoryMapping {
+ public:
+ ~DummyMemoryMapping() override;
+
+ // Interns a frame based solely on function name and source file. This is
+ // useful for profilers that do not emit an address nor a mapping.
+ FrameId InternDummyFrame(base::StringView function_name,
+ base::StringView source_file);
+
+ private:
+ friend class MappingTracker;
+ DummyMemoryMapping(TraceProcessorContext* context,
+ CreateMappingParams params);
+
+ struct DummyFrameKey {
+ StringId function_name_id;
+ StringId source_file_id;
+
+ bool operator==(const DummyFrameKey& o) const {
+ return function_name_id == o.function_name_id &&
+ source_file_id == o.source_file_id;
+ }
+
+ struct Hasher {
+ size_t operator()(const DummyFrameKey& k) const {
+ return static_cast<size_t>(base::Hasher::Combine(
+ k.function_name_id.raw_id(), k.source_file_id.raw_id()));
+ }
+ };
+ };
+ base::FlatHashMap<DummyFrameKey, FrameId, DummyFrameKey::Hasher>
+ interned_dummy_frames_;
+};
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/BUILD.gn b/src/trace_processor/importers/ftrace/BUILD.gn
index 19354a7..1cc2b0d 100644
--- a/src/trace_processor/importers/ftrace/BUILD.gn
+++ b/src/trace_processor/importers/ftrace/BUILD.gn
@@ -47,6 +47,8 @@
"iostat_tracker.h",
"mali_gpu_event_tracker.cc",
"mali_gpu_event_tracker.h",
+ "pixel_mm_kswapd_event_tracker.cc",
+ "pixel_mm_kswapd_event_tracker.h",
"pkvm_hyp_cpu_tracker.cc",
"pkvm_hyp_cpu_tracker.h",
"rss_stat_tracker.cc",
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 4e346e1..9e2adb2 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
namespace trace_processor {
namespace {
-std::array<FtraceMessageDescriptor, 538> descriptors{{
+std::array<FtraceMessageDescriptor, 540> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -5353,26 +5353,32 @@
},
{
"sched_switch_with_ctrs",
- 17,
+ 23,
{
{},
{"old_pid", ProtoSchemaType::kInt32},
{"new_pid", ProtoSchemaType::kInt32},
- {"cctr", ProtoSchemaType::kUint32},
- {"ctr0", ProtoSchemaType::kUint32},
- {"ctr1", ProtoSchemaType::kUint32},
- {"ctr2", ProtoSchemaType::kUint32},
- {"ctr3", ProtoSchemaType::kUint32},
+ {"cctr", ProtoSchemaType::kUint64},
+ {"ctr0", ProtoSchemaType::kUint64},
+ {"ctr1", ProtoSchemaType::kUint64},
+ {"ctr2", ProtoSchemaType::kUint64},
+ {"ctr3", ProtoSchemaType::kUint64},
{"lctr0", ProtoSchemaType::kUint32},
{"lctr1", ProtoSchemaType::kUint32},
- {"ctr4", ProtoSchemaType::kUint32},
- {"ctr5", ProtoSchemaType::kUint32},
+ {"ctr4", ProtoSchemaType::kUint64},
+ {"ctr5", ProtoSchemaType::kUint64},
{"prev_comm", ProtoSchemaType::kString},
{"prev_pid", ProtoSchemaType::kInt32},
{"cyc", ProtoSchemaType::kUint32},
{"inst", ProtoSchemaType::kUint32},
{"stallbm", ProtoSchemaType::kUint32},
{"l3dm", ProtoSchemaType::kUint32},
+ {"next_pid", ProtoSchemaType::kInt32},
+ {"next_comm", ProtoSchemaType::kString},
+ {"prev_state", ProtoSchemaType::kInt64},
+ {"amu0", ProtoSchemaType::kUint64},
+ {"amu1", ProtoSchemaType::kUint64},
+ {"amu2", ProtoSchemaType::kUint64},
},
},
{
@@ -5950,6 +5956,23 @@
{"active", ProtoSchemaType::kUint64},
},
},
+ {
+ "pixel_mm_kswapd_wake",
+ 1,
+ {
+ {},
+ {"whatever", ProtoSchemaType::kInt32},
+ },
+ },
+ {
+ "pixel_mm_kswapd_done",
+ 2,
+ {
+ {},
+ {"delta_nr_scanned", ProtoSchemaType::kUint64},
+ {"delta_nr_reclaimed", ProtoSchemaType::kUint64},
+ },
+ },
}};
} // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 735b8de..73498c2 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -320,6 +320,7 @@
pkvm_hyp_cpu_tracker_(context),
gpu_work_period_tracker_(context),
thermal_tracker_(context),
+ pixel_mm_kswapd_event_tracker_(context),
sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
sched_waking_name_id_(context->storage->InternString("sched_waking")),
cpu_id_(context->storage->InternString("cpu")),
@@ -1297,6 +1298,14 @@
ParseBclIrq(ts, fld_bytes);
break;
}
+ case FtraceEvent::kPixelMmKswapdWakeFieldNumber: {
+ pixel_mm_kswapd_event_tracker_.ParsePixelMmKswapdWake(ts, pid);
+ break;
+ }
+ case FtraceEvent::kPixelMmKswapdDoneFieldNumber: {
+ pixel_mm_kswapd_event_tracker_.ParsePixelMmKswapdDone(ts, pid, fld_bytes);
+ break;
+ }
default:
break;
}
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index f2c50ac..a9a7e5e 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -37,6 +37,7 @@
#include "src/trace_processor/importers/ftrace/rss_stat_tracker.h"
#include "src/trace_processor/importers/ftrace/thermal_tracker.h"
#include "src/trace_processor/importers/ftrace/virtio_gpu_tracker.h"
+#include "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h"
#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto {
@@ -317,6 +318,7 @@
PkvmHypervisorCpuTracker pkvm_hyp_cpu_tracker_;
GpuWorkPeriodTracker gpu_work_period_tracker_;
ThermalTracker thermal_tracker_;
+ PixelMmKswapdEventTracker pixel_mm_kswapd_event_tracker_;
const StringId sched_wakeup_name_id_;
const StringId sched_waking_name_id_;
diff --git a/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc b/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc
new file mode 100644
index 0000000..a43e764
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.cc
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h"
+
+#include "perfetto/ext/base/string_utils.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/pixel_mm.pbzero.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+PixelMmKswapdEventTracker::PixelMmKswapdEventTracker(
+ TraceProcessorContext* context)
+ : context_(context),
+ kswapd_efficiency_name_(
+ context->storage->InternString("kswapd_efficiency")),
+ efficiency_pct_name_(context->storage->InternString("efficiency %")),
+ pages_scanned_name_(context->storage->InternString("pages scanned")),
+ pages_reclaimed_name_(context->storage->InternString("pages reclaimed")) {
+}
+
+void PixelMmKswapdEventTracker::ParsePixelMmKswapdWake(int64_t timestamp,
+ uint32_t pid) {
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+ TrackId details_track = context_->track_tracker->InternThreadTrack(utid);
+
+ context_->slice_tracker->Begin(timestamp, details_track, kNullStringId,
+ kswapd_efficiency_name_);
+}
+
+void PixelMmKswapdEventTracker::ParsePixelMmKswapdDone(
+ int64_t timestamp,
+ uint32_t pid,
+ protozero::ConstBytes blob) {
+ UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+ TrackId details_track = context_->track_tracker->InternThreadTrack(utid);
+
+ protos::pbzero::PixelMmKswapdDoneFtraceEvent::Decoder decoder(blob.data,
+ blob.size);
+
+ context_->slice_tracker->End(
+ timestamp, details_track, kNullStringId, kswapd_efficiency_name_,
+ [this, &decoder](ArgsTracker::BoundInserter* inserter) {
+ if (decoder.has_delta_nr_scanned()) {
+ inserter->AddArg(
+ pages_scanned_name_,
+ Variadic::UnsignedInteger(decoder.delta_nr_scanned()));
+ }
+ if (decoder.has_delta_nr_reclaimed()) {
+ inserter->AddArg(
+ pages_reclaimed_name_,
+ Variadic::UnsignedInteger(decoder.delta_nr_reclaimed()));
+ }
+
+ if (decoder.has_delta_nr_reclaimed() &&
+ decoder.has_delta_nr_scanned()) {
+ double efficiency =
+ static_cast<double>(decoder.delta_nr_reclaimed()) * 100 /
+ static_cast<double>(decoder.delta_nr_scanned());
+
+ inserter->AddArg(efficiency_pct_name_,
+ Variadic::UnsignedInteger(
+ static_cast<uint64_t>(std::round(efficiency))));
+ }
+ });
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h b/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h
new file mode 100644
index 0000000..dfdd6a0
--- /dev/null
+++ b/src/trace_processor/importers/ftrace/pixel_mm_kswapd_event_tracker.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_PIXEL_MM_KSWAPD_EVENT_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_PIXEL_MM_KSWAPD_EVENT_TRACKER_H_
+
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/util/descriptors.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class PixelMmKswapdEventTracker {
+ public:
+ explicit PixelMmKswapdEventTracker(TraceProcessorContext*);
+
+ void ParsePixelMmKswapdWake(int64_t timestamp, uint32_t pid);
+ void ParsePixelMmKswapdDone(int64_t timestamp,
+ uint32_t pid,
+ protozero::ConstBytes);
+
+ private:
+ TraceProcessorContext* context_;
+ const StringId kswapd_efficiency_name_;
+ const StringId efficiency_pct_name_;
+ const StringId pages_scanned_name_;
+ const StringId pages_reclaimed_name_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_PIXEL_MM_KSWAPD_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/fuchsia/BUILD.gn b/src/trace_processor/importers/fuchsia/BUILD.gn
index 47d430b..8aa0826 100644
--- a/src/trace_processor/importers/fuchsia/BUILD.gn
+++ b/src/trace_processor/importers/fuchsia/BUILD.gn
@@ -39,7 +39,9 @@
"../../../../gn:default_deps",
"../../sorter",
"../../storage",
+ "../../tables",
"../../types",
+ "../../util:trace_type",
"../common",
"../proto:minimal",
]
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
index f15a725..fb669a9 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
@@ -17,14 +17,22 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_TOKENIZER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_TOKENIZER_H_
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "perfetto/base/status.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h"
+#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/task_state.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
+#include "src/trace_processor/util/trace_type.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -37,7 +45,7 @@
~FuchsiaTraceTokenizer() override;
// ChunkedTraceReader implementation
- util::Status Parse(TraceBlobView) override;
+ base::Status Parse(TraceBlobView) override;
base::Status NotifyEndOfFile() override;
private:
@@ -130,7 +138,6 @@
std::unordered_map<uint64_t, Thread> threads_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_TOKENIZER_H_
diff --git a/src/trace_processor/importers/instruments/BUILD.gn b/src/trace_processor/importers/instruments/BUILD.gn
new file mode 100644
index 0000000..3ca66d7
--- /dev/null
+++ b/src/trace_processor/importers/instruments/BUILD.gn
@@ -0,0 +1,48 @@
+# 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("../../../../gn/test.gni")
+
+source_set("row") {
+ sources = [ "row.h" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../containers",
+ "../../util:build_id",
+ ]
+}
+
+source_set("instruments") {
+ sources = [
+ "instruments_xml_tokenizer.cc",
+ "instruments_xml_tokenizer.h",
+ "row_data_tracker.cc",
+ "row_data_tracker.h",
+ "row_parser.cc",
+ "row_parser.h",
+ ]
+ public_deps = [ ":row" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../../gn:expat",
+ "../../../../include/perfetto/ext/base:base",
+ "../../../../include/perfetto/public",
+ "../../../../include/perfetto/trace_processor:trace_processor",
+ "../../../../protos/perfetto/trace:zero",
+ "../../sorter",
+ "../../storage",
+ "../../types",
+ "../common:common",
+ ]
+}
diff --git a/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc
new file mode 100644
index 0000000..51cee76
--- /dev/null
+++ b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc
@@ -0,0 +1,499 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/instruments/instruments_xml_tokenizer.h"
+
+#include <map>
+
+#include <expat.h>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/public/fnv1a.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "src/trace_processor/importers/common/clock_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/importers/instruments/row_data_tracker.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+namespace {
+
+std::string MakeTrimmed(const char* chars, int len) {
+ while (len > 0 && std::isspace(*chars)) {
+ chars++;
+ len--;
+ }
+ while (len > 0 && std::isspace(chars[len - 1])) {
+ len--;
+ }
+ return std::string(chars, static_cast<size_t>(len));
+}
+
+} // namespace
+
+// The Instruments XML tokenizer reads instruments traces exported with:
+//
+// xctrace export --input /path/to/profile.trace --xpath
+// '//trace-toc/run/data/table[@schema="os-signpost and
+// @category="PointsOfInterest"] |
+// //trace-toc/run/data/table[@schema="time-sample"]'
+//
+// This exports two tables:
+// 1. Points of interest signposts
+// 2. Time samples
+//
+// The first is used for clock synchronization -- perfetto emits signpost events
+// during tracing which allow synchronization of the xctrace clock (relative to
+// start of profiling) with the perfetto boottime clock. The second contains
+// the samples themselves.
+//
+// The expected format of the rows in the clock sync table is:
+//
+// <row>
+// <event-time>1234</event-time>
+// <subsystem>dev.perfetto.clock_sync</subsystem>
+// <os-log-metadata>
+// <uint64>5678</uint64>
+// </os-log-metadata>
+// </row>
+//
+// There may be other rows with other data (from other subsystems), and
+// additional data in the row (such as thread data and other metadata) -- this
+// can be safely ignored.
+//
+// The expected format of the rows in the time sample table is:
+//
+// <row>
+// <sample-time>1234</sample-time>
+// <thread fmt="Thread name">
+// <tid>1</tid>
+// <process fmt="Process name">
+// <pid>1<pid>
+// </process>
+// </thread>
+// <core>0</core>
+// <backtrace>
+// <frame addr="0x120001234">
+// <binary
+// name="MyBinary" UUID="01234567-89ABC-CDEF-0123-456789ABCDEF"
+// load-addr="0x120000000" path="/path/to/MyBinary.app/MyBinary" />
+// </frame>
+// ... more frames ...
+// </row>
+//
+// Here we do not expect other rows with other data -- every row should have a
+// backtrace, and we use the presence of a backtrace to distinguish time samples
+// and clock sync eventst. However, there can be additional data in the row
+// (such as other metadata) -- this can be safely ignored.
+//
+// In addition, the XML format annotates elements with ids, to later reuse the
+// same data by id without needing to repeat its contents. For example, you
+// might have thread data for a sample:
+//
+// <thread id="11" fmt="My Thread"><tid id="12">10</tid>...</thread>
+//
+// and subsequent samples on that thread will simply have
+//
+// <thread ref="11" />
+//
+// This means that most elements have to have their pertinent data cached by id,
+// including any data store in child elements (which themselves also have to
+// be cached by id, like the <tid> in the example above).
+//
+// This importer reads the XML data using a streaming XML parser, which means
+// it has to maintain some parsing state (such as the current stack of tags, or
+// the current element for which we are reading data).
+class InstrumentsXmlTokenizer::Impl {
+ public:
+ explicit Impl(TraceProcessorContext* context)
+ : context_(context), data_(RowDataTracker::GetOrCreate(context_)) {
+ parser_ = XML_ParserCreate(nullptr);
+ XML_SetElementHandler(parser_, ElementStart, ElementEnd);
+ XML_SetCharacterDataHandler(parser_, CharacterData);
+ XML_SetUserData(parser_, this);
+
+ const char* subsystem = "dev.perfetto.instruments_clock";
+ clock_ = static_cast<ClockTracker::ClockId>(
+ PerfettoFnv1a(subsystem, strlen(subsystem)) | 0x80000000);
+
+ // Use the above clock if we can, in case there is no other trace and
+ // no clock sync events.
+ context_->clock_tracker->SetTraceTimeClock(clock_);
+ }
+ ~Impl() { XML_ParserFree(parser_); }
+
+ base::Status Parse(TraceBlobView view) {
+ if (!XML_Parse(parser_, reinterpret_cast<const char*>(view.data()),
+ static_cast<int>(view.length()), false)) {
+ return base::ErrStatus("XML parse error at line %lu: %s\n",
+ XML_GetCurrentLineNumber(parser_),
+ XML_ErrorString(XML_GetErrorCode(parser_)));
+ }
+ return base::OkStatus();
+ }
+
+ base::Status End() {
+ if (!XML_Parse(parser_, nullptr, 0, true)) {
+ return base::ErrStatus("XML parse error at end, line %lu: %s\n",
+ XML_GetCurrentLineNumber(parser_),
+ XML_ErrorString(XML_GetErrorCode(parser_)));
+ }
+ return base::OkStatus();
+ }
+
+ private:
+ static void ElementStart(void* data, const char* el, const char** attr) {
+ reinterpret_cast<Impl*>(data)->ElementStart(el, attr);
+ }
+ static void ElementEnd(void* data, const char* el) {
+ reinterpret_cast<Impl*>(data)->ElementEnd(el);
+ }
+ static void CharacterData(void* data, const char* chars, int len) {
+ reinterpret_cast<Impl*>(data)->CharacterData(chars, len);
+ }
+
+ void ElementStart(const char* el, const char** attrs) {
+ tag_stack_.emplace_back(el);
+ std::string_view tag_name = tag_stack_.back();
+
+ if (tag_name == "row") {
+ current_row_ = Row{};
+ } else if (tag_name == "thread") {
+ MaybeCachedRef<ThreadId> thread_lookup =
+ GetOrInsertByRef(attrs, thread_ref_to_thread_);
+ if (thread_lookup.is_new) {
+ auto new_thread = data_.NewThread();
+ thread_lookup.ref = new_thread.id;
+
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "fmt") {
+ new_thread.ptr->fmt = InternString(attrs[i + 1]);
+ }
+ }
+
+ current_new_thread_ = new_thread.id;
+ }
+ current_row_.thread = thread_lookup.ref;
+ } else if (tag_name == "process") {
+ MaybeCachedRef<ProcessId> process_lookup =
+ GetOrInsertByRef(attrs, process_ref_to_process_);
+ if (process_lookup.is_new) {
+ // Can only be processing a new process when processing a new thread.
+ PERFETTO_DCHECK(current_new_thread_ != kNullId);
+ auto new_process = data_.NewProcess();
+ process_lookup.ref = new_process.id;
+
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "fmt") {
+ new_process.ptr->fmt = InternString(attrs[i + 1]);
+ }
+ }
+
+ current_new_process_ = new_process.id;
+ }
+ if (current_new_thread_) {
+ data_.GetThread(current_new_thread_)->process = process_lookup.ref;
+ }
+ } else if (tag_name == "core") {
+ MaybeCachedRef<uint32_t> core_id_lookup =
+ GetOrInsertByRef(attrs, core_ref_to_core_);
+ if (core_id_lookup.is_new) {
+ current_new_core_id_ = &core_id_lookup.ref;
+ } else {
+ current_row_.core_id = core_id_lookup.ref;
+ }
+ } else if (tag_name == "sample-time" || tag_name == "event-time") {
+ // Share time lookup logic between sample times and event times, including
+ // updating the current row's sample time for both.
+ MaybeCachedRef<int64_t> time_lookup =
+ GetOrInsertByRef(attrs, sample_time_ref_to_time_);
+ if (time_lookup.is_new) {
+ current_time_ref_ = &time_lookup.ref;
+ } else {
+ current_row_.timestamp_ = time_lookup.ref;
+ }
+ } else if (tag_name == "subsystem") {
+ MaybeCachedRef<std::string> subsystem_lookup =
+ GetOrInsertByRef(attrs, subsystem_ref_to_subsystem_);
+ current_subsystem_ref_ = &subsystem_lookup.ref;
+ } else if (tag_name == "uint64") {
+ // The only uint64 we care about is the one for the clock sync, which is
+ // expected to contain exactly one uint64 value -- we'll
+ // map all uint64 to a single value and check against the subsystem
+ // when the row is closed.
+ MaybeCachedRef<uint64_t> uint64_lookup =
+ GetOrInsertByRef(attrs, os_log_metadata_or_uint64_ref_to_uint64_);
+ if (uint64_lookup.is_new) {
+ current_uint64_ref_ = &uint64_lookup.ref;
+ } else {
+ if (current_os_log_metadata_uint64_ref_) {
+ // Update the os-log-metadata's uint64 value with this uint64 value.
+ *current_os_log_metadata_uint64_ref_ = uint64_lookup.ref;
+ }
+ }
+ } else if (tag_name == "os-log-metadata") {
+ // The only os-log-metadata we care about is the one with the single
+ // uint64 clock sync value, so also map this to uint64 values with its own
+ // id.
+ MaybeCachedRef<uint64_t> uint64_lookup =
+ GetOrInsertByRef(attrs, os_log_metadata_or_uint64_ref_to_uint64_);
+ current_os_log_metadata_uint64_ref_ = &uint64_lookup.ref;
+ } else if (tag_name == "backtrace") {
+ MaybeCachedRef<BacktraceId> backtrace_lookup =
+ GetOrInsertByRef(attrs, backtrace_ref_to_backtrace_);
+ if (backtrace_lookup.is_new) {
+ backtrace_lookup.ref = data_.NewBacktrace().id;
+ }
+ current_row_.backtrace = backtrace_lookup.ref;
+ } else if (tag_name == "frame") {
+ MaybeCachedRef<BacktraceFrameId> frame_lookup =
+ GetOrInsertByRef(attrs, frame_ref_to_frame_);
+ if (frame_lookup.is_new) {
+ IdPtr<Frame> new_frame = data_.NewFrame();
+ frame_lookup.ref = new_frame.id;
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "addr") {
+ new_frame.ptr->addr = strtoll(attrs[i + 1], nullptr, 16);
+ }
+ }
+ current_new_frame_ = new_frame.id;
+ }
+ data_.GetBacktrace(current_row_.backtrace)
+ ->frames.push_back(frame_lookup.ref);
+ } else if (tag_name == "binary") {
+ // Can only be processing a binary when processing a new frame.
+ PERFETTO_DCHECK(current_new_frame_ != kNullId);
+
+ MaybeCachedRef<BinaryId> binary_lookup =
+ GetOrInsertByRef(attrs, binary_ref_to_binary_);
+ if (binary_lookup.is_new) {
+ auto new_binary = data_.NewBinary();
+ binary_lookup.ref = new_binary.id;
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "path") {
+ new_binary.ptr->path = std::string(attrs[i + 1]);
+ } else if (key == "UUID") {
+ new_binary.ptr->uuid =
+ BuildId::FromHex(base::StringView(attrs[i + 1]));
+ } else if (key == "load-addr") {
+ new_binary.ptr->load_addr = strtoll(attrs[i + 1], nullptr, 16);
+ }
+ }
+ new_binary.ptr->max_addr = new_binary.ptr->load_addr;
+ }
+ PERFETTO_DCHECK(data_.GetFrame(current_new_frame_)->binary == kNullId);
+ data_.GetFrame(current_new_frame_)->binary = binary_lookup.ref;
+ }
+ }
+
+ void ElementEnd(const char* el) {
+ PERFETTO_DCHECK(el == tag_stack_.back());
+ std::string tag_name = std::move(tag_stack_.back());
+ tag_stack_.pop_back();
+
+ if (tag_name == "row") {
+ if (current_row_.backtrace) {
+ // Rows with backtraces are assumed to be time samples.
+ base::StatusOr<int64_t> trace_ts =
+ ToTraceTimestamp(current_row_.timestamp_);
+ if (!trace_ts.ok()) {
+ PERFETTO_DLOG("Skipping timestamp %ld, no clock snapshot yet",
+ current_row_.timestamp_);
+ } else {
+ context_->sorter->PushInstrumentsRow(*trace_ts,
+ std::move(current_row_));
+ }
+ } else if (current_subsystem_ref_ != nullptr) {
+ // Rows without backtraces are assumed to be signpost events -- filter
+ // these for `dev.perfetto.clock_sync` events.
+ if (*current_subsystem_ref_ == "dev.perfetto.clock_sync") {
+ PERFETTO_DCHECK(current_os_log_metadata_uint64_ref_ != nullptr);
+ uint64_t clock_sync_timestamp = *current_os_log_metadata_uint64_ref_;
+ if (latest_clock_sync_timestamp_ > clock_sync_timestamp) {
+ PERFETTO_DLOG("Skipping timestamp %ld, non-monotonic sync deteced",
+ current_row_.timestamp_);
+ } else {
+ latest_clock_sync_timestamp_ = clock_sync_timestamp;
+ auto status = context_->clock_tracker->AddSnapshot(
+ {{clock_, current_row_.timestamp_},
+ {protos::pbzero::ClockSnapshot::Clock::BOOTTIME,
+ static_cast<int64_t>(latest_clock_sync_timestamp_)}});
+ if (!status.ok()) {
+ PERFETTO_FATAL("Error adding clock snapshot: %s",
+ status.status().c_message());
+ }
+ }
+ }
+ current_subsystem_ref_ = nullptr;
+ current_os_log_metadata_uint64_ref_ = nullptr;
+ current_uint64_ref_ = nullptr;
+ }
+ } else if (current_new_frame_ != kNullId && tag_name == "frame") {
+ Frame* frame = data_.GetFrame(current_new_frame_);
+ if (frame->binary) {
+ Binary* binary = data_.GetBinary(frame->binary);
+ // We don't know what the binary's mapping end is, but we know that the
+ // current frame is inside of it, so use that.
+ PERFETTO_DCHECK(frame->addr > binary->load_addr);
+ if (frame->addr > binary->max_addr) {
+ binary->max_addr = frame->addr;
+ }
+ }
+ current_new_frame_ = kNullId;
+ } else if (current_new_thread_ != kNullId && tag_name == "thread") {
+ current_new_thread_ = kNullId;
+ } else if (current_new_process_ != kNullId && tag_name == "process") {
+ current_new_process_ = kNullId;
+ } else if (current_new_core_id_ != nullptr && tag_name == "core") {
+ current_new_core_id_ = nullptr;
+ }
+ }
+
+ void CharacterData(const char* chars, int len) {
+ std::string_view tag_name = tag_stack_.back();
+ if (current_time_ref_ != nullptr &&
+ (tag_name == "sample-time" || tag_name == "event-time")) {
+ std::string s = MakeTrimmed(chars, len);
+ current_row_.timestamp_ = *current_time_ref_ = stoll(s);
+ current_time_ref_ = nullptr;
+ } else if (current_new_thread_ != kNullId && tag_name == "tid") {
+ std::string s = MakeTrimmed(chars, len);
+ data_.GetThread(current_new_thread_)->tid = stoi(s);
+ } else if (current_new_process_ != kNullId && tag_name == "pid") {
+ std::string s = MakeTrimmed(chars, len);
+ data_.GetProcess(current_new_process_)->pid = stoi(s);
+ } else if (current_new_core_id_ != nullptr && tag_name == "core") {
+ std::string s = MakeTrimmed(chars, len);
+ *current_new_core_id_ = static_cast<uint32_t>(stoul(s));
+ } else if (current_subsystem_ref_ != nullptr && tag_name == "subsystem") {
+ std::string s = MakeTrimmed(chars, len);
+ *current_subsystem_ref_ = s;
+ } else if (current_uint64_ref_ != nullptr &&
+ current_os_log_metadata_uint64_ref_ != nullptr &&
+ tag_name == "uint64") {
+ std::string s = MakeTrimmed(chars, len);
+ *current_os_log_metadata_uint64_ref_ = *current_uint64_ref_ = stoull(s);
+ }
+ }
+
+ base::StatusOr<int64_t> ToTraceTimestamp(int64_t time) {
+ base::StatusOr<int64_t> trace_ts =
+ context_->clock_tracker->ToTraceTime(clock_, time);
+
+ if (PERFETTO_LIKELY(trace_ts.ok())) {
+ latest_timestamp_ = std::max(latest_timestamp_, *trace_ts);
+ }
+
+ return trace_ts;
+ }
+
+ StringId InternString(base::StringView string_view) {
+ return context_->storage->InternString(string_view);
+ }
+ StringId InternString(const char* string) {
+ return InternString(base::StringView(string));
+ }
+ StringId InternString(const char* data, size_t len) {
+ return InternString(base::StringView(data, len));
+ }
+
+ template <typename Value>
+ struct MaybeCachedRef {
+ Value& ref;
+ bool is_new;
+ };
+ // Implement the element caching mechanism. Either insert an element by its
+ // id attribute into the given map, or look up the element in the cache by its
+ // ref attribute. The returned value is a reference into the map, to allow
+ // in-place modification.
+ template <typename Value>
+ MaybeCachedRef<Value> GetOrInsertByRef(const char** attrs,
+ std::map<unsigned long, Value>& map) {
+ PERFETTO_DCHECK(attrs[0] != nullptr);
+ PERFETTO_DCHECK(attrs[1] != nullptr);
+ const char* key = attrs[0];
+ // The id or ref attribute has to be the first attribute on the element.
+ PERFETTO_DCHECK(strcmp(key, "ref") == 0 || strcmp(key, "id") == 0);
+ unsigned long id = strtoul(attrs[1], nullptr, 10);
+ // If the first attribute key is `id`, then this is a new entry in the
+ // cache -- otherwise, for lookup by ref, it should already exist.
+ bool is_new = strcmp(key, "id") == 0;
+ PERFETTO_DCHECK(is_new == (map.find(id) == map.end()));
+ return {map[id], is_new};
+ }
+
+ TraceProcessorContext* context_;
+ RowDataTracker& data_;
+
+ XML_Parser parser_;
+ std::vector<std::string> tag_stack_;
+ int64_t latest_timestamp_;
+
+ // These maps store the cached element data. These currently have to be
+ // std::map, because they require pointer stability under insertion,
+ // as the various `current_foo_` pointers below point directly into the map
+ // data.
+ //
+ // TODO(leszeks): Relax this pointer stability requirement, and use
+ // base::FlatHashMap.
+ // TODO(leszeks): Consider merging these into a single map from ID to
+ // a variant (or similar).
+ std::map<unsigned long, ThreadId> thread_ref_to_thread_;
+ std::map<unsigned long, ProcessId> process_ref_to_process_;
+ std::map<unsigned long, uint32_t> core_ref_to_core_;
+ std::map<unsigned long, int64_t> sample_time_ref_to_time_;
+ std::map<unsigned long, BinaryId> binary_ref_to_binary_;
+ std::map<unsigned long, BacktraceFrameId> frame_ref_to_frame_;
+ std::map<unsigned long, BacktraceId> backtrace_ref_to_backtrace_;
+ std::map<unsigned long, std::string> subsystem_ref_to_subsystem_;
+ std::map<unsigned long, uint64_t> os_log_metadata_or_uint64_ref_to_uint64_;
+
+ Row current_row_;
+ int64_t* current_time_ref_ = nullptr;
+ ThreadId current_new_thread_ = kNullId;
+ ProcessId current_new_process_ = kNullId;
+ uint32_t* current_new_core_id_ = nullptr;
+ BacktraceFrameId current_new_frame_ = kNullId;
+
+ ClockTracker::ClockId clock_;
+ std::string* current_subsystem_ref_ = nullptr;
+ uint64_t* current_os_log_metadata_uint64_ref_ = nullptr;
+ uint64_t* current_uint64_ref_ = nullptr;
+ uint64_t latest_clock_sync_timestamp_ = 0;
+};
+
+InstrumentsXmlTokenizer::InstrumentsXmlTokenizer(TraceProcessorContext* context)
+ : impl_(new Impl(context)) {}
+InstrumentsXmlTokenizer::~InstrumentsXmlTokenizer() {
+ delete impl_;
+}
+
+base::Status InstrumentsXmlTokenizer::Parse(TraceBlobView view) {
+ return impl_->Parse(std::move(view));
+}
+
+[[nodiscard]] base::Status InstrumentsXmlTokenizer::NotifyEndOfFile() {
+ return impl_->End();
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
diff --git a/src/trace_processor/importers/instruments/instruments_xml_tokenizer.h b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.h
new file mode 100644
index 0000000..be044dd
--- /dev/null
+++ b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_XML_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_XML_TOKENIZER_H_
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+class InstrumentsXmlTokenizer : public ChunkedTraceReader {
+ public:
+ explicit InstrumentsXmlTokenizer(TraceProcessorContext*);
+ ~InstrumentsXmlTokenizer() override;
+
+ base::Status Parse(TraceBlobView) override;
+
+ [[nodiscard]] base::Status NotifyEndOfFile() override;
+
+ private:
+ class Impl;
+
+ class Impl* impl_;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_XML_TOKENIZER_H_
diff --git a/src/trace_processor/importers/instruments/row.h b/src/trace_processor/importers/instruments/row.h
new file mode 100644
index 0000000..531a36d
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_H_
+
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+// TODO(leszeks): Would be nice if these were strong type aliases, to be
+// type safe.
+using ThreadId = uint32_t;
+using ProcessId = uint32_t;
+using BacktraceId = uint32_t;
+using BacktraceFrameId = uint32_t;
+using BinaryId = uint32_t;
+
+constexpr uint32_t kNullId = 0u;
+
+struct Binary {
+ std::string path;
+ BuildId uuid = BuildId::FromRaw(std::string(""));
+ long long load_addr = 0;
+ long long max_addr = 0;
+};
+
+struct Frame {
+ long long addr = 0;
+ BinaryId binary = kNullId;
+};
+
+struct Process {
+ int pid = 0;
+ StringPool::Id fmt = StringPool::Id::Null();
+};
+
+struct Thread {
+ int tid = 0;
+ StringPool::Id fmt = StringPool::Id::Null();
+ ProcessId process = kNullId;
+};
+
+struct Backtrace {
+ std::vector<BacktraceFrameId> frames;
+};
+
+struct alignas(8) Row {
+ int64_t timestamp_;
+ uint32_t core_id;
+ ThreadId thread = kNullId;
+ BacktraceId backtrace = kNullId;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_H_
diff --git a/src/trace_processor/importers/instruments/row_data_tracker.cc b/src/trace_processor/importers/instruments/row_data_tracker.cc
new file mode 100644
index 0000000..bd27629
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_data_tracker.cc
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/instruments/row_data_tracker.h"
+
+#include "perfetto/base/status.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+RowDataTracker::RowDataTracker() {}
+RowDataTracker::~RowDataTracker() = default;
+
+IdPtr<Thread> RowDataTracker::NewThread() {
+ ThreadId id = static_cast<ThreadId>(threads_.size());
+ Thread* ptr = &threads_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Thread* RowDataTracker::GetThread(ThreadId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &threads_[id - 1];
+}
+
+IdPtr<Process> RowDataTracker::NewProcess() {
+ ProcessId id = static_cast<ProcessId>(processes_.size());
+ Process* ptr = &processes_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Process* RowDataTracker::GetProcess(ProcessId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &processes_[id - 1];
+}
+
+IdPtr<Frame> RowDataTracker::NewFrame() {
+ BacktraceFrameId id = static_cast<BacktraceFrameId>(frames_.size());
+ Frame* ptr = &frames_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Frame* RowDataTracker::GetFrame(BacktraceFrameId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &frames_[id - 1];
+}
+
+IdPtr<Backtrace> RowDataTracker::NewBacktrace() {
+ BacktraceId id = static_cast<BacktraceId>(backtraces_.size());
+ Backtrace* ptr = &backtraces_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Backtrace* RowDataTracker::GetBacktrace(BacktraceId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &backtraces_[id - 1];
+}
+
+IdPtr<Binary> RowDataTracker::NewBinary() {
+ BinaryId id = static_cast<BinaryId>(binaries_.size());
+ Binary* ptr = &binaries_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Binary* RowDataTracker::GetBinary(BinaryId id) {
+ // Frames are allowed to have null binaries.
+ if (id == kNullId)
+ return nullptr;
+ return &binaries_[id - 1];
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
diff --git a/src/trace_processor/importers/instruments/row_data_tracker.h b/src/trace_processor/importers/instruments/row_data_tracker.h
new file mode 100644
index 0000000..247326e
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_data_tracker.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_DATA_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_DATA_TRACKER_H_
+
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+template <typename T>
+struct IdPtr {
+ uint32_t id;
+ T* ptr;
+};
+
+// Keeps track of row data.
+class RowDataTracker : public Destructible {
+ public:
+ static RowDataTracker& GetOrCreate(TraceProcessorContext* context) {
+ if (!context->instruments_row_data_tracker) {
+ context->instruments_row_data_tracker.reset(new RowDataTracker());
+ }
+ return static_cast<RowDataTracker&>(*context->instruments_row_data_tracker);
+ }
+ ~RowDataTracker() override;
+
+ IdPtr<Thread> NewThread();
+ Thread* GetThread(ThreadId id);
+
+ IdPtr<Process> NewProcess();
+ Process* GetProcess(ProcessId id);
+
+ IdPtr<Frame> NewFrame();
+ Frame* GetFrame(BacktraceFrameId id);
+
+ IdPtr<Backtrace> NewBacktrace();
+ Backtrace* GetBacktrace(BacktraceId id);
+
+ IdPtr<Binary> NewBinary();
+ Binary* GetBinary(BinaryId id);
+
+ private:
+ explicit RowDataTracker();
+
+ std::vector<Thread> threads_;
+ std::vector<Process> processes_;
+ std::vector<Frame> frames_;
+ std::vector<Backtrace> backtraces_;
+ std::vector<Binary> binaries_;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_DATA_TRACKER_H_
diff --git a/src/trace_processor/importers/instruments/row_parser.cc b/src/trace_processor/importers/instruments/row_parser.cc
new file mode 100644
index 0000000..184d1f1
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_parser.cc
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/instruments/row_parser.h"
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/importers/instruments/row_data_tracker.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+RowParser::RowParser(TraceProcessorContext* context)
+ : context_(context), data_(RowDataTracker::GetOrCreate(context)) {}
+
+void RowParser::ParseInstrumentsRow(int64_t ts, instruments_importer::Row row) {
+ if (!row.backtrace) {
+ return;
+ }
+
+ Thread* thread = data_.GetThread(row.thread);
+ Process* process = data_.GetProcess(thread->process);
+ uint32_t tid = static_cast<uint32_t>(thread->tid);
+ uint32_t pid = static_cast<uint32_t>(process->pid);
+
+ UniqueTid utid = context_->process_tracker->UpdateThread(tid, pid);
+ UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
+
+ // TODO(leszeks): Avoid setting thread/process name if we've already seen this
+ // Thread* / Process*.
+ context_->process_tracker->UpdateThreadNameByUtid(utid, thread->fmt,
+ ThreadNamePriority::kOther);
+ context_->process_tracker->SetProcessNameIfUnset(upid, process->fmt);
+
+ Backtrace* backtrace = data_.GetBacktrace(row.backtrace);
+ std::optional<CallsiteId> parent;
+ uint32_t depth = 0;
+ base::FlatHashMap<FrameId, CallsiteTreeNode>* frame_to_callsite = &top_frames;
+ auto leaf = backtrace->frames.rend() - 1;
+ for (auto it = backtrace->frames.rbegin(); it != backtrace->frames.rend();
+ ++it) {
+ Frame* frame = data_.GetFrame(*it);
+ Binary* binary = data_.GetBinary(frame->binary);
+
+ uint64_t rel_pc = static_cast<uint64_t>(frame->addr);
+ if (frame->binary) {
+ rel_pc -= static_cast<uint64_t>(binary->load_addr);
+ }
+
+ // For non-leaf functions, the pc will be after the end of the call. Adjust
+ // it to be within the call instruction.
+ if (rel_pc != 0 && it != leaf) {
+ --rel_pc;
+ }
+
+ auto frame_inserted = frame_to_frame_id_.Insert(*it, FrameId{0});
+ if (frame_inserted.second) {
+ auto mapping_inserted = binary_to_mapping_.Insert(frame->binary, nullptr);
+ if (mapping_inserted.second) {
+ if (binary == nullptr) {
+ *mapping_inserted.first = GetDummyMapping(upid);
+ } else {
+ BuildId build_id = binary->uuid;
+ *mapping_inserted.first =
+ &context_->mapping_tracker->CreateUserMemoryMapping(
+ upid, {AddressRange(static_cast<uint64_t>(binary->load_addr),
+ static_cast<uint64_t>(binary->max_addr)),
+ 0, 0, 0, binary->path, build_id});
+ }
+ }
+ VirtualMemoryMapping* mapping = *mapping_inserted.first;
+
+ // Intern the frame with no function name -- the symbolizer will annotate
+ // frames later.
+ *frame_inserted.first =
+ mapping->InternFrame(rel_pc, base::StringView(""));
+ }
+ FrameId frame_id = *frame_inserted.first;
+
+ // Lookup the frame id in the current callsite prefix tree node.
+ auto callsite_node_inserted =
+ frame_to_callsite->Insert(frame_id, CallsiteTreeNode{});
+ if (callsite_node_inserted.second) {
+ callsite_node_inserted.first->callsite_id =
+ context_->storage->mutable_stack_profile_callsite_table()
+ ->Insert({depth, parent, frame_id})
+ .id;
+ }
+ parent = callsite_node_inserted.first->callsite_id;
+ frame_to_callsite = &callsite_node_inserted.first->next_frames;
+ depth++;
+ }
+
+ context_->storage->mutable_instruments_sample_table()->Insert(
+ {ts, utid, row.core_id, parent});
+}
+
+DummyMemoryMapping* RowParser::GetDummyMapping(UniquePid upid) {
+ if (auto it = dummy_mappings_.Find(upid); it) {
+ return *it;
+ }
+
+ DummyMemoryMapping* mapping =
+ &context_->mapping_tracker->CreateDummyMapping("");
+ dummy_mappings_.Insert(upid, mapping);
+ return mapping;
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
diff --git a/src/trace_processor/importers/instruments/row_parser.h b/src/trace_processor/importers/instruments/row_parser.h
new file mode 100644
index 0000000..a5e915d
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_parser.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_PARSER_H_
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+class RowDataTracker;
+
+class RowParser : public InstrumentsRowParser {
+ public:
+ explicit RowParser(TraceProcessorContext*);
+ ~RowParser() override = default;
+
+ void ParseInstrumentsRow(int64_t, instruments_importer::Row) override;
+
+ private:
+ DummyMemoryMapping* GetDummyMapping(UniquePid upid);
+
+ TraceProcessorContext* context_;
+ RowDataTracker& data_;
+
+ // Cache FrameId and binary mappings by instruments frame and binary
+ // pointers, respectively. These are already de-duplicated in the
+ // instruments XML parsing.
+ base::FlatHashMap<BacktraceFrameId, FrameId> frame_to_frame_id_;
+ base::FlatHashMap<BinaryId, VirtualMemoryMapping*> binary_to_mapping_;
+ base::FlatHashMap<UniquePid, DummyMemoryMapping*> dummy_mappings_;
+
+ // Cache callsites by FrameId in a prefix tree, where children in the
+ // prefix tree are child frames at the callsite. This should be more
+ // efficient than looking up frame+parent pairs in a hashmap.
+ // TODO(leszeks): Verify that this is more efficient and share the code
+ // with other importers.
+ struct CallsiteTreeNode {
+ CallsiteId callsite_id{0};
+ base::FlatHashMap<FrameId, CallsiteTreeNode> next_frames{};
+ };
+ base::FlatHashMap<FrameId, CallsiteTreeNode> top_frames;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_PARSER_H_
diff --git a/src/trace_processor/importers/json/json_trace_parser_impl.h b/src/trace_processor/importers/json/json_trace_parser_impl.h
index 4c0e269..c675f23 100644
--- a/src/trace_processor/importers/json/json_trace_parser_impl.h
+++ b/src/trace_processor/importers/json/json_trace_parser_impl.h
@@ -17,21 +17,19 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_TRACE_PARSER_IMPL_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_TRACE_PARSER_IMPL_H_
-#include <stdint.h>
-
-#include <memory>
-#include <tuple>
+#include <cstdint>
+#include <string>
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/systrace/systrace_line.h"
#include "src/trace_processor/importers/systrace/systrace_line_parser.h"
+#include "src/trace_processor/storage/trace_storage.h"
namespace Json {
class Value;
}
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -43,8 +41,8 @@
~JsonTraceParserImpl() override;
// TraceParser implementation.
- void ParseJsonPacket(int64_t timestamp, std::string string_value) override;
- void ParseSystraceLine(int64_t timestamp, SystraceLine line) override;
+ void ParseJsonPacket(int64_t, std::string) override;
+ void ParseSystraceLine(int64_t, SystraceLine) override;
private:
TraceProcessorContext* const context_;
@@ -53,7 +51,6 @@
void MaybeAddFlow(TrackId track_id, const Json::Value& event);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_TRACE_PARSER_IMPL_H_
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.cc b/src/trace_processor/importers/json/json_trace_tokenizer.cc
index 8345a9e..5a35f3c 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.cc
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.cc
@@ -16,23 +16,32 @@
#include "src/trace_processor/importers/json/json_trace_tokenizer.h"
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
-#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
-
+#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/json/json_utils.h"
-#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/importers/systrace/systrace_line.h"
+#include "src/trace_processor/sorter/trace_sorter.h" // IWYU pragma: keep
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/util/status_macros.h"
-namespace perfetto {
-namespace trace_processor {
-
+namespace perfetto::trace_processor {
namespace {
+std::string FormatErrorContext(const char* s, const char* e) {
+ return {s, static_cast<size_t>(e - s)};
+}
+
base::Status AppendUnescapedCharacter(char c,
bool is_escaping,
std::string* key) {
@@ -64,7 +73,7 @@
key->append("\\u");
break;
default:
- return base::ErrStatus("Illegal character in JSON");
+ return base::ErrStatus("Illegal character in JSON %c", c);
}
} else if (c != '\\') {
key->push_back(c);
@@ -213,7 +222,7 @@
return ReadDictRes::kEndOfTrace;
if (--braces > 0)
continue;
- size_t len = static_cast<size_t>((s + 1) - dict_begin);
+ auto len = static_cast<size_t>((s + 1) - dict_begin);
*value = base::StringView(dict_begin, len);
*next = s + 1;
return ReadDictRes::kFoundDict;
@@ -324,11 +333,12 @@
state = kInsideDict;
continue;
}
- return base::ErrStatus("Unexpected character before JSON dict");
+ return base::ErrStatus("Unexpected character before JSON dict: '%c'", *s);
}
- if (state == kAfterDict)
- return base::ErrStatus("Unexpected character after JSON dict");
+ if (state == kAfterDict) {
+ return base::ErrStatus("Unexpected character after JSON dict: '%c'", *s);
+ }
PERFETTO_DCHECK(state == kInsideDict);
PERFETTO_DCHECK(s < end);
@@ -347,18 +357,22 @@
if (res == ReadKeyRes::kFatalError) {
return base::ErrStatus(
"Failure parsing JSON: encountered fatal error while parsing key for "
- "value");
+ "value: '%s'",
+ FormatErrorContext(s, end).c_str());
}
if (res == ReadKeyRes::kNeedsMoreData) {
- return base::ErrStatus("Failure parsing JSON: partial JSON dictionary");
+ return base::ErrStatus(
+ "Failure parsing JSON: partial JSON dictionary: '%s'",
+ FormatErrorContext(s, end).c_str());
}
PERFETTO_DCHECK(res == ReadKeyRes::kFoundKey);
if (*s == '[') {
return base::ErrStatus(
- "Failure parsing JSON: unsupported JSON dictionary with array");
+ "Failure parsing JSON: unsupported JSON dictionary with array: '%s'",
+ FormatErrorContext(s, end).c_str());
}
std::string value_str;
@@ -369,14 +383,17 @@
dict_res == ReadDictRes::kEndOfArray ||
dict_res == ReadDictRes::kEndOfTrace) {
return base::ErrStatus(
- "Failure parsing JSON: unable to parse dictionary");
+ "Failure parsing JSON: unable to parse dictionary: '%s'",
+ FormatErrorContext(s, end).c_str());
}
value_str = dict_str.ToStdString();
} else if (*s == '"') {
auto str_res = ReadOneJsonString(s, end, &value_str, &s);
if (str_res == ReadStringRes::kNeedsMoreData ||
str_res == ReadStringRes::kFatalError) {
- return base::ErrStatus("Failure parsing JSON: unable to parse string");
+ return base::ErrStatus(
+ "Failure parsing JSON: unable to parse string: '%s",
+ FormatErrorContext(s, end).c_str());
}
} else {
const char* value_start = s;
@@ -396,8 +413,10 @@
}
}
- if (state != kAfterDict)
- return base::ErrStatus("Failure parsing JSON: malformed dictionary");
+ if (state != kAfterDict) {
+ return base::ErrStatus("Failure parsing JSON: malformed dictionary: '%s'",
+ FormatErrorContext(start, end).c_str());
+ }
*value = std::nullopt;
return base::OkStatus();
@@ -673,5 +692,4 @@
: base::ErrStatus("JSON trace file is incomplete");
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.h b/src/trace_processor/importers/json/json_trace_tokenizer.h
index 8672def..00af4ca 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.h
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.h
@@ -17,18 +17,21 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_TRACE_TOKENIZER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_TRACE_TOKENIZER_H_
-#include <stdint.h>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/importers/systrace/systrace_line_tokenizer.h"
-#include "src/trace_processor/storage/trace_storage.h"
namespace Json {
class Value;
}
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -162,7 +165,6 @@
std::vector<char> buffer_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_JSON_JSON_TRACE_TOKENIZER_H_
diff --git a/src/trace_processor/importers/json/json_utils.cc b/src/trace_processor/importers/json/json_utils.cc
index d2e1c18..d4157a1 100644
--- a/src/trace_processor/importers/json/json_utils.cc
+++ b/src/trace_processor/importers/json/json_utils.cc
@@ -29,14 +29,6 @@
namespace trace_processor {
namespace json {
-bool IsJsonSupported() {
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
- return true;
-#else
- return false;
-#endif
-}
-
std::optional<int64_t> CoerceToTs(const Json::Value& value) {
PERFETTO_DCHECK(IsJsonSupported());
diff --git a/src/trace_processor/importers/json/json_utils.h b/src/trace_processor/importers/json/json_utils.h
index b9d0762..83b79ed 100644
--- a/src/trace_processor/importers/json/json_utils.h
+++ b/src/trace_processor/importers/json/json_utils.h
@@ -38,7 +38,13 @@
// Returns whether JSON related functioanlity is supported with the current
// build flags.
-bool IsJsonSupported();
+constexpr bool IsJsonSupported() {
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+ return true;
+#else
+ return false;
+#endif
+}
std::optional<int64_t> CoerceToTs(const Json::Value& value);
std::optional<int64_t> CoerceToTs(const std::string& value);
diff --git a/src/trace_processor/importers/json/json_utils_unittest.cc b/src/trace_processor/importers/json/json_utils_unittest.cc
index 5b17cc3..3d9be27 100644
--- a/src/trace_processor/importers/json/json_utils_unittest.cc
+++ b/src/trace_processor/importers/json/json_utils_unittest.cc
@@ -16,13 +16,12 @@
#include "src/trace_processor/importers/json/json_utils.h"
+#include <json/config.h>
#include <json/value.h>
#include "test/gtest_and_gmock.h"
-namespace perfetto {
-namespace trace_processor {
-namespace json {
+namespace perfetto::trace_processor::json {
namespace {
TEST(JsonTraceUtilsTest, CoerceToUint32) {
@@ -78,6 +77,4 @@
}
} // namespace
-} // namespace json
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor::json
diff --git a/src/trace_processor/importers/perf/record_parser.cc b/src/trace_processor/importers/perf/record_parser.cc
index a63a5bd..0b0d169 100644
--- a/src/trace_processor/importers/perf/record_parser.cc
+++ b/src/trace_processor/importers/perf/record_parser.cc
@@ -225,7 +225,7 @@
context_->storage->IncrementStats(stats::perf_dummy_mapping_used);
// Simpleperf will not create mappings for anonymous executable mappings
// which are used by JITted code (e.g. V8 JavaScript).
- mapping = mapping_tracker_->GetDummyMapping();
+ mapping = GetDummyMapping(upid);
}
const FrameId frame_id =
@@ -346,4 +346,14 @@
return base::OkStatus();
}
+DummyMemoryMapping* RecordParser::GetDummyMapping(UniquePid upid) {
+ if (auto it = dummy_mappings_.Find(upid); it) {
+ return *it;
+ }
+
+ DummyMemoryMapping* mapping = &mapping_tracker_->CreateDummyMapping("");
+ dummy_mappings_.Insert(upid, mapping);
+ return mapping;
+}
+
} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/record_parser.h b/src/trace_processor/importers/perf/record_parser.h
index 76926d1..6230845 100644
--- a/src/trace_processor/importers/perf/record_parser.h
+++ b/src/trace_processor/importers/perf/record_parser.h
@@ -22,6 +22,7 @@
#include <optional>
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/perf/mmap_record.h"
#include "src/trace_processor/importers/perf/record.h"
@@ -31,6 +32,7 @@
namespace perfetto {
namespace trace_processor {
+class DummyMemoryMapping;
class MappingTracker;
class TraceProcessorContext;
@@ -66,8 +68,11 @@
UniquePid GetUpid(const CommonMmapRecordFields& fields) const;
- TraceProcessorContext* const context_ = nullptr;
+ DummyMemoryMapping* GetDummyMapping(UniquePid upid);
+
+ TraceProcessorContext* const context_;
MappingTracker* const mapping_tracker_;
+ base::FlatHashMap<UniquePid, DummyMemoryMapping*> dummy_mappings_;
};
} // namespace perf_importer
diff --git a/src/trace_processor/importers/proto/android_probes_parser.cc b/src/trace_processor/importers/proto/android_probes_parser.cc
index a6cd9b6..e059404 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.cc
+++ b/src/trace_processor/importers/proto/android_probes_parser.cc
@@ -438,7 +438,8 @@
context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
context_->slice_tracker->Scoped(ts, track_id, kNullStringId, state_id, 0);
} else if (name.StartsWith("debug.tracing.battery_stats.") ||
- name == "debug.tracing.mcc" || name == "debug.tracing.mnc") {
+ name == "debug.tracing.mcc" || name == "debug.tracing.mnc" ||
+ name == "debug.tracing.desktop_mode_visible_tasks") {
StringId name_id = context_->storage->InternString(
name.substr(strlen("debug.tracing.")));
std::optional<int32_t> state =
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
index 8a11d5d..393bd79 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
@@ -16,21 +16,24 @@
#include "src/trace_processor/importers/proto/proto_trace_parser_impl.h"
-#include <string.h>
-
-#include <cinttypes>
+#include <cstdint>
+#include <cstring>
#include <string>
+#include <utility>
+#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/metatrace_events.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/string_writer.h"
-#include "perfetto/ext/base/uuid.h"
-
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/cpu_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/process_tracker.h"
@@ -39,8 +42,8 @@
#include "src/trace_processor/importers/etw/etw_module.h"
#include "src/trace_processor/importers/ftrace/ftrace_module.h"
#include "src/trace_processor/importers/proto/track_event_module.h"
-#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
@@ -49,8 +52,7 @@
#include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
ProtoTraceParserImpl::ProtoTraceParserImpl(TraceProcessorContext* context)
: context_(context),
@@ -109,8 +111,8 @@
}
void ProtoTraceParserImpl::ParseEtwEvent(uint32_t cpu,
- int64_t ts,
- TracePacketData data) {
+ int64_t ts,
+ TracePacketData data) {
PERFETTO_DCHECK(context_->etw_module);
context_->etw_module->ParseEtwEventData(cpu, ts, data);
@@ -121,8 +123,8 @@
}
void ProtoTraceParserImpl::ParseFtraceEvent(uint32_t cpu,
- int64_t ts,
- TracePacketData data) {
+ int64_t ts,
+ TracePacketData data) {
PERFETTO_DCHECK(context_->ftrace_module);
context_->ftrace_module->ParseFtraceEventData(cpu, ts, data);
@@ -133,8 +135,8 @@
}
void ProtoTraceParserImpl::ParseInlineSchedSwitch(uint32_t cpu,
- int64_t ts,
- InlineSchedSwitch data) {
+ int64_t ts,
+ InlineSchedSwitch data) {
PERFETTO_DCHECK(context_->ftrace_module);
context_->ftrace_module->ParseInlineSchedSwitch(cpu, ts, data);
@@ -145,8 +147,8 @@
}
void ProtoTraceParserImpl::ParseInlineSchedWaking(uint32_t cpu,
- int64_t ts,
- InlineSchedWaking data) {
+ int64_t ts,
+ InlineSchedWaking data) {
PERFETTO_DCHECK(context_->ftrace_module);
context_->ftrace_module->ParseInlineSchedWaking(cpu, ts, data);
@@ -156,6 +158,18 @@
context_->args_tracker->Flush();
}
+void ProtoTraceParserImpl::ParseLegacyV8ProfileEvent(
+ int64_t ts,
+ LegacyV8CpuProfileEvent event) {
+ base::Status status = context_->legacy_v8_cpu_profile_tracker->AddSample(
+ ts, event.session_id, event.pid, event.tid, event.callsite_id);
+ if (!status.ok()) {
+ context_->storage->IncrementStats(
+ stats::legacy_v8_cpu_profile_invalid_sample);
+ }
+ context_->args_tracker->Flush();
+}
+
void ProtoTraceParserImpl::ParseChromeEvents(int64_t ts, ConstBytes blob) {
TraceStorage* storage = context_->storage.get();
protos::pbzero::ChromeEventBundle::Decoder bundle(blob.data, blob.size);
@@ -373,5 +387,4 @@
return *maybe_id;
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.h b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
index 2c4dc07..f6b2304 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
@@ -17,11 +17,9 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_PARSER_IMPL_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_PARSER_IMPL_H_
-#include <stdint.h>
+#include <cstdint>
-#include <array>
-#include <memory>
-
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/protozero/field.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
@@ -29,11 +27,9 @@
namespace perfetto {
-namespace protos {
-namespace pbzero {
+namespace protos::pbzero {
class TracePacket_Decoder;
-} // namespace pbzero
-} // namespace protos
+} // namespace protos::pbzero
namespace trace_processor {
@@ -65,12 +61,14 @@
int64_t /*ts*/,
InlineSchedWaking data) override;
- void ParseChromeEvents(int64_t ts, ConstBytes);
- void ParseMetatraceEvent(int64_t ts, ConstBytes);
+ void ParseLegacyV8ProfileEvent(int64_t ts, LegacyV8CpuProfileEvent) override;
private:
StringId GetMetatraceInternedString(uint64_t iid);
+ void ParseChromeEvents(int64_t ts, ConstBytes);
+ void ParseMetatraceEvent(int64_t ts, ConstBytes);
+
TraceProcessorContext* context_;
const StringId metatrace_id_;
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index 54bfdba..1e72da0 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -16,33 +16,37 @@
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
+#include <algorithm>
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <map>
#include <numeric>
#include <optional>
-#include <string>
+#include <tuple>
+#include <utility>
#include <vector>
-#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_view.h"
-#include "perfetto/ext/base/utils.h"
+#include "perfetto/protozero/field.h"
#include "perfetto/protozero/proto_decoder.h"
-#include "perfetto/protozero/proto_utils.h"
#include "perfetto/public/compiler.h"
-#include "perfetto/trace_processor/status.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/machine_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
-#include "src/trace_processor/importers/ftrace/ftrace_module.h"
#include "src/trace_processor/importers/proto/packet_analyzer.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/types/variadic.h"
#include "src/trace_processor/util/descriptors.h"
-#include "src/trace_processor/util/gzip_utils.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/common/trace_stats.pbzero.h"
@@ -50,13 +54,11 @@
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "protos/perfetto/trace/extension_descriptor.pbzero.h"
#include "protos/perfetto/trace/perfetto/tracing_service_event.pbzero.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "protos/perfetto/trace/remote_clock_sync.pbzero.h"
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
ProtoTraceReader::ProtoTraceReader(TraceProcessorContext* ctx)
: context_(ctx),
@@ -65,13 +67,13 @@
ctx->storage->InternString("invalid_incremental_state")) {}
ProtoTraceReader::~ProtoTraceReader() = default;
-util::Status ProtoTraceReader::Parse(TraceBlobView blob) {
+base::Status ProtoTraceReader::Parse(TraceBlobView blob) {
return tokenizer_.Tokenize(std::move(blob), [this](TraceBlobView packet) {
return ParsePacket(std::move(packet));
});
}
-util::Status ProtoTraceReader::ParseExtensionDescriptor(ConstBytes descriptor) {
+base::Status ProtoTraceReader::ParseExtensionDescriptor(ConstBytes descriptor) {
protos::pbzero::ExtensionDescriptor::Decoder decoder(descriptor.data,
descriptor.size);
@@ -82,10 +84,10 @@
/*merge_existing_messages=*/true);
}
-util::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) {
+base::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) {
protos::pbzero::TracePacket::Decoder decoder(packet.data(), packet.length());
if (PERFETTO_UNLIKELY(decoder.bytes_left())) {
- return util::ErrStatus(
+ return base::ErrStatus(
"Failed to parse proto packet fully; the trace is probably corrupt.");
}
@@ -172,7 +174,7 @@
if (decoder.sequence_flags() &
protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
if (!seq_id) {
- return util::ErrStatus(
+ return base::ErrStatus(
"TracePacket specified SEQ_NEEDS_INCREMENTAL_STATE but the "
"TraceWriter's sequence_id is zero (the service is "
"probably too old)");
@@ -183,12 +185,12 @@
// Account for the skipped packet for trace proto content analysis,
// with a special annotation.
PacketAnalyzer::SampleAnnotation annotation;
- annotation.push_back(
- {skipped_packet_key_id_, invalid_incremental_state_key_id_});
+ annotation.emplace_back(skipped_packet_key_id_,
+ invalid_incremental_state_key_id_);
PacketAnalyzer::Get(context_)->ProcessPacket(packet, annotation);
}
context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
- return util::OkStatus();
+ return base::OkStatus();
}
}
@@ -223,7 +225,7 @@
ClockTracker::ClockId converted_clock_id = timestamp_clock_id;
if (ClockTracker::IsSequenceClock(converted_clock_id)) {
if (!seq_id) {
- return util::ErrStatus(
+ return base::ErrStatus(
"TracePacket specified a sequence-local clock id (%" PRIu32
") but the TraceWriter's sequence_id is zero (the service is "
"probably too old)",
@@ -239,7 +241,7 @@
// We don't return an error here as it will cause the trace to stop
// parsing. Instead, we rely on the stat increment in ToTraceTime() to
// inform the user about the error.
- return util::OkStatus();
+ return base::OkStatus();
}
timestamp = trace_ts.value();
}
@@ -280,7 +282,7 @@
context_->sorter->PushTracePacket(timestamp, state->current_generation(),
std::move(packet), context_->machine_id());
- return util::OkStatus();
+ return base::OkStatus();
}
void ProtoTraceReader::ParseTraceConfig(protozero::ConstBytes blob) {
@@ -373,7 +375,7 @@
}
}
-util::Status ProtoTraceReader::ParseClockSnapshot(ConstBytes blob,
+base::Status ProtoTraceReader::ParseClockSnapshot(ConstBytes blob,
uint32_t seq_id) {
std::vector<ClockTracker::ClockTimestamp> clock_timestamps;
protos::pbzero::ClockSnapshot::Decoder evt(blob.data, blob.size);
@@ -386,7 +388,7 @@
ClockTracker::ClockId clock_id = clk.clock_id();
if (ClockTracker::IsSequenceClock(clk.clock_id())) {
if (!seq_id) {
- return util::ErrStatus(
+ return base::ErrStatus(
"ClockSnapshot packet is specifying a sequence-scoped clock id "
"(%" PRIu64 ") but the TracePacket sequence_id is zero",
clock_id);
@@ -450,10 +452,10 @@
context_->storage->mutable_clock_snapshot_table()->Insert(row);
}
- return util::OkStatus();
+ return base::OkStatus();
}
-util::Status ProtoTraceReader::ParseRemoteClockSync(ConstBytes blob) {
+base::Status ProtoTraceReader::ParseRemoteClockSync(ConstBytes blob) {
protos::pbzero::RemoteClockSync::Decoder evt(blob.data, blob.size);
std::vector<SyncClockSnapshots> sync_clock_snapshots;
@@ -496,7 +498,7 @@
context_->clock_tracker->SetClockOffset(it.key(), it.value());
}
- return util::OkStatus();
+ return base::OkStatus();
}
base::FlatHashMap<int64_t /*Clock Id*/, int64_t /*Offset*/>
@@ -593,7 +595,7 @@
}
}
-util::Status ProtoTraceReader::ParseServiceEvent(int64_t ts, ConstBytes blob) {
+base::Status ProtoTraceReader::ParseServiceEvent(int64_t ts, ConstBytes blob) {
protos::pbzero::TracingServiceEvent::Decoder tse(blob);
if (tse.tracing_started()) {
context_->metadata_tracker->SetMetadata(metadata::tracing_started_ns,
@@ -615,7 +617,7 @@
if (tse.read_tracing_buffers_completed()) {
context_->sorter->NotifyReadBufferEvent();
}
- return util::OkStatus();
+ return base::OkStatus();
}
void ProtoTraceReader::ParseTraceStats(ConstBytes blob) {
@@ -741,5 +743,4 @@
return base::OkStatus();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h
index 45c0c9f..9864156 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.h
+++ b/src/trace_processor/importers/proto/proto_trace_reader.h
@@ -17,11 +17,13 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_READER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_READER_H_
-#include <stdint.h>
-
-#include <tuple>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
#include <utility>
+#include <vector>
+#include "perfetto/base/status.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/importers/proto/multi_machine_trace_manager.h"
@@ -35,12 +37,10 @@
namespace perfetto {
-namespace protos {
-namespace pbzero {
+namespace protos::pbzero {
class TracePacket_Decoder;
class TraceConfig_Decoder;
-} // namespace pbzero
-} // namespace protos
+} // namespace protos::pbzero
namespace trace_processor {
@@ -61,13 +61,13 @@
~ProtoTraceReader() override;
// ChunkedTraceReader implementation.
- util::Status Parse(TraceBlobView) override;
+ base::Status Parse(TraceBlobView) override;
base::Status NotifyEndOfFile() override;
using SyncClockSnapshots = base::FlatHashMap<
int64_t,
std::pair</*host ts*/ uint64_t, /*client ts*/ uint64_t>>;
- base::FlatHashMap<int64_t /*Clock Id*/, int64_t /*Offset*/>
+ static base::FlatHashMap<int64_t /*Clock Id*/, int64_t /*Offset*/>
CalculateClockOffsetsForTesting(
std::vector<SyncClockSnapshots>& sync_clock_snapshots) {
return CalculateClockOffsets(sync_clock_snapshots);
@@ -77,10 +77,10 @@
private:
using ConstBytes = protozero::ConstBytes;
- util::Status ParsePacket(TraceBlobView);
- util::Status ParseServiceEvent(int64_t ts, ConstBytes);
- util::Status ParseClockSnapshot(ConstBytes blob, uint32_t seq_id);
- util::Status ParseRemoteClockSync(ConstBytes blob);
+ base::Status ParsePacket(TraceBlobView);
+ base::Status ParseServiceEvent(int64_t ts, ConstBytes);
+ base::Status ParseClockSnapshot(ConstBytes blob, uint32_t seq_id);
+ base::Status ParseRemoteClockSync(ConstBytes blob);
void HandleIncrementalStateCleared(
const protos::pbzero::TracePacket_Decoder&);
void HandleFirstPacketOnSequence(uint32_t packet_sequence_id);
@@ -89,10 +89,10 @@
TraceBlobView trace_packet_defaults);
void ParseInternedData(const protos::pbzero::TracePacket_Decoder&,
TraceBlobView interned_data);
- void ParseTraceConfig(ConstBytes);
+ static void ParseTraceConfig(ConstBytes);
void ParseTraceStats(ConstBytes);
- base::FlatHashMap<int64_t /*Clock Id*/, int64_t /*Offset*/>
+ static base::FlatHashMap<int64_t /*Clock Id*/, int64_t /*Offset*/>
CalculateClockOffsets(std::vector<SyncClockSnapshots>&);
PacketSequenceStateBuilder* GetIncrementalStateForPacketSequence(
@@ -105,7 +105,7 @@
}
return builder;
}
- util::Status ParseExtensionDescriptor(ConstBytes descriptor);
+ base::Status ParseExtensionDescriptor(ConstBytes descriptor);
TraceProcessorContext* context_;
diff --git a/src/trace_processor/importers/proto/track_event_module.cc b/src/trace_processor/importers/proto/track_event_module.cc
index aee720c..0ae00f2 100644
--- a/src/trace_processor/importers/proto/track_event_module.cc
+++ b/src/trace_processor/importers/proto/track_event_module.cc
@@ -15,20 +15,21 @@
*/
#include "src/trace_processor/importers/proto/track_event_module.h"
-#include "perfetto/base/build_config.h"
+#include <cstdint>
+#include <utility>
+
#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/string_utils.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
+#include "perfetto/trace_processor/ref_counted.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/importers/proto/track_event_tracker.h"
#include "src/trace_processor/types/trace_processor_context.h"
-#include "protos/perfetto/config/data_source_config.pbzero.h"
-#include "protos/perfetto/config/trace_config.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
using perfetto::protos::pbzero::TracePacket;
@@ -59,9 +60,8 @@
return tokenizer_.TokenizeTrackDescriptorPacket(std::move(state), decoder,
packet_timestamp);
case TracePacket::kTrackEventFieldNumber:
- tokenizer_.TokenizeTrackEventPacket(std::move(state), decoder, packet,
- packet_timestamp);
- return ModuleResult::Handled();
+ return tokenizer_.TokenizeTrackEventPacket(std::move(state), decoder,
+ packet, packet_timestamp);
case TracePacket::kThreadDescriptorFieldNumber:
// TODO(eseckler): Remove once Chrome has switched to TrackDescriptors.
return tokenizer_.TokenizeThreadDescriptorPacket(std::move(state),
@@ -70,13 +70,6 @@
return ModuleResult::Ignored();
}
-void TrackEventModule::ParseTrackEventData(const TracePacket::Decoder& decoder,
- int64_t ts,
- const TrackEventData& data) {
- parser_.ParseTrackEvent(ts, &data, decoder.track_event(),
- decoder.trusted_packet_sequence_id());
-}
-
void TrackEventModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
int64_t ts,
const TracePacketData&,
@@ -107,9 +100,15 @@
track_event_tracker_->OnFirstPacketOnSequence(packet_sequence_id);
}
+void TrackEventModule::ParseTrackEventData(const TracePacket::Decoder& decoder,
+ int64_t ts,
+ const TrackEventData& data) {
+ parser_.ParseTrackEvent(ts, &data, decoder.track_event(),
+ decoder.trusted_packet_sequence_id());
+}
+
void TrackEventModule::NotifyEndOfFile() {
parser_.NotifyEndOfFile();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_module.h b/src/trace_processor/importers/proto/track_event_module.h
index 4a7ae69..756a1fc 100644
--- a/src/trace_processor/importers/proto/track_event_module.h
+++ b/src/trace_processor/importers/proto/track_event_module.h
@@ -17,7 +17,11 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_MODULE_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_MODULE_H_
+#include <cstdint>
+#include <memory>
+
#include "perfetto/trace_processor/ref_counted.h"
+#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/importers/proto/track_event_parser.h"
@@ -25,8 +29,7 @@
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TrackEventModule : public ProtoImporterModule {
public:
@@ -41,6 +44,11 @@
RefPtr<PacketSequenceStateGeneration> state,
uint32_t field_id) override;
+ void ParseTracePacketData(const protos::pbzero::TracePacket::Decoder& decoder,
+ int64_t ts,
+ const TracePacketData& data,
+ uint32_t field_id) override;
+
void OnIncrementalStateCleared(uint32_t) override;
void OnFirstPacketOnSequence(uint32_t) override;
@@ -49,11 +57,6 @@
int64_t ts,
const TrackEventData& data);
- void ParseTracePacketData(const protos::pbzero::TracePacket::Decoder& decoder,
- int64_t ts,
- const TracePacketData& data,
- uint32_t field_id) override;
-
void NotifyEndOfFile() override;
private:
@@ -62,7 +65,6 @@
TrackEventParser parser_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_MODULE_H_
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 6ca338e..360126c 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -31,7 +31,6 @@
#include "perfetto/protozero/proto_decoder.h"
#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/basic_types.h"
-#include "perfetto/trace_processor/status.h"
#include "src/trace_processor/containers/null_term_string_view.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/common/args_tracker.h"
@@ -39,6 +38,8 @@
#include "src/trace_processor/importers/common/cpu_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
@@ -209,8 +210,8 @@
if (context_->content_analyzer) {
PacketAnalyzer::SampleAnnotation annotation;
- annotation.push_back({parser_->event_category_key_id_, category_id_});
- annotation.push_back({parser_->event_name_key_id_, name_id_});
+ annotation.emplace_back(parser_->event_category_key_id_, category_id_);
+ annotation.emplace_back(parser_->event_name_key_id_, name_id_);
PacketAnalyzer::Get(context_)->ProcessPacket(
event_data_->trace_packet_data.packet, annotation);
}
@@ -1341,6 +1342,7 @@
std::optional<UniqueTid> upid_;
std::optional<int64_t> thread_timestamp_;
std::optional<int64_t> thread_instruction_count_;
+
// All events in legacy JSON require a thread ID, but for some types of
// events (e.g. async events or process/global-scoped instants), we don't
// store it in the slice/track model. To pass the utid through to the json
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
index 6198dd5..7843a53 100644
--- a/src/trace_processor/importers/proto/track_event_parser.h
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -18,11 +18,11 @@
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_PARSER_H_
#include <array>
-#include <map>
+#include <cstdint>
+#include <optional>
+#include <vector>
-#include "perfetto/base/build_config.h"
#include "perfetto/protozero/field.h"
-#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/trace_parser.h"
@@ -31,14 +31,11 @@
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/util/proto_to_args_parser.h"
-#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
-
namespace Json {
class Value;
}
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// Field numbers to be added to args table automatically via reflection
//
@@ -135,7 +132,6 @@
ActiveChromeProcessesTracker active_chrome_processes_tracker_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_PARSER_H_
diff --git a/src/trace_processor/importers/proto/track_event_sequence_state.cc b/src/trace_processor/importers/proto/track_event_sequence_state.cc
index 99bc93e..ee138b5 100644
--- a/src/trace_processor/importers/proto/track_event_sequence_state.cc
+++ b/src/trace_processor/importers/proto/track_event_sequence_state.cc
@@ -18,8 +18,7 @@
#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
void TrackEventSequenceState::SetThreadDescriptor(
const protos::pbzero::ThreadDescriptor::Decoder& decoder) {
@@ -33,5 +32,4 @@
thread_instruction_count_ = decoder.reference_thread_instruction_count();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index 238e247..268f45f 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -16,34 +16,51 @@
#include "src/trace_processor/importers/proto/track_event_tokenizer.h"
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "perfetto/base/compiler.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob_view.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
-#include "src/trace_processor/importers/common/machine_tracker.h"
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/json/json_utils.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
#include "src/trace_processor/importers/proto/track_event_tracker.h"
#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-#include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
-#include "protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/range_of_interest.pbzero.h"
#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/status_macros.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
using protos::pbzero::CounterDescriptor;
@@ -217,7 +234,7 @@
state.SetThreadDescriptor(thread);
}
-void TrackEventTokenizer::TokenizeTrackEventPacket(
+ModuleResult TrackEventTokenizer::TokenizeTrackEventPacket(
RefPtr<PacketSequenceStateGeneration> state,
const protos::pbzero::TracePacket::Decoder& packet,
TraceBlobView* packet_blob,
@@ -225,12 +242,10 @@
if (PERFETTO_UNLIKELY(!packet.has_trusted_packet_sequence_id())) {
PERFETTO_ELOG("TrackEvent packet without trusted_packet_sequence_id");
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
}
- auto field = packet.track_event();
- protos::pbzero::TrackEvent::Decoder event(field.data, field.size);
-
+ protos::pbzero::TrackEvent::Decoder event(packet.track_event());
protos::pbzero::TrackEventDefaults::Decoder* defaults =
state->GetTrackEventDefaults();
@@ -246,7 +261,7 @@
// packet loss.
if (!state->track_event_timestamps_valid()) {
context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
- return;
+ return ModuleResult::Handled();
}
timestamp = state->IncrementAndGetTrackEventTimeNs(
event.timestamp_delta_us() * 1000);
@@ -272,7 +287,16 @@
} else {
PERFETTO_ELOG("TrackEvent without valid timestamp");
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
+ }
+
+ // Handle legacy sample events which might have timestamps embedded inside.
+ if (PERFETTO_UNLIKELY(event.has_legacy_event())) {
+ protos::pbzero::TrackEvent::LegacyEvent::Decoder leg(event.legacy_event());
+ if (PERFETTO_UNLIKELY(leg.phase() == 'P')) {
+ RETURN_IF_ERROR(TokenizeLegacySampleEvent(
+ event, leg, *data.trace_packet_data.sequence_state));
+ }
}
if (event.has_thread_time_delta_us()) {
@@ -280,7 +304,7 @@
// packet loss.
if (!state->track_event_timestamps_valid()) {
context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
- return;
+ return ModuleResult::Handled();
}
data.thread_timestamp = state->IncrementAndGetTrackEventThreadTimeNs(
event.thread_time_delta_us() * 1000);
@@ -294,7 +318,7 @@
// packet loss.
if (!state->track_event_timestamps_valid()) {
context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
- return;
+ return ModuleResult::Handled();
}
data.thread_instruction_count =
state->IncrementAndGetTrackEventThreadInstructionCount(
@@ -315,7 +339,7 @@
PERFETTO_DLOG(
"Ignoring TrackEvent with counter_value but without track_uuid");
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
}
if (!event.has_counter_value() && !event.has_double_counter_value()) {
@@ -325,7 +349,7 @@
"track_uuid %" PRIu64,
track_uuid);
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
}
std::optional<double> value;
@@ -343,7 +367,7 @@
PERFETTO_DLOG("Ignoring TrackEvent with invalid track_uuid %" PRIu64,
track_uuid);
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
}
data.counter_value = *value;
@@ -358,7 +382,7 @@
if (!result.ok()) {
PERFETTO_DLOG("%s", result.c_message());
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
}
result = AddExtraCounterValues(
data, index, packet.trusted_packet_sequence_id(),
@@ -368,11 +392,12 @@
if (!result.ok()) {
PERFETTO_DLOG("%s", result.c_message());
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- return;
+ return ModuleResult::Handled();
}
context_->sorter->PushTrackEventPacket(timestamp, std::move(data),
context_->machine_id());
+ return ModuleResult::Handled();
}
template <typename T>
@@ -394,19 +419,19 @@
} else if (default_track_uuid_it) {
track_uuid_it = default_track_uuid_it;
} else {
- return base::Status(
+ return base::ErrStatus(
"Ignoring TrackEvent with extra_{double_,}counter_values but without "
"extra_{double_,}counter_track_uuids");
}
for (; value_it; ++value_it, ++track_uuid_it, ++index) {
if (!*track_uuid_it) {
- return base::Status(
+ return base::ErrStatus(
"Ignoring TrackEvent with more extra_{double_,}counter_values than "
"extra_{double_,}counter_track_uuids");
}
if (index >= TrackEventData::kMaxNumExtraCounters) {
- return base::Status(
+ return base::ErrStatus(
"Ignoring TrackEvent with more extra_{double_,}counter_values than "
"TrackEventData::kMaxNumExtraCounters");
}
@@ -415,7 +440,7 @@
*track_uuid_it, trusted_packet_sequence_id,
static_cast<double>(*value_it));
if (!abs_value) {
- return base::Status(
+ return base::ErrStatus(
"Ignoring TrackEvent with invalid extra counter track");
}
data.extra_counter_values[index] = *abs_value;
@@ -423,5 +448,76 @@
return base::OkStatus();
}
-} // namespace trace_processor
-} // namespace perfetto
+base::Status TrackEventTokenizer::TokenizeLegacySampleEvent(
+ const protos::pbzero::TrackEvent::Decoder& event,
+ const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy,
+ PacketSequenceStateGeneration& state) {
+ // We are just trying to parse out the V8 profiling events into the cpu
+ // sampling tables: if we don't have JSON enabled, just don't do this.
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+ for (auto it = event.debug_annotations(); it; ++it) {
+ protos::pbzero::DebugAnnotation::Decoder da(*it);
+ auto* interned_name = state.LookupInternedMessage<
+ protos::pbzero::InternedData::kDebugAnnotationNamesFieldNumber,
+ protos::pbzero::DebugAnnotationName>(da.name_iid());
+ base::StringView name(interned_name->name());
+ if (name != "data" || !da.has_legacy_json_value()) {
+ continue;
+ }
+ auto opt_val = json::ParseJsonString(da.legacy_json_value());
+ if (!opt_val) {
+ continue;
+ }
+ const auto& val = *opt_val;
+ if (val.isMember("startTime")) {
+ ASSIGN_OR_RETURN(int64_t ts, context_->clock_tracker->ToTraceTime(
+ protos::pbzero::BUILTIN_CLOCK_MONOTONIC,
+ val["startTime"].asInt64() * 1000));
+ context_->legacy_v8_cpu_profile_tracker->SetStartTsForSessionAndPid(
+ legacy.unscoped_id(), static_cast<uint32_t>(state.pid()), ts);
+ continue;
+ }
+ const auto& profile = val["cpuProfile"];
+ for (const auto& n : profile["nodes"]) {
+ uint32_t node_id = n["id"].asUInt();
+ std::optional<uint32_t> parent_node_id =
+ n.isMember("parent") ? std::make_optional(n["parent"].asUInt())
+ : std::nullopt;
+ const auto& frame = n["callFrame"];
+ base::StringView url =
+ frame.isMember("url") ? frame["url"].asCString() : base::StringView();
+ base::StringView function_name = frame["functionName"].asCString();
+ base::Status status =
+ context_->legacy_v8_cpu_profile_tracker->AddCallsite(
+ legacy.unscoped_id(), static_cast<uint32_t>(state.pid()), node_id,
+ parent_node_id, url, function_name);
+ if (!status.ok()) {
+ context_->storage->IncrementStats(
+ stats::legacy_v8_cpu_profile_invalid_callsite);
+ continue;
+ }
+ }
+ const auto& samples = profile["samples"];
+ const auto& deltas = val["timeDeltas"];
+ if (samples.size() != deltas.size()) {
+ return base::ErrStatus(
+ "v8 legacy profile: samples and timestamps do not have same size");
+ }
+ for (uint32_t i = 0; i < samples.size(); ++i) {
+ ASSIGN_OR_RETURN(
+ int64_t ts,
+ context_->legacy_v8_cpu_profile_tracker->AddDeltaAndGetTs(
+ legacy.unscoped_id(), static_cast<uint32_t>(state.pid()),
+ deltas[i].asInt64() * 1000));
+ context_->sorter->PushLegacyV8CpuProfileEvent(
+ ts, legacy.unscoped_id(), static_cast<uint32_t>(state.pid()),
+ static_cast<uint32_t>(state.tid()), samples[i].asUInt());
+ }
+ }
+#else
+ base::ignore_result(event, legacy, state);
+#endif
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.h b/src/trace_processor/importers/proto/track_event_tokenizer.h
index 6ce3347..0627562 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.h
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.h
@@ -17,23 +17,26 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TOKENIZER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TOKENIZER_H_
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
+#include "perfetto/base/status.h"
#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/trace_processor/ref_counted.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto {
-namespace protos {
-namespace pbzero {
+namespace protos::pbzero {
class ChromeThreadDescriptor_Decoder;
class ProcessDescriptor_Decoder;
class ThreadDescriptor_Decoder;
class TracePacket_Decoder;
-} // namespace pbzero
-} // namespace protos
+class TrackEvent_Decoder;
+class TrackEvent_LegacyEvent_Decoder;
+} // namespace protos::pbzero
namespace trace_processor {
@@ -57,10 +60,11 @@
ModuleResult TokenizeThreadDescriptorPacket(
RefPtr<PacketSequenceStateGeneration> state,
const protos::pbzero::TracePacket_Decoder&);
- void TokenizeTrackEventPacket(RefPtr<PacketSequenceStateGeneration> state,
- const protos::pbzero::TracePacket_Decoder&,
- TraceBlobView* packet,
- int64_t packet_timestamp);
+ ModuleResult TokenizeTrackEventPacket(
+ RefPtr<PacketSequenceStateGeneration> state,
+ const protos::pbzero::TracePacket_Decoder&,
+ TraceBlobView* packet,
+ int64_t packet_timestamp);
private:
void TokenizeThreadDescriptor(
@@ -74,6 +78,10 @@
protozero::RepeatedFieldIterator<T> value_it,
protozero::RepeatedFieldIterator<uint64_t> packet_track_uuid_it,
protozero::RepeatedFieldIterator<uint64_t> default_track_uuid_it);
+ base::Status TokenizeLegacySampleEvent(
+ const protos::pbzero::TrackEvent_Decoder&,
+ const protos::pbzero::TrackEvent_LegacyEvent_Decoder&,
+ PacketSequenceStateGeneration& state);
TraceProcessorContext* context_;
TrackEventTracker* track_event_tracker_;
diff --git a/src/trace_processor/importers/proto/track_event_tracker.h b/src/trace_processor/importers/proto/track_event_tracker.h
index 2eb7092..e32138d 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.h
+++ b/src/trace_processor/importers/proto/track_event_tracker.h
@@ -17,14 +17,21 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TRACKER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TRACKER_H_
+#include <cstdint>
+#include <map>
+#include <optional>
+#include <tuple>
#include <unordered_set>
+#include <vector>
-#include "src/trace_processor/importers/common/args_tracker.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
// Tracks and stores tracks based on track types, ids and scopes.
class TrackEventTracker {
@@ -266,7 +273,6 @@
TraceProcessorContext* const context_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index 9946a23..317374e 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.cc
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc
@@ -49,6 +49,23 @@
kWinscopeDescriptor.size());
}
+ModuleResult WinscopeModule::TokenizePacket(
+ const protos::pbzero::TracePacket::Decoder& decoder,
+ TraceBlobView* /*packet*/,
+ int64_t /*packet_timestamp*/,
+ RefPtr<PacketSequenceStateGeneration> /*state*/,
+ uint32_t field_id) {
+
+ switch (field_id) {
+ case TracePacket::kProtologViewerConfigFieldNumber:
+ protolog_parser_.ParseProtoLogViewerConfig(
+ decoder.protolog_viewer_config());
+ return ModuleResult::Handled();
+ }
+
+ return ModuleResult::Ignored();
+}
+
void WinscopeModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
int64_t timestamp,
const TracePacketData& data,
@@ -73,10 +90,6 @@
protolog_parser_.ParseProtoLogMessage(
data.sequence_state.get(), decoder.protolog_message(), timestamp);
return;
- case TracePacket::kProtologViewerConfigFieldNumber:
- protolog_parser_.ParseProtoLogViewerConfig(
- decoder.protolog_viewer_config());
- return;
case TracePacket::kWinscopeExtensionsFieldNumber:
ParseWinscopeExtensionsData(decoder.winscope_extensions(), timestamp,
data);
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h
index 649e329..e14be59 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.h
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.h
@@ -36,6 +36,13 @@
public:
explicit WinscopeModule(TraceProcessorContext* context);
+ ModuleResult TokenizePacket(
+ const protos::pbzero::TracePacket::Decoder& decoder,
+ TraceBlobView* packet,
+ int64_t packet_timestamp,
+ RefPtr<PacketSequenceStateGeneration> state,
+ uint32_t field_id) override;
+
void ParseTracePacketData(const protos::pbzero::TracePacket::Decoder&,
int64_t ts,
const TracePacketData&,
diff --git a/src/trace_processor/importers/systrace/systrace_line.h b/src/trace_processor/importers/systrace/systrace_line.h
index d125d0e..a5aa427 100644
--- a/src/trace_processor/importers/systrace/systrace_line.h
+++ b/src/trace_processor/importers/systrace/systrace_line.h
@@ -17,11 +17,10 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_SYSTRACE_SYSTRACE_LINE_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_SYSTRACE_SYSTRACE_LINE_H_
-#include <cinttypes>
+#include <cstdint>
#include <string>
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
struct alignas(8) SystraceLine {
int64_t ts;
@@ -35,7 +34,6 @@
std::string args_str;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_SYSTRACE_SYSTRACE_LINE_H_
diff --git a/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql b/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql
index 3011c54..1ed0a3d 100644
--- a/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_app_startup_rails.sql
@@ -31,7 +31,7 @@
DROP VIEW IF EXISTS wattson_app_startup_rails_output;
CREATE PERFETTO VIEW wattson_app_startup_rails_output AS
SELECT AndroidWattsonTimePeriodMetric(
- 'metric_version', 2,
+ 'metric_version', 3,
'period_info', (
SELECT RepeatedField(
AndroidWattsonEstimateInfo(
diff --git a/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql b/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql
index e96ad8d..73e8e0d 100644
--- a/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_markers_rails.sql
@@ -33,7 +33,7 @@
DROP VIEW IF EXISTS wattson_markers_rails_output;
CREATE PERFETTO VIEW wattson_markers_rails_output AS
SELECT AndroidWattsonTimePeriodMetric(
- 'metric_version', 2,
+ 'metric_version', 3,
'period_info', (
SELECT RepeatedField(
AndroidWattsonEstimateInfo(
diff --git a/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql b/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql
index 761de55..ce8666f 100644
--- a/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_markers_threads.sql
@@ -37,27 +37,27 @@
CREATE PERFETTO VIEW _wattson_thread_attribution AS
SELECT
-- active time of thread divided by total time where Wattson is defined
- SUM(estimate_mw * dur) / 1000000000 as estimate_mws,
+ SUM(estimated_mw * dur) / 1000000000 as estimated_mws,
(
- SUM(estimate_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
- ) as estimate_mw,
+ SUM(estimated_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
+ ) as estimated_mw,
thread_name,
process_name,
tid,
pid
FROM _windowed_threads_system_state
GROUP BY utid
-ORDER BY estimate_mw DESC;
+ORDER BY estimated_mw DESC;
DROP VIEW IF EXISTS wattson_markers_threads_output;
CREATE PERFETTO VIEW wattson_markers_threads_output AS
SELECT AndroidWattsonTasksAttributionMetric(
- 'metric_version', 1,
+ 'metric_version', 2,
'task_info', (
SELECT RepeatedField(
AndroidWattsonTaskInfo(
- 'estimate_mws', ROUND(estimate_mws, 6),
- 'estimate_mw', ROUND(estimate_mw, 6),
+ 'estimated_mws', ROUND(estimated_mws, 6),
+ 'estimated_mw', ROUND(estimated_mw, 6),
'thread_name', thread_name,
'process_name', process_name,
'thread_id', tid,
diff --git a/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql b/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql
index 57b805b..4a2c1b0 100644
--- a/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_rail_relations.sql
@@ -76,47 +76,47 @@
is_defined,
period_id,
period_dur,
- cast_double!(IIF(is_defined, sum_mw, NULL)) as estimate_mw,
+ cast_double!(IIF(is_defined, sum_mw, NULL)) as estimated_mw,
AndroidWattsonPolicyEstimate(
- 'estimate_mw', cast_double!(IIF(is_defined, sum_mw, NULL)),
+ 'estimated_mw', cast_double!(IIF(is_defined, sum_mw, NULL)),
'cpu0', IIF(
cpu0_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu0_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu0_mw),
NULL
),
'cpu1', IIF(
cpu1_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu1_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu1_mw),
NULL
),
'cpu2', IIF(
cpu2_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu2_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu2_mw),
NULL
),
'cpu3', IIF(
cpu3_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu3_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu3_mw),
NULL
),
'cpu4', IIF(
cpu4_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu4_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu4_mw),
NULL
),
'cpu5', IIF(
cpu5_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu5_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu5_mw),
NULL
),
'cpu6', IIF(
cpu6_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu6_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu6_mw),
NULL
),
'cpu7', IIF(
cpu7_mw,
- AndroidWattsonCpuEstimate('estimate_mw', cpu7_mw),
+ AndroidWattsonCpuEstimate('estimated_mw', cpu7_mw),
NULL
)
) AS proto
@@ -304,21 +304,21 @@
period_id,
period_dur,
dsu_scu.dsu_scu_mw,
- IIF(p0.is_defined, p0.estimate_mw, NULL) as p0_mw,
+ IIF(p0.is_defined, p0.estimated_mw, NULL) as p0_mw,
IIF(p0.is_defined, p0.proto, NULL) as p0_proto,
- IIF(p1.is_defined, p1.estimate_mw, NULL) as p1_mw,
+ IIF(p1.is_defined, p1.estimated_mw, NULL) as p1_mw,
IIF(p1.is_defined, p1.proto, NULL) as p1_proto,
- IIF(p2.is_defined, p2.estimate_mw, NULL) as p2_mw,
+ IIF(p2.is_defined, p2.estimated_mw, NULL) as p2_mw,
IIF(p2.is_defined, p2.proto, NULL) as p2_proto,
- IIF(p3.is_defined, p3.estimate_mw, NULL) as p3_mw,
+ IIF(p3.is_defined, p3.estimated_mw, NULL) as p3_mw,
IIF(p3.is_defined, p3.proto, NULL) as p3_proto,
- IIF(p4.is_defined, p4.estimate_mw, NULL) as p4_mw,
+ IIF(p4.is_defined, p4.estimated_mw, NULL) as p4_mw,
IIF(p4.is_defined, p4.proto, NULL) as p4_proto,
- IIF(p5.is_defined, p5.estimate_mw, NULL) as p5_mw,
+ IIF(p5.is_defined, p5.estimated_mw, NULL) as p5_mw,
IIF(p5.is_defined, p5.proto, NULL) as p5_proto,
- IIF(p6.is_defined, p6.estimate_mw, NULL) as p6_mw,
+ IIF(p6.is_defined, p6.estimated_mw, NULL) as p6_mw,
IIF(p6.is_defined, p6.proto, NULL) as p6_proto,
- IIF(p7.is_defined, p7.estimate_mw, NULL) as p7_mw,
+ IIF(p7.is_defined, p7.estimated_mw, NULL) as p7_mw,
IIF(p7.is_defined, p7.proto, NULL) as p7_proto
FROM _estimate_policy0_proto AS p0
JOIN _estimate_policy1_proto AS p1 USING (period_id, period_dur)
@@ -344,7 +344,7 @@
period_id,
period_dur,
AndroidWattsonCpuSubsystemEstimate(
- 'estimate_mw', sum_mw,
+ 'estimated_mw', sum_mw,
'policy0', p0_proto,
'policy1', p1_proto,
'policy2', p2_proto,
@@ -353,7 +353,7 @@
'policy5', p5_proto,
'policy6', p6_proto,
'policy7', p7_proto,
- 'dsu_scu', AndroidWattsonDsuScuEstimate('estimate_mw', dsu_scu_mw)
+ 'dsu_scu', AndroidWattsonDsuScuEstimate('estimated_mw', dsu_scu_mw)
) as proto
FROM components_w_sum;
diff --git a/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql b/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql
index 8f98714..61353c0 100644
--- a/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_tasks_attribution.sql
@@ -25,39 +25,39 @@
-- "Unpivot" the table so that table can by PARTITIONED BY cpu
DROP TABLE IF EXISTS _unioned_windowed_wattson;
CREATE PERFETTO TABLE _unioned_windowed_wattson AS
- SELECT ts, dur, 0 as cpu, cpu0_mw as estimate_mw
+ SELECT ts, dur, 0 as cpu, cpu0_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 0 = cpu)
UNION ALL
- SELECT ts, dur, 1 as cpu, cpu1_mw as estimate_mw
+ SELECT ts, dur, 1 as cpu, cpu1_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 1 = cpu)
UNION ALL
- SELECT ts, dur, 2 as cpu, cpu2_mw as estimate_mw
+ SELECT ts, dur, 2 as cpu, cpu2_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 2 = cpu)
UNION ALL
- SELECT ts, dur, 3 as cpu, cpu3_mw as estimate_mw
+ SELECT ts, dur, 3 as cpu, cpu3_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 3 = cpu)
UNION ALL
- SELECT ts, dur, 4 as cpu, cpu4_mw as estimate_mw
+ SELECT ts, dur, 4 as cpu, cpu4_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 4 = cpu)
UNION ALL
- SELECT ts, dur, 5 as cpu, cpu5_mw as estimate_mw
+ SELECT ts, dur, 5 as cpu, cpu5_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 5 = cpu)
UNION ALL
- SELECT ts, dur, 6 as cpu, cpu6_mw as estimate_mw
+ SELECT ts, dur, 6 as cpu, cpu6_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 6 = cpu)
UNION ALL
- SELECT ts, dur, 7 as cpu, cpu7_mw as estimate_mw
+ SELECT ts, dur, 7 as cpu, cpu7_mw as estimated_mw
FROM _windowed_wattson
WHERE EXISTS (SELECT cpu FROM _dev_cpu_policy_map WHERE 7 = cpu)
UNION ALL
- SELECT ts, dur, -1 as cpu, dsu_scu_mw as estimate_mw
+ SELECT ts, dur, -1 as cpu, dsu_scu_mw as estimated_mw
FROM _windowed_wattson;
DROP TABLE IF EXISTS _windowed_threads_system_state;
diff --git a/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql b/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
index ab035c0..e5489c4 100644
--- a/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
@@ -33,7 +33,7 @@
DROP VIEW IF EXISTS wattson_trace_rails_output;
CREATE PERFETTO VIEW wattson_trace_rails_output AS
SELECT AndroidWattsonTimePeriodMetric(
- 'metric_version', 2,
+ 'metric_version', 3,
'period_info', (
SELECT RepeatedField(
AndroidWattsonEstimateInfo(
diff --git a/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql b/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql
index 455b876..bb83cab 100644
--- a/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_trace_threads.sql
@@ -36,27 +36,27 @@
CREATE PERFETTO VIEW _wattson_thread_attribution AS
SELECT
-- active time of thread divided by total time of trace
- SUM(estimate_mw * dur) / 1000000000 as estimate_mws,
+ SUM(estimated_mw * dur) / 1000000000 as estimated_mws,
(
- SUM(estimate_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
- ) as estimate_mw,
+ SUM(estimated_mw * dur) / (SELECT SUM(dur) from _windowed_wattson)
+ ) as estimated_mw,
thread_name,
process_name,
tid,
pid
FROM _windowed_threads_system_state
GROUP BY utid
-ORDER BY estimate_mw DESC;
+ORDER BY estimated_mw DESC;
DROP VIEW IF EXISTS wattson_trace_threads_output;
CREATE PERFETTO VIEW wattson_trace_threads_output AS
SELECT AndroidWattsonTasksAttributionMetric(
- 'metric_version', 1,
+ 'metric_version', 2,
'task_info', (
SELECT RepeatedField(
AndroidWattsonTaskInfo(
- 'estimate_mws', ROUND(estimate_mws, 6),
- 'estimate_mw', ROUND(estimate_mw, 6),
+ 'estimated_mws', ROUND(estimated_mws, 6),
+ 'estimated_mw', ROUND(estimated_mw, 6),
'thread_name', thread_name,
'process_name', process_name,
'thread_id', tid,
diff --git a/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql b/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql
index 702151b..276f7d7 100644
--- a/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql
@@ -68,7 +68,7 @@
-- significant fraction of the runtime on big traces.
IFNULL(
DEMANGLE(COALESCE(s.name, f.deobfuscated_name, f.name)),
- COALESCE(s.name, f.deobfuscated_name, f.name)
+ COALESCE(s.name, f.deobfuscated_name, f.name, '[Unknown]')
) AS name,
f.mapping AS mapping_id,
s.source_file,
@@ -112,3 +112,28 @@
JOIN _callstack_spc_forest f USING (id)
JOIN stack_profile_mapping m ON f.mapping_id = m.id
);
+
+CREATE PERFETTO MACRO _callstacks_for_cpu_profile_stack_samples(
+ samples TableOrSubquery
+)
+RETURNS TableOrSubquery
+AS
+(
+ WITH metrics AS MATERIALIZED (
+ SELECT
+ callsite_id,
+ COUNT() AS self_count
+ FROM $samples
+ GROUP BY callsite_id
+ )
+ SELECT
+ c.id,
+ c.parent_id,
+ c.name,
+ c.mapping_name,
+ c.source_file,
+ c.line_number,
+ IFNULL(m.self_count, 0) AS self_count
+ FROM _callstacks_for_stack_profile_samples!(metrics) c
+ LEFT JOIN metrics m USING (callsite_id)
+);
diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn
index 42654eb..60d462d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn
@@ -19,6 +19,7 @@
"frequency.sql",
"idle.sql",
"idle_stats.sql",
+ "idle_time_in_state.sql",
]
deps = [ "utilization" ]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql
new file mode 100644
index 0000000..8bff4b3
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle_time_in_state.sql
@@ -0,0 +1,74 @@
+--
+-- Copyright 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
+--
+-- https://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.
+INCLUDE PERFETTO MODULE time.conversion;
+
+-- Counter information for sysfs cpuidle states.
+-- Tracks the percentage of time spent in each state between two timestamps, by
+-- dividing the incremental time spent in one state, by time all CPUS spent in
+-- any state.
+CREATE PERFETTO TABLE cpu_idle_time_in_state_counters(
+ -- Timestamp.
+ ts LONG,
+ -- State name.
+ state_name STRING,
+ -- Percentage of time all CPUS spent in this state.
+ idle_percentage DOUBLE,
+ -- Incremental time spent in this state (residency), in microseconds.
+ total_residency DOUBLE,
+ -- Time all CPUS spent in any state, in microseconds.
+ time_slice INT
+) AS
+WITH residency_deltas AS (
+ SELECT
+ ts,
+ c.name as state_name,
+ value - (LAG(value) OVER (PARTITION BY c.name, cct.cpu ORDER BY ts)) as delta
+ FROM counters c
+ JOIN cpu_counter_track cct on c.track_id=cct.id
+ WHERE c.name GLOB 'cpuidle.*'
+),
+total_residency_calc AS (
+SELECT
+ ts,
+ state_name,
+ sum(delta) as total_residency,
+ -- Perfetto timestamp is in nanoseconds whereas sysfs cpuidle time
+ -- is in microseconds.
+ (
+ (SELECT count(distinct cpu) from cpu_counter_track) *
+ (time_to_us(ts - LAG(ts,1) over (partition by state_name order by ts)))
+ ) as time_slice
+ FROM residency_deltas
+GROUP BY ts, state_name
+)
+SELECT
+ ts,
+ state_name,
+ MIN(100, (total_residency / time_slice) * 100) as idle_percentage,
+ total_residency,
+ time_slice
+FROM total_residency_calc
+WHERE time_slice IS NOT NULL
+UNION ALL
+-- Calculate c0 state by subtracting all other states from total time.
+SELECT
+ ts,
+ 'cpuidle.C0' as state_name,
+ (MAX(0,time_slice - SUM(total_residency)) / time_slice) * 100 AS idle_percentage,
+ time_slice - SUM(total_residency),
+ time_slice
+FROM total_residency_calc
+WHERE time_slice IS NOT NULL
+GROUP BY ts;
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
index 7a1d5d0..c950484 100644
--- a/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
@@ -15,6 +15,20 @@
INCLUDE PERFETTO MODULE viz.summary.slices;
+CREATE PERFETTO TABLE _thread_track_summary_by_utid_and_name AS
+SELECT
+ utid,
+ name,
+ -- Only meaningful when track_count == 1.
+ id as track_id,
+ -- Only meaningful when track_count == 1.
+ max_depth as max_depth,
+ GROUP_CONCAT(id) AS track_ids,
+ COUNT() AS track_count
+FROM thread_track
+JOIN _slice_track_summary USING (id)
+GROUP BY utid, name;
+
CREATE PERFETTO TABLE _process_track_summary_by_upid_and_name AS
SELECT
upid,
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql
index 6188c01..abb146f 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/idle_attribution.sql
@@ -117,7 +117,7 @@
WHEN 6 THEN power.cpu6_mw
WHEN 7 THEN power.cpu7_mw
ELSE 0
- END estimate_mw
+ END estimated_mw
FROM _interval_intersect!(
(
_ii_table!(_idle_w_threads),
@@ -134,7 +134,7 @@
CREATE PERFETTO FUNCTION _filter_idle_attribution(ts LONG, dur LONG)
RETURNS Table(idle_cost_mws LONG, utid INT, upid INT, cpu INT) AS
SELECT
- cost.estimate_mw * cost.dur / 1e9 as idle_cost_mws,
+ cost.estimated_mw * cost.dur / 1e9 as idle_cost_mws,
cost.utid,
cost.upid,
cost.cpu
diff --git a/src/trace_processor/sorter/BUILD.gn b/src/trace_processor/sorter/BUILD.gn
index a429c30..22400e4 100644
--- a/src/trace_processor/sorter/BUILD.gn
+++ b/src/trace_processor/sorter/BUILD.gn
@@ -33,7 +33,9 @@
"../importers/common:parser_types",
"../importers/common:trace_parser_hdr",
"../importers/fuchsia:fuchsia_record",
+ "../importers/instruments:row",
"../importers/perf:record",
+ "../importers/proto:packet_sequence_state_generation_hdr",
"../importers/systrace:systrace_line",
"../storage",
"../types",
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index b684e1a..924660a 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -202,6 +202,10 @@
context.perf_record_parser->ParsePerfRecord(
event.ts, token_buffer_.Extract<perf_importer::Record>(id));
return;
+ case TimestampedEvent::Type::kInstrumentsRow:
+ context.instruments_row_parser->ParseInstrumentsRow(
+ event.ts, token_buffer_.Extract<instruments_importer::Row>(id));
+ return;
case TimestampedEvent::Type::kTracePacket:
context.proto_trace_parser->ParseTracePacket(
event.ts, token_buffer_.Extract<TracePacketData>(id));
@@ -226,6 +230,10 @@
context.android_log_event_parser->ParseAndroidLogEvent(
event.ts, token_buffer_.Extract<AndroidLogEvent>(id));
return;
+ case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
+ context.proto_trace_parser->ParseLegacyV8ProfileEvent(
+ event.ts, token_buffer_.Extract<LegacyV8CpuProfileEvent>(id));
+ return;
case TimestampedEvent::Type::kInlineSchedSwitch:
case TimestampedEvent::Type::kInlineSchedWaking:
case TimestampedEvent::Type::kEtwEvent:
@@ -251,9 +259,11 @@
case TimestampedEvent::Type::kSystraceLine:
case TimestampedEvent::Type::kTracePacket:
case TimestampedEvent::Type::kPerfRecord:
+ case TimestampedEvent::Type::kInstrumentsRow:
case TimestampedEvent::Type::kJsonValue:
case TimestampedEvent::Type::kFuchsiaRecord:
case TimestampedEvent::Type::kAndroidLogEvent:
+ case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
PERFETTO_FATAL("Invalid event type");
}
PERFETTO_FATAL("For GCC");
@@ -281,9 +291,11 @@
case TimestampedEvent::Type::kSystraceLine:
case TimestampedEvent::Type::kTracePacket:
case TimestampedEvent::Type::kPerfRecord:
+ case TimestampedEvent::Type::kInstrumentsRow:
case TimestampedEvent::Type::kJsonValue:
case TimestampedEvent::Type::kFuchsiaRecord:
case TimestampedEvent::Type::kAndroidLogEvent:
+ case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
PERFETTO_FATAL("Invalid event type");
}
PERFETTO_FATAL("For GCC");
@@ -319,9 +331,15 @@
case TimestampedEvent::Type::kPerfRecord:
base::ignore_result(token_buffer_.Extract<perf_importer::Record>(id));
return;
+ case TimestampedEvent::Type::kInstrumentsRow:
+ base::ignore_result(token_buffer_.Extract<instruments_importer::Row>(id));
+ return;
case TimestampedEvent::Type::kAndroidLogEvent:
base::ignore_result(token_buffer_.Extract<AndroidLogEvent>(id));
return;
+ case TimestampedEvent::Type::kLegacyV8CpuProfileEvent:
+ base::ignore_result(token_buffer_.Extract<LegacyV8CpuProfileEvent>(id));
+ return;
}
PERFETTO_FATAL("For GCC");
}
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index 656b283..183f376 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -38,6 +38,7 @@
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_record.h"
+#include "src/trace_processor/importers/instruments/row.h"
#include "src/trace_processor/importers/perf/record.h"
#include "src/trace_processor/importers/systrace/systrace_line.h"
#include "src/trace_processor/sorter/trace_token_buffer.h"
@@ -128,6 +129,15 @@
machine_id);
}
+ inline void PushInstrumentsRow(
+ int64_t timestamp,
+ instruments_importer::Row row,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ TraceTokenBuffer::Id id = token_buffer_.Append(std::move(row));
+ AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kInstrumentsRow, id,
+ machine_id);
+ }
+
inline void PushTracePacket(
int64_t timestamp,
TracePacketData data,
@@ -199,6 +209,21 @@
UpdateAppendMaxTs(queue);
}
+ inline void PushLegacyV8CpuProfileEvent(
+ int64_t timestamp,
+ uint64_t session_id,
+ uint32_t pid,
+ uint32_t tid,
+ uint32_t callsite_id,
+ std::optional<MachineId> machine_id = std::nullopt) {
+ TraceTokenBuffer::Id id = token_buffer_.Append(
+ LegacyV8CpuProfileEvent{session_id, pid, tid, callsite_id});
+ auto* queue = GetQueue(0, machine_id);
+ queue->Append(timestamp, TimestampedEvent::Type::kLegacyV8CpuProfileEvent,
+ id);
+ UpdateAppendMaxTs(queue);
+ }
+
inline void PushInlineFtraceEvent(
uint32_t cpu,
int64_t timestamp,
@@ -264,6 +289,7 @@
enum class Type : uint8_t {
kFtraceEvent,
kPerfRecord,
+ kInstrumentsRow,
kTracePacket,
kInlineSchedSwitch,
kInlineSchedWaking,
@@ -273,7 +299,8 @@
kSystraceLine,
kEtwEvent,
kAndroidLogEvent,
- kMax = kAndroidLogEvent,
+ kLegacyV8CpuProfileEvent,
+ kMax = kLegacyV8CpuProfileEvent,
};
// Number of bits required to store the max element in |Type|.
diff --git a/src/trace_processor/sorter/trace_token_buffer.cc b/src/trace_processor/sorter/trace_token_buffer.cc
index 7541a4d..ab8b15d 100644
--- a/src/trace_processor/sorter/trace_token_buffer.cc
+++ b/src/trace_processor/sorter/trace_token_buffer.cc
@@ -16,24 +16,24 @@
#include "src/trace_processor/sorter/trace_token_buffer.h"
-#include <stdint.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <functional>
#include <limits>
#include <optional>
-#include <type_traits>
#include <utility>
#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/ref_counted.h"
#include "perfetto/trace_processor/trace_blob.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
#include "src/trace_processor/util/bump_allocator.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
struct alignas(8) TrackEventDataDescriptor {
@@ -111,7 +111,7 @@
InternedIndex interned_index = GetInternedIndex(alloc_id);
// Compute the interning information for the TrackBlob and the SequenceState.
- const TracePacketData& tpd = ted.trace_packet_data;
+ TracePacketData& tpd = ted.trace_packet_data;
desc.intern_blob_offset = InternTraceBlob(interned_index, tpd.packet);
desc.intern_blob_index =
static_cast<uint16_t>(interned_blobs_.at(interned_index).size() - 1);
@@ -280,5 +280,4 @@
return static_cast<size_t>(interned_index);
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index bddd78d..8838b4f 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -17,11 +17,9 @@
#ifndef SRC_TRACE_PROCESSOR_STORAGE_STATS_H_
#define SRC_TRACE_PROCESSOR_STORAGE_STATS_H_
-#include <stddef.h>
+#include <cstddef>
-namespace perfetto {
-namespace trace_processor {
-namespace stats {
+namespace perfetto::trace_processor::stats {
// Compile time list of parsing and processing stats.
// clang-format off
@@ -381,7 +379,12 @@
F(mali_unknown_mcu_state_id, kSingle, kError, kAnalysis, \
"An invalid Mali GPU MCU state ID was detected."), \
F(pixel_modem_negative_timestamp, kSingle, kError, kAnalysis, \
- "A negative timestamp was received from a Pixel modem event.")
+ "A negative timestamp was received from a Pixel modem event."), \
+ F(legacy_v8_cpu_profile_invalid_callsite, kSingle, kInfo, kAnalysis, \
+ "Indicates a callsite in legacy v8 CPU profiling is invalid."), \
+ F(legacy_v8_cpu_profile_invalid_sample, kSingle, kError, kAnalysis, \
+ "Indicates a sample in legacy v8 CPU profile is invalid. This will " \
+ "cause CPU samples to be missing in the UI.")
// clang-format on
enum Type {
@@ -435,8 +438,6 @@
constexpr char const* kDescriptions[] = {
PERFETTO_TP_STATS(PERFETTO_TP_STATS_DESCRIPTION)};
-} // namespace stats
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor::stats
#endif // SRC_TRACE_PROCESSOR_STORAGE_STATS_H_
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index b005f44..2a07c33 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -637,13 +637,6 @@
return &trace_file_table_;
}
- const tables::StackSampleTable& stack_sample_table() const {
- return stack_sample_table_;
- }
- tables::StackSampleTable* mutable_stack_sample_table() {
- return &stack_sample_table_;
- }
-
const tables::CpuProfileStackSampleTable& cpu_profile_stack_sample_table()
const {
return cpu_profile_stack_sample_table_;
@@ -666,6 +659,13 @@
return &perf_sample_table_;
}
+ const tables::InstrumentsSampleTable& instruments_sample_table() const {
+ return instruments_sample_table_;
+ }
+ tables::InstrumentsSampleTable* mutable_instruments_sample_table() {
+ return &instruments_sample_table_;
+ }
+
const tables::SymbolTable& symbol_table() const { return symbol_table_; }
tables::SymbolTable* mutable_symbol_table() { return &symbol_table_; }
@@ -1152,13 +1152,13 @@
tables::StackProfileFrameTable stack_profile_frame_table_{&string_pool_};
tables::StackProfileCallsiteTable stack_profile_callsite_table_{
&string_pool_};
- tables::StackSampleTable stack_sample_table_{&string_pool_};
tables::HeapProfileAllocationTable heap_profile_allocation_table_{
&string_pool_};
tables::CpuProfileStackSampleTable cpu_profile_stack_sample_table_{
- &string_pool_, &stack_sample_table_};
+ &string_pool_};
tables::PerfSessionTable perf_session_table_{&string_pool_};
tables::PerfSampleTable perf_sample_table_{&string_pool_};
+ tables::InstrumentsSampleTable instruments_sample_table_{&string_pool_};
tables::PackageListTable package_list_table_{&string_pool_};
tables::AndroidGameInterventionListTable
android_game_intervention_list_table_{&string_pool_};
diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py
index 2c55640..60cb1fc 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -209,39 +209,22 @@
'''Frame at this position in the callstack.'''
}))
-STACK_SAMPLE_TABLE = Table(
- python_module=__file__,
- class_name='StackSampleTable',
- sql_name='stack_sample',
- columns=[
- C('ts', CppInt64(), flags=ColumnFlag.SORTED),
- C('callsite_id', CppTableId(STACK_PROFILE_CALLSITE_TABLE)),
- ],
- tabledoc=TableDoc(
- doc='''
- Root table for timestamped stack samples.
- ''',
- group='Callstack profilers',
- columns={
- 'ts': '''timestamp of the sample.''',
- 'callsite_id': '''unwound callstack.'''
- }))
-
CPU_PROFILE_STACK_SAMPLE_TABLE = Table(
python_module=__file__,
class_name='CpuProfileStackSampleTable',
sql_name='cpu_profile_stack_sample',
columns=[
+ C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+ C('callsite_id', CppTableId(STACK_PROFILE_CALLSITE_TABLE)),
C('utid', CppUint32()),
C('process_priority', CppInt32()),
],
- parent=STACK_SAMPLE_TABLE,
tabledoc=TableDoc(
- doc='''
- Samples from the Chromium stack sampler.
- ''',
+ doc='Table containing stack samples from CPU profiling.',
group='Callstack profilers',
columns={
+ 'ts': '''timestamp of the sample.''',
+ 'callsite_id': '''unwound callstack.''',
'utid': '''thread that was active when the sample was taken.''',
'process_priority': ''''''
}))
@@ -254,9 +237,7 @@
C('cmdline', CppOptional(CppString())),
],
tabledoc=TableDoc(
- doc='''
- Perf sessions.
- ''',
+ doc='''Perf sessions.''',
group='Callstack profilers',
columns={
'cmdline': '''Command line used to collect the data.''',
@@ -276,9 +257,7 @@
C('perf_session_id', CppTableId(PERF_SESSION_TABLE)),
],
tabledoc=TableDoc(
- doc='''
- Samples from the traced_perf profiler.
- ''',
+ doc='''Samples from the traced_perf profiler.''',
group='Callstack profilers',
columns={
'ts':
@@ -302,6 +281,32 @@
streams (i.e. multiple data sources).'''
}))
+INSTRUMENTS_SAMPLE_TABLE = Table(
+ python_module=__file__,
+ class_name='InstrumentsSampleTable',
+ sql_name='instruments_sample',
+ columns=[
+ C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+ C('utid', CppUint32()),
+ C('cpu', CppOptional(CppUint32())),
+ C('callsite_id', CppOptional(CppTableId(STACK_PROFILE_CALLSITE_TABLE))),
+ ],
+ tabledoc=TableDoc(
+ doc='''
+ Samples from MacOS Instruments.
+ ''',
+ group='Callstack profilers',
+ columns={
+ 'ts':
+ '''Timestamp of the sample.''',
+ 'utid':
+ '''Sampled thread.''',
+ 'cpu':
+ '''Core the sampled thread was running on.''',
+ 'callsite_id':
+ '''If set, unwound callstack of the sampled thread.''',
+ }))
+
SYMBOL_TABLE = Table(
python_module=__file__,
class_name='SymbolTable',
@@ -659,6 +664,7 @@
HEAP_GRAPH_CLASS_TABLE,
HEAP_GRAPH_OBJECT_TABLE,
HEAP_GRAPH_REFERENCE_TABLE,
+ INSTRUMENTS_SAMPLE_TABLE,
HEAP_PROFILE_ALLOCATION_TABLE,
PACKAGE_LIST_TABLE,
PERF_SAMPLE_TABLE,
@@ -667,7 +673,6 @@
STACK_PROFILE_CALLSITE_TABLE,
STACK_PROFILE_FRAME_TABLE,
STACK_PROFILE_MAPPING_TABLE,
- STACK_SAMPLE_TABLE,
SYMBOL_TABLE,
VULKAN_MEMORY_ALLOCATIONS_TABLE,
PERF_COUNTER_TRACK_TABLE,
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 229c724..f993a51 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -70,10 +70,10 @@
StackProfileMappingTable::~StackProfileMappingTable() = default;
StackProfileFrameTable::~StackProfileFrameTable() = default;
StackProfileCallsiteTable::~StackProfileCallsiteTable() = default;
-StackSampleTable::~StackSampleTable() = default;
CpuProfileStackSampleTable::~CpuProfileStackSampleTable() = default;
PerfSessionTable::~PerfSessionTable() = default;
PerfSampleTable::~PerfSampleTable() = default;
+InstrumentsSampleTable::~InstrumentsSampleTable() = default;
SymbolTable::~SymbolTable() = default;
HeapProfileAllocationTable::~HeapProfileAllocationTable() = default;
ExperimentalFlamegraphTable::~ExperimentalFlamegraphTable() = default;
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 0da4693..53876b0 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -15,21 +15,22 @@
*/
#include "src/trace_processor/types/trace_processor_context.h"
+
#include <memory>
#include <optional>
+#include "perfetto/base/logging.h"
#include "src/trace_processor/forwarding_trace_parser.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/args_translation_table.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
-#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/importers/common/clock_converter.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/cpu_tracker.h"
-#include "src/trace_processor/importers/common/deobfuscation_mapping_table.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.h"
#include "src/trace_processor/importers/common/machine_tracker.h"
#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
@@ -41,20 +42,16 @@
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
#include "src/trace_processor/importers/common/trace_file_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
-#include "src/trace_processor/importers/ftrace/ftrace_module.h"
#include "src/trace_processor/importers/proto/android_track_event.descriptor.h"
#include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h"
#include "src/trace_processor/importers/proto/multi_machine_trace_manager.h"
#include "src/trace_processor/importers/proto/perf_sample_tracker.h"
#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/importers/proto/track_event.descriptor.h"
-#include "src/trace_processor/importers/proto/track_event_module.h"
-#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/trace_reader_registry.h"
-#include "src/trace_processor/types/destructible.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
TraceProcessorContext::TraceProcessorContext(const InitArgs& args)
: config(args.config), storage(args.storage) {
@@ -109,11 +106,17 @@
});
trace_file_tracker = std::make_unique<TraceFileTracker>(this);
+ legacy_v8_cpu_profile_tracker =
+ std::make_unique<LegacyV8CpuProfileTracker>(this);
}
TraceProcessorContext::TraceProcessorContext() = default;
TraceProcessorContext::~TraceProcessorContext() = default;
+TraceProcessorContext::TraceProcessorContext(TraceProcessorContext&&) = default;
+TraceProcessorContext& TraceProcessorContext::operator=(
+ TraceProcessorContext&&) = default;
+
std::optional<MachineId> TraceProcessorContext::machine_id() const {
if (!machine_tracker) {
// Doesn't require that |machine_tracker| is initialzed, e.g. in unit tests.
@@ -122,5 +125,4 @@
return machine_tracker->machine_id();
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9f101bd..3a8dac5 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -51,6 +51,8 @@
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h"
#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
+#include "src/trace_processor/importers/instruments/instruments_xml_tokenizer.h"
+#include "src/trace_processor/importers/instruments/row_parser.h"
#include "src/trace_processor/importers/json/json_trace_parser_impl.h"
#include "src/trace_processor/importers/json/json_trace_tokenizer.h"
#include "src/trace_processor/importers/json/json_utils.h"
@@ -355,6 +357,10 @@
start_ns = std::min(it.ts(), start_ns);
end_ns = std::max(it.ts(), end_ns);
}
+ for (auto it = storage.instruments_sample_table().IterateRows(); it; ++it) {
+ start_ns = std::min(it.ts(), start_ns);
+ end_ns = std::max(it.ts(), end_ns);
+ }
for (auto it = storage.cpu_profile_stack_sample_table().IterateRows(); it;
++it) {
start_ns = std::min(it.ts(), start_ns);
@@ -394,6 +400,12 @@
context_.perf_record_parser =
std::make_unique<perf_importer::RecordParser>(&context_);
+ context_.reader_registry
+ ->RegisterTraceReader<instruments_importer::InstrumentsXmlTokenizer>(
+ kInstrumentsXmlTraceType);
+ context_.instruments_row_parser =
+ std::make_unique<instruments_importer::RowParser>(&context_);
+
if (util::IsGzipSupported()) {
context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
kGzipTraceType);
@@ -907,6 +919,7 @@
RegisterStaticTable(storage->mutable_cpu_profile_stack_sample_table());
RegisterStaticTable(storage->mutable_perf_session_table());
RegisterStaticTable(storage->mutable_perf_sample_table());
+ RegisterStaticTable(storage->mutable_instruments_sample_table());
RegisterStaticTable(storage->mutable_stack_profile_callsite_table());
RegisterStaticTable(storage->mutable_stack_profile_mapping_table());
RegisterStaticTable(storage->mutable_stack_profile_frame_table());
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 7478dd9..8325d87 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -22,38 +22,35 @@
#include <memory>
#include <utility>
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/uuid.h"
+#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/forwarding_trace_parser.h"
#include "src/trace_processor/importers/common/args_tracker.h"
-#include "src/trace_processor/importers/common/args_translation_table.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
-#include "src/trace_processor/importers/common/clock_converter.h"
+#include "src/trace_processor/importers/common/clock_converter.h" // IWYU pragma: keep
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/flow_tracker.h"
-#include "src/trace_processor/importers/common/machine_tracker.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
-#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/sched_event_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
-#include "src/trace_processor/importers/common/slice_translation_table.h"
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
#include "src/trace_processor/importers/common/trace_file_tracker.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/perf/dso_tracker.h"
-#include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h"
#include "src/trace_processor/importers/proto/default_modules.h"
#include "src/trace_processor/importers/proto/packet_analyzer.h"
#include "src/trace_processor/importers/proto/perf_sample_tracker.h"
#include "src/trace_processor/importers/proto/proto_importer_module.h"
#include "src/trace_processor/importers/proto/proto_trace_parser_impl.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
-#include "src/trace_processor/importers/proto/track_event.descriptor.h"
-#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/trace_reader_registry.h"
-#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/status_macros.h"
#include "src/trace_processor/util/trace_type.h"
namespace perfetto::trace_processor {
diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc
index b071295..dcecaf9 100644
--- a/src/trace_processor/trace_reader_registry.cc
+++ b/src/trace_processor/trace_reader_registry.cc
@@ -37,6 +37,7 @@
case kNinjaLogTraceType:
case kSystraceTraceType:
case kPerfDataTraceType:
+ case kInstrumentsXmlTraceType:
case kUnknownTraceType:
case kJsonTraceType:
case kFuchsiaTraceType:
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 909959c..3e7ba55 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -17,6 +17,7 @@
#ifndef SRC_TRACE_PROCESSOR_TYPES_TRACE_PROCESSOR_CONTEXT_H_
#define SRC_TRACE_PROCESSOR_TYPES_TRACE_PROCESSOR_CONTEXT_H_
+#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
@@ -24,10 +25,8 @@
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/types/destructible.h"
-#include "src/trace_processor/util/trace_type.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class AndroidLogEventParser;
class ArgsTracker;
@@ -47,7 +46,9 @@
class FuchsiaRecordParser;
class GlobalArgsTracker;
class HeapGraphTracker;
+class InstrumentsRowParser;
class JsonTraceParser;
+class LegacyV8CpuProfileTracker;
class MachineTracker;
class MappingTracker;
class MetadataTracker;
@@ -79,13 +80,15 @@
std::shared_ptr<TraceStorage> storage;
uint32_t raw_machine_id = 0;
};
+
explicit TraceProcessorContext(const InitArgs&);
+
// The default constructor is used in testing.
TraceProcessorContext();
~TraceProcessorContext();
- TraceProcessorContext(TraceProcessorContext&&) = default;
- TraceProcessorContext& operator=(TraceProcessorContext&&) = default;
+ TraceProcessorContext(TraceProcessorContext&&);
+ TraceProcessorContext& operator=(TraceProcessorContext&&);
Config config;
@@ -128,6 +131,7 @@
std::unique_ptr<MetadataTracker> metadata_tracker;
std::unique_ptr<CpuTracker> cpu_tracker;
std::unique_ptr<TraceFileTracker> trace_file_tracker;
+ std::unique_ptr<LegacyV8CpuProfileTracker> legacy_v8_cpu_profile_tracker;
// These fields are stored as pointers to Destructible objects rather than
// their actual type (a subclass of Destructible), as the concrete subclass
@@ -135,25 +139,26 @@
// the GetOrCreate() method on their subclass type, e.g.
// SyscallTracker::GetOrCreate(context)
// clang-format off
- std::unique_ptr<Destructible> android_probes_tracker; // AndroidProbesTracker
- std::unique_ptr<Destructible> binder_tracker; // BinderTracker
- std::unique_ptr<Destructible> heap_graph_tracker; // HeapGraphTracker
- std::unique_ptr<Destructible> syscall_tracker; // SyscallTracker
- std::unique_ptr<Destructible> system_info_tracker; // SystemInfoTracker
- std::unique_ptr<Destructible> v4l2_tracker; // V4l2Tracker
- std::unique_ptr<Destructible> virtio_video_tracker; // VirtioVideoTracker
- std::unique_ptr<Destructible> systrace_parser; // SystraceParser
- std::unique_ptr<Destructible> thread_state_tracker; // ThreadStateTracker
- std::unique_ptr<Destructible> i2c_tracker; // I2CTracker
- std::unique_ptr<Destructible> perf_data_tracker; // PerfDataTracker
- std::unique_ptr<Destructible> content_analyzer; // ProtoContentAnalyzer
- std::unique_ptr<Destructible> shell_transitions_tracker; // ShellTransitionsTracker
- std::unique_ptr<Destructible> protolog_messages_tracker; // ProtoLogMessagesTracker
- std::unique_ptr<Destructible> ftrace_sched_tracker; // FtraceSchedEventTracker
- std::unique_ptr<Destructible> v8_tracker; // V8Tracker
- std::unique_ptr<Destructible> jit_tracker; // JitTracker
- std::unique_ptr<Destructible> perf_dso_tracker; // DsoTracker
- std::unique_ptr<Destructible> protolog_message_decoder; // ProtoLogMessageDecoder
+ std::unique_ptr<Destructible> android_probes_tracker; // AndroidProbesTracker
+ std::unique_ptr<Destructible> binder_tracker; // BinderTracker
+ std::unique_ptr<Destructible> heap_graph_tracker; // HeapGraphTracker
+ std::unique_ptr<Destructible> syscall_tracker; // SyscallTracker
+ std::unique_ptr<Destructible> system_info_tracker; // SystemInfoTracker
+ std::unique_ptr<Destructible> v4l2_tracker; // V4l2Tracker
+ std::unique_ptr<Destructible> virtio_video_tracker; // VirtioVideoTracker
+ std::unique_ptr<Destructible> systrace_parser; // SystraceParser
+ std::unique_ptr<Destructible> thread_state_tracker; // ThreadStateTracker
+ std::unique_ptr<Destructible> i2c_tracker; // I2CTracker
+ std::unique_ptr<Destructible> perf_data_tracker; // PerfDataTracker
+ std::unique_ptr<Destructible> content_analyzer; // ProtoContentAnalyzer
+ std::unique_ptr<Destructible> shell_transitions_tracker; // ShellTransitionsTracker
+ std::unique_ptr<Destructible> protolog_messages_tracker; // ProtoLogMessagesTracker
+ std::unique_ptr<Destructible> ftrace_sched_tracker; // FtraceSchedEventTracker
+ std::unique_ptr<Destructible> v8_tracker; // V8Tracker
+ std::unique_ptr<Destructible> jit_tracker; // JitTracker
+ std::unique_ptr<Destructible> perf_dso_tracker; // DsoTracker
+ std::unique_ptr<Destructible> protolog_message_decoder; // ProtoLogMessageDecoder
+ std::unique_ptr<Destructible> instruments_row_data_tracker; // RowDataTracker
// clang-format on
std::unique_ptr<ProtoTraceParser> proto_trace_parser;
@@ -164,6 +169,7 @@
std::unique_ptr<JsonTraceParser> json_trace_parser;
std::unique_ptr<FuchsiaRecordParser> fuchsia_record_parser;
std::unique_ptr<PerfRecordParser> perf_record_parser;
+ std::unique_ptr<InstrumentsRowParser> instruments_row_parser;
std::unique_ptr<AndroidLogEventParser> android_log_event_parser;
// This field contains the list of proto descriptors that can be used by
@@ -192,7 +198,6 @@
std::unique_ptr<MultiMachineTraceManager> multi_machine_trace_manager;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_TYPES_TRACE_PROCESSOR_CONTEXT_H_
diff --git a/src/trace_processor/util/build_id.cc b/src/trace_processor/util/build_id.cc
index 20d76dd..d037e0e 100644
--- a/src/trace_processor/util/build_id.cc
+++ b/src/trace_processor/util/build_id.cc
@@ -83,6 +83,10 @@
}
while (it != hex.end()) {
+ if (*it == '-') {
+ ++it;
+ continue;
+ }
int v = (HexToBinary(*it++) << 4);
v += HexToBinary(*it++);
res.push_back(static_cast<char>(v));
diff --git a/src/trace_processor/util/debug_annotation_parser.cc b/src/trace_processor/util/debug_annotation_parser.cc
index 0c2792e..6669b4d 100644
--- a/src/trace_processor/util/debug_annotation_parser.cc
+++ b/src/trace_processor/util/debug_annotation_parser.cc
@@ -36,7 +36,7 @@
return result;
}
-bool IsJsonSupported() {
+constexpr bool IsJsonSupported() {
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
return true;
#else
diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc
index 4c4b042..3c64c3f 100644
--- a/src/trace_processor/util/trace_type.cc
+++ b/src/trace_processor/util/trace_type.cc
@@ -119,6 +119,8 @@
return "zip";
case kPerfDataTraceType:
return "perf";
+ case kInstrumentsXmlTraceType:
+ return "instruments_xml";
case kAndroidLogcatTraceType:
return "android_logcat";
case kAndroidDumpstateTraceType:
@@ -172,6 +174,10 @@
base::StartsWith(lower_start, "<html>"))
return kSystraceTraceType;
+ // MacOS Instruments XML export.
+ if (base::StartsWith(start, "<?xml version=\"1.0\"?>\n<trace-query-result>"))
+ return kInstrumentsXmlTraceType;
+
// Traces obtained from atrace -z (compress).
// They all have the string "TRACE:" followed by 78 9C which is a zlib header
// for "deflate, default compression, window size=32K" (see b/208691037)
diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h
index 7e730fc..dbf72ea 100644
--- a/src/trace_processor/util/trace_type.h
+++ b/src/trace_processor/util/trace_type.h
@@ -37,6 +37,7 @@
kSystraceTraceType,
kUnknownTraceType,
kZipFile,
+ kInstrumentsXmlTraceType,
};
constexpr size_t kGuessTraceMaxLookahead = 64;
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 4bc6a0c..247b41d 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -8227,19 +8227,19 @@
"new_pid", 2, ProtoSchemaType::kInt32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "cctr", 3, ProtoSchemaType::kUint32,
+ "cctr", 3, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr0", 4, ProtoSchemaType::kUint32,
+ "ctr0", 4, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr1", 5, ProtoSchemaType::kUint32,
+ "ctr1", 5, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr2", 6, ProtoSchemaType::kUint32,
+ "ctr2", 6, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr3", 7, ProtoSchemaType::kUint32,
+ "ctr3", 7, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
"lctr0", 8, ProtoSchemaType::kUint32,
@@ -8248,10 +8248,10 @@
"lctr1", 9, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr4", 10, ProtoSchemaType::kUint32,
+ "ctr4", 10, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr5", 11, ProtoSchemaType::kUint32,
+ "ctr5", 11, ProtoSchemaType::kUint64,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
"prev_comm", 12, ProtoSchemaType::kString,
@@ -8271,10 +8271,51 @@
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
"l3dm", 17, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "next_pid", 18, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "next_comm", 19, ProtoSchemaType::kString,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "prev_state", 20, ProtoSchemaType::kInt64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "amu0", 21, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "amu1", 22, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "amu2", 23, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
},
kUnsetFtraceId,
487,
kUnsetSize},
+ {"pixel_mm_kswapd_wake",
+ "pixel_mm",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "whatever", 1, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 538,
+ kUnsetSize},
+ {"pixel_mm_kswapd_done",
+ "pixel_mm",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "delta_nr_scanned", 1, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "delta_nr_reclaimed", 2, ProtoSchemaType::kUint64,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 539,
+ kUnsetSize},
{"cpu_frequency",
"power",
{
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/pixel_mm/pixel_mm_kswapd_done/format b/src/traced/probes/ftrace/test/data/synthetic/events/pixel_mm/pixel_mm_kswapd_done/format
new file mode 100644
index 0000000..8a06a55
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/pixel_mm/pixel_mm_kswapd_done/format
@@ -0,0 +1,12 @@
+name: pixel_mm_kswapd_done
+ID: 1092
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:unsigned long delta_nr_scanned; offset:8; size:8; signed:0;
+ field:unsigned long delta_nr_reclaimed; offset:16; size:8; signed:0;
+
+print fmt: "delta_nr_scanned=%lu, delta_nr_reclaimed=%lu", REC->delta_nr_scanned, REC->delta_nr_reclaimed
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/pixel_mm/pixel_mm_kswapd_wake/format b/src/traced/probes/ftrace/test/data/synthetic/events/pixel_mm/pixel_mm_kswapd_wake/format
new file mode 100644
index 0000000..6697281
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/pixel_mm/pixel_mm_kswapd_wake/format
@@ -0,0 +1,11 @@
+name: pixel_mm_kswapd_wake
+ID: 1091
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int whatever; offset:8; size:4; signed:1;
+
+print fmt: "%s", ""
diff --git a/src/traced/probes/ftrace/test/data/synthetic_alt/events/perf_trace_counters/sched_switch_with_ctrs/format b/src/traced/probes/ftrace/test/data/synthetic_alt/events/perf_trace_counters/sched_switch_with_ctrs/format
new file mode 100644
index 0000000..1e27eaf
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic_alt/events/perf_trace_counters/sched_switch_with_ctrs/format
@@ -0,0 +1,25 @@
+name: sched_switch_with_ctrs
+ID: 1241
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:pid_t prev_pid; offset:8; size:4; signed:1;
+ field:pid_t next_pid; offset:12; size:4; signed:1;
+ field:char prev_comm[16]; offset:16; size:16; signed:0;
+ field:char next_comm[16]; offset:32; size:16; signed:0;
+ field:long prev_state; offset:48; size:8; signed:1;
+ field:unsigned long cctr; offset:56; size:8; signed:0;
+ field:unsigned long ctr0; offset:64; size:8; signed:0;
+ field:unsigned long ctr1; offset:72; size:8; signed:0;
+ field:unsigned long ctr2; offset:80; size:8; signed:0;
+ field:unsigned long ctr3; offset:88; size:8; signed:0;
+ field:unsigned long ctr4; offset:96; size:8; signed:0;
+ field:unsigned long ctr5; offset:104; size:8; signed:0;
+ field:unsigned long amu0; offset:112; size:8; signed:0;
+ field:unsigned long amu1; offset:120; size:8; signed:0;
+ field:unsigned long amu2; offset:128; size:8; signed:0;
+
+print fmt: "prev_comm=%s prev_pid=%d prev_state=%s%s ==> next_comm=%s next_pid=%d CCNTR=%lu CTR0=%lu CTR1=%lu CTR2=%lu CTR3=%lu CTR4=%lu CTR5=%lu, CYC: %lu, INST: %lu, STALL: %lu", REC->prev_comm, REC->prev_pid, (REC->prev_state & ((((0x00000000 | 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010 | 0x00000020 | 0x00000040) + 1) << 1) - 1)) ? __print_flags(REC->prev_state & ((((0x00000000 | 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010 | 0x00000020 | 0x00000040) + 1) << 1) - 1), "|", { 0x00000001, "S" }, { 0x00000002, "D" }, { 0x00000004, "T" }, { 0x00000008, "t" }, { 0x00000010, "X" }, { 0x00000020, "Z" }, { 0x00000040, "P" }, { 0x00000080, "I" }) : "R", REC->prev_state & (((0x00000000 | 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010 | 0x00000020 | 0x00000040) + 1) << 1) ? "+" : "", REC->next_comm, REC->next_pid, REC->cctr, REC->ctr0, REC->ctr1, REC->ctr2, REC->ctr3, REC->ctr4, REC->ctr5, REC->amu0, REC->amu1, REC->amu2
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 4ad7f44..6658512 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -30,6 +30,9 @@
#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_MAC)
+#include <os/signpost.h>
+#endif
using perfetto::protos::pbzero::ClockSnapshot;
@@ -420,6 +423,18 @@
thread_time_counter_track.uuid);
}
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_MAC)
+ // Emit a MacOS point-of-interest signpost to synchonize Mac profiler time
+ // with boot time.
+ // TODO(leszeks): Consider allowing synchronization against other clocks
+ // than boot time.
+ static os_log_t log_handle = os_log_create(
+ "dev.perfetto.clock_sync", OS_LOG_CATEGORY_POINTS_OF_INTEREST);
+ os_signpost_event_emit(
+ log_handle, OS_SIGNPOST_ID_EXCLUSIVE, "boottime", "%" PRId64,
+ static_cast<uint64_t>(perfetto::base::GetBootTimeNs().count()));
+#endif
+
if (tls_state.default_clock != static_cast<uint32_t>(GetClockId())) {
ClockSnapshot* clocks = packet->set_clock_snapshot();
// Trace clock.
diff --git a/test/data/instruments_trace.xml.sha256 b/test/data/instruments_trace.xml.sha256
new file mode 100644
index 0000000..f524f24
--- /dev/null
+++ b/test/data/instruments_trace.xml.sha256
@@ -0,0 +1 @@
+1f87b2e3f5617f947c3c22fe2282e3341ed4d3b4f37ad2e6752c6dd54836db8a
\ No newline at end of file
diff --git a/test/data/instruments_trace_symbols.pb.sha256 b/test/data/instruments_trace_symbols.pb.sha256
new file mode 100644
index 0000000..8e33c19
--- /dev/null
+++ b/test/data/instruments_trace_symbols.pb.sha256
@@ -0,0 +1 @@
+1f5096a97bd9b7176b1ec52863d02e3e85e19eedbfbbe51cbbecedce8a0248cb
\ No newline at end of file
diff --git a/test/data/instruments_trace_with_symbols.zip.sha256 b/test/data/instruments_trace_with_symbols.zip.sha256
new file mode 100644
index 0000000..adbf7aa
--- /dev/null
+++ b/test/data/instruments_trace_with_symbols.zip.sha256
@@ -0,0 +1 @@
+70733124cf53b8065512204d9a72c7818ebd04afb10cf3c69cc926a2aa5ee07e
\ No newline at end of file
diff --git a/test/data/v8-samples.pftrace.sha256 b/test/data/v8-samples.pftrace.sha256
new file mode 100644
index 0000000..c9a7d2f
--- /dev/null
+++ b/test/data/v8-samples.pftrace.sha256
@@ -0,0 +1 @@
+564159912db8d8252562f143e6206ae9964759e16193ebd3addd04823d02a6ae
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 5840800..1f39362 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -72,6 +72,7 @@
from diff_tests.parser.graphics.tests import GraphicsParser
from diff_tests.parser.graphics.tests_drm_related_ftrace_events import GraphicsDrmRelatedFtraceEvents
from diff_tests.parser.graphics.tests_gpu_trace import GraphicsGpuTrace
+from diff_tests.parser.instruments.tests import Instruments
from diff_tests.parser.json.tests import JsonParser
from diff_tests.parser.memory.tests import MemoryParser
from diff_tests.parser.network.tests import NetworkParser
@@ -238,6 +239,7 @@
*Zip(index_path, 'parser/zip', 'Zip').fetch(),
*AndroidInputEvent(index_path, 'parser/android',
'AndroidInputEvent').fetch(),
+ *Instruments(index_path, 'parser/instruments', 'Instruments').fetch(),
]
metrics_tests = [
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index ff3de29..347f95f 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -375,47 +375,47 @@
query=Metric("wattson_app_startup_rails"),
out=Csv("""
wattson_app_startup_rails {
- metric_version: 2
+ metric_version: 3
period_info {
period_id: 1
period_dur: 384847255
cpu_subsystem {
- estimate_mw: 4568.1772
+ estimated_mw: 4568.1772
policy0 {
- estimate_mw: 578.31256
+ estimated_mw: 578.31256
cpu0 {
- estimate_mw: 148.99423
+ estimated_mw: 148.99423
}
cpu1 {
- estimate_mw: 130.13142
+ estimated_mw: 130.13142
}
cpu2 {
- estimate_mw: 127.60357
+ estimated_mw: 127.60357
}
cpu3 {
- estimate_mw: 171.58333
+ estimated_mw: 171.58333
}
}
policy4 {
- estimate_mw: 684.18835
+ estimated_mw: 684.18835
cpu4 {
- estimate_mw: 344.39563
+ estimated_mw: 344.39563
}
cpu5 {
- estimate_mw: 339.7927
+ estimated_mw: 339.7927
}
}
policy6 {
- estimate_mw: 2163.158
+ estimated_mw: 2163.158
cpu6 {
- estimate_mw: 1080.6881
+ estimated_mw: 1080.6881
}
cpu7 {
- estimate_mw: 1082.47
+ estimated_mw: 1082.47
}
}
dsu_scu {
- estimate_mw: 1142.5181
+ estimated_mw: 1142.5181
}
}
}
@@ -428,29 +428,29 @@
query=Metric("wattson_trace_rails"),
out=Csv("""
wattson_trace_rails {
- metric_version: 2
+ metric_version: 3
period_info {
period_id: 1
period_dur: 61792616758
cpu_subsystem {
- estimate_mw: 42.12355
+ estimated_mw: 42.12355
policy0 {
- estimate_mw: 34.71888
+ estimated_mw: 34.71888
cpu0 {
- estimate_mw: 10.7050705
+ estimated_mw: 10.7050705
}
cpu1 {
- estimate_mw: 8.315672
+ estimated_mw: 8.315672
}
cpu2 {
- estimate_mw: 7.7776303
+ estimated_mw: 7.7776303
}
cpu3 {
- estimate_mw: 7.920505
+ estimated_mw: 7.920505
}
}
dsu_scu {
- estimate_mw: 7.404673
+ estimated_mw: 7.404673
}
}
}
@@ -481,29 +481,29 @@
query=Metric("wattson_markers_rails"),
out=Csv("""
wattson_markers_rails {
- metric_version: 2
+ metric_version: 3
period_info {
period_id: 1
period_dur: 2031871358
cpu_subsystem {
- estimate_mw: 46.540943
+ estimated_mw: 46.540943
policy0 {
- estimate_mw: 34.037483
+ estimated_mw: 34.037483
cpu0 {
- estimate_mw: 14.416655
+ estimated_mw: 14.416655
}
cpu1 {
- estimate_mw: 6.641429
+ estimated_mw: 6.641429
}
cpu2 {
- estimate_mw: 8.134797
+ estimated_mw: 8.134797
}
cpu3 {
- estimate_mw: 4.8446035
+ estimated_mw: 4.8446035
}
}
dsu_scu {
- estimate_mw: 12.503458
+ estimated_mw: 12.503458
}
}
}
diff --git a/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out b/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out
index 44a2490..28464f2 100644
--- a/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out
+++ b/test/trace_processor/diff_tests/metrics/android/wattson_markers_threads.out
@@ -1,655 +1,655 @@
wattson_markers_threads {
- metric_version: 1
+ metric_version: 2
task_info {
- estimate_mws: 15.333553
- estimate_mw: 7.546518
+ estimated_mws: 15.333553
+ estimated_mw: 7.546518
thread_name: "swapper"
thread_id: 0
process_id: 0
}
task_info {
- estimate_mws: 11.805121
- estimate_mw: 5.809974
+ estimated_mws: 11.805121
+ estimated_mw: 5.809974
thread_name: "RenderThread"
process_name: "com.google.android.wearable.sysui"
thread_id: 3099
process_id: 2710
}
task_info {
- estimate_mws: 9.112684
- estimate_mw: 4.484872
+ estimated_mws: 9.112684
+ estimated_mw: 4.484872
thread_name: "binder:683_3"
process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
thread_id: 816
process_id: 683
}
task_info {
- estimate_mws: 8.802570
- estimate_mw: 4.332248
+ estimated_mws: 8.802570
+ estimated_mw: 4.332248
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 742
process_id: 742
}
task_info {
- estimate_mws: 4.007993
- estimate_mw: 1.972562
+ estimated_mws: 4.007993
+ estimated_mw: 1.972562
thread_name: ".wearable.sysui"
process_name: "com.google.android.wearable.sysui"
thread_id: 2710
process_id: 2710
}
task_info {
- estimate_mws: 1.779128
- estimate_mw: 0.875610
+ estimated_mws: 1.779128
+ estimated_mw: 0.875610
thread_name: "crtc_commit:80"
process_name: "crtc_commit:80"
thread_id: 300
process_id: 300
}
task_info {
- estimate_mws: 1.436499
- estimate_mw: 0.706983
+ estimated_mws: 1.436499
+ estimated_mw: 0.706983
thread_name: "binder:2710_E"
process_name: "com.google.android.wearable.sysui"
thread_id: 6515
process_id: 2710
}
task_info {
- estimate_mws: 1.262685
- estimate_mw: 0.621440
+ estimated_mws: 1.262685
+ estimated_mw: 0.621440
thread_name: "TimerDispatch"
process_name: "/system/bin/surfaceflinger"
thread_id: 819
process_id: 742
}
task_info {
- estimate_mws: 1.242906
- estimate_mw: 0.611705
+ estimated_mws: 1.242906
+ estimated_mw: 0.611705
thread_name: "kworker/u8:4"
process_name: "kworker/u8:4"
thread_id: 11407
process_id: 11407
}
task_info {
- estimate_mws: 1.231494
- estimate_mw: 0.606089
+ estimated_mws: 1.231494
+ estimated_mw: 0.606089
thread_name: "BckgrndExec HP"
process_name: "/system/bin/surfaceflinger"
thread_id: 837
process_id: 742
}
task_info {
- estimate_mws: 1.194067
- estimate_mw: 0.587669
+ estimated_mws: 1.194067
+ estimated_mw: 0.587669
thread_name: "kworker/u8:5"
process_name: "kworker/u8:5"
thread_id: 10610
process_id: 10610
}
task_info {
- estimate_mws: 1.132809
- estimate_mw: 0.557520
+ estimated_mws: 1.132809
+ estimated_mw: 0.557520
thread_name: "binder:742_2"
process_name: "/system/bin/surfaceflinger"
thread_id: 791
process_id: 742
}
task_info {
- estimate_mws: 1.065350
- estimate_mw: 0.524320
+ estimated_mws: 1.065350
+ estimated_mw: 0.524320
thread_name: "rcu_preempt"
process_name: "rcu_preempt"
thread_id: 14
process_id: 14
}
task_info {
- estimate_mws: 0.872391
- estimate_mw: 0.429353
+ estimated_mws: 0.872391
+ estimated_mw: 0.429353
thread_name: "binder:2710_7"
process_name: "com.google.android.wearable.sysui"
thread_id: 5691
process_id: 2710
}
task_info {
- estimate_mws: 0.865098
- estimate_mw: 0.425764
+ estimated_mws: 0.865098
+ estimated_mw: 0.425764
thread_name: "traced_probes"
process_name: "/system/bin/traced_probes"
thread_id: 916
process_id: 916
}
task_info {
- estimate_mws: 0.844506
- estimate_mw: 0.415630
+ estimated_mws: 0.844506
+ estimated_mw: 0.415630
thread_name: "sleep"
process_name: "sleep"
thread_id: 11474
process_id: 11474
}
task_info {
- estimate_mws: 0.795564
- estimate_mw: 0.391542
+ estimated_mws: 0.795564
+ estimated_mw: 0.391542
thread_name: "kgsl_dispatcher"
process_name: "kgsl_dispatcher"
thread_id: 122
process_id: 122
}
task_info {
- estimate_mws: 0.715993
- estimate_mw: 0.352381
+ estimated_mws: 0.715993
+ estimated_mw: 0.352381
thread_name: "irq/33-4520300."
process_name: "irq/33-4520300."
thread_id: 307
process_id: 307
}
task_info {
- estimate_mws: 0.715212
- estimate_mw: 0.351997
+ estimated_mws: 0.715212
+ estimated_mw: 0.351997
thread_name: "binder:742_1"
process_name: "/system/bin/surfaceflinger"
thread_id: 786
process_id: 742
}
task_info {
- estimate_mws: 0.659174
- estimate_mw: 0.324417
+ estimated_mws: 0.659174
+ estimated_mw: 0.324417
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 788
process_id: 742
}
task_info {
- estimate_mws: 0.653970
- estimate_mw: 0.321856
+ estimated_mws: 0.653970
+ estimated_mw: 0.321856
thread_name: "app"
process_name: "/system/bin/surfaceflinger"
thread_id: 820
process_id: 742
}
task_info {
- estimate_mws: 0.463974
- estimate_mw: 0.228348
+ estimated_mws: 0.463974
+ estimated_mw: 0.228348
thread_name: "rcuog/0"
process_name: "rcuog/0"
thread_id: 15
process_id: 15
}
task_info {
- estimate_mws: 0.434532
- estimate_mw: 0.213858
+ estimated_mws: 0.434532
+ estimated_mw: 0.213858
thread_name: "Primes-Jank"
process_name: "com.google.android.wearable.sysui"
thread_id: 5094
process_id: 2710
}
task_info {
- estimate_mws: 0.356684
- estimate_mw: 0.175544
+ estimated_mws: 0.356684
+ estimated_mw: 0.175544
thread_name: "crtc_event:80"
process_name: "crtc_event:80"
thread_id: 301
process_id: 301
}
task_info {
- estimate_mws: 0.271999
- estimate_mw: 0.133866
+ estimated_mws: 0.271999
+ estimated_mw: 0.133866
thread_name: "rcuog/2"
process_name: "rcuog/2"
thread_id: 40
process_id: 40
}
task_info {
- estimate_mws: 0.204649
- estimate_mw: 0.100719
+ estimated_mws: 0.204649
+ estimated_mw: 0.100719
thread_name: "binder:2710_2"
process_name: "com.google.android.wearable.sysui"
thread_id: 3100
process_id: 2710
}
task_info {
- estimate_mws: 0.197450
- estimate_mw: 0.097176
+ estimated_mws: 0.197450
+ estimated_mw: 0.097176
thread_name: "FileWatcherThre"
process_name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
thread_id: 1544
process_id: 1529
}
task_info {
- estimate_mws: 0.165350
- estimate_mw: 0.081378
+ estimated_mws: 0.165350
+ estimated_mw: 0.081378
thread_name: "rcuop/0"
process_name: "rcuop/0"
thread_id: 16
process_id: 16
}
task_info {
- estimate_mws: 0.123135
- estimate_mw: 0.060602
+ estimated_mws: 0.123135
+ estimated_mw: 0.060602
thread_name: "msm_irqbalance"
process_name: "/vendor/bin/msm_irqbalance"
thread_id: 3230
process_id: 3230
}
task_info {
- estimate_mws: 0.122703
- estimate_mw: 0.060389
+ estimated_mws: 0.122703
+ estimated_mw: 0.060389
thread_name: "kgsl-events"
process_name: "kgsl-events"
thread_id: 120
process_id: 120
}
task_info {
- estimate_mws: 0.106642
- estimate_mw: 0.052485
+ estimated_mws: 0.106642
+ estimated_mw: 0.052485
thread_name: "traced"
process_name: "/system/bin/traced"
thread_id: 919
process_id: 919
}
task_info {
- estimate_mws: 0.104195
- estimate_mw: 0.051280
+ estimated_mws: 0.104195
+ estimated_mw: 0.051280
thread_name: "kworker/2:0"
process_name: "kworker/2:0"
thread_id: 11444
process_id: 11444
}
task_info {
- estimate_mws: 0.095284
- estimate_mw: 0.046894
+ estimated_mws: 0.095284
+ estimated_mw: 0.046894
thread_name: "rcuop/2"
process_name: "rcuop/2"
thread_id: 41
process_id: 41
}
task_info {
- estimate_mws: 0.084534
- estimate_mw: 0.041604
+ estimated_mws: 0.084534
+ estimated_mw: 0.041604
thread_name: "RegSampIdle"
process_name: "/system/bin/surfaceflinger"
thread_id: 826
process_id: 742
}
task_info {
- estimate_mws: 0.076505
- estimate_mw: 0.037652
+ estimated_mws: 0.076505
+ estimated_mw: 0.037652
thread_name: "rcuop/1"
process_name: "rcuop/1"
thread_id: 32
process_id: 32
}
task_info {
- estimate_mws: 0.067736
- estimate_mw: 0.033337
+ estimated_mws: 0.067736
+ estimated_mw: 0.033337
thread_name: "sh"
process_name: "/system/bin/sh"
thread_id: 11472
process_id: 11472
}
task_info {
- estimate_mws: 0.065940
- estimate_mw: 0.032453
+ estimated_mws: 0.065940
+ estimated_mw: 0.032453
thread_name: "BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 3524
process_id: 2710
}
task_info {
- estimate_mws: 0.053141
- estimate_mw: 0.026154
+ estimated_mws: 0.053141
+ estimated_mw: 0.026154
thread_name: "StateService"
process_name: "com.google.android.apps.scone"
thread_id: 3621
process_id: 3505
}
task_info {
- estimate_mws: 0.052200
- estimate_mw: 0.025691
+ estimated_mws: 0.052200
+ estimated_mw: 0.025691
thread_name: "Blocking Thread"
process_name: "com.fitbit.FitbitMobile"
thread_id: 11310
process_id: 11279
}
task_info {
- estimate_mws: 0.040767
- estimate_mw: 0.020064
+ estimated_mws: 0.040767
+ estimated_mw: 0.020064
thread_name: "kworker/0:1"
process_name: "kworker/0:1"
thread_id: 11436
process_id: 11436
}
task_info {
- estimate_mws: 0.040587
- estimate_mw: 0.019975
+ estimated_mws: 0.040587
+ estimated_mw: 0.019975
thread_name: "binder:1629_7"
process_name: "system_server"
thread_id: 2635
process_id: 1629
}
task_info {
- estimate_mws: 0.040484
- estimate_mw: 0.019924
+ estimated_mws: 0.040484
+ estimated_mw: 0.019924
thread_name: "rcuop/3"
process_name: "rcuop/3"
thread_id: 49
process_id: 49
}
task_info {
- estimate_mws: 0.038016
- estimate_mw: 0.018710
+ estimated_mws: 0.038016
+ estimated_mw: 0.018710
thread_name: "atchdog.monitor"
process_name: "system_server"
thread_id: 1669
process_id: 1629
}
task_info {
- estimate_mws: 0.036888
- estimate_mw: 0.018155
+ estimated_mws: 0.036888
+ estimated_mw: 0.018155
thread_name: "logd.writer"
process_name: "/system/bin/logd"
thread_id: 228
process_id: 213
}
task_info {
- estimate_mws: 0.032972
- estimate_mw: 0.016227
+ estimated_mws: 0.032972
+ estimated_mw: 0.016227
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 828
process_id: 742
}
task_info {
- estimate_mws: 0.032239
- estimate_mw: 0.015867
+ estimated_mws: 0.032239
+ estimated_mw: 0.015867
thread_name: "it.FitbitMobile"
process_name: "com.fitbit.FitbitMobile"
thread_id: 11279
process_id: 11279
}
task_info {
- estimate_mws: 0.031160
- estimate_mw: 0.015336
+ estimated_mws: 0.031160
+ estimated_mw: 0.015336
thread_name: "binder:11279_4"
process_name: "com.fitbit.FitbitMobile"
thread_id: 11426
process_id: 11279
}
task_info {
- estimate_mws: 0.028389
- estimate_mw: 0.013972
+ estimated_mws: 0.028389
+ estimated_mw: 0.013972
thread_name: "irq/207-dwc3"
process_name: "irq/207-dwc3"
thread_id: 9733
process_id: 9733
}
task_info {
- estimate_mws: 0.027208
- estimate_mw: 0.013391
+ estimated_mws: 0.027208
+ estimated_mw: 0.013391
thread_name: "UsbFfs-worker"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 9734
process_id: 5154
}
task_info {
- estimate_mws: 0.024832
- estimate_mw: 0.012221
+ estimated_mws: 0.024832
+ estimated_mw: 0.012221
thread_name: "logcat"
process_name: "logcat"
thread_id: 1199
process_id: 1199
}
task_info {
- estimate_mws: 0.023707
- estimate_mw: 0.011668
+ estimated_mws: 0.023707
+ estimated_mw: 0.011668
thread_name: "logd.reader.per"
process_name: "/system/bin/logd"
thread_id: 1227
process_id: 213
}
task_info {
- estimate_mws: 0.022160
- estimate_mw: 0.010906
+ estimated_mws: 0.022160
+ estimated_mw: 0.010906
thread_name: "kworker/u8:2"
process_name: "kworker/u8:2"
thread_id: 11458
process_id: 11458
}
task_info {
- estimate_mws: 0.019052
- estimate_mw: 0.009376
+ estimated_mws: 0.019052
+ estimated_mw: 0.009376
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 3575
process_id: 2710
}
task_info {
- estimate_mws: 0.018414
- estimate_mw: 0.009063
+ estimated_mws: 0.018414
+ estimated_mw: 0.009063
thread_name: "RegionSampling"
process_name: "/system/bin/surfaceflinger"
thread_id: 825
process_id: 742
}
task_info {
- estimate_mws: 0.016701
- estimate_mw: 0.008220
+ estimated_mws: 0.016701
+ estimated_mw: 0.008220
thread_name: "halt_drain_rqs"
process_name: "halt_drain_rqs"
thread_id: 108
process_id: 108
}
task_info {
- estimate_mws: 0.011023
- estimate_mw: 0.005425
+ estimated_mws: 0.011023
+ estimated_mw: 0.005425
thread_name: "irq/26-4744000."
process_name: "irq/26-4744000."
thread_id: 112
process_id: 112
}
task_info {
- estimate_mws: 0.010004
- estimate_mw: 0.004924
+ estimated_mws: 0.010004
+ estimated_mw: 0.004924
thread_name: "migration/2"
process_name: "migration/2"
thread_id: 35
process_id: 35
}
task_info {
- estimate_mws: 0.008819
- estimate_mw: 0.004341
+ estimated_mws: 0.008819
+ estimated_mw: 0.004341
thread_name: "ksoftirqd/0"
process_name: "ksoftirqd/0"
thread_id: 13
process_id: 13
}
task_info {
- estimate_mws: 0.007911
- estimate_mw: 0.003894
+ estimated_mws: 0.007911
+ estimated_mw: 0.003894
thread_name: "watchdog"
process_name: "system_server"
thread_id: 1676
process_id: 1629
}
task_info {
- estimate_mws: 0.007796
- estimate_mw: 0.003837
+ estimated_mws: 0.007796
+ estimated_mw: 0.003837
thread_name: "pool-283-thread"
process_name: "system_server"
thread_id: 4427
process_id: 1629
}
task_info {
- estimate_mws: 0.007628
- estimate_mw: 0.003754
+ estimated_mws: 0.007628
+ estimated_mw: 0.003754
thread_name: "adbd"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 5154
process_id: 5154
}
task_info {
- estimate_mws: 0.006796
- estimate_mw: 0.003344
+ estimated_mws: 0.006796
+ estimated_mw: 0.003344
thread_name: "pool-1-thread-1"
process_name: "system_server"
thread_id: 2655
process_id: 1629
}
task_info {
- estimate_mws: 0.005691
- estimate_mw: 0.002801
+ estimated_mws: 0.005691
+ estimated_mw: 0.002801
thread_name: "pool-1-thread-1"
process_name: "com.google.android.apps.scone"
thread_id: 3625
process_id: 3505
}
task_info {
- estimate_mws: 0.005476
- estimate_mw: 0.002695
+ estimated_mws: 0.005476
+ estimated_mw: 0.002695
thread_name: "binder:237_2"
process_name: "/system/bin/vold"
thread_id: 237
process_id: 237
}
task_info {
- estimate_mws: 0.004537
- estimate_mw: 0.002233
+ estimated_mws: 0.004537
+ estimated_mw: 0.002233
thread_name: "shell svc 11472"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 11473
process_id: 5154
}
task_info {
- estimate_mws: 0.003924
- estimate_mw: 0.001931
+ estimated_mws: 0.003924
+ estimated_mw: 0.001931
thread_name: "ksoftirqd/1"
process_name: "ksoftirqd/1"
thread_id: 29
process_id: 29
}
task_info {
- estimate_mws: 0.002908
- estimate_mw: 0.001431
+ estimated_mws: 0.002908
+ estimated_mw: 0.001431
thread_name: "BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 5230
process_id: 2710
}
task_info {
- estimate_mws: 0.002492
- estimate_mw: 0.001226
+ estimated_mws: 0.002492
+ estimated_mw: 0.001226
thread_name: "kworker/3:2"
process_name: "kworker/3:2"
thread_id: 9832
process_id: 9832
}
task_info {
- estimate_mws: 0.002333
- estimate_mw: 0.001148
+ estimated_mws: 0.002333
+ estimated_mw: 0.001148
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 3577
process_id: 2710
}
task_info {
- estimate_mws: 0.002293
- estimate_mw: 0.001128
+ estimated_mws: 0.002293
+ estimated_mw: 0.001128
thread_name: "InputReader"
process_name: "system_server"
thread_id: 2560
process_id: 1629
}
task_info {
- estimate_mws: 0.002261
- estimate_mw: 0.001113
+ estimated_mws: 0.002261
+ estimated_mw: 0.001113
thread_name: "DefaultDispatch"
process_name: "com.google.android.wearable.media.sessions"
thread_id: 3618
process_id: 3553
}
task_info {
- estimate_mws: 0.002226
- estimate_mw: 0.001095
+ estimated_mws: 0.002226
+ estimated_mw: 0.001095
thread_name: "migration/3"
process_name: "migration/3"
thread_id: 44
process_id: 44
}
task_info {
- estimate_mws: 0.002100
- estimate_mw: 0.001034
+ estimated_mws: 0.002100
+ estimated_mw: 0.001034
thread_name: "InputDispatcher"
process_name: "system_server"
thread_id: 2559
process_id: 1629
}
task_info {
- estimate_mws: 0.001973
- estimate_mw: 0.000971
+ estimated_mws: 0.001973
+ estimated_mw: 0.000971
thread_name: "kworker/1:0"
process_name: "kworker/1:0"
thread_id: 10984
process_id: 10984
}
task_info {
- estimate_mws: 0.001966
- estimate_mw: 0.000967
+ estimated_mws: 0.001966
+ estimated_mw: 0.000967
thread_name: "irq/193-wdog-ba"
process_name: "irq/193-wdog-ba"
thread_id: 344
process_id: 344
}
task_info {
- estimate_mws: 0.001867
- estimate_mw: 0.000919
+ estimated_mws: 0.001867
+ estimated_mw: 0.000919
thread_name: "DefaultDispatch"
process_name: "com.google.android.wearable.media.sessions"
thread_id: 3615
process_id: 3553
}
task_info {
- estimate_mws: 0.001867
- estimate_mw: 0.000919
+ estimated_mws: 0.001867
+ estimated_mw: 0.000919
thread_name: "irq/25-mmc0"
process_name: "irq/25-mmc0"
thread_id: 115
process_id: 115
}
task_info {
- estimate_mws: 0.001728
- estimate_mw: 0.000851
+ estimated_mws: 0.001728
+ estimated_mw: 0.000851
thread_name: "iou-wrk-214"
process_name: "/system/bin/lmkd"
thread_id: 11440
process_id: 214
}
task_info {
- estimate_mws: 0.001600
- estimate_mw: 0.000787
+ estimated_mws: 0.001600
+ estimated_mw: 0.000787
thread_name: "DefaultDispatch"
process_name: "com.google.android.wearable.media.sessions"
thread_id: 3616
process_id: 3553
}
task_info {
- estimate_mws: 0.001393
- estimate_mw: 0.000686
+ estimated_mws: 0.001393
+ estimated_mw: 0.000686
thread_name: "kworker/u8:1"
process_name: "kworker/u8:1"
thread_id: 11185
process_id: 11185
}
task_info {
- estimate_mws: 0.001373
- estimate_mw: 0.000676
+ estimated_mws: 0.001373
+ estimated_mw: 0.000676
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 3576
process_id: 2710
}
task_info {
- estimate_mws: 0.000811
- estimate_mw: 0.000399
+ estimated_mws: 0.000811
+ estimated_mw: 0.000399
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 3622
diff --git a/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out b/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out
index 77fe7cd..76b3c98 100644
--- a/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out
+++ b/test/trace_processor/diff_tests/metrics/android/wattson_trace_threads.out
@@ -1,3835 +1,3835 @@
wattson_trace_threads {
- metric_version: 1
+ metric_version: 2
task_info {
- estimate_mws: 34.415016
- estimate_mw: 3.979049
+ estimated_mws: 34.415016
+ estimated_mw: 3.979049
thread_name: "swapper"
thread_id: 0
process_id: 0
}
task_info {
- estimate_mws: 19.853703
- estimate_mw: 2.295476
+ estimated_mws: 19.853703
+ estimated_mw: 2.295476
thread_name: "RenderThread"
process_name: "com.google.android.wearable.sysui"
thread_id: 1986
process_id: 1926
}
task_info {
- estimate_mws: 17.530441
- estimate_mw: 2.026862
+ estimated_mws: 17.530441
+ estimated_mw: 2.026862
thread_name: "Jit thread pool"
process_name: "system_server"
thread_id: 1344
process_id: 1302
}
task_info {
- estimate_mws: 16.980274
- estimate_mw: 1.963252
+ estimated_mws: 16.980274
+ estimated_mw: 1.963252
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 755
process_id: 755
}
task_info {
- estimate_mws: 14.908094
- estimate_mw: 1.723667
+ estimated_mws: 14.908094
+ estimated_mw: 1.723667
thread_name: ".wearable.sysui"
process_name: "com.google.android.wearable.sysui"
thread_id: 1926
process_id: 1926
}
task_info {
- estimate_mws: 13.373355
- estimate_mw: 1.546221
+ estimated_mws: 13.373355
+ estimated_mw: 1.546221
thread_name: "binder:685_3"
process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
thread_id: 804
process_id: 685
}
task_info {
- estimate_mws: 6.747261
- estimate_mw: 0.780115
+ estimated_mws: 6.747261
+ estimated_mw: 0.780115
thread_name: "binder:1302_7"
process_name: "system_server"
thread_id: 1671
process_id: 1302
}
task_info {
- estimate_mws: 6.504173
- estimate_mw: 0.752010
+ estimated_mws: 6.504173
+ estimated_mw: 0.752010
thread_name: "binder:1302_A"
process_name: "system_server"
thread_id: 2015
process_id: 1302
}
task_info {
- estimate_mws: 4.858775
- estimate_mw: 0.561769
+ estimated_mws: 4.858775
+ estimated_mw: 0.561769
thread_name: "android.anim"
process_name: "system_server"
thread_id: 1419
process_id: 1302
}
task_info {
- estimate_mws: 4.769800
- estimate_mw: 0.551482
+ estimated_mws: 4.769800
+ estimated_mw: 0.551482
thread_name: "RenderEngine"
process_name: "/system/bin/surfaceflinger"
thread_id: 788
process_id: 755
}
task_info {
- estimate_mws: 4.672233
- estimate_mw: 0.540201
+ estimated_mws: 4.672233
+ estimated_mw: 0.540201
thread_name: "kswapd0"
process_name: "kswapd0"
thread_id: 63
process_id: 63
}
task_info {
- estimate_mws: 4.314495
- estimate_mw: 0.498840
+ estimated_mws: 4.314495
+ estimated_mw: 0.498840
thread_name: "lowpool[2]"
process_name: "com.google.android.gms"
thread_id: 3525
process_id: 2856
}
task_info {
- estimate_mws: 4.117818
- estimate_mw: 0.476100
+ estimated_mws: 4.117818
+ estimated_mw: 0.476100
thread_name: "logd.writer"
process_name: "/system/bin/logd"
thread_id: 221
process_id: 211
}
task_info {
- estimate_mws: 4.108276
- estimate_mw: 0.474997
+ estimated_mws: 4.108276
+ estimated_mw: 0.474997
thread_name: "binder:1302_17"
process_name: "system_server"
thread_id: 5202
process_id: 1302
}
task_info {
- estimate_mws: 3.723955
- estimate_mw: 0.430562
+ estimated_mws: 3.723955
+ estimated_mw: 0.430562
thread_name: "binder:1302_6"
process_name: "system_server"
thread_id: 1662
process_id: 1302
}
task_info {
- estimate_mws: 3.666289
- estimate_mw: 0.423895
+ estimated_mws: 3.666289
+ estimated_mw: 0.423895
thread_name: "e.watchface.rwf"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 1999
process_id: 1999
}
task_info {
- estimate_mws: 3.524869
- estimate_mw: 0.407544
+ estimated_mws: 3.524869
+ estimated_mw: 0.407544
thread_name: "killall"
process_name: "/system/bin/sh"
thread_id: 5620
process_id: 5620
}
task_info {
- estimate_mws: 3.495762
- estimate_mw: 0.404178
+ estimated_mws: 3.495762
+ estimated_mw: 0.404178
thread_name: "CachedAppOptimi"
process_name: "system_server"
thread_id: 1773
process_id: 1302
}
task_info {
- estimate_mws: 3.459922
- estimate_mw: 0.400035
+ estimated_mws: 3.459922
+ estimated_mw: 0.400035
thread_name: "logcat"
process_name: "logcat"
thread_id: 1230
process_id: 1230
}
task_info {
- estimate_mws: 3.429554
- estimate_mw: 0.396524
+ estimated_mws: 3.429554
+ estimated_mw: 0.396524
thread_name: "system_server"
process_name: "system_server"
thread_id: 1302
process_id: 1302
}
task_info {
- estimate_mws: 3.300661
- estimate_mw: 0.381621
+ estimated_mws: 3.300661
+ estimated_mw: 0.381621
thread_name: "crtc_commit:80"
process_name: "crtc_commit:80"
thread_id: 244
process_id: 244
}
task_info {
- estimate_mws: 3.194881
- estimate_mw: 0.369391
+ estimated_mws: 3.194881
+ estimated_mw: 0.369391
thread_name: "InputDispatcher"
process_name: "system_server"
thread_id: 1783
process_id: 1302
}
task_info {
- estimate_mws: 3.011913
- estimate_mw: 0.348236
+ estimated_mws: 3.011913
+ estimated_mw: 0.348236
thread_name: "binder:755_1"
process_name: "/system/bin/surfaceflinger"
thread_id: 782
process_id: 755
}
task_info {
- estimate_mws: 3.006022
- estimate_mw: 0.347555
+ estimated_mws: 3.006022
+ estimated_mw: 0.347555
thread_name: "android.display"
process_name: "system_server"
thread_id: 1418
process_id: 1302
}
task_info {
- estimate_mws: 2.856301
- estimate_mw: 0.330244
+ estimated_mws: 2.856301
+ estimated_mw: 0.330244
thread_name: "binder:524_2"
process_name: "/vendor/bin/mcu_mgmtd"
thread_id: 524
process_id: 524
}
task_info {
- estimate_mws: 2.712443
- estimate_mw: 0.313611
+ estimated_mws: 2.712443
+ estimated_mw: 0.313611
thread_name: "traced_probes"
process_name: "/system/bin/traced_probes"
thread_id: 904
process_id: 904
}
task_info {
- estimate_mws: 2.550916
- estimate_mw: 0.294936
+ estimated_mws: 2.550916
+ estimated_mw: 0.294936
thread_name: "kworker/u8:0"
process_name: "kworker/u8:0"
thread_id: 8
process_id: 8
}
task_info {
- estimate_mws: 2.487099
- estimate_mw: 0.287557
+ estimated_mws: 2.487099
+ estimated_mw: 0.287557
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 883
process_id: 755
}
task_info {
- estimate_mws: 2.386123
- estimate_mw: 0.275883
+ estimated_mws: 2.386123
+ estimated_mw: 0.275883
thread_name: "binder:1302_15"
process_name: "system_server"
thread_id: 3754
process_id: 1302
}
task_info {
- estimate_mws: 2.258779
- estimate_mw: 0.261159
+ estimated_mws: 2.258779
+ estimated_mw: 0.261159
thread_name: "logd.reader.per"
process_name: "/system/bin/logd"
thread_id: 1274
process_id: 211
}
task_info {
- estimate_mws: 2.171289
- estimate_mw: 0.251044
+ estimated_mws: 2.171289
+ estimated_mw: 0.251044
thread_name: "RenderThread"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 2301
process_id: 1999
}
task_info {
- estimate_mws: 2.143151
- estimate_mw: 0.247790
+ estimated_mws: 2.143151
+ estimated_mw: 0.247790
thread_name: "InputReader"
process_name: "system_server"
thread_id: 1784
process_id: 1302
}
task_info {
- estimate_mws: 2.091430
- estimate_mw: 0.241810
+ estimated_mws: 2.091430
+ estimated_mw: 0.241810
thread_name: "rcu_preempt"
process_name: "rcu_preempt"
thread_id: 14
process_id: 14
}
task_info {
- estimate_mws: 2.048920
- estimate_mw: 0.236895
+ estimated_mws: 2.048920
+ estimated_mw: 0.236895
thread_name: "binder:1926_4"
process_name: "com.google.android.wearable.sysui"
thread_id: 2262
process_id: 1926
}
task_info {
- estimate_mws: 1.914560
- estimate_mw: 0.221361
+ estimated_mws: 1.914560
+ estimated_mw: 0.221361
thread_name: "arable.systemui"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2171
process_id: 2171
}
task_info {
- estimate_mws: 1.854433
- estimate_mw: 0.214409
+ estimated_mws: 1.854433
+ estimated_mw: 0.214409
thread_name: "android.ui"
process_name: "system_server"
thread_id: 1416
process_id: 1302
}
task_info {
- estimate_mws: 1.777087
- estimate_mw: 0.205466
+ estimated_mws: 1.777087
+ estimated_mw: 0.205466
thread_name: "kworker/u8:4"
process_name: "kworker/u8:4"
thread_id: 431
process_id: 431
}
task_info {
- estimate_mws: 1.773777
- estimate_mw: 0.205083
+ estimated_mws: 1.773777
+ estimated_mw: 0.205083
thread_name: "TimerDispatch"
process_name: "/system/bin/surfaceflinger"
thread_id: 865
process_id: 755
}
task_info {
- estimate_mws: 1.760400
- estimate_mw: 0.203537
+ estimated_mws: 1.760400
+ estimated_mw: 0.203537
thread_name: "ActivityManager"
process_name: "system_server"
thread_id: 1431
process_id: 1302
}
task_info {
- estimate_mws: 1.733169
- estimate_mw: 0.200388
+ estimated_mws: 1.733169
+ estimated_mw: 0.200388
thread_name: "PowerManagerSer"
process_name: "system_server"
thread_id: 1506
process_id: 1302
}
task_info {
- estimate_mws: 1.639501
- estimate_mw: 0.189558
+ estimated_mws: 1.639501
+ estimated_mw: 0.189558
thread_name: "WifiHandlerThre"
process_name: "system_server"
thread_id: 1818
process_id: 1302
}
task_info {
- estimate_mws: 1.631037
- estimate_mw: 0.188580
+ estimated_mws: 1.631037
+ estimated_mw: 0.188580
thread_name: "binder:755_5"
process_name: "/system/bin/surfaceflinger"
thread_id: 1987
process_id: 755
}
task_info {
- estimate_mws: 1.605931
- estimate_mw: 0.185677
+ estimated_mws: 1.605931
+ estimated_mw: 0.185677
thread_name: "kgsl_dispatcher"
process_name: "kgsl_dispatcher"
thread_id: 111
process_id: 111
}
task_info {
- estimate_mws: 1.564964
- estimate_mw: 0.180940
+ estimated_mws: 1.564964
+ estimated_mw: 0.180940
thread_name: "binder:1302_8"
process_name: "system_server"
thread_id: 1679
process_id: 1302
}
task_info {
- estimate_mws: 1.476619
- estimate_mw: 0.170726
+ estimated_mws: 1.476619
+ estimated_mw: 0.170726
thread_name: "lowpool[5]"
process_name: "com.google.android.gms.persistent"
thread_id: 3489
process_id: 1949
}
task_info {
- estimate_mws: 1.470155
- estimate_mw: 0.169979
+ estimated_mws: 1.470155
+ estimated_mw: 0.169979
thread_name: "-Executor] idle"
process_name: "com.google.android.gms"
thread_id: 5591
process_id: 2856
}
task_info {
- estimate_mws: 1.469958
- estimate_mw: 0.169956
+ estimated_mws: 1.469958
+ estimated_mw: 0.169956
thread_name: "binder:1302_B"
process_name: "system_server"
thread_id: 2033
process_id: 1302
}
task_info {
- estimate_mws: 1.390635
- estimate_mw: 0.160785
+ estimated_mws: 1.390635
+ estimated_mw: 0.160785
thread_name: "binder:755_4"
process_name: "/system/bin/surfaceflinger"
thread_id: 1125
process_id: 755
}
task_info {
- estimate_mws: 1.327049
- estimate_mw: 0.153433
+ estimated_mws: 1.327049
+ estimated_mw: 0.153433
thread_name: "batterystats-ha"
process_name: "system_server"
thread_id: 1484
process_id: 1302
}
task_info {
- estimate_mws: 1.312721
- estimate_mw: 0.151776
+ estimated_mws: 1.312721
+ estimated_mw: 0.151776
thread_name: "statsd.writer"
process_name: "/apex/com.android.os.statsd/bin/statsd"
thread_id: 980
process_id: 545
}
task_info {
- estimate_mws: 1.252738
- estimate_mw: 0.144841
+ estimated_mws: 1.252738
+ estimated_mw: 0.144841
thread_name: "kworker/u8:2"
process_name: "kworker/u8:2"
thread_id: 62
process_id: 62
}
task_info {
- estimate_mws: 1.251944
- estimate_mw: 0.144749
+ estimated_mws: 1.251944
+ estimated_mw: 0.144749
thread_name: "app"
process_name: "/system/bin/surfaceflinger"
thread_id: 867
process_id: 755
}
task_info {
- estimate_mws: 1.233674
- estimate_mw: 0.142637
+ estimated_mws: 1.233674
+ estimated_mw: 0.142637
thread_name: "system_server"
process_name: "system_server"
thread_id: 1343
process_id: 1302
}
task_info {
- estimate_mws: 1.228813
- estimate_mw: 0.142075
+ estimated_mws: 1.228813
+ estimated_mw: 0.142075
thread_name: "irq/33-4520300."
process_name: "irq/33-4520300.qcom,bwmon-ddr"
thread_id: 95
process_id: 95
}
task_info {
- estimate_mws: 1.068197
- estimate_mw: 0.123504
+ estimated_mws: 1.068197
+ estimated_mw: 0.123504
thread_name: "logd.klogd"
process_name: "/system/bin/logd"
thread_id: 234
process_id: 211
}
task_info {
- estimate_mws: 1.007070
- estimate_mw: 0.116437
+ estimated_mws: 1.007070
+ estimated_mw: 0.116437
thread_name: "android.fg"
process_name: "system_server"
thread_id: 1415
process_id: 1302
}
task_info {
- estimate_mws: 0.969980
- estimate_mw: 0.112149
+ estimated_mws: 0.969980
+ estimated_mw: 0.112149
thread_name: "rcuog/0"
process_name: "rcuog/0"
thread_id: 15
process_id: 15
}
task_info {
- estimate_mws: 0.952077
- estimate_mw: 0.110079
+ estimated_mws: 0.952077
+ estimated_mw: 0.110079
thread_name: "binder:1926_3"
process_name: "com.google.android.wearable.sysui"
thread_id: 1940
process_id: 1926
}
task_info {
- estimate_mws: 0.946746
- estimate_mw: 0.109462
+ estimated_mws: 0.946746
+ estimated_mw: 0.109462
thread_name: "gle.android.gms"
process_name: "com.google.android.gms"
thread_id: 2856
process_id: 2856
}
task_info {
- estimate_mws: 0.930774
- estimate_mw: 0.107616
+ estimated_mws: 0.930774
+ estimated_mw: 0.107616
thread_name: "crtc_event:80"
process_name: "crtc_event:80"
thread_id: 245
process_id: 245
}
task_info {
- estimate_mws: 0.907425
- estimate_mw: 0.104916
+ estimated_mws: 0.907425
+ estimated_mw: 0.104916
thread_name: "binder:755_3"
process_name: "/system/bin/surfaceflinger"
thread_id: 1124
process_id: 755
}
task_info {
- estimate_mws: 0.897620
- estimate_mw: 0.103782
+ estimated_mws: 0.897620
+ estimated_mw: 0.103782
thread_name: "init"
process_name: "/system/bin/init"
thread_id: 143
process_id: 1
}
task_info {
- estimate_mws: 0.880853
- estimate_mw: 0.101844
+ estimated_mws: 0.880853
+ estimated_mw: 0.101844
thread_name: "wmshell.main"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2260
process_id: 2171
}
task_info {
- estimate_mws: 0.870598
- estimate_mw: 0.100658
+ estimated_mws: 0.870598
+ estimated_mw: 0.100658
thread_name: "Primes-1"
process_name: "com.google.android.wearable.sysui"
thread_id: 1944
process_id: 1926
}
task_info {
- estimate_mws: 0.847641
- estimate_mw: 0.098004
+ estimated_mws: 0.847641
+ estimated_mw: 0.098004
thread_name: "init"
process_name: "/system/bin/init"
thread_id: 1
process_id: 1
}
task_info {
- estimate_mws: 0.846054
- estimate_mw: 0.097820
+ estimated_mws: 0.846054
+ estimated_mw: 0.097820
thread_name: "binder:1302_D"
process_name: "system_server"
thread_id: 2043
process_id: 1302
}
task_info {
- estimate_mws: 0.844958
- estimate_mw: 0.097694
+ estimated_mws: 0.844958
+ estimated_mw: 0.097694
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 786
process_id: 755
}
task_info {
- estimate_mws: 0.833920
- estimate_mw: 0.096417
+ estimated_mws: 0.833920
+ estimated_mw: 0.096417
thread_name: "kworker/u8:5"
process_name: "kworker/u8:5"
thread_id: 5304
process_id: 5304
}
task_info {
- estimate_mws: 0.780835
- estimate_mw: 0.090280
+ estimated_mws: 0.780835
+ estimated_mw: 0.090280
thread_name: "kworker/2:4"
process_name: "kworker/2:4"
thread_id: 4995
process_id: 4995
}
task_info {
- estimate_mws: 0.747755
- estimate_mw: 0.086455
+ estimated_mws: 0.747755
+ estimated_mw: 0.086455
thread_name: "binder:2171_4"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2374
process_id: 2171
}
task_info {
- estimate_mws: 0.746488
- estimate_mw: 0.086309
+ estimated_mws: 0.746488
+ estimated_mw: 0.086309
thread_name: "binder:1999_5"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 3678
process_id: 1999
}
task_info {
- estimate_mws: 0.744159
- estimate_mw: 0.086039
+ estimated_mws: 0.744159
+ estimated_mw: 0.086039
thread_name: "servicemanager"
process_name: "/system/bin/servicemanager"
thread_id: 213
process_id: 213
}
task_info {
- estimate_mws: 0.717520
- estimate_mw: 0.082959
+ estimated_mws: 0.717520
+ estimated_mw: 0.082959
thread_name: "wmshell.anim"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2269
process_id: 2171
}
task_info {
- estimate_mws: 0.699681
- estimate_mw: 0.080897
+ estimated_mws: 0.699681
+ estimated_mw: 0.080897
thread_name: "GoogleApiHandle"
process_name: "com.google.android.gms"
thread_id: 3208
process_id: 2856
}
task_info {
- estimate_mws: 0.675997
- estimate_mw: 0.078159
+ estimated_mws: 0.675997
+ estimated_mw: 0.078159
thread_name: "binder:1302_4"
process_name: "system_server"
thread_id: 1592
process_id: 1302
}
task_info {
- estimate_mws: 0.647999
- estimate_mw: 0.074921
+ estimated_mws: 0.647999
+ estimated_mw: 0.074921
thread_name: "batterystats-wo"
process_name: "system_server"
thread_id: 1487
process_id: 1302
}
task_info {
- estimate_mws: 0.640576
- estimate_mw: 0.074063
+ estimated_mws: 0.640576
+ estimated_mw: 0.074063
thread_name: ".gms.persistent"
process_name: "com.google.android.gms.persistent"
thread_id: 1949
process_id: 1949
}
task_info {
- estimate_mws: 0.631830
- estimate_mw: 0.073052
+ estimated_mws: 0.631830
+ estimated_mw: 0.073052
thread_name: "binder:1926_6"
process_name: "com.google.android.wearable.sysui"
thread_id: 5211
process_id: 1926
}
task_info {
- estimate_mws: 0.627672
- estimate_mw: 0.072571
+ estimated_mws: 0.627672
+ estimated_mw: 0.072571
thread_name: "DisplayOffloadB"
process_name: "system_server"
thread_id: 1512
process_id: 1302
}
task_info {
- estimate_mws: 0.627487
- estimate_mw: 0.072550
+ estimated_mws: 0.627487
+ estimated_mw: 0.072550
thread_name: "binder:682_2"
process_name: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
thread_id: 682
process_id: 682
}
task_info {
- estimate_mws: 0.624294
- estimate_mw: 0.072181
+ estimated_mws: 0.624294
+ estimated_mw: 0.072181
thread_name: "rcuog/2"
process_name: "rcuog/2"
thread_id: 37
process_id: 37
}
task_info {
- estimate_mws: 0.623909
- estimate_mw: 0.072136
+ estimated_mws: 0.623909
+ estimated_mw: 0.072136
thread_name: "kworker/0:6"
process_name: "kworker/0:6"
thread_id: 586
process_id: 586
}
task_info {
- estimate_mws: 0.597177
- estimate_mw: 0.069045
+ estimated_mws: 0.597177
+ estimated_mw: 0.069045
thread_name: "diag-router"
process_name: "/vendor/bin/diag-router"
thread_id: 634
process_id: 634
}
task_info {
- estimate_mws: 0.582498
- estimate_mw: 0.067348
+ estimated_mws: 0.582498
+ estimated_mw: 0.067348
thread_name: "HeapTaskDaemon"
process_name: "com.google.android.gms"
thread_id: 2882
process_id: 2856
}
task_info {
- estimate_mws: 0.579675
- estimate_mw: 0.067022
+ estimated_mws: 0.579675
+ estimated_mw: 0.067022
thread_name: "FileWatcherThre"
process_name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
thread_id: 1411
process_id: 1404
}
task_info {
- estimate_mws: 0.568415
- estimate_mw: 0.065720
+ estimated_mws: 0.568415
+ estimated_mw: 0.065720
thread_name: "TaskSnapshotPer"
process_name: "system_server"
thread_id: 1913
process_id: 1302
}
task_info {
- estimate_mws: 0.565566
- estimate_mw: 0.065390
+ estimated_mws: 0.565566
+ estimated_mw: 0.065390
thread_name: "lmkd"
process_name: "/system/bin/lmkd"
thread_id: 212
process_id: 212
}
task_info {
- estimate_mws: 0.554734
- estimate_mw: 0.064138
+ estimated_mws: 0.554734
+ estimated_mw: 0.064138
thread_name: "binder:1949_8"
process_name: "com.google.android.gms.persistent"
thread_id: 3269
process_id: 1949
}
task_info {
- estimate_mws: 0.517529
- estimate_mw: 0.059836
+ estimated_mws: 0.517529
+ estimated_mw: 0.059836
thread_name: "appSf"
process_name: "/system/bin/surfaceflinger"
thread_id: 868
process_id: 755
}
task_info {
- estimate_mws: 0.514221
- estimate_mw: 0.059454
+ estimated_mws: 0.514221
+ estimated_mw: 0.059454
thread_name: "kworker/1:1"
process_name: "kworker/1:1"
thread_id: 47
process_id: 47
}
task_info {
- estimate_mws: 0.507581
- estimate_mw: 0.058686
+ estimated_mws: 0.507581
+ estimated_mw: 0.058686
thread_name: "android.hardwar"
process_name: "/vendor/bin/hw/android.hardware.usb-service.qti"
thread_id: 1861
process_id: 665
}
task_info {
- estimate_mws: 0.504068
- estimate_mw: 0.058280
+ estimated_mws: 0.504068
+ estimated_mw: 0.058280
thread_name: "Primes-Jank"
process_name: "com.google.android.wearable.sysui"
thread_id: 2389
process_id: 1926
}
task_info {
- estimate_mws: 0.493578
- estimate_mw: 0.057067
+ estimated_mws: 0.493578
+ estimated_mw: 0.057067
thread_name: "binder:2171_3"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2235
process_id: 2171
}
task_info {
- estimate_mws: 0.490345
- estimate_mw: 0.056694
+ estimated_mws: 0.490345
+ estimated_mw: 0.056694
thread_name: "traced"
process_name: "/system/bin/traced"
thread_id: 905
process_id: 905
}
task_info {
- estimate_mws: 0.468415
- estimate_mw: 0.054158
+ estimated_mws: 0.468415
+ estimated_mw: 0.054158
thread_name: "eduling.default"
process_name: "system_server"
thread_id: 1761
process_id: 1302
}
task_info {
- estimate_mws: 0.462913
- estimate_mw: 0.053522
+ estimated_mws: 0.462913
+ estimated_mw: 0.053522
thread_name: "binder:545_2"
process_name: "/apex/com.android.os.statsd/bin/statsd"
thread_id: 553
process_id: 545
}
task_info {
- estimate_mws: 0.462537
- estimate_mw: 0.053478
+ estimated_mws: 0.462537
+ estimated_mw: 0.053478
thread_name: "User"
process_name: "com.google.android.wearable.sysui"
thread_id: 2234
process_id: 1926
}
task_info {
- estimate_mws: 0.454063
- estimate_mw: 0.052499
+ estimated_mws: 0.454063
+ estimated_mw: 0.052499
thread_name: "putmethod.latin"
process_name: "com.google.android.inputmethod.latin"
thread_id: 4997
process_id: 4997
}
task_info {
- estimate_mws: 0.450612
- estimate_mw: 0.052100
+ estimated_mws: 0.450612
+ estimated_mw: 0.052100
thread_name: "ueventd"
process_name: "/system/bin/ueventd"
thread_id: 145
process_id: 145
}
task_info {
- estimate_mws: 0.448044
- estimate_mw: 0.051803
+ estimated_mws: 0.448044
+ estimated_mw: 0.051803
thread_name: "wpa_supplicant"
process_name: "/vendor/bin/hw/wpa_supplicant"
thread_id: 5214
process_id: 5214
}
task_info {
- estimate_mws: 0.431304
- estimate_mw: 0.049867
+ estimated_mws: 0.431304
+ estimated_mw: 0.049867
thread_name: "rcuop/0"
process_name: "rcuop/0"
thread_id: 16
process_id: 16
}
task_info {
- estimate_mws: 0.416635
- estimate_mw: 0.048171
+ estimated_mws: 0.416635
+ estimated_mw: 0.048171
thread_name: "Jit thread pool"
process_name: "com.google.android.wearable.sysui"
thread_id: 1933
process_id: 1926
}
task_info {
- estimate_mws: 0.404592
- estimate_mw: 0.046779
+ estimated_mws: 0.404592
+ estimated_mw: 0.046779
thread_name: "pixelstats-vend"
process_name: "/vendor/bin/pixelstats-vendor"
thread_id: 267
process_id: 255
}
task_info {
- estimate_mws: 0.396838
- estimate_mw: 0.045882
+ estimated_mws: 0.396838
+ estimated_mw: 0.045882
thread_name: "irq/236-NVT-ts"
process_name: "irq/236-NVT-ts"
thread_id: 505
process_id: 505
}
task_info {
- estimate_mws: 0.393249
- estimate_mw: 0.045467
+ estimated_mws: 0.393249
+ estimated_mw: 0.045467
thread_name: "nanohub"
process_name: "nanohub"
thread_id: 297
process_id: 297
}
task_info {
- estimate_mws: 0.376686
- estimate_mw: 0.043552
+ estimated_mws: 0.376686
+ estimated_mw: 0.043552
thread_name: "android.bg"
process_name: "system_server"
thread_id: 1430
process_id: 1302
}
task_info {
- estimate_mws: 0.375869
- estimate_mw: 0.043458
+ estimated_mws: 0.375869
+ estimated_mw: 0.043458
thread_name: "chre"
process_name: "/vendor/bin/chre"
thread_id: 1041
process_id: 1041
}
task_info {
- estimate_mws: 0.373520
- estimate_mw: 0.043186
+ estimated_mws: 0.373520
+ estimated_mw: 0.043186
thread_name: "lowpool[1]"
process_name: "com.google.android.gms.persistent"
thread_id: 2279
process_id: 1949
}
task_info {
- estimate_mws: 0.366001
- estimate_mw: 0.042317
+ estimated_mws: 0.366001
+ estimated_mw: 0.042317
thread_name: "TracingMuxer"
process_name: "/system/bin/surfaceflinger"
thread_id: 783
process_id: 755
}
task_info {
- estimate_mws: 0.359494
- estimate_mw: 0.041565
+ estimated_mws: 0.359494
+ estimated_mw: 0.041565
thread_name: "kgsl-events"
process_name: "kgsl-events"
thread_id: 109
process_id: 109
}
task_info {
- estimate_mws: 0.359130
- estimate_mw: 0.041522
+ estimated_mws: 0.359130
+ estimated_mw: 0.041522
thread_name: "IpClient.wlan0"
process_name: "com.android.networkstack.process"
thread_id: 5216
process_id: 2049
}
task_info {
- estimate_mws: 0.346517
- estimate_mw: 0.040064
+ estimated_mws: 0.346517
+ estimated_mw: 0.040064
thread_name: "binder:257_5"
process_name: "/system/bin/hw/android.system.suspend-service"
thread_id: 1491
process_id: 257
}
task_info {
- estimate_mws: 0.341200
- estimate_mw: 0.039449
+ estimated_mws: 0.341200
+ estimated_mw: 0.039449
thread_name: "binder:1901_3"
process_name: "/vendor/bin/hw/android.hardware.wifi-service-lazy"
thread_id: 1905
process_id: 1901
}
task_info {
- estimate_mws: 0.335534
- estimate_mw: 0.038794
+ estimated_mws: 0.335534
+ estimated_mw: 0.038794
thread_name: "binder:740_1"
process_name: "/system/bin/audioserver"
thread_id: 821
process_id: 740
}
task_info {
- estimate_mws: 0.331405
- estimate_mw: 0.038317
+ estimated_mws: 0.331405
+ estimated_mw: 0.038317
thread_name: "BG"
process_name: "com.google.wear.services"
thread_id: 2023
process_id: 1948
}
task_info {
- estimate_mws: 0.326344
- estimate_mw: 0.037732
+ estimated_mws: 0.326344
+ estimated_mw: 0.037732
thread_name: "kworker/0:5H"
process_name: "kworker/0:5H"
thread_id: 1337
process_id: 1337
}
task_info {
- estimate_mws: 0.322384
- estimate_mw: 0.037274
+ estimated_mws: 0.322384
+ estimated_mw: 0.037274
thread_name: "binder:755_2"
process_name: "/system/bin/surfaceflinger"
thread_id: 784
process_id: 755
}
task_info {
- estimate_mws: 0.319511
- estimate_mw: 0.036942
+ estimated_mws: 0.319511
+ estimated_mw: 0.036942
thread_name: "audioserver"
process_name: "/system/bin/audioserver"
thread_id: 740
process_id: 740
}
task_info {
- estimate_mws: 0.310996
- estimate_mw: 0.035957
+ estimated_mws: 0.310996
+ estimated_mw: 0.035957
thread_name: "binder:1949_2"
process_name: "com.google.android.gms.persistent"
thread_id: 1978
process_id: 1949
}
task_info {
- estimate_mws: 0.302115
- estimate_mw: 0.034930
+ estimated_mws: 0.302115
+ estimated_mw: 0.034930
thread_name: "-Executor] idle"
process_name: "com.google.android.gms.persistent"
thread_id: 5602
process_id: 1949
}
task_info {
- estimate_mws: 0.301578
- estimate_mw: 0.034868
+ estimated_mws: 0.301578
+ estimated_mw: 0.034868
thread_name: "pool-11-thread-"
process_name: "com.google.android.wearable.healthservices"
thread_id: 3329
process_id: 3028
}
task_info {
- estimate_mws: 0.299169
- estimate_mw: 0.034590
+ estimated_mws: 0.299169
+ estimated_mw: 0.034590
thread_name: "android.io"
process_name: "system_server"
thread_id: 1417
process_id: 1302
}
task_info {
- estimate_mws: 0.296825
- estimate_mw: 0.034319
+ estimated_mws: 0.296825
+ estimated_mw: 0.034319
thread_name: "binder:1901_3"
process_name: "/vendor/bin/hw/android.hardware.wifi-service-lazy"
thread_id: 5205
process_id: 1901
}
task_info {
- estimate_mws: 0.294242
- estimate_mw: 0.034020
+ estimated_mws: 0.294242
+ estimated_mw: 0.034020
thread_name: "rcuop/1"
process_name: "rcuop/1"
thread_id: 30
process_id: 30
}
task_info {
- estimate_mws: 0.286642
- estimate_mw: 0.033141
+ estimated_mws: 0.286642
+ estimated_mw: 0.033141
thread_name: "binder:1948_6"
process_name: "com.google.wear.services"
thread_id: 5315
process_id: 1948
}
task_info {
- estimate_mws: 0.285983
- estimate_mw: 0.033065
+ estimated_mws: 0.285983
+ estimated_mw: 0.033065
thread_name: "AssistantHandle"
process_name: "com.google.android.wearable.assistant"
thread_id: 4081
process_id: 4038
}
task_info {
- estimate_mws: 0.283378
- estimate_mw: 0.032764
+ estimated_mws: 0.283378
+ estimated_mw: 0.032764
thread_name: "binder:1999_1"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 2016
process_id: 1999
}
task_info {
- estimate_mws: 0.279959
- estimate_mw: 0.032369
+ estimated_mws: 0.279959
+ estimated_mw: 0.032369
thread_name: "binder:2182_7"
process_name: "com.android.phone"
thread_id: 2694
process_id: 2182
}
task_info {
- estimate_mws: 0.279816
- estimate_mw: 0.032352
+ estimated_mws: 0.279816
+ estimated_mw: 0.032352
thread_name: "kworker/3:2H"
process_name: "kworker/3:2H"
thread_id: 226
process_id: 226
}
task_info {
- estimate_mws: 0.277230
- estimate_mw: 0.032053
+ estimated_mws: 0.277230
+ estimated_mw: 0.032053
thread_name: "BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 3005
process_id: 1926
}
task_info {
- estimate_mws: 0.274735
- estimate_mw: 0.031765
+ estimated_mws: 0.274735
+ estimated_mw: 0.031765
thread_name: "lowpool[3]"
process_name: "com.google.android.gms"
thread_id: 3527
process_id: 2856
}
task_info {
- estimate_mws: 0.267749
- estimate_mw: 0.030957
+ estimated_mws: 0.267749
+ estimated_mw: 0.030957
thread_name: "hvdcp_opti"
process_name: "/vendor/bin/hvdcp_opti"
thread_id: 1276
process_id: 1270
}
task_info {
- estimate_mws: 0.262081
- estimate_mw: 0.030302
+ estimated_mws: 0.262081
+ estimated_mw: 0.030302
thread_name: "binder:1926_3"
process_name: "com.google.android.wearable.sysui"
thread_id: 2022
process_id: 1926
}
task_info {
- estimate_mws: 0.259248
- estimate_mw: 0.029974
+ estimated_mws: 0.259248
+ estimated_mw: 0.029974
thread_name: "kworker/3:5"
process_name: "kworker/3:5"
thread_id: 104
process_id: 104
}
task_info {
- estimate_mws: 0.256714
- estimate_mw: 0.029681
+ estimated_mws: 0.256714
+ estimated_mw: 0.029681
thread_name: "binder:257_2"
process_name: "/system/bin/hw/android.system.suspend-service"
thread_id: 264
process_id: 257
}
task_info {
- estimate_mws: 0.247037
- estimate_mw: 0.028562
+ estimated_mws: 0.247037
+ estimated_mw: 0.028562
thread_name: "SDM_EventThread"
process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
thread_id: 727
process_id: 685
}
task_info {
- estimate_mws: 0.244112
- estimate_mw: 0.028224
+ estimated_mws: 0.244112
+ estimated_mw: 0.028224
thread_name: "POSIX timer 2"
process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
thread_id: 1600
process_id: 664
}
task_info {
- estimate_mws: 0.242754
- estimate_mw: 0.028067
+ estimated_mws: 0.242754
+ estimated_mw: 0.028067
thread_name: "binder:2856_4"
process_name: "com.google.android.gms"
thread_id: 3679
process_id: 2856
}
task_info {
- estimate_mws: 0.241348
- estimate_mw: 0.027905
+ estimated_mws: 0.241348
+ estimated_mw: 0.027905
thread_name: "pool-2-thread-1"
process_name: "com.android.networkstack.process"
thread_id: 2416
process_id: 2049
}
task_info {
- estimate_mws: 0.231145
- estimate_mw: 0.026725
+ estimated_mws: 0.231145
+ estimated_mw: 0.026725
thread_name: "rcuop/3"
process_name: "rcuop/3"
thread_id: 45
process_id: 45
}
task_info {
- estimate_mws: 0.230341
- estimate_mw: 0.026632
+ estimated_mws: 0.230341
+ estimated_mw: 0.026632
thread_name: "f2fs_ckpt-254:4"
process_name: "f2fs_ckpt-254:43"
thread_id: 347
process_id: 347
}
task_info {
- estimate_mws: 0.229722
- estimate_mw: 0.026560
+ estimated_mws: 0.229722
+ estimated_mw: 0.026560
thread_name: "OomAdjuster"
process_name: "system_server"
thread_id: 1482
process_id: 1302
}
task_info {
- estimate_mws: 0.226417
- estimate_mw: 0.026178
+ estimated_mws: 0.226417
+ estimated_mw: 0.026178
thread_name: "binder:740_6"
process_name: "/system/bin/audioserver"
thread_id: 2639
process_id: 740
}
task_info {
- estimate_mws: 0.226007
- estimate_mw: 0.026131
+ estimated_mws: 0.226007
+ estimated_mw: 0.026131
thread_name: "rcuop/2"
process_name: "rcuop/2"
thread_id: 38
process_id: 38
}
task_info {
- estimate_mws: 0.225133
- estimate_mw: 0.026030
+ estimated_mws: 0.225133
+ estimated_mw: 0.026030
thread_name: "kworker/0:7"
process_name: "kworker/0:7"
thread_id: 598
process_id: 598
}
task_info {
- estimate_mws: 0.212788
- estimate_mw: 0.024602
+ estimated_mws: 0.212788
+ estimated_mw: 0.024602
thread_name: "queued-work-loo"
process_name: "system_server"
thread_id: 1886
process_id: 1302
}
task_info {
- estimate_mws: 0.202562
- estimate_mw: 0.023420
+ estimated_mws: 0.202562
+ estimated_mw: 0.023420
thread_name: "pool-13-thread-"
process_name: "com.google.android.wearable.healthservices"
thread_id: 3327
process_id: 3028
}
task_info {
- estimate_mws: 0.201687
- estimate_mw: 0.023319
+ estimated_mws: 0.201687
+ estimated_mw: 0.023319
thread_name: "WearSdkThread"
process_name: "com.google.android.wearable.sysui"
thread_id: 2207
process_id: 1926
}
task_info {
- estimate_mws: 0.201119
- estimate_mw: 0.023253
+ estimated_mws: 0.201119
+ estimated_mw: 0.023253
thread_name: "qrtr_ns"
process_name: "qrtr_ns"
thread_id: 88
process_id: 88
}
task_info {
- estimate_mws: 0.200639
- estimate_mw: 0.023198
+ estimated_mws: 0.200639
+ estimated_mw: 0.023198
thread_name: "binder:740_7"
process_name: "/system/bin/audioserver"
thread_id: 5206
process_id: 740
}
task_info {
- estimate_mws: 0.196587
- estimate_mw: 0.022729
+ estimated_mws: 0.196587
+ estimated_mw: 0.022729
thread_name: "binder:4997_4"
process_name: "com.google.android.inputmethod.latin"
thread_id: 5122
process_id: 4997
}
task_info {
- estimate_mws: 0.194754
- estimate_mw: 0.022517
+ estimated_mws: 0.194754
+ estimated_mw: 0.022517
thread_name: "m.android.phone"
process_name: "com.android.phone"
thread_id: 2182
process_id: 2182
}
task_info {
- estimate_mws: 0.192336
- estimate_mw: 0.022238
+ estimated_mws: 0.192336
+ estimated_mw: 0.022238
thread_name: "HwcAsyncWorker"
process_name: "/system/bin/surfaceflinger"
thread_id: 835
process_id: 755
}
task_info {
- estimate_mws: 0.190522
- estimate_mw: 0.022028
+ estimated_mws: 0.190522
+ estimated_mw: 0.022028
thread_name: "binder:636_2"
process_name: "/vendor/bin/hw/android.hardware.audio.service"
thread_id: 636
process_id: 636
}
task_info {
- estimate_mws: 0.188908
- estimate_mw: 0.021841
+ estimated_mws: 0.188908
+ estimated_mw: 0.021841
thread_name: "SettingsProvide"
process_name: "system_server"
thread_id: 1771
process_id: 1302
}
task_info {
- estimate_mws: 0.181172
- estimate_mw: 0.020947
+ estimated_mws: 0.181172
+ estimated_mw: 0.020947
thread_name: "binder:1302_2"
process_name: "system_server"
thread_id: 1350
process_id: 1302
}
task_info {
- estimate_mws: 0.177579
- estimate_mw: 0.020532
+ estimated_mws: 0.177579
+ estimated_mw: 0.020532
thread_name: "RegSampIdle"
process_name: "/system/bin/surfaceflinger"
thread_id: 872
process_id: 755
}
task_info {
- estimate_mws: 0.168738
- estimate_mw: 0.019509
+ estimated_mws: 0.168738
+ estimated_mw: 0.019509
thread_name: "ice] processing"
process_name: "com.google.android.gms"
thread_id: 3238
process_id: 2856
}
task_info {
- estimate_mws: 0.162808
- estimate_mw: 0.018824
+ estimated_mws: 0.162808
+ estimated_mw: 0.018824
thread_name: "binder:682_3"
process_name: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
thread_id: 2308
process_id: 682
}
task_info {
- estimate_mws: 0.158857
- estimate_mw: 0.018367
+ estimated_mws: 0.158857
+ estimated_mw: 0.018367
thread_name: "binder:678_3"
process_name: "/apex/com.google.wearable.wac.whshal/bin/hw/vendor.google.wearable.wac.whshal@2.0-service"
thread_id: 1884
process_id: 678
}
task_info {
- estimate_mws: 0.158692
- estimate_mw: 0.018348
+ estimated_mws: 0.158692
+ estimated_mw: 0.018348
thread_name: "binder:650_4"
process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
thread_id: 5498
process_id: 650
}
task_info {
- estimate_mws: 0.157956
- estimate_mw: 0.018263
+ estimated_mws: 0.157956
+ estimated_mw: 0.018263
thread_name: "vndservicemanag"
process_name: "/vendor/bin/vndservicemanager"
thread_id: 215
process_id: 215
}
task_info {
- estimate_mws: 0.153600
- estimate_mw: 0.017759
+ estimated_mws: 0.153600
+ estimated_mw: 0.017759
thread_name: "GoogleLocationS"
process_name: "com.google.android.gms.persistent"
thread_id: 3355
process_id: 1949
}
task_info {
- estimate_mws: 0.153038
- estimate_mw: 0.017694
+ estimated_mws: 0.153038
+ estimated_mw: 0.017694
thread_name: "TransportThread"
process_name: "/vendor/bin/chre"
thread_id: 1078
process_id: 1041
}
task_info {
- estimate_mws: 0.152058
- estimate_mw: 0.017581
+ estimated_mws: 0.152058
+ estimated_mw: 0.017581
thread_name: "kworker/2:1H"
process_name: "kworker/2:1H"
thread_id: 123
process_id: 123
}
task_info {
- estimate_mws: 0.148559
- estimate_mw: 0.017176
+ estimated_mws: 0.148559
+ estimated_mw: 0.017176
thread_name: "BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 2120
process_id: 1926
}
task_info {
- estimate_mws: 0.148352
- estimate_mw: 0.017152
+ estimated_mws: 0.148352
+ estimated_mw: 0.017152
thread_name: "vendor.google.w"
process_name: "/apex/com.google.wearable.wac.whshal/bin/hw/vendor.google.wearable.wac.whshal@2.0-service"
thread_id: 1881
process_id: 678
}
task_info {
- estimate_mws: 0.140864
- estimate_mw: 0.016287
+ estimated_mws: 0.140864
+ estimated_mw: 0.016287
thread_name: "NetworkStats"
process_name: "system_server"
thread_id: 1814
process_id: 1302
}
task_info {
- estimate_mws: 0.138579
- estimate_mw: 0.016022
+ estimated_mws: 0.138579
+ estimated_mw: 0.016022
thread_name: "binder:969_2"
process_name: "/system/vendor/bin/cnd"
thread_id: 1011
process_id: 969
}
task_info {
- estimate_mws: 0.134607
- estimate_mw: 0.015563
+ estimated_mws: 0.134607
+ estimated_mw: 0.015563
thread_name: "dmabuf-deferred"
process_name: "dmabuf-deferred-free-worker"
thread_id: 69
process_id: 69
}
task_info {
- estimate_mws: 0.129449
- estimate_mw: 0.014967
+ estimated_mws: 0.129449
+ estimated_mw: 0.014967
thread_name: "highpool[5]"
process_name: "com.google.android.gms.persistent"
thread_id: 3354
process_id: 1949
}
task_info {
- estimate_mws: 0.126973
- estimate_mw: 0.014681
+ estimated_mws: 0.126973
+ estimated_mw: 0.014681
thread_name: "ice] processing"
process_name: "com.google.android.gms.persistent"
thread_id: 2363
process_id: 1949
}
task_info {
- estimate_mws: 0.126830
- estimate_mw: 0.014664
+ estimated_mws: 0.126830
+ estimated_mw: 0.014664
thread_name: "ediator.Toggler"
process_name: "system_server"
thread_id: 1910
process_id: 1302
}
task_info {
- estimate_mws: 0.125742
- estimate_mw: 0.014538
+ estimated_mws: 0.125742
+ estimated_mw: 0.014538
thread_name: "surfaceflinger"
process_name: "/system/bin/surfaceflinger"
thread_id: 875
process_id: 755
}
task_info {
- estimate_mws: 0.123833
- estimate_mw: 0.014317
+ estimated_mws: 0.123833
+ estimated_mw: 0.014317
thread_name: "wificond"
process_name: "/system/bin/wificond"
thread_id: 964
process_id: 964
}
task_info {
- estimate_mws: 0.123248
- estimate_mw: 0.014250
+ estimated_mws: 0.123248
+ estimated_mw: 0.014250
thread_name: "MobileDataStats"
process_name: "system_server"
thread_id: 1912
process_id: 1302
}
task_info {
- estimate_mws: 0.119885
- estimate_mw: 0.013861
+ estimated_mws: 0.119885
+ estimated_mw: 0.013861
thread_name: "GlobalScheduler"
process_name: "com.google.android.gms"
thread_id: 3156
process_id: 2856
}
task_info {
- estimate_mws: 0.119479
- estimate_mw: 0.013814
+ estimated_mws: 0.119479
+ estimated_mw: 0.013814
thread_name: "RenderThread"
thread_id: 5599
}
task_info {
- estimate_mws: 0.119243
- estimate_mw: 0.013787
+ estimated_mws: 0.119243
+ estimated_mw: 0.013787
thread_name: "TouchTimer"
process_name: "/system/bin/surfaceflinger"
thread_id: 866
process_id: 755
}
task_info {
- estimate_mws: 0.114249
- estimate_mw: 0.013209
+ estimated_mws: 0.114249
+ estimated_mw: 0.013209
thread_name: "binder:4038_1"
process_name: "com.google.android.wearable.assistant"
thread_id: 4050
process_id: 4038
}
task_info {
- estimate_mws: 0.112705
- estimate_mw: 0.013031
+ estimated_mws: 0.112705
+ estimated_mw: 0.013031
thread_name: "displayoffload@"
process_name: "/vendor/bin/hw/vendor.google_clockwork.displayoffload@2.0-service.1p"
thread_id: 937
process_id: 937
}
task_info {
- estimate_mws: 0.111279
- estimate_mw: 0.012866
+ estimated_mws: 0.111279
+ estimated_mw: 0.012866
thread_name: "adbd"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 5544
process_id: 5544
}
task_info {
- estimate_mws: 0.108114
- estimate_mw: 0.012500
+ estimated_mws: 0.108114
+ estimated_mw: 0.012500
thread_name: "kworker/1:2H"
process_name: "kworker/1:2H"
thread_id: 300
process_id: 300
}
task_info {
- estimate_mws: 0.107442
- estimate_mw: 0.012422
+ estimated_mws: 0.107442
+ estimated_mw: 0.012422
thread_name: "RenderThread"
thread_id: 5584
}
task_info {
- estimate_mws: 0.105863
- estimate_mw: 0.012240
+ estimated_mws: 0.105863
+ estimated_mw: 0.012240
thread_name: "Primes-2"
process_name: "com.google.android.wearable.sysui"
thread_id: 1946
process_id: 1926
}
task_info {
- estimate_mws: 0.104306
- estimate_mw: 0.012060
+ estimated_mws: 0.104306
+ estimated_mw: 0.012060
thread_name: "iptables-restor"
process_name: "/system/bin/iptables-restore"
thread_id: 558
process_id: 558
}
task_info {
- estimate_mws: 0.104093
- estimate_mw: 0.012035
+ estimated_mws: 0.104093
+ estimated_mw: 0.012035
thread_name: "RenderThread"
process_name: "system_server"
thread_id: 5223
process_id: 1302
}
task_info {
- estimate_mws: 0.102912
- estimate_mw: 0.011899
+ estimated_mws: 0.102912
+ estimated_mw: 0.011899
thread_name: "irq/168-nanohub"
process_name: "irq/168-nanohub-irq1"
thread_id: 296
process_id: 296
}
task_info {
- estimate_mws: 0.102167
- estimate_mw: 0.011812
+ estimated_mws: 0.102167
+ estimated_mw: 0.011812
thread_name: "RenderThread"
thread_id: 5604
}
task_info {
- estimate_mws: 0.101945
- estimate_mw: 0.011787
+ estimated_mws: 0.101945
+ estimated_mw: 0.011787
thread_name: "ksoftirqd/2"
process_name: "ksoftirqd/2"
thread_id: 34
process_id: 34
}
task_info {
- estimate_mws: 0.101282
- estimate_mw: 0.011710
+ estimated_mws: 0.101282
+ estimated_mw: 0.011710
thread_name: "PhotonicModulat"
process_name: "system_server"
thread_id: 1899
process_id: 1302
}
task_info {
- estimate_mws: 0.100285
- estimate_mw: 0.011595
+ estimated_mws: 0.100285
+ estimated_mw: 0.011595
thread_name: "ip6tables-resto"
process_name: "/system/bin/ip6tables-restore"
thread_id: 559
process_id: 559
}
task_info {
- estimate_mws: 0.099432
- estimate_mw: 0.011496
+ estimated_mws: 0.099432
+ estimated_mw: 0.011496
thread_name: "init"
process_name: "/system/bin/init"
thread_id: 144
process_id: 144
}
task_info {
- estimate_mws: 0.096314
- estimate_mw: 0.011136
+ estimated_mws: 0.096314
+ estimated_mw: 0.011136
thread_name: "FrameworkReceiv"
process_name: ".qtidataservices"
thread_id: 2793
process_id: 2118
}
task_info {
- estimate_mws: 0.095442
- estimate_mw: 0.011035
+ estimated_mws: 0.095442
+ estimated_mw: 0.011035
thread_name: "Jit thread pool"
process_name: "com.google.android.gms.persistent"
thread_id: 1969
process_id: 1949
}
task_info {
- estimate_mws: 0.094448
- estimate_mw: 0.010920
+ estimated_mws: 0.094448
+ estimated_mw: 0.010920
thread_name: "pool-14-thread-"
process_name: "com.google.android.wearable.healthservices"
thread_id: 3314
process_id: 3028
}
task_info {
- estimate_mws: 0.093937
- estimate_mw: 0.010861
+ estimated_mws: 0.093937
+ estimated_mw: 0.010861
thread_name: "binder:1926_2"
process_name: "com.google.android.wearable.sysui"
thread_id: 1939
process_id: 1926
}
task_info {
- estimate_mws: 0.093621
- estimate_mw: 0.010824
+ estimated_mws: 0.093621
+ estimated_mw: 0.010824
thread_name: "ChreMsgHandler"
process_name: "/vendor/bin/chre"
thread_id: 1080
process_id: 1041
}
task_info {
- estimate_mws: 0.091738
- estimate_mw: 0.010607
+ estimated_mws: 0.091738
+ estimated_mw: 0.010607
thread_name: "DispatcherModul"
process_name: "/vendor/bin/hw/qcrilNrd"
thread_id: 1673
process_id: 1062
}
task_info {
- estimate_mws: 0.091698
- estimate_mw: 0.010602
+ estimated_mws: 0.091698
+ estimated_mw: 0.010602
thread_name: "irq/234-pixart_"
process_name: "irq/234-pixart_pat9126_irq"
thread_id: 500
process_id: 500
}
task_info {
- estimate_mws: 0.090883
- estimate_mw: 0.010508
+ estimated_mws: 0.090883
+ estimated_mw: 0.010508
thread_name: "scheduler_threa"
process_name: "scheduler_thread"
thread_id: 5198
process_id: 5198
}
task_info {
- estimate_mws: 0.089001
- estimate_mw: 0.010290
+ estimated_mws: 0.089001
+ estimated_mw: 0.010290
thread_name: "binder:2085_4"
process_name: "com.google.android.bluetooth"
thread_id: 2713
process_id: 2085
}
task_info {
- estimate_mws: 0.086934
- estimate_mw: 0.010051
+ estimated_mws: 0.086934
+ estimated_mw: 0.010051
thread_name: "binder:3028_5"
process_name: "com.google.android.wearable.healthservices"
thread_id: 5434
process_id: 3028
}
task_info {
- estimate_mws: 0.085941
- estimate_mw: 0.009936
+ estimated_mws: 0.085941
+ estimated_mw: 0.009936
thread_name: "binder:2670_6"
process_name: "com.android.nfc"
thread_id: 3159
process_id: 2670
}
task_info {
- estimate_mws: 0.083859
- estimate_mw: 0.009696
+ estimated_mws: 0.083859
+ estimated_mw: 0.009696
thread_name: "psimon"
process_name: "psimon"
thread_id: 1480
process_id: 1480
}
task_info {
- estimate_mws: 0.083773
- estimate_mw: 0.009686
+ estimated_mws: 0.083773
+ estimated_mw: 0.009686
thread_name: "binder:233_2"
process_name: "/system/bin/vold"
thread_id: 252
process_id: 233
}
task_info {
- estimate_mws: 0.080959
- estimate_mw: 0.009360
+ estimated_mws: 0.080959
+ estimated_mw: 0.009360
thread_name: "binder:2049_2"
process_name: "com.android.networkstack.process"
thread_id: 2068
process_id: 2049
}
task_info {
- estimate_mws: 0.080298
- estimate_mw: 0.009284
+ estimated_mws: 0.080298
+ estimated_mw: 0.009284
thread_name: "netd"
process_name: "/system/bin/netd"
thread_id: 568
process_id: 546
}
task_info {
- estimate_mws: 0.080265
- estimate_mw: 0.009280
+ estimated_mws: 0.080265
+ estimated_mw: 0.009280
thread_name: "UEventObserver"
process_name: "system_server"
thread_id: 1857
process_id: 1302
}
task_info {
- estimate_mws: 0.079337
- estimate_mw: 0.009173
+ estimated_mws: 0.079337
+ estimated_mw: 0.009173
thread_name: "RenderThread"
thread_id: 5619
}
task_info {
- estimate_mws: 0.078696
- estimate_mw: 0.009099
+ estimated_mws: 0.078696
+ estimated_mw: 0.009099
thread_name: "pool-8-thread-1"
process_name: "com.google.android.gms"
thread_id: 3102
process_id: 2856
}
task_info {
- estimate_mws: 0.077362
- estimate_mw: 0.008945
+ estimated_mws: 0.077362
+ estimated_mw: 0.008945
thread_name: "mcu_mgmtd"
process_name: "/vendor/bin/mcu_mgmtd"
thread_id: 594
process_id: 524
}
task_info {
- estimate_mws: 0.074501
- estimate_mw: 0.008614
+ estimated_mws: 0.074501
+ estimated_mw: 0.008614
thread_name: "spi0"
process_name: "spi0"
thread_id: 295
process_id: 295
}
task_info {
- estimate_mws: 0.073914
- estimate_mw: 0.008546
+ estimated_mws: 0.073914
+ estimated_mw: 0.008546
thread_name: "com.android.nfc"
process_name: "com.android.nfc"
thread_id: 2670
process_id: 2670
}
task_info {
- estimate_mws: 0.072671
- estimate_mw: 0.008402
+ estimated_mws: 0.072671
+ estimated_mw: 0.008402
thread_name: "rcu_exp_gp_kthr"
process_name: "rcu_exp_gp_kthread_worker"
thread_id: 19
process_id: 19
}
task_info {
- estimate_mws: 0.070587
- estimate_mw: 0.008161
+ estimated_mws: 0.070587
+ estimated_mw: 0.008161
thread_name: "adbd"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 5546
process_id: 5544
}
task_info {
- estimate_mws: 0.070105
- estimate_mw: 0.008105
+ estimated_mws: 0.070105
+ estimated_mw: 0.008105
thread_name: "servicemanager"
thread_id: 5598
}
task_info {
- estimate_mws: 0.069214
- estimate_mw: 0.008002
+ estimated_mws: 0.069214
+ estimated_mw: 0.008002
thread_name: "android.imms"
process_name: "system_server"
thread_id: 1791
process_id: 1302
}
task_info {
- estimate_mws: 0.068600
- estimate_mw: 0.007931
+ estimated_mws: 0.068600
+ estimated_mw: 0.007931
thread_name: "lowpool[2]"
process_name: "com.google.android.gms.persistent"
thread_id: 2321
process_id: 1949
}
task_info {
- estimate_mws: 0.068467
- estimate_mw: 0.007916
+ estimated_mws: 0.068467
+ estimated_mw: 0.007916
thread_name: "FileObserver"
process_name: "com.google.android.gms"
thread_id: 3035
process_id: 2856
}
task_info {
- estimate_mws: 0.068316
- estimate_mw: 0.007899
+ estimated_mws: 0.068316
+ estimated_mw: 0.007899
thread_name: "binder:546_3"
process_name: "/system/bin/netd"
thread_id: 546
process_id: 546
}
task_info {
- estimate_mws: 0.066410
- estimate_mw: 0.007678
+ estimated_mws: 0.066410
+ estimated_mw: 0.007678
thread_name: "pool-51-thread-"
process_name: "com.google.android.gms"
thread_id: 4215
process_id: 2856
}
task_info {
- estimate_mws: 0.064761
- estimate_mw: 0.007488
+ estimated_mws: 0.064761
+ estimated_mw: 0.007488
thread_name: "AudioService"
process_name: "system_server"
thread_id: 1844
process_id: 1302
}
task_info {
- estimate_mws: 0.064339
- estimate_mw: 0.007439
+ estimated_mws: 0.064339
+ estimated_mw: 0.007439
thread_name: "adbd"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 5545
process_id: 5544
}
task_info {
- estimate_mws: 0.063188
- estimate_mw: 0.007306
+ estimated_mws: 0.063188
+ estimated_mw: 0.007306
thread_name: "droid.bluetooth"
process_name: "com.google.android.bluetooth"
thread_id: 2085
process_id: 2085
}
task_info {
- estimate_mws: 0.061069
- estimate_mw: 0.007061
+ estimated_mws: 0.061069
+ estimated_mw: 0.007061
thread_name: "msm_irqbalance"
process_name: "/vendor/bin/msm_irqbalance"
thread_id: 2466
process_id: 2466
}
task_info {
- estimate_mws: 0.060920
- estimate_mw: 0.007044
+ estimated_mws: 0.060920
+ estimated_mw: 0.007044
thread_name: "BackgroundInsta"
process_name: "system_server"
thread_id: 1875
process_id: 1302
}
task_info {
- estimate_mws: 0.059901
- estimate_mw: 0.006926
+ estimated_mws: 0.059901
+ estimated_mw: 0.006926
thread_name: "ConnectivitySer"
process_name: "system_server"
thread_id: 1827
process_id: 1302
}
task_info {
- estimate_mws: 0.059295
- estimate_mw: 0.006856
+ estimated_mws: 0.059295
+ estimated_mw: 0.006856
thread_name: "pool-1-thread-1"
process_name: "system_server"
thread_id: 1873
process_id: 1302
}
task_info {
- estimate_mws: 0.059196
- estimate_mw: 0.006844
+ estimated_mws: 0.059196
+ estimated_mw: 0.006844
thread_name: "binder:3028_1"
process_name: "com.google.android.wearable.healthservices"
thread_id: 3049
process_id: 3028
}
task_info {
- estimate_mws: 0.058807
- estimate_mw: 0.006799
+ estimated_mws: 0.058807
+ estimated_mw: 0.006799
thread_name: "roid.apps.scone"
process_name: "com.google.android.apps.scone"
thread_id: 5245
process_id: 5245
}
task_info {
- estimate_mws: 0.057400
- estimate_mw: 0.006637
+ estimated_mws: 0.057400
+ estimated_mw: 0.006637
thread_name: "android.hardwar"
process_name: "/vendor/bin/hw/android.hardware.health-service.eos"
thread_id: 1271
process_id: 1271
}
task_info {
- estimate_mws: 0.056947
- estimate_mw: 0.006584
+ estimated_mws: 0.056947
+ estimated_mw: 0.006584
thread_name: "bgres-controlle"
process_name: "system_server"
thread_id: 1495
process_id: 1302
}
task_info {
- estimate_mws: 0.056879
- estimate_mw: 0.006576
+ estimated_mws: 0.056879
+ estimated_mw: 0.006576
thread_name: "netd"
process_name: "/system/bin/netd"
thread_id: 569
process_id: 546
}
task_info {
- estimate_mws: 0.055771
- estimate_mw: 0.006448
+ estimated_mws: 0.055771
+ estimated_mw: 0.006448
thread_name: "UsbFfs-worker"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 5560
process_id: 5544
}
task_info {
- estimate_mws: 0.055642
- estimate_mw: 0.006433
+ estimated_mws: 0.055642
+ estimated_mw: 0.006433
thread_name: "system_server"
thread_id: 5590
}
task_info {
- estimate_mws: 0.054764
- estimate_mw: 0.006332
+ estimated_mws: 0.054764
+ estimated_mw: 0.006332
thread_name: "binder:2049_4"
process_name: "com.android.networkstack.process"
thread_id: 2083
process_id: 2049
}
task_info {
- estimate_mws: 0.053127
- estimate_mw: 0.006142
+ estimated_mws: 0.053127
+ estimated_mw: 0.006142
thread_name: "binder:1949_4"
process_name: "com.google.android.gms.persistent"
thread_id: 2302
process_id: 1949
}
task_info {
- estimate_mws: 0.052984
- estimate_mw: 0.006126
+ estimated_mws: 0.052984
+ estimated_mw: 0.006126
thread_name: "oid.grilservice"
process_name: "com.google.android.grilservice"
thread_id: 2129
process_id: 2129
}
task_info {
- estimate_mws: 0.052980
- estimate_mw: 0.006126
+ estimated_mws: 0.052980
+ estimated_mw: 0.006126
thread_name: "binder:2856_9"
process_name: "com.google.android.gms"
thread_id: 5585
process_id: 2856
}
task_info {
- estimate_mws: 0.052715
- estimate_mw: 0.006095
+ estimated_mws: 0.052715
+ estimated_mw: 0.006095
thread_name: "StateService"
process_name: "com.google.android.apps.scone"
thread_id: 5269
process_id: 5245
}
task_info {
- estimate_mws: 0.052232
- estimate_mw: 0.006039
+ estimated_mws: 0.052232
+ estimated_mw: 0.006039
thread_name: "vndservicemanag"
thread_id: 5597
}
task_info {
- estimate_mws: 0.052027
- estimate_mw: 0.006015
+ estimated_mws: 0.052027
+ estimated_mw: 0.006015
thread_name: "binder:1949_9"
process_name: "com.google.android.gms.persistent"
thread_id: 3359
process_id: 1949
}
task_info {
- estimate_mws: 0.051717
- estimate_mw: 0.005979
+ estimated_mws: 0.051717
+ estimated_mw: 0.005979
thread_name: "Ipc-5004:1"
process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
thread_id: 5483
process_id: 650
}
task_info {
- estimate_mws: 0.049546
- estimate_mw: 0.005729
+ estimated_mws: 0.049546
+ estimated_mw: 0.005729
thread_name: "PackageManager"
process_name: "system_server"
thread_id: 1530
process_id: 1302
}
task_info {
- estimate_mws: 0.048227
- estimate_mw: 0.005576
+ estimated_mws: 0.048227
+ estimated_mw: 0.005576
thread_name: "android.hardwar"
process_name: "/vendor/bin/hw/android.hardware.power-service"
thread_id: 660
process_id: 660
}
task_info {
- estimate_mws: 0.047454
- estimate_mw: 0.005487
+ estimated_mws: 0.047454
+ estimated_mw: 0.005487
thread_name: "BluetoothScanMa"
process_name: "com.google.android.bluetooth"
thread_id: 2609
process_id: 2085
}
task_info {
- estimate_mws: 0.046472
- estimate_mw: 0.005373
+ estimated_mws: 0.046472
+ estimated_mw: 0.005373
thread_name: "subsystem_ramdu"
process_name: "/system/vendor/bin/subsystem_ramdump"
thread_id: 816
process_id: 799
}
task_info {
- estimate_mws: 0.046295
- estimate_mw: 0.005353
+ estimated_mws: 0.046295
+ estimated_mw: 0.005353
thread_name: "Ipc-5004:2"
process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
thread_id: 5484
process_id: 650
}
task_info {
- estimate_mws: 0.045805
- estimate_mw: 0.005296
+ estimated_mws: 0.045805
+ estimated_mw: 0.005296
thread_name: "binder:975_2"
process_name: "/vendor/bin/imsdaemon"
thread_id: 1047
process_id: 975
}
task_info {
- estimate_mws: 0.045282
- estimate_mw: 0.005235
+ estimated_mws: 0.045282
+ estimated_mw: 0.005235
thread_name: ".healthservices"
process_name: "com.google.android.wearable.healthservices"
thread_id: 3028
process_id: 3028
}
task_info {
- estimate_mws: 0.045087
- estimate_mw: 0.005213
+ estimated_mws: 0.045087
+ estimated_mw: 0.005213
thread_name: "queued-work-loo"
process_name: "com.google.android.gms"
thread_id: 3236
process_id: 2856
}
task_info {
- estimate_mws: 0.043921
- estimate_mw: 0.005078
+ estimated_mws: 0.043921
+ estimated_mw: 0.005078
thread_name: "powerstateservi"
process_name: "/vendor/bin/hw/vendor.qti.hardware.powerstateservice@1.0-service"
thread_id: 276
process_id: 269
}
task_info {
- estimate_mws: 0.043653
- estimate_mw: 0.005047
+ estimated_mws: 0.043653
+ estimated_mw: 0.005047
thread_name: "wlan_logging_th"
process_name: "wlan_logging_thread"
thread_id: 368
process_id: 368
}
task_info {
- estimate_mws: 0.043574
- estimate_mw: 0.005038
+ estimated_mws: 0.043574
+ estimated_mw: 0.005038
thread_name: "FlpThread"
process_name: "com.google.android.gms.persistent"
thread_id: 3279
process_id: 1949
}
task_info {
- estimate_mws: 0.043436
- estimate_mw: 0.005022
+ estimated_mws: 0.043436
+ estimated_mw: 0.005022
thread_name: "Light-P0-2"
process_name: "com.google.android.inputmethod.latin"
thread_id: 5071
process_id: 4997
}
task_info {
- estimate_mws: 0.042985
- estimate_mw: 0.004970
+ estimated_mws: 0.042985
+ estimated_mw: 0.004970
thread_name: "binder:5245_4"
process_name: "com.google.android.apps.scone"
thread_id: 5270
process_id: 5245
}
task_info {
- estimate_mws: 0.042946
- estimate_mw: 0.004965
+ estimated_mws: 0.042946
+ estimated_mw: 0.004965
thread_name: "HWC_UeventThrea"
process_name: "/vendor/bin/hw/vendor.qti.hardware.display.composer-service"
thread_id: 717
process_id: 685
}
task_info {
- estimate_mws: 0.042762
- estimate_mw: 0.004944
+ estimated_mws: 0.042762
+ estimated_mw: 0.004944
thread_name: "pool-12-thread-"
process_name: "com.google.android.wearable.sysui"
thread_id: 2371
process_id: 1926
}
task_info {
- estimate_mws: 0.042752
- estimate_mw: 0.004943
+ estimated_mws: 0.042752
+ estimated_mw: 0.004943
thread_name: "android.hardwar"
process_name: "/vendor/bin/hw/android.hardware.contexthub-service.wac"
thread_id: 668
process_id: 644
}
task_info {
- estimate_mws: 0.042468
- estimate_mw: 0.004910
+ estimated_mws: 0.042468
+ estimated_mw: 0.004910
thread_name: "binder:1948_3"
process_name: "com.google.wear.services"
thread_id: 1976
process_id: 1948
}
task_info {
- estimate_mws: 0.042220
- estimate_mw: 0.004881
+ estimated_mws: 0.042220
+ estimated_mw: 0.004881
thread_name: "netlink socket"
process_name: "/system/vendor/bin/ipacm"
thread_id: 538
process_id: 523
}
task_info {
- estimate_mws: 0.040507
- estimate_mw: 0.004683
+ estimated_mws: 0.040507
+ estimated_mw: 0.004683
thread_name: "RegionSampling"
process_name: "/system/bin/surfaceflinger"
thread_id: 871
process_id: 755
}
task_info {
- estimate_mws: 0.040385
- estimate_mw: 0.004669
+ estimated_mws: 0.040385
+ estimated_mw: 0.004669
thread_name: "TransportThread"
process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
thread_id: 794
process_id: 664
}
task_info {
- estimate_mws: 0.040349
- estimate_mw: 0.004665
+ estimated_mws: 0.040349
+ estimated_mw: 0.004665
thread_name: "binder:2856_1"
process_name: "com.google.android.gms"
thread_id: 2898
process_id: 2856
}
task_info {
- estimate_mws: 0.040180
- estimate_mw: 0.004646
+ estimated_mws: 0.040180
+ estimated_mw: 0.004646
thread_name: "servicemanager"
thread_id: 5595
}
task_info {
- estimate_mws: 0.039525
- estimate_mw: 0.004570
+ estimated_mws: 0.039525
+ estimated_mw: 0.004570
thread_name: "pd-mapper"
process_name: "/vendor/bin/pd-mapper"
thread_id: 752
process_id: 725
}
task_info {
- estimate_mws: 0.039196
- estimate_mw: 0.004532
+ estimated_mws: 0.039196
+ estimated_mw: 0.004532
thread_name: "vndservicemanag"
thread_id: 5618
}
task_info {
- estimate_mws: 0.039101
- estimate_mw: 0.004521
+ estimated_mws: 0.039101
+ estimated_mw: 0.004521
thread_name: "vndservicemanag"
thread_id: 5605
}
task_info {
- estimate_mws: 0.038960
- estimate_mw: 0.004505
+ estimated_mws: 0.038960
+ estimated_mw: 0.004505
thread_name: "WifiScanningSer"
process_name: "system_server"
thread_id: 1823
process_id: 1302
}
task_info {
- estimate_mws: 0.038580
- estimate_mw: 0.004461
+ estimated_mws: 0.038580
+ estimated_mw: 0.004461
thread_name: "cnss-daemon"
process_name: "/system/vendor/bin/cnss-daemon"
thread_id: 5204
process_id: 1009
}
task_info {
- estimate_mws: 0.038053
- estimate_mw: 0.004400
+ estimated_mws: 0.038053
+ estimated_mw: 0.004400
thread_name: "shell svc 5620"
process_name: "/apex/com.android.adbd/bin/adbd"
thread_id: 5622
process_id: 5544
}
task_info {
- estimate_mws: 0.037116
- estimate_mw: 0.004291
+ estimated_mws: 0.037116
+ estimated_mw: 0.004291
thread_name: "rmt_storage"
process_name: "/vendor/bin/rmt_storage"
thread_id: 758
process_id: 758
}
task_info {
- estimate_mws: 0.036357
- estimate_mw: 0.004204
+ estimated_mws: 0.036357
+ estimated_mw: 0.004204
thread_name: "halt_drain_rqs"
process_name: "halt_drain_rqs"
thread_id: 105
process_id: 105
}
task_info {
- estimate_mws: 0.035907
- estimate_mw: 0.004152
+ estimated_mws: 0.035907
+ estimated_mw: 0.004152
thread_name: "BG Thread #2"
process_name: "com.google.android.wearable.assistant"
thread_id: 4106
process_id: 4038
}
task_info {
- estimate_mws: 0.035876
- estimate_mw: 0.004148
+ estimated_mws: 0.035876
+ estimated_mw: 0.004148
thread_name: "-Executor] idle"
process_name: "com.google.android.gms"
thread_id: 5592
process_id: 2856
}
task_info {
- estimate_mws: 0.035444
- estimate_mw: 0.004098
+ estimated_mws: 0.035444
+ estimated_mw: 0.004098
thread_name: "tftp_server"
process_name: "/vendor/bin/tftp_server"
thread_id: 759
process_id: 759
}
task_info {
- estimate_mws: 0.035386
- estimate_mw: 0.004091
+ estimated_mws: 0.035386
+ estimated_mw: 0.004091
thread_name: "FinalizerWatchd"
process_name: "com.google.android.gms"
thread_id: 2887
process_id: 2856
}
task_info {
- estimate_mws: 0.035171
- estimate_mw: 0.004066
+ estimated_mws: 0.035171
+ estimated_mw: 0.004066
thread_name: "servicemanager"
thread_id: 5606
}
task_info {
- estimate_mws: 0.035157
- estimate_mw: 0.004065
+ estimated_mws: 0.035157
+ estimated_mw: 0.004065
thread_name: "Blocking Thread"
process_name: "com.google.android.wearable.assistant"
thread_id: 5587
process_id: 4038
}
task_info {
- estimate_mws: 0.035034
- estimate_mw: 0.004051
+ estimated_mws: 0.035034
+ estimated_mw: 0.004051
thread_name: "vndservicemanag"
thread_id: 5611
}
task_info {
- estimate_mws: 0.034307
- estimate_mw: 0.003967
+ estimated_mws: 0.034307
+ estimated_mw: 0.003967
thread_name: "vndservicemanag"
thread_id: 5593
}
task_info {
- estimate_mws: 0.034030
- estimate_mw: 0.003935
+ estimated_mws: 0.034030
+ estimated_mw: 0.003935
thread_name: "servicemanager"
thread_id: 5621
}
task_info {
- estimate_mws: 0.032631
- estimate_mw: 0.003773
+ estimated_mws: 0.032631
+ estimated_mw: 0.003773
thread_name: "binder:685_3"
thread_id: 5586
}
task_info {
- estimate_mws: 0.031847
- estimate_mw: 0.003682
+ estimated_mws: 0.031847
+ estimated_mw: 0.003682
thread_name: "radioext@1.0-se"
process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
thread_id: 676
process_id: 676
}
task_info {
- estimate_mws: 0.031818
- estimate_mw: 0.003679
+ estimated_mws: 0.031818
+ estimated_mw: 0.003679
thread_name: "BgBroadcastRegi"
process_name: "com.google.wear.services"
thread_id: 2017
process_id: 1948
}
task_info {
- estimate_mws: 0.031204
- estimate_mw: 0.003608
+ estimated_mws: 0.031204
+ estimated_mw: 0.003608
thread_name: "DefaultExecutor"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 5600
process_id: 1999
}
task_info {
- estimate_mws: 0.030138
- estimate_mw: 0.003484
+ estimated_mws: 0.030138
+ estimated_mw: 0.003484
thread_name: "Light-P0-1"
process_name: "com.google.android.inputmethod.latin"
thread_id: 5064
process_id: 4997
}
task_info {
- estimate_mws: 0.029778
- estimate_mw: 0.003443
+ estimated_mws: 0.029778
+ estimated_mw: 0.003443
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1368
process_id: 978
}
task_info {
- estimate_mws: 0.029627
- estimate_mw: 0.003425
+ estimated_mws: 0.029627
+ estimated_mw: 0.003425
thread_name: "atchdog.monitor"
process_name: "system_server"
thread_id: 1414
process_id: 1302
}
task_info {
- estimate_mws: 0.029590
- estimate_mw: 0.003421
+ estimated_mws: 0.029590
+ estimated_mw: 0.003421
thread_name: "UsfHalWorker"
process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
thread_id: 792
process_id: 664
}
task_info {
- estimate_mws: 0.028316
- estimate_mw: 0.003274
+ estimated_mws: 0.028316
+ estimated_mw: 0.003274
thread_name: "binder:1999_5"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 4985
process_id: 1999
}
task_info {
- estimate_mws: 0.028097
- estimate_mw: 0.003249
+ estimated_mws: 0.028097
+ estimated_mw: 0.003249
thread_name: "SatelliteContro"
process_name: "com.android.phone"
thread_id: 2382
process_id: 2182
}
task_info {
- estimate_mws: 0.027797
- estimate_mw: 0.003214
+ estimated_mws: 0.027797
+ estimated_mw: 0.003214
thread_name: "pm-service"
process_name: "/vendor/bin/pm-service"
thread_id: 745
process_id: 730
}
task_info {
- estimate_mws: 0.027301
- estimate_mw: 0.003157
+ estimated_mws: 0.027301
+ estimated_mw: 0.003157
thread_name: "-Executor] idle"
process_name: "com.google.android.gms.persistent"
thread_id: 5603
process_id: 1949
}
task_info {
- estimate_mws: 0.027287
- estimate_mw: 0.003155
+ estimated_mws: 0.027287
+ estimated_mw: 0.003155
thread_name: "perfetto"
process_name: "perfetto"
thread_id: 5581
process_id: 5581
}
task_info {
- estimate_mws: 0.026942
- estimate_mw: 0.003115
+ estimated_mws: 0.026942
+ estimated_mw: 0.003115
thread_name: "ExeSeq-P10-1"
process_name: "com.google.android.inputmethod.latin"
thread_id: 5074
process_id: 4997
}
task_info {
- estimate_mws: 0.026776
- estimate_mw: 0.003096
+ estimated_mws: 0.026776
+ estimated_mw: 0.003096
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1364
process_id: 978
}
task_info {
- estimate_mws: 0.026328
- estimate_mw: 0.003044
+ estimated_mws: 0.026328
+ estimated_mw: 0.003044
thread_name: "HwBinder:2129_1"
process_name: "com.google.android.grilservice"
thread_id: 3649
process_id: 2129
}
task_info {
- estimate_mws: 0.026013
- estimate_mw: 0.003008
+ estimated_mws: 0.026013
+ estimated_mw: 0.003008
thread_name: "DefaultExecutor"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 5588
process_id: 1999
}
task_info {
- estimate_mws: 0.025701
- estimate_mw: 0.002972
+ estimated_mws: 0.025701
+ estimated_mw: 0.002972
thread_name: "rkstack.process"
process_name: "com.android.networkstack.process"
thread_id: 2049
process_id: 2049
}
task_info {
- estimate_mws: 0.024843
- estimate_mw: 0.002872
+ estimated_mws: 0.024843
+ estimated_mw: 0.002872
thread_name: "hwuiTask1"
process_name: "com.google.android.wearable.sysui"
thread_id: 1997
process_id: 1926
}
task_info {
- estimate_mws: 0.024811
- estimate_mw: 0.002869
+ estimated_mws: 0.024811
+ estimated_mw: 0.002869
thread_name: "pool-1-thread-1"
process_name: "com.google.android.apps.scone"
thread_id: 5271
process_id: 5245
}
task_info {
- estimate_mws: 0.024633
- estimate_mw: 0.002848
+ estimated_mws: 0.024633
+ estimated_mw: 0.002848
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1360
process_id: 978
}
task_info {
- estimate_mws: 0.023564
- estimate_mw: 0.002724
+ estimated_mws: 0.023564
+ estimated_mw: 0.002724
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1366
process_id: 978
}
task_info {
- estimate_mws: 0.023405
- estimate_mw: 0.002706
+ estimated_mws: 0.023405
+ estimated_mw: 0.002706
thread_name: "cnss-daemon"
process_name: "/system/vendor/bin/cnss-daemon"
thread_id: 5613
process_id: 1009
}
task_info {
- estimate_mws: 0.023393
- estimate_mw: 0.002705
+ estimated_mws: 0.023393
+ estimated_mw: 0.002705
thread_name: "servicemanager"
thread_id: 5608
}
task_info {
- estimate_mws: 0.022813
- estimate_mw: 0.002638
+ estimated_mws: 0.022813
+ estimated_mw: 0.002638
thread_name: "android.hardwar"
process_name: "/vendor/bin/hw/android.hardware.contexthub-service.wac"
thread_id: 644
process_id: 644
}
task_info {
- estimate_mws: 0.022774
- estimate_mw: 0.002633
+ estimated_mws: 0.022774
+ estimated_mw: 0.002633
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1362
process_id: 978
}
task_info {
- estimate_mws: 0.022714
- estimate_mw: 0.002626
+ estimated_mws: 0.022714
+ estimated_mw: 0.002626
thread_name: "binder:685_3"
thread_id: 5594
}
task_info {
- estimate_mws: 0.022642
- estimate_mw: 0.002618
+ estimated_mws: 0.022642
+ estimated_mw: 0.002618
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1358
process_id: 978
}
task_info {
- estimate_mws: 0.022617
- estimate_mw: 0.002615
+ estimated_mws: 0.022617
+ estimated_mw: 0.002615
thread_name: "vndservicemanag"
thread_id: 5607
}
task_info {
- estimate_mws: 0.021814
- estimate_mw: 0.002522
+ estimated_mws: 0.021814
+ estimated_mw: 0.002522
thread_name: "it.FitbitMobile"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5377
process_id: 5377
}
task_info {
- estimate_mws: 0.021313
- estimate_mw: 0.002464
+ estimated_mws: 0.021313
+ estimated_mw: 0.002464
thread_name: "irq/199-dwc3"
process_name: "irq/199-dwc3"
thread_id: 5559
process_id: 5559
}
task_info {
- estimate_mws: 0.021307
- estimate_mw: 0.002463
+ estimated_mws: 0.021307
+ estimated_mw: 0.002463
thread_name: "SysUiBg"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2294
process_id: 2171
}
task_info {
- estimate_mws: 0.020941
- estimate_mw: 0.002421
+ estimated_mws: 0.020941
+ estimated_mw: 0.002421
thread_name: "-Executor] idle"
process_name: "com.google.android.gms.persistent"
thread_id: 5623
process_id: 1949
}
task_info {
- estimate_mws: 0.020393
- estimate_mw: 0.002358
+ estimated_mws: 0.020393
+ estimated_mw: 0.002358
thread_name: "vndservicemanag"
thread_id: 5582
}
task_info {
- estimate_mws: 0.019946
- estimate_mw: 0.002306
+ estimated_mws: 0.019946
+ estimated_mw: 0.002306
thread_name: "qcom,system-poo"
process_name: "qcom,system-pool-refill-thread"
thread_id: 81
process_id: 81
}
task_info {
- estimate_mws: 0.019901
- estimate_mw: 0.002301
+ estimated_mws: 0.019901
+ estimated_mw: 0.002301
thread_name: "binder:2129_9"
process_name: "com.google.android.grilservice"
thread_id: 5203
process_id: 2129
}
task_info {
- estimate_mws: 0.019723
- estimate_mw: 0.002280
+ estimated_mws: 0.019723
+ estimated_mw: 0.002280
thread_name: "servicemanager"
thread_id: 5610
}
task_info {
- estimate_mws: 0.019537
- estimate_mw: 0.002259
+ estimated_mws: 0.019537
+ estimated_mw: 0.002259
thread_name: "cnss-daemon"
process_name: "/system/vendor/bin/cnss-daemon"
thread_id: 1009
process_id: 1009
}
task_info {
- estimate_mws: 0.019390
- estimate_mw: 0.002242
+ estimated_mws: 0.019390
+ estimated_mw: 0.002242
thread_name: "pool-9-thread-1"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 2073
process_id: 1999
}
task_info {
- estimate_mws: 0.019219
- estimate_mw: 0.002222
+ estimated_mws: 0.019219
+ estimated_mw: 0.002222
thread_name: "binder:1302_3"
process_name: "system_server"
thread_id: 1433
process_id: 1302
}
task_info {
- estimate_mws: 0.019059
- estimate_mw: 0.002204
+ estimated_mws: 0.019059
+ estimated_mw: 0.002204
thread_name: "mcu_mgmtd"
process_name: "/vendor/bin/mcu_mgmtd"
thread_id: 587
process_id: 524
}
task_info {
- estimate_mws: 0.018619
- estimate_mw: 0.002153
+ estimated_mws: 0.018619
+ estimated_mw: 0.002153
thread_name: "WCMTelemetryLog"
process_name: "system_server"
thread_id: 1906
process_id: 1302
}
task_info {
- estimate_mws: 0.018508
- estimate_mw: 0.002140
+ estimated_mws: 0.018508
+ estimated_mw: 0.002140
thread_name: "droid.tethering"
process_name: "com.android.networkstack.process"
thread_id: 2158
process_id: 2049
}
task_info {
- estimate_mws: 0.018055
- estimate_mw: 0.002087
+ estimated_mws: 0.018055
+ estimated_mw: 0.002087
thread_name: "DefaultWallpape"
process_name: "com.google.android.wearable.watchface.rwf"
thread_id: 2082
process_id: 1999
}
task_info {
- estimate_mws: 0.017725
- estimate_mw: 0.002049
+ estimated_mws: 0.017725
+ estimated_mw: 0.002049
thread_name: "HeapTaskDaemon"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5386
process_id: 5377
}
task_info {
- estimate_mws: 0.017125
- estimate_mw: 0.001980
+ estimated_mws: 0.017125
+ estimated_mw: 0.001980
thread_name: "WearConnectionT"
process_name: "com.google.android.wearable.sysui"
thread_id: 2172
process_id: 1926
}
task_info {
- estimate_mws: 0.016887
- estimate_mw: 0.001952
+ estimated_mws: 0.016887
+ estimated_mw: 0.001952
thread_name: "wear-services-w"
process_name: "com.google.wear.services"
thread_id: 2029
process_id: 1948
}
task_info {
- estimate_mws: 0.016252
- estimate_mw: 0.001879
+ estimated_mws: 0.016252
+ estimated_mw: 0.001879
thread_name: "BG Thread #1"
process_name: "com.google.android.wearable.assistant"
thread_id: 4080
process_id: 4038
}
task_info {
- estimate_mws: 0.016215
- estimate_mw: 0.001875
+ estimated_mws: 0.016215
+ estimated_mw: 0.001875
thread_name: "GlobalScheduler"
process_name: "com.google.android.gms.persistent"
thread_id: 2276
process_id: 1949
}
task_info {
- estimate_mws: 0.016141
- estimate_mw: 0.001866
+ estimated_mws: 0.016141
+ estimated_mw: 0.001866
thread_name: "pool-31-thread-"
process_name: "com.google.android.wearable.sysui"
thread_id: 5617
process_id: 1926
}
task_info {
- estimate_mws: 0.016069
- estimate_mw: 0.001858
+ estimated_mws: 0.016069
+ estimated_mw: 0.001858
thread_name: "servicemanager"
thread_id: 5583
}
task_info {
- estimate_mws: 0.015376
- estimate_mw: 0.001778
+ estimated_mws: 0.015376
+ estimated_mw: 0.001778
thread_name: "RenderEngine"
process_name: "/system/bin/surfaceflinger"
thread_id: 5601
process_id: 755
}
task_info {
- estimate_mws: 0.015374
- estimate_mw: 0.001777
+ estimated_mws: 0.015374
+ estimated_mw: 0.001777
thread_name: "pool-11-thread-"
process_name: "com.google.android.wearable.healthservices"
thread_id: 5596
process_id: 3028
}
task_info {
- estimate_mws: 0.015097
- estimate_mw: 0.001745
+ estimated_mws: 0.015097
+ estimated_mw: 0.001745
thread_name: "dsi_err_workq"
process_name: "dsi_err_workq"
thread_id: 5589
process_id: 5589
}
task_info {
- estimate_mws: 0.015062
- estimate_mw: 0.001741
+ estimated_mws: 0.015062
+ estimated_mw: 0.001741
thread_name: "InteractionJank"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2300
process_id: 2171
}
task_info {
- estimate_mws: 0.015034
- estimate_mw: 0.001738
+ estimated_mws: 0.015034
+ estimated_mw: 0.001738
thread_name: "vndservicemanag"
thread_id: 5609
}
task_info {
- estimate_mws: 0.014592
- estimate_mw: 0.001687
+ estimated_mws: 0.014592
+ estimated_mw: 0.001687
thread_name: "hwuiTask0"
process_name: "com.google.android.wearable.sysui"
thread_id: 1996
process_id: 1926
}
task_info {
- estimate_mws: 0.014520
- estimate_mw: 0.001679
+ estimated_mws: 0.014520
+ estimated_mw: 0.001679
thread_name: "tworkPolicy.uid"
process_name: "system_server"
thread_id: 1817
process_id: 1302
}
task_info {
- estimate_mws: 0.014278
- estimate_mw: 0.001651
+ estimated_mws: 0.014278
+ estimated_mw: 0.001651
thread_name: "highpool[10]"
process_name: "com.google.android.gms.persistent"
thread_id: 3417
process_id: 1949
}
task_info {
- estimate_mws: 0.014123
- estimate_mw: 0.001633
+ estimated_mws: 0.014123
+ estimated_mw: 0.001633
thread_name: "LowMemThread"
process_name: "system_server"
thread_id: 1481
process_id: 1302
}
task_info {
- estimate_mws: 0.014026
- estimate_mw: 0.001622
+ estimated_mws: 0.014026
+ estimated_mw: 0.001622
thread_name: "pixelstats-vend"
process_name: "/vendor/bin/pixelstats-vendor"
thread_id: 266
process_id: 255
}
task_info {
- estimate_mws: 0.013579
- estimate_mw: 0.001570
+ estimated_mws: 0.013579
+ estimated_mw: 0.001570
thread_name: "kworker/u9:0"
process_name: "kworker/u9:0"
thread_id: 64
process_id: 64
}
task_info {
- estimate_mws: 0.013322
- estimate_mw: 0.001540
+ estimated_mws: 0.013322
+ estimated_mw: 0.001540
thread_name: "migration/1"
process_name: "migration/1"
thread_id: 25
process_id: 25
}
task_info {
- estimate_mws: 0.013321
- estimate_mw: 0.001540
+ estimated_mws: 0.013321
+ estimated_mw: 0.001540
thread_name: "GlobalDispatchi"
process_name: "com.google.android.gms.persistent"
thread_id: 2290
process_id: 1949
}
task_info {
- estimate_mws: 0.013127
- estimate_mw: 0.001518
+ estimated_mws: 0.013127
+ estimated_mw: 0.001518
thread_name: "main"
process_name: "/vendor/bin/hw/qcrilNrd"
thread_id: 1568
process_id: 1062
}
task_info {
- estimate_mws: 0.012863
- estimate_mw: 0.001487
+ estimated_mws: 0.012863
+ estimated_mw: 0.001487
thread_name: "servicemanager"
thread_id: 5612
}
task_info {
- estimate_mws: 0.012835
- estimate_mw: 0.001484
+ estimated_mws: 0.012835
+ estimated_mw: 0.001484
thread_name: "qtidataservices"
process_name: ".qtidataservices"
thread_id: 2846
process_id: 2118
}
task_info {
- estimate_mws: 0.012734
- estimate_mw: 0.001472
+ estimated_mws: 0.012734
+ estimated_mw: 0.001472
thread_name: "shortcut"
process_name: "system_server"
thread_id: 1874
process_id: 1302
}
task_info {
- estimate_mws: 0.012433
- estimate_mw: 0.001438
+ estimated_mws: 0.012433
+ estimated_mw: 0.001438
thread_name: "irq/25-mmc0"
process_name: "irq/25-mmc0"
thread_id: 120
process_id: 120
}
task_info {
- estimate_mws: 0.012244
- estimate_mw: 0.001416
+ estimated_mws: 0.012244
+ estimated_mw: 0.001416
thread_name: "GlobalDispatchi"
process_name: "com.google.android.gms"
thread_id: 3155
process_id: 2856
}
task_info {
- estimate_mws: 0.012130
- estimate_mw: 0.001402
+ estimated_mws: 0.012130
+ estimated_mw: 0.001402
thread_name: "ConnectivityThr"
process_name: "com.google.android.gms"
thread_id: 4172
process_id: 2856
}
task_info {
- estimate_mws: 0.011932
- estimate_mw: 0.001380
+ estimated_mws: 0.011932
+ estimated_mw: 0.001380
thread_name: "RenderThread"
thread_id: 5616
}
task_info {
- estimate_mws: 0.011783
- estimate_mw: 0.001362
+ estimated_mws: 0.011783
+ estimated_mw: 0.001362
thread_name: "AGMIPC@1.0-serv"
process_name: "/vendor/bin/hw/vendor.qti.hardware.AGMIPC@1.0-service"
thread_id: 1454
process_id: 1446
}
task_info {
- estimate_mws: 0.011706
- estimate_mw: 0.001353
+ estimated_mws: 0.011706
+ estimated_mw: 0.001353
thread_name: "binder:2171_2"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2209
process_id: 2171
}
task_info {
- estimate_mws: 0.011675
- estimate_mw: 0.001350
+ estimated_mws: 0.011675
+ estimated_mw: 0.001350
thread_name: "radioext@1.0-se"
process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
thread_id: 714
process_id: 676
}
task_info {
- estimate_mws: 0.011615
- estimate_mw: 0.001343
+ estimated_mws: 0.011615
+ estimated_mw: 0.001343
thread_name: "servicemanager"
thread_id: 5615
}
task_info {
- estimate_mws: 0.011467
- estimate_mw: 0.001326
+ estimated_mws: 0.011467
+ estimated_mw: 0.001326
thread_name: "LocApiMsgTask"
process_name: "/vendor/bin/hw/android.hardware.gnss-aidl-service-qti"
thread_id: 694
process_id: 650
}
task_info {
- estimate_mws: 0.011318
- estimate_mw: 0.001309
+ estimated_mws: 0.011318
+ estimated_mw: 0.001309
thread_name: "LocApiMsgTask"
process_name: "xtra-daemon"
thread_id: 1090
process_id: 1031
}
task_info {
- estimate_mws: 0.011273
- estimate_mw: 0.001303
+ estimated_mws: 0.011273
+ estimated_mw: 0.001303
thread_name: "vndservicemanag"
thread_id: 5614
}
task_info {
- estimate_mws: 0.011024
- estimate_mw: 0.001275
+ estimated_mws: 0.011024
+ estimated_mw: 0.001275
thread_name: "TimerThread"
process_name: "/system/bin/audioserver"
thread_id: 1486
process_id: 740
}
task_info {
- estimate_mws: 0.010869
- estimate_mw: 0.001257
+ estimated_mws: 0.010869
+ estimated_mw: 0.001257
thread_name: "irq/26-4744000."
process_name: "irq/26-4744000.sdhci"
thread_id: 117
process_id: 117
}
task_info {
- estimate_mws: 0.010764
- estimate_mw: 0.001245
+ estimated_mws: 0.010764
+ estimated_mw: 0.001245
thread_name: "SurfaceSyncGrou"
process_name: "com.google.android.wearable.sysui"
thread_id: 1994
process_id: 1926
}
task_info {
- estimate_mws: 0.010731
- estimate_mw: 0.001241
+ estimated_mws: 0.010731
+ estimated_mw: 0.001241
thread_name: "migration/3"
process_name: "migration/3"
thread_id: 40
process_id: 40
}
task_info {
- estimate_mws: 0.010156
- estimate_mw: 0.001174
+ estimated_mws: 0.010156
+ estimated_mw: 0.001174
thread_name: "id.wearable.app"
process_name: "com.google.android.wearable.app"
thread_id: 3857
process_id: 3857
}
task_info {
- estimate_mws: 0.009691
- estimate_mw: 0.001121
+ estimated_mws: 0.009691
+ estimated_mw: 0.001121
thread_name: "ksoftirqd/0"
process_name: "ksoftirqd/0"
thread_id: 13
process_id: 13
}
task_info {
- estimate_mws: 0.009668
- estimate_mw: 0.001118
+ estimated_mws: 0.009668
+ estimated_mw: 0.001118
thread_name: "cnss-daemon"
process_name: "/system/vendor/bin/cnss-daemon"
thread_id: 1052
process_id: 1009
}
task_info {
- estimate_mws: 0.009639
- estimate_mw: 0.001114
+ estimated_mws: 0.009639
+ estimated_mw: 0.001114
thread_name: "kworker/u9:2"
process_name: "kworker/u9:2"
thread_id: 338
process_id: 338
}
task_info {
- estimate_mws: 0.009404
- estimate_mw: 0.001087
+ estimated_mws: 0.009404
+ estimated_mw: 0.001087
thread_name: "binder:2856_3"
process_name: "com.google.android.gms"
thread_id: 3157
process_id: 2856
}
task_info {
- estimate_mws: 0.009364
- estimate_mw: 0.001083
+ estimated_mws: 0.009364
+ estimated_mw: 0.001083
thread_name: "binder:969_3"
process_name: "/system/vendor/bin/cnd"
thread_id: 1013
process_id: 969
}
task_info {
- estimate_mws: 0.008837
- estimate_mw: 0.001022
+ estimated_mws: 0.008837
+ estimated_mw: 0.001022
thread_name: "binder:2118_2"
process_name: ".qtidataservices"
thread_id: 2142
process_id: 2118
}
task_info {
- estimate_mws: 0.008775
- estimate_mw: 0.001015
+ estimated_mws: 0.008775
+ estimated_mw: 0.001015
thread_name: "thermal-engine-"
process_name: "/vendor/bin/thermal-engine-v2"
thread_id: 2520
process_id: 2493
}
task_info {
- estimate_mws: 0.008724
- estimate_mw: 0.001009
+ estimated_mws: 0.008724
+ estimated_mw: 0.001009
thread_name: "binder:2856_7"
process_name: "com.google.android.gms"
thread_id: 4825
process_id: 2856
}
task_info {
- estimate_mws: 0.008275
- estimate_mw: 0.000957
+ estimated_mws: 0.008275
+ estimated_mw: 0.000957
thread_name: "binder:2856_6"
process_name: "com.google.android.gms"
thread_id: 4824
process_id: 2856
}
task_info {
- estimate_mws: 0.008136
- estimate_mw: 0.000941
+ estimated_mws: 0.008136
+ estimated_mw: 0.000941
thread_name: "binder:3857_3"
process_name: "com.google.android.wearable.app"
thread_id: 3872
process_id: 3857
}
task_info {
- estimate_mws: 0.007913
- estimate_mw: 0.000915
+ estimated_mws: 0.007913
+ estimated_mw: 0.000915
thread_name: "binder:975_3"
process_name: "/vendor/bin/imsdaemon"
thread_id: 1630
process_id: 975
}
task_info {
- estimate_mws: 0.007892
- estimate_mw: 0.000913
+ estimated_mws: 0.007892
+ estimated_mw: 0.000913
thread_name: "time_daemon"
process_name: "/vendor/bin/time_daemon"
thread_id: 525
process_id: 522
}
task_info {
- estimate_mws: 0.007750
- estimate_mw: 0.000896
+ estimated_mws: 0.007750
+ estimated_mw: 0.000896
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1044
process_id: 978
}
task_info {
- estimate_mws: 0.007591
- estimate_mw: 0.000878
+ estimated_mws: 0.007591
+ estimated_mw: 0.000878
thread_name: "binder:2856_5"
process_name: "com.google.android.gms"
thread_id: 3681
process_id: 2856
}
task_info {
- estimate_mws: 0.007392
- estimate_mw: 0.000855
+ estimated_mws: 0.007392
+ estimated_mw: 0.000855
thread_name: "binder:2182_1"
process_name: "com.android.phone"
thread_id: 2231
process_id: 2182
}
task_info {
- estimate_mws: 0.007384
- estimate_mw: 0.000854
+ estimated_mws: 0.007384
+ estimated_mw: 0.000854
thread_name: "binder:2171_5"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2638
process_id: 2171
}
task_info {
- estimate_mws: 0.007245
- estimate_mw: 0.000838
+ estimated_mws: 0.007245
+ estimated_mw: 0.000838
thread_name: "qrtr_rx"
process_name: "qrtr_rx"
thread_id: 1556
process_id: 1556
}
task_info {
- estimate_mws: 0.007086
- estimate_mw: 0.000819
+ estimated_mws: 0.007086
+ estimated_mw: 0.000819
thread_name: "radioext@1.0-se"
process_name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
thread_id: 1605
process_id: 676
}
task_info {
- estimate_mws: 0.006850
- estimate_mw: 0.000792
+ estimated_mws: 0.006850
+ estimated_mw: 0.000792
thread_name: "hwservicemanage"
process_name: "/system/system_ext/bin/hwservicemanager"
thread_id: 214
process_id: 214
}
task_info {
- estimate_mws: 0.006731
- estimate_mw: 0.000778
+ estimated_mws: 0.006731
+ estimated_mw: 0.000778
thread_name: "rcub/0"
process_name: "rcub/0"
thread_id: 17
process_id: 17
}
task_info {
- estimate_mws: 0.006663
- estimate_mw: 0.000770
+ estimated_mws: 0.006663
+ estimated_mw: 0.000770
thread_name: "binder:2856_8"
process_name: "com.google.android.gms"
thread_id: 4826
process_id: 2856
}
task_info {
- estimate_mws: 0.006650
- estimate_mw: 0.000769
+ estimated_mws: 0.006650
+ estimated_mw: 0.000769
thread_name: "kthreadd"
process_name: "kthreadd"
thread_id: 2
process_id: 2
}
task_info {
- estimate_mws: 0.006574
- estimate_mw: 0.000760
+ estimated_mws: 0.006574
+ estimated_mw: 0.000760
thread_name: "binder:233_2"
process_name: "/system/bin/vold"
thread_id: 233
process_id: 233
}
task_info {
- estimate_mws: 0.006115
- estimate_mw: 0.000707
+ estimated_mws: 0.006115
+ estimated_mw: 0.000707
thread_name: "cds_ol_rx_threa"
process_name: "cds_ol_rx_thread"
thread_id: 5199
process_id: 5199
}
task_info {
- estimate_mws: 0.005829
- estimate_mw: 0.000674
+ estimated_mws: 0.005829
+ estimated_mw: 0.000674
thread_name: "NsdService"
process_name: "system_server"
thread_id: 1831
process_id: 1302
}
task_info {
- estimate_mws: 0.005734
- estimate_mw: 0.000663
+ estimated_mws: 0.005734
+ estimated_mw: 0.000663
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1367
process_id: 978
}
task_info {
- estimate_mws: 0.005699
- estimate_mw: 0.000659
+ estimated_mws: 0.005699
+ estimated_mw: 0.000659
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 2895
process_id: 1926
}
task_info {
- estimate_mws: 0.005530
- estimate_mw: 0.000639
+ estimated_mws: 0.005530
+ estimated_mw: 0.000639
thread_name: "binder:2856_2"
process_name: "com.google.android.gms"
thread_id: 2903
process_id: 2856
}
task_info {
- estimate_mws: 0.005484
- estimate_mw: 0.000634
+ estimated_mws: 0.005484
+ estimated_mw: 0.000634
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1361
process_id: 978
}
task_info {
- estimate_mws: 0.005366
- estimate_mw: 0.000620
+ estimated_mws: 0.005366
+ estimated_mw: 0.000620
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1357
process_id: 978
}
task_info {
- estimate_mws: 0.005364
- estimate_mw: 0.000620
+ estimated_mws: 0.005364
+ estimated_mw: 0.000620
thread_name: "FileObserver"
process_name: "system_server"
thread_id: 1498
process_id: 1302
}
task_info {
- estimate_mws: 0.005278
- estimate_mw: 0.000610
+ estimated_mws: 0.005278
+ estimated_mw: 0.000610
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1359
process_id: 978
}
task_info {
- estimate_mws: 0.005261
- estimate_mw: 0.000608
+ estimated_mws: 0.005261
+ estimated_mw: 0.000608
thread_name: "Lite Thread #0"
process_name: "com.google.android.wearable.assistant"
thread_id: 4109
process_id: 4038
}
task_info {
- estimate_mws: 0.005011
- estimate_mw: 0.000579
+ estimated_mws: 0.005011
+ estimated_mw: 0.000579
thread_name: "kworker/3:1H"
process_name: "kworker/3:1H"
thread_id: 122
process_id: 122
}
task_info {
- estimate_mws: 0.004996
- estimate_mw: 0.000578
+ estimated_mws: 0.004996
+ estimated_mw: 0.000578
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1365
process_id: 978
}
task_info {
- estimate_mws: 0.004986
- estimate_mw: 0.000576
+ estimated_mws: 0.004986
+ estimated_mw: 0.000576
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 2890
process_id: 1926
}
task_info {
- estimate_mws: 0.004877
- estimate_mw: 0.000564
+ estimated_mws: 0.004877
+ estimated_mw: 0.000564
thread_name: "perfetto_hprof_"
process_name: "com.google.android.gms"
thread_id: 2880
process_id: 2856
}
task_info {
- estimate_mws: 0.004761
- estimate_mw: 0.000550
+ estimated_mws: 0.004761
+ estimated_mw: 0.000550
thread_name: "backup-0"
process_name: "system_server"
thread_id: 2660
process_id: 1302
}
task_info {
- estimate_mws: 0.004757
- estimate_mw: 0.000550
+ estimated_mws: 0.004757
+ estimated_mw: 0.000550
thread_name: "tts-player-0"
process_name: "com.google.android.wearable.assistant"
thread_id: 4209
process_id: 4038
}
task_info {
- estimate_mws: 0.004444
- estimate_mw: 0.000514
+ estimated_mws: 0.004444
+ estimated_mw: 0.000514
thread_name: "Signal Catcher"
process_name: "com.google.android.gms"
thread_id: 2878
process_id: 2856
}
task_info {
- estimate_mws: 0.004331
- estimate_mw: 0.000501
+ estimated_mws: 0.004331
+ estimated_mw: 0.000501
thread_name: "binder:978_2"
process_name: "/system/vendor/bin/nicmd"
thread_id: 1363
process_id: 978
}
task_info {
- estimate_mws: 0.004292
- estimate_mw: 0.000496
+ estimated_mws: 0.004292
+ estimated_mw: 0.000496
thread_name: "f2fs_discard-25"
process_name: "f2fs_discard-254:43"
thread_id: 349
process_id: 349
}
task_info {
- estimate_mws: 0.004283
- estimate_mw: 0.000495
+ estimated_mws: 0.004283
+ estimated_mw: 0.000495
thread_name: "irq/24-glink-na"
process_name: "irq/24-glink-native-rpm-glink"
thread_id: 86
process_id: 86
}
task_info {
- estimate_mws: 0.004252
- estimate_mw: 0.000492
+ estimated_mws: 0.004252
+ estimated_mw: 0.000492
thread_name: "pool-4-thread-1"
process_name: "system_server"
thread_id: 1774
process_id: 1302
}
task_info {
- estimate_mws: 0.004010
- estimate_mw: 0.000464
+ estimated_mws: 0.004010
+ estimated_mw: 0.000464
thread_name: "PasspointProvis"
process_name: "system_server"
thread_id: 1821
process_id: 1302
}
task_info {
- estimate_mws: 0.003934
- estimate_mw: 0.000455
+ estimated_mws: 0.003934
+ estimated_mw: 0.000455
thread_name: "binder:5377_5"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5573
process_id: 5377
}
task_info {
- estimate_mws: 0.003880
- estimate_mw: 0.000449
+ estimated_mws: 0.003880
+ estimated_mw: 0.000449
thread_name: "BG Thread #3"
process_name: "com.google.android.wearable.assistant"
thread_id: 4107
process_id: 4038
}
task_info {
- estimate_mws: 0.003872
- estimate_mw: 0.000448
+ estimated_mws: 0.003872
+ estimated_mw: 0.000448
thread_name: "TransportThread"
process_name: "/vendor/bin/mcu_mgmtd"
thread_id: 3540
process_id: 524
}
task_info {
- estimate_mws: 0.003835
- estimate_mw: 0.000443
+ estimated_mws: 0.003835
+ estimated_mw: 0.000443
thread_name: "kworker/1:1H"
process_name: "kworker/1:1H"
thread_id: 127
process_id: 127
}
task_info {
- estimate_mws: 0.003754
- estimate_mw: 0.000434
+ estimated_mws: 0.003754
+ estimated_mw: 0.000434
thread_name: "watchdog"
process_name: "system_server"
thread_id: 1421
process_id: 1302
}
task_info {
- estimate_mws: 0.003628
- estimate_mw: 0.000419
+ estimated_mws: 0.003628
+ estimated_mw: 0.000419
thread_name: "PackageInstalle"
process_name: "system_server"
thread_id: 1744
process_id: 1302
}
task_info {
- estimate_mws: 0.003469
- estimate_mw: 0.000401
+ estimated_mws: 0.003469
+ estimated_mw: 0.000401
thread_name: "Lite Thread #1"
process_name: "com.google.android.wearable.assistant"
thread_id: 4118
process_id: 4038
}
task_info {
- estimate_mws: 0.003393
- estimate_mw: 0.000392
+ estimated_mws: 0.003393
+ estimated_mw: 0.000392
thread_name: "FinalizerWatchd"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5389
process_id: 5377
}
task_info {
- estimate_mws: 0.003365
- estimate_mw: 0.000389
+ estimated_mws: 0.003365
+ estimated_mw: 0.000389
thread_name: "DFacilitator-1"
process_name: "com.google.android.inputmethod.latin"
thread_id: 5128
process_id: 4997
}
task_info {
- estimate_mws: 0.003243
- estimate_mw: 0.000375
+ estimated_mws: 0.003243
+ estimated_mw: 0.000375
thread_name: "pool-8-thread-1"
process_name: "com.google.android.wearable.healthservices"
thread_id: 3308
process_id: 3028
}
task_info {
- estimate_mws: 0.002873
- estimate_mw: 0.000332
+ estimated_mws: 0.002873
+ estimated_mw: 0.000332
thread_name: "lowpool[1]"
process_name: "com.google.android.gms"
thread_id: 3503
process_id: 2856
}
task_info {
- estimate_mws: 0.002842
- estimate_mw: 0.000329
+ estimated_mws: 0.002842
+ estimated_mw: 0.000329
thread_name: "ReferenceQueueD"
process_name: "com.google.android.gms"
thread_id: 2883
process_id: 2856
}
task_info {
- estimate_mws: 0.002778
- estimate_mw: 0.000321
+ estimated_mws: 0.002778
+ estimated_mw: 0.000321
thread_name: "ipacm-diag"
process_name: "/system/vendor/bin/ipacm-diag"
thread_id: 976
process_id: 976
}
task_info {
- estimate_mws: 0.002739
- estimate_mw: 0.000317
+ estimated_mws: 0.002739
+ estimated_mw: 0.000317
thread_name: "migration/2"
process_name: "migration/2"
thread_id: 32
process_id: 32
}
task_info {
- estimate_mws: 0.002654
- estimate_mw: 0.000307
+ estimated_mws: 0.002654
+ estimated_mw: 0.000307
thread_name: "qrtr_rx"
process_name: "qrtr_rx"
thread_id: 564
process_id: 564
}
task_info {
- estimate_mws: 0.002601
- estimate_mw: 0.000301
+ estimated_mws: 0.002601
+ estimated_mw: 0.000301
thread_name: "card0-crtc0"
process_name: "card0-crtc0"
thread_id: 247
process_id: 247
}
task_info {
- estimate_mws: 0.002574
- estimate_mw: 0.000298
+ estimated_mws: 0.002574
+ estimated_mw: 0.000298
thread_name: "pool-7-thread-3"
process_name: "com.google.android.wearable.healthservices"
thread_id: 5435
process_id: 3028
}
task_info {
- estimate_mws: 0.002458
- estimate_mw: 0.000284
+ estimated_mws: 0.002458
+ estimated_mw: 0.000284
thread_name: "RenderThread"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2319
process_id: 2171
}
task_info {
- estimate_mws: 0.002443
- estimate_mw: 0.000283
+ estimated_mws: 0.002443
+ estimated_mw: 0.000283
thread_name: "highpool[0]"
process_name: "com.google.android.gms"
thread_id: 3154
process_id: 2856
}
task_info {
- estimate_mws: 0.002437
- estimate_mw: 0.000282
+ estimated_mws: 0.002437
+ estimated_mw: 0.000282
thread_name: "lowpool[0]"
process_name: "com.google.android.gms"
thread_id: 3478
process_id: 2856
}
task_info {
- estimate_mws: 0.002241
- estimate_mw: 0.000259
+ estimated_mws: 0.002241
+ estimated_mw: 0.000259
thread_name: "queued-work-loo"
process_name: "com.google.android.gms.persistent"
thread_id: 3533
process_id: 1949
}
task_info {
- estimate_mws: 0.002222
- estimate_mw: 0.000257
+ estimated_mws: 0.002222
+ estimated_mw: 0.000257
thread_name: "arch_disk_io_2"
process_name: "com.google.android.gms"
thread_id: 4174
process_id: 2856
}
task_info {
- estimate_mws: 0.002160
- estimate_mw: 0.000250
+ estimated_mws: 0.002160
+ estimated_mw: 0.000250
thread_name: "binder:523_2"
process_name: "/system/vendor/bin/ipacm"
thread_id: 537
process_id: 523
}
task_info {
- estimate_mws: 0.002129
- estimate_mw: 0.000246
+ estimated_mws: 0.002129
+ estimated_mw: 0.000246
thread_name: "Jit thread pool"
process_name: "com.google.android.gms"
thread_id: 2881
process_id: 2856
}
task_info {
- estimate_mws: 0.002082
- estimate_mw: 0.000241
+ estimated_mws: 0.002082
+ estimated_mw: 0.000241
thread_name: "pool-48-thread-"
process_name: "com.google.android.gms"
thread_id: 4110
process_id: 2856
}
task_info {
- estimate_mws: 0.002071
- estimate_mw: 0.000239
+ estimated_mws: 0.002071
+ estimated_mw: 0.000239
thread_name: "RenderThread"
process_name: "com.google.android.inputmethod.latin"
thread_id: 5120
process_id: 4997
}
task_info {
- estimate_mws: 0.002020
- estimate_mw: 0.000234
+ estimated_mws: 0.002020
+ estimated_mw: 0.000234
thread_name: "arch_disk_io_0"
process_name: "com.google.android.gms"
thread_id: 4031
process_id: 2856
}
task_info {
- estimate_mws: 0.001985
- estimate_mw: 0.000229
+ estimated_mws: 0.001985
+ estimated_mw: 0.000229
thread_name: "arch_disk_io_1"
process_name: "com.google.android.gms"
thread_id: 4034
process_id: 2856
}
task_info {
- estimate_mws: 0.001856
- estimate_mw: 0.000215
+ estimated_mws: 0.001856
+ estimated_mw: 0.000215
thread_name: "BG Thread #0"
process_name: "com.google.android.wearable.assistant"
thread_id: 4061
process_id: 4038
}
task_info {
- estimate_mws: 0.001782
- estimate_mw: 0.000206
+ estimated_mws: 0.001782
+ estimated_mw: 0.000206
thread_name: "binder:1926_1"
process_name: "com.google.android.wearable.sysui"
thread_id: 1938
process_id: 1926
}
task_info {
- estimate_mws: 0.001776
- estimate_mw: 0.000205
+ estimated_mws: 0.001776
+ estimated_mw: 0.000205
thread_name: "highpool[3]"
process_name: "com.google.android.gms"
thread_id: 3470
process_id: 2856
}
task_info {
- estimate_mws: 0.001776
- estimate_mw: 0.000205
+ estimated_mws: 0.001776
+ estimated_mw: 0.000205
thread_name: "migration/0"
process_name: "migration/0"
thread_id: 21
process_id: 21
}
task_info {
- estimate_mws: 0.001772
- estimate_mw: 0.000205
+ estimated_mws: 0.001772
+ estimated_mw: 0.000205
thread_name: "AsyncTask #2"
process_name: "com.google.android.gms"
thread_id: 4164
process_id: 2856
}
task_info {
- estimate_mws: 0.001720
- estimate_mw: 0.000199
+ estimated_mws: 0.001720
+ estimated_mw: 0.000199
thread_name: "arch_disk_io_3"
process_name: "com.google.android.gms"
thread_id: 4175
process_id: 2856
}
task_info {
- estimate_mws: 0.001562
- estimate_mw: 0.000181
+ estimated_mws: 0.001562
+ estimated_mw: 0.000181
thread_name: "POSIX timer 0"
process_name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
thread_id: 850
process_id: 664
}
task_info {
- estimate_mws: 0.001520
- estimate_mw: 0.000176
+ estimated_mws: 0.001520
+ estimated_mw: 0.000176
thread_name: "ksoftirqd/3"
process_name: "ksoftirqd/3"
thread_id: 42
process_id: 42
}
task_info {
- estimate_mws: 0.001401
- estimate_mw: 0.000162
+ estimated_mws: 0.001401
+ estimated_mw: 0.000162
thread_name: "Primes-1"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5394
process_id: 5377
}
task_info {
- estimate_mws: 0.001320
- estimate_mw: 0.000153
+ estimated_mws: 0.001320
+ estimated_mw: 0.000153
thread_name: "binder:5377_3"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5392
process_id: 5377
}
task_info {
- estimate_mws: 0.001316
- estimate_mw: 0.000152
+ estimated_mws: 0.001316
+ estimated_mw: 0.000152
thread_name: "msm-watchdog"
process_name: "msm-watchdog"
thread_id: 76
process_id: 76
}
task_info {
- estimate_mws: 0.001222
- estimate_mw: 0.000141
+ estimated_mws: 0.001222
+ estimated_mw: 0.000141
thread_name: "Lite Thread #1"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5421
process_id: 5377
}
task_info {
- estimate_mws: 0.001220
- estimate_mw: 0.000141
+ estimated_mws: 0.001220
+ estimated_mw: 0.000141
thread_name: "Signal Catcher"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5382
process_id: 5377
}
task_info {
- estimate_mws: 0.001179
- estimate_mw: 0.000136
+ estimated_mws: 0.001179
+ estimated_mw: 0.000136
thread_name: "GoogleApiHandle"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5398
process_id: 5377
}
task_info {
- estimate_mws: 0.001127
- estimate_mw: 0.000130
+ estimated_mws: 0.001127
+ estimated_mw: 0.000130
thread_name: "binder:2171_6"
process_name: "com.google.android.apps.wearable.systemui"
thread_id: 2678
process_id: 2171
}
task_info {
- estimate_mws: 0.001103
- estimate_mw: 0.000128
+ estimated_mws: 0.001103
+ estimated_mw: 0.000128
thread_name: "Blocking Thread"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5574
process_id: 5377
}
task_info {
- estimate_mws: 0.001055
- estimate_mw: 0.000122
+ estimated_mws: 0.001055
+ estimated_mw: 0.000122
thread_name: "WM.task-3"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5430
process_id: 5377
}
task_info {
- estimate_mws: 0.000990
- estimate_mw: 0.000114
+ estimated_mws: 0.000990
+ estimated_mw: 0.000114
thread_name: "highpool[1]"
process_name: "com.google.android.gms"
thread_id: 3373
process_id: 2856
}
task_info {
- estimate_mws: 0.000984
- estimate_mw: 0.000114
+ estimated_mws: 0.000984
+ estimated_mw: 0.000114
thread_name: "Primes-nativecr"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5397
process_id: 5377
}
task_info {
- estimate_mws: 0.000961
- estimate_mw: 0.000111
+ estimated_mws: 0.000961
+ estimated_mw: 0.000111
thread_name: "binder:740_4"
process_name: "/system/bin/audioserver"
thread_id: 2183
process_id: 740
}
task_info {
- estimate_mws: 0.000954
- estimate_mw: 0.000110
+ estimated_mws: 0.000954
+ estimated_mw: 0.000110
thread_name: "Lite Thread #0"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5404
process_id: 5377
}
task_info {
- estimate_mws: 0.000927
- estimate_mw: 0.000107
+ estimated_mws: 0.000927
+ estimated_mw: 0.000107
thread_name: "BG Thread #0"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5395
process_id: 5377
}
task_info {
- estimate_mws: 0.000926
- estimate_mw: 0.000107
+ estimated_mws: 0.000926
+ estimated_mw: 0.000107
thread_name: "FinalizerDaemon"
process_name: "com.google.android.gms"
thread_id: 2885
process_id: 2856
}
task_info {
- estimate_mws: 0.000887
- estimate_mw: 0.000103
+ estimated_mws: 0.000887
+ estimated_mw: 0.000103
thread_name: "BG Thread #1"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5396
process_id: 5377
}
task_info {
- estimate_mws: 0.000865
- estimate_mw: 0.000100
+ estimated_mws: 0.000865
+ estimated_mw: 0.000100
thread_name: "Jit thread pool"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5385
process_id: 5377
}
task_info {
- estimate_mws: 0.000858
- estimate_mw: 0.000099
+ estimated_mws: 0.000858
+ estimated_mw: 0.000099
thread_name: "highpool[2]"
process_name: "com.google.android.gms"
thread_id: 3375
process_id: 2856
}
task_info {
- estimate_mws: 0.000855
- estimate_mw: 0.000099
+ estimated_mws: 0.000855
+ estimated_mw: 0.000099
thread_name: "ConnectivityThr"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5423
process_id: 5377
}
task_info {
- estimate_mws: 0.000828
- estimate_mw: 0.000096
+ estimated_mws: 0.000828
+ estimated_mw: 0.000096
thread_name: "Profile Saver"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5393
process_id: 5377
}
task_info {
- estimate_mws: 0.000808
- estimate_mw: 0.000093
+ estimated_mws: 0.000808
+ estimated_mw: 0.000093
thread_name: "ReferenceQueueD"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5387
process_id: 5377
}
task_info {
- estimate_mws: 0.000803
- estimate_mw: 0.000093
+ estimated_mws: 0.000803
+ estimated_mw: 0.000093
thread_name: "binder:5377_4"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5433
process_id: 5377
}
task_info {
- estimate_mws: 0.000783
- estimate_mw: 0.000091
+ estimated_mws: 0.000783
+ estimated_mw: 0.000091
thread_name: "ksoftirqd/1"
process_name: "ksoftirqd/1"
thread_id: 27
process_id: 27
}
task_info {
- estimate_mws: 0.000782
- estimate_mw: 0.000090
+ estimated_mws: 0.000782
+ estimated_mw: 0.000090
thread_name: "HsConnectionMan"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5422
process_id: 5377
}
task_info {
- estimate_mws: 0.000769
- estimate_mw: 0.000089
+ estimated_mws: 0.000769
+ estimated_mw: 0.000089
thread_name: "ADB-JDWP Connec"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5384
process_id: 5377
}
task_info {
- estimate_mws: 0.000742
- estimate_mw: 0.000086
+ estimated_mws: 0.000742
+ estimated_mw: 0.000086
thread_name: "Scheduler Threa"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5428
process_id: 5377
}
task_info {
- estimate_mws: 0.000733
- estimate_mw: 0.000085
+ estimated_mws: 0.000733
+ estimated_mw: 0.000085
thread_name: "WM.task-2"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5429
process_id: 5377
}
task_info {
- estimate_mws: 0.000730
- estimate_mw: 0.000084
+ estimated_mws: 0.000730
+ estimated_mw: 0.000084
thread_name: "Scheduled BG"
process_name: "com.google.android.wearable.sysui"
thread_id: 2896
process_id: 1926
}
task_info {
- estimate_mws: 0.000727
- estimate_mw: 0.000084
+ estimated_mws: 0.000727
+ estimated_mw: 0.000084
thread_name: "DefaultDispatch"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5431
process_id: 5377
}
task_info {
- estimate_mws: 0.000726
- estimate_mw: 0.000084
+ estimated_mws: 0.000726
+ estimated_mw: 0.000084
thread_name: "BG Thread #3"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5400
process_id: 5377
}
task_info {
- estimate_mws: 0.000724
- estimate_mw: 0.000084
+ estimated_mws: 0.000724
+ estimated_mw: 0.000084
thread_name: "WM.task-1"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5427
process_id: 5377
}
task_info {
- estimate_mws: 0.000718
- estimate_mw: 0.000083
+ estimated_mws: 0.000718
+ estimated_mw: 0.000083
thread_name: "binder:5377_1"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5390
process_id: 5377
}
task_info {
- estimate_mws: 0.000689
- estimate_mw: 0.000080
+ estimated_mws: 0.000689
+ estimated_mw: 0.000080
thread_name: "DefaultDispatch"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5432
process_id: 5377
}
task_info {
- estimate_mws: 0.000669
- estimate_mw: 0.000077
+ estimated_mws: 0.000669
+ estimated_mw: 0.000077
thread_name: "BG Thread #2"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5399
process_id: 5377
}
task_info {
- estimate_mws: 0.000630
- estimate_mw: 0.000073
+ estimated_mws: 0.000630
+ estimated_mw: 0.000073
thread_name: "binder:5377_2"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5391
process_id: 5377
}
task_info {
- estimate_mws: 0.000583
- estimate_mw: 0.000067
+ estimated_mws: 0.000583
+ estimated_mw: 0.000067
thread_name: "perfetto_hprof_"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5383
process_id: 5377
}
task_info {
- estimate_mws: 0.000507
- estimate_mw: 0.000059
+ estimated_mws: 0.000507
+ estimated_mw: 0.000059
thread_name: "Primes-2"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5444
process_id: 5377
}
task_info {
- estimate_mws: 0.000403
- estimate_mw: 0.000047
+ estimated_mws: 0.000403
+ estimated_mw: 0.000047
thread_name: "FinalizerDaemon"
process_name: "com.fitbit.FitbitMobile"
thread_id: 5388
diff --git a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
index d4a0dc4..f77a47a 100644
--- a/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
+++ b/test/trace_processor/diff_tests/parser/chrome/tests_v8.py
@@ -100,3 +100,41 @@
0
"""),
)
+
+ def test_v8_cpu_samples(self):
+ return DiffTestBlueprint(
+ trace=DataPath('v8-samples.pftrace'),
+ query='''
+ include perfetto module callstacks.stack_profile;
+
+ select name, source_file, self_count
+ from _callstacks_for_cpu_profile_stack_samples!(
+ cpu_profile_stack_sample
+ )
+ where self_count > 0
+ order by self_count desc
+ limit 20
+ ''',
+ out=Csv('''
+ "name","source_file","self_count"
+ "(program)","[NULL]",17083
+ "(program)","[NULL]",15399
+ "(program)","[NULL]",9853
+ "(program)","[NULL]",9391
+ "(program)","[NULL]",7299
+ "(program)","[NULL]",5245
+ "(program)","[NULL]",2443
+ "(garbage collector)","[NULL]",107
+ "_.mg","chrome-untrusted://new-tab-page/one-google-bar?paramsencoded=",38
+ "(garbage collector)","[NULL]",34
+ "","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/am=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAEKAAAABR4AAAAgAAAAAAAAAAQIAQDEAQAAAgA4AAAEAQAEABQQAAAKEATgUTYAgAAwAQAIAAAQAAACQAAACAAAAAMAACAIAAAAAKAAAAAAAAAAAAAAAAAAYAABBAAAAAAAAAAAAIACAAAAoAMAAAAAgAAAgIAAANghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=1/ed=1/dg=3/br=1/rs=ACT90oH8sSQRHJq5R0DO9ABVW-vZJa5Baw/ee=ALeJib:B8gLwd;AfeaP:TkrAjf;BMxAGc:E5bFse;BgS6mb:fidj5d;BjwMce:cXX2Wb;CxXAWb:YyRLvc;DULqB:RKfG5c;Dkk6ge:wJqrrd;DpcR3d:zL72xf;EABSZ:MXZt9d;ESrPQc:mNTJvc;EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;EnlcNd:WeHg4;Erl4fe:FloWmf,FloWmf;F9mqte:UoRcbe;Fmv9Nc:O1Tzwc;G0KhTb:LIaoZ;G6wU6e:hezEbd;GleZL:J1A7Od;HMDDWe:G8QUdb;HoYVKb:PkDN7e;HqeXPd:cmbnH;IBADCc:RYquRb;IZrNqe:P8ha2c;IoGlCf:b5lhvb;IsdWVc:qzxzOb;JXS8fb:Qj0suc;JbMT3:M25sS;JsbNhc:Xd8iUd;KOxcK:OZqGte;KQzWid:ZMKkN;KcokUb:KiuZBf;KpRAue:Tia57b;LBgRLc:SdcwHb,XVMNvd;LEikZe:byfTOb,lsjVmc;LXA8b:q7OdKd;LsNahb:ucGLNb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Np8Qkd:Dpx6qc;Nyt6ic:jn2sGd;OgagBe:",33
+ "_.m.Ddb","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/am=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAEKAAAABR4AAAAgAAAAAAAAAAQIAQDEAQAAAgA4AAAEAQAEABQQAAAKEATgUTYAgAAwAQAIAAAQAAACQAAACAAAAAMAACAIAAAAAKAAAAAAAAAAAAAAAAAAYAABBAAAAAAAAAAAAIACAAAAoAMAAAAAgAAAgIAAANghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=1/ed=1/dg=3/br=1/rs=ACT90oH8sSQRHJq5R0DO9ABVW-vZJa5Baw/ee=ALeJib:B8gLwd;AfeaP:TkrAjf;BMxAGc:E5bFse;BgS6mb:fidj5d;BjwMce:cXX2Wb;CxXAWb:YyRLvc;DULqB:RKfG5c;Dkk6ge:wJqrrd;DpcR3d:zL72xf;EABSZ:MXZt9d;ESrPQc:mNTJvc;EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;EnlcNd:WeHg4;Erl4fe:FloWmf,FloWmf;F9mqte:UoRcbe;Fmv9Nc:O1Tzwc;G0KhTb:LIaoZ;G6wU6e:hezEbd;GleZL:J1A7Od;HMDDWe:G8QUdb;HoYVKb:PkDN7e;HqeXPd:cmbnH;IBADCc:RYquRb;IZrNqe:P8ha2c;IoGlCf:b5lhvb;IsdWVc:qzxzOb;JXS8fb:Qj0suc;JbMT3:M25sS;JsbNhc:Xd8iUd;KOxcK:OZqGte;KQzWid:ZMKkN;KcokUb:KiuZBf;KpRAue:Tia57b;LBgRLc:SdcwHb,XVMNvd;LEikZe:byfTOb,lsjVmc;LXA8b:q7OdKd;LsNahb:ucGLNb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Np8Qkd:Dpx6qc;Nyt6ic:jn2sGd;OgagBe:",18
+ "da","https://www.google.com/",15
+ "","https://www.gstatic.com/_/mss/boq-one-google/_/js/k=boq-one-google.OneGoogleWidgetUi.en.Dv_TT86KXl4.es5.O/ck=boq-one-google.OneGoogleWidgetUi.xexmpZqkioA.L.B1.O/am=QKBgwGw/d=1/exm=_b,_tp/excm=_b,_tp,calloutview/ed=1/wt=2/ujg=1/rs=AM-SdHu61g-i-YBZiLcGm3tURf4VJO5hyA/ee=EVNhjf:pw70Gc;EmZ2Bf:zr1jrb;Erl4fe:FloWmf;JsbNhc:Xd8iUd;LBgRLc:SdcwHb;Me32dd:MEeYgc;NPKaK:SdcwHb;NSEoX:lazG7b;Oj465e:KG2eXe;Pjplud:EEDORb;QGR0gd:Mlhmy;SNUn3:ZwDk9d;a56pNe:JEfCwb;cEt90b:ws9Tlc;dIoSBb:SpsfSb;eBAeSb:zbML3c;iFQyKf:QIhFr;io8t5d:yDVVkb;kMFpHd:OTA3Ae;nAFL3:s39S4;oGtAuc:sOXFj;pXdRYb:MdUzUe;qddgKe:xQtZb;sP4Vbe:VwDzFe;uY49fb:COQbmf;ul9GGd:VDovNc;wR5FRb:O1Gjze;xqZiqf:wmnU7d;yxTchf:KUM7Z;zxnPse:GkRiKb/m=ws9Tlc,n73qwf,GkRiKb,e5qFLc,IZT63,UUJqVe,O1Gjze,byfTOb,lsjVmc,xUdipf,OTA3Ae,COQbmf,fKUV3e,aurFic,U0aPgd,ZwDk9d,V3dDOb,mI3LFb,yYB61,O6y8ed,PrPYRd,MpJwZc,LEikZe,NwH0H,OmgaI,lazG7b,XVMNvd,L1AAkb,KUM7Z,Mlhmy,s39S4,lwddkf,gychg,w9hDv,EEDORb,RMhBfe,SdcwHb,aW3pY,pw70Gc,EFQ78c,Ulmmrd,ZfAoz,mdR7q,wmnU7d,xQtZb,JNoxi,kWgXee,MI6k7c,kjKdXe,BVgquf,QIhFr,ov",13
+ "updateAttrs","https://ui.perfetto.dev/v46.0-0a53e685b/frontend_bundle.js",12
+ "","https://www.gstatic.com/_/mss/boq-one-google/_/js/k=boq-one-google.OneGoogleWidgetUi.en.Dv_TT86KXl4.es5.O/am=QKBgwGw/d=1/excm=_b,_tp,calloutview/ed=1/dg=0/wt=2/ujg=1/rs=AM-SdHsuxqEW2z6uUf-9MJvUVpOyFk0ecQ/m=_b,_tp",11
+ "a._isVisible","https://ogs.google.com/widget/callout?prid=19037050&pgid=19037049&puid=6a851fbb7ce797ac&eom=1&cce=1&dc=1&origin=https%3A%2F%2Fwww.google.com&cn=callout&pid=1&spid=538&hl=en&dm=",11
+ "","chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js",11
+ "","https://www.google.com/xjs/_/js/k=xjs.hd.en.nSJdbfIGUiE.O/ck=xjs.hd.F00K1IyvS9A.L.B1.O/am=IFEAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAIAAAEAAAAAAAASAEakAAABZ5sAMBiAAAABAAIAAQIAQDEAQAAAwQ4AAAEAQAUABQREAEKEgTgUTYAhIAwAQQoQAgUQAICQBCFCAAAAAMAACEIDDAMQKgAYBQgAAAAAEBABAAAYAA3BhAgAMAPAAAYAKICAAAhoAMQAAABgAJAgIACAtghAwgAAAQAAACgDwCCB8AghQcAAAAAAAAAAAAAAAKQIJgLCSgIQAAAAAAAAAAAAAAAAACkpIkLCw/d=0/dg=0/br=1/ujg=1/rs=ACT90oFYV6TnvY5P3NcVPbMRvVPRlxmm8A/m=sb_wiz,aa,abd,sytt,syts,sytn,syfx,sytr,sytd,sy101,syz7,syti,syz6,syto,sytq,sytm,syu7,sytb,syu8,syu9,syu0,syu4,sytj,syty,syu1,syu2,sytv,sytw,syte,sytf,sys4,syru,syrs,syrr,syth,syz5,syug,syuh,syuf,async,syvk,ifl,pHXghd,sf,sy1c2,sy1c5,sy4e0,sonic,TxCJfd,sy4e4,qzxzOb,IsdWVc,sy4e6,sy1gs,sy1d4,sy1d0,syrq,syro,syrp,syrn,syrm,sy4cl,sy4co,sy2ib,sy18p,sy18r,sy13l,sy13m,syrj,syrh,syfb,sybv,syby,sybt,sybx,sybw,sycp,spch,sys7,sys6,rtH1bd,sy1ea,sy19r,sy18g,syg9,sy1e9,sy13t,sy1e8,sy18h,sygb,sy1eb,SMquOb,sy8f,sygh,sygf,sygg,sygi,syge,sygp,sygn,sygl,sygd,sycm,sych,syck,syak,syac,syb6,syaj,syai,sya",10
+ "maybeUpdateMoreOptions","chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js",10
+ '''))
diff --git a/test/trace_processor/diff_tests/parser/instruments/tests.py b/test/trace_processor/diff_tests/parser/instruments/tests.py
new file mode 100644
index 0000000..5558ec9
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/instruments/tests.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+# 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 a
+#
+# 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.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+# These diff tests use some locally collected trace.
+class Instruments(TestSuite):
+
+ def test_xml_stacks(self):
+ return DiffTestBlueprint(
+ trace=DataPath('instruments_trace.xml'),
+ query='''
+ WITH
+ child AS (
+ SELECT
+ spc.id AS root,
+ spc.id,
+ spc.parent_id,
+ rel_pc AS path
+ FROM
+ instruments_sample s
+ JOIN stack_profile_callsite spc ON (s.callsite_id = spc.id)
+ JOIN stack_profile_frame f ON (f.id = frame_id)
+ UNION ALL
+ SELECT
+ child.root,
+ parent.id,
+ parent.parent_id,
+ COALESCE(f.rel_pc || ',', '') || child.path AS path
+ FROM
+ child
+ JOIN stack_profile_callsite parent ON (child.parent_id = parent.id)
+ LEFT JOIN stack_profile_frame f ON (f.id = frame_id)
+ )
+ SELECT
+ s.id,
+ s.ts,
+ s.utid,
+ c.path
+ FROM
+ instruments_sample s
+ JOIN child c ON s.callsite_id = c.root
+ WHERE
+ c.parent_id IS NULL
+ ''',
+ out=Csv('''
+ "id","ts","utid","path"
+ 0,175685291,1,"23999,34891,37935,334037"
+ 1,176684208,1,"24307,28687,265407,160467,120123,391295,336787,8955,340991,392555,136711,5707,7603,10507,207839,207495,23655,17383,23211,208391,6225"
+ 2,177685166,1,"24915,16095,15891,32211,91151,26907,87887,60651,28343,29471,30159,11087,36269"
+ 3,178683916,1,"24915,16107,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16021"
+ 4,179687000,1,"24915,16107,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16005"
+ 5,180683708,1,"24915,16107,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16047,16005"
+ '''))
+
+ def test_symbolized_frames(self):
+ return DiffTestBlueprint(
+ trace=DataPath('instruments_trace_with_symbols.zip'),
+ query='''
+ SELECT
+ f.id,
+ m.name,
+ m.build_id,
+ f.rel_pc,
+ s.name,
+ s.source_file,
+ s.line_number
+ FROM
+ stack_profile_frame f
+ JOIN stack_profile_mapping m ON f.mapping = m.id
+ JOIN stack_profile_symbol s ON f.symbol_set_id = s.symbol_set_id
+ ''',
+ out=Csv('''
+ "id","name","build_id","rel_pc","name","source_file","line_number"
+ 26,"/private/tmp/test","c3b3bdbd348730f18f9ddd08b7708d49",16095,"main","/tmp/test.cpp",25
+ 27,"/private/tmp/test","c3b3bdbd348730f18f9ddd08b7708d49",15891,"EmitSignpost()","/tmp/test.cpp",8
+ 38,"/private/tmp/test","c3b3bdbd348730f18f9ddd08b7708d49",16107,"main","/tmp/test.cpp",27
+ 39,"/private/tmp/test","c3b3bdbd348730f18f9ddd08b7708d49",16047,"fib(int)","/tmp/test.cpp",21
+ 40,"/private/tmp/test","c3b3bdbd348730f18f9ddd08b7708d49",16021,"fib(int)","/tmp/test.cpp",22
+ 41,"/private/tmp/test","c3b3bdbd348730f18f9ddd08b7708d49",16005,"fib(int)","/tmp/test.cpp",15
+ '''))
+
+ def test_symbolized_stacks(self):
+ return DiffTestBlueprint(
+ trace=DataPath('instruments_trace_with_symbols.zip'),
+ query='''
+ WITH
+ frame AS (
+ SELECT
+ f.id AS frame_id,
+ COALESCE(s.name || ':' || s.line_number, f.rel_pc) as name
+ FROM
+ stack_profile_frame f
+ LEFT JOIN stack_profile_symbol s USING (symbol_set_id)
+ ),
+ child AS (
+ SELECT
+ spc.id AS root,
+ spc.id,
+ spc.parent_id,
+ name AS path
+ FROM
+ instruments_sample s
+ JOIN stack_profile_callsite spc ON (s.callsite_id = spc.id)
+ LEFT JOIN frame f USING (frame_id)
+ UNION ALL
+ SELECT
+ child.root,
+ parent.id,
+ parent.parent_id,
+ COALESCE(f.name || ',', '') || child.path AS path
+ FROM
+ child
+ JOIN stack_profile_callsite parent ON (child.parent_id = parent.id)
+ LEFT JOIN frame f USING (frame_id)
+ )
+ SELECT
+ s.id,
+ s.ts,
+ s.utid,
+ c.path
+ FROM
+ instruments_sample s
+ JOIN child c ON s.callsite_id = c.root
+ WHERE
+ c.parent_id IS NULL
+ ''',
+ out=Csv('''
+ "id","ts","utid","path"
+ 0,175685291,1,"23999,34891,37935,334037"
+ 1,176684208,1,"24307,28687,265407,160467,120123,391295,336787,8955,340991,392555,136711,5707,7603,10507,207839,207495,23655,17383,23211,208391,6225"
+ 2,177685166,1,"24915,main:25,EmitSignpost():8,32211,91151,26907,87887,60651,28343,29471,30159,11087,36269"
+ 3,178683916,1,"24915,main:27,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):22"
+ 4,179687000,1,"24915,main:27,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):15"
+ 5,180683708,1,"24915,main:27,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):21,fib(int):15"
+ '''))
diff --git a/test/trace_processor/diff_tests/stdlib/linux/cpu.py b/test/trace_processor/diff_tests/stdlib/linux/cpu.py
index 6dfe8bd..5251151 100644
--- a/test/trace_processor/diff_tests/stdlib/linux/cpu.py
+++ b/test/trace_processor/diff_tests/stdlib/linux/cpu.py
@@ -323,3 +323,58 @@
"cpu","state","count","dur","avg_dur","idle_percent"
0,2,2,2000000,1000000,40.000000
"""))
+
+ def test_linux_cpu_idle_time_in_state(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ sys_stats {
+ cpuidle_state {
+ cpu_id: 0
+ cpuidle_state_entry {
+ state: "C8"
+ duration_us: 1000000
+ }
+ }
+ }
+ timestamp: 200000000000
+ trusted_packet_sequence_id: 2
+ }
+ packet {
+ sys_stats {
+ cpuidle_state {
+ cpu_id: 0
+ cpuidle_state_entry {
+ state: "C8"
+ duration_us: 1000100
+ }
+ }
+ }
+ timestamp: 200001000000
+ trusted_packet_sequence_id: 2
+ }
+ packet {
+ sys_stats {
+ cpuidle_state {
+ cpu_id: 0
+ cpuidle_state_entry {
+ state: "C8"
+ duration_us: 1000200
+ }
+ }
+ }
+ timestamp: 200002000000
+ trusted_packet_sequence_id: 2
+ }
+ """),
+ query="""
+ INCLUDE PERFETTO MODULE linux.cpu.idle_time_in_state;
+ SELECT * FROM cpu_idle_time_in_state_counters;
+ """,
+ out=Csv("""
+ "ts","state_name","idle_percentage","total_residency","time_slice"
+ 200001000000,"cpuidle.C8",10.000000,100.000000,1000
+ 200002000000,"cpuidle.C8",10.000000,100.000000,1000
+ 200001000000,"cpuidle.C0",90.000000,900.000000,1000
+ 200002000000,"cpuidle.C0",90.000000,900.000000,1000
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
index 69d787a..1ddead7 100644
--- a/test/trace_processor/diff_tests/stdlib/wattson/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -362,7 +362,7 @@
query=("""
INCLUDE PERFETTO MODULE wattson.curves.idle_attribution;
SELECT
- SUM(estimate_mw * dur) / 1000000000 as idle_transition_cost_mws,
+ SUM(estimated_mw * dur) / 1000000000 as idle_transition_cost_mws,
utid,
upid
FROM _idle_transition_cost
diff --git a/tools/gen_amalgamated_sql.py b/tools/gen_amalgamated_sql.py
index 17fbe1e..ac7a530 100755
--- a/tools/gen_amalgamated_sql.py
+++ b/tools/gen_amalgamated_sql.py
@@ -66,8 +66,10 @@
def filename_to_variable(filename: str):
- return "k" + "".join(
- [x.capitalize() for x in filename.replace(os.path.sep, '_').split("_")])
+ return "k" + "".join([
+ x.capitalize()
+ for x in filename.replace(os.path.sep, '_').replace('-', '_').split("_")
+ ])
def main():
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 9dbeac7..032b38a 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -382,6 +382,16 @@
module.shared_libs.add('libz')
+def enable_expat(module):
+ if module.type == 'cc_binary_host':
+ module.static_libs.add('libexpat')
+ elif module.host_supported:
+ module.android.shared_libs.add('libexpat')
+ module.host.static_libs.add('libexpat')
+ else:
+ module.shared_libs.add('libexpat')
+
+
def enable_uapi_headers(module):
module.include_dirs.add('bionic/libc/kernel')
@@ -417,6 +427,8 @@
enable_sqlite,
'//gn:zlib':
enable_zlib,
+ '//gn:expat':
+ enable_expat,
'//gn:bionic_kernel_uapi_headers':
enable_uapi_headers,
'//src/profiling/memory:bionic_libc_platform_headers_on_android':
diff --git a/tools/gen_bazel b/tools/gen_bazel
index ec48704..4f9df8f 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -144,6 +144,7 @@
external_deps = {
'//gn:default_deps': [],
'//gn:base_platform': ['PERFETTO_CONFIG.deps.base_platform'],
+ '//gn:expat': ['PERFETTO_CONFIG.deps.expat'],
'//gn:jsoncpp': ['PERFETTO_CONFIG.deps.jsoncpp'],
'//gn:linenoise': ['PERFETTO_CONFIG.deps.linenoise'],
'//gn:protobuf_full': ['PERFETTO_CONFIG.deps.protobuf_full'],
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 771f43a..7b2b8d7 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -257,6 +257,15 @@
'all',
'all'),
+ # Libexpat for Instruments XML import.
+ # If updating the version, also update bazel/deps.bzl.
+ Dependency(
+ 'buildtools/expat/src',
+ 'https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git',
+ 'fa75b96546c069d17b8f80d91e0f4ef0cde3790d', # refs/tags/upstream/R_2_6_2.
+ 'all',
+ 'all'),
+
# Archive with only the demangling sources from llvm-project.
# See tools/repackage_llvm_demangler.sh on how to update this.
# File suffix is the git reference to the commit at which we rearchived the
diff --git a/ui/build.js b/ui/build.js
index 2df02c3..2e6269e 100644
--- a/ui/build.js
+++ b/ui/build.js
@@ -575,9 +575,10 @@
}
function startServer() {
+ const host = cfg.httpServerListenHost == '127.0.0.1' ? 'localhost' : cfg.httpServerListenHost;
console.log(
'Starting HTTP server on',
- `http://${cfg.httpServerListenHost}:${cfg.httpServerListenPort}`);
+ `http://${host}:${cfg.httpServerListenPort}`);
http.createServer(function(req, res) {
console.debug(req.method, req.url);
let uri = req.url.split('?', 1)[0];
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 75a3d6b..23336d5 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
"channels": [
{
"name": "stable",
- "rev": "206f403988fb603720111dcabb14f38f6ebc1a54"
+ "rev": "0a53e685b5a2744119b2a7f86cb98bd6c5674480"
},
{
"name": "canary",
- "rev": "f098f373db79c554c96aaecb68997a798ee4e86f"
+ "rev": "2a491b8cca83a103a80335acc048f6a4a882cb49"
},
{
"name": "autopush",
diff --git a/ui/src/assets/widgets/popup.scss b/ui/src/assets/widgets/popup.scss
index 6d26981..6d18d47 100644
--- a/ui/src/assets/widgets/popup.scss
+++ b/ui/src/assets/widgets/popup.scss
@@ -21,6 +21,13 @@
// When width = 0 it can cause layout issues in popup content, so we give this
// element some width manually
width: 100%;
+
+ // Move the portal to the top of the page. This appears to fix issues where
+ // popups can sometimes be rendered below the rest of the page momentarily,
+ // causing the whole page to judder up and down while popper.js sorts out the
+ // positioning.
+ // TODO(stevegolton): There is probably a better way to fix this issue.
+ top: 0;
}
.pf-popup {
diff --git a/ui/src/base/fuzzy.ts b/ui/src/base/fuzzy.ts
index 7bc00a1..bd15658 100644
--- a/ui/src/base/fuzzy.ts
+++ b/ui/src/base/fuzzy.ts
@@ -26,14 +26,14 @@
// Finds approx matching in arbitrary lists of items.
export class FuzzyFinder<T> {
- private items: T[];
- private keyLookup: KeyLookup<T>;
+ private readonly items: ReadonlyArray<T>;
+ private readonly keyLookup: KeyLookup<T>;
// Because we operate on arbitrary lists, a key lookup function is required to
// so we know which part of the list is to be be searched. It should return
// the relevant search string for each item.
- constructor(items: ArrayLike<T>, keyLookup: KeyLookup<T>) {
- this.items = Array.from(items);
+ constructor(items: ReadonlyArray<T>, keyLookup: KeyLookup<T>) {
+ this.items = items;
this.keyLookup = keyLookup;
}
diff --git a/ui/src/base/hotkeys.ts b/ui/src/base/hotkeys.ts
index 2dec16c..aad5bd6 100644
--- a/ui/src/base/hotkeys.ts
+++ b/ui/src/base/hotkeys.ts
@@ -281,3 +281,26 @@
export function getPlatform(): Platform {
return window.navigator.platform.indexOf('Mac') !== -1 ? 'Mac' : 'PC';
}
+
+// Returns a cross-platform check for whether the event has "Mod" key pressed
+// (e.g. as a part of Mod-Click UX pattern).
+// On Mac, Mod-click is actually Command-click and on PC it's Control-click,
+// so this function handles this for all platforms.
+export function hasModKey(event: {
+ readonly metaKey: boolean;
+ readonly ctrlKey: boolean;
+}): boolean {
+ if (getPlatform() === 'Mac') {
+ return event.metaKey;
+ } else {
+ return event.ctrlKey;
+ }
+}
+
+export function modKey(): {metaKey?: boolean; ctrlKey?: boolean} {
+ if (getPlatform() === 'Mac') {
+ return {metaKey: true};
+ } else {
+ return {ctrlKey: true};
+ }
+}
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 51e765d..577ae09 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,7 +15,7 @@
import {Draft} from 'immer';
import {SortDirection} from '../base/comparison_utils';
-import {assertExists, assertTrue} from '../base/logging';
+import {assertTrue} from '../base/logging';
import {duration, time} from '../base/time';
import {RecordConfig} from '../controller/record_config_types';
import {randomColor} from '../core/colorizer';
@@ -45,6 +45,7 @@
} from './metatracing';
import {
AdbRecordingTarget,
+ Area,
EngineMode,
LoadedConfig,
NewEngineMode,
@@ -52,28 +53,14 @@
OmniboxState,
PendingDeeplinkState,
PivotTableResult,
- PrimaryTrackSortKey,
ProfileType,
RecordingTarget,
- SCROLLING_TRACK_GROUP,
State,
Status,
- ThreadTrackSortKey,
- TrackSortKey,
- UtidToTrackSortKey,
} from './state';
type StateDraft = Draft<State>;
-export interface AddTrackArgs {
- key?: string;
- uri: string;
- name: string;
- trackSortKey: TrackSortKey;
- trackGroup?: string;
- closeable?: boolean;
-}
-
export interface PostedTrace {
buffer: ArrayBuffer;
title: string;
@@ -127,32 +114,6 @@
return nextId;
}
-// A helper to clean the state for a given removeable track.
-// This is not exported as action to make it clear that not all
-// tracks are removeable.
-function removeTrack(state: StateDraft, trackKey: string) {
- const track = state.tracks[trackKey];
- if (track === undefined) {
- return;
- }
- delete state.tracks[trackKey];
-
- const removeTrackId = (arr: string[]) => {
- const index = arr.indexOf(trackKey);
- if (index !== -1) arr.splice(index, 1);
- };
-
- if (track.trackGroup === SCROLLING_TRACK_GROUP) {
- removeTrackId(state.scrollingTracks);
- } else if (track.trackGroup !== undefined) {
- const trackGroup = state.trackGroups[track.trackGroup];
- if (trackGroup !== undefined) {
- removeTrackId(trackGroup.tracks);
- }
- }
- state.pinnedTracks = state.pinnedTracks.filter((key) => key !== trackKey);
-}
-
let statusTraceEvent: TraceEventScope | undefined;
export const StateActions = {
@@ -200,126 +161,6 @@
state.traceUuid = args.traceUuid;
},
- addTracks(state: StateDraft, args: {tracks: AddTrackArgs[]}) {
- args.tracks.forEach((track) => {
- const trackKey =
- track.key === undefined ? generateNextId(state) : track.key;
- const name = track.name;
- state.tracks[trackKey] = {
- key: trackKey,
- name,
- trackSortKey: track.trackSortKey,
- trackGroup: track.trackGroup,
- uri: track.uri,
- closeable: track.closeable,
- };
- if (track.trackGroup === SCROLLING_TRACK_GROUP) {
- state.scrollingTracks.push(trackKey);
- } else if (track.trackGroup !== undefined) {
- const group = state.trackGroups[track.trackGroup];
- if (group !== undefined) {
- group.tracks.push(trackKey);
- }
- }
- });
- },
-
- // Note: While this action has traditionally been omitted, with more and more
- // dynamic tracks being added and existing ones being moved to plugins, it
- // makes sense to have a generic "removeTracks" action which is un-opinionated
- // about what type of tracks we are removing.
- // E.g. Once debug tracks have been moved to a plugin, it makes no sense to
- // keep the "removeDebugTrack()" action, as the core should have no concept of
- // what debug tracks are.
- removeTracks(state: StateDraft, args: {trackKeys: string[]}) {
- for (const trackKey of args.trackKeys) {
- removeTrack(state, trackKey);
- }
- },
-
- setUtidToTrackSortKey(
- state: StateDraft,
- args: {threadOrderingMetadata: UtidToTrackSortKey},
- ) {
- state.utidToThreadSortKey = args.threadOrderingMetadata;
- },
-
- addTrack(state: StateDraft, args: AddTrackArgs): void {
- this.addTracks(state, {tracks: [args]});
- },
-
- addTrackGroup(
- state: StateDraft,
- // Define ID in action so a track group can be referred to without running
- // the reducer.
- args: {
- name: string;
- key: string;
- summaryTrackKey?: string;
- collapsed: boolean;
- fixedOrdering?: boolean;
- },
- ): void {
- state.trackGroups[args.key] = {
- name: args.name,
- key: args.key,
- collapsed: args.collapsed,
- tracks: [],
- summaryTrack: args.summaryTrackKey,
- fixedOrdering: args.fixedOrdering,
- };
- },
-
- maybeExpandOnlyTrackGroup(state: StateDraft, _: {}): void {
- const trackGroups = Object.values(state.trackGroups);
- if (trackGroups.length === 1) {
- trackGroups[0].collapsed = false;
- }
- },
-
- sortThreadTracks(state: StateDraft, _: {}) {
- const getFullKey = (a: string) => {
- const track = state.tracks[a];
- const threadTrackSortKey = track.trackSortKey as ThreadTrackSortKey;
- if (threadTrackSortKey.utid === undefined) {
- const sortKey = track.trackSortKey as PrimaryTrackSortKey;
- return [sortKey, 0, 0, 0];
- }
- const threadSortKey = state.utidToThreadSortKey[threadTrackSortKey.utid];
- return [
- /* eslint-disable @typescript-eslint/strict-boolean-expressions */
- threadSortKey
- ? threadSortKey.sortKey
- : PrimaryTrackSortKey.ORDINARY_THREAD,
- threadSortKey && threadSortKey.tid !== undefined
- ? threadSortKey.tid
- : Number.MAX_VALUE,
- /* eslint-enable */
- threadTrackSortKey.utid,
- threadTrackSortKey.priority,
- ];
- };
-
- // Use a numeric collator so threads are sorted as T1, T2, ..., T10, T11,
- // rather than T1, T10, T11, ..., T2, T20, T21 .
- const coll = new Intl.Collator([], {sensitivity: 'base', numeric: true});
- for (const group of Object.values(state.trackGroups)) {
- if (group.fixedOrdering) continue;
-
- group.tracks.sort((a: string, b: string) => {
- const aRank = getFullKey(a);
- const bRank = getFullKey(b);
- for (let i = 0; i < aRank.length; i++) {
- if (aRank[i] !== bRank[i]) return aRank[i] - bRank[i];
- }
-
- const aName = state.tracks[a].name.toLocaleLowerCase();
- const bName = state.tracks[b].name.toLocaleLowerCase();
- return coll.compare(aName, bName);
- });
- }
- },
-
updateAggregateSorting(
state: StateDraft,
args: {id: string; column: string},
@@ -349,57 +190,6 @@
}
},
- moveTrack(
- state: StateDraft,
- args: {srcId: string; op: 'before' | 'after'; dstId: string},
- ): void {
- const moveWithinTrackList = (trackList: string[]) => {
- const newList: string[] = [];
- for (let i = 0; i < trackList.length; i++) {
- const curTrackId = trackList[i];
- if (curTrackId === args.dstId && args.op === 'before') {
- newList.push(args.srcId);
- }
- if (curTrackId !== args.srcId) {
- newList.push(curTrackId);
- }
- if (curTrackId === args.dstId && args.op === 'after') {
- newList.push(args.srcId);
- }
- }
- trackList.splice(0);
- newList.forEach((x) => {
- trackList.push(x);
- });
- };
-
- moveWithinTrackList(state.pinnedTracks);
- moveWithinTrackList(state.scrollingTracks);
- },
-
- toggleTrackPinned(state: StateDraft, args: {trackKey: string}): void {
- const key = args.trackKey;
- const isPinned = state.pinnedTracks.includes(key);
- const trackGroup = assertExists(state.tracks[key]).trackGroup;
-
- if (isPinned) {
- state.pinnedTracks.splice(state.pinnedTracks.indexOf(key), 1);
- if (trackGroup === SCROLLING_TRACK_GROUP) {
- state.scrollingTracks.unshift(key);
- }
- } else {
- if (trackGroup === SCROLLING_TRACK_GROUP) {
- state.scrollingTracks.splice(state.scrollingTracks.indexOf(key), 1);
- }
- state.pinnedTracks.push(key);
- }
- },
-
- toggleTrackGroupCollapsed(state: StateDraft, args: {groupKey: string}): void {
- const trackGroup = assertExists(state.trackGroups[args.groupKey]);
- trackGroup.collapsed = !trackGroup.collapsed;
- },
-
requestTrackReload(state: StateDraft, _: {}) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (state.lastTrackReloadRequest) {
@@ -605,14 +395,14 @@
selectSlice(
state: StateDraft,
- args: {id: number; trackKey: string; table?: string; scroll?: boolean},
+ args: {id: number; trackUri: string; table?: string; scroll?: boolean},
): void {
state.selection = {
kind: 'legacy',
legacySelection: {
kind: 'SLICE',
id: args.id,
- trackKey: args.trackKey,
+ trackUri: args.trackUri,
table: args.table,
},
};
@@ -626,7 +416,7 @@
sqlTableName: string;
start: time;
duration: duration;
- trackKey: string;
+ trackUri: string;
detailsPanelConfig: {
kind: string;
config: GenericSliceDetailsTabConfigBase;
@@ -646,7 +436,7 @@
sqlTableName: args.sqlTableName,
start: args.start,
duration: args.duration,
- trackKey: args.trackKey,
+ trackUri: args.trackUri,
detailsPanelConfig: {
kind: args.detailsPanelConfig.kind,
config: detailsPanelConfig,
@@ -665,14 +455,14 @@
selectThreadState(
state: StateDraft,
- args: {id: number; trackKey: string},
+ args: {id: number; trackUri: string},
): void {
state.selection = {
kind: 'legacy',
legacySelection: {
kind: 'THREAD_STATE',
id: args.id,
- trackKey: args.trackKey,
+ trackUri: args.trackUri,
},
};
},
@@ -719,57 +509,50 @@
state.omniboxState.mode = args.mode;
},
- selectArea(
- state: StateDraft,
- args: {start: time; end: time; tracks: string[]},
- ): void {
- const {start, end, tracks} = args;
+ selectArea(state: StateDraft, args: Area): void {
+ const {start, end} = args;
assertTrue(start <= end);
state.selection = {
kind: 'area',
- start,
- end,
- tracks,
+ ...args,
};
},
- toggleTrackSelection(
- state: StateDraft,
- args: {key: string; isTrackGroup: boolean},
- ) {
+ toggleTrackAreaSelection(state: StateDraft, args: {key: string}) {
const selection = state.selection;
if (selection.kind !== 'area') {
return;
}
- const index = selection.tracks.indexOf(args.key);
- if (index > -1) {
- selection.tracks.splice(index, 1);
- if (args.isTrackGroup) {
- // Also remove all child tracks.
- for (const childTrack of state.trackGroups[args.key].tracks) {
- const childIndex = selection.tracks.indexOf(childTrack);
- if (childIndex > -1) {
- selection.tracks.splice(childIndex, 1);
- }
- }
- }
+ if (!selection.trackUris.includes(args.key)) {
+ selection.trackUris.push(args.key);
} else {
- selection.tracks.push(args.key);
- if (args.isTrackGroup) {
- // Also add all child tracks.
- for (const childTrack of state.trackGroups[args.key].tracks) {
- if (!selection.tracks.includes(childTrack)) {
- selection.tracks.push(childTrack);
- }
- }
- }
+ selection.trackUris = selection.trackUris.filter((t) => t !== args.key);
}
- // It's super unexpected that |toggleTrackSelection| does not cause
- // selection to be updated and this leads to bugs for people who do:
- // if (oldSelection !== state.selection) etc.
- // To solve this re-create the selection object here:
- state.selection = Object.assign({}, state.selection);
+ },
+
+ toggleGroupAreaSelection(state: StateDraft, args: {trackUris: string[]}) {
+ const currentSelection = state.selection;
+ if (currentSelection.kind !== 'area') {
+ return;
+ }
+
+ const allTracksSelected = args.trackUris.every((t) =>
+ currentSelection.trackUris.includes(t),
+ );
+
+ if (allTracksSelected) {
+ // Deselect all tracks in the list
+ currentSelection.trackUris = currentSelection.trackUris.filter(
+ (t) => !args.trackUris.includes(t),
+ );
+ } else {
+ args.trackUris.forEach((t) => {
+ if (!currentSelection.trackUris.includes(t)) {
+ currentSelection.trackUris.push(t);
+ }
+ });
+ }
},
setChromeCategories(state: StateDraft, args: {categories: string[]}): void {
@@ -868,18 +651,7 @@
}
},
- clearAllPinnedTracks(state: StateDraft, _: {}) {
- const pinnedTracks = state.pinnedTracks.slice();
- for (let index = pinnedTracks.length - 1; index >= 0; index--) {
- const trackKey = pinnedTracks[index];
- this.toggleTrackPinned(state, {trackKey});
- }
- },
-
- togglePivotTable(
- state: StateDraft,
- args: {area?: {start: time; end: time; tracks: string[]}},
- ) {
+ togglePivotTable(state: StateDraft, args: {area?: Area}) {
state.nonSerializableState.pivotTable.selectionArea = args.area;
state.nonSerializableState.pivotTable.queryResult = null;
},
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 9289d09..26e65ae 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -15,242 +15,10 @@
import {produce} from 'immer';
import {assertExists} from '../base/logging';
-import {PrimaryTrackSortKey} from '../public';
-import {PROCESS_SCHEDULING_TRACK_KIND} from '../core_plugins/process_summary/process_scheduling_track';
import {StateActions} from './actions';
import {createEmptyState} from './empty_state';
-import {
- InThreadTrackSortKey,
- SCROLLING_TRACK_GROUP,
- State,
- TraceUrlSource,
- TrackSortKey,
-} from './state';
-import {
- HEAP_PROFILE_TRACK_KIND,
- THREAD_SLICE_TRACK_KIND,
- THREAD_STATE_TRACK_KIND,
-} from '../core/track_kinds';
-
-function fakeTrack(
- state: State,
- args: {
- key: string;
- uri?: string;
- trackGroup?: string;
- trackSortKey?: TrackSortKey;
- name?: string;
- tid?: string;
- },
-): State {
- return produce(state, (draft) => {
- StateActions.addTrack(draft, {
- uri: args.uri ?? 'sometrack',
- key: args.key,
- name: args.name ?? 'A track',
- trackSortKey:
- args.trackSortKey === undefined
- ? PrimaryTrackSortKey.ORDINARY_TRACK
- : args.trackSortKey,
- trackGroup: args.trackGroup ?? SCROLLING_TRACK_GROUP,
- });
- });
-}
-
-function fakeTrackGroup(
- state: State,
- args: {key: string; summaryTrackKey: string},
-): State {
- return produce(state, (draft) => {
- StateActions.addTrackGroup(draft, {
- name: 'A group',
- key: args.key,
- collapsed: false,
- summaryTrackKey: args.summaryTrackKey,
- });
- });
-}
-
-function pinnedAndScrollingTracks(
- state: State,
- keys: string[],
- pinnedTracks: string[],
- scrollingTracks: string[],
-): State {
- for (const key of keys) {
- state = fakeTrack(state, {key});
- }
- state = produce(state, (draft) => {
- draft.pinnedTracks = pinnedTracks;
- draft.scrollingTracks = scrollingTracks;
- });
- return state;
-}
-
-test('add scrolling tracks', () => {
- const once = produce(createEmptyState(), (draft) => {
- StateActions.addTrack(draft, {
- uri: 'cpu',
- name: 'Cpu 1',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- });
- });
- const twice = produce(once, (draft) => {
- StateActions.addTrack(draft, {
- uri: 'cpu',
- name: 'Cpu 2',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- });
- });
-
- expect(Object.values(twice.tracks).length).toBe(2);
- expect(twice.scrollingTracks.length).toBe(2);
-});
-
-test('add track to track group', () => {
- let state = createEmptyState();
- state = fakeTrack(state, {key: 's'});
-
- const afterGroup = produce(state, (draft) => {
- StateActions.addTrackGroup(draft, {
- name: 'A track group',
- key: '123-123-123',
- summaryTrackKey: 's',
- collapsed: false,
- });
- });
-
- const afterTrackAdd = produce(afterGroup, (draft) => {
- StateActions.addTrack(draft, {
- key: '1',
- uri: 'slices',
- name: 'renderer 1',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: '123-123-123',
- });
- });
-
- expect(afterTrackAdd.trackGroups['123-123-123'].summaryTrack).toBe('s');
- expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('1');
-});
-
-test('reorder tracks', () => {
- const once = produce(createEmptyState(), (draft) => {
- StateActions.addTrack(draft, {
- uri: 'cpu',
- name: 'Cpu 1',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- });
- StateActions.addTrack(draft, {
- uri: 'cpu',
- name: 'Cpu 2',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- });
- });
-
- const firstTrackKey = once.scrollingTracks[0];
- const secondTrackKey = once.scrollingTracks[1];
-
- const twice = produce(once, (draft) => {
- StateActions.moveTrack(draft, {
- srcId: `${firstTrackKey}`,
- op: 'after',
- dstId: `${secondTrackKey}`,
- });
- });
-
- expect(twice.scrollingTracks[0]).toBe(secondTrackKey);
- expect(twice.scrollingTracks[1]).toBe(firstTrackKey);
-});
-
-test('reorder pinned to scrolling', () => {
- let state = createEmptyState();
- state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']);
-
- const after = produce(state, (draft) => {
- StateActions.moveTrack(draft, {
- srcId: 'b',
- op: 'before',
- dstId: 'c',
- });
- });
-
- expect(after.pinnedTracks).toEqual(['a']);
- expect(after.scrollingTracks).toEqual(['b', 'c']);
-});
-
-test('reorder scrolling to pinned', () => {
- let state = createEmptyState();
- state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']);
-
- const after = produce(state, (draft) => {
- StateActions.moveTrack(draft, {
- srcId: 'b',
- op: 'after',
- dstId: 'a',
- });
- });
-
- expect(after.pinnedTracks).toEqual(['a', 'b']);
- expect(after.scrollingTracks).toEqual(['c']);
-});
-
-test('reorder clamp bottom', () => {
- let state = createEmptyState();
- state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']);
-
- const after = produce(state, (draft) => {
- StateActions.moveTrack(draft, {
- srcId: 'a',
- op: 'before',
- dstId: 'a',
- });
- });
- expect(after).toEqual(state);
-});
-
-test('reorder clamp top', () => {
- let state = createEmptyState();
- state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']);
-
- const after = produce(state, (draft) => {
- StateActions.moveTrack(draft, {
- srcId: 'c',
- op: 'after',
- dstId: 'c',
- });
- });
- expect(after).toEqual(state);
-});
-
-test('pin', () => {
- let state = createEmptyState();
- state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']);
-
- const after = produce(state, (draft) => {
- StateActions.toggleTrackPinned(draft, {
- trackKey: 'c',
- });
- });
- expect(after.pinnedTracks).toEqual(['a', 'c']);
- expect(after.scrollingTracks).toEqual(['b']);
-});
-
-test('unpin', () => {
- let state = createEmptyState();
- state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']);
-
- const after = produce(state, (draft) => {
- StateActions.toggleTrackPinned(draft, {
- trackKey: 'a',
- });
- });
- expect(after.pinnedTracks).toEqual(['b']);
- expect(after.scrollingTracks).toEqual(['a', 'c']);
-});
+import {TraceUrlSource} from './state';
test('open trace', () => {
const state = createEmptyState();
@@ -275,15 +43,7 @@
});
});
- const twice = produce(once, (draft) => {
- StateActions.addTrack(draft, {
- uri: 'cpu',
- name: 'Cpu 1',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- });
- });
-
- const thrice = produce(twice, (draft) => {
+ const thrice = produce(once, (draft) => {
StateActions.openTraceFromUrl(draft, {
url: 'https://example.com/foo',
});
@@ -293,8 +53,6 @@
expect((thrice.engine!!.source as TraceUrlSource).url).toBe(
'https://example.com/foo',
);
- expect(thrice.pinnedTracks.length).toBe(0);
- expect(thrice.scrollingTracks.length).toBe(0);
});
test('setEngineReady with missing engine is ignored', () => {
@@ -323,137 +81,3 @@
});
expect(after.engine!!.ready).toBe(true);
});
-
-test('sortTracksByPriority', () => {
- let state = createEmptyState();
- state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'});
- state = fakeTrack(state, {
- key: 'b',
- uri: HEAP_PROFILE_TRACK_KIND,
- trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
- trackGroup: 'g',
- });
- state = fakeTrack(state, {
- key: 'a',
- uri: PROCESS_SCHEDULING_TRACK_KIND,
- trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK,
- trackGroup: 'g',
- });
-
- const after = produce(state, (draft) => {
- StateActions.sortThreadTracks(draft, {});
- });
-
- // High Priority tracks should be sorted before Low Priority tracks:
- // 'b' appears twice because it's the summary track
- expect(after.trackGroups['g'].tracks).toEqual(['a', 'b']);
-});
-
-test('sortTracksByPriorityAndKindAndName', () => {
- let state = createEmptyState();
- state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'});
- state = fakeTrack(state, {
- key: 'a',
- uri: PROCESS_SCHEDULING_TRACK_KIND,
- trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK,
- trackGroup: 'g',
- });
- state = fakeTrack(state, {
- key: 'b',
- uri: THREAD_SLICE_TRACK_KIND,
- trackGroup: 'g',
- trackSortKey: PrimaryTrackSortKey.MAIN_THREAD,
- });
- state = fakeTrack(state, {
- key: 'c',
- uri: THREAD_SLICE_TRACK_KIND,
- trackGroup: 'g',
- trackSortKey: PrimaryTrackSortKey.RENDER_THREAD,
- });
- state = fakeTrack(state, {
- key: 'd',
- uri: THREAD_SLICE_TRACK_KIND,
- trackGroup: 'g',
- trackSortKey: PrimaryTrackSortKey.GPU_COMPLETION_THREAD,
- });
- state = fakeTrack(state, {
- key: 'e',
- uri: HEAP_PROFILE_TRACK_KIND,
- trackGroup: 'g',
- });
- state = fakeTrack(state, {
- key: 'f',
- uri: THREAD_SLICE_TRACK_KIND,
- trackGroup: 'g',
- name: 'T2',
- });
- state = fakeTrack(state, {
- key: 'g',
- uri: THREAD_SLICE_TRACK_KIND,
- trackGroup: 'g',
- name: 'T10',
- });
-
- const after = produce(state, (draft) => {
- StateActions.sortThreadTracks(draft, {});
- });
-
- // The order should be determined by:
- // 1.High priority
- // 2.Non ordinary track kinds
- // 3.Low priority
- // 4.Collated name string (ie. 'T2' will be before 'T10')
- expect(after.trackGroups['g'].tracks).toEqual([
- 'a',
- 'b',
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- ]);
-});
-
-test('sortTracksByTidThenName', () => {
- let state = createEmptyState();
- state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'a'});
- state = fakeTrack(state, {
- key: 'a',
- uri: THREAD_SLICE_TRACK_KIND,
- trackSortKey: {
- utid: 1,
- priority: InThreadTrackSortKey.ORDINARY,
- },
- trackGroup: 'g',
- name: 'aaa',
- tid: '1',
- });
- state = fakeTrack(state, {
- key: 'b',
- uri: THREAD_SLICE_TRACK_KIND,
- trackSortKey: {
- utid: 2,
- priority: InThreadTrackSortKey.ORDINARY,
- },
- trackGroup: 'g',
- name: 'bbb',
- tid: '2',
- });
- state = fakeTrack(state, {
- key: 'c',
- uri: THREAD_STATE_TRACK_KIND,
- trackSortKey: {
- utid: 1,
- priority: InThreadTrackSortKey.ORDINARY,
- },
- trackGroup: 'g',
- name: 'ccc',
- tid: '1',
- });
-
- const after = produce(state, (draft) => {
- StateActions.sortThreadTracks(draft, {});
- });
-
- expect(after.trackGroups['g'].tracks).toEqual(['a', 'c', 'b']);
-});
diff --git a/ui/src/common/arg_types.ts b/ui/src/common/arg_types.ts
index 551333b..b54d2b7 100644
--- a/ui/src/common/arg_types.ts
+++ b/ui/src/common/arg_types.ts
@@ -14,5 +14,5 @@
export type ArgValue =
| string
- | {kind: 'SCHED_SLICE'; trackId: string; sliceId: number; rawValue: string};
+ | {kind: 'SCHED_SLICE'; trackUri: string; sliceId: number; rawValue: string};
export type Args = Map<string, ArgValue>;
diff --git a/ui/src/common/commands.ts b/ui/src/common/commands.ts
index 2375559..6465329 100644
--- a/ui/src/common/commands.ts
+++ b/ui/src/common/commands.ts
@@ -27,6 +27,10 @@
return this.registry.get(commandId);
}
+ hasCommand(commandId: string): boolean {
+ return this.registry.has(commandId);
+ }
+
get commands(): Command[] {
return Array.from(this.registry.values());
}
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index 1c9585d..e031148 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -94,12 +94,7 @@
version: STATE_VERSION,
nextId: '-1',
newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
- tracks: {},
- utidToThreadSortKey: {},
aggregatePreferences: {},
- trackGroups: {},
- pinnedTracks: [],
- scrollingTracks: [],
queries: {},
notes: {},
diff --git a/ui/src/common/legacy_flamegraph_unittest.ts b/ui/src/common/legacy_flamegraph_unittest.ts
deleted file mode 100644
index 222b2e2..0000000
--- a/ui/src/common/legacy_flamegraph_unittest.ts
+++ /dev/null
@@ -1,1072 +0,0 @@
-// Copyright (C) 2019 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 {CallsiteInfo, mergeCallsites} from './legacy_flamegraph_util';
-
-test('zeroCallsitesMerged', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 4,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 2,
- name: 'B4',
- depth: 1,
- totalSize: 4,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 5);
-
- // Small callsites are not next ot each other, nothing should be changed.
- expect(mergedCallsites).toEqual(callsites);
-});
-
-test('zeroCallsitesMerged2', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 6,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: 'A4',
- depth: 1,
- totalSize: 4,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 5,
- parentId: 2,
- name: 'B5',
- depth: 1,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 5);
-
- // Small callsites are not next ot each other, nothing should be changed.
- expect(mergedCallsites).toEqual(callsites);
-});
-
-test('twoCallsitesMerged', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: 'A2',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 6);
-
- expect(mergedCallsites).toEqual([
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: '[merged]',
- depth: 1,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- ]);
-});
-
-test('manyCallsitesMerged', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: 'A2',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 3,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: 'A4',
- depth: 1,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 5,
- parentId: 1,
- name: 'A5',
- depth: 1,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 6,
- parentId: 3,
- name: 'A36',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 7,
- parentId: 4,
- name: 'A47',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 8,
- parentId: 5,
- name: 'A58',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const expectedMergedCallsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: 'A2',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: '[merged]',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- {
- id: 6,
- parentId: 3,
- name: '[merged]',
- depth: 2,
- totalSize: 3,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 4);
-
- // In this case, callsites A3, A4 and A5 should be merged since they are
- // smaller then 4 and are on same depth with same parent. Callsites A36, A47
- // and A58 should also be merged since their parents are merged.
- expect(mergedCallsites).toEqual(expectedMergedCallsites);
-});
-
-test('manyCallsitesMergedWithoutChildren', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 3,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: 'A4',
- depth: 1,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 5,
- parentId: 1,
- name: 'A5',
- depth: 1,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 6,
- parentId: 2,
- name: 'B6',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 7,
- parentId: 4,
- name: 'A47',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 8,
- parentId: 6,
- name: 'B68',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const expectedMergedCallsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: '[merged]',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- {
- id: 6,
- parentId: 2,
- name: 'B6',
- depth: 1,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 7,
- parentId: 3,
- name: 'A47',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 8,
- parentId: 6,
- name: 'B68',
- depth: 2,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 4);
-
- // In this case, callsites A3, A4 and A5 should be merged since they are
- // smaller then 4 and are on same depth with same parent. Callsite A47
- // should not be merged with B68 althought they are small because they don't
- // have sam parent. A47 should now have parent A3 because A4 is merged.
- expect(mergedCallsites).toEqual(expectedMergedCallsites);
-});
-
-test('smallCallsitesNotNextToEachOtherInArray', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 20,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: 'A2',
- depth: 1,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 1,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: 'A4',
- depth: 1,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 5,
- parentId: 1,
- name: 'A5',
- depth: 1,
- totalSize: 3,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const expectedMergedCallsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 20,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: 'A2',
- depth: 1,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: '[merged]',
- depth: 1,
- totalSize: 4,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: 'A4',
- depth: 1,
- totalSize: 8,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 4);
-
- // In this case, callsites A3, A4 and A5 should be merged since they are
- // smaller then 4 and are on same depth with same parent. Callsite A47
- // should not be merged with B68 althought they are small because they don't
- // have sam parent. A47 should now have parent A3 because A4 is merged.
- expect(mergedCallsites).toEqual(expectedMergedCallsites);
-});
-
-test('smallCallsitesNotMerged', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: 1,
- name: 'A2',
- depth: 1,
- totalSize: 2,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 2,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 1);
-
- expect(mergedCallsites).toEqual(callsites);
-});
-
-test('mergingRootCallsites', () => {
- const callsites: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 2,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const mergedCallsites = mergeCallsites(callsites, 20);
-
- expect(mergedCallsites).toEqual([
- {
- id: 1,
- parentId: -1,
- name: '[merged]',
- depth: 0,
- totalSize: 12,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- ]);
-});
-
-test('largerFlamegraph', () => {
- const data: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 60,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 40,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 25,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: 'A4',
- depth: 1,
- totalSize: 15,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 5,
- parentId: 1,
- name: 'A5',
- depth: 1,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 6,
- parentId: 1,
- name: 'A6',
- depth: 1,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 7,
- parentId: 2,
- name: 'B7',
- depth: 1,
- totalSize: 30,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 8,
- parentId: 2,
- name: 'B8',
- depth: 1,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 9,
- parentId: 3,
- name: 'A39',
- depth: 2,
- totalSize: 20,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 10,
- parentId: 4,
- name: 'A410',
- depth: 2,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 11,
- parentId: 4,
- name: 'A411',
- depth: 2,
- totalSize: 3,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 12,
- parentId: 4,
- name: 'A412',
- depth: 2,
- totalSize: 2,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 13,
- parentId: 5,
- name: 'A513',
- depth: 2,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 14,
- parentId: 5,
- name: 'A514',
- depth: 2,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 15,
- parentId: 7,
- name: 'A715',
- depth: 2,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 16,
- parentId: 7,
- name: 'A716',
- depth: 2,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 17,
- parentId: 7,
- name: 'A717',
- depth: 2,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 18,
- parentId: 7,
- name: 'A718',
- depth: 2,
- totalSize: 5,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 19,
- parentId: 9,
- name: 'A919',
- depth: 3,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 20,
- parentId: 17,
- name: 'A1720',
- depth: 3,
- totalSize: 2,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- const expectedData: CallsiteInfo[] = [
- {
- id: 1,
- parentId: -1,
- name: 'A',
- depth: 0,
- totalSize: 60,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 2,
- parentId: -1,
- name: 'B',
- depth: 0,
- totalSize: 40,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 3,
- parentId: 1,
- name: 'A3',
- depth: 1,
- totalSize: 25,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 4,
- parentId: 1,
- name: '[merged]',
- depth: 1,
- totalSize: 35,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- {
- id: 7,
- parentId: 2,
- name: 'B7',
- depth: 1,
- totalSize: 30,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 8,
- parentId: 2,
- name: 'B8',
- depth: 1,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 9,
- parentId: 3,
- name: 'A39',
- depth: 2,
- totalSize: 20,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 10,
- parentId: 4,
- name: '[merged]',
- depth: 2,
- totalSize: 25,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- {
- id: 15,
- parentId: 7,
- name: '[merged]',
- depth: 2,
- totalSize: 25,
- selfSize: 0,
- mapping: 'x',
- merged: true,
- highlighted: false,
- },
- {
- id: 19,
- parentId: 9,
- name: 'A919',
- depth: 3,
- totalSize: 10,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- {
- id: 20,
- parentId: 15,
- name: 'A1720',
- depth: 3,
- totalSize: 2,
- selfSize: 0,
- mapping: 'x',
- merged: false,
- highlighted: false,
- },
- ];
-
- // In this case, on depth 1, callsites A4, A5 and A6 should be merged and
- // initiate merging of their children A410, A411, A412, A513, A514. On depth2,
- // callsites A715, A716, A717 and A718 should be merged.
- const actualData = mergeCallsites(data, 16);
-
- expect(actualData).toEqual(expectedData);
-});
diff --git a/ui/src/common/legacy_flamegraph_util.ts b/ui/src/common/legacy_flamegraph_util.ts
deleted file mode 100644
index cb540dd..0000000
--- a/ui/src/common/legacy_flamegraph_util.ts
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright (C) 2019 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 {featureFlags} from '../core/feature_flags';
-import {ProfileType} from './state';
-
-export enum FlamegraphViewingOption {
- SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE',
- ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE',
- OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS',
- OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS',
- PERF_SAMPLES_KEY = 'PERF_SAMPLES',
- DOMINATOR_TREE_OBJ_SIZE_KEY = 'DOMINATED_OBJ_SIZE',
- DOMINATOR_TREE_OBJ_COUNT_KEY = 'DOMINATED_OBJ_COUNT',
-}
-
-interface ViewingOption {
- option: FlamegraphViewingOption;
- name: string;
-}
-
-export interface CallsiteInfo {
- id: number;
- parentId: number;
- depth: number;
- name?: string;
- totalSize: number;
- selfSize: number;
- mapping: string;
- merged: boolean;
- highlighted: boolean;
- location?: string;
-}
-
-export const SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG = featureFlags.register({
- id: 'showHeapGraphDominatorTree',
- name: 'Show heap graph dominator tree',
- description: 'Show dominated size and objects tabs in Java heap graph view.',
- defaultValue: true,
-});
-
-export function viewingOptions(profileType: ProfileType): Array<ViewingOption> {
- switch (profileType) {
- case ProfileType.PERF_SAMPLE:
- return [
- {
- option: FlamegraphViewingOption.PERF_SAMPLES_KEY,
- name: 'Samples',
- },
- ];
- case ProfileType.JAVA_HEAP_GRAPH:
- return [
- {
- option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
- name: 'Size',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
- name: 'Objects',
- },
- ].concat(
- SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG.get()
- ? [
- {
- option: FlamegraphViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
- name: 'Dominated size',
- },
- {
- option: FlamegraphViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
- name: 'Dominated objects',
- },
- ]
- : [],
- );
- case ProfileType.HEAP_PROFILE:
- return [
- {
- option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
- name: 'Unreleased size',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
- name: 'Unreleased count',
- },
- {
- option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
- name: 'Total size',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
- name: 'Total count',
- },
- ];
- case ProfileType.NATIVE_HEAP_PROFILE:
- return [
- {
- option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
- name: 'Unreleased malloc size',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
- name: 'Unreleased malloc count',
- },
- {
- option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
- name: 'Total malloc size',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
- name: 'Total malloc count',
- },
- ];
- case ProfileType.JAVA_HEAP_SAMPLES:
- return [
- {
- option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
- name: 'Total allocation size',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
- name: 'Total allocation count',
- },
- ];
- case ProfileType.MIXED_HEAP_PROFILE:
- return [
- {
- option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
- name: 'Total allocation size (malloc + java)',
- },
- {
- option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
- name: 'Total allocation count (malloc + java)',
- },
- ];
- default:
- const exhaustiveCheck: never = profileType;
- throw new Error(`Unhandled case: ${exhaustiveCheck}`);
- }
-}
-
-export function defaultViewingOption(
- profileType: ProfileType,
-): FlamegraphViewingOption {
- return viewingOptions(profileType)[0].option;
-}
-
-export function expandCallsites(
- data: ReadonlyArray<CallsiteInfo>,
- clickedCallsiteIndex: number,
-): ReadonlyArray<CallsiteInfo> {
- if (clickedCallsiteIndex === -1) return data;
- const expandedCallsites: CallsiteInfo[] = [];
- if (clickedCallsiteIndex >= data.length || clickedCallsiteIndex < -1) {
- return expandedCallsites;
- }
- const clickedCallsite = data[clickedCallsiteIndex];
- expandedCallsites.unshift(clickedCallsite);
- // Adding parents
- let parentId = clickedCallsite.parentId;
- while (parentId > -1) {
- expandedCallsites.unshift(data[parentId]);
- parentId = data[parentId].parentId;
- }
- // Adding children
- const parents: number[] = [];
- parents.push(clickedCallsiteIndex);
- for (let i = clickedCallsiteIndex + 1; i < data.length; i++) {
- const element = data[i];
- if (parents.includes(element.parentId)) {
- expandedCallsites.push(element);
- parents.push(element.id);
- }
- }
- return expandedCallsites;
-}
-
-// Merge callsites that have approximately width less than
-// MIN_PIXEL_DISPLAYED. All small callsites in the same depth and with same
-// parent will be merged to one with total size of all merged callsites.
-export function mergeCallsites(
- data: ReadonlyArray<CallsiteInfo>,
- minSizeDisplayed: number,
-) {
- const mergedData: CallsiteInfo[] = [];
- const mergedCallsites: Map<number, number> = new Map();
- for (let i = 0; i < data.length; i++) {
- // When a small callsite is found, it will be merged with other small
- // callsites of the same depth. So if the current callsite has already been
- // merged we can skip it.
- if (mergedCallsites.has(data[i].id)) {
- continue;
- }
- const copiedCallsite = copyCallsite(data[i]);
- copiedCallsite.parentId = getCallsitesParentHash(
- copiedCallsite,
- mergedCallsites,
- );
-
- let mergedAny = false;
- // If current callsite is small, find other small callsites with same depth
- // and parent and merge them into the current one, marking them as merged.
- if (copiedCallsite.totalSize <= minSizeDisplayed && i + 1 < data.length) {
- let j = i + 1;
- let nextCallsite = data[j];
- while (j < data.length && copiedCallsite.depth === nextCallsite.depth) {
- if (
- copiedCallsite.parentId ===
- getCallsitesParentHash(nextCallsite, mergedCallsites) &&
- nextCallsite.totalSize <= minSizeDisplayed
- ) {
- copiedCallsite.totalSize += nextCallsite.totalSize;
- mergedCallsites.set(nextCallsite.id, copiedCallsite.id);
- mergedAny = true;
- }
- j++;
- nextCallsite = data[j];
- }
- if (mergedAny) {
- copiedCallsite.name = '[merged]';
- copiedCallsite.merged = true;
- }
- }
- mergedData.push(copiedCallsite);
- }
- return mergedData;
-}
-
-function copyCallsite(callsite: CallsiteInfo): CallsiteInfo {
- return {
- id: callsite.id,
- parentId: callsite.parentId,
- depth: callsite.depth,
- name: callsite.name,
- totalSize: callsite.totalSize,
- mapping: callsite.mapping,
- selfSize: callsite.selfSize,
- merged: callsite.merged,
- highlighted: callsite.highlighted,
- location: callsite.location,
- };
-}
-
-function getCallsitesParentHash(
- callsite: CallsiteInfo,
- map: Map<number, number>,
-): number {
- return map.has(callsite.parentId)
- ? +map.get(callsite.parentId)!
- : callsite.parentId;
-}
-export function findRootSize(data: ReadonlyArray<CallsiteInfo>) {
- let totalSize = 0;
- let i = 0;
- while (i < data.length && data[i].depth === 0) {
- totalSize += data[i].totalSize;
- i++;
- }
- return totalSize;
-}
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index d57c6d1..e6196f3 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-
import {Registry} from '../base/registry';
import {TimeSpan, time} from '../base/time';
import {globals} from '../frontend/globals';
@@ -26,18 +24,13 @@
PluginContext,
PluginContextTrace,
PluginDescriptor,
- PrimaryTrackSortKey,
Store,
TabDescriptor,
TrackDescriptor,
- TrackPredicate,
- GroupPredicate,
- TrackRef,
SidebarMenuItem,
} from '../public';
import {EngineBase, Engine} from '../trace_processor/engine';
import {Actions} from './actions';
-import {SCROLLING_TRACK_GROUP} from './state';
import {addQueryResultsTab} from '../frontend/query_result_tab';
import {Flag, featureFlags} from '../core/feature_flags';
import {assertExists} from '../base/logging';
@@ -47,6 +40,7 @@
import {horizontalScrollToTs} from '../frontend/scroll_helper';
import {DisposableStack} from '../base/disposable_stack';
import {TraceContext} from '../frontend/trace_context';
+import {Workspace} from '../public/workspace';
// Every plugin gets its own PluginContext. This is how we keep track
// what each plugin is doing and how we can blame issues on particular
@@ -125,19 +119,15 @@
this.trash.use(dispose);
}
- addDefaultTrack(track: TrackRef): void {
+ registerTrackAndShowOnTraceLoad(track: TrackDescriptor): void {
+ this.registerTrack(track);
+
// Silently ignore if context is dead.
if (!this.alive) return;
-
- const dispose = globals.trackManager.addPotentialTrack(track);
+ const dispose = globals.trackManager.autoShowOnTraceLoad(track);
this.trash.use(dispose);
}
- registerStaticTrack(track: TrackDescriptor & TrackRef): void {
- this.registerTrack(track);
- this.addDefaultTrack(track);
- }
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
runCommand(id: string, ...args: any[]): any {
return this.ctx.runCommand(id, ...args);
@@ -182,130 +172,6 @@
}
readonly timeline = {
- // Add a new track to the timeline, returning its key.
- addTrack(uri: string, displayName: string): string {
- const trackKey = uuidv4();
- globals.dispatch(
- Actions.addTrack({
- key: trackKey,
- uri,
- name: displayName,
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- }),
- );
- return trackKey;
- },
-
- removeTrack(key: string): void {
- globals.dispatch(Actions.removeTracks({trackKeys: [key]}));
- },
-
- pinTrack(key: string) {
- if (!isPinned(key)) {
- globals.dispatch(Actions.toggleTrackPinned({trackKey: key}));
- }
- },
-
- unpinTrack(key: string) {
- if (isPinned(key)) {
- globals.dispatch(Actions.toggleTrackPinned({trackKey: key}));
- }
- },
-
- pinTracksByPredicate(predicate: TrackPredicate) {
- const tracks = Object.values(globals.state.tracks);
- for (const track of tracks) {
- const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackDesc && predicate(trackDesc) && !isPinned(track.key)) {
- globals.dispatch(
- Actions.toggleTrackPinned({
- trackKey: track.key,
- }),
- );
- }
- }
- },
-
- unpinTracksByPredicate(predicate: TrackPredicate) {
- const tracks = Object.values(globals.state.tracks);
- for (const track of tracks) {
- const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackDesc && predicate(trackDesc) && isPinned(track.key)) {
- globals.dispatch(
- Actions.toggleTrackPinned({
- trackKey: track.key,
- }),
- );
- }
- }
- },
-
- removeTracksByPredicate(predicate: TrackPredicate) {
- const trackKeysToRemove = Object.values(globals.state.tracks)
- .filter((track) => {
- const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
- return trackDesc && predicate(trackDesc);
- })
- .map((trackState) => trackState.key);
-
- globals.dispatch(Actions.removeTracks({trackKeys: trackKeysToRemove}));
- },
-
- expandGroupsByPredicate(predicate: GroupPredicate) {
- const groups = globals.state.trackGroups;
- const groupsToExpand = Object.values(groups)
- .filter((group) => group.collapsed)
- .filter((group) => {
- const ref = {
- displayName: group.name,
- collapsed: group.collapsed,
- };
- return predicate(ref);
- })
- .map((group) => group.key);
-
- for (const groupKey of groupsToExpand) {
- globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
- }
- },
-
- collapseGroupsByPredicate(predicate: GroupPredicate) {
- const groups = globals.state.trackGroups;
- const groupsToCollapse = Object.values(groups)
- .filter((group) => !group.collapsed)
- .filter((group) => {
- const ref = {
- displayName: group.name,
- collapsed: group.collapsed,
- };
- return predicate(ref);
- })
- .map((group) => group.key);
-
- for (const groupKey of groupsToCollapse) {
- globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
- }
- },
-
- get tracks(): TrackRef[] {
- const tracks = Object.values(globals.state.tracks);
- const pinnedTracks = globals.state.pinnedTracks;
- const groups = globals.state.trackGroups;
- return tracks.map((trackState) => {
- const group = trackState.trackGroup
- ? groups[trackState.trackGroup]
- : undefined;
- return {
- title: trackState.name,
- uri: trackState.uri,
- key: trackState.key,
- groupName: group?.name,
- isPinned: pinnedTracks.includes(trackState.key),
- };
- });
- },
-
panToTimestamp(ts: time): void {
horizontalScrollToTs(ts);
},
@@ -317,6 +183,10 @@
get viewport(): TimeSpan {
return globals.timeline.visibleWindow.toTimeSpan();
},
+
+ get workspace(): Workspace {
+ return globals.workspace;
+ },
};
[Symbol.dispose]() {
@@ -348,10 +218,6 @@
}
}
-function isPinned(trackId: string): boolean {
- return globals.state.pinnedTracks.includes(trackId);
-}
-
// 'Static' registry of all known plugins.
export class PluginRegistry extends Registry<PluginDescriptor> {
constructor() {
diff --git a/ui/src/common/search_data.ts b/ui/src/common/search_data.ts
index 7209c04..cd5856b 100644
--- a/ui/src/common/search_data.ts
+++ b/ui/src/common/search_data.ts
@@ -24,7 +24,7 @@
eventIds: Float64Array;
tses: BigInt64Array;
utids: Float64Array;
- trackKeys: string[];
+ trackUris: string[];
sources: SearchSource[];
totalResults: number;
}
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 3ad7c5d..ceb26cc 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -40,36 +40,6 @@
CpuProfileSampleSelection,
} from '../core/selection_manager';
-// Tracks within track groups (usually corresponding to processes) are sorted.
-// As we want to group all tracks related to a given thread together, we use
-// two keys:
-// - Primary key corresponds to a priority of a track block (all tracks related
-// to a given thread or a single track if it's not thread-associated).
-// - Secondary key corresponds to a priority of a given thread-associated track
-// within its thread track block.
-// Each track will have a sort key, which either a primary sort key
-// (for non-thread tracks) or a tid and secondary sort key (mapping of tid to
-// primary sort key is done independently).
-export enum PrimaryTrackSortKey {
- DEBUG_TRACK,
- NULL_TRACK,
- PROCESS_SCHEDULING_TRACK,
- PROCESS_SUMMARY_TRACK,
- EXPECTED_FRAMES_SLICE_TRACK,
- ACTUAL_FRAMES_SLICE_TRACK,
- PERF_SAMPLES_PROFILE_TRACK,
- HEAP_PROFILE_TRACK,
- MAIN_THREAD,
- RENDER_THREAD,
- GPU_COMPLETION_THREAD,
- CHROME_IO_THREAD,
- CHROME_COMPOSITOR_THREAD,
- ORDINARY_THREAD,
- COUNTER_TRACK,
- ASYNC_SLICE_TRACK,
- ORDINARY_TRACK,
-}
-
/**
* A plain js object, holding objects of type |Class| keyed by string id.
* We use this instead of using |Map| object since it is simpler and faster to
@@ -95,7 +65,7 @@
export interface Area {
start: time;
end: time;
- tracks: string[];
+ trackUris: string[];
}
export const MAX_TIME = 180;
@@ -175,34 +145,6 @@
export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE' | 'FORCE_BUILTIN_WASM';
-// Key that is used to sort tracks within a block of tracks associated with a
-// given thread.
-export enum InThreadTrackSortKey {
- THREAD_COUNTER_TRACK,
- THREAD_SCHEDULING_STATE_TRACK,
- CPU_STACK_SAMPLES_TRACK,
- VISUALISED_ARGS_TRACK,
- ORDINARY,
- DEFAULT_TRACK,
-}
-
-// Sort key used for sorting tracks associated with a thread.
-export type ThreadTrackSortKey = {
- utid: number;
- priority: InThreadTrackSortKey;
-};
-
-// Sort key for all tracks: both thread-associated and non-thread associated.
-export type TrackSortKey = PrimaryTrackSortKey | ThreadTrackSortKey;
-
-// Mapping which defines order for threads within a given process.
-export type UtidToTrackSortKey = {
- [utid: number]: {
- tid?: number;
- sortKey: PrimaryTrackSortKey;
- };
-};
-
export interface TraceFileSource {
type: 'FILE';
file: File;
@@ -245,24 +187,6 @@
| TraceUrlSource
| TraceHttpRpcSource;
-export interface TrackState {
- uri: string;
- key: string;
- name: string;
- trackSortKey: TrackSortKey;
- trackGroup?: string;
- closeable?: boolean;
-}
-
-export interface TrackGroupState {
- key: string;
- name: string;
- collapsed: boolean;
- tracks: string[]; // Child track ids.
- fixedOrdering?: boolean; // Render tracks without sorting.
- summaryTrack: string | undefined;
-}
-
export interface EngineConfig {
id: string;
mode?: EngineMode; // Is undefined until |ready| is true.
@@ -348,11 +272,7 @@
}
// Input parameters to check whether the pivot table needs to be re-queried.
-export interface PivotTableAreaState {
- start: time;
- end: time;
- tracks: string[];
-}
+export type PivotTableAreaState = Area;
export interface PivotTableState {
// Currently selected area, if null, pivot table is not going to be visible.
@@ -433,12 +353,8 @@
newEngineMode: NewEngineMode;
engine?: EngineConfig;
traceUuid?: string;
- trackGroups: ObjectByKey<TrackGroupState>;
- tracks: ObjectByKey<TrackState>;
- utidToThreadSortKey: UtidToTrackSortKey;
+
aggregatePreferences: ObjectById<AggregationState>;
- scrollingTracks: string[];
- pinnedTracks: string[];
debugTrackId?: string;
lastTrackReloadRequest?: number;
queries: ObjectById<QueryConfig>;
@@ -863,21 +779,6 @@
];
}
-export function getContainingGroupKey(
- state: State,
- trackKey: string,
-): null | string {
- const track = state.tracks[trackKey];
- if (track === undefined) {
- return null;
- }
- const parentGroupKey = track.trackGroup;
- if (!parentGroupKey) {
- return null;
- }
- return parentGroupKey;
-}
-
export function getLegacySelection(state: State): LegacySelection | null {
return selectionToLegacySelection(state.selection);
}
diff --git a/ui/src/common/state_serialization.ts b/ui/src/common/state_serialization.ts
index b72551c..ad7479c 100644
--- a/ui/src/common/state_serialization.ts
+++ b/ui/src/common/state_serialization.ts
@@ -81,7 +81,7 @@
if (stateSel.kind === 'single') {
selection.push({
kind: 'TRACK_EVENT',
- trackKey: stateSel.trackKey,
+ trackKey: stateSel.trackUri,
eventId: stateSel.eventId.toString(),
});
} else if (stateSel.kind === 'legacy') {
@@ -123,7 +123,7 @@
return {
version: SERIALIZED_STATE_VERSION,
- pinnedTracks: globals.state.pinnedTracks,
+ pinnedTracks: globals.workspace.pinnedTracks.map((t) => t.uri),
viewport: {
start: vizWindow.start,
end: vizWindow.end,
@@ -186,16 +186,16 @@
new TimeSpan(appState.viewport.start, appState.viewport.end),
);
}
- globals.store.edit((draft) => {
- // Restore the pinned tracks, if they exist.
- const tracksToPin: string[] = [];
- for (const trackKey of appState.pinnedTracks) {
- if (trackKey in globals.state.tracks) {
- tracksToPin.push(trackKey);
- }
- }
- draft.pinnedTracks = tracksToPin;
+ // Restore the pinned tracks, if they exist.
+ for (const uri of appState.pinnedTracks) {
+ const track = globals.workspace.getTrackByUri(uri);
+ if (track) {
+ track.pin();
+ }
+ }
+
+ globals.store.edit((draft) => {
// Restore notes.
for (const note of appState.notes) {
const commonArgs = {
@@ -226,7 +226,7 @@
case 'TRACK_EVENT':
draft.selection = {
kind: 'single',
- trackKey: sel.trackKey,
+ trackUri: sel.trackKey,
eventId: parseInt(sel.eventId),
};
break;
diff --git a/ui/src/common/state_serialization_schema.ts b/ui/src/common/state_serialization_schema.ts
index 7adbecb..27737a9 100644
--- a/ui/src/common/state_serialization_schema.ts
+++ b/ui/src/common/state_serialization_schema.ts
@@ -31,6 +31,7 @@
const SELECTION_SCHEMA = z.discriminatedUnion('kind', [
z.object({
kind: z.literal('TRACK_EVENT'),
+ // This is actually the track URI but let's not rename for backwards compat
trackKey: z.string(),
eventId: z.string(),
}),
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index 3dc65e2..af8f5f9 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -12,34 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {PrimaryTrackSortKey} from '../public';
-
import {createEmptyState} from './empty_state';
-import {getContainingGroupKey, State} from './state';
+import {State} from './state';
test('createEmptyState', () => {
const state: State = createEmptyState();
expect(state.engine).toEqual(undefined);
});
-
-test('getContainingTrackId', () => {
- const state: State = createEmptyState();
- state.tracks['a'] = {
- key: 'a',
- uri: 'Foo',
- name: 'a track',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- };
-
- state.tracks['b'] = {
- key: 'b',
- uri: 'Foo',
- name: 'b track',
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: 'containsB',
- };
-
- expect(getContainingGroupKey(state, 'z')).toEqual(null);
- expect(getContainingGroupKey(state, 'a')).toEqual(null);
- expect(getContainingGroupKey(state, 'b')).toEqual('containsB');
-});
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
deleted file mode 100644
index 5234240..0000000
--- a/ui/src/common/track_cache.ts
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright (C) 2023 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 {Optional, exists} from '../base/utils';
-import {Registry} from '../base/registry';
-import {Store} from '../base/store';
-import {Track, TrackContext, TrackDescriptor, TrackRef} from '../public';
-
-import {ObjectByKey, State, TrackState} from './state';
-import {AsyncLimiter} from '../base/async_limiter';
-import {assertFalse} from '../base/logging';
-import {TrackRenderContext} from '../public/tracks';
-
-export interface TrackCacheEntry extends Disposable {
- readonly trackKey: string;
- readonly track: Track;
- desc: TrackDescriptor;
- render(ctx: TrackRenderContext): void;
- getError(): Optional<Error>;
-}
-
-// This class is responsible for managing the lifecycle of tracks over render
-// cycles.
-
-// Example usage:
-// function render() {
-// const trackCache = new TrackCache();
-// const foo = trackCache.resolveTrack('foo', 'exampleURI', {});
-// const bar = trackCache.resolveTrack('bar', 'exampleURI', {});
-// trackCache.flushOldTracks(); // <-- Destroys any unused cached tracks
-// }
-
-// Example of how flushing works:
-// First cycle
-// resolveTrack('foo', ...) <-- new track 'foo' created
-// resolveTrack('bar', ...) <-- new track 'bar' created
-// flushTracks()
-// Second cycle
-// resolveTrack('foo', ...) <-- returns cached 'foo' track
-// flushTracks() <-- 'bar' is destroyed, as it was not resolved this cycle
-// Third cycle
-// flushTracks() <-- 'foo' is destroyed.
-export class TrackManager {
- private _trackKeyByTrackId = new Map<number, string>();
- private newTracks = new Map<string, TrackCacheEntry>();
- private currentTracks = new Map<string, TrackCacheEntry>();
- private trackRegistry = new Registry<TrackDescriptor>(({uri}) => uri);
- private defaultTracks = new Set<TrackRef>();
-
- private store: Store<State>;
- private trackState?: ObjectByKey<TrackState>;
-
- constructor(store: Store<State>) {
- this.store = store;
- }
-
- get trackKeyByTrackId() {
- this.updateTrackKeyByTrackIdMap();
- return this._trackKeyByTrackId;
- }
-
- registerTrack(trackDesc: TrackDescriptor): Disposable {
- return this.trackRegistry.register(trackDesc);
- }
-
- addPotentialTrack(track: TrackRef): Disposable {
- this.defaultTracks.add(track);
- return {
- [Symbol.dispose]: () => this.defaultTracks.delete(track),
- };
- }
-
- findPotentialTracks(): TrackRef[] {
- return Array.from(this.defaultTracks);
- }
-
- getAllTracks(): TrackDescriptor[] {
- return Array.from(this.trackRegistry.values());
- }
-
- // Look up track into for a given track's URI.
- // Returns |undefined| if no track can be found.
- resolveTrackInfo(uri: string): TrackDescriptor | undefined {
- return this.trackRegistry.tryGet(uri);
- }
-
- // Creates a new track using |uri| and |params| or retrieves a cached track if
- // |key| exists in the cache.
- resolveTrack(key: string, trackDesc: TrackDescriptor): TrackCacheEntry {
- // Search for a cached version of this track,
- const cached = this.currentTracks.get(key);
-
- // Ensure the cached track has the same factory type as the resolved track.
- // If this has changed, the track should be re-created.
- if (cached && trackDesc.trackFactory === cached.desc.trackFactory) {
- // Keep our cached track descriptor up to date, if anything's changed.
- cached.desc = trackDesc;
-
- // Move this track from the recycle bin to the safe cache, which means
- // it's safe from disposal for this cycle.
- this.newTracks.set(key, cached);
-
- return cached;
- } else {
- // Cached track doesn't exist or is out of date, create a new one.
- const trackContext: TrackContext = {
- trackKey: key,
- };
- const track = trackDesc.trackFactory(trackContext);
- const entry = new TrackFSM(key, track, trackDesc, trackContext);
-
- // Push track into the safe cache.
- this.newTracks.set(key, entry);
- return entry;
- }
- }
-
- // Destroys all current tracks not present in the new cache.
- flushOldTracks() {
- for (const [key, entry] of this.currentTracks.entries()) {
- if (!this.newTracks.has(key)) {
- entry[Symbol.dispose]();
- }
- }
-
- this.currentTracks = this.newTracks;
- this.newTracks = new Map<string, TrackCacheEntry>();
- }
-
- private updateTrackKeyByTrackIdMap() {
- if (this.trackState === this.store.state.tracks) {
- return;
- }
-
- const trackKeyByTrackId = new Map<number, string>();
-
- const trackList = Object.entries(this.store.state.tracks);
- trackList.forEach(([key, {uri}]) => {
- const desc = this.trackRegistry.get(uri);
- for (const trackId of desc?.tags?.trackIds ?? []) {
- const existingKey = trackKeyByTrackId.get(trackId);
- if (exists(existingKey)) {
- throw new Error(
- `Trying to map track id ${trackId} to UI track ${key}, already mapped to ${existingKey}`,
- );
- }
- trackKeyByTrackId.set(trackId, key);
- }
- });
-
- this._trackKeyByTrackId = trackKeyByTrackId;
- this.trackState = this.store.state.tracks;
- }
-}
-
-/**
- * This function describes the asynchronous lifecycle of a track using an async
- * generator. This saves us having to build out the state machine explicitly,
- * using conventional serial programming techniques to describe the lifecycle
- * instead, which is more natural and easier to understand.
- *
- * We expect the params to onUpdate to be passed into the generator via the
- * yield function.
- *
- * @param track The track to run the lifecycle for.
- * @param ctx The trace context, passed to various lifecycle methods.
- */
-async function* trackLifecycle(
- track: Track,
- ctx: TrackContext,
-): AsyncGenerator<void, void, TrackRenderContext> {
- try {
- // Wait for parameters to be passed in before initializing the track
- const trackRenderCtx = yield;
- await Promise.resolve(track.onCreate?.(ctx));
- await Promise.resolve(track.onUpdate?.(trackRenderCtx));
-
- // Wait for parameters to be passed in before subsequent calls to onUpdate()
- while (true) {
- await Promise.resolve(track.onUpdate?.(yield));
- }
- } finally {
- // Ensure we always clean up, even on throw or early return
- await Promise.resolve(track.onDestroy?.());
- }
-}
-
-/**
- * Wrapper that manages lifecycle hooks on behalf of a track, ensuring lifecycle
- * hooks are called synchronously and in the correct order.
- */
-class TrackFSM implements TrackCacheEntry {
- public readonly trackKey: string;
- public readonly track: Track;
- public readonly desc: TrackDescriptor;
-
- private readonly limiter = new AsyncLimiter();
- private readonly ctx: TrackContext;
- private readonly generator: ReturnType<typeof trackLifecycle>;
-
- private error?: Error;
- private isDisposed = false;
-
- constructor(
- trackKey: string,
- track: Track,
- desc: TrackDescriptor,
- ctx: TrackContext,
- ) {
- this.trackKey = trackKey;
- this.track = track;
- this.desc = desc;
- this.ctx = ctx;
-
- this.generator = trackLifecycle(this.track, this.ctx);
-
- // This just starts the generator, which will pause at the first yield
- // without doing anything - note that the parameter to the first next() call
- // is ignored in generators
- this.generator.next();
- }
-
- render(ctx: TrackRenderContext): void {
- assertFalse(this.isDisposed);
-
- // The generator will ensure that track lifecycle calls don't overlap, but
- // it'll also enqueue every single call to next() which can create a large
- // backlog of updates assuming render is called faster than updates can
- // complete (this is usually the case), so we use an AsyncLimiter here to
- // avoid enqueueing more than one next().
- this.limiter
- .schedule(async () => {
- // Pass in the parameters to onUpdate() here (i.e. the track size)
- await this.generator.next(ctx);
- })
- .catch((e) => {
- // Errors thrown inside lifecycle hooks will bubble up through the
- // generator and AsyncLimiter to here, where we can swallow and capture
- // the error
- this.error = e;
- });
-
- // Always call render synchronously
- this.track.render(ctx);
- }
-
- [Symbol.dispose](): void {
- assertFalse(this.isDisposed);
- this.isDisposed = true;
-
- // Ask the generator to stop, it'll handle any cleanup and return at the
- // next yield
- this.generator.return();
- }
-
- getError(): Optional<Error> {
- return this.error;
- }
-}
diff --git a/ui/src/common/track_manager.ts b/ui/src/common/track_manager.ts
new file mode 100644
index 0000000..625c732
--- /dev/null
+++ b/ui/src/common/track_manager.ts
@@ -0,0 +1,191 @@
+// Copyright (C) 2023 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 {Optional} from '../base/utils';
+import {Registry} from '../base/registry';
+import {Track, TrackDescriptor} from '../public';
+
+import {AsyncLimiter} from '../base/async_limiter';
+import {TrackRenderContext} from '../public/tracks';
+
+export interface TrackRenderer {
+ readonly track: Track;
+ desc: TrackDescriptor;
+ render(ctx: TrackRenderContext): void;
+ getError(): Optional<Error>;
+}
+
+/**
+ * TrackManager is responsible for managing the registry of tracks and their
+ * lifecycle of tracks over render cycles.
+ *
+ * Example usage:
+ * function render() {
+ * const trackCache = new TrackCache();
+ * const foo = trackCache.getTrackRenderer('foo', 'exampleURI', {});
+ * const bar = trackCache.getTrackRenderer('bar', 'exampleURI', {});
+ * trackCache.flushOldTracks(); // <-- Destroys any unused cached tracks
+ * }
+ *
+ * Example of how flushing works:
+ * First cycle
+ * getTrackRenderer('foo', ...) <-- new track 'foo' created
+ * getTrackRenderer('bar', ...) <-- new track 'bar' created
+ * flushTracks()
+ * Second cycle
+ * getTrackRenderer('foo', ...) <-- returns cached 'foo' track
+ * flushTracks() <-- 'bar' is destroyed, as it was not resolved this cycle
+ * Third cycle
+ * flushTracks() <-- 'foo' is destroyed.
+ */
+export class TrackManager {
+ private tracks = new Registry<TrackFSM>((x) => x.desc.uri);
+
+ // This contains the tracks refs that plugins want to get auto-added on trace
+ // load, rather than bothering manually adding them to the workspace. They
+ // come from plugins calling registerTrackAndShowOnTraceLoad().
+ // TODO(primiano): this is going away soon.
+ private autoShowTracks = new Set<TrackDescriptor>();
+
+ registerTrack(trackDesc: TrackDescriptor): Disposable {
+ return this.tracks.register(new TrackFSM(trackDesc));
+ }
+
+ // TODO(primiano): this is going away soon.
+ autoShowOnTraceLoad(track: TrackDescriptor): Disposable {
+ this.autoShowTracks.add(track);
+ return {
+ [Symbol.dispose]: () => this.autoShowTracks.delete(track),
+ };
+ }
+
+ getAutoShowTracks(): TrackDescriptor[] {
+ return Array.from(this.autoShowTracks);
+ }
+
+ findTrack(
+ predicate: (desc: TrackDescriptor) => boolean | undefined,
+ ): TrackDescriptor | undefined {
+ for (const t of this.tracks.values()) {
+ if (predicate(t.desc)) return t.desc;
+ }
+ return undefined;
+ }
+
+ getAllTracks(): TrackDescriptor[] {
+ return Array.from(this.tracks.valuesAsArray().map((t) => t.desc));
+ }
+
+ // Look up track into for a given track's URI.
+ // Returns |undefined| if no track can be found.
+ getTrack(uri: string): TrackDescriptor | undefined {
+ return this.tracks.tryGet(uri)?.desc;
+ }
+
+ // This is only called by the viewer_page.ts.
+ getTrackRenderer(uri: string): TrackRenderer | undefined {
+ // Search for a cached version of this track,
+ const trackFsm = this.tracks.tryGet(uri);
+ trackFsm?.markUsed();
+ return trackFsm;
+ }
+
+ // Destroys all tracks that didn't recently get a getTrackRenderer() call.
+ flushOldTracks() {
+ for (const trackFsm of this.tracks.values()) {
+ trackFsm.tick();
+ }
+ }
+}
+
+const DESTROY_IF_NOT_SEEN_FOR_TICK_COUNT = 1;
+
+/**
+ * Owns all runtime information about a track and manages its lifecycle,
+ * ensuring lifecycle hooks are called synchronously and in the correct order.
+ *
+ * There are quite some subtle properties that this class guarantees:
+ * - It make sure that lifecycle methods don't overlap with each other.
+ * - It prevents a chain of onCreate > onDestroy > onCreate if the first
+ * onCreate() is still oustanding. This is by virtue of using AsyncLimiter
+ * which under the hoods holds only the most recent task and skips the
+ * intermediate ones.
+ * - Ensures that a track never sees two consecutive onCreate, or onDestroy or
+ * an onDestroy without an onCreate.
+ * - Ensures that onUpdate never overlaps or follows with onDestroy. This is
+ * particularly important because tracks often drop tables/views onDestroy
+ * and they shouldn't try to fetch more data onUpdate past that point.
+ */
+class TrackFSM implements TrackRenderer {
+ public readonly desc: TrackDescriptor;
+
+ private readonly limiter = new AsyncLimiter();
+ private error?: Error;
+ private tickSinceLastUsed = 0;
+ private created = false;
+
+ constructor(desc: TrackDescriptor) {
+ this.desc = desc;
+ }
+
+ markUsed(): void {
+ this.tickSinceLastUsed = 0;
+ }
+
+ // Increment the lastUsed counter, and maybe call onDestroy().
+ tick(): void {
+ if (this.tickSinceLastUsed++ === DESTROY_IF_NOT_SEEN_FOR_TICK_COUNT) {
+ // Schedule an onDestroy
+ this.limiter
+ .schedule(async () => {
+ if (this.created) {
+ await Promise.resolve(this.track.onDestroy?.());
+ this.created = false;
+ }
+ })
+ .catch((e) => {
+ // Errors thrown inside lifecycle hooks will bubble up through the
+ // AsyncLimiter to here, where we can swallow and capture the error.
+ this.error = e;
+ });
+ }
+ }
+
+ render(ctx: TrackRenderContext): void {
+ this.limiter
+ .schedule(async () => {
+ // Call onCreate() if we have been destroyed or were never created in
+ // the first place.
+ if (!this.created) {
+ await Promise.resolve(this.track.onCreate?.(ctx));
+ this.created = true;
+ }
+ await Promise.resolve(this.track.onUpdate?.(ctx));
+ })
+ .catch((e) => {
+ // Errors thrown inside lifecycle hooks will bubble up through the
+ // AsyncLimiter to here, where we can swallow and capture the error.
+ this.error = e;
+ });
+ this.track.render(ctx);
+ }
+
+ getError(): Optional<Error> {
+ return this.error;
+ }
+
+ get track(): Track {
+ return this.desc.track;
+ }
+}
diff --git a/ui/src/common/track_cache_unittest.ts b/ui/src/common/track_manager_unittest.ts
similarity index 78%
rename from ui/src/common/track_cache_unittest.ts
rename to ui/src/common/track_manager_unittest.ts
index c65f615..d8099bf 100644
--- a/ui/src/common/track_cache_unittest.ts
+++ b/ui/src/common/track_manager_unittest.ts
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {assertExists} from '../base/logging';
import {Duration} from '../base/time';
import {PxSpan, TimeScale} from '../frontend/time_scale';
-import {createStore, TrackDescriptor} from '../public';
+import {TrackDescriptor} from '../public';
import {TrackRenderContext} from '../public/tracks';
-import {createEmptyState} from './empty_state';
import {HighPrecisionTime} from './high_precision_time';
import {HighPrecisionTimeSpan} from './high_precision_time_span';
-import {TrackManager} from './track_cache';
+import {TrackManager} from './track_manager';
function makeMockTrack() {
return {
@@ -48,7 +48,7 @@
let trackManager: TrackManager;
const visibleWindow = new HighPrecisionTimeSpan(HighPrecisionTime.ZERO, 0);
const dummyCtx: TrackRenderContext = {
- trackKey: 'foo',
+ trackUri: 'foo',
ctx: new CanvasRenderingContext2D(),
size: {width: 123, height: 123},
visibleWindow,
@@ -61,29 +61,31 @@
td = {
uri: 'test',
title: 'foo',
- trackFactory: () => mockTrack,
+ track: mockTrack,
};
- const store = createStore(createEmptyState());
- trackManager = new TrackManager(store);
+ trackManager = new TrackManager();
+ trackManager.registerTrack(td);
});
describe('TrackManager', () => {
it('calls track lifecycle hooks', async () => {
- const entry = trackManager.resolveTrack('foo', td);
+ const entry = assertExists(trackManager.getTrackRenderer(td.uri));
entry.render(dummyCtx);
await settle();
expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
expect(mockTrack.onUpdate).toHaveBeenCalledTimes(1);
- entry[Symbol.dispose]();
+ // Double flush should destroy all tracks
+ trackManager.flushOldTracks();
+ trackManager.flushOldTracks();
await settle();
expect(mockTrack.onDestroy).toHaveBeenCalledTimes(1);
});
it('calls onCrate lazily', async () => {
// Check we wait until the first call to render before calling onCreate
- const entry = trackManager.resolveTrack('foo', td);
+ const entry = assertExists(trackManager.getTrackRenderer(td.uri));
await settle();
expect(mockTrack.onCreate).not.toHaveBeenCalled();
@@ -93,12 +95,12 @@
});
it('reuses tracks', async () => {
- const first = trackManager.resolveTrack('foo', td);
+ const first = assertExists(trackManager.getTrackRenderer(td.uri));
trackManager.flushOldTracks();
first.render(dummyCtx);
await settle();
- const second = trackManager.resolveTrack('foo', td);
+ const second = assertExists(trackManager.getTrackRenderer(td.uri));
trackManager.flushOldTracks();
second.render(dummyCtx);
await settle();
@@ -109,7 +111,7 @@
});
it('destroys tracks when they are not resolved for one cycle', async () => {
- const entry = trackManager.resolveTrack('foo', td);
+ const entry = assertExists(trackManager.getTrackRenderer(td.uri));
entry.render(dummyCtx);
// Double flush should destroy all tracks
@@ -121,20 +123,8 @@
expect(mockTrack.onDestroy).toHaveBeenCalledTimes(1);
});
- it('throws on render after destroy', async () => {
- const entry = trackManager.resolveTrack('foo', td);
-
- // Double flush should destroy all tracks
- trackManager.flushOldTracks();
- trackManager.flushOldTracks();
-
- await settle();
-
- expect(() => entry.render(dummyCtx)).toThrow();
- });
-
it('contains crash inside onCreate()', async () => {
- const entry = trackManager.resolveTrack('foo', td);
+ const entry = assertExists(trackManager.getTrackRenderer(td.uri));
const e = new Error();
// Mock crash inside onCreate
@@ -147,12 +137,11 @@
expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
expect(mockTrack.onUpdate).not.toHaveBeenCalled();
- expect(mockTrack.onDestroy).toHaveBeenCalledTimes(1);
expect(entry.getError()).toBe(e);
});
it('contains crash inside onUpdate()', async () => {
- const entry = trackManager.resolveTrack('foo', td);
+ const entry = assertExists(trackManager.getTrackRenderer(td.uri));
const e = new Error();
// Mock crash inside onUpdate
@@ -164,12 +153,12 @@
await settle();
expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
- expect(mockTrack.onDestroy).toHaveBeenCalledTimes(1);
+ expect(mockTrack.onUpdate).toHaveBeenCalledTimes(1);
expect(entry.getError()).toBe(e);
});
it('handles dispose after crash', async () => {
- const entry = trackManager.resolveTrack('foo', td);
+ const entry = assertExists(trackManager.getTrackRenderer(td.uri));
const e = new Error();
// Mock crash inside onUpdate
@@ -180,8 +169,8 @@
entry.render(dummyCtx);
await settle();
- // Ensure we don't crash while disposing
- entry[Symbol.dispose]();
+ // Ensure we don't crash during the next render cycle
+ entry.render(dummyCtx);
await settle();
});
});
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index f8f25c4..b4d02cc 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -24,13 +24,10 @@
export class CounterAggregationController extends AggregationController {
async createAggregateView(engine: Engine, area: Area) {
const trackIds: (string | number)[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === COUNTER_TRACK_KIND) {
- trackInfo.tags?.trackIds && trackIds.push(...trackInfo.tags.trackIds);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === COUNTER_TRACK_KIND) {
+ trackInfo.tags?.trackIds && trackIds.push(...trackInfo.tags.trackIds);
}
}
if (trackIds.length === 0) return false;
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index 6ea3f67..085ffe2 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -24,13 +24,10 @@
export class CpuAggregationController extends AggregationController {
async createAggregateView(engine: Engine, area: Area) {
const selectedCpus: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
+ exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
}
}
if (selectedCpus.length === 0) return false;
diff --git a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
index eeb0942..2d8baa3 100644
--- a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
@@ -24,13 +24,10 @@
export class CpuByProcessAggregationController extends AggregationController {
async createAggregateView(engine: Engine, area: Area) {
const selectedCpus: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
+ exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
}
}
if (selectedCpus.length === 0) return false;
diff --git a/ui/src/controller/aggregation/frame_aggregation_controller.ts b/ui/src/controller/aggregation/frame_aggregation_controller.ts
index 7c91b4b..3d25557 100644
--- a/ui/src/controller/aggregation/frame_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/frame_aggregation_controller.ts
@@ -23,15 +23,11 @@
export class FrameAggregationController extends AggregationController {
async createAggregateView(engine: Engine, area: Area) {
const selectedSqlTrackIds: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- // Track will be undefined for track groups.
- if (track?.uri !== undefined) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
- trackInfo.tags.trackIds &&
- selectedSqlTrackIds.push(...trackInfo.tags.trackIds);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
+ trackInfo.tags.trackIds &&
+ selectedSqlTrackIds.push(...trackInfo.tags.trackIds);
}
}
if (selectedSqlTrackIds.length === 0) return false;
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index 96cb0ea..f1fdd5f 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -25,19 +25,15 @@
export function getSelectedTrackKeys(area: Area): number[] {
const selectedTrackKeys: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- // Track will be undefined for track groups.
- if (track?.uri !== undefined) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === THREAD_SLICE_TRACK_KIND) {
- trackInfo.tags.trackIds &&
- selectedTrackKeys.push(...trackInfo.tags.trackIds);
- }
- if (trackInfo?.tags?.kind === ASYNC_SLICE_TRACK_KIND) {
- trackInfo.tags.trackIds &&
- selectedTrackKeys.push(...trackInfo.tags.trackIds);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === THREAD_SLICE_TRACK_KIND) {
+ trackInfo.tags.trackIds &&
+ selectedTrackKeys.push(...trackInfo.tags.trackIds);
+ }
+ if (trackInfo?.tags?.kind === ASYNC_SLICE_TRACK_KIND) {
+ trackInfo.tags.trackIds &&
+ selectedTrackKeys.push(...trackInfo.tags.trackIds);
}
}
return selectedTrackKeys;
diff --git a/ui/src/controller/aggregation/thread_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
index 4f502e2..db593ad 100644
--- a/ui/src/controller/aggregation/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -28,20 +28,16 @@
setThreadStateUtids(tracks: string[]) {
this.utids = [];
- for (const trackId of tracks) {
- const track = globals.state.tracks[trackId];
- // Track will be undefined for track groups.
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === THREAD_STATE_TRACK_KIND) {
- exists(trackInfo.tags.utid) && this.utids.push(trackInfo.tags.utid);
- }
+ for (const trackUri of tracks) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === THREAD_STATE_TRACK_KIND) {
+ exists(trackInfo.tags.utid) && this.utids.push(trackInfo.tags.utid);
}
}
}
async createAggregateView(engine: Engine, area: Area) {
- this.setThreadStateUtids(area.tracks);
+ this.setThreadStateUtids(area.trackUris);
if (this.utids === undefined || this.utids.length === 0) return false;
await engine.query(`
@@ -68,7 +64,7 @@
}
async getExtra(engine: Engine, area: Area): Promise<ThreadStateExtra | void> {
- this.setThreadStateUtids(area.tracks);
+ this.setThreadStateUtids(area.trackUris);
if (this.utids === undefined || this.utids.length === 0) return;
const query = `
diff --git a/ui/src/controller/aggregation/wattson/estimate_aggregation_controller.ts b/ui/src/controller/aggregation/wattson/estimate_aggregation_controller.ts
index 896b136..4a17bab 100644
--- a/ui/src/controller/aggregation/wattson/estimate_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/wattson/estimate_aggregation_controller.ts
@@ -29,16 +29,13 @@
if (!(await hasWattsonSupport(engine))) return false;
const estimateTracks: string[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (
- trackInfo?.tags?.kind === CPUSS_ESTIMATE_TRACK_KIND &&
- exists(trackInfo.tags?.wattson)
- ) {
- estimateTracks.push(`${trackInfo.tags.wattson}`);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (
+ trackInfo?.tags?.kind === CPUSS_ESTIMATE_TRACK_KIND &&
+ exists(trackInfo.tags?.wattson)
+ ) {
+ estimateTracks.push(`${trackInfo.tags.wattson}`);
}
}
if (estimateTracks.length === 0) return false;
@@ -98,14 +95,14 @@
columnId: 'name',
},
{
- title: 'Average estimated power (mW)',
+ title: 'Average power (estimated mW)',
kind: 'NUMBER',
columnConstructor: Float64Array,
columnId: 'power',
sum: true,
},
{
- title: 'Total estimated energy (mWs)',
+ title: 'Total energy (estimated mWs)',
kind: 'NUMBER',
columnConstructor: Float64Array,
columnId: 'energy',
diff --git a/ui/src/controller/aggregation/wattson/package_aggregation_controller.ts b/ui/src/controller/aggregation/wattson/package_aggregation_controller.ts
index d43efdd..998b325 100644
--- a/ui/src/controller/aggregation/wattson/package_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/wattson/package_aggregation_controller.ts
@@ -36,13 +36,10 @@
if (packageInfo.firstRow({isValid: NUM}).isValid === 0) return false;
const selectedCpus: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
+ exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
}
}
if (selectedCpus.length === 0) return false;
@@ -88,14 +85,14 @@
columnId: 'dur_ms',
},
{
- title: 'Average estimated power (mW)',
+ title: 'Average power (estimated mW)',
kind: 'NUMBER',
columnConstructor: Float64Array,
columnId: 'avg_mw',
sum: true,
},
{
- title: 'Total estimated energy (mWs)',
+ title: 'Total energy (estimated mWs)',
kind: 'NUMBER',
columnConstructor: Float64Array,
columnId: 'total_mws',
diff --git a/ui/src/controller/aggregation/wattson/process_aggregation_controller.ts b/ui/src/controller/aggregation/wattson/process_aggregation_controller.ts
index d7675f8..e0c8631 100644
--- a/ui/src/controller/aggregation/wattson/process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/wattson/process_aggregation_controller.ts
@@ -29,14 +29,11 @@
if (!(await hasWattsonSupport(engine))) return false;
const selectedCpus: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
- }
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND &&
+ exists(trackInfo.tags.cpu) &&
+ selectedCpus.push(trackInfo.tags.cpu);
}
if (selectedCpus.length === 0) return false;
diff --git a/ui/src/controller/aggregation/wattson/thread_aggregation_controller.ts b/ui/src/controller/aggregation/wattson/thread_aggregation_controller.ts
index 125731b..69c4ed5 100644
--- a/ui/src/controller/aggregation/wattson/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/wattson/thread_aggregation_controller.ts
@@ -29,13 +29,10 @@
if (!(await hasWattsonSupport(engine))) return false;
const selectedCpus: number[] = [];
- for (const trackKey of area.tracks) {
- const track = globals.state.tracks[trackKey];
- if (track?.uri) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
+ exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu);
}
}
if (selectedCpus.length === 0) return false;
diff --git a/ui/src/controller/cpu_profile_controller.ts b/ui/src/controller/cpu_profile_controller.ts
deleted file mode 100644
index d658170..0000000
--- a/ui/src/controller/cpu_profile_controller.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (C) 2020 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 {CallsiteInfo} from '../common/legacy_flamegraph_util';
-import {CpuProfileSampleSelection, getLegacySelection} from '../common/state';
-import {CpuProfileDetails, globals} from '../frontend/globals';
-import {publishCpuProfileDetails} from '../frontend/publish';
-import {Engine} from '../trace_processor/engine';
-import {NUM, STR} from '../trace_processor/query_result';
-
-import {Controller} from './controller';
-
-export interface CpuProfileControllerArgs {
- engine: Engine;
-}
-
-export class CpuProfileController extends Controller<'main'> {
- private lastSelectedSample?: CpuProfileSampleSelection;
- private requestingData = false;
- private queuedRunRequest = false;
-
- constructor(private args: CpuProfileControllerArgs) {
- super('main');
- }
-
- run() {
- const selection = getLegacySelection(globals.state);
- if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') {
- return;
- }
-
- const selectedSample = selection as CpuProfileSampleSelection;
- if (!this.shouldRequestData(selectedSample)) {
- return;
- }
-
- if (this.requestingData) {
- this.queuedRunRequest = true;
- return;
- }
-
- this.requestingData = true;
- publishCpuProfileDetails({});
- this.lastSelectedSample = this.copyCpuProfileSample(selection);
-
- this.getSampleData(selectedSample.id)
- .then((sampleData) => {
- /* eslint-disable @typescript-eslint/strict-boolean-expressions */
- if (
- sampleData !== undefined &&
- selectedSample &&
- /* eslint-enable */
- this.lastSelectedSample &&
- this.lastSelectedSample.id === selectedSample.id
- ) {
- const cpuProfileDetails: CpuProfileDetails = {
- id: selectedSample.id,
- ts: selectedSample.ts,
- utid: selectedSample.utid,
- stack: sampleData,
- };
-
- publishCpuProfileDetails(cpuProfileDetails);
- }
- })
- .finally(() => {
- this.requestingData = false;
- if (this.queuedRunRequest) {
- this.queuedRunRequest = false;
- this.run();
- }
- });
- }
-
- private copyCpuProfileSample(
- cpuProfileSample: CpuProfileSampleSelection,
- ): CpuProfileSampleSelection {
- return {
- kind: cpuProfileSample.kind,
- id: cpuProfileSample.id,
- utid: cpuProfileSample.utid,
- ts: cpuProfileSample.ts,
- };
- }
-
- private shouldRequestData(selection: CpuProfileSampleSelection) {
- return (
- this.lastSelectedSample === undefined ||
- (this.lastSelectedSample !== undefined &&
- this.lastSelectedSample.id !== selection.id)
- );
- }
-
- async getSampleData(id: number) {
- // The goal of the query is to get all the frames of
- // the callstack at the callsite given by |id|. To do this, it does
- // the following:
- // 1. Gets the leaf callsite id for the sample given by |id|.
- // 2. For this callsite, get all the frame ids and depths
- // for the frame and all ancestors in the callstack.
- // 3. For each frame, get the mapping name (i.e. library which
- // contains the frame).
- // 4. Symbolize each frame using the symbol table if possible.
- // 5. Sort the query by the depth of the callstack frames.
- const sampleQuery = `
- SELECT
- samples.id as id,
- IFNULL(
- (
- SELECT name
- FROM stack_profile_symbol symbol
- WHERE symbol.symbol_set_id = spf.symbol_set_id
- LIMIT 1
- ),
- COALESCE(spf.deobfuscated_name, spf.name, "")
- ) AS name,
- spm.name AS mapping
- FROM cpu_profile_stack_sample AS samples
- LEFT JOIN (
- SELECT
- id,
- frame_id,
- depth
- FROM stack_profile_callsite
- UNION ALL
- SELECT
- leaf.id AS id,
- callsite.frame_id AS frame_id,
- callsite.depth AS depth
- FROM stack_profile_callsite leaf
- JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite
- ) AS callsites
- ON samples.callsite_id = callsites.id
- LEFT JOIN stack_profile_frame AS spf
- ON callsites.frame_id = spf.id
- LEFT JOIN stack_profile_mapping AS spm
- ON spf.mapping = spm.id
- WHERE samples.id = ${id}
- ORDER BY callsites.depth;
- `;
-
- const callsites = await this.args.engine.query(sampleQuery);
-
- if (callsites.numRows() === 0) {
- return undefined;
- }
-
- const it = callsites.iter({
- id: NUM,
- name: STR,
- mapping: STR,
- });
-
- const sampleData: CallsiteInfo[] = [];
- for (; it.valid(); it.next()) {
- sampleData.push({
- id: it.id,
- totalSize: 0,
- depth: 0,
- parentId: 0,
- name: it.name,
- selfSize: 0,
- mapping: it.mapping,
- merged: false,
- highlighted: false,
- });
- }
-
- return sampleData;
- }
-}
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index d6e1cd5..e570b38 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -27,6 +27,7 @@
ACTUAL_FRAMES_SLICE_TRACK_KIND,
THREAD_SLICE_TRACK_KIND,
} from '../core/track_kinds';
+import {TrackDescriptor} from '../public';
export interface FlowEventsControllerArgs {
engine: Engine;
@@ -190,7 +191,6 @@
// We end up with one Info POJOs for each UI async slice track
// which has at least one flow {begin,end}ing in one of its slices.
interface Info {
- uiTrackId: string;
siblingTrackIds: number[];
sliceIds: number[];
nodes: Array<{
@@ -199,11 +199,17 @@
}>;
}
- const uiTrackIdToInfo = new Map<string, null | Info>();
+ const trackUriToInfo = new Map<string, null | Info>();
const trackIdToInfo = new Map<number, null | Info>();
- const trackIdToUiTrackId = globals.trackManager.trackKeyByTrackId;
- const tracks = globals.state.tracks;
+ const trackIdToTrack = new Map<number, TrackDescriptor>();
+ globals.trackManager
+ .getAllTracks()
+ .forEach((trackDescriptor) =>
+ trackDescriptor.tags?.trackIds?.forEach((trackId) =>
+ trackIdToTrack.set(trackId, trackDescriptor),
+ ),
+ );
const getInfo = (trackId: number): null | Info => {
let info = trackIdToInfo.get(trackId);
@@ -211,19 +217,13 @@
return info;
}
- const uiTrackId = trackIdToUiTrackId.get(trackId);
- if (uiTrackId === undefined) {
+ const trackDescriptor = trackIdToTrack.get(trackId);
+ if (trackDescriptor === undefined) {
trackIdToInfo.set(trackId, null);
return null;
}
- const track = tracks[uiTrackId];
- if (track === undefined) {
- trackIdToInfo.set(trackId, null);
- return null;
- }
-
- info = uiTrackIdToInfo.get(uiTrackId);
+ info = trackUriToInfo.get(trackDescriptor.uri);
if (info !== undefined) {
return info;
}
@@ -233,22 +233,20 @@
// anything if there is only one TP track in this async track. In
// that case experimental_slice_layout is just an expensive way
// to find out depth === layout_depth.
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- const trackIds = trackInfo?.tags?.trackIds;
+ const trackIds = trackDescriptor?.tags?.trackIds;
if (trackIds === undefined || trackIds.length <= 1) {
- uiTrackIdToInfo.set(uiTrackId, null);
+ trackUriToInfo.set(trackDescriptor.uri, null);
trackIdToInfo.set(trackId, null);
return null;
}
const newInfo = {
- uiTrackId,
siblingTrackIds: [...trackIds],
sliceIds: [],
nodes: [],
};
- uiTrackIdToInfo.set(uiTrackId, newInfo);
+ trackUriToInfo.set(trackDescriptor.uri, newInfo);
trackIdToInfo.set(trackId, newInfo);
return newInfo;
@@ -268,7 +266,7 @@
// Second pass, for each async track:
// - Query to find the layout_depth for each relevant sliceId
// - Iterate through the nodes updating the depth in place
- for (const info of uiTrackIdToInfo.values()) {
+ for (const info of trackUriToInfo.values()) {
if (info === null) {
continue;
}
@@ -361,19 +359,16 @@
private areaSelected(area: AreaSelection) {
const trackIds: number[] = [];
- for (const uiTrackId of area.tracks) {
- const track = globals.state.tracks[uiTrackId];
- if (track?.uri !== undefined) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- const kind = trackInfo?.tags?.kind;
- if (
- kind === THREAD_SLICE_TRACK_KIND ||
- kind === ACTUAL_FRAMES_SLICE_TRACK_KIND
- ) {
- if (trackInfo?.tags?.trackIds) {
- for (const trackId of trackInfo.tags.trackIds) {
- trackIds.push(trackId);
- }
+ for (const trackUri of area.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ const kind = trackInfo?.tags?.kind;
+ if (
+ kind === THREAD_SLICE_TRACK_KIND ||
+ kind === ACTUAL_FRAMES_SLICE_TRACK_KIND
+ ) {
+ if (trackInfo?.tags?.trackIds) {
+ for (const trackId of trackInfo.tags.trackIds) {
+ trackIds.push(trackId);
}
}
}
diff --git a/ui/src/controller/pivot_table_controller.ts b/ui/src/controller/pivot_table_controller.ts
index bfa46ac..036138c 100644
--- a/ui/src/controller/pivot_table_controller.ts
+++ b/ui/src/controller/pivot_table_controller.ts
@@ -217,7 +217,7 @@
return false;
}
- const newTracks = new Set(selection.tracks);
+ const newTracks = new Set(selection.trackUris);
if (
this.lastQueryArea !== state.selectionArea ||
!this.sameTracks(newTracks)
@@ -308,6 +308,6 @@
function areasEqual(a: Area, b: Area): boolean {
if (a.start !== b.start) return false;
if (a.end !== b.end) return false;
- if (!arrayEquals(a.tracks, b.tracks)) return false;
+ if (!arrayEquals(a.trackUris, b.trackUris)) return false;
return true;
}
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index b7d2c45..ad0031a 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -13,10 +13,13 @@
// limitations under the License.
import {sqliteString} from '../base/string_utils';
-import {exists} from '../base/utils';
+import {exists, Optional} from '../base/utils';
import {CurrentSearchResults, SearchSource} from '../common/search_data';
import {OmniboxState} from '../common/state';
-import {CPU_SLICE_TRACK_KIND} from '../core/track_kinds';
+import {
+ ANDROID_LOGS_TRACK_KIND,
+ CPU_SLICE_TRACK_KIND,
+} from '../core/track_kinds';
import {globals} from '../frontend/globals';
import {publishSearchResult} from '../frontend/publish';
import {Engine} from '../trace_processor/engine';
@@ -65,7 +68,7 @@
tses: new BigInt64Array(0),
utids: new Float64Array(0),
sources: [],
- trackKeys: [],
+ trackUris: [],
totalResults: 0,
});
return;
@@ -85,16 +88,24 @@
private async specificSearch(search: string) {
const searchLiteral = escapeSearchQuery(search);
- // TODO(hjd): we should avoid recomputing this every time. This will be
- // easier once the track table has entries for all the tracks.
- const cpuToTrackId = new Map();
- for (const track of Object.values(globals.state.tracks)) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.tags.cpu) &&
- cpuToTrackId.set(trackInfo.tags.cpu, track.key);
- }
- }
+
+ // TODO(stevegolton): Avoid recomputing these indexes each time.
+ const trackUrisByCpu = new Map<number, string>();
+ globals.trackManager.getAllTracks().forEach((td) => {
+ const tags = td?.tags;
+ const cpu = tags?.cpu;
+ const kind = tags?.kind;
+ exists(cpu) &&
+ kind === CPU_SLICE_TRACK_KIND &&
+ trackUrisByCpu.set(cpu, td.uri);
+ });
+
+ const trackUrisByTrackId = new Map<number, string>();
+ globals.trackManager.getAllTracks().forEach((td) => {
+ const trackIds = td?.tags?.trackIds;
+ trackIds &&
+ trackIds.forEach((trackId) => trackUrisByTrackId.set(trackId, td.uri));
+ });
const utidRes = await this.query(`select utid from thread join process
using(upid) where
@@ -155,18 +166,18 @@
tses: new BigInt64Array(0),
utids: new Float64Array(0),
sources: [],
- trackKeys: [],
+ trackUris: [],
totalResults: 0,
};
const lowerSearch = search.toLowerCase();
- for (const track of Object.values(globals.state.tracks)) {
- if (track.name.toLowerCase().indexOf(lowerSearch) === -1) {
+ for (const track of globals.workspace.flatTracks) {
+ if (track.displayName.toLowerCase().indexOf(lowerSearch) === -1) {
continue;
}
searchResults.totalResults++;
searchResults.sources.push('track');
- searchResults.trackKeys.push(track.key);
+ searchResults.trackUris.push(track.uri);
}
const rows = res.numRows();
@@ -189,30 +200,24 @@
utid: NUM,
});
for (; it.valid(); it.next()) {
- let trackId = undefined;
+ let track: Optional<string> = undefined;
if (it.source === 'cpu') {
- trackId = cpuToTrackId.get(it.sourceId);
+ track = trackUrisByCpu.get(it.sourceId);
} else if (it.source === 'slice') {
- trackId = globals.trackManager.trackKeyByTrackId.get(it.sourceId);
+ track = trackUrisByTrackId.get(it.sourceId);
} else if (it.source === 'log') {
- const logTracks = Object.values(globals.state.tracks).filter(
- (track) => {
- const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
- return trackDesc && trackDesc.tags?.kind === 'AndroidLogTrack';
- },
- );
- if (logTracks.length > 0) {
- trackId = logTracks[0].key;
- }
+ track = globals.trackManager
+ .getAllTracks()
+ .find((td) => td.tags?.kind === ANDROID_LOGS_TRACK_KIND)?.uri;
}
// The .get() calls above could return undefined, this isn't just an else.
- if (trackId === undefined) {
+ if (track === undefined) {
continue;
}
const i = searchResults.totalResults++;
- searchResults.trackKeys.push(trackId);
+ searchResults.trackUris.push(track);
searchResults.sources.push(it.source as SearchSource);
searchResults.eventIds[i] = it.sliceId;
searchResults.tses[i] = it.ts;
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 4cf83b4..e68b088 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -288,10 +288,10 @@
const name = it.name;
const value = it.value ?? 'NULL';
if (name === 'destination slice id' && !isNaN(Number(value))) {
- const destTrackId = await this.getDestTrackId(value);
+ const destTrackUri = await this.getDestTrackUri(value);
args.set('Destination Slice', {
kind: 'SCHED_SLICE',
- trackId: destTrackId,
+ trackUri: destTrackUri,
sliceId: Number(value),
rawValue: value,
});
@@ -302,25 +302,23 @@
return args;
}
- async getDestTrackId(sliceId: string): Promise<string> {
+ async getDestTrackUri(sliceId: string): Promise<string> {
const trackIdQuery = `select track_id as trackId from slice
where slice_id = ${sliceId}`;
const result = await this.args.engine.query(trackIdQuery);
const trackId = result.firstRow({trackId: NUM}).trackId;
// TODO(hjd): If we had a consistent mapping from TP track_id
// UI track id for slice tracks this would be unnecessary.
- let trackKey = '';
- for (const track of Object.values(globals.state.tracks)) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
+ for (const track of globals.workspace.flatTracks) {
+ const trackInfo = globals.trackManager.getTrack(track.uri);
if (trackInfo?.tags?.kind === THREAD_SLICE_TRACK_KIND) {
const trackIds = trackInfo?.tags?.trackIds;
if (trackIds && trackIds.length > 0 && trackIds[0] === trackId) {
- trackKey = track.key;
- break;
+ return track.uri;
}
}
}
- return trackKey;
+ return '';
}
// TODO(altimin): We currently rely on the ThreadStateDetails for supporting
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 95b7bc5..812fd2f 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -69,10 +69,6 @@
import {ThreadAggregationController} from './aggregation/thread_aggregation_controller';
import {Child, Children, Controller} from './controller';
import {
- CpuProfileController,
- CpuProfileControllerArgs,
-} from './cpu_profile_controller';
-import {
FlowEventsController,
FlowEventsControllerArgs,
} from './flow_events_controller';
@@ -94,13 +90,12 @@
TraceStream,
} from '../core/trace_stream';
import {decideTracks} from './track_decider';
-import {profileType} from '../frontend/legacy_flamegraph_panel';
-import {LegacyFlamegraphCache} from '../core/legacy_flamegraph_cache';
import {
deserializeAppStatePhase1,
deserializeAppStatePhase2,
} from '../common/state_serialization';
import {TraceContext} from '../frontend/trace_context';
+import {profileType} from '../core/selection_manager';
type States = 'init' | 'loading_trace' | 'ready';
@@ -275,11 +270,6 @@
Child('flowEvents', FlowEventsController, flowEventsArgs),
);
- const cpuProfileArgs: CpuProfileControllerArgs = {engine};
- childControllers.push(
- Child('cpuProfile', CpuProfileController, cpuProfileArgs),
- );
-
childControllers.push(
Child('cpu_aggregation', CpuAggregationController, {
engine,
@@ -388,10 +378,6 @@
onDestroy() {
pluginManager.onTraceClose();
globals.engines.delete(this.engineId);
-
- // Invalidate the flamegraph cache.
- // TODO(stevegolton): migrate this to the new system when it's ready.
- globals.areaFlamegraphCache = new LegacyFlamegraphCache('area');
}
private async loadTrace(): Promise<EngineMode> {
@@ -548,6 +534,10 @@
await defineMaxLayoutDepthSqlFunction(engine);
+ // Remove all workspaces, and create an empty default workspace, ready for
+ // tracks to be inserted.
+ globals.resetWorkspaces();
+
if (globals.restoreAppStateAfterTraceLoad) {
deserializeAppStatePhase1(globals.restoreAppStateAfterTraceLoad);
}
@@ -557,9 +547,10 @@
});
{
- // When we reload from a permalink don't create extra tracks:
- const {pinnedTracks, tracks} = globals.state;
- if (!pinnedTracks.length && !Object.keys(tracks).length) {
+ // When we reload from a permalink don't create extra tracks.
+ // TODO(stevegolton): This is a terrible way of telling whether we have
+ // loaded from a permalink or not.
+ if (globals.workspace.flatTracks.length === 0) {
await this.listTracks();
}
}
@@ -583,9 +574,6 @@
publishHasFtrace(res.numRows() > 0);
}
- globals.dispatch(Actions.sortThreadTracks({}));
- globals.dispatch(Actions.maybeExpandOnlyTrackGroup({}));
-
await this.selectFirstHeapProfile();
await this.selectPerfSample(traceDetails);
@@ -610,8 +598,6 @@
}
}
- globals.dispatch(Actions.maybeExpandOnlyTrackGroup({}));
-
// Trace Processor doesn't support the reliable range feature for JSON
// traces.
if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()) {
@@ -642,12 +628,14 @@
}
private async selectPerfSample(traceTime: {start: time; end: time}) {
- const query = `select upid
- from perf_sample
- join thread using (utid)
- where callsite_id is not null
- order by ts desc limit 1`;
- const profile = await assertExists(this.engine).query(query);
+ const profile = await assertExists(this.engine).query(`
+ select upid
+ from perf_sample
+ join thread using (utid)
+ where callsite_id is not null
+ order by ts desc
+ limit 1
+ `);
if (profile.numRows() !== 1) return;
const row = profile.firstRow({upid: NUM});
const upid = row.upid;
@@ -665,28 +653,29 @@
}
private async selectFirstHeapProfile() {
- const query = `select * from (
- select
- min(ts) AS ts,
- 'heap_profile:' || group_concat(distinct heap_name) AS type,
- upid
- from heap_profile_allocation
- group by upid
- union
- select distinct graph_sample_ts as ts, 'graph' as type, upid
- from heap_graph_object)
- order by ts limit 1`;
+ const query = `
+ select * from (
+ select
+ min(ts) AS ts,
+ 'heap_profile:' || group_concat(distinct heap_name) AS type,
+ upid
+ from heap_profile_allocation
+ group by upid
+ union
+ select distinct graph_sample_ts as ts, 'graph' as type, upid
+ from heap_graph_object
+ )
+ order by ts
+ limit 1
+ `;
const profile = await assertExists(this.engine).query(query);
if (profile.numRows() !== 1) return;
const row = profile.firstRow({ts: LONG, type: STR, upid: NUM});
const ts = Time.fromRaw(row.ts);
- let profType = row.type;
- if (profType == 'heap_profile:libc.malloc,com.android.art') {
- profType = 'heap_profile:com.android.art,libc.malloc';
- }
- const type = profileType(profType);
const upid = row.upid;
- globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
+ globals.dispatch(
+ Actions.selectHeapProfile({id: 0, upid, ts, type: profileType(row.type)}),
+ );
}
private async selectPendingDeeplink(link: PendingDeeplinkState) {
@@ -722,15 +711,17 @@
});
const id = row.traceProcessorTrackId;
- const trackKey = globals.trackManager.trackKeyByTrackId.get(id);
- if (trackKey === undefined) {
+ const track = globals.workspace.flatTracks.find((t) =>
+ globals.trackManager.getTrack(t.uri)?.tags?.trackIds?.includes(id),
+ );
+ if (track === undefined) {
return;
}
globals.setLegacySelection(
{
kind: 'SLICE',
id: row.id,
- trackKey,
+ trackUri: track.uri,
table: 'slice',
},
{
@@ -745,8 +736,7 @@
private async listTracks() {
this.updateStatus('Loading tracks');
const engine = assertExists(this.engine);
- const actions = await decideTracks(engine);
- globals.dispatchMultiple(actions);
+ await decideTracks(engine);
}
// Show the list of default tabs, but don't make them active!
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 7b6ad92..d7a9e0e 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -12,21 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-
import {assertExists} from '../base/logging';
-import {Actions, AddTrackArgs, DeferredAction} from '../common/actions';
-import {
- InThreadTrackSortKey,
- SCROLLING_TRACK_GROUP,
- TrackSortKey,
- UtidToTrackSortKey,
-} from '../common/state';
import {globals} from '../frontend/globals';
-import {PrimaryTrackSortKey, TrackDescriptor} from '../public';
-import {getThreadOrProcUri, getTrackName} from '../public/utils';
+import {TrackDescriptor} from '../public';
import {Engine, EngineBase} from '../trace_processor/engine';
-import {NUM, NUM_NULL, STR_NULL} from '../trace_processor/query_result';
+import {NUM, STR, STR_NULL} from '../trace_processor/query_result';
import {
ACTUAL_FRAMES_SLICE_TRACK_KIND,
ASYNC_SLICE_TRACK_KIND,
@@ -44,8 +34,8 @@
THREAD_SLICE_TRACK_KIND,
THREAD_STATE_TRACK_KIND,
} from '../core/track_kinds';
-import {exists} from '../base/utils';
-import {sqliteString} from '../base/string_utils';
+import {exists, Optional} from '../base/utils';
+import {GroupNode, ContainerNode, TrackNode} from '../public/workspace';
const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
const MEM_DMA = 'mem.dma_buffer';
@@ -78,34 +68,31 @@
const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*');
const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
const MISC_GROUP = 'Misc Global Tracks';
-const SCROLL_JANK_GROUP_ID = 'chrome-scroll-jank-track-group';
-export async function decideTracks(
- engine: EngineBase,
-): Promise<DeferredAction[]> {
- return new TrackDecider(engine).decideTracks();
+export async function decideTracks(engine: EngineBase): Promise<void> {
+ await new TrackDecider(engine).decideTracks();
}
class TrackDecider {
private engine: EngineBase;
- private upidToUuid = new Map<number, string>();
- private utidToUuid = new Map<number, string>();
- private tracksToAdd: AddTrackArgs[] = [];
- private tracksToPin: string[] = [];
- private addTrackGroupActions: DeferredAction[] = [];
+ private threadGroups = new Map<number, GroupNode>();
+ private processGroups = new Map<number, GroupNode>();
constructor(engine: EngineBase) {
this.engine = engine;
}
private groupGlobalIonTracks(): void {
- const ionTracks: AddTrackArgs[] = [];
+ const ionTracks: TrackNode[] = [];
let hasSummary = false;
- for (const track of this.tracksToAdd) {
- const isIon = track.name.startsWith(MEM_ION);
- const isIonCounter = track.name === MEM_ION;
- const isDmaHeapCounter = track.name === MEM_DMA_COUNTER_NAME;
- const isDmaBuffferSlices = track.name === MEM_DMA;
+
+ for (const track of globals.workspace.children) {
+ if (!(track instanceof TrackNode)) continue;
+
+ const isIon = track.displayName.startsWith(MEM_ION);
+ const isIonCounter = track.displayName === MEM_ION;
+ const isDmaHeapCounter = track.displayName === MEM_DMA_COUNTER_NAME;
+ const isDmaBuffferSlices = track.displayName === MEM_DMA;
if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) {
ionTracks.push(track);
}
@@ -117,136 +104,85 @@
return;
}
- const groupUuid = uuidv4();
- const summaryTrackKey = uuidv4();
- let foundSummary = false;
-
+ let group: Optional<GroupNode>;
for (const track of ionTracks) {
- if (
- !foundSummary &&
- [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name)
- ) {
- foundSummary = true;
- track.key = summaryTrackKey;
- track.trackGroup = undefined;
+ if (!group && [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.uri)) {
+ globals.workspace.removeChild(track);
+ group = new GroupNode(track.displayName);
+ group.headerTrackUri = track.uri;
+ globals.workspace.addChild(group);
} else {
- track.trackGroup = groupUuid;
+ group?.addChild(track);
}
}
-
- const addGroup = Actions.addTrackGroup({
- summaryTrackKey,
- name: MEM_DMA_COUNTER_NAME,
- key: groupUuid,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
}
- private groupGlobalIostatTracks(tag: string, group: string): void {
- const iostatTracks: AddTrackArgs[] = [];
- const devMap = new Map<string, string>();
+ private groupGlobalIostatTracks(tag: string, groupName: string): void {
+ const devMap = new Map<string, GroupNode>();
- for (const track of this.tracksToAdd) {
- if (track.name.startsWith(tag)) {
- iostatTracks.push(track);
+ for (const track of globals.workspace.children) {
+ if (track instanceof TrackNode && track.displayName.startsWith(tag)) {
+ const name = track.displayName.split('.', 3);
+ const key = name[1];
+
+ let parentGroup = devMap.get(key);
+ if (!parentGroup) {
+ const group = new GroupNode(groupName);
+ globals.workspace.addChild(group);
+ devMap.set(key, group);
+ parentGroup = group;
+ }
+
+ track.displayName = name[2];
+ parentGroup.addChild(track);
}
}
-
- if (iostatTracks.length === 0) {
- return;
- }
-
- for (const track of iostatTracks) {
- const name = track.name.split('.', 3);
-
- if (!devMap.has(name[1])) {
- devMap.set(name[1], uuidv4());
- }
- track.name = name[2];
- track.trackGroup = devMap.get(name[1]);
- }
-
- for (const [key, value] of devMap) {
- const groupName = group + key;
- const addGroup = Actions.addTrackGroup({
- name: groupName,
- key: value,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
- }
}
private groupGlobalBuddyInfoTracks(): void {
- const buddyInfoTracks: AddTrackArgs[] = [];
- const devMap = new Map<string, string>();
+ const devMap = new Map<string, GroupNode>();
- for (const track of this.tracksToAdd) {
- if (track.name.startsWith(BUDDY_INFO_TAG)) {
- buddyInfoTracks.push(track);
+ for (const track of globals.workspace.children) {
+ if (
+ track instanceof TrackNode &&
+ track.displayName.startsWith(BUDDY_INFO_TAG)
+ ) {
+ const tokens = track.uri.split('[');
+ const node = tokens[1].slice(0, -1);
+ const zone = tokens[2].slice(0, -1);
+ const size = tokens[3].slice(0, -1);
+
+ const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone;
+ if (!devMap.has(groupName)) {
+ const group = new GroupNode(groupName);
+ devMap.set(groupName, group);
+ globals.workspace.addChild(group);
+ }
+ track.displayName = 'Chunk size: ' + size;
+ const group = devMap.get(groupName)!;
+ group.addChild(track);
}
}
-
- if (buddyInfoTracks.length === 0) {
- return;
- }
-
- for (const track of buddyInfoTracks) {
- const tokens = track.name.split('[');
- const node = tokens[1].slice(0, -1);
- const zone = tokens[2].slice(0, -1);
- const size = tokens[3].slice(0, -1);
-
- const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone;
- if (!devMap.has(groupName)) {
- devMap.set(groupName, uuidv4());
- }
- track.name = 'Chunk size: ' + size;
- track.trackGroup = devMap.get(groupName);
- }
-
- for (const [key, value] of devMap) {
- const groupName = key;
- const addGroup = Actions.addTrackGroup({
- name: groupName,
- key: value,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
- }
}
private groupFrequencyTracks(groupName: string): void {
- let groupUuid = undefined;
- for (const track of this.tracksToAdd) {
+ const group = new GroupNode(groupName);
+
+ for (const track of globals.workspace.children) {
+ if (!(track instanceof TrackNode)) continue;
// Group all the frequency tracks together (except the CPU and GPU
// frequency ones).
if (
- track.name.endsWith('Frequency') &&
- !track.name.startsWith('Cpu') &&
- !track.name.startsWith('Gpu')
+ track.displayName.endsWith('Frequency') &&
+ !track.displayName.startsWith('Cpu') &&
+ !track.displayName.startsWith('Gpu')
) {
- if (
- track.trackGroup !== undefined &&
- track.trackGroup !== SCROLLING_TRACK_GROUP
- ) {
- continue;
- }
- if (groupUuid === undefined) {
- groupUuid = uuidv4();
- }
- track.trackGroup = groupUuid;
+ group.addChild(track);
}
}
- if (groupUuid !== undefined) {
- const addGroup = Actions.addTrackGroup({
- name: groupName,
- key: groupUuid,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
+ if (group.children.length > 0) {
+ globals.workspace.addChild(group);
}
}
@@ -261,62 +197,35 @@
new RegExp('^Android logs$'),
];
- let groupUuid = undefined;
- for (const track of this.tracksToAdd) {
- if (
- track.trackGroup !== undefined &&
- track.trackGroup !== SCROLLING_TRACK_GROUP
- ) {
- continue;
- }
+ const group = new GroupNode(groupName);
+ for (const track of globals.workspace.children) {
+ if (!(track instanceof TrackNode)) continue;
let allowlisted = false;
for (const regex of ALLOWLIST_REGEXES) {
- allowlisted = allowlisted || regex.test(track.name);
+ allowlisted = allowlisted || regex.test(track.displayName);
}
if (allowlisted) {
continue;
}
- if (groupUuid === undefined) {
- groupUuid = uuidv4();
- }
- track.trackGroup = groupUuid;
+ group.addChild(track);
}
- if (groupUuid !== undefined) {
- const addGroup = Actions.addTrackGroup({
- name: groupName,
- key: groupUuid,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
+ if (group.children.length > 0) {
+ globals.workspace.addChild(group);
}
}
private groupTracksByRegex(regex: RegExp, groupName: string): void {
- let groupUuid = undefined;
+ const group = new GroupNode(groupName);
- for (const track of this.tracksToAdd) {
- if (regex.test(track.name)) {
- if (
- track.trackGroup !== undefined &&
- track.trackGroup !== SCROLLING_TRACK_GROUP
- ) {
- continue;
- }
- if (groupUuid === undefined) {
- groupUuid = uuidv4();
- }
- track.trackGroup = groupUuid;
+ for (const track of globals.workspace.children) {
+ if (track instanceof TrackNode && regex.test(track.displayName)) {
+ group.addChild(track);
}
}
- if (groupUuid !== undefined) {
- const addGroup = Actions.addTrackGroup({
- name: groupName,
- key: groupUuid,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
+ if (group.children.length > 0) {
+ globals.workspace.addChild(group);
}
}
@@ -324,13 +233,7 @@
const annotationTracks = tracks.filter(
({tags}) => tags?.scope === 'annotation',
);
-
- interface GroupIds {
- id: string;
- summaryTrackKey: string;
- }
-
- const groupNameToKeys = new Map<string, GroupIds>();
+ const groups = new Map<string, GroupNode>();
annotationTracks
.filter(({tags}) => tags?.kind === THREAD_SLICE_TRACK_KIND)
@@ -338,60 +241,42 @@
const upid = assertExists(td.tags?.upid);
const groupName = td.tags?.groupName;
- let summaryTrackKey = undefined;
- let trackGroupId =
- upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid);
+ // We want to try and find a group to put this track in. If groupName is
+ // defined, create a new group or place in existing one if it already
+ // exists Otherwise, try upid to see if we can put this in a process
+ // group
- if (groupName !== undefined) {
- // If this is the first track encountered for a certain group,
- // create an id for the group and use this track as the group's
- // summary track.
- const groupKeys = groupNameToKeys.get(groupName);
- if (groupKeys) {
- trackGroupId = groupKeys.id;
+ let container: ContainerNode;
+ if (groupName) {
+ const existingGroup = groups.get(groupName);
+ if (!existingGroup) {
+ const group = new GroupNode(groupName);
+ group.headerTrackUri = td.uri;
+ container = group;
+ groups.set(groupName, group);
+ globals.workspace.addChild(group);
} else {
- trackGroupId = uuidv4();
- summaryTrackKey = uuidv4();
- groupNameToKeys.set(groupName, {
- id: trackGroupId,
- summaryTrackKey,
- });
+ container = existingGroup;
+ }
+ } else {
+ const procGroup = this.processGroups.get(upid);
+ if (upid !== 0 && procGroup) {
+ container = procGroup;
+ } else {
+ container = globals.workspace;
}
}
- this.tracksToAdd.push({
- uri: td.uri,
- key: summaryTrackKey,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: trackGroupId,
- });
+ container.addChild(new TrackNode(td.uri, td.title));
});
- for (const [groupName, groupKeys] of groupNameToKeys) {
- const addGroup = Actions.addTrackGroup({
- summaryTrackKey: groupKeys.summaryTrackKey,
- name: groupName,
- key: groupKeys.id,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addGroup);
- }
-
annotationTracks
.filter(({tags}) => tags?.kind === COUNTER_TRACK_KIND)
.forEach((td) => {
const upid = td.tags?.upid;
-
- this.tracksToAdd.push({
- uri: td.uri,
- key: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
- trackGroup: exists(upid)
- ? this.upidToUuid.get(upid)
- : SCROLLING_TRACK_GROUP,
- });
+ const parent =
+ (exists(upid) && this.processGroups.get(upid)) || globals.workspace;
+ parent.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -402,27 +287,9 @@
tags?.kind === THREAD_STATE_TRACK_KIND && tags?.utid !== undefined,
)
.forEach((td) => {
- const upid = td.tags?.upid ?? null;
const utid = assertExists(td.tags?.utid);
-
- const groupId = this.getUuidUnchecked(utid, upid);
- if (groupId === undefined) {
- // If a thread has no scheduling activity (i.e. the sched table has zero
- // rows for that uid) no track group will be created and we want to skip
- // the track creation as well.
- return;
- }
-
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackGroup: groupId,
- trackSortKey: {
- utid,
- priority: InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK,
- },
- });
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -436,18 +303,8 @@
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
- const upid = td.tags?.upid ?? null;
- const groupId = this.getUuid(utid, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: {
- utid,
- priority: InThreadTrackSortKey.CPU_STACK_SAMPLES_TRACK,
- },
- trackGroup: groupId,
- });
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -461,18 +318,8 @@
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
- const upid = td.tags?.upid ?? null;
- const groupId = this.getUuid(utid, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: {
- utid,
- priority: InThreadTrackSortKey.ORDINARY,
- },
- trackGroup: groupId,
- });
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -488,14 +335,36 @@
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
- const groupId = this.getUuid(null, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
- trackGroup: groupId,
- });
+ const group = this.getProcGroup(upid);
+ group.addChild(new TrackNode(td.uri, td.title));
+ });
+ }
+
+ private addUserAsyncSliceTracks(
+ tracks: ReadonlyArray<TrackDescriptor>,
+ ): void {
+ const groupMap = new Map<string, GroupNode>();
+ tracks
+ .filter(
+ ({tags}) =>
+ tags?.kind === ASYNC_SLICE_TRACK_KIND &&
+ tags?.scope === 'user' &&
+ tags?.rawName !== undefined,
+ )
+ .forEach((td) => {
+ const rawName = td.tags?.rawName;
+ if (typeof rawName === 'string') {
+ const track = new TrackNode(td.uri, td.title);
+ const existingGroup = groupMap.get(rawName);
+ if (existingGroup) {
+ existingGroup.addChild(track);
+ } else {
+ const group = new GroupNode(rawName);
+ globals.workspace.addChild(group);
+ groupMap.set(rawName, group);
+ group.addChild(track);
+ }
+ }
});
}
@@ -508,15 +377,8 @@
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
- const groupId = this.getUuid(null, upid);
-
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK,
- trackGroup: groupId,
- });
+ const group = this.getProcGroup(upid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -531,15 +393,8 @@
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
- const groupId = this.getUuid(null, upid);
-
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK,
- trackGroup: groupId,
- });
+ const group = this.getProcGroup(upid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -551,22 +406,27 @@
)
.forEach((td) => {
const utid = assertExists(td.tags?.utid);
- const upid = td.tags?.upid ?? null;
- const isDefaultTrackForScope = Boolean(td.tags?.isDefaultTrackForScope);
- const groupId = this.getUuid(utid, upid);
+ // const upid = td.tags?.upid;
+ // const isDefaultTrackForScope = Boolean(td.tags?.isDefaultTrackForScope);
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
+ });
+ }
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackGroup: groupId,
- trackSortKey: {
- utid,
- priority: isDefaultTrackForScope
- ? InThreadTrackSortKey.DEFAULT_TRACK
- : InThreadTrackSortKey.ORDINARY,
- },
- });
+ private addAsyncThreadSliceTracks(
+ tracks: ReadonlyArray<TrackDescriptor>,
+ ): void {
+ tracks
+ .filter(
+ ({tags}) =>
+ tags?.kind === ASYNC_SLICE_TRACK_KIND &&
+ tags?.utid !== undefined &&
+ tags?.scope === 'thread',
+ )
+ .forEach((td) => {
+ const utid = assertExists(td.tags?.utid);
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -582,21 +442,11 @@
for (const td of processCounterTracks) {
const upid = assertExists(td.tags?.upid);
- const groupId = this.getUuid(null, upid);
- const trackNameTag = td.tags?.trackName;
- const trackName =
- typeof trackNameTag === 'string' ? trackNameTag : undefined;
-
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack(
- upid,
- trackName,
- ),
- trackGroup: groupId,
- });
+ const group = this.getProcGroup(upid);
+ // const trackNameTag = td.tags?.trackName;
+ // const trackName =
+ // typeof trackNameTag === 'string' ? trackNameTag : undefined;
+ group.addChild(new TrackNode(td.uri, td.title));
}
}
@@ -610,14 +460,8 @@
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
- const groupId = this.getUuid(null, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
- trackGroup: groupId,
- });
+ const group = this.getProcGroup(upid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -633,14 +477,8 @@
)
.forEach((td) => {
const upid = assertExists(td.tags?.upid);
- const groupId = this.getUuid(null, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK,
- trackGroup: groupId,
- });
+ const group = this.getProcGroup(upid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
@@ -654,44 +492,29 @@
tags?.utid !== undefined,
)
.forEach((td) => {
- const upid = td.tags?.upid ?? null;
+ // const upid = td.tags?.upid;
const utid = assertExists(td.tags?.utid);
- const groupId = this.getUuid(utid, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK,
- trackGroup: groupId,
- });
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
- private getUuidUnchecked(utid: number | null, upid: number | null) {
- return upid === null
- ? this.utidToUuid.get(utid!)
- : this.upidToUuid.get(upid);
- }
-
- private getUuid(utid: number | null, upid: number | null) {
- return assertExists(this.getUuidUnchecked(utid, upid));
- }
-
- private getOrCreateUuid(utid: number | null, upid: number | null) {
- let uuid = this.getUuidUnchecked(utid, upid);
- if (uuid === undefined) {
- uuid = uuidv4();
- if (upid === null) {
- this.utidToUuid.set(utid!, uuid);
- } else {
- this.upidToUuid.set(upid, uuid);
- }
+ private getProcGroup(upid: number): GroupNode {
+ const group = this.processGroups.get(upid);
+ if (group) {
+ return group;
+ } else {
+ throw new Error(`Unable to find proc group with upid ${upid}`);
}
- return uuid;
}
- private setUuidForUpid(upid: number, uuid: string) {
- this.upidToUuid.set(upid, uuid);
+ private getThreadGroup(utid: number): GroupNode {
+ const group = this.threadGroups.get(utid);
+ if (group) {
+ return group;
+ } else {
+ throw new Error(`Unable to find thread group with utid ${utid}`);
+ }
}
private async addKernelThreadGrouping(engine: Engine): Promise<void> {
@@ -735,41 +558,25 @@
// main track. It doesn't summarise the kernel threads within the group,
// but creating a dedicated track type is out of scope at the time of
// writing.
- const kthreadGroupUuid = uuidv4();
- const summaryTrackKey = uuidv4();
- this.tracksToAdd.push({
- uri: '/kernel',
- key: summaryTrackKey,
- trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
- name: `Kernel thread summary`,
- });
- const addTrackGroup = Actions.addTrackGroup({
- summaryTrackKey,
- name: `Kernel threads`,
- key: kthreadGroupUuid,
- collapsed: true,
- });
- this.addTrackGroupActions.push(addTrackGroup);
+ const group = new GroupNode('Kernel threads');
+ group.headerTrackUri = '/kernel'; // Summary track
+ globals.workspace.addChild(group);
// Set the group for all kernel threads (including kthreadd itself).
for (; it.valid(); it.next()) {
- this.setUuidForUpid(it.upid, kthreadGroupUuid);
+ const {utid} = it;
+
+ const threadGroup = new GroupNode(`Kernel Thread ${utid}`);
+ threadGroup.headless = true;
+ group.addChild(threadGroup);
+
+ this.threadGroups.set(utid, threadGroup);
}
}
- private async addProcessTrackGroups(engine: Engine): Promise<void> {
- // We want to create groups of tracks in a specific order.
- // The tracks should be grouped:
- // by upid
- // or (if upid is null) by utid
- // the groups should be sorted by:
- // Chrome-based process rank based on process names (e.g. Browser)
- // has a heap profile or not
- // total cpu time *for the whole parent process*
- // process name
- // upid
- // thread name
- // utid
+ // Adds top level groups for processes and thread that don't belong to a
+ // process.
+ private async addProcessGroups(engine: Engine): Promise<void> {
const result = await engine.query(`
with processGroups as (
select
@@ -813,16 +620,10 @@
select *
from (
select
- upid,
- null as utid,
- pid,
- null as tid,
- processName,
- null as threadName,
- sumRunningDur > 0 as hasSched,
- heapProfileAllocationCount > 0
- or heapGraphObjectCount > 0 as hasHeapInfo,
- ifnull(chromeProcessLabels, '') as chromeProcessLabels
+ 'process' as kind,
+ upid as uid,
+ pid as id,
+ processName as name
from processGroups
order by
chromeProcessRank desc,
@@ -838,15 +639,10 @@
select *
from (
select
- null,
- utid,
- null as pid,
- tid,
- null as processName,
- threadName,
- sumRunningDur > 0 as hasSched,
- 0 as hasHeapInfo,
- '' as chromeProcessLabels
+ 'thread' as kind,
+ utid as uid,
+ tid as id,
+ threadName as name
from threadGroups
order by
perfSampleCount desc,
@@ -858,172 +654,176 @@
`);
const it = result.iter({
- upid: NUM_NULL,
- utid: NUM_NULL,
- pid: NUM_NULL,
- tid: NUM_NULL,
- processName: STR_NULL,
- threadName: STR_NULL,
- hasSched: NUM_NULL,
- hasHeapInfo: NUM_NULL,
+ kind: STR,
+ uid: NUM,
+ id: NUM,
+ name: STR_NULL,
});
for (; it.valid(); it.next()) {
- const utid = it.utid;
- const upid = it.upid;
- const pid = it.pid;
- const tid = it.tid;
- const threadName = it.threadName;
- const processName = it.processName;
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- const hasSched = !!it.hasSched;
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- const hasHeapInfo = !!it.hasHeapInfo;
+ const {kind, uid, id, name} = it;
- const summaryTrackKey = uuidv4();
+ if (kind === 'process') {
+ // Ignore kernel process groups
+ if (this.processGroups.has(uid)) {
+ continue;
+ }
- const uri = getThreadOrProcUri(upid, utid);
+ function getProcessDisplayName(
+ processName: string | undefined,
+ pid: number,
+ ) {
+ if (processName) {
+ return `${stripPathFromExecutable(processName)} ${pid}`;
+ } else {
+ return `Process ${pid}`;
+ }
+ }
- // If previous groupings (e.g. kernel threads) picked up there tracks,
- // don't try to regroup them.
- const pUuid =
- upid === null ? this.utidToUuid.get(utid!) : this.upidToUuid.get(upid);
- if (pUuid !== undefined) {
- continue;
+ const displayName = getProcessDisplayName(name ?? undefined, id);
+
+ const group = new GroupNode(displayName);
+ group.headerTrackUri = `/process_${uid}`; // Summary track URI
+ globals.workspace.addChild(group);
+ this.processGroups.set(uid, group);
+ } else {
+ // Ignore kernel process groups
+ if (this.threadGroups.has(uid)) {
+ continue;
+ }
+
+ function getThreadDisplayName(
+ threadName: string | undefined,
+ pid: number,
+ ) {
+ if (threadName) {
+ return `${stripPathFromExecutable(threadName)} ${pid}`;
+ } else {
+ return `Thread ${pid}`;
+ }
+ }
+
+ const displayName = getThreadDisplayName(name ?? undefined, id);
+
+ const group = new GroupNode(displayName);
+ group.headerTrackUri = `/thread_${uid}`; // Summary track URI
+ globals.workspace.addChild(group);
+ this.threadGroups.set(uid, group);
}
-
- this.tracksToAdd.push({
- uri,
- key: summaryTrackKey,
- trackSortKey: hasSched
- ? PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK
- : PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
- name: `${upid === null ? tid : pid} summary`,
- });
-
- const name = getTrackName({
- utid,
- processName,
- pid,
- threadName,
- tid,
- upid,
- });
-
- const addTrackGroup = Actions.addTrackGroup({
- summaryTrackKey,
- name: stripPathFromExecutable(name),
- key: this.getOrCreateUuid(utid, upid),
- // Perf profiling tracks remain collapsed, otherwise we would have too
- // many expanded process tracks for some perf traces, leading to
- // jankyness.
- collapsed: !hasHeapInfo,
- });
- this.addTrackGroupActions.push(addTrackGroup);
}
}
- private async computeThreadOrderingMetadata(): Promise<UtidToTrackSortKey> {
- const result = await this.engine.query(`
- select
- utid,
- tid,
- (select pid from process p where t.upid = p.upid) as pid,
- t.name as threadName
- from thread t
- `);
+ // Create all the nested & headless thread groups that live inside existing
+ // process groups.
+ private async addThreadGroups(engine: Engine): Promise<void> {
+ const result = await engine.query(`
+ with threadGroups as (
+ select
+ utid,
+ upid,
+ tid,
+ thread.name as threadName,
+ CASE
+ WHEN thread.is_main_thread = 1 THEN 10
+ WHEN thread.name = 'CrBrowserMain' THEN 10
+ WHEN thread.name = 'CrRendererMain' THEN 10
+ WHEN thread.name = 'CrGpuMain' THEN 10
+ WHEN thread.name glob '*RenderThread*' THEN 9
+ WHEN thread.name glob '*GPU completion*' THEN 8
+ WHEN thread.name = 'Chrome_ChildIOThread' THEN 7
+ WHEN thread.name = 'Chrome_IOThread' THEN 7
+ WHEN thread.name = 'Compositor' THEN 6
+ WHEN thread.name = 'VizCompositorThread' THEN 6
+ ELSE 5
+ END as priority
+ from _thread_available_info_summary
+ join thread using (utid)
+ where upid is not null
+ )
+ select *
+ from (
+ select
+ utid,
+ upid,
+ tid,
+ threadName
+ from threadGroups
+ order by
+ priority desc,
+ tid asc
+ )
+ `);
const it = result.iter({
utid: NUM,
- tid: NUM_NULL,
- pid: NUM_NULL,
+ tid: NUM,
+ upid: NUM,
threadName: STR_NULL,
});
-
- const threadOrderingMetadata: UtidToTrackSortKey = {};
for (; it.valid(); it.next()) {
- threadOrderingMetadata[it.utid] = {
- tid: it.tid === null ? undefined : it.tid,
- sortKey: TrackDecider.getThreadSortKey(it.threadName, it.tid, it.pid),
- };
+ const {utid, tid, upid, threadName} = it;
+
+ // Ignore kernel thread groups
+ if (this.threadGroups.has(utid)) {
+ continue;
+ }
+
+ const threadGroup = new GroupNode(threadName ?? `Thread ${tid}`);
+ this.threadGroups.set(utid, threadGroup);
+ threadGroup.headless = true;
+ threadGroup.expand();
+
+ this.processGroups.get(upid)?.addChild(threadGroup);
}
- return threadOrderingMetadata;
}
private addPluginTracks(): void {
- const groupNameToUuid = new Map<string, string>();
- const tracks = globals.trackManager.findPotentialTracks();
+ const groups = new Map<string, GroupNode>();
+ const tracks = globals.trackManager.getAutoShowTracks();
for (const info of tracks) {
- const groupName = info.groupName;
+ const groupName = info.tags?.groupName;
+ let container: ContainerNode = globals.workspace;
- let groupUuid = SCROLLING_TRACK_GROUP;
if (groupName) {
- const uuid = groupNameToUuid.get(groupName);
- if (uuid) {
- groupUuid = uuid;
+ const existingGroup = groups.get(groupName);
+ if (existingGroup) {
+ container = existingGroup;
} else {
// Add the group
- groupUuid = uuidv4();
- const addGroup = Actions.addTrackGroup({
- name: groupName,
- key: groupUuid,
- collapsed: true,
- fixedOrdering: true,
- });
- this.addTrackGroupActions.push(addGroup);
-
- // Add group to the map
- groupNameToUuid.set(groupName, groupUuid);
+ const group = new GroupNode(groupName);
+ container = group;
+ globals.workspace.addChild(group);
+ groups.set(groupName, group);
}
}
- this.tracksToAdd.push({
- uri: info.uri,
- key: info.uri,
- name: info.title,
- // TODO(hjd): Fix how sorting works. Plugins should expose
- // 'sort keys' which the user can use to choose a sort order.
- trackSortKey: info.sortKey ?? PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: groupUuid,
- });
-
- if (info.isPinned) {
- this.tracksToPin.push(info.uri);
- }
+ const track = new TrackNode(info.uri, info.title);
+ container.addChild(track);
}
}
private addScrollJankPluginTracks(
tracks: ReadonlyArray<TrackDescriptor>,
): void {
- let scrollTracks = this.addTracks(
- tracks,
- ({tags}) => tags?.kind === CHROME_TOPLEVEL_SCROLLS_KIND,
- SCROLL_JANK_GROUP_ID,
- );
- scrollTracks = scrollTracks.concat(
- this.addTracks(
- tracks,
- ({tags}) => tags?.kind === SCROLL_JANK_V3_TRACK_KIND,
- SCROLL_JANK_GROUP_ID,
- ),
- );
- scrollTracks = scrollTracks.concat(
- this.addTracks(
- tracks,
- ({tags}) => tags?.kind === CHROME_EVENT_LATENCY_TRACK_KIND,
- SCROLL_JANK_GROUP_ID,
- ),
- );
- if (scrollTracks.length > 0) {
- this.addTrackGroupActions.push(
- Actions.addTrackGroup({
- name: 'Chrome Scroll Jank',
- key: SCROLL_JANK_GROUP_ID,
- collapsed: false,
- fixedOrdering: true,
- }),
- );
+ const group = new GroupNode('Chrome Scroll Jank');
+ group.expand();
+ tracks
+ .filter(({tags}) => tags?.kind === CHROME_TOPLEVEL_SCROLLS_KIND)
+ .forEach((td) => {
+ group.addChild(new TrackNode(td.uri, td.title));
+ });
+ tracks
+ .filter(({tags}) => tags?.kind === SCROLL_JANK_V3_TRACK_KIND)
+ .forEach((td) => {
+ group.addChild(new TrackNode(td.uri, td.title));
+ });
+ tracks
+ .filter(({tags}) => tags?.kind === CHROME_EVENT_LATENCY_TRACK_KIND)
+ .forEach((td) => {
+ group.addChild(new TrackNode(td.uri, td.title));
+ });
+ if (group.children.length) {
+ globals.workspace.addChild(group);
}
}
@@ -1033,45 +833,28 @@
tracks
.filter(({tags}) => tags?.kind === CHROME_SCROLL_JANK_TRACK_KIND)
.forEach((td) => {
- const upid = assertExists(td.tags?.upid);
const utid = assertExists(td.tags?.utid);
- const groupId = this.getUuid(utid, upid);
- this.tracksToAdd.push({
- key: td.uri,
- uri: td.uri,
- name: td.title,
- trackSortKey: {
- utid,
- priority: InThreadTrackSortKey.ORDINARY,
- },
- trackGroup: groupId,
- });
+ const group = this.getThreadGroup(utid);
+ group.addChild(new TrackNode(td.uri, td.title));
});
}
// Add an ordinary track from a track descriptor
- private addTrack(track: TrackDescriptor, groupId?: string): void {
- this.tracksToAdd.push({
- key: track.uri,
- uri: track.uri,
- name: track.title,
- trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
- trackGroup: groupId ?? SCROLLING_TRACK_GROUP,
- });
+ private addTrack(track: TrackDescriptor): void {
+ globals.workspace.addChild(new TrackNode(track.uri, track.title));
}
// Add tracks that match some predicate
private addTracks(
source: ReadonlyArray<TrackDescriptor>,
predicate: (td: TrackDescriptor) => boolean,
- groupId?: string,
): ReadonlyArray<TrackDescriptor> {
const filteredTracks = source.filter(predicate);
- filteredTracks.forEach((a) => this.addTrack(a, groupId));
+ filteredTracks.forEach((a) => this.addTrack(a));
return filteredTracks;
}
- public async decideTracks(): Promise<DeferredAction[]> {
+ public async decideTracks(): Promise<void> {
const tracks = globals.trackManager.getAllTracks();
// Add first the global tracks that don't require per-process track groups.
@@ -1125,11 +908,7 @@
// Add user slice tracks before listing the processes. These tracks will
// be listed with their user/package name only, and they will be grouped
// under on their original shared track names. E.g. "GPU Work Period"
- this.addTracks(
- tracks,
- ({tags}) =>
- tags?.kind === ASYNC_SLICE_TRACK_KIND && tags?.scope === 'user',
- );
+ this.addUserAsyncSliceTracks(tracks);
// Pre-group all kernel "threads" (actually processes) if this is a linux
// system trace. Below, addProcessTrackGroups will skip them due to an
@@ -1145,110 +924,57 @@
// create a track per process. If a process has been completely idle and has
// no sched events, no track group will be emitted.
// Will populate this.addTrackGroupActions
- await this.addProcessTrackGroups(
+ await this.addProcessGroups(
this.engine.getProxy('TrackDecider::addProcessTrackGroups'),
);
- this.addProcessHeapProfileTracks(tracks);
- this.addProcessPerfSamplesTracks(tracks);
- this.addThreadPerfSamplesTracks(tracks);
- await this.addProcessCounterTracks(tracks);
- this.addProcessAsyncSliceTracks(tracks);
- this.addActualFramesTracks(tracks);
this.addExpectedFramesTracks(tracks);
- this.addThreadCounterTracks(tracks);
+ this.addActualFramesTracks(tracks);
+ this.addProcessPerfSamplesTracks(tracks);
+ this.addProcessHeapProfileTracks(tracks);
+
+ await this.addThreadGroups(
+ this.engine.getProxy('TrackDecider::addThreadTrackGroups'),
+ );
+
+ this.addThreadPerfSamplesTracks(tracks);
+ this.addThreadCpuSampleTracks(tracks);
this.addThreadStateTracks(tracks);
this.addThreadSliceTracks(tracks);
- this.addThreadCpuSampleTracks(tracks);
+ this.addThreadCounterTracks(tracks);
+
+ await this.addProcessCounterTracks(tracks);
+ this.addProcessAsyncSliceTracks(tracks);
+ this.addAsyncThreadSliceTracks(tracks);
this.addChromeScrollJankTrack(tracks);
- this.addTrackGroupActions.push(
- Actions.addTracks({tracks: this.tracksToAdd}),
- );
-
- // Add the actions to pin any tracks we need to pin
- for (const trackKey of this.tracksToPin) {
- this.addTrackGroupActions.push(Actions.toggleTrackPinned({trackKey}));
- }
-
- const threadOrderingMetadata = await this.computeThreadOrderingMetadata();
- this.addTrackGroupActions.push(
- Actions.setUtidToTrackSortKey({threadOrderingMetadata}),
- );
-
- return this.addTrackGroupActions;
- }
-
- // Some process counter tracks are tied to specific threads based on their
- // name.
- private async resolveTrackSortKeyForProcessCounterTrack(
- upid: number,
- threadName?: string,
- ): Promise<TrackSortKey> {
- if (threadName !== 'GPU completion') {
- return PrimaryTrackSortKey.COUNTER_TRACK;
- }
- const result = await this.engine.query(`
- select utid
- from thread
- where upid=${upid} and name=${sqliteString(threadName)}
- `);
- const it = result.iter({
- utid: NUM,
+ // Remove any empty groups
+ globals.workspace.children.forEach((n) => {
+ if (n instanceof GroupNode && n.children.length === 0) {
+ globals.workspace.removeChild(n);
+ }
});
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- for (; it; it.next()) {
- return {
- utid: it.utid,
- priority: InThreadTrackSortKey.THREAD_COUNTER_TRACK,
- };
- }
- return PrimaryTrackSortKey.COUNTER_TRACK;
- }
- private static getThreadSortKey(
- threadName?: string | null,
- tid?: number | null,
- pid?: number | null,
- ): PrimaryTrackSortKey {
- if (pid !== undefined && pid !== null && pid === tid) {
- return PrimaryTrackSortKey.MAIN_THREAD;
- }
- if (threadName === undefined || threadName === null) {
- return PrimaryTrackSortKey.ORDINARY_THREAD;
- }
+ // Move groups underneath tracks
+ Array.from(globals.workspace.children)
+ .sort((a, b) => {
+ // Define the desired order
+ const order = [TrackNode, GroupNode];
- // Chrome main threads should always come first within their process.
- if (
- threadName === 'CrBrowserMain' ||
- threadName === 'CrRendererMain' ||
- threadName === 'CrGpuMain'
- ) {
- return PrimaryTrackSortKey.MAIN_THREAD;
- }
+ // Get the index in the order array
+ const indexA = order.findIndex((type) => a instanceof type);
+ const indexB = order.findIndex((type) => b instanceof type);
- // Chrome IO threads should always come immediately after the main thread.
- if (
- threadName === 'Chrome_ChildIOThread' ||
- threadName === 'Chrome_IOThread'
- ) {
- return PrimaryTrackSortKey.CHROME_IO_THREAD;
- }
+ // Sort based on the index in the order array
+ return indexA - indexB;
+ })
+ .forEach((n) => globals.workspace.addChild(n));
- // A Chrome process can have only one compositor thread, so we want to put
- // it next to other named processes.
- if (threadName === 'Compositor' || threadName === 'VizCompositorThread') {
- return PrimaryTrackSortKey.CHROME_COMPOSITOR_THREAD;
- }
-
- switch (true) {
- case /.*RenderThread.*/.test(threadName):
- return PrimaryTrackSortKey.RENDER_THREAD;
- case /.*GPU completion.*/.test(threadName):
- return PrimaryTrackSortKey.GPU_COMPLETION_THREAD;
- default:
- return PrimaryTrackSortKey.ORDINARY_THREAD;
+ // If there is only one group, expand it
+ const groups = globals.workspace.children;
+ if (groups.length === 1 && groups[0] instanceof GroupNode) {
+ groups[0].expand();
}
}
}
diff --git a/ui/src/core/colorizer.ts b/ui/src/core/colorizer.ts
index 3f0d1b3..ac495d3 100644
--- a/ui/src/core/colorizer.ts
+++ b/ui/src/core/colorizer.ts
@@ -238,13 +238,10 @@
return materialColorScheme(name);
}
-export function colorForSample(callsiteId: number, isHovered: boolean): string {
- let colorScheme;
+export function getColorForSample(callsiteId: number): ColorScheme {
if (USE_CONSISTENT_COLORS.get()) {
- colorScheme = materialColorScheme(String(callsiteId));
+ return materialColorScheme(String(callsiteId));
} else {
- colorScheme = proceduralColorScheme(String(callsiteId));
+ return proceduralColorScheme(String(callsiteId));
}
-
- return isHovered ? colorScheme.variant.cssString : colorScheme.base.cssString;
}
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index d057f66..1cba444 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -47,6 +47,7 @@
'perfetto.CpuFreq',
'perfetto.CpuProfile',
'perfetto.CpuSlices',
+ 'perfetto.CriticalPath',
'perfetto.CriticalUserInteraction',
'perfetto.DebugTracks',
'perfetto.ExampleTraces',
diff --git a/ui/src/core/legacy_flamegraph_cache.ts b/ui/src/core/legacy_flamegraph_cache.ts
deleted file mode 100644
index ae37877..0000000
--- a/ui/src/core/legacy_flamegraph_cache.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size 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 {Engine} from '../trace_processor/engine';
-
-export class LegacyFlamegraphCache {
- private cache: Map<string, string>;
- private prefix: string;
- private tableId: number;
- private cacheSizeLimit: number;
-
- constructor(prefix: string) {
- this.cache = new Map<string, string>();
- this.prefix = prefix;
- this.tableId = 0;
- this.cacheSizeLimit = 10;
- }
-
- async getTableName(engine: Engine, query: string): Promise<string> {
- let tableName = this.cache.get(query);
- if (tableName === undefined) {
- // TODO(hjd): This should be LRU.
- if (this.cache.size > this.cacheSizeLimit) {
- for (const name of this.cache.values()) {
- await engine.query(`drop table ${name}`);
- }
- this.cache.clear();
- }
- tableName = `${this.prefix}_${this.tableId++}`;
- await engine.query(
- `create temp table if not exists ${tableName} as ${query}`,
- );
- this.cache.set(query, tableName);
- }
- return tableName;
- }
-
- hasQuery(query: string): boolean {
- return this.cache.get(query) !== undefined;
- }
-}
diff --git a/ui/src/core/query_flamegraph.ts b/ui/src/core/query_flamegraph.ts
index 802aebc..8fdfc70 100644
--- a/ui/src/core/query_flamegraph.ts
+++ b/ui/src/core/query_flamegraph.ts
@@ -32,8 +32,6 @@
FlamegraphView,
} from '../widgets/flamegraph';
-import {featureFlags} from './feature_flags';
-
export interface QueryFlamegraphColumn {
// The name of the column in SQL.
readonly name: string;
@@ -452,10 +450,3 @@
}
return '0';
}
-
-export const USE_NEW_FLAMEGRAPH_IMPL = featureFlags.register({
- id: 'useNewFlamegraphImpl',
- name: 'Use new flamegraph implementation',
- description: 'Use new flamgraph implementation in details panels.',
- defaultValue: true,
-});
diff --git a/ui/src/core/selection_manager.ts b/ui/src/core/selection_manager.ts
index 2ddb75b..84271c3 100644
--- a/ui/src/core/selection_manager.ts
+++ b/ui/src/core/selection_manager.ts
@@ -26,6 +26,19 @@
PERF_SAMPLE = 'perf',
}
+export function profileType(s: string): ProfileType {
+ if (s === 'heap_profile:libc.malloc,com.android.art') {
+ s = 'heap_profile:com.android.art,libc.malloc';
+ }
+ if (Object.values(ProfileType).includes(s as ProfileType)) {
+ return s as ProfileType;
+ }
+ if (s.startsWith('heap_profile')) {
+ return ProfileType.HEAP_PROFILE;
+ }
+ throw new Error('Unknown type ${s}');
+}
+
// LEGACY Selection types:
export interface SliceSelection {
kind: 'SCHED_SLICE';
@@ -71,7 +84,7 @@
export interface LogSelection {
kind: 'LOG';
id: number;
- trackKey: string;
+ trackUri: string;
}
export interface GenericSliceSelection {
@@ -93,7 +106,7 @@
| PerfSamplesSelection
| LogSelection
| GenericSliceSelection
-) & {trackKey?: string};
+) & {trackUri?: string};
export type SelectionKind = LegacySelection['kind']; // 'THREAD_STATE' | 'SLICE' ...
// New Selection types:
@@ -104,13 +117,13 @@
export interface SingleSelection {
kind: 'single';
- trackKey: string;
+ trackUri: string;
eventId: number;
}
export interface AreaSelection {
kind: 'area';
- tracks: string[];
+ trackUris: string[];
start: time;
end: time;
}
@@ -217,22 +230,22 @@
}
setEvent(
- trackKey: string,
+ trackUri: string,
eventId: number,
legacySelection?: LegacySelection,
) {
this.clear();
- this.addEvent(trackKey, eventId, legacySelection);
+ this.addEvent(trackUri, eventId, legacySelection);
}
addEvent(
- trackKey: string,
+ trackUri: string,
eventId: number,
legacySelection?: LegacySelection,
) {
this.addSelection({
kind: 'single',
- trackKey,
+ trackUri,
eventId,
});
if (legacySelection) {
diff --git a/ui/src/core/track_kinds.ts b/ui/src/core/track_kinds.ts
index 93dec00..05671c9 100644
--- a/ui/src/core/track_kinds.ts
+++ b/ui/src/core/track_kinds.ts
@@ -35,3 +35,4 @@
'org.chromium.ScrollJank.scroll_jank_v3_track';
export const CHROME_SCROLL_JANK_TRACK_KIND =
'org.chromium.ScrollJank.BrowserUIThreadLongTasks';
+export const ANDROID_LOGS_TRACK_KIND = 'AndroidLogTrack';
diff --git a/ui/src/core_plugins/android_log/index.ts b/ui/src/core_plugins/android_log/index.ts
index 67e840a..7ecaf75 100644
--- a/ui/src/core_plugins/android_log/index.ts
+++ b/ui/src/core_plugins/android_log/index.ts
@@ -17,6 +17,7 @@
import {LogFilteringCriteria, LogPanel} from './logs_panel';
import {
PerfettoPlugin,
+ ANDROID_LOGS_TRACK_KIND,
PluginContextTrace,
PluginDescriptor,
} from '../../public';
@@ -24,8 +25,6 @@
import {AndroidLogTrack} from './logs_track';
import {exists} from '../../base/utils';
-export const ANDROID_LOGS_TRACK_KIND = 'AndroidLogTrack';
-
const VERSION = 1;
const DEFAULT_STATE: AndroidLogPluginState = {
@@ -57,11 +56,11 @@
);
const logCount = result.firstRow({cnt: NUM}).cnt;
if (logCount > 0) {
- ctx.registerStaticTrack({
+ ctx.registerTrackAndShowOnTraceLoad({
uri: 'perfetto.AndroidLog',
title: 'Android logs',
tags: {kind: ANDROID_LOGS_TRACK_KIND},
- trackFactory: () => new AndroidLogTrack(ctx.engine),
+ track: new AndroidLogTrack(ctx.engine),
});
}
diff --git a/ui/src/core_plugins/annotation/index.ts b/ui/src/core_plugins/annotation/index.ts
index da0db45..0e34ca0 100644
--- a/ui/src/core_plugins/annotation/index.ts
+++ b/ui/src/core_plugins/annotation/index.ts
@@ -52,8 +52,9 @@
for (; it.valid(); it.next()) {
const {id, name, upid, groupName} = it;
+ const uri = `/annotation_${id}`;
ctx.registerTrack({
- uri: `/annotation_${id}`,
+ uri,
title: name,
tags: {
kind: THREAD_SLICE_TRACK_KIND,
@@ -62,17 +63,15 @@
...(groupName && {groupName}),
},
chips: ['metric'],
- trackFactory: ({trackKey}) => {
- return new ThreadSliceTrack(
- {
- engine: ctx.engine,
- trackKey,
- },
- id,
- 0,
- 'annotation_slice',
- );
- },
+ track: new ThreadSliceTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ id,
+ 0,
+ 'annotation_slice',
+ ),
});
}
}
@@ -99,8 +98,9 @@
for (; counterIt.valid(); counterIt.next()) {
const {id: trackId, name, upid} = counterIt;
+ const uri = `/annotation_counter_${trackId}`;
ctx.registerTrack({
- uri: `/annotation_counter_${trackId}`,
+ uri,
title: name,
tags: {
kind: COUNTER_TRACK_KIND,
@@ -108,14 +108,12 @@
upid,
},
chips: ['metric'],
- trackFactory: (trackCtx) => {
- return new TraceProcessorCounterTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId,
- rootTable: 'annotation_counter',
- });
- },
+ track: new TraceProcessorCounterTrack({
+ engine: ctx.engine,
+ uri,
+ trackId,
+ rootTable: 'annotation_counter',
+ }),
});
}
}
diff --git a/ui/src/core_plugins/async_slices/index.ts b/ui/src/core_plugins/async_slices/index.ts
index d9b244e..f636191 100644
--- a/ui/src/core_plugins/async_slices/index.ts
+++ b/ui/src/core_plugins/async_slices/index.ts
@@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {removeFalsyValues} from '../../base/array_utils';
import {ASYNC_SLICE_TRACK_KIND} from '../../public';
import {
PerfettoPlugin,
PluginContextTrace,
PluginDescriptor,
} from '../../public';
-import {getTrackName} from '../../public/utils';
+import {getThreadUriPrefix, getTrackName} from '../../public/utils';
import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
import {AsyncSliceTrack} from './async_slice_track';
@@ -27,6 +28,7 @@
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
await this.addGlobalAsyncTracks(ctx);
await this.addProcessAsyncSliceTracks(ctx);
+ await this.addThreadAsyncSliceTracks(ctx);
await this.addUserAsyncSliceTracks(ctx);
}
@@ -68,17 +70,16 @@
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
const maxDepth = it.maxDepth;
+ const uri = `/async_slices_${rawName}_${it.parentId}`;
ctx.registerTrack({
- uri: `/async_slices_${rawName}_${it.parentId}`,
+ uri,
title: displayName,
tags: {
trackIds,
kind: ASYNC_SLICE_TRACK_KIND,
scope: 'global',
},
- trackFactory: ({trackKey}) => {
- return new AsyncSliceTrack({engine, trackKey}, maxDepth, trackIds);
- },
+ track: new AsyncSliceTrack({engine, uri}, maxDepth, trackIds),
});
}
}
@@ -123,8 +124,9 @@
kind,
});
+ const uri = `/process_${upid}/async_slices_${rawTrackIds}`;
ctx.registerTrack({
- uri: `/process_${upid}/async_slices_${rawTrackIds}`,
+ uri,
title: displayName,
tags: {
trackIds,
@@ -132,13 +134,88 @@
scope: 'process',
upid,
},
- trackFactory: ({trackKey}) => {
- return new AsyncSliceTrack(
- {engine: ctx.engine, trackKey},
- maxDepth,
- trackIds,
- );
+ track: new AsyncSliceTrack(
+ {engine: ctx.engine, uri},
+ maxDepth,
+ trackIds,
+ ),
+ });
+ }
+ }
+
+ async addThreadAsyncSliceTracks(ctx: PluginContextTrace): Promise<void> {
+ const result = await ctx.engine.query(`
+ include perfetto module viz.summary.slices;
+ include perfetto module viz.summary.threads;
+ include perfetto module viz.threads;
+
+ select
+ t.utid,
+ thread.upid,
+ t.name as trackName,
+ thread.name as threadName,
+ thread.tid as tid,
+ t.track_ids as trackIds,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth,
+ k.is_main_thread as isMainThread,
+ k.is_kernel_thread AS isKernelThread
+ from _thread_track_summary_by_utid_and_name t
+ join _threads_with_kernel_flag k using(utid)
+ join thread using (utid)
+ where t.track_count > 1
+ `);
+
+ const it = result.iter({
+ utid: NUM,
+ upid: NUM_NULL,
+ trackName: STR_NULL,
+ trackIds: STR,
+ maxDepth: NUM,
+ isMainThread: NUM_NULL,
+ isKernelThread: NUM,
+ threadName: STR_NULL,
+ tid: NUM_NULL,
+ });
+ for (; it.valid(); it.next()) {
+ const {
+ utid,
+ upid,
+ trackName,
+ isMainThread,
+ isKernelThread,
+ maxDepth,
+ threadName,
+ tid,
+ } = it;
+ const rawTrackIds = it.trackIds;
+ const trackIds = rawTrackIds.split(',').map((v) => Number(v));
+ const displayName = getTrackName({
+ name: trackName,
+ utid,
+ tid,
+ threadName,
+ kind: 'Slices',
+ });
+
+ const uri = `/${getThreadUriPrefix(upid, utid)}_slice_${rawTrackIds}`;
+ ctx.registerTrack({
+ uri,
+ title: displayName,
+ tags: {
+ trackIds,
+ kind: ASYNC_SLICE_TRACK_KIND,
+ scope: 'thread',
+ utid,
+ upid: upid ?? undefined,
},
+ chips: removeFalsyValues([
+ isKernelThread === 0 && isMainThread === 1 && 'main thread',
+ ]),
+ track: new AsyncSliceTrack(
+ {engine: ctx.engine, uri},
+ maxDepth,
+ trackIds,
+ ),
});
}
}
@@ -194,17 +271,17 @@
uidTrack: true,
});
+ const uri = `/async_slices_${rawName}_${uid}`;
ctx.registerTrack({
- uri: `/async_slices_${rawName}_${uid}`,
+ uri,
title: displayName,
tags: {
trackIds,
kind: ASYNC_SLICE_TRACK_KIND,
scope: 'user',
+ rawName, // Defines grouping
},
- trackFactory: ({trackKey}) => {
- return new AsyncSliceTrack({engine, trackKey}, maxDepth, trackIds);
- },
+ track: new AsyncSliceTrack({engine, uri}, maxDepth, trackIds),
});
}
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
index d074b46..ce46c9f 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
@@ -139,7 +139,7 @@
sqlTableName: this.tableName,
start: args.slice.ts,
duration: args.slice.dur,
- trackKey: this.trackKey,
+ trackUri: this.uri,
detailsPanelConfig: {
kind: detailsPanelConfig.kind,
config: detailsPanelConfig.config,
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
index 7ecc73d..1bb7980 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
@@ -14,36 +14,29 @@
import {v4 as uuidv4} from 'uuid';
-import {Actions} from '../../common/actions';
-import {SCROLLING_TRACK_GROUP} from '../../common/state';
import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {globals} from '../../frontend/globals';
import {
BottomTabToSCSAdapter,
PerfettoPlugin,
PluginContext,
PluginContextTrace,
PluginDescriptor,
- PrimaryTrackSortKey,
} from '../../public';
import {PageLoadDetailsPanel} from './page_load_details_panel';
import {StartupDetailsPanel} from './startup_details_panel';
import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
import {CriticalUserInteractionTrack} from './critical_user_interaction_track';
+import {TrackNode} from '../../public/workspace';
+import {globals} from '../../frontend/globals';
function addCriticalUserInteractionTrack() {
- const trackKey = uuidv4();
- globals.dispatchMultiple([
- Actions.addTrack({
- key: trackKey,
- uri: CriticalUserInteractionTrack.kind,
- name: `Chrome Interactions`,
- trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- }),
- Actions.toggleTrackPinned({trackKey}),
- ]);
+ const track = new TrackNode(
+ CriticalUserInteractionTrack.kind,
+ 'Chrome Interactions',
+ );
+ globals.workspace.addChild(track);
+ track.pin();
}
class CriticalUserInteractionPlugin implements PerfettoPlugin {
@@ -54,11 +47,10 @@
kind: CriticalUserInteractionTrack.kind,
},
title: 'Chrome Interactions',
- trackFactory: (trackCtx) =>
- new CriticalUserInteractionTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- }),
+ track: new CriticalUserInteractionTrack({
+ engine: ctx.engine,
+ uri: CriticalUserInteractionTrack.kind,
+ }),
});
ctx.registerDetailsPanel(
diff --git a/ui/src/core_plugins/chrome_scroll_jank/common.ts b/ui/src/core_plugins/chrome_scroll_jank/common.ts
index cefbed8..c6232a2 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/common.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/common.ts
@@ -48,12 +48,12 @@
public registerTrack(args: {
kind: string;
- trackKey: string;
+ trackUri: string;
tableName: string;
detailsPanelConfig: CustomSqlDetailsPanelConfig;
}): void {
this.tracks[args.kind] = {
- key: args.trackKey,
+ key: args.trackUri,
sqlTableName: args.tableName,
detailsPanelConfig: args.detailsPanelConfig,
};
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
index a170a22..88e56de 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_details_panel.ts
@@ -60,6 +60,7 @@
} from './scroll_jank_slice';
import {sliceRef} from '../../frontend/widgets/slice';
import {SCROLL_JANK_V3_TRACK_KIND} from '../../public';
+import {globals} from '../../frontend/globals';
// Given a node in the slice tree, return a path from root to it.
function getPath(slice: SliceTreeNode): string[] {
@@ -132,6 +133,8 @@
// Stages tree for the prev EventLatency.
private prevEventLatencyBreakdown?: SliceTreeNode;
+ private tracksByTrackId: Map<number, string>;
+
static create(
args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
): EventLatencySliceDetailsPanel {
@@ -141,6 +144,13 @@
constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
super(args);
+ this.tracksByTrackId = new Map<number, string>();
+ globals.trackManager.getAllTracks().forEach((td) => {
+ td.tags?.trackIds?.forEach((trackId) => {
+ this.tracksByTrackId.set(trackId, td.uri);
+ });
+ });
+
this.loadData();
}
@@ -326,7 +336,7 @@
const columns: ColumnDescriptor<RelevantThreadRow>[] = [
widgetColumn<RelevantThreadRow>('Relevant Thread', (x) =>
- getCauseLink(x.tracks, x.ts, x.dur),
+ getCauseLink(x.tracks, this.tracksByTrackId, x.ts, x.dur),
),
widgetColumn<RelevantThreadRow>('Description', (x) => {
if (x.description === '') {
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
index 4660e51..0dbc019 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
@@ -37,7 +37,7 @@
super(args);
ScrollJankPluginState.getInstance().registerTrack({
kind: CHROME_EVENT_LATENCY_TRACK_KIND,
- trackKey: this.trackKey,
+ trackUri: this.uri,
tableName: this.tableName,
detailsPanelConfig: this.getDetailsPanel(),
});
diff --git a/ui/src/core_plugins/chrome_scroll_jank/index.ts b/ui/src/core_plugins/chrome_scroll_jank/index.ts
index bfcbd43..f24539e 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/index.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/index.ts
@@ -111,20 +111,19 @@
});
const {upid, utid} = it;
+ const uri = 'perfetto.ChromeScrollJank';
ctx.registerTrack({
- uri: 'perfetto.ChromeScrollJank',
+ uri,
title: 'Scroll Jank causes - long tasks',
tags: {
kind: CHROME_SCROLL_JANK_TRACK_KIND,
upid,
utid,
},
- trackFactory: ({trackKey}) => {
- return new ChromeTasksScrollJankTrack({
- engine: ctx.engine,
- trackKey,
- });
- },
+ track: new ChromeTasksScrollJankTrack({
+ engine: ctx.engine,
+ uri,
+ }),
});
}
@@ -134,18 +133,17 @@
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
`);
+ const uri = 'perfetto.ChromeScrollJank#toplevelScrolls';
ctx.registerTrack({
- uri: 'perfetto.ChromeScrollJank#toplevelScrolls',
+ uri,
title: 'Chrome Scrolls',
tags: {
kind: CHROME_TOPLEVEL_SCROLLS_KIND,
},
- trackFactory: ({trackKey}) => {
- return new TopLevelScrollTrack({
- engine: ctx.engine,
- trackKey,
- });
- },
+ track: new TopLevelScrollTrack({
+ engine: ctx.engine,
+ uri,
+ }),
});
ctx.registerDetailsPanel(
@@ -265,15 +263,14 @@
);
await ctx.engine.query(tableDefSql);
+ const uri = 'perfetto.ChromeScrollJank#eventLatency';
ctx.registerTrack({
- uri: 'perfetto.ChromeScrollJank#eventLatency',
+ uri,
title: 'Chrome Scroll Input Latencies',
tags: {
kind: CHROME_EVENT_LATENCY_TRACK_KIND,
},
- trackFactory: ({trackKey}) => {
- return new EventLatencyTrack({engine: ctx.engine, trackKey}, baseTable);
- },
+ track: new EventLatencyTrack({engine: ctx.engine, uri}, baseTable),
});
ctx.registerDetailsPanel(
@@ -304,18 +301,17 @@
`INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`,
);
+ const uri = 'perfetto.ChromeScrollJank#scrollJankV3';
ctx.registerTrack({
- uri: 'perfetto.ChromeScrollJank#scrollJankV3',
+ uri,
title: 'Chrome Scroll Janks',
tags: {
kind: SCROLL_JANK_V3_TRACK_KIND,
},
- trackFactory: ({trackKey}) => {
- return new ScrollJankV3Track({
- engine: ctx.engine,
- trackKey,
- });
- },
+ track: new ScrollJankV3Track({
+ engine: ctx.engine,
+ uri,
+ }),
});
ctx.registerDetailsPanel(
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_cause_link_utils.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
index 6b57e46..963be02 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
@@ -179,19 +179,20 @@
export function getCauseLink(
threadTracks: EventLatencyCauseThreadTracks,
+ tracksByTrackId: Map<number, string>,
ts: time | undefined,
dur: duration | undefined,
): m.Child {
- const trackKeys: string[] = [];
+ const trackUris: string[] = [];
for (const trackId of threadTracks.trackIds) {
- const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
- if (trackKey === undefined) {
+ const track = tracksByTrackId.get(trackId);
+ if (track === undefined) {
return `Could not locate track ${trackId} for thread ${threadTracks.thread} in the global state`;
}
- trackKeys.push(trackKey);
+ trackUris.push(track);
}
- if (trackKeys.length == 0) {
+ if (trackUris.length == 0) {
return `No valid tracks for thread ${threadTracks.thread}.`;
}
@@ -204,16 +205,15 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- verticalScrollToTrack(trackKeys[0], true);
+ verticalScrollToTrack(trackUris[0], true);
if (exists(ts) && exists(dur)) {
focusHorizontalRange(ts, Time.fromRaw(ts + dur), 0.3);
- globals.timeline.selectArea(ts, Time.fromRaw(ts + dur), trackKeys);
globals.dispatch(
Actions.selectArea({
start: ts,
end: Time.fromRaw(ts + dur),
- tracks: trackKeys,
+ trackUris,
}),
);
}
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
index 34dc713..be7c837 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_slice.ts
@@ -181,18 +181,20 @@
throw new Error(`${vnode.attrs.kind} track is not registered.`);
}
+ const trackUri = track.key;
+
globals.makeSelection(
Actions.selectGenericSlice({
id: vnode.attrs.id,
sqlTableName: track.sqlTableName,
start: vnode.attrs.ts,
duration: vnode.attrs.dur,
- trackKey: track.key,
+ trackUri,
detailsPanelConfig: track.detailsPanelConfig,
}),
);
- scrollToTrackAndTs(track.key, vnode.attrs.ts, true);
+ scrollToTrackAndTs(trackUri, vnode.attrs.ts, true);
},
},
vnode.attrs.name,
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
index 65c5ade..64259c1 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -36,7 +36,7 @@
super(args);
ScrollJankPluginState.getInstance().registerTrack({
kind: SCROLL_JANK_V3_TRACK_KIND,
- trackKey: this.trackKey,
+ trackUri: this.uri,
tableName: this.tableName,
detailsPanelConfig: this.getDetailsPanel(),
});
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
index 5819968..eb857ee 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
@@ -48,7 +48,7 @@
ScrollJankPluginState.getInstance().registerTrack({
kind: TopLevelScrollTrack.kind,
- trackKey: this.trackKey,
+ trackUri: this.uri,
tableName: this.tableName,
detailsPanelConfig: this.getDetailsPanel(),
});
diff --git a/ui/src/core_plugins/chrome_tasks/index.ts b/ui/src/core_plugins/chrome_tasks/index.ts
index 36767f5..81713ed 100644
--- a/ui/src/core_plugins/chrome_tasks/index.ts
+++ b/ui/src/core_plugins/chrome_tasks/index.ts
@@ -102,11 +102,10 @@
for (; it.valid(); it.next()) {
const utid = it.utid;
const uri = `org.chromium.ChromeTasks#thread.${utid}`;
- ctx.registerStaticTrack({
+ ctx.registerTrackAndShowOnTraceLoad({
uri,
- trackFactory: ({trackKey}) =>
- new ChromeTasksThreadTrack(ctx.engine, trackKey, asUtid(utid)),
- groupName: `Chrome Tasks`,
+ track: new ChromeTasksThreadTrack(ctx.engine, uri, asUtid(utid)),
+ tags: {groupName: `Chrome Tasks`},
title: `${it.threadName} ${it.tid}`,
});
}
diff --git a/ui/src/core_plugins/chrome_tasks/track.ts b/ui/src/core_plugins/chrome_tasks/track.ts
index 6e25b03..811e6b8 100644
--- a/ui/src/core_plugins/chrome_tasks/track.ts
+++ b/ui/src/core_plugins/chrome_tasks/track.ts
@@ -25,10 +25,10 @@
export class ChromeTasksThreadTrack extends CustomSqlTableSliceTrack {
constructor(
engine: Engine,
- trackKey: string,
+ uri: string,
private utid: Utid,
) {
- super({engine, trackKey});
+ super({engine, uri});
}
getSqlDataSource(): CustomSqlTableDefConfig {
diff --git a/ui/src/core_plugins/commands/index.ts b/ui/src/core_plugins/commands/index.ts
index cc3e6a0..0430f63 100644
--- a/ui/src/core_plugins/commands/index.ts
+++ b/ui/src/core_plugins/commands/index.ts
@@ -33,6 +33,7 @@
addSqlTableTabImpl,
SqlTableTabConfig,
} from '../../frontend/sql_table_tab';
+import {Workspace} from '../../public/workspace';
const SQL_STATS = `
with first as (select started as ts from sqlstats limit 1)
@@ -230,9 +231,8 @@
id: 'perfetto.CoreCommands#UnpinAllTracks',
name: 'Unpin all pinned tracks',
callback: () => {
- ctx.timeline.unpinTracksByPredicate((_) => {
- return true;
- });
+ const workspace = ctx.timeline.workspace;
+ workspace.pinnedTracks.forEach((t) => workspace.unpinTrack(t));
},
});
@@ -240,9 +240,7 @@
id: 'perfetto.CoreCommands#ExpandAllGroups',
name: 'Expand all track groups',
callback: () => {
- ctx.timeline.expandGroupsByPredicate((_) => {
- return true;
- });
+ ctx.timeline.workspace.flatGroups.forEach((g) => g.expand());
},
});
@@ -250,9 +248,7 @@
id: 'perfetto.CoreCommands#CollapseAllGroups',
name: 'Collapse all track groups',
callback: () => {
- ctx.timeline.collapseGroupsByPredicate((_) => {
- return true;
- });
+ ctx.timeline.workspace.flatGroups.forEach((g) => g.collapse());
},
});
@@ -298,6 +294,43 @@
addSqlTableTabImpl(args as SqlTableTabConfig);
},
});
+
+ ctx.registerCommand({
+ id: 'createNewEmptyWorkspace',
+ name: 'Create new empty workspace',
+ callback: async () => {
+ try {
+ const name = await ctx.prompt('Give it a name...');
+ const newWorkspace = new Workspace(name);
+ globals.workspaces.push(newWorkspace);
+ globals.switchWorkspace(newWorkspace);
+ } finally {
+ }
+ },
+ });
+
+ ctx.registerCommand({
+ id: 'switchWorkspace',
+ name: 'Switch workspace',
+ callback: async () => {
+ try {
+ const options = globals.workspaces.map((ws) => {
+ return {key: ws.uuid, displayName: ws.displayName};
+ });
+ const workspaceUuid = await ctx.prompt(
+ 'Choose a workspace...',
+ options,
+ );
+ const workspace = globals.workspaces.find(
+ (ws) => ws.uuid === workspaceUuid,
+ );
+ if (workspace) {
+ globals.switchWorkspace(workspace);
+ }
+ } finally {
+ }
+ },
+ });
}
onDeactivate(_: PluginContext): void {
diff --git a/ui/src/core_plugins/counter/index.ts b/ui/src/core_plugins/counter/index.ts
index 3f9b612..3b08e7e 100644
--- a/ui/src/core_plugins/counter/index.ts
+++ b/ui/src/core_plugins/counter/index.ts
@@ -20,7 +20,6 @@
PerfettoPlugin,
PluginContextTrace,
PluginDescriptor,
- PrimaryTrackSortKey,
STR,
LONG,
Engine,
@@ -176,25 +175,23 @@
const trackId = it.id;
const displayName = it.name;
const unit = it.unit ?? undefined;
- ctx.registerStaticTrack({
- uri: `/counter_${trackId}`,
+ const uri = `/counter_${trackId}`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: displayName,
tags: {
kind: COUNTER_TRACK_KIND,
trackIds: [trackId],
},
- trackFactory: (trackCtx) => {
- return new TraceProcessorCounterTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId,
- options: {
- ...getDefaultCounterOptions(displayName),
- unit,
- },
- });
- },
- sortKey: PrimaryTrackSortKey.COUNTER_TRACK,
+ track: new TraceProcessorCounterTrack({
+ engine: ctx.engine,
+ uri,
+ trackId,
+ options: {
+ ...getDefaultCounterOptions(displayName),
+ unit,
+ },
+ }),
detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, displayName),
getEventBounds: async (id) => {
return await getCounterEventBounds(ctx.engine, trackId, id);
@@ -246,22 +243,21 @@
for (; it.valid(); it.next()) {
const name = it.name;
const trackId = it.id;
+ const uri = `counter.cpu.${trackId}`;
ctx.registerTrack({
- uri: `counter.cpu.${trackId}`,
+ uri,
title: name,
tags: {
kind: COUNTER_TRACK_KIND,
trackIds: [trackId],
scope,
},
- trackFactory: (trackCtx) => {
- return new TraceProcessorCounterTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId: trackId,
- options: getDefaultCounterOptions(name),
- });
- },
+ track: new TraceProcessorCounterTrack({
+ engine: ctx.engine,
+ uri,
+ trackId: trackId,
+ options: getDefaultCounterOptions(name),
+ }),
detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
getEventBounds: async (id) => {
return await getCounterEventBounds(ctx.engine, trackId, id);
@@ -313,8 +309,9 @@
threadName,
threadTrack: true,
});
+ const uri = `${getThreadUriPrefix(upid, utid)}_counter_${trackId}`;
ctx.registerTrack({
- uri: `${getThreadUriPrefix(upid, utid)}_counter_${trackId}`,
+ uri,
title: name,
tags: {
kind,
@@ -323,14 +320,12 @@
upid: upid ?? undefined,
scope: 'thread',
},
- trackFactory: (trackCtx) => {
- return new TraceProcessorCounterTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId: trackId,
- options: getDefaultCounterOptions(name),
- });
- },
+ track: new TraceProcessorCounterTrack({
+ engine: ctx.engine,
+ uri,
+ trackId: trackId,
+ options: getDefaultCounterOptions(name),
+ }),
detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
getEventBounds: async (id) => {
return await getCounterEventBounds(ctx.engine, trackId, id);
@@ -373,8 +368,9 @@
processName,
...(exists(trackName) && {trackName}),
});
+ const uri = `/process_${upid}/counter_${trackId}`;
ctx.registerTrack({
- uri: `/process_${upid}/counter_${trackId}`,
+ uri,
title: name,
tags: {
kind,
@@ -382,14 +378,12 @@
upid,
scope: 'process',
},
- trackFactory: (trackCtx) => {
- return new TraceProcessorCounterTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId: trackId,
- options: getDefaultCounterOptions(name),
- });
- },
+ track: new TraceProcessorCounterTrack({
+ engine: ctx.engine,
+ uri,
+ trackId: trackId,
+ options: getDefaultCounterOptions(name),
+ }),
detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
getEventBounds: async (id) => {
return await getCounterEventBounds(ctx.engine, trackId, id);
@@ -424,14 +418,12 @@
trackIds: [trackId],
scope: 'gpuFreq',
},
- trackFactory: (trackCtx) => {
- return new TraceProcessorCounterTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId: trackId,
- options: getDefaultCounterOptions(name),
- });
- },
+ track: new TraceProcessorCounterTrack({
+ engine: ctx.engine,
+ uri,
+ trackId: trackId,
+ options: getDefaultCounterOptions(name),
+ }),
detailsPanel: new CounterDetailsPanel(ctx.engine, trackId, name),
getEventBounds: async (id) => {
return await getCounterEventBounds(ctx.engine, trackId, id);
diff --git a/ui/src/core_plugins/counter/trace_processor_counter_track.ts b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
index b02278b..407a9d7 100644
--- a/ui/src/core_plugins/counter/trace_processor_counter_track.ts
+++ b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
@@ -79,7 +79,7 @@
return;
}
const id = it.id;
- globals.selectSingleEvent(this.trackKey, id);
+ globals.selectSingleEvent(this.uri, id);
});
return true;
diff --git a/ui/src/core_plugins/cpu_freq/cpu_freq_track.ts b/ui/src/core_plugins/cpu_freq/cpu_freq_track.ts
new file mode 100644
index 0000000..49e5b20
--- /dev/null
+++ b/ui/src/core_plugins/cpu_freq/cpu_freq_track.ts
@@ -0,0 +1,428 @@
+// Copyright (C) 2021 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {searchSegment} from '../../base/binary_search';
+import {assertTrue} from '../../base/logging';
+import {duration, time, Time} from '../../base/time';
+import {drawTrackHoverTooltip} from '../../common/canvas_utils';
+import {colorForCpu} from '../../core/colorizer';
+import {TrackData} from '../../common/track_data';
+import {TimelineFetcher} from '../../common/track_helper';
+import {checkerboardExcept} from '../../frontend/checkerboard';
+import {globals} from '../../frontend/globals';
+import {Engine, Track} from '../../public';
+import {LONG, NUM} from '../../trace_processor/query_result';
+import {uuidv4Sql} from '../../base/uuid';
+import {TrackMouseEvent, TrackRenderContext} from '../../public/tracks';
+import {Vector} from '../../base/geom';
+import {createView, createVirtualTable} from '../../trace_processor/sql_utils';
+import {AsyncDisposableStack} from '../../base/disposable_stack';
+
+export interface Data extends TrackData {
+ timestamps: BigInt64Array;
+ minFreqKHz: Uint32Array;
+ maxFreqKHz: Uint32Array;
+ lastFreqKHz: Uint32Array;
+ lastIdleValues: Int8Array;
+}
+
+interface Config {
+ cpu: number;
+ freqTrackId: number;
+ idleTrackId?: number;
+ maximumValue: number;
+}
+
+// 0.5 Makes the horizontal lines sharp.
+const MARGIN_TOP = 4.5;
+const RECT_HEIGHT = 20;
+
+export class CpuFreqTrack implements Track {
+ private mousePos: Vector = {x: 0, y: 0};
+ private hoveredValue: number | undefined = undefined;
+ private hoveredTs: time | undefined = undefined;
+ private hoveredTsEnd: time | undefined = undefined;
+ private hoveredIdle: number | undefined = undefined;
+ private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
+
+ private engine: Engine;
+ private config: Config;
+ private trackUuid = uuidv4Sql();
+
+ private trash!: AsyncDisposableStack;
+
+ constructor(config: Config, engine: Engine) {
+ this.config = config;
+ this.engine = engine;
+ }
+
+ async onCreate() {
+ this.trash = new AsyncDisposableStack();
+ if (this.config.idleTrackId === undefined) {
+ this.trash.use(
+ await createView(
+ this.engine,
+ `raw_freq_idle_${this.trackUuid}`,
+ `
+ select ts, dur, value as freqValue, -1 as idleValue
+ from experimental_counter_dur c
+ where track_id = ${this.config.freqTrackId}
+ `,
+ ),
+ );
+ } else {
+ this.trash.use(
+ await createView(
+ this.engine,
+ `raw_freq_${this.trackUuid}`,
+ `
+ select ts, dur, value as freqValue
+ from experimental_counter_dur c
+ where track_id = ${this.config.freqTrackId}
+ `,
+ ),
+ );
+
+ this.trash.use(
+ await createView(
+ this.engine,
+ `raw_idle_${this.trackUuid}`,
+ `
+ select
+ ts,
+ dur,
+ iif(value = 4294967295, -1, cast(value as int)) as idleValue
+ from experimental_counter_dur c
+ where track_id = ${this.config.idleTrackId}
+ `,
+ ),
+ );
+
+ this.trash.use(
+ await createVirtualTable(
+ this.engine,
+ `raw_freq_idle_${this.trackUuid}`,
+ `span_join(raw_freq_${this.trackUuid}, raw_idle_${this.trackUuid})`,
+ ),
+ );
+ }
+
+ this.trash.use(
+ await createVirtualTable(
+ this.engine,
+ `cpu_freq_${this.trackUuid}`,
+ `
+ __intrinsic_counter_mipmap((
+ select ts, freqValue as value
+ from raw_freq_idle_${this.trackUuid}
+ ))
+ `,
+ ),
+ );
+
+ this.trash.use(
+ await createVirtualTable(
+ this.engine,
+ `cpu_idle_${this.trackUuid}`,
+ `
+ __intrinsic_counter_mipmap((
+ select ts, idleValue as value
+ from raw_freq_idle_${this.trackUuid}
+ ))
+ `,
+ ),
+ );
+ }
+
+ async onUpdate({
+ visibleWindow,
+ resolution,
+ }: TrackRenderContext): Promise<void> {
+ await this.fetcher.requestData(visibleWindow.toTimeSpan(), resolution);
+ }
+
+ async onDestroy(): Promise<void> {
+ await this.trash.asyncDispose();
+ }
+
+ async onBoundsChange(
+ start: time,
+ end: time,
+ resolution: duration,
+ ): Promise<Data> {
+ // The resolution should always be a power of two for the logic of this
+ // function to make sense.
+ assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
+
+ const freqResult = await this.engine.query(`
+ SELECT
+ min_value as minFreq,
+ max_value as maxFreq,
+ last_ts as ts,
+ last_value as lastFreq
+ FROM cpu_freq_${this.trackUuid}(
+ ${start},
+ ${end},
+ ${resolution}
+ );
+ `);
+ const idleResult = await this.engine.query(`
+ SELECT last_value as lastIdle
+ FROM cpu_idle_${this.trackUuid}(
+ ${start},
+ ${end},
+ ${resolution}
+ );
+ `);
+
+ const freqRows = freqResult.numRows();
+ const idleRows = idleResult.numRows();
+ assertTrue(freqRows == idleRows);
+
+ const data: Data = {
+ start,
+ end,
+ resolution,
+ length: freqRows,
+ timestamps: new BigInt64Array(freqRows),
+ minFreqKHz: new Uint32Array(freqRows),
+ maxFreqKHz: new Uint32Array(freqRows),
+ lastFreqKHz: new Uint32Array(freqRows),
+ lastIdleValues: new Int8Array(freqRows),
+ };
+
+ const freqIt = freqResult.iter({
+ ts: LONG,
+ minFreq: NUM,
+ maxFreq: NUM,
+ lastFreq: NUM,
+ });
+ const idleIt = idleResult.iter({
+ lastIdle: NUM,
+ });
+ for (let i = 0; freqIt.valid(); ++i, freqIt.next(), idleIt.next()) {
+ data.timestamps[i] = freqIt.ts;
+ data.minFreqKHz[i] = freqIt.minFreq;
+ data.maxFreqKHz[i] = freqIt.maxFreq;
+ data.lastFreqKHz[i] = freqIt.lastFreq;
+ data.lastIdleValues[i] = idleIt.lastIdle;
+ }
+ return data;
+ }
+
+ getHeight() {
+ return MARGIN_TOP + RECT_HEIGHT;
+ }
+
+ render({ctx, size, timescale, visibleWindow}: TrackRenderContext): void {
+ // TODO: fonts and colors should come from the CSS and not hardcoded here.
+ const data = this.fetcher.data;
+
+ if (data === undefined || data.timestamps.length === 0) {
+ // Can't possibly draw anything.
+ return;
+ }
+
+ assertTrue(data.timestamps.length === data.lastFreqKHz.length);
+ assertTrue(data.timestamps.length === data.minFreqKHz.length);
+ assertTrue(data.timestamps.length === data.maxFreqKHz.length);
+ assertTrue(data.timestamps.length === data.lastIdleValues.length);
+
+ const endPx = size.width;
+ const zeroY = MARGIN_TOP + RECT_HEIGHT;
+
+ // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
+ let yMax = this.config.maximumValue;
+ const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
+ const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
+ const pow10 = Math.pow(10, exp);
+ yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4);
+ const unitGroup = Math.floor(exp / 3);
+ const num = yMax / Math.pow(10, unitGroup * 3);
+ // The values we have for cpufreq are in kHz so +1 to unitGroup.
+ const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
+
+ const color = colorForCpu(this.config.cpu);
+ let saturation = 45;
+ if (globals.state.hoveredUtid !== -1) {
+ saturation = 0;
+ }
+
+ ctx.fillStyle = color.setHSL({s: saturation, l: 70}).cssString;
+ ctx.strokeStyle = color.setHSL({s: saturation, l: 55}).cssString;
+
+ const calculateX = (timestamp: time) => {
+ return Math.floor(timescale.timeToPx(timestamp));
+ };
+ const calculateY = (value: number) => {
+ return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
+ };
+
+ const timespan = visibleWindow.toTimeSpan();
+ const start = timespan.start;
+ const end = timespan.end;
+
+ const [rawStartIdx] = searchSegment(data.timestamps, start);
+ const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
+
+ const [, rawEndIdx] = searchSegment(data.timestamps, end);
+ const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
+
+ // Draw the CPU frequency graph.
+ {
+ ctx.beginPath();
+ const timestamp = Time.fromRaw(data.timestamps[startIdx]);
+ ctx.moveTo(Math.max(calculateX(timestamp), 0), zeroY);
+
+ let lastDrawnY = zeroY;
+ for (let i = startIdx; i < endIdx; i++) {
+ const timestamp = Time.fromRaw(data.timestamps[i]);
+ const x = Math.max(0, calculateX(timestamp));
+ const minY = calculateY(data.minFreqKHz[i]);
+ const maxY = calculateY(data.maxFreqKHz[i]);
+ const lastY = calculateY(data.lastFreqKHz[i]);
+
+ ctx.lineTo(x, lastDrawnY);
+ if (minY === maxY) {
+ assertTrue(lastY === minY);
+ ctx.lineTo(x, lastY);
+ } else {
+ ctx.lineTo(x, minY);
+ ctx.lineTo(x, maxY);
+ ctx.lineTo(x, lastY);
+ }
+ lastDrawnY = lastY;
+ }
+ ctx.lineTo(endPx, lastDrawnY);
+ ctx.lineTo(endPx, zeroY);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+ }
+
+ // Draw CPU idle rectangles that overlay the CPU freq graph.
+ ctx.fillStyle = `rgba(240, 240, 240, 1)`;
+ {
+ for (let i = startIdx; i < endIdx; i++) {
+ if (data.lastIdleValues[i] < 0) {
+ continue;
+ }
+
+ // We intentionally don't use the floor function here when computing x
+ // coordinates. Instead we use floating point which prevents flickering as
+ // we pan and zoom; this relies on the browser anti-aliasing pixels
+ // correctly.
+ const timestamp = Time.fromRaw(data.timestamps[i]);
+ const x = timescale.timeToPx(timestamp);
+ const xEnd =
+ i === data.lastIdleValues.length - 1
+ ? endPx
+ : timescale.timeToPx(Time.fromRaw(data.timestamps[i + 1]));
+
+ const width = xEnd - x;
+ const height = calculateY(data.lastFreqKHz[i]) - zeroY;
+
+ ctx.fillRect(x, zeroY, width, height);
+ }
+ }
+
+ ctx.font = '10px Roboto Condensed';
+
+ if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
+ let text = `${this.hoveredValue.toLocaleString()}kHz`;
+
+ ctx.fillStyle = color.setHSL({s: 45, l: 75}).cssString;
+ ctx.strokeStyle = color.setHSL({s: 45, l: 45}).cssString;
+
+ const xStart = Math.floor(timescale.timeToPx(this.hoveredTs));
+ const xEnd =
+ this.hoveredTsEnd === undefined
+ ? endPx
+ : Math.floor(timescale.timeToPx(this.hoveredTsEnd));
+ const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
+
+ // Highlight line.
+ ctx.beginPath();
+ ctx.moveTo(xStart, y);
+ ctx.lineTo(xEnd, y);
+ ctx.lineWidth = 3;
+ ctx.stroke();
+ ctx.lineWidth = 1;
+
+ // Draw change marker.
+ ctx.beginPath();
+ ctx.arc(
+ xStart,
+ y,
+ 3 /* r*/,
+ 0 /* start angle*/,
+ 2 * Math.PI /* end angle*/,
+ );
+ ctx.fill();
+ ctx.stroke();
+
+ // Display idle value if current hover is idle.
+ if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) {
+ // Display the idle value +1 to be consistent with catapult.
+ text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`;
+ }
+
+ // Draw the tooltip.
+ drawTrackHoverTooltip(ctx, this.mousePos, size, text);
+ }
+
+ // Write the Y scale on the top left corner.
+ ctx.textBaseline = 'alphabetic';
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
+ ctx.fillRect(0, 0, 42, 18);
+ ctx.fillStyle = '#666';
+ ctx.textAlign = 'left';
+ ctx.fillText(`${yLabel}`, 4, 14);
+
+ // If the cached trace slices don't fully cover the visible time range,
+ // show a gray rectangle with a "Loading..." label.
+ checkerboardExcept(
+ ctx,
+ this.getHeight(),
+ 0,
+ size.width,
+ timescale.timeToPx(data.start),
+ timescale.timeToPx(data.end),
+ );
+ }
+
+ onMouseMove({x, y, timescale}: TrackMouseEvent) {
+ const data = this.fetcher.data;
+ if (data === undefined) return;
+ this.mousePos = {x, y};
+ const time = timescale.pxToHpTime(x);
+
+ const [left, right] = searchSegment(data.timestamps, time.toTime());
+
+ this.hoveredTs =
+ left === -1 ? undefined : Time.fromRaw(data.timestamps[left]);
+ this.hoveredTsEnd =
+ right === -1 ? undefined : Time.fromRaw(data.timestamps[right]);
+ this.hoveredValue = left === -1 ? undefined : data.lastFreqKHz[left];
+ this.hoveredIdle = left === -1 ? undefined : data.lastIdleValues[left];
+ }
+
+ onMouseOut() {
+ this.hoveredValue = undefined;
+ this.hoveredTs = undefined;
+ this.hoveredTsEnd = undefined;
+ this.hoveredIdle = undefined;
+ }
+}
diff --git a/ui/src/core_plugins/cpu_freq/index.ts b/ui/src/core_plugins/cpu_freq/index.ts
index 0945223..0dd4218 100644
--- a/ui/src/core_plugins/cpu_freq/index.ts
+++ b/ui/src/core_plugins/cpu_freq/index.ts
@@ -12,396 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {searchSegment} from '../../base/binary_search';
-import {assertTrue} from '../../base/logging';
-import {duration, time, Time} from '../../base/time';
-import {drawTrackHoverTooltip} from '../../common/canvas_utils';
-import {colorForCpu} from '../../core/colorizer';
-import {TrackData} from '../../common/track_data';
-import {TimelineFetcher} from '../../common/track_helper';
-import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
import {
CPU_FREQ_TRACK_KIND,
- Engine,
PerfettoPlugin,
PluginContextTrace,
PluginDescriptor,
- Track,
} from '../../public';
-import {LONG, NUM, NUM_NULL} from '../../trace_processor/query_result';
-import {uuidv4Sql} from '../../base/uuid';
-import {TrackMouseEvent, TrackRenderContext} from '../../public/tracks';
-import {Vector} from '../../base/geom';
-
-export interface Data extends TrackData {
- timestamps: BigInt64Array;
- minFreqKHz: Uint32Array;
- maxFreqKHz: Uint32Array;
- lastFreqKHz: Uint32Array;
- lastIdleValues: Int8Array;
-}
-
-interface Config {
- cpu: number;
- freqTrackId: number;
- idleTrackId?: number;
- maximumValue: number;
-}
-
-// 0.5 Makes the horizontal lines sharp.
-const MARGIN_TOP = 4.5;
-const RECT_HEIGHT = 20;
-
-class CpuFreqTrack implements Track {
- private mousePos: Vector = {x: 0, y: 0};
- private hoveredValue: number | undefined = undefined;
- private hoveredTs: time | undefined = undefined;
- private hoveredTsEnd: time | undefined = undefined;
- private hoveredIdle: number | undefined = undefined;
- private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
-
- private engine: Engine;
- private config: Config;
- private trackUuid = uuidv4Sql();
-
- constructor(config: Config, engine: Engine) {
- this.config = config;
- this.engine = engine;
- }
-
- async onCreate() {
- if (this.config.idleTrackId === undefined) {
- await this.engine.query(`
- create view raw_freq_idle_${this.trackUuid} as
- select ts, dur, value as freqValue, -1 as idleValue
- from experimental_counter_dur c
- where track_id = ${this.config.freqTrackId}
- `);
- } else {
- await this.engine.query(`
- create view raw_freq_${this.trackUuid} as
- select ts, dur, value as freqValue
- from experimental_counter_dur c
- where track_id = ${this.config.freqTrackId};
-
- create view raw_idle_${this.trackUuid} as
- select
- ts,
- dur,
- iif(value = 4294967295, -1, cast(value as int)) as idleValue
- from experimental_counter_dur c
- where track_id = ${this.config.idleTrackId};
-
- create virtual table raw_freq_idle_${this.trackUuid}
- using span_join(raw_freq_${this.trackUuid}, raw_idle_${this.trackUuid});
- `);
- }
-
- await this.engine.query(`
- create virtual table cpu_freq_${this.trackUuid}
- using __intrinsic_counter_mipmap((
- select ts, freqValue as value
- from raw_freq_idle_${this.trackUuid}
- ));
-
- create virtual table cpu_idle_${this.trackUuid}
- using __intrinsic_counter_mipmap((
- select ts, idleValue as value
- from raw_freq_idle_${this.trackUuid}
- ));
- `);
- }
-
- async onUpdate({
- visibleWindow,
- resolution,
- }: TrackRenderContext): Promise<void> {
- await this.fetcher.requestData(visibleWindow.toTimeSpan(), resolution);
- }
-
- async onDestroy(): Promise<void> {
- await this.engine.tryQuery(`drop table cpu_freq_${this.trackUuid}`);
- await this.engine.tryQuery(`drop table cpu_idle_${this.trackUuid}`);
- await this.engine.tryQuery(`drop table raw_freq_idle_${this.trackUuid}`);
- await this.engine.tryQuery(
- `drop view if exists raw_freq_${this.trackUuid}`,
- );
- await this.engine.tryQuery(
- `drop view if exists raw_idle_${this.trackUuid}`,
- );
- }
-
- async onBoundsChange(
- start: time,
- end: time,
- resolution: duration,
- ): Promise<Data> {
- // The resolution should always be a power of two for the logic of this
- // function to make sense.
- assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
-
- const freqResult = await this.engine.query(`
- SELECT
- min_value as minFreq,
- max_value as maxFreq,
- last_ts as ts,
- last_value as lastFreq
- FROM cpu_freq_${this.trackUuid}(
- ${start},
- ${end},
- ${resolution}
- );
- `);
- const idleResult = await this.engine.query(`
- SELECT last_value as lastIdle
- FROM cpu_idle_${this.trackUuid}(
- ${start},
- ${end},
- ${resolution}
- );
- `);
-
- const freqRows = freqResult.numRows();
- const idleRows = idleResult.numRows();
- assertTrue(freqRows == idleRows);
-
- const data: Data = {
- start,
- end,
- resolution,
- length: freqRows,
- timestamps: new BigInt64Array(freqRows),
- minFreqKHz: new Uint32Array(freqRows),
- maxFreqKHz: new Uint32Array(freqRows),
- lastFreqKHz: new Uint32Array(freqRows),
- lastIdleValues: new Int8Array(freqRows),
- };
-
- const freqIt = freqResult.iter({
- ts: LONG,
- minFreq: NUM,
- maxFreq: NUM,
- lastFreq: NUM,
- });
- const idleIt = idleResult.iter({
- lastIdle: NUM,
- });
- for (let i = 0; freqIt.valid(); ++i, freqIt.next(), idleIt.next()) {
- data.timestamps[i] = freqIt.ts;
- data.minFreqKHz[i] = freqIt.minFreq;
- data.maxFreqKHz[i] = freqIt.maxFreq;
- data.lastFreqKHz[i] = freqIt.lastFreq;
- data.lastIdleValues[i] = idleIt.lastIdle;
- }
- return data;
- }
-
- getHeight() {
- return MARGIN_TOP + RECT_HEIGHT;
- }
-
- render({ctx, size, timescale, visibleWindow}: TrackRenderContext): void {
- // TODO: fonts and colors should come from the CSS and not hardcoded here.
- const data = this.fetcher.data;
-
- if (data === undefined || data.timestamps.length === 0) {
- // Can't possibly draw anything.
- return;
- }
-
- assertTrue(data.timestamps.length === data.lastFreqKHz.length);
- assertTrue(data.timestamps.length === data.minFreqKHz.length);
- assertTrue(data.timestamps.length === data.maxFreqKHz.length);
- assertTrue(data.timestamps.length === data.lastIdleValues.length);
-
- const endPx = size.width;
- const zeroY = MARGIN_TOP + RECT_HEIGHT;
-
- // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
- let yMax = this.config.maximumValue;
- const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
- const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
- const pow10 = Math.pow(10, exp);
- yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4);
- const unitGroup = Math.floor(exp / 3);
- const num = yMax / Math.pow(10, unitGroup * 3);
- // The values we have for cpufreq are in kHz so +1 to unitGroup.
- const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
-
- const color = colorForCpu(this.config.cpu);
- let saturation = 45;
- if (globals.state.hoveredUtid !== -1) {
- saturation = 0;
- }
-
- ctx.fillStyle = color.setHSL({s: saturation, l: 70}).cssString;
- ctx.strokeStyle = color.setHSL({s: saturation, l: 55}).cssString;
-
- const calculateX = (timestamp: time) => {
- return Math.floor(timescale.timeToPx(timestamp));
- };
- const calculateY = (value: number) => {
- return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
- };
-
- const timespan = visibleWindow.toTimeSpan();
- const start = timespan.start;
- const end = timespan.end;
-
- const [rawStartIdx] = searchSegment(data.timestamps, start);
- const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
-
- const [, rawEndIdx] = searchSegment(data.timestamps, end);
- const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
-
- // Draw the CPU frequency graph.
- {
- ctx.beginPath();
- const timestamp = Time.fromRaw(data.timestamps[startIdx]);
- ctx.moveTo(Math.max(calculateX(timestamp), 0), zeroY);
-
- let lastDrawnY = zeroY;
- for (let i = startIdx; i < endIdx; i++) {
- const timestamp = Time.fromRaw(data.timestamps[i]);
- const x = Math.max(0, calculateX(timestamp));
- const minY = calculateY(data.minFreqKHz[i]);
- const maxY = calculateY(data.maxFreqKHz[i]);
- const lastY = calculateY(data.lastFreqKHz[i]);
-
- ctx.lineTo(x, lastDrawnY);
- if (minY === maxY) {
- assertTrue(lastY === minY);
- ctx.lineTo(x, lastY);
- } else {
- ctx.lineTo(x, minY);
- ctx.lineTo(x, maxY);
- ctx.lineTo(x, lastY);
- }
- lastDrawnY = lastY;
- }
- ctx.lineTo(endPx, lastDrawnY);
- ctx.lineTo(endPx, zeroY);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- }
-
- // Draw CPU idle rectangles that overlay the CPU freq graph.
- ctx.fillStyle = `rgba(240, 240, 240, 1)`;
- {
- for (let i = startIdx; i < endIdx; i++) {
- if (data.lastIdleValues[i] < 0) {
- continue;
- }
-
- // We intentionally don't use the floor function here when computing x
- // coordinates. Instead we use floating point which prevents flickering as
- // we pan and zoom; this relies on the browser anti-aliasing pixels
- // correctly.
- const timestamp = Time.fromRaw(data.timestamps[i]);
- const x = timescale.timeToPx(timestamp);
- const xEnd =
- i === data.lastIdleValues.length - 1
- ? endPx
- : timescale.timeToPx(Time.fromRaw(data.timestamps[i + 1]));
-
- const width = xEnd - x;
- const height = calculateY(data.lastFreqKHz[i]) - zeroY;
-
- ctx.fillRect(x, zeroY, width, height);
- }
- }
-
- ctx.font = '10px Roboto Condensed';
-
- if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
- let text = `${this.hoveredValue.toLocaleString()}kHz`;
-
- ctx.fillStyle = color.setHSL({s: 45, l: 75}).cssString;
- ctx.strokeStyle = color.setHSL({s: 45, l: 45}).cssString;
-
- const xStart = Math.floor(timescale.timeToPx(this.hoveredTs));
- const xEnd =
- this.hoveredTsEnd === undefined
- ? endPx
- : Math.floor(timescale.timeToPx(this.hoveredTsEnd));
- const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
-
- // Highlight line.
- ctx.beginPath();
- ctx.moveTo(xStart, y);
- ctx.lineTo(xEnd, y);
- ctx.lineWidth = 3;
- ctx.stroke();
- ctx.lineWidth = 1;
-
- // Draw change marker.
- ctx.beginPath();
- ctx.arc(
- xStart,
- y,
- 3 /* r*/,
- 0 /* start angle*/,
- 2 * Math.PI /* end angle*/,
- );
- ctx.fill();
- ctx.stroke();
-
- // Display idle value if current hover is idle.
- if (this.hoveredIdle !== undefined && this.hoveredIdle !== -1) {
- // Display the idle value +1 to be consistent with catapult.
- text += ` (Idle: ${(this.hoveredIdle + 1).toLocaleString()})`;
- }
-
- // Draw the tooltip.
- drawTrackHoverTooltip(ctx, this.mousePos, size, text);
- }
-
- // Write the Y scale on the top left corner.
- ctx.textBaseline = 'alphabetic';
- ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
- ctx.fillRect(0, 0, 42, 18);
- ctx.fillStyle = '#666';
- ctx.textAlign = 'left';
- ctx.fillText(`${yLabel}`, 4, 14);
-
- // If the cached trace slices don't fully cover the visible time range,
- // show a gray rectangle with a "Loading..." label.
- checkerboardExcept(
- ctx,
- this.getHeight(),
- 0,
- size.width,
- timescale.timeToPx(data.start),
- timescale.timeToPx(data.end),
- );
- }
-
- onMouseMove({x, y, timescale}: TrackMouseEvent) {
- const data = this.fetcher.data;
- if (data === undefined) return;
- this.mousePos = {x, y};
- const time = timescale.pxToHpTime(x);
-
- const [left, right] = searchSegment(data.timestamps, time.toTime());
-
- this.hoveredTs =
- left === -1 ? undefined : Time.fromRaw(data.timestamps[left]);
- this.hoveredTsEnd =
- right === -1 ? undefined : Time.fromRaw(data.timestamps[right]);
- this.hoveredValue = left === -1 ? undefined : data.lastFreqKHz[left];
- this.hoveredIdle = left === -1 ? undefined : data.lastIdleValues[left];
- }
-
- onMouseOut() {
- this.hoveredValue = undefined;
- this.hoveredTs = undefined;
- this.hoveredTsEnd = undefined;
- this.hoveredIdle = undefined;
- }
-}
+import {NUM, NUM_NULL} from '../../trace_processor/query_result';
+import {CpuFreqTrack} from './cpu_freq_track';
class CpuFreq implements PerfettoPlugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
@@ -451,14 +69,15 @@
idleTrackId,
};
+ const uri = `/cpu_freq_cpu${cpu}`;
ctx.registerTrack({
- uri: `/cpu_freq_cpu${cpu}`,
+ uri,
title: `Cpu ${cpu} Frequency`,
tags: {
kind: CPU_FREQ_TRACK_KIND,
cpu,
},
- trackFactory: () => new CpuFreqTrack(config, ctx.engine),
+ track: new CpuFreqTrack(config, ctx.engine),
});
}
}
diff --git a/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts b/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
index 5c283db..2e70b2f 100644
--- a/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
+++ b/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
@@ -12,237 +12,75 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {searchSegment} from '../../base/binary_search';
-import {duration, Time, time} from '../../base/time';
-import {getLegacySelection} from '../../common/state';
+import {assertExists} from '../../base/logging';
+import {Time} from '../../base/time';
import {Actions} from '../../common/actions';
-import {colorForSample} from '../../core/colorizer';
-import {TrackData} from '../../common/track_data';
-import {TimelineFetcher} from '../../common/track_helper';
+import {LegacySelection} from '../../common/state';
+import {getColorForSample} from '../../core/colorizer';
+import {
+ BaseSliceTrack,
+ OnSliceClickArgs,
+} from '../../frontend/base_slice_track';
import {globals} from '../../frontend/globals';
-import {TimeScale} from '../../frontend/time_scale';
-import {Engine, Track} from '../../public';
-import {LONG, NUM} from '../../trace_processor/query_result';
-import {TrackMouseEvent, TrackRenderContext} from '../../public/tracks';
+import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
+import {NewTrackArgs} from '../../frontend/track';
+import {NUM, Slice} from '../../public';
-const BAR_HEIGHT = 3;
-const MARGIN_TOP = 4.5;
-const RECT_HEIGHT = 30.5;
-
-interface Data extends TrackData {
- ids: Float64Array;
- tsStarts: BigInt64Array;
- callsiteId: Uint32Array;
+interface CpuProfileRow extends NamedRow {
+ callsiteId: number;
}
-export class CpuProfileTrack implements Track {
- private centerY = this.getHeight() / 2 + BAR_HEIGHT;
- private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
- private hoveredTs: time | undefined = undefined;
- private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
- private engine: Engine;
- private utid: number;
-
- constructor(engine: Engine, utid: number) {
- this.engine = engine;
- this.utid = utid;
+export class CpuProfileTrack extends BaseSliceTrack<Slice, CpuProfileRow> {
+ constructor(
+ args: NewTrackArgs,
+ private utid: number,
+ ) {
+ super(args);
}
- async onUpdate({
- visibleWindow,
- resolution,
- }: TrackRenderContext): Promise<void> {
- await this.fetcher.requestData(visibleWindow.toTimeSpan(), resolution);
+ protected getRowSpec(): CpuProfileRow {
+ return {...NAMED_ROW, callsiteId: NUM};
}
- async onBoundsChange(
- start: time,
- end: time,
- resolution: duration,
- ): Promise<Data> {
- const query = `select
- id,
+ protected rowToSlice(row: CpuProfileRow): Slice {
+ const baseSlice = super.rowToSliceBase(row);
+ const name = assertExists(row.name);
+ const colorScheme = getColorForSample(row.callsiteId);
+ return {...baseSlice, title: name, colorScheme};
+ }
+
+ isSelectionHandled(selection: LegacySelection): boolean {
+ return selection.kind === 'CPU_PROFILE_SAMPLE';
+ }
+
+ onUpdatedSlices(slices: Slice[]) {
+ for (const slice of slices) {
+ slice.isHighlighted = slice === this.hoveredSlice;
+ }
+ }
+
+ getSqlSource(): string {
+ return `
+ select
+ p.id,
ts,
+ 0 as dur,
+ 0 as depth,
+ 'CPU Sample' as name,
callsite_id as callsiteId
- from cpu_profile_stack_sample
+ from cpu_profile_stack_sample p
where utid = ${this.utid}
- order by ts`;
-
- const result = await this.engine.query(query);
- const numRows = result.numRows();
- const data: Data = {
- start,
- end,
- resolution,
- length: numRows,
- ids: new Float64Array(numRows),
- tsStarts: new BigInt64Array(numRows),
- callsiteId: new Uint32Array(numRows),
- };
-
- const it = result.iter({id: NUM, ts: LONG, callsiteId: NUM});
- for (let row = 0; it.valid(); it.next(), ++row) {
- data.ids[row] = it.id;
- data.tsStarts[row] = it.ts;
- data.callsiteId[row] = it.callsiteId;
- }
-
- return data;
+ order by ts
+ `;
}
- async onDestroy(): Promise<void> {
- this.fetcher[Symbol.dispose]();
- }
-
- getHeight() {
- return MARGIN_TOP + RECT_HEIGHT - 1;
- }
-
- render({ctx, timescale: timeScale}: TrackRenderContext): void {
- const data = this.fetcher.data;
-
- if (data === undefined) return;
-
- for (let i = 0; i < data.tsStarts.length; i++) {
- const centerX = Time.fromRaw(data.tsStarts[i]);
- const selection = getLegacySelection(globals.state);
- const isHovered = this.hoveredTs === centerX;
- const isSelected =
- selection !== null &&
- selection.kind === 'CPU_PROFILE_SAMPLE' &&
- selection.ts === centerX;
- const strokeWidth = isSelected ? 3 : 0;
- this.drawMarker(
- ctx,
- timeScale.timeToPx(centerX),
- this.centerY,
- isHovered,
- strokeWidth,
- data.callsiteId[i],
- );
- }
-
- // Group together identical identical CPU profile samples by connecting them
- // with an horizontal bar.
- let clusterStartIndex = 0;
- while (clusterStartIndex < data.tsStarts.length) {
- const callsiteId = data.callsiteId[clusterStartIndex];
-
- // Find the end of the cluster by searching for the next different CPU
- // sample. The resulting range [clusterStartIndex, clusterEndIndex] is
- // inclusive and within array bounds.
- let clusterEndIndex = clusterStartIndex;
- while (
- clusterEndIndex + 1 < data.tsStarts.length &&
- data.callsiteId[clusterEndIndex + 1] === callsiteId
- ) {
- clusterEndIndex++;
- }
-
- // If there are multiple CPU samples in the cluster, draw a line.
- if (clusterStartIndex !== clusterEndIndex) {
- const startX = Time.fromRaw(data.tsStarts[clusterStartIndex]);
- const endX = Time.fromRaw(data.tsStarts[clusterEndIndex]);
- const leftPx = timeScale.timeToPx(startX) - this.markerWidth;
- const rightPx = timeScale.timeToPx(endX) + this.markerWidth;
- const width = rightPx - leftPx;
- ctx.fillStyle = colorForSample(callsiteId, false);
- ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
- }
-
- // Move to the next cluster.
- clusterStartIndex = clusterEndIndex + 1;
- }
- }
-
- drawMarker(
- ctx: CanvasRenderingContext2D,
- x: number,
- y: number,
- isHovered: boolean,
- strokeWidth: number,
- callsiteId: number,
- ): void {
- ctx.beginPath();
- ctx.moveTo(x - this.markerWidth, y - this.markerWidth);
- ctx.lineTo(x, y + this.markerWidth);
- ctx.lineTo(x + this.markerWidth, y - this.markerWidth);
- ctx.lineTo(x - this.markerWidth, y - this.markerWidth);
- ctx.closePath();
- ctx.fillStyle = colorForSample(callsiteId, isHovered);
- ctx.fill();
- if (strokeWidth > 0) {
- ctx.strokeStyle = colorForSample(callsiteId, false);
- ctx.lineWidth = strokeWidth;
- ctx.stroke();
- }
- }
-
- onMouseMove({x, y, timescale}: TrackMouseEvent) {
- const data = this.fetcher.data;
- if (data === undefined) return;
- const time = timescale.pxToHpTime(x);
- const [left, right] = searchSegment(data.tsStarts, time.toTime());
- const index = this.findTimestampIndex(left, timescale, data, x, y, right);
- this.hoveredTs =
- index === -1 ? undefined : Time.fromRaw(data.tsStarts[index]);
- }
-
- onMouseOut() {
- this.hoveredTs = undefined;
- }
-
- onMouseClick({x, y, timescale}: TrackMouseEvent) {
- const data = this.fetcher.data;
- if (data === undefined) return false;
-
- const time = timescale.pxToHpTime(x);
- const [left, right] = searchSegment(data.tsStarts, time.toTime());
-
- const index = this.findTimestampIndex(left, timescale, data, x, y, right);
-
- if (index !== -1) {
- const id = data.ids[index];
- const ts = Time.fromRaw(data.tsStarts[index]);
-
- globals.makeSelection(
- Actions.selectCpuProfileSample({id, utid: this.utid, ts}),
- );
- return true;
- }
- return false;
- }
-
- // If the markers overlap the rightmost one will be selected.
- findTimestampIndex(
- left: number,
- timeScale: TimeScale,
- data: Data,
- x: number,
- y: number,
- right: number,
- ): number {
- let index = -1;
- if (left !== -1) {
- const start = Time.fromRaw(data.tsStarts[left]);
- const centerX = timeScale.timeToPx(start);
- if (this.isInMarker(x, y, centerX)) {
- index = left;
- }
- }
- if (right !== -1) {
- const start = Time.fromRaw(data.tsStarts[right]);
- const centerX = timeScale.timeToPx(start);
- if (this.isInMarker(x, y, centerX)) {
- index = right;
- }
- }
- return index;
- }
-
- isInMarker(x: number, y: number, centerX: number) {
- return (
- Math.abs(x - centerX) + Math.abs(y - this.centerY) <= this.markerWidth
+ onSliceClick({slice}: OnSliceClickArgs<Slice>) {
+ globals.makeSelection(
+ Actions.selectCpuProfileSample({
+ id: slice.id,
+ utid: this.utid,
+ ts: Time.fromRaw(slice.ts),
+ }),
);
}
}
diff --git a/ui/src/core_plugins/cpu_profile/index.ts b/ui/src/core_plugins/cpu_profile/index.ts
index 95350a5..08c68ea 100644
--- a/ui/src/core_plugins/cpu_profile/index.ts
+++ b/ui/src/core_plugins/cpu_profile/index.ts
@@ -14,9 +14,10 @@
import m from 'mithril';
-import {CpuProfileDetailsPanel} from '../../frontend/cpu_profile_panel';
import {
CPU_PROFILE_TRACK_KIND,
+ Engine,
+ LegacyDetailsPanel,
PerfettoPlugin,
PluginContextTrace,
PluginDescriptor,
@@ -25,6 +26,16 @@
import {CpuProfileTrack} from './cpu_profile_track';
import {getThreadUriPrefix} from '../../public/utils';
import {exists} from '../../base/utils';
+import {Monitor} from '../../base/monitor';
+import {
+ metricsFromTableOrSubquery,
+ QueryFlamegraph,
+ QueryFlamegraphAttrs,
+} from '../../core/query_flamegraph';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {assertExists} from '../../base/logging';
+import {DetailsShell} from '../../widgets/details_shell';
+import {CpuProfileSampleSelection, LegacySelection} from '../../common/state';
class CpuProfile implements PerfettoPlugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
@@ -40,7 +51,8 @@
upid,
thread.name as threadName
from thread_cpu_sample
- join thread using(utid)`);
+ join thread using(utid)
+ `);
const it = result.iter({
utid: NUM,
@@ -52,27 +64,98 @@
const utid = it.utid;
const upid = it.upid;
const threadName = it.threadName;
+ const uri = `${getThreadUriPrefix(upid, utid)}_cpu_samples`;
ctx.registerTrack({
- uri: `${getThreadUriPrefix(upid, utid)}_cpu_samples`,
+ uri,
title: `${threadName} (CPU Stack Samples)`,
tags: {
kind: CPU_PROFILE_TRACK_KIND,
utid,
...(exists(upid) && {upid}),
},
- trackFactory: () => new CpuProfileTrack(ctx.engine, utid),
+ track: new CpuProfileTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ utid,
+ ),
});
}
+ ctx.registerDetailsPanel(
+ new CpuProfileSampleFlamegraphDetailsPanel(ctx.engine),
+ );
+ }
+}
- ctx.registerDetailsPanel({
- render: (sel) => {
- if (sel.kind === 'CPU_PROFILE_SAMPLE') {
- return m(CpuProfileDetailsPanel);
- } else {
- return undefined;
- }
- },
- });
+class CpuProfileSampleFlamegraphDetailsPanel implements LegacyDetailsPanel {
+ private sel?: CpuProfileSampleSelection;
+ private selMonitor = new Monitor([() => this.sel?.ts, () => this.sel?.utid]);
+ private flamegraphAttrs?: QueryFlamegraphAttrs;
+
+ constructor(private engine: Engine) {}
+
+ render(sel: LegacySelection) {
+ if (sel.kind !== 'CPU_PROFILE_SAMPLE') {
+ this.sel = undefined;
+ return undefined;
+ }
+ const {ts, utid} = sel;
+ this.sel = sel;
+ if (this.selMonitor.ifStateChanged()) {
+ this.flamegraphAttrs = {
+ engine: this.engine,
+ metrics: [
+ ...metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_count
+ from _callstacks_for_cpu_profile_stack_samples!((
+ select p.callsite_id
+ from cpu_profile_stack_sample p
+ where p.ts = ${ts} and p.utid = ${utid}
+ ))
+ )
+ `,
+ [
+ {
+ name: 'CPU Profile Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ],
+ 'include perfetto module callstacks.stack_profile',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {name: 'source_file', displayName: 'Source File'},
+ {name: 'line_number', displayName: 'Line Number'},
+ ],
+ ),
+ ],
+ };
+ }
+ return m(
+ '.flamegraph-profile',
+ m(
+ DetailsShell,
+ {
+ fillParent: true,
+ title: m('.title', 'CPU Profile Samples'),
+ description: [],
+ buttons: [
+ m('div.time', `Timestamp: `, m(Timestamp, {ts: this.sel.ts})),
+ ],
+ },
+ m(QueryFlamegraph, assertExists(this.flamegraphAttrs)),
+ ),
+ );
}
}
diff --git a/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts b/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
index 7f1b619..c37cd9b 100644
--- a/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
+++ b/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
@@ -61,12 +61,12 @@
private lastRowId = -1;
private engine: Engine;
private cpu: number;
- private trackKey: string;
+ private uri: string;
private trackUuid = uuidv4Sql();
- constructor(engine: Engine, trackKey: string, cpu: number) {
+ constructor(engine: Engine, uri: string, cpu: number) {
this.engine = engine;
- this.trackKey = trackKey;
+ this.uri = uri;
this.cpu = cpu;
}
@@ -438,7 +438,7 @@
{
kind: 'SCHED_SLICE',
id,
- trackKey: this.trackKey,
+ trackUri: this.uri,
},
{
clearSearch: true,
diff --git a/ui/src/core_plugins/cpu_slices/index.ts b/ui/src/core_plugins/cpu_slices/index.ts
index a377639..8d13c9c 100644
--- a/ui/src/core_plugins/cpu_slices/index.ts
+++ b/ui/src/core_plugins/cpu_slices/index.ts
@@ -42,9 +42,7 @@
kind: CPU_SLICE_TRACK_KIND,
cpu,
},
- trackFactory: ({trackKey}) => {
- return new CpuSliceTrack(ctx.engine, trackKey, cpu);
- },
+ track: new CpuSliceTrack(ctx.engine, uri, cpu),
});
}
diff --git a/ui/src/core_plugins/critical_path/OWNERS b/ui/src/core_plugins/critical_path/OWNERS
new file mode 100644
index 0000000..cd23fd9
--- /dev/null
+++ b/ui/src/core_plugins/critical_path/OWNERS
@@ -0,0 +1,2 @@
+zezeozue@google.com
+lalitm@google.com
\ No newline at end of file
diff --git a/ui/src/core_plugins/critical_path/index.ts b/ui/src/core_plugins/critical_path/index.ts
new file mode 100644
index 0000000..f16dd65
--- /dev/null
+++ b/ui/src/core_plugins/critical_path/index.ts
@@ -0,0 +1,332 @@
+// 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 {
+ getThreadInfo,
+ ThreadInfo,
+} from '../../trace_processor/sql_utils/thread';
+
+import {
+ addDebugSliceTrack,
+ Engine,
+ PerfettoPlugin,
+ PluginContextTrace,
+ PluginDescriptor,
+ THREAD_STATE_TRACK_KIND,
+} from '../../public';
+import {
+ getTimeSpanOfSelectionOrVisibleWindow,
+ globals,
+} from '../../frontend/globals';
+import {asUtid, Utid} from '../../trace_processor/sql_utils/core_types';
+import {addQueryResultsTab} from '../../frontend/query_result_tab';
+import {showModal} from '../../widgets/modal';
+import {Optional} from '../../base/utils';
+import {
+ CRITICAL_PATH_CMD,
+ CRITICAL_PATH_LITE_CMD,
+} from '../../public/exposed_commands';
+
+const criticalPathSliceColumns = {
+ ts: 'ts',
+ dur: 'dur',
+ name: 'name',
+};
+
+const criticalPathsliceColumnNames = [
+ 'id',
+ 'utid',
+ 'ts',
+ 'dur',
+ 'name',
+ 'table_name',
+];
+
+const criticalPathsliceLiteColumns = {
+ ts: 'ts',
+ dur: 'dur',
+ name: 'thread_name',
+};
+
+const criticalPathsliceLiteColumnNames = [
+ 'id',
+ 'utid',
+ 'ts',
+ 'dur',
+ 'thread_name',
+ 'process_name',
+ 'table_name',
+];
+
+const sliceLiteColumns = {ts: 'ts', dur: 'dur', name: 'thread_name'};
+
+const sliceLiteColumnNames = [
+ 'id',
+ 'utid',
+ 'ts',
+ 'dur',
+ 'thread_name',
+ 'process_name',
+ 'table_name',
+];
+
+const sliceColumns = {ts: 'ts', dur: 'dur', name: 'name'};
+
+const sliceColumnNames = ['id', 'utid', 'ts', 'dur', 'name', 'table_name'];
+
+function getFirstUtidOfSelectionOrVisibleWindow(): number {
+ const selection = globals.state.selection;
+ if (selection.kind === 'area') {
+ for (const trackUri of selection.trackUris) {
+ const trackDesc = globals.trackManager.getTrack(trackUri);
+ if (
+ trackDesc?.tags?.kind === THREAD_STATE_TRACK_KIND &&
+ trackDesc?.tags?.utid !== undefined
+ ) {
+ return trackDesc.tags.utid;
+ }
+ }
+ }
+
+ return 0;
+}
+
+function showModalErrorAreaSelectionRequired() {
+ showModal({
+ title: 'Error: range selection required',
+ content:
+ 'This command requires an area selection over a thread state track.',
+ });
+}
+
+function showModalErrorThreadStateRequired() {
+ showModal({
+ title: 'Error: thread state selection required',
+ content: 'This command requires a thread state slice to be selected.',
+ });
+}
+
+// If utid is undefined, returns the utid for the selected thread state track,
+// if any. If it's defined, looks up the info about that specific utid.
+async function getThreadInfoForUtidOrSelection(
+ engine: Engine,
+ utid?: Utid,
+): Promise<Optional<ThreadInfo>> {
+ if (utid === undefined) {
+ if (
+ globals.state.selection.kind !== 'legacy' ||
+ globals.state.selection.legacySelection.kind !== 'THREAD_STATE'
+ ) {
+ return undefined;
+ }
+ const trackUri = globals.state.selection.legacySelection.trackUri;
+ if (trackUri === undefined) return undefined;
+ const track = globals.trackManager.getTrack(trackUri);
+ utid = asUtid(track?.tags?.utid);
+ if (utid === undefined) return undefined;
+ }
+ return getThreadInfo(engine, utid);
+}
+
+class CriticalPath implements PerfettoPlugin {
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ // The 3 commands below are used in two contextes:
+ // 1. By clicking a slice and using the command palette. In this case the
+ // utid argument is undefined and we need to look at the selection.
+ // 2. Invoked via runCommand(...) by thread_state_tab.ts when the user
+ // clicks on the buttons in the details panel. In this case the details
+ // panel passes the utid explicitly.
+ ctx.registerCommand({
+ id: CRITICAL_PATH_LITE_CMD,
+ name: 'Critical path lite (selected thread state slice)',
+ callback: async (utid?: Utid) => {
+ const thdInfo = await getThreadInfoForUtidOrSelection(ctx.engine, utid);
+ if (thdInfo === undefined) {
+ return showModalErrorThreadStateRequired();
+ }
+ ctx.engine
+ .query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`)
+ .then(() =>
+ addDebugSliceTrack(
+ ctx,
+ {
+ sqlSource: `
+ SELECT
+ cr.id,
+ cr.utid,
+ cr.ts,
+ cr.dur,
+ thread.name AS thread_name,
+ process.name AS process_name,
+ 'thread_state' AS table_name
+ FROM
+ _thread_executing_span_critical_path(
+ ${thdInfo.utid},
+ trace_bounds.start_ts,
+ trace_bounds.end_ts - trace_bounds.start_ts) cr,
+ trace_bounds
+ JOIN thread USING(utid)
+ JOIN process USING(upid)
+ `,
+ columns: sliceLiteColumnNames,
+ },
+ `${thdInfo.name}`,
+ sliceLiteColumns,
+ sliceLiteColumnNames,
+ ),
+ );
+ },
+ });
+
+ ctx.registerCommand({
+ id: CRITICAL_PATH_CMD,
+ name: 'Critical path (selected thread state slice)',
+ callback: async (utid?: Utid) => {
+ const thdInfo = await getThreadInfoForUtidOrSelection(ctx.engine, utid);
+ if (thdInfo === undefined) {
+ return showModalErrorThreadStateRequired();
+ }
+ ctx.engine
+ .query(
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
+ )
+ .then(() =>
+ addDebugSliceTrack(
+ ctx,
+ {
+ sqlSource: `
+ SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
+ FROM
+ _thread_executing_span_critical_path_stack(
+ ${thdInfo.utid},
+ trace_bounds.start_ts,
+ trace_bounds.end_ts - trace_bounds.start_ts) cr,
+ trace_bounds WHERE name IS NOT NULL
+ `,
+ columns: sliceColumnNames,
+ },
+ `${thdInfo.name}`,
+ sliceColumns,
+ sliceColumnNames,
+ ),
+ );
+ },
+ });
+
+ ctx.registerCommand({
+ id: 'perfetto.CriticalPathLite_AreaSelection',
+ name: 'Critical path lite (over area selection)',
+ callback: async () => {
+ const trackUtid = getFirstUtidOfSelectionOrVisibleWindow();
+ const window = await getTimeSpanOfSelectionOrVisibleWindow();
+ if (trackUtid === 0) {
+ return showModalErrorAreaSelectionRequired();
+ }
+ await ctx.engine.query(
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
+ );
+ await addDebugSliceTrack(
+ ctx,
+ {
+ sqlSource: `
+ SELECT
+ cr.id,
+ cr.utid,
+ cr.ts,
+ cr.dur,
+ thread.name AS thread_name,
+ process.name AS process_name,
+ 'thread_state' AS table_name
+ FROM
+ _thread_executing_span_critical_path(
+ ${trackUtid},
+ ${window.start},
+ ${window.end} - ${window.start}) cr
+ JOIN thread USING(utid)
+ JOIN process USING(upid)
+ `,
+ columns: criticalPathsliceLiteColumnNames,
+ },
+ (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
+ '<thread name>',
+ criticalPathsliceLiteColumns,
+ criticalPathsliceLiteColumnNames,
+ );
+ },
+ });
+
+ ctx.registerCommand({
+ id: 'perfetto.CriticalPath_AreaSelection',
+ name: 'Critical path (over area selection)',
+ callback: async () => {
+ const trackUtid = getFirstUtidOfSelectionOrVisibleWindow();
+ const window = await getTimeSpanOfSelectionOrVisibleWindow();
+ if (trackUtid === 0) {
+ return showModalErrorAreaSelectionRequired();
+ }
+ await ctx.engine.query(
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
+ );
+ await addDebugSliceTrack(
+ ctx,
+ {
+ sqlSource: `
+ SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
+ FROM
+ _critical_path_stack(
+ ${trackUtid},
+ ${window.start},
+ ${window.end} - ${window.start}, 1, 1, 1, 1) cr
+ WHERE name IS NOT NULL
+ `,
+ columns: criticalPathsliceColumnNames,
+ },
+ (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
+ '<thread name>',
+ criticalPathSliceColumns,
+ criticalPathsliceColumnNames,
+ );
+ },
+ });
+
+ ctx.registerCommand({
+ id: 'perfetto.CriticalPathPprof_AreaSelection',
+ name: 'Critical path pprof (over area selection)',
+ callback: async () => {
+ const trackUtid = getFirstUtidOfSelectionOrVisibleWindow();
+ const window = await getTimeSpanOfSelectionOrVisibleWindow();
+ if (trackUtid === 0) {
+ return showModalErrorAreaSelectionRequired();
+ }
+ addQueryResultsTab({
+ query: `
+ INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
+ SELECT *
+ FROM
+ _thread_executing_span_critical_path_graph(
+ "criical_path",
+ ${trackUtid},
+ ${window.start},
+ ${window.end} - ${window.start}) cr`,
+ title: 'Critical path',
+ });
+ },
+ });
+ }
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: 'perfetto.CriticalPath',
+ plugin: CriticalPath,
+};
diff --git a/ui/src/core_plugins/frames/actual_frames_track.ts b/ui/src/core_plugins/frames/actual_frames_track.ts
index 1f3fe4c..5f4cdaf 100644
--- a/ui/src/core_plugins/frames/actual_frames_track.ts
+++ b/ui/src/core_plugins/frames/actual_frames_track.ts
@@ -48,10 +48,10 @@
constructor(
engine: Engine,
maxDepth: number,
- trackKey: string,
+ uri: string,
private trackIds: number[],
) {
- super({engine, trackKey});
+ super({engine, uri});
this.sliceLayout = {
...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
depthGuess: maxDepth,
diff --git a/ui/src/core_plugins/frames/expected_frames_track.ts b/ui/src/core_plugins/frames/expected_frames_track.ts
index ee51060..c3e110e 100644
--- a/ui/src/core_plugins/frames/expected_frames_track.ts
+++ b/ui/src/core_plugins/frames/expected_frames_track.ts
@@ -28,10 +28,10 @@
constructor(
engine: Engine,
maxDepth: number,
- trackKey: string,
+ uri: string,
private trackIds: number[],
) {
- super({engine, trackKey});
+ super({engine, uri});
this.sliceLayout = {
...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS,
depthGuess: maxDepth,
diff --git a/ui/src/core_plugins/frames/index.ts b/ui/src/core_plugins/frames/index.ts
index 45e2e2e..9c1447d 100644
--- a/ui/src/core_plugins/frames/index.ts
+++ b/ui/src/core_plugins/frames/index.ts
@@ -74,12 +74,11 @@
kind: 'ExpectedFrames',
});
+ const uri = `/process_${upid}/expected_frames`;
ctx.registerTrack({
uri: `/process_${upid}/expected_frames`,
title: displayName,
- trackFactory: ({trackKey}) => {
- return new ExpectedFramesTrack(engine, maxDepth, trackKey, trackIds);
- },
+ track: new ExpectedFramesTrack(engine, maxDepth, uri, trackIds),
tags: {
trackIds,
upid,
@@ -135,12 +134,11 @@
kind,
});
+ const uri = `/process_${upid}/actual_frames`;
ctx.registerTrack({
- uri: `/process_${upid}/actual_frames`,
+ uri,
title: displayName,
- trackFactory: ({trackKey}) => {
- return new ActualFramesTrack(engine, maxDepth, trackKey, trackIds);
- },
+ track: new ActualFramesTrack(engine, maxDepth, uri, trackIds),
tags: {
upid,
trackIds,
diff --git a/ui/src/core_plugins/ftrace/index.ts b/ui/src/core_plugins/ftrace/index.ts
index b1486ee..3f9e88e 100644
--- a/ui/src/core_plugins/ftrace/index.ts
+++ b/ui/src/core_plugins/ftrace/index.ts
@@ -64,16 +64,14 @@
for (const cpuNum of cpus) {
const uri = `/ftrace/cpu${cpuNum}`;
- ctx.registerStaticTrack({
+ ctx.registerTrackAndShowOnTraceLoad({
uri,
- groupName: 'Ftrace Events',
title: `Ftrace Track for CPU ${cpuNum}`,
tags: {
cpu: cpuNum,
+ groupName: 'Ftrace Events',
},
- trackFactory: () => {
- return new FtraceRawTrack(ctx.engine, cpuNum, filterStore);
- },
+ track: new FtraceRawTrack(ctx.engine, cpuNum, filterStore),
});
}
diff --git a/ui/src/core_plugins/heap_profile/heap_profile_track.ts b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
index 12311dc..82c1f1f 100644
--- a/ui/src/core_plugins/heap_profile/heap_profile_track.ts
+++ b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
@@ -14,6 +14,7 @@
import {Actions} from '../../common/actions';
import {LegacySelection, ProfileType} from '../../common/state';
+import {profileType} from '../../core/selection_manager';
import {
BASE_ROW,
BaseSliceTrack,
@@ -21,7 +22,6 @@
OnSliceOverArgs,
} from '../../frontend/base_slice_track';
import {globals} from '../../frontend/globals';
-import {profileType} from '../../frontend/legacy_flamegraph_panel';
import {NewTrackArgs} from '../../frontend/track';
import {Slice} from '../../public';
import {STR} from '../../trace_processor/query_result';
@@ -99,13 +99,9 @@
rowToSlice(row: HeapProfileRow): HeapProfileSlice {
const slice = this.rowToSliceBase(row);
- let type = row.type;
- if (type === 'heap_profile:libc.malloc,com.android.art') {
- type = 'heap_profile:com.android.art,libc.malloc';
- }
return {
...slice,
- type: profileType(type),
+ type: profileType(row.type),
};
}
diff --git a/ui/src/core_plugins/heap_profile/index.ts b/ui/src/core_plugins/heap_profile/index.ts
index 2133173..c99e8bf 100644
--- a/ui/src/core_plugins/heap_profile/index.ts
+++ b/ui/src/core_plugins/heap_profile/index.ts
@@ -16,16 +16,11 @@
import {assertExists, assertFalse} from '../../base/logging';
import {Monitor} from '../../base/monitor';
-import {LegacyFlamegraphCache} from '../../core/legacy_flamegraph_cache';
import {
HeapProfileSelection,
LegacySelection,
ProfileType,
} from '../../core/selection_manager';
-import {
- LegacyFlamegraphDetailsPanel,
- profileType,
-} from '../../frontend/legacy_flamegraph_panel';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {
Engine,
@@ -42,7 +37,6 @@
import {
QueryFlamegraph,
QueryFlamegraphAttrs,
- USE_NEW_FLAMEGRAPH_IMPL,
metricsFromTableOrSubquery,
} from '../../core/query_flamegraph';
import {time} from '../../base/time';
@@ -57,7 +51,6 @@
import {Modal} from '../../widgets/modal';
import {Router} from '../../frontend/router';
import {Actions} from '../../common/actions';
-import {SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG} from '../../common/legacy_flamegraph_util';
class HeapProfilePlugin implements PerfettoPlugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
@@ -68,22 +61,21 @@
`);
for (const it = result.iter({upid: NUM}); it.valid(); it.next()) {
const upid = it.upid;
+ const uri = `/process_${upid}/heap_profile`;
ctx.registerTrack({
- uri: `/process_${upid}/heap_profile`,
+ uri,
title: 'Heap Profile',
tags: {
kind: HEAP_PROFILE_TRACK_KIND,
upid,
},
- trackFactory: ({trackKey}) => {
- return new HeapProfileTrack(
- {
- engine: ctx.engine,
- trackKey,
- },
- upid,
- );
- },
+ track: new HeapProfileTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ upid,
+ ),
});
}
const it = await ctx.engine.query(`
@@ -105,7 +97,6 @@
() => this.sel?.type,
]);
private flamegraphAttrs?: QueryFlamegraphAttrs;
- private cache = new LegacyFlamegraphCache('heap_profile');
constructor(
private engine: Engine,
@@ -117,18 +108,6 @@
this.sel = undefined;
return undefined;
}
- if (!USE_NEW_FLAMEGRAPH_IMPL.get()) {
- this.sel = undefined;
- return m(LegacyFlamegraphDetailsPanel, {
- cache: this.cache,
- selection: {
- profileType: profileType(sel.type),
- start: sel.ts,
- end: sel.ts,
- upids: [sel.upid],
- },
- });
- }
const {ts, upid, type} = sel;
this.sel = sel;
@@ -319,37 +298,6 @@
}
function flamegraphAttrsForHeapGraph(engine: Engine, ts: time, upid: number) {
- const dominator = SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG.get()
- ? metricsFromTableOrSubquery(
- `
- (
- select
- id,
- parent_id as parentId,
- name,
- root_type,
- self_size,
- self_count
- from _heap_graph_dominator_class_tree
- where graph_sample_ts = ${ts} and upid = ${upid}
- )
- `,
- [
- {
- name: 'Dominated Object Size',
- unit: 'B',
- columnName: 'self_size',
- },
- {
- name: 'Dominated Object Count',
- unit: '',
- columnName: 'self_count',
- },
- ],
- 'include perfetto module android.memory.heap_graph.dominator_class_tree;',
- [{name: 'root_type', displayName: 'Root Type'}],
- )
- : [];
return {
engine,
metrics: [
@@ -382,7 +330,35 @@
'include perfetto module android.memory.heap_graph.class_tree;',
[{name: 'root_type', displayName: 'Root Type'}],
),
- ...dominator,
+ ...metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ root_type,
+ self_size,
+ self_count
+ from _heap_graph_dominator_class_tree
+ where graph_sample_ts = ${ts} and upid = ${upid}
+ )
+ `,
+ [
+ {
+ name: 'Dominated Object Size',
+ unit: 'B',
+ columnName: 'self_size',
+ },
+ {
+ name: 'Dominated Object Count',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ],
+ 'include perfetto module android.memory.heap_graph.dominator_class_tree;',
+ [{name: 'root_type', displayName: 'Root Type'}],
+ ),
],
};
}
diff --git a/ui/src/core_plugins/perf_samples_profile/index.ts b/ui/src/core_plugins/perf_samples_profile/index.ts
index 1390688..3a9092d 100644
--- a/ui/src/core_plugins/perf_samples_profile/index.ts
+++ b/ui/src/core_plugins/perf_samples_profile/index.ts
@@ -20,11 +20,6 @@
LegacyDetailsPanel,
PERF_SAMPLES_PROFILE_TRACK_KIND,
} from '../../public';
-import {LegacyFlamegraphCache} from '../../core/legacy_flamegraph_cache';
-import {
- LegacyFlamegraphDetailsPanel,
- profileType,
-} from '../../frontend/legacy_flamegraph_panel';
import {
PerfettoPlugin,
PluginContextTrace,
@@ -38,7 +33,6 @@
import {
QueryFlamegraph,
QueryFlamegraphAttrs,
- USE_NEW_FLAMEGRAPH_IMPL,
metricsFromTableOrSubquery,
} from '../../core/query_flamegraph';
import {Monitor} from '../../base/monitor';
@@ -65,21 +59,21 @@
`);
for (const it = pResult.iter({upid: NUM}); it.valid(); it.next()) {
const upid = it.upid;
+ const uri = `/process_${upid}/perf_samples_profile`;
ctx.registerTrack({
- uri: `/process_${upid}/perf_samples_profile`,
+ uri,
title: `Process Callstacks`,
tags: {
kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
upid,
},
- trackFactory: ({trackKey}) =>
- new ProcessPerfSamplesProfileTrack(
- {
- engine: ctx.engine,
- trackKey,
- },
- upid,
- ),
+ track: new ProcessPerfSamplesProfileTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ upid,
+ ),
});
}
const tResult = await ctx.engine.query(`
@@ -107,22 +101,22 @@
threadName === null
? `Thread Callstacks ${tid}`
: `${threadName} Callstacks ${tid}`;
+ const uri = `${getThreadUriPrefix(upid, utid)}_perf_samples_profile`;
ctx.registerTrack({
- uri: `${getThreadUriPrefix(upid, utid)}_perf_samples_profile`,
+ uri,
title: displayName,
tags: {
kind: PERF_SAMPLES_PROFILE_TRACK_KIND,
utid,
upid: upid ?? undefined,
},
- trackFactory: ({trackKey}) =>
- new ThreadPerfSamplesProfileTrack(
- {
- engine: ctx.engine,
- trackKey,
- },
- utid,
- ),
+ track: new ThreadPerfSamplesProfileTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ utid,
+ ),
});
}
ctx.registerDetailsPanel(new PerfSamplesFlamegraphDetailsPanel(ctx.engine));
@@ -139,7 +133,6 @@
() => this.sel?.type,
]);
private flamegraphAttrs?: QueryFlamegraphAttrs;
- private cache = new LegacyFlamegraphCache('perf_samples');
constructor(private engine: Engine) {}
@@ -148,22 +141,6 @@
this.sel = undefined;
return undefined;
}
- if (
- !USE_NEW_FLAMEGRAPH_IMPL.get() &&
- sel.utid === undefined &&
- sel.upid !== undefined
- ) {
- this.sel = undefined;
- return m(LegacyFlamegraphDetailsPanel, {
- cache: this.cache,
- selection: {
- profileType: profileType(sel.type),
- start: sel.leftTs,
- end: sel.rightTs,
- upids: [sel.upid],
- },
- });
- }
const {leftTs, rightTs, upid, utid} = sel;
this.sel = sel;
diff --git a/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts b/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
index 0e43da4..b8b9d0c 100644
--- a/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
+++ b/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
@@ -12,35 +12,40 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Slice} from '../../public';
+import {NUM, Slice} from '../../public';
import {
BaseSliceTrack,
OnSliceClickArgs,
} from '../../frontend/base_slice_track';
import {NewTrackArgs} from '../../frontend/track';
import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
-import {getColorForSlice} from '../../core/colorizer';
+import {getColorForSample} from '../../core/colorizer';
import {Time} from '../../base/time';
import {globals} from '../../frontend/globals';
import {Actions} from '../../common/actions';
import {LegacySelection, ProfileType} from '../../core/selection_manager';
+import {assertExists} from '../../base/logging';
+
+interface PerfSampleRow extends NamedRow {
+ callsiteId: number;
+}
abstract class BasePerfSamplesProfileTrack extends BaseSliceTrack<
Slice,
- NamedRow
+ PerfSampleRow
> {
constructor(args: NewTrackArgs) {
super(args);
}
- protected getRowSpec(): NamedRow {
- return NAMED_ROW;
+ protected getRowSpec(): PerfSampleRow {
+ return {...NAMED_ROW, callsiteId: NUM};
}
- protected rowToSlice(row: NamedRow): Slice {
+ protected rowToSlice(row: PerfSampleRow): Slice {
const baseSlice = super.rowToSliceBase(row);
- const name = row.name ?? '';
- const colorScheme = getColorForSlice(name);
+ const name = assertExists(row.name);
+ const colorScheme = getColorForSample(row.callsiteId);
return {...baseSlice, title: name, colorScheme};
}
@@ -65,11 +70,16 @@
getSqlSource(): string {
return `
- select p.id, ts, 0 as dur, 0 as depth, 'Perf Sample' as name
+ select
+ p.id,
+ ts,
+ 0 as dur,
+ 0 as depth,
+ 'Perf Sample' as name,
+ callsite_id as callsiteId
from perf_sample p
join thread using (utid)
- where upid = ${this.upid}
- and callsite_id is not null
+ where upid = ${this.upid} and callsite_id is not null
order by ts
`;
}
@@ -97,10 +107,15 @@
getSqlSource(): string {
return `
- select p.id, ts, 0 as dur, 0 as depth, 'Perf Sample' as name
+ select
+ p.id,
+ ts,
+ 0 as dur,
+ 0 as depth,
+ 'Perf Sample' as name,
+ callsite_id as callsiteId
from perf_sample p
- where utid = ${this.utid}
- and callsite_id is not null
+ where utid = ${this.utid} and callsite_id is not null
order by ts
`;
}
diff --git a/ui/src/core_plugins/process/table.ts b/ui/src/core_plugins/process/table.ts
index 9d5f046..bb18e50 100644
--- a/ui/src/core_plugins/process/table.ts
+++ b/ui/src/core_plugins/process/table.ts
@@ -12,6 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import m from 'mithril';
+
+import {
+ AggregationConfig,
+ SourceTable,
+ SqlColumn,
+ TableColumn,
+ TableManager,
+} from '../../frontend/widgets/sql/table/column';
+import {getStandardContextMenuItems} from '../../frontend/widgets/sql/table/render_cell_utils';
import {SqlTableDescription} from '../../frontend/widgets/sql/table/table_description';
import {
ArgSetColumnSet,
@@ -19,21 +29,95 @@
StandardColumn,
TimestampColumn,
} from '../../frontend/widgets/sql/table/well_known_columns';
+import {SqlValue} from '../../trace_processor/sql_utils';
+import {PopupMenu2} from '../../widgets/menu';
+import {Anchor} from '../../widgets/anchor';
+import {showProcessDetailsMenuItem} from '../../frontend/widgets/process';
+import {asUpid} from '../../trace_processor/sql_utils/core_types';
+
+// ProcessIdColumn is a column type for displaying primary key of the `process` table.
+// All other references (foreign keys) should use `ProcessColumn` instead.
+class ProcessIdColumn extends TableColumn {
+ private columns: {pid: SqlColumn};
+
+ constructor(private upid: SqlColumn) {
+ super({});
+
+ const processTable: SourceTable = {
+ table: 'process',
+ joinOn: {id: this.upid},
+ innerJoin: true,
+ };
+
+ this.columns = {
+ pid: {
+ column: 'pid',
+ source: processTable,
+ },
+ };
+ }
+
+ primaryColumn(): SqlColumn {
+ return this.upid;
+ }
+
+ getTitle() {
+ return 'upid';
+ }
+
+ dependentColumns() {
+ return {
+ pid: this.columns.pid,
+ };
+ }
+
+ renderCell(
+ value: SqlValue,
+ manager: TableManager,
+ data: {[key: string]: SqlValue},
+ ): m.Children {
+ const upid = value;
+ const rawPid = data['pid'];
+
+ if (typeof upid !== 'bigint') {
+ throw new Error(
+ `process.upid is expected to be bigint, got ${typeof upid}`,
+ );
+ }
+
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, `${upid}`),
+ },
+
+ showProcessDetailsMenuItem(
+ asUpid(Number(upid)),
+ rawPid === null ? undefined : Number(rawPid),
+ ),
+ getStandardContextMenuItems(upid, this.upid, manager),
+ );
+ }
+
+ aggregation(): AggregationConfig {
+ return {dataType: 'nominal'};
+ }
+}
export function getProcessTable(): SqlTableDescription {
return {
name: 'process',
columns: [
- new StandardColumn('upid'),
- new StandardColumn('pid'),
+ new ProcessIdColumn('upid'),
+ new StandardColumn('pid', {aggregationType: 'nominal'}),
new StandardColumn('name'),
new TimestampColumn('start_ts'),
new TimestampColumn('end_ts'),
new ProcessColumn('parent_upid'),
- new StandardColumn('uid'),
- new StandardColumn('android_appid'),
+ new StandardColumn('uid', {aggregationType: 'nominal'}),
+ new StandardColumn('android_appid', {aggregationType: 'nominal'}),
new StandardColumn('cmdline', {startsHidden: true}),
- new StandardColumn('machine_id'),
+ new StandardColumn('machine_id', {aggregationType: 'nominal'}),
new ArgSetColumnSet('arg_set_id'),
],
};
diff --git a/ui/src/core_plugins/process_summary/index.ts b/ui/src/core_plugins/process_summary/index.ts
index a6c39a0..096045f 100644
--- a/ui/src/core_plugins/process_summary/index.ts
+++ b/ui/src/core_plugins/process_summary/index.ts
@@ -125,9 +125,7 @@
kind: PROCESS_SCHEDULING_TRACK_KIND,
},
chips,
- trackFactory: () => {
- return new ProcessSchedulingTrack(ctx.engine, config, cpuCount);
- },
+ track: new ProcessSchedulingTrack(ctx.engine, config, cpuCount),
subtitle,
});
} else {
@@ -144,7 +142,7 @@
kind: PROCESS_SUMMARY_TRACK,
},
chips,
- trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
+ track: new ProcessSummaryTrack(ctx.engine, config),
subtitle,
});
}
@@ -202,7 +200,7 @@
tags: {
kind: PROCESS_SUMMARY_TRACK,
},
- trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
+ track: new ProcessSummaryTrack(ctx.engine, config),
});
}
}
diff --git a/ui/src/core_plugins/sched/active_cpu_count.ts b/ui/src/core_plugins/sched/active_cpu_count.ts
index 794cd62..3ee99aa 100644
--- a/ui/src/core_plugins/sched/active_cpu_count.ts
+++ b/ui/src/core_plugins/sched/active_cpu_count.ts
@@ -13,14 +13,15 @@
// limitations under the License.
import m from 'mithril';
-
+import {Icons} from '../../base/semantic_icons';
import {sqliteString} from '../../base/string_utils';
import {
BaseCounterTrack,
CounterOptions,
} from '../../frontend/base_counter_track';
-import {CloseTrackButton} from '../../frontend/close_track_button';
+import {globals} from '../../frontend/globals';
import {Engine, TrackContext} from '../../public';
+import {Button} from '../../widgets/button';
export enum CPUType {
Big = 'big',
@@ -34,14 +35,19 @@
constructor(ctx: TrackContext, engine: Engine, cpuType?: CPUType) {
super({
engine,
- trackKey: ctx.trackKey,
+ uri: ctx.trackUri,
});
this.cpuType = cpuType;
}
getTrackShellButtons(): m.Children {
- return m(CloseTrackButton, {
- trackKey: this.trackKey,
+ return m(Button, {
+ onclick: () => {
+ globals.workspace.getTrackByUri(this.uri)?.remove();
+ },
+ icon: Icons.Close,
+ title: 'Close',
+ compact: true,
});
}
diff --git a/ui/src/core_plugins/sched/index.ts b/ui/src/core_plugins/sched/index.ts
index 10cf5d7..b35638b 100644
--- a/ui/src/core_plugins/sched/index.ts
+++ b/ui/src/core_plugins/sched/index.ts
@@ -12,17 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {uuidv4} from '../../base/uuid';
-import {Actions} from '../../common/actions';
-import {SCROLLING_TRACK_GROUP} from '../../common/state';
-import {globals} from '../../frontend/globals';
import {addSqlTableTab} from '../../frontend/sql_table_tab_command';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
+import {TrackNode} from '../../public/workspace';
import {
PerfettoPlugin,
PluginContextTrace,
PluginDescriptor,
- PrimaryTrackSortKey,
} from '../../public';
import {ActiveCPUCountTrack, CPUType} from './active_cpu_count';
@@ -35,17 +31,16 @@
ctx.registerTrack({
uri: runnableThreadCountUri,
title: 'Runnable thread count',
- trackFactory: (trackCtx) =>
- new RunnableThreadCountTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- }),
+ track: new RunnableThreadCountTrack({
+ engine: ctx.engine,
+ uri: runnableThreadCountUri,
+ }),
});
ctx.registerCommand({
id: 'dev.perfetto.Sched.AddRunnableThreadCountTrackCommand',
name: 'Add track: runnable thread count',
callback: () =>
- addPinnedTrack(runnableThreadCountUri, 'Runnable thread count'),
+ addPinnedTrack(ctx, runnableThreadCountUri, 'Runnable thread count'),
});
const uri = uriForActiveCPUCountTrack();
@@ -53,12 +48,12 @@
ctx.registerTrack({
uri,
title: title,
- trackFactory: (trackCtx) => new ActiveCPUCountTrack(trackCtx, ctx.engine),
+ track: new ActiveCPUCountTrack({trackUri: uri}, ctx.engine),
});
ctx.registerCommand({
id: 'dev.perfetto.Sched.AddActiveCPUCountTrackCommand',
name: 'Add track: active CPU count',
- callback: () => addPinnedTrack(uri, title),
+ callback: () => addPinnedTrack(ctx, uri, title),
});
for (const cpuType of Object.values(CPUType)) {
@@ -67,14 +62,13 @@
ctx.registerTrack({
uri,
title: title,
- trackFactory: (trackCtx) =>
- new ActiveCPUCountTrack(trackCtx, ctx.engine, cpuType),
+ track: new ActiveCPUCountTrack({trackUri: uri}, ctx.engine, cpuType),
});
ctx.registerCommand({
id: `dev.perfetto.Sched.AddActiveCPUCountTrackCommand.${cpuType}`,
name: `Add track: active ${cpuType} CPU count`,
- callback: () => addPinnedTrack(uri, title),
+ callback: () => addPinnedTrack(ctx, uri, title),
});
}
@@ -100,18 +94,11 @@
}
}
-function addPinnedTrack(uri: string, title: string) {
- const key = uuidv4();
- globals.dispatchMultiple([
- Actions.addTrack({
- key,
- uri,
- name: title,
- trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- }),
- Actions.toggleTrackPinned({trackKey: key}),
- ]);
+function addPinnedTrack(ctx: PluginContextTrace, uri: string, title: string) {
+ const track = new TrackNode(uri, title);
+ // Add track to the top of the stack
+ ctx.timeline.workspace.prependChild(track);
+ track.pin();
}
export const plugin: PluginDescriptor = {
diff --git a/ui/src/core_plugins/sched/runnable_thread_count.ts b/ui/src/core_plugins/sched/runnable_thread_count.ts
index 3908572..9b5e9c5 100644
--- a/ui/src/core_plugins/sched/runnable_thread_count.ts
+++ b/ui/src/core_plugins/sched/runnable_thread_count.ts
@@ -12,13 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import m from 'mithril';
-
import {
BaseCounterTrack,
CounterOptions,
} from '../../frontend/base_counter_track';
-import {CloseTrackButton} from '../../frontend/close_track_button';
import {NewTrackArgs} from '../../frontend/track';
export class RunnableThreadCountTrack extends BaseCounterTrack {
@@ -26,12 +23,6 @@
super(args);
}
- getTrackShellButtons(): m.Children {
- return m(CloseTrackButton, {
- trackKey: this.trackKey,
- });
- }
-
protected getDefaultCounterOptions(): CounterOptions {
const options = super.getDefaultCounterOptions();
options.yRangeRounding = 'strict';
diff --git a/ui/src/core_plugins/sched/table.ts b/ui/src/core_plugins/sched/table.ts
index 0f0b274..105c80b 100644
--- a/ui/src/core_plugins/sched/table.ts
+++ b/ui/src/core_plugins/sched/table.ts
@@ -29,9 +29,9 @@
new SchedIdColumn('id'),
new TimestampColumn('ts'),
new DurationColumn('dur'),
- new StandardColumn('cpu'),
- new StandardColumn('priority'),
- new ThreadColumn('utid', {title: 'Thread'}),
+ new StandardColumn('cpu', {aggregationType: 'nominal'}),
+ new StandardColumn('priority', {aggregationType: 'nominal'}),
+ new ThreadColumn('utid', {title: 'Thread', notNull: true}),
new ProcessColumn(
{
column: 'upid',
@@ -40,12 +40,16 @@
joinOn: {
utid: 'utid',
},
+ innerJoin: true,
},
},
- {title: 'Process'},
+ {title: 'Process', notNull: true},
),
new StandardColumn('end_state'),
- new StandardColumn('ucpu'),
+ new StandardColumn('ucpu', {
+ aggregationType: 'nominal',
+ startsHidden: true,
+ }),
],
};
}
diff --git a/ui/src/core_plugins/screenshots/index.ts b/ui/src/core_plugins/screenshots/index.ts
index a1bacde..1a0320d 100644
--- a/ui/src/core_plugins/screenshots/index.ts
+++ b/ui/src/core_plugins/screenshots/index.ts
@@ -41,12 +41,10 @@
ctx.registerTrack({
uri,
title: displayName,
- trackFactory: ({trackKey}) => {
- return new ScreenshotsTrack({
- engine: ctx.engine,
- trackKey,
- });
- },
+ track: new ScreenshotsTrack({
+ engine: ctx.engine,
+ uri,
+ }),
tags: {
kind: ScreenshotsTrack.kind,
},
diff --git a/ui/src/core_plugins/slice/table.ts b/ui/src/core_plugins/slice/table.ts
index fa800d3..29b453c 100644
--- a/ui/src/core_plugins/slice/table.ts
+++ b/ui/src/core_plugins/slice/table.ts
@@ -29,13 +29,17 @@
name: '_slice_with_thread_and_process_info',
displayName: 'slice',
columns: [
- new SliceIdColumn('id'),
+ new SliceIdColumn('id', {notNull: true}),
new TimestampColumn('ts', {title: 'Timestamp'}),
new DurationColumn('dur', {title: 'Duration'}),
new DurationColumn('thread_dur', {title: 'Thread duration'}),
new StandardColumn('category', {title: 'Category'}),
new StandardColumn('name', {title: 'Name'}),
- new StandardColumn('track_id', {title: 'Track ID', startsHidden: true}),
+ new StandardColumn('track_id', {
+ title: 'Track ID',
+ aggregationType: 'nominal',
+ startsHidden: true,
+ }),
new ThreadColumn('utid', {title: 'Thread'}),
new ProcessColumn('upid', {title: 'Process'}),
new StandardColumn('depth', {title: 'Depth', startsHidden: true}),
diff --git a/ui/src/core_plugins/thread/table.ts b/ui/src/core_plugins/thread/table.ts
index 920039f..6deb63e 100644
--- a/ui/src/core_plugins/thread/table.ts
+++ b/ui/src/core_plugins/thread/table.ts
@@ -12,24 +12,110 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import m from 'mithril';
+
+import {
+ AggregationConfig,
+ SourceTable,
+ SqlColumn,
+ TableColumn,
+ TableManager,
+} from '../../frontend/widgets/sql/table/column';
import {SqlTableDescription} from '../../frontend/widgets/sql/table/table_description';
+import {SqlValue} from '../../trace_processor/query_result';
+import {PopupMenu2} from '../../widgets/menu';
+import {Anchor} from '../../widgets/anchor';
+import {getStandardContextMenuItems} from '../../frontend/widgets/sql/table/render_cell_utils';
import {
ProcessColumn,
StandardColumn,
TimestampColumn,
} from '../../frontend/widgets/sql/table/well_known_columns';
+import {showThreadDetailsMenuItem} from '../../frontend/widgets/thread';
+import {asUtid} from '../../trace_processor/sql_utils/core_types';
+
+// ThreadIdColumn is a column type for displaying primary key of the `thread` table.
+// All other references (foreign keys) should use `ThreadColumn` instead.
+class ThreadIdColumn extends TableColumn {
+ private columns: {tid: SqlColumn};
+
+ constructor(private utid: SqlColumn) {
+ super({});
+
+ const threadTable: SourceTable = {
+ table: 'thread',
+ joinOn: {id: this.utid},
+ innerJoin: true,
+ };
+
+ this.columns = {
+ tid: {
+ column: 'tid',
+ source: threadTable,
+ },
+ };
+ }
+
+ primaryColumn(): SqlColumn {
+ return this.utid;
+ }
+
+ getTitle() {
+ return 'utid';
+ }
+
+ dependentColumns() {
+ return {
+ tid: this.columns.tid,
+ };
+ }
+
+ renderCell(
+ value: SqlValue,
+ manager: TableManager,
+ data: {[key: string]: SqlValue},
+ ): m.Children {
+ const utid = value;
+ const rawTid = data['tid'];
+
+ if (typeof utid !== 'bigint') {
+ throw new Error(
+ `thread.upid is expected to be bigint, got ${typeof utid}`,
+ );
+ }
+
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, `${utid}`),
+ },
+
+ showThreadDetailsMenuItem(
+ asUtid(Number(utid)),
+ rawTid === null ? undefined : Number(rawTid),
+ ),
+ getStandardContextMenuItems(utid, this.utid, manager),
+ );
+ }
+
+ aggregation(): AggregationConfig {
+ return {dataType: 'nominal'};
+ }
+}
export function getThreadTable(): SqlTableDescription {
return {
name: 'thread',
columns: [
- new StandardColumn('utid'),
- new StandardColumn('tid'),
+ new ThreadIdColumn('utid'),
+ new StandardColumn('tid', {aggregationType: 'nominal'}),
new StandardColumn('name'),
new TimestampColumn('start_ts'),
new TimestampColumn('end_ts'),
- new ProcessColumn('upid'),
- new StandardColumn('is_main_thread'),
+ new ProcessColumn('upid', {notNull: true}),
+ new StandardColumn('is_main_thread', {
+ aggregationType: 'nominal',
+ }),
],
};
}
diff --git a/ui/src/core_plugins/thread_slice/index.ts b/ui/src/core_plugins/thread_slice/index.ts
index 74eebc2..e2c4395 100644
--- a/ui/src/core_plugins/thread_slice/index.ts
+++ b/ui/src/core_plugins/thread_slice/index.ts
@@ -36,22 +36,24 @@
include perfetto module viz.threads;
select
- thread_track.utid as utid,
- thread_track.id as trackId,
- thread_track.name as trackName,
- EXTRACT_ARG(thread_track.source_arg_set_id,
- 'is_root_in_scope') as isDefaultTrackForScope,
- tid,
+ tt.utid as utid,
+ tt.id as trackId,
+ tt.name as trackName,
+ extract_arg(
+ tt.source_arg_set_id,
+ 'is_root_in_scope'
+ ) as isDefaultTrackForScope,
+ t.tid,
t.name as threadName,
- max_depth as maxDepth,
+ s.max_depth as maxDepth,
t.upid as upid,
- is_main_thread as isMainThread,
- is_kernel_thread AS isKernelThread
- from thread_track
+ t.is_main_thread as isMainThread,
+ t.is_kernel_thread AS isKernelThread
+ from _thread_track_summary_by_utid_and_name s
join _threads_with_kernel_flag t using(utid)
- join _slice_track_summary sts on sts.id = thread_track.id
+ join thread_track tt on s.track_id = tt.id
+ where s.track_count = 1
`);
-
const it = result.iter({
utid: NUM,
trackId: NUM,
@@ -64,7 +66,6 @@
isMainThread: NUM_NULL,
isKernelThread: NUM,
});
-
for (; it.valid(); it.next()) {
const {
upid,
@@ -86,8 +87,9 @@
kind: 'Slices',
});
+ const uri = `${getThreadUriPrefix(upid, utid)}_slice_${trackId}`;
ctx.registerTrack({
- uri: `${getThreadUriPrefix(upid, utid)}_slice_${trackId}`,
+ uri,
title: displayName,
tags: {
trackIds: [trackId],
@@ -99,13 +101,14 @@
chips: removeFalsyValues([
isKernelThread === 0 && isMainThread === 1 && 'main thread',
]),
- trackFactory: ({trackKey}) => {
- const newTrackArgs = {
+ track: new ThreadSliceTrack(
+ {
engine: ctx.engine,
- trackKey,
- };
- return new ThreadSliceTrack(newTrackArgs, trackId, maxDepth);
- },
+ uri,
+ },
+ trackId,
+ maxDepth,
+ ),
});
}
diff --git a/ui/src/core_plugins/thread_state/index.ts b/ui/src/core_plugins/thread_state/index.ts
index 6a91252..617da03 100644
--- a/ui/src/core_plugins/thread_state/index.ts
+++ b/ui/src/core_plugins/thread_state/index.ts
@@ -66,8 +66,9 @@
kind: THREAD_STATE_TRACK_KIND,
});
+ const uri = `${getThreadUriPrefix(upid, utid)}_state`;
ctx.registerTrack({
- uri: `${getThreadUriPrefix(upid, utid)}_state`,
+ uri,
title: displayName,
tags: {
kind: THREAD_STATE_TRACK_KIND,
@@ -77,15 +78,13 @@
chips: removeFalsyValues([
isKernelThread === 0 && isMainThread === 1 && 'main thread',
]),
- trackFactory: ({trackKey}) => {
- return new ThreadStateTrack(
- {
- engine: ctx.engine,
- trackKey,
- },
- utid,
- );
- },
+ track: new ThreadStateTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ utid,
+ ),
});
}
diff --git a/ui/src/core_plugins/thread_state/table.ts b/ui/src/core_plugins/thread_state/table.ts
index 3372665..7897127 100644
--- a/ui/src/core_plugins/thread_state/table.ts
+++ b/ui/src/core_plugins/thread_state/table.ts
@@ -26,12 +26,12 @@
return {
name: 'thread_state',
columns: [
- new ThreadStateIdColumn('id'),
+ new ThreadStateIdColumn('id', {notNull: true}),
new TimestampColumn('ts'),
new DurationColumn('dur'),
new StandardColumn('state'),
- new StandardColumn('cpu'),
- new ThreadColumn('utid', {title: 'Thread'}),
+ new StandardColumn('cpu', {aggregationType: 'nominal'}),
+ new ThreadColumn('utid', {title: 'Thread', notNull: true}),
new ProcessColumn(
{
column: 'upid',
@@ -40,16 +40,20 @@
joinOn: {
utid: 'utid',
},
+ innerJoin: true,
},
},
- {title: 'Process'},
+ {title: 'Process', notNull: true},
),
- new StandardColumn('io_wait'),
+ new StandardColumn('io_wait', {aggregationType: 'nominal'}),
new StandardColumn('blocked_function'),
new ThreadColumn('waker_utid', {title: 'Waker thread'}),
new ThreadStateIdColumn('waker_id'),
- new StandardColumn('irq_context'),
- new StandardColumn('ucpu'),
+ new StandardColumn('irq_context', {aggregationType: 'nominal'}),
+ new StandardColumn('ucpu', {
+ aggregationType: 'nominal',
+ startsHidden: true,
+ }),
],
};
}
diff --git a/ui/src/core_plugins/thread_state/thread_state_track.ts b/ui/src/core_plugins/thread_state/thread_state_track.ts
index 660700e..150ebb4 100644
--- a/ui/src/core_plugins/thread_state/thread_state_track.ts
+++ b/ui/src/core_plugins/thread_state/thread_state_track.ts
@@ -89,7 +89,7 @@
globals.makeSelection(
Actions.selectThreadState({
id: args.slice.id,
- trackKey: this.trackKey,
+ trackUri: this.uri,
}),
);
}
diff --git a/ui/src/core_plugins/track_utils/index.ts b/ui/src/core_plugins/track_utils/index.ts
index 5c46f06..a51a170 100644
--- a/ui/src/core_plugins/track_utils/index.ts
+++ b/ui/src/core_plugins/track_utils/index.ts
@@ -66,24 +66,15 @@
sortedOptions,
);
- // Find the first track with this URI
- const firstTrack = Object.values(globals.state.tracks).find(
- ({uri}) => uri === selectedUri,
+ verticalScrollToTrack(selectedUri, true);
+ const traceTime = globals.traceContext;
+ globals.makeSelection(
+ Actions.selectArea({
+ start: traceTime.start,
+ end: traceTime.end,
+ trackUris: [selectedUri],
+ }),
);
- if (firstTrack) {
- console.log(firstTrack);
- verticalScrollToTrack(firstTrack.key, true);
- const traceTime = globals.traceContext;
- globals.makeSelection(
- Actions.selectArea({
- start: traceTime.start,
- end: traceTime.end,
- tracks: [firstTrack.key],
- }),
- );
- } else {
- alert(`No tracks with uri ${selectedUri} on the timeline`);
- }
} catch {
// Prompt was probably cancelled - do nothing.
}
diff --git a/ui/src/core_plugins/wattson/index.ts b/ui/src/core_plugins/wattson/index.ts
index 8fa20ee..54841fb 100644
--- a/ui/src/core_plugins/wattson/index.ts
+++ b/ui/src/core_plugins/wattson/index.ts
@@ -37,28 +37,29 @@
const cpus = globals.traceContext.cpus;
for (const cpu of cpus) {
const queryKey = `cpu${cpu}_curve`;
- ctx.registerStaticTrack({
- uri: `/wattson/cpu_subsystem_estimate_cpu${cpu}`,
+ const uri = `/wattson/cpu_subsystem_estimate_cpu${cpu}`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: `Cpu${cpu} Estimate`,
- trackFactory: ({trackKey}) =>
- new CpuSubsystemEstimateTrack(ctx.engine, trackKey, queryKey),
- groupName: `Wattson`,
+ track: new CpuSubsystemEstimateTrack(ctx.engine, uri, queryKey),
+
tags: {
kind: CPUSS_ESTIMATE_TRACK_KIND,
wattson: `CPU${cpu}`,
+ groupName: `Wattson`,
},
});
}
- ctx.registerStaticTrack({
- uri: `/wattson/cpu_subsystem_estimate_dsu_scu`,
+ const uri = `/wattson/cpu_subsystem_estimate_dsu_scu`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: `DSU/SCU Estimate`,
- trackFactory: ({trackKey}) =>
- new CpuSubsystemEstimateTrack(ctx.engine, trackKey, `dsu_scu`),
- groupName: `Wattson`,
+ track: new CpuSubsystemEstimateTrack(ctx.engine, uri, `dsu_scu`),
tags: {
kind: CPUSS_ESTIMATE_TRACK_KIND,
wattson: 'Dsu_Scu',
+ groupName: `Wattson`,
},
});
}
@@ -68,10 +69,10 @@
readonly engine: Engine;
readonly queryKey: string;
- constructor(engine: Engine, trackKey: string, queryKey: string) {
+ constructor(engine: Engine, uri: string, queryKey: string) {
super({
- engine: engine,
- trackKey: trackKey,
+ engine,
+ uri,
});
this.engine = engine;
this.queryKey = queryKey;
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index 8abaa06..aefc40b 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -23,24 +23,20 @@
import {EmptyState} from '../widgets/empty_state';
import {FlowEventsAreaSelectedPanel} from './flow_events_panel';
import {PivotTable} from './pivot_table';
-import {
- LegacyFlamegraphDetailsPanel,
- FlamegraphSelectionParams,
-} from './legacy_flamegraph_panel';
-import {AreaSelection, ProfileType, TrackState} from '../common/state';
-import {assertExists} from '../base/logging';
+import {AreaSelection} from '../common/state';
import {Monitor} from '../base/monitor';
import {
+ CPU_PROFILE_TRACK_KIND,
PERF_SAMPLES_PROFILE_TRACK_KIND,
THREAD_SLICE_TRACK_KIND,
} from '../core/track_kinds';
import {
QueryFlamegraph,
QueryFlamegraphAttrs,
- USE_NEW_FLAMEGRAPH_IMPL,
metricsFromTableOrSubquery,
} from '../core/query_flamegraph';
import {DisposableStack} from '../base/disposable_stack';
+import {assertExists} from '../base/logging';
interface View {
key: string;
@@ -51,9 +47,9 @@
class AreaDetailsPanel implements m.ClassComponent {
private readonly monitor = new Monitor([() => globals.state.selection]);
private currentTab: string | undefined = undefined;
+ private cpuProfileFlamegraphAttrs?: QueryFlamegraphAttrs;
private perfSampleFlamegraphAttrs?: QueryFlamegraphAttrs;
private sliceFlamegraphAttrs?: QueryFlamegraphAttrs;
- private legacyFlamegraphSelection?: FlamegraphSelectionParams;
private getCurrentView(): string | undefined {
const types = this.getViews().map(({key}) => key);
@@ -87,7 +83,11 @@
}
const pivotTableState = globals.state.nonSerializableState.pivotTable;
- if (pivotTableState.selectionArea !== undefined) {
+ const tree = pivotTableState.queryResult?.tree;
+ if (
+ pivotTableState.selectionArea != undefined &&
+ (tree === undefined || tree.children.size > 0 || tree?.rows.length > 0)
+ ) {
views.push({
key: 'pivot_table',
name: 'Pivot Table',
@@ -97,12 +97,7 @@
});
}
- const isChanged = this.monitor.ifStateChanged();
- if (USE_NEW_FLAMEGRAPH_IMPL.get()) {
- this.addFlamegraphView(isChanged, views);
- } else {
- this.addLegacyFlamegraphView(isChanged, views);
- }
+ this.addFlamegraphView(this.monitor.ifStateChanged(), views);
// Add this after all aggregation panels, to make it appear after 'Slices'
if (globals.selectedFlows.length > 0) {
@@ -163,6 +158,15 @@
}
private addFlamegraphView(isChanged: boolean, views: View[]) {
+ this.cpuProfileFlamegraphAttrs =
+ this.computeCpuProfileFlamegraphAttrs(isChanged);
+ if (this.cpuProfileFlamegraphAttrs !== undefined) {
+ views.push({
+ key: 'cpu_profile_flamegraph_selection',
+ name: 'CPU Profile Sample Flamegraph',
+ content: m(QueryFlamegraph, this.cpuProfileFlamegraphAttrs),
+ });
+ }
this.perfSampleFlamegraphAttrs =
this.computePerfSampleFlamegraphAttrs(isChanged);
if (this.perfSampleFlamegraphAttrs !== undefined) {
@@ -182,6 +186,67 @@
}
}
+ private computeCpuProfileFlamegraphAttrs(isChanged: boolean) {
+ const currentSelection = globals.state.selection;
+ if (currentSelection.kind !== 'area') {
+ return undefined;
+ }
+ if (!isChanged) {
+ // If the selection has not changed, just return a copy of the last seen
+ // attrs.
+ return this.cpuProfileFlamegraphAttrs;
+ }
+ const utids = [];
+ for (const trackUri of currentSelection.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
+ if (trackInfo?.tags?.kind === CPU_PROFILE_TRACK_KIND) {
+ utids.push(trackInfo.tags?.utid);
+ }
+ }
+ if (utids.length === 0) {
+ return undefined;
+ }
+ return {
+ engine: assertExists(this.getCurrentEngine()),
+ metrics: [
+ ...metricsFromTableOrSubquery(
+ `
+ (
+ select
+ id,
+ parent_id as parentId,
+ name,
+ mapping_name,
+ source_file,
+ cast(line_number AS text) as line_number,
+ self_count
+ from _callstacks_for_cpu_profile_stack_samples!((
+ select p.callsite_id
+ from cpu_profile_stack_sample p
+ where p.ts >= ${currentSelection.start}
+ and p.ts <= ${currentSelection.end}
+ and p.utid in (${utids.join(',')})
+ ))
+ )
+ `,
+ [
+ {
+ name: 'CPU Profile Samples',
+ unit: '',
+ columnName: 'self_count',
+ },
+ ],
+ 'include perfetto module callstacks.stack_profile',
+ [{name: 'mapping_name', displayName: 'Mapping'}],
+ [
+ {name: 'source_file', displayName: 'Source File'},
+ {name: 'line_number', displayName: 'Line Number'},
+ ],
+ ),
+ ],
+ };
+ }
+
private computePerfSampleFlamegraphAttrs(isChanged: boolean) {
const currentSelection = globals.state.selection;
if (currentSelection.kind !== 'area') {
@@ -194,6 +259,9 @@
}
const upids = getUpidsFromPerfSampleAreaSelection(currentSelection);
const utids = getUtidsFromPerfSampleAreaSelection(currentSelection);
+ if (utids.length === 0 && upids.length === 0) {
+ return undefined;
+ }
return {
engine: assertExists(this.getCurrentEngine()),
metrics: [
@@ -238,9 +306,8 @@
return this.sliceFlamegraphAttrs;
}
const trackIds = [];
- for (const trackId of currentSelection.tracks) {
- const track: TrackState | undefined = globals.state.tracks[trackId];
- const trackInfo = globals.trackManager.resolveTrackInfo(track?.uri);
+ for (const trackUri of currentSelection.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
if (trackInfo?.tags?.kind !== THREAD_SLICE_TRACK_KIND) {
continue;
}
@@ -286,44 +353,6 @@
};
}
- private addLegacyFlamegraphView(isChanged: boolean, views: View[]) {
- this.legacyFlamegraphSelection =
- this.computeLegacyFlamegraphSelection(isChanged);
- if (this.legacyFlamegraphSelection === undefined) {
- return;
- }
- views.push({
- key: 'flamegraph_selection',
- name: 'Flamegraph Selection',
- content: m(LegacyFlamegraphDetailsPanel, {
- cache: globals.areaFlamegraphCache,
- selection: this.legacyFlamegraphSelection,
- }),
- });
- }
-
- private computeLegacyFlamegraphSelection(isChanged: boolean) {
- const currentSelection = globals.state.selection;
- if (currentSelection.kind !== 'area') {
- return undefined;
- }
- if (!isChanged) {
- // If the selection has not changed, just return a copy of the last seen
- // selection.
- return this.legacyFlamegraphSelection;
- }
- const upids = getUpidsFromPerfSampleAreaSelection(currentSelection);
- if (upids.length === 0) {
- return undefined;
- }
- return {
- profileType: ProfileType.PERF_SAMPLE,
- start: currentSelection.start,
- end: currentSelection.end,
- upids,
- };
- }
-
private getCurrentEngine() {
const engineId = globals.getCurrentEngine()?.id;
if (engineId === undefined) return undefined;
@@ -355,9 +384,8 @@
function getUpidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) {
const upids = [];
- for (const trackId of currentSelection.tracks) {
- const track: TrackState | undefined = globals.state.tracks[trackId];
- const trackInfo = globals.trackManager.resolveTrackInfo(track?.uri);
+ for (const trackUri of currentSelection.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
if (
trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
trackInfo.tags?.utid === undefined
@@ -370,9 +398,8 @@
function getUtidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) {
const utids = [];
- for (const trackId of currentSelection.tracks) {
- const track: TrackState | undefined = globals.state.tracks[trackId];
- const trackInfo = globals.trackManager.resolveTrackInfo(track?.uri);
+ for (const trackUri of currentSelection.trackUris) {
+ const trackInfo = globals.trackManager.getTrack(trackUri);
if (
trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
trackInfo.tags?.utid !== undefined
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index bafeade..63931e2 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -189,7 +189,7 @@
export abstract class BaseCounterTrack implements Track {
protected engine: Engine;
- protected trackKey: string;
+ protected uri: string;
protected trackUuid = uuidv4Sql();
// This is the over-skirted cached bounds:
@@ -249,7 +249,7 @@
constructor(args: BaseCounterTrackArgs) {
this.engine = args.engine;
- this.trackKey = args.trackKey;
+ this.uri = args.uri;
this.defaultOptions = args.options ?? {};
this.trash = new AsyncDisposableStack();
}
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 772aa22..eafdeba 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -171,7 +171,7 @@
{
protected sliceLayout: SliceLayout = {...DEFAULT_SLICE_LAYOUT};
protected engine: Engine;
- protected trackKey: string;
+ protected uri: string;
protected trackUuid = uuidv4Sql();
// This is the over-skirted cached bounds:
@@ -249,7 +249,7 @@
constructor(args: NewTrackArgs) {
this.engine = args.engine;
- this.trackKey = args.trackKey;
+ this.uri = args.uri;
// Work out the extra columns.
// This is the union of the embedder-defined columns and the base columns
// we know about (ts, dur, ...).
diff --git a/ui/src/frontend/charts/histogram/state.ts b/ui/src/frontend/charts/histogram/state.ts
index b07b9a9..23cf8a6 100644
--- a/ui/src/frontend/charts/histogram/state.ts
+++ b/ui/src/frontend/charts/histogram/state.ts
@@ -16,7 +16,7 @@
import {Row} from '../../../trace_processor/query_result';
interface ChartConfig {
- binAxisType?: 'nominal' | 'quantitative';
+ binAxisType: 'nominal' | 'quantitative';
binAxis: 'x' | 'y';
countAxis: 'x' | 'y';
sort: string;
@@ -24,36 +24,53 @@
labelLimit?: number;
}
-export class HistogramState {
- private readonly sqlColumn: string;
- private readonly engine: Engine;
- private readonly query: string;
+interface HistogramData {
+ readonly rows: Row[];
+ readonly error?: string;
+ readonly chartConfig: ChartConfig;
+}
- data?: Row[];
- chartConfig: ChartConfig;
-
- get isLoading() {
- return this.data === undefined;
- }
-
- constructor(engine: Engine, query: string, column: string) {
- this.engine = engine;
- this.query = query;
- this.sqlColumn = column;
-
- this.chartConfig = {
+function getHistogramConfig(
+ aggregationType: 'nominal' | 'quantitative',
+): ChartConfig {
+ const labelLimit = 500;
+ if (aggregationType === 'nominal') {
+ return {
+ binAxisType: aggregationType,
+ binAxis: 'y',
+ countAxis: 'x',
+ sort: `{
+ "op": "count",
+ "order": "descending"
+ }`,
+ isBinned: false,
+ labelLimit,
+ };
+ } else {
+ return {
+ binAxisType: aggregationType,
binAxis: 'x',
- binAxisType: 'nominal',
countAxis: 'y',
sort: 'false',
isBinned: true,
- labelLimit: 500,
+ labelLimit,
};
+ }
+}
- this.getData();
+export class HistogramState {
+ data?: HistogramData;
+
+ constructor(
+ private readonly engine: Engine,
+ private readonly query: string,
+ private readonly sqlColumn: string,
+ private readonly aggregationType?: 'nominal' | 'quantitative',
+ ) {
+ this.loadData();
}
- async getData() {
+ private async loadData() {
const res = await this.engine.query(`
SELECT ${this.sqlColumn}
FROM (
@@ -63,14 +80,12 @@
const rows: Row[] = [];
+ let hasQuantitativeData = false;
+
for (const it = res.iter({}); it.valid(); it.next()) {
const rowVal = it.get(this.sqlColumn);
-
- if (
- this.chartConfig.binAxisType === 'nominal' &&
- typeof rowVal === 'bigint'
- ) {
- this.chartConfig.binAxisType = 'quantitative';
+ if (typeof rowVal === 'bigint') {
+ hasQuantitativeData = true;
}
rows.push({
@@ -78,17 +93,17 @@
});
}
- this.data = rows;
+ const aggregationType =
+ this.aggregationType !== undefined
+ ? this.aggregationType
+ : hasQuantitativeData
+ ? 'quantitative'
+ : 'nominal';
- if (this.chartConfig.binAxisType === 'nominal') {
- this.chartConfig.binAxis = 'y';
- this.chartConfig.countAxis = 'x';
- this.chartConfig.sort = `{
- "op": "count",
- "order": "descending"
- }`;
- this.chartConfig.isBinned = false;
- }
+ this.data = {
+ rows,
+ chartConfig: getHistogramConfig(aggregationType),
+ };
raf.scheduleFullRedraw();
}
diff --git a/ui/src/frontend/charts/histogram/tab.ts b/ui/src/frontend/charts/histogram/tab.ts
index 329e6f6..f732932 100644
--- a/ui/src/frontend/charts/histogram/tab.ts
+++ b/ui/src/frontend/charts/histogram/tab.ts
@@ -19,6 +19,7 @@
import {addBottomTab} from '../../../common/addEphemeralTab';
import {Engine} from '../../../public';
import {DetailsShell} from '../../../widgets/details_shell';
+import {Spinner} from '../../../widgets/spinner';
import {VegaView} from '../../../widgets/vega_view';
import {BottomTab, NewBottomTabArgs} from '../../bottom_tab';
import {Filter, filterTitle} from '../../widgets/sql/table/column';
@@ -31,6 +32,7 @@
filters?: Filter[]; // Filters applied to SQL table
tableDisplay?: string; // Human readable table name (ex: slices)
query: string; // SQL query for the underlying data
+ aggregationType?: 'nominal' | 'quantitative'; // Aggregation type.
}
export function addHistogramTab(
@@ -58,6 +60,7 @@
this.engine,
this.config.query,
this.config.sqlColumn,
+ this.config.aggregationType,
);
}
@@ -66,6 +69,10 @@
}
viewTab() {
+ const data = this.state.data;
+ if (data === undefined) {
+ return m(Spinner);
+ }
return m(
DetailsShell,
{
@@ -80,24 +87,20 @@
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"mark": "bar",
"data": {
- "values": ${
- this.state.data
- ? stringifyJsonWithBigints(this.state.data)
- : []
- }
+ "values": ${stringifyJsonWithBigints(data.rows)}
},
"encoding": {
- "${this.state.chartConfig.binAxis}": {
- "bin": ${this.state.chartConfig.isBinned},
+ "${data.chartConfig.binAxis}": {
+ "bin": ${data.chartConfig.isBinned},
"field": "${this.config.sqlColumn}",
- "type": "${this.state.chartConfig.binAxisType}",
+ "type": "${data.chartConfig.binAxisType}",
"title": "${this.config.columnTitle}",
- "sort": ${this.state.chartConfig.sort},
+ "sort": ${data.chartConfig.sort},
"axis": {
- "labelLimit": ${this.state.chartConfig.labelLimit}
+ "labelLimit": ${data.chartConfig.labelLimit}
}
},
- "${this.state.chartConfig.countAxis}": {
+ "${data.chartConfig.countAxis}": {
"aggregate": "count",
"title": "Count"
}
@@ -112,7 +115,7 @@
getTitle(): string {
return `${this.toTitleCase(this.config.columnTitle)} ${
- this.state.chartConfig.binAxisType === 'quantitative'
+ this.state.data?.chartConfig.binAxisType === 'quantitative'
? 'Histogram'
: 'Counts'
}`;
@@ -140,6 +143,6 @@
}
isLoading(): boolean {
- return this.state.isLoading;
+ return this.state.data === undefined;
}
}
diff --git a/ui/src/frontend/close_track_button.ts b/ui/src/frontend/close_track_button.ts
deleted file mode 100644
index 1b0fd91..0000000
--- a/ui/src/frontend/close_track_button.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size 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 {Icons} from '../base/semantic_icons';
-import {Actions} from '../common/actions';
-
-import {globals} from './globals';
-import {Button} from '../widgets/button';
-
-export interface CloseTrackButtonAttrs {
- trackKey: string;
-}
-
-export class CloseTrackButton
- implements m.ClassComponent<CloseTrackButtonAttrs>
-{
- view({attrs}: m.CVnode<CloseTrackButtonAttrs>) {
- return m(Button, {
- onclick: () => {
- globals.dispatch(Actions.removeTracks({trackKeys: [attrs.trackKey]}));
- },
- icon: Icons.Close,
- title: 'Close',
- compact: true,
- });
- }
-}
diff --git a/ui/src/frontend/cpu_profile_panel.ts b/ui/src/frontend/cpu_profile_panel.ts
deleted file mode 100644
index 0987d50..0000000
--- a/ui/src/frontend/cpu_profile_panel.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size 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 {globals} from './globals';
-import {CallsiteInfo} from '../common/legacy_flamegraph_util';
-
-interface CpuProfileDetailsPanelAttrs {}
-
-export class CpuProfileDetailsPanel
- implements m.ClassComponent<CpuProfileDetailsPanelAttrs>
-{
- view() {
- const sampleDetails = globals.cpuProfileDetails;
- const header = m(
- '.details-panel-heading',
- m('h2', `CPU Profile Sample Details`),
- );
- if (sampleDetails.id === undefined) {
- return m('.details-panel', header);
- }
-
- return m(
- '.details-panel',
- header,
- m('table', this.getStackText(sampleDetails.stack)),
- );
- }
-
- getStackText(stack?: CallsiteInfo[]): m.Vnode[] {
- if (!stack) return [];
-
- const result = [];
- for (let i = stack.length - 1; i >= 0; --i) {
- result.push(m('tr', m('td', stack[i].name), m('td', stack[i].mapping)));
- }
-
- return result;
- }
-}
diff --git a/ui/src/frontend/debug_tracks/counter_track.ts b/ui/src/frontend/debug_tracks/counter_track.ts
index db00e87..a8d55f9 100644
--- a/ui/src/frontend/debug_tracks/counter_track.ts
+++ b/ui/src/frontend/debug_tracks/counter_track.ts
@@ -12,9 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import m from 'mithril';
import {BaseCounterTrack} from '../../frontend/base_counter_track';
import {TrackContext} from '../../public';
import {Engine} from '../../trace_processor/engine';
+import {Button} from '../../widgets/button';
+import {globals} from '../globals';
+import {Icons} from '../../base/semantic_icons';
export class DebugCounterTrack extends BaseCounterTrack {
private readonly sqlTableName: string;
@@ -22,7 +26,7 @@
constructor(engine: Engine, ctx: TrackContext, tableName: string) {
super({
engine,
- trackKey: ctx.trackKey,
+ uri: ctx.trackUri,
});
this.sqlTableName = tableName;
}
@@ -30,4 +34,15 @@
getSqlSource(): string {
return `select * from ${this.sqlTableName}`;
}
+
+ getTrackShellButtons(): m.Children {
+ return m(Button, {
+ onclick: () => {
+ globals.workspace.getTrackByUri(this.uri)?.remove();
+ },
+ icon: Icons.Close,
+ title: 'Close',
+ compact: true,
+ });
+ }
}
diff --git a/ui/src/frontend/debug_tracks/debug_tracks.ts b/ui/src/frontend/debug_tracks/debug_tracks.ts
index a2dbe3b..ee7ef32 100644
--- a/ui/src/frontend/debug_tracks/debug_tracks.ts
+++ b/ui/src/frontend/debug_tracks/debug_tracks.ts
@@ -13,8 +13,6 @@
// limitations under the License.
import {uuidv4, uuidv4Sql} from '../../base/uuid';
-import {Actions, DeferredAction} from '../../common/actions';
-import {PrimaryTrackSortKey, SCROLLING_TRACK_GROUP} from '../../common/state';
import {globals} from '../globals';
import {TrackDescriptor} from '../../public';
import {DebugSliceTrack} from './slice_track';
@@ -26,6 +24,8 @@
import {Engine} from '../../trace_processor/engine';
import {DebugCounterTrack} from './counter_track';
import {ARG_PREFIX} from './details_tab';
+import {TrackNode} from '../../public/workspace';
+import {raf} from '../../core/raf_scheduler';
// We need to add debug tracks from the core and from plugins. In order to add a
// debug track we need to pass a context through with we can add the track. This
@@ -67,26 +67,13 @@
// have an effect. Use this variant if you want to create many tracks at
// once or want to tweak the actions once produced. Otherwise, use
// addDebugSliceTrack().
-function createAddDebugTrackActions(
- trackName: string,
- uri: string,
-): DeferredAction<{}>[] {
+function addDebugTrack(trackName: string, uri: string): void {
const debugTrackId = ++debugTrackCount;
- const trackKey = uuidv4();
-
- const actions: DeferredAction<{}>[] = [
- Actions.addTrack({
- key: trackKey,
- name: trackName.trim() || `Debug Track ${debugTrackId}`,
- uri,
- trackSortKey: PrimaryTrackSortKey.DEBUG_TRACK,
- trackGroup: SCROLLING_TRACK_GROUP,
- closeable: true,
- }),
- Actions.toggleTrackPinned({trackKey}),
- ];
-
- return actions;
+ const displayName = trackName.trim() || `Debug Track ${debugTrackId}`;
+ const track = new TrackNode(uri, displayName);
+ globals.workspace.prependChild(track);
+ track.pin();
+ raf.scheduleFullRedraw();
}
export async function addPivotedTracks(
@@ -152,14 +139,11 @@
ctx.registerTrack({
uri,
title: trackName,
- trackFactory: (trackCtx) => {
- return new DebugSliceTrack(ctx.engine, trackCtx, tableName);
- },
+ track: new DebugSliceTrack(ctx.engine, {trackUri: uri}, tableName),
});
// Create the actions to add this track to the tracklist
- const actions = await createAddDebugTrackActions(trackName, uri);
- globals.dispatchMultiple(actions);
+ addDebugTrack(trackName, uri);
}
function createDebugSliceTrackTableExpr(
@@ -237,14 +221,11 @@
ctx.registerTrack({
uri,
title: trackName,
- trackFactory: (trackCtx) => {
- return new DebugCounterTrack(ctx.engine, trackCtx, tableName);
- },
+ track: new DebugCounterTrack(ctx.engine, {trackUri: uri}, tableName),
});
// Create the actions to add this track to the tracklist
- const actions = await createAddDebugTrackActions(trackName, uri);
- globals.dispatchMultiple(actions);
+ addDebugTrack(trackName, uri);
}
function createDebugCounterTrackTableExpr(
diff --git a/ui/src/frontend/debug_tracks/slice_track.ts b/ui/src/frontend/debug_tracks/slice_track.ts
index 51458b2..86a44b7 100644
--- a/ui/src/frontend/debug_tracks/slice_track.ts
+++ b/ui/src/frontend/debug_tracks/slice_track.ts
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import m from 'mithril';
import {
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
@@ -20,6 +21,9 @@
import {TrackContext} from '../../public';
import {Engine} from '../../trace_processor/engine';
import {DebugSliceDetailsTab} from './details_tab';
+import {Button} from '../../widgets/button';
+import {Icons} from '../../base/semantic_icons';
+import {globals} from '../globals';
export class DebugSliceTrack extends CustomSqlTableSliceTrack {
private readonly sqlTableName: string;
@@ -27,7 +31,7 @@
constructor(engine: Engine, ctx: TrackContext, tableName: string) {
super({
engine,
- trackKey: ctx.trackKey,
+ uri: ctx.trackUri,
});
this.sqlTableName = tableName;
}
@@ -47,4 +51,15 @@
},
};
}
+
+ getTrackShellButtons(): m.Children {
+ return m(Button, {
+ onclick: () => {
+ globals.workspace.getTrackByUri(this.uri)?.remove();
+ },
+ icon: Icons.Close,
+ title: 'Close',
+ compact: true,
+ });
+ }
}
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index 143a53f..d5bef7e 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -75,6 +75,11 @@
return;
}
+ if (err.message.includes('(ERR:ws)')) {
+ showWebsocketConnectionIssue(err.message);
+ return;
+ }
+
// This is only for older version of the UI and for ease of tracking across
// cherry-picks. Newer versions don't have this exception anymore.
if (err.message.includes('State hash does not match')) {
@@ -443,7 +448,11 @@
export function showWebsocketConnectionIssue(message: string): void {
showModal({
title: 'Unable to connect to the device via websocket',
- content: m('div', m('span', message), m('br')),
+ content: m(
+ 'div',
+ m('div', 'trace_processor_shell --httpd is unreachable or crashed.'),
+ m('pre', message),
+ ),
});
}
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index 83e848b..bccbd90 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -65,13 +65,15 @@
}
const flowClickHandler = (sliceId: number, trackId: number) => {
- const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
- if (trackKey) {
+ const track = globals.trackManager.findTrack((td) =>
+ td.tags?.trackIds?.includes(trackId),
+ );
+ if (track) {
globals.setLegacySelection(
{
kind: 'SLICE',
id: sliceId,
- trackKey,
+ trackUri: track.uri,
table: 'slice',
},
{
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index b62efd9..1e8b3d6 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -15,11 +15,12 @@
import {ArrowHeadStyle, drawBezierArrow} from '../base/canvas/bezier_arrow';
import {Size, Vector} from '../base/geom';
import {Optional} from '../base/utils';
-import {SCROLLING_TRACK_GROUP, TrackState} from '../common/state';
+
import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
import {Flow, globals} from './globals';
import {RenderedPanelInfo} from './panel_container';
import {PxSpan, TimeScale} from './time_scale';
+import {TrackNode} from '../public/workspace';
const TRACK_GROUP_CONNECTION_OFFSET = 5;
const TRIANGLE_SIZE = 5;
@@ -63,10 +64,23 @@
// Create indexes for the tracks and groups by key for quick access
const trackPanelsByKey = new Map(
- panels.map((panel) => [panel.panel.trackKey, panel]),
+ panels.map((panel) => [panel.panel.trackUri, panel]),
);
const groupPanelsByKey = new Map(
- panels.map((panel) => [panel.panel.groupKey, panel]),
+ panels.map((panel) => [panel.panel.groupUri, panel]),
+ );
+
+ // Build a track index on trackIds. Note: We need to find the track nodes
+ // specifically here (not just the URIs) because we might need to navigate up
+ // the tree to find containing groups.
+
+ const trackIdToTrack = new Map<number, TrackNode>();
+ globals.workspace.flatTracks.forEach((track) =>
+ globals.trackManager
+ .getTrack(track.uri)
+ ?.tags?.trackIds?.forEach((trackId) =>
+ trackIdToTrack.set(trackId, track),
+ ),
);
const drawFlow = (flow: Flow, hue: number) => {
@@ -122,12 +136,12 @@
depth: number,
x: number,
): Optional<VerticalEdgeOrPoint> => {
- const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
- if (!trackKey) {
+ const track = trackIdToTrack.get(trackId);
+ if (!track) {
return undefined;
}
- const trackPanel = trackPanelsByKey.get(trackKey);
+ const trackPanel = trackPanelsByKey.get(track.uri);
if (trackPanel) {
const trackRect = trackPanel.rect;
const sliceRectRaw = trackPanel.panel.getSliceVerticalBounds?.(depth);
@@ -151,21 +165,15 @@
};
}
} else {
- // If we didn't find a panel for this track, it might inside a group, so
- // try to place the target on the group instead
- const trackState = globals.state.tracks[trackKey] as Optional<TrackState>;
- if (trackState) {
- if (trackState.trackGroup !== SCROLLING_TRACK_GROUP) {
- const groupKey = trackState.trackGroup;
- const groupPanel = groupPanelsByKey.get(groupKey);
- if (groupPanel) {
- return {
- kind: 'point',
- x,
- y: groupPanel.rect.bottom - TRACK_GROUP_CONNECTION_OFFSET,
- };
- }
- }
+ // If we didn't find a track, it might inside a group, so check for the group
+ const group = track.closestVisibleAncestor;
+ const groupPanel = group && groupPanelsByKey.get(group.uri);
+ if (groupPanel) {
+ return {
+ kind: 'point',
+ x,
+ y: groupPanel.rect.bottom - TRACK_GROUP_CONNECTION_OFFSET,
+ };
}
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 94da791..b889596 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -29,7 +29,7 @@
import {EngineConfig, State, getLegacySelection} from '../common/state';
import {TabManager} from '../common/tab_registry';
import {TimestampFormat, timestampFormat} from '../core/timestamp_format';
-import {TrackManager} from '../common/track_cache';
+import {TrackManager} from '../common/track_manager';
import {setPerfHooks} from '../core/perf';
import {raf} from '../core/raf_scheduler';
import {ServiceWorkerController} from './service_worker_controller';
@@ -41,8 +41,6 @@
import {SelectionManager, LegacySelection} from '../core/selection_manager';
import {Optional, exists} from '../base/utils';
import {OmniboxManager} from './omnibox_manager';
-import {CallsiteInfo} from '../common/legacy_flamegraph_util';
-import {LegacyFlamegraphCache} from '../core/legacy_flamegraph_cache';
import {SerializedAppState} from '../common/state_serialization_schema';
import {getServingRoot} from '../base/http_utils';
import {
@@ -53,6 +51,7 @@
import {TraceContext} from './trace_context';
import {Registry} from '../base/registry';
import {SidebarMenuItem} from '../public';
+import {Workspace} from '../public/workspace';
const INSTANT_FOCUS_DURATION = 1n;
const INCOMPLETE_SLICE_DURATION = 30_000n;
@@ -133,13 +132,6 @@
dur?: duration;
}
-export interface CpuProfileDetails {
- id?: number;
- ts?: time;
- utid?: number;
- stack?: CallsiteInfo[];
-}
-
export interface QuantizedLoad {
start: time;
end: time;
@@ -196,6 +188,8 @@
readonly modules: SqlModule[];
}
+const DEFAULT_WORKSPACE_NAME = 'Default Workspace';
+
/**
* Global accessors for state/dispatch in the frontend.
*/
@@ -221,7 +215,6 @@
private _connectedFlows?: Flow[] = undefined;
private _selectedFlows?: Flow[] = undefined;
private _visibleFlowCategories?: Map<string, boolean> = undefined;
- private _cpuProfileDetails?: CpuProfileDetails = undefined;
private _numQueriesQueued = 0;
private _bufferUsage?: number = undefined;
private _recordingLog?: string = undefined;
@@ -233,15 +226,16 @@
private _hideSidebar?: boolean = undefined;
private _cmdManager = new CommandManager();
private _tabManager = new TabManager();
- private _trackManager = new TrackManager(this._store);
+ private _trackManager = new TrackManager();
private _selectionManager = new SelectionManager(this._store);
private _hasFtrace: boolean = false;
private _searchOverviewTrack?: SearchOverviewTrack;
+ readonly workspaces: Workspace[] = [];
+ private _currentWorkspace: Workspace;
omnibox = new OmniboxManager();
- areaFlamegraphCache = new LegacyFlamegraphCache('area');
- scrollToTrackKey?: string | number;
+ scrollToTrackUri?: string;
httpRpcState: HttpRpcState = {connected: false};
showPanningHint = false;
permalinkHash?: string;
@@ -252,6 +246,21 @@
readonly sidebarMenuItems = new Registry<SidebarMenuItem>((m) => m.commandId);
+ get workspace(): Workspace {
+ return this._currentWorkspace;
+ }
+
+ resetWorkspaces(): void {
+ this.workspaces.length = 0;
+ const defaultWorkspace = new Workspace(DEFAULT_WORKSPACE_NAME);
+ this.workspaces.push(defaultWorkspace);
+ this._currentWorkspace = defaultWorkspace;
+ }
+
+ switchWorkspace(workspace: Workspace): void {
+ this._currentWorkspace = workspace;
+ }
+
// This is the app's equivalent of a plugin's onTraceLoad() function.
// TODO(stevegolton): Eventually initialization that should be done on trace
// load should be moved into here, and then we can remove TraceController
@@ -282,6 +291,10 @@
// Alternatively we could decide that we don't want to support switching
// traces at all, in which case we can ignore tear down entirely.
this._searchOverviewTrack = await createSearchOverviewTrack(engine, this);
+
+ // Reset the trackManager - this clears out the cache and any registered
+ // tracks
+ this._trackManager = new TrackManager();
}
// Used for permalink load by trace_controller.ts.
@@ -294,7 +307,7 @@
eventIds: new Float64Array(0),
tses: new BigInt64Array(0),
utids: new Float64Array(0),
- trackKeys: [],
+ trackUris: [],
sources: [],
totalResults: 0,
};
@@ -304,6 +317,10 @@
constructor() {
const {start, end} = defaultTraceContext;
this._timeline = new Timeline(this._store, new TimeSpan(start, end));
+
+ const defaultWorkspace = new Workspace(DEFAULT_WORKSPACE_NAME);
+ this.workspaces.push(defaultWorkspace);
+ this._currentWorkspace = defaultWorkspace;
}
initialize(
@@ -343,7 +360,6 @@
this._selectedFlows = [];
this._visibleFlowCategories = new Map<string, boolean>();
this._threadStateDetails = {};
- this._cpuProfileDetails = {};
this.engines.clear();
this._selectionManager.clear();
}
@@ -474,14 +490,6 @@
this._metricResult = result;
}
- get cpuProfileDetails() {
- return assertExists(this._cpuProfileDetails);
- }
-
- set cpuProfileDetails(click: CpuProfileDetails) {
- this._cpuProfileDetails = assertExists(click);
- }
-
set numQueuedQueries(value: number) {
this._numQueriesQueued = value;
}
@@ -596,11 +604,11 @@
}
selectSingleEvent(
- trackKey: string,
+ trackUri: string,
eventId: number,
args: Partial<LegacySelectionArgs> = {},
): void {
- this._selectionManager.setEvent(trackKey, eventId);
+ this._selectionManager.setEvent(trackUri, eventId);
this.handleSelectionArgs(args);
}
@@ -649,7 +657,7 @@
eventIds: new Float64Array(0),
tses: new BigInt64Array(0),
utids: new Float64Array(0),
- trackKeys: [],
+ trackUris: [],
sources: [],
totalResults: 0,
};
@@ -750,17 +758,15 @@
}
}
} else if (sel.kind === 'single') {
- const uri = globals.state.tracks[sel.trackKey]?.uri;
- if (uri) {
- const bounds = await globals.trackManager
- .resolveTrackInfo(uri)
- ?.getEventBounds?.(sel.eventId);
- if (bounds) {
- return {
- start: bounds.ts,
- end: Time.add(bounds.ts, bounds.dur),
- };
- }
+ const uri = sel.trackUri;
+ const bounds = await globals.trackManager
+ .getTrack(uri)
+ ?.getEventBounds?.(sel.eventId);
+ if (bounds) {
+ return {
+ start: bounds.ts,
+ end: Time.add(bounds.ts, bounds.dur),
+ };
}
return undefined;
}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index e331eee..d61b5a6 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -36,7 +36,7 @@
import {initWasm} from '../trace_processor/wasm_engine_proxy';
import {setScheduleFullRedraw} from '../widgets/raf';
-import {App} from './app';
+import {UiMain} from './ui_main';
import {initCssConstants} from './css_constants';
import {registerDebugGlobals} from './debug';
import {maybeShowErrorDialog} from './error_dialog';
@@ -289,7 +289,7 @@
router.onRouteChanged = routeChange;
raf.domRedraw = () => {
- m.render(document.body, m(App, router.resolve()));
+ m.render(document.body, m(UiMain, router.resolve()));
};
if (
@@ -356,7 +356,7 @@
});
// Force one initial render to get everything in place
- m.render(document.body, m(App, router.resolve()));
+ m.render(document.body, m(UiMain, router.resolve()));
// Initialize plugins, now that we are ready to go
pluginManager.initialize();
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 53d3352..38b7c70 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -96,14 +96,17 @@
for (const flow of globals.connectedFlows) {
if (flow.id === flowId) {
const flowPoint = direction === 'Backward' ? flow.begin : flow.end;
- const trackKeyByTrackId = globals.trackManager.trackKeyByTrackId;
- const trackKey = trackKeyByTrackId.get(flowPoint.trackId);
- if (trackKey) {
+ const track = globals.workspace.flatTracks.find((t) => {
+ return globals.trackManager
+ .getTrack(t.uri)
+ ?.tags?.trackIds?.includes(flowPoint.trackId);
+ });
+ if (track) {
globals.setLegacySelection(
{
kind: 'SLICE',
id: flowPoint.sliceId,
- trackKey,
+ trackUri: track.uri,
table: 'slice',
},
{
@@ -126,7 +129,7 @@
focusHorizontalRange(range.start, range.end);
}
- if (selection.trackKey) {
- verticalScrollToTrack(selection.trackKey, true);
+ if (selection.trackUri) {
+ verticalScrollToTrack(selection.trackUri, true);
}
}
diff --git a/ui/src/frontend/legacy_flamegraph.ts b/ui/src/frontend/legacy_flamegraph.ts
deleted file mode 100644
index caa5e3e..0000000
--- a/ui/src/frontend/legacy_flamegraph.ts
+++ /dev/null
@@ -1,489 +0,0 @@
-// Copyright (C) 2018 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 {CallsiteInfo} from '../common/legacy_flamegraph_util';
-import {searchSegment} from '../base/binary_search';
-import {cropText} from '../base/string_utils';
-
-interface Node {
- width: number;
- x: number;
- nextXForChildren: number;
- size: number;
-}
-
-interface CallsiteInfoWidth {
- callsite: CallsiteInfo;
- width: number;
-}
-
-// Height of one 'row' on the flame chart including 1px of whitespace
-// below the box.
-const NODE_HEIGHT = 18;
-
-export const FLAMEGRAPH_HOVERED_COLOR = 'hsl(224, 45%, 55%)';
-
-export function findRootSize(data: ReadonlyArray<CallsiteInfo>) {
- let totalSize = 0;
- let i = 0;
- while (i < data.length && data[i].depth === 0) {
- totalSize += data[i].totalSize;
- i++;
- }
- return totalSize;
-}
-
-export interface NodeRendering {
- totalSize?: string;
- selfSize?: string;
-}
-
-export class Flamegraph {
- private nodeRendering: NodeRendering = {};
- private flamegraphData: ReadonlyArray<CallsiteInfo>;
- private highlightSomeNodes = false;
- private maxDepth = -1;
- private totalSize = -1;
- // Initialised on first draw() call
- private labelCharWidth = 0;
- private labelFontStyle = '12px Roboto Mono';
- private rolloverFontStyle = '12px Roboto Condensed';
- // Key for the map is depth followed by x coordinate - `depth;x`
- private graphData: Map<string, CallsiteInfoWidth> = new Map();
- private xStartsPerDepth: Map<number, number[]> = new Map();
-
- private hoveredX = -1;
- private hoveredY = -1;
- private hoveredCallsite?: CallsiteInfo;
- private clickedCallsite?: CallsiteInfo;
-
- private startingY = 0;
-
- constructor(flamegraphData: CallsiteInfo[]) {
- this.flamegraphData = flamegraphData;
- this.findMaxDepth();
- }
-
- private findMaxDepth() {
- this.maxDepth = Math.max(
- ...this.flamegraphData.map((value) => value.depth),
- );
- }
-
- // Instead of highlighting the interesting nodes, we actually want to
- // de-emphasize the non-highlighted nodes. Returns true if there
- // are any highlighted nodes in the flamegraph.
- private highlightingExists() {
- this.highlightSomeNodes = this.flamegraphData.some((e) => e.highlighted);
- }
-
- generateColor(
- name: string,
- isGreyedOut = false,
- highlighted: boolean,
- ): string {
- if (isGreyedOut) {
- return '#d9d9d9';
- }
- if (name === 'unknown' || name === 'root') {
- return '#c0c0c0';
- }
- let x = 0;
- for (let i = 0; i < name.length; i += 1) {
- x += name.charCodeAt(i) % 64;
- }
- x = x % 360;
- let l = '76';
- // Make non-highlighted node lighter.
- if (this.highlightSomeNodes && !highlighted) {
- l = '90';
- }
- return `hsl(${x}deg, 45%, ${l}%)`;
- }
-
- // Caller will have to call draw method after updating data to have updated
- // graph.
- updateDataIfChanged(
- nodeRendering: NodeRendering,
- flamegraphData: ReadonlyArray<CallsiteInfo>,
- clickedCallsite?: CallsiteInfo,
- ) {
- this.nodeRendering = nodeRendering;
- this.clickedCallsite = clickedCallsite;
- if (this.flamegraphData === flamegraphData) {
- return;
- }
- this.flamegraphData = flamegraphData;
- this.clickedCallsite = clickedCallsite;
- this.findMaxDepth();
- this.highlightingExists();
- // Finding total size of roots.
- this.totalSize = findRootSize(flamegraphData);
- }
-
- draw(
- ctx: CanvasRenderingContext2D,
- width: number,
- height: number,
- x = 0,
- y = 0,
- unit = 'B',
- ) {
- if (this.flamegraphData === undefined) {
- return;
- }
-
- ctx.font = this.labelFontStyle;
- ctx.textBaseline = 'middle';
- if (this.labelCharWidth === 0) {
- this.labelCharWidth = ctx.measureText('_').width;
- }
-
- this.startingY = y;
-
- // For each node, we use this map to get information about its parent
- // (total size of it, width and where it starts in graph) so we can
- // calculate it's own position in graph.
- const nodesMap = new Map<number, Node>();
- let currentY = y;
- nodesMap.set(-1, {width, nextXForChildren: x, size: this.totalSize, x});
-
- // Initialize data needed for click/hover behavior.
- this.graphData = new Map();
- this.xStartsPerDepth = new Map();
-
- // Draw root node.
- ctx.fillStyle = this.generateColor('root', false, false);
- ctx.fillRect(x, currentY, width, NODE_HEIGHT - 1);
- const text = cropText(
- `root: ${this.displaySize(
- this.totalSize,
- unit,
- unit === 'B' ? 1024 : 1000,
- )}`,
- this.labelCharWidth,
- width - 2,
- );
- ctx.fillStyle = 'black';
- ctx.fillText(text, x + 5, currentY + (NODE_HEIGHT - 1) / 2);
- currentY += NODE_HEIGHT;
-
- // Set style for borders.
- ctx.strokeStyle = 'white';
- ctx.lineWidth = 0.5;
-
- for (let i = 0; i < this.flamegraphData.length; i++) {
- if (currentY > height) {
- break;
- }
- const value = this.flamegraphData[i];
- const parentNode = nodesMap.get(value.parentId);
- if (parentNode === undefined) {
- continue;
- }
-
- const isClicked = this.clickedCallsite !== undefined;
- const isFullWidth =
- isClicked && value.depth <= this.clickedCallsite!.depth;
- const isGreyedOut =
- isClicked && value.depth < this.clickedCallsite!.depth;
-
- const parent = value.parentId;
- const parentSize = parent === -1 ? this.totalSize : parentNode.size;
- // Calculate node's width based on its proportion in parent.
- const width =
- (isFullWidth ? 1 : value.totalSize / parentSize) * parentNode.width;
-
- const currentX = parentNode.nextXForChildren;
- currentY = y + NODE_HEIGHT * (value.depth + 1);
-
- // Draw node.
- const name = this.getCallsiteName(value);
- ctx.fillStyle = this.generateColor(name, isGreyedOut, value.highlighted);
- ctx.fillRect(currentX, currentY, width, NODE_HEIGHT - 1);
-
- // Set current node's data in map for children to use.
- nodesMap.set(value.id, {
- width,
- nextXForChildren: currentX,
- size: value.totalSize,
- x: currentX,
- });
- // Update next x coordinate in parent.
- nodesMap.set(value.parentId, {
- width: parentNode.width,
- nextXForChildren: currentX + width,
- size: parentNode.size,
- x: parentNode.x,
- });
-
- // Draw name.
- const labelPaddingPx = 5;
- const maxLabelWidth = width - labelPaddingPx * 2;
- let text = cropText(name, this.labelCharWidth, maxLabelWidth);
- // If cropped text and the original text are within 20% we keep the
- // original text and just squish it a bit.
- if (text.length * 1.2 > name.length) {
- text = name;
- }
- ctx.fillStyle = 'black';
- ctx.fillText(
- text,
- currentX + labelPaddingPx,
- currentY + (NODE_HEIGHT - 1) / 2,
- maxLabelWidth,
- );
-
- // Draw border on the right of node.
- ctx.beginPath();
- ctx.moveTo(currentX + width, currentY);
- ctx.lineTo(currentX + width, currentY + NODE_HEIGHT);
- ctx.stroke();
-
- // Add this node for recognizing in click/hover.
- // Map graphData contains one callsite which is on that depth and X
- // start. Map xStartsPerDepth for each depth contains all X start
- // coordinates that callsites on that level have.
- this.graphData.set(`${value.depth};${currentX}`, {
- callsite: value,
- width,
- });
- const xStarts = this.xStartsPerDepth.get(value.depth);
- if (xStarts === undefined) {
- this.xStartsPerDepth.set(value.depth, [currentX]);
- } else {
- xStarts.push(currentX);
- }
- }
-
- // Draw the tooltip.
- if (this.hoveredX > -1 && this.hoveredY > -1 && this.hoveredCallsite) {
- // Must set these before measureText below.
- ctx.font = this.rolloverFontStyle;
- ctx.textBaseline = 'top';
-
- // Size in px of the border around the text and the edge of the rollover
- // background.
- const paddingPx = 8;
- // Size in px of the x and y offset between the mouse and the top left
- // corner of the rollover box.
- const offsetPx = 4;
-
- const lines: string[] = [];
-
- let textWidth = this.addToTooltip(
- this.getCallsiteName(this.hoveredCallsite),
- width - paddingPx,
- ctx,
- lines,
- );
- if (this.hoveredCallsite.location != null) {
- textWidth = Math.max(
- textWidth,
- this.addToTooltip(this.hoveredCallsite.location, width, ctx, lines),
- );
- }
- textWidth = Math.max(
- textWidth,
- this.addToTooltip(this.hoveredCallsite.mapping, width, ctx, lines),
- );
-
- if (this.nodeRendering.totalSize !== undefined) {
- const percentage =
- (this.hoveredCallsite.totalSize / this.totalSize) * 100;
- const totalSizeText = `${
- this.nodeRendering.totalSize
- }: ${this.displaySize(
- this.hoveredCallsite.totalSize,
- unit,
- unit === 'B' ? 1024 : 1000,
- )} (${percentage.toFixed(2)}%)`;
- textWidth = Math.max(
- textWidth,
- this.addToTooltip(totalSizeText, width, ctx, lines),
- );
- }
-
- if (
- this.nodeRendering.selfSize !== undefined &&
- this.hoveredCallsite.selfSize > 0
- ) {
- const selfPercentage =
- (this.hoveredCallsite.selfSize / this.totalSize) * 100;
- const selfSizeText = `${
- this.nodeRendering.selfSize
- }: ${this.displaySize(
- this.hoveredCallsite.selfSize,
- unit,
- unit === 'B' ? 1024 : 1000,
- )} (${selfPercentage.toFixed(2)}%)`;
- textWidth = Math.max(
- textWidth,
- this.addToTooltip(selfSizeText, width, ctx, lines),
- );
- }
-
- // Compute a line height as the bounding box height + 50%:
- const heightSample = ctx.measureText(
- 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
- );
- const lineHeight = Math.round(
- heightSample.actualBoundingBoxDescent * 1.5,
- );
-
- const rectWidth = textWidth + 2 * paddingPx;
- const rectHeight = lineHeight * lines.length + 2 * paddingPx;
-
- let rectXStart = this.hoveredX + offsetPx;
- let rectYStart = this.hoveredY + offsetPx;
-
- if (rectXStart + rectWidth > width) {
- rectXStart = width - rectWidth;
- }
-
- if (rectYStart + rectHeight > height) {
- rectYStart = height - rectHeight;
- }
-
- ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
- ctx.fillRect(rectXStart, rectYStart, rectWidth, rectHeight);
- ctx.fillStyle = 'hsl(200, 50%, 40%)';
- ctx.textAlign = 'left';
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- ctx.fillText(
- line,
- rectXStart + paddingPx,
- rectYStart + paddingPx + i * lineHeight,
- );
- }
- }
- }
-
- private addToTooltip(
- text: string,
- width: number,
- ctx: CanvasRenderingContext2D,
- lines: string[],
- ): number {
- const lineSplitter: LineSplitter = splitIfTooBig(
- text,
- width,
- ctx.measureText(text).width,
- );
- lines.push(...lineSplitter.lines);
- return lineSplitter.lineWidth;
- }
-
- private getCallsiteName(value: CallsiteInfo): string {
- return value.name === undefined || value.name === ''
- ? 'unknown'
- : value.name;
- }
-
- private displaySize(totalSize: number, unit: string, step = 1024): string {
- if (unit === '') return totalSize.toLocaleString();
- if (totalSize === 0) return `0 ${unit}`;
- const units = [
- ['', 1],
- ['K', step],
- ['M', Math.pow(step, 2)],
- ['G', Math.pow(step, 3)],
- ];
- let unitsIndex = Math.trunc(Math.log(totalSize) / Math.log(step));
- unitsIndex = unitsIndex > units.length - 1 ? units.length - 1 : unitsIndex;
- const result = totalSize / +units[unitsIndex][1];
- const resultString =
- totalSize % +units[unitsIndex][1] === 0
- ? result.toString()
- : result.toFixed(2);
- return `${resultString} ${units[unitsIndex][0]}${unit}`;
- }
-
- onMouseMove({x, y}: {x: number; y: number}) {
- this.hoveredX = x;
- this.hoveredY = y;
- this.hoveredCallsite = this.findSelectedCallsite(x, y);
- const isCallsiteSelected = this.hoveredCallsite !== undefined;
- if (!isCallsiteSelected) {
- this.onMouseOut();
- }
- return isCallsiteSelected;
- }
-
- onMouseOut() {
- this.hoveredX = -1;
- this.hoveredY = -1;
- this.hoveredCallsite = undefined;
- }
-
- onMouseClick({x, y}: {x: number; y: number}): CallsiteInfo | undefined {
- const clickedCallsite = this.findSelectedCallsite(x, y);
- // TODO(b/148596659): Allow to expand [merged] callsites. Currently,
- // this expands to the biggest of the nodes that were merged, which
- // is confusing, so we disallow clicking on them.
- if (clickedCallsite === undefined || clickedCallsite.merged) {
- return undefined;
- }
- return clickedCallsite;
- }
-
- private findSelectedCallsite(x: number, y: number): CallsiteInfo | undefined {
- const depth = Math.trunc((y - this.startingY) / NODE_HEIGHT) - 1; // at 0 is root
- if (depth >= 0 && this.xStartsPerDepth.has(depth)) {
- const startX = this.searchSmallest(this.xStartsPerDepth.get(depth)!, x);
- const result = this.graphData.get(`${depth};${startX}`);
- if (result !== undefined) {
- const width = result.width;
- return startX + width >= x ? result.callsite : undefined;
- }
- }
- return undefined;
- }
-
- searchSmallest(haystack: number[], needle: number): number {
- haystack = haystack.sort((n1, n2) => n1 - n2);
- const [left] = searchSegment(haystack, needle);
- return left === -1 ? -1 : haystack[left];
- }
-
- getHeight(): number {
- return this.flamegraphData.length === 0
- ? 0
- : (this.maxDepth + 2) * NODE_HEIGHT;
- }
-}
-
-export interface LineSplitter {
- lineWidth: number;
- lines: string[];
-}
-
-export function splitIfTooBig(
- line: string,
- width: number,
- lineWidth: number,
-): LineSplitter {
- if (line === '') return {lineWidth, lines: []};
- const lines: string[] = [];
- const charWidth = lineWidth / line.length;
- const maxWidth = width - 32;
- const maxLineLen = Math.trunc(maxWidth / charWidth);
- while (line.length > 0) {
- lines.push(line.slice(0, maxLineLen));
- line = line.slice(maxLineLen);
- }
- lineWidth = Math.min(maxLineLen * charWidth, lineWidth);
- return {lineWidth, lines};
-}
diff --git a/ui/src/frontend/legacy_flamegraph_panel.ts b/ui/src/frontend/legacy_flamegraph_panel.ts
deleted file mode 100644
index 9a476b8..0000000
--- a/ui/src/frontend/legacy_flamegraph_panel.ts
+++ /dev/null
@@ -1,902 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size 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, {Vnode} from 'mithril';
-
-import {findRef} from '../base/dom_utils';
-import {assertExists, assertTrue} from '../base/logging';
-import {time} from '../base/time';
-import {Actions} from '../common/actions';
-import {
- CallsiteInfo,
- FlamegraphViewingOption,
- defaultViewingOption,
- expandCallsites,
- findRootSize,
- mergeCallsites,
- viewingOptions,
-} from '../common/legacy_flamegraph_util';
-import {ProfileType} from '../common/state';
-import {raf} from '../core/raf_scheduler';
-import {Button} from '../widgets/button';
-import {Icon} from '../widgets/icon';
-import {Modal, ModalAttrs} from '../widgets/modal';
-import {Popup} from '../widgets/popup';
-import {EmptyState} from '../widgets/empty_state';
-import {Spinner} from '../widgets/spinner';
-
-import {Flamegraph, NodeRendering} from './legacy_flamegraph';
-import {globals} from './globals';
-import {debounce} from './rate_limiters';
-import {Router} from './router';
-import {ButtonBar} from '../widgets/button';
-import {DurationWidget} from './widgets/duration';
-import {DetailsShell} from '../widgets/details_shell';
-import {Intent} from '../widgets/common';
-import {Engine, NUM, STR} from '../public';
-import {Monitor} from '../base/monitor';
-import {arrayEquals} from '../base/array_utils';
-import {getCurrentTrace} from './sidebar';
-import {convertTraceToPprofAndDownload} from './trace_converter';
-import {AsyncLimiter} from '../base/async_limiter';
-import {LegacyFlamegraphCache} from '../core/legacy_flamegraph_cache';
-
-const HEADER_HEIGHT = 30;
-
-export function profileType(s: string): ProfileType {
- if (isProfileType(s)) {
- return s;
- }
- if (s.startsWith('heap_profile')) {
- return ProfileType.HEAP_PROFILE;
- }
- throw new Error('Unknown type ${s}');
-}
-
-function isProfileType(s: string): s is ProfileType {
- return Object.values(ProfileType).includes(s as ProfileType);
-}
-
-function getFlamegraphType(type: ProfileType) {
- switch (type) {
- case ProfileType.HEAP_PROFILE:
- case ProfileType.MIXED_HEAP_PROFILE:
- case ProfileType.NATIVE_HEAP_PROFILE:
- case ProfileType.JAVA_HEAP_SAMPLES:
- return 'native';
- case ProfileType.JAVA_HEAP_GRAPH:
- return 'graph';
- case ProfileType.PERF_SAMPLE:
- return 'perf';
- default:
- const exhaustiveCheck: never = type;
- throw new Error(`Unhandled case: ${exhaustiveCheck}`);
- }
-}
-
-const HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS = [
- FlamegraphViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
- FlamegraphViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
-] as const;
-
-export type HeapGraphDominatorTreeViewingOption =
- (typeof HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS)[number];
-
-export function isHeapGraphDominatorTreeViewingOption(
- option: FlamegraphViewingOption,
-): option is HeapGraphDominatorTreeViewingOption {
- return (
- HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS as readonly FlamegraphViewingOption[]
- ).includes(option);
-}
-
-const MIN_PIXEL_DISPLAYED = 1;
-
-function toSelectedCallsite(c: CallsiteInfo | undefined): string {
- if (c !== undefined && c.name !== undefined) {
- return c.name;
- }
- return '(none)';
-}
-
-const RENDER_SELF_AND_TOTAL: NodeRendering = {
- selfSize: 'Self',
- totalSize: 'Total',
-};
-const RENDER_OBJ_COUNT: NodeRendering = {
- selfSize: 'Self objects',
- totalSize: 'Subtree objects',
-};
-
-export interface FlamegraphSelectionParams {
- readonly profileType: ProfileType;
- readonly upids: number[];
- readonly start: time;
- readonly end: time;
-}
-
-interface FlamegraphDetailsPanelAttrs {
- cache: LegacyFlamegraphCache;
- selection: FlamegraphSelectionParams;
-}
-
-interface FlamegraphResult {
- queryResults: ReadonlyArray<CallsiteInfo>;
- incomplete: boolean;
- renderResults?: ReadonlyArray<CallsiteInfo>;
-}
-
-interface FlamegraphState {
- selection: FlamegraphSelectionParams;
- viewingOption: FlamegraphViewingOption;
- focusRegex: string;
- result?: FlamegraphResult;
- selectedCallsites: Readonly<{
- [key: string]: CallsiteInfo | undefined;
- }>;
-}
-
-export class LegacyFlamegraphDetailsPanel
- implements m.ClassComponent<FlamegraphDetailsPanelAttrs>
-{
- private undebouncedFocusRegex = '';
- private updateFocusRegexDebounced = debounce(() => {
- if (this.state === undefined) {
- return;
- }
- this.state.focusRegex = this.undebouncedFocusRegex;
- raf.scheduleFullRedraw();
- }, 20);
-
- private flamegraph: Flamegraph = new Flamegraph([]);
- private queryLimiter = new AsyncLimiter();
-
- private state?: FlamegraphState;
- private queryMonitor = new Monitor([
- () => this.state?.selection,
- () => this.state?.focusRegex,
- () => this.state?.viewingOption,
- ]);
- private selectedCallsitesMonitor = new Monitor([
- () => this.state?.selection,
- () => this.state?.focusRegex,
- ]);
- private renderResultMonitor = new Monitor([
- () => this.state?.result?.queryResults,
- () => this.state?.selectedCallsites,
- ]);
-
- view({attrs}: Vnode<FlamegraphDetailsPanelAttrs>) {
- if (attrs.selection === undefined) {
- this.state = undefined;
- } else if (
- attrs.selection.profileType !== this.state?.selection.profileType ||
- attrs.selection.start !== this.state.selection.start ||
- attrs.selection.end !== this.state.selection.end ||
- !arrayEquals(attrs.selection.upids, this.state.selection.upids)
- ) {
- this.state = {
- selection: attrs.selection,
- focusRegex: '',
- viewingOption: defaultViewingOption(attrs.selection.profileType),
- selectedCallsites: {},
- };
- }
- if (this.state === undefined) {
- return m(
- '.details-panel',
- m('.details-panel-heading', m('h2', `Flamegraph Profile`)),
- );
- }
-
- if (this.queryMonitor.ifStateChanged()) {
- this.state.result = undefined;
- const state = this.state;
- this.queryLimiter.schedule(() => {
- return LegacyFlamegraphDetailsPanel.fetchQueryResults(
- assertExists(this.getCurrentEngine()),
- attrs.cache,
- state,
- );
- });
- }
-
- if (this.selectedCallsitesMonitor.ifStateChanged()) {
- this.state.selectedCallsites = {};
- }
-
- if (
- this.renderResultMonitor.ifStateChanged() &&
- this.state.result !== undefined
- ) {
- const selected = this.state.selectedCallsites[this.state.viewingOption];
- const expanded = expandCallsites(
- this.state.result.queryResults,
- selected?.id ?? -1,
- );
- this.state.result.renderResults = mergeCallsites(
- expanded,
- LegacyFlamegraphDetailsPanel.getMinSizeDisplayed(
- expanded,
- selected?.totalSize,
- ),
- );
- }
-
- let height: number | undefined;
- if (this.state.result?.renderResults !== undefined) {
- this.flamegraph.updateDataIfChanged(
- this.nodeRendering(),
- this.state.result.renderResults,
- this.state.selectedCallsites[this.state.viewingOption],
- );
- height = this.flamegraph.getHeight() + HEADER_HEIGHT;
- } else {
- height = undefined;
- }
-
- return m(
- '.flamegraph-profile',
- this.maybeShowModal(),
- m(
- DetailsShell,
- {
- fillParent: true,
- title: m(
- 'div.title',
- this.getTitle(),
- this.state.selection.profileType ===
- ProfileType.MIXED_HEAP_PROFILE &&
- m(
- Popup,
- {
- trigger: m(Icon, {icon: 'warning'}),
- },
- m(
- '',
- {style: {width: '300px'}},
- 'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
- ),
- ),
- ':',
- ),
- description: this.getViewingOptionButtons(),
- buttons: [
- m(
- 'div.selected',
- `Selected function: ${toSelectedCallsite(
- this.state.selectedCallsites[this.state.viewingOption],
- )}`,
- ),
- m(
- 'div.time',
- `Snapshot time: `,
- m(DurationWidget, {
- dur: this.state.selection.end - this.state.selection.start,
- }),
- ),
- m('input[type=text][placeholder=Focus]', {
- oninput: (e: Event) => {
- const target = e.target as HTMLInputElement;
- this.undebouncedFocusRegex = target.value;
- this.updateFocusRegexDebounced();
- },
- // Required to stop hot-key handling:
- onkeydown: (e: Event) => e.stopPropagation(),
- }),
- (this.state.selection.profileType ===
- ProfileType.NATIVE_HEAP_PROFILE ||
- this.state.selection.profileType ===
- ProfileType.JAVA_HEAP_SAMPLES) &&
- m(Button, {
- icon: 'file_download',
- intent: Intent.Primary,
- onclick: () => {
- this.downloadPprof();
- raf.scheduleFullRedraw();
- },
- }),
- ],
- },
- m(
- '.flamegraph-content',
- this.state.result === undefined
- ? m(
- '.loading-container',
- m(
- EmptyState,
- {
- icon: 'bar_chart',
- title: 'Computing graph ...',
- className: 'flamegraph-loading',
- },
- m(Spinner, {easing: true}),
- ),
- )
- : m(`canvas[ref=canvas]`, {
- style: `height:${height}px; width:100%`,
- onmousemove: (e: MouseEvent) => {
- const {offsetX, offsetY} = e;
- this.flamegraph.onMouseMove({x: offsetX, y: offsetY});
- raf.scheduleFullRedraw();
- },
- onmouseout: () => {
- this.flamegraph.onMouseOut();
- raf.scheduleFullRedraw();
- },
- onclick: (e: MouseEvent) => {
- if (
- this.state === undefined ||
- this.state.result === undefined
- ) {
- return;
- }
- const {offsetX, offsetY} = e;
- const cs = {...this.state.selectedCallsites};
- cs[this.state.viewingOption] = this.flamegraph.onMouseClick({
- x: offsetX,
- y: offsetY,
- });
- this.state.selectedCallsites = cs;
- raf.scheduleFullRedraw();
- },
- }),
- ),
- ),
- );
- }
-
- private getTitle(): string {
- const state = assertExists(this.state);
- switch (state.selection.profileType) {
- case ProfileType.MIXED_HEAP_PROFILE:
- return 'Mixed heap profile';
- case ProfileType.HEAP_PROFILE:
- return 'Heap profile';
- case ProfileType.NATIVE_HEAP_PROFILE:
- return 'Native heap profile';
- case ProfileType.JAVA_HEAP_SAMPLES:
- return 'Java heap samples';
- case ProfileType.JAVA_HEAP_GRAPH:
- return 'Java heap graph';
- case ProfileType.PERF_SAMPLE:
- return 'Profile';
- default:
- throw new Error('unknown type');
- }
- }
-
- private nodeRendering(): NodeRendering {
- const state = assertExists(this.state);
- const profileType = state.selection.profileType;
- switch (profileType) {
- case ProfileType.JAVA_HEAP_GRAPH:
- if (
- state.viewingOption ===
- FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY ||
- state.viewingOption ===
- FlamegraphViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY
- ) {
- return RENDER_OBJ_COUNT;
- } else {
- return RENDER_SELF_AND_TOTAL;
- }
- case ProfileType.MIXED_HEAP_PROFILE:
- case ProfileType.HEAP_PROFILE:
- case ProfileType.NATIVE_HEAP_PROFILE:
- case ProfileType.JAVA_HEAP_SAMPLES:
- case ProfileType.PERF_SAMPLE:
- return RENDER_SELF_AND_TOTAL;
- default:
- const exhaustiveCheck: never = profileType;
- throw new Error(`Unhandled case: ${exhaustiveCheck}`);
- }
- }
-
- private getViewingOptionButtons(): m.Children {
- const ret = [];
- const state = assertExists(this.state);
- for (const {option, name} of viewingOptions(state.selection.profileType)) {
- ret.push(
- m(Button, {
- label: name,
- active: option === state.viewingOption,
- onclick: () => {
- const state = assertExists(this.state);
- state.viewingOption = option;
- raf.scheduleFullRedraw();
- },
- }),
- );
- }
- return m(ButtonBar, ret);
- }
-
- onupdate({dom}: m.VnodeDOM<FlamegraphDetailsPanelAttrs>) {
- const canvas = findRef(dom, 'canvas');
- if (canvas === null || !(canvas instanceof HTMLCanvasElement)) {
- return;
- }
- if (!this.state?.result?.renderResults) {
- return;
- }
- canvas.width = canvas.offsetWidth * devicePixelRatio;
- canvas.height = canvas.offsetHeight * devicePixelRatio;
-
- const ctx = canvas.getContext('2d');
- if (ctx === null) {
- return;
- }
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.save();
- ctx.scale(devicePixelRatio, devicePixelRatio);
- const {offsetWidth: width, offsetHeight: height} = canvas;
- const unit =
- this.state.viewingOption ===
- FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY ||
- this.state.viewingOption ===
- FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY ||
- this.state.viewingOption ===
- FlamegraphViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY
- ? 'B'
- : '';
- this.flamegraph.draw(ctx, width, height, 0, 0, unit);
- ctx.restore();
- }
-
- private static async fetchQueryResults(
- engine: Engine,
- cache: LegacyFlamegraphCache,
- state: FlamegraphState,
- ) {
- const table = await LegacyFlamegraphDetailsPanel.prepareViewsAndTables(
- engine,
- cache,
- state,
- );
- const queryResults =
- await LegacyFlamegraphDetailsPanel.getFlamegraphDataFromTables(
- engine,
- table,
- state.viewingOption,
- state.focusRegex,
- );
-
- let incomplete = false;
- if (state.selection.profileType === ProfileType.JAVA_HEAP_GRAPH) {
- const it = await engine.query(`
- select value from stats
- where severity = 'error' and name = 'heap_graph_non_finalized_graph'
- `);
- incomplete = it.firstRow({value: NUM}).value > 0;
- }
- state.result = {
- queryResults,
- incomplete,
- };
- raf.scheduleFullRedraw();
- }
-
- private static async prepareViewsAndTables(
- engine: Engine,
- cache: LegacyFlamegraphCache,
- state: FlamegraphState,
- ): Promise<string> {
- const flamegraphType = getFlamegraphType(state.selection.profileType);
- if (state.selection.profileType === ProfileType.PERF_SAMPLE) {
- let upid: string;
- let upidGroup: string;
- if (state.selection.upids.length > 1) {
- upid = `NULL`;
- upidGroup = `'${this.serializeUpidGroup(state.selection.upids)}'`;
- } else {
- upid = `${state.selection.upids[0]}`;
- upidGroup = `NULL`;
- }
- return cache.getTableName(
- engine,
- `
- select
- id,
- name,
- map_name,
- parent_id,
- depth,
- cumulative_size,
- cumulative_alloc_size,
- cumulative_count,
- cumulative_alloc_count,
- size,
- alloc_size,
- count,
- alloc_count,
- source_file,
- line_number
- from experimental_flamegraph(
- '${flamegraphType}',
- NULL,
- '>=${state.selection.start},<=${state.selection.end}',
- ${upid},
- ${upidGroup},
- '${state.focusRegex}'
- )
- `,
- );
- }
- if (
- state.selection.profileType === ProfileType.JAVA_HEAP_GRAPH &&
- isHeapGraphDominatorTreeViewingOption(state.viewingOption)
- ) {
- assertTrue(state.selection.start == state.selection.end);
- return cache.getTableName(
- engine,
- await this.loadHeapGraphDominatorTreeQuery(
- engine,
- cache,
- state.selection.upids[0],
- state.selection.start,
- ),
- );
- }
- assertTrue(state.selection.start == state.selection.end);
- return cache.getTableName(
- engine,
- `
- select
- id,
- name,
- map_name,
- parent_id,
- depth,
- cumulative_size,
- cumulative_alloc_size,
- cumulative_count,
- cumulative_alloc_count,
- size,
- alloc_size,
- count,
- alloc_count,
- source_file,
- line_number
- from experimental_flamegraph(
- '${flamegraphType}',
- ${state.selection.start},
- NULL,
- ${state.selection.upids[0]},
- NULL,
- '${state.focusRegex}'
- )
- `,
- );
- }
-
- private static async loadHeapGraphDominatorTreeQuery(
- engine: Engine,
- cache: LegacyFlamegraphCache,
- upid: number,
- timestamp: time,
- ) {
- const outputTableName = `heap_graph_type_dominated_${upid}_${timestamp}`;
- const outputQuery = `SELECT * FROM ${outputTableName}`;
- if (cache.hasQuery(outputQuery)) {
- return outputQuery;
- }
-
- await engine.query(`
- INCLUDE PERFETTO MODULE android.memory.heap_graph.dominator_tree;
-
- -- heap graph dominator tree with objects as nodes and all relavant
- -- object self stats and dominated stats
- CREATE PERFETTO TABLE _heap_graph_object_dominated AS
- SELECT
- node.id,
- node.idom_id,
- node.dominated_obj_count,
- node.dominated_size_bytes + node.dominated_native_size_bytes AS dominated_size,
- node.depth,
- obj.type_id,
- obj.root_type,
- obj.self_size + obj.native_size AS self_size
- FROM heap_graph_dominator_tree node
- JOIN heap_graph_object obj USING(id)
- WHERE obj.upid = ${upid} AND obj.graph_sample_ts = ${timestamp}
- -- required to accelerate the recursive cte below
- ORDER BY idom_id;
-
- -- calculate for each object node in the dominator tree the
- -- HASH(path of type_id's from the super root to the object)
- CREATE PERFETTO TABLE _dominator_tree_path_hash AS
- WITH RECURSIVE _tree_visitor(id, path_hash) AS (
- SELECT
- id,
- HASH(
- CAST(type_id AS TEXT) || '-' || IFNULL(root_type, '')
- ) AS path_hash
- FROM _heap_graph_object_dominated
- WHERE depth = 1
- UNION ALL
- SELECT
- child.id,
- HASH(CAST(parent.path_hash AS TEXT) || '/' || CAST(type_id AS TEXT)) AS path_hash
- FROM _heap_graph_object_dominated child
- JOIN _tree_visitor parent ON child.idom_id = parent.id
- )
- SELECT * from _tree_visitor
- ORDER BY id;
-
- -- merge object nodes with the same path into one "class type node", so the
- -- end result is a tree where nodes are identified by their types and the
- -- dominator relationships are preserved.
- CREATE PERFETTO TABLE ${outputTableName} AS
- SELECT
- map.path_hash as id,
- COALESCE(cls.deobfuscated_name, cls.name, '[NULL]') || IIF(
- node.root_type IS NOT NULL,
- ' [' || node.root_type || ']', ''
- ) AS name,
- IFNULL(parent_map.path_hash, -1) AS parent_id,
- node.depth - 1 AS depth,
- sum(dominated_size) AS cumulative_size,
- -1 AS cumulative_alloc_size,
- sum(dominated_obj_count) AS cumulative_count,
- -1 AS cumulative_alloc_count,
- '' as map_name,
- '' as source_file,
- -1 as line_number,
- sum(self_size) AS size,
- count(*) AS count
- FROM _heap_graph_object_dominated node
- JOIN _dominator_tree_path_hash map USING(id)
- LEFT JOIN _dominator_tree_path_hash parent_map ON node.idom_id = parent_map.id
- JOIN heap_graph_class cls ON node.type_id = cls.id
- GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number;
-
- -- These are intermediates and not needed
- DROP TABLE _heap_graph_object_dominated;
- DROP TABLE _dominator_tree_path_hash;
- `);
-
- return outputQuery;
- }
-
- private static async getFlamegraphDataFromTables(
- engine: Engine,
- tableName: string,
- viewingOption: FlamegraphViewingOption,
- focusRegex: string,
- ) {
- let orderBy = '';
- let totalColumnName:
- | 'cumulativeSize'
- | 'cumulativeAllocSize'
- | 'cumulativeCount'
- | 'cumulativeAllocCount' = 'cumulativeSize';
- let selfColumnName: 'size' | 'count' = 'size';
- // TODO(fmayer): Improve performance so this is no longer necessary.
- // Alternatively consider collapsing frames of the same label.
- const maxDepth = 100;
- switch (viewingOption) {
- case FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY:
- orderBy = `where cumulative_alloc_size > 0 and depth < ${maxDepth} order by depth, parent_id,
- cumulative_alloc_size desc, name`;
- totalColumnName = 'cumulativeAllocSize';
- selfColumnName = 'size';
- break;
- case FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY:
- orderBy = `where cumulative_count > 0 and depth < ${maxDepth} order by depth, parent_id,
- cumulative_count desc, name`;
- totalColumnName = 'cumulativeCount';
- selfColumnName = 'count';
- break;
- case FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY:
- orderBy = `where cumulative_alloc_count > 0 and depth < ${maxDepth} order by depth, parent_id,
- cumulative_alloc_count desc, name`;
- totalColumnName = 'cumulativeAllocCount';
- selfColumnName = 'count';
- break;
- case FlamegraphViewingOption.PERF_SAMPLES_KEY:
- case FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY:
- orderBy = `where cumulative_size > 0 and depth < ${maxDepth} order by depth, parent_id,
- cumulative_size desc, name`;
- totalColumnName = 'cumulativeSize';
- selfColumnName = 'size';
- break;
- case FlamegraphViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY:
- orderBy = `where depth < ${maxDepth} order by depth,
- cumulativeCount desc, name`;
- totalColumnName = 'cumulativeCount';
- selfColumnName = 'count';
- break;
- case FlamegraphViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY:
- orderBy = `where depth < ${maxDepth} order by depth,
- cumulativeSize desc, name`;
- totalColumnName = 'cumulativeSize';
- selfColumnName = 'size';
- break;
- default:
- const exhaustiveCheck: never = viewingOption;
- throw new Error(`Unhandled case: ${exhaustiveCheck}`);
- break;
- }
-
- const callsites = await engine.query(`
- SELECT
- id as hash,
- IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name,
- IFNULL(parent_id, -1) as parentHash,
- depth,
- cumulative_size as cumulativeSize,
- cumulative_alloc_size as cumulativeAllocSize,
- cumulative_count as cumulativeCount,
- cumulative_alloc_count as cumulativeAllocCount,
- map_name as mapping,
- size,
- count,
- IFNULL(source_file, '') as sourceFile,
- IFNULL(line_number, -1) as lineNumber
- from ${tableName}
- ${orderBy}
- `);
-
- const flamegraphData: CallsiteInfo[] = [];
- const hashToindex: Map<number, number> = new Map();
- const it = callsites.iter({
- hash: NUM,
- name: STR,
- parentHash: NUM,
- depth: NUM,
- cumulativeSize: NUM,
- cumulativeAllocSize: NUM,
- cumulativeCount: NUM,
- cumulativeAllocCount: NUM,
- mapping: STR,
- sourceFile: STR,
- lineNumber: NUM,
- size: NUM,
- count: NUM,
- });
- for (let i = 0; it.valid(); ++i, it.next()) {
- const hash = it.hash;
- let name = it.name;
- const parentHash = it.parentHash;
- const depth = it.depth;
- const totalSize = it[totalColumnName];
- const selfSize = it[selfColumnName];
- const mapping = it.mapping;
- const highlighted =
- focusRegex !== '' &&
- name.toLocaleLowerCase().includes(focusRegex.toLocaleLowerCase());
- const parentId = hashToindex.has(+parentHash)
- ? hashToindex.get(+parentHash)!
- : -1;
-
- let location: string | undefined;
- if (/[a-zA-Z]/i.test(it.sourceFile)) {
- location = it.sourceFile;
- if (it.lineNumber !== -1) {
- location += `:${it.lineNumber}`;
- }
- }
-
- if (depth === maxDepth - 1) {
- name += ' [tree truncated]';
- }
- // Instead of hash, we will store index of callsite in this original array
- // as an id of callsite. That way, we have quicker access to parent and it
- // will stay unique:
- hashToindex.set(hash, i);
-
- flamegraphData.push({
- id: i,
- totalSize,
- depth,
- parentId,
- name,
- selfSize,
- mapping,
- merged: false,
- highlighted,
- location,
- });
- }
- return flamegraphData;
- }
-
- private async downloadPprof() {
- if (this.state === undefined) {
- return;
- }
- const engine = this.getCurrentEngine();
- if (engine === undefined) {
- return;
- }
- try {
- assertTrue(
- this.state.selection.upids.length === 1,
- 'Native profiles can only contain one pid.',
- );
- const pid = await engine.query(
- `select pid from process where upid = ${this.state.selection.upids[0]}`,
- );
- const trace = await getCurrentTrace();
- convertTraceToPprofAndDownload(
- trace,
- pid.firstRow({pid: NUM}).pid,
- this.state.selection.start,
- );
- } catch (error) {
- throw new Error(`Failed to get current trace ${error}`);
- }
- }
-
- private maybeShowModal() {
- const state = assertExists(this.state);
- if (state.result?.incomplete === undefined || !state.result.incomplete) {
- return undefined;
- }
- if (globals.state.flamegraphModalDismissed) {
- return undefined;
- }
- return m(Modal, {
- title: 'The flamegraph is incomplete',
- vAlign: 'TOP',
- content: m(
- 'div',
- 'The current trace does not have a fully formed flamegraph',
- ),
- buttons: [
- {
- text: 'Show the errors',
- primary: true,
- action: () => Router.navigate('#!/info'),
- },
- {
- text: 'Skip',
- action: () => {
- globals.dispatch(Actions.dismissFlamegraphModal({}));
- raf.scheduleFullRedraw();
- },
- },
- ],
- } as ModalAttrs);
- }
-
- private static getMinSizeDisplayed(
- flamegraphData: ReadonlyArray<CallsiteInfo>,
- rootSize?: number,
- ): number {
- // Note: This is a hack. Really we should obtain the size of the canvas and
- // use that to determine the number of buckets to display, but this code is
- // legacy and going away soon, and the calculation before was just plain
- // wrong anyway so this isn't really any worse.
- //
- // 800 buckets is a decent placeholder until the new flamegraph code lands.
- const bucketCount = 800;
- if (rootSize === undefined) {
- rootSize = findRootSize(flamegraphData);
- }
- return (MIN_PIXEL_DISPLAYED * rootSize) / bucketCount;
- }
-
- private static serializeUpidGroup(upids: number[]) {
- return new Array(upids).join();
- }
-
- private getCurrentEngine() {
- const engineId = globals.getCurrentEngine()?.id;
- if (engineId === undefined) return undefined;
- return globals.engines.get(engineId);
- }
-}
diff --git a/ui/src/frontend/legacy_flamegraph_unittest.ts b/ui/src/frontend/legacy_flamegraph_unittest.ts
deleted file mode 100644
index ddb006a..0000000
--- a/ui/src/frontend/legacy_flamegraph_unittest.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2019 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 {splitIfTooBig} from './legacy_flamegraph';
-
-test('textGoingToMultipleLines', () => {
- const text = 'Dummy text to go to multiple lines.';
-
- const lineSplitter = splitIfTooBig(text, 7 + 32, text.length);
-
- expect(lineSplitter).toEqual({
- lines: ['Dummy t', 'ext to ', 'go to m', 'ultiple', ' lines.'],
- lineWidth: 7,
- });
-});
-
-test('emptyText', () => {
- const text = '';
-
- const lineSplitter = splitIfTooBig(text, 10, 5);
-
- expect(lineSplitter).toEqual({lines: [], lineWidth: 5});
-});
-
-test('textEnoughForOneLine', () => {
- const text = 'Dummy text to go to one lines.';
-
- const lineSplitter = splitIfTooBig(text, text.length + 32, text.length);
-
- expect(lineSplitter).toEqual({lines: [text], lineWidth: text.length});
-});
-
-test('textGoingToTwoLines', () => {
- const text = 'Dummy text to go to two lines.';
-
- const lineSplitter = splitIfTooBig(text, text.length / 2 + 32, text.length);
-
- expect(lineSplitter).toEqual({
- lines: ['Dummy text to g', 'o to two lines.'],
- lineWidth: text.length / 2,
- });
-});
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index 24fa2cf..34220b8 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -72,7 +72,7 @@
{
kind: 'SLICE',
id: args.slice.id,
- trackKey: this.trackKey,
+ trackUri: this.uri,
table: 'slice',
},
{
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index a9ab565..d4ad50a 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -65,9 +65,7 @@
private hoveredX: null | number = null;
render(): m.Children {
- const allCollapsed = Object.values(globals.state.trackGroups).every(
- (group) => group.collapsed,
- );
+ const allCollapsed = globals.workspace.flatGroups.every((n) => n.collapsed);
return m(
'.notes-panel',
@@ -114,7 +112,10 @@
m(Button, {
onclick: (e: Event) => {
e.preventDefault();
- globals.dispatch(Actions.clearAllPinnedTracks({}));
+ globals.workspace.pinnedTracks.forEach((t) =>
+ globals.workspace.unpinTrack(t),
+ );
+ raf.scheduleFullRedraw();
},
title: 'Clear all pinned tracks',
icon: 'clear_all',
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 7537f85..5c2ab81 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -46,8 +46,8 @@
readonly kind: 'panel';
render(): m.Children;
readonly selectable: boolean;
- readonly trackKey?: string; // Defined if this panel represents are track
- readonly groupKey?: string; // Defined if this panel represents a group - i.e. a group summary track
+ readonly trackUri?: string; // Defined if this panel represents are track
+ readonly groupUri?: string; // Defined if this panel represents a group - i.e. a group summary track
renderCanvas(ctx: CanvasRenderingContext2D, size: Size): void;
getSliceVerticalBounds?(depth: number): Optional<VerticalBounds>;
}
@@ -55,8 +55,8 @@
export interface PanelGroup {
readonly kind: 'group';
readonly collapsed: boolean;
- readonly header: Panel;
- readonly childPanels: Panel[];
+ readonly header?: Panel;
+ readonly childPanels: PanelOrGroup[];
}
export type PanelOrGroup = Panel | PanelGroup;
@@ -189,17 +189,19 @@
// Get the track ids from the panels.
const tracks = [];
for (const panel of panels) {
- if (panel.trackKey !== undefined) {
- tracks.push(panel.trackKey);
+ if (panel.trackUri !== undefined) {
+ tracks.push(panel.trackUri);
continue;
}
- if (panel.groupKey !== undefined) {
- const trackGroup = globals.state.trackGroups[panel.groupKey];
+ if (panel.groupUri !== undefined) {
+ const trackGroup = globals.workspace.flatGroups.find(
+ (g) => g.uri === panel.groupUri,
+ );
// Only select a track group and all child tracks if it is closed.
- if (trackGroup.collapsed) {
- tracks.push(panel.groupKey);
- for (const track of trackGroup.tracks) {
- tracks.push(track);
+ if (trackGroup && trackGroup.collapsed) {
+ tracks.push(panel.groupUri);
+ for (const track of trackGroup.flatTracks) {
+ tracks.push(track.uri);
}
}
}
@@ -289,11 +291,12 @@
if (node.kind === 'group') {
return m(
'div.pf-panel-group',
- this.renderPanel(
- node.header,
- `${panelId}-header`,
- node.collapsed ? '' : '.pf-sticky',
- ),
+ node.header &&
+ this.renderPanel(
+ node.header,
+ `${panelId}-header`,
+ node.collapsed ? '' : '.pf-sticky',
+ ),
...node.childPanels.map((child, index) =>
this.renderTree(child, `${panelId}-${index}`),
),
@@ -338,7 +341,7 @@
const panel = assertExists(this.panelById.get(panelId));
// NOTE: the id can be undefined for singletons like overview timeline.
- const key = panel.trackKey || panel.groupKey || '';
+ const key = panel.trackUri || panel.groupUri || '';
const rect = panelElement.getBoundingClientRect();
this.panelInfos.push({
trackOrGroupKey: key,
@@ -456,14 +459,14 @@
) {
return;
}
- if (this.panelInfos.length === 0 || area.tracks.length === 0) return;
+ if (this.panelInfos.length === 0 || area.trackUris.length === 0) return;
// Find the minY and maxY of the selected tracks in this panel container.
let selectedTracksMinY = this.panelContainerHeight + this.panelContainerTop;
let selectedTracksMaxY = this.panelContainerTop;
let trackFromCurrentContainerSelected = false;
for (let i = 0; i < this.panelInfos.length; i++) {
- if (area.tracks.includes(this.panelInfos[i].trackOrGroupKey)) {
+ if (area.trackUris.includes(this.panelInfos[i].trackOrGroupKey)) {
trackFromCurrentContainerSelected = true;
selectedTracksMinY = Math.min(
selectedTracksMinY,
diff --git a/ui/src/frontend/pivot_table.ts b/ui/src/frontend/pivot_table.ts
index 87b176b..2b17b93 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -45,7 +45,7 @@
import {DurationWidget} from './widgets/duration';
import {addSqlTableTab} from './sql_table_tab_command';
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
-import {assertExists} from '../base/logging';
+import {assertExists, assertFalse} from '../base/logging';
import {Filter, SqlColumn} from './widgets/sql/table/column';
import {argSqlColumn} from './widgets/sql/table/well_known_columns';
@@ -206,6 +206,8 @@
) {
if (typeof value === 'bigint') {
return m(DurationWidget, {dur: value});
+ } else if (typeof value === 'number') {
+ return m(DurationWidget, {dur: BigInt(Math.round(value))});
}
}
return `${value}`;
@@ -546,12 +548,10 @@
const queryResult: PivotTableResult = state.queryResult;
const renderedRows: m.Vnode[] = [];
- const tree = state.queryResult.tree;
- if (tree.children.size === 0 && tree.rows.length === 0) {
- // Empty result, render a special message
- return m('.empty-result', 'No slices in the current selection.');
- }
+ // We should not even be showing the tab if there's no results.
+ const tree = state.queryResult.tree;
+ assertFalse(tree.children.size === 0 && tree.rows.length === 0);
this.renderTree(
attrs.selectionArea,
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index 84158c0..5572814 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -22,7 +22,6 @@
import {getLegacySelection} from '../common/state';
import {
- CpuProfileDetails,
Flow,
globals,
QuantizedLoad,
@@ -73,11 +72,6 @@
raf.scheduleFullRedraw();
}
-export function publishCpuProfileDetails(details: CpuProfileDetails) {
- globals.cpuProfileDetails = details;
- globals.publishRedraw();
-}
-
export function publishHasFtrace(value: boolean): void {
globals.hasFtrace = value;
globals.publishRedraw();
@@ -143,9 +137,9 @@
globals.publishRedraw();
}
-export function publishSliceDetails(click: SliceDetails) {
- globals.sliceDetails = click;
- const id = click.id;
+export function publishSliceDetails(sliceDetails: SliceDetails) {
+ globals.sliceDetails = sliceDetails;
+ const id = sliceDetails.id;
if (id !== undefined && id === globals.state.pendingScrollId) {
findCurrentSelection();
globals.dispatch(Actions.clearPendingScrollId({id: undefined}));
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index dd7cc19..84fe7ed 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -30,7 +30,7 @@
import {downloadData} from './download_utils';
import {globals} from './globals';
import {Router} from './router';
-import {reveal} from './scroll_helper';
+import {scrollToTrackAndTimeSpan} from './scroll_helper';
interface QueryTableRowAttrs {
row: Row;
@@ -149,24 +149,31 @@
const sliceStart = Time.fromRaw(BigInt(row.ts));
// row.dur can be negative. Clamp to 1ns.
const sliceDur = BigintMath.max(BigInt(row.dur), 1n);
- const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
- if (trackKey !== undefined) {
- reveal(trackKey, sliceStart, Time.add(sliceStart, sliceDur), true);
+ const trackUri = globals.trackManager.findTrack((td) =>
+ td.tags?.trackIds?.includes(trackId),
+ )?.uri;
+ if (trackUri !== undefined) {
+ scrollToTrackAndTimeSpan(
+ trackUri,
+ sliceStart,
+ Time.add(sliceStart, sliceDur),
+ true,
+ );
const sliceId = getSliceId(row);
if (sliceId !== undefined) {
- this.selectSlice(sliceId, trackKey, switchToCurrentSelectionTab);
+ this.selectSlice(sliceId, trackUri, switchToCurrentSelectionTab);
}
}
}
private selectSlice(
sliceId: number,
- trackKey: string,
+ trackUuid: string,
switchToCurrentSelectionTab: boolean,
) {
const action = Actions.selectSlice({
id: sliceId,
- trackKey,
+ trackUri: trackUuid,
table: 'slice',
});
globals.makeSelection(action, {switchToCurrentSelectionTab});
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index 6511744..b74069d 100644
--- a/ui/src/frontend/scroll_helper.ts
+++ b/ui/src/frontend/scroll_helper.ts
@@ -14,10 +14,8 @@
import {time} from '../base/time';
import {escapeCSSSelector, exists} from '../base/utils';
-import {Actions} from '../common/actions';
import {HighPrecisionTime} from '../common/high_precision_time';
import {HighPrecisionTimeSpan} from '../common/high_precision_time_span';
-import {getContainingGroupKey} from '../common/state';
import {raf} from '../core/raf_scheduler';
import {globals} from './globals';
@@ -64,8 +62,8 @@
// Given a track id, find a track with that id and scroll it into view. If the
// track is nested inside a track group, scroll to that track group instead.
// If |openGroup| then open the track group and scroll to the track.
-export function verticalScrollToTrack(trackKey: string, openGroup = false) {
- const track = document.querySelector('#track_' + escapeCSSSelector(trackKey));
+export function verticalScrollToTrack(trackUri: string, openGroup = false) {
+ const track = document.querySelector('#track_' + escapeCSSSelector(trackUri));
if (track) {
// block: 'nearest' means that it will only scroll if the track is not
@@ -74,49 +72,46 @@
return;
}
- let trackGroup = null;
- const groupKey = getContainingGroupKey(globals.state, trackKey);
- if (groupKey) {
- trackGroup = document.querySelector('#track_' + groupKey);
- }
+ // If we get here, the element for this track was not present in the DOM, this
+ // might be because it's inside a collapsed group.
+ // Find the track node in the current workspace, and reveal it.
+ const trackNode = globals.workspace.getTrackByUri(trackUri);
+ if (!trackNode) return;
- if (!groupKey || !trackGroup) {
- console.error(`Can't scroll, track (${trackKey}) not found.`);
- return;
- }
-
- // The requested track is inside a closed track group, either open the track
- // group and scroll to the track or just scroll to the track group.
if (openGroup) {
- // After the track exists in the dom, it will be scrolled to.
- globals.scrollToTrackKey = trackKey;
- globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
+ trackNode.reveal();
+ globals.scrollToTrackUri = trackUri;
return;
- } else {
- trackGroup.scrollIntoView({behavior: 'smooth', block: 'nearest'});
}
+ // Find the first closed ancestor of our target track.
+ const groupNode = trackNode.closestVisibleAncestor;
+ if (groupNode) {
+ document
+ .querySelector('#track_' + groupNode.uri)
+ ?.scrollIntoView({behavior: 'smooth', block: 'nearest'});
+ }
+ // If we get here, it means this track isn't in the workspace.
+ // TODO(stevegolton): Warn the user about this?
}
-// Scroll vertically and horizontally to reach track (|trackKey|) at |ts|.
+// Scroll vertically and horizontally to reach track |track| at |ts|.
export function scrollToTrackAndTs(
- trackKey: string | undefined,
+ trackUri: string,
ts: time,
openGroup = false,
) {
- if (trackKey !== undefined) {
- verticalScrollToTrack(trackKey, openGroup);
- }
+ verticalScrollToTrack(trackUri, openGroup);
horizontalScrollToTs(ts);
}
// Scroll vertically and horizontally to a track and time range
-export function reveal(
- trackKey: string,
+export function scrollToTrackAndTimeSpan(
+ trackUri: string,
start: time,
end: time,
openGroup = false,
) {
- verticalScrollToTrack(trackKey, openGroup);
+ verticalScrollToTrack(trackUri, openGroup);
focusHorizontalRange(start, end);
}
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index 8a65bcb..eac3ff8 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -85,20 +85,20 @@
const searchIndex = globals.state.searchIndex;
const source = globals.currentSearchResults.sources[searchIndex];
const currentId = globals.currentSearchResults.eventIds[searchIndex];
- const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
+ const uri = globals.currentSearchResults.trackUris[searchIndex];
if (currentId === undefined) return;
switch (source) {
case 'track':
- verticalScrollToTrack(trackKey, true);
+ verticalScrollToTrack(uri, true);
break;
case 'cpu':
globals.setLegacySelection(
{
kind: 'SCHED_SLICE',
id: currentId,
- trackKey,
+ trackUri: uri,
},
{
clearSearch: false,
@@ -112,7 +112,7 @@
{
kind: 'LOG',
id: currentId,
- trackKey,
+ trackUri: uri,
},
{
clearSearch: false,
@@ -128,7 +128,7 @@
{
kind: 'SLICE',
id: currentId,
- trackKey,
+ trackUri: uri,
table: 'slice',
},
{
diff --git a/ui/src/frontend/simple_counter_track.ts b/ui/src/frontend/simple_counter_track.ts
index 2e784b5..42f7c6d 100644
--- a/ui/src/frontend/simple_counter_track.ts
+++ b/ui/src/frontend/simple_counter_track.ts
@@ -35,7 +35,7 @@
) {
super({
engine,
- trackKey: ctx.trackKey,
+ uri: ctx.trackUri,
options: config.options,
});
this.config = config;
diff --git a/ui/src/frontend/simple_slice_track.ts b/ui/src/frontend/simple_slice_track.ts
index 1a49da7..c63f6e3 100644
--- a/ui/src/frontend/simple_slice_track.ts
+++ b/ui/src/frontend/simple_slice_track.ts
@@ -40,11 +40,11 @@
) {
super({
engine,
- trackKey: ctx.trackKey,
+ uri: ctx.trackUri,
});
this.config = config;
- this.sqlTableName = `__simple_slice_${uuidv4Sql(ctx.trackKey)}`;
+ this.sqlTableName = `__simple_slice_${uuidv4Sql(ctx.trackUri)}`;
}
async getSqlDataSource(): Promise<CustomSqlTableDefConfig> {
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts
index bdf83d8..9c23048 100644
--- a/ui/src/frontend/slice_details_panel.ts
+++ b/ui/src/frontend/slice_details_panel.ts
@@ -226,29 +226,22 @@
return;
}
- let trackKey: string | undefined;
- for (const track of Object.values(globals.state.tracks)) {
- const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
- // TODO(stevegolton): Handle v2.
- if (
- trackDesc &&
- trackDesc.tags?.kind === THREAD_STATE_TRACK_KIND &&
- trackDesc.tags?.utid === threadInfo.utid
- ) {
- trackKey = track.key;
- }
- }
+ const trackDescriptor = globals.trackManager.findTrack(
+ (td) =>
+ td.tags?.kind === THREAD_STATE_TRACK_KIND &&
+ td.tags?.utid === threadInfo.utid,
+ );
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (trackKey && sliceInfo.threadStateId) {
+ if (trackDescriptor && sliceInfo.threadStateId) {
globals.makeSelection(
Actions.selectThreadState({
id: sliceInfo.threadStateId,
- trackKey: trackKey.toString(),
+ trackUri: trackDescriptor.uri,
}),
);
- scrollToTrackAndTs(trackKey, sliceInfo.ts, true);
+ scrollToTrackAndTs(trackDescriptor.uri, sliceInfo.ts, true);
}
}
diff --git a/ui/src/frontend/tab_panel.ts b/ui/src/frontend/tab_panel.ts
index 05f443e..1601351 100644
--- a/ui/src/frontend/tab_panel.ts
+++ b/ui/src/frontend/tab_panel.ts
@@ -148,11 +148,10 @@
// Show single selection panels if they are registered
if (currentSelection.kind === 'single') {
- const trackKey = currentSelection.trackKey;
- const uri = globals.state.tracks[trackKey]?.uri;
+ const uri = currentSelection.trackUri;
if (uri) {
- const trackDesc = globals.trackManager.resolveTrackInfo(uri);
+ const trackDesc = globals.trackManager.getTrack(uri);
const panel = trackDesc?.detailsPanel;
if (panel) {
return {
diff --git a/ui/src/frontend/thread_slice_track.ts b/ui/src/frontend/thread_slice_track.ts
index 11a1b78..0bcf2b5 100644
--- a/ui/src/frontend/thread_slice_track.ts
+++ b/ui/src/frontend/thread_slice_track.ts
@@ -87,7 +87,7 @@
{
kind: 'SLICE',
id: args.slice.id,
- trackKey: this.trackKey,
+ trackUri: this.uri,
table: this.tableName,
},
{
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index ed20d99..e137437 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -38,7 +38,6 @@
} from '../trace_processor/sql_utils/thread_state';
import {DurationWidget, renderDuration} from './widgets/duration';
import {Timestamp} from './widgets/timestamp';
-import {addDebugSliceTrack} from './debug_tracks/debug_tracks';
import {globals} from './globals';
import {getProcessName} from '../trace_processor/sql_utils/process';
import {
@@ -47,6 +46,10 @@
getThreadName,
} from '../trace_processor/sql_utils/thread';
import {ThreadStateRef} from './widgets/thread_state';
+import {
+ CRITICAL_PATH_CMD,
+ CRITICAL_PATH_LITE_CMD,
+} from '../public/exposed_commands';
interface ThreadStateTabConfig {
// Id into |thread_state| sql table.
@@ -257,22 +260,9 @@
name,
});
- const sliceColumns = {ts: 'ts', dur: 'dur', name: 'name'};
- const sliceColumnNames = ['id', 'utid', 'ts', 'dur', 'name', 'table_name'];
-
- const sliceLiteColumns = {ts: 'ts', dur: 'dur', name: 'thread_name'};
- const sliceLiteColumnNames = [
- 'id',
- 'utid',
- 'ts',
- 'dur',
- 'thread_name',
- 'process_name',
- 'table_name',
- ];
-
const nameForNextOrPrev = (state: ThreadState) =>
`${state.state} for ${renderDuration(state.dur)}`;
+
return [
m(
Tree,
@@ -321,83 +311,28 @@
),
),
),
- m(Button, {
- label: 'Critical path lite',
- intent: Intent.Primary,
- onclick: () =>
- this.engine
- .query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`)
- .then(() =>
- addDebugSliceTrack(
- // NOTE(stevegolton): This is a temporary patch, this menu
- // should become part of a critical path plugin, at which point
- // we can just use the plugin's context object.
- {
- engine: this.engine,
- registerTrack: (x) => globals.trackManager.registerTrack(x),
- },
- {
- sqlSource: `
- SELECT
- cr.id,
- cr.utid,
- cr.ts,
- cr.dur,
- thread.name AS thread_name,
- process.name AS process_name,
- 'thread_state' AS table_name
- FROM
- _thread_executing_span_critical_path(
- ${this.state?.thread?.utid},
- trace_bounds.start_ts,
- trace_bounds.end_ts - trace_bounds.start_ts) cr,
- trace_bounds
- JOIN thread USING(utid)
- JOIN process USING(upid)
- `,
- columns: sliceLiteColumnNames,
- },
- `${this.state?.thread?.name}`,
- sliceLiteColumns,
- sliceLiteColumnNames,
- ),
- ),
- }),
- m(Button, {
- label: 'Critical path',
- intent: Intent.Primary,
- onclick: () =>
- this.engine
- .query(
- `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
- )
- .then(() =>
- addDebugSliceTrack(
- // NOTE(stevegolton): This is a temporary patch, this menu
- // should become part of a critical path plugin, at which point
- // we can just use the plugin's context object.
- {
- engine: this.engine,
- registerTrack: (x) => globals.trackManager.registerTrack(x),
- },
- {
- sqlSource: `
- SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
- FROM
- _thread_executing_span_critical_path_stack(
- ${this.state?.thread?.utid},
- trace_bounds.start_ts,
- trace_bounds.end_ts - trace_bounds.start_ts) cr,
- trace_bounds WHERE name IS NOT NULL
- `,
- columns: sliceColumnNames,
- },
- `${this.state?.thread?.name}`,
- sliceColumns,
- sliceColumnNames,
- ),
- ),
- }),
+ globals.commandManager.hasCommand(CRITICAL_PATH_LITE_CMD) &&
+ m(Button, {
+ label: 'Critical path lite',
+ intent: Intent.Primary,
+ onclick: () => {
+ globals.commandManager.runCommand(
+ CRITICAL_PATH_LITE_CMD,
+ this.state?.thread?.utid,
+ );
+ },
+ }),
+ globals.commandManager.hasCommand(CRITICAL_PATH_CMD) &&
+ m(Button, {
+ label: 'Critical path',
+ intent: Intent.Primary,
+ onclick: () => {
+ globals.commandManager.runCommand(
+ CRITICAL_PATH_CMD,
+ this.state?.thread?.utid,
+ );
+ },
+ }),
];
}
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index c980970..943e6a7 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -33,6 +33,7 @@
export class TimeAxisPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
+ readonly id = 'time-axis-panel';
render(): m.Children {
return m('.time-axis-panel');
diff --git a/ui/src/frontend/timeline.ts b/ui/src/frontend/timeline.ts
index bdd244e..854f687 100644
--- a/ui/src/frontend/timeline.ts
+++ b/ui/src/frontend/timeline.ts
@@ -83,13 +83,13 @@
selectArea(
start: time,
end: time,
- tracks = this._selectedArea ? this._selectedArea.tracks : [],
+ tracks = this._selectedArea ? this._selectedArea.trackUris : [],
) {
assertTrue(
end >= start,
`Impossible select area: start [${start}] >= end [${end}]`,
);
- this._selectedArea = {start, end, tracks};
+ this._selectedArea = {start, end, trackUris: tracks};
raf.scheduleFullRedraw();
}
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index c6462b0..1ea3a40 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -15,6 +15,6 @@
import {Engine} from '../trace_processor/engine';
export interface NewTrackArgs {
- trackKey: string;
+ uri: string;
engine: Engine;
}
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 55a5ad5..5d02c78 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -15,9 +15,7 @@
import m from 'mithril';
import {Icons} from '../base/semantic_icons';
-import {Actions} from '../common/actions';
-import {getContainingGroupKey} from '../common/state';
-import {TrackCacheEntry} from '../common/track_cache';
+import {TrackRenderer} from '../common/track_manager';
import {TrackTags} from '../public';
import {TRACK_SHELL_WIDTH} from './css_constants';
@@ -41,14 +39,17 @@
import {PxSpan, TimeScale} from './time_scale';
import {exists} from '../base/utils';
import {classNames} from '../base/classnames';
+import {GroupNode} from '../public/workspace';
+import {raf} from '../core/raf_scheduler';
+import {Actions} from '../common/actions';
interface Attrs {
- readonly groupKey: string;
+ readonly groupNode: GroupNode;
readonly title: m.Children;
readonly tooltip: string;
readonly collapsed: boolean;
readonly collapsable: boolean;
- readonly trackFSM?: TrackCacheEntry;
+ readonly trackRenderer?: TrackRenderer;
readonly tags?: TrackTags;
readonly subtitle?: string;
readonly chips?: ReadonlyArray<string>;
@@ -57,14 +58,14 @@
export class TrackGroupPanel implements Panel {
readonly kind = 'panel';
readonly selectable = true;
- readonly groupKey: string;
+ readonly groupUri: string;
- constructor(private attrs: Attrs) {
- this.groupKey = attrs.groupKey;
+ constructor(private readonly attrs: Attrs) {
+ this.groupUri = attrs.groupNode.uri;
}
render(): m.Children {
- const {groupKey, title, subtitle, chips, collapsed, trackFSM, tooltip} =
+ const {title, subtitle, chips, collapsed, trackRenderer, tooltip} =
this.attrs;
// The shell should be highlighted if the current search result is inside
@@ -72,37 +73,38 @@
let highlightClass = '';
const searchIndex = globals.state.searchIndex;
if (searchIndex !== -1) {
- const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
- const containingGroupKey = getContainingGroupKey(globals.state, trackKey);
- if (containingGroupKey === groupKey) {
+ const uri = globals.currentSearchResults.trackUris[searchIndex];
+ if (this.attrs.groupNode.flatTracks.find((t) => t.uri === uri)) {
highlightClass = 'flash';
}
}
const selection = globals.state.selection;
- const trackGroup = globals.state.trackGroups[groupKey];
+ // const trackGroup = globals.state.trackGroups[groupKey];
let checkBox = Icons.BlankCheckbox;
if (selection.kind === 'area') {
if (
- selection.tracks.includes(groupKey) &&
- trackGroup.tracks.every((id) => selection.tracks.includes(id))
+ this.attrs.groupNode.flatTracks.every((track) =>
+ selection.trackUris.includes(track.uri),
+ )
) {
checkBox = Icons.Checkbox;
} else if (
- selection.tracks.includes(groupKey) ||
- trackGroup.tracks.some((id) => selection.tracks.includes(id))
+ this.attrs.groupNode.flatTracks.some((track) =>
+ selection.trackUris.includes(track.uri),
+ )
) {
checkBox = Icons.IndeterminateCheckbox;
}
}
- const error = trackFSM?.getError();
+ const error = trackRenderer?.getError();
return m(
`.track-group-panel[collapsed=${collapsed}]`,
{
- id: 'track_' + groupKey,
+ id: 'track_' + this.groupUri,
oncreate: () => this.onupdate(),
onupdate: () => this.onupdate(),
},
@@ -116,11 +118,8 @@
onclick: (e: MouseEvent) => {
if (e.defaultPrevented) return;
if (this.attrs.collapsable) {
- globals.dispatch(
- Actions.toggleTrackGroupCollapsed({
- groupKey,
- }),
- );
+ this.attrs.groupNode.toggleCollapsed();
+ raf.scheduleFullRedraw();
}
e.stopPropagation();
},
@@ -150,9 +149,11 @@
m(Button, {
onclick: (e: MouseEvent) => {
globals.dispatch(
- Actions.toggleTrackSelection({
- key: groupKey,
- isTrackGroup: true,
+ Actions.toggleGroupAreaSelection({
+ // Dump URIs of all contained tracks & nodes, including this group
+ trackUris: this.attrs.groupNode.flatNodes
+ .map((t) => t.uri)
+ .concat(this.attrs.groupNode.uri),
}),
);
e.stopPropagation();
@@ -162,13 +163,13 @@
}),
),
),
- trackFSM
+ trackRenderer
? m(
TrackContent,
{
- track: trackFSM.track,
- hasError: Boolean(trackFSM.getError()),
- height: this.attrs.trackFSM?.track.getHeight(),
+ track: trackRenderer.track,
+ hasError: Boolean(trackRenderer.getError()),
+ height: this.attrs.trackRenderer?.track.getHeight(),
},
!collapsed && subtitle !== null ? m('span', subtitle) : null,
)
@@ -177,8 +178,8 @@
}
private onupdate() {
- if (this.attrs.trackFSM !== undefined) {
- this.attrs.trackFSM.track.onFullRedraw?.();
+ if (this.attrs.trackRenderer !== undefined) {
+ this.attrs.trackRenderer.track.onFullRedraw?.();
}
}
@@ -189,8 +190,11 @@
) {
const selection = globals.state.selection;
if (selection.kind !== 'area') return;
+ const someSelected = this.attrs.groupNode.flatTracks.some((track) =>
+ selection.trackUris.includes(track.uri),
+ );
const selectedAreaDuration = selection.end - selection.start;
- if (selection.tracks.includes(this.groupKey)) {
+ if (someSelected) {
ctx.fillStyle = 'rgba(131, 152, 230, 0.3)';
ctx.fillRect(
timescale.timeToPx(selection.start),
@@ -202,7 +206,7 @@
}
renderCanvas(ctx: CanvasRenderingContext2D, size: Size) {
- const {collapsed, trackFSM: track} = this.attrs;
+ const {collapsed, trackRenderer: track} = this.attrs;
if (!collapsed) return;
@@ -230,7 +234,7 @@
visibleWindow,
size: trackSize,
ctx,
- trackKey: track.trackKey,
+ trackUri: track.desc.uri,
resolution: calculateResolution(visibleWindow, trackSize.width),
timescale,
};
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index fa55e6f..e11cbbf 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -19,7 +19,7 @@
import {Icons} from '../base/semantic_icons';
import {TimeSpan} from '../base/time';
import {Actions} from '../common/actions';
-import {TrackCacheEntry} from '../common/track_cache';
+import {TrackRenderer} from '../common/track_manager';
import {raf} from '../core/raf_scheduler';
import {Track, TrackTags} from '../public';
@@ -33,7 +33,6 @@
import {generateTicks, TickType, getMaxMajorTicks} from './gridline_helper';
import {Size, VerticalBounds} from '../base/geom';
import {Panel} from './panel_container';
-import {verticalScrollToTrack} from './scroll_helper';
import {drawVerticalLineAtTime} from './vertical_line_helper';
import {classNames} from '../base/classnames';
import {Button, ButtonBar} from '../widgets/button';
@@ -41,13 +40,13 @@
import {canvasClip} from '../common/canvas_utils';
import {PxSpan, TimeScale} from './time_scale';
import {getLegacySelection} from '../common/state';
-import {CloseTrackButton} from './close_track_button';
import {exists, Optional} from '../base/utils';
import {Intent} from '../widgets/common';
import {TrackRenderContext} from '../public/tracks';
import {calculateResolution} from '../common/resolution';
import {featureFlags} from '../core/feature_flags';
import {Tree, TreeNode} from '../widgets/tree';
+import {TrackNode} from '../public/workspace';
export const SHOW_TRACK_DETAILS_BUTTON = featureFlags.register({
id: 'showTrackDetailsButton',
@@ -76,14 +75,10 @@
return undefined;
}
-function isTrackPinned(trackKey: string) {
- return globals.state.pinnedTracks.indexOf(trackKey) !== -1;
-}
-
-function isTrackSelected(trackKey: string) {
+function isTrackSelected(track: TrackNode) {
const selection = globals.state.selection;
if (selection.kind !== 'area') return false;
- return selection.tracks.includes(trackKey);
+ return selection.trackUris.includes(track.uri);
}
interface TrackChipAttrs {
@@ -135,13 +130,13 @@
}
interface TrackShellAttrs {
- readonly trackKey: string;
readonly title: m.Children;
readonly buttons: m.Children;
readonly tags?: TrackTags;
readonly chips?: ReadonlyArray<string>;
readonly button?: string;
readonly pluginId?: string;
+ readonly track: TrackNode;
}
class TrackShell implements m.ClassComponent<TrackShellAttrs> {
@@ -155,14 +150,14 @@
let highlightClass = undefined;
const searchIndex = globals.state.searchIndex;
if (searchIndex !== -1) {
- const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
- if (trackKey === attrs.trackKey) {
+ const uri = globals.currentSearchResults.trackUris[searchIndex];
+ if (uri === attrs.track.uri) {
highlightClass = 'flash';
}
}
const currentSelection = globals.state.selection;
- const pinned = isTrackPinned(attrs.trackKey);
+ const pinned = attrs.track.isPinned;
return m(
`.track-shell[draggable=true]`,
@@ -172,18 +167,18 @@
this.dragging && 'drag',
this.dropping && `drop-${this.dropping}`,
),
- ondragstart: (e: DragEvent) => this.ondragstart(e, attrs.trackKey),
+ ondragstart: (e: DragEvent) => this.ondragstart(e, attrs.track),
ondragend: this.ondragend.bind(this),
ondragover: this.ondragover.bind(this),
ondragleave: this.ondragleave.bind(this),
- ondrop: (e: DragEvent) => this.ondrop(e, attrs.trackKey),
+ ondrop: (e: DragEvent) => this.ondrop(e, attrs.track),
},
m(
'.track-menubar',
m(
'h1',
{
- title: attrs.title,
+ title: attrs.track.displayName,
},
attrs.title,
attrs.chips && renderChips(attrs.chips),
@@ -193,13 +188,12 @@
{className: 'track-buttons'},
attrs.buttons,
SHOW_TRACK_DETAILS_BUTTON.get() &&
- this.renderTrackDetailsButton(pinned, attrs),
+ this.renderTrackDetailsButton(attrs),
m(Button, {
className: classNames(!pinned && 'pf-visible-on-hover'),
onclick: () => {
- globals.dispatch(
- Actions.toggleTrackPinned({trackKey: attrs.trackKey}),
- );
+ pinned ? attrs.track.unpin() : attrs.track.pin();
+ raf.scheduleFullRedraw();
},
icon: Icons.Pin,
iconFilled: pinned,
@@ -210,18 +204,17 @@
? m(Button, {
onclick: (e: MouseEvent) => {
globals.dispatch(
- Actions.toggleTrackSelection({
- key: attrs.trackKey,
- isTrackGroup: false,
+ Actions.toggleTrackAreaSelection({
+ key: attrs.track.uri,
}),
);
e.stopPropagation();
},
compact: true,
- icon: isTrackSelected(attrs.trackKey)
+ icon: isTrackSelected(attrs.track)
? Icons.Checkbox
: Icons.BlankCheckbox,
- title: isTrackSelected(attrs.trackKey)
+ title: isTrackSelected(attrs.track)
? 'Remove track'
: 'Add track to selection',
})
@@ -231,12 +224,12 @@
);
}
- ondragstart(e: DragEvent, trackKey: string) {
+ ondragstart(e: DragEvent, track: TrackNode) {
const dataTransfer = e.dataTransfer;
if (dataTransfer === null) return;
this.dragging = true;
raf.scheduleFullRedraw();
- dataTransfer.setData('perfetto/track', `${trackKey}`);
+ dataTransfer.setData('perfetto/track', `${track.uri}`);
dataTransfer.setDragImage(new Image(), 0, 0);
}
@@ -269,23 +262,30 @@
raf.scheduleFullRedraw();
}
- ondrop(e: DragEvent, trackKey: string) {
+ ondrop(e: DragEvent, track: TrackNode) {
if (this.dropping === undefined) return;
const dataTransfer = e.dataTransfer;
if (dataTransfer === null) return;
raf.scheduleFullRedraw();
const srcId = dataTransfer.getData('perfetto/track');
- const dstId = trackKey;
- globals.dispatch(Actions.moveTrack({srcId, op: this.dropping, dstId}));
+ const dstId = track.uri;
+ console.log(srcId, dstId);
+ // globals.dispatch(Actions.moveTrack({srcId, op: this.dropping, dstId}));
this.dropping = undefined;
}
- private renderTrackDetailsButton(pinned: boolean, attrs: TrackShellAttrs) {
+ private renderTrackDetailsButton(attrs: TrackShellAttrs) {
+ let parent = attrs.track.parent;
+ let fullPath: m.ChildArray = [attrs.track.displayName];
+ while (parent && parent !== globals.workspace) {
+ fullPath = [parent.displayName, ' \u2023 ', ...fullPath];
+ parent = parent.parent;
+ }
return m(
Popup,
{
trigger: m(Button, {
- className: classNames(!pinned && 'pf-visible-on-hover'),
+ className: 'pf-visible-on-hover',
icon: 'info',
title: 'Show track details',
compact: true,
@@ -298,10 +298,14 @@
Tree,
m(TreeNode, {
left: 'URI',
- right: globals.state.tracks[attrs.trackKey]?.uri,
+ right: attrs.track.uri,
}),
- m(TreeNode, {left: 'Title', right: attrs.title}),
- m(TreeNode, {left: 'Track Key', right: attrs.trackKey}),
+ m(TreeNode, {
+ left: 'Key',
+ right: attrs.track.uri,
+ }),
+ m(TreeNode, {left: 'Path', right: fullPath}),
+ m(TreeNode, {left: 'Display Name', right: attrs.track.displayName}),
m(TreeNode, {left: 'Plugin ID', right: attrs.pluginId}),
m(
TreeNode,
@@ -409,7 +413,6 @@
}
interface TrackComponentAttrs {
- readonly trackKey: string;
readonly heightPx?: number;
readonly title: m.Children;
readonly buttons?: m.Children;
@@ -417,8 +420,8 @@
readonly chips?: ReadonlyArray<string>;
readonly track?: Track;
readonly error?: Error | undefined;
- readonly closeable: boolean;
readonly pluginId?: string;
+ readonly trackNode: TrackNode;
// Issues a scrollTo() on this DOM element at creation time. Default: false.
revealOnCreate?: boolean;
@@ -442,20 +445,19 @@
// Round up to the nearest integer number of pixels.
height: `${Math.ceil(trackHeight)}px`,
},
- id: 'track_' + attrs.trackKey,
+ id: 'track_' + attrs.trackNode.uri,
},
[
m(TrackShell, {
buttons: [
attrs.error && m(CrashButton, {error: attrs.error}),
- attrs.closeable && m(CloseTrackButton, {trackKey: attrs.trackKey}),
attrs.buttons,
],
title: attrs.title,
- trackKey: attrs.trackKey,
tags: attrs.tags,
chips: attrs.chips,
pluginId: attrs.pluginId,
+ track: attrs.trackNode,
}),
attrs.track &&
m(TrackContent, {
@@ -469,9 +471,9 @@
oncreate(vnode: m.VnodeDOM<TrackComponentAttrs>) {
const {attrs} = vnode;
- if (globals.scrollToTrackKey === attrs.trackKey) {
- verticalScrollToTrack(attrs.trackKey);
- globals.scrollToTrackKey = undefined;
+ if (globals.scrollToTrackUri === attrs.trackNode.uri) {
+ vnode.dom.scrollIntoView();
+ globals.scrollToTrackUri = undefined;
}
this.onupdate(vnode);
@@ -486,14 +488,13 @@
}
interface TrackPanelAttrs {
- readonly trackKey: string;
readonly title: m.Children;
readonly tags?: TrackTags;
readonly chips?: ReadonlyArray<string>;
- readonly trackFSM?: TrackCacheEntry;
+ readonly trackRenderer?: TrackRenderer;
readonly revealOnCreate?: boolean;
- readonly closeable: boolean;
readonly pluginId?: string;
+ readonly track: TrackNode;
}
export class TrackPanel implements Panel {
@@ -502,46 +503,43 @@
constructor(private readonly attrs: TrackPanelAttrs) {}
- get trackKey(): string {
- return this.attrs.trackKey;
+ get trackUri(): string {
+ return this.attrs.track.uri;
}
render(): m.Children {
const attrs = this.attrs;
- if (attrs.trackFSM) {
- if (attrs.trackFSM.getError()) {
+ if (attrs.trackRenderer) {
+ if (attrs.trackRenderer.getError()) {
return m(TrackComponent, {
title: attrs.title,
- trackKey: attrs.trackKey,
- error: attrs.trackFSM.getError(),
- track: attrs.trackFSM.track,
- closeable: attrs.closeable,
+ error: attrs.trackRenderer.getError(),
+ track: attrs.trackRenderer.track,
chips: attrs.chips,
pluginId: attrs.pluginId,
+ trackNode: attrs.track,
});
}
return m(TrackComponent, {
- trackKey: attrs.trackKey,
title: attrs.title,
- heightPx: attrs.trackFSM.track.getHeight(),
- buttons: attrs.trackFSM.track.getTrackShellButtons?.(),
+ heightPx: attrs.trackRenderer.track.getHeight(),
+ buttons: attrs.trackRenderer.track.getTrackShellButtons?.(),
tags: attrs.tags,
- track: attrs.trackFSM.track,
- error: attrs.trackFSM.getError(),
+ track: attrs.trackRenderer.track,
+ error: attrs.trackRenderer.getError(),
revealOnCreate: attrs.revealOnCreate,
- closeable: attrs.closeable,
chips: attrs.chips,
pluginId: attrs.pluginId,
+ trackNode: attrs.track,
});
} else {
return m(TrackComponent, {
- trackKey: attrs.trackKey,
title: attrs.title,
revealOnCreate: attrs.revealOnCreate,
- closeable: attrs.closeable,
chips: attrs.chips,
pluginId: attrs.pluginId,
+ trackNode: attrs.track,
});
}
}
@@ -556,7 +554,7 @@
return;
}
const selectedAreaDuration = selection.end - selection.start;
- if (selection.tracks.includes(this.attrs.trackKey)) {
+ if (selection.trackUris.includes(this.attrs.track.uri)) {
ctx.fillStyle = SELECTION_FILL_COLOR;
ctx.fillRect(
timescale.timeToPx(selection.start),
@@ -582,11 +580,11 @@
);
drawGridLines(ctx, timespan, timescale, trackSize);
- const track = this.attrs.trackFSM;
+ const track = this.attrs.trackRenderer;
if (track !== undefined) {
const trackRenderCtx: TrackRenderContext = {
- trackKey: track.trackKey,
+ trackUri: track.desc.uri,
visibleWindow,
size: trackSize,
resolution: calculateResolution(visibleWindow, trackSize.width),
@@ -612,10 +610,10 @@
}
getSliceVerticalBounds(depth: number): Optional<VerticalBounds> {
- if (this.attrs.trackFSM === undefined) {
+ if (this.attrs.trackRenderer === undefined) {
return undefined;
}
- return this.attrs.trackFSM.track.getSliceVerticalBounds?.(depth);
+ return this.attrs.trackRenderer.track.getSliceVerticalBounds?.(depth);
}
}
diff --git a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
index 832fe44..51a29f1 100644
--- a/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
+++ b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
@@ -114,7 +114,7 @@
if (selection.kind !== 'GENERIC_SLICE') {
return false;
}
- return selection.trackKey === this.trackKey;
+ return selection.trackUri === this.uri;
}
onSliceClick(args: OnSliceClickArgs<Slice>) {
@@ -129,7 +129,7 @@
sqlTableName: this.tableName,
start: args.slice.ts,
duration: args.slice.dur,
- trackKey: this.trackKey,
+ trackUri: this.uri,
detailsPanelConfig: {
kind: detailsPanelConfig.kind,
config: detailsPanelConfig.config,
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/ui_main.ts
similarity index 75%
rename from ui/src/frontend/app.ts
rename to ui/src/frontend/ui_main.ts
index 872c232..7e62524 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/ui_main.ts
@@ -28,7 +28,7 @@
TimestampFormat,
} from '../core/timestamp_format';
import {raf} from '../core/raf_scheduler';
-import {Command, Engine, addDebugSliceTrack} from '../public';
+import {Command} from '../public';
import {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
import {HotkeyGlyphs} from '../widgets/hotkey_glyphs';
import {maybeRenderFullscreenModalDialog} from '../widgets/modal';
@@ -52,10 +52,7 @@
} from './keyboard_event_handler';
import {publishPermalinkHash} from './publish';
import {OmniboxMode, PromptOption} from './omnibox_manager';
-import {Utid} from '../trace_processor/sql_utils/core_types';
-import {THREAD_STATE_TRACK_KIND} from '../core/track_kinds';
import {DisposableStack} from '../base/disposable_stack';
-import {getThreadInfo} from '../trace_processor/sql_utils/thread';
function renderPermalink(): m.Children {
const hash = globals.permalinkHash;
@@ -81,36 +78,7 @@
}
}
-const criticalPathSliceColumns = {
- ts: 'ts',
- dur: 'dur',
- name: 'name',
-};
-const criticalPathsliceColumnNames = [
- 'id',
- 'utid',
- 'ts',
- 'dur',
- 'name',
- 'table_name',
-];
-
-const criticalPathsliceLiteColumns = {
- ts: 'ts',
- dur: 'dur',
- name: 'thread_name',
-};
-const criticalPathsliceLiteColumnNames = [
- 'id',
- 'utid',
- 'ts',
- 'dur',
- 'thread_name',
- 'process_name',
- 'table_name',
-];
-
-export class App implements m.ClassComponent {
+export class UiMain implements m.ClassComponent {
private trash = new DisposableStack();
static readonly OMNIBOX_INPUT_REF = 'omnibox';
private omniboxInputEl?: HTMLInputElement;
@@ -121,37 +89,6 @@
this.trash.use(new AggregationsTabs());
}
- private getEngine(): Engine | undefined {
- const engineId = globals.getCurrentEngine()?.id;
- if (engineId === undefined) {
- return undefined;
- }
- const engine = globals.engines.get(engineId)?.getProxy('QueryPage');
- return engine;
- }
-
- private getFirstUtidOfSelectionOrVisibleWindow(): number {
- const selection = globals.state.selection;
- if (selection.kind === 'area') {
- const firstThreadStateTrack = selection.tracks.find((trackId) => {
- return globals.state.tracks[trackId];
- });
-
- if (firstThreadStateTrack) {
- const trackInfo = globals.state.tracks[firstThreadStateTrack];
- const trackDesc = globals.trackManager.resolveTrackInfo(trackInfo.uri);
- if (
- trackDesc?.tags?.kind === THREAD_STATE_TRACK_KIND &&
- trackDesc?.tags?.utid !== undefined
- ) {
- return trackDesc.tags.utid;
- }
- }
- }
-
- return 0;
- }
-
private cmds: Command[] = [
{
id: 'perfetto.SetTimestampFormat',
@@ -205,116 +142,6 @@
},
},
{
- id: 'perfetto.CriticalPathLite',
- name: `Critical path lite`,
- callback: async () => {
- const trackUtid = this.getFirstUtidOfSelectionOrVisibleWindow();
- const window = await getTimeSpanOfSelectionOrVisibleWindow();
- const engine = this.getEngine();
-
- if (engine !== undefined && trackUtid != 0) {
- await engine.query(
- `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
- );
- await addDebugSliceTrack(
- // NOTE(stevegolton): This is a temporary patch, this menu should
- // become part of a critical path plugin, at which point we can just
- // use the plugin's context object.
- {
- engine,
- registerTrack: (x) => globals.trackManager.registerTrack(x),
- },
- {
- sqlSource: `
- SELECT
- cr.id,
- cr.utid,
- cr.ts,
- cr.dur,
- thread.name AS thread_name,
- process.name AS process_name,
- 'thread_state' AS table_name
- FROM
- _thread_executing_span_critical_path(
- ${trackUtid},
- ${window.start},
- ${window.end} - ${window.start}) cr
- JOIN thread USING(utid)
- JOIN process USING(upid)
- `,
- columns: criticalPathsliceLiteColumnNames,
- },
- (await getThreadInfo(engine, trackUtid as Utid)).name ??
- '<thread name>',
- criticalPathsliceLiteColumns,
- criticalPathsliceLiteColumnNames,
- );
- }
- },
- },
- {
- id: 'perfetto.CriticalPath',
- name: `Critical path`,
- callback: async () => {
- const trackUtid = this.getFirstUtidOfSelectionOrVisibleWindow();
- const window = await getTimeSpanOfSelectionOrVisibleWindow();
- const engine = this.getEngine();
-
- if (engine !== undefined && trackUtid != 0) {
- await engine.query(
- `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
- );
- await addDebugSliceTrack(
- // NOTE(stevegolton): This is a temporary patch, this menu should
- // become part of a critical path plugin, at which point we can just
- // use the plugin's context object.
- {
- engine,
- registerTrack: (x) => globals.trackManager.registerTrack(x),
- },
- {
- sqlSource: `
- SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
- FROM
- _critical_path_stack(
- ${trackUtid},
- ${window.start},
- ${window.end} - ${window.start}, 1, 1, 1, 1) cr WHERE name IS NOT NULL
- `,
- columns: criticalPathsliceColumnNames,
- },
- (await getThreadInfo(engine, trackUtid as Utid)).name ??
- '<thread name>',
- criticalPathSliceColumns,
- criticalPathsliceColumnNames,
- );
- }
- },
- },
- {
- id: 'perfetto.CriticalPathPprof',
- name: `Critical path pprof`,
- callback: async () => {
- const trackUtid = this.getFirstUtidOfSelectionOrVisibleWindow();
- const window = await getTimeSpanOfSelectionOrVisibleWindow();
- const engine = this.getEngine();
-
- if (engine !== undefined && trackUtid != 0) {
- addQueryResultsTab({
- query: `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
- SELECT *
- FROM
- _thread_executing_span_critical_path_graph(
- "criical_path",
- ${trackUtid},
- ${window.start},
- ${window.end} - ${window.start}) cr`,
- title: 'Critical path',
- });
- }
- },
- },
- {
id: 'perfetto.TogglePerformanceMetrics',
name: 'Toggle performance metrics',
callback: () => {
@@ -384,7 +211,6 @@
id: 'perfetto.Deselect',
name: 'Deselect',
callback: () => {
- globals.timeline.deselectArea();
globals.clearSelection();
globals.dispatch(Actions.removeNote({id: '0'}));
},
@@ -482,22 +308,22 @@
// If the current selection is an area which does not cover the
// entire time range, preserve the list of selected tracks and
// expand the time range.
- tracksToSelect = selection.tracks;
+ tracksToSelect = selection.trackUris;
} else {
// If the entire time range is already covered, update the selection
// to cover all tracks.
- tracksToSelect = Object.keys(globals.state.tracks);
+ tracksToSelect = globals.workspace.flatTracks.map((t) => t.uri);
}
} else {
// If the current selection is not an area, select all.
- tracksToSelect = Object.keys(globals.state.tracks);
+ tracksToSelect = globals.workspace.flatTracks.map((t) => t.uri);
}
const {start, end} = globals.traceContext;
globals.dispatch(
Actions.selectArea({
start,
end,
- tracks: tracksToSelect,
+ trackUris: tracksToSelect,
}),
);
},
@@ -562,7 +388,7 @@
return m(Omnibox, {
value: globals.omnibox.text,
placeholder: prompt.text,
- inputRef: App.OMNIBOX_INPUT_REF,
+ inputRef: UiMain.OMNIBOX_INPUT_REF,
extraClasses: 'prompt-mode',
closeOnOutsideClick: true,
options,
@@ -622,7 +448,7 @@
return m(Omnibox, {
value: globals.omnibox.text,
placeholder: 'Filter commands...',
- inputRef: App.OMNIBOX_INPUT_REF,
+ inputRef: UiMain.OMNIBOX_INPUT_REF,
extraClasses: 'command-mode',
options,
closeOnSubmit: true,
@@ -666,7 +492,7 @@
return m(Omnibox, {
value: globals.omnibox.text,
placeholder: ph,
- inputRef: App.OMNIBOX_INPUT_REF,
+ inputRef: UiMain.OMNIBOX_INPUT_REF,
extraClasses: 'query-mode',
onInput: (value) => {
@@ -703,7 +529,7 @@
return m(Omnibox, {
value: globals.state.omniboxState.omnibox,
placeholder: "Search or type '>' for commands or ':' for SQL mode",
- inputRef: App.OMNIBOX_INPUT_REF,
+ inputRef: UiMain.OMNIBOX_INPUT_REF,
onInput: (value, prev) => {
if (prev === '') {
if (value === '>') {
@@ -822,7 +648,7 @@
}
private updateOmniboxInputRef(dom: Element): void {
- const el = findRef(dom, App.OMNIBOX_INPUT_REF);
+ const el = findRef(dom, UiMain.OMNIBOX_INPUT_REF);
if (el && el instanceof HTMLInputElement) {
this.omniboxInputEl = el;
}
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index e5d5ae5..40d851e 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -18,10 +18,8 @@
import {clamp} from '../base/math_utils';
import {Time} from '../base/time';
import {Actions} from '../common/actions';
-import {TrackCacheEntry} from '../common/track_cache';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
-import {TrackTags} from '../public';
import {TRACK_SHELL_WIDTH} from './css_constants';
import {globals} from './globals';
@@ -30,7 +28,6 @@
import {createPage} from './pages';
import {PanAndZoomHandler} from './pan_and_zoom_handler';
import {
- Panel,
PanelContainer,
PanelOrGroup,
RenderedPanelInfo,
@@ -45,9 +42,10 @@
import {TrackPanel, getTitleFontSize} from './track_panel';
import {assertExists} from '../base/logging';
import {PxSpan, TimeScale} from './time_scale';
-import {TrackGroupState} from '../common/state';
-import {FuzzyFinder, fuzzyMatch, FuzzySegment} from '../base/fuzzy';
-import {exists} from '../base/utils';
+import {GroupNode, Node, TrackNode} from '../public/workspace';
+import {fuzzyMatch, FuzzySegment} from '../base/fuzzy';
+
+import {exists, Optional} from '../base/utils';
import {EmptyState} from '../widgets/empty_state';
import {removeFalsyValues} from '../base/array_utils';
import {renderFlows} from './flow_events_renderer';
@@ -195,7 +193,7 @@
timeline.selectArea(
Time.max(Time.min(keepTime, newTime), traceTime.start),
Time.min(Time.max(keepTime, newTime), traceTime.end),
- selection.tracks,
+ selection.trackUris,
);
}
} else {
@@ -276,17 +274,16 @@
),
m(PanelContainer, {
className: 'pinned-panel-container',
- panels: globals.state.pinnedTracks.map((key) => {
- const trackBundle = resolveTrack(key);
+ panels: globals.workspace.pinnedTracks.map((track) => {
+ const tr = globals.trackManager.getTrackRenderer(track.uri);
return new TrackPanel({
- trackKey: key,
- title: trackBundle.title,
- tags: trackBundle.tags,
- trackFSM: trackBundle.trackFSM,
+ track: track,
+ title: track.displayName,
+ tags: tr?.desc.tags,
+ trackRenderer: tr,
revealOnCreate: true,
- closeable: trackBundle.closeable,
- chips: trackBundle.chips,
- pluginId: trackBundle.pluginId,
+ chips: tr?.desc.chips,
+ pluginId: tr?.desc.pluginId,
});
}),
}),
@@ -355,160 +352,128 @@
}
// Render the toplevel "scrolling" tracks and track groups
-function renderToplevelPanels(filterTerm: string | undefined): PanelOrGroup[] {
- const scrollingPanels: PanelOrGroup[] = renderTrackPanels(
- globals.state.scrollingTracks,
- filterTerm,
- );
-
- for (const group of Object.values(globals.state.trackGroups)) {
- if (filterTermIsValid(filterTerm)) {
- const tokens = tokenizeFilterTerm(filterTerm);
- // Match group names that match any of the tokens
- const result = fuzzyMatch(group.name, ...tokens);
- if (result.matches) {
- // If the group name matches, render the entire group as normal
- const title = renderFuzzyMatchedTrackTitle(result.segments);
- scrollingPanels.push({
- kind: 'group',
- collapsed: group.collapsed,
- childPanels: group.collapsed ? [] : renderTrackPanels(group.tracks),
- header: renderTrackGroupPanel(group, true, group.collapsed, title),
- });
- } else {
- // If we are filtering, render the group header only if it contains
- // matching tracks
- const childPanels = renderTrackPanels(group.tracks, filterTerm);
- if (childPanels.length === 0) continue;
- scrollingPanels.push({
- kind: 'group',
- collapsed: false,
- childPanels,
- header: renderTrackGroupPanel(group, false, false),
- });
- }
- } else {
- // Always render the group header, but only render child tracks if not
- // collapsed
- scrollingPanels.push({
- kind: 'group',
- collapsed: group.collapsed,
- childPanels: group.collapsed ? [] : renderTrackPanels(group.tracks),
- header: renderTrackGroupPanel(group, true, group.collapsed),
- });
- }
- }
-
- return scrollingPanels;
+function renderToplevelPanels(filterTerm: Optional<string>): PanelOrGroup[] {
+ return renderNodes(globals.workspace.children, filterTerm);
}
// Given a list of tracks and a filter term, return a list pf panels filtered by
// the filter term
-function renderTrackPanels(trackKeys: string[], filterTerm?: string): Panel[] {
- if (filterTermIsValid(filterTerm)) {
- const tokens = tokenizeFilterTerm(filterTerm);
- const matcher = new FuzzyFinder(trackKeys, (key) => {
- return globals.state.tracks[key].name;
- });
- // Filter tracks which match any of the tokens
- const filtered = matcher.find(...tokens);
- return filtered.map(({item: key, segments}) => {
- return renderTrackPanel(key, renderFuzzyMatchedTrackTitle(segments));
- });
- } else {
- // No point in applying any filtering...
- return trackKeys.map((key) => {
- return renderTrackPanel(key);
- });
- }
+function renderNodes(
+ nodes: ReadonlyArray<Node>,
+ filterTerm?: string,
+): PanelOrGroup[] {
+ return nodes.flatMap((node) => {
+ if (node instanceof GroupNode) {
+ if (node.headless) {
+ return renderNodes(node.children, filterTerm);
+ } else {
+ if (filterTermIsValid(filterTerm)) {
+ const tokens = tokenizeFilterTerm(filterTerm);
+ const match = fuzzyMatch(node.displayName, ...tokens);
+ if (match.matches) {
+ return {
+ kind: 'group',
+ collapsed: node.collapsed,
+ header: renderGroupHeaderPanel(
+ node,
+ true,
+ node.collapsed,
+ renderFuzzyMatchedTrackTitle(match.segments),
+ ),
+ childPanels: node.collapsed ? [] : renderNodes(node.children),
+ };
+ } else {
+ const childPanels = renderNodes(node.children, filterTerm);
+ if (childPanels.length > 0) {
+ return {
+ kind: 'group',
+ collapsed: false,
+ header: renderGroupHeaderPanel(node, false, node.collapsed),
+ childPanels,
+ };
+ }
+ return [];
+ }
+ } else {
+ return {
+ kind: 'group',
+ collapsed: node.collapsed,
+ header: renderGroupHeaderPanel(node, true, node.collapsed),
+ childPanels: node.collapsed
+ ? []
+ : renderNodes(node.children, filterTerm),
+ };
+ }
+ }
+ } else {
+ if (filterTermIsValid(filterTerm)) {
+ const tokens = tokenizeFilterTerm(filterTerm);
+ const match = fuzzyMatch(node.displayName, ...tokens);
+ if (match.matches) {
+ return renderTrackPanel(
+ node,
+ renderFuzzyMatchedTrackTitle(match.segments),
+ );
+ } else {
+ return [];
+ }
+ } else {
+ return renderTrackPanel(node);
+ }
+ }
+ });
}
-function renderTrackPanel(key: string, title?: m.Children) {
- const trackBundle = resolveTrack(key);
+function renderTrackPanel(track: TrackNode, title?: m.Children) {
+ const tr = globals.trackManager.getTrackRenderer(track.uri);
return new TrackPanel({
- trackKey: key,
+ track: track,
title: m(
'span',
{
style: {
- 'font-size': getTitleFontSize(trackBundle.title),
+ 'font-size': getTitleFontSize(track.displayName),
},
},
- Boolean(title) ? title : trackBundle.title,
+ Boolean(title) ? title : track.displayName,
),
- tags: trackBundle.tags,
- trackFSM: trackBundle.trackFSM,
- closeable: trackBundle.closeable,
- chips: trackBundle.chips,
- pluginId: trackBundle.pluginId,
+ tags: tr?.desc.tags,
+ trackRenderer: tr,
+ chips: tr?.desc.chips,
+ pluginId: tr?.desc.pluginId,
});
}
-function renderTrackGroupPanel(
- group: TrackGroupState,
+function renderGroupHeaderPanel(
+ group: GroupNode,
collapsable: boolean,
collapsed: boolean,
title?: m.Children,
): TrackGroupPanel {
- const summaryTrackKey = group.summaryTrack;
-
- if (exists(summaryTrackKey)) {
- const trackBundle = resolveTrack(summaryTrackKey);
+ if (group.headerTrackUri !== undefined) {
+ const tr = globals.trackManager.getTrackRenderer(group.headerTrackUri);
return new TrackGroupPanel({
- groupKey: group.key,
- trackFSM: trackBundle.trackFSM,
- subtitle: trackBundle.subtitle,
- tags: trackBundle.tags,
- chips: trackBundle.chips,
+ groupNode: group,
+ trackRenderer: tr,
+ subtitle: tr?.desc.subtitle,
+ tags: tr?.desc.tags,
+ chips: tr?.desc.chips,
collapsed,
- title: exists(title) ? title : group.name,
- tooltip: group.name,
+ title: exists(title) ? title : group.displayName,
+ tooltip: group.displayName,
collapsable,
});
} else {
return new TrackGroupPanel({
- groupKey: group.key,
+ groupNode: group,
collapsed,
- title: exists(title) ? title : group.name,
- tooltip: group.name,
+ title: exists(title) ? title : group.displayName,
+ tooltip: group.displayName,
collapsable,
});
}
}
-// Resolve a track and its metadata through the track cache
-function resolveTrack(key: string): TrackBundle {
- const trackState = globals.state.tracks[key];
- const {uri, name, closeable} = trackState;
- const trackDesc = globals.trackManager.resolveTrackInfo(uri);
- const trackCacheEntry =
- trackDesc && globals.trackManager.resolveTrack(key, trackDesc);
- const trackFSM = trackCacheEntry;
- const tags = trackCacheEntry?.desc.tags;
- const subtitle = trackCacheEntry?.desc.subtitle;
- const chips = trackCacheEntry?.desc.chips;
- const plugin = trackCacheEntry?.desc.pluginId;
- return {
- title: name,
- subtitle,
- closeable: closeable ?? false,
- tags,
- trackFSM,
- chips,
- pluginId: plugin,
- };
-}
-
-interface TrackBundle {
- readonly title: string;
- readonly subtitle?: string;
- readonly closeable: boolean;
- readonly trackFSM?: TrackCacheEntry;
- readonly tags?: TrackTags;
- readonly chips?: ReadonlyArray<string>;
- readonly pluginId?: string;
-}
-
export const ViewerPage = createPage({
view() {
return m(TraceViewer);
diff --git a/ui/src/frontend/visualized_args_track.ts b/ui/src/frontend/visualized_args_track.ts
index d2064bc..be81eaf 100644
--- a/ui/src/frontend/visualized_args_track.ts
+++ b/ui/src/frontend/visualized_args_track.ts
@@ -14,17 +14,16 @@
import m from 'mithril';
-import {Actions} from '../common/actions';
-import {globals} from './globals';
import {Button} from '../widgets/button';
import {Icons} from '../base/semantic_icons';
import {ThreadSliceTrack} from './thread_slice_track';
import {uuidv4Sql} from '../base/uuid';
import {Engine} from '../trace_processor/engine';
import {createView} from '../trace_processor/sql_utils';
+import {globals} from './globals';
export interface VisualizedArgsTrackAttrs {
- readonly trackKey: string;
+ readonly uri: string;
readonly engine: Engine;
readonly trackId: number;
readonly maxDepth: number;
@@ -36,7 +35,7 @@
private readonly argName: string;
constructor({
- trackKey,
+ uri,
engine,
trackId,
maxDepth,
@@ -46,7 +45,7 @@
const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_');
const viewName = `__arg_visualisation_helper_${escapedArgName}_${uuid}_slice`;
- super({engine, trackKey}, trackId, maxDepth, viewName);
+ super({engine, uri}, trackId, maxDepth, viewName);
this.viewName = viewName;
this.argName = argName;
}
@@ -84,13 +83,7 @@
getTrackShellButtons(): m.Children {
return m(Button, {
onclick: () => {
- // This behavior differs to the original behavior a little.
- // Originally, hitting the close button on a single track removed ALL
- // tracks with this argName, whereas this one only closes the single
- // track.
- // This will be easily fixable once we transition to using dynamic
- // tracks instead of this "initial state" approach to add these tracks.
- globals.dispatch(Actions.removeTracks({trackKeys: [this.trackKey]}));
+ globals.workspace.getTrackByUri(this.uri)?.remove();
},
icon: Icons.Close,
title: 'Close',
diff --git a/ui/src/frontend/visualized_args_tracks.ts b/ui/src/frontend/visualized_args_tracks.ts
index 9aea25e..eb7c3bc 100644
--- a/ui/src/frontend/visualized_args_tracks.ts
+++ b/ui/src/frontend/visualized_args_tracks.ts
@@ -12,15 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertExists} from '../base/logging';
import {uuidv4} from '../base/uuid';
-import {Actions, AddTrackArgs} from '../common/actions';
-import {InThreadTrackSortKey} from '../common/state';
+// import {THREAD_SLICE_TRACK_KIND} from '../public';
import {TrackDescriptor} from '../public/tracks';
import {Engine} from '../trace_processor/engine';
import {NUM} from '../trace_processor/query_result';
import {globals} from './globals';
import {VisualisedArgsTrack} from './visualized_args_track';
+import {TrackNode} from '../public/workspace';
const VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX = 'perfetto.VisualisedArgs';
@@ -75,48 +74,40 @@
group by track_id;
`);
- const tracksToAdd: AddTrackArgs[] = [];
const it = result.iter({trackId: NUM, maxDepth: NUM});
- const addedTrackKeys: string[] = [];
for (; it.valid(); it.next()) {
const trackId = it.trackId;
const maxDepth = it.maxDepth;
- const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
- const track = globals.state.tracks[assertExists(trackKey)];
- const utid = (track.trackSortKey as {utid?: number}).utid;
- const key = uuidv4();
- addedTrackKeys.push(key);
const uri = `${VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX}#${uuidv4()}`;
ctx.registerTrack({
uri,
title: argName,
chips: ['metric'],
- trackFactory: (trackCtx) => {
- return new VisualisedArgsTrack({
- engine: ctx.engine,
- trackKey: trackCtx.trackKey,
- trackId,
- maxDepth,
- argName,
- });
- },
+ track: new VisualisedArgsTrack({
+ engine: ctx.engine,
+ uri,
+ trackId,
+ maxDepth,
+ argName,
+ }),
});
- tracksToAdd.push({
- key,
- trackGroup: track.trackGroup,
- name: argName,
- trackSortKey:
- utid === undefined
- ? track.trackSortKey
- : {utid, priority: InThreadTrackSortKey.VISUALISED_ARGS_TRACK},
- uri,
+ // Find the thread slice track that corresponds with this trackID and insert
+ // this track before it.
+ const threadSliceTrack = globals.workspace.flatTracks.find((trackNode) => {
+ const trackDescriptor = globals.trackManager.getTrack(trackNode.uri);
+ return (
+ trackDescriptor &&
+ trackDescriptor.tags?.kind === 'ThreadSliceTrack' &&
+ trackDescriptor.tags?.trackIds?.includes(trackId)
+ );
});
+
+ const parentGroup = threadSliceTrack?.parent;
+ if (parentGroup) {
+ const newTrack = new TrackNode(uri, argName);
+ parentGroup.insertBefore(newTrack, threadSliceTrack);
+ }
}
-
- globals.dispatchMultiple([
- Actions.addTracks({tracks: tracksToAdd}),
- Actions.sortThreadTracks({}),
- ]);
}
diff --git a/ui/src/frontend/widgets/process.ts b/ui/src/frontend/widgets/process.ts
index 7b668aa..a1af563 100644
--- a/ui/src/frontend/widgets/process.ts
+++ b/ui/src/frontend/widgets/process.ts
@@ -34,6 +34,25 @@
} from './sql/details/sql_ref_renderer_registry';
import {asUpid} from '../../trace_processor/sql_utils/core_types';
+export function showProcessDetailsMenuItem(
+ upid: Upid,
+ pid?: number,
+): m.Children {
+ return m(MenuItem, {
+ icon: Icons.ExternalLink,
+ label: 'Show process details',
+ onclick: () =>
+ addEphemeralTab(
+ 'processDetails',
+ new ProcessDetailsTab({
+ engine: getEngine('ProcessDetails'),
+ upid,
+ pid,
+ }),
+ ),
+ });
+}
+
export function processRefMenuItems(info: {
upid: Upid;
name?: string;
@@ -59,19 +78,7 @@
label: 'Copy upid',
onclick: () => copyToClipboard(`${info.upid}`),
}),
- m(MenuItem, {
- icon: Icons.ExternalLink,
- label: 'Show process details',
- onclick: () =>
- addEphemeralTab(
- 'processDetails',
- new ProcessDetailsTab({
- engine: getEngine('ProcessDetails'),
- upid: info.upid,
- pid: info.pid,
- }),
- ),
- }),
+ showProcessDetailsMenuItem(info.upid, info.pid),
];
}
diff --git a/ui/src/frontend/widgets/sched.ts b/ui/src/frontend/widgets/sched.ts
index 03ab24e..2ee8886 100644
--- a/ui/src/frontend/widgets/sched.ts
+++ b/ui/src/frontend/widgets/sched.ts
@@ -20,7 +20,6 @@
import {globals} from '../globals';
import {CPU_SLICE_TRACK_KIND} from '../../core/track_kinds';
import {scrollToTrackAndTs} from '../scroll_helper';
-import {exists} from '../../base/utils';
interface SchedRefAttrs {
id: SchedSqlId;
@@ -37,29 +36,21 @@
}
export function findSchedTrack(cpu: number): string | undefined {
- for (const track of Object.values(globals.state.tracks)) {
- if (exists(track?.uri)) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- if (trackInfo?.tags?.cpu === cpu) {
- return track.key;
- }
- }
- }
- }
- return undefined;
+ return globals.trackManager.findTrack((t) => {
+ return t.tags?.kind === CPU_SLICE_TRACK_KIND && t.tags.cpu === cpu;
+ })?.uri;
}
export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: time) {
- const trackKey = findSchedTrack(cpu);
- if (trackKey === undefined) {
+ const trackUri = findSchedTrack(cpu);
+ if (trackUri === undefined) {
return;
}
globals.setLegacySelection(
{
kind: 'SCHED_SLICE',
id,
- trackKey,
+ trackUri,
},
{
clearSearch: true,
@@ -68,7 +59,7 @@
},
);
- scrollToTrackAndTs(trackKey, ts);
+ scrollToTrackAndTs(trackUri, ts);
}
export class SchedRef implements m.ClassComponent<SchedRefAttrs> {
@@ -78,14 +69,14 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- const trackKey = findSchedTrack(vnode.attrs.cpu);
- if (trackKey === undefined) return;
+ const trackUri = findSchedTrack(vnode.attrs.cpu);
+ if (trackUri === undefined) return;
globals.setLegacySelection(
{
kind: 'SCHED_SLICE',
id: vnode.attrs.id,
- trackKey,
+ trackUri,
},
{
clearSearch: true,
@@ -95,7 +86,7 @@
},
);
- scrollToTrackAndTs(trackKey, vnode.attrs.ts, true);
+ scrollToTrackAndTs(trackUri, vnode.attrs.ts, true);
},
},
vnode.attrs.name ?? `Sched ${vnode.attrs.id}`,
diff --git a/ui/src/frontend/widgets/slice.ts b/ui/src/frontend/widgets/slice.ts
index 3f6dc7a..d60a10e 100644
--- a/ui/src/frontend/widgets/slice.ts
+++ b/ui/src/frontend/widgets/slice.ts
@@ -51,10 +51,11 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- const trackKeyByTrackId = globals.trackManager.trackKeyByTrackId;
- const trackKey = trackKeyByTrackId.get(vnode.attrs.sqlTrackId);
- if (trackKey === undefined) return;
- verticalScrollToTrack(trackKey, true);
+ const track = globals.trackManager.findTrack((td) => {
+ return td.tags?.trackIds?.includes(vnode.attrs.sqlTrackId);
+ });
+ if (track === undefined) return;
+ verticalScrollToTrack(track.uri, true);
// Clamp duration to 1 - i.e. for instant events
const dur = BigintMath.max(1n, vnode.attrs.dur);
focusHorizontalRange(
@@ -66,7 +67,7 @@
{
kind: 'SLICE',
id: vnode.attrs.id,
- trackKey,
+ trackUri: track.uri,
table: 'slice',
},
{
diff --git a/ui/src/frontend/widgets/sql/table/argument_selector.ts b/ui/src/frontend/widgets/sql/table/argument_selector.ts
index 0114e79..a2039af 100644
--- a/ui/src/frontend/widgets/sql/table/argument_selector.ts
+++ b/ui/src/frontend/widgets/sql/table/argument_selector.ts
@@ -15,7 +15,6 @@
import m from 'mithril';
import {raf} from '../../../../core/raf_scheduler';
-import {FilterableSelect} from '../../../../widgets/select';
import {Spinner} from '../../../../widgets/spinner';
import {
@@ -24,6 +23,11 @@
TableColumnSet,
TableManager,
} from './column';
+import {TextInput} from '../../../../widgets/text_input';
+import {scheduleFullRedraw} from '../../../../widgets/raf';
+import {hasModKey, modKey} from '../../../../base/hotkeys';
+import {MenuItem} from '../../../../widgets/menu';
+import {uuidv4} from '../../../../base/uuid';
const MAX_ARGS_TO_DISPLAY = 15;
@@ -38,33 +42,85 @@
export class ArgumentSelector
implements m.ClassComponent<ArgumentSelectorAttrs>
{
- columns?: {[key: string]: TableColumn};
+ searchText = '';
+ columns?: {key: string; column: TableColumn}[];
constructor({attrs}: m.Vnode<ArgumentSelectorAttrs>) {
this.load(attrs);
}
private async load(attrs: ArgumentSelectorAttrs) {
- const potentialColumns = await attrs.columnSet.discover(attrs.tableManager);
- this.columns = Object.fromEntries(
- potentialColumns
- .filter(
- ({column}) =>
- !attrs.alreadySelectedColumnIds.has(tableColumnId(column)),
- )
- .map(({key, column}) => [key, column]),
- );
+ this.columns = await attrs.columnSet.discover(attrs.tableManager);
raf.scheduleFullRedraw();
}
view({attrs}: m.Vnode<ArgumentSelectorAttrs>) {
const columns = this.columns;
if (columns === undefined) return m(Spinner);
- return m(FilterableSelect, {
- values: Object.keys(columns),
- onSelected: (value: string) => attrs.onArgumentSelected(columns[value]),
- maxDisplayedItems: MAX_ARGS_TO_DISPLAY,
- autofocusInput: true,
+
+ // Candidates are the columns which have not been selected yet.
+ const candidates = columns.filter(
+ ({column}) => !attrs.alreadySelectedColumnIds.has(tableColumnId(column)),
+ );
+
+ // Filter the candidates based on the search text.
+ const filtered = candidates.filter(({key}) => {
+ return key.toLowerCase().includes(this.searchText.toLowerCase());
});
+
+ const displayed = filtered.slice(0, MAX_ARGS_TO_DISPLAY);
+
+ const extraItems = Math.max(0, filtered.length - MAX_ARGS_TO_DISPLAY);
+
+ const firstButtonUuid = uuidv4();
+
+ return [
+ m(
+ '.pf-search-bar',
+ m(TextInput, {
+ autofocus: true,
+ oninput: (event: Event) => {
+ const eventTarget = event.target as HTMLTextAreaElement;
+ this.searchText = eventTarget.value;
+ scheduleFullRedraw();
+ },
+ onkeydown: (event: KeyboardEvent) => {
+ if (filtered.length === 0) return;
+ if (event.key === 'Enter') {
+ // If there is only one item or Mod-Enter was pressed, select the first element.
+ if (filtered.length === 1 || hasModKey(event)) {
+ const params = {bubbles: true};
+ if (hasModKey(event)) {
+ Object.assign(params, modKey());
+ }
+ const pointerEvent = new PointerEvent('click', params);
+ (
+ document.getElementById(firstButtonUuid) as HTMLElement | null
+ )?.dispatchEvent(pointerEvent);
+ }
+ }
+ },
+ value: this.searchText,
+ placeholder: 'Filter...',
+ className: 'pf-search-box',
+ }),
+ ),
+ ...displayed.map(({key, column}, index) =>
+ m(MenuItem, {
+ id: index === 0 ? firstButtonUuid : undefined,
+ label: key,
+ onclick: (event) => {
+ attrs.onArgumentSelected(column);
+ // For Control-Click, we don't want to close the menu to allow the user
+ // to select multiple items in one go.
+ if (hasModKey(event)) {
+ event.stopPropagation();
+ }
+ // Otherwise this popup will be closed.
+ },
+ }),
+ ),
+ Boolean(extraItems) && m('i', `+${extraItems} more`),
+ ];
}
}
diff --git a/ui/src/frontend/widgets/sql/table/column.ts b/ui/src/frontend/widgets/sql/table/column.ts
index bf1164a..1815551 100644
--- a/ui/src/frontend/widgets/sql/table/column.ts
+++ b/ui/src/frontend/widgets/sql/table/column.ts
@@ -30,6 +30,10 @@
export type SourceTable = {
table: string;
joinOn: {[key: string]: SqlColumn};
+ // Whether more performant 'INNER JOIN' can be used instead of 'LEFT JOIN'.
+ // Special care should be taken to ensure that a) all rows exist in a target table, and b) the source is not null, otherwise the rows will be filtered out.
+ // false by default.
+ innerJoin?: boolean;
};
// A column in the SQL query. It can be either a column from a base table or a "lookup" column from a joined table.
@@ -41,17 +45,31 @@
};
// A unique identifier for the SQL column.
-export function sqlColumnId(column: SqlColumn) {
+export function sqlColumnId(column: SqlColumn): string {
if (typeof column === 'string') {
return column;
}
- // If the join is performed on a single column `id`, we can use a simpler representation (i.e. `table[id].column`).
+ // Special case: If the join is performed on a single column `id`, we can use a simpler representation (i.e. `table[id].column`).
if (arrayEquals(Object.keys(column.source.joinOn), ['id'])) {
- return `${column.source.table}[${Object.values(column.source.joinOn)[0]}].${column.column}`;
+ return `${column.source.table}[${sqlColumnId(Object.values(column.source.joinOn)[0])}].${column.column}`;
+ }
+ // Special case: args lookup. For it, we can use a simpler representation (i.e. `arg_set_id[key]`).
+ if (
+ column.column === 'display_value' &&
+ column.source.table === 'args' &&
+ arrayEquals(Object.keys(column.source.joinOn).sort(), ['arg_set_id', 'key'])
+ ) {
+ const key = column.source.joinOn['key'];
+ const argSetId = column.source.joinOn['arg_set_id'];
+ return `${sqlColumnId(argSetId)}[${sqlColumnId(key)}]`;
}
// Otherwise, we need to list all the join constraints.
const lookup = Object.entries(column.source.joinOn)
- .map(([key, value]): string => `${key}=${sqlColumnId(value)}`)
+ .map(([key, value]): string => {
+ const valueStr = sqlColumnId(value);
+ if (key === valueStr) return key;
+ return `${key}=${sqlColumnId(value)}`;
+ })
.join(', ');
return `${column.source.table}[${lookup}].${column.column}`;
}
@@ -84,6 +102,10 @@
startsHidden?: boolean;
}
+export interface AggregationConfig {
+ dataType?: 'nominal' | 'quantitative';
+}
+
// Class which represents a column in a table, which can be displayed to the user.
// It is based on the primary SQL column, but also contains additional information needed for displaying it as a part of a table.
export abstract class TableColumn {
@@ -122,6 +144,11 @@
tableManager: TableManager,
dependentColumns: {[key: string]: SqlValue},
): m.Children;
+
+ // Specifies how this column should be aggregated. If not set, then all
+ // numeric columns will be treated as quantitative, and all other columns as
+ // nominal.
+ aggregation?(): AggregationConfig;
}
// Returns a unique identifier for the table column.
diff --git a/ui/src/frontend/widgets/sql/table/column_unittest.ts b/ui/src/frontend/widgets/sql/table/column_unittest.ts
new file mode 100644
index 0000000..faa9a94
--- /dev/null
+++ b/ui/src/frontend/widgets/sql/table/column_unittest.ts
@@ -0,0 +1,138 @@
+// 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 {sqlColumnId} from './column';
+import {argSqlColumn} from './well_known_columns';
+
+test('sql_column_id.basic', () => {
+ // Straightforward case: just a column selection.
+ expect(sqlColumnId('utid')).toBe('utid');
+});
+
+test('sql_column_id.single_join', () => {
+ expect(
+ sqlColumnId({
+ column: 'bar',
+ source: {
+ table: 'foo',
+ joinOn: {
+ foo_id: 'id',
+ },
+ },
+ }),
+ ).toBe('foo[foo_id=id].bar');
+});
+
+test('sql_column_id.double_join', () => {
+ expect(
+ sqlColumnId({
+ column: 'abc',
+ source: {
+ table: 'alphabet',
+ joinOn: {
+ abc_id: {
+ column: 'bar',
+ source: {
+ table: 'foo',
+ joinOn: {
+ foo_id: 'id',
+ },
+ },
+ },
+ },
+ },
+ }),
+ ).toBe('alphabet[abc_id=foo[foo_id=id].bar].abc');
+});
+
+test('sql_column_id.join_on_id', () => {
+ // Special case: joins on `id` should be simplified.
+ expect(
+ sqlColumnId({
+ column: 'name',
+ source: {
+ table: 'foo',
+ joinOn: {
+ id: 'foo_id',
+ },
+ },
+ }),
+ ).toBe('foo[foo_id].name');
+});
+
+test('sql_column_id.nested_join_on_id', () => {
+ // Special case: joins on `id` should be simplified in nested joins.
+ expect(
+ sqlColumnId({
+ column: 'name',
+ source: {
+ table: 'foo',
+ joinOn: {
+ id: {
+ column: 'foo_id',
+ source: {
+ table: 'bar',
+ joinOn: {
+ x: 'y',
+ },
+ },
+ },
+ },
+ },
+ }),
+ ).toBe('foo[bar[x=y].foo_id].name');
+});
+
+test('sql_column_id.simplied_join', () => {
+ // Special case: if both sides of the join are the same, only one can be shown.
+ expect(
+ sqlColumnId({
+ column: 'name',
+ source: {
+ table: 'foo',
+ joinOn: {
+ x: 'y',
+ z: 'z',
+ },
+ },
+ }),
+ ).toBe('foo[x=y, z].name');
+});
+
+test('sql_column_id.arg_set_id', () => {
+ // Special case: arg_set_id.
+ expect(sqlColumnId(argSqlColumn('arg_set_id', 'arg1'))).toBe(
+ "arg_set_id['arg1']",
+ );
+});
+
+test('sql_column_id.arg_set_id_with_join', () => {
+ // Special case: arg_set_id.
+ expect(
+ sqlColumnId(
+ argSqlColumn(
+ {
+ column: 'arg_set_id',
+ source: {
+ table: 'foo',
+ joinOn: {
+ x: 'y',
+ },
+ },
+ },
+ 'arg1',
+ ),
+ ),
+ ).toBe("foo[x=y].arg_set_id['arg1']");
+});
diff --git a/ui/src/frontend/widgets/sql/table/query_builder.ts b/ui/src/frontend/widgets/sql/table/query_builder.ts
index 6e23288..be3400d 100644
--- a/ui/src/frontend/widgets/sql/table/query_builder.ts
+++ b/ui/src/frontend/widgets/sql/table/query_builder.ts
@@ -36,6 +36,7 @@
type NormalisedSourceTable = {
table: string;
joinOn: {[key: string]: NormalisedSqlColumn};
+ innerJoin: boolean;
};
// Checks whether two join constraints are equal.
@@ -95,6 +96,7 @@
const table = this.tables[i];
if (
table.table === column.source.table &&
+ table.innerJoin === (column.source.innerJoin ?? false) &&
areJoinConstraintsEqual(table.joinOn, normalisedJoinOn)
) {
return {
@@ -108,6 +110,7 @@
this.tables.push({
table: column.source.table,
joinOn: normalisedJoinOn,
+ innerJoin: column.source.innerJoin ?? false,
});
return {
column: column.column,
@@ -136,7 +139,7 @@
([key, value]) => `${alias}.${key} = ${this.printColumn(value)}`,
);
// Join IDs are 0-indexed, but we want to display them as 1-indexed to reserve 0 for the primary table.
- return `LEFT JOIN ${join.table} AS ${alias} ON ${clauses.join(' AND ')}`;
+ return `${join.innerJoin ? '' : 'LEFT '}JOIN ${join.table} AS ${alias} ON ${clauses.join(' AND ')}`;
}
}
diff --git a/ui/src/frontend/widgets/sql/table/query_builder_unittest.ts b/ui/src/frontend/widgets/sql/table/query_builder_unittest.ts
index e5af636..1403dea 100644
--- a/ui/src/frontend/widgets/sql/table/query_builder_unittest.ts
+++ b/ui/src/frontend/widgets/sql/table/query_builder_unittest.ts
@@ -107,6 +107,83 @@
);
});
+// Check a query with INNER JOIN instead of LEFT JOIN.
+test('query_builder.left_join', () => {
+ expect(
+ normalise(
+ buildSqlQuery({
+ table: 'foo',
+ columns: {
+ foo_id: 'id',
+ slice_name: {
+ column: 'name',
+ source: {
+ table: 'slice',
+ innerJoin: true,
+ joinOn: {
+ id: 'slice_id',
+ },
+ },
+ },
+ },
+ }),
+ ),
+ ).toBe(
+ normalise(`
+ SELECT
+ foo_0.id AS foo_id,
+ slice_1.name AS slice_name
+ FROM foo AS foo_0
+ JOIN slice AS slice_1 ON slice_1.id = foo_0.slice_id
+ `),
+ );
+});
+
+// Check a query which has both INNER JOIN and LEFT JOIN on the same table.
+// The correct behaviour here is debatable (probably we can upgrade INNER JOIN to LEFT JOIN),
+// but for now we just generate the query with two separate joins.
+test('query_builder.left_join_and_inner_join', () => {
+ expect(
+ normalise(
+ buildSqlQuery({
+ table: 'foo',
+ columns: {
+ foo_id: 'id',
+ slice_name: {
+ column: 'name',
+ source: {
+ table: 'slice',
+ innerJoin: true,
+ joinOn: {
+ id: 'slice_id',
+ },
+ },
+ },
+ slice_depth: {
+ column: 'depth',
+ source: {
+ table: 'slice',
+ joinOn: {
+ id: 'slice_id',
+ },
+ },
+ },
+ },
+ }),
+ ),
+ ).toBe(
+ normalise(`
+ SELECT
+ foo_0.id AS foo_id,
+ slice_1.name AS slice_name,
+ slice_2.depth AS slice_depth
+ FROM foo AS foo_0
+ JOIN slice AS slice_1 ON slice_1.id = foo_0.slice_id
+ LEFT JOIN slice AS slice_2 ON slice_2.id = foo_0.slice_id
+ `),
+ );
+});
+
test('query_builder.join_with_multiple_columns', () => {
// This test checks that the query builder can correctly deduplicate joins when we request multiple columns from the joined table.
const parent: SourceTable = {
diff --git a/ui/src/frontend/widgets/sql/table/table.ts b/ui/src/frontend/widgets/sql/table/table.ts
index 3bf4b24..34f0a22 100644
--- a/ui/src/frontend/widgets/sql/table/table.ts
+++ b/ui/src/frontend/widgets/sql/table/table.ts
@@ -65,6 +65,38 @@
return sqlColumnId(column.primaryColumn());
}
+interface AddColumnMenuItemAttrs {
+ table: SqlTable;
+ state: SqlTableState;
+ index: number;
+}
+
+// This is separated into a separate class to store the index of the column to be
+// added and increment it when multiple columns are added from the same popup menu.
+class AddColumnMenuItem implements m.ClassComponent<AddColumnMenuItemAttrs> {
+ // Index where the new column should be inserted.
+ // In the regular case, a click would close the popup (destroying this class) and
+ // the `index` would not change during its lifetime.
+ // However, for mod-click, we want to keep adding columns to the right of the recently
+ // added column, so to achieve that we keep track of the index and increment it for
+ // each new column added.
+ index: number;
+
+ constructor({attrs}: m.Vnode<AddColumnMenuItemAttrs>) {
+ this.index = attrs.index;
+ }
+
+ view({attrs}: m.Vnode<AddColumnMenuItemAttrs>) {
+ return m(
+ MenuItem,
+ {label: 'Add column', icon: Icons.AddColumn},
+ attrs.table.renderAddColumnOptions((column) => {
+ attrs.state.addColumn(column, this.index++);
+ }),
+ );
+ }
+}
+
export class SqlTable implements m.ClassComponent<SqlTableConfig> {
private readonly table: SqlTableDescription;
@@ -201,6 +233,7 @@
query: this.state.getSqlQuery(
Object.fromEntries([[columnAlias, column.primaryColumn()]]),
),
+ aggregationType: column.aggregation?.().dataType,
},
this.state.engine,
);
@@ -209,13 +242,7 @@
// Menu items before divider apply to selected column
m(MenuDivider),
// Menu items after divider apply to entire table
- m(
- MenuItem,
- {label: 'Add column', icon: Icons.AddColumn},
- this.renderAddColumnOptions((column) => {
- this.state.addColumn(column, index);
- }),
- ),
+ m(AddColumnMenuItem, {table: this, state: this.state, index}),
);
}
diff --git a/ui/src/frontend/widgets/sql/table/well_known_columns.ts b/ui/src/frontend/widgets/sql/table/well_known_columns.ts
index 954e2ca..e41e85e 100644
--- a/ui/src/frontend/widgets/sql/table/well_known_columns.ts
+++ b/ui/src/frontend/widgets/sql/table/well_known_columns.ts
@@ -14,25 +14,10 @@
import m from 'mithril';
-import {
- TableColumn,
- TableColumnSet,
- TableManager,
- SqlColumn,
- sqlColumnId,
- TableColumnParams,
- SourceTable,
-} from './column';
-import {
- getStandardContextMenuItems,
- getStandardFilters,
- renderStandardCell,
-} from './render_cell_utils';
-import {Timestamp} from '../../timestamp';
+import {Icons} from '../../../../base/semantic_icons';
+import {sqliteString} from '../../../../base/string_utils';
import {Duration, Time} from '../../../../base/time';
-import {DurationWidget} from '../../duration';
-import {renderError} from '../../../../widgets/error';
-import {SliceRef} from '../../slice';
+import {SqlValue, STR} from '../../../../trace_processor/query_result';
import {
asSchedSqlId,
asSliceSqlId,
@@ -40,26 +25,53 @@
asUpid,
asUtid,
} from '../../../../trace_processor/sql_utils/core_types';
-import {sqliteString} from '../../../../base/string_utils';
-import {ThreadStateRef} from '../../thread_state';
-import {MenuDivider, MenuItem, PopupMenu2} from '../../../../widgets/menu';
+import {getProcessName} from '../../../../trace_processor/sql_utils/process';
import {getThreadName} from '../../../../trace_processor/sql_utils/thread';
import {Anchor} from '../../../../widgets/anchor';
-import {threadRefMenuItems} from '../../thread';
-import {Icons} from '../../../../base/semantic_icons';
-import {SqlValue, STR} from '../../../../trace_processor/query_result';
-import {getProcessName} from '../../../../trace_processor/sql_utils/process';
+import {renderError} from '../../../../widgets/error';
+import {MenuDivider, MenuItem, PopupMenu2} from '../../../../widgets/menu';
+import {DurationWidget} from '../../duration';
import {processRefMenuItems} from '../../process';
import {SchedRef} from '../../sched';
+import {SliceRef} from '../../slice';
+import {threadRefMenuItems} from '../../thread';
+import {ThreadStateRef} from '../../thread_state';
+import {Timestamp} from '../../timestamp';
-type ColumnParams = TableColumnParams & {
+import {
+ AggregationConfig,
+ SourceTable,
+ SqlColumn,
+ sqlColumnId,
+ TableColumn,
+ TableColumnParams,
+ TableColumnSet,
+ TableManager,
+} from './column';
+import {
+ getStandardContextMenuItems,
+ getStandardFilters,
+ renderStandardCell,
+} from './render_cell_utils';
+
+export type ColumnParams = TableColumnParams & {
title?: string;
};
+export type StandardColumnParams = ColumnParams & {
+ aggregationType?: 'nominal' | 'quantitative';
+};
+
+export interface IdColumnParams {
+ // Whether the column is guaranteed not to have null values.
+ // (this will allow us to upgrage the joins on this column to more performant INNER JOINs).
+ notNull?: boolean;
+}
+
export class StandardColumn extends TableColumn {
constructor(
private column: SqlColumn,
- private params?: ColumnParams,
+ private params?: StandardColumnParams,
) {
super(params);
}
@@ -68,6 +80,10 @@
return this.column;
}
+ aggregate(): AggregationConfig {
+ return {dataType: this.params?.aggregationType};
+ }
+
getTitle() {
return this.params?.title;
}
@@ -151,13 +167,15 @@
constructor(
private id: SqlColumn,
- private params?: ColumnParams,
+ private params?: ColumnParams & IdColumnParams,
) {
super(params);
const sliceTable: SourceTable = {
table: 'slice',
joinOn: {id: this.id},
+ // If the column is guaranteed not to have null values, we can use an INNER JOIN.
+ innerJoin: this.params?.notNull === true,
};
this.columns = {
@@ -231,13 +249,15 @@
constructor(
private id: SqlColumn,
- private params?: ColumnParams,
+ private params?: ColumnParams & IdColumnParams,
) {
super(params);
const schedTable: SourceTable = {
table: 'sched',
joinOn: {id: this.id},
+ // If the column is guaranteed not to have null values, we can use an INNER JOIN.
+ innerJoin: this.params?.notNull === true,
};
this.columns = {
@@ -315,13 +335,15 @@
constructor(
private id: SqlColumn,
- private params?: ColumnParams,
+ private params?: ColumnParams & IdColumnParams,
) {
super(params);
const threadStateTable: SourceTable = {
table: 'thread_state',
joinOn: {id: this.id},
+ // If the column is guaranteed not to have null values, we can use an INNER JOIN.
+ innerJoin: this.params?.notNull === true,
};
this.columns = {
@@ -399,13 +421,15 @@
constructor(
private utid: SqlColumn,
- private params?: ColumnParams,
+ private params?: ColumnParams & IdColumnParams,
) {
super(params);
const threadTable: SourceTable = {
table: 'thread',
joinOn: {id: this.utid},
+ // If the column is guaranteed not to have null values, we can use an INNER JOIN.
+ innerJoin: this.params?.notNull === true,
};
this.columns = {
@@ -504,6 +528,10 @@
),
);
}
+
+ aggregation(): AggregationConfig {
+ return {dataType: 'nominal'};
+ }
}
export class ProcessColumn extends TableColumn {
@@ -511,13 +539,15 @@
constructor(
private upid: SqlColumn,
- private params?: ColumnParams,
+ private params?: ColumnParams & IdColumnParams,
) {
super(params);
const processTable: SourceTable = {
table: 'process',
joinOn: {id: this.upid},
+ // If the column is guaranteed not to have null values, we can use an INNER JOIN.
+ innerJoin: this.params?.notNull === true,
};
this.columns = {
@@ -613,6 +643,10 @@
),
);
}
+
+ aggregation(): AggregationConfig {
+ return {dataType: 'nominal'};
+ }
}
export class ArgSetColumnSet extends TableColumnSet {
diff --git a/ui/src/frontend/widgets/thread.ts b/ui/src/frontend/widgets/thread.ts
index 1ffab44..5cdad79 100644
--- a/ui/src/frontend/widgets/thread.ts
+++ b/ui/src/frontend/widgets/thread.ts
@@ -34,6 +34,25 @@
import {asUtid} from '../../trace_processor/sql_utils/core_types';
import {Utid} from '../../trace_processor/sql_utils/core_types';
+export function showThreadDetailsMenuItem(
+ utid: Utid,
+ tid?: number,
+): m.Children {
+ return m(MenuItem, {
+ icon: Icons.ExternalLink,
+ label: 'Show thread details',
+ onclick: () =>
+ addEphemeralTab(
+ 'threadDetails',
+ new ThreadDetailsTab({
+ engine: getEngine('ThreadDetails'),
+ utid,
+ tid,
+ }),
+ ),
+ });
+}
+
export function threadRefMenuItems(info: {
utid: Utid;
name?: string;
@@ -59,19 +78,7 @@
label: 'Copy utid',
onclick: () => copyToClipboard(`${info.utid}`),
}),
- m(MenuItem, {
- icon: Icons.ExternalLink,
- label: 'Show thread details',
- onclick: () =>
- addEphemeralTab(
- 'threadDetails',
- new ThreadDetailsTab({
- engine: getEngine('ThreadDetails'),
- utid: info.utid,
- tid: info.tid,
- }),
- ),
- }),
+ showThreadDetailsMenuItem(info.utid, info.tid),
];
}
diff --git a/ui/src/frontend/widgets/thread_state.ts b/ui/src/frontend/widgets/thread_state.ts
index a572666..fdb86b5 100644
--- a/ui/src/frontend/widgets/thread_state.ts
+++ b/ui/src/frontend/widgets/thread_state.ts
@@ -46,25 +46,21 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- let trackKey: string | undefined;
- for (const track of Object.values(globals.state.tracks)) {
- const trackDesc = globals.trackManager.resolveTrackInfo(track.uri);
- if (
- trackDesc &&
- trackDesc.tags?.kind === THREAD_STATE_TRACK_KIND &&
- trackDesc.tags?.utid === vnode.attrs.utid
- ) {
- trackKey = track.key;
- }
- }
+ const trackDescriptor = globals.trackManager
+ .getAllTracks()
+ .find(
+ (td) =>
+ td.tags?.kind === THREAD_STATE_TRACK_KIND &&
+ td.tags?.utid === vnode.attrs.utid,
+ );
- if (trackKey === undefined) return;
+ if (trackDescriptor === undefined) return;
globals.setLegacySelection(
{
kind: 'THREAD_STATE',
id: vnode.attrs.id,
- trackKey,
+ trackUri: trackDescriptor.uri,
},
{
clearSearch: true,
@@ -74,7 +70,7 @@
},
);
- scrollToTrackAndTs(trackKey, vnode.attrs.ts, true);
+ scrollToTrackAndTs(trackDescriptor.uri, vnode.attrs.ts, true);
},
},
vnode.attrs.name ?? `Thread State ${vnode.attrs.id}`,
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 9b258b1..e61be11 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -37,7 +37,7 @@
} from '../widgets/multiselect';
import {Popup, PopupPosition} from '../widgets/popup';
import {Portal} from '../widgets/portal';
-import {FilterableSelect, Select} from '../widgets/select';
+import {Select} from '../widgets/select';
import {Spinner} from '../widgets/spinner';
import {Switch} from '../widgets/switch';
import {TextInput} from '../widgets/text_input';
@@ -724,14 +724,6 @@
},
}),
m(WidgetShowcase, {
- label: 'Filterable Select',
- renderWidget: () =>
- m(FilterableSelect, {
- values: ['foo', 'bar', 'baz'],
- onSelected: () => {},
- }),
- }),
- m(WidgetShowcase, {
label: 'Empty State',
renderWidget: ({header, content}) =>
m(
diff --git a/ui/src/plugins/com.google.android.GoogleCamera/index.ts b/ui/src/plugins/com.google.android.GoogleCamera/index.ts
index 3a33a5f..5f7ca77 100644
--- a/ui/src/plugins/com.google.android.GoogleCamera/index.ts
+++ b/ui/src/plugins/com.google.android.GoogleCamera/index.ts
@@ -16,7 +16,6 @@
PerfettoPlugin,
PluginContextTrace,
PluginDescriptor,
- TrackRef,
} from '../../public';
import * as cameraConstants from './googleCameraConstants';
@@ -58,15 +57,11 @@
this.pinTracks(cameraConstants.STARTUP_RELATED_TRACKS);
}
- private pinTracks(trackNames: string[]) {
- const tracks: TrackRef[] = this.ctx.timeline.tracks;
- trackNames.forEach((trackName) => {
- const desiredTracks = tracks.filter((track) => {
- return track.title.match(trackName);
- });
- desiredTracks.forEach((desiredTrack) => {
- this.ctx.timeline.pinTrack(desiredTrack.key!);
- });
+ private pinTracks(trackNames: ReadonlyArray<string>) {
+ this.ctx.timeline.workspace.flatTracks.forEach((track) => {
+ if (trackNames.includes(track.displayName)) {
+ track.pin();
+ }
});
}
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts b/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts
index e661076..88043b0 100644
--- a/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts
@@ -14,11 +14,15 @@
import {globals} from '../../frontend/globals';
import {SimpleSliceTrackConfig} from '../../frontend/simple_slice_track';
-import {addDebugSliceTrack, PluginContextTrace} from '../../public';
+import {
+ addDebugSliceTrack,
+ PluginContextTrace,
+ TrackDescriptor,
+} from '../../public';
import {findCurrentSelection} from '../../frontend/keyboard_event_handler';
import {time, Time} from '../../base/time';
import {BigintMath} from '../../base/bigint_math';
-import {reveal} from '../../frontend/scroll_helper';
+import {scrollToTrackAndTimeSpan} from '../../frontend/scroll_helper';
/**
* Adds debug tracks from SimpleSliceTrackConfig
@@ -83,12 +87,12 @@
return;
}
const trackId = slice.trackId;
- const trackKey = getTrackKey(trackId);
+ const track = getTrackForTrackId(trackId);
globals.setLegacySelection(
{
kind: 'SLICE',
id: slice.sliceId,
- trackKey: trackKey,
+ trackUri: track?.uri,
table: 'slice',
},
{
@@ -101,13 +105,14 @@
}
/**
- * Given the trackId of the track, retrieves its trackKey
+ * Given the trackId of the track, retrieves its corresponding TrackDescriptor.
*
- * @param {number} trackId track_id of the track
- * @returns {string} trackKey given to the track with queried trackId
+ * @param trackId track_id of the track
*/
-function getTrackKey(trackId: number): string | undefined {
- return globals.trackManager.trackKeyByTrackId.get(trackId);
+function getTrackForTrackId(trackId: number): TrackDescriptor | undefined {
+ return globals.trackManager.findTrack((trackDescriptor) => {
+ return trackDescriptor?.tags?.trackIds?.includes(trackId);
+ });
}
/**
@@ -131,10 +136,15 @@
const sliceStart = slice.ts;
// row.dur can be negative. Clamp to 1ns.
const sliceDur = BigintMath.max(slice.dur, 1n);
- const trackKey = getTrackKey(trackId);
+ const track = getTrackForTrackId(trackId);
// true for whether to expand the process group the track belongs to
- if (trackKey == undefined) {
+ if (track == undefined) {
return;
}
- reveal(trackKey, sliceStart, Time.add(sliceStart, sliceDur), true);
+ scrollToTrackAndTimeSpan(
+ track.uri,
+ sliceStart,
+ Time.add(sliceStart, sliceDur),
+ true,
+ );
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 90342b7..99c8b98 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -27,6 +27,7 @@
SimpleCounterTrack,
SimpleCounterTrackConfig,
} from '../../frontend/simple_counter_track';
+import {globals} from '../../frontend/globals';
interface ContainedTrace {
uuid: string;
@@ -1173,13 +1174,11 @@
} else {
uri = `/long_battery_tracing_${name}`;
}
- ctx.registerStaticTrack({
+ ctx.registerTrackAndShowOnTraceLoad({
uri,
title: name,
- trackFactory: (trackCtx) => {
- return new SimpleSliceTrack(ctx.engine, trackCtx, config);
- },
- groupName,
+ track: new SimpleSliceTrack(ctx.engine, {trackUri: uri}, config),
+ tags: {groupName},
});
}
@@ -1206,13 +1205,11 @@
uri = `/long_battery_tracing_${name}`;
}
- ctx.registerStaticTrack({
+ ctx.registerTrackAndShowOnTraceLoad({
uri,
title: name,
- trackFactory: (trackCtx) => {
- return new SimpleCounterTrack(ctx.engine, trackCtx, config);
- },
- groupName,
+ track: new SimpleCounterTrack(ctx.engine, {trackUri: uri}, config),
+ tags: {groupName},
});
}
@@ -1466,6 +1463,41 @@
);
const e = ctx.engine;
+
+ if (
+ globals.extraSqlPackages.find((x) => x.name === 'google3') !== undefined
+ ) {
+ await e.query(
+ `INCLUDE PERFETTO MODULE
+ google3.wireless.android.telemetry.trace_extractor.modules.modem_tea_metrics`,
+ );
+ const counters = await e.query(
+ `select distinct name from pixel_modem_counters`,
+ );
+ const countersIt = counters.iter({name: 'str'});
+ for (; countersIt.valid(); countersIt.next()) {
+ this.addCounterTrack(
+ ctx,
+ countersIt.name,
+ `select ts, value from pixel_modem_counters where name = '${countersIt.name}'`,
+ groupName,
+ );
+ }
+ const slices = await e.query(
+ `select distinct track_name from pixel_modem_slices`,
+ );
+ const slicesIt = slices.iter({track_name: 'str'});
+ for (; slicesIt.valid(); slicesIt.next()) {
+ this.addSliceTrack(
+ ctx,
+ it.name,
+ `select ts dur, slice_name as name from pixel_modem_counters
+ where track_name = '${slicesIt.track_name}'`,
+ groupName,
+ );
+ }
+ }
+
await e.query(MODEM_RIL_STRENGTH);
await e.query(MODEM_RIL_CHANNELS_PREAMBLE);
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
index fb7586f..69754fd 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts
@@ -46,7 +46,7 @@
process_name,
intent,
'slice' AS table_name
- FROM _android_app_process_starts
+ FROM android_app_process_starts
WHERE reason = '${reason}'
`,
columns: sliceColumns,
diff --git a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
index 59ae3d5..45541e1 100644
--- a/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
@@ -44,12 +44,11 @@
columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: [],
};
- ctx.registerStaticTrack({
- uri: `/android_startups`,
+ const uri = `/android_startups`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: 'Android App Startups',
- trackFactory: (trackCtx) => {
- return new SimpleSliceTrack(ctx.engine, trackCtx, config);
- },
+ track: new SimpleSliceTrack(ctx.engine, {trackUri: uri}, config),
});
}
}
diff --git a/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts b/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
index d57ad70..1075645 100644
--- a/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
+++ b/ui/src/plugins/dev.perfetto.GpuByProcess/index.ts
@@ -82,12 +82,11 @@
processName = `${it.pid}`;
}
- ctx.registerStaticTrack({
- uri: `dev.perfetto.GpuByProcess#${upid}`,
+ const uri = `dev.perfetto.GpuByProcess#${upid}`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: `GPU ${processName}`,
- trackFactory: ({trackKey}) => {
- return new GpuPidTrack({engine: ctx.engine, trackKey}, upid);
- },
+ track: new GpuPidTrack({engine: ctx.engine, uri}, upid),
});
}
}
diff --git a/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts b/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
index 6a302a9..dc06537 100644
--- a/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.LargeScreensPerf/index.ts
@@ -24,19 +24,21 @@
id: 'dev.perfetto.LargeScreensPerf#PinUnfoldLatencyTracks',
name: 'Pin: Unfold latency tracks',
callback: () => {
- ctx.timeline.pinTracksByPredicate((track) => {
- return (
- !!track.title.includes('UnfoldTransition') ||
- track.title.includes('Screen on blocked') ||
- track.title.includes('hingeAngle') ||
- track.title.includes('UnfoldLightRevealOverlayAnimation') ||
- track.title.startsWith('waitForAllWindowsDrawn') ||
- track.title.endsWith('UNFOLD_ANIM>') ||
- track.title.endsWith('UNFOLD>') ||
- track.title == 'Waiting for KeyguardDrawnCallback#onDrawn' ||
- track.title == 'FoldedState' ||
- track.title == 'FoldUpdate'
- );
+ ctx.timeline.workspace.flatTracks.forEach((track) => {
+ if (
+ !!track.displayName.includes('UnfoldTransition') ||
+ track.displayName.includes('Screen on blocked') ||
+ track.displayName.includes('hingeAngle') ||
+ track.displayName.includes('UnfoldLightRevealOverlayAnimation') ||
+ track.displayName.startsWith('waitForAllWindowsDrawn') ||
+ track.displayName.endsWith('UNFOLD_ANIM>') ||
+ track.displayName.endsWith('UNFOLD>') ||
+ track.displayName == 'Waiting for KeyguardDrawnCallback#onDrawn' ||
+ track.displayName == 'FoldedState' ||
+ track.displayName == 'FoldUpdate'
+ ) {
+ track.pin();
+ }
});
},
});
diff --git a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/OWNERS b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/OWNERS
index b655639..de52817 100644
--- a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/OWNERS
+++ b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/OWNERS
@@ -1,4 +1,3 @@
-paulsoumyadeep@google.com
nishantpanwar@google.com
bvineeth@google.com
nicomazz@google.com
\ No newline at end of file
diff --git a/ui/src/plugins/dev.perfetto.PinSysUITracks/OWNERS b/ui/src/plugins/dev.perfetto.PinSysUITracks/OWNERS
index b655639..de52817 100644
--- a/ui/src/plugins/dev.perfetto.PinSysUITracks/OWNERS
+++ b/ui/src/plugins/dev.perfetto.PinSysUITracks/OWNERS
@@ -1,4 +1,3 @@
-paulsoumyadeep@google.com
nishantpanwar@google.com
bvineeth@google.com
nicomazz@google.com
\ No newline at end of file
diff --git a/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts b/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts
index 025c91c..b67c836 100644
--- a/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts
+++ b/ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts
@@ -54,21 +54,24 @@
id: 'dev.perfetto.PinSysUITracks#PinSysUITracks',
name: 'Pin: System UI Related Tracks',
callback: () => {
- ctx.timeline.pinTracksByPredicate((track) => {
- if (!track.uri.startsWith(`/process_${sysuiUpid}`)) return false;
+ ctx.timeline.workspace.flatTracks.forEach((track) => {
+ // Ensure we only grab tracks that are in the SysUI process group
+ if (!track.uri.startsWith(`/process_${sysuiUpid}`)) return;
if (
!TRACKS_TO_PIN.some((trackName) =>
- track.title.startsWith(trackName),
+ track.displayName.startsWith(trackName),
)
) {
- return false;
+ return;
}
- return true;
+ track.pin();
});
// expand the sysui process tracks group
- ctx.timeline.expandGroupsByPredicate((groupRef) => {
- return groupRef.displayName?.startsWith(SYSTEM_UI_PROCESS) ?? false;
+ ctx.timeline.workspace.flatGroups.forEach((group) => {
+ if (group.displayName.startsWith(SYSTEM_UI_PROCESS)) {
+ group.expand();
+ }
});
},
});
diff --git a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
index a8a612f..bdafb8a 100644
--- a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
+++ b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {Optional} from '../../base/utils';
+import {GroupNode, TrackNode} from '../../public/workspace';
import {
PerfettoPlugin,
PluginContext,
PluginContextTrace,
PluginDescriptor,
- TrackRef,
} from '../../public';
const PLUGIN_ID = 'dev.perfetto.RestorePinnedTrack';
@@ -54,12 +55,11 @@
}
private saveTracks() {
- const pinnedTracks = this.ctx.timeline.tracks.filter(
- (trackRef) => trackRef.isPinned,
- );
- const tracksToSave: SavedPinnedTrack[] = pinnedTracks.map((trackRef) => ({
- groupName: trackRef.groupName,
- trackName: trackRef.title,
+ const workspace = this.ctx.timeline.workspace;
+ const pinnedTracks = workspace.pinnedTracks;
+ const tracksToSave: SavedPinnedTrack[] = pinnedTracks.map((track) => ({
+ groupName: groupName(track),
+ trackName: track.displayName,
}));
window.localStorage.setItem(SAVED_TRACKS_KEY, JSON.stringify(tracksToSave));
}
@@ -71,19 +71,19 @@
return;
}
const tracksToRestore: SavedPinnedTrack[] = JSON.parse(savedTracks);
- const tracks: TrackRef[] = this.ctx.timeline.tracks;
+ const workspace = this.ctx.timeline.workspace;
+ const tracks = workspace.flatTracks;
tracksToRestore.forEach((trackToRestore) => {
// Check for an exact match
const exactMatch = tracks.find((track) => {
return (
- track.key &&
- trackToRestore.trackName === track.title &&
- trackToRestore.groupName === track.groupName
+ trackToRestore.trackName === track.displayName &&
+ trackToRestore.groupName === groupName(track)
);
});
if (exactMatch) {
- this.ctx.timeline.pinTrack(exactMatch.key!);
+ exactMatch.pin();
} else {
// We attempt a match after removing numbers to potentially pin a
// "similar" track from a different trace. Removing numbers allows
@@ -96,16 +96,15 @@
// other process matching the package name is attempted.
const fuzzyMatch = tracks.find((track) => {
return (
- track.key &&
this.removeNumbers(trackToRestore.trackName) ===
- this.removeNumbers(track.title) &&
+ this.removeNumbers(track.displayName) &&
this.removeNumbers(trackToRestore.groupName) ===
- this.removeNumbers(track.groupName)
+ this.removeNumbers(groupName(track))
);
});
if (fuzzyMatch) {
- this.ctx.timeline.pinTrack(fuzzyMatch.key!);
+ fuzzyMatch.pin();
} else {
console.warn(
'[RestorePinnedTracks] No track found that matches',
@@ -121,6 +120,16 @@
}
}
+// Return the displayname of the containing group
+// If the track is a child of a workspace, return undefined...
+function groupName(track: TrackNode): Optional<string> {
+ const parent = track.parent;
+ if (parent instanceof GroupNode) {
+ return parent.displayName;
+ }
+ return undefined;
+}
+
interface SavedPinnedTrack {
// Optional: group name for the track. Usually matches with process name.
groupName?: string;
diff --git a/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts b/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
index 65ce8dd..a5a7fcc 100644
--- a/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
+++ b/ui/src/plugins/dev.perfetto.TraceMetadata/index.ts
@@ -29,11 +29,14 @@
if (row.cnt === 0) {
return;
}
- ctx.registerStaticTrack({
- uri: `/clock_snapshots`,
+ const uri = `/clock_snapshots`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: 'Clock Snapshots',
- trackFactory: (trackCtx) => {
- return new SimpleSliceTrack(ctx.engine, trackCtx, {
+ track: new SimpleSliceTrack(
+ ctx.engine,
+ {trackUri: uri},
+ {
data: {
sqlSource: `
select ts, 0 as dur, 'Snapshot' as name
@@ -43,8 +46,8 @@
},
columns: {ts: 'ts', dur: 'dur', name: 'name'},
argColumns: [],
- });
- },
+ },
+ ),
});
}
}
diff --git a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
index 4f1ac0e..613aa08 100644
--- a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
+++ b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
@@ -44,24 +44,23 @@
const trackId = it.trackId;
const displayName = it.name ?? `${trackId}`;
- ctx.registerStaticTrack({
- uri: `/kernel_devices/${displayName}`,
+ const uri = `/kernel_devices/${displayName}`;
+ ctx.registerTrackAndShowOnTraceLoad({
+ uri,
title: displayName,
- trackFactory: ({trackKey}) => {
- return new AsyncSliceTrack(
- {
- engine: ctx.engine,
- trackKey,
- },
- 0,
- [trackId],
- );
- },
+ track: new AsyncSliceTrack(
+ {
+ engine: ctx.engine,
+ uri,
+ },
+ 0,
+ [trackId],
+ ),
tags: {
kind: ASYNC_SLICE_TRACK_KIND,
trackIds: [trackId],
+ groupName: `Linux Kernel Devices`,
},
- groupName: `Linux Kernel Devices`,
});
}
}
diff --git a/ui/src/public/exposed_commands.ts b/ui/src/public/exposed_commands.ts
new file mode 100644
index 0000000..d75275e
--- /dev/null
+++ b/ui/src/public/exposed_commands.ts
@@ -0,0 +1,26 @@
+// 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.
+
+// This file contains constants for some command IDs that are used directly
+// from frontend code (e.g. the details panel that has buttons for critical
+// path). They exist to deal with all cases where some feature cannot be done
+// just with the existing API (e.g. the command palette), and a more direct
+// coupling between frontend and commands is necessary.
+// Adding entries to this file usually is the symptom of a missing API in the
+// plugin surface (e.g. the ability to customize context menus).
+// These constants exist just to make the dependency evident at code
+// search time, rather than copy-pasting the string in two places.
+
+export const CRITICAL_PATH_CMD = 'perfetto.CriticalPath';
+export const CRITICAL_PATH_LITE_CMD = 'perfetto.CriticalPathLite';
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index fbb42a0..63eeb30 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -18,11 +18,11 @@
import {TimeSpan, duration, time} from '../base/time';
import {Migrate, Store} from '../base/store';
import {ColorScheme} from '../core/colorizer';
-import {PrimaryTrackSortKey} from '../common/state';
import {Engine} from '../trace_processor/engine';
import {PromptOption} from '../frontend/omnibox_manager';
import {LegacyDetailsPanel, TrackDescriptor} from './tracks';
import {TraceContext} from '../frontend/trace_context';
+import {Workspace} from './workspace';
export {Engine} from '../trace_processor/engine';
export {
@@ -36,7 +36,6 @@
export {BottomTabToSCSAdapter} from './utils';
export {createStore, Migrate, Store} from '../base/store';
export {PromptOption} from '../frontend/omnibox_manager';
-export {PrimaryTrackSortKey} from '../common/state';
export {addDebugSliceTrack} from '../frontend/debug_tracks/debug_tracks';
export * from '../core/track_kinds';
@@ -164,41 +163,6 @@
addSidebarMenuItem(menuItem: SidebarMenuItem): void;
}
-export interface SliceTrackColNames {
- ts: string;
- name: string;
- dur: string;
-}
-
-export interface DebugSliceTrackArgs {
- // Title of the track. If omitted a placeholder name will be chosen instead.
- trackName?: string;
-
- // Mapping definitions of the 'ts', 'dur', and 'name' columns.
- // By default, columns called ts, dur and name will be used.
- // If dur is assigned the value '0', all slices shall be instant events.
- columnMapping?: Partial<SliceTrackColNames>;
-
- // Any extra columns to be used as args.
- args?: string[];
-
- // Optional renaming of columns.
- columns?: string[];
-}
-
-export interface CounterTrackColNames {
- ts: string;
- value: string;
-}
-
-export interface DebugCounterTrackArgs {
- // Title of the track. If omitted a placeholder name will be chosen instead.
- trackName?: string;
-
- // Mapping definitions of the ts and value columns.
- columnMapping?: Partial<CounterTrackColNames>;
-}
-
export interface Tab {
render(): m.Children;
getTitle(): string;
@@ -220,37 +184,6 @@
// Control over the main timeline.
timeline: {
- // Add a new track to the scrolling track section, returning the newly
- // created track key.
- addTrack(uri: string, displayName: string, params?: unknown): string;
-
- // Remove a single track from the timeline.
- removeTrack(key: string): void;
-
- // Pin a single track.
- pinTrack(key: string): void;
-
- // Unpin a single track.
- unpinTrack(key: string): void;
-
- // Pin all tracks that match a predicate.
- pinTracksByPredicate(predicate: TrackPredicate): void;
-
- // Unpin all tracks that match a predicate.
- unpinTracksByPredicate(predicate: TrackPredicate): void;
-
- // Remove all tracks that match a predicate.
- removeTracksByPredicate(predicate: TrackPredicate): void;
-
- // Expand all groups that match a predicate.
- expandGroupsByPredicate(predicate: GroupPredicate): void;
-
- // Collapse all groups that match a predicate.
- collapseGroupsByPredicate(predicate: GroupPredicate): void;
-
- // Retrieve a list of tracks on the timeline.
- tracks: TrackRef[];
-
// Bring a timestamp into view.
panToTimestamp(ts: time): void;
@@ -259,6 +192,10 @@
// A span representing the current viewport location
readonly viewport: TimeSpan;
+
+ // Access the default workspace - used for adding, removing and reorganizing
+ // tracks
+ readonly workspace: Workspace;
};
// Control over the bottom details pane.
@@ -273,23 +210,16 @@
hideTab(uri: string): void;
};
- // Register a new track against a unique key known as a URI.
- // Once a track is registered it can be referenced multiple times on the
- // timeline with different params to allow customising each instance.
+ // Register a new track against a unique key known as a URI. The track is not
+ // shown by default and callers need to either manually add it to a
+ // Workspace or use registerTrackAndShowOnTraceLoad() below.
registerTrack(trackDesc: TrackDescriptor): void;
- // Add a new entry to the pool of default tracks. Default tracks are a list
- // of track references that describe the list of tracks that should be added
- // to the main timeline on startup.
- // Default tracks are only used when a trace is first loaded, not when
- // loading from a permalink, where the existing list of tracks from the
+ // Register a track and mark it as "automatically show on trace load".
+ // These tracks are shown only when the trace is loaded from scratch, not
+ // when loading from a permalink, where the existing list of tracks from the
// shared state is used instead.
- addDefaultTrack(track: TrackRef): void;
-
- // Simultaneously register a track and add it as a default track in one go.
- // This is simply a helper which calls registerTrack() and addDefaultTrack()
- // with the same URI.
- registerStaticTrack(track: TrackDescriptor & TrackRef): void;
+ registerTrackAndShowOnTraceLoad(track: TrackDescriptor): void;
// Register a new tab for this plugin. Will be unregistered when the plugin
// is deactivated or when the trace is unloaded.
@@ -342,43 +272,6 @@
new (): PerfettoPlugin;
}
-// Describes a reference to a registered track.
-export interface TrackRef {
- // URI of the registered track.
- readonly uri: string;
-
- // A human readable name for this track - displayed in the track shell.
- readonly title: string;
-
- // Optional: Used to define default sort order for new traces.
- // Note: This will be deprecated soon in favour of tags & sort rules.
- readonly sortKey?: PrimaryTrackSortKey;
-
- // Optional: Add tracks to a group with this name.
- readonly groupName?: string;
-
- // Optional: Track key
- readonly key?: string;
-
- // Optional: Whether the track is pinned
- readonly isPinned?: boolean;
-}
-
-// A predicate for selecting a subset of tracks.
-export type TrackPredicate = (info: TrackDescriptor) => boolean;
-
-// Describes a reference to a group of tracks.
-export interface GroupRef {
- // A human readable name for this track group.
- displayName: string;
-
- // True if the track is open else false.
- collapsed: boolean;
-}
-
-// A predicate for selecting a subset of groups.
-export type GroupPredicate = (info: GroupRef) => boolean;
-
// Plugins can be class refs or concrete plugin implementations.
export type PluginFactory = PluginClass | PerfettoPlugin;
diff --git a/ui/src/public/tracks.ts b/ui/src/public/tracks.ts
index b45ab34..fb0c53f 100644
--- a/ui/src/public/tracks.ts
+++ b/ui/src/public/tracks.ts
@@ -22,8 +22,8 @@
import {HighPrecisionTimeSpan} from '../common/high_precision_time_span';
export interface TrackContext {
- // This track's key, used for making selections et al.
- readonly trackKey: string;
+ // This track's URI, used for making selections et al.
+ readonly trackUri: string;
}
/**
@@ -69,7 +69,7 @@
readonly uri: string;
// A factory function returning a new track instance.
- readonly trackFactory: (ctx: TrackContext) => Track;
+ readonly track: Track;
// Human readable title. Always displayed.
readonly title: string;
diff --git a/ui/src/public/workspace.ts b/ui/src/public/workspace.ts
new file mode 100644
index 0000000..d286b57
--- /dev/null
+++ b/ui/src/public/workspace.ts
@@ -0,0 +1,287 @@
+// 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 {Optional} from '../base/utils';
+import {uuidv4} from '../base/uuid';
+import {raf} from '../core/raf_scheduler';
+
+export class TrackNode {
+ // This is the URI of the track this node references.
+ public readonly uri: string;
+ public displayName: string;
+ public parent?: ContainerNode;
+
+ constructor(uri: string, displayName: string) {
+ this.uri = uri;
+ this.displayName = displayName;
+ }
+
+ // Expand all ancestors
+ reveal(): void {
+ let parent = this.parent;
+ while (parent && parent instanceof GroupNode) {
+ parent.expand();
+ parent = parent.parent;
+ }
+ }
+
+ get workspace(): Optional<Workspace> {
+ let parent = this.parent;
+ while (parent && !(parent instanceof Workspace)) {
+ parent = parent.parent;
+ }
+ return parent;
+ }
+
+ remove(): void {
+ this.workspace?.unpinTrack(this);
+ this.parent?.removeChild(this);
+ }
+
+ pin(): void {
+ this.workspace?.pinTrack(this);
+ }
+
+ unpin(): void {
+ this.workspace?.unpinTrack(this);
+ }
+
+ get isPinned(): boolean {
+ return Boolean(this.workspace?.pinnedTracks.includes(this));
+ }
+
+ get closestVisibleAncestor(): Optional<GroupNode> {
+ // Build a path back up to the root.
+ const path: ContainerNode[] = [];
+ let group = this.parent;
+ while (group) {
+ path.unshift(group);
+ group = group.parent;
+ }
+
+ // Find the first collapsed group in the path starting from the root.
+ // This will be the last ancestor which isn't collapsed behind a group.
+ for (const p of path) {
+ if (p instanceof GroupNode && p.collapsed) {
+ return p;
+ }
+ }
+
+ return undefined;
+ }
+}
+
+/**
+ * A base class for any node with children (i.e. a group or a workspace).
+ */
+export class ContainerNode {
+ public displayName: string;
+ public parent?: ContainerNode;
+ private _children: Array<Node>;
+
+ clear(): void {
+ this._children = [];
+ }
+
+ get children(): ReadonlyArray<Node> {
+ return this._children;
+ }
+
+ constructor(displayName: string) {
+ this.displayName = displayName;
+ this._children = [];
+ }
+
+ private adopt(child: Node): void {
+ if (child.parent) {
+ child.parent.removeChild(child);
+ }
+ child.parent = this;
+ }
+
+ addChild(child: Node): void {
+ this.adopt(child);
+ this._children.push(child);
+ raf.scheduleFullRedraw();
+ }
+
+ prependChild(child: Node): void {
+ this.adopt(child);
+ this._children.unshift(child);
+ raf.scheduleFullRedraw();
+ }
+
+ removeChild(child: Node): void {
+ this._children = this.children.filter((x) => child !== x);
+ child.parent = undefined;
+ raf.scheduleFullRedraw();
+ }
+
+ insertBefore(newNode: Node, referenceNode: Node): void {
+ const indexOfReference = this.children.indexOf(referenceNode);
+ if (indexOfReference === -1) {
+ throw new Error('Reference node is not a child of this node');
+ }
+
+ if (newNode.parent) {
+ newNode.parent.removeChild(newNode);
+ }
+ newNode.parent = this;
+
+ this._children.splice(indexOfReference, 0, newNode);
+ raf.scheduleFullRedraw();
+ }
+
+ insertAfter(newNode: Node, referenceNode: Node): void {
+ const indexOfReference = this.children.indexOf(referenceNode);
+ if (indexOfReference === -1) {
+ throw new Error('Reference node is not a child of this node');
+ }
+
+ if (newNode.parent) {
+ newNode.parent.removeChild(newNode);
+ }
+ newNode.parent = this;
+
+ this._children.splice(indexOfReference + 1, 0, newNode);
+ raf.scheduleFullRedraw();
+ }
+
+ /**
+ * Returns an array containing the flattened list of all nodes (tracks and
+ * groups) within this node.
+ */
+ get flatNodes(): ReadonlyArray<Node> {
+ const nodes = this.children.flatMap((node) => {
+ if (node instanceof TrackNode) {
+ return node;
+ } else {
+ return [node, ...node.flatNodes];
+ }
+ });
+ return nodes;
+ }
+
+ /**
+ * Returns an array containing the flattened list of tracks within this node.
+ */
+ get flatTracks(): ReadonlyArray<TrackNode> {
+ return this.flatNodes.filter((t) => t instanceof TrackNode);
+ }
+
+ /**
+ * Returns an array containing the flattened list of groups within this
+ * workspace.
+ */
+ get flatGroups(): ReadonlyArray<GroupNode> {
+ return this.flatNodes.filter((t) => t instanceof GroupNode);
+ }
+}
+
+export class GroupNode extends ContainerNode {
+ // A unique URI used to identify this group
+ public readonly uri: string;
+
+ // Optional URI of a track to show on this group's header.
+ public headerTrackUri?: string;
+
+ // If true, this track will not show a header & permanently expanded.
+ public headless: boolean;
+
+ private _collapsed: boolean;
+
+ constructor(displayName: string) {
+ super(displayName);
+ this._collapsed = true;
+ this.headless = false;
+ this.uri = uuidv4();
+ }
+
+ expand(): void {
+ this._collapsed = false;
+ raf.scheduleFullRedraw();
+ }
+
+ collapse(): void {
+ this._collapsed = true;
+ raf.scheduleFullRedraw();
+ }
+
+ toggleCollapsed(): void {
+ this._collapsed = !this._collapsed;
+ raf.scheduleFullRedraw();
+ }
+
+ get collapsed(): boolean {
+ return this._collapsed;
+ }
+
+ get expanded(): boolean {
+ return !this._collapsed;
+ }
+}
+
+export type Node = TrackNode | GroupNode;
+
+/**
+ * Defines a workspace containing a track tree and a pinned area.
+ */
+export class Workspace extends ContainerNode {
+ public pinnedTracks: Array<TrackNode>;
+ public readonly uuid: string;
+
+ constructor(displayName: string) {
+ super(displayName);
+ this.pinnedTracks = [];
+ this.uuid = uuidv4();
+ }
+
+ /**
+ * Reset the entire workspace including the pinned tracks.
+ */
+ clear(): void {
+ this.pinnedTracks = [];
+ super.clear();
+ raf.scheduleFullRedraw();
+ }
+
+ /**
+ * Adds a track node to this workspace's pinned area.
+ */
+ pinTrack(track: TrackNode): void {
+ // TODO(stevegolton): Check if the track exists in this workspace first
+ // otherwise we might get surprises.
+ this.pinnedTracks.push(track);
+ raf.scheduleFullRedraw();
+ }
+
+ /**
+ * Removes a track node from this workspace's pinned area.
+ */
+ unpinTrack(track: TrackNode): void {
+ this.pinnedTracks = this.pinnedTracks.filter((t) => t !== track);
+ raf.scheduleFullRedraw();
+ }
+
+ /**
+ * Get a track node by its URI.
+ *
+ * @param uri - The URI of the track to look up.
+ * @returns The track node if it exists in this workspace, otherwise
+ * undefined.
+ */
+ getTrackByUri(uri: string): Optional<TrackNode> {
+ return this.flatTracks.find((t) => t.uri === uri);
+ }
+}
diff --git a/ui/src/public/workspace_unittest.ts b/ui/src/public/workspace_unittest.ts
new file mode 100644
index 0000000..0e1ddc7
--- /dev/null
+++ b/ui/src/public/workspace_unittest.ts
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 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 {GroupNode, TrackNode, Workspace} from './workspace';
+
+test('Workspace', () => {
+ const fooTrack = new TrackNode('foo', 'Foo');
+
+ const fooGroup = new GroupNode('foo');
+ fooGroup.addChild(fooTrack);
+
+ const workspace = new Workspace('Default Workspace');
+ workspace.addChild(fooGroup);
+});
diff --git a/ui/src/trace_processor/http_rpc_engine.ts b/ui/src/trace_processor/http_rpc_engine.ts
index af573d0..27c1c8b 100644
--- a/ui/src/trace_processor/http_rpc_engine.ts
+++ b/ui/src/trace_processor/http_rpc_engine.ts
@@ -49,7 +49,7 @@
this.websocket.onclose = (e) => this.onWebsocketClosed(e);
this.websocket.onerror = (e) =>
this.errorHandler(
- `WebSocket error (state=${(e.target as WebSocket)?.readyState})`,
+ `WebSocket error rs=${(e.target as WebSocket)?.readyState} (ERR:ws)`,
);
}
@@ -78,7 +78,7 @@
this.connected = false;
this.rpcSendRequestBytes(new Uint8Array()); // Triggers a reconnection.
} else {
- this.errorHandler(`Websocket closed (${e.code}: ${e.reason})`);
+ this.errorHandler(`Websocket closed (${e.code}: ${e.reason}) (ERR:ws)`);
}
}
diff --git a/ui/src/trace_processor/sql_utils.ts b/ui/src/trace_processor/sql_utils.ts
index 97b5f84..9b2c342 100644
--- a/ui/src/trace_processor/sql_utils.ts
+++ b/ui/src/trace_processor/sql_utils.ts
@@ -196,7 +196,7 @@
*
* @param engine - The database engine to execute the query.
* @param viewName - The name of the view to be created.
- * @param expression - The SQL expression to define the table.
+ * @param as - The SQL expression to define the table.
* @returns An AsyncDisposable which drops the created table when disposed.
*
* @example
@@ -214,9 +214,9 @@
export async function createView(
engine: Engine,
viewName: string,
- expression: string,
+ as: string,
): Promise<AsyncDisposable> {
- await engine.query(`CREATE VIEW ${viewName} AS ${expression}`);
+ await engine.query(`CREATE VIEW ${viewName} AS ${as}`);
return {
[Symbol.asyncDispose]: async () => {
await engine.tryQuery(`DROP VIEW IF EXISTS ${viewName}`);
diff --git a/ui/src/trace_processor/sql_utils/thread_state.ts b/ui/src/trace_processor/sql_utils/thread_state.ts
index 4836ddf..8f02f52 100644
--- a/ui/src/trace_processor/sql_utils/thread_state.ts
+++ b/ui/src/trace_processor/sql_utils/thread_state.ts
@@ -13,7 +13,6 @@
// limitations under the License.
import {duration, Time, time} from '../../base/time';
-import {exists} from '../../base/utils';
import {translateState} from '../../common/thread_state';
import {Engine} from '../engine';
import {LONG, NUM, NUM_NULL, STR_NULL} from '../query_result';
@@ -129,26 +128,17 @@
}
export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: time) {
- let trackId: string | undefined;
- for (const track of Object.values(globals.state.tracks)) {
- if (exists(track?.uri)) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) {
- if (trackInfo?.tags?.cpu === cpu) {
- trackId = track.key;
- break;
- }
- }
- }
- }
- if (trackId === undefined) {
+ const track = globals.trackManager.findTrack(
+ (td) => td.tags?.kind === CPU_SLICE_TRACK_KIND && td.tags.cpu === cpu,
+ );
+ if (track === undefined) {
return;
}
globals.setLegacySelection(
{
kind: 'SCHED_SLICE',
id,
- trackKey: trackId,
+ trackUri: track.uri,
},
{
clearSearch: true,
@@ -157,5 +147,5 @@
},
);
- scrollToTrackAndTs(trackId, ts);
+ scrollToTrackAndTs(track.uri, ts);
}
diff --git a/ui/src/widgets/menu.ts b/ui/src/widgets/menu.ts
index 9c53c49..c34e891 100644
--- a/ui/src/widgets/menu.ts
+++ b/ui/src/widgets/menu.ts
@@ -120,9 +120,9 @@
// A siple container for a menu.
// The menu contents are passed in as children, and are typically MenuItems or
// MenuDividers, but really they can be any Mithril component.
-export class Menu implements m.ClassComponent {
- view({children}: m.CVnode) {
- return m('.pf-menu', children);
+export class Menu implements m.ClassComponent<HTMLAttrs> {
+ view({attrs, children}: m.CVnode<HTMLAttrs>) {
+ return m('.pf-menu', attrs, children);
}
}
diff --git a/ui/src/widgets/select.ts b/ui/src/widgets/select.ts
index 7d59a79..9e7250a 100644
--- a/ui/src/widgets/select.ts
+++ b/ui/src/widgets/select.ts
@@ -14,86 +14,10 @@
import m from 'mithril';
-import {exists} from '../base/utils';
-
import {HTMLInputAttrs} from './common';
-import {Menu, MenuItem} from './menu';
-import {scheduleFullRedraw} from './raf';
-import {TextInput} from './text_input';
export class Select implements m.ClassComponent<HTMLInputAttrs> {
view({attrs, children}: m.CVnode<HTMLInputAttrs>) {
return m('select.pf-select', attrs, children);
}
}
-
-export interface FilterableSelectAttrs {
- // Whether to show a search box. Defaults to false.
- filterable?: boolean;
- // The values to show in the select.
- values: string[];
- // Called when the user selects an option.
- onSelected: (value: string) => void;
- // If set, only the first maxDisplayedItems will be shown.
- maxDisplayedItems?: number;
- // Whether the input field should be focused when the widget is created.
- autofocusInput?: boolean;
-}
-
-// A select widget with a search box, allowing the user to filter the options.
-export class FilterableSelect
- implements m.ClassComponent<FilterableSelectAttrs>
-{
- searchText = '';
-
- view({attrs}: m.CVnode<FilterableSelectAttrs>) {
- const filteredValues = attrs.values.filter((name) => {
- return name.toLowerCase().includes(this.searchText.toLowerCase());
- });
-
- const displayedValues =
- attrs.maxDisplayedItems === undefined
- ? filteredValues
- : filteredValues.slice(0, attrs.maxDisplayedItems);
-
- const extraItems =
- exists(attrs.maxDisplayedItems) &&
- Math.max(0, filteredValues.length - attrs.maxDisplayedItems);
-
- // TODO(altimin): when the user presses enter and there is only one item,
- // select the first one.
- // MAYBE(altimin): when the user presses enter and there are multiple items,
- // select the first one.
- return m(
- 'div',
- m(
- '.pf-search-bar',
- m(TextInput, {
- oninput: (event: Event) => {
- const eventTarget = event.target as HTMLTextAreaElement;
- this.searchText = eventTarget.value;
- scheduleFullRedraw();
- },
- onload: (event: Event) => {
- if (!attrs.autofocusInput) return;
- const eventTarget = event.target as HTMLTextAreaElement;
- eventTarget.focus();
- },
- value: this.searchText,
- placeholder: 'Filter...',
- className: 'pf-search-box',
- }),
- m(
- Menu,
- ...displayedValues.map((value) =>
- m(MenuItem, {
- label: value,
- onclick: () => attrs.onSelected(value),
- }),
- ),
- Boolean(extraItems) && m('i', `+${extraItems} more`),
- ),
- ),
- );
- }
-}
diff --git a/ui/src/widgets/text_input.ts b/ui/src/widgets/text_input.ts
index fe4487c..7aec3c8 100644
--- a/ui/src/widgets/text_input.ts
+++ b/ui/src/widgets/text_input.ts
@@ -16,13 +16,24 @@
import {HTMLInputAttrs} from './common';
+type TextInputAttrs = HTMLInputAttrs & {
+ // Whether the input should autofocus when it is created.
+ autofocus?: boolean;
+};
+
// For now, this component is just a simple wrapper around a plain old input
// element, which does no more than specify a class. However, in the future we
// might want to add more features such as an optional icon or button (e.g. a
// clear button), at which point the benefit of having this as a component would
// become more apparent.
-export class TextInput implements m.ClassComponent<HTMLInputAttrs> {
- view({attrs}: m.CVnode<HTMLInputAttrs>) {
+export class TextInput implements m.ClassComponent<TextInputAttrs> {
+ oncreate(vnode: m.CVnodeDOM<TextInputAttrs>) {
+ if (vnode.attrs.autofocus) {
+ (vnode.dom as HTMLElement).focus();
+ }
+ }
+
+ view({attrs}: m.CVnode<TextInputAttrs>) {
return m('input.pf-text-input', attrs);
}
}