Merge "Add consumer API and --query cmdline to list data sources"
diff --git a/Android.bp b/Android.bp
index 6985687..79de486 100644
--- a/Android.bp
+++ b/Android.bp
@@ -215,6 +215,20 @@
],
}
+// GN target: //:idle_alloc
+cc_binary {
+ name: "idle_alloc",
+ srcs: [
+ "tools/idle_alloc.cc",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ defaults: [
+ "perfetto_defaults",
+ ],
+}
+
// GN target: //:libperfetto
cc_library_shared {
name: "libperfetto",
@@ -392,6 +406,149 @@
],
}
+// GN target: //:libperfetto_client_experimental
+cc_library_static {
+ name: "libperfetto_client_experimental",
+ srcs: [
+ ":perfetto_protos_perfetto_common_lite_gen",
+ ":perfetto_protos_perfetto_common_zero_gen",
+ ":perfetto_protos_perfetto_config_lite_gen",
+ ":perfetto_protos_perfetto_config_zero_gen",
+ ":perfetto_protos_perfetto_ipc_ipc_gen",
+ ":perfetto_protos_perfetto_trace_android_zero_gen",
+ ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+ ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
+ ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
+ ":perfetto_protos_perfetto_trace_interned_data_zero_gen",
+ ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+ ":perfetto_protos_perfetto_trace_power_zero_gen",
+ ":perfetto_protos_perfetto_trace_profiling_zero_gen",
+ ":perfetto_protos_perfetto_trace_ps_zero_gen",
+ ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
+ ":perfetto_protos_perfetto_trace_track_event_zero_gen",
+ ":perfetto_protos_perfetto_trace_trusted_lite_gen",
+ ":perfetto_protos_perfetto_trace_zero_gen",
+ ":perfetto_src_ipc_wire_protocol_gen",
+ "src/base/event.cc",
+ "src/base/file_utils.cc",
+ "src/base/metatrace.cc",
+ "src/base/paged_memory.cc",
+ "src/base/pipe.cc",
+ "src/base/string_splitter.cc",
+ "src/base/string_utils.cc",
+ "src/base/string_view.cc",
+ "src/base/temp_file.cc",
+ "src/base/thread_checker.cc",
+ "src/base/thread_task_runner.cc",
+ "src/base/time.cc",
+ "src/base/unix_socket.cc",
+ "src/base/unix_task_runner.cc",
+ "src/base/virtual_destructors.cc",
+ "src/base/watchdog_posix.cc",
+ "src/ipc/buffered_frame_deserializer.cc",
+ "src/ipc/client_impl.cc",
+ "src/ipc/deferred.cc",
+ "src/ipc/host_impl.cc",
+ "src/ipc/service_proxy.cc",
+ "src/ipc/virtual_destructors.cc",
+ "src/protozero/message.cc",
+ "src/protozero/message_handle.cc",
+ "src/protozero/proto_decoder.cc",
+ "src/protozero/scattered_heap_buffer.cc",
+ "src/protozero/scattered_stream_null_delegate.cc",
+ "src/protozero/scattered_stream_writer.cc",
+ "src/tracing/core/chrome_config.cc",
+ "src/tracing/core/commit_data_request.cc",
+ "src/tracing/core/data_source_config.cc",
+ "src/tracing/core/data_source_descriptor.cc",
+ "src/tracing/core/id_allocator.cc",
+ "src/tracing/core/null_trace_writer.cc",
+ "src/tracing/core/observable_events.cc",
+ "src/tracing/core/packet_stream_validator.cc",
+ "src/tracing/core/shared_memory_abi.cc",
+ "src/tracing/core/shared_memory_arbiter_impl.cc",
+ "src/tracing/core/sliced_protobuf_input_stream.cc",
+ "src/tracing/core/startup_trace_writer.cc",
+ "src/tracing/core/startup_trace_writer_registry.cc",
+ "src/tracing/core/test_config.cc",
+ "src/tracing/core/trace_buffer.cc",
+ "src/tracing/core/trace_config.cc",
+ "src/tracing/core/trace_packet.cc",
+ "src/tracing/core/trace_stats.cc",
+ "src/tracing/core/trace_writer_impl.cc",
+ "src/tracing/core/tracing_service_impl.cc",
+ "src/tracing/core/virtual_destructors.cc",
+ "src/tracing/data_source.cc",
+ "src/tracing/internal/in_process_tracing_backend.cc",
+ "src/tracing/internal/system_tracing_backend.cc",
+ "src/tracing/internal/tracing_muxer_impl.cc",
+ "src/tracing/platform.cc",
+ "src/tracing/platform_posix.cc",
+ "src/tracing/trace_writer_base.cc",
+ "src/tracing/tracing.cc",
+ "src/tracing/virtual_destructors.cc",
+ ],
+ shared_libs: [
+ "libprotobuf-cpp-lite",
+ ],
+ static_libs: [
+ "perfetto_src_tracing_ipc",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ generated_headers: [
+ "perfetto_protos_perfetto_common_lite_gen_headers",
+ "perfetto_protos_perfetto_common_zero_gen_headers",
+ "perfetto_protos_perfetto_config_lite_gen_headers",
+ "perfetto_protos_perfetto_config_zero_gen_headers",
+ "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+ "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+ "perfetto_protos_perfetto_trace_power_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
+ "perfetto_protos_perfetto_trace_zero_gen_headers",
+ "perfetto_src_ipc_wire_protocol_gen_headers",
+ ],
+ export_generated_headers: [
+ "perfetto_protos_perfetto_common_lite_gen_headers",
+ "perfetto_protos_perfetto_common_zero_gen_headers",
+ "perfetto_protos_perfetto_config_lite_gen_headers",
+ "perfetto_protos_perfetto_config_zero_gen_headers",
+ "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+ "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+ "perfetto_protos_perfetto_trace_power_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+ "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
+ "perfetto_protos_perfetto_trace_zero_gen_headers",
+ "perfetto_src_ipc_wire_protocol_gen_headers",
+ ],
+ defaults: [
+ "perfetto_defaults",
+ ],
+ cflags: [
+ "-DGOOGLE_PROTOBUF_NO_RTTI",
+ "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+ "-DPERFETTO_BUILD_WITH_ANDROID",
+ ],
+}
+
// GN target: //:perfetto
cc_binary {
name: "perfetto",
@@ -3599,4 +3756,30 @@
srcs: [
"protos/perfetto/config/perfetto_config.proto",
],
+}
+
+// This sample target shows how to use the perfetto client API from within the
+// Android tree.
+cc_binary {
+ name: "libperfetto_client_example",
+ srcs: [
+ "test/android_client_api_example.cc",
+ ],
+ static_libs: [
+ "libperfetto_client_experimental",
+ "perfetto_src_tracing_ipc",
+ "perfetto_trace_protos",
+ ],
+ shared_libs: [
+ "libprotobuf-cpp-lite",
+ "liblog",
+ ],
+ local_include_dirs: [
+ "include",
+ ],
+ cflags: [
+ "-DGOOGLE_PROTOBUF_NO_RTTI",
+ "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+ "-DPERFETTO_BUILD_WITH_ANDROID",
+ ],
}
\ No newline at end of file
diff --git a/Android.bp.extras b/Android.bp.extras
index 84c0f10..54de0c9 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -64,3 +64,29 @@
"protos/perfetto/config/perfetto_config.proto",
],
}
+
+// This sample target shows how to use the perfetto client API from within the
+// Android tree.
+cc_binary {
+ name: "libperfetto_client_example",
+ srcs: [
+ "test/android_client_api_example.cc",
+ ],
+ static_libs: [
+ "libperfetto_client_experimental",
+ "perfetto_src_tracing_ipc",
+ "perfetto_trace_protos",
+ ],
+ shared_libs: [
+ "libprotobuf-cpp-lite",
+ "liblog",
+ ],
+ local_include_dirs: [
+ "include",
+ ],
+ cflags: [
+ "-DGOOGLE_PROTOBUF_NO_RTTI",
+ "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+ "-DPERFETTO_BUILD_WITH_ANDROID",
+ ],
+}
diff --git a/BUILD.gn b/BUILD.gn
index 367b8b6..e28ab12 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -271,6 +271,19 @@
"src/android_internal",
]
}
+
+ # Client library target for the Android tree.
+ # Still in experimental stage and not API stable yet.
+ # See "libperfetto_client_example" (in Android.bp.extras) for an example
+ # on how to use the Perfetto Client API from the android tree.
+ static_library("libperfetto_client_experimental") {
+ complete_static_lib = true
+ deps = [
+ "gn:default_deps",
+ "src/tracing:client_api",
+ "src/tracing:platform_posix",
+ ]
+ }
} # if (perfetto_build_with_android)
} # if (perfetto_build_standalone || perfetto_build_with_android)
@@ -364,3 +377,9 @@
]
}
}
+
+executable("idle_alloc") {
+ sources = [
+ "tools/idle_alloc.cc",
+ ]
+}
diff --git a/heapprofd.rc b/heapprofd.rc
index 2e69f60..86b8c74 100644
--- a/heapprofd.rc
+++ b/heapprofd.rc
@@ -24,7 +24,10 @@
# DAC_READ_SEARCH is denied by SELinux on user builds because the SELinux
# permission is userdebug_or_eng only.
# This is fine as this is not needed for central heapprofd in fork mode.
- capabilities KILL DAC_READ_SEARCH
+ # SYS_ADMIN and SYS_PTRACE will be denied unless SELinux enforcement is
+ # off. They are needed to implement the idle page tracking feature, which
+ # only works without SELinux enforcement on.
+ capabilities KILL DAC_READ_SEARCH DAC_OVERRIDE SYS_ADMIN SYS_PTRACE
on property:persist.heapprofd.enable=1
start heapprofd
diff --git a/include/perfetto/ext/base/string_writer.h b/include/perfetto/ext/base/string_writer.h
index 10e92a0..6bc7fad 100644
--- a/include/perfetto/ext/base/string_writer.h
+++ b/include/perfetto/ext/base/string_writer.h
@@ -68,34 +68,20 @@
// digits of the integer is less than |padding|.
template <char padchar, uint64_t padding>
void AppendPaddedInt(int64_t sign_value) {
- // Need to add 2 to the number of digits to account for minus sign and
- // rounding down of digits10.
- constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
- constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
- PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);
-
- char data[kSizeNeeded];
const bool negate = signbit(static_cast<double>(sign_value));
- uint64_t value = static_cast<uint64_t>(std::abs(sign_value));
+ uint64_t absolute_value = static_cast<uint64_t>(std::abs(sign_value));
+ AppendPaddedInt<padchar, padding>(absolute_value, negate);
+ }
- size_t idx;
- for (idx = kSizeNeeded - 1; value >= 10;) {
- char digit = value % 10;
- value /= 10;
- data[idx--] = digit + '0';
- }
- data[idx--] = static_cast<char>(value) + '0';
+ void AppendUnsignedInt(uint64_t value) {
+ AppendPaddedUnsignedInt<'0', 0>(value);
+ }
- if (padding > 0) {
- size_t num_digits = kSizeNeeded - 1 - idx;
- for (size_t i = num_digits; i < padding; i++) {
- data[idx--] = padchar;
- }
- }
-
- if (negate)
- buffer_[pos_++] = '-';
- AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
+ // Appends an unsigned integer to the buffer, padding with |padchar| if the
+ // number of digits of the integer is less than |padding|.
+ template <char padchar, uint64_t padding>
+ void AppendPaddedUnsignedInt(uint64_t value) {
+ AppendPaddedInt<padchar, padding>(value, false);
}
// Appends a hex integer to the buffer.
@@ -118,6 +104,14 @@
pos_ += res;
}
+ void AppendBool(bool value) {
+ if (value) {
+ AppendLiteral("true");
+ return;
+ }
+ AppendLiteral("false");
+ }
+
StringView GetStringView() {
PERFETTO_DCHECK(pos_ <= size_);
return StringView(buffer_, pos_);
@@ -137,6 +131,36 @@
void reset() { pos_ = 0; }
private:
+ template <char padchar, uint64_t padding>
+ void AppendPaddedInt(uint64_t absolute_value, bool negate) {
+ // Need to add 2 to the number of digits to account for minus sign and
+ // rounding down of digits10.
+ constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
+ constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
+ PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);
+
+ char data[kSizeNeeded];
+
+ size_t idx;
+ for (idx = kSizeNeeded - 1; absolute_value >= 10;) {
+ char digit = absolute_value % 10;
+ absolute_value /= 10;
+ data[idx--] = digit + '0';
+ }
+ data[idx--] = static_cast<char>(absolute_value) + '0';
+
+ if (padding > 0) {
+ size_t num_digits = kSizeNeeded - 1 - idx;
+ for (size_t i = num_digits; i < padding; i++) {
+ data[idx--] = padchar;
+ }
+ }
+
+ if (negate)
+ buffer_[pos_++] = '-';
+ AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
+ }
+
char* buffer_ = nullptr;
size_t size_ = 0;
size_t pos_ = 0;
diff --git a/include/perfetto/tracing.h b/include/perfetto/tracing.h
index 6e0abef..3dfb88a 100644
--- a/include/perfetto/tracing.h
+++ b/include/perfetto/tracing.h
@@ -28,4 +28,9 @@
#include "perfetto/tracing/tracing.h"
#include "perfetto/tracing/tracing_backend.h"
+// TODO(primiano): move these generated classes from /ext/ into public. Right
+// now these are a layering violation.
+#include "perfetto/ext/tracing/core/data_source_descriptor.h" // nogncheck
+#include "perfetto/ext/tracing/core/trace_config.h" // nogncheck
+
#endif // INCLUDE_PERFETTO_TRACING_H_
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index fb15d07..e404c75 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3132,29 +3132,29 @@
optional int32 legacy_sort_index = 3;
enum ChromeThreadType {
- THREAD_TYPE_UNSPECIFIED = 0;
+ CHROME_THREAD_UNSPECIFIED = 0;
- THREAD_TYPE_MAIN = 1;
- THREAD_TYPE_IO = 2;
+ CHROME_THREAD_MAIN = 1;
+ CHROME_THREAD_IO = 2;
// Scheduler:
- THREAD_TYPE_THREAD_POOL_BG_WORKER = 3;
- THREAD_TYPE_THREAD_POOL_FG_WORKER = 4;
- THREAD_TYPE_THREAD_POOL_FB_BLOCKING = 5;
- THREAD_TYPE_THREAD_POOL_BG_BLOCKING = 6;
- THREAD_TYPE_THREAD_POOL_SERVICE = 7;
+ CHROME_THREAD_POOL_BG_WORKER = 3;
+ CHROME_THREAD_POOL_FG_WORKER = 4;
+ CHROME_THREAD_POOL_FB_BLOCKING = 5;
+ CHROME_THREAD_POOL_BG_BLOCKING = 6;
+ CHROME_THREAD_POOL_SERVICE = 7;
// Compositor:
- THREAD_TYPE_COMPOSITOR = 8;
- THREAD_TYPE_VIZ_COMPOSITOR = 9;
- THREAD_TYPE_COMPOSITOR_WORKER = 10;
+ CHROME_THREAD_COMPOSITOR = 8;
+ CHROME_THREAD_VIZ_COMPOSITOR = 9;
+ CHROME_THREAD_COMPOSITOR_WORKER = 10;
// Renderer:
- THREAD_TYPE_SERVICE_WORKER = 11;
+ CHROME_THREAD_SERVICE_WORKER = 11;
// Tracing related threads:
- THREAD_TYPE_MEMORY_INFRA = 50;
- THREAD_TYPE_SAMPLING_PROFILER = 51;
+ CHROME_THREAD_MEMORY_INFRA = 50;
+ CHROME_THREAD_SAMPLING_PROFILER = 51;
};
optional ChromeThreadType chrome_thread_type = 4;
diff --git a/protos/perfetto/trace/track_event/thread_descriptor.proto b/protos/perfetto/trace/track_event/thread_descriptor.proto
index 85a5824..78afc36 100644
--- a/protos/perfetto/trace/track_event/thread_descriptor.proto
+++ b/protos/perfetto/trace/track_event/thread_descriptor.proto
@@ -32,29 +32,29 @@
optional int32 legacy_sort_index = 3;
enum ChromeThreadType {
- THREAD_TYPE_UNSPECIFIED = 0;
+ CHROME_THREAD_UNSPECIFIED = 0;
- THREAD_TYPE_MAIN = 1;
- THREAD_TYPE_IO = 2;
+ CHROME_THREAD_MAIN = 1;
+ CHROME_THREAD_IO = 2;
// Scheduler:
- THREAD_TYPE_THREAD_POOL_BG_WORKER = 3;
- THREAD_TYPE_THREAD_POOL_FG_WORKER = 4;
- THREAD_TYPE_THREAD_POOL_FB_BLOCKING = 5;
- THREAD_TYPE_THREAD_POOL_BG_BLOCKING = 6;
- THREAD_TYPE_THREAD_POOL_SERVICE = 7;
+ CHROME_THREAD_POOL_BG_WORKER = 3;
+ CHROME_THREAD_POOL_FG_WORKER = 4;
+ CHROME_THREAD_POOL_FB_BLOCKING = 5;
+ CHROME_THREAD_POOL_BG_BLOCKING = 6;
+ CHROME_THREAD_POOL_SERVICE = 7;
// Compositor:
- THREAD_TYPE_COMPOSITOR = 8;
- THREAD_TYPE_VIZ_COMPOSITOR = 9;
- THREAD_TYPE_COMPOSITOR_WORKER = 10;
+ CHROME_THREAD_COMPOSITOR = 8;
+ CHROME_THREAD_VIZ_COMPOSITOR = 9;
+ CHROME_THREAD_COMPOSITOR_WORKER = 10;
// Renderer:
- THREAD_TYPE_SERVICE_WORKER = 11;
+ CHROME_THREAD_SERVICE_WORKER = 11;
// Tracing related threads:
- THREAD_TYPE_MEMORY_INFRA = 50;
- THREAD_TYPE_SAMPLING_PROFILER = 51;
+ CHROME_THREAD_MEMORY_INFRA = 50;
+ CHROME_THREAD_SAMPLING_PROFILER = 51;
};
optional ChromeThreadType chrome_thread_type = 4;
diff --git a/src/base/string_writer_unittest.cc b/src/base/string_writer_unittest.cc
index 8d83dcc..12a7872 100644
--- a/src/base/string_writer_unittest.cc
+++ b/src/base/string_writer_unittest.cc
@@ -37,6 +37,11 @@
}
{
base::StringWriter writer(buffer, sizeof(buffer));
+ writer.AppendUnsignedInt(523);
+ ASSERT_EQ(writer.GetStringView().ToStdString(), "523");
+ }
+ {
+ base::StringWriter writer(buffer, sizeof(buffer));
writer.AppendPaddedInt<'0', 3>(0);
ASSERT_EQ(writer.GetStringView().ToStdString(), "000");
}
@@ -62,6 +67,11 @@
}
{
base::StringWriter writer(buffer, sizeof(buffer));
+ writer.AppendPaddedUnsignedInt<' ', 5>(123);
+ ASSERT_EQ(writer.GetStringView().ToStdString(), " 123");
+ }
+ {
+ base::StringWriter writer(buffer, sizeof(buffer));
writer.AppendDouble(123.25);
ASSERT_EQ(writer.GetStringView().ToStdString(), "123.250000");
}
@@ -75,6 +85,21 @@
writer.AppendInt(std::numeric_limits<int64_t>::max());
ASSERT_EQ(writer.GetStringView().ToStdString(), "9223372036854775807");
}
+ {
+ base::StringWriter writer(buffer, sizeof(buffer));
+ writer.AppendUnsignedInt(std::numeric_limits<uint64_t>::max());
+ ASSERT_EQ(writer.GetStringView().ToStdString(), "18446744073709551615");
+ }
+ {
+ base::StringWriter writer(buffer, sizeof(buffer));
+ writer.AppendBool(true);
+ ASSERT_EQ(writer.GetStringView().ToStdString(), "true");
+ }
+ {
+ base::StringWriter writer(buffer, sizeof(buffer));
+ writer.AppendBool(false);
+ ASSERT_EQ(writer.GetStringView().ToStdString(), "false");
+ }
constexpr char kTestStr[] = "test";
{
@@ -105,13 +130,16 @@
base::StringWriter writer(buffer, sizeof(buffer));
writer.AppendChar('0');
writer.AppendInt(132545);
+ writer.AppendUnsignedInt(523);
writer.AppendPaddedInt<'0', 0>(1);
writer.AppendPaddedInt<'0', 3>(0);
writer.AppendPaddedInt<'0', 1>(1);
writer.AppendPaddedInt<'0', 2>(1);
writer.AppendPaddedInt<'0', 3>(1);
writer.AppendPaddedInt<' ', 5>(123);
+ writer.AppendPaddedUnsignedInt<' ', 5>(456);
writer.AppendDouble(123.25);
+ writer.AppendBool(true);
constexpr char kTestStr[] = "test";
writer.AppendLiteral(kTestStr);
@@ -119,7 +147,7 @@
writer.AppendString(kTestStr);
ASSERT_EQ(writer.GetStringView().ToStdString(),
- "01325451000101001 123123.250000testtesttest");
+ "01325455231000101001 123 456123.250000truetesttesttest");
}
} // namespace
diff --git a/src/profiling/memory/client.cc b/src/profiling/memory/client.cc
index 7ea4528..86550b2 100644
--- a/src/profiling/memory/client.cc
+++ b/src/profiling/memory/client.cc
@@ -152,11 +152,6 @@
PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/self/mem");
return nullptr;
}
- base::ScopedFile pagemap(base::OpenFile("/proc/self/pagemap", O_RDONLY));
- if (!pagemap) {
- PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/self/pagemap");
- return nullptr;
- }
// Restore original dumpability value if we overrode it.
unset_dumpable.reset();
@@ -164,7 +159,6 @@
int fds[kHandshakeSize];
fds[kHandshakeMaps] = *maps;
fds[kHandshakeMem] = *mem;
- fds[kHandshakePageMap] = *pagemap;
// Send an empty record to transfer fds for /proc/self/maps and
// /proc/self/mem.
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index 20782ff..f55306d 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -87,6 +87,22 @@
return i;
}
+base::Optional<PageIdleChecker> MakePageIdleChecker(base::ScopedFile pagemap) {
+ base::Optional<PageIdleChecker> res;
+ if (!pagemap) {
+ PERFETTO_PLOG("Invalid pagemap.");
+ return res;
+ }
+ base::ScopedFile bitmap(
+ base::OpenFile("/sys/kernel/mm/page_idle/bitmap", O_RDWR));
+ if (!bitmap) {
+ PERFETTO_PLOG("Failed to open /sys/kernel/mm/page_idle/bitmap.");
+ return res;
+ }
+ res = PageIdleChecker(std::move(pagemap), std::move(bitmap));
+ return res;
+}
+
} // namespace
const uint64_t LogHistogram::kMaxBucket = 0;
@@ -590,6 +606,8 @@
[&dump_state](const HeapTracker::CallstackAllocations& alloc) {
dump_state.WriteAllocation(alloc);
});
+ if (process_state.page_idle_checker)
+ process_state.page_idle_checker->MarkPagesIdle();
}
dump_state.DumpCallstacks(&callsites_);
@@ -679,8 +697,8 @@
char buf[1];
self->Receive(buf, sizeof(buf), fds, base::ArraySize(fds));
- static_assert(kHandshakeSize == 3, "change if and else if below.");
- if (fds[kHandshakeMaps] && fds[kHandshakeMem] && fds[kHandshakePageMap]) {
+ static_assert(kHandshakeSize == 2, "change if and else if below.");
+ if (fds[kHandshakeMaps] && fds[kHandshakeMem]) {
auto ds_it =
producer_->data_sources_.find(pending_process.data_source_instance_id);
if (ds_it == producer_->data_sources_.end()) {
@@ -694,13 +712,17 @@
ProcessState& process_state = it_and_inserted.first->second;
if (data_source.config.idle_allocations()) {
- base::ScopedFile kpageflags(base::OpenFile("/proc/kpageflags", O_RDONLY));
- if (kpageflags) {
- process_state.page_idle_checker = PageIdleChecker(
- std::move(fds[kHandshakePageMap]), std::move(kpageflags));
- } else {
- PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/kpageflags");
- }
+ // We have to open this here, because reading the PFN requires
+ // the process that opened the file to have CAP_SYS_ADMIN. We can work
+ // around this by making this a setenforce 0 only feature, giving
+ // heapprofd very broad capabilities (CAP_SYS_ADMIN and CAP_SYS_PTRACE)
+ // which will get rejected by SELinux on real builds.
+ std::string procfs_path =
+ "/proc/" + std::to_string(self->peer_pid()) + "/pagemap";
+ base::ScopedFile pagemap_fd(
+ base::OpenFile(procfs_path.c_str(), O_RDONLY));
+ process_state.page_idle_checker =
+ MakePageIdleChecker(std::move(pagemap_fd));
}
PERFETTO_DLOG("%d: Received FDs.", self->peer_pid());
@@ -722,8 +744,7 @@
producer_->UnwinderForPID(self->peer_pid())
.PostHandoffSocket(std::move(handoff_data));
producer_->pending_processes_.erase(it);
- } else if (fds[kHandshakeMaps] || fds[kHandshakeMem] ||
- fds[kHandshakePageMap]) {
+ } else if (fds[kHandshakeMaps] || fds[kHandshakeMem]) {
PERFETTO_DFATAL_OR_ELOG("%d: Received partial FDs.", self->peer_pid());
producer_->pending_processes_.erase(it);
} else {
diff --git a/src/profiling/memory/page_idle_checker.cc b/src/profiling/memory/page_idle_checker.cc
index d018b6b..10cd1fe 100644
--- a/src/profiling/memory/page_idle_checker.cc
+++ b/src/profiling/memory/page_idle_checker.cc
@@ -18,6 +18,7 @@
#include "perfetto/ext/base/utils.h"
#include "src/profiling/memory/utils.h"
+#include <inttypes.h>
#include <vector>
namespace perfetto {
@@ -27,57 +28,103 @@
constexpr uint64_t kIsInRam = 1ULL << 63;
constexpr uint64_t kRamPhysicalPageMask = ~(~0ULL << 55);
-constexpr uint64_t kPhysPageReferenced = 1ULL << 2;
-
} // namespace
int64_t PageIdleChecker::OnIdlePage(uint64_t addr, size_t size) {
uint64_t page_nr = addr / base::kPageSize;
- uint64_t page_aligned_addr = page_nr * base::kPageSize;
uint64_t end_page_nr = (addr + size) / base::kPageSize;
// The trailing division will have rounded down, unless the end is at a page
// boundary. Add one page if we rounded down.
- if (addr + size % base::kPageSize != 0)
+ if ((addr + size) % base::kPageSize != 0)
end_page_nr++;
- uint64_t page_aligned_end_addr = base::kPageSize * end_page_nr;
- size_t pages = (page_aligned_end_addr - page_aligned_addr) / base::kPageSize;
+ size_t pages = end_page_nr - page_nr;
std::vector<uint64_t> virt_page_infos(pages);
off64_t virt_off = static_cast<off64_t>(page_nr * sizeof(virt_page_infos[0]));
size_t virt_rd_size = pages * sizeof(virt_page_infos[0]);
- if (ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
- virt_rd_size, virt_off) !=
- static_cast<ssize_t>(virt_rd_size)) {
+ ssize_t rd = ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
+ virt_rd_size, virt_off);
+ if (rd != static_cast<ssize_t>(virt_rd_size)) {
+ PERFETTO_ELOG("Invalid read from pagemap: %zd", rd);
return -1;
}
int64_t idle_mem = 0;
for (size_t i = 0; i < pages; ++i) {
- if (!(virt_page_infos[i] & kIsInRam))
+ if (!virt_page_infos[i]) {
+ PERFETTO_DLOG("Empty pageinfo.");
continue;
- uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
- uint64_t phys_page_info;
- off64_t phys_off =
- static_cast<off64_t>(phys_page_nr * sizeof(phys_page_info));
- if (ReadAtOffsetClobberSeekPos(*kpageflags_fd_, &phys_page_info,
- sizeof(phys_page_info),
- phys_off) != sizeof(phys_page_info)) {
- return -1;
}
- if (!(phys_page_info & kPhysPageReferenced)) {
+
+ if (!(virt_page_infos[i] & kIsInRam)) {
+ PERFETTO_DLOG("Page is not in RAM.");
+ continue;
+ }
+
+ uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
+ if (!phys_page_nr) {
+ PERFETTO_ELOG("Failed to get physical page number.");
+ continue;
+ }
+
+ int idle = IsPageIdle(phys_page_nr);
+ if (idle == -1)
+ continue;
+
+ if (idle) {
if (i == 0)
idle_mem += GetFirstPageShare(addr, size);
else if (i == pages - 1)
idle_mem += GetLastPageShare(addr, size);
else
idle_mem += base::kPageSize;
+ } else {
+ touched_phys_page_nrs_.emplace(phys_page_nr);
}
}
return idle_mem;
}
+void PageIdleChecker::MarkPagesIdle() {
+ for (uint64_t phys_page_nr : touched_phys_page_nrs_)
+ MarkPageIdle(phys_page_nr);
+ touched_phys_page_nrs_.clear();
+}
+
+void PageIdleChecker::MarkPageIdle(uint64_t phys_page_nr) {
+ // The file implements a bitmap where each bit corresponds to a memory page.
+ // The bitmap is represented by an array of 8-byte integers, and the page at
+ // PFN #i is mapped to bit #i%64 of array element #i/64, byte order i
+ // native. When a bit is set, the corresponding page is idle.
+ //
+ // The kernel ORs the value written with the existing bitmap, so we do not
+ // override previously written values.
+ // See https://www.kernel.org/doc/Documentation/vm/idle_page_tracking.txt
+ off64_t offset = 8 * (phys_page_nr / 64);
+ size_t bit_offset = phys_page_nr % 64;
+ uint64_t bit_pattern = 1 << bit_offset;
+ if (WriteAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern,
+ sizeof(bit_pattern), offset) !=
+ static_cast<ssize_t>(sizeof(bit_pattern))) {
+ PERFETTO_PLOG("Failed to write bit pattern at %" PRIi64 ".", offset);
+ }
+}
+
+int PageIdleChecker::IsPageIdle(uint64_t phys_page_nr) {
+ off64_t offset = 8 * (phys_page_nr / 64);
+ size_t bit_offset = phys_page_nr % 64;
+ uint64_t bit_pattern;
+ if (ReadAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern, sizeof(bit_pattern),
+ offset) !=
+ static_cast<ssize_t>(sizeof(bit_pattern))) {
+ PERFETTO_PLOG("Failed to read bit pattern at %" PRIi64 ".", offset);
+ return -1;
+ }
+ return static_cast<int>(bit_pattern & (1 << bit_offset));
+}
+
uint64_t GetFirstPageShare(uint64_t addr, size_t size) {
// Our allocation is xxxx in this illustration:
// +----------------------------------------------+
diff --git a/src/profiling/memory/page_idle_checker.h b/src/profiling/memory/page_idle_checker.h
index 2a9ab7b..529c097 100644
--- a/src/profiling/memory/page_idle_checker.h
+++ b/src/profiling/memory/page_idle_checker.h
@@ -17,6 +17,8 @@
#ifndef SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
#define SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
+#include <set>
+
#include <stddef.h>
#include <stdint.h>
@@ -30,18 +32,25 @@
class PageIdleChecker {
public:
- PageIdleChecker(base::ScopedFile pagemap_fd, base::ScopedFile kpageflags_fd)
- : pagemap_fd_(std::move(pagemap_fd)),
- kpageflags_fd_(std::move(kpageflags_fd)) {}
+ PageIdleChecker(base::ScopedFile pagemap_fd, base::ScopedFile bitmap_fd)
+ : pagemap_fd_(std::move(pagemap_fd)), bitmap_fd_(std::move(bitmap_fd)) {}
// Return number of bytes of allocation of size bytes starting at alloc that
// are on unreferenced pages.
// Return -1 on error.
int64_t OnIdlePage(uint64_t addr, size_t size);
+ void MarkPagesIdle();
+
private:
+ void MarkPageIdle(uint64_t phys_page_nr);
+ // Return 1 if page is idle, 0 if it is not idle, or -1 on error.
+ int IsPageIdle(uint64_t phys_page_nr);
+
+ std::set<uint64_t> touched_phys_page_nrs_;
+
base::ScopedFile pagemap_fd_;
- base::ScopedFile kpageflags_fd_;
+ base::ScopedFile bitmap_fd_;
};
} // namespace profiling
diff --git a/src/profiling/memory/utils.cc b/src/profiling/memory/utils.cc
index 3e2e78d..b424192 100644
--- a/src/profiling/memory/utils.cc
+++ b/src/profiling/memory/utils.cc
@@ -35,5 +35,21 @@
#endif
}
+// Behaves as a pread64, emulating it if not already exposed by the standard
+// library.
+// Clobbers the |fd| seek position if emulating.
+ssize_t WriteAtOffsetClobberSeekPos(int fd,
+ void* buf,
+ size_t count,
+ off64_t addr) {
+#ifdef __BIONIC__
+ return pwrite64(fd, buf, count, addr);
+#else
+ if (lseek64(fd, addr, SEEK_SET) == -1)
+ return -1;
+ return write(fd, buf, count);
+#endif
+}
+
} // namespace profiling
} // namespace perfetto
diff --git a/src/profiling/memory/utils.h b/src/profiling/memory/utils.h
index d338678..5e375c9 100644
--- a/src/profiling/memory/utils.h
+++ b/src/profiling/memory/utils.h
@@ -26,6 +26,11 @@
void* buf,
size_t count,
off64_t addr);
+
+ssize_t WriteAtOffsetClobberSeekPos(int fd,
+ void* buf,
+ size_t count,
+ off64_t addr);
}
} // namespace perfetto
diff --git a/src/profiling/memory/wire_protocol.h b/src/profiling/memory/wire_protocol.h
index b970dd4..9de58d6 100644
--- a/src/profiling/memory/wire_protocol.h
+++ b/src/profiling/memory/wire_protocol.h
@@ -119,9 +119,8 @@
enum HandshakeFDs : size_t {
kHandshakeMaps = 0,
- kHandshakeMem = 1,
- kHandshakePageMap = 2,
- kHandshakeSize = 3,
+ kHandshakeMem,
+ kHandshakeSize,
};
struct WireMessage {
diff --git a/src/trace_processor/args_table.cc b/src/trace_processor/args_table.cc
index 8b781f2..bc89ef0 100644
--- a/src/trace_processor/args_table.cc
+++ b/src/trace_processor/args_table.cc
@@ -79,14 +79,30 @@
case Variadic::Type::kInt:
sqlite_utils::ReportSqliteResult(ctx, value.int_value);
break;
- case Variadic::Type::kReal:
- sqlite_utils::ReportSqliteResult(ctx, value.real_value);
+ case Variadic::Type::kUint:
+ // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+ sqlite_utils::ReportSqliteResult(ctx,
+ static_cast<int64_t>(value.uint_value));
break;
case Variadic::Type::kString: {
const char* str = storage_->GetString(value.string_value).c_str();
sqlite3_result_text(ctx, str, -1, sqlite_utils::kSqliteStatic);
break;
}
+ case Variadic::Type::kReal:
+ sqlite_utils::ReportSqliteResult(ctx, value.real_value);
+ break;
+ case Variadic::Type::kPointer:
+ // BEWARE: pointers are handled as signed int64 for SQLite operations.
+ sqlite_utils::ReportSqliteResult(
+ ctx, static_cast<int64_t>(value.pointer_value));
+ break;
+ case Variadic::Type::kBool:
+ sqlite_utils::ReportSqliteResult(ctx, value.bool_value);
+ break;
+ case Variadic::Type::kJson:
+ sqlite_utils::ReportSqliteResult(ctx, value.json_value);
+ break;
}
}
@@ -110,13 +126,16 @@
});
break;
}
- case Variadic::Type::kReal: {
+ case Variadic::Type::kUint: {
bool op_is_null = sqlite_utils::IsOpIsNull(op);
- auto predicate = sqlite_utils::CreateNumericPredicate<double>(op, value);
+ // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+ auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
index->FilterRows(
[this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
const auto& arg = storage_->args().arg_values()[row];
- return arg.type == type_ ? predicate(arg.real_value) : op_is_null;
+ return arg.type == type_
+ ? predicate(static_cast<int64_t>(arg.uint_value))
+ : op_is_null;
});
break;
}
@@ -131,6 +150,50 @@
});
break;
}
+ case Variadic::Type::kReal: {
+ bool op_is_null = sqlite_utils::IsOpIsNull(op);
+ auto predicate = sqlite_utils::CreateNumericPredicate<double>(op, value);
+ index->FilterRows(
+ [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+ const auto& arg = storage_->args().arg_values()[row];
+ return arg.type == type_ ? predicate(arg.real_value) : op_is_null;
+ });
+ break;
+ }
+ case Variadic::Type::kPointer: {
+ bool op_is_null = sqlite_utils::IsOpIsNull(op);
+ // BEWARE: pointers are handled as signed int64 for SQLite operations.
+ auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
+ index->FilterRows(
+ [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+ const auto& arg = storage_->args().arg_values()[row];
+ return arg.type == type_
+ ? predicate(static_cast<int64_t>(arg.pointer_value))
+ : op_is_null;
+ });
+ break;
+ }
+ case Variadic::Type::kBool: {
+ bool op_is_null = sqlite_utils::IsOpIsNull(op);
+ auto predicate = sqlite_utils::CreateNumericPredicate<bool>(op, value);
+ index->FilterRows(
+ [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+ const auto& arg = storage_->args().arg_values()[row];
+ return arg.type == type_ ? predicate(arg.bool_value) : op_is_null;
+ });
+ break;
+ }
+ case Variadic::Type::kJson: {
+ auto predicate = sqlite_utils::CreateStringPredicate(op, value);
+ index->FilterRows([this,
+ &predicate](uint32_t row) PERFETTO_ALWAYS_INLINE {
+ const auto& arg = storage_->args().arg_values()[row];
+ return arg.type == type_
+ ? predicate(storage_->GetString(arg.json_value).c_str())
+ : predicate(nullptr);
+ });
+ break;
+ }
}
}
@@ -150,14 +213,32 @@
switch (type_) {
case Variadic::Type::kInt:
return sqlite_utils::CompareValuesAsc(arg_f.int_value, arg_s.int_value);
- case Variadic::Type::kReal:
- return sqlite_utils::CompareValuesAsc(arg_f.real_value,
- arg_s.real_value);
+ case Variadic::Type::kUint:
+ // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+ return sqlite_utils::CompareValuesAsc(
+ static_cast<int64_t>(arg_f.uint_value),
+ static_cast<int64_t>(arg_s.uint_value));
case Variadic::Type::kString: {
const auto& f_str = storage_->GetString(arg_f.string_value);
const auto& s_str = storage_->GetString(arg_s.string_value);
return sqlite_utils::CompareValuesAsc(f_str, s_str);
}
+ case Variadic::Type::kReal:
+ return sqlite_utils::CompareValuesAsc(arg_f.real_value,
+ arg_s.real_value);
+ case Variadic::Type::kPointer:
+ // BEWARE: pointers are handled as signed int64 for SQLite operations.
+ return sqlite_utils::CompareValuesAsc(
+ static_cast<int64_t>(arg_f.pointer_value),
+ static_cast<int64_t>(arg_s.pointer_value));
+ case Variadic::Type::kBool:
+ return sqlite_utils::CompareValuesAsc(arg_f.bool_value,
+ arg_s.bool_value);
+ case Variadic::Type::kJson: {
+ const auto& f_str = storage_->GetString(arg_f.json_value);
+ const auto& s_str = storage_->GetString(arg_s.json_value);
+ return sqlite_utils::CompareValuesAsc(f_str, s_str);
+ }
}
} else if (arg_s.type == type_) {
return -1;
diff --git a/src/trace_processor/args_table.h b/src/trace_processor/args_table.h
index 0d9fd4b..3327e57 100644
--- a/src/trace_processor/args_table.h
+++ b/src/trace_processor/args_table.h
@@ -56,9 +56,17 @@
switch (type_) {
case Variadic::Type::kInt:
return Table::ColumnType::kLong;
+ case Variadic::Type::kUint:
+ return Table::ColumnType::kLong;
+ case Variadic::Type::kString:
+ return Table::ColumnType::kString;
case Variadic::Type::kReal:
return Table::ColumnType::kDouble;
- case Variadic::Type::kString:
+ case Variadic::Type::kPointer:
+ return Table::ColumnType::kLong;
+ case Variadic::Type::kBool:
+ return Table::ColumnType::kBool;
+ case Variadic::Type::kJson:
return Table::ColumnType::kString;
}
PERFETTO_FATAL("Not reached"); // For gcc
diff --git a/src/trace_processor/args_tracker.h b/src/trace_processor/args_tracker.h
index b966dc9..7eb27d7 100644
--- a/src/trace_processor/args_tracker.h
+++ b/src/trace_processor/args_tracker.h
@@ -32,10 +32,12 @@
virtual ~ArgsTracker();
// Adds a arg for this row id with the given key and value.
- void AddArg(RowId row_id, StringId flat_key, StringId key, Variadic);
+ // Virtual for testing.
+ virtual void AddArg(RowId row_id, StringId flat_key, StringId key, Variadic);
// Commits the added args to storage.
- void Flush();
+ // Virtual for testing.
+ virtual void Flush();
private:
std::vector<TraceStorage::Args::Arg> args_;
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 38f4abd..b2dc1de 100644
--- a/src/trace_processor/proto_incremental_state.h
+++ b/src/trace_processor/proto_incremental_state.h
@@ -77,7 +77,7 @@
struct InternedDataView {
InternedDataView(TraceBlobView msg) : message(std::move(msg)) {}
- typename MessageType::Decoder CreateDecoder() {
+ typename MessageType::Decoder CreateDecoder() const {
return typename MessageType::Decoder(message.data(), message.length());
}
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index ae94745..3355766 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -118,7 +118,11 @@
oom_score_adj_id_(context->storage->InternString("oom_score_adj")),
ion_total_unknown_id_(context->storage->InternString("mem.ion.unknown")),
ion_change_unknown_id_(
- context->storage->InternString("mem.ion_change.unknown")) {
+ context->storage->InternString("mem.ion_change.unknown")),
+ task_file_name_args_key_id_(
+ context->storage->InternString("task.posted_from.file_name")),
+ task_function_name_args_key_id_(
+ context->storage->InternString("task.posted_from.function_name")) {
for (const auto& name : BuildMeminfoCounterNames()) {
meminfo_strs_id_.emplace_back(context->storage->InternString(name));
}
@@ -1436,21 +1440,35 @@
// TODO(eseckler): Handle thread timestamp/duration, debug annotations, task
// souce locations, legacy event attributes, ...
+ auto args_callback = [this, &event, &sequence_state](
+ ArgsTracker* args_tracker, RowId row) {
+ for (auto it = event.debug_annotations(); it; ++it) {
+ ParseDebugAnnotationArgs(it->as_bytes(), sequence_state, args_tracker,
+ row);
+ }
+
+ if (event.has_task_execution()) {
+ ParseTaskExecutionArgs(event.task_execution(), sequence_state,
+ args_tracker, row);
+ }
+ };
+
int32_t phase = legacy_event.phase();
switch (static_cast<char>(phase)) {
case 'B': { // TRACE_EVENT_PHASE_BEGIN.
- slice_tracker->Begin(ts, utid, category_id, name_id);
+ slice_tracker->Begin(ts, utid, category_id, name_id, args_callback);
break;
}
case 'E': { // TRACE_EVENT_PHASE_END.
- slice_tracker->End(ts, utid, category_id, name_id);
+ slice_tracker->End(ts, utid, category_id, name_id, args_callback);
break;
}
case 'X': { // TRACE_EVENT_PHASE_COMPLETE.
auto duration_ns = legacy_event.duration_us() * 1000;
if (duration_ns < 0)
return;
- slice_tracker->Scoped(ts, utid, category_id, name_id, duration_ns);
+ slice_tracker->Scoped(ts, utid, category_id, name_id, duration_ns,
+ args_callback);
break;
}
case 'M': { // TRACE_EVENT_PHASE_METADATA (process and thread names).
@@ -1486,6 +1504,176 @@
}
}
+void ProtoTraceParser::ParseDebugAnnotationArgs(
+ ConstBytes debug_annotation,
+ ProtoIncrementalState::PacketSequenceState* sequence_state,
+ ArgsTracker* args_tracker,
+ RowId row) {
+ protos::pbzero::DebugAnnotation::Decoder annotation(debug_annotation.data,
+ debug_annotation.size);
+ uint32_t iid = annotation.name_iid();
+ if (!iid)
+ return;
+
+ auto* map =
+ sequence_state->GetInternedDataMap<protos::pbzero::DebugAnnotationName>();
+ auto name_view_it = map->find(iid);
+ if (name_view_it == map->end()) {
+ PERFETTO_ELOG(
+ "Could not find debug annotation name interning entry for ID %u", iid);
+ return;
+ }
+
+ TraceStorage* storage = context_->storage.get();
+
+ StringId name_id = 0;
+
+ // If the name is already in the pool, no need to decode it again.
+ if (name_view_it->second.storage_refs) {
+ name_id = name_view_it->second.storage_refs->name_id;
+ } else {
+ auto name = name_view_it->second.CreateDecoder();
+ std::string name_prefixed = "debug." + name.name().ToStdString();
+ name_id = storage->InternString(base::StringView(name_prefixed));
+ // Avoid having to decode & look up the name again in the future.
+ name_view_it->second.storage_refs =
+ ProtoIncrementalState::StorageReferences<
+ protos::pbzero::DebugAnnotationName>{name_id};
+ }
+
+ if (annotation.has_bool_value()) {
+ args_tracker->AddArg(row, name_id, name_id,
+ Variadic::Boolean(annotation.bool_value()));
+ } else if (annotation.has_uint_value()) {
+ args_tracker->AddArg(row, name_id, name_id,
+ Variadic::UnsignedInteger(annotation.uint_value()));
+ } else if (annotation.has_int_value()) {
+ args_tracker->AddArg(row, name_id, name_id,
+ Variadic::Integer(annotation.int_value()));
+ } else if (annotation.has_double_value()) {
+ args_tracker->AddArg(row, name_id, name_id,
+ Variadic::Real(annotation.double_value()));
+ } else if (annotation.has_string_value()) {
+ args_tracker->AddArg(
+ row, name_id, name_id,
+ Variadic::String(storage->InternString(annotation.string_value())));
+ } else if (annotation.has_pointer_value()) {
+ args_tracker->AddArg(row, name_id, name_id,
+ Variadic::Pointer(annotation.pointer_value()));
+ } else if (annotation.has_legacy_json_value()) {
+ args_tracker->AddArg(row, name_id, name_id,
+ Variadic::String(storage->InternString(
+ annotation.legacy_json_value())));
+ } else if (annotation.has_nested_value()) {
+ auto name = storage->GetString(name_id);
+ ParseNestedValueArgs(annotation.nested_value(), name, name, args_tracker,
+ row);
+ }
+}
+
+void ProtoTraceParser::ParseNestedValueArgs(ConstBytes nested_value,
+ base::StringView flat_key,
+ base::StringView key,
+ ArgsTracker* args_tracker,
+ RowId row) {
+ protos::pbzero::DebugAnnotation::NestedValue::Decoder value(
+ nested_value.data, nested_value.size);
+ switch (value.nested_type()) {
+ case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
+ auto flat_key_id = context_->storage->InternString(flat_key);
+ auto key_id = context_->storage->InternString(key);
+ // Leaf value.
+ if (value.has_bool_value()) {
+ args_tracker->AddArg(row, flat_key_id, key_id,
+ Variadic::Boolean(value.bool_value()));
+ } else if (value.has_int_value()) {
+ args_tracker->AddArg(row, flat_key_id, key_id,
+ Variadic::Integer(value.int_value()));
+ } else if (value.has_double_value()) {
+ args_tracker->AddArg(row, flat_key_id, key_id,
+ Variadic::Real(value.double_value()));
+ } else if (value.has_string_value()) {
+ args_tracker->AddArg(row, flat_key_id, key_id,
+ Variadic::String(context_->storage->InternString(
+ value.string_value())));
+ }
+ break;
+ }
+ case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
+ auto key_it = value.dict_keys();
+ auto value_it = value.dict_values();
+ for (; key_it && value_it; ++key_it, ++value_it) {
+ std::string child_name = key_it->as_std_string();
+ std::string child_flat_key = flat_key.ToStdString() + "." + child_name;
+ std::string child_key = key.ToStdString() + "." + child_name;
+ ParseNestedValueArgs(value_it->as_bytes(),
+ base::StringView(child_flat_key),
+ base::StringView(child_key), args_tracker, row);
+ }
+ break;
+ }
+ case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
+ int child_index = 0;
+ std::string child_flat_key = flat_key.ToStdString();
+ for (auto value_it = value.array_values(); value_it;
+ ++value_it, ++child_index) {
+ std::string child_key =
+ key.ToStdString() + "[" + std::to_string(child_index) + "]";
+ ParseNestedValueArgs(value_it->as_bytes(),
+ base::StringView(child_flat_key),
+ base::StringView(child_key), args_tracker, row);
+ }
+ break;
+ }
+ }
+}
+
+void ProtoTraceParser::ParseTaskExecutionArgs(
+ ConstBytes task_execution,
+ ProtoIncrementalState::PacketSequenceState* sequence_state,
+ ArgsTracker* args_tracker,
+ RowId row) {
+ protos::pbzero::TaskExecution::Decoder task(task_execution.data,
+ task_execution.size);
+ uint32_t iid = task.posted_from_iid();
+ if (!iid)
+ return;
+
+ auto* map =
+ sequence_state->GetInternedDataMap<protos::pbzero::SourceLocation>();
+ auto location_view_it = map->find(iid);
+ if (location_view_it == map->end()) {
+ PERFETTO_ELOG("Could not find source location interning entry for ID %u",
+ iid);
+ return;
+ }
+
+ StringId file_name_id = 0;
+ StringId function_name_id = 0;
+
+ // If the names are already in the pool, no need to decode them again.
+ if (location_view_it->second.storage_refs) {
+ file_name_id = location_view_it->second.storage_refs->file_name_id;
+ function_name_id = location_view_it->second.storage_refs->function_name_id;
+ } else {
+ TraceStorage* storage = context_->storage.get();
+ auto location = location_view_it->second.CreateDecoder();
+ file_name_id = storage->InternString(location.file_name());
+ function_name_id = storage->InternString(location.function_name());
+ // Avoid having to decode & look up the names again in the future.
+ location_view_it->second.storage_refs =
+ ProtoIncrementalState::StorageReferences<
+ protos::pbzero::SourceLocation>{file_name_id, function_name_id};
+ }
+
+ args_tracker->AddArg(row, task_file_name_args_key_id_,
+ task_file_name_args_key_id_,
+ Variadic::String(file_name_id));
+ args_tracker->AddArg(row, task_function_name_args_key_id_,
+ task_function_name_args_key_id_,
+ Variadic::String(function_name_id));
+}
+
void ProtoTraceParser::ParseChromeBenchmarkMetadata(ConstBytes blob) {
TraceStorage* storage = context_->storage.get();
protos::pbzero::ChromeBenchmarkMetadata::Decoder packet(blob.data, blob.size);
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index a66573d..0fbe329 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -33,6 +33,7 @@
namespace perfetto {
namespace trace_processor {
+class ArgsTracker;
class TraceProcessorContext;
class ProtoTraceParser : public TraceParser {
@@ -92,6 +93,21 @@
int64_t tts,
ProtoIncrementalState::PacketSequenceState*,
ConstBytes);
+ void ParseDebugAnnotationArgs(
+ ConstBytes debug_annotation,
+ ProtoIncrementalState::PacketSequenceState* sequence_state,
+ ArgsTracker* args_tracker,
+ RowId row);
+ void ParseNestedValueArgs(ConstBytes nested_value,
+ base::StringView flat_key,
+ base::StringView key,
+ ArgsTracker* args_tracker,
+ RowId row);
+ void ParseTaskExecutionArgs(
+ ConstBytes task_execution,
+ ProtoIncrementalState::PacketSequenceState* sequence_state,
+ ArgsTracker* args_tracker,
+ RowId row);
void ParseChromeBenchmarkMetadata(ConstBytes);
private:
@@ -123,6 +139,8 @@
const StringId oom_score_adj_id_;
const StringId ion_total_unknown_id_;
const StringId ion_change_unknown_id_;
+ const StringId task_file_name_args_key_id_;
+ const StringId task_function_name_args_key_id_;
std::vector<StringId> meminfo_strs_id_;
std::vector<StringId> vmstat_strs_id_;
std::vector<StringId> rss_members_;
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 6107f4f..a40868d 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -104,6 +104,7 @@
MockTraceStorage() : TraceStorage() {}
MOCK_METHOD1(InternString, StringId(base::StringView));
+ MOCK_CONST_METHOD1(GetString, NullTermStringView(StringId));
MOCK_METHOD2(SetMetadata, void(size_t, Variadic));
MOCK_METHOD2(AppendMetadata, void(size_t, Variadic));
};
@@ -147,8 +148,7 @@
ProtoTraceParserTest() {
nice_storage_ = new NiceMock<MockTraceStorage>();
context_.storage.reset(nice_storage_);
- args_ = new MockArgsTracker(&context_);
- context_.args_tracker.reset(args_);
+ context_.args_tracker.reset(new ArgsTracker(&context_));
event_ = new MockEventTracker(&context_);
context_.event_tracker.reset(event_);
process_ = new MockProcessTracker(&context_);
@@ -189,7 +189,6 @@
std::unique_ptr<protozero::ScatteredStreamWriter> stream_writer_;
protos::pbzero::Trace trace_;
TraceProcessorContext context_;
- MockArgsTracker* args_;
MockEventTracker* event_;
MockProcessTracker* process_;
MockSliceTracker* slice_;
@@ -976,6 +975,264 @@
context_.sorter->ExtractEventsForced();
}
+TEST_F(ProtoTraceParserTest, TrackEventWithDebugAnnotations) {
+ InitStorage();
+ context_.sorter.reset(new TraceSorter(
+ &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+ MockArgsTracker args(&context_);
+
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ packet->set_incremental_state_cleared(true);
+ auto* thread_desc = packet->set_thread_descriptor();
+ thread_desc->set_pid(15);
+ thread_desc->set_tid(16);
+ thread_desc->set_reference_timestamp_us(1000);
+ thread_desc->set_reference_thread_time_us(2000);
+ }
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ auto* event = packet->set_track_event();
+ event->set_timestamp_delta_us(10); // absolute: 1010.
+ event->set_thread_time_delta_us(5); // absolute: 2005.
+ event->add_category_iids(1);
+ auto* annotation1 = event->add_debug_annotations();
+ annotation1->set_name_iid(1);
+ annotation1->set_uint_value(10u);
+ auto* annotation2 = event->add_debug_annotations();
+ annotation2->set_name_iid(2);
+ auto* nested = annotation2->set_nested_value();
+ nested->set_nested_type(protos::pbzero::DebugAnnotation::NestedValue::DICT);
+ nested->add_dict_keys("child1");
+ nested->add_dict_keys("child2");
+ auto* child1 = nested->add_dict_values();
+ child1->set_nested_type(
+ protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+ child1->set_bool_value(true);
+ auto* child2 = nested->add_dict_values();
+ child2->set_nested_type(
+ protos::pbzero::DebugAnnotation::NestedValue::ARRAY);
+ auto* child21 = child2->add_array_values();
+ child21->set_nested_type(
+ protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+ child21->set_string_value("child21");
+ auto* child22 = child2->add_array_values();
+ child22->set_nested_type(
+ protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+ child22->set_double_value(2.2);
+ auto* child23 = child2->add_array_values();
+ child23->set_nested_type(
+ protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+ child23->set_int_value(23);
+ auto* legacy_event = event->set_legacy_event();
+ legacy_event->set_name_iid(1);
+ legacy_event->set_phase('B');
+
+ 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_legacy_event_names();
+ ev1->set_iid(1);
+ ev1->set_name("ev1");
+ auto an1 = interned_data->add_debug_annotation_names();
+ an1->set_iid(1);
+ an1->set_name("an1");
+ auto an2 = interned_data->add_debug_annotation_names();
+ an2->set_iid(2);
+ an2->set_name("an2");
+ }
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ auto* event = packet->set_track_event();
+ event->set_timestamp_delta_us(10); // absolute: 1020.
+ event->set_thread_time_delta_us(5); // absolute: 2010.
+ event->add_category_iids(1);
+ auto* annotation3 = event->add_debug_annotations();
+ annotation3->set_name_iid(3);
+ annotation3->set_int_value(-3);
+ auto* annotation4 = event->add_debug_annotations();
+ annotation4->set_name_iid(4);
+ annotation4->set_bool_value(true);
+ auto* annotation5 = event->add_debug_annotations();
+ annotation5->set_name_iid(5);
+ annotation5->set_double_value(-5.5);
+ auto* annotation6 = event->add_debug_annotations();
+ annotation6->set_name_iid(6);
+ annotation6->set_pointer_value(20u);
+ auto* annotation7 = event->add_debug_annotations();
+ annotation7->set_name_iid(7);
+ annotation7->set_string_value("val7");
+ auto* annotation8 = event->add_debug_annotations();
+ annotation8->set_name_iid(8);
+ annotation8->set_legacy_json_value("val8");
+ auto* legacy_event = event->set_legacy_event();
+ legacy_event->set_name_iid(1);
+ legacy_event->set_phase('E');
+
+ auto* interned_data = packet->set_interned_data();
+ auto an3 = interned_data->add_debug_annotation_names();
+ an3->set_iid(3);
+ an3->set_name("an3");
+ auto an4 = interned_data->add_debug_annotation_names();
+ an4->set_iid(4);
+ an4->set_name("an4");
+ auto an5 = interned_data->add_debug_annotation_names();
+ an5->set_iid(5);
+ an5->set_name("an5");
+ auto an6 = interned_data->add_debug_annotation_names();
+ an6->set_iid(6);
+ an6->set_name("an6");
+ auto an7 = interned_data->add_debug_annotation_names();
+ an7->set_iid(7);
+ an7->set_name("an7");
+ auto an8 = interned_data->add_debug_annotation_names();
+ an8->set_iid(8);
+ an8->set_name("an8");
+ }
+
+ Tokenize();
+
+ EXPECT_CALL(*process_, UpdateThread(16, 15))
+ .Times(2)
+ .WillRepeatedly(Return(1));
+
+ 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, 2, _))
+ .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an1")))
+ .WillOnce(Return(3));
+ EXPECT_CALL(args, AddArg(1u, 3, 3, Variadic::UnsignedInteger(10u)));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2")))
+ .WillOnce(Return(4));
+ EXPECT_CALL(*storage_, GetString(4)).WillOnce(Return("debug.an2"));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child1")))
+ .Times(2)
+ .WillRepeatedly(Return(5));
+ EXPECT_CALL(args, AddArg(1u, 5, 5, Variadic::Boolean(true)));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+ .WillOnce(Return(6));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[0]")))
+ .WillOnce(Return(7));
+ EXPECT_CALL(*storage_, InternString(base::StringView("child21")))
+ .WillOnce(Return(8));
+ EXPECT_CALL(args, AddArg(1u, 6, 7, Variadic::String(8)));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+ .WillOnce(Return(6));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[1]")))
+ .WillOnce(Return(9));
+ EXPECT_CALL(args, AddArg(1u, 6, 9, Variadic::Real(2.2)));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+ .WillOnce(Return(6));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[2]")))
+ .WillOnce(Return(10));
+ EXPECT_CALL(args, AddArg(1u, 6, 10, Variadic::Integer(23)));
+
+ EXPECT_CALL(*slice_, End(1020000, 1, 1, 2, _))
+ .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an3")))
+ .WillOnce(Return(11));
+ EXPECT_CALL(args, AddArg(1u, 11, 11, Variadic::Integer(-3)));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an4")))
+ .WillOnce(Return(12));
+ EXPECT_CALL(args, AddArg(1u, 12, 12, Variadic::Boolean(true)));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an5")))
+ .WillOnce(Return(13));
+ EXPECT_CALL(args, AddArg(1u, 13, 13, Variadic::Real(-5.5)));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an6")))
+ .WillOnce(Return(14));
+ EXPECT_CALL(args, AddArg(1u, 14, 14, Variadic::Pointer(20u)));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an7")))
+ .WillOnce(Return(15));
+ EXPECT_CALL(*storage_, InternString(base::StringView("val7")))
+ .WillOnce(Return(16));
+ EXPECT_CALL(args, AddArg(1u, 15, 15, Variadic::String(16)));
+ EXPECT_CALL(*storage_, InternString(base::StringView("debug.an8")))
+ .WillOnce(Return(17));
+ EXPECT_CALL(*storage_, InternString(base::StringView("val8")))
+ .WillOnce(Return(18));
+ EXPECT_CALL(args, AddArg(1u, 17, 17, Variadic::String(18)));
+
+ context_.sorter->ExtractEventsForced();
+}
+
+TEST_F(ProtoTraceParserTest, TrackEventWithTaskExecution) {
+ InitStorage();
+ context_.sorter.reset(new TraceSorter(
+ &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+ MockArgsTracker args(&context_);
+
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ packet->set_incremental_state_cleared(true);
+ auto* thread_desc = packet->set_thread_descriptor();
+ thread_desc->set_pid(15);
+ thread_desc->set_tid(16);
+ thread_desc->set_reference_timestamp_us(1000);
+ thread_desc->set_reference_thread_time_us(2000);
+ }
+ {
+ auto* packet = trace_.add_packet();
+ packet->set_trusted_packet_sequence_id(1);
+ auto* event = packet->set_track_event();
+ event->set_timestamp_delta_us(10); // absolute: 1010.
+ event->set_thread_time_delta_us(5); // absolute: 2005.
+ event->add_category_iids(1);
+ auto* task_execution = event->set_task_execution();
+ task_execution->set_posted_from_iid(1);
+ auto* legacy_event = event->set_legacy_event();
+ legacy_event->set_name_iid(1);
+ legacy_event->set_phase('B');
+
+ 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_legacy_event_names();
+ ev1->set_iid(1);
+ ev1->set_name("ev1");
+ auto loc1 = interned_data->add_source_locations();
+ loc1->set_iid(1);
+ loc1->set_file_name("file1");
+ loc1->set_function_name("func1");
+ }
+
+ Tokenize();
+
+ EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+
+ 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, 2, _))
+ .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+ EXPECT_CALL(*storage_, InternString(base::StringView("file1")))
+ .WillOnce(Return(3));
+ EXPECT_CALL(*storage_, InternString(base::StringView("func1")))
+ .WillOnce(Return(4));
+ EXPECT_CALL(args, AddArg(1u, _, _, Variadic::String(3)));
+ EXPECT_CALL(args, AddArg(1u, _, _, Variadic::String(4)));
+
+ context_.sorter->ExtractEventsForced();
+}
+
TEST_F(ProtoTraceParserTest, LoadChromeBenchmarkMetadata) {
static const char kName[] = "name";
static const char kTag2[] = "tag1";
diff --git a/src/trace_processor/raw_table.cc b/src/trace_processor/raw_table.cc
index 1f9196e..d7dcffe 100644
--- a/src/trace_processor/raw_table.cc
+++ b/src/trace_processor/raw_table.cc
@@ -90,12 +90,27 @@
case Variadic::kInt:
writer->AppendInt(value.int_value);
break;
- case Variadic::kReal:
- writer->AppendDouble(value.real_value);
+ case Variadic::kUint:
+ writer->AppendUnsignedInt(value.uint_value);
break;
case Variadic::kString: {
const auto& str = storage_->GetString(value.string_value);
writer->AppendString(str.c_str(), str.size());
+ break;
+ }
+ case Variadic::kReal:
+ writer->AppendDouble(value.real_value);
+ break;
+ case Variadic::kPointer:
+ writer->AppendUnsignedInt(value.pointer_value);
+ break;
+ case Variadic::kBool:
+ writer->AppendBool(value.bool_value);
+ break;
+ case Variadic::kJson: {
+ const auto& str = storage_->GetString(value.json_value);
+ writer->AppendString(str.c_str(), str.size());
+ break;
}
}
};
diff --git a/src/trace_processor/sqlite_utils.h b/src/trace_processor/sqlite_utils.h
index 60d095f..64ad4fb 100644
--- a/src/trace_processor/sqlite_utils.h
+++ b/src/trace_processor/sqlite_utils.h
@@ -132,6 +132,13 @@
return sqlite3_value_double(value);
}
+template <>
+inline bool ExtractSqliteValue(sqlite3_value* value) {
+ auto type = sqlite3_value_type(value);
+ PERFETTO_DCHECK(type == SQLITE_INTEGER);
+ return static_cast<bool>(sqlite3_value_int(value));
+}
+
// Do not add a uint64_t version of ExtractSqliteValue. You should not be using
// uint64_t at all given that SQLite doesn't support it.
@@ -362,6 +369,11 @@
}
template <>
+inline void ReportSqliteResult(sqlite3_context* ctx, bool value) {
+ sqlite3_result_int(ctx, value);
+}
+
+template <>
inline void ReportSqliteResult(sqlite3_context* ctx, double value) {
sqlite3_result_double(ctx, value);
}
@@ -430,6 +442,8 @@
type = Table::ColumnType::kString;
} else if (strcmp(raw_type, "DOUBLE") == 0) {
type = Table::ColumnType::kDouble;
+ } else if (strcmp(raw_type, "BOOLEAN") == 0) {
+ type = Table::ColumnType::kBool;
} else if (!*raw_type) {
PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
name);
diff --git a/src/trace_processor/table.cc b/src/trace_processor/table.cc
index 1218b5a..1110d6f 100644
--- a/src/trace_processor/table.cc
+++ b/src/trace_processor/table.cc
@@ -39,6 +39,8 @@
return "INT";
case Table::ColumnType::kDouble:
return "DOUBLE";
+ case Table::ColumnType::kBool:
+ return "BOOLEAN";
case Table::ColumnType::kUnknown:
PERFETTO_FATAL("Cannot map unknown column type");
}
diff --git a/src/trace_processor/table.h b/src/trace_processor/table.h
index 0c8e638..c89f002 100644
--- a/src/trace_processor/table.h
+++ b/src/trace_processor/table.h
@@ -49,7 +49,8 @@
kLong = 3,
kInt = 4,
kDouble = 5,
- kUnknown = 6,
+ kBool = 6,
+ kUnknown = 7,
};
// Describes a column of this table.
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 45ffed3..de4dfa5 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -131,12 +131,24 @@
case Variadic::Type::kInt:
hash.Update(arg.value.int_value);
break;
+ case Variadic::Type::kUint:
+ hash.Update(arg.value.uint_value);
+ break;
case Variadic::Type::kString:
hash.Update(arg.value.string_value);
break;
case Variadic::Type::kReal:
hash.Update(arg.value.real_value);
break;
+ case Variadic::Type::kPointer:
+ hash.Update(arg.value.pointer_value);
+ break;
+ case Variadic::Type::kBool:
+ hash.Update(arg.value.bool_value);
+ break;
+ case Variadic::Type::kJson:
+ hash.Update(arg.value.json_value);
+ break;
}
return hash.digest();
}
@@ -832,7 +844,8 @@
}
// Reading methods.
- NullTermStringView GetString(StringId id) const {
+ // Virtual for testing.
+ virtual NullTermStringView GetString(StringId id) const {
return string_pool_.Get(id);
}
diff --git a/src/trace_processor/variadic.h b/src/trace_processor/variadic.h
index 761aea3..6df011e 100644
--- a/src/trace_processor/variadic.h
+++ b/src/trace_processor/variadic.h
@@ -24,7 +24,7 @@
// Variadic type representing value of different possible types.
struct Variadic {
- enum Type { kInt, kString, kReal };
+ enum Type { kInt, kUint, kString, kReal, kPointer, kBool, kJson };
static Variadic Integer(int64_t int_value) {
Variadic variadic;
@@ -33,6 +33,17 @@
return variadic;
}
+ // BEWARE: Unsigned 64-bit integers will be handled as signed integers by
+ // SQLite for built-in SQL operators. This variadic type is used to
+ // distinguish between int64 and uint64 for correct JSON export of TrackEvent
+ // arguments.
+ static Variadic UnsignedInteger(uint64_t uint_value) {
+ Variadic variadic;
+ variadic.type = Type::kUint;
+ variadic.uint_value = uint_value;
+ return variadic;
+ }
+
static Variadic String(StringPool::Id string_id) {
Variadic variadic;
variadic.type = Type::kString;
@@ -47,16 +58,49 @@
return variadic;
}
+ // This variadic type is used to distinguish between integers and pointer
+ // values for correct JSON export of TrackEvent arguments.
+ static Variadic Pointer(uint64_t pointer_value) {
+ Variadic variadic;
+ variadic.type = Type::kPointer;
+ variadic.pointer_value = pointer_value;
+ return variadic;
+ }
+
+ static Variadic Boolean(bool bool_value) {
+ Variadic variadic;
+ variadic.type = Type::kBool;
+ variadic.bool_value = bool_value;
+ return variadic;
+ }
+
+ // This variadic type is used to distinguish between regular string and JSON
+ // string values for correct JSON export of TrackEvent arguments.
+ static Variadic Json(StringPool::Id json_value) {
+ Variadic variadic;
+ variadic.type = Type::kJson;
+ variadic.json_value = json_value;
+ return variadic;
+ }
+
// Used in tests.
bool operator==(const Variadic& other) const {
if (type == other.type) {
switch (type) {
case kInt:
return int_value == other.int_value;
+ case kUint:
+ return uint_value == other.uint_value;
case kString:
return string_value == other.string_value;
case kReal:
return std::equal_to<double>()(real_value, other.real_value);
+ case kPointer:
+ return pointer_value == other.pointer_value;
+ case kBool:
+ return bool_value == other.bool_value;
+ case kJson:
+ return json_value == other.json_value;
}
}
return false;
@@ -65,8 +109,12 @@
Type type;
union {
int64_t int_value;
+ uint64_t uint_value;
StringPool::Id string_value;
double real_value;
+ uint64_t pointer_value;
+ bool bool_value;
+ StringPool::Id json_value;
};
};
diff --git a/test/android_client_api_example.cc b/test/android_client_api_example.cc
new file mode 100644
index 0000000..459dd5f
--- /dev/null
+++ b/test/android_client_api_example.cc
@@ -0,0 +1,60 @@
+/*
+ * 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 "perfetto/tracing.h"
+
+#include "perfetto/trace/test_event.pbzero.h"
+#include "perfetto/trace/trace.pb.h"
+#include "perfetto/trace/trace_packet.pbzero.h"
+
+// Deliberately not pulling any non-public perfetto header to spot accidental
+// header public -> non-public dependency while building this file.
+
+class MyDataSource : public perfetto::DataSource<MyDataSource> {
+ public:
+ void OnSetup(const SetupArgs& args) override {
+ // This can be used to access the domain-specific DataSourceConfig, via
+ // args.config->xxx_config_raw().
+ PERFETTO_ILOG("OnSetup called, name: %s", args.config->name().c_str());
+ }
+
+ void OnStart(const StartArgs&) override { PERFETTO_ILOG("OnStart called"); }
+
+ void OnStop(const StopArgs&) override { PERFETTO_ILOG("OnStop called"); }
+};
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MyDataSource);
+
+int main() {
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kSystemBackend;
+ perfetto::Tracing::Initialize(args);
+
+ // DataSourceDescriptor can be used to advertise domain-specific features.
+ perfetto::DataSourceDescriptor dsd;
+ dsd.set_name("com.example.mytrace");
+ MyDataSource::Register(dsd);
+
+ for (;;) {
+ MyDataSource::Trace([](MyDataSource::TraceContext ctx) {
+ PERFETTO_LOG("Tracing lambda called");
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp(42);
+ packet->set_for_testing()->set_str("event 1");
+ });
+ sleep(1);
+ }
+}
diff --git a/tools/check_include_violations b/tools/check_include_violations
index 5e05b86..82e36ab 100755
--- a/tools/check_include_violations
+++ b/tools/check_include_violations
@@ -26,16 +26,6 @@
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-# TODO(primiano): fix violation from protozero -> base/logging.h .
-WHITELIST = [('include/perfetto/protozero/.*', 'perfetto/ext/base/logging.h')]
-
-
-def whitelisted(rel_path, incl):
- for path_regex, incl_regex in WHITELIST:
- if re.match(path_regex, rel_path) and re.match(incl_regex, incl):
- return True
- return False
-
def main():
errors = 0
@@ -69,10 +59,13 @@
errors += 1
continue
+ # Ignore lines marked with nogncheck.
+ if '// nogncheck' in line:
+ continue
+
# Public (non-/ext/) headers cannot include /ext/ headers.
if (not rel_path.startswith('include/perfetto/ext/') and
- incl.startswith('perfetto/ext/') and
- not whitelisted(rel_path, incl)):
+ incl.startswith('perfetto/ext/')):
sys.stderr.write(('Public header %s cannot include the non-public' +
'/ext/ header %s.\n') % (rel_path, incl))
errors += 1
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index f34f55a..45b7ff3 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -36,6 +36,7 @@
# Default targets to translate to the blueprint file.
default_targets = [
'//:libperfetto',
+ '//:libperfetto_client_experimental',
'//:libperfetto_android_internal',
'//:perfetto_integrationtests',
'//:perfetto_trace_protos',
@@ -47,6 +48,7 @@
'//:heapprofd_client',
'//:heapprofd',
'//:trigger_perfetto',
+ '//:idle_alloc',
]
# Defines a custom init_rc argument to be applied to the corresponding output
diff --git a/tools/heap_profile b/tools/heap_profile
index 7f9395a..40debf1 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -30,8 +30,8 @@
import urllib
TRACE_TO_TEXT_SHAS = {
- 'linux': 'a8171d85c5964ccafe457142dbb7df68ca8da543',
- 'mac': '268c2fc096039566979d16c1a7a99eabef0d9682',
+ 'linux': '2ef44a2ce2f9cfdccd3a21c80299fa6c0e6b4e38',
+ 'mac': '32f29cf000e1e4638f8b96c7d004586a74a72f72',
}
TRACE_TO_TEXT_PATH = tempfile.gettempdir()
TRACE_TO_TEXT_BASE_URL = (
diff --git a/tools/idle_alloc.cc b/tools/idle_alloc.cc
new file mode 100644
index 0000000..1a1dd5a
--- /dev/null
+++ b/tools/idle_alloc.cc
@@ -0,0 +1,228 @@
+/*
+ * 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 <memory>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace {
+
+constexpr auto kIdleSize = 10000 * 4096;
+constexpr auto kNoIdleSize = 1000 * 4096;
+
+static bool interrupted = false;
+
+volatile unsigned char* __attribute__((noinline)) AllocIdle(size_t bytes);
+volatile unsigned char* __attribute__((noinline)) AllocIdle(size_t bytes) {
+ // This volatile is needed to prevent the compiler from trying to be
+ // helpful and compiling a "useless" malloc + free into a noop.
+ volatile unsigned char* x = static_cast<unsigned char*>(malloc(bytes));
+ if (x) {
+ x[1] = 'x';
+ }
+ return x;
+}
+
+volatile unsigned char* __attribute__((noinline)) AllocNoIdle(size_t bytes);
+volatile unsigned char* __attribute__((noinline)) AllocNoIdle(size_t bytes) {
+ // This volatile is needed to prevent the compiler from trying to be
+ // helpful and compiling a "useless" malloc + free into a noop.
+ volatile unsigned char* x = static_cast<unsigned char*>(malloc(bytes));
+ if (x) {
+ x[0] = 'x';
+ }
+ return x;
+}
+
+class MemoryToucher {
+ public:
+ virtual void Touch(volatile unsigned char* nonidle) = 0;
+ virtual ~MemoryToucher() = default;
+};
+
+class ReadDevZeroChunks : public MemoryToucher {
+ public:
+ ReadDevZeroChunks(size_t chunk_size)
+ : chunk_size_(chunk_size), fd_(open("/dev/zero", O_RDONLY)) {
+ if (fd_ == -1) {
+ fprintf(stderr, "Failed to open: %s", strerror(errno));
+ abort();
+ }
+ }
+
+ ~ReadDevZeroChunks() override = default;
+
+ void Touch(volatile unsigned char* nonidle) override {
+ size_t total_rd = 0;
+ while (total_rd < kNoIdleSize) {
+ size_t chunk = chunk_size_;
+ if (chunk > kNoIdleSize - total_rd)
+ chunk = kNoIdleSize - total_rd;
+
+ ssize_t rd =
+ read(fd_, const_cast<unsigned char*>(nonidle) + total_rd, chunk);
+ if (rd == -1) {
+ fprintf(stderr, "Failed to write: %s.", strerror(errno));
+ abort();
+ }
+ total_rd += static_cast<size_t>(rd);
+ }
+ }
+
+ private:
+ size_t chunk_size_;
+ int fd_;
+};
+
+class ReadDevZeroChunksAndSleep : public ReadDevZeroChunks {
+ public:
+ ReadDevZeroChunksAndSleep(size_t chunk_size)
+ : ReadDevZeroChunks(chunk_size) {}
+ void Touch(volatile unsigned char* nonidle) override {
+ ReadDevZeroChunks::Touch(nonidle);
+ sleep(1);
+ }
+};
+
+class SumUp : public MemoryToucher {
+ public:
+ SumUp()
+ : sum_(const_cast<volatile uint64_t*>(
+ static_cast<uint64_t*>(malloc(sizeof(uint64_t))))) {}
+ ~SumUp() override = default;
+
+ void Touch(volatile unsigned char* nonidle) override {
+ for (size_t i = 0; i < kNoIdleSize; ++i)
+ *sum_ += nonidle[i];
+ }
+
+ private:
+ volatile uint64_t* sum_;
+};
+
+class ReadDevZeroChunksAndSum : public ReadDevZeroChunks {
+ public:
+ ReadDevZeroChunksAndSum(size_t chunk_size) : ReadDevZeroChunks(chunk_size) {}
+ void Touch(volatile unsigned char* nonidle) override {
+ ReadDevZeroChunks::Touch(nonidle);
+ sum_up_.Touch(nonidle);
+ }
+
+ private:
+ SumUp sum_up_;
+};
+
+class AssignValues : public MemoryToucher {
+ public:
+ ~AssignValues() override = default;
+
+ void Touch(volatile unsigned char* nonidle) override {
+ for (size_t i = 0; i < kNoIdleSize; ++i)
+ nonidle[i] = static_cast<unsigned char>(i % 256);
+ }
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+ volatile auto* idle = AllocIdle(kIdleSize);
+ volatile auto* nonidle = AllocNoIdle(kNoIdleSize);
+
+ printf("Own PID: %" PRIdMAX "\n", static_cast<intmax_t>(getpid()));
+ printf("Idle: %p\n", static_cast<void*>(const_cast<unsigned char*>(idle)));
+ printf("Nonidle: %p\n",
+ static_cast<void*>(const_cast<unsigned char*>(nonidle)));
+
+ for (size_t i = 0; i < kIdleSize; ++i)
+ idle[i] = static_cast<unsigned char>(i % 256);
+ for (size_t i = 0; i < kNoIdleSize; ++i)
+ nonidle[i] = static_cast<unsigned char>(i % 256);
+
+ printf("Allocated everything.\n");
+
+ struct sigaction action = {};
+ action.sa_handler = [](int) { interrupted = true; };
+ if (sigaction(SIGUSR1, &action, nullptr) != 0) {
+ fprintf(stderr, "Failed to register signal handler.\n");
+ abort();
+ }
+
+ if (argc < 2) {
+ fprintf(stderr,
+ "Specifiy one of AssignValues / SumUp / ReadDevZeroChunks\n");
+ abort();
+ }
+
+ std::unique_ptr<MemoryToucher> toucher;
+ if (strcmp(argv[1], "AssignValues") == 0) {
+ toucher.reset(new AssignValues());
+ printf("Using AssignValues.\n");
+ } else if (strcmp(argv[1], "SumUp") == 0) {
+ toucher.reset(new SumUp());
+ printf("Using SumUp.\n");
+ } else if (strcmp(argv[1], "ReadDevZeroChunks") == 0 ||
+ strcmp(argv[1], "ReadDevZeroChunksAndSleep") == 0 ||
+ strcmp(argv[1], "ReadDevZeroChunksAndSum") == 0) {
+ if (argc < 3) {
+ fprintf(stderr, "Specify chunk size.\n");
+ abort();
+ }
+ char* end;
+ long long chunk_arg = strtoll(argv[2], &end, 10);
+ if (*end != '\0' || *argv[2] == '\0') {
+ fprintf(stderr, "Invalid chunk size: %s\n", argv[2]);
+ abort();
+ }
+ if (strcmp(argv[1], "ReadDevZeroChunksAndSleep") == 0) {
+ printf("Using ReadDevZeroChunksAndSleep.\n");
+ toucher.reset(
+ new ReadDevZeroChunksAndSleep(static_cast<size_t>(chunk_arg)));
+ } else if (strcmp(argv[1], "ReadDevZeroChunksAndSum") == 0) {
+ printf("Using ReadDevZeroChunksAndSum.\n");
+ toucher.reset(
+ new ReadDevZeroChunksAndSum(static_cast<size_t>(chunk_arg)));
+ } else {
+ printf("Using ReadDevZeroChunks.\n");
+ toucher.reset(new ReadDevZeroChunks(static_cast<size_t>(chunk_arg)));
+ }
+ } else {
+ fprintf(stderr, "Invalid input.\n");
+ abort();
+ }
+
+ while (true) {
+ bool report = interrupted;
+ if (report) {
+ printf("Waiting to finish touching everything.\n");
+ interrupted = false;
+ sleep(2);
+ }
+
+ toucher->Touch(nonidle);
+
+ if (report)
+ printf("Touched everything.\n");
+ }
+}