Merge "ui: show slice aggregation view even when pivot table is available" into main
diff --git a/Android.bp b/Android.bp
index d60a652..3dd9d0d 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",
@@ -12244,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",
@@ -12407,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",
@@ -12686,7 +12705,6 @@
srcs: [
"src/trace_processor/importers/proto/winscope/android_input_event_parser.cc",
"src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc",
- "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc",
"src/trace_processor/importers/proto/winscope/protolog_parser.cc",
"src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
"src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
@@ -15310,6 +15328,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",
@@ -15449,6 +15469,7 @@
],
shared_libs: [
"libbase",
+ "libexpat",
"libicu",
"liblog",
"libprocinfo",
@@ -16284,8 +16305,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",
@@ -16359,6 +16383,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",
@@ -16492,6 +16518,7 @@
target: {
android: {
shared_libs: [
+ "libexpat",
"libicu",
"liblog",
"libprotobuf-cpp-full",
@@ -16505,6 +16532,7 @@
},
host: {
static_libs: [
+ "libexpat",
"libprotobuf-cpp-full",
"libsqlite_static_noicu",
"libz",
@@ -16589,6 +16617,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",
@@ -16689,8 +16718,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",
@@ -16762,6 +16794,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",
@@ -16821,6 +16855,7 @@
":perfetto_src_traceconv_utils",
],
static_libs: [
+ "libexpat",
"libsqlite_static_noicu",
"libz",
"perfetto_src_trace_processor_demangle",
diff --git a/BUILD b/BUILD
index ed3355b..5adc8df 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",
@@ -1707,6 +1715,28 @@
],
)
+# GN target: //src/trace_processor/importers/instruments:instruments
+perfetto_filegroup(
+ name = "src_trace_processor_importers_instruments_instruments",
+ srcs = [
+ "src/trace_processor/importers/instruments/instruments_utils.h",
+ "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",
@@ -1801,8 +1831,6 @@
"src/trace_processor/importers/proto/winscope/android_input_event_parser.h",
"src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc",
"src/trace_processor/importers/proto/winscope/protolog_message_decoder.h",
- "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc",
- "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h",
"src/trace_processor/importers/proto/winscope/protolog_parser.cc",
"src/trace_processor/importers/proto/winscope/protolog_parser.h",
"src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
@@ -6232,6 +6260,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",
@@ -6298,8 +6328,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",
@@ -6362,7 +6395,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 +
@@ -6384,8 +6418,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",
@@ -6414,6 +6451,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",
@@ -6535,7 +6574,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 +
@@ -6626,8 +6666,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",
@@ -6656,6 +6699,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",
@@ -6778,7 +6823,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..54196e2 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -1402,6 +1402,43 @@
deps = [ "//gn:default_deps" ]
}
+config("no_format_warning") {
+ cflags = [ "-Wno-format" ]
+}
+
+if (enable_perfetto_trace_processor_mac_instruments) {
+ 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),
+ ]
+ }
+
+ source_set("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..1bd21b4 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -87,6 +87,7 @@
"PERFETTO_TP_LINENOISE=$enable_perfetto_trace_processor_linenoise",
"PERFETTO_TP_HTTPD=$enable_perfetto_trace_processor_httpd",
"PERFETTO_TP_JSON=$enable_perfetto_trace_processor_json",
+ "PERFETTO_TP_INSTRUMENTS=$enable_perfetto_trace_processor_mac_instruments",
"PERFETTO_LOCAL_SYMBOLIZER=$perfetto_local_symbolizer",
"PERFETTO_ZLIB=$enable_perfetto_zlib",
"PERFETTO_TRACED_PERF=$enable_perfetto_traced_perf",
@@ -363,6 +364,16 @@
}
}
+if (enable_perfetto_trace_processor_mac_instruments) {
+ 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/gn/perfetto.gni b/gn/perfetto.gni
index ce106c5..6e632ec 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -298,6 +298,14 @@
enable_perfetto_trace_processor_json =
enable_perfetto_trace_processor && !perfetto_build_with_android
+ # Enables the support for importing profiles from the MacOS Instruments app.
+ # Requires a dependency on libexpat for XML parsing.
+ # Disabled in chromium due to some fuzzer related build failure (b/363347029).
+ enable_perfetto_trace_processor_mac_instruments =
+ enable_perfetto_trace_processor &&
+ (perfetto_build_standalone || perfetto_build_with_android ||
+ is_perfetto_build_generator)
+
# Enables httpd RPC support in the trace processor.
# Further per-OS conditionals are applied in gn/BUILD.gn.
# Chromium+Win: httpd support depends on enable_perfetto_ipc, which is not
diff --git a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
index c36d04b..52deaa0 100644
--- a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
@@ -38,6 +38,7 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (0)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (0)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_INSTRUMENTS() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN())
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ZLIB() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TRACED_PERF() (1)
diff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
index 54fe273..c982321 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -38,6 +38,7 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (1)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_INSTRUMENTS() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN())
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ZLIB() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TRACED_PERF() (0)
diff --git a/include/perfetto/ext/tracing/core/shared_memory.h b/include/perfetto/ext/tracing/core/shared_memory.h
index f2e5274..4773c34 100644
--- a/include/perfetto/ext/tracing/core/shared_memory.h
+++ b/include/perfetto/ext/tracing/core/shared_memory.h
@@ -20,6 +20,7 @@
#include <stddef.h>
#include <memory>
+#include <utility>
#include "perfetto/base/export.h"
#include "perfetto/base/platform_handle.h"
@@ -45,7 +46,20 @@
// this object region when destroyed.
virtual ~SharedMemory();
- virtual void* start() const = 0;
+ // Read/write and read-only access to underlying buffer. The non-const method
+ // is implemented in terms of the const one so subclasses need only provide a
+ // single implementation; implementing in the opposite order would be unsafe
+ // since subclasses could effectively mutate state from inside a const method.
+ //
+ // N.B. This signature implements "deep const" that ties the constness of this
+ // object to the constness of the underlying buffer, as opposed to "shallow
+ // const" that would have the signature `void* start() const;`; this is less
+ // flexible for callers but prevents corner cases where it's transitively
+ // possible to change this object's state via the controlled memory.
+ void* start() { return const_cast<void*>(std::as_const(*this).start()); }
+ virtual const void* start() const = 0;
+
+
virtual size_t size() const = 0;
};
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 702cb98..718da72 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -3173,6 +3173,10 @@
// Polls /sys/devices/system/cpu/cpu*/cpuidle/state* every X ms, if non-zero.
// This is required to be > 10ms to avoid excessive CPU usage.
optional uint32 cpuidle_period_ms = 13;
+
+ // Polls device-specific GPU frequency info every X ms, if non-zero.
+ // This is required to be > 10ms to avoid excessive CPU usage.
+ optional uint32 gpufreq_period_ms = 14;
}
// End of protos/perfetto/config/sys_stats/sys_stats_config.proto
diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto
index 3466255..fc16456 100644
--- a/protos/perfetto/config/sys_stats/sys_stats_config.proto
+++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -87,4 +87,8 @@
// Polls /sys/devices/system/cpu/cpu*/cpuidle/state* every X ms, if non-zero.
// This is required to be > 10ms to avoid excessive CPU usage.
optional uint32 cpuidle_period_ms = 13;
+
+ // Polls device-specific GPU frequency info every X ms, if non-zero.
+ // This is required to be > 10ms to avoid excessive CPU usage.
+ optional uint32 gpufreq_period_ms = 14;
}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index a13fdff..ca676b3 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3173,6 +3173,10 @@
// Polls /sys/devices/system/cpu/cpu*/cpuidle/state* every X ms, if non-zero.
// This is required to be > 10ms to avoid excessive CPU usage.
optional uint32 cpuidle_period_ms = 13;
+
+ // Polls device-specific GPU frequency info every X ms, if non-zero.
+ // This is required to be > 10ms to avoid excessive CPU usage.
+ optional uint32 gpufreq_period_ms = 14;
}
// End of protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -14493,7 +14497,11 @@
repeated CpuIdleStateEntry cpuidle_state_entry = 2;
}
repeated CpuIdleState cpuidle_state = 16;
+
+ // Read GPU frequency info on Intel/AMD devices.
+ repeated uint64 gpufreq_mhz = 17;
}
+
// End of protos/perfetto/trace/sys_stats/sys_stats.proto
// Begin of protos/perfetto/trace/system_info.proto
diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto
index d0ad10f..8532d78 100644
--- a/protos/perfetto/trace/sys_stats/sys_stats.proto
+++ b/protos/perfetto/trace/sys_stats/sys_stats.proto
@@ -179,4 +179,7 @@
repeated CpuIdleStateEntry cpuidle_state_entry = 2;
}
repeated CpuIdleState cpuidle_state = 16;
-}
\ No newline at end of file
+
+ // Read GPU frequency info on Intel/AMD devices.
+ repeated uint64 gpufreq_mhz = 17;
+}
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index a3784cc..50e2b2d 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -1448,7 +1448,11 @@
}
optional StepName step = 1;
optional FrameSinkId frame_sink_id = 2;
+
+ // Id used to link `ChromeGraphicsPipeline`s corresponding to work done
+ // on creating and presenting one frame.
optional int64 display_trace_id = 3;
+
optional LocalSurfaceId local_surface_id = 4;
optional int64 frame_sequence = 5;
optional FrameSkippedReason frame_skipped_reason = 6;
diff --git a/python/perfetto/bigtrace_clickhouse/grpc_client.py b/python/perfetto/bigtrace_clickhouse/grpc_client.py
new file mode 100755
index 0000000..1f68d70
--- /dev/null
+++ b/python/perfetto/bigtrace_clickhouse/grpc_client.py
@@ -0,0 +1,62 @@
+#!/usr/bin/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 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.
+
+# Executable script used by Clickhouse to make gRPC calls to the Orchestrator
+# from a TVF
+
+import grpc
+import sys
+import os
+
+from protos.perfetto.bigtrace.orchestrator_pb2 import BigtraceQueryArgs
+from protos.perfetto.bigtrace.orchestrator_pb2_grpc import BigtraceOrchestratorStub
+from query_result_iterator import QueryResultIterator
+
+
+def main():
+ orchestrator_address = os.environ.get("BIGTRACE_ORCHESTRATOR_ADDRESS")
+
+ for input in sys.stdin:
+ # Clickhouse input is specified as tab separated
+ traces, sql_query = input.rstrip("\n").split("\t")
+ # Convert the string representation of list of traces given by Clickhouse into
+ # a Python list
+ trace_list = [x[1:-1] for x in traces[1:-1].split(',')]
+
+ channel = grpc.insecure_channel(orchestrator_address)
+ stub = BigtraceOrchestratorStub(channel)
+ args = BigtraceQueryArgs(traces=trace_list, sql_query=sql_query)
+
+ responses = stub.Query(args, wait_for_ready=False)
+ for response in responses:
+ repeated_batches = []
+ results = response.result
+ column_names = results[0].column_names
+ for result in results:
+ repeated_batches.extend(result.batch)
+ qr_it = QueryResultIterator(column_names, repeated_batches)
+
+ for row in qr_it:
+ # Retrieve all values from columns and replace nulls with empty string
+ data = [(x if x else "") for x in row.__repr__().values()]
+ # Convert the list to a tab separated format for Clickhouse to ingest
+ data_str = '\t'.join(data)
+ print(data_str + '\n', end='')
+
+ sys.stdout.flush()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/perfetto/bigtrace_clickhouse/query_result_iterator.py b/python/perfetto/bigtrace_clickhouse/query_result_iterator.py
new file mode 100644
index 0000000..8fdb56f
--- /dev/null
+++ b/python/perfetto/bigtrace_clickhouse/query_result_iterator.py
@@ -0,0 +1,165 @@
+# 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.
+
+
+class PerfettoException(Exception):
+
+ def __init__(self, message):
+ super().__init__(message)
+
+
+# Provides a Python interface to operate on the contents of QueryResult protos
+class QueryResultIterator:
+ # Values of these constants correspond to the QueryResponse message at
+ # protos/perfetto/trace_processor/trace_processor.proto
+ QUERY_CELL_INVALID_FIELD_ID = 0
+ QUERY_CELL_NULL_FIELD_ID = 1
+ QUERY_CELL_VARINT_FIELD_ID = 2
+ QUERY_CELL_FLOAT64_FIELD_ID = 3
+ QUERY_CELL_STRING_FIELD_ID = 4
+ QUERY_CELL_BLOB_FIELD_ID = 5
+
+ # This is the class returned to the user and contains one row of the
+ # resultant query. Each column name is stored as an attribute of this
+ # class, with the value corresponding to the column name and row in
+ # the query results table.
+ class Row(object):
+ # Required for pytype to correctly infer attributes from Row objects
+ _HAS_DYNAMIC_ATTRIBUTES = True
+
+ def __str__(self):
+ return str(self.__dict__)
+
+ def __repr__(self):
+ return self.__dict__
+
+ def __init__(self, column_names, batches):
+ self.__column_names = list(column_names)
+ self.__column_count = 0
+ self.__count = 0
+ self.__cells = []
+ self.__data_lists = [[], [], [], [], [], []]
+ self.__data_lists_index = [0, 0, 0, 0, 0, 0]
+ self.__current_index = 0
+
+ # Iterate over all the batches and collect their
+ # contents into lists based on the type of the batch
+ batch_index = 0
+ while True:
+ # It's possible on some occasions that there are non UTF-8 characters
+ # in the string_cells field. If this is the case, string_cells is
+ # a bytestring which needs to be decoded (but passing ignore so that
+ # we don't fail in decoding).
+ strings_batch_str = batches[batch_index].string_cells
+ try:
+ strings_batch_str = strings_batch_str.decode('utf-8', 'ignore')
+ except AttributeError:
+ # AttributeError can occur when |strings_batch_str| is an str which
+ # happens when everything in it is UTF-8 (protobuf automatically
+ # does the conversion if it can).
+ pass
+
+ # Null-terminated strings in a batch are concatenated
+ # into a single large byte array, so we split on the
+ # null-terminator to get the individual strings
+ strings_batch = strings_batch_str.split('\0')[:-1]
+ self.__data_lists[QueryResultIterator.QUERY_CELL_STRING_FIELD_ID].extend(
+ strings_batch)
+ self.__data_lists[QueryResultIterator.QUERY_CELL_VARINT_FIELD_ID].extend(
+ batches[batch_index].varint_cells)
+ self.__data_lists[QueryResultIterator.QUERY_CELL_FLOAT64_FIELD_ID].extend(
+ batches[batch_index].float64_cells)
+ self.__data_lists[QueryResultIterator.QUERY_CELL_BLOB_FIELD_ID].extend(
+ batches[batch_index].blob_cells)
+ self.__cells.extend(batches[batch_index].cells)
+
+ if batches[batch_index].is_last_batch:
+ break
+
+ batch_index += 1
+
+ # If there are no rows in the query result, don't bother updating the
+ # counts to avoid dealing with / 0 errors.
+ if len(self.__cells) == 0:
+ return
+
+ # The count we collected so far was a count of all individual columns
+ # in the query result, so we divide by the number of columns in a row
+ # to get the number of rows
+ self.__column_count = len(self.__column_names)
+ self.__count = int(len(self.__cells) / self.__column_count)
+
+ # Data integrity check - see that we have the expected amount of cells
+ # for the number of rows that we need to return
+ if len(self.__cells) % self.__column_count != 0:
+ raise PerfettoException("Cell count " + str(len(self.__cells)) +
+ " is not a multiple of column count " +
+ str(len(self.__column_names)))
+
+ # To use the query result as a populated Pandas dataframe, this
+ # function must be called directly after calling query inside
+ # TraceProcessor / Bigtrace.
+ def as_pandas_dataframe(self):
+ try:
+ import pandas as pd
+
+ # Populate the dataframe with the query results
+ rows = []
+ for i in range(0, self.__count):
+ row = []
+ base_cell_index = i * self.__column_count
+ for num in range(len(self.__column_names)):
+ col_type = self.__cells[base_cell_index + num]
+ if col_type == QueryResultIterator.QUERY_CELL_INVALID_FIELD_ID:
+ raise PerfettoException('Invalid cell type')
+
+ if col_type == QueryResultIterator.QUERY_CELL_NULL_FIELD_ID:
+ row.append(None)
+ else:
+ col_index = self.__data_lists_index[col_type]
+ self.__data_lists_index[col_type] += 1
+ row.append(self.__data_lists[col_type][col_index])
+ rows.append(row)
+
+ df = pd.DataFrame(rows, columns=self.__column_names)
+ return df.astype(object).where(df.notnull(), None).reset_index(drop=True)
+
+ except ModuleNotFoundError:
+ raise PerfettoException(
+ 'Python dependencies missing. Please pip3 install pandas numpy')
+
+ def __len__(self):
+ return self.__count
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.__current_index == self.__count:
+ raise StopIteration
+ result = QueryResultIterator.Row()
+ base_cell_index = self.__current_index * self.__column_count
+ for num, column_name in enumerate(self.__column_names):
+ col_type = self.__cells[base_cell_index + num]
+ if col_type == QueryResultIterator.QUERY_CELL_INVALID_FIELD_ID:
+ raise PerfettoException('Invalid cell type')
+ if col_type != QueryResultIterator.QUERY_CELL_NULL_FIELD_ID:
+ col_index = self.__data_lists_index[col_type]
+ self.__data_lists_index[col_type] += 1
+ setattr(result, column_name, self.__data_lists[col_type][col_index])
+ else:
+ setattr(result, column_name, None)
+
+ self.__current_index += 1
+ return result
diff --git a/python/setup.py b/python/setup.py
index 9b1e81e..7afeec2 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -8,7 +8,7 @@
],
package_data={'perfetto.trace_processor': ['*.descriptor']},
include_package_data=True,
- version='0.8.0',
+ version='0.9.0',
license='apache-2.0',
description='Python API for Perfetto\'s Trace Processor',
author='Perfetto',
diff --git a/python/test/bigtrace_api_integrationtest.py b/python/test/bigtrace_api_integrationtest.py
index 76677a1..65766fc 100644
--- a/python/test/bigtrace_api_integrationtest.py
+++ b/python/test/bigtrace_api_integrationtest.py
@@ -51,20 +51,18 @@
def test_valid_traces(self):
result = self.client.query([
- f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace",
- f"{self.root_dir}/test/data/api24_startup_hot.perfetto-trace"
+ f"/local/{self.root_dir}/test/data/api24_startup_cold.perfetto-trace",
+ f"/local/{self.root_dir}/test/data/api24_startup_hot.perfetto-trace"
], "SELECT count(1) as count FROM slice LIMIT 5")
self.assertEqual(
- result.loc[
- result['_trace_address'] ==
- f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace",
- 'count'].iloc[0], 9726)
+ result.loc[result['_trace_address'] ==
+ f"/local/{self.root_dir}/test/data/"
+ "api24_startup_cold.perfetto-trace", 'count'].iloc[0], 9726)
self.assertEqual(
- result.loc[
- result['_trace_address'] ==
- f"{self.root_dir}/test/data/api24_startup_hot.perfetto-trace",
- 'count'].iloc[0], 5726)
+ result.loc[result['_trace_address'] ==
+ f"/local/{self.root_dir}/test/data/"
+ "api24_startup_hot.perfetto-trace", 'count'].iloc[0], 5726)
def test_empty_traces(self):
with self.assertRaises(PerfettoException):
@@ -73,6 +71,19 @@
def test_empty_sql_string(self):
with self.assertRaises(PerfettoException):
result = self.client.query([
- f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace",
- f"{self.root_dir}/test/data/api24_startup_hot.perfetto-trace"
+ f"/local/{self.root_dir}/test/data/api24_startup_cold.perfetto-trace",
+ f"/local/{self.root_dir}/test/data/api24_startup_hot.perfetto-trace"
], "")
+
+ def test_invalid_prefix(self):
+ with self.assertRaises(PerfettoException):
+ result = self.client.query([
+ f"/badprefix/{self.root_dir}/test/data/"
+ "api24_startup_cold.perfetto-trace"
+ ], "SELECT count(1) FROM slice LIMIT 5"),
+
+ def test_no_prefix(self):
+ with self.assertRaises(PerfettoException):
+ result = self.client.query(
+ [f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace"],
+ "SELECT count(1) FROM slice LIMIT 5")
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/bigtrace/orchestrator/orchestrator_impl.h b/src/bigtrace/orchestrator/orchestrator_impl.h
index b989c68..71baa29 100644
--- a/src/bigtrace/orchestrator/orchestrator_impl.h
+++ b/src/bigtrace/orchestrator/orchestrator_impl.h
@@ -14,13 +14,13 @@
* limitations under the License.
*/
+#ifndef SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_
+#define SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_
+
#include "perfetto/ext/base/threading/thread_pool.h"
#include "protos/perfetto/bigtrace/orchestrator.grpc.pb.h"
#include "protos/perfetto/bigtrace/worker.grpc.pb.h"
-#ifndef SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_
-#define SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_
-
namespace perfetto::bigtrace {
class OrchestratorImpl final : public protos::BigtraceOrchestrator::Service {
diff --git a/src/bigtrace/worker/BUILD.gn b/src/bigtrace/worker/BUILD.gn
index 2f91816..0c23c4e 100644
--- a/src/bigtrace/worker/BUILD.gn
+++ b/src/bigtrace/worker/BUILD.gn
@@ -26,14 +26,17 @@
"worker_main.cc",
]
deps = [
+ "../../../gn:cpp_httplib",
"../../../gn:default_deps",
"../../../gn:grpc",
+ "../../../gn:jsoncpp",
"../../../include/perfetto/ext/trace_processor/rpc:query_result_serializer",
"../../../protos/perfetto/bigtrace:worker_grpc",
"../../../protos/perfetto/bigtrace:worker_lite",
"../../../src/trace_processor",
"../../../src/trace_processor/rpc:rpc",
"../../base",
+ "repository_policies:repository_policies",
]
}
}
diff --git a/src/bigtrace/worker/repository_policies/BUILD.gn b/src/bigtrace/worker/repository_policies/BUILD.gn
new file mode 100644
index 0000000..3b496dc
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/BUILD.gn
@@ -0,0 +1,40 @@
+# 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/perfetto.gni")
+import("../../../../gn/test.gni")
+
+assert(enable_perfetto_trace_processor &&
+ enable_perfetto_trace_processor_sqlite && enable_perfetto_grpc)
+
+source_set("repository_policies") {
+ sources = [
+ "gcs_trace_processor_loader.cc",
+ "gcs_trace_processor_loader.h",
+ "local_trace_processor_loader.cc",
+ "local_trace_processor_loader.h",
+ "trace_processor_loader.cc",
+ "trace_processor_loader.h",
+ ]
+ deps = [
+ "../../../../gn:cpp_httplib",
+ "../../../../gn:default_deps",
+ "../../../../gn:grpc",
+ "../../../../gn:jsoncpp",
+ "../../../../src/trace_processor",
+ "../../../../src/trace_processor/rpc:rpc",
+ "../../../../src/trace_processor/util:util",
+ "../../../base",
+ ]
+}
diff --git a/src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.cc b/src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.cc
new file mode 100644
index 0000000..91649b8
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.cc
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#define CPPHTTPLIB_NO_EXCEPTIONS
+#define CPPHTTPLIB_OPENSSL_SUPPORT
+#include <httplib.h>
+#include <json/json.h>
+
+#include "perfetto/base/status.h"
+#include "src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::bigtrace {
+
+namespace {
+
+constexpr char kAuthDomain[] = "http://metadata.google.internal";
+constexpr char kAuthPath[] =
+ "/computeMetadata/v1/instance/service-accounts/default/token";
+constexpr char kGcsDomain[] = "https://storage.googleapis.com";
+constexpr char kGcsBucketPath[] = "/download/storage/v1/b/";
+constexpr char kGcsParams[] = "?alt=media";
+
+} // namespace
+
+base::StatusOr<std::unique_ptr<trace_processor::TraceProcessor>>
+GcsTraceProcessorLoader::LoadTraceProcessor(const std::string& path) {
+ trace_processor::Config config;
+ std::unique_ptr<trace_processor::TraceProcessor> tp =
+ trace_processor::TraceProcessor::CreateInstance(config);
+
+ // Retrieve access token to use in GET request to GCS
+ httplib::Headers auth_headers{{"Metadata-Flavor", "Google"}};
+ httplib::Client auth_client(kAuthDomain);
+
+ httplib::Result auth_response = auth_client.Get(kAuthPath, auth_headers);
+ std::string json_string = auth_response->body;
+
+ if (auth_response->status != httplib::StatusCode::OK_200) {
+ return base::ErrStatus("Failed to get access token: %s",
+ auth_response->body.c_str());
+ }
+
+ // Parse access token from response
+ Json::Value json_value;
+ Json::Reader json_reader;
+ bool parsed_successfully = json_reader.parse(json_string, json_value);
+ if (!parsed_successfully) {
+ return base::ErrStatus("Failed to parse GCS access token");
+ }
+ std::string access_token = json_value["access_token"].asString();
+
+ // Download trace from GCS
+ std::string gcs_path = kGcsBucketPath + path + kGcsParams;
+ httplib::Headers gcs_headers{{"Authorization", "Bearer " + access_token}};
+
+ httplib::Client gcs_client(kGcsDomain);
+ base::Status response_status;
+
+ httplib::Result trace_response = gcs_client.Get(
+ gcs_path, gcs_headers,
+ [&](const httplib::Response& response) {
+ if (httplib::StatusCode::OK_200 != response.status) {
+ response_status = base::ErrStatus("Failed to download trace: %s",
+ response.reason.c_str());
+ return false;
+ }
+ return true;
+ },
+ [&](const char* data, size_t data_length) {
+ std::unique_ptr<uint8_t[]> buf(new uint8_t[data_length]);
+ memcpy(buf.get(), data, data_length);
+ auto status = tp->Parse(std::move(buf), data_length);
+ return true;
+ });
+
+ tp->NotifyEndOfFile();
+
+ RETURN_IF_ERROR(response_status);
+
+ return tp;
+}
+} // namespace perfetto::bigtrace
diff --git a/src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.h b/src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.h
new file mode 100644
index 0000000..223edaf
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.h
@@ -0,0 +1,33 @@
+/*
+ * 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_BIGTRACE_WORKER_REPOSITORY_POLICIES_GCS_TRACE_PROCESSOR_LOADER_H_
+#define SRC_BIGTRACE_WORKER_REPOSITORY_POLICIES_GCS_TRACE_PROCESSOR_LOADER_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_processor.h"
+#include "src/bigtrace/worker/repository_policies/trace_processor_loader.h"
+
+namespace perfetto::bigtrace {
+
+class GcsTraceProcessorLoader : public TraceProcessorLoader {
+ public:
+ base::StatusOr<std::unique_ptr<trace_processor::TraceProcessor>>
+ LoadTraceProcessor(const std::string& path) override;
+};
+
+} // namespace perfetto::bigtrace
+
+#endif // SRC_BIGTRACE_WORKER_REPOSITORY_POLICIES_GCS_TRACE_PROCESSOR_LOADER_H_
diff --git a/src/bigtrace/worker/repository_policies/local_trace_processor_loader.cc b/src/bigtrace/worker/repository_policies/local_trace_processor_loader.cc
new file mode 100644
index 0000000..536405a
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/local_trace_processor_loader.cc
@@ -0,0 +1,33 @@
+/*
+ * 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/bigtrace/worker/repository_policies/local_trace_processor_loader.h"
+#include "perfetto/trace_processor/read_trace.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::bigtrace {
+
+base::StatusOr<std::unique_ptr<trace_processor::TraceProcessor>>
+LocalTraceProcessorLoader::LoadTraceProcessor(const std::string& path) {
+ trace_processor::Config config;
+ std::unique_ptr<trace_processor::TraceProcessor> tp =
+ trace_processor::TraceProcessor::CreateInstance(config);
+
+ RETURN_IF_ERROR(trace_processor::ReadTrace(tp.get(), path.c_str()));
+
+ return tp;
+}
+
+} // namespace perfetto::bigtrace
diff --git a/src/bigtrace/worker/repository_policies/local_trace_processor_loader.h b/src/bigtrace/worker/repository_policies/local_trace_processor_loader.h
new file mode 100644
index 0000000..0f60f89
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/local_trace_processor_loader.h
@@ -0,0 +1,34 @@
+/*
+ * 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_BIGTRACE_WORKER_REPOSITORY_POLICIES_LOCAL_TRACE_PROCESSOR_LOADER_H_
+#define SRC_BIGTRACE_WORKER_REPOSITORY_POLICIES_LOCAL_TRACE_PROCESSOR_LOADER_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_processor.h"
+#include "src/bigtrace/worker/repository_policies/trace_processor_loader.h"
+
+namespace perfetto::bigtrace {
+
+class LocalTraceProcessorLoader : public TraceProcessorLoader {
+ public:
+ base::StatusOr<std::unique_ptr<trace_processor::TraceProcessor>>
+ LoadTraceProcessor(const std::string& path) override;
+};
+
+} // namespace perfetto::bigtrace
+
+#endif // SRC_BIGTRACE_WORKER_REPOSITORY_POLICIES_LOCAL_TRACE_PROCESSOR_LOADER_H_
diff --git a/src/bigtrace/worker/repository_policies/trace_processor_loader.cc b/src/bigtrace/worker/repository_policies/trace_processor_loader.cc
new file mode 100644
index 0000000..d55bec7
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/trace_processor_loader.cc
@@ -0,0 +1,22 @@
+/*
+ * 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/bigtrace/worker/repository_policies/trace_processor_loader.h"
+
+namespace perfetto::bigtrace {
+
+TraceProcessorLoader::~TraceProcessorLoader() = default;
+
+}
diff --git a/src/bigtrace/worker/repository_policies/trace_processor_loader.h b/src/bigtrace/worker/repository_policies/trace_processor_loader.h
new file mode 100644
index 0000000..b1a6522
--- /dev/null
+++ b/src/bigtrace/worker/repository_policies/trace_processor_loader.h
@@ -0,0 +1,41 @@
+/*
+ * 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_BIGTRACE_WORKER_REPOSITORY_POLICIES_TRACE_PROCESSOR_LOADER_H_
+#define SRC_BIGTRACE_WORKER_REPOSITORY_POLICIES_TRACE_PROCESSOR_LOADER_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_processor.h"
+
+namespace perfetto::bigtrace {
+
+// This interface is designed to facilitate interaction with multiple file
+// systems/object stores e.g. GCS, S3 or the local filesystem by allowing
+// implementation of classes which retrieve a trace using the specific store's
+// interface and returns a TraceProcessor instance containing the loaded trace
+// to the Worker
+class TraceProcessorLoader {
+ public:
+ virtual ~TraceProcessorLoader();
+ // Virtual method to load a trace from a given filesystem/object store and
+ // returns a TraceProcessor instance with the loaded trace
+ virtual base::StatusOr<std::unique_ptr<trace_processor::TraceProcessor>>
+ LoadTraceProcessor(const std::string& path) = 0;
+};
+
+} // namespace perfetto::bigtrace
+
+#endif // SRC_BIGTRACE_WORKER_REPOSITORY_POLICIES_TRACE_PROCESSOR_LOADER_H_
diff --git a/src/bigtrace/worker/worker_impl.cc b/src/bigtrace/worker/worker_impl.cc
index 53f8606..43729db 100644
--- a/src/bigtrace/worker/worker_impl.cc
+++ b/src/bigtrace/worker/worker_impl.cc
@@ -16,8 +16,9 @@
#include "src/bigtrace/worker/worker_impl.h"
#include "perfetto/ext/trace_processor/rpc/query_result_serializer.h"
-#include "perfetto/trace_processor/read_trace.h"
#include "perfetto/trace_processor/trace_processor.h"
+#include "src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.h"
+#include "src/bigtrace/worker/repository_policies/local_trace_processor_loader.h"
namespace perfetto::bigtrace {
@@ -25,16 +26,31 @@
grpc::ServerContext*,
const protos::BigtraceQueryTraceArgs* args,
protos::BigtraceQueryTraceResponse* response) {
- trace_processor::Config config;
- std::unique_ptr<trace_processor::TraceProcessor> tp =
- trace_processor::TraceProcessor::CreateInstance(config);
+ std::string args_trace = args->trace();
- base::Status status =
- trace_processor::ReadTrace(tp.get(), args->trace().c_str());
- if (!status.ok()) {
- const std::string& error_message = status.c_message();
+ std::string prefix = args_trace.substr(0, args_trace.find("/", 1));
+ if (registry_.find(prefix) == registry_.end()) {
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Invalid path prefix specified");
+ }
+
+ if (prefix.length() == args_trace.length()) {
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Empty path is invalid");
+ }
+
+ std::string path = args_trace.substr(prefix.length() + 1);
+
+ base::StatusOr<std::unique_ptr<trace_processor::TraceProcessor>> tp_or =
+ registry_[prefix]->LoadTraceProcessor(path);
+
+ if (!tp_or.ok()) {
+ const std::string& error_message = tp_or.status().message();
return grpc::Status(grpc::StatusCode::INTERNAL, error_message);
}
+
+ std::unique_ptr<trace_processor::TraceProcessor> tp = std::move(*tp_or);
+
auto iter = tp->ExecuteQuery(args->sql_query());
trace_processor::QueryResultSerializer serializer =
trace_processor::QueryResultSerializer(std::move(iter));
diff --git a/src/bigtrace/worker/worker_impl.h b/src/bigtrace/worker/worker_impl.h
index b5a9664..946715d 100644
--- a/src/bigtrace/worker/worker_impl.h
+++ b/src/bigtrace/worker/worker_impl.h
@@ -13,20 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include "protos/perfetto/bigtrace/worker.grpc.pb.h"
-#include "protos/perfetto/bigtrace/worker.pb.h"
-
#ifndef SRC_BIGTRACE_WORKER_WORKER_IMPL_H_
#define SRC_BIGTRACE_WORKER_WORKER_IMPL_H_
+#include <unordered_map>
+
+#include "protos/perfetto/bigtrace/worker.grpc.pb.h"
+#include "protos/perfetto/bigtrace/worker.pb.h"
+#include "src/bigtrace/worker/repository_policies/trace_processor_loader.h"
+
namespace perfetto::bigtrace {
class WorkerImpl final : public protos::BigtraceWorker::Service {
public:
+ explicit WorkerImpl(
+ std::unordered_map<std::string, std::unique_ptr<TraceProcessorLoader>>
+ registry)
+ : registry_(std::move(registry)) {}
grpc::Status QueryTrace(
grpc::ServerContext*,
const protos::BigtraceQueryTraceArgs* args,
protos::BigtraceQueryTraceResponse* response) override;
+
+ private:
+ std::unordered_map<std::string, std::unique_ptr<TraceProcessorLoader>>
+ registry_;
};
} // namespace perfetto::bigtrace
diff --git a/src/bigtrace/worker/worker_main.cc b/src/bigtrace/worker/worker_main.cc
index b985f69..c61cfc8 100644
--- a/src/bigtrace/worker/worker_main.cc
+++ b/src/bigtrace/worker/worker_main.cc
@@ -21,6 +21,9 @@
#include "perfetto/base/status.h"
#include "perfetto/ext/base/getopt.h"
+#include "src/bigtrace/worker/repository_policies/gcs_trace_processor_loader.h"
+#include "src/bigtrace/worker/repository_policies/local_trace_processor_loader.h"
+#include "src/bigtrace/worker/repository_policies/trace_processor_loader.h"
#include "src/bigtrace/worker/worker_impl.h"
namespace perfetto::bigtrace {
@@ -53,7 +56,13 @@
CommandLineOptions options = ParseCommandLineOptions(argc, argv);
std::string socket =
options.socket.empty() ? "127.0.0.1:5052" : options.socket;
- auto service = std::make_unique<WorkerImpl>();
+
+ std::unordered_map<std::string, std::unique_ptr<TraceProcessorLoader>>
+ registry;
+ registry["/gcs"] = std::make_unique<GcsTraceProcessorLoader>();
+ registry["/local"] = std::make_unique<LocalTraceProcessorLoader>();
+
+ auto service = std::make_unique<WorkerImpl>(std::move(registry));
grpc::ServerBuilder builder;
builder.RegisterService(service.get());
builder.AddListeningPort(socket, grpc::InsecureServerCredentials());
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index 4d3d6fd..5271e82 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -226,13 +226,27 @@
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<std::string> GetMachOUuid(char* mem, size_t size) {
+std::optional<BinaryInfo> GetMachOBinaryInfo(char* mem, size_t size) {
if (size < sizeof(mach_header_64))
return {};
@@ -242,36 +256,48 @@
if (size < sizeof(mach_header_64) + header.sizeofcmds)
return {};
- char* cmd = mem + sizeof(mach_header_64);
- char* cmds_end = cmd + header.sizeofcmds;
- while (cmd < cmds_end) {
- load_command cmd_header;
- memcpy(&cmd_header, cmd, sizeof(load_command));
+ std::optional<std::string> build_id;
+ uint64_t load_bias = 0;
- if (cmd_header.cmd == 0x1b) {
- return std::string(cmd + sizeof(load_command),
- cmd_header.cmdsize - sizeof(load_command));
+ 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;
}
- cmd += cmd_header.cmdsize;
+ 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<BinaryType> GetMachOBinaryType(char* mem, size_t size) {
- if (size < sizeof(mach_header_64))
- return {};
-
- mach_header_64 header;
- memcpy(&header, mem, sizeof(mach_header_64));
-
- constexpr uint32_t MH_DSYM = 0xa;
-
- return header.filetype == MH_DSYM ? BinaryType::kMachODsym
- : BinaryType::kMachO;
-}
-
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)
@@ -285,7 +311,6 @@
std::optional<std::string> build_id;
std::optional<uint64_t> load_bias;
- std::optional<BinaryType> type;
if (IsElf(mem, size)) {
switch (mem[EI_CLASS]) {
case ELFCLASS32:
@@ -299,14 +324,11 @@
default:
return std::nullopt;
}
- type = BinaryType::kElf;
+ if (build_id && load_bias) {
+ return BinaryInfo{*build_id, *load_bias, BinaryType::kElf};
+ }
} else if (IsMachO64(mem, size)) {
- build_id = GetMachOUuid(mem, size);
- load_bias = {0};
- type = GetMachOBinaryType(mem, size);
- }
- if (build_id && load_bias && type) {
- return BinaryInfo{*build_id, *load_bias, *type};
+ return GetMachOBinaryInfo(mem, size);
}
return std::nullopt;
}
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 2a23b6c..bd32bde 100644
--- a/src/trace_processor/importers/common/parser_types.h
+++ b/src/trace_processor/importers/common/parser_types.h
@@ -37,6 +37,7 @@
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.
@@ -91,6 +92,14 @@
};
static_assert(sizeof(TracePacketData) % 8 == 0);
+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 94594bc..cf7c84a 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -24,6 +24,9 @@
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/gzip/gzip_trace_parser.cc b/src/trace_processor/importers/gzip/gzip_trace_parser.cc
index dfa45e0..d133b2c 100644
--- a/src/trace_processor/importers/gzip/gzip_trace_parser.cc
+++ b/src/trace_processor/importers/gzip/gzip_trace_parser.cc
@@ -79,11 +79,9 @@
// Our default uncompressed buffer size is 32MB as it allows for good
// throughput.
constexpr size_t kUncompressedBufferSize = 32ul * 1024 * 1024;
-
- needs_more_input_ = false;
decompressor_.Feed(start, len);
- for (auto ret = ResultCode::kOk; ret != ResultCode::kEof;) {
+ for (;;) {
if (!buffer_) {
buffer_.reset(new uint8_t[kUncompressedBufferSize]);
bytes_written_ = 0;
@@ -92,39 +90,44 @@
auto result =
decompressor_.ExtractOutput(buffer_.get() + bytes_written_,
kUncompressedBufferSize - bytes_written_);
- ret = result.ret;
+ util::GzipDecompressor::ResultCode ret = result.ret;
if (ret == ResultCode::kError)
return base::ErrStatus("Failed to decompress trace chunk");
if (ret == ResultCode::kNeedsMoreInput) {
PERFETTO_DCHECK(result.bytes_written == 0);
- needs_more_input_ = true;
return base::OkStatus();
}
bytes_written_ += result.bytes_written;
+ output_state_ = kMidStream;
if (bytes_written_ == kUncompressedBufferSize || ret == ResultCode::kEof) {
TraceBlob blob =
TraceBlob::TakeOwnership(std::move(buffer_), bytes_written_);
RETURN_IF_ERROR(inner_->Parse(TraceBlobView(std::move(blob))));
}
+
+ // We support multiple gzip streams in a single gzip file (which is valid
+ // according to RFC1952 section 2.2): in that case, we just need to reset
+ // the decompressor to begin processing the next stream: all other variables
+ // can be preserved.
+ if (ret == ResultCode::kEof) {
+ decompressor_.Reset();
+ output_state_ = kStreamBoundary;
+
+ if (decompressor_.AvailIn() == 0) {
+ return base::OkStatus();
+ }
+ }
}
- return base::OkStatus();
}
base::Status GzipTraceParser::NotifyEndOfFile() {
- // TODO(lalitm): this should really be an error returned to the caller but
- // due to historical implementation, NotifyEndOfFile does not return a
- // base::Status.
- if (needs_more_input_) {
+ if (output_state_ != kStreamBoundary || decompressor_.AvailIn() > 0) {
return base::ErrStatus("GZIP stream incomplete, trace is likely corrupt");
}
- PERFETTO_DCHECK(!buffer_);
-
- if (!inner_) {
- base::OkStatus();
- }
- return inner_->NotifyEndOfFile();
+ PERFETTO_CHECK(!buffer_);
+ return inner_ ? inner_->NotifyEndOfFile() : base::OkStatus();
}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/gzip/gzip_trace_parser.h b/src/trace_processor/importers/gzip/gzip_trace_parser.h
index 1cd862d..4a565e6 100644
--- a/src/trace_processor/importers/gzip/gzip_trace_parser.h
+++ b/src/trace_processor/importers/gzip/gzip_trace_parser.h
@@ -41,8 +41,6 @@
base::Status ParseUnowned(const uint8_t*, size_t);
- bool needs_more_input() const { return needs_more_input_; }
-
private:
TraceProcessorContext* const context_;
util::GzipDecompressor decompressor_;
@@ -52,7 +50,7 @@
size_t bytes_written_ = 0;
bool first_chunk_parsed_ = false;
- bool needs_more_input_ = false;
+ enum { kStreamBoundary, kMidStream } output_state_ = kStreamBoundary;
};
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/instruments/BUILD.gn b/src/trace_processor/importers/instruments/BUILD.gn
new file mode 100644
index 0000000..e5ac896
--- /dev/null
+++ b/src/trace_processor/importers/instruments/BUILD.gn
@@ -0,0 +1,55 @@
+# 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_utils.h",
+ "instruments_xml_tokenizer.h",
+ "row_parser.h",
+ ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../../include/perfetto/ext/base:base",
+ ]
+ if (enable_perfetto_trace_processor_mac_instruments) {
+ public_deps = [ ":row" ]
+ sources += [
+ "instruments_xml_tokenizer.cc",
+ "row_data_tracker.cc",
+ "row_data_tracker.h",
+ "row_parser.cc",
+ ]
+ deps += [
+ "../../../../gn:expat",
+ "../../../../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_utils.h b/src/trace_processor/importers/instruments/instruments_utils.h
new file mode 100644
index 0000000..b121468
--- /dev/null
+++ b/src/trace_processor/importers/instruments/instruments_utils.h
@@ -0,0 +1,34 @@
+/*
+ * 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_UTILS_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_UTILS_H_
+
+#include "perfetto/base/build_config.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+inline bool IsInstrumentsSupported() {
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_INSTRUMENTS)
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_UTILS_H_
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..5523c13
--- /dev/null
+++ b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc
@@ -0,0 +1,506 @@
+/*
+ * 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 <stdint.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"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_TP_INSTRUMENTS)
+#error \
+ "This file should not be built when enable_perfetto_trace_processor_mac_instruments=false"
+#endif
+
+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 %" PRId64 ", 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 %" PRId64
+ ", 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..ff17b7d
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_data_tracker.cc
@@ -0,0 +1,88 @@
+/*
+ * 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"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_TP_INSTRUMENTS)
+#error \
+ "This file should not be built when enable_perfetto_trace_processor_mac_instruments=false"
+#endif
+
+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..a5d463e
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_parser.cc
@@ -0,0 +1,130 @@
+/*
+ * 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"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_TP_INSTRUMENTS)
+#error \
+ "This file should not be built when enable_perfetto_trace_processor_mac_instruments=false"
+#endif
+
+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/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/proto_trace_parser_impl.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
index 68a30bd..393bd79 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
@@ -33,6 +33,7 @@
#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"
@@ -157,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);
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 0c1db93..f6b2304 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
@@ -61,6 +61,8 @@
int64_t /*ts*/,
InlineSchedWaking data) override;
+ void ParseLegacyV8ProfileEvent(int64_t ts, LegacyV8CpuProfileEvent) override;
+
private:
StringId GetMetatraceInternedString(uint64_t iid);
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 96c367c..360126c 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -39,6 +39,7 @@
#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"
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index be85689..268f45f 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -23,6 +23,7 @@
#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"
@@ -34,6 +35,7 @@
#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/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"
@@ -288,6 +290,15 @@
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()) {
// Delta timestamps require a valid ThreadDescriptor packet since the last
// packet loss.
@@ -437,4 +448,76 @@
return base::OkStatus();
}
+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 743258c..0627562 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.h
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.h
@@ -78,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/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index ab09809..e04c4f0 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -20,8 +20,6 @@
"android_input_event_parser.h",
"protolog_message_decoder.cc",
"protolog_message_decoder.h",
- "protolog_messages_tracker.cc",
- "protolog_messages_tracker.h",
"protolog_parser.cc",
"protolog_parser.h",
"shell_transitions_parser.cc",
diff --git a/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc
index de82a0e..71bca76 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc
+++ b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc
@@ -26,7 +26,8 @@
namespace perfetto::trace_processor {
-ProtoLogMessageDecoder::ProtoLogMessageDecoder() = default;
+ProtoLogMessageDecoder::ProtoLogMessageDecoder(TraceProcessorContext* context)
+ : context_(context) {}
ProtoLogMessageDecoder::~ProtoLogMessageDecoder() = default;
std::optional<DecodedMessage> ProtoLogMessageDecoder::Decode(
@@ -123,6 +124,11 @@
}
void ProtoLogMessageDecoder::TrackGroup(uint32_t id, const std::string& tag) {
+ auto tracked_group = tracked_groups_.Find(id);
+ if (tracked_group != nullptr && tracked_group->tag != tag) {
+ context_->storage->IncrementStats(
+ stats::winscope_protolog_view_config_collision);
+ }
tracked_groups_.Insert(id, TrackedGroup{tag});
}
@@ -132,6 +138,11 @@
uint32_t group_id,
const std::string& message,
const std::optional<std::string>& location) {
+ auto tracked_message = tracked_messages_.Find(message_id);
+ if (tracked_message != nullptr && tracked_message->message != message) {
+ context_->storage->IncrementStats(
+ stats::winscope_protolog_view_config_collision);
+ }
tracked_messages_.Insert(message_id,
TrackedMessage{level, group_id, message, location});
}
diff --git a/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h
index 8955c01..c99dd43 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h
@@ -59,12 +59,12 @@
class ProtoLogMessageDecoder : public Destructible {
public:
- explicit ProtoLogMessageDecoder();
+ explicit ProtoLogMessageDecoder(TraceProcessorContext* context);
virtual ~ProtoLogMessageDecoder() override;
static ProtoLogMessageDecoder* GetOrCreate(TraceProcessorContext* context) {
if (!context->protolog_message_decoder) {
- context->protolog_message_decoder.reset(new ProtoLogMessageDecoder());
+ context->protolog_message_decoder.reset(new ProtoLogMessageDecoder(context));
}
return static_cast<ProtoLogMessageDecoder*>(
context->protolog_message_decoder.get());
@@ -86,6 +86,7 @@
const std::optional<std::string>& location);
private:
+ TraceProcessorContext* const context_;
base::FlatHashMap<uint64_t, TrackedGroup> tracked_groups_;
base::FlatHashMap<uint64_t, TrackedMessage> tracked_messages_;
};
diff --git a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc
deleted file mode 100644
index 2cf095a..0000000
--- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc
+++ /dev/null
@@ -1,51 +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.
- */
-
-#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h"
-
-#include <cstdint>
-#include <optional>
-#include <vector>
-
-namespace perfetto::trace_processor {
-
-ProtoLogMessagesTracker::ProtoLogMessagesTracker() = default;
-ProtoLogMessagesTracker::~ProtoLogMessagesTracker() = default;
-
-void ProtoLogMessagesTracker::TrackMessage(
- TrackedProtoLogMessage tracked_protolog_message) {
- tracked_protolog_messages
- .Insert(tracked_protolog_message.message_id,
- std::vector<TrackedProtoLogMessage>())
- .first->emplace_back(tracked_protolog_message);
-}
-
-std::optional<std::vector<ProtoLogMessagesTracker::TrackedProtoLogMessage>*>
-ProtoLogMessagesTracker::GetTrackedMessagesByMessageId(uint64_t message_id) {
- auto* tracked_messages = tracked_protolog_messages.Find(message_id);
- if (tracked_messages == nullptr) {
- // No tracked messages found for this id
- return std::nullopt;
- }
- return tracked_messages;
-}
-
-void ProtoLogMessagesTracker::ClearTrackedMessagesForMessageId(
- uint64_t message_id) {
- tracked_protolog_messages.Erase(message_id);
-}
-
-} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
deleted file mode 100644
index 3a34ffb..0000000
--- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
+++ /dev/null
@@ -1,69 +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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGES_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGES_TRACKER_H_
-
-#include <cstdint>
-#include <optional>
-#include <string>
-#include <vector>
-
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/winscope_tables_py.h"
-#include "src/trace_processor/types/destructible.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto::trace_processor {
-
-class ProtoLogMessagesTracker : public Destructible {
- public:
- explicit ProtoLogMessagesTracker();
- virtual ~ProtoLogMessagesTracker() override;
-
- struct TrackedProtoLogMessage {
- uint64_t message_id;
- std::vector<int64_t> sint64_params;
- std::vector<double> double_params;
- std::vector<bool> boolean_params;
- std::vector<std::string> string_params;
- std::optional<StringId> stacktrace;
- tables::ProtoLogTable::Id table_row_id;
- int64_t timestamp;
- };
-
- static ProtoLogMessagesTracker* GetOrCreate(TraceProcessorContext* context) {
- if (!context->protolog_messages_tracker) {
- context->protolog_messages_tracker.reset(new ProtoLogMessagesTracker());
- }
- return static_cast<ProtoLogMessagesTracker*>(
- context->protolog_messages_tracker.get());
- }
-
- void TrackMessage(TrackedProtoLogMessage tracked_protolog_message);
- std::optional<std::vector<ProtoLogMessagesTracker::TrackedProtoLogMessage>*>
- GetTrackedMessagesByMessageId(uint64_t message_id);
- void ClearTrackedMessagesForMessageId(uint64_t message_id);
-
- private:
- base::FlatHashMap<uint64_t, std::vector<TrackedProtoLogMessage>>
- tracked_protolog_messages;
-};
-
-} // namespace perfetto::trace_processor
-
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGES_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.cc b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
index 0df2967..9233080 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
@@ -35,7 +35,6 @@
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
#include "src/trace_processor/importers/proto/winscope/protolog_message_decoder.h"
-#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h"
#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
@@ -134,33 +133,19 @@
row_id, decoded_message.log_level, decoded_message.group_tag,
decoded_message.message, stacktrace, location);
} else {
- // Viewer config used to decode messages not yet processed for this message.
- // Delaying decoding...
- auto* protolog_message_tracker =
- ProtoLogMessagesTracker::GetOrCreate(context_);
-
- protolog_message_tracker->TrackMessage(
- ProtoLogMessagesTracker::TrackedProtoLogMessage{
- protolog_message.message_id(), std::move(sint64_params),
- std::move(double_params), std::move(boolean_params),
- std::move(string_params), stacktrace, row_id, timestamp});
+ // Failed to fully decode the message.
+ // This shouldn't happen since we should have processed all viewer config
+ // messages in the tokenization state, and process the protolog messages
+ // only in the parsing state.
+ context_->storage->IncrementStats(
+ stats::winscope_protolog_message_decoding_failed);
}
}
-void ProtoLogParser::ParseProtoLogViewerConfig(protozero::ConstBytes blob) {
+void ProtoLogParser::ParseAndAddViewerConfigToMessageDecoder(
+ protozero::ConstBytes blob) {
protos::pbzero::ProtoLogViewerConfig::Decoder protolog_viewer_config(blob);
- AddViewerConfigToMessageDecoder(protolog_viewer_config);
-
- for (auto it = protolog_viewer_config.messages(); it; ++it) {
- protos::pbzero::ProtoLogViewerConfig::MessageData::Decoder message_data(
- *it);
- ProcessPendingMessagesWithId(message_data.message_id());
- }
-}
-
-void ProtoLogParser::AddViewerConfigToMessageDecoder(
- protos::pbzero::ProtoLogViewerConfig::Decoder& protolog_viewer_config) {
auto* protolog_message_decoder =
ProtoLogMessageDecoder::GetOrCreate(context_);
@@ -186,38 +171,6 @@
}
}
-void ProtoLogParser::ProcessPendingMessagesWithId(uint64_t message_id) {
- auto* protolog_message_decoder =
- ProtoLogMessageDecoder::GetOrCreate(context_);
- auto* protolog_message_tracker =
- ProtoLogMessagesTracker::GetOrCreate(context_);
-
- auto tracked_messages_opt =
- protolog_message_tracker->GetTrackedMessagesByMessageId(message_id);
-
- if (tracked_messages_opt.has_value()) {
- // There are undecoded messages that can now be docoded to populate the
- // table.
- for (const auto& tracked_message : *tracked_messages_opt.value()) {
- auto message = protolog_message_decoder
- ->Decode(tracked_message.message_id,
- tracked_message.sint64_params,
- tracked_message.double_params,
- tracked_message.boolean_params,
- tracked_message.string_params)
- .value();
-
- std::optional<std::string> location = message.location;
- PopulateReservedRowWithMessage(
- tracked_message.table_row_id, message.log_level, message.group_tag,
- message.message, tracked_message.stacktrace, location);
- }
-
- // Clear to avoid decoding again
- protolog_message_tracker->ClearTrackedMessagesForMessageId(message_id);
- }
-}
-
void ProtoLogParser::PopulateReservedRowWithMessage(
tables::ProtoLogTable::Id table_row_id,
ProtoLogLevel log_level,
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.h b/src/trace_processor/importers/proto/winscope/protolog_parser.h
index b7e0e5f..13348d6 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.h
@@ -23,7 +23,6 @@
#include "protos/perfetto/trace/android/protolog.pbzero.h"
#include "src/trace_processor/importers/proto/winscope/protolog_message_decoder.h"
-#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/util/descriptors.h"
#include "src/trace_processor/util/proto_to_args_parser.h"
@@ -38,12 +37,9 @@
void ParseProtoLogMessage(PacketSequenceStateGeneration* sequence_state,
protozero::ConstBytes,
int64_t timestamp);
- void ParseProtoLogViewerConfig(protozero::ConstBytes);
+ void ParseAndAddViewerConfigToMessageDecoder(protozero::ConstBytes);
private:
- void AddViewerConfigToMessageDecoder(
- protos::pbzero::ProtoLogViewerConfig::Decoder& protolog_viewer_config);
- void ProcessPendingMessagesWithId(uint64_t message_id);
void PopulateReservedRowWithMessage(tables::ProtoLogTable::Id table_row_id,
ProtoLogLevel level,
std::string& group_tag,
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index 9946a23..ac81193 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_.ParseAndAddViewerConfigToMessageDecoder(
+ 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/read_trace.cc b/src/trace_processor/read_trace.cc
index cd4050a..91e784a 100644
--- a/src/trace_processor/read_trace.cc
+++ b/src/trace_processor/read_trace.cc
@@ -94,10 +94,7 @@
std::unique_ptr<ChunkedTraceReader> reader(
new SerializingProtoTraceReader(output));
GzipTraceParser parser(std::move(reader));
-
RETURN_IF_ERROR(parser.ParseUnowned(data, size));
- if (parser.needs_more_input())
- return base::ErrStatus("Cannot decompress partial trace file");
return parser.NotifyEndOfFile();
}
diff --git a/src/trace_processor/sorter/BUILD.gn b/src/trace_processor/sorter/BUILD.gn
index 913ed83..22400e4 100644
--- a/src/trace_processor/sorter/BUILD.gn
+++ b/src/trace_processor/sorter/BUILD.gn
@@ -33,6 +33,7 @@
"../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",
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/storage/stats.h b/src/trace_processor/storage/stats.h
index 5d340bb..a8c7892 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -353,6 +353,12 @@
F(winscope_protolog_missing_interned_stacktrace_parse_errors, \
kSingle, kInfo, kAnalysis, \
"Failed to find interned ProtoLog stacktrace."), \
+ F(winscope_protolog_message_decoding_failed, \
+ kSingle, kInfo, kAnalysis, \
+ "Failed to decode ProtoLog message."), \
+ F(winscope_protolog_view_config_collision, \
+ kSingle, kInfo, kAnalysis, \
+ "Got a viewer config collision!"), \
F(winscope_viewcapture_parse_errors, \
kSingle, kInfo, kAnalysis, \
"ViewCapture packet has unknown fields, which results in some " \
@@ -379,7 +385,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 {
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 513e251..2a07c33 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -659,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_; }
@@ -1151,6 +1158,7 @@
&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 bbe4f0a..60cb1fc 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -281,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',
@@ -638,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,
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 6d3c6cb..f993a51 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -73,6 +73,7 @@
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 d6493a9..53876b0 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -30,6 +30,7 @@
#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"
@@ -105,6 +106,8 @@
});
trace_file_tracker = std::make_unique<TraceFileTracker>(this);
+ legacy_v8_cpu_profile_tracker =
+ std::make_unique<LegacyV8CpuProfileTracker>(this);
}
TraceProcessorContext::TraceProcessorContext() = default;
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9f101bd..bb47ab5 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -51,6 +51,9 @@
#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_utils.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 +358,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 +401,14 @@
context_.perf_record_parser =
std::make_unique<perf_importer::RecordParser>(&context_);
+ if (instruments_importer::IsInstrumentsSupported()) {
+ 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 +922,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_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 c448bef..3e7ba55 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -46,7 +46,9 @@
class FuchsiaRecordParser;
class GlobalArgsTracker;
class HeapGraphTracker;
+class InstrumentsRowParser;
class JsonTraceParser;
+class LegacyV8CpuProfileTracker;
class MachineTracker;
class MappingTracker;
class MetadataTracker;
@@ -129,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
@@ -136,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;
@@ -165,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
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/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index 393be27..490881b 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -146,8 +146,8 @@
stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
}
- std::array<uint32_t, 10> periods_ms{};
- std::array<uint32_t, 10> ticks{};
+ std::array<uint32_t, 11> periods_ms{};
+ std::array<uint32_t, 11> ticks{};
static_assert(periods_ms.size() == ticks.size(), "must have same size");
periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
@@ -160,6 +160,7 @@
periods_ms[7] = ClampTo10Ms(cfg.psi_period_ms(), "psi_period_ms");
periods_ms[8] = ClampTo10Ms(cfg.thermal_period_ms(), "thermal_period_ms");
periods_ms[9] = ClampTo10Ms(cfg.cpuidle_period_ms(), "cpuidle_period_ms");
+ periods_ms[10] = ClampTo10Ms(cfg.gpufreq_period_ms(), "gpufreq_period_ms");
tick_period_ms_ = 0;
for (uint32_t ms : periods_ms) {
@@ -188,6 +189,7 @@
psi_ticks_ = ticks[7];
thermal_ticks_ = ticks[8];
cpuidle_ticks_ = ticks[9];
+ gpufreq_ticks_ = ticks[10];
}
void SysStatsDataSource::Start() {
@@ -249,6 +251,9 @@
if (cpuidle_ticks_ && tick_ % cpuidle_ticks_ == 0)
ReadCpuIdleStates(sys_stats);
+ if (gpufreq_ticks_ && tick_ % gpufreq_ticks_ == 0)
+ ReadGpuFrequency(sys_stats);
+
sys_stats->set_collection_end_timestamp(
static_cast<uint64_t>(base::GetBootTimeNs().count()));
@@ -376,6 +381,44 @@
}
}
+std::optional<uint64_t> SysStatsDataSource::ReadAMDGpuFreq() {
+ std::optional<std::string> amd_gpu_freq =
+ ReadFileToString("/sys/class/drm/card0/device/pp_dpm_sclk");
+ if (!amd_gpu_freq) {
+ return std::nullopt;
+ }
+ for (base::StringSplitter lines(*amd_gpu_freq, '\n'); lines.Next();) {
+ base::StringView line(lines.cur_token(), lines.cur_token_size());
+ // Current frequency indicated with asterisk.
+ if (line.EndsWith("*")) {
+ for (base::StringSplitter words(line.ToStdString(), ' '); words.Next();) {
+ if (!base::EndsWith(words.cur_token(), "Mhz"))
+ continue;
+ // Strip suffix "Mhz".
+ std::string maybe_freq = std::string(words.cur_token())
+ .substr(0, words.cur_token_size() - 3);
+ auto freq = base::StringToUInt32(maybe_freq);
+ return freq;
+ }
+ }
+ }
+ return std::nullopt;
+}
+
+void SysStatsDataSource::ReadGpuFrequency(protos::pbzero::SysStats* sys_stats) {
+ std::optional<uint64_t> freq;
+ // Intel GPU Frequency.
+ freq = ReadFileToUInt64("/sys/class/drm/card0/gt_act_freq_mhz");
+ if (freq) {
+ sys_stats->add_gpufreq_mhz((*freq));
+ return;
+ }
+ freq = ReadAMDGpuFreq();
+ if (freq) {
+ sys_stats->add_gpufreq_mhz((*freq));
+ }
+}
+
void SysStatsDataSource::ReadDiskStat(protos::pbzero::SysStats* sys_stats) {
size_t rsize = ReadFile(&diskstat_fd_, "/proc/diskstats");
if (!rsize) {
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.h b/src/traced/probes/sys_stats/sys_stats_data_source.h
index 7e4749b..5e212f5 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.h
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.h
@@ -102,6 +102,8 @@
void ReadPsi(protos::pbzero::SysStats* sys_stats);
void ReadThermalZones(protos::pbzero::SysStats* sys_stats);
void ReadCpuIdleStates(protos::pbzero::SysStats* sys_stats);
+ void ReadGpuFrequency(protos::pbzero::SysStats* sys_stats);
+ std::optional<uint64_t> ReadAMDGpuFreq();
size_t ReadFile(base::ScopedFile*, const char* path);
@@ -133,6 +135,7 @@
uint32_t psi_ticks_ = 0;
uint32_t thermal_ticks_ = 0;
uint32_t cpuidle_ticks_ = 0;
+ uint32_t gpufreq_ticks_ = 0;
std::unique_ptr<CpuFreqInfo> cpu_freq_info_;
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
index fd5dcb2..7a934c1 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
@@ -212,7 +212,13 @@
const char kMockThermalType[] = "TSR0";
const uint64_t kMockCpuIdleStateTime = 10000;
const char kMockCpuIdleStateName[] = "MOCK_STATE_NAME";
-
+const uint64_t kMockIntelGpuFreq = 300;
+// kMockAMDGpuFreq whitespace is intentional.
+const char kMockAMDGpuFreq[] = R"(
+0: 200Mhz
+1: 400Mhz *
+2: 2000Mhz
+)";
class TestSysStatsDataSource : public SysStatsDataSource {
public:
TestSysStatsDataSource(base::TaskRunner* task_runner,
@@ -581,6 +587,51 @@
}
}
+TEST_F(SysStatsDataSourceTest, IntelGpuFrequency) {
+ DataSourceConfig config;
+ protos::gen::SysStatsConfig sys_cfg;
+ sys_cfg.set_gpufreq_period_ms(10);
+ config.set_sys_stats_config_raw(sys_cfg.SerializeAsString());
+ auto data_source = GetSysStatsDataSource(config);
+
+ EXPECT_CALL(*data_source,
+ ReadFileToUInt64("/sys/class/drm/card0/gt_act_freq_mhz"))
+ .WillRepeatedly(Return(std::optional<uint64_t>(kMockIntelGpuFreq)));
+
+ WaitTick(data_source.get());
+
+ protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket();
+ ASSERT_TRUE(packet.has_sys_stats());
+ const auto& sys_stats = packet.sys_stats();
+ EXPECT_EQ(sys_stats.gpufreq_mhz_size(), 1);
+ uint32_t intel_gpufreq = 300;
+ EXPECT_EQ(sys_stats.gpufreq_mhz()[0], intel_gpufreq);
+}
+
+TEST_F(SysStatsDataSourceTest, AMDGpuFrequency) {
+ DataSourceConfig config;
+ protos::gen::SysStatsConfig sys_cfg;
+ sys_cfg.set_gpufreq_period_ms(10);
+ config.set_sys_stats_config_raw(sys_cfg.SerializeAsString());
+ auto data_source = GetSysStatsDataSource(config);
+
+ // Ignore other GPU freq calls.
+ EXPECT_CALL(*data_source,
+ ReadFileToUInt64("/sys/class/drm/card0/gt_act_freq_mhz"));
+ EXPECT_CALL(*data_source,
+ ReadFileToString("/sys/class/drm/card0/device/pp_dpm_sclk"))
+ .WillRepeatedly(Return(std::optional<std::string>(kMockAMDGpuFreq)));
+
+ WaitTick(data_source.get());
+
+ protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket();
+ ASSERT_TRUE(packet.has_sys_stats());
+ const auto& sys_stats = packet.sys_stats();
+ EXPECT_EQ(sys_stats.gpufreq_mhz_size(), 1);
+ uint32_t amd_gpufreq = 400;
+ EXPECT_EQ(sys_stats.gpufreq_mhz()[0], amd_gpufreq);
+}
+
TEST_F(SysStatsDataSourceTest, DevfreqAll) {
DataSourceConfig config;
protos::gen::SysStatsConfig sys_cfg;
diff --git a/src/tracing/core/in_process_shared_memory.cc b/src/tracing/core/in_process_shared_memory.cc
index 65ae53a..435bcdb 100644
--- a/src/tracing/core/in_process_shared_memory.cc
+++ b/src/tracing/core/in_process_shared_memory.cc
@@ -21,7 +21,7 @@
InProcessSharedMemory::~InProcessSharedMemory() = default;
InProcessSharedMemory::Factory::~Factory() = default;
-void* InProcessSharedMemory::start() const {
+const void* InProcessSharedMemory::start() const {
return mem_.Get();
}
size_t InProcessSharedMemory::size() const {
diff --git a/src/tracing/core/in_process_shared_memory.h b/src/tracing/core/in_process_shared_memory.h
index 353d44b..9ac692f 100644
--- a/src/tracing/core/in_process_shared_memory.h
+++ b/src/tracing/core/in_process_shared_memory.h
@@ -42,7 +42,8 @@
}
// SharedMemory implementation.
- void* start() const override;
+ using SharedMemory::start; // Equal priority to const and non-const versions
+ const void* start() const override;
size_t size() const override;
class Factory : public SharedMemory::Factory {
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/src/tracing/ipc/posix_shared_memory.h b/src/tracing/ipc/posix_shared_memory.h
index d620991..d7b9e20 100644
--- a/src/tracing/ipc/posix_shared_memory.h
+++ b/src/tracing/ipc/posix_shared_memory.h
@@ -61,7 +61,8 @@
int fd() const { return fd_.get(); }
// SharedMemory implementation.
- void* start() const override { return start_; }
+ using SharedMemory::start; // Equal priority to const and non-const versions
+ const void* start() const override { return start_; }
size_t size() const override { return size_; }
private:
diff --git a/src/tracing/test/aligned_buffer_test.h b/src/tracing/test/aligned_buffer_test.h
index 18ee4ba..d604f4e 100644
--- a/src/tracing/test/aligned_buffer_test.h
+++ b/src/tracing/test/aligned_buffer_test.h
@@ -20,6 +20,7 @@
#include <stdlib.h>
#include <memory>
+#include <utility>
#include "perfetto/ext/base/utils.h"
#include "src/tracing/test/test_shared_memory.h"
@@ -34,7 +35,10 @@
void SetUp() override;
void TearDown() override;
- uint8_t* buf() const { return reinterpret_cast<uint8_t*>(buf_->start()); }
+ uint8_t* buf() { return const_cast<uint8_t*>(std::as_const(*this).buf()); }
+ const uint8_t* buf() const {
+ return reinterpret_cast<const uint8_t*>(buf_->start());
+ }
size_t buf_size() const { return buf_->size(); }
size_t page_size() const { return page_size_; }
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/sfgate-gzip-multi-stream.json.gz.sha256 b/test/data/sfgate-gzip-multi-stream.json.gz.sha256
new file mode 100644
index 0000000..eca4350
--- /dev/null
+++ b/test/data/sfgate-gzip-multi-stream.json.gz.sha256
@@ -0,0 +1 @@
+c46a162875fd826893daec6f1271f8d98710d7ae9a096c108c4a0635b06f14ac
\ 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..14199e0 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -72,6 +72,8 @@
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.gzip.tests import Gzip
+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 +240,8 @@
*Zip(index_path, 'parser/zip', 'Zip').fetch(),
*AndroidInputEvent(index_path, 'parser/android',
'AndroidInputEvent').fetch(),
+ *Instruments(index_path, 'parser/instruments', 'Instruments').fetch(),
+ *Gzip(index_path, 'parser/gzip', 'Gzip').fetch(),
]
metrics_tests = [
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/gzip/tests.py b/test/trace_processor/diff_tests/parser/gzip/tests.py
new file mode 100644
index 0000000..a0ac38f
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/gzip/tests.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+# 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 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, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Gzip(TestSuite):
+
+ def test_gzip_multi_stream(self):
+ return DiffTestBlueprint(
+ trace=DataPath('sfgate-gzip-multi-stream.json.gz'),
+ query='''select ts, dur, name from slice limit 10''',
+ out=Csv('''
+ "ts","dur","name"
+ 2213649212614000,239000,"ThreadTimers::sharedTimerFiredInternal"
+ 2213649212678000,142000,"LayoutView::hitTest"
+ 2213649214331000,34000,"ThreadTimers::sharedTimerFiredInternal"
+ 2213649215569000,16727000,"ThreadTimers::sharedTimerFiredInternal"
+ 2213649216760000,50000,"Node::updateDistribution"
+ 2213649217290000,1373000,"StyleElement::processStyleSheet"
+ 2213649218908000,4862000,"Document::updateRenderTree"
+ 2213649218917000,50000,"Node::updateDistribution"
+ 2213649218970000,4796000,"Document::updateStyle"
+ 2213649218995000,54000,"RuleSet::addRulesFromSheet"
+ '''))
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/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/gen_clickhouse_bigtrace_protos.py b/tools/gen_clickhouse_bigtrace_protos.py
new file mode 100755
index 0000000..3b0d3b2
--- /dev/null
+++ b/tools/gen_clickhouse_bigtrace_protos.py
@@ -0,0 +1,63 @@
+#!/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 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 subprocess
+import os
+"""
+Compile the gRPC python code for Clickhouse for Bigtrace
+and modify the include paths to point to the correct file paths
+
+"""
+
+
+def main():
+ subprocess.run([
+ "python",
+ "-m",
+ "grpc_tools.protoc",
+ "-I.",
+ "--python_out=python/perfetto/bigtrace_clickhouse",
+ "--pyi_out=python/perfetto/bigtrace_clickhouse",
+ "protos/perfetto/bigtrace/orchestrator.proto",
+ "protos/perfetto/trace_processor/trace_processor.proto",
+ "protos/perfetto/common/descriptor.proto",
+ "protos/perfetto/trace_processor/metatrace_categories.proto",
+ ])
+ subprocess.run([
+ "python",
+ "-m",
+ "grpc_tools.protoc",
+ "-I.",
+ "--python_out=python/perfetto/bigtrace_clickhouse",
+ "--pyi_out=python/perfetto/bigtrace_clickhouse",
+ "--grpc_python_out=python/perfetto/bigtrace_clickhouse",
+ "protos/perfetto/bigtrace/orchestrator.proto",
+ ])
+ subprocess.run([
+ "sed",
+ "-i",
+ "-e",
+ "s/protos\.perfetto/\./",
+ "python/perfetto/bigtrace_clickhouse/protos/perfetto/bigtrace/orchestrator_pb2_grpc.py",
+ "python/perfetto/bigtrace_clickhouse/protos/perfetto/bigtrace/orchestrator_pb2.py",
+ "python/perfetto/bigtrace_clickhouse/protos/perfetto/bigtrace/orchestrator_pb2.pyi",
+ "python/perfetto/bigtrace_clickhouse/protos/perfetto/trace_processor/trace_processor_pb2.py",
+ "python/perfetto/bigtrace_clickhouse/protos/perfetto/trace_processor/trace_processor_pb2.pyi",
+ ])
+ return 0
+
+
+if __name__ == "__main__":
+ main()
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/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/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 9766b37..e6196f3 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -40,7 +40,7 @@
import {horizontalScrollToTs} from '../frontend/scroll_helper';
import {DisposableStack} from '../base/disposable_stack';
import {TraceContext} from '../frontend/trace_context';
-import {Workspace} from '../frontend/workspace';
+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
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 7bba871..3a55bc2 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -87,13 +87,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';
@@ -374,10 +373,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> {
@@ -653,28 +648,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) {
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 440f907..d7a9e0e 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -35,7 +35,7 @@
THREAD_STATE_TRACK_KIND,
} from '../core/track_kinds';
import {exists, Optional} from '../base/utils';
-import {GroupNode, ContainerNode, TrackNode} from '../frontend/workspace';
+import {GroupNode, ContainerNode, TrackNode} from '../public/workspace';
const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
const MEM_DMA = 'mem.dma_buffer';
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 4ac571f..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';
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 3c50299..1bb7980 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
@@ -27,7 +27,7 @@
import {StartupDetailsPanel} from './startup_details_panel';
import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
import {CriticalUserInteractionTrack} from './critical_user_interaction_track';
-import {TrackNode} from '../../frontend/workspace';
+import {TrackNode} from '../../public/workspace';
import {globals} from '../../frontend/globals';
function addCriticalUserInteractionTrack() {
diff --git a/ui/src/core_plugins/commands/index.ts b/ui/src/core_plugins/commands/index.ts
index 91c3d71..0430f63 100644
--- a/ui/src/core_plugins/commands/index.ts
+++ b/ui/src/core_plugins/commands/index.ts
@@ -33,7 +33,7 @@
addSqlTableTabImpl,
SqlTableTabConfig,
} from '../../frontend/sql_table_tab';
-import {Workspace} from '../../frontend/workspace';
+import {Workspace} from '../../public/workspace';
const SQL_STATS = `
with first as (select started as ts from sqlstats limit 1)
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 c620ebc..0dd4218 100644
--- a/ui/src/core_plugins/cpu_freq/index.ts
+++ b/ui/src/core_plugins/cpu_freq/index.ts
@@ -12,427 +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';
-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;
-
-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;
- }
-}
+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> {
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 196f65f..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,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {assertExists} from '../../base/logging';
import {Time} from '../../base/time';
import {Actions} from '../../common/actions';
import {LegacySelection} from '../../common/state';
-import {getColorForSlice} from '../../core/colorizer';
+import {getColorForSample} from '../../core/colorizer';
import {
BaseSliceTrack,
OnSliceClickArgs,
@@ -23,9 +24,13 @@
import {globals} from '../../frontend/globals';
import {NAMED_ROW, NamedRow} from '../../frontend/named_slice_track';
import {NewTrackArgs} from '../../frontend/track';
-import {Slice} from '../../public';
+import {NUM, Slice} from '../../public';
-export class CpuProfileTrack extends BaseSliceTrack<Slice, NamedRow> {
+interface CpuProfileRow extends NamedRow {
+ callsiteId: number;
+}
+
+export class CpuProfileTrack extends BaseSliceTrack<Slice, CpuProfileRow> {
constructor(
args: NewTrackArgs,
private utid: number,
@@ -33,14 +38,14 @@
super(args);
}
- protected getRowSpec(): NamedRow {
- return NAMED_ROW;
+ protected getRowSpec(): CpuProfileRow {
+ return {...NAMED_ROW, callsiteId: NUM};
}
- protected rowToSlice(row: NamedRow): Slice {
+ protected rowToSlice(row: CpuProfileRow): 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};
}
@@ -56,7 +61,13 @@
getSqlSource(): string {
return `
- select p.id, ts, 0 as dur, 0 as depth, 'CPU Sample' as name
+ select
+ p.id,
+ ts,
+ 0 as dur,
+ 0 as depth,
+ 'CPU Sample' as name,
+ callsite_id as callsiteId
from cpu_profile_stack_sample p
where utid = ${this.utid}
order by ts
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/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 3039b38..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> {
@@ -104,7 +97,6 @@
() => this.sel?.type,
]);
private flamegraphAttrs?: QueryFlamegraphAttrs;
- private cache = new LegacyFlamegraphCache('heap_profile');
constructor(
private engine: Engine,
@@ -116,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;
@@ -318,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: [
@@ -381,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 c2cdf92..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';
@@ -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/sched/index.ts b/ui/src/core_plugins/sched/index.ts
index 2523a84..b35638b 100644
--- a/ui/src/core_plugins/sched/index.ts
+++ b/ui/src/core_plugins/sched/index.ts
@@ -14,7 +14,7 @@
import {addSqlTableTab} from '../../frontend/sql_table_tab_command';
import {sqlTableRegistry} from '../../frontend/widgets/sql/table/sql_table_registry';
-import {TrackNode} from '../../frontend/workspace';
+import {TrackNode} from '../../public/workspace';
import {
PerfettoPlugin,
PluginContextTrace,
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index 8bec08e..aefc40b 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -23,12 +23,7 @@
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} from '../common/state';
-import {assertExists} from '../base/logging';
+import {AreaSelection} from '../common/state';
import {Monitor} from '../base/monitor';
import {
CPU_PROFILE_TRACK_KIND,
@@ -38,10 +33,10 @@
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;
@@ -55,7 +50,6 @@
private cpuProfileFlamegraphAttrs?: QueryFlamegraphAttrs;
private perfSampleFlamegraphAttrs?: QueryFlamegraphAttrs;
private sliceFlamegraphAttrs?: QueryFlamegraphAttrs;
- private legacyFlamegraphSelection?: FlamegraphSelectionParams;
private getCurrentView(): string | undefined {
const types = this.getViews().map(({key}) => key);
@@ -103,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) {
@@ -364,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;
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/debug_tracks.ts b/ui/src/frontend/debug_tracks/debug_tracks.ts
index 4286a2b..ee7ef32 100644
--- a/ui/src/frontend/debug_tracks/debug_tracks.ts
+++ b/ui/src/frontend/debug_tracks/debug_tracks.ts
@@ -24,7 +24,7 @@
import {Engine} from '../../trace_processor/engine';
import {DebugCounterTrack} from './counter_track';
import {ARG_PREFIX} from './details_tab';
-import {TrackNode} from '../workspace';
+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
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_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 4838a09..1e8b3d6 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -20,7 +20,7 @@
import {Flow, globals} from './globals';
import {RenderedPanelInfo} from './panel_container';
import {PxSpan, TimeScale} from './time_scale';
-import {TrackNode} from './workspace';
+import {TrackNode} from '../public/workspace';
const TRACK_GROUP_CONNECTION_OFFSET = 5;
const TRIANGLE_SIZE = 5;
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index ee034d9..b889596 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -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,7 +51,7 @@
import {TraceContext} from './trace_context';
import {Registry} from '../base/registry';
import {SidebarMenuItem} from '../public';
-import {Workspace} from './workspace';
+import {Workspace} from '../public/workspace';
const INSTANT_FOCUS_DURATION = 1n;
const INCOMPLETE_SLICE_DURATION = 30_000n;
@@ -134,13 +132,6 @@
dur?: duration;
}
-export interface CpuProfileDetails {
- id?: number;
- ts?: time;
- utid?: number;
- stack?: CallsiteInfo[];
-}
-
export interface QuantizedLoad {
start: time;
end: time;
@@ -224,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;
@@ -244,7 +234,6 @@
private _currentWorkspace: Workspace;
omnibox = new OmniboxManager();
- areaFlamegraphCache = new LegacyFlamegraphCache('area');
scrollToTrackUri?: string;
httpRpcState: HttpRpcState = {connected: false};
@@ -371,7 +360,6 @@
this._selectedFlows = [];
this._visibleFlowCategories = new Map<string, boolean>();
this._threadStateDetails = {};
- this._cpuProfileDetails = {};
this.engines.clear();
this._selectionManager.clear();
}
@@ -502,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;
}
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/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/pivot_table.ts b/ui/src/frontend/pivot_table.ts
index 784daa1..2b17b93 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -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}`;
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index 5451946..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();
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/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 8b4d837..5d02c78 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -39,7 +39,7 @@
import {PxSpan, TimeScale} from './time_scale';
import {exists} from '../base/utils';
import {classNames} from '../base/classnames';
-import {GroupNode} from './workspace';
+import {GroupNode} from '../public/workspace';
import {raf} from '../core/raf_scheduler';
import {Actions} from '../common/actions';
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 65adb48..e11cbbf 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -46,7 +46,7 @@
import {calculateResolution} from '../common/resolution';
import {featureFlags} from '../core/feature_flags';
import {Tree, TreeNode} from '../widgets/tree';
-import {TrackNode} from './workspace';
+import {TrackNode} from '../public/workspace';
export const SHOW_TRACK_DETAILS_BUTTON = featureFlags.register({
id: 'showTrackDetailsButton',
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/ui_main.ts
similarity index 77%
rename from ui/src/frontend/app.ts
rename to ui/src/frontend/ui_main.ts
index fbab675..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,33 +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') {
- 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;
- }
-
private cmds: Command[] = [
{
id: 'perfetto.SetTimestampFormat',
@@ -201,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: () => {
@@ -557,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,
@@ -617,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,
@@ -661,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) => {
@@ -698,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 === '>') {
@@ -817,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 cd842d9..40d851e 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -42,7 +42,7 @@
import {TrackPanel, getTitleFontSize} from './track_panel';
import {assertExists} from '../base/logging';
import {PxSpan, TimeScale} from './time_scale';
-import {GroupNode, Node, TrackNode} from './workspace';
+import {GroupNode, Node, TrackNode} from '../public/workspace';
import {fuzzyMatch, FuzzySegment} from '../base/fuzzy';
import {exists, Optional} from '../base/utils';
diff --git a/ui/src/frontend/visualized_args_tracks.ts b/ui/src/frontend/visualized_args_tracks.ts
index 6c22f50..eb7c3bc 100644
--- a/ui/src/frontend/visualized_args_tracks.ts
+++ b/ui/src/frontend/visualized_args_tracks.ts
@@ -19,7 +19,7 @@
import {NUM} from '../trace_processor/query_result';
import {globals} from './globals';
import {VisualisedArgsTrack} from './visualized_args_track';
-import {TrackNode} from './workspace';
+import {TrackNode} from '../public/workspace';
const VISUALISED_ARGS_SLICE_TRACK_URI_PREFIX = 'perfetto.VisualisedArgs';
diff --git a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
index d01b349..bdafb8a 100644
--- a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
+++ b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Optional} from '../../base/utils';
-import {GroupNode, TrackNode} from '../../frontend/workspace';
+import {GroupNode, TrackNode} from '../../public/workspace';
import {
PerfettoPlugin,
PluginContext,
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 ea13f72..63eeb30 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -22,7 +22,7 @@
import {PromptOption} from '../frontend/omnibox_manager';
import {LegacyDetailsPanel, TrackDescriptor} from './tracks';
import {TraceContext} from '../frontend/trace_context';
-import {Workspace} from '../frontend/workspace';
+import {Workspace} from './workspace';
export {Engine} from '../trace_processor/engine';
export {
diff --git a/ui/src/frontend/workspace.ts b/ui/src/public/workspace.ts
similarity index 100%
rename from ui/src/frontend/workspace.ts
rename to ui/src/public/workspace.ts
diff --git a/ui/src/frontend/workspace_unittest.ts b/ui/src/public/workspace_unittest.ts
similarity index 100%
rename from ui/src/frontend/workspace_unittest.ts
rename to ui/src/public/workspace_unittest.ts
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)`);
}
}