[trace-processor] Add importer for Instruments XML files
This adds an importer for Instruments XML files, as exported from an
Instruments trace with `xtrace export` (see code comments for the exact
command line and XML format).
Specifically, the XML tokenizer parses the XML into time sample Row data
structures, which are inserted into the sorter and then semi-trivially
parsed after sorting into frames, callsites, and samples. Row timestamps
are relative to the start of Instruments profiling -- there is support
for clock sync events in the profile to sync these timestamps against
Perfetto's boottime clock.
The XML parsing requires pulling in a dependency on libexpat, which is a
third-party streaming XML parser.
Bug: 362665903
Change-Id: I7e638f0b3c7275214aef3410f6263fb75e3968ac
diff --git a/Android.bp b/Android.bp
index d60a652..ee4e0fd 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",
@@ -12407,6 +12410,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",
@@ -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..0eb8b6c 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 +
@@ -1707,6 +1713,27 @@
],
)
+# GN target: //src/trace_processor/importers/instruments:instruments
+perfetto_filegroup(
+ name = "src_trace_processor_importers_instruments_instruments",
+ srcs = [
+ "src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc",
+ "src/trace_processor/importers/instruments/instruments_xml_tokenizer.h",
+ "src/trace_processor/importers/instruments/row_data_tracker.cc",
+ "src/trace_processor/importers/instruments/row_data_tracker.h",
+ "src/trace_processor/importers/instruments/row_parser.cc",
+ "src/trace_processor/importers/instruments/row_parser.h",
+ ],
+)
+
+# GN target: //src/trace_processor/importers/instruments:row
+perfetto_filegroup(
+ name = "src_trace_processor_importers_instruments_row",
+ srcs = [
+ "src/trace_processor/importers/instruments/row.h",
+ ],
+)
+
# GN target: //src/trace_processor/importers/json:full
perfetto_filegroup(
name = "src_trace_processor_importers_json_full",
@@ -6232,6 +6259,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 +6327,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 +6394,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 +6417,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 +6450,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 +6573,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 +6665,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 +6698,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 +6822,8 @@
":src_trace_processor_perfetto_sql_stdlib_stdlib",
":src_traceconv_gen_cc_trace_descriptor",
":src_traceconv_gen_cc_winscope_descriptor",
- ] + PERFETTO_CONFIG.deps.jsoncpp +
+ ] + PERFETTO_CONFIG.deps.expat +
+ PERFETTO_CONFIG.deps.jsoncpp +
PERFETTO_CONFIG.deps.sqlite +
PERFETTO_CONFIG.deps.sqlite_ext_percentile +
PERFETTO_CONFIG.deps.zlib +
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
index 69dbcbb..d97179a 100644
--- a/bazel/deps.bzl
+++ b/bazel/deps.bzl
@@ -67,6 +67,14 @@
)
_add_repo_if_not_existing(
+ new_git_repository,
+ name = "perfetto_dep_expat",
+ remote = "https://github.com/libexpat/libexpat",
+ commit = "fa75b96546c069d17b8f80d91e0f4ef0cde3790d", # R_2_6_2
+ build_file = "//bazel:expat.BUILD",
+ )
+
+ _add_repo_if_not_existing(
http_archive,
name = "perfetto_dep_zlib",
url = "https://storage.googleapis.com/perfetto/zlib-6d3f6aa0f87c9791ca7724c279ef61384f331dfd.tar.gz",
diff --git a/bazel/expat.BUILD b/bazel/expat.BUILD
new file mode 100644
index 0000000..10f88c7
--- /dev/null
+++ b/bazel/expat.BUILD
@@ -0,0 +1,64 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+
+cc_library(
+ name = "expat",
+ hdrs = glob(["expat/lib/*.h"]),
+ deps = [
+ ":expat_impl",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+cc_library(
+ name = "expat_impl",
+ srcs = [
+ "expat/lib/xmlparse.c",
+ "expat/lib/xmlrole.c",
+ "expat/lib/xmltok.c",
+ ],
+ hdrs = [
+ "expat/lib/ascii.h",
+ "expat/lib/asciitab.h",
+ "expat/lib/expat.h",
+ "expat/lib/expat_external.h",
+ "expat/lib/iasciitab.h",
+ "expat/lib/internal.h",
+ "expat/lib/latin1tab.h",
+ "expat/lib/nametab.h",
+ "expat/lib/siphash.h",
+ "expat/lib/utf8tab.h",
+ "expat/lib/winconfig.h",
+ "expat/lib/xmlrole.h",
+ "expat/lib/xmltok.h",
+ "expat/lib/xmltok_impl.c",
+ "expat/lib/xmltok_impl.h",
+ "expat/lib/xmltok_ns.c",
+ ],
+ deps = [
+ "@perfetto//buildtools/expat/include:expat_config",
+ ],
+ copts = [
+ "-DHAVE_EXPAT_CONFIG_H",
+ ] + PERFETTO_CONFIG.deps_copts.expat,
+ defines = [
+ "XML_STATIC"
+ ],
+ includes = [
+ "expat",
+ "expat/lib",
+ ],
+)
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index cbabb29..254bcb8 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -45,6 +45,7 @@
base_platform = ["//:perfetto_base_default_platform"],
zlib = ["@perfetto_dep_zlib//:zlib"],
+ expat = ["@perfetto_dep_expat//:expat"],
jsoncpp = ["@perfetto_dep_jsoncpp//:jsoncpp"],
linenoise = ["@perfetto_dep_linenoise//:linenoise"],
sqlite = ["@perfetto_dep_sqlite//:sqlite"],
@@ -83,6 +84,7 @@
# initialized with the Perfetto build files (i.e. via perfetto_deps()).
deps_copts = struct(
zlib = [],
+ expat = [],
jsoncpp = [],
linenoise = [],
sqlite = [],
diff --git a/buildtools/.gitignore b/buildtools/.gitignore
index a3e6376..42137c3 100644
--- a/buildtools/.gitignore
+++ b/buildtools/.gitignore
@@ -11,6 +11,7 @@
/catapult_trace_viewer/
/clang_format/
/clang/
+/expat/src/
/d8/
/debian_sid_arm-sysroot/
/debian_sid_arm64-sysroot/
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index b58b678..b4af804 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -1402,6 +1402,41 @@
deps = [ "//gn:default_deps" ]
}
+config("expat_public_config") {
+ defines = [ "XML_STATIC" ]
+ cflags = [
+ # Using -isystem instead of include_dirs (-I), so we don't need to suppress
+ # warnings coming from third-party headers. Doing so would mask warnings in
+ # our own code.
+ perfetto_isystem_cflag,
+ rebase_path("expat/src/expat/lib", root_build_dir),
+ perfetto_isystem_cflag,
+ rebase_path("expat/include", root_build_dir),
+ ]
+}
+
+config("no_format_warning") {
+ cflags = [ "-Wno-format" ]
+}
+
+static_library("expat") {
+ sources = [
+ "expat/src/expat/lib/expat.h",
+ "expat/src/expat/lib/xmlparse.c",
+ "expat/src/expat/lib/xmlrole.c",
+ "expat/src/expat/lib/xmltok.c",
+ ]
+
+ public_configs = [ ":expat_public_config" ]
+ configs -= [ "//gn/standalone:extra_warnings" ]
+ configs += [ ":no_format_warning" ]
+
+ defines = [
+ "_LIB",
+ "HAVE_EXPAT_CONFIG_H",
+ ]
+}
+
config("linenoise_config") {
visibility = _buildtools_visibility
cflags = [
diff --git a/buildtools/expat/include/BUILD b/buildtools/expat/include/BUILD
new file mode 100644
index 0000000..1c6cb8f
--- /dev/null
+++ b/buildtools/expat/include/BUILD
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cc_library(
+ name = "expat_config",
+ hdrs = [
+ "expat_config.h",
+ ],
+ includes = [
+ ".",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/buildtools/expat/include/expat_config.h b/buildtools/expat/include/expat_config.h
new file mode 100644
index 0000000..8ce2388
--- /dev/null
+++ b/buildtools/expat/include/expat_config.h
@@ -0,0 +1,146 @@
+/* expat_config.h. Generated from expat_config.h.in by configure. */
+/* expat_config.h.in. Generated from configure.ac by autoheader. */
+
+#ifndef EXPAT_CONFIG_H
+#define EXPAT_CONFIG_H 1
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* 1234 = LILENDIAN, 4321 = BIGENDIAN */
+#define BYTEORDER 1234
+
+/* Define to 1 if you have the `arc4random' function. */
+/* #undef HAVE_ARC4RANDOM */
+
+/* Define to 1 if you have the `arc4random_buf' function. */
+/* #define HAVE_ARC4RANDOM_BUF 1 */
+
+/* define if the compiler supports basic C++11 syntax */
+#define HAVE_CXX11 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `getpagesize' function. */
+#define HAVE_GETPAGESIZE 1
+
+/* Define to 1 if you have the `getrandom' function. */
+/* #define HAVE_GETRANDOM 1 */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `bsd' library (-lbsd). */
+/* #undef HAVE_LIBBSD */
+
+/* Define to 1 if you have a working `mmap' system call. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have `syscall' and `SYS_getrandom'. */
+/* #define HAVE_SYSCALL_GETRANDOM 1 */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "expat"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "https://github.com/libexpat/libexpat/issues"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "expat"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "expat 2.6.2"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "expat"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.6.2"
+
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+ required in a freestanding environment). This macro is provided for
+ backward compatibility; new code need not use it. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "2.6.2"
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+#if defined __BIG_ENDIAN__
+#define WORDS_BIGENDIAN 1
+#endif
+#else
+#ifndef WORDS_BIGENDIAN
+/* # undef WORDS_BIGENDIAN */
+#endif
+#endif
+
+/* Define to allow retrieving the byte offsets for attribute names and values.
+ */
+/* #undef XML_ATTR_INFO */
+
+/* Define to specify how much context to retain around the current parse
+ point, 0 to disable. */
+#define XML_CONTEXT_BYTES 1024
+
+/* Define to include code reading entropy from `/dev/urandom'. */
+#define XML_DEV_URANDOM 1
+
+/* Define to make parameter entity parsing functionality available. */
+#define XML_DTD 1
+
+/* Define as 1/0 to enable/disable support for general entities. */
+#define XML_GE 1
+
+/* Define to make XML Namespaces functionality available. */
+#define XML_NS 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+#endif // ndef EXPAT_CONFIG_H
\ No newline at end of file
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index e61fb9f..bfa7a8e 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -363,6 +363,14 @@
}
}
+group("expat") {
+ if (perfetto_root_path == "//") {
+ public_deps = [ "//buildtools:expat" ]
+ } else {
+ public_deps = [ "//third_party/expat:expat" ]
+ }
+}
+
if (enable_perfetto_trace_processor_json) {
group("jsoncpp") {
if (perfetto_root_path == "//") {
diff --git a/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/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..2bf3f3a 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;
@@ -65,6 +68,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/instruments/BUILD.gn b/src/trace_processor/importers/instruments/BUILD.gn
new file mode 100644
index 0000000..3ca66d7
--- /dev/null
+++ b/src/trace_processor/importers/instruments/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../gn/test.gni")
+
+source_set("row") {
+ sources = [ "row.h" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../containers",
+ "../../util:build_id",
+ ]
+}
+
+source_set("instruments") {
+ sources = [
+ "instruments_xml_tokenizer.cc",
+ "instruments_xml_tokenizer.h",
+ "row_data_tracker.cc",
+ "row_data_tracker.h",
+ "row_parser.cc",
+ "row_parser.h",
+ ]
+ public_deps = [ ":row" ]
+ deps = [
+ "../../../../gn:default_deps",
+ "../../../../gn:expat",
+ "../../../../include/perfetto/ext/base:base",
+ "../../../../include/perfetto/public",
+ "../../../../include/perfetto/trace_processor:trace_processor",
+ "../../../../protos/perfetto/trace:zero",
+ "../../sorter",
+ "../../storage",
+ "../../types",
+ "../common:common",
+ ]
+}
diff --git a/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc
new file mode 100644
index 0000000..51cee76
--- /dev/null
+++ b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.cc
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/instruments/instruments_xml_tokenizer.h"
+
+#include <map>
+
+#include <expat.h>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/public/fnv1a.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "src/trace_processor/importers/common/clock_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/importers/instruments/row_data_tracker.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+namespace {
+
+std::string MakeTrimmed(const char* chars, int len) {
+ while (len > 0 && std::isspace(*chars)) {
+ chars++;
+ len--;
+ }
+ while (len > 0 && std::isspace(chars[len - 1])) {
+ len--;
+ }
+ return std::string(chars, static_cast<size_t>(len));
+}
+
+} // namespace
+
+// The Instruments XML tokenizer reads instruments traces exported with:
+//
+// xctrace export --input /path/to/profile.trace --xpath
+// '//trace-toc/run/data/table[@schema="os-signpost and
+// @category="PointsOfInterest"] |
+// //trace-toc/run/data/table[@schema="time-sample"]'
+//
+// This exports two tables:
+// 1. Points of interest signposts
+// 2. Time samples
+//
+// The first is used for clock synchronization -- perfetto emits signpost events
+// during tracing which allow synchronization of the xctrace clock (relative to
+// start of profiling) with the perfetto boottime clock. The second contains
+// the samples themselves.
+//
+// The expected format of the rows in the clock sync table is:
+//
+// <row>
+// <event-time>1234</event-time>
+// <subsystem>dev.perfetto.clock_sync</subsystem>
+// <os-log-metadata>
+// <uint64>5678</uint64>
+// </os-log-metadata>
+// </row>
+//
+// There may be other rows with other data (from other subsystems), and
+// additional data in the row (such as thread data and other metadata) -- this
+// can be safely ignored.
+//
+// The expected format of the rows in the time sample table is:
+//
+// <row>
+// <sample-time>1234</sample-time>
+// <thread fmt="Thread name">
+// <tid>1</tid>
+// <process fmt="Process name">
+// <pid>1<pid>
+// </process>
+// </thread>
+// <core>0</core>
+// <backtrace>
+// <frame addr="0x120001234">
+// <binary
+// name="MyBinary" UUID="01234567-89ABC-CDEF-0123-456789ABCDEF"
+// load-addr="0x120000000" path="/path/to/MyBinary.app/MyBinary" />
+// </frame>
+// ... more frames ...
+// </row>
+//
+// Here we do not expect other rows with other data -- every row should have a
+// backtrace, and we use the presence of a backtrace to distinguish time samples
+// and clock sync eventst. However, there can be additional data in the row
+// (such as other metadata) -- this can be safely ignored.
+//
+// In addition, the XML format annotates elements with ids, to later reuse the
+// same data by id without needing to repeat its contents. For example, you
+// might have thread data for a sample:
+//
+// <thread id="11" fmt="My Thread"><tid id="12">10</tid>...</thread>
+//
+// and subsequent samples on that thread will simply have
+//
+// <thread ref="11" />
+//
+// This means that most elements have to have their pertinent data cached by id,
+// including any data store in child elements (which themselves also have to
+// be cached by id, like the <tid> in the example above).
+//
+// This importer reads the XML data using a streaming XML parser, which means
+// it has to maintain some parsing state (such as the current stack of tags, or
+// the current element for which we are reading data).
+class InstrumentsXmlTokenizer::Impl {
+ public:
+ explicit Impl(TraceProcessorContext* context)
+ : context_(context), data_(RowDataTracker::GetOrCreate(context_)) {
+ parser_ = XML_ParserCreate(nullptr);
+ XML_SetElementHandler(parser_, ElementStart, ElementEnd);
+ XML_SetCharacterDataHandler(parser_, CharacterData);
+ XML_SetUserData(parser_, this);
+
+ const char* subsystem = "dev.perfetto.instruments_clock";
+ clock_ = static_cast<ClockTracker::ClockId>(
+ PerfettoFnv1a(subsystem, strlen(subsystem)) | 0x80000000);
+
+ // Use the above clock if we can, in case there is no other trace and
+ // no clock sync events.
+ context_->clock_tracker->SetTraceTimeClock(clock_);
+ }
+ ~Impl() { XML_ParserFree(parser_); }
+
+ base::Status Parse(TraceBlobView view) {
+ if (!XML_Parse(parser_, reinterpret_cast<const char*>(view.data()),
+ static_cast<int>(view.length()), false)) {
+ return base::ErrStatus("XML parse error at line %lu: %s\n",
+ XML_GetCurrentLineNumber(parser_),
+ XML_ErrorString(XML_GetErrorCode(parser_)));
+ }
+ return base::OkStatus();
+ }
+
+ base::Status End() {
+ if (!XML_Parse(parser_, nullptr, 0, true)) {
+ return base::ErrStatus("XML parse error at end, line %lu: %s\n",
+ XML_GetCurrentLineNumber(parser_),
+ XML_ErrorString(XML_GetErrorCode(parser_)));
+ }
+ return base::OkStatus();
+ }
+
+ private:
+ static void ElementStart(void* data, const char* el, const char** attr) {
+ reinterpret_cast<Impl*>(data)->ElementStart(el, attr);
+ }
+ static void ElementEnd(void* data, const char* el) {
+ reinterpret_cast<Impl*>(data)->ElementEnd(el);
+ }
+ static void CharacterData(void* data, const char* chars, int len) {
+ reinterpret_cast<Impl*>(data)->CharacterData(chars, len);
+ }
+
+ void ElementStart(const char* el, const char** attrs) {
+ tag_stack_.emplace_back(el);
+ std::string_view tag_name = tag_stack_.back();
+
+ if (tag_name == "row") {
+ current_row_ = Row{};
+ } else if (tag_name == "thread") {
+ MaybeCachedRef<ThreadId> thread_lookup =
+ GetOrInsertByRef(attrs, thread_ref_to_thread_);
+ if (thread_lookup.is_new) {
+ auto new_thread = data_.NewThread();
+ thread_lookup.ref = new_thread.id;
+
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "fmt") {
+ new_thread.ptr->fmt = InternString(attrs[i + 1]);
+ }
+ }
+
+ current_new_thread_ = new_thread.id;
+ }
+ current_row_.thread = thread_lookup.ref;
+ } else if (tag_name == "process") {
+ MaybeCachedRef<ProcessId> process_lookup =
+ GetOrInsertByRef(attrs, process_ref_to_process_);
+ if (process_lookup.is_new) {
+ // Can only be processing a new process when processing a new thread.
+ PERFETTO_DCHECK(current_new_thread_ != kNullId);
+ auto new_process = data_.NewProcess();
+ process_lookup.ref = new_process.id;
+
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "fmt") {
+ new_process.ptr->fmt = InternString(attrs[i + 1]);
+ }
+ }
+
+ current_new_process_ = new_process.id;
+ }
+ if (current_new_thread_) {
+ data_.GetThread(current_new_thread_)->process = process_lookup.ref;
+ }
+ } else if (tag_name == "core") {
+ MaybeCachedRef<uint32_t> core_id_lookup =
+ GetOrInsertByRef(attrs, core_ref_to_core_);
+ if (core_id_lookup.is_new) {
+ current_new_core_id_ = &core_id_lookup.ref;
+ } else {
+ current_row_.core_id = core_id_lookup.ref;
+ }
+ } else if (tag_name == "sample-time" || tag_name == "event-time") {
+ // Share time lookup logic between sample times and event times, including
+ // updating the current row's sample time for both.
+ MaybeCachedRef<int64_t> time_lookup =
+ GetOrInsertByRef(attrs, sample_time_ref_to_time_);
+ if (time_lookup.is_new) {
+ current_time_ref_ = &time_lookup.ref;
+ } else {
+ current_row_.timestamp_ = time_lookup.ref;
+ }
+ } else if (tag_name == "subsystem") {
+ MaybeCachedRef<std::string> subsystem_lookup =
+ GetOrInsertByRef(attrs, subsystem_ref_to_subsystem_);
+ current_subsystem_ref_ = &subsystem_lookup.ref;
+ } else if (tag_name == "uint64") {
+ // The only uint64 we care about is the one for the clock sync, which is
+ // expected to contain exactly one uint64 value -- we'll
+ // map all uint64 to a single value and check against the subsystem
+ // when the row is closed.
+ MaybeCachedRef<uint64_t> uint64_lookup =
+ GetOrInsertByRef(attrs, os_log_metadata_or_uint64_ref_to_uint64_);
+ if (uint64_lookup.is_new) {
+ current_uint64_ref_ = &uint64_lookup.ref;
+ } else {
+ if (current_os_log_metadata_uint64_ref_) {
+ // Update the os-log-metadata's uint64 value with this uint64 value.
+ *current_os_log_metadata_uint64_ref_ = uint64_lookup.ref;
+ }
+ }
+ } else if (tag_name == "os-log-metadata") {
+ // The only os-log-metadata we care about is the one with the single
+ // uint64 clock sync value, so also map this to uint64 values with its own
+ // id.
+ MaybeCachedRef<uint64_t> uint64_lookup =
+ GetOrInsertByRef(attrs, os_log_metadata_or_uint64_ref_to_uint64_);
+ current_os_log_metadata_uint64_ref_ = &uint64_lookup.ref;
+ } else if (tag_name == "backtrace") {
+ MaybeCachedRef<BacktraceId> backtrace_lookup =
+ GetOrInsertByRef(attrs, backtrace_ref_to_backtrace_);
+ if (backtrace_lookup.is_new) {
+ backtrace_lookup.ref = data_.NewBacktrace().id;
+ }
+ current_row_.backtrace = backtrace_lookup.ref;
+ } else if (tag_name == "frame") {
+ MaybeCachedRef<BacktraceFrameId> frame_lookup =
+ GetOrInsertByRef(attrs, frame_ref_to_frame_);
+ if (frame_lookup.is_new) {
+ IdPtr<Frame> new_frame = data_.NewFrame();
+ frame_lookup.ref = new_frame.id;
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "addr") {
+ new_frame.ptr->addr = strtoll(attrs[i + 1], nullptr, 16);
+ }
+ }
+ current_new_frame_ = new_frame.id;
+ }
+ data_.GetBacktrace(current_row_.backtrace)
+ ->frames.push_back(frame_lookup.ref);
+ } else if (tag_name == "binary") {
+ // Can only be processing a binary when processing a new frame.
+ PERFETTO_DCHECK(current_new_frame_ != kNullId);
+
+ MaybeCachedRef<BinaryId> binary_lookup =
+ GetOrInsertByRef(attrs, binary_ref_to_binary_);
+ if (binary_lookup.is_new) {
+ auto new_binary = data_.NewBinary();
+ binary_lookup.ref = new_binary.id;
+ for (int i = 2; attrs[i]; i += 2) {
+ std::string key(attrs[i]);
+ if (key == "path") {
+ new_binary.ptr->path = std::string(attrs[i + 1]);
+ } else if (key == "UUID") {
+ new_binary.ptr->uuid =
+ BuildId::FromHex(base::StringView(attrs[i + 1]));
+ } else if (key == "load-addr") {
+ new_binary.ptr->load_addr = strtoll(attrs[i + 1], nullptr, 16);
+ }
+ }
+ new_binary.ptr->max_addr = new_binary.ptr->load_addr;
+ }
+ PERFETTO_DCHECK(data_.GetFrame(current_new_frame_)->binary == kNullId);
+ data_.GetFrame(current_new_frame_)->binary = binary_lookup.ref;
+ }
+ }
+
+ void ElementEnd(const char* el) {
+ PERFETTO_DCHECK(el == tag_stack_.back());
+ std::string tag_name = std::move(tag_stack_.back());
+ tag_stack_.pop_back();
+
+ if (tag_name == "row") {
+ if (current_row_.backtrace) {
+ // Rows with backtraces are assumed to be time samples.
+ base::StatusOr<int64_t> trace_ts =
+ ToTraceTimestamp(current_row_.timestamp_);
+ if (!trace_ts.ok()) {
+ PERFETTO_DLOG("Skipping timestamp %ld, no clock snapshot yet",
+ current_row_.timestamp_);
+ } else {
+ context_->sorter->PushInstrumentsRow(*trace_ts,
+ std::move(current_row_));
+ }
+ } else if (current_subsystem_ref_ != nullptr) {
+ // Rows without backtraces are assumed to be signpost events -- filter
+ // these for `dev.perfetto.clock_sync` events.
+ if (*current_subsystem_ref_ == "dev.perfetto.clock_sync") {
+ PERFETTO_DCHECK(current_os_log_metadata_uint64_ref_ != nullptr);
+ uint64_t clock_sync_timestamp = *current_os_log_metadata_uint64_ref_;
+ if (latest_clock_sync_timestamp_ > clock_sync_timestamp) {
+ PERFETTO_DLOG("Skipping timestamp %ld, non-monotonic sync deteced",
+ current_row_.timestamp_);
+ } else {
+ latest_clock_sync_timestamp_ = clock_sync_timestamp;
+ auto status = context_->clock_tracker->AddSnapshot(
+ {{clock_, current_row_.timestamp_},
+ {protos::pbzero::ClockSnapshot::Clock::BOOTTIME,
+ static_cast<int64_t>(latest_clock_sync_timestamp_)}});
+ if (!status.ok()) {
+ PERFETTO_FATAL("Error adding clock snapshot: %s",
+ status.status().c_message());
+ }
+ }
+ }
+ current_subsystem_ref_ = nullptr;
+ current_os_log_metadata_uint64_ref_ = nullptr;
+ current_uint64_ref_ = nullptr;
+ }
+ } else if (current_new_frame_ != kNullId && tag_name == "frame") {
+ Frame* frame = data_.GetFrame(current_new_frame_);
+ if (frame->binary) {
+ Binary* binary = data_.GetBinary(frame->binary);
+ // We don't know what the binary's mapping end is, but we know that the
+ // current frame is inside of it, so use that.
+ PERFETTO_DCHECK(frame->addr > binary->load_addr);
+ if (frame->addr > binary->max_addr) {
+ binary->max_addr = frame->addr;
+ }
+ }
+ current_new_frame_ = kNullId;
+ } else if (current_new_thread_ != kNullId && tag_name == "thread") {
+ current_new_thread_ = kNullId;
+ } else if (current_new_process_ != kNullId && tag_name == "process") {
+ current_new_process_ = kNullId;
+ } else if (current_new_core_id_ != nullptr && tag_name == "core") {
+ current_new_core_id_ = nullptr;
+ }
+ }
+
+ void CharacterData(const char* chars, int len) {
+ std::string_view tag_name = tag_stack_.back();
+ if (current_time_ref_ != nullptr &&
+ (tag_name == "sample-time" || tag_name == "event-time")) {
+ std::string s = MakeTrimmed(chars, len);
+ current_row_.timestamp_ = *current_time_ref_ = stoll(s);
+ current_time_ref_ = nullptr;
+ } else if (current_new_thread_ != kNullId && tag_name == "tid") {
+ std::string s = MakeTrimmed(chars, len);
+ data_.GetThread(current_new_thread_)->tid = stoi(s);
+ } else if (current_new_process_ != kNullId && tag_name == "pid") {
+ std::string s = MakeTrimmed(chars, len);
+ data_.GetProcess(current_new_process_)->pid = stoi(s);
+ } else if (current_new_core_id_ != nullptr && tag_name == "core") {
+ std::string s = MakeTrimmed(chars, len);
+ *current_new_core_id_ = static_cast<uint32_t>(stoul(s));
+ } else if (current_subsystem_ref_ != nullptr && tag_name == "subsystem") {
+ std::string s = MakeTrimmed(chars, len);
+ *current_subsystem_ref_ = s;
+ } else if (current_uint64_ref_ != nullptr &&
+ current_os_log_metadata_uint64_ref_ != nullptr &&
+ tag_name == "uint64") {
+ std::string s = MakeTrimmed(chars, len);
+ *current_os_log_metadata_uint64_ref_ = *current_uint64_ref_ = stoull(s);
+ }
+ }
+
+ base::StatusOr<int64_t> ToTraceTimestamp(int64_t time) {
+ base::StatusOr<int64_t> trace_ts =
+ context_->clock_tracker->ToTraceTime(clock_, time);
+
+ if (PERFETTO_LIKELY(trace_ts.ok())) {
+ latest_timestamp_ = std::max(latest_timestamp_, *trace_ts);
+ }
+
+ return trace_ts;
+ }
+
+ StringId InternString(base::StringView string_view) {
+ return context_->storage->InternString(string_view);
+ }
+ StringId InternString(const char* string) {
+ return InternString(base::StringView(string));
+ }
+ StringId InternString(const char* data, size_t len) {
+ return InternString(base::StringView(data, len));
+ }
+
+ template <typename Value>
+ struct MaybeCachedRef {
+ Value& ref;
+ bool is_new;
+ };
+ // Implement the element caching mechanism. Either insert an element by its
+ // id attribute into the given map, or look up the element in the cache by its
+ // ref attribute. The returned value is a reference into the map, to allow
+ // in-place modification.
+ template <typename Value>
+ MaybeCachedRef<Value> GetOrInsertByRef(const char** attrs,
+ std::map<unsigned long, Value>& map) {
+ PERFETTO_DCHECK(attrs[0] != nullptr);
+ PERFETTO_DCHECK(attrs[1] != nullptr);
+ const char* key = attrs[0];
+ // The id or ref attribute has to be the first attribute on the element.
+ PERFETTO_DCHECK(strcmp(key, "ref") == 0 || strcmp(key, "id") == 0);
+ unsigned long id = strtoul(attrs[1], nullptr, 10);
+ // If the first attribute key is `id`, then this is a new entry in the
+ // cache -- otherwise, for lookup by ref, it should already exist.
+ bool is_new = strcmp(key, "id") == 0;
+ PERFETTO_DCHECK(is_new == (map.find(id) == map.end()));
+ return {map[id], is_new};
+ }
+
+ TraceProcessorContext* context_;
+ RowDataTracker& data_;
+
+ XML_Parser parser_;
+ std::vector<std::string> tag_stack_;
+ int64_t latest_timestamp_;
+
+ // These maps store the cached element data. These currently have to be
+ // std::map, because they require pointer stability under insertion,
+ // as the various `current_foo_` pointers below point directly into the map
+ // data.
+ //
+ // TODO(leszeks): Relax this pointer stability requirement, and use
+ // base::FlatHashMap.
+ // TODO(leszeks): Consider merging these into a single map from ID to
+ // a variant (or similar).
+ std::map<unsigned long, ThreadId> thread_ref_to_thread_;
+ std::map<unsigned long, ProcessId> process_ref_to_process_;
+ std::map<unsigned long, uint32_t> core_ref_to_core_;
+ std::map<unsigned long, int64_t> sample_time_ref_to_time_;
+ std::map<unsigned long, BinaryId> binary_ref_to_binary_;
+ std::map<unsigned long, BacktraceFrameId> frame_ref_to_frame_;
+ std::map<unsigned long, BacktraceId> backtrace_ref_to_backtrace_;
+ std::map<unsigned long, std::string> subsystem_ref_to_subsystem_;
+ std::map<unsigned long, uint64_t> os_log_metadata_or_uint64_ref_to_uint64_;
+
+ Row current_row_;
+ int64_t* current_time_ref_ = nullptr;
+ ThreadId current_new_thread_ = kNullId;
+ ProcessId current_new_process_ = kNullId;
+ uint32_t* current_new_core_id_ = nullptr;
+ BacktraceFrameId current_new_frame_ = kNullId;
+
+ ClockTracker::ClockId clock_;
+ std::string* current_subsystem_ref_ = nullptr;
+ uint64_t* current_os_log_metadata_uint64_ref_ = nullptr;
+ uint64_t* current_uint64_ref_ = nullptr;
+ uint64_t latest_clock_sync_timestamp_ = 0;
+};
+
+InstrumentsXmlTokenizer::InstrumentsXmlTokenizer(TraceProcessorContext* context)
+ : impl_(new Impl(context)) {}
+InstrumentsXmlTokenizer::~InstrumentsXmlTokenizer() {
+ delete impl_;
+}
+
+base::Status InstrumentsXmlTokenizer::Parse(TraceBlobView view) {
+ return impl_->Parse(std::move(view));
+}
+
+[[nodiscard]] base::Status InstrumentsXmlTokenizer::NotifyEndOfFile() {
+ return impl_->End();
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
diff --git a/src/trace_processor/importers/instruments/instruments_xml_tokenizer.h b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.h
new file mode 100644
index 0000000..be044dd
--- /dev/null
+++ b/src/trace_processor/importers/instruments/instruments_xml_tokenizer.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_XML_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_XML_TOKENIZER_H_
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+class InstrumentsXmlTokenizer : public ChunkedTraceReader {
+ public:
+ explicit InstrumentsXmlTokenizer(TraceProcessorContext*);
+ ~InstrumentsXmlTokenizer() override;
+
+ base::Status Parse(TraceBlobView) override;
+
+ [[nodiscard]] base::Status NotifyEndOfFile() override;
+
+ private:
+ class Impl;
+
+ class Impl* impl_;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_INSTRUMENTS_XML_TOKENIZER_H_
diff --git a/src/trace_processor/importers/instruments/row.h b/src/trace_processor/importers/instruments/row.h
new file mode 100644
index 0000000..531a36d
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_H_
+
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+// TODO(leszeks): Would be nice if these were strong type aliases, to be
+// type safe.
+using ThreadId = uint32_t;
+using ProcessId = uint32_t;
+using BacktraceId = uint32_t;
+using BacktraceFrameId = uint32_t;
+using BinaryId = uint32_t;
+
+constexpr uint32_t kNullId = 0u;
+
+struct Binary {
+ std::string path;
+ BuildId uuid = BuildId::FromRaw(std::string(""));
+ long long load_addr = 0;
+ long long max_addr = 0;
+};
+
+struct Frame {
+ long long addr = 0;
+ BinaryId binary = kNullId;
+};
+
+struct Process {
+ int pid = 0;
+ StringPool::Id fmt = StringPool::Id::Null();
+};
+
+struct Thread {
+ int tid = 0;
+ StringPool::Id fmt = StringPool::Id::Null();
+ ProcessId process = kNullId;
+};
+
+struct Backtrace {
+ std::vector<BacktraceFrameId> frames;
+};
+
+struct alignas(8) Row {
+ int64_t timestamp_;
+ uint32_t core_id;
+ ThreadId thread = kNullId;
+ BacktraceId backtrace = kNullId;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_H_
diff --git a/src/trace_processor/importers/instruments/row_data_tracker.cc b/src/trace_processor/importers/instruments/row_data_tracker.cc
new file mode 100644
index 0000000..bd27629
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_data_tracker.cc
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/instruments/row_data_tracker.h"
+
+#include "perfetto/base/status.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+RowDataTracker::RowDataTracker() {}
+RowDataTracker::~RowDataTracker() = default;
+
+IdPtr<Thread> RowDataTracker::NewThread() {
+ ThreadId id = static_cast<ThreadId>(threads_.size());
+ Thread* ptr = &threads_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Thread* RowDataTracker::GetThread(ThreadId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &threads_[id - 1];
+}
+
+IdPtr<Process> RowDataTracker::NewProcess() {
+ ProcessId id = static_cast<ProcessId>(processes_.size());
+ Process* ptr = &processes_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Process* RowDataTracker::GetProcess(ProcessId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &processes_[id - 1];
+}
+
+IdPtr<Frame> RowDataTracker::NewFrame() {
+ BacktraceFrameId id = static_cast<BacktraceFrameId>(frames_.size());
+ Frame* ptr = &frames_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Frame* RowDataTracker::GetFrame(BacktraceFrameId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &frames_[id - 1];
+}
+
+IdPtr<Backtrace> RowDataTracker::NewBacktrace() {
+ BacktraceId id = static_cast<BacktraceId>(backtraces_.size());
+ Backtrace* ptr = &backtraces_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Backtrace* RowDataTracker::GetBacktrace(BacktraceId id) {
+ PERFETTO_DCHECK(id != kNullId);
+ return &backtraces_[id - 1];
+}
+
+IdPtr<Binary> RowDataTracker::NewBinary() {
+ BinaryId id = static_cast<BinaryId>(binaries_.size());
+ Binary* ptr = &binaries_.emplace_back();
+ // Always add 1 to ids, so that they're non-zero.
+ return {id + 1, ptr};
+}
+Binary* RowDataTracker::GetBinary(BinaryId id) {
+ // Frames are allowed to have null binaries.
+ if (id == kNullId)
+ return nullptr;
+ return &binaries_[id - 1];
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
diff --git a/src/trace_processor/importers/instruments/row_data_tracker.h b/src/trace_processor/importers/instruments/row_data_tracker.h
new file mode 100644
index 0000000..247326e
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_data_tracker.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_DATA_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_DATA_TRACKER_H_
+
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+template <typename T>
+struct IdPtr {
+ uint32_t id;
+ T* ptr;
+};
+
+// Keeps track of row data.
+class RowDataTracker : public Destructible {
+ public:
+ static RowDataTracker& GetOrCreate(TraceProcessorContext* context) {
+ if (!context->instruments_row_data_tracker) {
+ context->instruments_row_data_tracker.reset(new RowDataTracker());
+ }
+ return static_cast<RowDataTracker&>(*context->instruments_row_data_tracker);
+ }
+ ~RowDataTracker() override;
+
+ IdPtr<Thread> NewThread();
+ Thread* GetThread(ThreadId id);
+
+ IdPtr<Process> NewProcess();
+ Process* GetProcess(ProcessId id);
+
+ IdPtr<Frame> NewFrame();
+ Frame* GetFrame(BacktraceFrameId id);
+
+ IdPtr<Backtrace> NewBacktrace();
+ Backtrace* GetBacktrace(BacktraceId id);
+
+ IdPtr<Binary> NewBinary();
+ Binary* GetBinary(BinaryId id);
+
+ private:
+ explicit RowDataTracker();
+
+ std::vector<Thread> threads_;
+ std::vector<Process> processes_;
+ std::vector<Frame> frames_;
+ std::vector<Backtrace> backtraces_;
+ std::vector<Binary> binaries_;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_DATA_TRACKER_H_
diff --git a/src/trace_processor/importers/instruments/row_parser.cc b/src/trace_processor/importers/instruments/row_parser.cc
new file mode 100644
index 0000000..184d1f1
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_parser.cc
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/instruments/row_parser.h"
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/importers/instruments/row_data_tracker.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+RowParser::RowParser(TraceProcessorContext* context)
+ : context_(context), data_(RowDataTracker::GetOrCreate(context)) {}
+
+void RowParser::ParseInstrumentsRow(int64_t ts, instruments_importer::Row row) {
+ if (!row.backtrace) {
+ return;
+ }
+
+ Thread* thread = data_.GetThread(row.thread);
+ Process* process = data_.GetProcess(thread->process);
+ uint32_t tid = static_cast<uint32_t>(thread->tid);
+ uint32_t pid = static_cast<uint32_t>(process->pid);
+
+ UniqueTid utid = context_->process_tracker->UpdateThread(tid, pid);
+ UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
+
+ // TODO(leszeks): Avoid setting thread/process name if we've already seen this
+ // Thread* / Process*.
+ context_->process_tracker->UpdateThreadNameByUtid(utid, thread->fmt,
+ ThreadNamePriority::kOther);
+ context_->process_tracker->SetProcessNameIfUnset(upid, process->fmt);
+
+ Backtrace* backtrace = data_.GetBacktrace(row.backtrace);
+ std::optional<CallsiteId> parent;
+ uint32_t depth = 0;
+ base::FlatHashMap<FrameId, CallsiteTreeNode>* frame_to_callsite = &top_frames;
+ auto leaf = backtrace->frames.rend() - 1;
+ for (auto it = backtrace->frames.rbegin(); it != backtrace->frames.rend();
+ ++it) {
+ Frame* frame = data_.GetFrame(*it);
+ Binary* binary = data_.GetBinary(frame->binary);
+
+ uint64_t rel_pc = static_cast<uint64_t>(frame->addr);
+ if (frame->binary) {
+ rel_pc -= static_cast<uint64_t>(binary->load_addr);
+ }
+
+ // For non-leaf functions, the pc will be after the end of the call. Adjust
+ // it to be within the call instruction.
+ if (rel_pc != 0 && it != leaf) {
+ --rel_pc;
+ }
+
+ auto frame_inserted = frame_to_frame_id_.Insert(*it, FrameId{0});
+ if (frame_inserted.second) {
+ auto mapping_inserted = binary_to_mapping_.Insert(frame->binary, nullptr);
+ if (mapping_inserted.second) {
+ if (binary == nullptr) {
+ *mapping_inserted.first = GetDummyMapping(upid);
+ } else {
+ BuildId build_id = binary->uuid;
+ *mapping_inserted.first =
+ &context_->mapping_tracker->CreateUserMemoryMapping(
+ upid, {AddressRange(static_cast<uint64_t>(binary->load_addr),
+ static_cast<uint64_t>(binary->max_addr)),
+ 0, 0, 0, binary->path, build_id});
+ }
+ }
+ VirtualMemoryMapping* mapping = *mapping_inserted.first;
+
+ // Intern the frame with no function name -- the symbolizer will annotate
+ // frames later.
+ *frame_inserted.first =
+ mapping->InternFrame(rel_pc, base::StringView(""));
+ }
+ FrameId frame_id = *frame_inserted.first;
+
+ // Lookup the frame id in the current callsite prefix tree node.
+ auto callsite_node_inserted =
+ frame_to_callsite->Insert(frame_id, CallsiteTreeNode{});
+ if (callsite_node_inserted.second) {
+ callsite_node_inserted.first->callsite_id =
+ context_->storage->mutable_stack_profile_callsite_table()
+ ->Insert({depth, parent, frame_id})
+ .id;
+ }
+ parent = callsite_node_inserted.first->callsite_id;
+ frame_to_callsite = &callsite_node_inserted.first->next_frames;
+ depth++;
+ }
+
+ context_->storage->mutable_instruments_sample_table()->Insert(
+ {ts, utid, row.core_id, parent});
+}
+
+DummyMemoryMapping* RowParser::GetDummyMapping(UniquePid upid) {
+ if (auto it = dummy_mappings_.Find(upid); it) {
+ return *it;
+ }
+
+ DummyMemoryMapping* mapping =
+ &context_->mapping_tracker->CreateDummyMapping("");
+ dummy_mappings_.Insert(upid, mapping);
+ return mapping;
+}
+
+} // namespace perfetto::trace_processor::instruments_importer
diff --git a/src/trace_processor/importers/instruments/row_parser.h b/src/trace_processor/importers/instruments/row_parser.h
new file mode 100644
index 0000000..a5e915d
--- /dev/null
+++ b/src/trace_processor/importers/instruments/row_parser.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_PARSER_H_
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/importers/instruments/row.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor::instruments_importer {
+
+class RowDataTracker;
+
+class RowParser : public InstrumentsRowParser {
+ public:
+ explicit RowParser(TraceProcessorContext*);
+ ~RowParser() override = default;
+
+ void ParseInstrumentsRow(int64_t, instruments_importer::Row) override;
+
+ private:
+ DummyMemoryMapping* GetDummyMapping(UniquePid upid);
+
+ TraceProcessorContext* context_;
+ RowDataTracker& data_;
+
+ // Cache FrameId and binary mappings by instruments frame and binary
+ // pointers, respectively. These are already de-duplicated in the
+ // instruments XML parsing.
+ base::FlatHashMap<BacktraceFrameId, FrameId> frame_to_frame_id_;
+ base::FlatHashMap<BinaryId, VirtualMemoryMapping*> binary_to_mapping_;
+ base::FlatHashMap<UniquePid, DummyMemoryMapping*> dummy_mappings_;
+
+ // Cache callsites by FrameId in a prefix tree, where children in the
+ // prefix tree are child frames at the callsite. This should be more
+ // efficient than looking up frame+parent pairs in a hashmap.
+ // TODO(leszeks): Verify that this is more efficient and share the code
+ // with other importers.
+ struct CallsiteTreeNode {
+ CallsiteId callsite_id{0};
+ base::FlatHashMap<FrameId, CallsiteTreeNode> next_frames{};
+ };
+ base::FlatHashMap<FrameId, CallsiteTreeNode> top_frames;
+};
+
+} // namespace perfetto::trace_processor::instruments_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_INSTRUMENTS_ROW_PARSER_H_
diff --git a/src/trace_processor/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..2316dae 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));
@@ -251,6 +255,7 @@
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:
@@ -281,6 +286,7 @@
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:
@@ -319,6 +325,9 @@
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;
diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h
index 656b283..b89a25a 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,
@@ -264,6 +274,7 @@
enum class Type : uint8_t {
kFtraceEvent,
kPerfRecord,
+ kInstrumentsRow,
kTracePacket,
kInlineSchedSwitch,
kInlineSchedWaking,
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_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9f101bd..3a8dac5 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -51,6 +51,8 @@
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h"
#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
+#include "src/trace_processor/importers/instruments/instruments_xml_tokenizer.h"
+#include "src/trace_processor/importers/instruments/row_parser.h"
#include "src/trace_processor/importers/json/json_trace_parser_impl.h"
#include "src/trace_processor/importers/json/json_trace_tokenizer.h"
#include "src/trace_processor/importers/json/json_utils.h"
@@ -355,6 +357,10 @@
start_ns = std::min(it.ts(), start_ns);
end_ns = std::max(it.ts(), end_ns);
}
+ for (auto it = storage.instruments_sample_table().IterateRows(); it; ++it) {
+ start_ns = std::min(it.ts(), start_ns);
+ end_ns = std::max(it.ts(), end_ns);
+ }
for (auto it = storage.cpu_profile_stack_sample_table().IterateRows(); it;
++it) {
start_ns = std::min(it.ts(), start_ns);
@@ -394,6 +400,12 @@
context_.perf_record_parser =
std::make_unique<perf_importer::RecordParser>(&context_);
+ context_.reader_registry
+ ->RegisterTraceReader<instruments_importer::InstrumentsXmlTokenizer>(
+ kInstrumentsXmlTraceType);
+ context_.instruments_row_parser =
+ std::make_unique<instruments_importer::RowParser>(&context_);
+
if (util::IsGzipSupported()) {
context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
kGzipTraceType);
@@ -907,6 +919,7 @@
RegisterStaticTable(storage->mutable_cpu_profile_stack_sample_table());
RegisterStaticTable(storage->mutable_perf_session_table());
RegisterStaticTable(storage->mutable_perf_sample_table());
+ RegisterStaticTable(storage->mutable_instruments_sample_table());
RegisterStaticTable(storage->mutable_stack_profile_callsite_table());
RegisterStaticTable(storage->mutable_stack_profile_mapping_table());
RegisterStaticTable(storage->mutable_stack_profile_frame_table());
diff --git a/src/trace_processor/trace_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..b9f4ae6 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -46,6 +46,7 @@
class FuchsiaRecordParser;
class GlobalArgsTracker;
class HeapGraphTracker;
+class InstrumentsRowParser;
class JsonTraceParser;
class MachineTracker;
class MappingTracker;
@@ -136,25 +137,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 +167,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/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/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 5840800..1f39362 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -72,6 +72,7 @@
from diff_tests.parser.graphics.tests import GraphicsParser
from diff_tests.parser.graphics.tests_drm_related_ftrace_events import GraphicsDrmRelatedFtraceEvents
from diff_tests.parser.graphics.tests_gpu_trace import GraphicsGpuTrace
+from diff_tests.parser.instruments.tests import Instruments
from diff_tests.parser.json.tests import JsonParser
from diff_tests.parser.memory.tests import MemoryParser
from diff_tests.parser.network.tests import NetworkParser
@@ -238,6 +239,7 @@
*Zip(index_path, 'parser/zip', 'Zip').fetch(),
*AndroidInputEvent(index_path, 'parser/android',
'AndroidInputEvent').fetch(),
+ *Instruments(index_path, 'parser/instruments', 'Instruments').fetch(),
]
metrics_tests = [
diff --git a/test/trace_processor/diff_tests/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/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