Merge changes I442d4291,I239ea5e4
* changes:
android builds (incl. standalone): switch from Oz to O2
O2-friendly GetStaticEventInfo
diff --git a/.gitignore b/.gitignore
index 821445d..655f094 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.android_config
+.ccls-cache
.cproject
.deps_sha1
.DS_Store
diff --git a/Android.bp b/Android.bp
index e6fe23b..a013fa9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -426,6 +426,7 @@
"liblog",
"libprotobuf-cpp-lite",
],
+ host_supported: true,
export_include_dirs: [
"include",
"include/perfetto/base/build_configs/android_tree",
@@ -4900,11 +4901,9 @@
"tools/trace_to_text/local_symbolizer.cc",
"tools/trace_to_text/main.cc",
"tools/trace_to_text/pprof_builder.cc",
- "tools/trace_to_text/profile_visitor.cc",
"tools/trace_to_text/proto_full_utils.cc",
"tools/trace_to_text/symbolize_profile.cc",
"tools/trace_to_text/symbolizer.cc",
- "tools/trace_to_text/trace_symbol_table.cc",
"tools/trace_to_text/trace_to_profile.cc",
"tools/trace_to_text/trace_to_systrace.cc",
"tools/trace_to_text/trace_to_text.cc",
diff --git a/BUILD b/BUILD
index b931137..a101de8 100644
--- a/BUILD
+++ b/BUILD
@@ -367,11 +367,13 @@
"include/perfetto/tracing/internal/data_source_internal.h",
"include/perfetto/tracing/internal/tracing_muxer.h",
"include/perfetto/tracing/internal/tracing_tls.h",
+ "include/perfetto/tracing/internal/track_event_data_source.h",
"include/perfetto/tracing/locked_handle.h",
"include/perfetto/tracing/platform.h",
"include/perfetto/tracing/trace_writer_base.h",
"include/perfetto/tracing/tracing.h",
"include/perfetto/tracing/tracing_backend.h",
+ "include/perfetto/tracing/track_event.h",
],
)
@@ -874,6 +876,24 @@
],
)
+# GN target: //src/tracing:client_api
+filegroup(
+ name = "src_tracing_client_api",
+ srcs = [
+ "src/tracing/data_source.cc",
+ "src/tracing/internal/in_process_tracing_backend.cc",
+ "src/tracing/internal/in_process_tracing_backend.h",
+ "src/tracing/internal/system_tracing_backend.cc",
+ "src/tracing/internal/system_tracing_backend.h",
+ "src/tracing/internal/tracing_muxer_impl.cc",
+ "src/tracing/internal/tracing_muxer_impl.h",
+ "src/tracing/platform.cc",
+ "src/tracing/tracing.cc",
+ "src/tracing/track_event.cc",
+ "src/tracing/virtual_destructors.cc",
+ ],
+)
+
# GN target: //src/tracing:common
filegroup(
name = "src_tracing_common",
@@ -910,6 +930,14 @@
],
)
+# GN target: //src/tracing:platform_posix
+filegroup(
+ name = "src_tracing_platform_posix",
+ srcs = [
+ "src/tracing/platform_posix.cc",
+ ],
+)
+
# GN target: //src/tracing:tracing
filegroup(
name = "src_tracing_tracing",
@@ -996,8 +1024,6 @@
name = "tools_trace_to_text_symbolizer",
srcs = [
"tools/trace_to_text/symbolizer.cc",
- "tools/trace_to_text/trace_symbol_table.cc",
- "tools/trace_to_text/trace_symbol_table.h",
],
)
@@ -1005,8 +1031,6 @@
filegroup(
name = "tools_trace_to_text_utils",
srcs = [
- "tools/trace_to_text/profile_visitor.cc",
- "tools/trace_to_text/profile_visitor.h",
"tools/trace_to_text/utils.cc",
"tools/trace_to_text/utils.h",
],
@@ -1273,6 +1297,14 @@
],
)
+# GN target: //protos/perfetto/config:merged_config
+perfetto_cc_proto_library(
+ name = "protos_perfetto_config_merged_config",
+ deps = [
+ ":protos_perfetto_config_merged_config_protos",
+ ],
+)
+
# GN target: //protos/perfetto/common:lite
perfetto_cc_proto_library(
name = "protos_perfetto_common_lite",
@@ -1281,6 +1313,17 @@
],
)
+# GN target: //protos/perfetto/config:merged_config
+perfetto_proto_library(
+ name = "protos_perfetto_config_merged_config_protos",
+ srcs = [
+ "protos/perfetto/config/perfetto_config.proto",
+ ],
+ visibility = [
+ "//visibility:public",
+ ],
+)
+
# GN target: //protos/perfetto/trace/ftrace:lite
perfetto_cc_proto_library(
name = "protos_perfetto_trace_ftrace_lite",
@@ -1690,6 +1733,9 @@
srcs = [
"protos/perfetto/trace/perfetto_trace.proto",
],
+ visibility = [
+ "//visibility:public",
+ ],
)
# GN target: //protos/perfetto/config/profiling:zero
@@ -1846,6 +1892,9 @@
srcs = [
"protos/perfetto/metrics/metrics.proto",
],
+ visibility = [
+ "//visibility:public",
+ ],
deps = [
":protos_perfetto_metrics_android_protos",
],
@@ -1931,6 +1980,76 @@
# Public targets
# ##############################################################################
+# GN target: //:libperfetto_client_experimental
+perfetto_cc_library(
+ name = "libperfetto_client_experimental",
+ srcs = [
+ "include/perfetto/tracing.h",
+ ":src_base_base",
+ ":src_base_unix_socket",
+ ":src_ipc_ipc",
+ ":src_protozero_protozero",
+ ":src_tracing_client_api",
+ ":src_tracing_common",
+ ":src_tracing_ipc",
+ ":src_tracing_platform_posix",
+ ":src_tracing_tracing",
+ ],
+ hdrs = [
+ ":include_perfetto_base_base",
+ ":include_perfetto_ext_base_base",
+ ":include_perfetto_ext_ipc_ipc",
+ ":include_perfetto_ext_tracing_core_core",
+ ":include_perfetto_ext_tracing_ipc_ipc",
+ ":include_perfetto_protozero_protozero",
+ ":include_perfetto_tracing_core_core",
+ ":include_perfetto_tracing_tracing",
+ ],
+ visibility = [
+ "//visibility:public",
+ ],
+ deps = [
+ ":protos_perfetto_common_lite",
+ ":protos_perfetto_common_zero",
+ ":protos_perfetto_config_android_lite",
+ ":protos_perfetto_config_android_zero",
+ ":protos_perfetto_config_ftrace_lite",
+ ":protos_perfetto_config_ftrace_zero",
+ ":protos_perfetto_config_gpu_lite",
+ ":protos_perfetto_config_gpu_zero",
+ ":protos_perfetto_config_inode_file_lite",
+ ":protos_perfetto_config_inode_file_zero",
+ ":protos_perfetto_config_lite",
+ ":protos_perfetto_config_power_lite",
+ ":protos_perfetto_config_power_zero",
+ ":protos_perfetto_config_process_stats_lite",
+ ":protos_perfetto_config_process_stats_zero",
+ ":protos_perfetto_config_profiling_lite",
+ ":protos_perfetto_config_profiling_zero",
+ ":protos_perfetto_config_sys_stats_lite",
+ ":protos_perfetto_config_sys_stats_zero",
+ ":protos_perfetto_config_zero",
+ ":protos_perfetto_ipc_ipc",
+ ":protos_perfetto_ipc_wire_protocol",
+ ":protos_perfetto_trace_android_zero",
+ ":protos_perfetto_trace_chrome_zero",
+ ":protos_perfetto_trace_filesystem_zero",
+ ":protos_perfetto_trace_ftrace_zero",
+ ":protos_perfetto_trace_gpu_zero",
+ ":protos_perfetto_trace_interned_data_zero",
+ ":protos_perfetto_trace_minimal_lite",
+ ":protos_perfetto_trace_minimal_zero",
+ ":protos_perfetto_trace_non_minimal_zero",
+ ":protos_perfetto_trace_perfetto_zero",
+ ":protos_perfetto_trace_power_zero",
+ ":protos_perfetto_trace_profiling_zero",
+ ":protos_perfetto_trace_ps_zero",
+ ":protos_perfetto_trace_sys_stats_zero",
+ ":protos_perfetto_trace_track_event_zero",
+ ":protos_perfetto_trace_trusted_lite",
+ ] + PERFETTO_CONFIG.deps.protobuf_lite,
+)
+
# GN target: //src/perfetto_cmd:perfetto
perfetto_cc_binary(
name = "perfetto",
diff --git a/BUILD.gn b/BUILD.gn
index 0a10260..0ba5807 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -210,6 +210,7 @@
configs += [ "//build/config/compiler:no_chromium_code" ]
public_deps = [
"include/perfetto/ext/tracing/core",
+ "protos/perfetto/common:zero",
"protos/perfetto/trace:zero",
"protos/perfetto/trace/chrome:zero",
"protos/perfetto/trace/interned_data:zero",
diff --git a/bazel/BUILD b/bazel/BUILD
index 069748d..1bf33a4 100644
--- a/bazel/BUILD
+++ b/bazel/BUILD
@@ -17,3 +17,16 @@
values = {"cpu": "darwin"},
visibility = ["//visibility:public"],
)
+
+config_setting(
+ name = "os_linux",
+ values = {"cpu": "k8"},
+ visibility = ["//visibility:public"],
+)
+
+# Note this config does not imply MSVC.
+config_setting(
+ name = "os_windows",
+ values = {"cpu": "x64_windows"},
+ visibility = ["//visibility:public"],
+)
diff --git a/bazel/jsoncpp.BUILD b/bazel/jsoncpp.BUILD
index 8a33eb8..bef06a1 100644
--- a/bazel/jsoncpp.BUILD
+++ b/bazel/jsoncpp.BUILD
@@ -12,6 +12,8 @@
# 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 = "jsoncpp",
srcs = [
@@ -38,7 +40,7 @@
],
copts = [
"-Isrc/lib_json",
- ],
+ ] + PERFETTO_CONFIG.deps_copts.jsoncpp,
defines = [
"JSON_USE_EXCEPTION=0",
],
diff --git a/bazel/linenoise.BUILD b/bazel/linenoise.BUILD
index d2b6053..050e7c5 100644
--- a/bazel/linenoise.BUILD
+++ b/bazel/linenoise.BUILD
@@ -12,6 +12,8 @@
# 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 = "linenoise",
srcs = [
@@ -23,5 +25,6 @@
includes = [
".",
],
+ copts = PERFETTO_CONFIG.deps_copts.linenoise,
visibility = ["//visibility:public"],
)
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 87c48c7..8713984 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -21,12 +21,14 @@
def default_cc_args():
return {
- "deps": [PERFETTO_CONFIG.root + ":build_config_hdr"],
+ "deps": PERFETTO_CONFIG.deps.build_config,
"copts": [],
"includes": ["include"],
"linkopts": select({
+ "@perfetto//bazel:os_linux": ["-ldl", "-lrt"],
"@perfetto//bazel:os_osx": [],
- "//conditions:default": ["-ldl", "-lrt"],
+ "@perfetto//bazel:os_windows": [],
+ "//conditions:default": ["-ldl"],
}),
}
diff --git a/bazel/sqlite.BUILD b/bazel/sqlite.BUILD
index e028f1f..8b02c6d 100644
--- a/bazel/sqlite.BUILD
+++ b/bazel/sqlite.BUILD
@@ -49,7 +49,7 @@
"-DSQLITE_TEMP_STORE=3",
"-DSQLITE_OMIT_LOAD_EXTENSION",
"-DSQLITE_OMIT_RANDOMNESS",
-]
+] + PERFETTO_CONFIG.deps_copts.sqlite
cc_library(
name = "sqlite",
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index e89c7a0..1ee8e52 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -28,6 +28,12 @@
# to allow perfetto embedders (e.g. gapid) and google internal builds to
# override paths and target names to their own third_party.
deps = struct(
+ # Target exposing the build config header. It should be a valid
+ # cc_library dependency as it will become a dependency of every
+ # perfetto_cc_library target. It needs to expose a
+ # "perfetto_build_flags.h" file that can be included via:
+ # #include "perfetto_build_flags.h".
+ build_config = ["//:build_config_hdr"],
zlib = ["@perfetto_dep_zlib//:zlib"],
jsoncpp = ["@perfetto_dep_jsoncpp//:jsoncpp"],
linenoise = ["@perfetto_dep_linenoise//:linenoise"],
@@ -39,6 +45,16 @@
protobuf_full = ["@com_google_protobuf//:protobuf"],
),
+ # This struct allows embedders to customize the cc_opts for Perfetto
+ # 3rd party dependencies. They only have an effect if the dependencies are
+ # initialized with the Perfetto build files (i.e. via perfetto_deps()).
+ deps_copts = struct(
+ zlib = [],
+ jsoncpp = [],
+ linenoise = [],
+ sqlite = [],
+ ),
+
# This struct allows the embedder to customize copts and other args passed
# to rules like cc_binary. Prefixed rules (e.g. perfetto_cc_binary) will
# look into this struct before falling back on native.cc_binary().
diff --git a/bazel/zlib.BUILD b/bazel/zlib.BUILD
index 1557352..5c723ce 100644
--- a/bazel/zlib.BUILD
+++ b/bazel/zlib.BUILD
@@ -12,6 +12,8 @@
# 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 = "zlib",
srcs = [
@@ -48,7 +50,7 @@
copts = [
"-DHAVE_HIDDEN",
"-Isrc",
- ],
+ ] + PERFETTO_CONFIG.deps_copts.zlib,
includes = ["zlib"],
visibility = ["//visibility:public"],
)
diff --git a/include/perfetto/ext/base/scoped_file.h b/include/perfetto/ext/base/scoped_file.h
index 2fee6a1..24c8970 100644
--- a/include/perfetto/ext/base/scoped_file.h
+++ b/include/perfetto/ext/base/scoped_file.h
@@ -88,7 +88,8 @@
mode_t mode = kInvalidMode) {
PERFETTO_DCHECK((flags & O_CREAT) == 0 || mode != kInvalidMode);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
- ScopedFile fd(open(path.c_str(), flags, mode));
+ // Always use O_BINARY on Windows, to avoid silly EOL translations.
+ ScopedFile fd(open(path.c_str(), flags | O_BINARY, mode));
#else
// Always open a ScopedFile with O_CLOEXEC so we can safely fork and exec.
ScopedFile fd(open(path.c_str(), flags | O_CLOEXEC, mode));
diff --git a/include/perfetto/ext/base/string_utils.h b/include/perfetto/ext/base/string_utils.h
index 977673f..df1ed7c 100644
--- a/include/perfetto/ext/base/string_utils.h
+++ b/include/perfetto/ext/base/string_utils.h
@@ -20,6 +20,8 @@
#include <string>
#include <vector>
+#include "perfetto/ext/base/string_view.h"
+
namespace perfetto {
namespace base {
@@ -34,6 +36,7 @@
bool StartsWith(const std::string& str, const std::string& prefix);
bool EndsWith(const std::string& str, const std::string& suffix);
bool Contains(const std::string& haystack, const std::string& needle);
+size_t Find(const StringView& needle, const StringView& haystack);
bool CaseInsensitiveEqual(const std::string& first, const std::string& second);
std::string Join(const std::vector<std::string>& parts,
const std::string& delim);
diff --git a/include/perfetto/profiling/BUILD.gn b/include/perfetto/profiling/BUILD.gn
index a897783..23e19c9 100644
--- a/include/perfetto/profiling/BUILD.gn
+++ b/include/perfetto/profiling/BUILD.gn
@@ -21,3 +21,9 @@
"symbolizer.h",
]
}
+
+source_set("normalize") {
+ sources = [
+ "normalize.h",
+ ]
+}
diff --git a/src/profiling/memory/ext.h b/include/perfetto/profiling/normalize.h
similarity index 91%
rename from src/profiling/memory/ext.h
rename to include/perfetto/profiling/normalize.h
index b353e98..7b7da1d 100644
--- a/src/profiling/memory/ext.h
+++ b/include/perfetto/profiling/normalize.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef SRC_PROFILING_MEMORY_EXT_H_
-#define SRC_PROFILING_MEMORY_EXT_H_
+#ifndef INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
+#define INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
// Header only code that gets used in other projects.
// This is currently used in
@@ -63,4 +63,4 @@
} // namespace profiling
} // namespace perfetto
-#endif // SRC_PROFILING_MEMORY_EXT_H_
+#endif // INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h
index db6f57b..2ec9515 100644
--- a/include/perfetto/profiling/pprof_builder.h
+++ b/include/perfetto/profiling/pprof_builder.h
@@ -21,6 +21,8 @@
#include <string>
#include <vector>
+#include "perfetto/trace_processor/trace_processor.h"
+
// TODO(135923303): do not depend on anything in this file as it will be
// changed heavily as part of fixing b/135923303.
namespace perfetto {
@@ -33,11 +35,22 @@
std::string serialized;
};
+bool TraceToPprof(trace_processor::TraceProcessor*,
+ std::vector<SerializedProfile>* output,
+ Symbolizer* symbolizer,
+ uint64_t pid = 0,
+ const std::vector<uint64_t>& timestamps = {});
+
bool TraceToPprof(std::istream* input,
std::vector<SerializedProfile>* output,
- Symbolizer* symbolizer);
+ Symbolizer* symbolizer,
+ uint64_t pid = 0,
+ const std::vector<uint64_t>& timestamps = {});
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output);
+bool TraceToPprof(std::istream* input,
+ std::vector<SerializedProfile>* output,
+ uint64_t pid = 0,
+ const std::vector<uint64_t>& timestamps = {});
} // namespace trace_to_text
} // namespace perfetto
diff --git a/include/perfetto/trace_processor/basic_types.h b/include/perfetto/trace_processor/basic_types.h
index 8bd946d..4c0f951 100644
--- a/include/perfetto/trace_processor/basic_types.h
+++ b/include/perfetto/trace_processor/basic_types.h
@@ -66,8 +66,8 @@
int Compare(const SqlValue& value) const {
// TODO(lalitm): this is almost the same as what SQLite does with the
- // exception of comparisons between long and double - we choose (for
- // performance reasons) to omit comparisons between them.
+ // exception of comparisions between long and double - we choose (for
+ // performance reasons) to omit comparisions between them.
if (type != value.type)
return type - value.type;
@@ -75,9 +75,11 @@
case Type::kNull:
return 0;
case Type::kLong:
- return signbit(long_value - value.long_value);
- case Type::kDouble:
- return signbit(double_value - value.double_value);
+ return static_cast<int>(long_value - value.long_value);
+ case Type::kDouble: {
+ double diff = double_value - value.double_value;
+ return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+ }
case Type::kString:
return strcmp(string_value, value.string_value);
case Type::kBytes: {
@@ -85,7 +87,7 @@
int ret = memcmp(bytes_value, value.bytes_value, bytes);
if (ret != 0)
return ret;
- return signbit(bytes_count - value.bytes_count);
+ return static_cast<int>(bytes_count - value.bytes_count);
}
}
PERFETTO_FATAL("For GCC");
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 2385d90..776876c 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -16,6 +16,7 @@
public_deps = [
"../../../gn:default_deps",
"../../../protos/perfetto/trace:zero",
+ "../../../protos/perfetto/trace/track_event:zero",
"../base",
"../protozero",
]
@@ -26,10 +27,12 @@
"internal/data_source_internal.h",
"internal/tracing_muxer.h",
"internal/tracing_tls.h",
+ "internal/track_event_data_source.h",
"locked_handle.h",
"platform.h",
"trace_writer_base.h",
"tracing.h",
"tracing_backend.h",
+ "track_event.h",
]
}
diff --git a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
index 7cd0594..e61377a 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
@@ -42,7 +42,7 @@
// bundle. The first is absolute, each next one is relative to its
// predecessor.
repeated uint64 switch_timestamp = 1 [packed = true];
- repeated int32 switch_prev_state = 2 [packed = true];
+ repeated int64 switch_prev_state = 2 [packed = true];
repeated int32 switch_next_pid = 3 [packed = true];
repeated int32 switch_next_prio = 4 [packed = true];
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 315a489..60832da 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -2320,7 +2320,7 @@
// bundle. The first is absolute, each next one is relative to its
// predecessor.
repeated uint64 switch_timestamp = 1 [packed = true];
- repeated int32 switch_prev_state = 2 [packed = true];
+ repeated int64 switch_prev_state = 2 [packed = true];
repeated int32 switch_next_pid = 3 [packed = true];
repeated int32 switch_next_prio = 4 [packed = true];
@@ -3112,8 +3112,32 @@
message Mapping {
optional uint64 iid = 1; // Interning key.
optional uint64 build_id = 2; // Interning key.
- // TODO(fmayer): Document difference between those two.
- optional uint64 exact_offset = 8;
+
+ // The linker may create multiple memory mappings for the same shared
+ // library.
+ // This is so that the ELF header is mapped as read only, while the
+ // executable memory is mapped as executable only.
+ // The details of this depend on the linker, a possible mapping of an ELF
+ // file is this:
+ // +----------------------+
+ // ELF |xxxxxxxxxyyyyyyyyyyyyy|
+ // +---------+------------+
+ // | |
+ // | read | executable
+ // v mapping v mapping
+ // +----------------------+
+ // Memory |xxxxxxxxx|yyyyyyyyyyyy|
+ // +------------------+---+
+ // ^ ^ ^
+ // + + +
+ // start exact relpc
+ // offset offset 0x1800
+ // 0x0000 0x1000
+ //
+ // exact_offset is the offset into the library file of this mapping.
+ // start_offset is the offset into the library file of the first mapping
+ // for that library. For native libraries (.so files) this should be 0.
+ optional uint64 exact_offset = 8; // This is not set on Android 10.
optional uint64 start_offset = 3;
optional uint64 start = 4;
optional uint64 end = 5;
diff --git a/protos/perfetto/trace/profiling/profile_common.proto b/protos/perfetto/trace/profiling/profile_common.proto
index a9e3bf7..9b88de2 100644
--- a/protos/perfetto/trace/profiling/profile_common.proto
+++ b/protos/perfetto/trace/profiling/profile_common.proto
@@ -87,8 +87,32 @@
message Mapping {
optional uint64 iid = 1; // Interning key.
optional uint64 build_id = 2; // Interning key.
- // TODO(fmayer): Document difference between those two.
- optional uint64 exact_offset = 8;
+
+ // The linker may create multiple memory mappings for the same shared
+ // library.
+ // This is so that the ELF header is mapped as read only, while the
+ // executable memory is mapped as executable only.
+ // The details of this depend on the linker, a possible mapping of an ELF
+ // file is this:
+ // +----------------------+
+ // ELF |xxxxxxxxxyyyyyyyyyyyyy|
+ // +---------+------------+
+ // | |
+ // | read | executable
+ // v mapping v mapping
+ // +----------------------+
+ // Memory |xxxxxxxxx|yyyyyyyyyyyy|
+ // +------------------+---+
+ // ^ ^ ^
+ // + + +
+ // start exact relpc
+ // offset offset 0x1800
+ // 0x0000 0x1000
+ //
+ // exact_offset is the offset into the library file of this mapping.
+ // start_offset is the offset into the library file of the first mapping
+ // for that library. For native libraries (.so files) this should be 0.
+ optional uint64 exact_offset = 8; // This is not set on Android 10.
optional uint64 start_offset = 3;
optional uint64 start = 4;
optional uint64 end = 5;
diff --git a/src/base/event_fd.cc b/src/base/event_fd.cc
index b8f75bf..976db19 100644
--- a/src/base/event_fd.cc
+++ b/src/base/event_fd.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
#include <stdint.h>
#include <unistd.h>
@@ -74,3 +77,5 @@
} // namespace base
} // namespace perfetto
+
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index 74c985a..b8020ad 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -22,7 +22,8 @@
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/utils.h"
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) || \
+ PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
#include <unistd.h>
#else
#include <corecrt_io.h>
diff --git a/src/base/pipe.cc b/src/base/pipe.cc
index 5c9c3d7..c61492f 100644
--- a/src/base/pipe.cc
+++ b/src/base/pipe.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
#include "perfetto/ext/base/pipe.h"
#include <sys/types.h>
@@ -54,3 +57,5 @@
} // namespace base
} // namespace perfetto
+
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index c042290..5dc1bc8 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -39,6 +39,18 @@
return haystack.find(needle) != std::string::npos;
}
+size_t Find(const StringView& needle, const StringView& haystack) {
+ if (needle.size() == 0)
+ return 0;
+ if (needle.size() > haystack.size())
+ return std::string::npos;
+ for (size_t i = 0; i < haystack.size() - (needle.size() - 1); ++i) {
+ if (strncmp(haystack.data() + i, needle.data(), needle.size()) == 0)
+ return i;
+ }
+ return std::string::npos;
+}
+
bool CaseInsensitiveEqual(const std::string& first, const std::string& second) {
return first.size() == second.size() &&
std::equal(
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
index c14415c..c38f746 100644
--- a/src/base/string_utils_unittest.cc
+++ b/src/base/string_utils_unittest.cc
@@ -108,6 +108,38 @@
EXPECT_EQ(StripChars("foobar", "froab", '_'), "______");
}
+TEST(StringUtilsTest, Contains) {
+ EXPECT_TRUE(Contains("", ""));
+ EXPECT_TRUE(Contains("abc", ""));
+ EXPECT_TRUE(Contains("abc", "a"));
+ EXPECT_TRUE(Contains("abc", "b"));
+ EXPECT_TRUE(Contains("abc", "c"));
+ EXPECT_TRUE(Contains("abc", "ab"));
+ EXPECT_TRUE(Contains("abc", "bc"));
+ EXPECT_TRUE(Contains("abc", "abc"));
+ EXPECT_FALSE(Contains("abc", "d"));
+ EXPECT_FALSE(Contains("abc", "ac"));
+ EXPECT_FALSE(Contains("abc", "abcd"));
+ EXPECT_FALSE(Contains("", "a"));
+ EXPECT_FALSE(Contains("", "abc"));
+}
+
+TEST(StringUtilsTest, Find) {
+ EXPECT_EQ(Find("", ""), 0u);
+ EXPECT_EQ(Find("", "abc"), 0u);
+ EXPECT_EQ(Find("a", "abc"), 0u);
+ EXPECT_EQ(Find("b", "abc"), 1u);
+ EXPECT_EQ(Find("c", "abc"), 2u);
+ EXPECT_EQ(Find("ab", "abc"), 0u);
+ EXPECT_EQ(Find("bc", "abc"), 1u);
+ EXPECT_EQ(Find("abc", "abc"), 0u);
+ EXPECT_EQ(Find("d", "abc"), std::string::npos);
+ EXPECT_EQ(Find("ac", "abc"), std::string::npos);
+ EXPECT_EQ(Find("abcd", "abc"), std::string::npos);
+ EXPECT_EQ(Find("a", ""), std::string::npos);
+ EXPECT_EQ(Find("abc", ""), std::string::npos);
+}
+
} // namespace
} // namespace base
} // namespace perfetto
diff --git a/src/base/temp_file.cc b/src/base/temp_file.cc
index d57d383..d1e77e9 100644
--- a/src/base/temp_file.cc
+++ b/src/base/temp_file.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
#include "perfetto/ext/base/temp_file.h"
#include <stdlib.h>
@@ -90,3 +93,6 @@
} // namespace base
} // namespace perfetto
+
+
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/thread_task_runner.cc b/src/base/thread_task_runner.cc
index d0a1036..0576ee9 100644
--- a/src/base/thread_task_runner.cc
+++ b/src/base/thread_task_runner.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
#include "perfetto/ext/base/thread_task_runner.h"
#include <condition_variable>
@@ -79,3 +82,5 @@
} // namespace base
} // namespace perfetto
+
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/unix_task_runner.cc b/src/base/unix_task_runner.cc
index 6ef71aa..422947f 100644
--- a/src/base/unix_task_runner.cc
+++ b/src/base/unix_task_runner.cc
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-#include "perfetto/ext/base/unix_task_runner.h"
-
#include "perfetto/base/build_config.h"
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include "perfetto/ext/base/unix_task_runner.h"
#include <errno.h>
#include <stdlib.h>
@@ -229,3 +230,5 @@
} // namespace base
} // namespace perfetto
+
+#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index a384fe8..10af659 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -93,6 +93,7 @@
source_set("proc_utils") {
deps = [
"../../../gn:default_deps",
+ "../../../include/perfetto/profiling:normalize",
"../../base",
]
sources = [
@@ -212,6 +213,7 @@
"../../../gn:default_deps",
"../../../gn:gtest_and_gmock",
"../../../gn:libunwindstack",
+ "../../../include/perfetto/profiling:normalize",
"../../base",
"../../base:test_support",
"../../tracing",
diff --git a/src/profiling/memory/bookkeeping_dump.cc b/src/profiling/memory/bookkeeping_dump.cc
index 13e24bc..ddd569f 100644
--- a/src/profiling/memory/bookkeeping_dump.cc
+++ b/src/profiling/memory/bookkeeping_dump.cc
@@ -84,38 +84,47 @@
}
void DumpState::WriteBuildIDString(const Interned<std::string>& str) {
- bool inserted;
- std::tie(std::ignore, inserted) =
- intern_state_->dumped_strings_.emplace(str.id());
- if (inserted) {
+ auto it_and_inserted = intern_state_->dumped_strings_.emplace(str.id(), 0);
+ auto it = it_and_inserted.first;
+ // This is for the rare case that the same string is used as two different
+ // types (e.g. a function name that matches a path segment). In that case
+ // we need to emit the string as all of its types.
+ if ((it->second & kDumpedBuildID) == 0) {
auto interned_string = GetCurrentInternedData()->add_build_ids();
interned_string->set_iid(str.id());
interned_string->set_str(reinterpret_cast<const uint8_t*>(str->c_str()),
str->size());
+ it->second |= kDumpedBuildID;
}
}
void DumpState::WriteMappingPathString(const Interned<std::string>& str) {
- bool inserted;
- std::tie(std::ignore, inserted) =
- intern_state_->dumped_strings_.emplace(str.id());
- if (inserted) {
+ auto it_and_inserted = intern_state_->dumped_strings_.emplace(str.id(), 0);
+ auto it = it_and_inserted.first;
+ // This is for the rare case that the same string is used as two different
+ // types (e.g. a function name that matches a path segment). In that case
+ // we need to emit the string as all of its types.
+ if ((it->second & kDumpedMappingPath) == 0) {
auto interned_string = GetCurrentInternedData()->add_mapping_paths();
interned_string->set_iid(str.id());
interned_string->set_str(reinterpret_cast<const uint8_t*>(str->c_str()),
str->size());
+ it->second |= kDumpedMappingPath;
}
}
void DumpState::WriteFunctionNameString(const Interned<std::string>& str) {
- bool inserted;
- std::tie(std::ignore, inserted) =
- intern_state_->dumped_strings_.emplace(str.id());
- if (inserted) {
+ auto it_and_inserted = intern_state_->dumped_strings_.emplace(str.id(), 0);
+ auto it = it_and_inserted.first;
+ // This is for the rare case that the same string is used as two different
+ // types (e.g. a function name that matches a path segment). In that case
+ // we need to emit the string as all of its types.
+ if ((it->second & kDumpedFunctionName) == 0) {
auto interned_string = GetCurrentInternedData()->add_function_names();
interned_string->set_iid(str.id());
interned_string->set_str(reinterpret_cast<const uint8_t*>(str->c_str()),
str->size());
+ it->second |= kDumpedFunctionName;
}
}
diff --git a/src/profiling/memory/bookkeeping_dump.h b/src/profiling/memory/bookkeeping_dump.h
index bb09914..517d02c 100644
--- a/src/profiling/memory/bookkeeping_dump.h
+++ b/src/profiling/memory/bookkeeping_dump.h
@@ -37,13 +37,17 @@
void WriteFixedInternings(TraceWriter* trace_writer);
+constexpr int kDumpedBuildID = 1 << 0;
+constexpr int kDumpedMappingPath = 1 << 1;
+constexpr int kDumpedFunctionName = 1 << 2;
+
class DumpState {
public:
class InternState {
private:
friend class DumpState;
- std::set<InternID> dumped_strings_;
+ std::map<InternID, int> dumped_strings_;
std::set<InternID> dumped_frames_;
std::set<InternID> dumped_mappings_;
std::set<uint64_t> dumped_callstacks_;
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index c9c7767..d1024cc 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -308,8 +308,7 @@
return;
}
- auto it = data_sources_.find(id);
- if (it != data_sources_.end()) {
+ if (data_sources_.find(id) != data_sources_.end()) {
PERFETTO_DFATAL_OR_ELOG(
"Received duplicated data source instance id: %" PRIu64, id);
return;
diff --git a/src/profiling/memory/proc_utils.cc b/src/profiling/memory/proc_utils.cc
index 07f85cd..6d0b6f3 100644
--- a/src/profiling/memory/proc_utils.cc
+++ b/src/profiling/memory/proc_utils.cc
@@ -21,7 +21,7 @@
#include <unistd.h>
#include "perfetto/ext/base/file_utils.h"
-#include "src/profiling/memory/ext.h"
+#include "perfetto/profiling/normalize.h"
namespace perfetto {
namespace profiling {
diff --git a/src/profiling/memory/proc_utils_unittest.cc b/src/profiling/memory/proc_utils_unittest.cc
index b1881bb..6c0065c 100644
--- a/src/profiling/memory/proc_utils_unittest.cc
+++ b/src/profiling/memory/proc_utils_unittest.cc
@@ -15,7 +15,7 @@
*/
#include "src/profiling/memory/proc_utils.h"
-#include "src/profiling/memory/ext.h"
+#include "perfetto/profiling/normalize.h"
#include "perfetto/ext/base/utils.h"
#include "test/gtest_and_gmock.h"
diff --git a/src/trace_processor/args_tracker.cc b/src/trace_processor/args_tracker.cc
index 79da621..c21f586 100644
--- a/src/trace_processor/args_tracker.cc
+++ b/src/trace_processor/args_tracker.cc
@@ -85,6 +85,10 @@
storage->mutable_metadata()->OverwriteMetadata(
row, Variadic::Integer(set_id));
break;
+ case TableId::kTrack:
+ storage->mutable_track_table()->mutable_source_arg_set_id()->Set(
+ row, set_id);
+ break;
default:
PERFETTO_FATAL("Unsupported table to insert args into");
}
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index b09dc86..7360779 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -60,6 +60,8 @@
]
sources = [
"bit_vector_benchmark.cc",
+ "row_map_benchmark.cc",
+ "sparse_vector_benchmark.cc",
]
}
}
diff --git a/src/trace_processor/db/bit_vector.cc b/src/trace_processor/db/bit_vector.cc
index 5e2db89..edf1af6 100644
--- a/src/trace_processor/db/bit_vector.cc
+++ b/src/trace_processor/db/bit_vector.cc
@@ -19,12 +19,19 @@
namespace perfetto {
namespace trace_processor {
-BitVector::BitVector(uint32_t count, bool value) : inner_(count, value) {}
+BitVector::BitVector() = default;
-BitVector::BitVector(std::vector<bool> inner) : inner_(std::move(inner)) {}
+BitVector::BitVector(uint32_t count, bool value) {
+ Resize(count, value);
+}
+
+BitVector::BitVector(std::vector<Block> blocks,
+ std::vector<uint32_t> counts,
+ uint32_t size)
+ : size_(size), counts_(std::move(counts)), blocks_(std::move(blocks)) {}
BitVector BitVector::Copy() const {
- return BitVector(inner_);
+ return BitVector(blocks_, counts_, size_);
}
} // namespace trace_processor
diff --git a/src/trace_processor/db/bit_vector.h b/src/trace_processor/db/bit_vector.h
index 4bddd75..bd85b76 100644
--- a/src/trace_processor/db/bit_vector.h
+++ b/src/trace_processor/db/bit_vector.h
@@ -18,8 +18,10 @@
#define SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_H_
#include <stdint.h>
+#include <stdio.h>
#include <algorithm>
+#include <array>
#include <vector>
#include "perfetto/base/logging.h"
@@ -29,17 +31,15 @@
// A bitvector which compactly stores a vector of bools using a single bit
// for each bool.
-// TODO(lalitm): currently this is just a thin wrapper around std::vector<bool>
-// but in the future, we plan to add quite a few optimizations around ranges
-// of set bits.
class BitVector {
public:
// Creates an empty bitvector.
- BitVector() = default;
+ BitVector();
// Creates a bitvector of |count| size filled with |value|.
BitVector(uint32_t count, bool value = false);
+ // Enable moving bitvectors as they have no unmovable state.
BitVector(BitVector&&) noexcept = default;
BitVector& operator=(BitVector&&) = default;
@@ -47,21 +47,14 @@
BitVector Copy() const;
// Returns the size of the bitvector.
- uint32_t size() const { return static_cast<uint32_t>(inner_.size()); }
+ uint32_t size() const { return static_cast<uint32_t>(size_); }
// Returns whether the bit at |idx| is set.
bool IsSet(uint32_t idx) const {
PERFETTO_DCHECK(idx < size());
- return inner_[idx];
- }
- // Returns the index of the next set bit at or after index |idx|.
- // If there is no other set bits, returns |size()|.
- uint32_t NextSet(uint32_t idx) const {
- PERFETTO_DCHECK(idx <= inner_.size());
- auto it = std::find(inner_.begin() + static_cast<ptrdiff_t>(idx),
- inner_.end(), true);
- return static_cast<uint32_t>(std::distance(inner_.begin(), it));
+ Address a = IndexToAddress(idx);
+ return blocks_[a.block_idx].IsSet(a.block_offset);
}
// Returns the number of set bits in the bitvector.
@@ -70,35 +63,184 @@
// Returns the number of set bits between the start of the bitvector
// (inclusive) and the index |end| (exclusive).
uint32_t GetNumBitsSet(uint32_t end) const {
- return static_cast<uint32_t>(std::count(
- inner_.begin(), inner_.begin() + static_cast<ptrdiff_t>(end), true));
+ if (end == 0)
+ return 0;
+
+ // Although the external interface we present uses an exclusive |end|,
+ // internally it's a lot nicer to work with an inclusive |end| (mainly
+ // because we get block rollovers on exclusive ends which means we need
+ // to have if checks to ensure we don't overflow the number of blocks).
+ Address addr = IndexToAddress(end - 1);
+ uint32_t idx = addr.block_idx;
+
+ // Add the number of set bits until the start of the block to the number
+ // of set bits until the end address inside the block.
+ return counts_[idx] + blocks_[idx].GetNumBitsSet(addr.block_offset);
}
- // Returns the index of the |n|'th set bit.
+ // Returns the index of the |n|th set bit. Should only be called with |n| <
+ // GetNumBitsSet().
uint32_t IndexOfNthSet(uint32_t n) const {
- // TODO(lalitm): improve the performance of this method by investigating
- // AVX instructions.
- uint32_t offset = 0;
- for (uint32_t i = NextSet(0); i < size(); i = NextSet(i + 1), ++offset) {
- if (offset == n)
- return i;
+ PERFETTO_DCHECK(n < GetNumBitsSet());
+
+ // First search for the block which, up until the start of it, has more than
+ // n bits set. Note that this should never return |counts.begin()| as
+ // that should always be 0.
+ // TODO(lalitm): investigate whether we can make this faster with small
+ // binary search followed by a linear search instead of binary searching the
+ // full way.
+ auto it = std::upper_bound(counts_.begin(), counts_.end(), n);
+ PERFETTO_DCHECK(it != counts_.begin());
+
+ // Go back one block to find the block which has the bit we are looking for.
+ uint16_t block_idx =
+ static_cast<uint16_t>(std::distance(counts_.begin(), it) - 1);
+
+ // Figure out how many set bits forward we are looking inside the block
+ // by taking away the number of bits at the start of the block from n.
+ uint32_t set_in_block = n - counts_[block_idx];
+
+ // Compute the address of the bit in the block then convert the full
+ // address back to an index.
+ BlockOffset block_offset = blocks_[block_idx].IndexOfNthSet(set_in_block);
+ return AddressToIndex(Address{block_idx, block_offset});
+ }
+
+ // Sets the bit at index |idx| to true.
+ void Set(uint32_t idx) {
+ // Set the bit to the correct value inside the block but store the old
+ // bit to help fix the counts.
+ auto addr = IndexToAddress(idx);
+ bool old_value = blocks_[addr.block_idx].IsSet(addr.block_offset);
+
+ // If the old value was unset, set the bit and add one to the count.
+ if (PERFETTO_LIKELY(!old_value)) {
+ blocks_[addr.block_idx].Set(addr.block_offset);
+
+ uint32_t size = static_cast<uint32_t>(counts_.size());
+ for (uint32_t i = addr.block_idx + 1; i < size; ++i) {
+ counts_[i]++;
+ }
}
- PERFETTO_FATAL("Index out of bounds");
}
- // Sets the value at index |idx| to |value|.
- void Set(uint32_t idx, bool value) {
- PERFETTO_DCHECK(idx < size());
- inner_[idx] = value;
+ // Sets the bit at index |idx| to false.
+ void Clear(uint32_t idx) {
+ // Set the bit to the correct value inside the block but store the old
+ // bit to help fix the counts.
+ auto addr = IndexToAddress(idx);
+ bool old_value = blocks_[addr.block_idx].IsSet(addr.block_offset);
+
+ // If the old value was set, clear the bit and subtract one from all the
+ // counts.
+ if (PERFETTO_LIKELY(old_value)) {
+ blocks_[addr.block_idx].Clear(addr.block_offset);
+
+ uint32_t size = static_cast<uint32_t>(counts_.size());
+ for (uint32_t i = addr.block_idx + 1; i < size; ++i) {
+ counts_[i]--;
+ }
+ }
}
- // Appends |value| to the bitvector.
- void Append(bool value) { inner_.push_back(value); }
+ // Appends true to the bitvector.
+ void AppendTrue() {
+ Address addr = IndexToAddress(size_);
+ uint32_t old_blocks_size = static_cast<uint32_t>(blocks_.size());
+ uint32_t new_blocks_size = addr.block_idx + 1;
+
+ if (PERFETTO_UNLIKELY(new_blocks_size > old_blocks_size)) {
+ uint32_t t = GetNumBitsSet();
+ blocks_.emplace_back();
+ counts_.emplace_back(t);
+ }
+
+ size_++;
+ blocks_[addr.block_idx].Set(addr.block_offset);
+ }
+
+ // Appends false to the bitvector.
+ void AppendFalse() {
+ Address addr = IndexToAddress(size_);
+ uint32_t old_blocks_size = static_cast<uint32_t>(blocks_.size());
+ uint32_t new_blocks_size = addr.block_idx + 1;
+
+ if (PERFETTO_UNLIKELY(new_blocks_size > old_blocks_size)) {
+ uint32_t t = GetNumBitsSet();
+ blocks_.emplace_back();
+ counts_.emplace_back(t);
+ }
+
+ size_++;
+ // We don't need to clear the bit as we ensure that anything after
+ // size_ is always set to false.
+ }
// Resizes the BitVector to the given |size|.
// Truncates the BitVector if |size| < |size()| or fills the new space with
- // |value| if |size| > |size()|.
- void Resize(uint32_t size, bool value = false) { inner_.resize(size, value); }
+ // |value| if |size| > |size()|. Calling this method is a noop if |size| ==
+ // |size()|.
+ void Resize(uint32_t size, bool value = false) {
+ uint32_t old_size = size_;
+ if (size == old_size)
+ return;
+
+ // Empty bitvectors should be memory efficient so we don't keep any data
+ // around in the bitvector.
+ if (size == 0) {
+ blocks_.clear();
+ counts_.clear();
+ size_ = 0;
+ return;
+ }
+
+ // Compute the address of the new last bit in the bitvector.
+ Address last_addr = IndexToAddress(size - 1);
+ uint32_t old_blocks_size = static_cast<uint32_t>(counts_.size());
+ uint32_t new_blocks_size = last_addr.block_idx + 1;
+
+ // Then, resize the block and count vectors to have the correct
+ // number of entries.
+ blocks_.resize(new_blocks_size);
+ counts_.resize(new_blocks_size);
+
+ if (new_blocks_size > old_blocks_size) {
+ // If we've increased the number of blocks, we need to fix the
+ // counts vector by setting the newly added counts to the
+ // count of the bitvector. This matches the empty blocks we just
+ // added. Below, we will actually set the bits in the newly added
+ // blocks and as we do that, we will update the counts.
+ //
+ // Note: as we haven't updated |size_| yet GetNumBitsSet() won't take into
+ // account the newly added blocks yet.
+ uint32_t count = GetNumBitsSet();
+ for (uint32_t i = old_blocks_size; i < new_blocks_size; ++i) {
+ counts_[i] = count;
+ }
+ }
+
+ // Actually update the size before we call |Set| below to ensure Set's
+ // invariants make sense.
+ size_ = size;
+ if (size > old_size) {
+ // Just go through all the newly added bits and set them to the true - we
+ // don't need to do this if !value because we always expect the newly
+ // added bits to be zeroed (this is ensured by the else branch).
+ // TODO(lalitm): this is clearly non optimal. Try and have a more
+ // optimized version of this based on setting whole blocks
+ // to 1.
+ if (value) {
+ for (uint32_t i = old_size; i < size; ++i) {
+ Set(i);
+ }
+ }
+ } else {
+ // Throw away all the bits after the new last bit. We do this to make
+ // future lookup, append and resize operations not have to worrying about
+ // trailing garbage bits in the last block.
+ blocks_[last_addr.block_idx].ClearAfter(last_addr.block_offset);
+ }
+ }
// Updates the ith set bit of this bitvector with the value of
// |other.IsSet(i)|.
@@ -116,20 +258,303 @@
void UpdateSetBits(const BitVector& other) {
PERFETTO_DCHECK(other.size() == GetNumBitsSet());
- uint32_t offset = 0;
- for (uint32_t i = NextSet(0); i < size(); i = NextSet(i + 1), ++offset) {
- if (!other.IsSet(offset))
- Set(i, false);
+ // Go through each set bit and if |other| has it unset, then unset the
+ // bit taking care to update the index we consider to take into account
+ // the bits we just unset.
+ // TODO(lalitm): we add an iterator implementation to remove this
+ // inefficient loop.
+ uint32_t removed = 0;
+ for (uint32_t i = 0, size = other.size(); i < size; ++i) {
+ if (!other.IsSet(i)) {
+ Clear(IndexOfNthSet(i - removed++));
+ }
}
}
private:
- BitVector(std::vector<bool>);
+ // Represents the offset of a bit within a block.
+ struct BlockOffset {
+ uint16_t word_idx;
+ uint16_t bit_idx;
+ };
+
+ // Represents the address of a bit within the bitvector.
+ struct Address {
+ uint32_t block_idx;
+ BlockOffset block_offset;
+ };
+
+ // Represents the smallest collection of bits we can refer to as
+ // one unit.
+ //
+ // Currently, this is implemented as a 64 bit integer as this is the
+ // largest type which we can assume to be present on all platforms.
+ class BitWord {
+ public:
+ static constexpr uint32_t kBits = 64;
+
+ // Returns whether the bit at the given index is set.
+ bool IsSet(uint32_t idx) const {
+ PERFETTO_DCHECK(idx < kBits);
+ return (word >> idx) & 1ull;
+ }
+
+ // Sets the bit at the given index to true.
+ void Set(uint32_t idx) {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // Or the value for the true shifted up to |idx| with the word.
+ word |= 1ull << idx;
+ }
+
+ // Sets the bit at the given index to false.
+ void Clear(uint32_t idx) {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // And the integer of all bits set apart from |idx| with the word.
+ word &= ~(1ull << idx);
+ }
+
+ // Clears all the bits (i.e. sets the atom to zero).
+ void ClearAll() { word = 0; }
+
+ // Returns the index of the nth set bit.
+ // Undefined if |n| >= |GetNumBitsSet()|.
+ uint16_t IndexOfNthSet(uint32_t n) const {
+ PERFETTO_DCHECK(n < kBits);
+
+ // The below code is very dense but essentially computes the nth set
+ // bit inside |atom| in the "broadword" style of programming (sometimes
+ // referred to as "SIMD within a register").
+ //
+ // Instead of treating a uint64 as an individual unit, broadword
+ // algorithms treat them as a packed vector of uint8. By doing this, they
+ // allow branchless algorithms when considering bits of a uint64.
+ //
+ // In benchmarks, this algorithm has found to be the fastest, portable
+ // way of computing the nth set bit (if we were only targetting new
+ // versions of x64, we could also use pdep + ctz but unfortunately
+ // this would fail on WASM - this about 2.5-3x faster on x64).
+ //
+ // The code below was taken from the paper
+ // http://vigna.di.unimi.it/ftp/papers/Broadword.pdf
+ uint64_t s = word - ((word & 0xAAAAAAAAAAAAAAAA) >> 1);
+ s = (s & 0x3333333333333333) + ((s >> 2) & 0x3333333333333333);
+ s = ((s + (s >> 4)) & 0x0F0F0F0F0F0F0F0F) * L8;
+
+ uint64_t b = (BwLessThan(s, n * L8) >> 7) * L8 >> 53 & ~7ull;
+ uint64_t l = n - ((s << 8) >> b & 0xFF);
+ s = (BwGtZero(((word >> b & 0xFF) * L8) & 0x8040201008040201) >> 7) * L8;
+
+ uint64_t ret = b + ((BwLessThan(s, l * L8) >> 7) * L8 >> 56);
+
+ return static_cast<uint16_t>(ret);
+ }
+
+ // Returns the number of set bits.
+ uint32_t GetNumBitsSet() const {
+ // We use __builtin_popcountll here as it's available natively for the two
+ // targets we care most about (x64 and WASM).
+ return static_cast<uint32_t>(__builtin_popcountll(word));
+ }
+
+ // Returns the number of set bits up to and including the bit at |idx|.
+ uint32_t GetNumBitsSet(uint32_t idx) const {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // We use __builtin_popcountll here as it's available natively for the two
+ // targets we care most about (x64 and WASM).
+ return static_cast<uint32_t>(__builtin_popcountll(WordUntil(idx)));
+ }
+
+ // Retains all bits up to and including the bit at |idx| and clears
+ // all bits after this point.
+ void ClearAfter(uint32_t idx) {
+ PERFETTO_DCHECK(idx < kBits);
+ word = WordUntil(idx);
+ }
+
+ private:
+ // Constant with all the low bit of every byte set.
+ static constexpr uint64_t L8 = 0x0101010101010101;
+
+ // Constant with all the high bit of every byte set.
+ static constexpr uint64_t H8 = 0x8080808080808080;
+
+ // Returns a packed uint64 encoding whether each byte of x is less
+ // than each corresponding byte of y.
+ // This is computed in the "broadword" style of programming; see
+ // IndexOfNthSet for details on this.
+ static uint64_t BwLessThan(uint64_t x, uint64_t y) {
+ return (((y | H8) - (x & ~H8)) ^ x ^ y) & H8;
+ }
+
+ // Returns a packed uint64 encoding whether each byte of x is greater
+ // than or equal zero.
+ // This is computed in the "broadword" style of programming; see
+ // IndexOfNthSet for details on this.
+ static uint64_t BwGtZero(uint64_t x) { return (((x | H8) - L8) | x) & H8; }
+
+ // Returns the bits up to and including the bit at |idx|.
+ uint64_t WordUntil(uint32_t idx) const {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // To understand what is happeninng here, consider an example.
+ // Suppose we want to all the bits up to the 7th bit in the atom
+ // 7th
+ // |
+ // v
+ // atom: 01010101011111000
+ //
+ // The easiest way to do this would be if we had a mask with only
+ // the bottom 7 bits set:
+ // mask: 00000000001111111
+ //
+ // Start with 1 and shift it up (idx + 1) bits we get:
+ // top : 00000000010000000
+ uint64_t top = 1ull << ((idx + 1ull) % kBits);
+
+ // We need to handle the case where idx == 63. In this case |top| will be
+ // zero because 1 << ((idx + 1) % 64) == 1 << (64 % 64) == 1.
+ // In this case, we actually want top == 0. We can do this by shifting
+ // down by (idx + 1) / kBits - this will be a noop for every index other
+ // than idx == 63. This should also be free on intel because of the mod
+ // instruction above.
+ top = top >> ((idx + 1) / kBits);
+
+ // Then if we take away 1, we get precisely the mask we want.
+ uint64_t mask = top - 1u;
+
+ // Finish up by anding the the atom with the computed msk.
+ return word & mask;
+ }
+
+ uint64_t word = 0;
+ };
+
+ // Represents a group of bits with a bitcount such that it is
+ // efficient to work on these bits.
+ //
+ // On x86 architectures we generally target for trace processor, the
+ // size of a cache line is 64 bytes (or 512 bits). For this reason,
+ // we make the size of the block contain 8 atoms as 8 * 64 == 512.
+ //
+ // TODO(lalitm): investigate whether we should tune this value for
+ // WASM and ARM.
+ class Block {
+ public:
+ // See class documentation for how these constants are chosen.
+ static constexpr uint32_t kWords = 8;
+ static constexpr uint32_t kBits = kWords * BitWord::kBits;
+
+ // Returns whether the bit at the given address is set.
+ bool IsSet(const BlockOffset& addr) const {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ return words_[addr.word_idx].IsSet(addr.bit_idx);
+ }
+
+ // Sets the bit at the given address to true.
+ void Set(const BlockOffset& addr) {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ words_[addr.word_idx].Set(addr.bit_idx);
+ }
+
+ // Sets the bit at the given address to false.
+ void Clear(const BlockOffset& addr) {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ words_[addr.word_idx].Clear(addr.bit_idx);
+ }
+
+ // Gets the offset of the nth set bit in this block.
+ BlockOffset IndexOfNthSet(uint32_t n) const {
+ uint32_t count = 0;
+ for (uint16_t i = 0; i < kWords; ++i) {
+ // Keep a running count of all the set bits in the atom.
+ uint32_t value = count + words_[i].GetNumBitsSet();
+ if (value <= n) {
+ count = value;
+ continue;
+ }
+
+ // The running count of set bits is more than |n|. That means this atom
+ // contains the bit we are looking for.
+
+ // Take away the number of set bits to the start of this atom from |n|.
+ uint32_t set_in_atom = n - count;
+
+ // Figure out the index of the set bit inside the atom and create the
+ // address of this bit from that.
+ uint16_t bit_idx = words_[i].IndexOfNthSet(set_in_atom);
+ PERFETTO_DCHECK(bit_idx < 64);
+ return BlockOffset{i, bit_idx};
+ }
+ PERFETTO_FATAL("Index out of bounds");
+ }
+
+ // Gets the number of set bits within a block up to and including the bit
+ // at the given address.
+ uint32_t GetNumBitsSet(const BlockOffset& addr) const {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ // Count all the set bits in the atom until we reach the last atom
+ // index.
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < addr.word_idx; ++i) {
+ count += words_[i].GetNumBitsSet();
+ }
+
+ // For the last atom, only count the bits upto and including the bit
+ // index.
+ return count + words_[addr.word_idx].GetNumBitsSet(addr.bit_idx);
+ }
+
+ // Retains all bits up to and including the bit at |addr| and clears
+ // all bits after this point.
+ void ClearAfter(const BlockOffset& offset) {
+ PERFETTO_DCHECK(offset.word_idx < kWords);
+
+ // In the first atom, keep the bits until the address specified.
+ words_[offset.word_idx].ClearAfter(offset.bit_idx);
+
+ // For all subsequent atoms, we just clear the whole atom.
+ for (uint32_t i = offset.word_idx + 1; i < kWords; ++i) {
+ words_[i].ClearAll();
+ }
+ }
+
+ private:
+ std::array<BitWord, kWords> words_{};
+ };
+
+ BitVector(std::vector<Block> blocks,
+ std::vector<uint32_t> counts,
+ uint32_t size);
BitVector(const BitVector&) = delete;
BitVector& operator=(const BitVector&) = delete;
- std::vector<bool> inner_;
+ static Address IndexToAddress(uint32_t idx) {
+ Address a;
+ a.block_idx = idx / Block::kBits;
+
+ uint16_t bit_idx_inside_block = idx % Block::kBits;
+ a.block_offset.word_idx = bit_idx_inside_block / BitWord::kBits;
+ a.block_offset.bit_idx = bit_idx_inside_block % BitWord::kBits;
+ return a;
+ }
+
+ static uint32_t AddressToIndex(Address addr) {
+ return addr.block_idx * Block::kBits +
+ addr.block_offset.word_idx * BitWord::kBits +
+ addr.block_offset.bit_idx;
+ }
+
+ uint32_t size_ = 0;
+ std::vector<uint32_t> counts_;
+ std::vector<Block> blocks_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/db/bit_vector_benchmark.cc b/src/trace_processor/db/bit_vector_benchmark.cc
index d6ed930..50e9baf 100644
--- a/src/trace_processor/db/bit_vector_benchmark.cc
+++ b/src/trace_processor/db/bit_vector_benchmark.cc
@@ -18,68 +18,119 @@
#include "src/trace_processor/db/bit_vector.h"
-static void BM_BitVectorAppend(benchmark::State& state) {
- static constexpr uint32_t kPoolSize = 1024 * 1024;
- std::vector<bool> bit_pool(kPoolSize);
+namespace {
- static constexpr uint32_t kRandomSeed = 42;
- std::minstd_rand0 rnd_engine(kRandomSeed);
- for (uint32_t i = 0; i < kPoolSize; ++i) {
- bit_pool[i] = rnd_engine() % 2;
- }
+using perfetto::trace_processor::BitVector;
- perfetto::trace_processor::BitVector bv;
- uint32_t pool_idx = 0;
+}
+
+static void BM_BitVectorAppendTrue(benchmark::State& state) {
+ BitVector bv;
for (auto _ : state) {
- bv.Append(bit_pool[pool_idx]);
- pool_idx = (pool_idx + 1) % kPoolSize;
+ bv.AppendTrue();
benchmark::ClobberMemory();
}
}
-BENCHMARK(BM_BitVectorAppend);
+BENCHMARK(BM_BitVectorAppendTrue);
+
+static void BM_BitVectorAppendFalse(benchmark::State& state) {
+ BitVector bv;
+ for (auto _ : state) {
+ bv.AppendFalse();
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_BitVectorAppendFalse);
static void BM_BitVectorSet(benchmark::State& state) {
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ }
+
static constexpr uint32_t kPoolSize = 1024 * 1024;
std::vector<bool> bit_pool(kPoolSize);
std::vector<uint32_t> row_pool(kPoolSize);
-
- static constexpr uint32_t kSize = 123456;
- perfetto::trace_processor::BitVector bv;
-
- static constexpr uint32_t kRandomSeed = 42;
- std::minstd_rand0 rnd_engine(kRandomSeed);
for (uint32_t i = 0; i < kPoolSize; ++i) {
bit_pool[i] = rnd_engine() % 2;
- row_pool[i] = rnd_engine() % kSize;
- }
-
- for (uint32_t i = 0; i < kSize; ++i) {
- bv.Append(rnd_engine() % 2);
+ row_pool[i] = rnd_engine() % size;
}
uint32_t pool_idx = 0;
for (auto _ : state) {
- bv.Set(row_pool[pool_idx], bit_pool[pool_idx]);
+ bv.Set(row_pool[pool_idx]);
pool_idx = (pool_idx + 1) % kPoolSize;
benchmark::ClobberMemory();
}
}
-BENCHMARK(BM_BitVectorSet);
+BENCHMARK(BM_BitVectorSet)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
-static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
- static constexpr uint32_t kPoolSize = 1024 * 1024;
- std::vector<uint32_t> row_pool(kPoolSize);
-
- static constexpr uint32_t kSize = 123456;
- perfetto::trace_processor::BitVector bv;
-
+static void BM_BitVectorClear(benchmark::State& state) {
static constexpr uint32_t kRandomSeed = 42;
std::minstd_rand0 rnd_engine(kRandomSeed);
- for (uint32_t i = 0; i < kSize; ++i) {
- bool value = rnd_engine() % 2;
- bv.Append(value);
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
}
+ static constexpr uint32_t kPoolSize = 1024 * 1024;
+ std::vector<uint32_t> row_pool(kPoolSize);
+ for (uint32_t i = 0; i < kPoolSize; ++i) {
+ row_pool[i] = rnd_engine() % size;
+ }
+
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ bv.Clear(row_pool[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_BitVectorClear)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
+
+static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ }
+
+ static constexpr uint32_t kPoolSize = 1024 * 1024;
+ std::vector<uint32_t> row_pool(kPoolSize);
uint32_t set_bit_count = bv.GetNumBitsSet();
for (uint32_t i = 0; i < kPoolSize; ++i) {
row_pool[i] = rnd_engine() % set_bit_count;
@@ -91,18 +142,28 @@
pool_idx = (pool_idx + 1) % kPoolSize;
}
}
-BENCHMARK(BM_BitVectorIndexOfNthSet);
+BENCHMARK(BM_BitVectorIndexOfNthSet)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
static void BM_BitVectorGetNumBitsSet(benchmark::State& state) {
- static constexpr uint32_t kSize = 123456;
- perfetto::trace_processor::BitVector bv;
- uint32_t count = 0;
-
static constexpr uint32_t kRandomSeed = 42;
std::minstd_rand0 rnd_engine(kRandomSeed);
- for (uint32_t i = 0; i < kSize; ++i) {
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ uint32_t count = 0;
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
bool value = rnd_engine() % 2;
- bv.Append(value);
+ if (value) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
if (value)
count++;
@@ -114,4 +175,9 @@
}
PERFETTO_CHECK(res == count);
}
-BENCHMARK(BM_BitVectorGetNumBitsSet);
+BENCHMARK(BM_BitVectorGetNumBitsSet)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
diff --git a/src/trace_processor/db/bit_vector_unittest.cc b/src/trace_processor/db/bit_vector_unittest.cc
index 371b4fe..7e7543b 100644
--- a/src/trace_processor/db/bit_vector_unittest.cc
+++ b/src/trace_processor/db/bit_vector_unittest.cc
@@ -16,97 +16,210 @@
#include "src/trace_processor/db/bit_vector.h"
+#include <random>
+
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace trace_processor {
namespace {
-TEST(BitVectorUnittest, Set) {
- BitVector bv(3, true);
- bv.Set(0, false);
- bv.Set(1, true);
+TEST(BitVectorUnittest, CreateAllTrue) {
+ BitVector bv(2049, true);
- ASSERT_EQ(bv.size(), 3u);
- ASSERT_FALSE(bv.IsSet(0));
+ // Ensure that a selection of interesting bits are set.
+ ASSERT_TRUE(bv.IsSet(0));
ASSERT_TRUE(bv.IsSet(1));
- ASSERT_TRUE(bv.IsSet(2));
+ ASSERT_TRUE(bv.IsSet(511));
+ ASSERT_TRUE(bv.IsSet(512));
+ ASSERT_TRUE(bv.IsSet(2047));
+ ASSERT_TRUE(bv.IsSet(2048));
}
-TEST(BitVectorUnittest, Append) {
+TEST(BitVectorUnittest, CreateAllFalse) {
+ BitVector bv(2049, false);
+
+ // Ensure that a selection of interesting bits are cleared.
+ ASSERT_FALSE(bv.IsSet(0));
+ ASSERT_FALSE(bv.IsSet(1));
+ ASSERT_FALSE(bv.IsSet(511));
+ ASSERT_FALSE(bv.IsSet(512));
+ ASSERT_FALSE(bv.IsSet(2047));
+ ASSERT_FALSE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, Set) {
+ BitVector bv(2049, false);
+ bv.Set(0);
+ bv.Set(1);
+ bv.Set(511);
+ bv.Set(512);
+ bv.Set(2047);
+
+ // Ensure the bits we touched are set.
+ ASSERT_TRUE(bv.IsSet(0));
+ ASSERT_TRUE(bv.IsSet(1));
+ ASSERT_TRUE(bv.IsSet(511));
+ ASSERT_TRUE(bv.IsSet(512));
+ ASSERT_TRUE(bv.IsSet(2047));
+
+ // Ensure that a selection of other interestinng bits are
+ // still cleared.
+ ASSERT_FALSE(bv.IsSet(2));
+ ASSERT_FALSE(bv.IsSet(63));
+ ASSERT_FALSE(bv.IsSet(64));
+ ASSERT_FALSE(bv.IsSet(510));
+ ASSERT_FALSE(bv.IsSet(513));
+ ASSERT_FALSE(bv.IsSet(1023));
+ ASSERT_FALSE(bv.IsSet(1024));
+ ASSERT_FALSE(bv.IsSet(2046));
+ ASSERT_FALSE(bv.IsSet(2048));
+ ASSERT_FALSE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, Clear) {
+ BitVector bv(2049, true);
+ bv.Clear(0);
+ bv.Clear(1);
+ bv.Clear(511);
+ bv.Clear(512);
+ bv.Clear(2047);
+
+ // Ensure the bits we touched are cleared.
+ ASSERT_FALSE(bv.IsSet(0));
+ ASSERT_FALSE(bv.IsSet(1));
+ ASSERT_FALSE(bv.IsSet(511));
+ ASSERT_FALSE(bv.IsSet(512));
+ ASSERT_FALSE(bv.IsSet(2047));
+
+ // Ensure that a selection of other interestinng bits are
+ // still set.
+ ASSERT_TRUE(bv.IsSet(2));
+ ASSERT_TRUE(bv.IsSet(63));
+ ASSERT_TRUE(bv.IsSet(64));
+ ASSERT_TRUE(bv.IsSet(510));
+ ASSERT_TRUE(bv.IsSet(513));
+ ASSERT_TRUE(bv.IsSet(1023));
+ ASSERT_TRUE(bv.IsSet(1024));
+ ASSERT_TRUE(bv.IsSet(2046));
+ ASSERT_TRUE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, AppendToEmpty) {
BitVector bv;
- bv.Append(true);
- bv.Append(false);
+ bv.AppendTrue();
+ bv.AppendFalse();
ASSERT_EQ(bv.size(), 2u);
ASSERT_TRUE(bv.IsSet(0));
ASSERT_FALSE(bv.IsSet(1));
}
-TEST(BitVectorUnittest, NextSet) {
- BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+TEST(BitVectorUnittest, AppendToExisting) {
+ BitVector bv(2046, false);
+ bv.AppendTrue();
+ bv.AppendFalse();
+ bv.AppendTrue();
+ bv.AppendTrue();
- ASSERT_EQ(bv.NextSet(0), 1u);
- ASSERT_EQ(bv.NextSet(1), 1u);
- ASSERT_EQ(bv.NextSet(2), 2u);
- ASSERT_EQ(bv.NextSet(3), 4u);
- ASSERT_EQ(bv.NextSet(4), 4u);
- ASSERT_EQ(bv.NextSet(5), 6u);
+ ASSERT_EQ(bv.size(), 2050u);
+ ASSERT_TRUE(bv.IsSet(2046));
+ ASSERT_FALSE(bv.IsSet(2047));
+ ASSERT_TRUE(bv.IsSet(2048));
+ ASSERT_TRUE(bv.IsSet(2049));
}
TEST(BitVectorUnittest, GetNumBitsSet) {
- BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+ BitVector bv(2049, false);
+ bv.Set(0);
+ bv.Set(1);
+ bv.Set(511);
+ bv.Set(512);
+ bv.Set(2047);
+ bv.Set(2048);
- ASSERT_EQ(bv.GetNumBitsSet(), 3u);
+ ASSERT_EQ(bv.GetNumBitsSet(), 6u);
ASSERT_EQ(bv.GetNumBitsSet(0), 0u);
- ASSERT_EQ(bv.GetNumBitsSet(1), 0u);
- ASSERT_EQ(bv.GetNumBitsSet(2), 1u);
+ ASSERT_EQ(bv.GetNumBitsSet(1), 1u);
+ ASSERT_EQ(bv.GetNumBitsSet(2), 2u);
ASSERT_EQ(bv.GetNumBitsSet(3), 2u);
- ASSERT_EQ(bv.GetNumBitsSet(4), 2u);
- ASSERT_EQ(bv.GetNumBitsSet(5), 3u);
- ASSERT_EQ(bv.GetNumBitsSet(6), 3u);
+ ASSERT_EQ(bv.GetNumBitsSet(511), 2u);
+ ASSERT_EQ(bv.GetNumBitsSet(512), 3u);
+ ASSERT_EQ(bv.GetNumBitsSet(1023), 4u);
+ ASSERT_EQ(bv.GetNumBitsSet(1024), 4u);
+ ASSERT_EQ(bv.GetNumBitsSet(2047), 4u);
+ ASSERT_EQ(bv.GetNumBitsSet(2048), 5u);
+ ASSERT_EQ(bv.GetNumBitsSet(2049), 6u);
}
TEST(BitVectorUnittest, IndexOfNthSet) {
- BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+ BitVector bv(2050, false);
+ bv.Set(0);
+ bv.Set(1);
+ bv.Set(511);
+ bv.Set(512);
+ bv.Set(2047);
+ bv.Set(2048);
- ASSERT_EQ(bv.IndexOfNthSet(0), 1u);
- ASSERT_EQ(bv.IndexOfNthSet(1), 2u);
- ASSERT_EQ(bv.IndexOfNthSet(2), 4u);
+ ASSERT_EQ(bv.IndexOfNthSet(0), 0u);
+ ASSERT_EQ(bv.IndexOfNthSet(1), 1u);
+ ASSERT_EQ(bv.IndexOfNthSet(2), 511u);
+ ASSERT_EQ(bv.IndexOfNthSet(3), 512u);
+ ASSERT_EQ(bv.IndexOfNthSet(4), 2047u);
+ ASSERT_EQ(bv.IndexOfNthSet(5), 2048u);
}
TEST(BitVectorUnittest, Resize) {
BitVector bv(1, false);
+
bv.Resize(2, true);
- bv.Resize(3, false);
-
- ASSERT_EQ(bv.IsSet(1), true);
- ASSERT_EQ(bv.IsSet(2), false);
-
- bv.Resize(2, false);
-
ASSERT_EQ(bv.size(), 2u);
ASSERT_EQ(bv.IsSet(1), true);
+
+ bv.Resize(2049, false);
+ ASSERT_EQ(bv.size(), 2049u);
+ ASSERT_EQ(bv.IsSet(2), false);
+ ASSERT_EQ(bv.IsSet(2047), false);
+ ASSERT_EQ(bv.IsSet(2048), false);
+
+ // Set these two bits; the first should be preserved and the
+ // second should disappear.
+ bv.Set(512);
+ bv.Set(513);
+
+ bv.Resize(513, false);
+ ASSERT_EQ(bv.size(), 513u);
+ ASSERT_EQ(bv.IsSet(1), true);
+ ASSERT_EQ(bv.IsSet(512), true);
+ ASSERT_EQ(bv.GetNumBitsSet(), 2u);
+
+ // When we resize up, we need to be sure that the set bit from
+ // before we resized down is not still present as a garbage bit.
+ bv.Resize(514, false);
+ ASSERT_EQ(bv.size(), 514u);
+ ASSERT_EQ(bv.IsSet(513), false);
+ ASSERT_EQ(bv.GetNumBitsSet(), 2u);
+}
+
+TEST(BitVectorUnittest, AppendAfterResizeDown) {
+ BitVector bv(2049, false);
+ bv.Set(2048);
+
+ bv.Resize(2048);
+ bv.AppendFalse();
+ ASSERT_EQ(bv.IsSet(2048), false);
+ ASSERT_EQ(bv.GetNumBitsSet(), 0u);
}
TEST(BitVectorUnittest, UpdateSetBits) {
BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+ bv.Set(1);
+ bv.Set(2);
+ bv.Set(4);
BitVector picker(3u, true);
- picker.Set(1, false);
+ picker.Clear(1);
bv.UpdateSetBits(picker);
@@ -115,6 +228,37 @@
ASSERT_TRUE(bv.IsSet(4));
}
+TEST(BitVectorUnittest, QueryStressTest) {
+ BitVector bv;
+ std::vector<bool> bool_vec;
+ std::vector<uint32_t> int_vec;
+
+ static constexpr uint32_t kCount = 4096;
+ std::minstd_rand0 rand;
+ for (uint32_t i = 0; i < kCount; ++i) {
+ bool res = rand() % 2u;
+ if (res) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ bool_vec.push_back(res);
+ if (res)
+ int_vec.emplace_back(i);
+ }
+
+ for (uint32_t i = 0; i < kCount; ++i) {
+ uint32_t count = static_cast<uint32_t>(std::count(
+ bool_vec.begin(), bool_vec.begin() + static_cast<int32_t>(i), true));
+ ASSERT_EQ(bv.IsSet(i), bool_vec[i]);
+ ASSERT_EQ(bv.GetNumBitsSet(i), count);
+ }
+
+ for (uint32_t i = 0; i < int_vec.size(); ++i) {
+ ASSERT_EQ(bv.IndexOfNthSet(i), int_vec[i]);
+ }
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/db/column.cc b/src/trace_processor/db/column.cc
index 9e46036..91b22ef 100644
--- a/src/trace_processor/db/column.cc
+++ b/src/trace_processor/db/column.cc
@@ -66,6 +66,9 @@
case FilterOp::kGt:
iv->RemoveIf([this, value](uint32_t row) { return Get(row) <= value; });
break;
+ case FilterOp::kNeq:
+ iv->RemoveIf([this, value](uint32_t row) { return Get(row) == value; });
+ break;
}
}
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 8930ba8..b545e10 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -30,8 +30,9 @@
namespace trace_processor {
// Represents the possible filter operations on a column.
-enum FilterOp {
+enum class FilterOp {
kEq,
+ kNeq,
kGt,
kLt,
};
@@ -161,6 +162,9 @@
// given filter constraint.
void FilterInto(FilterOp, SqlValue value, RowMap*) const;
+ // Returns true if this column is considered an id column.
+ bool IsId() const { return (flags_ & Flag::kId) != 0; }
+
const RowMap& row_map() const;
const char* name() const { return name_; }
SqlValue::Type type() const {
@@ -186,6 +190,9 @@
Constraint lt(SqlValue value) const {
return Constraint{col_idx_, FilterOp::kLt, value};
}
+ Constraint neq(SqlValue value) const {
+ return Constraint{col_idx_, FilterOp::kNeq, value};
+ }
// Returns an Order for each Order type for this Column.
Order ascending() const { return Order{col_idx_, false}; }
diff --git a/src/trace_processor/db/row_map.cc b/src/trace_processor/db/row_map.cc
index 29c493e..b9a3a9c 100644
--- a/src/trace_processor/db/row_map.cc
+++ b/src/trace_processor/db/row_map.cc
@@ -29,28 +29,31 @@
return compact_ ? RowMap(bit_vector_.Copy()) : RowMap(index_vector_);
}
-void RowMap::SelectRows(const RowMap& picker) {
+RowMap RowMap::SelectRows(const RowMap& picker) const {
if (compact_ && picker.compact_) {
- bit_vector_.UpdateSetBits(picker.bit_vector_);
+ BitVector bv = bit_vector_.Copy();
+ bv.UpdateSetBits(picker.bit_vector_);
+ return RowMap(std::move(bv));
} else if (compact_ && !picker.compact_) {
- index_vector_.resize(picker.index_vector_.size());
+ std::vector<uint32_t> iv(picker.index_vector_.size());
for (uint32_t i = 0; i < picker.index_vector_.size(); ++i) {
// TODO(lalitm): this is pretty inefficient.
- index_vector_[i] = bit_vector_.IndexOfNthSet(picker.index_vector_[i]);
+ iv[i] = bit_vector_.IndexOfNthSet(picker.index_vector_[i]);
}
- compact_ = false;
- bit_vector_ = BitVector();
+ return RowMap(std::move(iv));
} else if (!compact_ && picker.compact_) {
+ RowMap rm = Copy();
uint32_t idx = 0;
- RemoveIf(
+ rm.RemoveIf(
[&idx, &picker](uint32_t) { return !picker.bit_vector_.IsSet(idx++); });
+ return rm;
} else /* (!compact_ && !picker.compact_) */ {
- std::vector<uint32_t> old_idx_vector = std::move(index_vector_);
- index_vector_ = std::vector<uint32_t>(picker.index_vector_.size());
+ std::vector<uint32_t> iv(picker.index_vector_.size());
for (uint32_t i = 0; i < picker.index_vector_.size(); ++i) {
- PERFETTO_DCHECK(picker.index_vector_[i] < old_idx_vector.size());
- index_vector_[i] = old_idx_vector[picker.index_vector_[i]];
+ PERFETTO_DCHECK(picker.index_vector_[i] < index_vector_.size());
+ iv[i] = index_vector_[picker.index_vector_[i]];
}
+ return RowMap(std::move(iv));
}
}
diff --git a/src/trace_processor/db/row_map.h b/src/trace_processor/db/row_map.h
index 6dcdbc7..01006fb 100644
--- a/src/trace_processor/db/row_map.h
+++ b/src/trace_processor/db/row_map.h
@@ -22,6 +22,7 @@
#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/optional.h"
#include "src/trace_processor/db/bit_vector.h"
namespace perfetto {
@@ -68,12 +69,17 @@
}
// Returns the first index of the given |row| in the RowMap.
- uint32_t IndexOf(uint32_t row) const {
+ base::Optional<uint32_t> IndexOf(uint32_t row) const {
if (compact_) {
- return bit_vector_.GetNumBitsSet(row);
+ return row < bit_vector_.size() && bit_vector_.IsSet(row)
+ ? base::make_optional(bit_vector_.GetNumBitsSet(row))
+ : base::nullopt;
} else {
auto it = std::find(index_vector_.begin(), index_vector_.end(), row);
- return static_cast<uint32_t>(std::distance(index_vector_.begin(), it));
+ return it != index_vector_.end()
+ ? base::make_optional(static_cast<uint32_t>(
+ std::distance(index_vector_.begin(), it)))
+ : base::nullopt;
}
}
@@ -82,7 +88,7 @@
if (compact_) {
if (row >= bit_vector_.size())
bit_vector_.Resize(row + 1, false);
- bit_vector_.Set(row, true);
+ bit_vector_.Set(row);
} else {
index_vector_.emplace_back(row);
}
@@ -98,17 +104,24 @@
// this : [0, 10, 11, 11, 4]
//
// Conceptually, we are performing the following algorithm:
+ // RowMap rm = Copy()
// for (idx : picker)
- // this[i++] = this[idx]
- void SelectRows(const RowMap& picker);
+ // rm[i++] = this[idx]
+ // return rm;
+ RowMap SelectRows(const RowMap& picker) const;
// Removes any row where |p(row)| returns false from this RowMap.
template <typename Predicate>
void RemoveIf(Predicate p) {
if (compact_) {
const auto& bv = bit_vector_;
- for (uint32_t i = bv.NextSet(0); i < bv.size(); i = bv.NextSet(i + 1)) {
- bit_vector_.Set(i, !p(i));
+ uint32_t removed = 0;
+ for (uint32_t i = 0, size = bv.GetNumBitsSet(); i < size; ++i) {
+ uint32_t idx = bv.IndexOfNthSet(i - removed);
+ if (p(idx)) {
+ removed++;
+ bit_vector_.Clear(idx);
+ }
}
} else {
auto it = std::remove_if(index_vector_.begin(), index_vector_.end(), p);
diff --git a/src/trace_processor/db/row_map_benchmark.cc b/src/trace_processor/db/row_map_benchmark.cc
new file mode 100644
index 0000000..89f41a7
--- /dev/null
+++ b/src/trace_processor/db/row_map_benchmark.cc
@@ -0,0 +1,158 @@
+// 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.
+
+#include <random>
+
+#include <benchmark/benchmark.h>
+
+#include "src/trace_processor/db/row_map.h"
+
+using perfetto::trace_processor::BitVector;
+using perfetto::trace_processor::RowMap;
+
+namespace {
+
+static constexpr uint32_t kPoolSize = 100000;
+static constexpr uint32_t kSize = 123456;
+
+std::vector<uint32_t> CreateRandomIndexVector(uint32_t size, uint32_t mod) {
+ static constexpr uint32_t kRandomSeed = 476;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ std::vector<uint32_t> rows(size);
+ for (uint32_t i = 0; i < size; ++i) {
+ rows[i] = rnd_engine() % mod;
+ }
+ return rows;
+}
+
+BitVector CreateRandomBitVector(uint32_t size) {
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ }
+ return bv;
+}
+
+} // namespace
+
+static void BM_RowMapBitVectorGet(benchmark::State& state) {
+ RowMap rm(CreateRandomBitVector(kSize));
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, rm.size());
+
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.Get(pool_vec[pool_idx]));
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ }
+}
+BENCHMARK(BM_RowMapBitVectorGet);
+
+static void BM_RowMapIndexVectorGet(benchmark::State& state) {
+ RowMap rm(CreateRandomIndexVector(kSize, kSize));
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.Get(pool_vec[pool_idx]));
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ }
+}
+BENCHMARK(BM_RowMapIndexVectorGet);
+
+// TODO(lalitm): add benchmarks for IndexOf after BitVector is made faster.
+// We can't add them right now because they are just too slow to run.
+
+static void BM_RowMapBitVectorAdd(benchmark::State& state) {
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+ RowMap rm(BitVector{});
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ rm.Add(pool_vec[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_RowMapBitVectorAdd);
+
+static void BM_RowMapIndexVectorAdd(benchmark::State& state) {
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+ RowMap rm(std::vector<uint32_t>{});
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ rm.Add(pool_vec[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_RowMapIndexVectorAdd);
+
+static void BM_RowMapBvSelectBv(benchmark::State& state) {
+ RowMap rm(CreateRandomBitVector(kSize));
+ RowMap selector(CreateRandomBitVector(rm.size()));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapBvSelectBv);
+
+// TODO(lalitm): add benchmarks for BvSelectIv after BitVector is made faster.
+// We can't add them right now because they are just too slow to run.
+
+static void BM_RowMapIvSelectBv(benchmark::State& state) {
+ RowMap rm(CreateRandomIndexVector(kSize, kSize));
+ RowMap selector(CreateRandomBitVector(rm.size()));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapIvSelectBv);
+
+static void BM_RowMapIvSelectIv(benchmark::State& state) {
+ RowMap rm(CreateRandomIndexVector(kSize, kSize));
+ RowMap selector(CreateRandomIndexVector(rm.size(), rm.size()));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapIvSelectIv);
+
+static void BM_RowMapBvSelectSingleRow(benchmark::State& state) {
+ // This benchmark tests the performance of selecting just a single
+ // row of a RowMap. We specially test this case as it occurs on every join
+ // based on id originating from SQLite; nested subqueries will be performed
+ // on the id column and will select just a single row.
+ RowMap rm(CreateRandomBitVector(kSize));
+
+ static constexpr uint32_t kRandomSeed = 123;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ BitVector bv(rm.size(), false);
+ bv.Set(rnd_engine() % bv.size());
+ RowMap selector(std::move(bv));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapBvSelectSingleRow);
diff --git a/src/trace_processor/db/row_map_unittest.cc b/src/trace_processor/db/row_map_unittest.cc
index 0556e9b..4c62c79 100644
--- a/src/trace_processor/db/row_map_unittest.cc
+++ b/src/trace_processor/db/row_map_unittest.cc
@@ -27,12 +27,12 @@
std::shared_ptr<RowMap> BitVectorRowMap() {
BitVector bv;
- bv.Append(true);
- bv.Append(false);
- bv.Append(true);
- bv.Append(true);
- bv.Append(false);
- bv.Append(true);
+ bv.AppendTrue();
+ bv.AppendFalse();
+ bv.AppendTrue();
+ bv.AppendTrue();
+ bv.AppendFalse();
+ bv.AppendTrue();
return std::shared_ptr<RowMap>(new RowMap(std::move(bv)));
}
@@ -64,37 +64,38 @@
ASSERT_EQ(row_map.IndexOf(3u), 2u);
ASSERT_EQ(row_map.IndexOf(5u), 3u);
ASSERT_EQ(row_map.IndexOf(10u), 4u);
+ ASSERT_FALSE(row_map.IndexOf(6u));
}
TEST_P(RowMapUnittest, SelectRowsBitVector) {
RowMap row_map = GetParam()->Copy();
BitVector picker_bv;
- picker_bv.Append(true);
- picker_bv.Append(false);
- picker_bv.Append(false);
- picker_bv.Append(true);
+ picker_bv.AppendTrue();
+ picker_bv.AppendFalse();
+ picker_bv.AppendFalse();
+ picker_bv.AppendTrue();
RowMap picker(std::move(picker_bv));
- row_map.SelectRows(picker);
+ auto res = row_map.SelectRows(picker);
- ASSERT_EQ(row_map.size(), 2u);
- ASSERT_EQ(row_map.Get(0u), 0u);
- ASSERT_EQ(row_map.Get(1u), 5u);
+ ASSERT_EQ(res.size(), 2u);
+ ASSERT_EQ(res.Get(0u), 0u);
+ ASSERT_EQ(res.Get(1u), 5u);
}
TEST_P(RowMapUnittest, SelectRowsRowVector) {
RowMap row_map = GetParam()->Copy();
RowMap picker(std::vector<uint32_t>{1u, 0u, 3u, 0u, 0u});
- row_map.SelectRows(picker);
+ auto res = row_map.SelectRows(picker);
- ASSERT_EQ(row_map.size(), 5u);
- ASSERT_EQ(row_map.Get(0u), 2u);
- ASSERT_EQ(row_map.Get(1u), 0u);
- ASSERT_EQ(row_map.Get(2u), 5u);
- ASSERT_EQ(row_map.Get(3u), 0u);
- ASSERT_EQ(row_map.Get(4u), 0u);
+ ASSERT_EQ(res.size(), 5u);
+ ASSERT_EQ(res.Get(0u), 2u);
+ ASSERT_EQ(res.Get(1u), 0u);
+ ASSERT_EQ(res.Get(2u), 5u);
+ ASSERT_EQ(res.Get(3u), 0u);
+ ASSERT_EQ(res.Get(4u), 0u);
}
TEST_P(RowMapUnittest, RemoveIf) {
diff --git a/src/trace_processor/db/sparse_vector.h b/src/trace_processor/db/sparse_vector.h
index 12a2a60..c2472ac 100644
--- a/src/trace_processor/db/sparse_vector.h
+++ b/src/trace_processor/db/sparse_vector.h
@@ -51,11 +51,11 @@
// Adds the given value to the SparseVector.
void Append(T val) {
data_.emplace_back(val);
- valid_.Append(true);
+ valid_.AppendTrue();
}
// Adds a null value to the SparseVector.
- void AppendNull() { valid_.Append(false); }
+ void AppendNull() { valid_.AppendFalse(); }
// Adds the given optional value to the SparseVector.
void Append(base::Optional<T> val) {
@@ -76,7 +76,7 @@
data_[data_idx] = val;
} else {
data_.insert(data_.begin() + static_cast<ptrdiff_t>(data_idx), val);
- valid_.Set(idx, true);
+ valid_.Set(idx);
}
}
diff --git a/src/trace_processor/db/sparse_vector_benchmark.cc b/src/trace_processor/db/sparse_vector_benchmark.cc
new file mode 100644
index 0000000..f9403dc
--- /dev/null
+++ b/src/trace_processor/db/sparse_vector_benchmark.cc
@@ -0,0 +1,39 @@
+// 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.
+
+#include <random>
+
+#include <benchmark/benchmark.h>
+
+#include "src/trace_processor/db/sparse_vector.h"
+
+static void BM_SparseVectorAppend(benchmark::State& state) {
+ static constexpr uint32_t kPoolSize = 123456;
+ std::vector<uint8_t> data_pool(kPoolSize);
+
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ for (uint32_t i = 0; i < kPoolSize; ++i) {
+ data_pool[i] = rnd_engine() % std::numeric_limits<uint8_t>::max();
+ }
+
+ perfetto::trace_processor::SparseVector<uint8_t> sv;
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ sv.Append(data_pool[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_SparseVectorAppend);
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index 1bafb4f..90afc19 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -31,18 +31,6 @@
columns_.emplace_back(col, this, columns_.size(), col.row_map_idx_);
}
-Table& Table::operator=(const Table& other) {
- size_ = other.size_;
- string_pool_ = other.string_pool_;
-
- for (const RowMap& rm : other.row_maps_) {
- row_maps_.emplace_back(rm.Copy());
- }
- for (const Column& col : other.columns_)
- columns_.emplace_back(col, this, columns_.size(), col.row_map_idx_);
- return *this;
-}
-
Table& Table::operator=(Table&& other) noexcept {
size_ = other.size_;
string_pool_ = other.string_pool_;
@@ -55,12 +43,29 @@
return *this;
}
+Table Table::Copy() const {
+ Table table = CopyExceptRowMaps();
+ for (const RowMap& rm : row_maps_) {
+ table.row_maps_.emplace_back(rm.Copy());
+ }
+ return table;
+}
+
+Table Table::CopyExceptRowMaps() const {
+ Table table(string_pool_, nullptr);
+ table.size_ = size_;
+ for (const Column& col : columns_) {
+ table.columns_.emplace_back(col, &table, col.col_idx_, col.row_map_idx_);
+ }
+ return table;
+}
+
Table Table::Filter(const std::vector<Constraint>& cs) const {
// TODO(lalitm): we can add optimizations here depending on whether this is
// an lvalue or rvalue.
if (cs.empty())
- return *this;
+ return Copy();
// Create a RowMap indexing all rows and filter this down to the rows which
// meet all the constraints.
@@ -71,11 +76,12 @@
// Return a copy of this table with the RowMaps using the computed filter
// RowMap and with the updated size.
- Table table(*this);
- for (RowMap& map : table.row_maps_) {
- map.SelectRows(rm);
- }
+ Table table = CopyExceptRowMaps();
table.size_ = rm.size();
+ for (const RowMap& map : row_maps_) {
+ table.row_maps_.emplace_back(map.SelectRows(rm));
+ PERFETTO_DCHECK(table.row_maps_.back().size() == table.size());
+ }
return table;
}
@@ -84,7 +90,7 @@
// an lvalue or rvalue.
if (od.empty())
- return *this;
+ return Copy();
// Build an index vector with all the indices for the first |size_| rows.
std::vector<uint32_t> idx(size_);
@@ -104,11 +110,11 @@
// Return a copy of this table with the RowMaps using the computed ordered
// RowMap.
+ Table table = CopyExceptRowMaps();
RowMap rm(std::move(idx));
- Table table(*this);
- for (RowMap& map : table.row_maps_) {
- map.SelectRows(rm);
- PERFETTO_DCHECK(map.size() == table.size());
+ for (const RowMap& map : row_maps_) {
+ table.row_maps_.emplace_back(map.SelectRows(rm));
+ PERFETTO_DCHECK(table.row_maps_.back().size() == table.size());
}
return table;
}
@@ -124,7 +130,7 @@
for (const Column& col : columns_) {
// We skip id columns as they are misleading on join tables.
- if (strcmp(col.name_, "id") == 0)
+ if ((col.flags_ & Column::kId) != 0)
continue;
table.columns_.emplace_back(col, &table, table.columns_.size(),
col.row_map_idx_);
@@ -148,14 +154,13 @@
// join table as we go.
RowMap rm(std::move(indices));
for (const RowMap& ot : other.row_maps_) {
- table.row_maps_.emplace_back(ot.Copy());
- table.row_maps_.back().SelectRows(rm);
+ table.row_maps_.emplace_back(ot.SelectRows(rm));
}
uint32_t left_row_maps_size = static_cast<uint32_t>(row_maps_.size());
for (const Column& col : other.columns_) {
// We skip id columns as they are misleading on join tables.
- if (strcmp(col.name_, "id") == 0)
+ if ((col.flags_ & Column::kId) != 0)
continue;
// Ensure that we offset the RowMap index by the number of RowMaps in the
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index 51ad822..f2ecc8b 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -115,10 +115,8 @@
private:
friend class Column;
- // We explicitly define the copy constructor here because we need to change
- // the Table pointer in each column to the Table being copied into.
- Table(const Table& other) { *this = other; }
- Table& operator=(const Table& other);
+ Table Copy() const;
+ Table CopyExceptRowMaps() const;
};
} // namespace trace_processor
diff --git a/src/trace_processor/event_tracker.cc b/src/trace_processor/event_tracker.cc
index 8b30809..79757a9 100644
--- a/src/trace_processor/event_tracker.cc
+++ b/src/trace_processor/event_tracker.cc
@@ -66,33 +66,20 @@
prev_timestamp_ = ts;
PERFETTO_DCHECK(cpu < base::kMaxCpus);
- auto* slices = context_->storage->mutable_slices();
-
StringId next_comm_id = context_->storage->InternString(next_comm);
auto next_utid =
context_->process_tracker->UpdateThreadName(next_pid, next_comm_id);
// First use this data to close the previous slice.
bool prev_pid_match_prev_next_pid = false;
- auto* prev_slice = &pending_sched_per_cpu_[cpu];
- size_t slice_idx = prev_slice->storage_index;
- if (slice_idx < std::numeric_limits<size_t>::max()) {
- prev_pid_match_prev_next_pid = prev_pid == prev_slice->next_pid;
+ auto* pending_sched = &pending_sched_per_cpu_[cpu];
+ size_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
+ if (pending_slice_idx < std::numeric_limits<size_t>::max()) {
+ prev_pid_match_prev_next_pid = prev_pid == pending_sched->last_pid;
if (PERFETTO_LIKELY(prev_pid_match_prev_next_pid)) {
- int64_t duration = ts - slices->start_ns()[slice_idx];
- slices->set_duration(slice_idx, duration);
-
- // We store the state as a uint16 as we only consider values up to 2048
- // when unpacking the information inside; this allows savings of 48 bits
- // per slice.
- auto task_state =
- ftrace_utils::TaskState(static_cast<uint16_t>(prev_state));
- if (!task_state.is_valid()) {
- context_->storage->IncrementStats(stats::task_state_invalid);
- }
- slices->set_end_state(slice_idx, task_state);
+ ClosePendingSlice(pending_slice_idx, ts, prev_state);
} else {
- // If the pids ae not consistent, make a note of this.
+ // If the pids are not consistent, make a note of this.
context_->storage->IncrementStats(stats::mismatched_sched_switch_tids);
}
}
@@ -104,6 +91,92 @@
UniqueTid prev_utid =
context_->process_tracker->UpdateThreadName(prev_pid, prev_comm_id);
+ auto new_slice_idx = AddRawEventAndStartSlice(
+ cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio, prev_state,
+ next_utid, next_pid, next_comm_id, next_prio);
+
+ // Finally, update the info for the next sched switch on this CPU.
+ pending_sched->pending_slice_storage_idx = new_slice_idx;
+ pending_sched->last_pid = next_pid;
+ pending_sched->last_utid = next_utid;
+ pending_sched->last_prio = next_prio;
+}
+
+void EventTracker::PushSchedSwitchCompact(uint32_t cpu,
+ int64_t ts,
+ int64_t prev_state,
+ uint32_t next_pid,
+ int32_t next_prio,
+ StringId next_comm_id) {
+ // At this stage all events should be globally timestamp ordered.
+ if (ts < prev_timestamp_) {
+ PERFETTO_ELOG("sched_switch event out of order by %.4f ms, skipping",
+ (prev_timestamp_ - ts) / 1e6);
+ context_->storage->IncrementStats(stats::sched_switch_out_of_order);
+ return;
+ }
+ prev_timestamp_ = ts;
+ PERFETTO_DCHECK(cpu < base::kMaxCpus);
+
+ auto next_utid =
+ context_->process_tracker->UpdateThreadName(next_pid, next_comm_id);
+
+ auto* pending_sched = &pending_sched_per_cpu_[cpu];
+
+ // If we're processing the first compact event for this cpu, don't start a
+ // slice since we're missing the "prev_*" fields. The successive events will
+ // create slices as normal, but the first per-cpu switch is effectively
+ // discarded.
+ if (pending_sched->last_utid == std::numeric_limits<UniqueTid>::max()) {
+ pending_sched->last_pid = next_pid;
+ pending_sched->last_utid = next_utid;
+ pending_sched->last_prio = next_prio;
+ // Note: no pending slice, so leave |pending_slice_storage_idx| in its
+ // invalid state.
+ return;
+ }
+
+ // Close the pending slice if any (we won't have one when processing the first
+ // two compact events for a given cpu).
+ size_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
+ if (pending_slice_idx < std::numeric_limits<size_t>::max())
+ ClosePendingSlice(pending_slice_idx, ts, prev_state);
+
+ // Use the previous event's values to infer this event's "prev_*" fields.
+ // There are edge cases, but this assumption should still produce sensible
+ // results in the absence of data loss.
+ UniqueTid prev_utid = pending_sched->last_utid;
+ uint32_t prev_pid = pending_sched->last_pid;
+ int32_t prev_prio = pending_sched->last_prio;
+
+ // Do a fresh task name lookup in case it was updated by a task_rename while
+ // scheduled.
+ const auto& prev_thread = context_->storage->GetThread(prev_utid);
+ StringId prev_comm_id = prev_thread.name_id;
+
+ auto new_slice_idx = AddRawEventAndStartSlice(
+ cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio, prev_state,
+ next_utid, next_pid, next_comm_id, next_prio);
+
+ // Finally, update the info for the next sched switch on this CPU.
+ pending_sched->pending_slice_storage_idx = new_slice_idx;
+ pending_sched->last_pid = next_pid;
+ pending_sched->last_utid = next_utid;
+ pending_sched->last_prio = next_prio;
+}
+
+PERFETTO_ALWAYS_INLINE
+size_t EventTracker::AddRawEventAndStartSlice(uint32_t cpu,
+ int64_t ts,
+ UniqueTid prev_utid,
+ uint32_t prev_pid,
+ StringId prev_comm_id,
+ int32_t prev_prio,
+ int64_t prev_state,
+ UniqueTid next_utid,
+ uint32_t next_pid,
+ StringId next_comm_id,
+ int32_t next_prio) {
// Push the raw event - this is done as the raw ftrace event codepath does
// not insert sched_switch.
auto rid = context_->storage->mutable_raw_events()->AddRawEvent(
@@ -125,13 +198,30 @@
add_raw_arg(rid, SS::kNextPidFieldNumber, Variadic::Integer(next_pid));
add_raw_arg(rid, SS::kNextPrioFieldNumber, Variadic::Integer(next_prio));
- // Add the slice for the "next" slice.
- auto next_idx = slices->AddSlice(cpu, ts, 0 /* duration */, next_utid,
- ftrace_utils::TaskState(), next_prio);
+ // Open a new scheduling slice, corresponding to the task that was
+ // just switched to.
+ return context_->storage->mutable_slices()->AddSlice(
+ cpu, ts, 0 /* duration */, next_utid, ftrace_utils::TaskState(),
+ next_prio);
+}
- // Finally, update the info for the next sched switch on this CPU.
- prev_slice->storage_index = next_idx;
- prev_slice->next_pid = next_pid;
+PERFETTO_ALWAYS_INLINE
+void EventTracker::ClosePendingSlice(size_t pending_slice_idx,
+ int64_t ts,
+ int64_t prev_state) {
+ auto* slices = context_->storage->mutable_slices();
+
+ int64_t duration = ts - slices->start_ns()[pending_slice_idx];
+ slices->set_duration(pending_slice_idx, duration);
+
+ // We store the state as a uint16 as we only consider values up to 2048
+ // when unpacking the information inside; this allows savings of 48 bits
+ // per slice.
+ auto task_state = ftrace_utils::TaskState(static_cast<uint16_t>(prev_state));
+ if (!task_state.is_valid()) {
+ context_->storage->IncrementStats(stats::task_state_invalid);
+ }
+ slices->set_end_state(pending_slice_idx, task_state);
}
RowId EventTracker::PushCounter(int64_t timestamp,
@@ -208,7 +298,7 @@
int64_t end_ts = context_->storage->GetTraceTimestampBoundsNs().second;
auto* slices = context_->storage->mutable_slices();
for (const auto& pending_sched : pending_sched_per_cpu_) {
- size_t row = pending_sched.storage_index;
+ size_t row = pending_sched.pending_slice_storage_idx;
if (row == std::numeric_limits<size_t>::max())
continue;
diff --git a/src/trace_processor/event_tracker.h b/src/trace_processor/event_tracker.h
index 8c56e6d..c743263 100644
--- a/src/trace_processor/event_tracker.h
+++ b/src/trace_processor/event_tracker.h
@@ -38,7 +38,7 @@
EventTracker& operator=(const EventTracker&) = delete;
virtual ~EventTracker();
- // This method is called when a sched switch event is seen in the trace.
+ // This method is called when a sched_switch event is seen in the trace.
virtual void PushSchedSwitch(uint32_t cpu,
int64_t timestamp,
uint32_t prev_pid,
@@ -49,6 +49,15 @@
base::StringView next_comm,
int32_t next_prio);
+ // This method is called when parsing a sched_switch encoded in the compact
+ // format.
+ void PushSchedSwitchCompact(uint32_t cpu,
+ int64_t ts,
+ int64_t prev_state,
+ uint32_t next_pid,
+ int32_t next_prio,
+ StringId next_comm_id);
+
// This method is called when a counter event is seen in the trace.
virtual RowId PushCounter(int64_t timestamp,
double value,
@@ -75,10 +84,18 @@
void FlushPendingEvents();
private:
- // Represents a slice which is currently pending.
- struct PendingSchedSlice {
- size_t storage_index = std::numeric_limits<size_t>::max();
- uint32_t next_pid = 0;
+ // Infromation retained from the preceding sched_switch seen on a given cpu.
+ struct PendingSchedInfo {
+ // The pending scheduling slice that the next event will complete.
+ size_t pending_slice_storage_idx = std::numeric_limits<size_t>::max();
+
+ // pid/utid/prio corresponding to the last sched_switch seen on this cpu
+ // (its "next_*" fields). There is some duplication with respect to the
+ // slices storage, but we don't always have a slice when decoding events in
+ // the compact format.
+ uint32_t last_pid = std::numeric_limits<uint32_t>::max();
+ UniqueTid last_utid = std::numeric_limits<UniqueTid>::max();
+ int32_t last_prio = std::numeric_limits<int32_t>::max();
};
// Represents a counter event which is currently pending upid resolution.
@@ -94,8 +111,22 @@
UniqueTid utid = 0;
};
- // Store pending sched slices for each CPU.
- std::array<PendingSchedSlice, base::kMaxCpus> pending_sched_per_cpu_{};
+ size_t AddRawEventAndStartSlice(uint32_t cpu,
+ int64_t ts,
+ UniqueTid prev_utid,
+ uint32_t prev_pid,
+ StringId prev_comm_id,
+ int32_t prev_prio,
+ int64_t prev_state,
+ UniqueTid next_utid,
+ uint32_t next_pid,
+ StringId next_comm_id,
+ int32_t next_prio);
+
+ void ClosePendingSlice(size_t slice_idx, int64_t ts, int64_t prev_state);
+
+ // Infromation retained from the preceding sched_switch seen on a given cpu.
+ std::array<PendingSchedInfo, base::kMaxCpus> pending_sched_per_cpu_{};
// Store the rows in the counters table which need upids resolved.
std::vector<PendingUpidResolutionCounter> pending_upid_resolution_counter_;
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index bc3f3ef..2ff1f6e 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include <inttypes.h>
#include <json/reader.h>
#include <json/value.h>
@@ -292,14 +295,18 @@
}
if (slices.types()[i] == RefType::kRefTrack) { // Async event.
- const auto& async_track = storage->chrome_async_track_table();
TrackId track_id = static_cast<TrackId>(slices.refs()[i]);
- uint32_t async_row = *async_track.id().IndexOf(SqlValue::Long(track_id));
- auto opt_upid = storage->chrome_async_track_table().upid()[async_row];
- if (opt_upid.has_value()) {
+ // TODO(lalitm): add a check here for looking up source_arg_set_id
+ // and checking that this track originated from Chrome.
+
+ const auto& process_track = storage->process_track_table();
+ auto opt_process_row =
+ process_track.id().IndexOf(SqlValue::Long(track_id));
+ if (opt_process_row.has_value()) {
+ uint32_t upid = process_track.upid()[*opt_process_row];
event["id2"]["local"] = PrintUint64(track_id);
- event["pid"] = storage->GetProcess(*opt_upid).pid;
+ event["pid"] = storage->GetProcess(upid).pid;
} else {
event["id2"]["global"] = PrintUint64(track_id);
}
@@ -844,3 +851,5 @@
} // namespace json
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 3afe4cc..5e9cd14 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -67,13 +67,15 @@
const int64_t kThreadID = 100;
const char* kCategory = "cat";
const char* kName = "name";
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(kThreadID);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(
- kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track,
+ utid, RefType::kRefUtid, cat_id,
+ name_id, 0, 0, 0);
storage.mutable_thread_slices()->AddThreadSlice(
0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
kThreadInstructionDelta);
@@ -112,13 +114,15 @@
const int64_t kThreadID = 100;
const char* kCategory = "cat";
const char* kName = "name";
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(kThreadID);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(
- kTimestamp, kDuration, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track,
+ utid, RefType::kRefUtid, cat_id,
+ name_id, 0, 0, 0);
storage.mutable_thread_slices()->AddThreadSlice(
0, kThreadTimestamp, kThreadDuration, kThreadInstructionCount,
kThreadInstructionDelta);
@@ -176,11 +180,13 @@
}
TEST(ExportJsonTest, WrongRefType) {
+ constexpr TrackId track = 22;
+
TraceStorage storage;
StringId cat_id = storage.InternString("cat");
StringId name_id = storage.InternString("name");
- storage.mutable_nestable_slices()->AddSlice(0, 0, 0, RefType::kRefCpuId,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, 0, RefType::kRefCpuId, cat_id, name_id, 0, 0, 0);
base::TempFile temp_file = base::TempFile::Create();
FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -339,13 +345,14 @@
const char* kCategory = "cat";
const char* kName = "name";
const char* kSrc = "source_file.cc";
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(0);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
StringId arg_key_id =
storage.InternString(base::StringView("task.posted_from.file_name"));
@@ -378,13 +385,14 @@
const char* kCategory = "cat";
const char* kName = "name";
double kValues[] = {1.234, 2.345};
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(0);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
StringId arg_flat_key_id =
storage.InternString(base::StringView("debug.draw_duration_ms"));
@@ -427,13 +435,14 @@
const char* kName = "name";
uint64_t kValue0 = 1;
uint64_t kValue1 = std::numeric_limits<uint64_t>::max();
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(0);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
StringId arg_key0_id = storage.InternString(base::StringView("arg0"));
StringId arg_key1_id = storage.InternString(base::StringView("arg1"));
@@ -470,13 +479,14 @@
const char* kCategory = "cat";
const char* kName = "name";
int kValues[] = {123, 234};
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(0);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
StringId arg_flat_key_id = storage.InternString(base::StringView("a.b"));
StringId arg_key0_id = storage.InternString(base::StringView("a[0].b"));
@@ -515,13 +525,14 @@
const char* kCategory = "cat";
const char* kName = "name";
int kValues[] = {123, 234};
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(0);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
StringId arg_flat_key_id = storage.InternString(base::StringView("a"));
StringId arg_key0_id = storage.InternString(base::StringView("a[0][0]"));
@@ -560,13 +571,14 @@
TEST(ExportJsonTest, StorageWithLegacyJsonArgs) {
const char* kCategory = "cat";
const char* kName = "name";
+ constexpr TrackId track = 22;
TraceStorage storage;
UniqueTid utid = storage.AddEmptyThread(0);
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- storage.mutable_nestable_slices()->AddSlice(0, 0, utid, RefType::kRefUtid,
- cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(
+ 0, 0, track, utid, RefType::kRefUtid, cat_id, name_id, 0, 0, 0);
StringId arg_key_id = storage.InternString(base::StringView("a"));
StringId arg_value_id = storage.InternString(base::StringView("{\"b\":123}"));
@@ -598,12 +610,13 @@
const int64_t kTimestamp = 10000000;
const char* kCategory = "cat";
const char* kName = "name";
+ constexpr TrackId track = 22;
TraceStorage storage;
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
storage.mutable_nestable_slices()->AddSlice(
- kTimestamp, 0, 0, RefType::kRefNoRef, cat_id, name_id, 0, 0, 0);
+ kTimestamp, 0, track, 0, RefType::kRefNoRef, cat_id, name_id, 0, 0, 0);
base::TempFile temp_file = base::TempFile::Create();
FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -638,13 +651,13 @@
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- tables::ChromeAsyncTrackTable::Row track(name_id);
+ tables::ProcessTrackTable::Row track(name_id);
track.upid = upid;
- TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+ TrackId track_id = storage.mutable_process_track_table()->Insert(track);
storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track_id,
- RefType::kRefTrack, cat_id,
- name_id, 0, 0, 0);
+ track_id, RefType::kRefTrack,
+ cat_id, name_id, 0, 0, 0);
StringId arg_key_id = storage.InternString(base::StringView(kArgName));
TraceStorage::Args::Arg arg;
arg.flat_key = arg_key_id;
@@ -701,13 +714,13 @@
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- tables::ChromeAsyncTrackTable::Row track(name_id);
+ tables::ProcessTrackTable::Row track(name_id);
track.upid = upid;
- TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+ TrackId track_id = storage.mutable_process_track_table()->Insert(track);
auto slice_id = storage.mutable_nestable_slices()->AddSlice(
- kTimestamp, kDuration, track_id, RefType::kRefTrack, cat_id, name_id, 0,
- 0, 0);
+ kTimestamp, kDuration, track_id, track_id, RefType::kRefTrack, cat_id,
+ name_id, 0, 0, 0);
storage.mutable_virtual_track_slices()->AddVirtualTrackSlice(
slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
@@ -758,13 +771,13 @@
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- tables::ChromeAsyncTrackTable::Row track(name_id);
+ tables::ProcessTrackTable::Row track(name_id);
track.upid = upid;
- TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+ TrackId track_id = storage.mutable_process_track_table()->Insert(track);
auto slice_id = storage.mutable_nestable_slices()->AddSlice(
- kTimestamp, kDuration, track_id, RefType::kRefTrack, cat_id, name_id, 0,
- 0, 0);
+ kTimestamp, kDuration, track_id, track_id, RefType::kRefTrack, cat_id,
+ name_id, 0, 0, 0);
storage.mutable_virtual_track_slices()->AddVirtualTrackSlice(
slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
@@ -803,12 +816,13 @@
StringId cat_id = storage.InternString(base::StringView(kCategory));
StringId name_id = storage.InternString(base::StringView(kName));
- tables::ChromeAsyncTrackTable::Row track(name_id);
+ tables::ProcessTrackTable::Row track(name_id);
track.upid = upid;
- TrackId track_id = storage.mutable_chrome_async_track_table()->Insert(track);
+ TrackId track_id = storage.mutable_process_track_table()->Insert(track);
- storage.mutable_nestable_slices()->AddSlice(
- kTimestamp, 0, track_id, RefType::kRefTrack, cat_id, name_id, 0, 0, 0);
+ storage.mutable_nestable_slices()->AddSlice(kTimestamp, 0, track_id, track_id,
+ RefType::kRefTrack, cat_id,
+ name_id, 0, 0, 0);
StringId arg_key_id = storage.InternString(base::StringView("arg_name"));
TraceStorage::Args::Arg arg;
arg.flat_key = arg_key_id;
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index b3aced7..5f6a99f 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -143,14 +143,14 @@
base::StartsWith(start, "<html>"))
return kSystraceTraceType;
+ // Ctrace is deflate'ed systrace.
+ if (start.find("TRACE:") != std::string::npos)
+ return kCtraceTraceType;
+
// Systrace with no header or leading HTML.
if (base::StartsWith(start, " "))
return kSystraceTraceType;
- // Ctrace is deflate'ed systrace.
- if (base::StartsWith(start, "TRACE:"))
- return kCtraceTraceType;
-
// gzip'ed trace containing one of the other formats.
if (base::StartsWith(start, "\x1f\x8b"))
return kGzipTraceType;
diff --git a/src/trace_processor/ftrace_utils.cc b/src/trace_processor/ftrace_utils.cc
index 081ba94..b58fd73 100644
--- a/src/trace_processor/ftrace_utils.cc
+++ b/src/trace_processor/ftrace_utils.cc
@@ -97,6 +97,8 @@
state_ |= Atom::kParked;
else if (c == 'N')
state_ |= Atom::kNoLoad;
+ else if (c == '|')
+ continue;
else {
invalid_char = true;
break;
@@ -202,10 +204,13 @@
}
int64_t padding = 16 - static_cast<int64_t>(name.size());
- if (PERFETTO_LIKELY(padding > 0)) {
+ if (padding > 0) {
writer->AppendChar(' ', static_cast<size_t>(padding));
}
- writer->AppendString(name);
+ for (size_t i = 0; i < name.size(); ++i) {
+ char c = name.data()[i];
+ writer->AppendChar(c == '-' ? '_' : c);
+ }
writer->AppendChar('-');
size_t pre_pid_pos = writer->pos();
diff --git a/src/trace_processor/ftrace_utils_unittest.cc b/src/trace_processor/ftrace_utils_unittest.cc
index dbfe0e4..3675fb2 100644
--- a/src/trace_processor/ftrace_utils_unittest.cc
+++ b/src/trace_processor/ftrace_utils_unittest.cc
@@ -53,6 +53,9 @@
ASSERT_STREQ(TaskState(4096).ToString().data(), "R+");
ASSERT_STREQ(TaskState(130).ToString().data(), "DK");
ASSERT_STREQ(TaskState(258).ToString().data(), "DW");
+
+ ASSERT_EQ(TaskState("D|K").raw_state(), 130);
+ ASSERT_EQ(TaskState("D|W").raw_state(), 258);
}
} // namespace
diff --git a/src/trace_processor/fuchsia_trace_parser.cc b/src/trace_processor/fuchsia_trace_parser.cc
index a755bc6..74df7f3 100644
--- a/src/trace_processor/fuchsia_trace_parser.cc
+++ b/src/trace_processor/fuchsia_trace_parser.cc
@@ -257,7 +257,7 @@
ts, counter_value,
context_->storage->InternString(
base::StringView(counter_name_str)),
- utid, kRefUtid);
+ utid, RefType::kRefUtid);
}
}
break;
@@ -266,17 +266,19 @@
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
- slices->Begin(ts, utid, RefType::kRefUtid, cat, name);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ slices->Begin(ts, track_id, utid, RefType::kRefUtid, cat, name);
break;
}
case kDurationEnd: {
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
// TODO(b/131181693): |cat| and |name| are not passed here so that
// if two slices end at the same timestep, the slices get closed in
// the correct order regardless of which end event is processed first.
- slices->End(ts, utid, RefType::kRefUtid);
+ slices->End(ts, track_id);
break;
}
case kDurationComplete: {
@@ -285,27 +287,25 @@
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
- slices->Scoped(ts, utid, RefType::kRefUtid, cat, name, end_ts - ts);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ slices->Scoped(ts, track_id, utid, RefType::kRefUtid, cat, name,
+ end_ts - ts);
break;
}
case kAsyncBegin: {
- tables::FuchsiaAsyncTrackTable::Row track(name);
- track.correlation_id = static_cast<int64_t>(*current++);
-
- TrackId track_id =
- context_->track_tracker->InternFuchsiaAsyncTrack(track);
- slices->Begin(ts, track_id, RefType::kRefTrack, cat, name);
+ int64_t correlation_id = static_cast<int64_t>(*current++);
+ TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
+ name, correlation_id);
+ slices->Begin(ts, track_id, track_id, RefType::kRefTrack, cat, name);
break;
}
case kAsyncInstant: {
// TODO(eseckler): Consider storing these instants as 0-duration
// slices instead, so that they get nested underneath begin/end
// slices.
- tables::FuchsiaAsyncTrackTable::Row track(name);
- track.correlation_id = static_cast<int64_t>(*current++);
-
- TrackId track_id =
- context_->track_tracker->InternFuchsiaAsyncTrack(track);
+ int64_t correlation_id = static_cast<int64_t>(*current++);
+ TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
+ name, correlation_id);
RowId row = context_->event_tracker->PushInstant(
ts, name, 0, track_id, RefType::kRefTrack);
for (const Arg& arg : args) {
@@ -317,12 +317,10 @@
break;
}
case kAsyncEnd: {
- tables::FuchsiaAsyncTrackTable::Row track(name);
- track.correlation_id = static_cast<int64_t>(*current++);
-
- TrackId track_id =
- context_->track_tracker->InternFuchsiaAsyncTrack(track);
- slices->End(ts, track_id, RefType::kRefTrack, cat, name);
+ int64_t correlation_id = static_cast<int64_t>(*current++);
+ TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
+ name, correlation_id);
+ slices->End(ts, track_id, cat, name);
break;
}
}
diff --git a/src/trace_processor/graphics_event_parser.cc b/src/trace_processor/graphics_event_parser.cc
index 6044d2f..f6b8eae 100644
--- a/src/trace_processor/graphics_event_parser.cc
+++ b/src/trace_processor/graphics_event_parser.cc
@@ -192,9 +192,10 @@
snprintf(buffer, 64, "render stage(%zu)", stage_id);
stage_name = context_->storage->InternString(buffer);
}
+ TrackId track_id =
+ gpu_hw_queue_ids_[static_cast<size_t>(event.hw_queue_id())];
const auto slice_id = context_->slice_tracker->Scoped(
- ts, gpu_hw_queue_ids_[static_cast<size_t>(event.hw_queue_id())],
- RefType::kRefTrack, 0 /* cat */, stage_name,
+ ts, track_id, track_id, RefType::kRefTrack, 0 /* cat */, stage_name,
static_cast<int64_t>(event.duration()), args_callback);
context_->storage->mutable_gpu_slice_table()->Insert(
@@ -270,8 +271,9 @@
TrackId track_id = context_->track_tracker->InternGpuTrack(track);
const auto slice_id = context_->slice_tracker->Scoped(
- timestamp, track_id, RefType::kRefTrack, 0 /* cat */, event_name_id,
- duration, [this, layer_name_id](ArgsTracker* args_tracker, RowId row_id) {
+ timestamp, track_id, track_id, RefType::kRefTrack, 0 /* cat */,
+ event_name_id, duration,
+ [this, layer_name_id](ArgsTracker* args_tracker, RowId row_id) {
args_tracker->AddArg(row_id, layer_name_key_id_, layer_name_key_id_,
Variadic::String(layer_name_id));
});
diff --git a/src/trace_processor/gzip_trace_parser.cc b/src/trace_processor/gzip_trace_parser.cc
index a374019..4dfce0b 100644
--- a/src/trace_processor/gzip_trace_parser.cc
+++ b/src/trace_processor/gzip_trace_parser.cc
@@ -16,9 +16,13 @@
#include "src/trace_processor/gzip_trace_parser.h"
+#include <string>
+
#include <zlib.h>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/forwarding_trace_parser.h"
namespace perfetto {
@@ -45,13 +49,15 @@
if (!inner_) {
inner_.reset(new ForwardingTraceParser(context_));
- // .ctrace files begin with "TRACE:" strip this if present.
- static const char kSystraceFileHeader[] = "TRACE:\n";
- if (size >= strlen(kSystraceFileHeader) &&
- strncmp(reinterpret_cast<char*>(start), kSystraceFileHeader,
- strlen(kSystraceFileHeader)) == 0) {
- start += strlen(kSystraceFileHeader);
- len -= strlen(kSystraceFileHeader);
+ // .ctrace files begin with: "TRACE:\n" or "done. TRACE:\n" strip this if
+ // present.
+ base::StringView beginning(reinterpret_cast<char*>(start), size);
+
+ static const char* kSystraceFileHeader = "TRACE:\n";
+ size_t offset = Find(kSystraceFileHeader, beginning);
+ if (offset != std::string::npos) {
+ start += strlen(kSystraceFileHeader) + offset;
+ len -= strlen(kSystraceFileHeader) + offset;
}
}
diff --git a/src/trace_processor/json_trace_parser.cc b/src/trace_processor/json_trace_parser.cc
index 5dc2816..54a548f 100644
--- a/src/trace_processor/json_trace_parser.cc
+++ b/src/trace_processor/json_trace_parser.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include "src/trace_processor/json_trace_parser.h"
#include <inttypes.h>
@@ -23,7 +26,6 @@
#include <limits>
#include <string>
-#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/utils.h"
@@ -31,10 +33,7 @@
#include "src/trace_processor/process_tracker.h"
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#error JSON parsing and exporting is not supported in this build configuration
-#endif
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -88,11 +87,14 @@
switch (phase) {
case 'B': { // TRACE_EVENT_BEGIN.
- slice_tracker->Begin(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ slice_tracker->Begin(timestamp, track_id, utid, RefType::kRefUtid, cat_id,
+ name_id);
break;
}
case 'E': { // TRACE_EVENT_END.
- slice_tracker->End(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ slice_tracker->End(timestamp, track_id, cat_id, name_id);
break;
}
case 'X': { // TRACE_EVENT (scoped event).
@@ -100,8 +102,9 @@
json_trace_utils::CoerceToNs(value["dur"]);
if (!opt_dur.has_value())
return;
- slice_tracker->Scoped(timestamp, utid, RefType::kRefUtid, cat_id, name_id,
- opt_dur.value());
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ slice_tracker->Scoped(timestamp, track_id, utid, RefType::kRefUtid,
+ cat_id, name_id, opt_dur.value());
break;
}
case 'M': { // Metadata events (process and thread names).
@@ -124,3 +127,5 @@
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/json_trace_tokenizer.cc b/src/trace_processor/json_trace_tokenizer.cc
index 274d908..120cc69 100644
--- a/src/trace_processor/json_trace_tokenizer.cc
+++ b/src/trace_processor/json_trace_tokenizer.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include "src/trace_processor/json_trace_tokenizer.h"
#include <json/reader.h>
@@ -147,3 +150,5 @@
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/json_trace_utils.cc b/src/trace_processor/json_trace_utils.cc
index 0d6ba70..c4db237 100644
--- a/src/trace_processor/json_trace_utils.cc
+++ b/src/trace_processor/json_trace_utils.cc
@@ -14,17 +14,14 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include "src/trace_processor/json_trace_utils.h"
#include <json/value.h>
#include <limits>
-#include "perfetto/base/build_config.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#error JSON parsing and exporting is not supported in this build configuration
-#endif
-
namespace perfetto {
namespace trace_processor {
namespace json_trace_utils {
@@ -83,3 +80,5 @@
} // namespace json_trace_utils
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql b/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql
index 716b17c..f81b3d3 100644
--- a/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql
+++ b/src/trace_processor/metrics/android/heap_profile_callsite_stats.sql
@@ -37,6 +37,21 @@
SELECT *
FROM callsite_parser;
+-- Join frames with symbols
+CREATE TABLE symbolized_frame AS
+SELECT
+ spf.id AS id,
+ spf.mapping AS mapping,
+ IFNULL(
+ (SELECT name FROM stack_profile_symbol symbol
+ WHERE symbol.symbol_set_id = spf.symbol_set_id
+ LIMIT 1),
+ spf.name
+ ) AS name
+FROM stack_profile_frame spf;
+
+CREATE UNIQUE INDEX symbolized_frame_idx ON symbolized_frame(id);
+
-- Join with the frames table to get the symbol names.
-- Output order for position matters (as will be the order in the subsequent aggregate operations).
-- We use the cross join to force the join order between virtual and non-virtual tables.
@@ -45,16 +60,12 @@
callsite_id,
position,
HeapProfileCallsiteStats_Frame(
- 'name', IFNULL(
- (SELECT name FROM stack_profile_symbol symbol
- WHERE symbol.symbol_set_id = spf.symbol_set_id
- LIMIT 1),
- spf.name),
+ 'name', spf.name,
'mapping_name', stack_profile_mapping.name
) AS frame_proto
FROM flattened_callsite
CROSS JOIN stack_profile_callsite
-CROSS JOIN stack_profile_frame spf
+CROSS JOIN symbolized_frame spf
CROSS JOIN stack_profile_mapping
WHERE
flattened_callsite.current_id = stack_profile_callsite.id
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 408b2fd..2bfc910 100644
--- a/src/trace_processor/proto_incremental_state.h
+++ b/src/trace_processor/proto_incremental_state.h
@@ -82,6 +82,11 @@
} // namespace proto_incremental_state_internal
+struct DefaultFieldName;
+struct BuildIdFieldName;
+struct MappingPathsFieldName;
+struct FunctionNamesFieldName;
+
// Stores per-packet-sequence incremental state during trace parsing, such as
// reference timestamps for delta timestamp calculation and interned messages.
class ProtoIncrementalState {
@@ -115,26 +120,26 @@
class PacketSequenceState {
public:
int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
- PERFETTO_DCHECK(IsTrackEventStateValid());
+ PERFETTO_DCHECK(track_event_timestamps_valid());
track_event_timestamp_ns_ += delta_ns;
return track_event_timestamp_ns_;
}
int64_t IncrementAndGetTrackEventThreadTimeNs(int64_t delta_ns) {
- PERFETTO_DCHECK(IsTrackEventStateValid());
+ PERFETTO_DCHECK(track_event_timestamps_valid());
track_event_thread_timestamp_ns_ += delta_ns;
return track_event_thread_timestamp_ns_;
}
int64_t IncrementAndGetTrackEventThreadInstructionCount(int64_t delta) {
- PERFETTO_DCHECK(IsTrackEventStateValid());
+ PERFETTO_DCHECK(track_event_timestamps_valid());
track_event_thread_instruction_count_ += delta;
return track_event_thread_instruction_count_;
}
void OnPacketLoss() {
packet_loss_ = true;
- thread_descriptor_seen_ = false;
+ track_event_timestamps_valid_ = false;
}
void OnIncrementalStateCleared() { packet_loss_ = false; }
@@ -144,7 +149,8 @@
int64_t timestamp_ns,
int64_t thread_timestamp_ns,
int64_t thread_instruction_count) {
- thread_descriptor_seen_ = true;
+ track_event_timestamps_valid_ = true;
+ pid_and_tid_valid_ = true;
pid_ = pid;
tid_ = tid;
track_event_timestamp_ns_ = timestamp_ns;
@@ -154,14 +160,18 @@
bool IsIncrementalStateValid() const { return !packet_loss_; }
- bool IsTrackEventStateValid() const {
- return IsIncrementalStateValid() && thread_descriptor_seen_;
+ bool track_event_timestamps_valid() const {
+ return track_event_timestamps_valid_;
}
+ bool pid_and_tid_valid() const { return pid_and_tid_valid_; }
+
int32_t pid() const { return pid_; }
int32_t tid() const { return tid_; }
- template <typename MessageType>
+ // Use DefaultFieldName only if there is a single field in InternedData of
+ // the MessageType.
+ template <typename MessageType, typename FieldName = DefaultFieldName>
InternedDataMap<MessageType>* GetInternedDataMap();
private:
@@ -172,11 +182,15 @@
// We can only consider TrackEvent delta timestamps to be correct after we
// have observed a thread descriptor (since the last packet loss).
- bool thread_descriptor_seen_ = false;
+ bool track_event_timestamps_valid_ = false;
- // Process/thread ID of the packet sequence. Used as default values for
- // TrackEvents that don't specify a pid/tid override. Only valid while
- // |seen_thread_descriptor_| is true.
+ // |pid_| and |tid_| are only valid after we parsed at least one
+ // ThreadDescriptor packet on the sequence.
+ bool pid_and_tid_valid_ = false;
+
+ // Process/thread ID of the packet sequence set by a ThreadDescriptor
+ // packet. Used as default values for TrackEvents that don't specify a
+ // pid/tid override. Only valid after |pid_and_tid_valid_| is set to true.
int32_t pid_ = 0;
int32_t tid_ = 0;
@@ -191,7 +205,9 @@
InternedDataMap<protos::pbzero::DebugAnnotationName>
debug_annotation_names_;
InternedDataMap<protos::pbzero::SourceLocation> source_locations_;
- InternedDataMap<protos::pbzero::InternedString> interned_strings_;
+ InternedDataMap<protos::pbzero::InternedString> build_ids_;
+ InternedDataMap<protos::pbzero::InternedString> mapping_paths_;
+ InternedDataMap<protos::pbzero::InternedString> function_names_;
InternedDataMap<protos::pbzero::LogMessageBody> interned_log_messages_;
InternedDataMap<protos::pbzero::Mapping> mappings_;
InternedDataMap<protos::pbzero::Frame> frames_;
@@ -253,9 +269,25 @@
template <>
inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ProtoIncrementalState::PacketSequenceState::
+ GetInternedDataMap<protos::pbzero::InternedString, BuildIdFieldName>() {
+ return &build_ids_;
+}
+
+template <>
+inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
- protos::pbzero::InternedString>() {
- return &interned_strings_;
+ protos::pbzero::InternedString,
+ MappingPathsFieldName>() {
+ return &mapping_paths_;
+}
+
+template <>
+inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
+ protos::pbzero::InternedString,
+ FunctionNamesFieldName>() {
+ return &function_names_;
}
template <>
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 241b1f3..5b4e3be 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -23,9 +23,9 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/metatrace_events.h"
-#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/string_writer.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/traced/sys_stats_counters.h"
#include "perfetto/protozero/proto_decoder.h"
@@ -36,13 +36,14 @@
#include "src/trace_processor/heap_profile_tracker.h"
#include "src/trace_processor/metadata.h"
#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/stack_profile_tracker.h"
#include "src/trace_processor/syscall_tracker.h"
#include "src/trace_processor/systrace_parser.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/track_tracker.h"
#include "src/trace_processor/variadic.h"
-#include "perfetto/ext/base/string_writer.h"
#include "protos/perfetto/common/android_log_constants.pbzero.h"
#include "protos/perfetto/common/trace_stats.pbzero.h"
#include "protos/perfetto/trace/android/android_log.pbzero.h"
@@ -132,9 +133,24 @@
: seq_state_(seq_state) {}
base::Optional<base::StringView> GetString(
- StackProfileTracker::SourceStringId iid) const override {
- auto* map =
- seq_state_->GetInternedDataMap<protos::pbzero::InternedString>();
+ StackProfileTracker::SourceStringId iid,
+ StackProfileTracker::InternedStringType type) const override {
+ ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ map = nullptr;
+ switch (type) {
+ case StackProfileTracker::InternedStringType::kBuildId:
+ map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+ BuildIdFieldName>();
+ break;
+ case StackProfileTracker::InternedStringType::kFunctionName:
+ map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+ FunctionNamesFieldName>();
+ break;
+ case StackProfileTracker::InternedStringType::kMappingPath:
+ map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+ MappingPathsFieldName>();
+ break;
+ }
auto it = map->find(iid);
if (it == map->end()) {
PERFETTO_DLOG("Did not find string %" PRIu64 " in %zu elems", iid,
@@ -657,6 +673,18 @@
int64_t ts,
TraceSorter::TimestampedTracePiece ttp) {
PERFETTO_DCHECK(ttp.json_value == nullptr);
+
+ // Handle the (optional) alternative encoding format for sched_switch.
+ if (ttp.inline_event.type == TraceSorter::InlineEvent::Type::kSchedSwitch) {
+ const auto& event = ttp.inline_event.sched_switch;
+ context_->event_tracker->PushSchedSwitchCompact(
+ cpu, ts, event.prev_state, static_cast<uint32_t>(event.next_pid),
+ event.next_prio, event.next_comm);
+
+ context_->args_tracker->Flush();
+ return;
+ }
+
const TraceBlobView& ftrace = ttp.blob_view;
ProtoDecoder decoder(ftrace.data(), ftrace.length());
@@ -1576,32 +1604,27 @@
int64_t ticount,
ProtoIncrementalState::PacketSequenceState* sequence_state,
ConstBytes blob) {
+ using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
+
protos::pbzero::TrackEvent::Decoder event(blob.data, blob.size);
const auto legacy_event_blob = event.legacy_event();
- protos::pbzero::TrackEvent::LegacyEvent::Decoder legacy_event(
- legacy_event_blob.data, legacy_event_blob.size);
+ LegacyEvent::Decoder legacy_event(legacy_event_blob.data,
+ legacy_event_blob.size);
// TODO(eseckler): This legacy event field will eventually be replaced by
// fields in TrackEvent itself.
if (PERFETTO_UNLIKELY(!event.type() && !legacy_event.has_phase())) {
context_->storage->IncrementStats(stats::track_event_parser_errors);
- PERFETTO_ELOG("TrackEvent without type or phase");
+ PERFETTO_DLOG("TrackEvent without type or phase");
return;
}
ProcessTracker* procs = context_->process_tracker.get();
TraceStorage* storage = context_->storage.get();
+ TrackTracker* track_tracker = context_->track_tracker.get();
SliceTracker* slice_tracker = context_->slice_tracker.get();
- uint32_t pid = static_cast<uint32_t>(sequence_state->pid());
- uint32_t tid = static_cast<uint32_t>(sequence_state->tid());
- if (legacy_event.has_pid_override())
- pid = static_cast<uint32_t>(legacy_event.pid_override());
- if (legacy_event.has_tid_override())
- tid = static_cast<uint32_t>(legacy_event.tid_override());
- UniqueTid utid = procs->UpdateThread(tid, pid);
-
std::vector<uint64_t> category_iids;
for (auto it = event.category_iids(); it; ++it) {
category_iids.push_back(it->as_uint64());
@@ -1620,8 +1643,8 @@
sequence_state->GetInternedDataMap<protos::pbzero::EventCategory>();
auto cat_view_it = map->find(category_iids[0]);
if (cat_view_it == map->end()) {
- context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- PERFETTO_ELOG("Could not find category interning entry for ID %" PRIu64,
+ storage->IncrementStats(stats::track_event_tokenizer_errors);
+ PERFETTO_DLOG("Could not find category interning entry for ID %" PRIu64,
category_iids[0]);
} else {
// If the name is already in the pool, no need to decode it again.
@@ -1648,8 +1671,8 @@
for (uint64_t iid : category_iids) {
auto cat_view_it = map->find(iid);
if (cat_view_it == map->end()) {
- context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- PERFETTO_ELOG("Could not find category interning entry for ID %" PRIu64,
+ storage->IncrementStats(stats::track_event_tokenizer_errors);
+ PERFETTO_DLOG("Could not find category interning entry for ID %" PRIu64,
iid);
continue;
}
@@ -1666,9 +1689,6 @@
}
if (!categories.empty())
category_id = storage->InternString(base::StringView(categories));
- } else {
- context_->storage->IncrementStats(stats::track_event_parser_errors);
- PERFETTO_ELOG("TrackEvent without category");
}
StringId name_id = 0;
@@ -1681,8 +1701,8 @@
auto* map = sequence_state->GetInternedDataMap<protos::pbzero::EventName>();
auto name_view_it = map->find(name_iid);
if (name_view_it == map->end()) {
- context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- PERFETTO_ELOG("Could not find event name interning entry for ID %" PRIu64,
+ storage->IncrementStats(stats::track_event_tokenizer_errors);
+ PERFETTO_DLOG("Could not find event name interning entry for ID %" PRIu64,
name_iid);
} else {
// If the name is already in the pool, no need to decode it again.
@@ -1701,6 +1721,152 @@
name_id = storage->InternString(event.name());
}
+ // TODO(eseckler): Also consider track_uuid from TrackEventDefaults.
+ // Fall back to the default descriptor track (uuid 0).
+ uint64_t track_uuid = event.has_track_uuid() ? event.track_uuid() : 0u;
+ TrackId track_id;
+ base::Optional<UniqueTid> utid;
+ base::Optional<UniqueTid> upid;
+
+ // Determine track from track_uuid specified in either TrackEvent or
+ // TrackEventDefaults. If none is set, fall back to the track specified by the
+ // sequence's (or event's) pid + tid or a default track.
+ if (track_uuid) {
+ base::Optional<TrackId> opt_track_id =
+ track_tracker->GetDescriptorTrack(track_uuid);
+ if (!opt_track_id) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("TrackEvent with unknown track_uuid %" PRIu64, track_uuid);
+ return;
+ }
+ track_id = *opt_track_id;
+
+ auto thread_track_row =
+ context_->storage->thread_track_table().id().IndexOf(
+ SqlValue::Long(track_id));
+ if (thread_track_row) {
+ utid = storage->thread_track_table().utid()[*thread_track_row];
+ upid = storage->GetThread(*utid).upid;
+ } else {
+ auto process_track_row =
+ context_->storage->process_track_table().id().IndexOf(
+ SqlValue::Long(track_id));
+ if (process_track_row)
+ upid = storage->process_track_table().upid()[*process_track_row];
+ }
+ } else if (sequence_state->pid_and_tid_valid() ||
+ (legacy_event.has_pid_override() &&
+ legacy_event.has_tid_override())) {
+ uint32_t pid = static_cast<uint32_t>(sequence_state->pid());
+ uint32_t tid = static_cast<uint32_t>(sequence_state->tid());
+ if (legacy_event.has_pid_override())
+ pid = static_cast<uint32_t>(legacy_event.pid_override());
+ if (legacy_event.has_tid_override())
+ tid = static_cast<uint32_t>(legacy_event.tid_override());
+
+ utid = procs->UpdateThread(tid, pid);
+ upid = storage->GetThread(*utid).upid;
+ track_id = track_tracker->GetOrCreateDescriptorTrackForThread(*utid);
+ } else {
+ track_id = track_tracker->GetOrCreateDefaultDescriptorTrack();
+ }
+
+ // TODO(eseckler): Replace phase with type and remove handling of
+ // legacy_event.phase() once it is no longer used by producers.
+ int32_t phase = 0;
+ if (legacy_event.has_phase()) {
+ phase = legacy_event.phase();
+
+ switch (phase) {
+ case 'b':
+ case 'e':
+ case 'n': {
+ // Intern tracks for legacy async events based on legacy event ids.
+ base::Optional<UniquePid> event_upid;
+ int64_t source_id = 0;
+ if (legacy_event.has_unscoped_id()) {
+ source_id = static_cast<int64_t>(legacy_event.unscoped_id());
+ } else if (legacy_event.has_global_id()) {
+ source_id = static_cast<int64_t>(legacy_event.global_id());
+ } else if (legacy_event.has_local_id()) {
+ if (!upid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG(
+ "TrackEvent with local_id without process association");
+ return;
+ }
+
+ source_id = static_cast<int64_t>(legacy_event.local_id());
+ event_upid = upid;
+ } else {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("Async LegacyEvent without ID");
+ return;
+ }
+
+ StringId id_scope = 0;
+ if (legacy_event.has_id_scope()) {
+ id_scope = storage->InternString(legacy_event.id_scope());
+ }
+
+ track_id = context_->track_tracker->InternLegacyChromeAsyncTrack(
+ name_id, event_upid, source_id, id_scope);
+ break;
+ }
+ case 'i':
+ case 'I': {
+ // Intern tracks for global or process-scoped legacy instant events.
+ switch (legacy_event.instant_event_scope()) {
+ case LegacyEvent::SCOPE_UNSPECIFIED:
+ case LegacyEvent::SCOPE_THREAD:
+ // Thread-scoped legacy instant events already have the right track
+ // based on the tid/pid of the sequence.
+ if (!utid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG(
+ "Thread-scoped instant event without thread association");
+ return;
+ }
+ break;
+ case LegacyEvent::SCOPE_GLOBAL:
+ track_id = context_->track_tracker
+ ->GetOrCreateLegacyChromeGlobalInstantTrack();
+ break;
+ case LegacyEvent::SCOPE_PROCESS:
+ if (!upid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG(
+ "Process-scoped instant event without process association");
+ return;
+ }
+
+ track_id =
+ context_->track_tracker->InternLegacyChromeProcessInstantTrack(
+ *upid);
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ } else {
+ switch (event.type()) {
+ case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN:
+ phase = utid ? 'B' : 'b';
+ break;
+ case protos::pbzero::TrackEvent::TYPE_SLICE_END:
+ phase = utid ? 'E' : 'e';
+ break;
+ case protos::pbzero::TrackEvent::TYPE_INSTANT:
+ phase = utid ? 'i' : 'n';
+ break;
+ default:
+ PERFETTO_FATAL("unexpected event type %d", event.type());
+ return;
+ }
+ }
+
auto args_callback = [this, &event, &sequence_state, ts, utid](
ArgsTracker* args_tracker, RowId row_id) {
for (auto it = event.debug_annotations(); it; ++it) {
@@ -1719,47 +1885,17 @@
}
};
- using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
-
- tables::ChromeAsyncTrackTable::Row track(name_id);
- if (legacy_event.has_unscoped_id()) {
- track.async_id = static_cast<int64_t>(legacy_event.unscoped_id());
- } else if (legacy_event.has_global_id()) {
- track.async_id = static_cast<int64_t>(legacy_event.global_id());
- } else if (legacy_event.has_local_id()) {
- track.upid = procs->GetOrCreateProcess(pid);
- track.async_id = static_cast<int64_t>(legacy_event.local_id());
- }
-
- StringId id_scope = 0;
- if (legacy_event.has_id_scope()) {
- id_scope = storage->InternString(legacy_event.id_scope());
- }
-
- int32_t phase = 0;
- if (legacy_event.has_phase()) {
- phase = legacy_event.phase();
- } else {
- switch (event.type()) {
- case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN:
- phase = 'B';
- break;
- case protos::pbzero::TrackEvent::TYPE_SLICE_END:
- phase = 'E';
- break;
- case protos::pbzero::TrackEvent::TYPE_INSTANT:
- phase = 'i';
- break;
- default:
- PERFETTO_FATAL("unexpected event type %d", event.type());
- return;
- }
- }
-
switch (static_cast<char>(phase)) {
case 'B': { // TRACE_EVENT_PHASE_BEGIN.
- auto opt_slice_id = slice_tracker->Begin(
- ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
+ if (!utid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("TrackEvent with phase B without thread association");
+ return;
+ }
+
+ auto opt_slice_id =
+ slice_tracker->Begin(ts, track_id, *utid, RefType::kRefUtid,
+ category_id, name_id, args_callback);
if (opt_slice_id.has_value()) {
auto* thread_slices = storage->mutable_thread_slices();
PERFETTO_DCHECK(!thread_slices->slice_count() ||
@@ -1772,8 +1908,14 @@
break;
}
case 'E': { // TRACE_EVENT_PHASE_END.
- auto opt_slice_id = slice_tracker->End(
- ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
+ if (!utid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("TrackEvent with phase E without thread association");
+ return;
+ }
+
+ auto opt_slice_id =
+ slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
if (opt_slice_id.has_value()) {
auto* thread_slices = storage->mutable_thread_slices();
thread_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
@@ -1782,12 +1924,18 @@
break;
}
case 'X': { // TRACE_EVENT_PHASE_COMPLETE.
+ if (!utid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("TrackEvent with phase X without thread association");
+ return;
+ }
+
auto duration_ns = legacy_event.duration_us() * 1000;
if (duration_ns < 0)
return;
- auto opt_slice_id =
- slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
- name_id, duration_ns, args_callback);
+ auto opt_slice_id = slice_tracker->Scoped(
+ ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
+ duration_ns, args_callback);
if (opt_slice_id.has_value()) {
auto* thread_slices = storage->mutable_thread_slices();
PERFETTO_DCHECK(!thread_slices->slice_count() ||
@@ -1810,9 +1958,10 @@
switch (legacy_event.instant_event_scope()) {
case LegacyEvent::SCOPE_UNSPECIFIED:
case LegacyEvent::SCOPE_THREAD: {
- auto opt_slice_id =
- slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
- name_id, duration_ns, args_callback);
+ // TODO(lalitm): Associate thread slices with track instead.
+ auto opt_slice_id = slice_tracker->Scoped(
+ ts, track_id, *utid, RefType::kRefUtid, category_id, name_id,
+ duration_ns, args_callback);
if (opt_slice_id.has_value()) {
auto* thread_slices = storage->mutable_thread_slices();
PERFETTO_DCHECK(!thread_slices->slice_count() ||
@@ -1824,14 +1973,15 @@
break;
}
case LegacyEvent::SCOPE_GLOBAL: {
- slice_tracker->Scoped(ts, /*ref=*/0, RefType::kRefNoRef, category_id,
- name_id, duration_ns, args_callback);
+ slice_tracker->Scoped(ts, track_id, /*ref=*/0, RefType::kRefNoRef,
+ category_id, name_id, duration_ns,
+ args_callback);
break;
}
case LegacyEvent::SCOPE_PROCESS: {
- slice_tracker->Scoped(ts, procs->GetOrCreateProcess(pid),
- RefType::kRefUpid, category_id, name_id,
- duration_ns, args_callback);
+ slice_tracker->Scoped(ts, track_id, *upid, RefType::kRefUpid,
+ category_id, name_id, duration_ns,
+ args_callback);
break;
}
default: {
@@ -1843,11 +1993,9 @@
break;
}
case 'b': { // TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN
- TrackId track_id =
- context_->track_tracker->InternChromeAsyncTrack(track, id_scope);
auto opt_slice_id =
- slice_tracker->Begin(ts, track_id, RefType::kRefTrack, category_id,
- name_id, args_callback);
+ slice_tracker->Begin(ts, track_id, track_id, RefType::kRefTrack,
+ category_id, name_id, args_callback);
// For the time beeing, we only create vtrack slice rows if we need to
// store thread timestamps/counters.
if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
@@ -1862,11 +2010,8 @@
break;
}
case 'e': { // TRACE_EVENT_PHASE_NESTABLE_ASYNC_END
- TrackId track_id =
- context_->track_tracker->InternChromeAsyncTrack(track, id_scope);
auto opt_slice_id =
- slice_tracker->End(ts, track_id, RefType::kRefTrack, category_id,
- name_id, args_callback);
+ slice_tracker->End(ts, track_id, category_id, name_id, args_callback);
if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
auto* vtrack_slices = storage->mutable_virtual_track_slices();
vtrack_slices->UpdateThreadDeltasForSliceId(opt_slice_id.value(), tts,
@@ -1879,11 +2024,9 @@
// nested underneath their parent slices.
int64_t duration_ns = 0;
int64_t tidelta = 0;
- TrackId track_id =
- context_->track_tracker->InternChromeAsyncTrack(track, id_scope);
- auto opt_slice_id =
- slice_tracker->Scoped(ts, track_id, RefType::kRefTrack, category_id,
- name_id, duration_ns, args_callback);
+ auto opt_slice_id = slice_tracker->Scoped(
+ ts, track_id, track_id, RefType::kRefTrack, category_id, name_id,
+ duration_ns, args_callback);
if (legacy_event.use_async_tts() && opt_slice_id.has_value()) {
auto* vtrack_slices = storage->mutable_virtual_track_slices();
PERFETTO_DCHECK(!vtrack_slices->slice_count() ||
@@ -1900,6 +2043,13 @@
NullTermStringView event_name = storage->GetString(name_id);
PERFETTO_DCHECK(event_name.data());
if (strcmp(event_name.c_str(), "thread_name") == 0) {
+ if (!utid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG(
+ "thread_name metadata event without thread association");
+ return;
+ }
+
auto it = event.debug_annotations();
if (!it)
break;
@@ -1908,11 +2058,18 @@
auto thread_name = annotation.string_value();
if (!thread_name.size)
break;
- auto thread_name_id = context_->storage->InternString(thread_name);
- procs->UpdateThreadName(tid, thread_name_id);
+ auto thread_name_id = storage->InternString(thread_name);
+ procs->UpdateThreadName(storage->GetThread(*utid).tid, thread_name_id);
break;
}
if (strcmp(event_name.c_str(), "process_name") == 0) {
+ if (!upid) {
+ storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG(
+ "process_name metadata event without process association");
+ return;
+ }
+
auto it = event.debug_annotations();
if (!it)
break;
@@ -1921,7 +2078,8 @@
auto process_name = annotation.string_value();
if (!process_name.size)
break;
- procs->SetProcessMetadata(pid, base::nullopt, process_name);
+ procs->SetProcessMetadata(storage->GetProcess(*upid).pid, base::nullopt,
+ process_name);
break;
}
// Other metadata events are proxied via the raw table for JSON export.
@@ -1941,15 +2099,21 @@
int64_t ts,
int64_t tts,
int64_t ticount,
- UniqueTid utid,
+ base::Optional<UniqueTid> utid,
StringId category_id,
StringId name_id,
const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
SliceTracker::SetArgsCallback args_callback) {
using LegacyEvent = protos::pbzero::TrackEvent::LegacyEvent;
+ if (!utid) {
+ context_->storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("raw legacy event without thread association");
+ return;
+ }
+
RowId row_id = context_->storage->mutable_raw_events()->AddRawEvent(
- ts, raw_legacy_event_id_, 0, utid);
+ ts, raw_legacy_event_id_, 0, *utid);
ArgsTracker args(context_);
args.AddArg(row_id, legacy_event_category_key_id_,
legacy_event_category_key_id_, Variadic::String(category_id));
@@ -2080,7 +2244,7 @@
auto name_view_it = map->find(name_iid);
if (name_view_it == map->end()) {
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- PERFETTO_ELOG(
+ PERFETTO_DLOG(
"Could not find debug annotation name interning entry for ID "
"%" PRIu64,
name_iid);
@@ -2106,7 +2270,7 @@
name_id = storage->InternString(annotation.name());
} else {
context_->storage->IncrementStats(stats::track_event_parser_errors);
- PERFETTO_ELOG("Debug annotation without name");
+ PERFETTO_DLOG("Debug annotation without name");
return;
}
@@ -2213,7 +2377,7 @@
auto location_view_it = map->find(iid);
if (location_view_it == map->end()) {
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- PERFETTO_ELOG(
+ PERFETTO_DLOG(
"Could not find source location interning entry for ID %" PRIu64, iid);
return;
}
@@ -2256,9 +2420,15 @@
ConstBytes blob,
ProtoIncrementalState::PacketSequenceState* sequence_state,
int64_t ts,
- uint32_t utid,
+ base::Optional<UniqueTid> utid,
ArgsTracker* args_tracker,
RowId row) {
+ if (!utid) {
+ context_->storage->IncrementStats(stats::track_event_parser_errors);
+ PERFETTO_DLOG("LogMessage without thread association");
+ return;
+ }
+
protos::pbzero::LogMessage::Decoder message(blob.data, blob.size);
TraceStorage* storage = context_->storage.get();
@@ -2270,7 +2440,7 @@
auto message_body_entry = map->find(message.body_iid());
if (message_body_entry == map->end()) {
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
- PERFETTO_ELOG(
+ PERFETTO_DLOG(
"Could not find source location interning entry for ID %" PRIu64,
message.body_iid());
return;
@@ -2290,7 +2460,7 @@
// TODO(nicomazz): LogMessage also contains the source of the message (file
// and line number). Android logs doesn't support this so far.
context_->storage->mutable_android_log()->AddLogEvent(
- ts, utid,
+ ts, *utid,
/*priority*/ 0,
/*tag_id*/ 0, // TODO(nicomazz): Abuse tag_id to display
// "file_name:line_number".
@@ -2423,8 +2593,9 @@
sprintf(fallback, "Event %d", eid);
name_id = context_->storage->InternString(fallback);
}
- context_->slice_tracker->Scoped(ts, utid, RefType::kRefUtid, cat_id,
- name_id, event.event_duration_ns());
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ context_->slice_tracker->Scoped(ts, track_id, utid, RefType::kRefUtid,
+ cat_id, name_id, event.event_duration_ns());
} else if (event.has_counter_id()) {
auto cid = event.counter_id();
if (cid < metatrace::COUNTERS_MAX) {
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index c613b11..31ecd38 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -22,6 +22,7 @@
#include <array>
#include <memory>
+#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/protozero/field.h"
#include "src/trace_processor/ftrace_descriptors.h"
@@ -109,7 +110,7 @@
int64_t ts,
int64_t tts,
int64_t ticount,
- UniqueTid utid,
+ base::Optional<UniqueTid> utid,
StringId category_id,
StringId name_id,
const protos::pbzero::TrackEvent::LegacyEvent::Decoder& legacy_event,
@@ -136,7 +137,7 @@
void ParseLogMessage(ConstBytes,
ProtoIncrementalState::PacketSequenceState*,
int64_t,
- uint32_t,
+ base::Optional<UniqueTid>,
ArgsTracker*,
RowId);
void ParseModuleSymbols(ConstBytes);
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 88ad474..70552cf 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -50,6 +50,7 @@
#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
namespace perfetto {
@@ -142,10 +143,19 @@
ON_CALL(*this, GetString(_)).WillByDefault(Invoke([this](StringId id) {
return TraceStorage::GetString(id);
}));
+
+ ON_CALL(*this, GetThread(_))
+ .WillByDefault(Invoke(this, &MockTraceStorage::GetThreadImpl));
+ }
+
+ const Thread& GetThreadImpl(UniqueTid utid) {
+ return TraceStorage::GetThread(utid);
}
MOCK_METHOD1(InternString, StringId(base::StringView));
MOCK_CONST_METHOD1(GetString, NullTermStringView(StringId));
+
+ MOCK_CONST_METHOD1(GetThread, const Thread&(UniqueTid));
};
class MockArgsTracker : public ArgsTracker {
@@ -161,22 +171,23 @@
public:
MockSliceTracker(TraceProcessorContext* context) : SliceTracker(context) {}
- MOCK_METHOD6(Begin,
+ MOCK_METHOD7(Begin,
base::Optional<uint32_t>(int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId cat,
StringId name,
SetArgsCallback args_callback));
- MOCK_METHOD6(End,
+ MOCK_METHOD5(End,
base::Optional<uint32_t>(int64_t timestamp,
- int64_t ref,
- RefType ref_type,
+ TrackId track_id,
StringId cat,
StringId name,
SetArgsCallback args_callback));
- MOCK_METHOD7(Scoped,
+ MOCK_METHOD8(Scoped,
base::Optional<uint32_t>(int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId cat,
@@ -712,25 +723,31 @@
.Times(3)
.WillRepeatedly(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(3)
+ .WillRepeatedly(testing::ReturnRef(thread));
+
MockArgsTracker args(&context_);
+ constexpr TrackId track = 0u;
InSequence in_sequence; // Below slices should be sorted by timestamp.
- EXPECT_CALL(*slice_, Scoped(1005000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, 23000, _))
+ EXPECT_CALL(*slice_, Scoped(1005000, track, 1, RefType::kRefUtid,
+ kNullStringId, kNullStringId, 23000, _))
.WillOnce(DoAll(
- InvokeArgument<6>(
+ InvokeArgument<7>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
Return(0u)));
- EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, _))
+ EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid,
+ kNullStringId, kNullStringId, _))
.WillOnce(DoAll(
- InvokeArgument<5>(
+ InvokeArgument<6>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
Return(1u)));
- EXPECT_CALL(*slice_, End(1020000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, _))
+ EXPECT_CALL(*slice_, End(1020000, track, kNullStringId, kNullStringId, _))
.WillOnce(DoAll(
- InvokeArgument<5>(
+ InvokeArgument<4>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
Return(1u)));
@@ -799,25 +816,31 @@
.Times(3)
.WillRepeatedly(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(3)
+ .WillRepeatedly(testing::ReturnRef(thread));
+
MockArgsTracker args(&context_);
+ constexpr TrackId track = 0u;
InSequence in_sequence; // Below slices should be sorted by timestamp.
- EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, _))
- .WillOnce(DoAll(
- InvokeArgument<5>(
- &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
- Return(0u)));
- EXPECT_CALL(*slice_, Scoped(1015000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, 0, _))
+ EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid,
+ kNullStringId, kNullStringId, _))
.WillOnce(DoAll(
InvokeArgument<6>(
+ &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
+ Return(0u)));
+ EXPECT_CALL(*slice_, Scoped(1015000, track, 1, RefType::kRefUtid,
+ kNullStringId, kNullStringId, 0, _))
+ .WillOnce(DoAll(
+ InvokeArgument<7>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
Return(1u)));
- EXPECT_CALL(*slice_, End(1020000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, _))
+ EXPECT_CALL(*slice_, End(1020000, track, kNullStringId, kNullStringId, _))
.WillOnce(DoAll(
- InvokeArgument<5>(
+ InvokeArgument<4>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
Return(0u)));
@@ -937,20 +960,27 @@
.Times(5)
.WillRepeatedly(Return(1));
- EXPECT_CALL(*process_, GetOrCreateProcess(15)).WillOnce(Return(2));
+ TraceStorage::Thread thread(16);
+ thread.upid = 2u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(5)
+ .WillRepeatedly(testing::ReturnRef(thread));
MockArgsTracker args(&context_);
+ constexpr TrackId thread_1_track = 0u;
+ constexpr TrackId process_2_track = 1u;
+
InSequence in_sequence; // Below slices should be sorted by timestamp.
EXPECT_CALL(*storage_, InternString(base::StringView("cat2,cat3")))
.WillOnce(Return(1));
EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
.WillOnce(Return(2));
- EXPECT_CALL(*slice_, Scoped(1005000, 1, RefType::kRefUtid, StringId(1),
- StringId(2), 23000, _))
+ EXPECT_CALL(*slice_, Scoped(1005000, thread_1_track, 1, RefType::kRefUtid,
+ StringId(1), StringId(2), 23000, _))
.WillOnce(DoAll(
- InvokeArgument<6>(
+ InvokeArgument<7>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 0u)),
Return(0u)));
@@ -958,31 +988,31 @@
.WillOnce(Return(3));
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(4));
- EXPECT_CALL(*slice_,
- Begin(1010000, 1, RefType::kRefUtid, StringId(3), StringId(4), _))
- .WillOnce(DoAll(
- InvokeArgument<5>(
- &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
- Return(1u)));
-
- EXPECT_CALL(*slice_,
- End(1020000, 1, RefType::kRefUtid, StringId(3), StringId(4), _))
- .WillOnce(DoAll(
- InvokeArgument<5>(
- &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
- Return(1u)));
-
- EXPECT_CALL(*slice_, Scoped(1040000, 1, RefType::kRefUtid, StringId(3),
- StringId(4), 0, _))
+ EXPECT_CALL(*slice_, Begin(1010000, thread_1_track, 1, RefType::kRefUtid,
+ StringId(3), StringId(4), _))
.WillOnce(DoAll(
InvokeArgument<6>(
+ &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
+ Return(1u)));
+
+ EXPECT_CALL(*slice_,
+ End(1020000, thread_1_track, StringId(3), StringId(4), _))
+ .WillOnce(DoAll(
+ InvokeArgument<4>(
+ &args, TraceStorage::CreateRowId(TableId::kNestableSlices, 1u)),
+ Return(1u)));
+
+ EXPECT_CALL(*slice_, Scoped(1040000, thread_1_track, 1, RefType::kRefUtid,
+ StringId(3), StringId(4), 0, _))
+ .WillOnce(DoAll(
+ InvokeArgument<7>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 2u)),
Return(2u)));
- EXPECT_CALL(*slice_, Scoped(1050000, 2, RefType::kRefUpid, StringId(3),
- StringId(4), 0, _))
+ EXPECT_CALL(*slice_, Scoped(1050000, process_2_track, 2, RefType::kRefUpid,
+ StringId(3), StringId(4), 0, _))
.WillOnce(DoAll(
- InvokeArgument<6>(
+ InvokeArgument<7>(
&args, TraceStorage::CreateRowId(TableId::kNestableSlices, 3u)),
Return(3u)));
@@ -1094,7 +1124,12 @@
EXPECT_CALL(*process_, UpdateThread(16, 15))
.Times(4)
.WillRepeatedly(Return(1));
- EXPECT_CALL(*process_, GetOrCreateProcess(15)).WillOnce(Return(1));
+
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(4)
+ .WillRepeatedly(testing::ReturnRef(thread));
InSequence in_sequence; // Below slices should be sorted by timestamp.
@@ -1102,7 +1137,7 @@
.WillOnce(Return(1));
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(2));
- EXPECT_CALL(*slice_, Begin(1010000, 0, RefType::kRefTrack, StringId(1),
+ EXPECT_CALL(*slice_, Begin(1010000, 1, 1, RefType::kRefTrack, StringId(1),
StringId(2), _))
.WillOnce(Return(0u));
@@ -1110,25 +1145,26 @@
.WillOnce(Return(3));
EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
.WillOnce(Return(4));
- EXPECT_CALL(*slice_, Scoped(1015000, 0, RefType::kRefTrack, StringId(3),
+ EXPECT_CALL(*slice_, Scoped(1015000, 1, 1, RefType::kRefTrack, StringId(3),
StringId(4), 0, _));
- EXPECT_CALL(*slice_,
- End(1020000, 0, RefType::kRefTrack, StringId(1), StringId(2), _))
+ EXPECT_CALL(*slice_, End(1020000, 1, StringId(1), StringId(2), _))
.WillOnce(Return(0u));
EXPECT_CALL(*storage_, InternString(base::StringView("scope1")))
.WillOnce(Return(5));
- EXPECT_CALL(*slice_, Scoped(1030000, 1, RefType::kRefTrack, StringId(3),
+ EXPECT_CALL(*slice_, Scoped(1030000, 2, 2, RefType::kRefTrack, StringId(3),
StringId(4), 0, _));
context_.sorter->ExtractEventsForced();
- EXPECT_EQ(storage_->chrome_async_track_table().size(), 2u);
- EXPECT_EQ(storage_->chrome_async_track_table().name()[0], 2u);
- EXPECT_EQ(storage_->chrome_async_track_table().name()[1], 4u);
- EXPECT_EQ(storage_->chrome_async_track_table().upid()[0], base::nullopt);
- EXPECT_EQ(storage_->chrome_async_track_table().upid()[1], 1u);
+ // First track is for the thread; second and third are the async event tracks.
+ EXPECT_EQ(storage_->track_table().size(), 3u);
+ EXPECT_EQ(storage_->track_table().name()[1], 2u);
+ EXPECT_EQ(storage_->track_table().name()[2], 4u);
+
+ EXPECT_EQ(storage_->process_track_table().size(), 1u);
+ EXPECT_EQ(storage_->process_track_table().upid()[0], 1u);
EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], 0u);
@@ -1140,6 +1176,216 @@
20);
}
+// TODO(eseckler): Also test instant events on separate tracks.
+TEST_F(ProtoTraceParserTest, TrackEventWithTrackDescriptors) {
+ context_.sorter.reset(new TraceSorter(
+ &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+
+ // Sequence 1.
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ packet->set_incremental_state_cleared(true);
+ auto* track_desc = packet->set_track_descriptor();
+ track_desc->set_uuid(1234);
+ track_desc->set_name("Thread track 1");
+ auto* thread_desc = track_desc->set_thread();
+ thread_desc->set_pid(15);
+ thread_desc->set_tid(16);
+ }
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ auto* track_desc = packet->set_track_descriptor();
+ track_desc->set_uuid(5678);
+ track_desc->set_name("Async track 1");
+ }
+ {
+ // Async event started on "Async track 1".
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ packet->set_timestamp(1010000);
+ auto* event = packet->set_track_event();
+ event->set_track_uuid(5678);
+ event->set_thread_time_absolute_us(2005);
+ event->set_thread_instruction_count_absolute(3020);
+ event->add_category_iids(1);
+ event->set_name_iid(1);
+ event->set_type(protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN);
+ auto* legacy_event = event->set_legacy_event();
+ legacy_event->set_use_async_tts(true);
+
+ auto* interned_data = packet->set_interned_data();
+ auto cat1 = interned_data->add_event_categories();
+ cat1->set_iid(1);
+ cat1->set_name("cat1");
+ auto ev1 = interned_data->add_event_names();
+ ev1->set_iid(1);
+ ev1->set_name("ev1");
+ }
+ {
+ // Instant event on "Thread track 1".
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ packet->set_timestamp(1015000);
+ auto* event = packet->set_track_event();
+ event->set_track_uuid(1234);
+ event->set_thread_time_absolute_us(2007);
+ event->add_category_iids(2);
+ event->set_name_iid(2);
+ event->set_type(protos::pbzero::TrackEvent::TYPE_INSTANT);
+
+ auto* interned_data = packet->set_interned_data();
+ auto cat1 = interned_data->add_event_categories();
+ cat1->set_iid(2);
+ cat1->set_name("cat2");
+ auto ev1 = interned_data->add_event_names();
+ ev1->set_iid(2);
+ ev1->set_name("ev2");
+ }
+
+ // Sequence 2.
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(2);
+ packet->set_incremental_state_cleared(true);
+ auto* track_desc = packet->set_track_descriptor();
+ track_desc->set_uuid(4321);
+ track_desc->set_name("Thread track 2");
+ auto* thread_desc = track_desc->set_thread();
+ thread_desc->set_pid(15);
+ thread_desc->set_tid(17);
+ }
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(2);
+ auto* track_desc = packet->set_track_descriptor();
+ track_desc->set_uuid(5678); // "Async track 1" defined on sequence 1.
+ }
+ {
+ // Async event completed on "Async track 1".
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(2);
+ packet->set_timestamp(1020000);
+ auto* event = packet->set_track_event();
+ event->set_track_uuid(5678);
+ event->set_thread_time_absolute_us(2010);
+ event->set_thread_instruction_count_absolute(3040);
+ event->set_type(protos::pbzero::TrackEvent::TYPE_SLICE_END);
+ auto* legacy_event = event->set_legacy_event();
+ legacy_event->set_use_async_tts(true);
+ }
+ {
+ // Instant event on "Thread track 2".
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(2);
+ packet->set_timestamp(1016000);
+ auto* event = packet->set_track_event();
+ event->set_track_uuid(4321);
+ event->set_thread_time_absolute_us(2008);
+ event->add_category_iids(1);
+ event->set_name_iid(1);
+ event->set_type(protos::pbzero::TrackEvent::TYPE_INSTANT);
+
+ auto* interned_data = packet->set_interned_data();
+ auto cat1 = interned_data->add_event_categories();
+ cat1->set_iid(1);
+ cat1->set_name("cat3");
+ auto ev1 = interned_data->add_event_names();
+ ev1->set_iid(1);
+ ev1->set_name("ev3");
+ }
+
+ EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1));
+ EXPECT_CALL(*process_, UpdateThread(17, 15)).WillRepeatedly(Return(2));
+
+ TraceStorage::Thread thread1(16);
+ thread1.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .WillRepeatedly(testing::ReturnRef(thread1));
+ TraceStorage::Thread thread2(16);
+ thread2.upid = 2u;
+ EXPECT_CALL(*storage_, GetThread(2))
+ .WillRepeatedly(testing::ReturnRef(thread2));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("Thread track 1")))
+ .WillOnce(Return(10));
+ EXPECT_CALL(*storage_, InternString(base::StringView("Async track 1")))
+ .WillOnce(Return(11));
+ EXPECT_CALL(*storage_, InternString(base::StringView("Thread track 2")))
+ .WillOnce(Return(12));
+ EXPECT_CALL(*storage_, InternString(base::StringView("")))
+ .WillOnce(Return(0));
+
+ Tokenize();
+
+ // First track is "Thread track 1"; second is "Async track 1", third is
+ // "Thread track 2".
+ EXPECT_EQ(storage_->track_table().size(), 3u);
+ EXPECT_EQ(storage_->track_table().name()[0], 10u); // "Thread track 1"
+ EXPECT_EQ(storage_->track_table().name()[1], 11u); // "Async track 1"
+ EXPECT_EQ(storage_->track_table().name()[2], 12u); // "Thread track 2"
+ EXPECT_EQ(storage_->thread_track_table().size(), 2u);
+ EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
+ EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
+
+ InSequence in_sequence; // Below slices should be sorted by timestamp.
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
+ .WillOnce(Return(1));
+ EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
+ .WillOnce(Return(2));
+ EXPECT_CALL(*slice_, Begin(1010000, 1, 1, RefType::kRefTrack, StringId(1),
+ StringId(2), _))
+ .WillOnce(Return(0u));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("cat2")))
+ .WillOnce(Return(3));
+ EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
+ .WillOnce(Return(4));
+ EXPECT_CALL(*slice_, Scoped(1015000, 0, 1, RefType::kRefUtid, StringId(3),
+ StringId(4), 0, _))
+ .WillOnce(Return(1u));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("cat3")))
+ .WillOnce(Return(5));
+ EXPECT_CALL(*storage_, InternString(base::StringView("ev3")))
+ .WillOnce(Return(6));
+ EXPECT_CALL(*slice_, Scoped(1016000, 2, 2, RefType::kRefUtid, StringId(5),
+ StringId(6), 0, _))
+ .WillOnce(Return(2u));
+
+ EXPECT_CALL(*slice_, End(1020000, 1, StringId(0), StringId(0), _))
+ .WillOnce(Return(0u));
+
+ context_.sorter->ExtractEventsForced();
+
+ // Track tables shouldn't have changed.
+ EXPECT_EQ(storage_->track_table().size(), 3u);
+ EXPECT_EQ(storage_->thread_track_table().size(), 2u);
+
+ EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
+ EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], 0u);
+ EXPECT_EQ(storage_->virtual_track_slices().thread_timestamp_ns()[0], 2005000);
+ EXPECT_EQ(storage_->virtual_track_slices().thread_duration_ns()[0], 5000);
+ EXPECT_EQ(storage_->virtual_track_slices().thread_instruction_counts()[0],
+ 3020);
+ EXPECT_EQ(storage_->virtual_track_slices().thread_instruction_deltas()[0],
+ 20);
+
+ EXPECT_EQ(storage_->thread_slices().slice_count(), 2u);
+ EXPECT_EQ(storage_->thread_slices().slice_ids()[0], 1u);
+ EXPECT_EQ(storage_->thread_slices().thread_timestamp_ns()[0], 2007000);
+ EXPECT_EQ(storage_->thread_slices().thread_duration_ns()[0], 0);
+ EXPECT_EQ(storage_->thread_slices().thread_instruction_counts()[0], 0);
+ EXPECT_EQ(storage_->thread_slices().thread_instruction_deltas()[0], 0);
+ EXPECT_EQ(storage_->thread_slices().slice_ids()[1], 2u);
+ EXPECT_EQ(storage_->thread_slices().thread_timestamp_ns()[1], 2008000);
+ EXPECT_EQ(storage_->thread_slices().thread_duration_ns()[1], 0);
+ EXPECT_EQ(storage_->thread_slices().thread_instruction_counts()[1], 0);
+ EXPECT_EQ(storage_->thread_slices().thread_instruction_deltas()[1], 0);
+}
+
TEST_F(ProtoTraceParserTest, TrackEventWithoutIncrementalStateReset) {
context_.sorter.reset(new TraceSorter(
&context_, std::numeric_limits<int64_t>::max() /*window size*/));
@@ -1168,7 +1414,7 @@
Tokenize();
- EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _, _)).Times(0);
context_.sorter->ExtractEventsForced();
}
@@ -1192,7 +1438,7 @@
Tokenize();
- EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*slice_, Begin(_, _, _, _, _, _, _)).Times(0);
context_.sorter->ExtractEventsForced();
}
@@ -1286,11 +1532,17 @@
.Times(2)
.WillRepeatedly(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(2)
+ .WillRepeatedly(testing::ReturnRef(thread));
+
+ constexpr TrackId track = 0u;
InSequence in_sequence; // Below slices should be sorted by timestamp.
- EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, _));
- EXPECT_CALL(*slice_, End(2010000, 1, RefType::kRefUtid, kNullStringId,
- kNullStringId, _));
+ EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid,
+ kNullStringId, kNullStringId, _));
+ EXPECT_CALL(*slice_, End(2010000, track, kNullStringId, kNullStringId, _));
context_.sorter->ExtractEventsForced();
}
@@ -1389,6 +1641,20 @@
.Times(2)
.WillRepeatedly(Return(2));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(2)
+ .WillRepeatedly(testing::ReturnRef(thread));
+
+ TraceStorage::Thread thread2(17);
+ thread2.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(2))
+ .Times(2)
+ .WillRepeatedly(testing::ReturnRef(thread2));
+
+ constexpr TrackId thread_2_track = 0u;
+ constexpr TrackId thread_1_track = 1u;
InSequence in_sequence; // Below slices should be sorted by timestamp.
EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
@@ -1396,20 +1662,20 @@
EXPECT_CALL(*storage_, InternString(base::StringView("ev2")))
.WillOnce(Return(2));
- EXPECT_CALL(*slice_, Begin(1005000, 2, RefType::kRefUtid, StringId(1),
- StringId(2), _));
+ EXPECT_CALL(*slice_, Begin(1005000, thread_2_track, 2, RefType::kRefUtid,
+ StringId(1), StringId(2), _));
EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
.WillOnce(Return(1));
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(3));
- EXPECT_CALL(*slice_, Begin(1010000, 1, RefType::kRefUtid, StringId(1),
- StringId(3), _));
+ EXPECT_CALL(*slice_, Begin(1010000, thread_1_track, 1, RefType::kRefUtid,
+ StringId(1), StringId(3), _));
EXPECT_CALL(*slice_,
- End(1015000, 2, RefType::kRefUtid, StringId(1), StringId(2), _));
+ End(1015000, thread_2_track, StringId(1), StringId(2), _));
EXPECT_CALL(*slice_,
- End(1020000, 1, RefType::kRefUtid, StringId(1), StringId(3), _));
+ End(1020000, thread_1_track, StringId(1), StringId(3), _));
context_.sorter->ExtractEventsForced();
}
@@ -1538,15 +1804,22 @@
.Times(2)
.WillRepeatedly(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1))
+ .Times(2)
+ .WillRepeatedly(testing::ReturnRef(thread));
+
+ constexpr TrackId track = 0u;
InSequence in_sequence; // Below slices should be sorted by timestamp.
EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
.WillOnce(Return(1));
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(2));
- EXPECT_CALL(*slice_,
- Begin(1010000, 1, RefType::kRefUtid, StringId(1), StringId(2), _))
- .WillOnce(DoAll(InvokeArgument<5>(&args, 1u), Return(1u)));
+ EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid, StringId(1),
+ StringId(2), _))
+ .WillOnce(DoAll(InvokeArgument<6>(&args, 1u), Return(1u)));
EXPECT_CALL(*storage_, InternString(base::StringView("debug.an1")))
.WillOnce(Return(3));
EXPECT_CALL(args, AddArg(1u, StringId(3), StringId(3),
@@ -1583,9 +1856,8 @@
EXPECT_CALL(args,
AddArg(1u, StringId(6), StringId(10), Variadic::Integer(23)));
- EXPECT_CALL(*slice_,
- End(1020000, 1, RefType::kRefUtid, StringId(1), StringId(2), _))
- .WillOnce(DoAll(InvokeArgument<5>(&args, 1u), Return(1u)));
+ EXPECT_CALL(*slice_, End(1020000, track, StringId(1), StringId(2), _))
+ .WillOnce(DoAll(InvokeArgument<4>(&args, 1u), Return(1u)));
EXPECT_CALL(*storage_, InternString(base::StringView("debug.an3")))
.WillOnce(Return(11));
@@ -1664,15 +1936,20 @@
EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1)).WillOnce(testing::ReturnRef(thread));
+
+ constexpr TrackId track = 0u;
InSequence in_sequence; // Below slices should be sorted by timestamp.
EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
.WillOnce(Return(1));
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(2));
- EXPECT_CALL(*slice_,
- Begin(1010000, 1, RefType::kRefUtid, StringId(1), StringId(2), _))
- .WillOnce(DoAll(InvokeArgument<5>(&args, 1u), Return(1u)));
+ EXPECT_CALL(*slice_, Begin(1010000, track, 1, RefType::kRefUtid, StringId(1),
+ StringId(2), _))
+ .WillOnce(DoAll(InvokeArgument<6>(&args, 1u), Return(1u)));
EXPECT_CALL(*storage_, InternString(base::StringView("file1")))
.WillOnce(Return(3));
EXPECT_CALL(*storage_, InternString(base::StringView("func1")))
@@ -1739,6 +2016,11 @@
EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1)).WillOnce(testing::ReturnRef(thread));
+
+ constexpr TrackId track = 0;
InSequence in_sequence; // Below slices should be sorted by timestamp.
EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
@@ -1746,9 +2028,9 @@
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(2));
- EXPECT_CALL(*slice_, Scoped(1010000, 1, RefType::kRefUtid, StringId(1),
+ EXPECT_CALL(*slice_, Scoped(1010000, track, 1, RefType::kRefUtid, StringId(1),
StringId(2), 0, _))
- .WillOnce(DoAll(InvokeArgument<6>(&args, 1u), Return(1u)));
+ .WillOnce(DoAll(InvokeArgument<7>(&args, 1u), Return(1u)));
EXPECT_CALL(*storage_, InternString(base::StringView("body1")))
.WillOnce(Return(3));
@@ -1819,12 +2101,16 @@
EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+ TraceStorage::Thread thread(16);
+ thread.upid = 1u;
+ EXPECT_CALL(*storage_, GetThread(1)).WillOnce(testing::ReturnRef(thread));
+
EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
.WillOnce(Return(1));
EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
.WillOnce(Return(2));
EXPECT_CALL(*storage_, InternString(base::StringView("scope1")))
- .Times(2)
+ .Times(1)
.WillRepeatedly(Return(3));
EXPECT_CALL(*storage_, InternString(base::StringView("?")))
.WillOnce(Return(4));
@@ -1845,7 +2131,7 @@
EXPECT_EQ(raw_events.utids()[0], 1u);
EXPECT_EQ(raw_events.arg_set_ids()[0], 1u);
- EXPECT_EQ(storage_->args().args_count(), 13u);
+ EXPECT_GE(storage_->args().args_count(), 13u);
EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.category"),
Variadic::String(1u)));
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index e47ff6b..8366e57 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -28,10 +28,12 @@
#include "src/trace_processor/clock_tracker.h"
#include "src/trace_processor/event_tracker.h"
#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_incremental_state.h"
#include "src/trace_processor/stats.h"
#include "src/trace_processor/trace_blob_view.h"
#include "src/trace_processor/trace_sorter.h"
#include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
#include "protos/perfetto/config/trace_config.pbzero.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
@@ -40,9 +42,11 @@
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
#include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
namespace perfetto {
@@ -58,7 +62,7 @@
constexpr uint8_t kTracePacketTag =
MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
-template <typename MessageType>
+template <typename MessageType, typename FieldName = DefaultFieldName>
void InternMessage(TraceProcessorContext* context,
ProtoIncrementalState::PacketSequenceState* state,
TraceBlobView message) {
@@ -77,7 +81,7 @@
}
iid = field.as_uint64();
- auto res = state->GetInternedDataMap<MessageType>()->emplace(
+ auto res = state->GetInternedDataMap<MessageType, FieldName>()->emplace(
iid,
ProtoIncrementalState::InternedDataView<MessageType>(std::move(message)));
// If a message with this ID is already interned, its data should not have
@@ -224,12 +228,13 @@
? static_cast<int64_t>(decoder.timestamp())
: latest_timestamp_;
+ const uint32_t seq_id = decoder.trusted_packet_sequence_id();
+
// If the TracePacket specifies a non-zero clock-id, translate the timestamp
// into the trace-time clock domain.
if (decoder.timestamp_clock_id()) {
PERFETTO_DCHECK(decoder.has_timestamp());
ClockTracker::ClockId clock_id = decoder.timestamp_clock_id();
- const uint32_t seq_id = decoder.trusted_packet_sequence_id();
bool is_seq_scoped = ClockTracker::IsReservedSeqScopedClockId(clock_id);
if (is_seq_scoped) {
if (!seq_id) {
@@ -237,7 +242,7 @@
"TracePacket specified a sequence-local clock id (%" PRIu32
") but the TraceWriter's sequence_id is zero (the service is "
"probably too old)",
- seq_id);
+ decoder.timestamp_clock_id());
}
clock_id = ClockTracker::SeqScopedClockIdToGlobal(
seq_id, decoder.timestamp_clock_id());
@@ -260,17 +265,41 @@
}
latest_timestamp_ = std::max(timestamp, latest_timestamp_);
- if (decoder.incremental_state_cleared()) {
+ auto* state = GetIncrementalStateForPacketSequence(
+ decoder.trusted_packet_sequence_id());
+
+ uint32_t sequence_flags = decoder.sequence_flags();
+
+ if (decoder.incremental_state_cleared() ||
+ sequence_flags &
+ protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
HandleIncrementalStateCleared(decoder);
} else if (decoder.previous_packet_dropped()) {
HandlePreviousPacketDropped(decoder);
}
+ if (decoder.sequence_flags() &
+ protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
+ if (!seq_id) {
+ return util::ErrStatus(
+ "TracePacket specified SEQ_NEEDS_INCREMENTAL_STATE but the "
+ "TraceWriter's sequence_id is zero (the service is "
+ "probably too old)");
+ }
+
+ if (!state->IsIncrementalStateValid()) {
+ context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+ return util::OkStatus();
+ }
+ }
+
if (decoder.has_clock_snapshot()) {
return ParseClockSnapshot(decoder.clock_snapshot(),
decoder.trusted_packet_sequence_id());
}
+ // TODO(eseckler): Parse TracePacketDefaults.
+
if (decoder.has_interned_data()) {
auto field = decoder.interned_data();
const size_t offset = packet.offset_of(field.data);
@@ -284,11 +313,18 @@
return util::OkStatus();
}
- if (decoder.has_track_event()) {
- ParseTrackEventPacket(decoder, std::move(packet));
+ if (decoder.has_track_descriptor()) {
+ ParseTrackDescriptorPacket(decoder);
return util::OkStatus();
}
+ if (decoder.has_track_event()) {
+ ParseTrackEventPacket(decoder, std::move(packet), timestamp);
+ return util::OkStatus();
+ }
+
+ // TODO(eseckler): Remove this once Chrome has switched fully over to
+ // TrackDescriptors.
if (decoder.has_thread_descriptor()) {
ParseThreadDescriptorPacket(decoder);
return util::OkStatus();
@@ -347,9 +383,6 @@
}
}
- auto* state = GetIncrementalStateForPacketSequence(
- decoder.trusted_packet_sequence_id());
-
// Use parent data and length because we want to parse this again
// later to get the exact type of the packet.
context_->sorter->PushTracePacket(timestamp, state, std::move(packet));
@@ -424,17 +457,17 @@
for (auto it = interned_data_decoder.build_ids(); it; ++it) {
size_t offset = interned_data.offset_of(it->data());
- InternMessage<protos::pbzero::InternedString>(
+ InternMessage<protos::pbzero::InternedString, BuildIdFieldName>(
context_, state, interned_data.slice(offset, it->size()));
}
for (auto it = interned_data_decoder.mapping_paths(); it; ++it) {
size_t offset = interned_data.offset_of(it->data());
- InternMessage<protos::pbzero::InternedString>(
+ InternMessage<protos::pbzero::InternedString, MappingPathsFieldName>(
context_, state, interned_data.slice(offset, it->size()));
}
for (auto it = interned_data_decoder.function_names(); it; ++it) {
size_t offset = interned_data.offset_of(it->data());
- InternMessage<protos::pbzero::InternedString>(
+ InternMessage<protos::pbzero::InternedString, FunctionNamesFieldName>(
context_, state, interned_data.slice(offset, it->size()));
}
@@ -461,6 +494,51 @@
}
}
+void ProtoTraceTokenizer::ParseTrackDescriptorPacket(
+ const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+ auto track_descriptor_field = packet_decoder.track_descriptor();
+ protos::pbzero::TrackDescriptor::Decoder track_descriptor_decoder(
+ track_descriptor_field.data, track_descriptor_field.size);
+
+ if (!track_descriptor_decoder.has_uuid()) {
+ PERFETTO_ELOG("TrackDescriptor packet without trusted_packet_sequence_id");
+ context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+ return;
+ }
+
+ base::Optional<UniquePid> upid;
+ base::Optional<UniqueTid> utid;
+
+ if (track_descriptor_decoder.has_process()) {
+ auto process_descriptor_field = track_descriptor_decoder.process();
+ protos::pbzero::ProcessDescriptor::Decoder process_descriptor_decoder(
+ process_descriptor_field.data, process_descriptor_field.size);
+
+ // TODO(eseckler): Also parse process name / type here.
+
+ upid = context_->process_tracker->GetOrCreateProcess(
+ static_cast<uint32_t>(process_descriptor_decoder.pid()));
+ }
+
+ if (track_descriptor_decoder.has_thread()) {
+ auto thread_descriptor_field = track_descriptor_decoder.thread();
+ protos::pbzero::ThreadDescriptor::Decoder thread_descriptor_decoder(
+ thread_descriptor_field.data, thread_descriptor_field.size);
+
+ ParseThreadDescriptor(thread_descriptor_decoder);
+ utid = context_->process_tracker->UpdateThread(
+ static_cast<uint32_t>(thread_descriptor_decoder.tid()),
+ static_cast<uint32_t>(thread_descriptor_decoder.pid()));
+ upid = *context_->storage->GetThread(*utid).upid;
+ }
+
+ StringId name_id =
+ context_->storage->InternString(track_descriptor_decoder.name());
+
+ context_->track_tracker->UpdateDescriptorTrack(
+ track_descriptor_decoder.uuid(), name_id, upid, utid);
+}
+
void ProtoTraceTokenizer::ParseThreadDescriptorPacket(
const protos::pbzero::TracePacket::Decoder& packet_decoder) {
if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
@@ -478,8 +556,7 @@
// incorrectly once we move out of the packet loss state. Instead, wait until
// the first subsequent descriptor after incremental state is cleared.
if (!state->IsIncrementalStateValid()) {
- context_->storage->IncrementStats(
- stats::track_event_tokenizer_skipped_packets);
+ context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
return;
}
@@ -493,6 +570,12 @@
thread_descriptor_decoder.reference_thread_time_us() * 1000,
thread_descriptor_decoder.reference_thread_instruction_count());
+ ParseThreadDescriptor(thread_descriptor_decoder);
+}
+
+void ProtoTraceTokenizer::ParseThreadDescriptor(
+ const protos::pbzero::ThreadDescriptor::Decoder&
+ thread_descriptor_decoder) {
base::StringView name;
if (thread_descriptor_decoder.has_thread_name()) {
name = thread_descriptor_decoder.thread_name();
@@ -576,7 +659,8 @@
void ProtoTraceTokenizer::ParseTrackEventPacket(
const protos::pbzero::TracePacket::Decoder& packet_decoder,
- TraceBlobView packet) {
+ TraceBlobView packet,
+ int64_t packet_timestamp) {
constexpr auto kTimestampDeltaUsFieldNumber =
protos::pbzero::TrackEvent::kTimestampDeltaUsFieldNumber;
constexpr auto kTimestampAbsoluteUsFieldNumber =
@@ -599,11 +683,11 @@
auto* state = GetIncrementalStateForPacketSequence(
packet_decoder.trusted_packet_sequence_id());
- // TrackEvents can only be parsed correctly while incremental state for their
- // sequence is valid and after a ThreadDescriptor has been parsed.
- if (!state->IsTrackEventStateValid()) {
- context_->storage->IncrementStats(
- stats::track_event_tokenizer_skipped_packets);
+ // TODO(eseckler): For now, TrackEvents can only be parsed correctly while
+ // incremental state for their sequence is valid, because chromium doesn't set
+ // SEQ_NEEDS_INCREMENTAL_STATE yet. Remove this once it does.
+ if (!state->IsIncrementalStateValid()) {
+ context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
return;
}
@@ -614,14 +698,26 @@
int64_t thread_timestamp = 0;
int64_t thread_instructions = 0;
+ // TODO(eseckler): Remove handling of timestamps relative to ThreadDescriptors
+ // once all producers have switched to clock-domain timestamps (e.g.
+ // TracePacket's timestamp).
+
if (auto ts_delta_field =
event_decoder.FindField(kTimestampDeltaUsFieldNumber)) {
+ // Delta timestamps require a valid ThreadDescriptor packet since the last
+ // packet loss.
+ if (!state->track_event_timestamps_valid()) {
+ context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+ return;
+ }
timestamp = state->IncrementAndGetTrackEventTimeNs(
ts_delta_field.as_int64() * 1000);
} else if (auto ts_absolute_field =
event_decoder.FindField(kTimestampAbsoluteUsFieldNumber)) {
// One-off absolute timestamps don't affect delta computation.
timestamp = ts_absolute_field.as_int64() * 1000;
+ } else if (packet_decoder.has_timestamp()) {
+ timestamp = packet_timestamp;
} else {
PERFETTO_ELOG("TrackEvent without timestamp");
context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
@@ -632,6 +728,12 @@
if (auto tt_delta_field =
event_decoder.FindField(kThreadTimeDeltaUsFieldNumber)) {
+ // Delta timestamps require a valid ThreadDescriptor packet since the last
+ // packet loss.
+ if (!state->track_event_timestamps_valid()) {
+ context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+ return;
+ }
thread_timestamp = state->IncrementAndGetTrackEventThreadTimeNs(
tt_delta_field.as_int64() * 1000);
} else if (auto tt_absolute_field =
@@ -642,6 +744,12 @@
if (auto ti_delta_field =
event_decoder.FindField(kThreadInstructionCountDeltaFieldNumber)) {
+ // Delta timestamps require a valid ThreadDescriptor packet since the last
+ // packet loss.
+ if (!state->track_event_timestamps_valid()) {
+ context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+ return;
+ }
thread_instructions =
state->IncrementAndGetTrackEventThreadInstructionCount(
ti_delta_field.as_int64());
@@ -673,6 +781,11 @@
return;
}
+ if (decoder.has_compact_sched()) {
+ ParseFtraceCompactSched(cpu, decoder.compact_sched().data,
+ decoder.compact_sched().size);
+ }
+
for (auto it = decoder.event(); it; ++it) {
size_t off = bundle.offset_of(it->data());
ParseFtraceEvent(cpu, bundle.slice(off, it->size()));
@@ -680,6 +793,60 @@
context_->sorter->FinalizeFtraceEventBatch(cpu);
}
+void ProtoTraceTokenizer::ParseFtraceCompactSched(uint32_t cpu,
+ const uint8_t* data,
+ size_t size) {
+ protos::pbzero::FtraceEventBundle_CompactSched::Decoder compact(data, size);
+
+ // Build the interning table for next_comm fields.
+ std::vector<StringId> string_table;
+ string_table.reserve(512);
+ for (auto it = compact.switch_next_comm_table(); it; it++) {
+ StringId value = context_->storage->InternString(it->as_string());
+ string_table.push_back(value);
+ }
+
+ // Accumulator for timestamp deltas.
+ int64_t timestamp_acc = 0;
+
+ // The events' fields are stored in a structure-of-arrays style, using packed
+ // repeated fields. Walk each repeated field in step to recover individual
+ // events.
+ bool parse_error = false;
+ auto timestamp_it = compact.switch_timestamp(&parse_error);
+ auto pstate_it = compact.switch_prev_state(&parse_error);
+ auto npid_it = compact.switch_next_pid(&parse_error);
+ auto nprio_it = compact.switch_next_prio(&parse_error);
+ auto comm_it = compact.switch_next_comm_index(&parse_error);
+ for (; timestamp_it && pstate_it && npid_it && nprio_it && comm_it;
+ ++timestamp_it, ++pstate_it, ++npid_it, ++nprio_it, ++comm_it) {
+ TraceSorter::InlineSchedSwitch event{};
+
+ // delta-encoded timestamp
+ timestamp_acc += static_cast<int64_t>(*timestamp_it);
+ int64_t event_timestamp = timestamp_acc;
+
+ // index into the interned string table
+ PERFETTO_DCHECK(*comm_it < string_table.size());
+ event.next_comm = string_table[*comm_it];
+
+ event.prev_state = *pstate_it;
+ event.next_pid = *npid_it;
+ event.next_prio = *nprio_it;
+
+ context_->sorter->PushInlineFtraceEvent(
+ cpu, event_timestamp, TraceSorter::InlineEvent::SchedSwitch(event));
+ }
+
+ // Check that all packed buffers were decoded correctly, and fully.
+ bool sizes_match =
+ !timestamp_it && !pstate_it && !npid_it && !nprio_it && !comm_it;
+ if (parse_error || !sizes_match)
+ context_->storage->IncrementStats(stats::compact_sched_has_parse_errors);
+
+ latest_timestamp_ = std::max(timestamp_acc, latest_timestamp_);
+}
+
PERFETTO_ALWAYS_INLINE
void ProtoTraceTokenizer::ParseFtraceEvent(uint32_t cpu, TraceBlobView event) {
constexpr auto kTimestampFieldNumber =
diff --git a/src/trace_processor/proto_trace_tokenizer.h b/src/trace_processor/proto_trace_tokenizer.h
index 907bd4a..485effd 100644
--- a/src/trace_processor/proto_trace_tokenizer.h
+++ b/src/trace_processor/proto_trace_tokenizer.h
@@ -27,6 +27,7 @@
#include "src/trace_processor/trace_processor_impl.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
namespace protozero {
struct ConstBytes;
@@ -64,11 +65,15 @@
void HandlePreviousPacketDropped(const protos::pbzero::TracePacket::Decoder&);
void ParseInternedData(const protos::pbzero::TracePacket::Decoder&,
TraceBlobView interned_data);
+ void ParseTrackDescriptorPacket(const protos::pbzero::TracePacket::Decoder&);
void ParseThreadDescriptorPacket(const protos::pbzero::TracePacket::Decoder&);
+ void ParseThreadDescriptor(const protos::pbzero::ThreadDescriptor::Decoder&);
void ParseTrackEventPacket(const protos::pbzero::TracePacket::Decoder&,
- TraceBlobView packet);
+ TraceBlobView packet,
+ int64_t packet_timestamp);
void ParseFtraceBundle(TraceBlobView);
void ParseFtraceEvent(uint32_t cpu, TraceBlobView);
+ void ParseFtraceCompactSched(uint32_t cpu, const uint8_t* data, size_t size);
ProtoIncrementalState::PacketSequenceState*
GetIncrementalStateForPacketSequence(uint32_t sequence_id) {
diff --git a/src/trace_processor/slice_table.cc b/src/trace_processor/slice_table.cc
index 70a96f2..d2dca83 100644
--- a/src/trace_processor/slice_table.cc
+++ b/src/trace_processor/slice_table.cc
@@ -34,6 +34,7 @@
.AddGenericNumericColumn("slice_id", RowAccessor())
.AddOrderedNumericColumn("ts", &slices.start_ns())
.AddNumericColumn("dur", &slices.durations())
+ .AddNumericColumn("track_id", &slices.track_id())
.AddNumericColumn("ref", &slices.refs())
.AddStringColumn("ref_type", &slices.types(), &GetRefTypeStringMap())
.AddStringColumn("category", &slices.categories(),
diff --git a/src/trace_processor/slice_tracker.cc b/src/trace_processor/slice_tracker.cc
index 70083da..0ab1183 100644
--- a/src/trace_processor/slice_tracker.cc
+++ b/src/trace_processor/slice_tracker.cc
@@ -23,6 +23,7 @@
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -45,10 +46,13 @@
UniqueTid utid =
context_->process_tracker->UpdateThread(ftrace_tid, atrace_tgid);
ftrace_to_atrace_tgid_[ftrace_tid] = atrace_tgid;
- return Begin(timestamp, utid, RefType::kRefUtid, category, name);
+
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ return Begin(timestamp, track_id, utid, RefType::kRefUtid, category, name);
}
base::Optional<uint32_t> SliceTracker::Begin(int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
@@ -61,12 +65,13 @@
}
prev_timestamp_ = timestamp;
- MaybeCloseStack(timestamp, &stacks_[{ref, ref_type}]);
- return StartSlice(timestamp, kPendingDuration, ref, ref_type, category, name,
- args_callback);
+ MaybeCloseStack(timestamp, &stacks_[track_id]);
+ return StartSlice(timestamp, kPendingDuration, track_id, ref, ref_type,
+ category, name, args_callback);
}
base::Optional<uint32_t> SliceTracker::Scoped(int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
@@ -81,20 +86,21 @@
prev_timestamp_ = timestamp;
PERFETTO_DCHECK(duration >= 0);
- MaybeCloseStack(timestamp, &stacks_[{ref, ref_type}]);
- return StartSlice(timestamp, duration, ref, ref_type, category, name,
- args_callback);
+ MaybeCloseStack(timestamp, &stacks_[track_id]);
+ return StartSlice(timestamp, duration, track_id, ref, ref_type, category,
+ name, args_callback);
}
base::Optional<uint32_t> SliceTracker::StartSlice(
int64_t timestamp,
int64_t duration,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
StringId name,
SetArgsCallback args_callback) {
- auto* stack = &stacks_[{ref, ref_type}];
+ auto* stack = &stacks_[track_id];
auto* slices = context_->storage->mutable_nestable_slices();
const uint8_t depth = static_cast<uint8_t>(stack->size());
@@ -105,8 +111,8 @@
int64_t parent_stack_id =
depth == 0 ? 0 : slices->stack_ids()[stack->back().first];
uint32_t slice_idx =
- slices->AddSlice(timestamp, duration, ref, ref_type, category, name,
- depth, 0, parent_stack_id);
+ slices->AddSlice(timestamp, duration, track_id, ref, ref_type, category,
+ name, depth, 0, parent_stack_id);
stack->emplace_back(std::make_pair(slice_idx, ArgsTracker(context_)));
if (args_callback) {
@@ -137,12 +143,12 @@
}
UniqueTid utid =
context_->process_tracker->UpdateThread(ftrace_tid, actual_tgid);
- return End(timestamp, utid, RefType::kRefUtid);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ return End(timestamp, track_id);
}
base::Optional<uint32_t> SliceTracker::End(int64_t timestamp,
- int64_t ref,
- RefType ref_type,
+ TrackId track_id,
StringId category,
StringId name,
SetArgsCallback args_callback) {
@@ -153,10 +159,9 @@
}
prev_timestamp_ = timestamp;
- StackMapKey stack_key = {ref, ref_type};
- MaybeCloseStack(timestamp, &stacks_[stack_key]);
+ MaybeCloseStack(timestamp, &stacks_[track_id]);
- auto& stack = stacks_[stack_key];
+ auto& stack = stacks_[track_id];
if (stack.empty())
return base::nullopt;
@@ -179,7 +184,7 @@
TraceStorage::CreateRowId(TableId::kNestableSlices, slice_idx));
}
- return CompleteSlice(stack_key);
+ return CompleteSlice(track_id);
// TODO(primiano): auto-close B slices left open at the end.
}
@@ -194,8 +199,8 @@
stacks_.clear();
}
-base::Optional<uint32_t> SliceTracker::CompleteSlice(StackMapKey stack_key) {
- auto& stack = stacks_[stack_key];
+base::Optional<uint32_t> SliceTracker::CompleteSlice(TrackId track_id) {
+ auto& stack = stacks_[track_id];
uint32_t slice_idx = stack.back().first;
stack.pop_back();
return slice_idx;
diff --git a/src/trace_processor/slice_tracker.h b/src/trace_processor/slice_tracker.h
index 351aea0..658d32c 100644
--- a/src/trace_processor/slice_tracker.h
+++ b/src/trace_processor/slice_tracker.h
@@ -43,6 +43,7 @@
// virtual for testing
virtual base::Optional<uint32_t> Begin(
int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
@@ -52,6 +53,7 @@
// virtual for testing
virtual base::Optional<uint32_t> Scoped(
int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
@@ -66,8 +68,7 @@
// virtual for testing
virtual base::Optional<uint32_t> End(
int64_t timestamp,
- int64_t ref,
- RefType ref_type,
+ TrackId track_id,
StringId opt_category = {},
StringId opt_name = {},
SetArgsCallback args_callback = SetArgsCallback());
@@ -76,35 +77,17 @@
private:
using SlicesStack = std::vector<std::pair<uint32_t /* row */, ArgsTracker>>;
-
- struct StackMapKey {
- int64_t ref;
- RefType type;
-
- bool operator==(const StackMapKey& rhs) const {
- return std::tie(ref, type) == std::tie(rhs.ref, rhs.type);
- }
- };
-
- struct StackMapHash {
- size_t operator()(const StackMapKey& p) const {
- base::Hash hash;
- hash.Update(p.ref);
- hash.Update(p.type);
- return static_cast<size_t>(hash.digest());
- }
- };
-
- using StackMap = std::unordered_map<StackMapKey, SlicesStack, StackMapHash>;
+ using StackMap = std::unordered_map<TrackId, SlicesStack>;
base::Optional<uint32_t> StartSlice(int64_t timestamp,
int64_t duration,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
StringId name,
SetArgsCallback args_callback);
- base::Optional<uint32_t> CompleteSlice(StackMapKey stack_key);
+ base::Optional<uint32_t> CompleteSlice(TrackId track_id);
void MaybeCloseStack(int64_t end_ts, SlicesStack*);
int64_t GetStackHash(const SlicesStack&);
diff --git a/src/trace_processor/slice_tracker_unittest.cc b/src/trace_processor/slice_tracker_unittest.cc
index 203941e..b547ad1 100644
--- a/src/trace_processor/slice_tracker_unittest.cc
+++ b/src/trace_processor/slice_tracker_unittest.cc
@@ -55,17 +55,20 @@
context.storage.reset(new TraceStorage());
SliceTracker tracker(&context);
- tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
- tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
+ constexpr TrackId track = 22u;
+ tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 1 /*name*/);
+ tracker.End(10 /*ts*/, track, 0 /*cat*/, 1 /*name*/);
auto slices = context.storage->nestable_slices();
EXPECT_EQ(slices.slice_count(), 1u);
EXPECT_EQ(slices.start_ns()[0], 2);
EXPECT_EQ(slices.durations()[0], 8);
+ EXPECT_EQ(slices.track_id()[0], track);
EXPECT_EQ(slices.categories()[0], 0u);
EXPECT_EQ(slices.names()[0], 1u);
EXPECT_EQ(slices.refs()[0], 42);
- EXPECT_EQ(slices.types()[0], kRefUtid);
+ EXPECT_EQ(slices.types()[0], RefType::kRefUtid);
EXPECT_EQ(slices.depths()[0], 0);
EXPECT_EQ(slices.arg_set_ids()[0], kInvalidArgSetId);
}
@@ -75,12 +78,13 @@
context.storage.reset(new TraceStorage());
SliceTracker tracker(&context);
- tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
- [](ArgsTracker* args_tracker, RowId row) {
+ constexpr TrackId track = 22u;
+ tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 1 /*name*/, [](ArgsTracker* args_tracker, RowId row) {
args_tracker->AddArg(row, /*flat_key=*/1, /*key=*/2,
/*value=*/Variadic::Integer(10));
});
- tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
+ tracker.End(10 /*ts*/, track, 0 /*cat*/, 1 /*name*/,
[](ArgsTracker* args_tracker, RowId row) {
args_tracker->AddArg(row, /*flat_key=*/3, /*key=*/4,
/*value=*/Variadic::Integer(20));
@@ -90,10 +94,11 @@
EXPECT_EQ(slices.slice_count(), 1u);
EXPECT_EQ(slices.start_ns()[0], 2);
EXPECT_EQ(slices.durations()[0], 8);
+ EXPECT_EQ(slices.track_id()[0], track);
EXPECT_EQ(slices.categories()[0], 0u);
EXPECT_EQ(slices.names()[0], 1u);
EXPECT_EQ(slices.refs()[0], 42);
- EXPECT_EQ(slices.types()[0], kRefUtid);
+ EXPECT_EQ(slices.types()[0], RefType::kRefUtid);
EXPECT_EQ(slices.depths()[0], 0);
auto set_id = slices.arg_set_ids()[0];
@@ -113,10 +118,13 @@
context.storage.reset(new TraceStorage());
SliceTracker tracker(&context);
- tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
- tracker.Begin(3 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 2 /*name*/);
- tracker.End(5 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
- tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
+ constexpr TrackId track = 22u;
+ tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 1 /*name*/);
+ tracker.Begin(3 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 2 /*name*/);
+ tracker.End(5 /*ts*/, track);
+ tracker.End(10 /*ts*/, track);
auto slices = context.storage->nestable_slices();
@@ -125,18 +133,20 @@
size_t idx = 0;
EXPECT_EQ(slices.start_ns()[idx], 2);
EXPECT_EQ(slices.durations()[idx], 8);
+ EXPECT_EQ(slices.track_id()[idx], track);
EXPECT_EQ(slices.categories()[idx], 0u);
EXPECT_EQ(slices.names()[idx], 1u);
EXPECT_EQ(slices.refs()[idx], 42);
- EXPECT_EQ(slices.types()[idx], kRefUtid);
+ EXPECT_EQ(slices.types()[idx], RefType::kRefUtid);
EXPECT_EQ(slices.depths()[idx++], 0);
EXPECT_EQ(slices.start_ns()[idx], 3);
EXPECT_EQ(slices.durations()[idx], 2);
+ EXPECT_EQ(slices.track_id()[idx], track);
EXPECT_EQ(slices.categories()[idx], 0u);
EXPECT_EQ(slices.names()[idx], 2u);
EXPECT_EQ(slices.refs()[idx], 42);
- EXPECT_EQ(slices.types()[idx], kRefUtid);
+ EXPECT_EQ(slices.types()[idx], RefType::kRefUtid);
EXPECT_EQ(slices.depths()[idx], 1);
EXPECT_EQ(slices.parent_stack_ids()[0], 0);
@@ -149,11 +159,12 @@
context.storage.reset(new TraceStorage());
SliceTracker tracker(&context);
- tracker.Begin(0 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0);
- tracker.Begin(1 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0);
- tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0, 6);
- tracker.End(9 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
- tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
+ constexpr TrackId track = 22u;
+ tracker.Begin(0 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0, 0);
+ tracker.Begin(1 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0, 0);
+ tracker.Scoped(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0, 0, 6);
+ tracker.End(9 /*ts*/, track);
+ tracker.End(10 /*ts*/, track);
auto slices = ToSliceInfo(context.storage->nestable_slices());
EXPECT_THAT(slices,
@@ -165,10 +176,12 @@
context.storage.reset(new TraceStorage());
SliceTracker tracker(&context);
- tracker.Begin(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
- tracker.End(3 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 1 /*cat*/, 1 /*name*/);
- tracker.End(4 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 2 /*name*/);
- tracker.End(5 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/);
+ constexpr TrackId track = 22u;
+ tracker.Begin(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 1 /*name*/);
+ tracker.End(3 /*ts*/, track, 1 /*cat*/, 1 /*name*/);
+ tracker.End(4 /*ts*/, track, 0 /*cat*/, 2 /*name*/);
+ tracker.End(5 /*ts*/, track, 0 /*cat*/, 1 /*name*/);
auto slices = ToSliceInfo(context.storage->nestable_slices());
EXPECT_THAT(slices, ElementsAre(SliceInfo{2, 3}));
@@ -182,13 +195,14 @@
// Bug scenario: the second zero-length scoped slice prevents the first slice
// from being closed, leading to an inconsistency when we try to insert the
// final slice and it doesn't intersect with the still pending first slice.
- tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
- 10 /* dur */);
- tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/, 1 /*name*/,
- 0 /* dur */);
- tracker.Scoped(12 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ constexpr TrackId track = 22u;
+ tracker.Scoped(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 1 /*name*/, 10 /* dur */);
+ tracker.Scoped(2 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ 1 /*name*/, 0 /* dur */);
+ tracker.Scoped(12 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
1 /*name*/, 1 /* dur */);
- tracker.Scoped(13 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
+ tracker.Scoped(13 /*ts*/, track, 42 /*ref*/, RefType::kRefUtid, 0 /*cat*/,
1 /*name*/, 1 /* dur */);
auto slices = ToSliceInfo(context.storage->nestable_slices());
@@ -196,24 +210,26 @@
SliceInfo{12, 1}, SliceInfo{13, 1}));
}
-TEST(SliceTrackerTest, DifferentRefTypes) {
+TEST(SliceTrackerTest, DifferentTracks) {
TraceProcessorContext context;
context.storage.reset(new TraceStorage());
SliceTracker tracker(&context);
- tracker.Begin(0 /*ts*/, 42 /*ref*/, RefType::kRefUtid, 0, 0);
- tracker.Scoped(2 /*ts*/, 42 /*ref*/, RefType::kRefUpid, 0, 0, 6);
- tracker.Scoped(3 /*ts*/, 42 /*ref*/, RefType::kRefUpid, 0, 0, 4);
- tracker.End(10 /*ts*/, 42 /*ref*/, RefType::kRefUtid);
+ constexpr TrackId track_a = 22u;
+ constexpr TrackId track_b = 23u;
+ tracker.Begin(0 /*ts*/, track_a, 42 /*ref*/, RefType::kRefUtid, 0, 0);
+ tracker.Scoped(2 /*ts*/, track_b, 42 /*ref*/, RefType::kRefUtid, 0, 0, 6);
+ tracker.Scoped(3 /*ts*/, track_b, 42 /*ref*/, RefType::kRefUtid, 0, 0, 4);
+ tracker.End(10 /*ts*/, track_a);
tracker.FlushPendingSlices();
auto slices = ToSliceInfo(context.storage->nestable_slices());
EXPECT_THAT(slices,
ElementsAre(SliceInfo{0, 10}, SliceInfo{2, 6}, SliceInfo{3, 4}));
- EXPECT_EQ(context.storage->nestable_slices().types()[0], RefType::kRefUtid);
- EXPECT_EQ(context.storage->nestable_slices().types()[1], RefType::kRefUpid);
- EXPECT_EQ(context.storage->nestable_slices().types()[2], RefType::kRefUpid);
+ EXPECT_EQ(context.storage->nestable_slices().track_id()[0], track_a);
+ EXPECT_EQ(context.storage->nestable_slices().track_id()[1], track_b);
+ EXPECT_EQ(context.storage->nestable_slices().track_id()[2], track_b);
EXPECT_EQ(context.storage->nestable_slices().depths()[0], 0);
EXPECT_EQ(context.storage->nestable_slices().depths()[1], 0);
EXPECT_EQ(context.storage->nestable_slices().depths()[2], 1);
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 8853984..a2702d3 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -32,6 +32,8 @@
return FilterOp::kGt;
case SQLITE_INDEX_CONSTRAINT_LT:
return FilterOp::kLt;
+ case SQLITE_INDEX_CONSTRAINT_NE:
+ return FilterOp::kNeq;
default:
PERFETTO_FATAL("Currently unsupported constraint");
}
@@ -100,11 +102,43 @@
return util::OkStatus();
}
-int DbSqliteTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
- // TODO(lalitm): add proper cost estimation by looking at the columns.
+int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
+ // TODO(lalitm): investigate SQLITE_INDEX_SCAN_UNIQUE for id columns.
+ info->estimated_cost = static_cast<uint32_t>(EstimateCost(qc));
return SQLITE_OK;
}
+double DbSqliteTable::EstimateCost(const QueryConstraints& qc) {
+ // Currently our cost estimation algorithm is quite simplistic but is good
+ // enough for the simplest cases.
+ // TODO(lalitm): flesh out this algorithm to cover more complex cases.
+
+ // If we have no constraints, we always return the max possible value as we
+ // want to discourage the query planner from taking this road.
+ const auto& constraints = qc.constraints();
+ if (constraints.empty())
+ return std::numeric_limits<int32_t>::max();
+
+ // This means we have at least one constraint. Check if any of the constraints
+ // is an equality constraint on an id column.
+ auto id_filter = [this](const QueryConstraints::Constraint& c) {
+ uint32_t col_idx = static_cast<uint32_t>(c.iColumn);
+ const auto& col = table_->GetColumn(col_idx);
+ return sqlite_utils::IsOpEq(c.op) && col.IsId();
+ };
+
+ // If we have a eq constraint on an id column, we return 0 as it's an O(1)
+ // operation regardless of all the other constriants.
+ auto it = std::find_if(constraints.begin(), constraints.end(), id_filter);
+ if (it != constraints.end())
+ return 1;
+
+ // Otherwise, we divide the number of rows in the table by the number of
+ // constraints as a simple way of indiciating the more constraints we have
+ // the better we can do.
+ return table_->size() / constraints.size();
+}
+
std::unique_ptr<SqliteTable::Cursor> DbSqliteTable::CreateCursor() {
return std::unique_ptr<Cursor>(new Cursor(this));
}
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 97f94f4..39756aa 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -63,6 +63,8 @@
int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
private:
+ double EstimateCost(const QueryConstraints& qc);
+
const Table* table_ = nullptr;
};
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 83d1181..a30d87c 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -407,7 +407,10 @@
sqlite3_stmt* raw_stmt = nullptr;
int err = sqlite3_prepare_v2(db, sql, n, &raw_stmt, nullptr);
-
+ if (err != SQLITE_OK) {
+ PERFETTO_ELOG("Preparing database failed");
+ return {};
+ }
ScopedStmt stmt(raw_stmt);
PERFETTO_DCHECK(sqlite3_column_count(*stmt) == 2);
diff --git a/src/trace_processor/stack_profile_tracker.cc b/src/trace_processor/stack_profile_tracker.cc
index 8d0a157..be518f3 100644
--- a/src/trace_processor/stack_profile_tracker.cc
+++ b/src/trace_processor/stack_profile_tracker.cc
@@ -40,13 +40,15 @@
const InternLookup* intern_lookup) {
std::string path;
for (SourceStringId str_id : mapping.name_ids) {
- auto opt_str = FindString(str_id, intern_lookup);
+ auto opt_str =
+ FindString(str_id, intern_lookup, InternedStringType::kMappingPath);
if (!opt_str)
break;
path += "/" + *opt_str;
}
- auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup);
+ auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup,
+ InternedStringType::kBuildId);
if (!opt_build_id) {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
PERFETTO_DFATAL("Invalid string.");
@@ -86,7 +88,8 @@
int64_t StackProfileTracker::AddFrame(SourceFrameId id,
const SourceFrame& frame,
const InternLookup* intern_lookup) {
- auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup);
+ auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup,
+ InternedStringType::kFunctionName);
if (!opt_str_id) {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
PERFETTO_DFATAL("Invalid string.");
@@ -170,11 +173,12 @@
base::Optional<StringId> StackProfileTracker::FindAndInternString(
SourceStringId id,
- const InternLookup* intern_lookup) {
+ const InternLookup* intern_lookup,
+ StackProfileTracker::InternedStringType type) {
if (id == 0)
return empty_;
- auto opt_str = FindString(id, intern_lookup);
+ auto opt_str = FindString(id, intern_lookup, type);
if (!opt_str)
return empty_;
@@ -183,18 +187,20 @@
base::Optional<std::string> StackProfileTracker::FindString(
SourceStringId id,
- const InternLookup* intern_lookup) {
+ const InternLookup* intern_lookup,
+ StackProfileTracker::InternedStringType type) {
if (id == 0)
return "";
auto it = string_map_.find(id);
if (it == string_map_.end()) {
if (intern_lookup) {
- auto str = intern_lookup->GetString(id);
+ auto str = intern_lookup->GetString(id, type);
if (!str) {
context_->storage->IncrementStats(
stats::stackprofile_invalid_string_id);
PERFETTO_DFATAL("Invalid string.");
+ return base::nullopt;
}
return str->ToStdString();
}
diff --git a/src/trace_processor/stack_profile_tracker.h b/src/trace_processor/stack_profile_tracker.h
index caf5b91..114865c 100644
--- a/src/trace_processor/stack_profile_tracker.h
+++ b/src/trace_processor/stack_profile_tracker.h
@@ -60,6 +60,12 @@
public:
using SourceStringId = uint64_t;
+ enum class InternedStringType {
+ kMappingPath,
+ kBuildId,
+ kFunctionName,
+ };
+
struct SourceMapping {
SourceStringId build_id = 0;
uint64_t exact_offset = 0;
@@ -98,7 +104,8 @@
virtual ~InternLookup();
virtual base::Optional<base::StringView> GetString(
- SourceStringId) const = 0;
+ SourceStringId,
+ InternedStringType) const = 0;
virtual base::Optional<SourceMapping> GetMapping(SourceMappingId) const = 0;
virtual base::Optional<SourceFrame> GetFrame(SourceFrameId) const = 0;
virtual base::Optional<SourceCallstack> GetCallstack(
@@ -133,9 +140,11 @@
// InternedData (for versions newer than Q).
base::Optional<StringId> FindAndInternString(
SourceStringId,
- const InternLookup* intern_lookup);
+ const InternLookup* intern_lookup,
+ InternedStringType type);
base::Optional<std::string> FindString(SourceStringId,
- const InternLookup* intern_lookup);
+ const InternLookup* intern_lookup,
+ InternedStringType type);
base::Optional<int64_t> FindMapping(SourceMappingId,
const InternLookup* intern_lookup);
base::Optional<int64_t> FindFrame(SourceFrameId,
diff --git a/src/trace_processor/stats.h b/src/trace_processor/stats.h
index b7bd13e..0fce227 100644
--- a/src/trace_processor/stats.h
+++ b/src/trace_processor/stats.h
@@ -102,7 +102,7 @@
F(traced_tracing_sessions, kSingle, kInfo, kTrace), \
F(track_event_parser_errors, kSingle, kInfo, kAnalysis), \
F(track_event_tokenizer_errors, kSingle, kInfo, kAnalysis), \
- F(track_event_tokenizer_skipped_packets, kSingle, kInfo, kAnalysis), \
+ F(tokenizer_skipped_packets, kSingle, kInfo, kAnalysis), \
F(vmstat_unknown_keys, kSingle, kError, kAnalysis), \
F(clock_sync_failure, kSingle, kError, kAnalysis), \
F(process_tracker_errors, kSingle, kError, kAnalysis), \
@@ -112,7 +112,8 @@
F(heapprofd_rejected_concurrent, kIndexed, kError, kTrace), \
F(metatrace_overruns, kSingle, kError, kTrace), \
F(packages_list_has_parse_errors, kSingle, kError, kTrace), \
- F(packages_list_has_read_errors, kSingle, kError, kTrace)
+ F(packages_list_has_read_errors, kSingle, kError, kTrace), \
+ F(compact_sched_has_parse_errors, kSingle, kError, kTrace)
// clang-format on
enum Type {
diff --git a/src/trace_processor/storage_columns.h b/src/trace_processor/storage_columns.h
index 5bde9c9..83e855b 100644
--- a/src/trace_processor/storage_columns.h
+++ b/src/trace_processor/storage_columns.h
@@ -334,7 +334,7 @@
}
NullTermStringView Get(uint32_t idx) const override {
- const char* ptr = (*string_map_)[(*deque_)[idx]];
+ const char* ptr = (*string_map_)[static_cast<size_t>((*deque_)[idx])];
return ptr ? NullTermStringView(ptr) : NullTermStringView();
}
diff --git a/src/trace_processor/syscall_tracker.h b/src/trace_processor/syscall_tracker.h
index 76523dd..f367309 100644
--- a/src/trace_processor/syscall_tracker.h
+++ b/src/trace_processor/syscall_tracker.h
@@ -24,6 +24,7 @@
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -50,16 +51,17 @@
void Enter(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
StringId name = SyscallNumberToStringId(syscall_num);
if (!name.is_null()) {
- context_->slice_tracker->Begin(ts, utid, RefType::kRefUtid, 0 /* cat */,
- name);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ context_->slice_tracker->Begin(ts, track_id, utid, RefType::kRefUtid,
+ 0 /* cat */, name);
}
}
void Exit(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
StringId name = SyscallNumberToStringId(syscall_num);
if (!name.is_null()) {
- context_->slice_tracker->End(ts, utid, RefType::kRefUtid, 0 /* cat */,
- name);
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ context_->slice_tracker->End(ts, track_id, 0 /* cat */, name);
}
}
diff --git a/src/trace_processor/syscall_tracker_unittest.cc b/src/trace_processor/syscall_tracker_unittest.cc
index fa5fce5..70bf075 100644
--- a/src/trace_processor/syscall_tracker_unittest.cc
+++ b/src/trace_processor/syscall_tracker_unittest.cc
@@ -32,17 +32,17 @@
MockSliceTracker(TraceProcessorContext* context) : SliceTracker(context) {}
virtual ~MockSliceTracker() = default;
- MOCK_METHOD6(Begin,
+ MOCK_METHOD7(Begin,
base::Optional<uint32_t>(int64_t timestamp,
+ TrackId track_id,
int64_t ref,
RefType ref_type,
StringId cat,
StringId name,
SetArgsCallback args_callback));
- MOCK_METHOD6(End,
+ MOCK_METHOD5(End,
base::Optional<uint32_t>(int64_t timestamp,
- int64_t ref,
- RefType ref_type,
+ TrackId track_id,
StringId cat,
StringId name,
SetArgsCallback args_callback));
@@ -52,6 +52,8 @@
public:
SyscallTrackerTest() {
context.storage.reset(new TraceStorage());
+ track_tracker = new TrackTracker(&context);
+ context.track_tracker.reset(track_tracker);
slice_tracker = new MockSliceTracker(&context);
context.slice_tracker.reset(slice_tracker);
context.syscall_tracker.reset(new SyscallTracker(&context));
@@ -60,17 +62,18 @@
protected:
TraceProcessorContext context;
MockSliceTracker* slice_tracker;
+ TrackTracker* track_tracker;
};
TEST_F(SyscallTrackerTest, ReportUnknownSyscalls) {
+ constexpr TrackId track = 0u;
StringId begin_name = 0;
StringId end_name = 0;
EXPECT_CALL(*slice_tracker,
- Begin(100, 42, RefType::kRefUtid, kNullStringId, _, _))
- .WillOnce(DoAll(SaveArg<4>(&begin_name), Return(base::nullopt)));
- EXPECT_CALL(*slice_tracker,
- End(110, 42, RefType::kRefUtid, kNullStringId, _, _))
- .WillOnce(DoAll(SaveArg<4>(&end_name), Return(base::nullopt)));
+ Begin(100, track, 42, RefType::kRefUtid, kNullStringId, _, _))
+ .WillOnce(DoAll(SaveArg<5>(&begin_name), Return(base::nullopt)));
+ EXPECT_CALL(*slice_tracker, End(110, track, kNullStringId, _, _))
+ .WillOnce(DoAll(SaveArg<3>(&end_name), Return(base::nullopt)));
context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 57 /*sys_read*/);
context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 57 /*sys_read*/);
@@ -80,22 +83,22 @@
TEST_F(SyscallTrackerTest, IgnoreWriteSyscalls) {
context.syscall_tracker->SetArchitecture(kAarch64);
- EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _, _)).Times(0);
- EXPECT_CALL(*slice_tracker, End(_, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*slice_tracker, End(_, _, _, _, _)).Times(0);
context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 64 /*sys_write*/);
context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 64 /*sys_write*/);
}
TEST_F(SyscallTrackerTest, Aarch64) {
+ constexpr TrackId track = 0u;
StringId begin_name = 0;
StringId end_name = 0;
EXPECT_CALL(*slice_tracker,
- Begin(100, 42, RefType::kRefUtid, kNullStringId, _, _))
- .WillOnce(DoAll(SaveArg<4>(&begin_name), Return(base::nullopt)));
- EXPECT_CALL(*slice_tracker,
- End(110, 42, RefType::kRefUtid, kNullStringId, _, _))
- .WillOnce(DoAll(SaveArg<4>(&end_name), Return(base::nullopt)));
+ Begin(100, track, 42, RefType::kRefUtid, kNullStringId, _, _))
+ .WillOnce(DoAll(SaveArg<5>(&begin_name), Return(base::nullopt)));
+ EXPECT_CALL(*slice_tracker, End(110, track, kNullStringId, _, _))
+ .WillOnce(DoAll(SaveArg<3>(&end_name), Return(base::nullopt)));
context.syscall_tracker->SetArchitecture(kAarch64);
context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 63 /*sys_read*/);
@@ -105,14 +108,14 @@
}
TEST_F(SyscallTrackerTest, x8664) {
+ constexpr TrackId track = 0u;
StringId begin_name = 0;
StringId end_name = 0;
EXPECT_CALL(*slice_tracker,
- Begin(100, 42, RefType::kRefUtid, kNullStringId, _, _))
- .WillOnce(DoAll(SaveArg<4>(&begin_name), Return(base::nullopt)));
- EXPECT_CALL(*slice_tracker,
- End(110, 42, RefType::kRefUtid, kNullStringId, _, _))
- .WillOnce(DoAll(SaveArg<4>(&end_name), Return(base::nullopt)));
+ Begin(100, track, 42, RefType::kRefUtid, kNullStringId, _, _))
+ .WillOnce(DoAll(SaveArg<5>(&begin_name), Return(base::nullopt)));
+ EXPECT_CALL(*slice_tracker, End(110, track, kNullStringId, _, _))
+ .WillOnce(DoAll(SaveArg<3>(&end_name), Return(base::nullopt)));
context.syscall_tracker->SetArchitecture(kX86_64);
context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 0 /*sys_read*/);
@@ -122,8 +125,8 @@
}
TEST_F(SyscallTrackerTest, SyscallNumberTooLarge) {
- EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _, _)).Times(0);
- EXPECT_CALL(*slice_tracker, End(_, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*slice_tracker, Begin(_, _, _, _, _, _, _)).Times(0);
+ EXPECT_CALL(*slice_tracker, End(_, _, _, _, _)).Times(0);
context.syscall_tracker->SetArchitecture(kAarch64);
context.syscall_tracker->Enter(100 /*ts*/, 42 /*utid*/, 9999);
context.syscall_tracker->Exit(110 /*ts*/, 42 /*utid*/, 9999);
diff --git a/src/trace_processor/systrace_parser.cc b/src/trace_processor/systrace_parser.cc
index ec5d4c5..6bb6b72 100644
--- a/src/trace_processor/systrace_parser.cc
+++ b/src/trace_processor/systrace_parser.cc
@@ -20,6 +20,7 @@
#include "src/trace_processor/event_tracker.h"
#include "src/trace_processor/process_tracker.h"
#include "src/trace_processor/slice_tracker.h"
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -90,13 +91,21 @@
break;
}
- case 'S': {
- // Currently unsupported.
- break;
- }
-
+ case 'S':
case 'F': {
- // Currently unsupported.
+ StringId name_id = context_->storage->InternString(point.name);
+ int64_t cookie = static_cast<int64_t>(point.value);
+ UniquePid upid =
+ context_->process_tracker->GetOrCreateProcess(point.tgid);
+
+ TrackId track_id = context_->track_tracker->InternAndroidAsyncTrack(
+ name_id, upid, cookie);
+ if (point.phase == 'S') {
+ context_->slice_tracker->Begin(ts, track_id, track_id,
+ RefType::kRefTrack, 0, name_id);
+ } else {
+ context_->slice_tracker->End(ts, track_id);
+ }
break;
}
diff --git a/src/trace_processor/systrace_parser.h b/src/trace_processor/systrace_parser.h
index f85e1dd..c8c3452 100644
--- a/src/trace_processor/systrace_parser.h
+++ b/src/trace_processor/systrace_parser.h
@@ -99,6 +99,11 @@
size_t len = str.size();
*out = {};
+ constexpr const char* kClockSyncPrefix = "trace_event_clock_sync:";
+ if (len >= strlen(kClockSyncPrefix) &&
+ strncmp(kClockSyncPrefix, s, strlen(kClockSyncPrefix)) == 0)
+ return SystraceParseResult::kUnsupported;
+
if (len < 2)
return SystraceParseResult::kFailure;
diff --git a/src/trace_processor/systrace_parser_unittest.cc b/src/trace_processor/systrace_parser_unittest.cc
index 5d6f32e..d1a1158 100644
--- a/src/trace_processor/systrace_parser_unittest.cc
+++ b/src/trace_processor/systrace_parser_unittest.cc
@@ -69,6 +69,13 @@
ASSERT_EQ(ParseSystraceTracePoint("F|123|foo|456", &result),
Result::kSuccess);
EXPECT_EQ(result, SystraceTracePoint::F(123, "foo", 456));
+
+ ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: parent_ts=0.123\n",
+ &result),
+ Result::kUnsupported);
+ ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: realtime_ts=123\n",
+ &result),
+ Result::kUnsupported);
}
} // namespace
diff --git a/src/trace_processor/systrace_trace_parser.cc b/src/trace_processor/systrace_trace_parser.cc
index 3d81e52..777f262 100644
--- a/src/trace_processor/systrace_trace_parser.cc
+++ b/src/trace_processor/systrace_trace_parser.cc
@@ -34,7 +34,7 @@
namespace {
std::string SubstrTrim(const std::string& input, size_t start, size_t end) {
- auto s = input.substr(start, end - start);
+ std::string s = input.substr(start, end - start);
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
[](int ch) { return !std::isspace(ch); }));
s.erase(std::find_if(s.rbegin(), s.rend(),
@@ -44,6 +44,17 @@
return s;
}
+std::pair<size_t, size_t> FindTask(const std::string& line) {
+ size_t start;
+ for (start = 0; start < line.size() && isspace(line[start]); ++start)
+ ;
+ size_t length;
+ for (length = 0; start + length < line.size() && line[start + length] != '-';
+ ++length)
+ ;
+ return std::pair<size_t, size_t>(start, length);
+}
+
} // namespace
SystraceTraceParser::SystraceTraceParser(TraceProcessorContext* ctx)
@@ -94,6 +105,8 @@
return util::OkStatus();
}
+// TODO(hjd): This should be more robust to being passed random input.
+// This can happen if we mess up detecting a gzip trace for example.
util::Status SystraceTraceParser::ParseSingleSystraceEvent(
const std::string& buffer) {
// An example line from buffer looks something like the following:
@@ -102,8 +115,12 @@
// However, sometimes the tgid can be missing and buffer looks like this:
// <idle>-0 [000] ...2 0.002188: task_newtask: pid=1 ...
- auto task_idx = 16u;
- std::string task = SubstrTrim(buffer, 0, task_idx);
+ size_t task_start;
+ size_t task_length;
+ std::tie<size_t, size_t>(task_start, task_length) = FindTask(buffer);
+
+ size_t task_idx = task_start + task_length;
+ std::string task = buffer.substr(task_start, task_length);
// Try and figure out whether tgid is present by searching for '(' but only
// if it occurs before the start of cpu (indiciated by '[') - this is because
@@ -112,6 +129,10 @@
auto cpu_idx = buffer.find('[', task_idx + 1);
bool has_tgid = tgid_idx != std::string::npos && tgid_idx < cpu_idx;
+ if (cpu_idx == std::string::npos) {
+ return util::Status("Could not find [ in " + buffer);
+ }
+
auto pid_end = has_tgid ? cpu_idx : tgid_idx;
std::string pid_str = SubstrTrim(buffer, task_idx + 1, pid_end);
auto pid = static_cast<uint32_t>(std::stoi(pid_str));
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 519bae0..70068ac 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -192,11 +192,16 @@
#define PERFETTO_TP_COLUMN_APPEND(type, name, ...) \
name##_.Append(std::move(row.name));
-// Defines the accessor for a column.
-#define PERFETTO_TP_TABLE_COL_ACCESSOR(type, name, ...) \
- const TypedColumn<type>& name() const { \
- return static_cast<const TypedColumn<type>&>( \
- columns_[static_cast<uint32_t>(ColumnIndex::name)]); \
+// Defines the accessors for a column.
+#define PERFETTO_TP_TABLE_COL_ACCESSOR(type, name, ...) \
+ const TypedColumn<type>& name() const { \
+ return static_cast<const TypedColumn<type>&>( \
+ columns_[static_cast<uint32_t>(ColumnIndex::name)]); \
+ } \
+ \
+ TypedColumn<type>* mutable_##name() { \
+ return static_cast<TypedColumn<type>*>( \
+ &columns_[static_cast<uint32_t>(ColumnIndex::name)]); \
}
// Definition used as the parent of root tables.
@@ -278,8 +283,10 @@
} \
\
/* Expands to \
- * const SparseVector<col1_type>& col1() { return col1_; } \
- * const SparseVector<col2_type>& col2() { return col2_; } \
+ * const TypedColumn<col1_type>& col1() { return col1_; } \
+ * TypedColumn<col1_type>* mutable_col1() { return &col1_; } \
+ * const TypedColumn<col2_type>& col2() { return col2_; } \
+ * TypedColumn<col2_type>* mutable_col2() { return &col2_; } \
* ... \
*/ \
PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_COL_ACCESSOR) \
diff --git a/src/trace_processor/tables/track_tables.h b/src/trace_processor/tables/track_tables.h
index 79abe06..76a4417 100644
--- a/src/trace_processor/tables/track_tables.h
+++ b/src/trace_processor/tables/track_tables.h
@@ -27,30 +27,24 @@
#define PERFETTO_TP_TRACK_TABLE_DEF(NAME, PARENT, C) \
NAME(TrackTable, "track") \
PERFETTO_TP_ROOT_TABLE(PARENT, C) \
- C(StringPool::Id, name)
+ C(StringPool::Id, name) \
+ C(base::Optional<uint32_t>, source_arg_set_id)
PERFETTO_TP_TABLE(PERFETTO_TP_TRACK_TABLE_DEF);
-#define PERFETTO_TP_CHROME_SCOPED_TRACK_TABLE_DEF(NAME, PARENT, C) \
- NAME(ChromeScopedTrackTable, "chrome_scoped_track") \
- PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C) \
- C(base::Optional<uint32_t>, upid)
+#define PERFETTO_TP_PROCESS_TRACK_TABLE_DEF(NAME, PARENT, C) \
+ NAME(ProcessTrackTable, "process_track") \
+ PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C) \
+ C(uint32_t, upid)
-PERFETTO_TP_TABLE(PERFETTO_TP_CHROME_SCOPED_TRACK_TABLE_DEF);
+PERFETTO_TP_TABLE(PERFETTO_TP_PROCESS_TRACK_TABLE_DEF);
-#define PERFETTO_TP_CHROME_ASYNC_TRACK_TABLE_DEF(NAME, PARENT, C) \
- NAME(ChromeAsyncTrackTable, "chrome_async_track") \
- PARENT(PERFETTO_TP_CHROME_SCOPED_TRACK_TABLE_DEF, C) \
- C(int64_t, async_id)
+#define PERFETTO_TP_THREAD_TRACK_TABLE_DEF(NAME, PARENT, C) \
+ NAME(ThreadTrackTable, "thread_track") \
+ PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C) \
+ C(uint32_t, utid)
-PERFETTO_TP_TABLE(PERFETTO_TP_CHROME_ASYNC_TRACK_TABLE_DEF);
-
-#define PERFETTO_TP_FUCHSIA_ASYNC_TRACK_TABLE_DEF(NAME, PARENT, C) \
- NAME(FuchsiaAsyncTrackTable, "fuchsia_async_track") \
- PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C) \
- C(int64_t, correlation_id)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_FUCHSIA_ASYNC_TRACK_TABLE_DEF);
+PERFETTO_TP_TABLE(PERFETTO_TP_THREAD_TRACK_TABLE_DEF);
#define PERFETTO_TP_GPU_TRACK_DEF(NAME, PARENT, C) \
NAME(GpuTrackTable, "gpu_track") \
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index b0318ad..3b73e0e 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -306,9 +306,10 @@
const TraceStorage* storage = context_.storage.get();
DbSqliteTable::RegisterTable(*db_, &storage->track_table(),
storage->track_table().table_name());
- DbSqliteTable::RegisterTable(
- *db_, &storage->chrome_async_track_table(),
- storage->chrome_async_track_table().table_name());
+ DbSqliteTable::RegisterTable(*db_, &storage->thread_track_table(),
+ storage->thread_track_table().table_name());
+ DbSqliteTable::RegisterTable(*db_, &storage->process_track_table(),
+ storage->process_track_table().table_name());
DbSqliteTable::RegisterTable(*db_, &storage->gpu_slice_table(),
storage->gpu_slice_table().table_name());
DbSqliteTable::RegisterTable(*db_, &storage->gpu_track_table(),
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 342613d..d8eb471 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -771,31 +771,34 @@
Usage: %s [OPTIONS] trace_file.pb
Options:
- -h, --help Prints this guide.
- -v, --version Prints the version of trace processor.
- -d, --debug Enable virtual table debugging.
- -W, --wide Prints interactive output with double column
- width.
- -p, --perf-file FILE Writes the time taken to ingest the trace and
- execute the queries to the given file. Only
- valid with -q or --run-metrics and the file
- will only be written if the execution
- is successful.
- -q, --query-file FILE Read and execute an SQL query from a file.
- -i, --interactive Starts interactive mode even after a query file
- is specified with -q or --run-metrics.
- -e, --export FILE Export the trace into a SQLite database.
- --run-metrics x,y,z Runs a comma separated list of metrics and
- prints the result as a TraceMetrics proto to
- stdout. The specified can either be in-built
- metrics or SQL/proto files of extension
- metrics.
- --metrics-output=[binary|text] Allows the output of --run-metrics to be
- specified in either proto binary or proto
- text format (default: text).
- --extra-metrics PATH Registers all SQL files at the given path to
- the trace processor and extends the builtin
- metrics proto with $PATH/metrics-ext.proto.)",
+ -h, --help Prints this guide.
+ -v, --version Prints the version of trace processor.
+ -d, --debug Enable virtual table debugging.
+ -W, --wide Prints interactive output with double
+ column width.
+ -p, --perf-file FILE Writes the time taken to ingest the trace
+ and execute the queries to the given file.
+ Only valid with -q or --run-metrics and
+ the file will only be written if the
+ execution is successful.
+ -q, --query-file FILE Read and execute an SQL query from a file.
+ -i, --interactive Starts interactive mode even after a query
+ file is specified with -q or
+ --run-metrics.
+ -e, --export FILE Export the trace into a SQLite database.
+ --run-metrics x,y,z Runs a comma separated list of metrics and
+ prints the result as a TraceMetrics proto
+ to stdout. The specified can either be
+ in-built metrics or SQL/proto files of
+ extension metrics.
+ --metrics-output=[binary|text|json] Allows the output of --run-metrics to be
+ specified in either proto binary, proto
+ text format or JSON format (default: proto
+ text).
+ --extra-metrics PATH Registers all SQL files at the given path
+ to the trace processor and extends the
+ builtin metrics proto with
+ $PATH/metrics-ext.proto.)",
argv[0]);
}
diff --git a/src/trace_processor/trace_sorter.cc b/src/trace_processor/trace_sorter.cc
index c08ada8..6209890 100644
--- a/src/trace_processor/trace_sorter.cc
+++ b/src/trace_processor/trace_sorter.cc
@@ -40,7 +40,7 @@
// smaller than max_ts_.
PERFETTO_DCHECK(sort_min_ts_ < max_ts_);
- // We know that all events between [0, sort_start_idx_] are sorted. Witin
+ // We know that all events between [0, sort_start_idx_] are sorted. Within
// this range, perform a bound search and find the iterator for the min
// timestamp that broke the monotonicity. Re-sort from there to the end.
auto sort_end = events_.begin() + static_cast<ssize_t>(sort_start_idx_);
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 873f965..992c981 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -68,6 +68,31 @@
// from there to the end.
class TraceSorter {
public:
+ struct InlineSchedSwitch {
+ int64_t prev_state;
+ int32_t next_pid;
+ int32_t next_prio;
+ StringId next_comm;
+ };
+
+ // Discriminated union of events that are cannot be easily read from the
+ // mapped trace.
+ struct InlineEvent {
+ enum class Type { kInvalid = 0, kSchedSwitch };
+
+ static InlineEvent SchedSwitch(InlineSchedSwitch content) {
+ InlineEvent evt;
+ evt.type = Type::kSchedSwitch;
+ evt.sched_switch = content;
+ return evt;
+ }
+
+ Type type = Type::kInvalid;
+ union {
+ InlineSchedSwitch sched_switch;
+ };
+ };
+
struct TimestampedTracePiece {
TimestampedTracePiece(
int64_t ts,
@@ -81,7 +106,8 @@
std::move(tbv),
/*value=*/nullptr,
/*fpv=*/nullptr,
- /*sequence_state=*/sequence_state) {}
+ /*sequence_state=*/sequence_state,
+ InlineEvent{}) {}
TimestampedTracePiece(int64_t ts, uint64_t idx, TraceBlobView tbv)
: TimestampedTracePiece(ts,
@@ -91,7 +117,8 @@
std::move(tbv),
/*value=*/nullptr,
/*fpv=*/nullptr,
- /*sequence_state=*/nullptr) {}
+ /*sequence_state=*/nullptr,
+ InlineEvent{}) {}
TimestampedTracePiece(int64_t ts,
uint64_t idx,
@@ -105,7 +132,8 @@
TraceBlobView(nullptr, 0, 0),
std::move(value),
/*fpv=*/nullptr,
- /*sequence_state=*/nullptr) {}
+ /*sequence_state=*/nullptr,
+ InlineEvent{}) {}
TimestampedTracePiece(int64_t ts,
uint64_t idx,
@@ -118,7 +146,8 @@
std::move(tbv),
/*value=*/nullptr,
std::move(fpv),
- /*sequence_state=*/nullptr) {}
+ /*sequence_state=*/nullptr,
+ InlineEvent{}) {}
TimestampedTracePiece(
int64_t ts,
@@ -134,7 +163,19 @@
std::move(tbv),
/*value=*/nullptr,
/*fpv=*/nullptr,
- sequence_state) {}
+ sequence_state,
+ InlineEvent{}) {}
+
+ TimestampedTracePiece(int64_t ts, uint64_t idx, InlineEvent inline_evt)
+ : TimestampedTracePiece(ts,
+ /*thread_ts=*/0,
+ /*thread_instructions=*/0,
+ idx,
+ /*tbv=*/TraceBlobView(nullptr, 0, 0),
+ /*value=*/nullptr,
+ /*fpv=*/nullptr,
+ /*sequence_state=*/nullptr,
+ inline_evt) {}
TimestampedTracePiece(
int64_t ts,
@@ -144,7 +185,8 @@
TraceBlobView tbv,
std::unique_ptr<Json::Value> value,
std::unique_ptr<FuchsiaProviderView> fpv,
- ProtoIncrementalState::PacketSequenceState* sequence_state)
+ ProtoIncrementalState::PacketSequenceState* sequence_state,
+ InlineEvent inline_evt)
: json_value(std::move(value)),
fuchsia_provider_view(std::move(fpv)),
packet_sequence_state(sequence_state),
@@ -152,7 +194,8 @@
thread_timestamp(thread_ts),
thread_instruction_count(thread_instructions),
packet_idx_(idx),
- blob_view(std::move(tbv)) {}
+ blob_view(std::move(tbv)),
+ inline_event(inline_evt) {}
TimestampedTracePiece(TimestampedTracePiece&&) noexcept = default;
TimestampedTracePiece& operator=(TimestampedTracePiece&&) = default;
@@ -177,6 +220,7 @@
int64_t thread_instruction_count;
uint64_t packet_idx_;
TraceBlobView blob_view;
+ InlineEvent inline_event;
};
TraceSorter(TraceProcessorContext*, int64_t window_size_ns);
@@ -223,6 +267,21 @@
// for a bundle are pushed.
}
+ // As with |PushFtraceEvent|, doesn't immediately sort the affected queues.
+ // TODO(rsavitski): if a trace has a mix of normal & "compact" events (being
+ // pushed through this function), the ftrace batches will no longer be fully
+ // sorted by timestamp. In such situations, we will have to sort at the end of
+ // the batch. We can do better as both sub-sequences are sorted however.
+ // Consider adding extra queues, or pushing them in a merge-sort fashion
+ // instead.
+ inline void PushInlineFtraceEvent(uint32_t cpu,
+ int64_t timestamp,
+ InlineEvent inline_event) {
+ set_ftrace_batch_cpu_for_DCHECK(cpu);
+ GetQueue(cpu + 1)->Append(
+ TimestampedTracePiece(timestamp, packet_idx_++, inline_event));
+ }
+
inline void PushTrackEventPacket(
int64_t timestamp,
int64_t thread_time,
diff --git a/src/trace_processor/trace_storage.cc b/src/trace_processor/trace_storage.cc
index 4fed429..662c460 100644
--- a/src/trace_processor/trace_storage.cc
+++ b/src/trace_processor/trace_storage.cc
@@ -41,15 +41,15 @@
}
std::vector<const char*> CreateRefTypeStringMap() {
- std::vector<const char*> map(RefType::kRefMax);
- map[RefType::kRefNoRef] = nullptr;
- map[RefType::kRefUtid] = "utid";
- map[RefType::kRefCpuId] = "cpu";
- map[RefType::kRefGpuId] = "gpu";
- map[RefType::kRefIrq] = "irq";
- map[RefType::kRefSoftIrq] = "softirq";
- map[RefType::kRefUpid] = "upid";
- map[RefType::kRefTrack] = "track";
+ std::vector<const char*> map(static_cast<size_t>(RefType::kRefMax));
+ map[static_cast<size_t>(RefType::kRefNoRef)] = nullptr;
+ map[static_cast<size_t>(RefType::kRefUtid)] = "utid";
+ map[static_cast<size_t>(RefType::kRefCpuId)] = "cpu";
+ map[static_cast<size_t>(RefType::kRefGpuId)] = "gpu";
+ map[static_cast<size_t>(RefType::kRefIrq)] = "irq";
+ map[static_cast<size_t>(RefType::kRefSoftIrq)] = "softirq";
+ map[static_cast<size_t>(RefType::kRefUpid)] = "upid";
+ map[static_cast<size_t>(RefType::kRefTrack)] = "track";
return map;
}
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index e357180..6b17cae 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -66,6 +66,7 @@
kSched = 4,
kNestableSlices = 5,
kMetadataTable = 6,
+ kTrack = 7,
};
// The top 8 bits are set to the TableId and the bottom 32 to the row of the
@@ -78,7 +79,7 @@
using TrackId = uint32_t;
-enum RefType {
+enum class RefType {
kRefNoRef = 0,
kRefUtid = 1,
kRefCpuId = 2,
@@ -319,6 +320,7 @@
public:
inline uint32_t AddSlice(int64_t start_ns,
int64_t duration_ns,
+ TrackId track_id,
int64_t ref,
RefType type,
StringId category,
@@ -328,6 +330,7 @@
int64_t parent_stack_id) {
start_ns_.emplace_back(start_ns);
durations_.emplace_back(duration_ns);
+ track_id_.emplace_back(track_id);
refs_.emplace_back(ref);
types_.emplace_back(type);
categories_.emplace_back(category);
@@ -357,6 +360,7 @@
const std::deque<int64_t>& start_ns() const { return start_ns_; }
const std::deque<int64_t>& durations() const { return durations_; }
+ const std::deque<TrackId>& track_id() const { return track_id_; }
const std::deque<int64_t>& refs() const { return refs_; }
const std::deque<RefType>& types() const { return types_; }
const std::deque<StringId>& categories() const { return categories_; }
@@ -371,6 +375,7 @@
private:
std::deque<int64_t> start_ns_;
std::deque<int64_t> durations_;
+ std::deque<TrackId> track_id_;
std::deque<int64_t> refs_;
std::deque<RefType> types_;
std::deque<StringId> categories_;
@@ -1146,7 +1151,8 @@
return unique_processes_[upid];
}
- const Thread& GetThread(UniqueTid utid) const {
+ // Virtual for testing.
+ virtual const Thread& GetThread(UniqueTid utid) const {
// Allow utid == 0 for idle thread retrieval.
PERFETTO_DCHECK(utid < unique_threads_.size());
return unique_threads_[utid];
@@ -1166,18 +1172,18 @@
const tables::TrackTable& track_table() const { return track_table_; }
tables::TrackTable* mutable_track_table() { return &track_table_; }
- const tables::ChromeAsyncTrackTable& chrome_async_track_table() const {
- return chrome_async_track_table_;
+ const tables::ProcessTrackTable& process_track_table() const {
+ return process_track_table_;
}
- tables::ChromeAsyncTrackTable* mutable_chrome_async_track_table() {
- return &chrome_async_track_table_;
+ tables::ProcessTrackTable* mutable_process_track_table() {
+ return &process_track_table_;
}
- const tables::FuchsiaAsyncTrackTable& fuchsia_async_track_table() const {
- return fuchsia_async_track_table_;
+ const tables::ThreadTrackTable& thread_track_table() const {
+ return thread_track_table_;
}
- tables::FuchsiaAsyncTrackTable* mutable_fuchsia_async_track_table() {
- return &fuchsia_async_track_table_;
+ tables::ThreadTrackTable* mutable_thread_track_table() {
+ return &thread_track_table_;
}
const Slices& slices() const { return slices_; }
@@ -1316,12 +1322,8 @@
// Metadata for tracks.
tables::TrackTable track_table_{&string_pool_, nullptr};
tables::GpuTrackTable gpu_track_table_{&string_pool_, &track_table_};
- tables::FuchsiaAsyncTrackTable fuchsia_async_track_table_{&string_pool_,
- &track_table_};
- tables::ChromeScopedTrackTable chrome_scoped_track_table_{&string_pool_,
- &track_table_};
- tables::ChromeAsyncTrackTable chrome_async_track_table_{
- &string_pool_, &chrome_scoped_track_table_};
+ tables::ProcessTrackTable process_track_table_{&string_pool_, &track_table_};
+ tables::ThreadTrackTable thread_track_table_{&string_pool_, &track_table_};
// Metadata for gpu tracks.
GpuContexts gpu_contexts_;
diff --git a/src/trace_processor/track_tracker.cc b/src/trace_processor/track_tracker.cc
index 38e6f0e..58e1690 100644
--- a/src/trace_processor/track_tracker.cc
+++ b/src/trace_processor/track_tracker.cc
@@ -16,22 +16,53 @@
#include "src/trace_processor/track_tracker.h"
+#include "src/trace_processor/args_tracker.h"
+
namespace perfetto {
namespace trace_processor {
+// static
+constexpr TrackId TrackTracker::kDefaultDescriptorTrackUuid;
+
TrackTracker::TrackTracker(TraceProcessorContext* context)
- : context_(context) {}
+ : source_key_(context->storage->InternString("source")),
+ source_id_key_(context->storage->InternString("source_id")),
+ source_scope_key_(context->storage->InternString("source_scope")),
+ fuchsia_source_(context->storage->InternString("fuchsia")),
+ chrome_source_(context->storage->InternString("chrome")),
+ android_source_(context->storage->InternString("android")),
+ descriptor_source_(context->storage->InternString("descriptor")),
+ default_descriptor_track_name_(
+ context->storage->InternString("Default Track")),
+ context_(context) {}
-TrackId TrackTracker::InternFuchsiaAsyncTrack(
- const tables::FuchsiaAsyncTrackTable::Row& row) {
- FuchsiaAsyncTrackTuple tuple{row.correlation_id};
+TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
+ auto it = thread_tracks_.find(utid);
+ if (it != thread_tracks_.end())
+ return it->second;
- auto it = fuchsia_async_tracks_.find(tuple);
+ tables::ThreadTrackTable::Row row;
+ row.utid = utid;
+ auto id = context_->storage->mutable_thread_track_table()->Insert(row);
+ thread_tracks_[utid] = id;
+ return id;
+}
+
+TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
+ int64_t correlation_id) {
+ auto it = fuchsia_async_tracks_.find(correlation_id);
if (it != fuchsia_async_tracks_.end())
return it->second;
- auto id = context_->storage->mutable_fuchsia_async_track_table()->Insert(row);
- fuchsia_async_tracks_[tuple] = id;
+ tables::TrackTable::Row row(name);
+ auto id = context_->storage->mutable_track_table()->Insert(row);
+ fuchsia_async_tracks_[correlation_id] = id;
+
+ RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+ context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+ Variadic::String(fuchsia_source_));
+ context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+ Variadic::Integer(correlation_id));
return id;
}
@@ -47,29 +78,233 @@
return id;
}
-TrackId TrackTracker::InternChromeAsyncTrack(
- const tables::ChromeAsyncTrackTable::Row& row,
+TrackId TrackTracker::InternLegacyChromeAsyncTrack(
+ StringId name,
+ base::Optional<uint32_t> upid,
+ int64_t source_id,
StringId source_scope) {
ChromeTrackTuple tuple;
-
- if (row.upid.has_value()) {
- tuple.scope = ChromeTrackTuple::Scope::kProcess;
- tuple.upid = row.upid;
- } else {
- tuple.scope = ChromeTrackTuple::Scope::kGlobal;
- }
-
- tuple.source_id = row.async_id;
+ tuple.upid = upid;
+ tuple.source_id = source_id;
tuple.source_scope = source_scope;
auto it = chrome_tracks_.find(tuple);
if (it != chrome_tracks_.end())
return it->second;
- auto id = context_->storage->mutable_chrome_async_track_table()->Insert(row);
+ TrackId id;
+ if (upid.has_value()) {
+ tables::ProcessTrackTable::Row track(name);
+ track.upid = *upid;
+ id = context_->storage->mutable_process_track_table()->Insert(track);
+ } else {
+ tables::TrackTable::Row track(name);
+ id = context_->storage->mutable_track_table()->Insert(track);
+ }
chrome_tracks_[tuple] = id;
+
+ RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+ context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+ Variadic::String(chrome_source_));
+ context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+ Variadic::Integer(source_id));
+ context_->args_tracker->AddArg(row_id, source_scope_key_, source_scope_key_,
+ Variadic::String(source_scope));
return id;
}
+TrackId TrackTracker::InternAndroidAsyncTrack(StringId name,
+ UniquePid upid,
+ int64_t cookie) {
+ AndroidAsyncTrackTuple tuple{upid, cookie, name};
+
+ auto it = android_async_tracks_.find(tuple);
+ if (it != android_async_tracks_.end())
+ return it->second;
+
+ tables::ProcessTrackTable::Row row(name);
+ row.upid = upid;
+ auto id = context_->storage->mutable_process_track_table()->Insert(row);
+ android_async_tracks_[tuple] = id;
+
+ RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+ context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+ Variadic::String(android_source_));
+ context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+ Variadic::Integer(cookie));
+ return id;
+}
+
+TrackId TrackTracker::InternLegacyChromeProcessInstantTrack(UniquePid upid) {
+ auto it = chrome_process_instant_tracks_.find(upid);
+ if (it != chrome_process_instant_tracks_.end())
+ return it->second;
+
+ tables::ProcessTrackTable::Row row;
+ row.upid = upid;
+ auto id = context_->storage->mutable_process_track_table()->Insert(row);
+ chrome_process_instant_tracks_[upid] = id;
+ return id;
+}
+
+TrackId TrackTracker::GetOrCreateLegacyChromeGlobalInstantTrack() {
+ if (!chrome_global_instant_track_id_) {
+ chrome_global_instant_track_id_ =
+ context_->storage->mutable_track_table()->Insert({});
+ }
+ return *chrome_global_instant_track_id_;
+}
+
+TrackId TrackTracker::UpdateDescriptorTrack(uint64_t uuid,
+ StringId name,
+ base::Optional<UniquePid> upid,
+ base::Optional<UniqueTid> utid) {
+ auto it = descriptor_tracks_.find(uuid);
+ if (it != descriptor_tracks_.end()) {
+ // Update existing track for |uuid|.
+ TrackId track_id = it->second;
+ if (name != kNullStringId) {
+ context_->storage->mutable_track_table()->mutable_name()->Set(track_id,
+ name);
+ }
+
+#if PERFETTO_DLOG_IS_ON()
+ if (upid) {
+ // Verify that upid didn't change.
+ auto process_track_row =
+ context_->storage->process_track_table().id().IndexOf(
+ SqlValue::Long(track_id));
+ if (!process_track_row) {
+ PERFETTO_DLOG("Can't update non-scoped track with uuid %" PRIu64
+ " to a scoped track.",
+ uuid);
+ } else {
+ auto old_upid =
+ context_->storage->process_track_table().upid()[*process_track_row];
+ if (old_upid != upid) {
+ PERFETTO_DLOG("Ignoring upid change for track with uuid %" PRIu64
+ " from %" PRIu32 " to %" PRIu32 ".",
+ uuid, old_upid, *upid);
+ }
+ }
+ }
+
+ if (utid) {
+ // Verify that utid didn't change.
+ auto thread_track_row =
+ context_->storage->thread_track_table().id().IndexOf(
+ SqlValue::Long(track_id));
+ if (!thread_track_row) {
+ PERFETTO_DLOG("Can't update non-thread track with uuid %" PRIu64
+ " to a thread track.",
+ uuid);
+ } else {
+ auto old_utid =
+ context_->storage->thread_track_table().utid()[*thread_track_row];
+ if (old_utid != utid) {
+ PERFETTO_DLOG("Ignoring utid change for track with uuid %" PRIu64
+ " from %" PRIu32 " to %" PRIu32 ".",
+ uuid, old_utid, *utid);
+ }
+ }
+ }
+#endif // PERFETTO_DLOG_IS_ON()
+
+ return track_id;
+ }
+
+ TrackId track_id;
+
+ if (utid) {
+ // Update existing track for the thread if we have previously created one
+ // in GetOrCreateDescriptorTrackForThread().
+ auto utid_it = descriptor_tracks_by_utid_.find(*utid);
+ if (utid_it != descriptor_tracks_by_utid_.end()) {
+ TrackId candidate_track_id = utid_it->second;
+ // Only update this track if it hasn't been associated with a different
+ // UUID already.
+ auto descriptor_it = std::find_if(
+ descriptor_tracks_.begin(), descriptor_tracks_.end(),
+ [candidate_track_id](const std::pair<uint64_t, TrackId>& entry) {
+ return entry.second == candidate_track_id;
+ });
+ if (descriptor_it == descriptor_tracks_.end()) {
+ descriptor_tracks_[uuid] = candidate_track_id;
+
+ RowId row_id =
+ TraceStorage::CreateRowId(TableId::kTrack, candidate_track_id);
+ context_->args_tracker->AddArg(
+ row_id, source_id_key_, source_id_key_,
+ Variadic::Integer(static_cast<int64_t>(uuid)));
+
+ return candidate_track_id;
+ }
+ }
+
+ // New thread track.
+ tables::ThreadTrackTable::Row row(name);
+ row.utid = *utid;
+ track_id = context_->storage->mutable_thread_track_table()->Insert(row);
+ if (descriptor_tracks_by_utid_.find(*utid) ==
+ descriptor_tracks_by_utid_.end()) {
+ descriptor_tracks_by_utid_[*utid] = track_id;
+ }
+ } else if (upid) {
+ // New process-scoped async track.
+ tables::ProcessTrackTable::Row track(name);
+ track.upid = *upid;
+ track_id = context_->storage->mutable_process_track_table()->Insert(track);
+ } else {
+ // New global async track.
+ tables::TrackTable::Row track(name);
+ track_id = context_->storage->mutable_track_table()->Insert(track);
+ }
+
+ descriptor_tracks_[uuid] = track_id;
+
+ RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, track_id);
+ context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+ Variadic::String(descriptor_source_));
+ context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+ Variadic::Integer(static_cast<int64_t>(uuid)));
+
+ return track_id;
+}
+
+base::Optional<TrackId> TrackTracker::GetDescriptorTrack(uint64_t uuid) const {
+ auto it = descriptor_tracks_.find(uuid);
+ if (it == descriptor_tracks_.end())
+ return base::nullopt;
+ return it->second;
+}
+
+TrackId TrackTracker::GetOrCreateDescriptorTrackForThread(UniqueTid utid) {
+ auto it = descriptor_tracks_by_utid_.find(utid);
+ if (it != descriptor_tracks_by_utid_.end()) {
+ return it->second;
+ }
+ // TODO(eseckler): How should this track receive its name?
+ tables::ThreadTrackTable::Row row(/*name=*/kNullStringId);
+ row.utid = utid;
+ TrackId track_id =
+ context_->storage->mutable_thread_track_table()->Insert(row);
+ descriptor_tracks_by_utid_[utid] = track_id;
+
+ RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, track_id);
+ context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+ Variadic::String(descriptor_source_));
+ return track_id;
+}
+
+TrackId TrackTracker::GetOrCreateDefaultDescriptorTrack() {
+ base::Optional<TrackId> opt_track_id =
+ GetDescriptorTrack(kDefaultDescriptorTrackUuid);
+ if (opt_track_id)
+ return *opt_track_id;
+
+ return UpdateDescriptorTrack(kDefaultDescriptorTrackUuid,
+ default_descriptor_track_name_);
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/track_tracker.h b/src/trace_processor/track_tracker.h
index 6dd3872..7274edc 100644
--- a/src/trace_processor/track_tracker.h
+++ b/src/trace_processor/track_tracker.h
@@ -28,26 +28,53 @@
public:
explicit TrackTracker(TraceProcessorContext*);
- // Interns a given Fuchsia async track into the storage.
- TrackId InternFuchsiaAsyncTrack(
- const tables::FuchsiaAsyncTrackTable::Row& row);
+ // Interns a thread track into the storage.
+ TrackId InternThreadTrack(UniqueTid utid);
+
+ // Interns a Fuchsia async track into the storage.
+ TrackId InternFuchsiaAsyncTrack(StringId name, int64_t correlation_id);
// Interns a given GPU track into the storage.
TrackId InternGpuTrack(const tables::GpuTrackTable::Row& row);
- // Interns a given Chrome async track into the storage.
- TrackId InternChromeAsyncTrack(const tables::ChromeAsyncTrackTable::Row& row,
- StringId source_scope);
+ // Interns a legacy Chrome async event track into the storage.
+ TrackId InternLegacyChromeAsyncTrack(StringId name,
+ base::Optional<UniquePid> upid,
+ int64_t source_id,
+ StringId source_scope);
+
+ // Interns a Android async track into the storage.
+ TrackId InternAndroidAsyncTrack(StringId name,
+ UniquePid upid,
+ int64_t cookie);
+
+ // Interns a track for legacy Chrome process-scoped instant events into the
+ // storage.
+ TrackId InternLegacyChromeProcessInstantTrack(UniquePid upid);
+
+ // Lazily creates the track for legacy Chrome global instant events.
+ TrackId GetOrCreateLegacyChromeGlobalInstantTrack();
+
+ // Create or update the track for the TrackDescriptor with the given |uuid|.
+ // Optionally, associate the track with a process or thread.
+ TrackId UpdateDescriptorTrack(uint64_t uuid,
+ StringId name,
+ base::Optional<UniquePid> upid = base::nullopt,
+ base::Optional<UniqueTid> utid = base::nullopt);
+
+ // Returns the ID of the track for the TrackDescriptor with the given |uuid|.
+ // Returns nullopt if no TrackDescriptor with this |uuid| has been parsed yet.
+ base::Optional<TrackId> GetDescriptorTrack(uint64_t uuid) const;
+
+ // Returns the ID of the TrackDescriptor track associated with the given utid.
+ // If the trace contained multiple tracks associated with the utid, the first
+ // created track is returned. Creates a new track if no such track exists.
+ TrackId GetOrCreateDescriptorTrackForThread(UniqueTid utid);
+
+ // Returns the ID of the implicit trace-global default TrackDescriptor track.
+ TrackId GetOrCreateDefaultDescriptorTrack();
private:
- struct FuchsiaAsyncTrackTuple {
- int64_t correlation_id;
-
- friend bool operator<(const FuchsiaAsyncTrackTuple& l,
- const FuchsiaAsyncTrackTuple& r) {
- return std::tie(l.correlation_id) < std::tie(r.correlation_id);
- }
- };
struct GpuTrackTuple {
StringId track_name;
StringId scope;
@@ -59,26 +86,51 @@
}
};
struct ChromeTrackTuple {
- enum class Scope : uint32_t {
- kGlobal = 0,
- kProcess,
- };
-
- Scope scope;
base::Optional<int64_t> upid;
int64_t source_id = 0;
StringId source_scope = 0;
friend bool operator<(const ChromeTrackTuple& l,
const ChromeTrackTuple& r) {
- return std::tie(l.source_id, l.scope, l.upid, l.source_scope) <
- std::tie(r.source_id, r.scope, r.upid, r.source_scope);
+ return std::tie(l.source_id, l.upid, l.source_scope) <
+ std::tie(r.source_id, r.upid, r.source_scope);
+ }
+ };
+ struct AndroidAsyncTrackTuple {
+ UniquePid upid;
+ int64_t cookie;
+ StringId name;
+
+ friend bool operator<(const AndroidAsyncTrackTuple& l,
+ const AndroidAsyncTrackTuple& r) {
+ return std::tie(l.upid, l.cookie, l.name) <
+ std::tie(r.upid, r.cookie, r.name);
}
};
- std::map<FuchsiaAsyncTrackTuple, TrackId> fuchsia_async_tracks_;
+ static constexpr TrackId kDefaultDescriptorTrackUuid = 0u;
+
+ std::map<UniqueTid /* utid */, TrackId> thread_tracks_;
+ std::map<int64_t /* correlation_id */, TrackId> fuchsia_async_tracks_;
std::map<GpuTrackTuple, TrackId> gpu_tracks_;
std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
+ std::map<AndroidAsyncTrackTuple, TrackId> android_async_tracks_;
+ std::map<UniquePid, TrackId> chrome_process_instant_tracks_;
+ base::Optional<TrackId> chrome_global_instant_track_id_;
+ std::map<uint64_t /* uuid */, TrackId> descriptor_tracks_;
+ std::map<UniqueTid, TrackId> descriptor_tracks_by_utid_;
+
+ const StringId source_key_ = 0;
+ const StringId source_id_key_ = 0;
+ const StringId source_scope_key_ = 0;
+
+ const StringId fuchsia_source_ = 0;
+ const StringId chrome_source_ = 0;
+ const StringId android_source_ = 0;
+ const StringId descriptor_source_ = 0;
+
+ const StringId default_descriptor_track_name_ = 0;
+
TraceProcessorContext* const context_;
};
diff --git a/src/traced/service/builtin_producer.cc b/src/traced/service/builtin_producer.cc
index b11da94..16df2e1 100644
--- a/src/traced/service/builtin_producer.cc
+++ b/src/traced/service/builtin_producer.cc
@@ -66,13 +66,17 @@
metatrace_dsd.set_will_notify_on_stop(true);
endpoint_->RegisterDataSource(metatrace_dsd);
- DataSourceDescriptor lazy_heapprofd_dsd;
- lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
- endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
+ {
+ DataSourceDescriptor lazy_heapprofd_dsd;
+ lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
+ endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
+ }
- DataSourceDescriptor lazy_java_hprof_dsd;
- lazy_heapprofd_dsd.set_name(kJavaHprofDataSourceName);
- endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
+ {
+ DataSourceDescriptor lazy_java_hprof_dsd;
+ lazy_java_hprof_dsd.set_name(kJavaHprofDataSourceName);
+ endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
+ }
}
void BuiltinProducer::SetupDataSource(DataSourceInstanceID ds_id,
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index 5a93bbc..fcb67cb 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -252,6 +252,7 @@
"internal/in_process_tracing_backend.cc",
"internal/in_process_tracing_backend.h",
"internal/tracing_muxer_impl.cc",
+ "internal/tracing_muxer_impl.h",
"platform.cc",
"tracing.cc",
"track_event.cc",
diff --git a/test/configs/java_hprof.cfg b/test/configs/java_hprof.cfg
index a989d2c..652412c 100644
--- a/test/configs/java_hprof.cfg
+++ b/test/configs/java_hprof.cfg
@@ -7,7 +7,7 @@
config {
name: "android.java_hprof"
java_hprof_config {
- process_cmdline: "system_server"
+ process_cmdline: "com.google.android.inputmethod.latin"
}
}
}
diff --git a/test/trace_processor/android_async_slice.textproto b/test/trace_processor/android_async_slice.textproto
new file mode 100644
index 0000000..787d929
--- /dev/null
+++ b/test/trace_processor/android_async_slice.textproto
@@ -0,0 +1,26 @@
+packet {
+ ftrace_events {
+ cpu: 3
+ event {
+ timestamp: 74289018336
+ pid: 4064
+ print {
+ ip: 18446743562018522420
+ buf: "S|1204|launching: com.android.chrome|0\n"
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 2
+ event {
+ timestamp: 74662603008
+ pid: 1257
+ print {
+ ip: 18446743562018522420
+ buf: "F|1204|launching: com.android.chrome|0\n"
+ }
+ }
+ }
+}
diff --git a/test/trace_processor/index b/test/trace_processor/index
index 41ef5c2..7bb14f4 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -69,6 +69,9 @@
# Power rails
../data/power_rails.pb power_rails.sql power_rails_power_rails.out
+# Android userspace async slices
+android_async_slice.textproto process_track_slices.sql process_track_slices_android_async_slice.out
+
# The below tests check the autogenerated tables.
# Span join
@@ -120,3 +123,11 @@
# Graphics frame event trace tests.
graphics_frame_events.py graphics_frame_events.sql graphics_frame_events.out
+
+# Scheduling slices from sched_switch events. There are two tests, one for the
+# typical encoding of sched_switch events, and one for the same trace
+# re-encoded in the compact format. The output should be identical apart from
+# the latter having one slice fewer for each cpu (the first compact
+# sched_switch event doesn't start a slice). Six slices in this case.
+../data/sched_switch_original.pb sched_slices.sql sched_slices_sched_switch_original.out
+../data/sched_switch_compact.pb sched_slices.sql sched_slices_sched_switch_compact.out
diff --git a/test/trace_processor/process_track_slices.sql b/test/trace_processor/process_track_slices.sql
new file mode 100644
index 0000000..b84557f
--- /dev/null
+++ b/test/trace_processor/process_track_slices.sql
@@ -0,0 +1,9 @@
+SELECT
+ ts,
+ dur,
+ pid,
+ slice.name as slice_name,
+ process_track.name as track_name
+FROM slice
+INNER JOIN process_track ON slice.ref = process_track.id
+INNER JOIN process USING (upid)
diff --git a/test/trace_processor/process_track_slices_android_async_slice.out b/test/trace_processor/process_track_slices_android_async_slice.out
new file mode 100644
index 0000000..a30a8f0
--- /dev/null
+++ b/test/trace_processor/process_track_slices_android_async_slice.out
@@ -0,0 +1,2 @@
+"ts","dur","pid","slice_name","track_name"
+74289018336,373584672,1204,"launching: com.android.chrome","launching: com.android.chrome"
diff --git a/test/trace_processor/sched_slices.sql b/test/trace_processor/sched_slices.sql
new file mode 100644
index 0000000..1e808d5
--- /dev/null
+++ b/test/trace_processor/sched_slices.sql
@@ -0,0 +1,3 @@
+SELECT ts, cpu, dur, ts_end, end_state, priority, tid, name
+FROM sched JOIN thread ON sched.utid == thread.utid
+ORDER BY cpu, sched.ts ASC;
diff --git a/test/trace_processor/sched_slices_sched_switch_compact.out b/test/trace_processor/sched_slices_sched_switch_compact.out
new file mode 100644
index 0000000..c570299
--- /dev/null
+++ b/test/trace_processor/sched_slices_sched_switch_compact.out
@@ -0,0 +1,77 @@
+"ts","cpu","dur","ts_end","end_state","priority","tid","name"
+807082865097350,0,6629324,807082871726674,"R",120,0,"swapper/1"
+807082871726674,0,38229,807082871764903,"S",120,7,"rcu_preempt"
+807082871764903,0,40521,807082871805424,"S",120,45,"rcuop/4"
+807082871805424,0,29896,807082871835320,"S",120,7,"rcu_preempt"
+807082871835320,0,6778282,807082878613602,"R",120,0,"swapper/1"
+807082878613602,0,35781,807082878649383,"S",120,7,"rcu_preempt"
+807082878649383,0,216562,807082878865945,"S",120,45,"rcuop/4"
+807082878865945,0,135625,807082879001570,"D",120,22367,"kworker/u16:9"
+807082879001570,0,57917,807082879059487,"R",120,0,"swapper/1"
+807082879059487,0,120052,807082879179539,"S",120,22367,"kworker/u16:9"
+807082879179539,0,68398757,807082947578296,"R",120,0,"swapper/1"
+807082947578296,0,49271,807082947627567,"S",120,28673,"kworker/0:0"
+807082947627567,0,11515782,807082959143349,"R",120,0,"swapper/1"
+807082959143349,0,119167,807082959262516,"S",120,6,"ksoftirqd/0"
+807082959262516,0,126094,807082959388610,"S",120,22367,"kworker/u16:9"
+807082959388610,0,71435840,807083030824450,"R",120,0,"swapper/1"
+807083030824450,0,1662865,807083032487315,"S",120,29206,"traced"
+807083032487315,0,593281,807083033080596,"R",120,29207,"traced_probes"
+807082863108704,1,42240,807082863150944,"R",120,0,"swapper/1"
+807082863150944,1,42760,807082863193704,"S",49,4454,"irq/80-1436400."
+807082863193704,1,1931823,807082865125527,"R",120,0,"swapper/1"
+807082865125527,1,159532,807082865285059,"D",49,4458,"irq/81-114a000."
+807082865285059,1,34375,807082865319434,"R",120,0,"swapper/1"
+807082865319434,1,38333,807082865357767,"S",49,4458,"irq/81-114a000."
+807082865357767,1,82176415,807082947534182,"R",120,0,"swapper/1"
+807082947534182,1,56875,807082947591057,"S",120,28610,"kworker/1:0"
+807082947591057,1,15142918,807082962733975,"R",120,0,"swapper/1"
+807082962733975,1,77448,807082962811423,"S",120,18,"ksoftirqd/1"
+807082962811423,1,5196563,807082968007986,"R",120,0,"swapper/1"
+807082968007986,1,268437,807082968276423,"S",98,4009,"bluetooth@1.0-s"
+807082968276423,1,134688,807082968411111,"R",120,0,"swapper/1"
+807082968411111,1,116718,807082968527829,"S",120,22367,"kworker/u16:9"
+807082968527829,1,62510684,807083031038513,"R",120,0,"swapper/1"
+807083031038513,1,840416,807083031878929,"R+",120,22367,"kworker/u16:9"
+807083031878929,1,147032,807083032025961,"S",120,10,"rcuop/0"
+807083032025961,1,54218,807083032080179,"S",120,7,"rcu_preempt"
+807083032080179,1,455834,807083032536013,"D",120,22367,"kworker/u16:9"
+807083032536013,1,162604,807083032698617,"R",120,0,"swapper/1"
+807083032698617,1,276146,807083032974763,"D",120,22367,"kworker/u16:9"
+807083032974763,1,62396,807083033037159,"R",120,0,"swapper/1"
+807083033037159,1,43437,807083033080596,"S",120,22367,"kworker/u16:9"
+807083033080596,1,0,807083033080596,"R",120,0,"swapper/1"
+807082865061361,2,399583,807082865460944,"D",120,22367,"kworker/u16:9"
+807082865460944,2,46771,807082865507715,"R",120,0,"swapper/1"
+807082865507715,2,30312,807082865538027,"S",120,22367,"kworker/u16:9"
+807082865538027,2,2811459,807082868349486,"R",120,0,"swapper/1"
+807082868349486,2,60417,807082868409903,"S",120,8,"rcu_sched"
+807082868409903,2,31302,807082868441205,"S",120,46,"rcuos/4"
+807082868441205,2,78727456,807082947168661,"R",120,0,"swapper/1"
+807082947168661,2,296771,807082947465432,"S",49,627,"sugov:0"
+807082947465432,2,30989,807082947496421,"S",120,28887,"kworker/2:2"
+807082947496421,2,96927,807082947593348,"S",120,45,"rcuop/4"
+807082947593348,2,692344,807082948285692,"D",120,22367,"kworker/u16:9"
+807082948285692,2,363438,807082948649130,"R",120,0,"swapper/1"
+807082948649130,2,185052,807082948834182,"D",120,22367,"kworker/u16:9"
+807082948834182,2,357239,807082949191421,"R",120,0,"swapper/1"
+807082949191421,2,55677,807082949247098,"S",120,22367,"kworker/u16:9"
+807082949247098,2,83833498,807083033080596,"R",120,0,"swapper/1"
+807082947555119,3,37083,807082947592202,"R",120,0,"swapper/1"
+807082947592202,3,38177,807082947630379,"S",120,7,"rcu_preempt"
+807082947630379,3,4172813,807082951803192,"R",120,0,"swapper/1"
+807082951803192,3,83698,807082951886890,"S",120,7,"rcu_preempt"
+807082951886890,3,40000,807082951926890,"S",120,45,"rcuop/4"
+807082951926890,3,81153706,807083033080596,"R",120,0,"swapper/1"
+807082862976152,4,14792,807082862990944,"S",120,29207,"traced_probes"
+807082862990944,4,83901102,807082946892046,"R",120,0,"swapper/1"
+807082946892046,4,327500,807082947219546,"S",120,29207,"traced_probes"
+807082947219546,4,15052,807082947234598,"R",120,0,"swapper/1"
+807082947234598,4,68698,807082947303296,"S",120,42,"ksoftirqd/4"
+807082947303296,4,28958,807082947332254,"S",120,21092,"kworker/4:1"
+807082947332254,4,83363707,807083030695961,"R",120,0,"swapper/1"
+807083030695961,4,104895,807083030800856,"S",120,42,"ksoftirqd/4"
+807083030800856,4,2279740,807083033080596,"R",120,0,"swapper/1"
+807082952627307,7,1534375,807082954161682,"R",120,0,"swapper/1"
+807082954161682,7,43490,807082954205172,"S",49,628,"sugov:4"
+807082954205172,7,78875424,807083033080596,"R",120,0,"swapper/1"
diff --git a/test/trace_processor/sched_slices_sched_switch_original.out b/test/trace_processor/sched_slices_sched_switch_original.out
new file mode 100644
index 0000000..fe6f0c5
--- /dev/null
+++ b/test/trace_processor/sched_slices_sched_switch_original.out
@@ -0,0 +1,83 @@
+"ts","cpu","dur","ts_end","end_state","priority","tid","name"
+807082865062402,0,34948,807082865097350,"S",120,7,"rcu_preempt"
+807082865097350,0,6629324,807082871726674,"R",120,0,"swapper/1"
+807082871726674,0,38229,807082871764903,"S",120,7,"rcu_preempt"
+807082871764903,0,40521,807082871805424,"S",120,45,"rcuop/4"
+807082871805424,0,29896,807082871835320,"S",120,7,"rcu_preempt"
+807082871835320,0,6778282,807082878613602,"R",120,0,"swapper/1"
+807082878613602,0,35781,807082878649383,"S",120,7,"rcu_preempt"
+807082878649383,0,216562,807082878865945,"S",120,45,"rcuop/4"
+807082878865945,0,135625,807082879001570,"D",120,22367,"kworker/u16:9"
+807082879001570,0,57917,807082879059487,"R",120,0,"swapper/1"
+807082879059487,0,120052,807082879179539,"S",120,22367,"kworker/u16:9"
+807082879179539,0,68398757,807082947578296,"R",120,0,"swapper/1"
+807082947578296,0,49271,807082947627567,"S",120,28673,"kworker/0:0"
+807082947627567,0,11515782,807082959143349,"R",120,0,"swapper/1"
+807082959143349,0,119167,807082959262516,"S",120,6,"ksoftirqd/0"
+807082959262516,0,126094,807082959388610,"S",120,22367,"kworker/u16:9"
+807082959388610,0,71435840,807083030824450,"R",120,0,"swapper/1"
+807083030824450,0,1662865,807083032487315,"S",120,29206,"traced"
+807083032487315,0,593281,807083033080596,"R",120,29207,"traced_probes"
+807082862950996,1,157708,807082863108704,"D",49,4454,"irq/80-1436400."
+807082863108704,1,42240,807082863150944,"R",120,0,"swapper/1"
+807082863150944,1,42760,807082863193704,"S",49,4454,"irq/80-1436400."
+807082863193704,1,1931823,807082865125527,"R",120,0,"swapper/1"
+807082865125527,1,159532,807082865285059,"D",49,4458,"irq/81-114a000."
+807082865285059,1,34375,807082865319434,"R",120,0,"swapper/1"
+807082865319434,1,38333,807082865357767,"S",49,4458,"irq/81-114a000."
+807082865357767,1,82176415,807082947534182,"R",120,0,"swapper/1"
+807082947534182,1,56875,807082947591057,"S",120,28610,"kworker/1:0"
+807082947591057,1,15142918,807082962733975,"R",120,0,"swapper/1"
+807082962733975,1,77448,807082962811423,"S",120,18,"ksoftirqd/1"
+807082962811423,1,5196563,807082968007986,"R",120,0,"swapper/1"
+807082968007986,1,268437,807082968276423,"S",98,4009,"bluetooth@1.0-s"
+807082968276423,1,134688,807082968411111,"R",120,0,"swapper/1"
+807082968411111,1,116718,807082968527829,"S",120,22367,"kworker/u16:9"
+807082968527829,1,62510684,807083031038513,"R",120,0,"swapper/1"
+807083031038513,1,840416,807083031878929,"R+",120,22367,"kworker/u16:9"
+807083031878929,1,147032,807083032025961,"S",120,10,"rcuop/0"
+807083032025961,1,54218,807083032080179,"S",120,7,"rcu_preempt"
+807083032080179,1,455834,807083032536013,"D",120,22367,"kworker/u16:9"
+807083032536013,1,162604,807083032698617,"R",120,0,"swapper/1"
+807083032698617,1,276146,807083032974763,"D",120,22367,"kworker/u16:9"
+807083032974763,1,62396,807083033037159,"R",120,0,"swapper/1"
+807083033037159,1,43437,807083033080596,"S",120,22367,"kworker/u16:9"
+807083033080596,1,0,807083033080596,"R",120,0,"swapper/1"
+807082862645371,2,2415990,807082865061361,"R",120,0,"swapper/1"
+807082865061361,2,399583,807082865460944,"D",120,22367,"kworker/u16:9"
+807082865460944,2,46771,807082865507715,"R",120,0,"swapper/1"
+807082865507715,2,30312,807082865538027,"S",120,22367,"kworker/u16:9"
+807082865538027,2,2811459,807082868349486,"R",120,0,"swapper/1"
+807082868349486,2,60417,807082868409903,"S",120,8,"rcu_sched"
+807082868409903,2,31302,807082868441205,"S",120,46,"rcuos/4"
+807082868441205,2,78727456,807082947168661,"R",120,0,"swapper/1"
+807082947168661,2,296771,807082947465432,"S",49,627,"sugov:0"
+807082947465432,2,30989,807082947496421,"S",120,28887,"kworker/2:2"
+807082947496421,2,96927,807082947593348,"S",120,45,"rcuop/4"
+807082947593348,2,692344,807082948285692,"D",120,22367,"kworker/u16:9"
+807082948285692,2,363438,807082948649130,"R",120,0,"swapper/1"
+807082948649130,2,185052,807082948834182,"D",120,22367,"kworker/u16:9"
+807082948834182,2,357239,807082949191421,"R",120,0,"swapper/1"
+807082949191421,2,55677,807082949247098,"S",120,22367,"kworker/u16:9"
+807082949247098,2,83833498,807083033080596,"R",120,0,"swapper/1"
+807082947493504,3,61615,807082947555119,"S",120,28905,"kworker/3:0"
+807082947555119,3,37083,807082947592202,"R",120,0,"swapper/1"
+807082947592202,3,38177,807082947630379,"S",120,7,"rcu_preempt"
+807082947630379,3,4172813,807082951803192,"R",120,0,"swapper/1"
+807082951803192,3,83698,807082951886890,"S",120,7,"rcu_preempt"
+807082951886890,3,40000,807082951926890,"S",120,45,"rcuop/4"
+807082951926890,3,81153706,807083033080596,"R",120,0,"swapper/1"
+807082862918652,4,57500,807082862976152,"S",120,29206,"traced"
+807082862976152,4,14792,807082862990944,"S",120,29207,"traced_probes"
+807082862990944,4,83901102,807082946892046,"R",120,0,"swapper/1"
+807082946892046,4,327500,807082947219546,"S",120,29207,"traced_probes"
+807082947219546,4,15052,807082947234598,"R",120,0,"swapper/1"
+807082947234598,4,68698,807082947303296,"S",120,42,"ksoftirqd/4"
+807082947303296,4,28958,807082947332254,"S",120,21092,"kworker/4:1"
+807082947332254,4,83363707,807083030695961,"R",120,0,"swapper/1"
+807083030695961,4,104895,807083030800856,"S",120,42,"ksoftirqd/4"
+807083030800856,4,2279740,807083033080596,"R",120,0,"swapper/1"
+807082952486057,7,141250,807082952627307,"D",49,628,"sugov:4"
+807082952627307,7,1534375,807082954161682,"R",120,0,"swapper/1"
+807082954161682,7,43490,807082954205172,"S",49,628,"sugov:4"
+807082954205172,7,78875424,807083033080596,"R",120,0,"swapper/1"
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index e6c1f89..19313b5 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -23,6 +23,7 @@
deps = [
":copy_protoc",
":idle_alloc",
+ "compact_reencode",
"ftrace_proto_gen",
"proto_to_cpp",
"protoprofile",
diff --git a/tools/compact_reencode/BUILD.gn b/tools/compact_reencode/BUILD.gn
new file mode 100644
index 0000000..6556b9d
--- /dev/null
+++ b/tools/compact_reencode/BUILD.gn
@@ -0,0 +1,37 @@
+# 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("../../gn/perfetto_host_executable.gni")
+
+perfetto_host_executable("compact_reencode") {
+ testonly = true
+ deps = [
+ ":common",
+ "../../gn:default_deps",
+ ]
+}
+
+source_set("common") {
+ testonly = true
+ public_deps = [
+ "../../gn:default_deps",
+ "../../include/perfetto/protozero",
+ "../../protos/perfetto/trace:zero",
+ "../../protos/perfetto/trace/ftrace:zero",
+ "../../src/base",
+ ]
+ sources = [
+ "main.cc",
+ ]
+}
diff --git a/tools/compact_reencode/main.cc b/tools/compact_reencode/main.cc
new file mode 100644
index 0000000..139896d
--- /dev/null
+++ b/tools/compact_reencode/main.cc
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+// Re-encodes the given trace, converting sched events to their compact
+// representation.
+// Note: doesn't do bundle splitting/merging, the original trace must already
+// have multi-page bundles for the re-encoding to be realistic.
+
+namespace perfetto {
+namespace compact_reencode {
+namespace {
+
+void WriteToFile(const std::string& out, const char* path) {
+ PERFETTO_CHECK(!unlink(path) || errno == ENOENT);
+ auto out_fd = base::OpenFile(path, O_RDWR | O_CREAT, 0666);
+ if (!out_fd || base::WriteAll(out_fd.get(), out.data(), out.size()) !=
+ static_cast<ssize_t>(out.size())) {
+ PERFETTO_FATAL("WriteToFile");
+ }
+}
+
+static void CopyField(protozero::Message* out, const protozero::Field& field) {
+ using protozero::proto_utils::ProtoWireType;
+ if (field.type() == ProtoWireType::kVarInt) {
+ out->AppendVarInt(field.id(), field.as_uint64());
+ } else if (field.type() == ProtoWireType::kLengthDelimited) {
+ out->AppendBytes(field.id(), field.as_bytes().data, field.as_bytes().size);
+ } else if (field.type() == ProtoWireType::kFixed32) {
+ out->AppendFixed(field.id(), field.as_uint32());
+ } else if (field.type() == ProtoWireType::kFixed64) {
+ out->AppendFixed(field.id(), field.as_uint64());
+ } else {
+ PERFETTO_FATAL("unexpected wire type");
+ }
+}
+
+void ReEncodeBundle(protos::pbzero::TracePacket* packet_out,
+ const uint8_t* data,
+ size_t size) {
+ protos::pbzero::FtraceEventBundle::Decoder bundle(data, size);
+ auto* bundle_out = packet_out->set_ftrace_events();
+
+ if (bundle.has_lost_events())
+ bundle_out->set_lost_events(bundle.lost_events());
+ if (bundle.has_cpu())
+ bundle_out->set_cpu(bundle.cpu());
+
+ static constexpr size_t kMaxElements = 2560;
+ protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+ switch_timestamp;
+ protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+ switch_prev_state;
+ protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+ switch_next_pid;
+ protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+ switch_next_prio;
+ protozero::StackAllocated<protozero::PackedVarIntBuffer, kMaxElements>
+ switch_next_comm_index;
+
+ uint64_t last_switch_timestamp = 0;
+
+ std::vector<std::string> string_table;
+ auto intern = [&string_table](std::string str) {
+ for (size_t i = 0; i < string_table.size(); i++) {
+ if (str == string_table[i])
+ return static_cast<uint32_t>(i);
+ }
+ size_t new_idx = string_table.size();
+ string_table.push_back(str);
+ return static_cast<uint32_t>(new_idx);
+ };
+
+ for (auto event_it = bundle.event(); event_it; ++event_it) {
+ protos::pbzero::FtraceEvent::Decoder event(event_it->data(),
+ event_it->size());
+ if (!event.has_sched_switch()) {
+ CopyField(bundle_out, *event_it);
+ } else {
+ switch_timestamp.Append(event.timestamp() - last_switch_timestamp);
+ last_switch_timestamp = event.timestamp();
+
+ protos::pbzero::SchedSwitchFtraceEvent::Decoder sswitch(
+ event.sched_switch());
+
+ auto iid = intern(sswitch.next_comm().ToStdString());
+ switch_next_comm_index.Append(iid);
+
+ switch_next_pid.Append(sswitch.next_pid());
+ switch_next_prio.Append(sswitch.next_prio());
+ switch_prev_state.Append(sswitch.prev_state());
+ }
+ }
+ auto* compact_sched = bundle_out->set_compact_sched();
+
+ for (const auto& s : string_table)
+ compact_sched->add_switch_next_comm_table(s.data(), s.size());
+
+ compact_sched->set_switch_timestamp(switch_timestamp);
+ compact_sched->set_switch_next_comm_index(switch_next_comm_index);
+ compact_sched->set_switch_next_pid(switch_next_pid);
+ compact_sched->set_switch_next_prio(switch_next_prio);
+ compact_sched->set_switch_prev_state(switch_prev_state);
+}
+
+std::string ReEncode(const std::string& raw) {
+ protos::pbzero::Trace::Decoder trace(raw);
+ protozero::HeapBuffered<protos::pbzero::Trace> output;
+
+ for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+ const auto* data = packet_it->data();
+ size_t size = packet_it->size();
+ protozero::ProtoDecoder packet(data, size);
+ protos::pbzero::TracePacket* packet_out = output->add_packet();
+
+ for (auto field = packet.ReadField(); field.valid();
+ field = packet.ReadField()) {
+ if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
+ ReEncodeBundle(packet_out, field.data(), field.size());
+ } else {
+ CopyField(packet_out, field);
+ }
+ }
+ }
+ // Minor technicality: we will be a tiny bit off the real encoding since
+ // we've encoded the top-level Trace & TracePacket sizes redundantly, while
+ // the tracing service writes them as a minimal varint (so only a few bytes
+ // off per trace packet).
+ return output.SerializeAsString();
+}
+
+int Main(int argc, const char** argv) {
+ if (argc < 3) {
+ PERFETTO_LOG("Usage: %s input output", argv[0]);
+ return 1;
+ }
+ const char* in_path = argv[1];
+ const char* out_path = argv[2];
+
+ std::string raw;
+ if (!base::ReadFile(in_path, &raw)) {
+ PERFETTO_PLOG("ReadFile");
+ return 1;
+ }
+
+ std::string raw_out = ReEncode(raw);
+ WriteToFile(raw_out, out_path);
+ return 0;
+}
+
+} // namespace
+} // namespace compact_reencode
+} // namespace perfetto
+
+int main(int argc, const char** argv) {
+ return perfetto::compact_reencode::Main(argc, argv);
+}
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 89d7aa7..a633bbb 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -82,6 +82,7 @@
target_host_supported = [
'//protos/perfetto/trace:perfetto_trace_protos',
+ '//:libperfetto',
]
target_host_only = [
diff --git a/tools/gen_bazel b/tools/gen_bazel
index a8643ec..09fd086 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -47,6 +47,7 @@
# These targets will be exported with public visibility in the generated BUILD.
public_targets = [
+ '//:libperfetto_client_experimental',
'//src/perfetto_cmd:perfetto',
'//src/traced/probes:traced_probes',
'//src/traced/service:traced',
@@ -66,8 +67,10 @@
] + public_targets
# Root proto targets (to force discovery of intermediate proto targets).
+# These targets are marked public.
proto_targets = [
'//protos/perfetto/trace:merged_trace',
+ '//protos/perfetto/config:merged_config',
'//protos/perfetto/metrics:lite',
'//protos/perfetto/trace:lite',
'//protos/perfetto/config:lite',
@@ -230,6 +233,9 @@
deps = [':' + get_sources_label(x) for x in target.proto_deps]
sources_label.deps = sorted(deps)
+ if target.name in proto_targets:
+ sources_label.visibility = ['//visibility:public']
+
return [plugin_label, sources_label]
diff --git a/tools/heap_profile b/tools/heap_profile
index 3938b64..5680a68 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -22,6 +22,7 @@
import atexit
import hashlib
import os
+import shutil
import signal
import subprocess
import sys
@@ -342,6 +343,7 @@
if os.path.lexists(symlink_path):
os.unlink(symlink_path)
os.symlink(profile_path, symlink_path)
+ shutil.copyfile('/tmp/profile', os.path.join(profile_path, 'raw-trace'))
print("Wrote profiles to {} (symlink {})".format(profile_path, symlink_path))
print("These can be viewed using pprof. Googlers: head to pprof/ and "
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 166fe0b..f96a19b 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -192,8 +192,8 @@
# Example traces for regression tests.
('buildtools/test_data.zip',
- 'https://storage.googleapis.com/perfetto/test-data-20190830-110949.zip',
- '875db1f3da7574745d8abc593bc696c2abf6ee2d',
+ 'https://storage.googleapis.com/perfetto/test-data-20190920-011709.zip',
+ '75fc970bf2778d506c9d77f94a6381b04f10b692',
'all',
),
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index 485954d..44126e7 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -37,6 +37,9 @@
}
source_set("utils") {
+ deps = [
+ ":symbolizer",
+ ]
public_deps = [
"../../gn:default_deps",
"../../include/perfetto/base",
@@ -49,8 +52,6 @@
"../../src/trace_processor:lib",
]
sources = [
- "profile_visitor.cc",
- "profile_visitor.h",
"utils.cc",
"utils.h",
]
@@ -77,7 +78,6 @@
source_set("symbolizer") {
public_deps = [
- ":utils",
"../../gn:default_deps",
"../../include/perfetto/base",
"../../include/perfetto/ext/base",
@@ -90,8 +90,6 @@
]
sources = [
"symbolizer.cc",
- "trace_symbol_table.cc",
- "trace_symbol_table.h",
]
}
@@ -128,6 +126,7 @@
":pprofbuilder",
":symbolizer",
":utils",
+ "../../gn:zlib",
]
public_deps = [
"../../gn:default_deps",
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 225bf70..52ff38e 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -20,10 +20,10 @@
#include <vector>
#include "perfetto/base/logging.h"
+#include "tools/trace_to_text/symbolize_profile.h"
#include "tools/trace_to_text/trace_to_profile.h"
#include "tools/trace_to_text/trace_to_systrace.h"
#include "tools/trace_to_text/trace_to_text.h"
-#include "tools/trace_to_text/symbolize_profile.h"
#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
#include "perfetto_version.gen.h"
@@ -31,22 +31,22 @@
#define PERFETTO_GET_GIT_REVISION() "unknown"
#endif
+namespace perfetto {
+namespace trace_to_text {
namespace {
int Usage(const char* argv0) {
printf(
- "Usage: %s systrace|json|text|profile [--truncate start|end] [trace.pb] "
+ "Usage: %s systrace|json|ctrace|text|profile [--truncate start|end] "
+ "[trace.pb] "
"[trace.txt]\n",
argv0);
return 1;
}
-} // namespace
-
-int main(int argc, char** argv) {
+int Main(int argc, char** argv) {
std::vector<const char*> positional_args;
- perfetto::trace_to_text::Keep truncate_keep =
- perfetto::trace_to_text::Keep::kAll;
+ Keep truncate_keep = Keep::kAll;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
printf("%s\n", PERFETTO_GET_GIT_REVISION());
@@ -55,9 +55,9 @@
strcmp(argv[i], "--truncate") == 0) {
i++;
if (i <= argc && strcmp(argv[i], "start") == 0) {
- truncate_keep = perfetto::trace_to_text::Keep::kStart;
+ truncate_keep = Keep::kStart;
} else if (i <= argc && strcmp(argv[i], "end") == 0) {
- truncate_keep = perfetto::trace_to_text::Keep::kEnd;
+ truncate_keep = Keep::kEnd;
} else {
PERFETTO_ELOG(
"--truncate must specify whether to keep the end or the "
@@ -105,28 +105,39 @@
std::string format(positional_args[0]);
if (format == "json")
- return perfetto::trace_to_text::TraceToSystrace(input_stream, output_stream,
- truncate_keep,
- /*wrap_in_json=*/true);
+ return TraceToSystrace(input_stream, output_stream, kSystraceJson,
+ truncate_keep);
+
if (format == "systrace")
- return perfetto::trace_to_text::TraceToSystrace(input_stream, output_stream,
- truncate_keep,
- /*wrap_in_json=*/false);
- if (truncate_keep != perfetto::trace_to_text::Keep::kAll) {
+ return TraceToSystrace(input_stream, output_stream, kSystraceNormal,
+ truncate_keep);
+
+ if (format == "ctrace")
+ return TraceToSystrace(input_stream, output_stream, kSystraceCompressed,
+ truncate_keep);
+
+ if (truncate_keep != Keep::kAll) {
PERFETTO_ELOG(
"--truncate is unsupported for text|profile|symbolize format.");
return 1;
}
if (format == "text")
- return perfetto::trace_to_text::TraceToText(input_stream, output_stream);
+ return TraceToText(input_stream, output_stream);
if (format == "profile")
- return perfetto::trace_to_text::TraceToProfile(input_stream, output_stream);
+ return TraceToProfile(input_stream, output_stream);
if (format == "symbolize")
- return perfetto::trace_to_text::SymbolizeProfile(input_stream,
- output_stream);
+ return SymbolizeProfile(input_stream, output_stream);
return Usage(argv[0]);
}
+
+} // namespace
+} // namespace trace_to_text
+} // namespace perfetto
+
+int main(int argc, char** argv) {
+ return perfetto::trace_to_text::Main(argc, argv);
+}
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index c46b4a6..aef6eae 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -25,18 +25,19 @@
#include <utility>
#include <vector>
-#include "tools/trace_to_text/profile_visitor.h"
-#include "tools/trace_to_text/trace_symbol_table.h"
#include "tools/trace_to_text/utils.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/time.h"
#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/utils.h"
#include "perfetto/profiling/symbolizer.h"
#include "protos/perfetto/trace/profiling/profile_common.pb.h"
#include "protos/perfetto/trace/profiling/profile_packet.pb.h"
#include "protos/perfetto/trace/trace.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "protos/third_party/pprof/profile.pb.h"
namespace perfetto {
@@ -44,6 +45,25 @@
namespace {
+using ::protozero::proto_utils::kMessageLengthFieldSize;
+using ::protozero::proto_utils::MakeTagLengthDelimited;
+using ::protozero::proto_utils::WriteVarInt;
+
+using GLine = ::perfetto::third_party::perftools::profiles::Line;
+using GMapping = ::perfetto::third_party::perftools::profiles::Mapping;
+using GLocation = ::perfetto::third_party::perftools::profiles::Location;
+using GProfile = ::perfetto::third_party::perftools::profiles::Profile;
+using GValueType = ::perfetto::third_party::perftools::profiles::ValueType;
+using GFunction = ::perfetto::third_party::perftools::profiles::Function;
+using GSample = ::perfetto::third_party::perftools::profiles::Sample;
+
+struct View {
+ const char* type;
+ const char* unit;
+ const char* aggregator;
+ const char* filter;
+};
+
void MaybeDemangle(std::string* name) {
int ignored;
char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored);
@@ -53,296 +73,454 @@
}
}
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfilePacket;
-using ::perfetto::protos::InternedData;
+const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
+const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size > 0"};
+const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
+ "size > 0"};
+const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
-using GLine = ::perfetto::third_party::perftools::profiles::Line;
-using GMapping = ::perfetto::third_party::perftools::profiles::Mapping;
-using GLocation = ::perfetto::third_party::perftools::profiles::Location;
-using GProfile = ::perfetto::third_party::perftools::profiles::Profile;
-using GValueType = ::perfetto::third_party::perftools::profiles::ValueType;
-using GFunction = ::perfetto::third_party::perftools::profiles::Function;
-using GSample = ::perfetto::third_party::perftools::profiles::Sample;
+const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
+ kSpaceView};
-enum Strings : int64_t {
- kEmpty = 0,
- kObjects,
- kAllocObjects,
- kCount,
- kSpace,
- kAllocSpace,
- kBytes,
- kIdleSpace,
- kMaxSpace,
+using Iterator = trace_processor::TraceProcessor::Iterator;
+
+constexpr const char* kQueryProfiles =
+ "select distinct hpa.upid, hpa.ts, p.pid from heap_profile_allocation hpa, "
+ "process p where p.upid = hpa.upid;";
+
+struct Callsite {
+ int64_t id;
+ int64_t frame_id;
};
-class GProfileWriter : public ProfileVisitor {
+// Return map from callsite_id to list of frame_ids that make up the callstack.
+std::vector<std::vector<int64_t>> GetCallsiteToFrames(
+ trace_processor::TraceProcessor* tp) {
+ Iterator count_it =
+ tp->ExecuteQuery("select count(*) from stack_profile_callsite;");
+ if (!count_it.Next()) {
+ PERFETTO_DFATAL_OR_ELOG("Failed to get number of callsites: %s",
+ count_it.Status().message().c_str());
+ return {};
+ }
+ int64_t count = count_it.Get(0).long_value;
+
+ Iterator it = tp->ExecuteQuery(
+ "select id, parent_id, frame_id from stack_profile_callsite order by "
+ "depth;");
+ std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
+ while (it.Next()) {
+ int64_t id = it.Get(0).long_value;
+ int64_t parent_id = it.Get(1).long_value;
+ int64_t frame_id = it.Get(2).long_value;
+ std::vector<int64_t>& path = result[static_cast<size_t>(id)];
+ path.push_back(frame_id);
+ if (parent_id != -1) {
+ const std::vector<int64_t>& parent_path =
+ result[static_cast<size_t>(parent_id)];
+ path.insert(path.end(), parent_path.begin(), parent_path.end());
+ }
+ }
+
+ if (!it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+ it.Status().message().c_str());
+ return {};
+ }
+ return result;
+}
+
+struct Line {
+ int64_t symbol_id;
+ uint32_t line_number;
+};
+
+std::map<int64_t, std::vector<Line>> GetSymbolSetIdToLines(
+ trace_processor::TraceProcessor* tp) {
+ std::map<int64_t, std::vector<Line>> result;
+ Iterator it = tp->ExecuteQuery(
+ "SELECT symbol_set_id, id, line_number FROM stack_profile_symbol;");
+ while (it.Next()) {
+ int64_t symbol_set_id = it.Get(0).long_value;
+ int64_t id = it.Get(1).long_value;
+ int64_t line_number = it.Get(2).long_value;
+ result[symbol_set_id].emplace_back(
+ Line{id, static_cast<uint32_t>(line_number)});
+ }
+
+ if (!it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+ it.Status().message().c_str());
+ return {};
+ }
+ return result;
+}
+
+class GProfileBuilder {
public:
- GProfileWriter(TraceSymbolTable* symbol_table) : symbol_table_(symbol_table) {
- GValueType* value_type = profile_.add_sample_type();
- value_type->set_type(kMaxSpace);
- value_type->set_unit(kBytes);
-
- value_type = profile_.add_sample_type();
- value_type->set_type(kObjects);
- value_type->set_unit(kCount);
-
- value_type = profile_.add_sample_type();
- value_type->set_type(kAllocObjects);
- value_type->set_unit(kCount);
-
- value_type = profile_.add_sample_type();
- value_type->set_type(kIdleSpace);
- value_type->set_unit(kBytes);
-
- value_type = profile_.add_sample_type();
- value_type->set_type(kAllocSpace);
- value_type->set_unit(kBytes);
-
- // The last value is the default one selected.
- value_type = profile_.add_sample_type();
- value_type->set_type(kSpace);
- value_type->set_unit(kBytes);
+ GProfileBuilder(
+ const std::vector<std::vector<int64_t>>& callsite_to_frames,
+ const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines,
+ int64_t max_symbol_id)
+ : callsite_to_frames_(callsite_to_frames),
+ symbol_set_id_to_lines_(symbol_set_id_to_lines),
+ max_symbol_id_(max_symbol_id) {
+ // The pprof format expects the first entry in the string table to be the
+ // empty string.
+ Intern("");
}
- int64_t InternInGProfile(const std::string& str) {
- decltype(string_table_)::iterator it;
- std::tie(it, std::ignore) =
- string_table_.emplace(str, string_table_.size());
- return static_cast<int64_t>(it->second);
- }
-
- bool AddInternedString(const InternedString& interned_string) override {
- string_lookup_.emplace(interned_string.iid(), interned_string.str());
- return true;
- }
-
- bool AddCallstack(const Callstack& callstack) override {
- std::vector<uint64_t> frame_ids(
- static_cast<size_t>(callstack.frame_ids().size()));
- std::reverse_copy(callstack.frame_ids().cbegin(),
- callstack.frame_ids().cend(), frame_ids.begin());
- callstack_lookup_.emplace(callstack.iid(), std::move(frame_ids));
- return true;
- }
-
- bool AddMapping(const Mapping& mapping) override {
- mapping_base_.emplace(mapping.iid(), mapping.start() - mapping.load_bias());
- GMapping* gmapping = profile_.add_mapping();
- gmapping->set_id(mapping.iid());
- gmapping->set_memory_start(mapping.start());
- gmapping->set_memory_limit(mapping.end());
- gmapping->set_file_offset(mapping.exact_offset());
- std::string filename;
- for (uint64_t str_id : mapping.path_string_ids()) {
- auto it = string_lookup_.find(str_id);
- if (it == string_lookup_.end()) {
- PERFETTO_ELOG("Mapping %" PRIu64
- " referring to invalid string_id %" PRIu64 ".",
- static_cast<uint64_t>(mapping.iid()), str_id);
- return false;
- }
-
- filename += "/" + it->second;
+ std::vector<Iterator> BuildViewIterators(trace_processor::TraceProcessor* tp,
+ uint64_t upid,
+ uint64_t ts) {
+ std::vector<Iterator> view_its;
+ for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+ const View& v = kViews[i];
+ std::string query = "SELECT hpa.callsite_id ";
+ query += ", " + std::string(v.aggregator) +
+ " FROM heap_profile_allocation hpa ";
+ query += "WHERE hpa.upid = " + std::to_string(upid) + " ";
+ query += "AND hpa.ts <= " + std::to_string(ts) + " ";
+ if (v.filter)
+ query += "AND " + std::string(v.filter) + " ";
+ query += "GROUP BY hpa.callsite_id;";
+ view_its.emplace_back(tp->ExecuteQuery(query));
}
-
- gmapping->set_filename(InternInGProfile(filename));
-
- auto str_it = string_lookup_.find(mapping.build_id());
- if (str_it != string_lookup_.end()) {
- const std::string& build_id = str_it->second;
- gmapping->set_build_id(InternInGProfile(base::ToHex(build_id)));
- }
- return true;
+ return view_its;
}
- bool AddFrame(const Frame& frame) override {
- auto it = mapping_base_.find(frame.mapping_id());
- if (it == mapping_base_.end()) {
- PERFETTO_ELOG("Frame referring to invalid mapping ID %" PRIu64,
- static_cast<uint64_t>(frame.mapping_id()));
- return false;
- }
- uint64_t mapping_base = it->second;
-
- GLocation* glocation = profile_.add_location();
- glocation->set_id(frame.iid());
- glocation->set_mapping_id(frame.mapping_id());
- glocation->set_address(frame.rel_pc() + mapping_base);
-
- const std::vector<SymbolizedFrame>* symbolized_frames =
- symbol_table_->Get(frame.iid());
- std::vector<SymbolizedFrame> frames;
- if (symbolized_frames == nullptr) {
- // Write out whatever was in the profile initially.
- auto str_it = string_lookup_.find(frame.function_name_id());
- if (str_it == string_lookup_.end()) {
- PERFETTO_ELOG("Function referring to invalid string id %" PRIu64,
- static_cast<uint64_t>(frame.function_name_id()));
- return false;
- }
- frames.emplace_back(SymbolizedFrame{str_it->second, "", 0});
- } else {
- frames = *symbolized_frames;
- }
-
- for (const SymbolizedFrame& sym_frame : frames) {
- GLine* gline = glocation->add_line();
- uint64_t function_id = ++max_function_id_;
- gline->set_function_id(function_id);
- gline->set_line(sym_frame.line);
- std::string function_name = sym_frame.function_name;
- // This assumes both the device that captured the trace and the host
- // machine use the same mangling scheme. This is a reasonable
- // assumption as the Itanium ABI is the de-facto standard for mangling.
- MaybeDemangle(&function_name);
- GFunction* gfunction = profile_.add_function();
- gfunction->set_id(function_id);
- gfunction->set_name(InternInGProfile(function_name));
- gfunction->set_filename(InternInGProfile(sym_frame.file_name));
- }
- return true;
- }
-
- bool AddProfiledFrameSymbols(const protos::ProfiledFrameSymbols&) override {
- return true;
- }
-
- bool Finalize() {
- // We keep the interning table as string -> uint64_t for fast and easy
- // lookup. When dumping, we need to turn it into a uint64_t -> string
- // table so we get it sorted by key order.
- std::map<uint64_t, std::string> inverted_string_table;
- for (const auto& p : string_table_)
- inverted_string_table[p.second] = p.first;
- for (const auto& p : inverted_string_table)
- profile_.add_string_table(p.second);
- return true;
- }
-
- bool WriteProfileForProcess(
- uint64_t pid,
- const std::vector<const ProfilePacket::ProcessHeapSamples*>& proc_samples,
- std::string* serialized) {
- GProfile cur_profile = profile_;
- for (const ProfilePacket::ProcessHeapSamples* samples : proc_samples) {
- if (samples->rejected_concurrent()) {
- PERFETTO_ELOG("WARNING: The profile for %" PRIu64
- " was rejected due to a concurrent profile.",
- pid);
- }
- if (samples->buffer_overran()) {
- PERFETTO_ELOG("WARNING: The profile for %" PRIu64
- " ended early due to a buffer overrun.",
- pid);
- }
- if (samples->buffer_corrupted()) {
- PERFETTO_ELOG("WARNING: The profile for %" PRIu64
- " ended early due to a buffer corruption."
- " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
- " CLIENT MEMORY CORRUPTION.",
- pid);
- }
-
- for (const ProfilePacket::HeapSample& sample : samples->samples()) {
- GSample* gsample = cur_profile.add_sample();
- auto it = callstack_lookup_.find(sample.callstack_id());
- if (it == callstack_lookup_.end()) {
- PERFETTO_ELOG("Callstack referring to invalid callstack id %" PRIu64,
- static_cast<uint64_t>(sample.callstack_id()));
+ bool WriteAllocations(std::vector<Iterator>* view_its,
+ std::set<int64_t>* seen_frames) {
+ for (;;) {
+ bool all_next = true;
+ bool any_next = false;
+ for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+ Iterator& it = (*view_its)[i];
+ bool next = it.Next();
+ if (!it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
+ it.Status().message().c_str());
return false;
}
- for (uint64_t frame_id : it->second)
- gsample->add_location_id(frame_id);
- gsample->add_value(static_cast<int64_t>(sample.self_max()));
- gsample->add_value(
- static_cast<int64_t>(sample.alloc_count() - sample.free_count()));
- gsample->add_value(static_cast<int64_t>(sample.alloc_count()));
- gsample->add_value(static_cast<int64_t>(sample.self_idle()));
- gsample->add_value(static_cast<int64_t>(sample.self_allocated()));
- gsample->add_value(static_cast<int64_t>(sample.self_allocated() -
- sample.self_freed()));
+ all_next = all_next && next;
+ any_next = any_next || next;
+ }
+
+ if (!all_next) {
+ PERFETTO_DCHECK(!any_next);
+ break;
+ }
+
+ GSample* gsample = result_.add_sample();
+ for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+ int64_t callstack_id = (*view_its)[i].Get(0).long_value;
+ if (i == 0) {
+ auto frames = FramesForCallstack(callstack_id);
+ if (frames.empty())
+ return false;
+ for (int64_t frame : frames)
+ gsample->add_location_id(ToPprofId(frame));
+ seen_frames->insert(frames.cbegin(), frames.cend());
+ } else {
+ if (callstack_id != (*view_its)[i].Get(0).long_value) {
+ PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
+ return false;
+ }
+ }
+ gsample->add_value((*view_its)[i].Get(1).long_value);
}
}
- *serialized = cur_profile.SerializeAsString();
return true;
}
+ bool WriteMappings(trace_processor::TraceProcessor* tp,
+ const std::set<int64_t> seen_mappings) {
+ Iterator mapping_it = tp->ExecuteQuery(
+ "SELECT id, exact_offset, start, end, name "
+ "FROM stack_profile_mapping;");
+ size_t mappings_no = 0;
+ while (mapping_it.Next()) {
+ int64_t id = mapping_it.Get(0).long_value;
+ if (seen_mappings.find(id) == seen_mappings.end())
+ continue;
+ ++mappings_no;
+ GMapping* gmapping = result_.add_mapping();
+ gmapping->set_id(ToPprofId(id));
+ // Do not set the build_id here to avoid downstream services
+ // trying to symbolize (e.g. b/141735056)
+ gmapping->set_file_offset(
+ static_cast<uint64_t>(mapping_it.Get(1).long_value));
+ gmapping->set_memory_start(
+ static_cast<uint64_t>(mapping_it.Get(2).long_value));
+ gmapping->set_memory_limit(
+ static_cast<uint64_t>(mapping_it.Get(3).long_value));
+ gmapping->set_filename(Intern(mapping_it.Get(4).string_value));
+ }
+ if (!mapping_it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid mapping iterator: %s",
+ mapping_it.Status().message().c_str());
+ return false;
+ }
+ if (mappings_no != seen_mappings.size()) {
+ PERFETTO_DFATAL_OR_ELOG("Missing mappings.");
+ return false;
+ }
+ return true;
+ }
+
+ bool WriteSymbols(trace_processor::TraceProcessor* tp,
+ const std::set<int64_t>& seen_symbol_ids) {
+ Iterator symbol_it = tp->ExecuteQuery(
+ "SELECT id, name, source_file FROM stack_profile_symbol");
+ size_t symbols_no = 0;
+ while (symbol_it.Next()) {
+ int64_t id = symbol_it.Get(0).long_value;
+ if (seen_symbol_ids.find(id) == seen_symbol_ids.end())
+ continue;
+ ++symbols_no;
+ GFunction* gfunction = result_.add_function();
+ gfunction->set_id(ToPprofId(id));
+ gfunction->set_name(Intern(symbol_it.Get(1).string_value));
+ gfunction->set_filename(Intern(symbol_it.Get(2).string_value));
+ }
+
+ if (!symbol_it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+ symbol_it.Status().message().c_str());
+ return false;
+ }
+
+ if (symbols_no != seen_symbol_ids.size()) {
+ PERFETTO_DFATAL_OR_ELOG("Missing symbols.");
+ return false;
+ }
+ return true;
+ }
+
+ bool WriteFrames(trace_processor::TraceProcessor* tp,
+ const std::set<int64_t>& seen_frames,
+ std::set<int64_t>* seen_mappings,
+ std::set<int64_t>* seen_symbol_ids) {
+ Iterator frame_it = tp->ExecuteQuery(
+ "SELECT spf.id, spf.name, spf.mapping, spf.rel_pc, spf.symbol_set_id "
+ "FROM stack_profile_frame spf;");
+ size_t frames_no = 0;
+ while (frame_it.Next()) {
+ int64_t frame_id = frame_it.Get(0).long_value;
+ if (seen_frames.find(frame_id) == seen_frames.end())
+ continue;
+ frames_no++;
+ std::string frame_name = frame_it.Get(1).string_value;
+ int64_t mapping_id = frame_it.Get(2).long_value;
+ int64_t rel_pc = frame_it.Get(3).long_value;
+ int64_t symbol_set_id = frame_it.Get(4).long_value;
+
+ seen_mappings->emplace(mapping_id);
+ GLocation* glocation = result_.add_location();
+ glocation->set_id(ToPprofId(frame_id));
+ glocation->set_mapping_id(ToPprofId(mapping_id));
+ // TODO(fmayer): Convert to abspc.
+ // relpc + (mapping.start - (mapping.exact_offset -
+ // mapping.start_offset)).
+ glocation->set_address(static_cast<uint64_t>(rel_pc));
+ if (symbol_set_id) {
+ for (const Line& line : LineForSymbolSetId(symbol_set_id)) {
+ seen_symbol_ids->emplace(line.symbol_id);
+ GLine* gline = glocation->add_line();
+ gline->set_line(line.line_number);
+ gline->set_function_id(ToPprofId(line.symbol_id));
+ }
+ } else {
+ int64_t synthesized_symbol_id = ++max_symbol_id_;
+ std::string demangled_name = frame_name;
+ MaybeDemangle(&demangled_name);
+
+ GFunction* gfunction = result_.add_function();
+ gfunction->set_id(ToPprofId(synthesized_symbol_id));
+ gfunction->set_name(Intern(demangled_name));
+ gfunction->set_system_name(Intern(frame_name));
+
+ GLine* gline = glocation->add_line();
+ gline->set_line(0);
+ gline->set_function_id(ToPprofId(synthesized_symbol_id));
+ }
+ }
+
+ if (!frame_it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+ frame_it.Status().message().c_str());
+ return false;
+ }
+ if (frames_no != seen_frames.size()) {
+ PERFETTO_DFATAL_OR_ELOG("Missing frames.");
+ return false;
+ }
+ return true;
+ }
+
+ uint64_t ToPprofId(int64_t id) {
+ PERFETTO_DCHECK(id >= 0);
+ return static_cast<uint64_t>(id) + 1;
+ }
+
+ void WriteSampleTypes() {
+ for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+ auto* sample_type = result_.add_sample_type();
+ sample_type->set_type(Intern(kViews[i].type));
+ sample_type->set_unit(Intern(kViews[i].unit));
+ }
+ }
+
+ GProfile GenerateGProfile(trace_processor::TraceProcessor* tp,
+ uint64_t upid,
+ uint64_t ts) {
+ std::set<int64_t> seen_frames;
+ std::set<int64_t> seen_mappings;
+ std::set<int64_t> seen_symbol_ids;
+
+ std::vector<Iterator> view_its = BuildViewIterators(tp, upid, ts);
+
+ WriteSampleTypes();
+ if (!WriteAllocations(&view_its, &seen_frames))
+ return {};
+ if (!WriteFrames(tp, seen_frames, &seen_mappings, &seen_symbol_ids))
+ return {};
+ if (!WriteMappings(tp, seen_mappings))
+ return {};
+ if (!WriteSymbols(tp, seen_symbol_ids))
+ return {};
+ return std::move(result_);
+ }
+
+ const std::vector<int64_t>& FramesForCallstack(int64_t callstack_id) {
+ return callsite_to_frames_[static_cast<size_t>(callstack_id)];
+ }
+
+ const std::vector<Line>& LineForSymbolSetId(int64_t symbol_set_id) {
+ auto it = symbol_set_id_to_lines_.find(symbol_set_id);
+ if (it == symbol_set_id_to_lines_.end())
+ return empty_line_vector_;
+ return it->second;
+ }
+
+ int64_t Intern(const std::string& s) {
+ auto it = string_table_.find(s);
+ if (it == string_table_.end()) {
+ std::tie(it, std::ignore) =
+ string_table_.emplace(s, string_table_.size());
+ result_.add_string_table(s);
+ }
+ return it->second;
+ }
+
private:
- TraceSymbolTable* symbol_table_;
- GProfile profile_;
-
- uint64_t max_function_id_ = 0;
-
- std::map<uint64_t, uint64_t> mapping_base_;
- std::set<uint64_t> functions_to_dump_;
- std::map<uint64_t, const std::vector<uint64_t>> callstack_lookup_;
- std::map<uint64_t, std::string> string_lookup_;
- std::map<std::string, uint64_t> string_table_{
- {"", kEmpty},
- {"objects", kObjects},
- {"alloc_objects", kAllocObjects},
- {"count", kCount},
- {"space", kSpace},
- {"alloc_space", kAllocSpace},
- {"bytes", kBytes},
- {"idle_space", kIdleSpace},
- {"max_space", kMaxSpace}};
+ GProfile result_;
+ std::map<std::string, int64_t> string_table_;
+ const std::vector<std::vector<int64_t>>& callsite_to_frames_;
+ const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines_;
+ const std::vector<Line> empty_line_vector_;
+ int64_t max_symbol_id_;
};
-bool DumpProfilePacket(const std::vector<ProfilePacket>& packet_fragments,
- const SequencedBundle& bundle,
- std::vector<SerializedProfile>* output,
- Symbolizer* symbolizer) {
- TraceSymbolTable symbol_table(symbolizer);
- if (!symbol_table.Visit(packet_fragments, bundle))
- return false;
- if (!symbol_table.Finalize())
- return false;
-
- GProfileWriter writer(&symbol_table);
- if (!writer.Visit(packet_fragments, bundle))
- return false;
-
- if (!writer.Finalize())
- return false;
-
- std::map<uint64_t, std::vector<const ProfilePacket::ProcessHeapSamples*>>
- heap_samples;
- for (const ProfilePacket& packet : packet_fragments) {
- for (const ProfilePacket::ProcessHeapSamples& samples :
- packet.process_dumps()) {
- heap_samples[samples.pid()].emplace_back(&samples);
- }
- }
- for (const auto& p : heap_samples) {
- std::string serialized;
- if (!writer.WriteProfileForProcess(p.first, p.second, &serialized))
- return false;
- output->emplace_back(SerializedProfile{p.first, std::move(serialized)});
- }
- return true;
-}
-
} // namespace
bool TraceToPprof(std::istream* input,
std::vector<SerializedProfile>* output,
- Symbolizer* symbolizer) {
- return VisitCompletePacket(
- input, [output, symbolizer](
- uint32_t, const std::vector<ProfilePacket>& packet_fragments,
- const SequencedBundle& bundle) {
- return DumpProfilePacket(packet_fragments, bundle, output, symbolizer);
- });
+ Symbolizer* symbolizer,
+ uint64_t pid,
+ const std::vector<uint64_t>& timestamps) {
+ trace_processor::Config config;
+ std::unique_ptr<trace_processor::TraceProcessor> tp =
+ trace_processor::TraceProcessor::CreateInstance(config);
+
+ if (!ReadTrace(tp.get(), input))
+ return false;
+
+ tp->NotifyEndOfFile();
+ return TraceToPprof(tp.get(), output, symbolizer, pid, timestamps);
}
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output) {
- return TraceToPprof(input, output, nullptr);
+bool TraceToPprof(trace_processor::TraceProcessor* tp,
+ std::vector<SerializedProfile>* output,
+ Symbolizer* symbolizer,
+ uint64_t pid,
+ const std::vector<uint64_t>& timestamps) {
+ if (symbolizer) {
+ SymbolizeDatabase(
+ tp, symbolizer, [&tp](perfetto::protos::TracePacket packet) {
+ size_t size = static_cast<size_t>(packet.ByteSize());
+ std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
+ packet.SerializeToArray(buf.get(), packet.ByteSize());
+
+ std::unique_ptr<uint8_t[]> preamble(new uint8_t[11]);
+ preamble[0] =
+ MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
+ uint8_t* end = WriteVarInt(size, &preamble[1]);
+ size_t preamble_size = static_cast<size_t>(end - &preamble[0]);
+ auto status = tp->Parse(std::move(preamble), preamble_size);
+ if (!status.ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
+ status.message().c_str());
+ return;
+ }
+ status = tp->Parse(std::move(buf), size);
+ if (!status.ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
+ status.message().c_str());
+ return;
+ }
+ });
+ }
+
+ tp->NotifyEndOfFile();
+ auto max_symbol_id_it =
+ tp->ExecuteQuery("SELECT MAX(id) from stack_profile_symbol");
+ if (!max_symbol_id_it.Next()) {
+ PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
+ max_symbol_id_it.Status().message().c_str());
+ return false;
+ }
+
+ int64_t max_symbol_id = max_symbol_id_it.Get(0).long_value;
+ auto callsite_to_frames = GetCallsiteToFrames(tp);
+ auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
+
+ Iterator it = tp->ExecuteQuery(kQueryProfiles);
+ while (it.Next()) {
+ GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
+ max_symbol_id);
+ uint64_t upid = static_cast<uint64_t>(it.Get(0).long_value);
+ uint64_t ts = static_cast<uint64_t>(it.Get(1).long_value);
+ uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).long_value);
+ if ((pid > 0 && profile_pid != pid) ||
+ (!timestamps.empty() && std::find(timestamps.begin(), timestamps.end(),
+ ts) == timestamps.end())) {
+ continue;
+ }
+
+ std::string pid_query = "select pid from process where upid = ";
+ pid_query += std::to_string(upid) + ";";
+ Iterator pid_it = tp->ExecuteQuery(pid_query);
+ PERFETTO_CHECK(pid_it.Next());
+
+ GProfile profile = builder.GenerateGProfile(tp, upid, ts);
+ output->emplace_back(
+ SerializedProfile{static_cast<uint64_t>(pid_it.Get(0).long_value),
+ profile.SerializeAsString()});
+ }
+ if (!it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+ it.Status().message().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool TraceToPprof(std::istream* input,
+ std::vector<SerializedProfile>* output,
+ uint64_t pid,
+ const std::vector<uint64_t>& timestamps) {
+ return TraceToPprof(input, output, nullptr, pid, timestamps);
}
} // namespace trace_to_text
diff --git a/tools/trace_to_text/profile_visitor.cc b/tools/trace_to_text/profile_visitor.cc
deleted file mode 100644
index fb41fba..0000000
--- a/tools/trace_to_text/profile_visitor.cc
+++ /dev/null
@@ -1,169 +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.
- */
-
-#include "tools/trace_to_text/profile_visitor.h"
-
-#include <unordered_map>
-#include "protos/perfetto/trace/trace.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-
-#include "perfetto/ext/base/string_splitter.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-namespace {
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedData;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfiledFrameSymbols;
-using ::perfetto::protos::ProfilePacket;
-
-struct ProfilePackets {
- uint32_t seq_id;
- std::vector<protos::ProfilePacket> packets;
-};
-
-bool IsPacketIndexContiguous(
- const std::vector<perfetto::protos::ProfilePacket>& packets) {
- for (size_t i = 1; i < packets.size(); ++i) {
- // Ensure we are not missing a chunk.
- if (packets[i - 1].index() + 1 != packets[i].index()) {
- return false;
- }
- }
- return true;
-}
-} // namespace
-
-bool ProfileVisitor::Visit(
- const std::vector<protos::ProfilePacket>& packet_fragments,
- const SequencedBundle& bundle) {
- for (const ProfilePacket& packet : packet_fragments) {
- for (const InternedString& interned_string : packet.strings())
- if (!AddInternedString(interned_string))
- return false;
- }
- for (const InternedData& data : bundle.interned_data) {
- for (const InternedString& interned_string : data.build_ids())
- if (!AddInternedString(interned_string))
- return false;
- for (const InternedString& interned_string : data.mapping_paths())
- if (!AddInternedString(interned_string))
- return false;
- for (const InternedString& interned_string : data.function_names())
- if (!AddInternedString(interned_string))
- return false;
- for (const InternedString& interned_string : data.source_paths())
- if (!AddInternedString(interned_string))
- return false;
- // TODO (140860736): This should be outside the interned section.
- for (const ProfiledFrameSymbols& pfs : data.profiled_frame_symbols())
- if (!AddProfiledFrameSymbols(pfs))
- return false;
- }
- for (const ProfiledFrameSymbols& pfs : bundle.symbols)
- if (!AddProfiledFrameSymbols(pfs))
- return false;
-
- for (const ProfilePacket& packet : packet_fragments) {
- for (const Callstack& callstack : packet.callstacks())
- if (!AddCallstack(callstack))
- return false;
- }
- for (const InternedData& data : bundle.interned_data) {
- for (const Callstack& callstack : data.callstacks())
- if (!AddCallstack(callstack))
- return false;
- }
-
- for (const ProfilePacket& packet : packet_fragments) {
- for (const Mapping& mapping : packet.mappings())
- if (!AddMapping(mapping))
- return false;
- }
- for (const InternedData& data : bundle.interned_data) {
- for (const Mapping& callstack : data.mappings()) {
- if (!AddMapping(callstack))
- return false;
- }
- }
-
- for (const ProfilePacket& packet : packet_fragments) {
- for (const Frame& frame : packet.frames()) {
- if (!AddFrame(frame))
- return false;
- }
- }
- for (const InternedData& data : bundle.interned_data) {
- for (const Frame& frame : data.frames()) {
- if (!AddFrame(frame))
- return false;
- }
- }
- return true;
-}
-
-ProfileVisitor::~ProfileVisitor() = default;
-
-bool VisitCompletePacket(
- std::istream* input,
- const std::function<bool(uint32_t,
- const std::vector<protos::ProfilePacket>&,
- const SequencedBundle&)>& fn) {
- // Rolling profile packets per seq id. Cleared on finalization.
- std::unordered_map<uint32_t, std::vector<protos::ProfilePacket>>
- rolling_profile_packets_by_seq;
- std::vector<ProfilePackets> complete_profile_packets;
- // Append-only interned data and symbols by seq id
- std::unordered_map<uint32_t, SequencedBundle> bundle_by_seq;
- ForEachPacketInTrace(input, [&rolling_profile_packets_by_seq,
- &complete_profile_packets, &bundle_by_seq](
- const protos::TracePacket& packet) {
- uint32_t seq_id = packet.trusted_packet_sequence_id();
- if (packet.has_interned_data()) {
- bundle_by_seq[seq_id].interned_data.emplace_back(packet.interned_data());
- }
-
- if (packet.has_profile_packet()) {
- std::vector<protos::ProfilePacket>& rolling_profile_packets =
- rolling_profile_packets_by_seq[seq_id];
- rolling_profile_packets.emplace_back(packet.profile_packet());
-
- if (!packet.profile_packet().continued()) {
- if (IsPacketIndexContiguous(rolling_profile_packets)) {
- complete_profile_packets.push_back({seq_id, rolling_profile_packets});
- rolling_profile_packets_by_seq.erase(seq_id);
- }
- }
- }
- });
-
- bool success = true;
- for (const auto& packets : complete_profile_packets) {
- success &=
- fn(packets.seq_id, packets.packets, bundle_by_seq[packets.seq_id]);
- }
- if (!rolling_profile_packets_by_seq.empty()) {
- PERFETTO_ELOG("WARNING: Truncated heap dump.");
- return false;
- }
- return success;
-}
-} // namespace trace_to_text
-} // namespace perfetto
diff --git a/tools/trace_to_text/profile_visitor.h b/tools/trace_to_text/profile_visitor.h
deleted file mode 100644
index 4723f14..0000000
--- a/tools/trace_to_text/profile_visitor.h
+++ /dev/null
@@ -1,61 +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.
- */
-
-#ifndef TOOLS_TRACE_TO_TEXT_PROFILE_VISITOR_H_
-#define TOOLS_TRACE_TO_TEXT_PROFILE_VISITOR_H_
-
-#include <vector>
-
-#include "perfetto/base/logging.h"
-
-#include "tools/trace_to_text/utils.h"
-
-#include "protos/perfetto/trace/interned_data/interned_data.pb.h"
-#include "protos/perfetto/trace/profiling/profile_common.pb.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-struct SequencedBundle {
- std::vector<protos::InternedData> interned_data;
- std::vector<protos::ProfiledFrameSymbols> symbols;
-};
-
-class ProfileVisitor {
- public:
- bool Visit(const std::vector<protos::ProfilePacket>&, const SequencedBundle&);
- virtual bool AddInternedString(
- const protos::InternedString& interned_string) = 0;
- virtual bool AddCallstack(const protos::Callstack& callstack) = 0;
- virtual bool AddMapping(const protos::Mapping& mapping) = 0;
- virtual bool AddFrame(const protos::Frame& frame) = 0;
- virtual bool AddProfiledFrameSymbols(
- const protos::ProfiledFrameSymbols& symbol) = 0;
- virtual ~ProfileVisitor();
-};
-
-bool VisitCompletePacket(
- std::istream* input,
- const std::function<bool(uint32_t,
- const std::vector<protos::ProfilePacket>&,
- const SequencedBundle&)>& fn);
-
-} // namespace trace_to_text
-} // namespace perfetto
-
-#endif // TOOLS_TRACE_TO_TEXT_PROFILE_VISITOR_H_
diff --git a/tools/trace_to_text/symbolize_profile.cc b/tools/trace_to_text/symbolize_profile.cc
index 519021c..aa7c2ef 100644
--- a/tools/trace_to_text/symbolize_profile.cc
+++ b/tools/trace_to_text/symbolize_profile.cc
@@ -46,86 +46,6 @@
*output << std::string(length_field, end);
*output << str;
}
-
-using Iterator = trace_processor::TraceProcessor::Iterator;
-
-constexpr const char* kQueryUnsymbolized =
- "select spm.name, spm.build_id, spf.rel_pc "
- "from stack_profile_frame spf "
- "join stack_profile_mapping spm "
- "on spf.mapping = spm.id "
- "where spm.build_id != '' and spf.symbol_set_id == 0";
-
-std::string FromHex(const char* str, size_t size) {
- if (size % 2) {
- PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
- return "";
- }
- std::string result(size / 2, '\0');
- for (size_t i = 0; i < size; i += 2) {
- char hex_byte[3];
- hex_byte[0] = str[i];
- hex_byte[1] = str[i + 1];
- hex_byte[2] = '\0';
- char* end;
- long int byte = strtol(hex_byte, &end, 16);
- if (*end != '\0') {
- PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
- return "";
- }
- result[i / 2] = static_cast<char>(byte);
- }
- return result;
-}
-
-std::string FromHex(const std::string& str) {
- return FromHex(str.c_str(), str.size());
-}
-
-std::map<std::pair<std::string, std::string>, std::vector<uint64_t>>
-GetUnsymbolizedFrames(trace_processor::TraceProcessor* tp) {
- std::map<std::pair<std::string, std::string>, std::vector<uint64_t>> res;
- for (Iterator it = tp->ExecuteQuery(kQueryUnsymbolized); it.Next();)
- res[std::make_pair(it.Get(0).string_value, FromHex(it.Get(1).string_value))]
- .emplace_back(it.Get(2).long_value);
-
- return res;
-}
-
-} // namespace
-
-void SymbolizeDatabase(
- trace_processor::TraceProcessor* tp,
- Symbolizer* symbolizer,
- std::function<void(const perfetto::protos::TracePacket&)> callback) {
- PERFETTO_CHECK(symbolizer);
- auto unsymbolized = GetUnsymbolizedFrames(tp);
- for (auto it = unsymbolized.cbegin(); it != unsymbolized.cend(); ++it) {
- const auto& name_and_buildid = it->first;
- const std::vector<uint64_t>& rel_pcs = it->second;
- auto res = symbolizer->Symbolize(name_and_buildid.first,
- name_and_buildid.second, rel_pcs);
- if (res.empty())
- continue;
-
- perfetto::protos::TracePacket packet;
- perfetto::protos::ModuleSymbols* module_symbols =
- packet.mutable_module_symbols();
- module_symbols->set_path(name_and_buildid.first);
- module_symbols->set_build_id(name_and_buildid.second);
- PERFETTO_DCHECK(res.size() == rel_pcs.size());
- for (size_t i = 0; i < res.size(); ++i) {
- auto* address_symbols = module_symbols->add_address_symbols();
- address_symbols->set_address(rel_pcs[0]);
- for (const SymbolizedFrame& frame : res[i]) {
- auto* line = address_symbols->add_lines();
- line->set_function_name(frame.function_name);
- line->set_source_file_name(frame.file_name);
- line->set_line_number(frame.line);
- }
- }
- callback(packet);
- }
}
// Ingest profile, and emit a symbolization table for each sequence. This can
@@ -150,6 +70,8 @@
if (!ReadTrace(tp.get(), input))
PERFETTO_FATAL("Failed to read trace.");
+ tp->NotifyEndOfFile();
+
SymbolizeDatabase(tp.get(), symbolizer.get(),
[output](const perfetto::protos::TracePacket& packet) {
WriteTracePacket(packet.SerializeAsString(), output);
diff --git a/tools/trace_to_text/symbolize_profile.h b/tools/trace_to_text/symbolize_profile.h
index e0867ae..3c787fd 100644
--- a/tools/trace_to_text/symbolize_profile.h
+++ b/tools/trace_to_text/symbolize_profile.h
@@ -29,11 +29,6 @@
int SymbolizeProfile(std::istream* input, std::ostream* output);
-void SymbolizeDatabase(
- trace_processor::TraceProcessor* tp,
- Symbolizer* symbolizer,
- std::function<void(const perfetto::protos::TracePacket&)> callback);
-
} // namespace trace_to_text
} // namespace perfetto
diff --git a/tools/trace_to_text/trace_symbol_table.cc b/tools/trace_to_text/trace_symbol_table.cc
deleted file mode 100644
index 4b31301..0000000
--- a/tools/trace_to_text/trace_symbol_table.cc
+++ /dev/null
@@ -1,166 +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.
- */
-
-#include "tools/trace_to_text/trace_symbol_table.h"
-
-#include <algorithm>
-#include <map>
-#include <set>
-#include <string>
-#include <vector>
-
-#include <inttypes.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "perfetto/profiling/symbolizer.h"
-#include "perfetto/protozero/proto_utils.h"
-
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/optional.h"
-#include "perfetto/ext/base/pipe.h"
-#include "perfetto/ext/base/utils.h"
-
-#include "tools/trace_to_text/utils.h"
-
-#include "protos/perfetto/trace/profiling/profile_common.pb.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pb.h"
-#include "protos/perfetto/trace/trace.pbzero.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-
-#include "protos/perfetto/trace/interned_data/interned_data.pb.h"
-
-namespace perfetto {
-namespace trace_to_text {
-namespace {
-
-using ::protozero::proto_utils::kMessageLengthFieldSize;
-using ::protozero::proto_utils::MakeTagLengthDelimited;
-using ::protozero::proto_utils::WriteVarInt;
-
-using ::perfetto::protos::Callstack;
-using ::perfetto::protos::Frame;
-using ::perfetto::protos::InternedData;
-using ::perfetto::protos::InternedString;
-using ::perfetto::protos::Mapping;
-using ::perfetto::protos::ProfiledFrameSymbols;
-using ::perfetto::protos::ProfilePacket;
-
-} // namespace
-
-bool TraceSymbolTable::AddInternedString(const InternedString& string) {
- interned_strings_.emplace(string.iid(), string.str());
- max_string_intern_id_ =
- std::max<uint64_t>(string.iid(), max_string_intern_id_);
- return true;
-}
-
-bool TraceSymbolTable::AddMapping(const Mapping& mapping) {
- mappings_.emplace(mapping.iid(), ResolveMapping(mapping));
- return true;
-}
-
-bool TraceSymbolTable::AddFrame(const Frame& frame) {
- if (symbols_for_frame_.find(frame.iid()) == symbols_for_frame_.end()) {
- to_symbolize_[frame.mapping_id()].emplace_back(frame.iid());
- rel_pc_for_frame_[frame.iid()] = frame.rel_pc();
- }
- return true;
-}
-
-bool TraceSymbolTable::AddProfiledFrameSymbols(
- const protos::ProfiledFrameSymbols& symbol) {
- std::vector<SymbolizedFrame> frames;
- const auto& name_ids = symbol.function_name_id();
- const auto& file_ids = symbol.file_name_id();
- const auto& lines = symbol.line_number();
-
- if (name_ids.size() != file_ids.size() || file_ids.size() != lines.size()) {
- PERFETTO_DFATAL_OR_ELOG("Invalid ProfiledFrameSymbols");
- return false;
- }
-
- for (int i = 0; i < name_ids.size(); ++i) {
- SymbolizedFrame frame{ResolveString(name_ids.Get(i)),
- ResolveString(file_ids.Get(i)), lines.Get(i)};
- frames.emplace_back(std::move(frame));
- }
-
- symbols_for_frame_[symbol.frame_iid()] = std::move(frames);
- return true;
-}
-
-bool TraceSymbolTable::Finalize() {
- if (symbolizer_ == nullptr)
- return true;
-
- for (const auto& mapping_and_frame_iids : to_symbolize_) {
- auto it = mappings_.find(mapping_and_frame_iids.first);
- if (it == mappings_.end()) {
- PERFETTO_DFATAL_OR_ELOG("Invalid mapping.");
- return false;
- }
- const ResolvedMapping& mapping = it->second;
- const std::vector<uint64_t>& frame_iids = mapping_and_frame_iids.second;
- std::vector<uint64_t> rel_pcs;
- for (uint64_t frame_iid : frame_iids)
- rel_pcs.emplace_back(rel_pc_for_frame_[frame_iid]);
- auto result =
- symbolizer_->Symbolize(mapping.mapping_name, mapping.build_id, rel_pcs);
- if (result.empty())
- continue;
- if (result.size() != frame_iids.size()) {
- PERFETTO_DFATAL_OR_ELOG("Invalid response from symbolizer.");
- return false;
- }
- for (size_t i = 0; i < frame_iids.size(); ++i) {
- if (!result.empty())
- symbols_for_frame_[frame_iids[i]] = std::move(result[i]);
- }
- }
- return true;
-}
-
-const std::vector<SymbolizedFrame>* TraceSymbolTable::Get(
- uint64_t frame_iid) const {
- auto it = symbols_for_frame_.find(frame_iid);
- if (it == symbols_for_frame_.end())
- return nullptr;
- return &it->second;
-}
-
-const std::string& TraceSymbolTable::ResolveString(uint64_t iid) {
- auto it = interned_strings_.find(iid);
- if (it == interned_strings_.end())
- return kEmptyString;
- return it->second;
-}
-
-TraceSymbolTable::ResolvedMapping TraceSymbolTable::ResolveMapping(
- const Mapping& mapping) {
- std::string path;
- for (uint64_t iid : mapping.path_string_ids()) {
- path += "/";
- path += ResolveString(iid);
- }
- return {std::move(path), ResolveString(mapping.build_id())};
-}
-
-} // namespace trace_to_text
-} // namespace perfetto
diff --git a/tools/trace_to_text/trace_symbol_table.h b/tools/trace_to_text/trace_symbol_table.h
deleted file mode 100644
index 067d613..0000000
--- a/tools/trace_to_text/trace_symbol_table.h
+++ /dev/null
@@ -1,76 +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.
- */
-
-#ifndef TOOLS_TRACE_TO_TEXT_TRACE_SYMBOL_TABLE_H_
-#define TOOLS_TRACE_TO_TEXT_TRACE_SYMBOL_TABLE_H_
-
-#include <iostream>
-
-#include "perfetto/profiling/symbolizer.h"
-#include "tools/trace_to_text/profile_visitor.h"
-
-namespace perfetto {
-namespace trace_to_text {
-
-class TraceSymbolTable : public ProfileVisitor {
- public:
- TraceSymbolTable(Symbolizer* symbolizer) : symbolizer_(symbolizer) {}
- bool AddCallstack(const protos::Callstack&) override { return true; }
- bool AddInternedString(const protos::InternedString& string) override;
- bool AddFrame(const protos::Frame& frame) override;
- bool AddMapping(const protos::Mapping& mapping) override;
- bool AddProfiledFrameSymbols(
- const protos::ProfiledFrameSymbols& symbol) override;
-
- const std::vector<SymbolizedFrame>* Get(uint64_t frame_iid) const;
- // Call Finalize before using Get or WriteResult.
- bool Finalize();
-
- private:
- // This is so we can return a const std::string& in ResolveString.
- const std::string kEmptyString = "";
- struct ResolvedMapping {
- std::string mapping_name;
- std::string build_id;
- };
-
- const std::string& ResolveString(uint64_t iid);
- ResolvedMapping ResolveMapping(const protos::Mapping& mapping);
-
- // Can be nullptr to disable symbolization. Then TraceSymbolTable only reads
- // the symbol table from the trace.
- Symbolizer* symbolizer_;
-
- std::map<uint64_t, std::string> interned_strings_;
- std::map<uint64_t, ResolvedMapping> mappings_;
-
- std::map<std::string, uint64_t> intern_table_;
- uint64_t max_string_intern_id_ = 0;
-
- std::map<uint64_t /* frame id */, uint64_t /* rel_pc */> rel_pc_for_frame_;
-
- std::map<uint64_t /* mapping_id */, std::vector<uint64_t> /* frame id */>
- to_symbolize_;
-
- std::map<uint64_t /* frame_id */, std::vector<SymbolizedFrame>>
- symbols_for_frame_;
-};
-int SymbolizeProfile(std::istream* input, std::ostream* output);
-
-} // namespace trace_to_text
-} // namespace perfetto
-
-#endif // TOOLS_TRACE_TO_TEXT_TRACE_SYMBOL_TABLE_H_
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index e70931d..cc00194 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -47,7 +47,10 @@
namespace perfetto {
namespace trace_to_text {
-int TraceToProfile(std::istream* input, std::ostream* output) {
+int TraceToProfile(std::istream* input,
+ std::ostream* output,
+ uint64_t pid,
+ std::vector<uint64_t> timestamps) {
std::unique_ptr<Symbolizer> symbolizer;
auto binary_path = GetPerfettoBinaryPath();
if (!binary_path.empty()) {
@@ -61,7 +64,7 @@
}
std::vector<SerializedProfile> profiles;
- TraceToPprof(input, &profiles, symbolizer.get());
+ TraceToPprof(input, &profiles, symbolizer.get(), pid, timestamps);
if (profiles.empty()) {
return 0;
}
diff --git a/tools/trace_to_text/trace_to_profile.h b/tools/trace_to_text/trace_to_profile.h
index 4e84f8e..629d3ef 100644
--- a/tools/trace_to_text/trace_to_profile.h
+++ b/tools/trace_to_text/trace_to_profile.h
@@ -18,11 +18,15 @@
#define TOOLS_TRACE_TO_TEXT_TRACE_TO_PROFILE_H_
#include <iostream>
+#include <vector>
namespace perfetto {
namespace trace_to_text {
-int TraceToProfile(std::istream* input, std::ostream* output);
+int TraceToProfile(std::istream* input,
+ std::ostream* output,
+ uint64_t pid = 0,
+ std::vector<uint64_t> timestamps = {});
} // namespace trace_to_text
} // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_systrace.cc b/tools/trace_to_text/trace_to_systrace.cc
index c987165..a7afb5d 100644
--- a/tools/trace_to_text/trace_to_systrace.cc
+++ b/tools/trace_to_text/trace_to_systrace.cc
@@ -25,6 +25,8 @@
#include <memory>
#include <utility>
+#include <zlib.h>
+
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/paged_memory.h"
@@ -46,6 +48,8 @@
namespace {
+const size_t kCompressionBufferSize = 500 * 1024;
+
// Having an empty traceEvents object is necessary for trace viewer to
// load the json properly.
const char kTraceHeader[] = R"({
@@ -123,13 +127,79 @@
}
}
+class TraceWriter {
+ public:
+ TraceWriter(std::ostream* output) : output_(output) {}
+ virtual ~TraceWriter() = default;
+
+ void Write(std::string s) { Write(s.data(), s.size()); }
+
+ virtual void Write(const char* data, size_t sz) {
+ output_->write(data, static_cast<std::streamsize>(sz));
+ }
+
+ private:
+ std::ostream* output_;
+};
+
+class DeflateTraceWriter : public TraceWriter {
+ public:
+ DeflateTraceWriter(std::ostream* output)
+ : TraceWriter(output),
+ buf_(base::PagedMemory::Allocate(kCompressionBufferSize)),
+ start_(static_cast<uint8_t*>(buf_.Get())),
+ end_(start_ + buf_.size()) {
+ CheckEq(deflateInit(&stream_, 9), Z_OK);
+ stream_.next_out = start_;
+ stream_.avail_out = static_cast<unsigned int>(end_ - start_);
+ }
+
+ ~DeflateTraceWriter() override {
+ while (deflate(&stream_, Z_FINISH) != Z_STREAM_END) {
+ Flush();
+ }
+ CheckEq(deflateEnd(&stream_), Z_OK);
+ }
+
+ void Write(const char* data, size_t sz) override {
+ stream_.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(data));
+ stream_.avail_in = static_cast<unsigned int>(sz);
+ while (stream_.avail_in > 0) {
+ CheckEq(deflate(&stream_, Z_NO_FLUSH), Z_OK);
+ if (stream_.avail_out == 0) {
+ Flush();
+ }
+ }
+ }
+
+ private:
+ void Flush() {
+ TraceWriter::Write(reinterpret_cast<char*>(start_),
+ static_cast<size_t>(stream_.next_out - start_));
+ stream_.next_out = start_;
+ stream_.avail_out = static_cast<unsigned int>(end_ - start_);
+ }
+
+ void CheckEq(int actual_code, int expected_code) {
+ if (actual_code == expected_code)
+ return;
+ PERFETTO_FATAL("Expected %d got %d: %s", actual_code, expected_code,
+ stream_.msg);
+ }
+
+ z_stream stream_{};
+ base::PagedMemory buf_;
+ uint8_t* const start_;
+ uint8_t* const end_;
+};
+
class QueryWriter {
public:
- QueryWriter(trace_processor::TraceProcessor* tp, std::ostream* output)
+ QueryWriter(trace_processor::TraceProcessor* tp, TraceWriter* trace_writer)
: tp_(tp),
buffer_(base::PagedMemory::Allocate(kBufferSize)),
global_writer_(static_cast<char*>(buffer_.Get()), kBufferSize),
- output_(output) {}
+ trace_writer_(trace_writer) {}
template <typename Callback>
bool RunQuery(const std::string& sql, Callback callback) {
@@ -142,7 +212,7 @@
if (global_writer_.pos() + line_writer.pos() >= global_writer_.size()) {
fprintf(stderr, "Writing row %" PRIu32 PROGRESS_CHAR, rows);
auto str = global_writer_.GetStringView();
- output_->write(str.data(), static_cast<std::streamsize>(str.size()));
+ trace_writer_->Write(str.data(), str.size());
global_writer_.reset();
}
global_writer_.AppendStringView(line_writer.GetStringView());
@@ -157,7 +227,7 @@
// Flush any dangling pieces in the global writer.
auto str = global_writer_.GetStringView();
- output_->write(str.data(), static_cast<std::streamsize>(str.size()));
+ trace_writer_->Write(str.data(), str.size());
global_writer_.reset();
return true;
}
@@ -168,24 +238,31 @@
trace_processor::TraceProcessor* tp_ = nullptr;
base::PagedMemory buffer_;
base::StringWriter global_writer_;
- std::ostream* output_ = nullptr;
+ TraceWriter* trace_writer_;
};
} // namespace
int TraceToSystrace(std::istream* input,
std::ostream* output,
- Keep truncate_keep,
- bool wrap_in_json) {
+ SystraceKind kind,
+ Keep truncate_keep) {
+ bool wrap_in_json = kind == kSystraceJson;
+ bool compress = kind == kSystraceCompressed;
+
+ std::unique_ptr<TraceWriter> trace_writer(
+ compress ? new DeflateTraceWriter(output) : new TraceWriter(output));
+
trace_processor::Config config;
std::unique_ptr<trace_processor::TraceProcessor> tp =
trace_processor::TraceProcessor::CreateInstance(config);
if (!ReadTrace(tp.get(), input))
return 1;
+ tp->NotifyEndOfFile();
using Iterator = trace_processor::TraceProcessor::Iterator;
- QueryWriter q_writer(tp.get(), output);
+ QueryWriter q_writer(tp.get(), trace_writer.get());
if (wrap_in_json) {
*output << kTraceHeader;
@@ -230,18 +307,18 @@
*output << kFtraceJsonHeader;
} else {
*output << "TRACE:\n";
- *output << kFtraceHeader;
+ trace_writer->Write(kFtraceHeader);
}
fprintf(stderr, "Converting trace events" PROGRESS_CHAR);
fflush(stderr);
- static const char kEstimatSql[] = "select count(1) from raw";
+ static const char kEstimateSql[] = "select count(1) from raw";
uint32_t raw_events = 0;
auto e_callback = [&raw_events](Iterator* it, base::StringWriter*) {
raw_events = static_cast<uint32_t>(it->Get(0).long_value);
};
- if (!q_writer.RunQuery(kEstimatSql, e_callback))
+ if (!q_writer.RunQuery(kEstimateSql, e_callback))
return 1;
auto raw_callback = [wrap_in_json](Iterator* it, base::StringWriter* writer) {
diff --git a/tools/trace_to_text/trace_to_systrace.h b/tools/trace_to_text/trace_to_systrace.h
index c1c4659..3862578 100644
--- a/tools/trace_to_text/trace_to_systrace.h
+++ b/tools/trace_to_text/trace_to_systrace.h
@@ -23,11 +23,12 @@
namespace trace_to_text {
enum class Keep { kStart = 0, kEnd, kAll };
+enum SystraceKind { kSystraceNormal = 0, kSystraceCompressed, kSystraceJson };
int TraceToSystrace(std::istream* input,
std::ostream* output,
- Keep truncate_keep,
- bool wrap_in_json);
+ SystraceKind kind,
+ Keep truncate_keep);
} // namespace trace_to_text
} // namespace perfetto
diff --git a/tools/trace_to_text/utils.cc b/tools/trace_to_text/utils.cc
index 6813df1..4f5169f 100644
--- a/tools/trace_to_text/utils.cc
+++ b/tools/trace_to_text/utils.cc
@@ -33,6 +33,64 @@
namespace perfetto {
namespace trace_to_text {
+namespace {
+
+using Iterator = trace_processor::TraceProcessor::Iterator;
+
+constexpr const char* kQueryUnsymbolized =
+ "select spm.name, spm.build_id, spf.rel_pc "
+ "from stack_profile_frame spf "
+ "join stack_profile_mapping spm "
+ "on spf.mapping = spm.id "
+ "where spm.build_id != '' and spf.symbol_set_id == 0";
+
+std::string FromHex(const char* str, size_t size) {
+ if (size % 2) {
+ PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
+ return "";
+ }
+ std::string result(size / 2, '\0');
+ for (size_t i = 0; i < size; i += 2) {
+ char hex_byte[3];
+ hex_byte[0] = str[i];
+ hex_byte[1] = str[i + 1];
+ hex_byte[2] = '\0';
+ char* end;
+ long int byte = strtol(hex_byte, &end, 16);
+ if (*end != '\0') {
+ PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
+ return "";
+ }
+ result[i / 2] = static_cast<char>(byte);
+ }
+ return result;
+}
+
+std::string FromHex(const std::string& str) {
+ return FromHex(str.c_str(), str.size());
+}
+
+using NameAndBuildIdPair = std::pair<std::string, std::string>;
+
+std::map<NameAndBuildIdPair, std::vector<uint64_t>> GetUnsymbolizedFrames(
+ trace_processor::TraceProcessor* tp) {
+ std::map<std::pair<std::string, std::string>, std::vector<uint64_t>> res;
+ Iterator it = tp->ExecuteQuery(kQueryUnsymbolized);
+ while (it.Next()) {
+ auto name_and_buildid =
+ std::make_pair(it.Get(0).string_value, FromHex(it.Get(1).string_value));
+ int64_t rel_pc = it.Get(2).long_value;
+ res[name_and_buildid].emplace_back(rel_pc);
+ }
+ if (!it.Status().ok()) {
+ PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+ it.Status().message().c_str());
+ return {};
+ }
+ return res;
+}
+
+} // namespace
void ForEachPacketBlobInTrace(
std::istream* input,
@@ -138,12 +196,45 @@
file_size += static_cast<uint64_t>(rsize);
tp->Parse(std::move(buf), static_cast<size_t>(rsize));
}
- tp->NotifyEndOfFile();
fprintf(stderr, "Loaded trace%c", kProgressChar);
fflush(stderr);
return true;
}
+void SymbolizeDatabase(
+ trace_processor::TraceProcessor* tp,
+ Symbolizer* symbolizer,
+ std::function<void(perfetto::protos::TracePacket)> callback) {
+ PERFETTO_CHECK(symbolizer);
+ auto unsymbolized = GetUnsymbolizedFrames(tp);
+ for (auto it = unsymbolized.cbegin(); it != unsymbolized.cend(); ++it) {
+ const auto& name_and_buildid = it->first;
+ const std::vector<uint64_t>& rel_pcs = it->second;
+ auto res = symbolizer->Symbolize(name_and_buildid.first,
+ name_and_buildid.second, rel_pcs);
+ if (res.empty())
+ continue;
+
+ perfetto::protos::TracePacket packet;
+ perfetto::protos::ModuleSymbols* module_symbols =
+ packet.mutable_module_symbols();
+ module_symbols->set_path(name_and_buildid.first);
+ module_symbols->set_build_id(name_and_buildid.second);
+ PERFETTO_DCHECK(res.size() == rel_pcs.size());
+ for (size_t i = 0; i < res.size(); ++i) {
+ auto* address_symbols = module_symbols->add_address_symbols();
+ address_symbols->set_address(rel_pcs[i]);
+ for (const SymbolizedFrame& frame : res[i]) {
+ auto* line = address_symbols->add_lines();
+ line->set_function_name(frame.function_name);
+ line->set_source_file_name(frame.file_name);
+ line->set_line_number(frame.line);
+ }
+ }
+ callback(std::move(packet));
+ }
+}
+
} // namespace trace_to_text
} // namespace perfetto
diff --git a/tools/trace_to_text/utils.h b/tools/trace_to_text/utils.h
index 729d902..4bbcb98 100644
--- a/tools/trace_to_text/utils.h
+++ b/tools/trace_to_text/utils.h
@@ -27,6 +27,7 @@
#include <vector>
#include "perfetto/base/build_config.h"
+#include "perfetto/profiling/symbolizer.h"
#include "perfetto/trace_processor/trace_processor.h"
namespace perfetto {
@@ -57,6 +58,11 @@
bool ReadTrace(trace_processor::TraceProcessor* tp, std::istream* input);
+void SymbolizeDatabase(
+ trace_processor::TraceProcessor* tp,
+ Symbolizer* symbolizer,
+ std::function<void(perfetto::protos::TracePacket)> callback);
+
} // namespace trace_to_text
} // namespace perfetto
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 1acf957..b5ac993 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -459,6 +459,28 @@
}
}
+ button {
+ background-color: #262f3c;
+ color: #fff;
+ font-size: 0.875rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: .5rem;
+ padding-bottom: .5rem;
+ border-radius: .25rem;
+ margin-top: 12px;
+ }
+
+ .explanation {
+ font-size: 14px;
+ width: 35%;
+ margin-top: 10px;
+ }
+
+ .material-icons {
+ vertical-align: middle;
+ margin-right: 10px;
+ }
}
.tickbar {
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index abd54a1..13f9f6b 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,7 +15,7 @@
import {Draft} from 'immer';
import {assertExists} from '../base/logging';
-import {ConvertTrace} from '../controller/trace_converter';
+import {ConvertTrace, ConvertTraceToPprof} from '../controller/trace_converter';
import {
AdbRecordingTarget,
@@ -95,11 +95,21 @@
state.videoEnabled = true;
},
+ // TODO(b/141359485): Actions should only modify state.
convertTraceToJson(
- _: StateDraft, args: {file: File, truncate?: 'start'|'end'}): void {
+ _: StateDraft, args: {file: Blob, truncate?: 'start'|'end'}): void {
ConvertTrace(args.file, args.truncate);
},
+ convertTraceToPprof(_: StateDraft, args: {
+ pid: number,
+ src: string|File|ArrayBuffer,
+ ts1: number,
+ ts2?: number
+ }): void {
+ ConvertTraceToPprof(args.pid, args.src, args.ts1, args.ts2);
+ },
+
openTraceFromUrl(state: StateDraft, args: {url: string}): void {
clearTraceState(state);
const id = `${state.nextId++}`;
@@ -394,13 +404,16 @@
state.currentSelection = {kind: 'CHROME_SLICE', id: args.id};
},
- selectTimeSpan(
- state: StateDraft, args: {startTs: number, endTs: number}): void {
- state.currentSelection = {
- kind: 'TIMESPAN',
- startTs: args.startTs,
- endTs: args.endTs,
- };
+ selectTimeSpan(state: StateDraft, args: {startTs: number, endTs: number}):
+ void {
+ state.timeSpan = {
+ startTs: args.startTs,
+ endTs: args.endTs,
+ };
+ },
+
+ removeTimeSpan(state: StateDraft, _: {}): void {
+ state.timeSpan = null;
},
selectThreadState(
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index dffbaaf..130859c 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -121,8 +121,7 @@
id: number;
}
-export interface TimeSpanSelection {
- kind: 'TIMESPAN';
+export interface TimeSpanRange {
startTs: number;
endTs: number;
}
@@ -136,9 +135,8 @@
cpu: number;
}
-type Selection =
- NoteSelection|SliceSelection|CounterSelection|HeapDumpSelection|
- ChromeSliceSelection|TimeSpanSelection|ThreadStateSelection;
+type Selection = NoteSelection|SliceSelection|CounterSelection|
+ HeapDumpSelection|ChromeSliceSelection|ThreadStateSelection;
export interface LogsPagination {
offset: number;
@@ -177,6 +175,7 @@
permalink: PermalinkConfig;
notes: ObjectById<Note>;
status: Status;
+ timeSpan: TimeSpanRange|null;
currentSelection: Selection|null;
logsPagination: LogsPagination;
@@ -379,6 +378,7 @@
status: {msg: '', timestamp: 0},
currentSelection: null,
+ timeSpan: null,
video: null,
videoEnabled: false,
diff --git a/ui/src/controller/adb.ts b/ui/src/controller/adb.ts
index 54c6733..12ed7e5 100644
--- a/ui/src/controller/adb.ts
+++ b/ui/src/controller/adb.ts
@@ -263,8 +263,12 @@
// The stream will resolve this promise once it receives the
// acknowledgement message from the device.
- return new Promise<AdbStream>((resolve, _) => {
- stream.onConnect = () => resolve(stream);
+ return new Promise<AdbStream>((resolve, reject) => {
+ stream.onConnect = () => {
+ stream.onClose = () => {};
+ resolve(stream);
+ };
+ stream.onClose = () => reject();
});
}
diff --git a/ui/src/controller/adb_record_controller.ts b/ui/src/controller/adb_record_controller.ts
index f52714d..a1643b6 100644
--- a/ui/src/controller/adb_record_controller.ts
+++ b/ui/src/controller/adb_record_controller.ts
@@ -29,11 +29,10 @@
RECORDING,
FETCHING
}
-const DEFAULT_DESTINATION_FILE = '/data/misc/perfetto-traces/trace';
+const DEFAULT_DESTINATION_FILE = '/data/misc/perfetto-traces/trace-by-ui';
const textDecoder = new _TextDecoder();
export class AdbConsumerPort extends RpcConsumerPort {
- // public for testing
traceDestFile = DEFAULT_DESTINATION_FILE;
private state = AdbState.READY;
private adb: Adb;
@@ -171,7 +170,12 @@
}
generateReadTraceCommand(): string {
- return `gzip -c ${this.traceDestFile}`;
+ // We attempt to delete the trace file after tracing. On a non-root shell,
+ // this will fail (due to selinux denial), but perfetto cmd will be able to
+ // override the file later. However, on a root shell, we need to clean up
+ // the file since perfetto cmd might otherwise fail to override it in a
+ // future session.
+ return `gzip -c ${this.traceDestFile} && rm -f ${this.traceDestFile}`;
}
generateStartTracingCommand(tracingConfig: Uint8Array) {
diff --git a/ui/src/controller/adb_socket_controller.ts b/ui/src/controller/adb_socket_controller.ts
index 2a03154..bd88643 100644
--- a/ui/src/controller/adb_socket_controller.ts
+++ b/ui/src/controller/adb_socket_controller.ts
@@ -46,6 +46,8 @@
params: Uint8Array;
}
+const TRACED_SOCKET = '/dev/socket/traced_consumer';
+
export class AdbSocketConsumerPort extends RpcConsumerPort {
private state = State.DISCONNECTED;
private adb: Adb;
@@ -86,7 +88,7 @@
if (this.state === State.BINDING_IN_PROGRESS) return;
if (this.state === State.DISCONNECTED) {
this.state = State.BINDING_IN_PROGRESS;
- this.device = await this.findDevice();
+ this.device = await AdbSocketConsumerPort.findDevice();
if (!this.device) {
this.sendErrorMessage(`Device with serial ${
globals.state.serialAndroidDeviceConnected} not found.`);
@@ -109,7 +111,8 @@
const requestId = this.requestId++;
const methodId = this.findMethodId(method);
if (methodId === undefined) {
- this.sendErrorMessage('Calling unsupported method on target.');
+ // This can happen with 'GetTraceStats': it seems that not all the Andorid
+ // <= 9 devices support it.
console.error(`Method ${method} not supported by the target`);
return;
}
@@ -122,9 +125,7 @@
this.sendFrame(frame);
}
- async sendFrame(frame: Frame) {
- console.assert(this.socket !== undefined);
- if (!this.socket) return;
+ static generateFrameBufferToSend(frame: Frame): Uint8Array {
const frameProto: Uint8Array = perfetto.ipc.Frame.encode(frame).finish();
const frameLen = frameProto.length;
const buf = new Uint8Array(WIRE_PROTOCOL_HEADER_SIZE + frameLen);
@@ -133,13 +134,19 @@
for (let i = 0; i < frameLen; i++) {
dv.setUint8(WIRE_PROTOCOL_HEADER_SIZE + i, frameProto[i]);
}
+ return buf;
+ }
+
+ async sendFrame(frame: Frame) {
+ console.assert(this.socket !== undefined);
+ if (!this.socket) return;
+ const buf = AdbSocketConsumerPort.generateFrameBufferToSend(frame);
await this.socket.write(buf);
}
async listenForMessages() {
- this.socket = await this.adb.socket('/dev/socket/traced_consumer');
-
- this.socket.onData = newData => this.handleReceivedData(newData);
+ this.socket = await this.adb.socket(TRACED_SOCKET);
+ this.socket.onData = (raw) => this.handleReceivedData(raw);
this.socket.onClose = () => {
this.state = State.DISCONNECTED;
this.commandQueue = [];
@@ -304,13 +311,24 @@
return undefined;
}
- async findDevice() {
+ static async findDevice() {
if (!globals.state.androidDeviceConnected) return undefined;
const targetSerial = globals.state.androidDeviceConnected.serial;
const devices = await navigator.usb.getDevices();
return devices.find(d => d.serialNumber === targetSerial);
}
+ static async hasSocketAccess(device: USBDevice, adb: Adb) {
+ await adb.connect(device);
+ try {
+ const socket = await adb.socket(TRACED_SOCKET);
+ socket.close();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
handleIncomingFrame(frame: perfetto.ipc.Frame) {
const requestId = frame.requestId as number;
switch (frame.msg) {
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 062770b..179c0ee 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -27,8 +27,9 @@
import {ControllerAny} from './controller';
-type PublishKinds = 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|
- 'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'Loading'|
+type PublishKinds =
+ 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|'LegacyTrace'|
+ 'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'FileDownload'|'Loading'|
'Search'|'BufferUsage'|'RecordingLog'|'SearchResult';
export interface App {
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index ddf4276..7bafa23 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -24,6 +24,10 @@
export const BUCKET_NAME = 'perfetto-ui-data';
+function needsToBeUploaded(obj: {}): obj is ArrayBuffer|File {
+ return obj instanceof ArrayBuffer || obj instanceof File;
+}
+
export class PermalinkController extends Controller<'main'> {
private lastRequestId?: string;
constructor() {
@@ -57,10 +61,12 @@
const state = globals.state;
// Upload each loaded trace.
- const fileToUrl = new Map<File, string>();
+ const fileToUrl = new Map<File|ArrayBuffer, string>();
for (const engine of Object.values<EngineConfig>(state.engines)) {
- if (!(engine.source instanceof File)) continue;
- PermalinkController.updateStatus(`Uploading ${engine.source.name}`);
+ if (!needsToBeUploaded(engine.source)) continue;
+ const name = engine.source instanceof File ? engine.source.name :
+ `trace ${engine.id}`;
+ PermalinkController.updateStatus(`Uploading ${name}`);
const url = await this.saveTrace(engine.source);
fileToUrl.set(engine.source, url);
}
@@ -69,8 +75,9 @@
const uploadState = produce(state, draft => {
for (const engine of Object.values<Draft<EngineConfig>>(
draft.engines)) {
- if (!(engine.source instanceof File)) continue;
- engine.source = fileToUrl.get(engine.source)!;
+ if (!needsToBeUploaded(engine.source)) continue;
+ const newSource = fileToUrl.get(engine.source);
+ if (newSource) engine.source = newSource;
}
draft.permalink = {};
});
@@ -100,7 +107,7 @@
return hash;
}
- private static async saveTrace(trace: File): Promise<string> {
+ private static async saveTrace(trace: File|ArrayBuffer): Promise<string> {
// TODO(hjd): This should probably also be a hash but that requires
// trace processor support.
const name = uuidv4();
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 419c801..d14d695 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -33,6 +33,7 @@
} from '../common/protos';
import {MeminfoCounters, VmstatCounters} from '../common/protos';
import {
+ AdbRecordingTarget,
isAndroidTarget,
isChromeTarget,
MAX_TIME,
@@ -446,10 +447,14 @@
private consumerPort: ConsumerPort;
private traceBuffer: Uint8Array[] = [];
private bufferUpdateInterval: ReturnType<typeof setTimeout>|undefined;
+ private adb = new AdbOverWebUsb();
// We have a different controller for each targetOS. The correct one will be
- // created when needed, and stored here.
- private controllers = new Map<TargetOs, RpcConsumerPort>();
+ // created when needed, and stored here. When the key is a string, it is the
+ // serial of the target (used for android devices). When the key is a single
+ // char, it is the 'targetOS'
+ private controllerPromises = new Map<string, Promise<RpcConsumerPort>>();
+
constructor(args: {app: App, extensionPort: MessagePort}) {
super('main');
this.app = args.app;
@@ -566,6 +571,7 @@
}
onError(message: string) {
+ console.error('Error in record controller: ', message);
globals.dispatch(
Actions.setLastRecordingError({error: message.substr(0, 150)}));
globals.dispatch(Actions.stopRecording({}));
@@ -584,38 +590,59 @@
// - Android device target: WebUSB is used to communicate using the adb
// protocol. Actually, there is no full consumer_port implementation, but
// only the support to start tracing and fetch the file.
- async getTargetController(target: TargetOs): Promise<RpcConsumerPort> {
- let controller = this.controllers.get(target);
- if (controller) return controller;
+ async getTargetController(target: TargetOs, device?: AdbRecordingTarget):
+ Promise<RpcConsumerPort> {
+ const identifier = this.getTargetIdentifier(target, device);
- if (isChromeTarget(target)) {
- controller = new ChromeExtensionConsumerPort(this.extensionPort, this);
- } else if (isAndroidTarget(target)) {
- // TODO(nicomazz): create the correct controller also based on the
- // selected android device. TargetOS may be changed from a single char to
- // something else.
- const socketAccess = await this.hasSocketAccess();
- controller = socketAccess ?
- new AdbSocketConsumerPort(new AdbOverWebUsb(), this) :
- new AdbConsumerPort(new AdbOverWebUsb(), this);
- }
+ // The reason why caching the target 'record controller' Promise is that
+ // multiple rcp calls can happen while we are trying to understand if an
+ // android device has a socket connection available or not.
+ const precedentPromise = this.controllerPromises.get(identifier);
+ if (precedentPromise) return precedentPromise;
- if (!controller) throw Error(`Unknown target: ${target}`);
+ const controllerPromise =
+ new Promise<RpcConsumerPort>(async (resolve, _) => {
+ let controller: RpcConsumerPort|undefined = undefined;
+ if (isChromeTarget(target)) {
+ controller =
+ new ChromeExtensionConsumerPort(this.extensionPort, this);
+ } else if (isAndroidTarget(target)) {
+ if (!device) throw Error(`No android device connected`);
+ const socketAccess = await this.hasSocketAccess(device);
- this.controllers.set(target, controller);
- return controller;
+ controller = socketAccess ?
+ new AdbSocketConsumerPort(this.adb, this) :
+ new AdbConsumerPort(this.adb, this);
+ }
+
+ if (!controller) throw Error(`Unknown target: ${target}`);
+ resolve(controller);
+ });
+
+ this.controllerPromises.set(identifier, controllerPromise);
+ return controllerPromise;
}
- private hasSocketAccess() {
- // TODO(nicomazz): implement proper logic
- return Promise.resolve(false);
+ private getTargetIdentifier(target: TargetOs, device?: AdbRecordingTarget):
+ string {
+ return device ? device.serial : target;
+ }
+
+ private async hasSocketAccess(target: AdbRecordingTarget) {
+ const devices = await navigator.usb.getDevices();
+ const device = devices.find(d => d.serialNumber === target.serial);
+ console.assert(device);
+ if (!device) return Promise.resolve(false);
+ return AdbSocketConsumerPort.hasSocketAccess(device, this.adb);
}
private async rpcImpl(
method: RPCImplMethod, requestData: Uint8Array,
_callback: RPCImplCallback) {
try {
- (await this.getTargetController(this.app.state.recordConfig.targetOS))
+ const state = this.app.state;
+ (await this.getTargetController(
+ state.recordConfig.targetOS, state.androidDeviceConnected))
.handleCommand(method.name, requestData);
} catch (e) {
console.error(`error invoking ${method}: ${e.message}`);
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 9e881b7..99cbfcf 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -124,6 +124,9 @@
}
async heapDumpDetails(ts: number, upid: number) {
+ const pidValue = await this.args.engine.query(
+ `select pid from process where upid = ${upid}`);
+ const pid = pidValue.columns[0].longValues![0];
const allocatedMemory = await this.args.engine.query(
`select sum(size) from heap_profile_allocation where ts <= ${
ts} and size > 0 and upid = ${upid}`);
@@ -133,7 +136,7 @@
ts} and upid = ${upid}`);
const allocatedNotFreed = allocatedNotFreedMemory.columns[0].longValues![0];
const startTime = fromNs(ts) - globals.state.traceTime.startSec;
- return {ts: startTime, allocated, allocatedNotFreed};
+ return {ts: startTime, allocated, allocatedNotFreed, tsNs: ts, pid};
}
async counterDetails(ts: number, rightTs: number, id: number) {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 34a79a4..cfa03f2 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -307,6 +307,40 @@
}
}
+
+ const upidToProcessTracks = new Map();
+ const rawProcessTracks = await engine.query(`
+ select id, upid, name, maxDepth
+ from process_track
+ join (
+ select ref as id, max(depth) as maxDepth
+ from slice
+ where ref_type = 'track' group by ref
+ ) using(id)
+ `);
+ for (let i = 0; i < rawProcessTracks.numRecords; i++) {
+ const trackId = rawProcessTracks.columns[0].longValues![i];
+ const upid = rawProcessTracks.columns[1].longValues![i];
+ const name = rawProcessTracks.columns[2].stringValues![i];
+ const maxDepth = rawProcessTracks.columns[3].longValues![i];
+ const track = {
+ engineId: this.engineId,
+ kind: 'AsyncSliceTrack',
+ name,
+ config: {
+ trackId,
+ maxDepth,
+ },
+ };
+
+ const tracks = upidToProcessTracks.get(upid);
+ if (tracks) {
+ tracks.push(track);
+ } else {
+ upidToProcessTracks.set(upid, [track]);
+ }
+ }
+
const heapProfiles = await engine.query(`
select distinct(upid) from heap_profile_allocation`);
@@ -513,6 +547,12 @@
config: {upid}
});
}
+
+ if (upidToProcessTracks.has(upid)) {
+ for (const track of upidToProcessTracks.get(upid)) {
+ tracksToAdd.push(Object.assign(track, {trackGroup: pUuid}));
+ }
+ }
}
}
const counterThreadNames = counterUtids[utid];
diff --git a/ui/src/controller/trace_converter.ts b/ui/src/controller/trace_converter.ts
index 5941cb4..e4648a0 100644
--- a/ui/src/controller/trace_converter.ts
+++ b/ui/src/controller/trace_converter.ts
@@ -53,6 +53,74 @@
(self as {} as {mod: {}}).mod = mod;
}
+export async function ConvertTraceToPprof(
+ pid: number, src: string|File|ArrayBuffer, ts1: number, ts2?: number) {
+ generateBlob(src).then(result => {
+ const mod = trace_to_text({
+ noInitialRun: true,
+ locateFile: (s: string) => s,
+ print: updateStatus,
+ printErr: updateStatus,
+ onRuntimeInitialized: () => {
+ updateStatus('Converting trace');
+ const timestamps = `${ts1}${ts2 === undefined ? '' : `,${ts2}`}`;
+ mod.callMain([
+ 'profile',
+ `--pid`,
+ `${pid}`,
+ `--timestamps`,
+ timestamps,
+ '/fs/trace.proto'
+ ]);
+ updateStatus('Trace conversion completed');
+ const heapDirName =
+ Object.keys(mod.FS.lookupPath('/tmp/').node.contents)[0];
+ const heapDirContents =
+ mod.FS.lookupPath(`/tmp/${heapDirName}`).node.contents;
+ const heapDumpFiles = Object.keys(heapDirContents);
+ let fileNum = 0;
+ heapDumpFiles.forEach(heapDump => {
+ const fileContents =
+ mod.FS.lookupPath(`/tmp/${heapDirName}/${heapDump}`)
+ .node.contents;
+ fileNum++;
+ const fileName = `/heap_dump.${fileNum}.${pid}.pb`;
+ downloadFile(new Blob([fileContents]), fileName);
+ });
+ updateStatus('Profile(s) downloaded');
+ },
+ onAbort: () => {
+ console.log('ABORT');
+ },
+ });
+ mod.FS.mkdir('/fs');
+ mod.FS.mount(
+ mod.FS.filesystems.WORKERFS,
+ {blobs: [{name: 'trace.proto', data: result}]},
+ '/fs');
+ });
+}
+
+async function generateBlob(src: string|ArrayBuffer|File) {
+ let blob: Blob = new Blob();
+ if (typeof src === 'string') {
+ const resp = await fetch(src);
+ if (resp.status !== 200) {
+ throw new Error(`fetch() failed with HTTP error ${resp.status}`);
+ }
+ blob = await resp.blob();
+ } else if (src instanceof ArrayBuffer) {
+ blob = new Blob([new Uint8Array(src, 0, src.byteLength)]);
+ } else {
+ blob = src;
+ }
+ return blob;
+}
+
+function downloadFile(file: Blob, name: string) {
+ globals.publish('FileDownload', {file, name});
+}
+
function updateStatus(msg: {}) {
console.log(msg);
globals.dispatch(Actions.updateStatus({
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts
index 9a6f142..97ce65c 100644
--- a/ui/src/frontend/chrome_slice_panel.ts
+++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -31,9 +31,11 @@
[m('table',
[
m('tr', m('th', `Name`), m('td', `${sliceInfo.name}`)),
- m('tr',
- m('th', `Category`),
- m('td', `${sliceInfo.category}`)),
+ (sliceInfo.category === '[NULL]') ?
+ null :
+ m('tr',
+ m('th', `Category`),
+ m('td', `${sliceInfo.category}`)),
m('tr',
m('th', `Start time`),
m('td', `${timeToCode(sliceInfo.ts)}`)),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index f9aca97..e62a889 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -44,8 +44,10 @@
export interface HeapDumpDetails {
ts?: number;
+ tsNs?: number;
allocated?: number;
allocatedNotFreed?: number;
+ pid?: number;
}
export interface QuantizedLoad {
diff --git a/ui/src/frontend/heap_dump_panel.ts b/ui/src/frontend/heap_dump_panel.ts
index d6c14e1..7c31636 100644
--- a/ui/src/frontend/heap_dump_panel.ts
+++ b/ui/src/frontend/heap_dump_panel.ts
@@ -14,6 +14,7 @@
import * as m from 'mithril';
+import {Actions} from '../common/actions';
import {timeToCode} from '../common/time';
import {globals} from './globals';
@@ -22,13 +23,19 @@
interface HeapDumpDetailsPanelAttrs {}
export class HeapDumpDetailsPanel extends Panel<HeapDumpDetailsPanelAttrs> {
+ private ts = 0;
+ private pid = 0;
+
view() {
const heapDumpInfo = globals.heapDumpDetails;
if (heapDumpInfo && heapDumpInfo.ts && heapDumpInfo.allocated &&
- heapDumpInfo.allocatedNotFreed) {
+ heapDumpInfo.allocatedNotFreed && heapDumpInfo.tsNs &&
+ heapDumpInfo.pid) {
+ this.ts = heapDumpInfo.tsNs;
+ this.pid = heapDumpInfo.pid;
return m(
'.details-panel',
- m('.details-panel-heading', `Heap Snapshot Details:`),
+ m('.details-panel-heading', `Heap Profile Details:`),
m(
'.details-table',
[m('table',
@@ -47,7 +54,23 @@
heapDumpInfo.allocatedNotFreed
.toLocaleString()} bytes`)),
])],
- ));
+ ),
+ m('.explanation',
+ 'Heap profile support is in beta. To explore a heap profile,',
+ ' download and open it in ',
+ m(`a[href='https://pprof.corp.google.com']`, 'pprof'),
+ ' (Googlers only) or ',
+ m(`a[href='https://www.speedscope.app']`, 'Speedscope'),
+ '.'),
+ m('button',
+ {
+ onclick: () => {
+ this.downloadPprof();
+ }
+ },
+ m('i.material-icons', 'file_download'),
+ 'Download profile'),
+ );
} else {
return m(
'.details-panel',
@@ -55,5 +78,14 @@
}
}
+ downloadPprof() {
+ const engine = Object.values(globals.state.engines)[0];
+ if (!engine) return;
+ const src = engine.source;
+ // TODO(tneda): add second timestamp
+ globals.dispatch(
+ Actions.convertTraceToPprof({pid: this.pid, ts1: this.ts, src}));
+ }
+
renderCanvas() {}
}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 3790f61..2b1effe 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -128,6 +128,17 @@
this.redraw();
}
+ publishFileDownload(args: {file: File, name?: string}) {
+ const url = URL.createObjectURL(args.file);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = args.name !== undefined ? args.name : args.file.name;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ }
+
publishLoading(loading: boolean) {
globals.loading = loading;
globals.rafScheduler.scheduleRedraw();
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index e1ac1dc..3ad39f4 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -16,6 +16,7 @@
import {globals} from './globals';
import {toggleHelp} from './help_modal';
+import {horizontalScrollAndZoomToRange} from './scroll_helper';
import {executeSearch} from './search_handler';
// Handles all key events than are not handled by the
@@ -64,6 +65,8 @@
}
if (startTs !== -1 && endTs !== -1) {
- globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
+ globals.dispatch(Actions.selectTimeSpan({startTs, endTs}));
+ // Zoom into the highlighted time region.
+ horizontalScrollAndZoomToRange(startTs, endTs);
}
}
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
new file mode 100644
index 0000000..97f8de6
--- /dev/null
+++ b/ui/src/frontend/scroll_helper.ts
@@ -0,0 +1,83 @@
+// 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 {getContainingTrackId} from '../common/state';
+import {fromNs, TimeSpan, toNs} from '../common/time';
+
+import {globals} from './globals';
+
+/**
+ * Given a timestamp, if |ts| is not currently in view move the view to
+ * center |ts|, keeping the same zoom level.
+ */
+export function horizontalScrollToTs(ts: number) {
+ const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
+ const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+ const currentViewNs = endNs - startNs;
+ if (ts < startNs || ts > endNs) {
+ // TODO(taylori): This is an ugly jump, we should do a smooth pan instead.
+ globals.frontendLocalState.updateVisibleTime(new TimeSpan(
+ fromNs(ts - currentViewNs / 2), fromNs(ts + currentViewNs / 2)));
+ }
+}
+
+/**
+ * Given a start and end timestamp (in ns), move the view to center this range
+ * and zoom to a level where the range is 1/5 of the viewport.
+ */
+export function horizontalScrollAndZoomToRange(startTs: number, endTs: number) {
+ const visibleDur = globals.frontendLocalState.visibleWindowTime.end -
+ globals.frontendLocalState.visibleWindowTime.start;
+ const selectDur = endTs - startTs;
+ const viewStartNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
+ const viewEndNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+ if (selectDur / visibleDur < 0.05 || startTs < viewStartNs ||
+ endTs > viewEndNs) {
+ globals.frontendLocalState.updateVisibleTime(
+ new TimeSpan(startTs - (selectDur * 2), endTs + (selectDur * 2)));
+ }
+}
+
+/**
+ * Given a track id, find a track with that id and scroll it into view. If the
+ * track is nested inside a track group, scroll to that track group instead.
+ */
+export function verticalScrollToTrack(trackId: string|number) {
+ const trackIdString = trackId.toString();
+ let track = document.querySelector('#track_' + trackIdString);
+
+ if (!track) {
+ const parentTrackId = getContainingTrackId(globals.state, trackIdString);
+ if (parentTrackId) {
+ track = document.querySelector('#track_' + parentTrackId);
+ }
+ }
+
+ if (!track) {
+ console.error(`Can't scroll, track (${trackIdString}) not found.`);
+ return;
+ }
+
+ // block: 'nearest' means that it will only scroll if the track is not
+ // currently in view.
+ track.scrollIntoView({behavior: 'smooth', block: 'nearest'});
+}
+
+/**
+ * Scroll vertically and horizontally to reach track (|trackId|) at |ts|.
+ */
+export function scrollToTrackAndTs(trackId: string|number, ts: number) {
+ verticalScrollToTrack(trackId);
+ horizontalScrollToTs(ts);
+}
\ No newline at end of file
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index 4c64440..351fd27 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -14,10 +14,10 @@
import {searchSegment} from '../base/binary_search';
import {Actions} from '../common/actions';
-import {getContainingTrackId} from '../common/state';
-import {fromNs, TimeSpan, toNs} from '../common/time';
+import {toNs} from '../common/time';
import {globals} from './globals';
+import {scrollToTrackAndTs} from './scroll_helper';
export function executeSearch(reverse = false) {
const state = globals.frontendLocalState;
@@ -58,39 +58,11 @@
}
function moveViewportToCurrentSearch() {
- // Move viewport if our selection moves outside.
- const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
- const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
const currentTs = globals.currentSearchResults
.tsStarts[globals.frontendLocalState.searchIndex];
- const currentViewNs = endNs - startNs;
- if (currentTs < startNs || currentTs > endNs) {
- // TODO(taylori): This is an ugly jump, we should do a smooth pan instead.
- globals.frontendLocalState.updateVisibleTime(new TimeSpan(
- fromNs(currentTs - currentViewNs / 2),
- fromNs(currentTs + currentViewNs / 2)));
- }
-
- // Update vertical (up/down) scroll position
const trackId = globals.currentSearchResults
.trackIds[globals.frontendLocalState.searchIndex];
- let track = document.querySelector('#track_' + trackId);
-
- if (!track) {
- const parentTrackId = getContainingTrackId(globals.state, trackId);
- if (parentTrackId) {
- track = document.querySelector('#track_' + parentTrackId);
- }
- }
-
- if (!track) {
- console.error(`Can't scroll search result track not found (${trackId})`);
- return;
- }
-
- // block: 'nearest' means that it will only scroll if the track is not
- // currently in view.
- track.scrollIntoView({behavior: 'smooth', block: 'nearest'});
+ scrollToTrackAndTs(trackId, currentTs);
}
function selectCurrentSearchResult() {
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 1cee13e..4f453ea 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -111,19 +111,28 @@
i: 'filter_none'
},
{t: 'Record new trace', a: navigateRecord, i: 'fiber_smart_record'},
+ ],
+ },
+ {
+ title: 'Current Trace',
+ summary: 'Actions on the current trace',
+ expanded: true,
+ hideIfNoTraceLoaded: true,
+ items: [
{t: 'Show timeline', a: navigateViewer, i: 'line_style'},
{
- t: 'Share current trace',
+ t: 'Share',
a: dispatchCreatePermalink,
i: 'share',
disableInLocalOnlyMode: true,
},
{
- t: 'Download current trace',
+ t: 'Download',
a: downloadTrace,
i: 'file_download',
disableInLocalOnlyMode: true,
},
+ {t: 'Legacy UI', a: openCurrentTraceWithOldUI, i: 'filter_none'},
],
},
{
@@ -230,6 +239,31 @@
getFileElement().click();
}
+function openCurrentTraceWithOldUI() {
+ console.assert(isTraceLoaded());
+ if (!isTraceLoaded) return;
+ const engine = Object.values(globals.state.engines)[0];
+ const src = engine.source;
+ if (src instanceof ArrayBuffer) {
+ openInOldUIWithSizeCheck(new Blob([src]));
+ } else if (src instanceof File) {
+ openInOldUIWithSizeCheck(src);
+ } else {
+ console.assert(typeof src === 'string');
+ console.error('Loading from an URL to catapult is not yet supported');
+ // TODO(nicomazz): Find how to get the data of the current trace if it is
+ // from an URL. It seems that the trace downloaded is given to the trace
+ // processor, but not kept somewhere accessible. Maybe the only way is to
+ // download the trace (again), and then open it. An alternative can be to
+ // save a copy.
+ }
+}
+
+function isTraceLoaded(): boolean {
+ const engine = Object.values(globals.state.engines)[0];
+ return engine !== undefined;
+}
+
function popupVideoSelectionDialog(e: Event) {
e.preventDefault();
delete getFileElement().dataset['useCatapultLegacyUi'];
@@ -262,61 +296,7 @@
openFileWithLegacyTraceViewer(file);
return;
}
-
- // Perfetto traces smaller than 50mb can be safely opened in the legacy UI.
- if (file.size < 1024 * 1024 * 50) {
- globals.dispatch(Actions.convertTraceToJson({file}));
- return;
- }
-
- // Give the user the option to truncate larger perfetto traces.
- const size = Math.round(file.size / (1024 * 1024));
- showModal({
- title: 'Legacy UI may fail to open this trace',
- content:
- m('div',
- m('p',
- `This trace is ${size}mb, opening it in the legacy UI ` +
- `may fail.`),
- m('p',
- 'More options can be found at ',
- m('a',
- {
- href: 'https://goto.google.com/opening-large-traces',
- target: '_blank'
- },
- 'go/opening-large-traces'),
- '.')),
- buttons: [
- {
- text: 'Open full trace (not recommended)',
- primary: false,
- id: 'open',
- action: () => {
- globals.dispatch(Actions.convertTraceToJson({file}));
- }
- },
- {
- text: 'Open beginning of trace',
- primary: true,
- id: 'truncate-start',
- action: () => {
- globals.dispatch(
- Actions.convertTraceToJson({file, truncate: 'start'}));
- }
- },
- {
- text: 'Open end of trace',
- primary: true,
- id: 'truncate-end',
- action: () => {
- globals.dispatch(
- Actions.convertTraceToJson({file, truncate: 'end'}));
- }
- }
-
- ]
- });
+ openInOldUIWithSizeCheck(file);
return;
}
@@ -346,6 +326,64 @@
}
+function openInOldUIWithSizeCheck(trace: Blob) {
+ // Perfetto traces smaller than 50mb can be safely opened in the legacy UI.
+ if (trace.size < 1024 * 1024 * 50) {
+ globals.dispatch(Actions.convertTraceToJson({file: trace}));
+ return;
+ }
+
+ // Give the user the option to truncate larger perfetto traces.
+ const size = Math.round(trace.size / (1024 * 1024));
+ showModal({
+ title: 'Legacy UI may fail to open this trace',
+ content:
+ m('div',
+ m('p',
+ `This trace is ${size}mb, opening it in the legacy UI ` +
+ `may fail.`),
+ m('p',
+ 'More options can be found at ',
+ m('a',
+ {
+ href: 'https://goto.google.com/opening-large-traces',
+ target: '_blank'
+ },
+ 'go/opening-large-traces'),
+ '.')),
+ buttons: [
+ {
+ text: 'Open full trace (not recommended)',
+ primary: false,
+ id: 'open',
+ action: () => {
+ globals.dispatch(Actions.convertTraceToJson({file: trace}));
+ }
+ },
+ {
+ text: 'Open beginning of trace',
+ primary: true,
+ id: 'truncate-start',
+ action: () => {
+ globals.dispatch(
+ Actions.convertTraceToJson({file: trace, truncate: 'start'}));
+ }
+ },
+ {
+ text: 'Open end of trace',
+ primary: true,
+ id: 'truncate-end',
+ action: () => {
+ globals.dispatch(
+ Actions.convertTraceToJson({file: trace, truncate: 'end'}));
+ }
+ }
+
+ ]
+ });
+ return;
+}
+
function navigateRecord(e: Event) {
e.preventDefault();
globals.dispatch(Actions.navigate({route: '/record'}));
@@ -357,16 +395,12 @@
}
function localOnlyMode(): boolean {
- if (globals.frontendLocalState.localOnlyMode) return true;
- const engine = Object.values(globals.state.engines)[0];
- if (!engine) return true;
- const src = engine.source;
- return (src instanceof ArrayBuffer);
+ return globals.frontendLocalState.localOnlyMode;
}
function dispatchCreatePermalink(e: Event) {
e.preventDefault();
- if (localOnlyMode()) return;
+ if (localOnlyMode() || !isTraceLoaded()) return;
const result = confirm(
`Upload the trace and generate a permalink. ` +
@@ -376,26 +410,31 @@
function downloadTrace(e: Event) {
e.preventDefault();
- if (localOnlyMode()) return;
+ if (!isTraceLoaded() || localOnlyMode()) return;
const engine = Object.values(globals.state.engines)[0];
if (!engine) return;
const src = engine.source;
if (typeof src === 'string') {
window.open(src);
- } else if (src instanceof ArrayBuffer) {
- console.error('Can not download external trace.');
return;
- } else {
- const url = URL.createObjectURL(src);
- const a = document.createElement('a');
- a.href = url;
- a.download = src.name;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
}
+
+ let url = '';
+ if (src instanceof ArrayBuffer) {
+ const blob = new Blob([src], {type: 'application/octet-stream'});
+ url = URL.createObjectURL(blob);
+ } else {
+ console.assert(src instanceof File);
+ url = URL.createObjectURL(src);
+ }
+
+ const a = document.createElement('a');
+ a.href = url;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
}
@@ -403,6 +442,7 @@
view() {
const vdomSections = [];
for (const section of SECTIONS) {
+ if (section.hideIfNoTraceLoaded && !isTraceLoaded()) continue;
const vdomItems = [];
for (const item of section.items) {
let attrs = {
@@ -430,8 +470,8 @@
globals.rafScheduler.scheduleFullRedraw();
}
},
- m('h1', section.title),
- m('h2', section.summary), ),
+ m('h1', {title: section.summary}, section.title),
+ m('h2', section.summary)),
m('.section-content', m('ul', vdomItems))));
}
if (globals.state.videoEnabled) {
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index cc899db..7c4df77 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -124,10 +124,10 @@
ctx.fillRect(xAndTime[0], 0, 1, size.height);
}
- const selection = globals.state.currentSelection;
- if (selection !== null && selection.kind === `TIMESPAN`) {
- const start = Math.min(selection.startTs, selection.endTs);
- const end = Math.max(selection.startTs, selection.endTs);
+ const timeSpan = globals.state.timeSpan;
+ if (timeSpan !== null) {
+ const start = Math.min(timeSpan.startTs, timeSpan.endTs);
+ const end = Math.max(timeSpan.startTs, timeSpan.endTs);
this.renderSpan(ctx, size, new TimeSpan(start, end));
} else if (globals.frontendLocalState.showTimeSelectPreview) {
this.renderHover(ctx, size, globals.frontendLocalState.hoveredTimestamp);
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 9f2bd85..6b5cd99 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -135,6 +135,15 @@
size.height,
`rgb(52,69,150)`);
}
+ if (globals.state.timeSpan !== null) {
+ drawVerticalSelection(
+ ctx,
+ localState.timeScale,
+ globals.state.timeSpan.startTs,
+ globals.state.timeSpan.endTs,
+ size.height,
+ `rgba(0,0,0,0.5)`);
+ }
if (globals.state.currentSelection !== null) {
if (globals.state.currentSelection.kind === 'NOTE') {
const note = globals.state.notes[globals.state.currentSelection.id];
@@ -144,15 +153,6 @@
size.height,
note.color);
}
- if (globals.state.currentSelection.kind === 'TIMESPAN') {
- drawVerticalSelection(
- ctx,
- localState.timeScale,
- globals.state.currentSelection.startTs,
- globals.state.currentSelection.endTs,
- size.height,
- `rgba(0,0,0,0.5)`);
- }
if (globals.state.currentSelection.kind === 'SLICE' &&
globals.sliceDetails.wakeupTs !== undefined) {
drawVerticalLineAtTime(
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 18ad066..5912c96 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -146,11 +146,18 @@
attrs.track.onMouseOut();
globals.rafScheduler.scheduleRedraw();
},
- onclick: (e:MouseEvent) => {
+ onclick: (e: MouseEvent) => {
// If we are selecting a timespan - do not pass the click to the track.
- const selection = globals.state.currentSelection;
- if (selection && selection.kind === 'TIMESPAN') return;
- if (attrs.track.onMouseClick({x: e.layerX, y: e.layerY})) {
+ if (e.shiftKey) return;
+ // If the click is outside of the current timespan, clear it.
+ const clickTime =
+ globals.frontendLocalState.timeScale.pxToTime(e.layerX);
+ if (globals.state.timeSpan !== null &&
+ (clickTime < globals.state.timeSpan.startTs ||
+ clickTime > globals.state.timeSpan.endTs)) {
+ globals.dispatch(Actions.removeTimeSpan({}));
+ e.stopPropagation();
+ } else if (attrs.track.onMouseClick({x: e.layerX, y: e.layerY})) {
e.stopPropagation();
}
globals.rafScheduler.scheduleRedraw();
@@ -249,7 +256,15 @@
size.height,
`rgb(52,69,150)`);
}
-
+ if (globals.state.timeSpan !== null) {
+ drawVerticalSelection(
+ ctx,
+ localState.timeScale,
+ globals.state.timeSpan.startTs,
+ globals.state.timeSpan.endTs,
+ size.height,
+ `rgba(0,0,0,0.5)`);
+ }
if (globals.state.currentSelection !== null) {
if (globals.state.currentSelection.kind === 'NOTE') {
const note = globals.state.notes[globals.state.currentSelection.id];
@@ -259,15 +274,6 @@
size.height,
note.color);
}
- if (globals.state.currentSelection.kind === 'TIMESPAN') {
- drawVerticalSelection(
- ctx,
- localState.timeScale,
- globals.state.currentSelection.startTs,
- globals.state.currentSelection.endTs,
- size.height,
- `rgba(0,0,0,0.5)`);
- }
if (globals.state.currentSelection.kind === 'SLICE' &&
globals.sliceDetails.wakeupTs !== undefined) {
drawVerticalLineAtTime(
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index a6c415f..63d7a48 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -257,7 +257,7 @@
scale.pxToTime(startPx - TRACK_SHELL_WIDTH));
const endTs = Math.min(traceTime.endSec,
scale.pxToTime(endPx - TRACK_SHELL_WIDTH));
- globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
+ globals.dispatch(Actions.selectTimeSpan({startTs, endTs}));
globals.rafScheduler.scheduleRedraw();
}
});
@@ -309,9 +309,7 @@
}));
break;
case 'HEAP_DUMP':
- detailsPanels.push(m(HeapDumpDetailsPanel, {
- key: 'heap_dump',
- }));
+ detailsPanels.push(m(HeapDumpDetailsPanel, {key: 'heap_dump'}));
break;
case 'CHROME_SLICE':
detailsPanels.push(m(ChromeSliceDetailsPanel));
diff --git a/ui/src/tracks/all_controller.ts b/ui/src/tracks/all_controller.ts
index 79d3070..ca4f7c8 100644
--- a/ui/src/tracks/all_controller.ts
+++ b/ui/src/tracks/all_controller.ts
@@ -25,3 +25,4 @@
import './process_summary/controller';
import './thread_state/controller';
import './vsync/controller';
+import './async_slices/controller';
diff --git a/ui/src/tracks/all_frontend.ts b/ui/src/tracks/all_frontend.ts
index dfa57fc..3ce3de0 100644
--- a/ui/src/tracks/all_frontend.ts
+++ b/ui/src/tracks/all_frontend.ts
@@ -25,3 +25,4 @@
import './process_summary/frontend';
import './thread_state/frontend';
import './vsync/frontend';
+import './async_slices/frontend';
diff --git a/ui/src/tracks/async_slices/common.ts b/ui/src/tracks/async_slices/common.ts
new file mode 100644
index 0000000..3b7c885
--- /dev/null
+++ b/ui/src/tracks/async_slices/common.ts
@@ -0,0 +1,22 @@
+// 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.
+
+export {Data} from '../chrome_slices/common';
+
+export const SLICE_TRACK_KIND = 'AsyncSliceTrack';
+
+export interface Config {
+ maxDepth: number;
+ trackId: number;
+}
diff --git a/ui/src/tracks/async_slices/controller.ts b/ui/src/tracks/async_slices/controller.ts
new file mode 100644
index 0000000..e56999b
--- /dev/null
+++ b/ui/src/tracks/async_slices/controller.ts
@@ -0,0 +1,143 @@
+// 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 {fromNs, toNs} from '../../common/time';
+import {LIMIT} from '../../common/track_data';
+import {
+ TrackController,
+ trackControllerRegistry,
+} from '../../controller/track_controller';
+
+import {Config, Data, SLICE_TRACK_KIND} from './common';
+
+class AsyncSliceTrackController extends TrackController<Config, Data> {
+ static readonly kind = SLICE_TRACK_KIND;
+ private setup = false;
+
+ async onBoundsChange(start: number, end: number, resolution: number):
+ Promise<Data> {
+ const startNs = toNs(start);
+ const endNs = toNs(end);
+ // Ns in 1px width. We want all slices smaller than 1px to be grouped.
+ const minNs = toNs(resolution);
+
+ if (!this.setup) {
+ await this.query(
+ `create virtual table ${this.tableName('window')} using window;`);
+
+ await this.query(
+ `create view ${this.tableName('small')} as ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
+ `where ref_type = 'track' ` +
+ `and ref = ${this.config.trackId} ` +
+ `and dur < ${minNs} ` +
+ `order by ts;`);
+
+ await this.query(`create virtual table ${this.tableName('span')} using
+ span_join(${this.tableName('small')} PARTITIONED depth,
+ ${this.tableName('window')});`);
+
+ this.setup = true;
+ }
+
+ const windowDurNs = Math.max(1, endNs - startNs);
+
+ this.query(`update ${this.tableName('window')} set
+ window_start=${startNs},
+ window_dur=${windowDurNs},
+ quantum=${minNs}`);
+
+ await this.query(`drop view if exists ${this.tableName('small')}`);
+ await this.query(`drop view if exists ${this.tableName('big')}`);
+ await this.query(`drop view if exists ${this.tableName('summary')}`);
+
+ await this.query(
+ `create view ${this.tableName('small')} as ` +
+ `select ts,dur,depth,name, slice_id from slice ` +
+ `where ref_type = 'track' ` +
+ `and ref = ${this.config.trackId} ` +
+ `and dur < ${minNs} ` +
+ `order by ts `);
+
+ await this.query(
+ `create view ${this.tableName('big')} as ` +
+ `select ts,dur,depth,name, slice_id from slice ` +
+ `where ref_type = 'track' ` +
+ `and ref = ${this.config.trackId} ` +
+ `and ts >= ${startNs} - dur ` +
+ `and ts <= ${endNs} ` +
+ `and dur >= ${minNs} ` +
+ `order by ts `);
+
+ // So that busy slices never overlap, we use the start of the bucket
+ // as the ts, even though min(ts) would technically be more accurate.
+ await this.query(`create view ${this.tableName('summary')} as select
+ (quantum_ts * ${minNs} + ${startNs}) as ts,
+ ${minNs} as dur,
+ depth,
+ 'Busy' as name,
+ -1 as slice_id
+ from ${this.tableName('span')}
+ group by depth, quantum_ts
+ order by ts;`);
+
+ const query = `select * from ${this.tableName('summary')} UNION ` +
+ `select * from ${this.tableName('big')} order by ts limit ${LIMIT}`;
+
+ const rawResult = await this.query(query);
+
+ if (rawResult.error) {
+ throw new Error(`Query error "${query}": ${rawResult.error}`);
+ }
+
+ const numRows = +rawResult.numRecords;
+
+ const slices: Data = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new Float64Array(numRows),
+ ends: new Float64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ for (let row = 0; row < numRows; row++) {
+ const cols = rawResult.columns;
+ const startSec = fromNs(+cols[0].longValues![row]);
+ slices.starts[row] = startSec;
+ slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
+ slices.depths[row] = +cols[2].longValues![row];
+ slices.titles[row] = internString(cols[3].stringValues![row]);
+ slices.sliceIds[row] = +cols[4].longValues![row];
+ }
+ return slices;
+ }
+}
+
+
+trackControllerRegistry.register(AsyncSliceTrackController);
diff --git a/ui/src/tracks/async_slices/frontend.ts b/ui/src/tracks/async_slices/frontend.ts
new file mode 100644
index 0000000..918f831
--- /dev/null
+++ b/ui/src/tracks/async_slices/frontend.ts
@@ -0,0 +1,29 @@
+// 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 {TrackState} from '../../common/state';
+import {Track} from '../../frontend/track';
+import {trackRegistry} from '../../frontend/track_registry';
+import {ChromeSliceTrack} from '../chrome_slices/frontend';
+
+import {SLICE_TRACK_KIND} from './common';
+
+export class AsyncSliceTrack extends ChromeSliceTrack {
+ static readonly kind = SLICE_TRACK_KIND;
+ static create(trackState: TrackState): Track {
+ return new AsyncSliceTrack(trackState);
+ }
+}
+
+trackRegistry.register(AsyncSliceTrack);
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index ad240c3..68653da 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -30,5 +30,4 @@
ends: Float64Array;
depths: Uint16Array;
titles: Uint16Array; // Index in |strings|.
- categories: Uint16Array; // Index in |strings|.
}
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 1d44a7c..cbe6486 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -38,10 +38,8 @@
await this.query(
`create view ${this.tableName('small')} as ` +
- `select ts,dur,depth,cat,name,slice_id from slices ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
`where utid = ${this.config.utid} ` +
- `and ts >= ${startNs} - dur ` +
- `and ts <= ${endNs} ` +
`and dur < ${minNs} ` +
`order by ts;`);
@@ -65,16 +63,14 @@
await this.query(
`create view ${this.tableName('small')} as ` +
- `select ts,dur,depth,cat,name, slice_id from slices ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
`where utid = ${this.config.utid} ` +
- `and ts >= ${startNs} - dur ` +
- `and ts <= ${endNs} ` +
`and dur < ${minNs} ` +
`order by ts `);
await this.query(
`create view ${this.tableName('big')} as ` +
- `select ts,dur,depth,cat,name, slice_id from slices ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
`where utid = ${this.config.utid} ` +
`and ts >= ${startNs} - dur ` +
`and ts <= ${endNs} ` +
@@ -87,11 +83,10 @@
(quantum_ts * ${minNs} + ${startNs}) as ts,
${minNs} as dur,
depth,
- cat,
'Busy' as name,
-1 as slice_id
from ${this.tableName('span')}
- group by cat, depth, quantum_ts
+ group by depth, quantum_ts
order by ts;`);
const query = `select * from ${this.tableName('summary')} UNION ` +
@@ -116,7 +111,6 @@
ends: new Float64Array(numRows),
depths: new Uint16Array(numRows),
titles: new Uint16Array(numRows),
- categories: new Uint16Array(numRows),
};
const stringIndexes = new Map<string, number>();
@@ -135,9 +129,8 @@
slices.starts[row] = startSec;
slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
slices.depths[row] = +cols[2].longValues![row];
- slices.categories[row] = internString(cols[3].stringValues![row]);
- slices.titles[row] = internString(cols[4].stringValues![row]);
- slices.sliceIds[row] = +cols[5].longValues![row];
+ slices.titles[row] = internString(cols[3].stringValues![row]);
+ slices.sliceIds[row] = +cols[4].longValues![row];
}
return slices;
}
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 5826f67..544d84f 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -34,9 +34,9 @@
return hash & 0xff;
}
-class ChromeSliceTrack extends Track<Config, Data> {
- static readonly kind = SLICE_TRACK_KIND;
- static create(trackState: TrackState): ChromeSliceTrack {
+export class ChromeSliceTrack extends Track<Config, Data> {
+ static readonly kind: string = SLICE_TRACK_KIND;
+ static create(trackState: TrackState): Track {
return new ChromeSliceTrack(trackState);
}