Merge "trace writer: Reset last_packet_size_field_ on flush."
diff --git a/Android.bp b/Android.bp
index 4c7c85c..f89a890 100644
--- a/Android.bp
+++ b/Android.bp
@@ -828,6 +828,7 @@
"test/cts/end_to_end_integrationtest_cts.cc",
"test/cts/heapprofd_java_test_cts.cc",
"test/cts/heapprofd_test_cts.cc",
+ "test/cts/traced_perf_test_cts.cc",
"test/cts/utils.cc",
],
static_libs: [
@@ -835,6 +836,9 @@
"libgtest",
"libperfetto_client_experimental",
],
+ whole_static_libs: [
+ "perfetto_gtest_logcat_printer",
+ ],
export_include_dirs: [
"include",
"include/perfetto/base/build_configs/android_tree",
@@ -1209,6 +1213,25 @@
},
}
+// GN: //test:perfetto_gtest_logcat_printer
+cc_library_static {
+ name: "perfetto_gtest_logcat_printer",
+ srcs: [
+ "test/gtest_logcat_printer.cc",
+ ],
+ static_libs: [
+ "libgmock",
+ "libgtest",
+ ],
+ export_include_dirs: [
+ "include",
+ "include/perfetto/base/build_configs/android_tree",
+ ],
+ defaults: [
+ "perfetto_defaults",
+ ],
+}
+
// GN: //include/perfetto/base:base
filegroup {
name: "perfetto_include_perfetto_base_base",
@@ -1429,6 +1452,9 @@
"libgtest",
"libperfetto_client_experimental",
],
+ whole_static_libs: [
+ "perfetto_gtest_logcat_printer",
+ ],
generated_headers: [
"perfetto_protos_perfetto_common_cpp_gen_headers",
"perfetto_protos_perfetto_common_zero_gen_headers",
@@ -6193,10 +6219,10 @@
filegroup {
name: "perfetto_src_trace_processor_lib",
srcs: [
+ "src/trace_processor/experimental_counter_dur_generator.cc",
+ "src/trace_processor/experimental_flamegraph_generator.cc",
"src/trace_processor/read_trace.cc",
"src/trace_processor/sql_stats_table.cc",
- "src/trace_processor/sqlite_experimental_counter_dur_table.cc",
- "src/trace_processor/sqlite_experimental_flamegraph_table.cc",
"src/trace_processor/sqlite_raw_table.cc",
"src/trace_processor/stats_table.cc",
"src/trace_processor/trace_processor.cc",
@@ -6360,6 +6386,7 @@
srcs: [
"src/trace_processor/clock_tracker_unittest.cc",
"src/trace_processor/event_tracker_unittest.cc",
+ "src/trace_processor/experimental_counter_dur_generator_unittest.cc",
"src/trace_processor/forwarding_trace_parser_unittest.cc",
"src/trace_processor/ftrace_utils_unittest.cc",
"src/trace_processor/heap_profile_tracker_unittest.cc",
@@ -6372,7 +6399,6 @@
"src/trace_processor/process_tracker_unittest.cc",
"src/trace_processor/protozero_to_text_unittests.cc",
"src/trace_processor/slice_tracker_unittest.cc",
- "src/trace_processor/sqlite_experimental_counter_dur_table_unittest.cc",
"src/trace_processor/syscall_tracker_unittest.cc",
"src/trace_processor/trace_sorter_unittest.cc",
],
@@ -7257,6 +7283,9 @@
"libgmock",
"libgtest",
],
+ whole_static_libs: [
+ "perfetto_gtest_logcat_printer",
+ ],
generated_headers: [
"gen_merged_sql_metrics",
"perfetto_protos_perfetto_common_cpp_gen_headers",
@@ -7685,6 +7714,7 @@
":perfetto_src_protozero_protozero",
":perfetto_src_tracing_common",
":perfetto_src_tracing_core_core",
+ ":perfetto_src_tracing_core_service",
":perfetto_src_tracing_ipc_common",
":perfetto_src_tracing_ipc_producer_producer",
"src/profiling/perf/main.cc",
diff --git a/BUILD b/BUILD
index 427b2c1..0f6e5b9 100644
--- a/BUILD
+++ b/BUILD
@@ -845,13 +845,13 @@
filegroup(
name = "src_trace_processor_lib",
srcs = [
+ "src/trace_processor/experimental_counter_dur_generator.cc",
+ "src/trace_processor/experimental_counter_dur_generator.h",
+ "src/trace_processor/experimental_flamegraph_generator.cc",
+ "src/trace_processor/experimental_flamegraph_generator.h",
"src/trace_processor/read_trace.cc",
"src/trace_processor/sql_stats_table.cc",
"src/trace_processor/sql_stats_table.h",
- "src/trace_processor/sqlite_experimental_counter_dur_table.cc",
- "src/trace_processor/sqlite_experimental_counter_dur_table.h",
- "src/trace_processor/sqlite_experimental_flamegraph_table.cc",
- "src/trace_processor/sqlite_experimental_flamegraph_table.h",
"src/trace_processor/sqlite_raw_table.cc",
"src/trace_processor/sqlite_raw_table.h",
"src/trace_processor/stats_table.cc",
diff --git a/gn/perfetto_benchmarks.gni b/gn/perfetto_benchmarks.gni
index 2abd944..b70999a 100644
--- a/gn/perfetto_benchmarks.gni
+++ b/gn/perfetto_benchmarks.gni
@@ -17,11 +17,12 @@
perfetto_benchmarks_targets = [
"gn:default_deps",
"src/base:benchmarks",
- "src/traced/probes/ftrace:benchmarks",
"src/trace_processor/containers:benchmarks",
"src/trace_processor/tables:benchmarks",
- "src/tracing/core:benchmarks",
"src/traced/probes/ftrace/kallsyms:benchmarks",
+ "src/traced/probes/ftrace:benchmarks",
+ "src/tracing/core:benchmarks",
+ "src/tracing:benchmarks",
"test:benchmark_main",
"test:end_to_end_benchmarks",
]
diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h
index d40fb5b..8b4ccda 100644
--- a/include/perfetto/base/compiler.h
+++ b/include/perfetto/base/compiler.h
@@ -51,7 +51,7 @@
#define PERFETTO_PRINTF_FORMAT(x, y) \
__attribute__((__format__(__printf__, x, y)))
#else
-#defien PERFETTO_PRINTF_FORMAT(x, y)
+#define PERFETTO_PRINTF_FORMAT(x, y)
#endif
namespace perfetto {
diff --git a/include/perfetto/ext/base/metatrace_events.h b/include/perfetto/ext/base/metatrace_events.h
index e3cfb77..fd6a5dd 100644
--- a/include/perfetto/ext/base/metatrace_events.h
+++ b/include/perfetto/ext/base/metatrace_events.h
@@ -29,6 +29,7 @@
TAG_PROC_POLLERS = 1 << 1,
TAG_TRACE_WRITER = 1 << 2,
TAG_TRACE_SERVICE = 1 << 3,
+ TAG_PRODUCER = 1 << 4,
};
// The macros below generate matching enums and arrays of string literals.
@@ -60,7 +61,11 @@
F(FTRACE_READ_TICK), \
F(FTRACE_CPU_READ_CYCLE), \
F(FTRACE_CPU_READ_BATCH), \
- F(KALLSYMS_PARSE)
+ F(KALLSYMS_PARSE), \
+ F(PROFILER_READ_TICK), \
+ F(PROFILER_READ_CPU), \
+ F(PROFILER_UNWIND_TICK), \
+ F(PROFILER_UNWIND_SAMPLE)
// Append only, see above.
//
@@ -71,7 +76,8 @@
F(COUNTER_ZERO_UNUSED),\
F(FTRACE_PAGES_DRAINED), \
F(PS_PIDS_SCANNED), \
- F(TRACE_SERVICE_COMMIT_DATA)
+ F(TRACE_SERVICE_COMMIT_DATA), \
+ F(PROFILER_UNWIND_QUEUE_SZ)
// clang-format on
diff --git a/include/perfetto/ext/base/weak_ptr.h b/include/perfetto/ext/base/weak_ptr.h
index 7ba4ca3..778c4dd 100644
--- a/include/perfetto/ext/base/weak_ptr.h
+++ b/include/perfetto/ext/base/weak_ptr.h
@@ -90,9 +90,23 @@
// Can be safely called on any thread, since it simply copies |weak_ptr_|.
// Note that any accesses to the returned pointer need to be made on the
- // thread that created the factory.
+ // thread that created/reset the factory.
WeakPtr<T> GetWeakPtr() const { return weak_ptr_; }
+ // Reset the factory to a new owner & thread. May only be called before any
+ // weak pointers were passed out. Future weak pointers will be valid on the
+ // calling thread.
+ void Reset(T* owner) {
+ // Reset thread checker to current thread.
+ PERFETTO_DETACH_FROM_THREAD(thread_checker);
+ PERFETTO_DCHECK_THREAD(thread_checker);
+
+ // We should not have passed out any weak pointers yet at this point.
+ PERFETTO_DCHECK(weak_ptr_.handle_.use_count() == 1);
+
+ weak_ptr_ = WeakPtr<T>(std::shared_ptr<T*>(new T* {owner}));
+ }
+
private:
WeakPtrFactory(const WeakPtrFactory&) = delete;
WeakPtrFactory& operator=(const WeakPtrFactory&) = delete;
diff --git a/include/perfetto/ext/tracing/core/trace_writer.h b/include/perfetto/ext/tracing/core/trace_writer.h
index 99662b2..9be44ff 100644
--- a/include/perfetto/ext/tracing/core/trace_writer.h
+++ b/include/perfetto/ext/tracing/core/trace_writer.h
@@ -79,7 +79,7 @@
virtual WriterID writer_id() const = 0;
// Bytes written since creation. Is not reset when new chunks are acquired.
- virtual uint64_t written() const = 0;
+ virtual uint64_t written() const override = 0;
// Set the id of the first chunk the writer will emit. Returns |false| if not
// implemented or if the first chunk was already emitted by the writer.
diff --git a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
index 79b6ce6..5469ded 100644
--- a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
+++ b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
@@ -21,12 +21,13 @@
#include <string>
#include "perfetto/base/export.h"
+#include "perfetto/ext/tracing/core/shared_memory.h"
+#include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
#include "perfetto/ext/tracing/core/tracing_service.h"
namespace perfetto {
class Producer;
-class SharedMemory;
// Allows to connect to a remote Service through a UNIX domain socket.
// Exposed to:
@@ -42,9 +43,12 @@
// ProducerEndpoint serves also to delimit the scope of the callbacks invoked
// on the Producer interface: no more Producer callbacks are invoked
// immediately after its destruction and any pending callback will be dropped.
- // If |shm| is provided by the producer, the service will attempt to adopt the
- // provided SMB. If it fails, the ProducerEndpoint will disconnect, but |shm|
- // will remain valid until the client is destroyed.
+ // To provide a producer-allocated shared memory buffer, both |shm| and
+ // |shm_arbiter| should be set. |shm_arbiter| should be an unbound
+ // SharedMemoryArbiter instance. When |shm| and |shm_arbiter| are provided,
+ // the service will attempt to adopt the provided SMB. If this fails, the
+ // ProducerEndpoint will disconnect, but the SMB and arbiter will remain valid
+ // until the client is destroyed.
//
// TODO(eseckler): Support adoption failure more gracefully.
static std::unique_ptr<TracingService::ProducerEndpoint> Connect(
@@ -56,7 +60,8 @@
TracingService::ProducerSMBScrapingMode::kDefault,
size_t shared_memory_size_hint_bytes = 0,
size_t shared_memory_page_size_hint_bytes = 0,
- std::unique_ptr<SharedMemory> shm = nullptr);
+ std::unique_ptr<SharedMemory> shm = nullptr,
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
protected:
ProducerIPCClient() = delete;
diff --git a/include/perfetto/protozero/proto_decoder.h b/include/perfetto/protozero/proto_decoder.h
index b58e64e..8f98a62 100644
--- a/include/perfetto/protozero/proto_decoder.h
+++ b/include/perfetto/protozero/proto_decoder.h
@@ -40,7 +40,7 @@
// (see proto_decoder_fuzzer.cc).
// This class serves also as a building block for TypedProtoDecoder, used when
// the schema is known at compile time.
-class ProtoDecoder {
+class PERFETTO_EXPORT ProtoDecoder {
public:
// Creates a ProtoDecoder using the given |buffer| with size |length| bytes.
ProtoDecoder(const void* buffer, size_t length)
@@ -273,7 +273,7 @@
// [ field 0 (invalid) ] [ fields 1 .. N ] [ repeated fields ]
// ^ ^
// num_fields_ size_
-class TypedProtoDecoderBase : public ProtoDecoder {
+class PERFETTO_EXPORT TypedProtoDecoderBase : public ProtoDecoder {
public:
// If the field |id| is known at compile time, prefer the templated
// specialization at<kFieldNumber>().
diff --git a/include/perfetto/tracing/data_source.h b/include/perfetto/tracing/data_source.h
index 1a2f57e..2e8dc4e 100644
--- a/include/perfetto/tracing/data_source.h
+++ b/include/perfetto/tracing/data_source.h
@@ -188,6 +188,11 @@
tls_inst_->trace_writer->Flush(cb);
}
+ // Returns the number of bytes written on the current thread by the current
+ // data-source since its creation.
+ // This can be useful for splitting protos that might grow very large.
+ uint64_t written() { return tls_inst_->trace_writer->written(); }
+
// Returns a RAII handle to access the data source instance, guaranteeing
// that it won't be deleted on another thread (because of trace stopping)
// while accessing it from within the Trace() lambda.
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 05ebd9d..bef0382 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -402,23 +402,6 @@
}
};
- // Given either a static or dynamic category, extract a raw pointer to its
- // underlying name. Note that the pointer is only valid as long as the
- // parameter that was passed in.
- static const char* GetCategoryName(size_t category_index,
- const char* dynamic_category)
- PERFETTO_ALWAYS_INLINE {
- if (category_index == TrackEventCategoryRegistry::kDynamicCategoryIndex)
- return dynamic_category;
- return Registry->GetCategory(category_index)->name;
- }
-
- static const char* GetCategoryName(size_t,
- const DynamicCategory& dynamic_category)
- PERFETTO_ALWAYS_INLINE {
- return dynamic_category.name.c_str();
- }
-
// TODO(skyostil): Make |CategoryIndex| a regular parameter to reuse trace
// point code across different categories.
template <size_t CategoryIndex,
@@ -441,22 +424,35 @@
TraceWithInstances<CategoryIndex>(
instances, [&](typename Base::TraceContext ctx) {
// If this category is dynamic, first check whether it's enabled.
- if (CategoryIndex ==
- TrackEventCategoryRegistry::kDynamicCategoryIndex &&
- !IsDynamicCategoryEnabled(&ctx,
- DynamicCategory{dynamic_category})) {
+ constexpr bool kIsDynamic =
+ CategoryIndex ==
+ TrackEventCategoryRegistry::kDynamicCategoryIndex;
+ if (kIsDynamic && !IsDynamicCategoryEnabled(
+ &ctx, DynamicCategory{dynamic_category})) {
return;
}
+
{
// TODO(skyostil): Intern categories at compile time.
+ const Category* static_category =
+ kIsDynamic ? nullptr : Registry->GetCategory(CategoryIndex);
auto event_ctx = TrackEventInternal::WriteEvent(
ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
- GetCategoryName(CategoryIndex, dynamic_category), event_name,
- type, timestamp);
+ static_category, event_name, type, timestamp);
+ if (kIsDynamic) {
+ Category category{
+ Category::FromDynamicCategory(dynamic_category)};
+ category.ForEachGroupMember(
+ [&](const char* member_name, size_t name_size) {
+ event_ctx.event()->add_categories(member_name, name_size);
+ return true;
+ });
+ }
if (track)
event_ctx.event()->set_track_uuid(track.uuid);
arg_function(std::move(event_ctx));
- }
+ } // event_ctx
+
if (track) {
TrackEventInternal::WriteTrackDescriptorIfNeeded(
track, ctx.tls_inst_->trace_writer.get(),
@@ -503,8 +499,9 @@
// We haven't seen this category before. Let's figure out if it's enabled.
// This requires grabbing a lock to read the session's trace config.
auto ds = ctx->GetDataSourceLocked();
+ Category category{Category::FromDynamicCategory(dynamic_category)};
bool enabled = TrackEventInternal::IsCategoryEnabled(
- ds->config_, TrackEventCategory{dynamic_category.name.c_str()});
+ *Registry, ds->config_, category);
// TODO(skyostil): Cap the size of |dynamic_categories|.
incr_state->dynamic_categories[dynamic_category.name] = enabled;
return enabled;
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index 09511d8..ed3d7cb 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -30,6 +30,7 @@
namespace perfetto {
class EventContext;
+struct Category;
namespace protos {
namespace gen {
class TrackEventConfig;
@@ -40,7 +41,6 @@
} // namespace protos
namespace internal {
-struct TrackEventCategory;
class TrackEventCategoryRegistry;
class BaseTrackEventInternedDataIndex {
@@ -102,13 +102,14 @@
uint32_t instance_index);
static void DisableTracing(const TrackEventCategoryRegistry& registry,
uint32_t instance_index);
- static bool IsCategoryEnabled(const protos::gen::TrackEventConfig& config,
- const TrackEventCategory& category);
+ static bool IsCategoryEnabled(const TrackEventCategoryRegistry& registry,
+ const protos::gen::TrackEventConfig& config,
+ const Category& category);
static perfetto::EventContext WriteEvent(
TraceWriterBase*,
TrackEventIncrementalState*,
- const char* category,
+ const Category* category,
const char* name,
perfetto::protos::pbzero::TrackEvent::Type,
uint64_t timestamp = GetTimeNs());
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index 4d35f73..e0ea03c 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -40,8 +40,7 @@
//
#define PERFETTO_INTERNAL_DECLARE_CATEGORIES(...) \
namespace internal { \
- constexpr ::perfetto::internal::TrackEventCategory kCategories[] = { \
- __VA_ARGS__}; \
+ constexpr ::perfetto::Category kCategories[] = {__VA_ARGS__}; \
constexpr size_t kCategoryCount = \
sizeof(kCategories) / sizeof(kCategories[0]); \
/* The per-instance enable/disable state per category */ \
@@ -103,22 +102,21 @@
// if so, emits one trace event with the given arguments.
#define PERFETTO_INTERNAL_TRACK_EVENT(category, ...) \
do { \
- using namespace ::PERFETTO_TRACK_EVENT_NAMESPACE; \
- using namespace ::perfetto::internal; \
+ namespace tns = ::PERFETTO_TRACK_EVENT_NAMESPACE; \
/* Compute the category index outside the lambda to work around a */ \
/* GCC 7 bug */ \
constexpr auto PERFETTO_UID(kCatIndex) = \
PERFETTO_GET_CATEGORY_INDEX(category); \
- if (internal::IsDynamicCategory(category)) { \
- TrackEvent::CallIfEnabled([&](uint32_t instances) { \
- TrackEvent::TraceForCategory<PERFETTO_UID(kCatIndex)>( \
+ if (tns::internal::IsDynamicCategory(category)) { \
+ tns::TrackEvent::CallIfEnabled([&](uint32_t instances) { \
+ tns::TrackEvent::TraceForCategory<PERFETTO_UID(kCatIndex)>( \
instances, category, ##__VA_ARGS__); \
}); \
} else { \
- TrackEvent::CallIfCategoryEnabled<PERFETTO_UID(kCatIndex)>( \
+ tns::TrackEvent::CallIfCategoryEnabled<PERFETTO_UID(kCatIndex)>( \
[&](uint32_t instances) { \
/* TODO(skyostil): Get rid of the category name parameter. */ \
- TrackEvent::TraceForCategory<PERFETTO_UID(kCatIndex)>( \
+ tns::TrackEvent::TraceForCategory<PERFETTO_UID(kCatIndex)>( \
instances, nullptr, ##__VA_ARGS__); \
}); \
} \
diff --git a/include/perfetto/tracing/trace_writer_base.h b/include/perfetto/tracing/trace_writer_base.h
index 2d95668..824be1a 100644
--- a/include/perfetto/tracing/trace_writer_base.h
+++ b/include/perfetto/tracing/trace_writer_base.h
@@ -38,6 +38,7 @@
NewTracePacket() = 0;
virtual void Flush(std::function<void()> callback = {}) = 0;
+ virtual uint64_t written() const = 0;
};
} // namespace perfetto
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index 7d5979e..be20e0b 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -39,9 +39,9 @@
// e.g., my_tracing.h:
//
// PERFETTO_DEFINE_CATEGORIES(
-// PERFETTO_CATEGORY(base),
-// PERFETTO_CATEGORY(v8),
-// PERFETTO_CATEGORY(cc));
+// perfetto::Category("base"),
+// perfetto::Category("v8"),
+// perfetto::Category("cc"));
//
// Then in a single .cc file, e.g., my_tracing.cc:
//
@@ -125,10 +125,9 @@
#define PERFETTO_TRACK_EVENT_NAMESPACE perfetto
#endif
-// A name for a single category. Wrapped in a macro in case we need to introduce
-// more fields in the future.
+// Deprecated; see perfetto::Category().
#define PERFETTO_CATEGORY(name) \
- { #name }
+ ::perfetto::Category { #name }
// Internal helpers for determining if a given category is defined at build or
// runtime.
diff --git a/include/perfetto/tracing/track_event_category_registry.h b/include/perfetto/tracing/track_event_category_registry.h
index c769aef..ae58a31 100644
--- a/include/perfetto/tracing/track_event_category_registry.h
+++ b/include/perfetto/tracing/track_event_category_registry.h
@@ -19,10 +19,132 @@
#include "perfetto/tracing/data_source.h"
+#include <stddef.h>
+
#include <atomic>
#include <utility>
namespace perfetto {
+class DynamicCategory;
+
+// A compile-time representation of a track event category. See
+// PERFETTO_DEFINE_CATEGORIES for registering your own categories.
+struct Category {
+ using Tags = std::array<const char*, 4>;
+
+ const char* const name = nullptr;
+ const char* const description = nullptr;
+ const Tags tags = {};
+
+ constexpr Category(const Category&) = default;
+ constexpr explicit Category(const char* name_)
+ : name(CheckIsValidCategory(name_)),
+ name_sizes_(ComputeNameSizes(name_)) {}
+
+ constexpr Category SetDescription(const char* description_) const {
+ return Category(name, description_, tags, name_sizes_);
+ }
+
+ template <typename... Args>
+ constexpr Category SetTags(Args&&... args) const {
+ return Category(name, description, {std::forward<Args>(args)...},
+ name_sizes_);
+ }
+
+ // A comma separated list of multiple categories to be used in a single trace
+ // point.
+ static constexpr Category Group(const char* names) {
+ return Category(names, AllowGroup{});
+ }
+
+ // Used for parsing dynamic category groups. Note that |name| and
+ // |DynamicCategory| must outlive the returned object because the category
+ // name isn't copied.
+ static Category FromDynamicCategory(const char* name);
+ static Category FromDynamicCategory(const DynamicCategory&);
+
+ constexpr bool IsGroup() const { return GetNameSize(1) > 0; }
+
+ // Returns the number of character in the category name. Not valid for
+ // category groups.
+ size_t name_size() const {
+ PERFETTO_DCHECK(!IsGroup());
+ return GetNameSize(0);
+ }
+
+ // Iterates over all the members of this category group, or just the name of
+ // the category itself if this isn't a category group. Return false from
+ // |callback| to stop iteration.
+ template <typename T>
+ void ForEachGroupMember(T callback) const {
+ const char* name_ptr = name;
+ size_t i = 0;
+ while (size_t name_size = GetNameSize(i++)) {
+ if (!callback(name_ptr, name_size))
+ break;
+ name_ptr += name_size + 1;
+ }
+ }
+
+ private:
+ static constexpr size_t kMaxGroupSize = 4;
+ using NameSizes = std::array<uint8_t, kMaxGroupSize>;
+
+ constexpr Category(const char* name_,
+ const char* description_,
+ Tags tags_,
+ NameSizes name_sizes)
+ : name(name_),
+ description(description_),
+ tags(tags_),
+ name_sizes_(name_sizes) {}
+
+ enum AllowGroup {};
+ constexpr Category(const char* name_, AllowGroup)
+ : name(CheckIsValidCategoryGroup(name_)),
+ name_sizes_(ComputeNameSizes(name_)) {}
+
+ constexpr size_t GetNameSize(size_t i) const {
+ return i < name_sizes_.size() ? name_sizes_[i] : 0;
+ }
+
+ static constexpr NameSizes ComputeNameSizes(const char* s) {
+ static_assert(kMaxGroupSize == 4, "Unexpected maximum category group size");
+ return NameSizes{{static_cast<uint8_t>(GetNthNameSize(0, s, s)),
+ static_cast<uint8_t>(GetNthNameSize(1, s, s)),
+ static_cast<uint8_t>(GetNthNameSize(2, s, s)),
+ static_cast<uint8_t>(GetNthNameSize(3, s, s))}};
+ }
+
+ static constexpr ptrdiff_t GetNthNameSize(int n,
+ const char* start,
+ const char* end,
+ int counter = 0) {
+ return (!*end || *end == ',')
+ ? ((!*end || counter == n)
+ ? (counter == n ? end - start : 0)
+ : GetNthNameSize(n, end + 1, end + 1, counter + 1))
+ : GetNthNameSize(n, start, end + 1, counter);
+ }
+
+ static constexpr const char* CheckIsValidCategory(const char* n) {
+ // We just replace invalid input with a nullptr here; it will trigger a
+ // static assert in TrackEventCategoryRegistry::ValidateCategories().
+ return GetNthNameSize(1, n, n) ? nullptr : n;
+ }
+
+ static constexpr const char* CheckIsValidCategoryGroup(const char* n) {
+ // Same as above: replace invalid input with nullptr.
+ return !GetNthNameSize(1, n, n) || GetNthNameSize(kMaxGroupSize, n, n)
+ ? nullptr
+ : n;
+ }
+
+ // An array of lengths of the different names associated with this category.
+ // If this category doesn't represent a group of multiple categories, only the
+ // first element is non-zero.
+ const NameSizes name_sizes_ = {};
+};
// Dynamically constructed category names should marked as such through this
// container type to make it less likely for trace points to accidentally start
@@ -66,18 +188,12 @@
IsStringInPrefixList(str, std::forward<Args>(args)...);
}
-// A compile-time representation of a track event category. See
-// PERFETTO_DEFINE_CATEGORIES for registering your own categories.
-struct TrackEventCategory {
- const char* const name;
-};
-
// Holds all the registered categories for one category namespace. See
// PERFETTO_DEFINE_CATEGORIES for building the registry.
class TrackEventCategoryRegistry {
public:
constexpr TrackEventCategoryRegistry(size_t category_count,
- const TrackEventCategory* categories,
+ const Category* categories,
std::atomic<uint8_t>* state_storage)
: categories_(categories),
category_count_(category_count),
@@ -91,7 +207,7 @@
size_t category_count() const { return category_count_; }
// Returns a category based on its index.
- const TrackEventCategory* GetCategory(size_t index) const;
+ const Category* GetCategory(size_t index) const;
// Turn tracing on or off for the given category in a track event data source
// instance.
@@ -154,7 +270,7 @@
private:
// TODO(skyostil): Make the compile-time routines nicer with C++14.
static constexpr bool IsValidCategoryName(const char* name) {
- return (*name == '\"' || *name == '*')
+ return (!name || *name == '\"' || *name == '*' || *name == ' ')
? false
: *name ? IsValidCategoryName(name + 1) : true;
}
@@ -164,7 +280,7 @@
: (!*a || !*b) ? (*a == *b) : StringEq(a + 1, b + 1);
}
- const TrackEventCategory* const categories_;
+ const Category* const categories_;
const size_t category_count_;
std::atomic<uint8_t>* const state_storage_;
};
diff --git a/include/perfetto/tracing/track_event_interned_data_index.h b/include/perfetto/tracing/track_event_interned_data_index.h
index be4c128..cce2665 100644
--- a/include/perfetto/tracing/track_event_interned_data_index.h
+++ b/include/perfetto/tracing/track_event_interned_data_index.h
@@ -170,8 +170,12 @@
: public internal::BaseTrackEventInternedDataIndex {
public:
// Return an interning id for |value|. The returned id can be immediately
- // written to the trace.
- static size_t Get(EventContext* ctx, const ValueType& value) {
+ // written to the trace. The optional |add_args| are passed to the Add()
+ // function.
+ template <typename... Args>
+ static size_t Get(EventContext* ctx,
+ const ValueType& value,
+ Args&&... add_args) {
// First check if the value exists in the dictionary.
auto index_for_field = GetOrCreateIndexForField(ctx->incremental_state_);
size_t iid;
@@ -186,7 +190,7 @@
PERFETTO_DCHECK(iid);
InternedDataType::Add(
ctx->incremental_state_->serialized_interned_data.get(), iid,
- std::move(value));
+ std::move(value), std::forward<Args>(add_args)...);
return iid;
}
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 2acc000..102bac2 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -1154,13 +1154,35 @@
// The following fields define the set of enabled trace categories. Each list
// item is a glob.
//
- // To determine if category X is enabled:
+ // To determine if category is enabled, it is checked against the filters in
+ // the following order:
//
- // if (X in disabled_categories):
- // return (X in enabled_categories)
- // else if (X in disabled_tags):
- // return (X in enabled_categories or X in enabled_tags)
- // else return true
+ // 1. Exact matches in enabled categories.
+ // 2. Exact matches in enabled tags.
+ // 3. Exact matches in disabled categories.
+ // 4. Exact matches in disabled tags.
+ // 5. Pattern matches in enabled categories.
+ // 6. Pattern matches in enabled tags.
+ // 7. Pattern matches in disabled categories.
+ // 8. Pattern matches in disabled tags.
+ //
+ // If none of the steps produced a match, the category is enabled by default.
+ //
+ // Examples:
+ //
+ // - To enable all non-slow/debug categories:
+ //
+ // No configuration needed, happens by default.
+ //
+ // - To enable a specific category:
+ //
+ // disabled_categories = ["*"]
+ // enabled_categories = ["my_category"]
+ //
+ // - To enable only categories with a specific tag:
+ //
+ // disabled_tags = ["*"]
+ // enabled_tags = ["my_tag"]
//
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
diff --git a/protos/perfetto/config/track_event/track_event_config.proto b/protos/perfetto/config/track_event/track_event_config.proto
index 8fab47c..6ae1f8f 100644
--- a/protos/perfetto/config/track_event/track_event_config.proto
+++ b/protos/perfetto/config/track_event/track_event_config.proto
@@ -22,13 +22,35 @@
// The following fields define the set of enabled trace categories. Each list
// item is a glob.
//
- // To determine if category X is enabled:
+ // To determine if category is enabled, it is checked against the filters in
+ // the following order:
//
- // if (X in disabled_categories):
- // return (X in enabled_categories)
- // else if (X in disabled_tags):
- // return (X in enabled_categories or X in enabled_tags)
- // else return true
+ // 1. Exact matches in enabled categories.
+ // 2. Exact matches in enabled tags.
+ // 3. Exact matches in disabled categories.
+ // 4. Exact matches in disabled tags.
+ // 5. Pattern matches in enabled categories.
+ // 6. Pattern matches in enabled tags.
+ // 7. Pattern matches in disabled categories.
+ // 8. Pattern matches in disabled tags.
+ //
+ // If none of the steps produced a match, the category is enabled by default.
+ //
+ // Examples:
+ //
+ // - To enable all non-slow/debug categories:
+ //
+ // No configuration needed, happens by default.
+ //
+ // - To enable a specific category:
+ //
+ // disabled_categories = ["*"]
+ // enabled_categories = ["my_category"]
+ //
+ // - To enable only categories with a specific tag:
+ //
+ // disabled_tags = ["*"]
+ // enabled_tags = ["my_tag"]
//
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 16ab8d0..a2ea1d1 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -5591,13 +5591,35 @@
// The following fields define the set of enabled trace categories. Each list
// item is a glob.
//
- // To determine if category X is enabled:
+ // To determine if category is enabled, it is checked against the filters in
+ // the following order:
//
- // if (X in disabled_categories):
- // return (X in enabled_categories)
- // else if (X in disabled_tags):
- // return (X in enabled_categories or X in enabled_tags)
- // else return true
+ // 1. Exact matches in enabled categories.
+ // 2. Exact matches in enabled tags.
+ // 3. Exact matches in disabled categories.
+ // 4. Exact matches in disabled tags.
+ // 5. Pattern matches in enabled categories.
+ // 6. Pattern matches in enabled tags.
+ // 7. Pattern matches in disabled categories.
+ // 8. Pattern matches in disabled tags.
+ //
+ // If none of the steps produced a match, the category is enabled by default.
+ //
+ // Examples:
+ //
+ // - To enable all non-slow/debug categories:
+ //
+ // No configuration needed, happens by default.
+ //
+ // - To enable a specific category:
+ //
+ // disabled_categories = ["*"]
+ // enabled_categories = ["my_category"]
+ //
+ // - To enable only categories with a specific tag:
+ //
+ // disabled_tags = ["*"]
+ // enabled_tags = ["my_tag"]
//
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
diff --git a/src/perfetto_cmd/perfetto_config.descriptor.h b/src/perfetto_cmd/perfetto_config.descriptor.h
index 8a77cfd..fb22c36 100644
--- a/src/perfetto_cmd/perfetto_config.descriptor.h
+++ b/src/perfetto_cmd/perfetto_config.descriptor.h
@@ -27,7 +27,7 @@
// SHA1(tools/gen_binary_descriptors)
// d6628b15181dba5287e35b56b966b39ea93d42b1
// SHA1(protos/perfetto/config/perfetto_config.proto)
-// 0cf480593a4dbbfdf5aa88924418e6973523489d
+// a5fa2ae0a3cc1fc0f9e5ab400145cf0a3d086fa3
// This is the proto PerfettoConfig encoded as a ProtoFileDescriptor to allow
// for reflection without libprotobuf full/non-lite protos.
diff --git a/src/profiling/common/unwind_support.cc b/src/profiling/common/unwind_support.cc
index b597368..cb791bc 100644
--- a/src/profiling/common/unwind_support.cc
+++ b/src/profiling/common/unwind_support.cc
@@ -130,5 +130,26 @@
return FrameData{std::move(frame), std::move(build_id)};
}
+std::string StringifyLibUnwindstackError(unwindstack::ErrorCode e) {
+ switch (e) {
+ case unwindstack::ERROR_NONE:
+ return "NONE";
+ case unwindstack::ERROR_MEMORY_INVALID:
+ return "MEMORY_INVALID";
+ case unwindstack::ERROR_UNWIND_INFO:
+ return "UNWIND_INFO";
+ case unwindstack::ERROR_UNSUPPORTED:
+ return "UNSUPPORTED";
+ case unwindstack::ERROR_INVALID_MAP:
+ return "INVALID_MAP";
+ case unwindstack::ERROR_MAX_FRAMES_EXCEEDED:
+ return "MAX_FRAME_EXCEEDED";
+ case unwindstack::ERROR_REPEATED_FRAME:
+ return "REPEATED_FRAME";
+ case unwindstack::ERROR_INVALID_ELF:
+ return "INVALID_ELF";
+ }
+}
+
} // namespace profiling
} // namespace perfetto
diff --git a/src/profiling/common/unwind_support.h b/src/profiling/common/unwind_support.h
index 061ed61..732531c 100644
--- a/src/profiling/common/unwind_support.h
+++ b/src/profiling/common/unwind_support.h
@@ -125,6 +125,8 @@
#endif
};
+std::string StringifyLibUnwindstackError(unwindstack::ErrorCode);
+
} // namespace profiling
} // namespace perfetto
diff --git a/src/profiling/memory/client.cc b/src/profiling/memory/client.cc
index a0db2de..74cdafa 100644
--- a/src/profiling/memory/client.cc
+++ b/src/profiling/memory/client.cc
@@ -174,7 +174,7 @@
base::ScopedFile page_idle(base::OpenFile("/proc/self/page_idle", O_RDWR));
if (!page_idle) {
- PERFETTO_LOG("Failed to open /proc/self/page_idle. Continuing.");
+ PERFETTO_DLOG("Failed to open /proc/self/page_idle. Continuing.");
num_send_fds = kHandshakeSize - 1;
}
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 26a393a..c9e43fe 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -144,7 +144,7 @@
#endif
// Suppress incorrect "variable may be uninitialized" error for if condition
// after this loop. error_code = LastErrorCode gets run at least once.
- uint8_t error_code = 0;
+ unwindstack::ErrorCode error_code = unwindstack::ERROR_NONE;
for (int attempt = 0; attempt < 2; ++attempt) {
if (attempt > 0) {
if (metadata->last_maps_reparse_time + kMapsReparseInterval >
@@ -177,7 +177,8 @@
if (error_code != unwindstack::ERROR_NONE) {
PERFETTO_DLOG("Unwinding error %" PRIu8, error_code);
unwindstack::FrameData frame_data{};
- frame_data.function_name = "ERROR " + std::to_string(error_code);
+ frame_data.function_name =
+ "ERROR " + StringifyLibUnwindstackError(error_code);
frame_data.map_name = "ERROR";
out->frames.emplace_back(std::move(frame_data), "");
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
index 9b4e2d1..5f36377 100644
--- a/src/profiling/perf/BUILD.gn
+++ b/src/profiling/perf/BUILD.gn
@@ -44,6 +44,7 @@
public_deps = [
":regs_parsing",
"../../../include/perfetto/tracing/core",
+ "../../../src/tracing/core:service", # for metatrace
]
deps = [
":proc_descriptors",
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 0437254..c6fd0db 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -25,6 +25,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/weak_ptr.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/producer.h"
@@ -128,6 +129,13 @@
PERFETTO_LOG("StartDataSource(%zu, %s)", static_cast<size_t>(instance_id),
config.name().c_str());
+ if (config.name() == MetatraceWriter::kDataSourceName) {
+ StartMetatraceSource(instance_id,
+ static_cast<BufferID>(config.target_buffer()));
+ return;
+ }
+
+ // linux.perf data source
if (config.name() != kDataSourceName)
return;
@@ -192,6 +200,16 @@
void PerfProducer::StopDataSource(DataSourceInstanceID instance_id) {
PERFETTO_LOG("StopDataSource(%zu)", static_cast<size_t>(instance_id));
+
+ // Metatrace: stop immediately (will miss the events from the
+ // asynchronous shutdown of the primary data source).
+ auto meta_it = metatrace_writers_.find(instance_id);
+ if (meta_it != metatrace_writers_.end()) {
+ meta_it->second.WriteAllAndFlushTraceWriter([] {});
+ metatrace_writers_.erase(meta_it);
+ return;
+ }
+
auto ds_it = data_sources_.find(instance_id);
if (ds_it == data_sources_.end())
return;
@@ -209,14 +227,22 @@
void PerfProducer::Flush(FlushRequestID flush_id,
const DataSourceInstanceID* data_source_ids,
size_t num_data_sources) {
+ bool should_ack_flush = false;
for (size_t i = 0; i < num_data_sources; i++) {
auto ds_id = data_source_ids[i];
PERFETTO_DLOG("Flush(%zu)", static_cast<size_t>(ds_id));
- auto ds_it = data_sources_.find(ds_id);
- if (ds_it != data_sources_.end()) {
- endpoint_->NotifyFlushComplete(flush_id);
+
+ auto meta_it = metatrace_writers_.find(ds_id);
+ if (meta_it != metatrace_writers_.end()) {
+ meta_it->second.WriteAllAndFlushTraceWriter([] {});
+ should_ack_flush = true;
+ }
+ if (data_sources_.find(ds_id) != data_sources_.end()) {
+ should_ack_flush = true;
}
}
+ if (should_ack_flush)
+ endpoint_->NotifyFlushComplete(flush_id);
}
void PerfProducer::TickDataSourceRead(DataSourceInstanceID ds_id) {
@@ -228,6 +254,8 @@
}
DataSource& ds = it->second;
+ PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, PROFILER_READ_TICK);
+
// Make a pass over all per-cpu readers.
bool more_records_available = false;
for (EventReader& reader : ds.per_cpu_readers) {
@@ -256,6 +284,7 @@
DataSourceInstanceID ds_id,
DataSource* ds) {
using Status = DataSource::ProcDescriptors::Status;
+ PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, PROFILER_READ_CPU);
// If the kernel ring buffer dropped data, record it in the trace.
size_t cpu = reader->cpu();
@@ -279,7 +308,7 @@
continue;
}
- // Request proc-fds for the process if this is the first time we see it yet.
+ // Request proc-fds for the process if this is the first time we see it.
pid_t pid = sample->pid;
auto& fd_entry = ds->proc_fds[pid]; // created if absent
@@ -299,10 +328,14 @@
// Push the sample into a dedicated unwinding queue.
unwind_queues_[ds_id].emplace_back(std::move(sample.value()));
+
+ // Metatrace: counter sensible only when there's a single active source.
+ PERFETTO_METATRACE_COUNTER(TAG_PRODUCER, PROFILER_UNWIND_QUEUE_SZ,
+ unwind_queues_[ds_id].size());
}
- // Most likely more events in the buffer - technically, max_samples can stop
- // us right at the boundary.
+ // Most likely more events in the buffer. Though we might be exactly on the
+ // boundary due to |max_samples|.
return true;
}
@@ -371,6 +404,8 @@
auto unwind_it = unwind_queues_.find(ds_id);
PERFETTO_CHECK(unwind_it != unwind_queues_.end());
+ PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, PROFILER_UNWIND_TICK);
+
bool queue_active =
ProcessUnwindQueue(ds_id, &unwind_it->second, &ds_it->second);
@@ -439,11 +474,12 @@
// Sample ready - process it.
if (fd_status == Status::kResolved) {
+ PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, PROFILER_UNWIND_SAMPLE);
+
PerfProducer::CompletedSample unwound_sample =
UnwindSample(std::move(sample), &proc_fd_it->second);
PostEmitSample(ds_id, std::move(unwound_sample));
-
entry.valid = false;
continue;
}
@@ -457,6 +493,9 @@
queue.pop_front();
}
+ // Metatrace: counter sensible only when there's a single active source.
+ PERFETTO_METATRACE_COUNTER(TAG_PRODUCER, PROFILER_UNWIND_QUEUE_SZ,
+ queue.size());
PERFETTO_DLOG("Unwind queue drain: [%zu]->[%zu]", num_samples, queue.size());
// Return whether we're done with unwindings for this source.
@@ -638,6 +677,18 @@
}
}
+void PerfProducer::StartMetatraceSource(DataSourceInstanceID ds_id,
+ BufferID target_buffer) {
+ auto writer = endpoint_->CreateTraceWriter(target_buffer);
+
+ auto it_and_inserted = metatrace_writers_.emplace(
+ std::piecewise_construct, std::make_tuple(ds_id), std::make_tuple());
+ PERFETTO_DCHECK(it_and_inserted.second);
+ // Note: only the first concurrent writer will actually be active.
+ metatrace_writers_[ds_id].Enable(task_runner_, std::move(writer),
+ metatrace::TAG_ANY);
+}
+
void PerfProducer::ConnectWithRetries(const char* socket_name) {
PERFETTO_DCHECK(state_ == kNotStarted);
state_ = kNotConnected;
@@ -670,10 +721,19 @@
ResetConnectionBackoff();
PERFETTO_LOG("Connected to the service");
- DataSourceDescriptor desc;
- desc.set_name(kDataSourceName);
- desc.set_will_notify_on_stop(true);
- endpoint_->RegisterDataSource(desc);
+ {
+ // linux.perf
+ DataSourceDescriptor desc;
+ desc.set_name(kDataSourceName);
+ desc.set_will_notify_on_stop(true);
+ endpoint_->RegisterDataSource(desc);
+ }
+ {
+ // metatrace
+ DataSourceDescriptor desc;
+ desc.set_name(MetatraceWriter::kDataSourceName);
+ endpoint_->RegisterDataSource(desc);
+ }
}
void PerfProducer::OnDisconnect() {
diff --git a/src/profiling/perf/perf_producer.h b/src/profiling/perf/perf_producer.h
index ee2a661..1b127c7 100644
--- a/src/profiling/perf/perf_producer.h
+++ b/src/profiling/perf/perf_producer.h
@@ -41,6 +41,7 @@
#include "src/profiling/perf/event_config.h"
#include "src/profiling/perf/event_reader.h"
#include "src/profiling/perf/proc_descriptors.h"
+#include "src/tracing/core/metatrace_writer.h"
namespace perfetto {
namespace profiling {
@@ -197,6 +198,8 @@
// service of the stop.
void FinishDataSourceStop(DataSourceInstanceID ds_id);
+ void StartMetatraceSource(DataSourceInstanceID ds_id, BufferID target_buffer);
+
// Task runner owned by the main thread.
base::TaskRunner* const task_runner_;
State state_ = kNotStarted;
@@ -209,6 +212,10 @@
// Owns shared memory, must outlive trace writing.
std::unique_ptr<TracingService::ProducerEndpoint> endpoint_;
+ // If multiple metatrace sources are enabled concurrently,
+ // only the first one becomes active.
+ std::map<DataSourceInstanceID, MetatraceWriter> metatrace_writers_;
+
// Interns callstacks across all data sources.
// TODO(rsavitski): for long profiling sessions, consider purging trie when it
// grows too large (at the moment purged only when no sources are active).
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index e57d41b..2aec4e3 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -298,13 +298,13 @@
if (enable_perfetto_trace_processor_sqlite) {
source_set("lib") {
sources = [
+ "experimental_counter_dur_generator.cc",
+ "experimental_counter_dur_generator.h",
+ "experimental_flamegraph_generator.cc",
+ "experimental_flamegraph_generator.h",
"read_trace.cc",
"sql_stats_table.cc",
"sql_stats_table.h",
- "sqlite_experimental_counter_dur_table.cc",
- "sqlite_experimental_counter_dur_table.h",
- "sqlite_experimental_flamegraph_table.cc",
- "sqlite_experimental_flamegraph_table.h",
"sqlite_raw_table.cc",
"sqlite_raw_table.h",
"stats_table.cc",
@@ -409,7 +409,7 @@
]
if (enable_perfetto_trace_processor_sqlite) {
- sources += [ "sqlite_experimental_counter_dur_table_unittest.cc" ]
+ sources += [ "experimental_counter_dur_generator_unittest.cc" ]
deps += [
":lib",
"../../gn:sqlite",
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index e6e5387..b477e93 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -96,6 +96,12 @@
// This is used to speed up filters as we can safely index SparseVector
// directly if this flag is set.
kNonNull = 1 << 1,
+
+ // Indicates that the data in the column is "hidden". This can by used to
+ // hint to users of Table and Column that this column should not be
+ // displayed to the user as it is part of the internal implementation
+ // details of the table.
+ kHidden = 1 << 2,
};
// Flags specified for an id column.
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index b283ab0..f25aec5 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -82,6 +82,7 @@
SqlValue::Type type;
bool is_id;
bool is_sorted;
+ bool is_hidden;
};
std::vector<Column> columns;
};
diff --git a/src/trace_processor/experimental_counter_dur_generator.cc b/src/trace_processor/experimental_counter_dur_generator.cc
new file mode 100644
index 0000000..6026fd0
--- /dev/null
+++ b/src/trace_processor/experimental_counter_dur_generator.cc
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/experimental_counter_dur_generator.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ExperimentalCounterDurGenerator::ExperimentalCounterDurGenerator(
+ const tables::CounterTable& table)
+ : counter_table_(&table) {}
+ExperimentalCounterDurGenerator::~ExperimentalCounterDurGenerator() = default;
+
+Table::Schema ExperimentalCounterDurGenerator::CreateSchema() {
+ Table::Schema schema = tables::CounterTable::Schema();
+ schema.columns.emplace_back(
+ Table::Schema::Column{"dur", SqlValue::Type::kLong, false /* is_id */,
+ false /* is_sorted */, false /* is_hidden */});
+ return schema;
+}
+
+std::string ExperimentalCounterDurGenerator::TableName() {
+ return "experimental_counter_dur";
+}
+
+uint32_t ExperimentalCounterDurGenerator::EstimateRowCount() {
+ return counter_table_->row_count();
+}
+
+util::Status ExperimentalCounterDurGenerator::ValidateConstraints(
+ const QueryConstraints&) {
+ return util::OkStatus();
+}
+
+Table* ExperimentalCounterDurGenerator::ComputeTable(
+ const std::vector<Constraint>&,
+ const std::vector<Order>&) {
+ // We structure the code the following way (instead of just resetting the
+ // unique_ptr fields) to ensure that we don't hold onto a pointer to the
+ // sparsevector in the table after freeing the sparsevector.
+ std::unique_ptr<SparseVector<int64_t>> dur_column(
+ new SparseVector<int64_t>(ComputeDurColumn(*counter_table_)));
+ counter_with_dur_table_.reset(
+ new Table(counter_with_dur_table_->ExtendWithColumn(
+ "dur", dur_column.get(), TypedColumn<int64_t>::default_flags())));
+ dur_column_ = std::move(dur_column);
+ return counter_with_dur_table_.get();
+}
+
+// static
+SparseVector<int64_t> ExperimentalCounterDurGenerator::ComputeDurColumn(
+ const tables::CounterTable& table) {
+ // Keep track of the last seen row for each track id.
+ std::unordered_map<TrackId, uint32_t> last_row_for_track_id;
+ SparseVector<int64_t> dur;
+ for (uint32_t i = 0; i < table.row_count(); ++i) {
+ // Check if we already have a previous row for the current track id.
+ TrackId track_id = table.track_id()[i];
+ auto it = last_row_for_track_id.find(track_id);
+ if (it == last_row_for_track_id.end()) {
+ // This means we don't have any row - start tracking this row for the
+ // future.
+ last_row_for_track_id.emplace(track_id, i);
+ } else {
+ // This means we have an previous row for the current track id. Update
+ // the duration of the previous row to be up to the current ts.
+ uint32_t old_row = it->second;
+ it->second = i;
+ dur.Set(old_row, table.ts()[i] - table.ts()[old_row]);
+ }
+ // Append -1 to mark this event as not having been finished. On a later
+ // row, we may set this to have the correct value.
+ dur.Append(-1);
+ }
+ return dur;
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/experimental_counter_dur_generator.h b/src/trace_processor/experimental_counter_dur_generator.h
new file mode 100644
index 0000000..4be870f
--- /dev/null
+++ b/src/trace_processor/experimental_counter_dur_generator.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_EXPERIMENTAL_COUNTER_DUR_GENERATOR_H_
+#define SRC_TRACE_PROCESSOR_EXPERIMENTAL_COUNTER_DUR_GENERATOR_H_
+
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class ExperimentalCounterDurGenerator
+ : public DbSqliteTable::DynamicTableGenerator {
+ public:
+ ExperimentalCounterDurGenerator(const tables::CounterTable& table);
+ virtual ~ExperimentalCounterDurGenerator() override;
+
+ Table::Schema CreateSchema() override;
+ std::string TableName() override;
+ uint32_t EstimateRowCount() override;
+ util::Status ValidateConstraints(const QueryConstraints&) override;
+ Table* ComputeTable(const std::vector<Constraint>&,
+ const std::vector<Order>&) override;
+
+ // public + static for testing
+ static SparseVector<int64_t> ComputeDurColumn(
+ const tables::CounterTable& table);
+
+ private:
+ const tables::CounterTable* counter_table_ = nullptr;
+ std::unique_ptr<Table> counter_with_dur_table_;
+ std::unique_ptr<SparseVector<int64_t>> dur_column_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_EXPERIMENTAL_COUNTER_DUR_GENERATOR_H_
diff --git a/src/trace_processor/sqlite_experimental_counter_dur_table_unittest.cc b/src/trace_processor/experimental_counter_dur_generator_unittest.cc
similarity index 86%
rename from src/trace_processor/sqlite_experimental_counter_dur_table_unittest.cc
rename to src/trace_processor/experimental_counter_dur_generator_unittest.cc
index 81aa416..8538f1a 100644
--- a/src/trace_processor/sqlite_experimental_counter_dur_table_unittest.cc
+++ b/src/trace_processor/experimental_counter_dur_generator_unittest.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "src/trace_processor/sqlite_experimental_counter_dur_table.h"
+#include "src/trace_processor/experimental_counter_dur_generator.h"
#include "test/gtest_and_gmock.h"
@@ -25,11 +25,11 @@
tables::CounterTable::Row CounterRow(int64_t ts, uint32_t track_id) {
tables::CounterTable::Row row;
row.ts = ts;
- row.track_id = TrackId{track_id};
+ row.track_id = tables::TrackTable::Id{track_id};
return row;
}
-TEST(SqliteExperimentalCounterDurTable, SmokeDur) {
+TEST(ExperimentalCounterDurGenerator, SmokeDur) {
StringPool pool;
tables::CounterTable table(&pool, nullptr);
@@ -40,7 +40,7 @@
table.Insert(CounterRow(105 /* ts */, 2 /* track_id */));
table.Insert(CounterRow(110 /* ts */, 2 /* track_id */));
- auto dur = SqliteExperimentalCounterDurTable::ComputeDurColumn(table);
+ auto dur = ExperimentalCounterDurGenerator::ComputeDurColumn(table);
ASSERT_EQ(dur.size(), table.row_count());
ASSERT_EQ(dur.GetNonNull(0), 5);
diff --git a/src/trace_processor/experimental_flamegraph_generator.cc b/src/trace_processor/experimental_flamegraph_generator.cc
new file mode 100644
index 0000000..ccad480
--- /dev/null
+++ b/src/trace_processor/experimental_flamegraph_generator.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/experimental_flamegraph_generator.h"
+
+#include "src/trace_processor/heap_profile_tracker.h"
+#include "src/trace_processor/importers/proto/heap_graph_tracker.h"
+#include "src/trace_processor/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+ExperimentalFlamegraphGenerator::InputValues GetInputValues(
+ const std::vector<Constraint>& cs) {
+ using T = tables::ExperimentalFlamegraphNodesTable;
+
+ auto ts_fn = [](const Constraint& c) {
+ return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::ts) &&
+ c.op == FilterOp::kEq;
+ };
+ auto upid_fn = [](const Constraint& c) {
+ return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::upid) &&
+ c.op == FilterOp::kEq;
+ };
+ auto profile_type_fn = [](const Constraint& c) {
+ return c.col_idx == static_cast<uint32_t>(T::ColumnIndex::profile_type) &&
+ c.op == FilterOp::kEq;
+ };
+
+ auto ts_it = std::find_if(cs.begin(), cs.end(), ts_fn);
+ auto upid_it = std::find_if(cs.begin(), cs.end(), upid_fn);
+ auto profile_type_it = std::find_if(cs.begin(), cs.end(), profile_type_fn);
+
+ // We should always have valid iterators here because BestIndex should only
+ // allow the constraint set to be chosen when we have an equality constraint
+ // on both ts and upid.
+ PERFETTO_CHECK(ts_it != cs.end());
+ PERFETTO_CHECK(upid_it != cs.end());
+ PERFETTO_CHECK(profile_type_it != cs.end());
+
+ int64_t ts = ts_it->value.AsLong();
+ UniquePid upid = static_cast<UniquePid>(upid_it->value.AsLong());
+ std::string profile_type = profile_type_it->value.AsString();
+
+ return ExperimentalFlamegraphGenerator::InputValues{ts, upid, profile_type};
+}
+
+} // namespace
+
+ExperimentalFlamegraphGenerator::ExperimentalFlamegraphGenerator(
+ TraceProcessorContext* context)
+ : context_(context) {}
+
+ExperimentalFlamegraphGenerator::~ExperimentalFlamegraphGenerator() = default;
+
+util::Status ExperimentalFlamegraphGenerator::ValidateConstraints(
+ const QueryConstraints& qc) {
+ using T = tables::ExperimentalFlamegraphNodesTable;
+
+ const auto& cs = qc.constraints();
+
+ auto ts_fn = [](const QueryConstraints::Constraint& c) {
+ return c.column == static_cast<int>(T::ColumnIndex::ts) &&
+ c.op == SQLITE_INDEX_CONSTRAINT_EQ;
+ };
+ bool has_ts_cs = std::find_if(cs.begin(), cs.end(), ts_fn) != cs.end();
+
+ auto upid_fn = [](const QueryConstraints::Constraint& c) {
+ return c.column == static_cast<int>(T::ColumnIndex::upid) &&
+ c.op == SQLITE_INDEX_CONSTRAINT_EQ;
+ };
+ bool has_upid_cs = std::find_if(cs.begin(), cs.end(), upid_fn) != cs.end();
+
+ auto profile_type_fn = [](const QueryConstraints::Constraint& c) {
+ return c.column == static_cast<int>(T::ColumnIndex::profile_type) &&
+ c.op == SQLITE_INDEX_CONSTRAINT_EQ;
+ };
+ bool has_profile_type_cs =
+ std::find_if(cs.begin(), cs.end(), profile_type_fn) != cs.end();
+
+ return has_ts_cs && has_upid_cs && has_profile_type_cs
+ ? util::OkStatus()
+ : util::ErrStatus("Failed to find required constraints");
+}
+
+Table* ExperimentalFlamegraphGenerator::ComputeTable(
+ const std::vector<Constraint>& cs,
+ const std::vector<Order>&) {
+ // Get the input column values and compute the flamegraph using them.
+ auto values = GetInputValues(cs);
+
+ if (values.profile_type == "graph") {
+ auto* tracker = HeapGraphTracker::GetOrCreate(context_);
+ table_ = tracker->BuildFlamegraph(values.ts, values.upid);
+ }
+ if (values.profile_type == "native") {
+ table_ =
+ BuildNativeFlamegraph(context_->storage.get(), values.upid, values.ts);
+ }
+ return table_.get();
+}
+
+Table::Schema ExperimentalFlamegraphGenerator::CreateSchema() {
+ return tables::ExperimentalFlamegraphNodesTable::Schema();
+}
+
+std::string ExperimentalFlamegraphGenerator::TableName() {
+ return "experimental_flamegraph";
+}
+
+uint32_t ExperimentalFlamegraphGenerator::EstimateRowCount() {
+ // TODO(lalitm): return a better estimate here when possible.
+ return 1024;
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/experimental_flamegraph_generator.h b/src/trace_processor/experimental_flamegraph_generator.h
new file mode 100644
index 0000000..b74fa6d
--- /dev/null
+++ b/src/trace_processor/experimental_flamegraph_generator.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_EXPERIMENTAL_FLAMEGRAPH_GENERATOR_H_
+#define SRC_TRACE_PROCESSOR_EXPERIMENTAL_FLAMEGRAPH_GENERATOR_H_
+
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class ExperimentalFlamegraphGenerator
+ : public DbSqliteTable::DynamicTableGenerator {
+ public:
+ struct InputValues {
+ int64_t ts;
+ UniquePid upid;
+ std::string profile_type;
+ };
+
+ ExperimentalFlamegraphGenerator(TraceProcessorContext* context);
+ virtual ~ExperimentalFlamegraphGenerator() override;
+
+ Table::Schema CreateSchema() override;
+ std::string TableName() override;
+ uint32_t EstimateRowCount() override;
+ util::Status ValidateConstraints(const QueryConstraints&) override;
+ Table* ComputeTable(const std::vector<Constraint>& cs,
+ const std::vector<Order>& ob) override;
+
+ private:
+ std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> table_;
+ TraceProcessorContext* context_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_EXPERIMENTAL_FLAMEGRAPH_GENERATOR_H_
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 2a88b57..d84faeb 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -19,6 +19,16 @@
namespace perfetto {
namespace trace_processor {
+base::Optional<base::StringView> GetStaticClassTypeName(base::StringView type) {
+ static const base::StringView kJavaClassTemplate("java.lang.Class<");
+ if (!type.empty() && type.at(type.size() - 1) == '>' &&
+ type.substr(0, kJavaClassTemplate.size()) == kJavaClassTemplate) {
+ return type.substr(kJavaClassTemplate.size(),
+ type.size() - kJavaClassTemplate.size() - 1);
+ }
+ return {};
+}
+
size_t NumberOfArrays(base::StringView type) {
if (type.size() < 2)
return 0;
@@ -32,8 +42,30 @@
return arrays;
}
+NormalizedType GetNormalizedType(base::StringView type) {
+ auto static_class_type_name = GetStaticClassTypeName(type);
+ if (static_class_type_name.has_value()) {
+ type = static_class_type_name.value();
+ }
+ size_t number_of_arrays = NumberOfArrays(type);
+ return {base::StringView(type.data(), type.size() - number_of_arrays * 2),
+ static_class_type_name.has_value(), number_of_arrays};
+}
+
base::StringView NormalizeTypeName(base::StringView type) {
- return base::StringView(type.data(), type.size() - NumberOfArrays(type) * 2);
+ return GetNormalizedType(type).name;
+}
+
+std::string DenormalizeTypeName(NormalizedType normalized,
+ base::StringView deobfuscated_type_name) {
+ std::string result = deobfuscated_type_name.ToStdString();
+ for (size_t i = 0; i < normalized.number_of_arrays; ++i) {
+ result += "[]";
+ }
+ if (normalized.is_static_class) {
+ result = "java.lang.Class<" + result + ">";
+ }
+ return result;
}
HeapGraphTracker::HeapGraphTracker(TraceProcessorContext* context)
@@ -317,26 +349,18 @@
}
StringPool::Id HeapGraphTracker::MaybeDeobfuscate(StringPool::Id id) {
- StringPool::Id normalized_type_id = id;
base::StringView type_name = context_->storage->GetString(id);
- size_t array_count = NumberOfArrays(type_name);
- if (array_count > 0) {
- base::StringView normalized_type = NormalizeTypeName(type_name);
- normalized_type_id = context_->storage->InternString(normalized_type);
- }
- auto it = deobfuscation_mapping_.find(normalized_type_id);
+ auto normalized_type = GetNormalizedType(type_name);
+ auto it = deobfuscation_mapping_.find(
+ context_->storage->InternString(normalized_type.name));
if (it == deobfuscation_mapping_.end())
return id;
- StringPool::Id deobfuscated_name_id = it->second;
- if (array_count == 0)
- return deobfuscated_name_id;
-
- std::string deobfuscated_name =
- context_->storage->GetString(deobfuscated_name_id).ToStdString();
- for (size_t i = 0; i < array_count; ++i)
- deobfuscated_name += "[]";
- return context_->storage->InternString(base::StringView(deobfuscated_name));
+ base::StringView normalized_deobfuscated_name =
+ context_->storage->GetString(it->second);
+ std::string result =
+ DenormalizeTypeName(normalized_type, normalized_deobfuscated_name);
+ return context_->storage->InternString(base::StringView(result));
}
void HeapGraphTracker::AddDeobfuscationMapping(
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index 0da715e..74b2867 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -32,8 +32,17 @@
class TraceProcessorContext;
+struct NormalizedType {
+ base::StringView name;
+ bool is_static_class;
+ size_t number_of_arrays;
+};
+base::Optional<base::StringView> GetStaticClassTypeName(base::StringView type);
size_t NumberOfArrays(base::StringView type);
+NormalizedType GetNormalizedType(base::StringView type);
base::StringView NormalizeTypeName(base::StringView type);
+std::string DenormalizeTypeName(NormalizedType normalized,
+ base::StringView deobfuscated_type_name);
class HeapGraphTracker : public HeapGraphWalker::Delegate, public Destructible {
public:
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
index d00462c..7472818 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
@@ -146,6 +146,8 @@
static const char kDoubleArray[] = "X[][]";
static const char kNoArray[] = "X";
static const char kLongNoArray[] = "ABCDE";
+static const char kStaticClassNoArray[] = "java.lang.Class<abc>";
+static const char kStaticClassArray[] = "java.lang.Class<abc[]>";
TEST(HeapGraphTrackerTest, NormalizeTypeName) {
// sizeof(...) - 1 below to get rid of the null-byte.
@@ -163,6 +165,14 @@
base::StringView(kLongNoArray, sizeof(kLongNoArray) - 1))
.ToStdString(),
"ABCDE");
+ EXPECT_EQ(NormalizeTypeName(base::StringView(kStaticClassNoArray,
+ sizeof(kStaticClassNoArray) - 1))
+ .ToStdString(),
+ "abc");
+ EXPECT_EQ(NormalizeTypeName(base::StringView(kStaticClassArray,
+ sizeof(kStaticClassArray) - 1))
+ .ToStdString(),
+ "abc");
}
TEST(HeapGraphTrackerTest, NumberOfArray) {
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 98abb9b..20c2603 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -161,6 +161,8 @@
flow_direction_value_inout_id_(context->storage->InternString("inout")),
chrome_user_event_action_args_key_id_(
context->storage->InternString("user_event.action")),
+ chrome_user_event_action_hash_args_key_id_(
+ context->storage->InternString("user_event.action_hash")),
chrome_legacy_ipc_class_args_key_id_(
context->storage->InternString("legacy_ipc.class")),
chrome_legacy_ipc_line_args_key_id_(
@@ -175,6 +177,20 @@
context->storage->InternString("histogram_sample.sample")),
chrome_latency_info_trace_id_key_id_(
context->storage->InternString("latency_info.trace_id")),
+ chrome_latency_info_step_key_id_(
+ context->storage->InternString("latency_info.step")),
+ chrome_latency_info_frame_tree_node_id_key_id_(
+ context->storage->InternString("latency_info.frame_tree_node_id")),
+ chrome_latency_info_step_ids_{
+ {context->storage->InternString("STEP_UNSPECIFIED"),
+ context->storage->InternString(
+ "STEP_HANDLE_INPUT_EVENT_MAIN_COMMIT"),
+ context->storage->InternString("STEP_MAIN_THREAD_SCROLL_UPDATE"),
+ context->storage->InternString("STEP_SEND_INPUT_EVENT_UI"),
+ context->storage->InternString("STEP_HANDLE_INPUT_EVENT_MAIN"),
+ context->storage->InternString("STEP_HANDLE_INPUT_EVENT_IMPL"),
+ context->storage->InternString("STEP_SWAP_BUFFERS"),
+ context->storage->InternString("STEP_DRAW_AND_SWAP")}},
chrome_legacy_ipc_class_ids_{
{context->storage->InternString("UNSPECIFIED"),
context->storage->InternString("AUTOMATION"),
@@ -1314,6 +1330,10 @@
inserter->AddArg(chrome_user_event_action_args_key_id_,
Variadic::String(action_id));
}
+ if (event.has_action_hash()) {
+ inserter->AddArg(chrome_user_event_action_hash_args_key_id_,
+ Variadic::UnsignedInteger(event.action_hash()));
+ }
}
void TrackEventParser::ParseChromeLegacyIpc(
@@ -1359,6 +1379,18 @@
inserter->AddArg(chrome_latency_info_trace_id_key_id_,
Variadic::Integer(event.trace_id()));
}
+ if (event.has_step()) {
+ size_t step_index = static_cast<size_t>(event.step());
+ if (step_index >= chrome_latency_info_step_ids_.size())
+ step_index = 0;
+ inserter->AddArg(
+ chrome_latency_info_step_key_id_,
+ Variadic::String(chrome_latency_info_step_ids_[step_index]));
+ }
+ if (event.has_frame_tree_node_id()) {
+ inserter->AddArg(chrome_latency_info_frame_tree_node_id_key_id_,
+ Variadic::Integer(event.frame_tree_node_id()));
+ }
}
void TrackEventParser::ParseChromeHistogramSample(
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
index 8cca814..ae9c3af 100644
--- a/src/trace_processor/importers/proto/track_event_parser.h
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -131,6 +131,7 @@
const StringId flow_direction_value_out_id_;
const StringId flow_direction_value_inout_id_;
const StringId chrome_user_event_action_args_key_id_;
+ const StringId chrome_user_event_action_hash_args_key_id_;
const StringId chrome_legacy_ipc_class_args_key_id_;
const StringId chrome_legacy_ipc_line_args_key_id_;
const StringId chrome_keyed_service_name_args_key_id_;
@@ -138,7 +139,10 @@
const StringId chrome_histogram_sample_name_args_key_id_;
const StringId chrome_histogram_sample_sample_args_key_id_;
const StringId chrome_latency_info_trace_id_key_id_;
+ const StringId chrome_latency_info_step_key_id_;
+ const StringId chrome_latency_info_frame_tree_node_id_key_id_;
+ std::array<StringId, 8> chrome_latency_info_step_ids_;
std::array<StringId, 38> chrome_legacy_ipc_class_ids_;
std::array<StringId, 9> chrome_process_name_ids_;
std::array<StringId, 14> chrome_thread_name_ids_;
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index c0133a5..31edbf9 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -48,8 +48,10 @@
static SystraceTracePoint C(uint32_t tgid,
base::StringView name,
- double value) {
- return SystraceTracePoint('C', tgid, std::move(name), value);
+ double value,
+ base::StringView category_group = "") {
+ return SystraceTracePoint('C', tgid, std::move(name), value,
+ category_group);
}
static SystraceTracePoint S(uint32_t tgid,
@@ -64,8 +66,12 @@
return SystraceTracePoint('F', tgid, std::move(name), value);
}
- SystraceTracePoint(char p, uint32_t tg, base::StringView n, double v)
- : phase(p), tgid(tg), name(std::move(n)), value(v) {}
+ SystraceTracePoint(char p,
+ uint32_t tg,
+ base::StringView n,
+ double v,
+ base::StringView c = "")
+ : phase(p), tgid(tg), name(std::move(n)), value(v), category_group(c) {}
// Phase can be one of B, E, C, S, F.
char phase = '\0';
@@ -78,6 +84,9 @@
// For phase = 'C' only.
double value = 0;
+ // For phase = 'C' only (from Chrome).
+ base::StringView category_group;
+
// Visible for unittesting.
friend std::ostream& operator<<(std::ostream& os,
const SystraceTracePoint& point) {
@@ -94,6 +103,7 @@
// 4. C|1636|wq:monitor|0
// 5. S|1636|frame_capture|123
// 6. F|1636|frame_capture|456
+// 7. C|3209|TransfersBytesPendingOnDisk-value|0|Blob
// Visible for unittesting.
inline SystraceParseResult ParseSystraceTracePoint(base::StringView str,
SystraceTracePoint* out) {
@@ -160,10 +170,13 @@
out->name = base::StringView(s + name_index, name_length.value());
size_t value_index = name_index + name_length.value() + 1;
- size_t value_len = len - value_index;
+ size_t value_pipe = str.find('|', value_index);
+ size_t value_len = value_pipe == base::StringView::npos
+ ? len - value_index
+ : value_pipe - value_index;
if (value_len == 0)
return SystraceParseResult::kFailure;
- if (*(s + value_index + value_len - 1) == '\n')
+ if (s[value_index + value_len - 1] == '\n')
value_len--;
std::string value_str(s + value_index, value_len);
base::Optional<double> maybe_value = base::StringToDouble(value_str);
@@ -171,6 +184,16 @@
return SystraceParseResult::kFailure;
}
out->value = maybe_value.value();
+
+ if (value_pipe != base::StringView::npos) {
+ size_t group_len = len - value_pipe - 1;
+ if (group_len == 0)
+ return SystraceParseResult::kFailure;
+ if (s[len - 1] == '\n')
+ group_len--;
+ out->category_group = base::StringView(s + value_pipe + 1, group_len);
+ }
+
return SystraceParseResult::kSuccess;
}
default:
diff --git a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
index 55d42cc..2c51906 100644
--- a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
@@ -60,6 +60,11 @@
ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|8", &result), Result::kSuccess);
EXPECT_EQ(result, SystraceTracePoint::C(543, "foo", 8));
+ ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|8|", &result), Result::kFailure);
+ ASSERT_EQ(ParseSystraceTracePoint("C|543|foo|8|group", &result),
+ Result::kSuccess);
+ EXPECT_EQ(result, SystraceTracePoint::C(543, "foo", 8, "group"));
+
ASSERT_EQ(ParseSystraceTracePoint("S|", &result), Result::kFailure);
ASSERT_EQ(ParseSystraceTracePoint("S|123|foo|456", &result),
diff --git a/src/trace_processor/metrics/android/unmapped_java_symbols.sql b/src/trace_processor/metrics/android/unmapped_java_symbols.sql
index b7d4560..0fc8443 100644
--- a/src/trace_processor/metrics/android/unmapped_java_symbols.sql
+++ b/src/trace_processor/metrics/android/unmapped_java_symbols.sql
@@ -20,7 +20,9 @@
WITH distinct_unmapped_type_names AS (
SELECT DISTINCT upid, type_name
FROM (
- SELECT upid, RTRIM(type_name, '[]') AS type_name
+ SELECT
+ upid,
+ RTRIM(REPLACE(type_name, 'java.lang.Class<', ''), '[]>') AS type_name
FROM heap_graph_object
WHERE deobfuscated_type_name IS NULL
)
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index a98e85d..d4094a9 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -86,7 +86,9 @@
DbSqliteTable::DbSqliteTable(sqlite3*, Context context)
: cache_(context.cache),
schema_(std::move(context.schema)),
- table_(context.table) {}
+ computation_(context.computation),
+ static_table_(context.static_table),
+ generator_(std::move(context.generator)) {}
DbSqliteTable::~DbSqliteTable() = default;
void DbSqliteTable::RegisterTable(sqlite3* db,
@@ -94,8 +96,19 @@
Table::Schema schema,
const Table* table,
const std::string& name) {
- SqliteTable::Register<DbSqliteTable, Context>(
- db, Context{cache, schema, table}, name);
+ Context context{cache, schema, TableComputation::kStatic, table, nullptr};
+ SqliteTable::Register<DbSqliteTable, Context>(db, std::move(context), name);
+}
+
+void DbSqliteTable::RegisterTable(
+ sqlite3* db,
+ QueryCache* cache,
+ std::unique_ptr<DynamicTableGenerator> generator) {
+ Table::Schema schema = generator->CreateSchema();
+ std::string name = generator->TableName();
+ Context context{cache, std::move(schema), TableComputation::kDynamic, nullptr,
+ std::move(generator)};
+ SqliteTable::Register<DbSqliteTable, Context>(db, std::move(context), name);
}
util::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) {
@@ -108,7 +121,7 @@
std::vector<SqliteTable::Column> schema_cols;
for (uint32_t i = 0; i < schema.columns.size(); ++i) {
const auto& col = schema.columns[i];
- schema_cols.emplace_back(i, col.name, col.type);
+ schema_cols.emplace_back(i, col.name, col.type, col.is_hidden);
}
// TODO(lalitm): this is hardcoded to be the id column but change this to be
@@ -129,7 +142,17 @@
}
int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
- BestIndex(schema_, table_->row_count(), qc, info);
+ switch (computation_) {
+ case TableComputation::kStatic:
+ BestIndex(schema_, static_table_->row_count(), qc, info);
+ break;
+ case TableComputation::kDynamic:
+ util::Status status = generator_->ValidateConstraints(qc);
+ if (!status.ok())
+ return SQLITE_CONSTRAINT;
+ BestIndex(schema_, generator_->EstimateRowCount(), qc, info);
+ break;
+ }
return SQLITE_OK;
}
@@ -291,15 +314,13 @@
}
std::unique_ptr<SqliteTable::Cursor> DbSqliteTable::CreateCursor() {
- return std::unique_ptr<Cursor>(new Cursor(this, cache_, table_));
+ return std::unique_ptr<Cursor>(new Cursor(this, cache_));
}
-DbSqliteTable::Cursor::Cursor(SqliteTable* sqlite_table,
- QueryCache* cache,
- const Table* table)
+DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
: SqliteTable::Cursor(sqlite_table),
- cache_(cache),
- initial_db_table_(table) {}
+ db_sqlite_table_(sqlite_table),
+ cache_(cache) {}
void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
const QueryConstraints& qc,
@@ -314,7 +335,7 @@
// Check if the new constraint set is cached by another cursor.
sorted_cache_table_ =
- cache_->GetIfCached(initial_db_table_, qc.constraints());
+ cache_->GetIfCached(upstream_table_, qc.constraints());
return;
}
@@ -343,13 +364,13 @@
// If the column is already sorted, we don't need to cache at all.
uint32_t col = static_cast<uint32_t>(c.column);
- if (initial_db_table_->GetColumn(col).IsSorted())
+ if (upstream_table_->GetColumn(col).IsSorted())
return;
// Try again to get the result or start caching it.
sorted_cache_table_ =
- cache_->GetOrCache(initial_db_table_, qc.constraints(), [this, col]() {
- return initial_db_table_->Sort({Order{col, false}});
+ cache_->GetOrCache(upstream_table_, qc.constraints(), [this, col]() {
+ return upstream_table_->Sort({Order{col, false}});
});
}
@@ -386,6 +407,23 @@
orders_[i] = Order{col, static_cast<bool>(ob.desc)};
}
+ // Setup the upstream table based on the computation state.
+ switch (db_sqlite_table_->computation_) {
+ case TableComputation::kStatic:
+ // If we have a static table, just set the upstream table to be the static
+ // table.
+ upstream_table_ = db_sqlite_table_->static_table_;
+ break;
+ case TableComputation::kDynamic:
+ // If we have a dynamically created table, regenerate the table based on
+ // the new constraints.
+ upstream_table_ =
+ db_sqlite_table_->generator_->ComputeTable(constraints_, orders_);
+ if (!upstream_table_)
+ return SQLITE_CONSTRAINT;
+ break;
+ }
+
// Tries to create a sorted cached table which can be used to speed up
// filters below.
TryCacheCreateSortedTable(qc, history);
@@ -482,5 +520,7 @@
return SQLITE_OK;
}
+DbSqliteTable::DynamicTableGenerator::~DynamicTableGenerator() = default;
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index a020008..cfb5003 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -27,9 +27,54 @@
// Implements the SQLite table interface for db tables.
class DbSqliteTable : public SqliteTable {
public:
+ enum class TableComputation {
+ // Mode when the table is static (i.e. passed in at construction
+ // time).
+ kStatic,
+
+ // Mode when table is dynamically computed at filter time.
+ kDynamic,
+ };
+
+ // Interface which can be subclassed to allow generation of tables dynamically
+ // at filter time.
+ // This class is used to implement table-valued functions and other similar
+ // tables.
+ class DynamicTableGenerator {
+ public:
+ virtual ~DynamicTableGenerator();
+
+ // Returns the schema of the table that will be returned by ComputeTable.
+ virtual Table::Schema CreateSchema() = 0;
+
+ // Returns the name of the dynamic table.
+ // This will be used to register the table with SQLite.
+ virtual std::string TableName() = 0;
+
+ // Returns the estimated number of rows the table would generate.
+ virtual uint32_t EstimateRowCount() = 0;
+
+ // Checks that the constraint set is valid.
+ //
+ // Returning util::OkStatus means that the required constraints are present
+ // in |qc| for dynamically computing the table (e.g. any required
+ // constraints on hidden columns for table-valued functions are present).
+ virtual util::Status ValidateConstraints(const QueryConstraints& qc) = 0;
+
+ // Dynamically computes the table given the constraints and order by
+ // vectors.
+ //
+ // Implementations should store the generated table inside a stable pointer
+ // (e.g. unique_ptr, shared_ptr or similar) and return the pointer to that
+ // object. The pointer should not be freed until a successive call to
+ // |ComputeTable|.
+ virtual Table* ComputeTable(const std::vector<Constraint>& cs,
+ const std::vector<Order>& ob) = 0;
+ };
+
class Cursor : public SqliteTable::Cursor {
public:
- Cursor(SqliteTable*, QueryCache*, const Table*);
+ Cursor(DbSqliteTable*, QueryCache*);
Cursor(Cursor&&) noexcept = default;
Cursor& operator=(Cursor&&) = default;
@@ -42,12 +87,6 @@
int Eof() override;
int Column(sqlite3_context*, int N) override;
- protected:
- // Sets the table this class uses as the reference for all filter
- // operations. Should be immediately followed by a call to Filter with
- // |FilterHistory::kDifferent|.
- void set_table(const Table* table) { initial_db_table_ = table; }
-
private:
enum class Mode {
kSingleRow,
@@ -61,14 +100,16 @@
const Table* SourceTable() const {
// Try and use the sorted cache table (if it exists) to speed up the
// sorting. Otherwise, just use the original table.
- return sorted_cache_table_ ? &*sorted_cache_table_ : initial_db_table_;
+ return sorted_cache_table_ ? &*sorted_cache_table_ : upstream_table_;
}
Cursor(const Cursor&) = delete;
Cursor& operator=(const Cursor&) = delete;
+ DbSqliteTable* db_sqlite_table_ = nullptr;
QueryCache* cache_ = nullptr;
- const Table* initial_db_table_ = nullptr;
+
+ const Table* upstream_table_ = nullptr;
// Only valid for Mode::kSingleRow.
base::Optional<uint32_t> single_row_;
@@ -100,7 +141,13 @@
struct Context {
QueryCache* cache;
Table::Schema schema;
- const Table* table;
+ TableComputation computation;
+
+ // Only valid when computation == TableComputation::kStatic.
+ const Table* static_table;
+
+ // Only valid when computation == TableComputation::kDynamic.
+ std::unique_ptr<DynamicTableGenerator> generator;
};
static void RegisterTable(sqlite3* db,
@@ -109,6 +156,10 @@
const Table* table,
const std::string& name);
+ static void RegisterTable(sqlite3* db,
+ QueryCache* cache,
+ std::unique_ptr<DynamicTableGenerator> generator);
+
DbSqliteTable(sqlite3*, Context context);
virtual ~DbSqliteTable() override;
@@ -138,7 +189,14 @@
private:
QueryCache* cache_ = nullptr;
Table::Schema schema_;
- const Table* table_ = nullptr;
+
+ TableComputation computation_ = TableComputation::kStatic;
+
+ // Only valid when computation_ == TableComputation::kStatic.
+ const Table* static_table_ = nullptr;
+
+ // Only valid when computation_ == TableComputation::kDynamic.
+ std::unique_ptr<DynamicTableGenerator> generator_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
index ace779f..78b6961 100644
--- a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -24,16 +25,16 @@
Table::Schema CreateSchema() {
Table::Schema schema;
- schema.columns.push_back(
- {"id", SqlValue::Type::kLong, true /* is_id */, true /* is_sorted */});
+ schema.columns.push_back({"id", SqlValue::Type::kLong, true /* is_id */,
+ true /* is_sorted */, false /* is_hidden */});
schema.columns.push_back({"type", SqlValue::Type::kLong, false /* is_id */,
- false /* is_sorted */});
+ false /* is_sorted */, false /* is_hidden */});
schema.columns.push_back({"test1", SqlValue::Type::kLong, false /* is_id */,
- true /* is_sorted */});
+ true /* is_sorted */, false /* is_hidden */});
schema.columns.push_back({"test2", SqlValue::Type::kLong, false /* is_id */,
- false /* is_sorted */});
+ false /* is_sorted */, false /* is_hidden */});
schema.columns.push_back({"test3", SqlValue::Type::kLong, false /* is_id */,
- false /* is_sorted */});
+ false /* is_sorted */, false /* is_hidden */});
return schema;
}
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
index 2cb7a0f..a6a038f 100644
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ b/src/trace_processor/sqlite/sqlite_table.h
@@ -20,6 +20,7 @@
#include <sqlite3.h>
#include <functional>
+#include <limits>
#include <memory>
#include <string>
#include <vector>
@@ -212,7 +213,7 @@
auto create_fn = [](sqlite3* xdb, void* arg, int argc,
const char* const* argv, sqlite3_vtab** tab,
char** pzErr) {
- const auto* xdesc = static_cast<const TableDescriptor<Context>*>(arg);
+ auto* xdesc = static_cast<TableDescriptor<Context>*>(arg);
auto table = xdesc->factory(xdb, std::move(xdesc->context));
table->name_ = xdesc->name;
diff --git a/src/trace_processor/sqlite_experimental_counter_dur_table.cc b/src/trace_processor/sqlite_experimental_counter_dur_table.cc
deleted file mode 100644
index bf5cf7b..0000000
--- a/src/trace_processor/sqlite_experimental_counter_dur_table.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/sqlite_experimental_counter_dur_table.h"
-
-#include "src/trace_processor/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-SqliteExperimentalCounterDurTable::SqliteExperimentalCounterDurTable(
- sqlite3* db,
- Context context)
- : DbSqliteTable(db,
- {context.cache, std::move(context.schema), context.table}),
- cache_(context.cache),
- counter_table_(context.table) {}
-
-SqliteExperimentalCounterDurTable::~SqliteExperimentalCounterDurTable() =
- default;
-
-void SqliteExperimentalCounterDurTable::RegisterTable(
- sqlite3* db,
- QueryCache* cache,
- const tables::CounterTable& table) {
- // Add the dur column to the counter schema and use that as the base schema.
- auto schema = tables::CounterTable::Schema();
- schema.columns.push_back(
- {"dur", SqlValue::Type::kLong, false /* is_id */, false /* is_sorted */});
-
- SqliteTable::Register<SqliteExperimentalCounterDurTable>(
- db, Context{cache, std::move(schema), &table},
- "experimental_counter_dur");
-}
-
-std::unique_ptr<SqliteTable::Cursor>
-SqliteExperimentalCounterDurTable::CreateCursor() {
- std::unique_ptr<TableAndColumn> table_and_column(new TableAndColumn());
- table_and_column->dur = ComputeDurColumn(*counter_table_);
- table_and_column->table = counter_table_->ExtendWithColumn(
- "dur", &table_and_column->dur, TypedColumn<int64_t>::default_flags());
- return std::unique_ptr<Cursor>(
- new Cursor(this, cache_, std::move(table_and_column)));
-}
-
-// static
-SparseVector<int64_t> SqliteExperimentalCounterDurTable::ComputeDurColumn(
- const tables::CounterTable& table) {
- // Keep track of the last seen row for each track id.
- std::unordered_map<TrackId, uint32_t> last_row_for_track_id;
- SparseVector<int64_t> dur;
- for (uint32_t i = 0; i < table.row_count(); ++i) {
- // Check if we already have a previous row for the current track id.
- TrackId track_id = table.track_id()[i];
- auto it = last_row_for_track_id.find(track_id);
- if (it == last_row_for_track_id.end()) {
- // This means we don't have any row - start tracking this row for the
- // future.
- last_row_for_track_id.emplace(track_id, i);
- } else {
- // This means we have an previous row for the current track id. Update
- // the duration of the previous row to be up to the current ts.
- uint32_t old_row = it->second;
- it->second = i;
- dur.Set(old_row, table.ts()[i] - table.ts()[old_row]);
- }
- // Append -1 to make this event as not having been finished. On a later
- // row, we may set this to have the correct value.
- dur.Append(-1);
- }
- return dur;
-}
-
-SqliteExperimentalCounterDurTable::Cursor::Cursor(
- SqliteTable* sqlite_table,
- QueryCache* cache,
- std::unique_ptr<TableAndColumn> table_and_column)
- : DbSqliteTable::Cursor(sqlite_table, cache, &table_and_column->table),
- table_and_column_(std::move(table_and_column)) {}
-
-SqliteExperimentalCounterDurTable::Cursor::~Cursor() = default;
-
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/sqlite_experimental_counter_dur_table.h b/src/trace_processor/sqlite_experimental_counter_dur_table.h
deleted file mode 100644
index b38201b..0000000
--- a/src/trace_processor/sqlite_experimental_counter_dur_table.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_EXPERIMENTAL_COUNTER_DUR_TABLE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_EXPERIMENTAL_COUNTER_DUR_TABLE_H_
-
-#include "src/trace_processor/sqlite/db_sqlite_table.h"
-
-#include "src/trace_processor/storage/trace_storage.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class SqliteExperimentalCounterDurTable : public DbSqliteTable {
- public:
- struct Context {
- QueryCache* cache;
- Table::Schema schema;
- const tables::CounterTable* table;
- };
-
- struct TableAndColumn {
- Table table;
- SparseVector<int64_t> dur;
- };
-
- class Cursor : public DbSqliteTable::Cursor {
- public:
- Cursor(SqliteTable*,
- QueryCache* cache,
- std::unique_ptr<TableAndColumn> table_and_column);
- ~Cursor() override;
-
- private:
- std::unique_ptr<TableAndColumn> table_and_column_;
- };
-
- SqliteExperimentalCounterDurTable(sqlite3*, Context context);
- ~SqliteExperimentalCounterDurTable() override;
-
- static void RegisterTable(sqlite3* db,
- QueryCache* cache,
- const tables::CounterTable& table);
-
- // SqliteTable implementation.
- std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-
- static SparseVector<int64_t> ComputeDurColumn(
- const tables::CounterTable& table);
-
- private:
- QueryCache* cache_ = nullptr;
- const tables::CounterTable* counter_table_ = nullptr;
-};
-
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_SQLITE_EXPERIMENTAL_COUNTER_DUR_TABLE_H_
diff --git a/src/trace_processor/sqlite_experimental_flamegraph_table.cc b/src/trace_processor/sqlite_experimental_flamegraph_table.cc
deleted file mode 100644
index 29364ce..0000000
--- a/src/trace_processor/sqlite_experimental_flamegraph_table.cc
+++ /dev/null
@@ -1,179 +0,0 @@
-
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/sqlite_experimental_flamegraph_table.h"
-
-#include "src/trace_processor/heap_profile_tracker.h"
-#include "src/trace_processor/importers/proto/heap_graph_tracker.h"
-#include "src/trace_processor/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-namespace {
-
-SqliteExperimentalFlamegraphTable::InputValues GetInputValues(
- const QueryConstraints& qc,
- sqlite3_value** argv) {
- using T = tables::ExperimentalFlamegraphNodesTable;
-
- const auto& cs = qc.constraints();
-
- auto ts_fn = [](const QueryConstraints::Constraint& c) {
- return c.column == static_cast<int>(T::ColumnIndex::ts) &&
- c.op == SQLITE_INDEX_CONSTRAINT_EQ;
- };
- auto upid_fn = [](const QueryConstraints::Constraint& c) {
- return c.column == static_cast<int>(T::ColumnIndex::upid) &&
- c.op == SQLITE_INDEX_CONSTRAINT_EQ;
- };
- auto profile_type_fn = [](const QueryConstraints::Constraint& c) {
- return c.column == static_cast<int>(T::ColumnIndex::profile_type) &&
- c.op == SQLITE_INDEX_CONSTRAINT_EQ;
- };
-
- auto ts_idx = static_cast<uint32_t>(
- std::distance(cs.begin(), std::find_if(cs.begin(), cs.end(), ts_fn)));
- auto upid_idx = static_cast<uint32_t>(
- std::distance(cs.begin(), std::find_if(cs.begin(), cs.end(), upid_fn)));
- auto profile_type_idx = static_cast<uint32_t>(std::distance(
- cs.begin(), std::find_if(cs.begin(), cs.end(), profile_type_fn)));
-
- // We should always have valid indices here because BestIndex should only
- // allow the constraint set to be chosen when we have an equality constraint
- // on both ts and upid.
- PERFETTO_CHECK(ts_idx < cs.size());
- PERFETTO_CHECK(upid_idx < cs.size());
- PERFETTO_CHECK(profile_type_idx < cs.size());
-
- int64_t ts = sqlite3_value_int64(argv[ts_idx]);
- UniquePid upid = static_cast<UniquePid>(sqlite3_value_int64(argv[upid_idx]));
- std::string profile_type =
- reinterpret_cast<const char*>(sqlite3_value_text(argv[profile_type_idx]));
-
- return SqliteExperimentalFlamegraphTable::InputValues{ts, upid, profile_type};
-}
-
-} // namespace
-
-SqliteExperimentalFlamegraphTable::SqliteExperimentalFlamegraphTable(
- sqlite3*,
- TraceProcessorContext* context)
- : context_(context) {}
-
-SqliteExperimentalFlamegraphTable::~SqliteExperimentalFlamegraphTable() =
- default;
-
-void SqliteExperimentalFlamegraphTable::RegisterTable(
- sqlite3* db,
- TraceProcessorContext* context) {
- SqliteTable::Register<SqliteExperimentalFlamegraphTable>(
- db, context, "experimental_flamegraph");
-}
-
-util::Status SqliteExperimentalFlamegraphTable::Init(
- int,
- const char* const*,
- SqliteTable::Schema* schema) {
- // Create an empty table for the sake of getting the schema.
- *schema = DbSqliteTable::ComputeSchema(
- tables::ExperimentalFlamegraphNodesTable::Schema(), name().c_str());
-
- using T = tables::ExperimentalFlamegraphNodesTable;
-
- // TODO(lalitm): make it so that this happens on the macro table itself.
- auto& cols = *schema->mutable_columns();
- cols[static_cast<uint32_t>(T::ColumnIndex::ts)].set_hidden(true);
- cols[static_cast<uint32_t>(T::ColumnIndex::upid)].set_hidden(true);
- cols[static_cast<uint32_t>(T::ColumnIndex::profile_type)].set_hidden(true);
-
- return util::OkStatus();
-}
-
-int SqliteExperimentalFlamegraphTable::BestIndex(const QueryConstraints& qc,
- BestIndexInfo*) {
- using T = tables::ExperimentalFlamegraphNodesTable;
-
- const auto& cs = qc.constraints();
-
- auto ts_fn = [](const QueryConstraints::Constraint& c) {
- return c.column == static_cast<int>(T::ColumnIndex::ts) &&
- c.op == SQLITE_INDEX_CONSTRAINT_EQ;
- };
- bool has_ts_cs = std::find_if(cs.begin(), cs.end(), ts_fn) != cs.end();
-
- auto upid_fn = [](const QueryConstraints::Constraint& c) {
- return c.column == static_cast<int>(T::ColumnIndex::upid) &&
- c.op == SQLITE_INDEX_CONSTRAINT_EQ;
- };
- bool has_upid_cs = std::find_if(cs.begin(), cs.end(), upid_fn) != cs.end();
-
- auto profile_type_fn = [](const QueryConstraints::Constraint& c) {
- return c.column == static_cast<int>(T::ColumnIndex::profile_type) &&
- c.op == SQLITE_INDEX_CONSTRAINT_EQ;
- };
- bool has_profile_type_cs =
- std::find_if(cs.begin(), cs.end(), profile_type_fn) != cs.end();
-
- return has_ts_cs && has_upid_cs && has_profile_type_cs ? SQLITE_OK
- : SQLITE_CONSTRAINT;
-}
-
-std::unique_ptr<SqliteTable::Cursor>
-SqliteExperimentalFlamegraphTable::CreateCursor() {
- return std::unique_ptr<Cursor>(new Cursor(this, context_));
-}
-
-SqliteExperimentalFlamegraphTable::Cursor::Cursor(
- SqliteTable* sqlite_table,
- TraceProcessorContext* context)
- : DbSqliteTable::Cursor(sqlite_table, nullptr, nullptr),
- context_(context) {}
-
-int SqliteExperimentalFlamegraphTable::Cursor::Filter(
- const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory) {
- // Extract the old table to free after we call the parent Filter function.
- // We need to do this to make sure that we don't get a use-after-free for
- // any pointers the parent is holding onto in this table.
- auto old_table = std::move(table_);
-
- // Get the input column values and compute the flamegraph using them.
- values_ = GetInputValues(qc, argv);
-
- if (values_.profile_type == "graph") {
- auto* tracker = HeapGraphTracker::GetOrCreate(context_);
- table_ = tracker->BuildFlamegraph(values_.ts, values_.upid);
- }
- if (values_.profile_type == "native") {
- table_ = BuildNativeFlamegraph(context_->storage.get(),
- values_.upid, values_.ts);
- }
-
- // table_ can be nullptr precisely where the constraints passed to us don't
- // make sense. Therefore, we can just return this to SQLite.
- if (!table_)
- return SQLITE_CONSTRAINT;
-
- // Set the table in the parent to the correct value and then filter.
- DbSqliteTable::Cursor::set_table(table_.get());
- return DbSqliteTable::Cursor::Filter(qc, argv, FilterHistory::kDifferent);
-}
-
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/sqlite_experimental_flamegraph_table.h b/src/trace_processor/sqlite_experimental_flamegraph_table.h
deleted file mode 100644
index 5bd9731..0000000
--- a/src/trace_processor/sqlite_experimental_flamegraph_table.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_EXPERIMENTAL_FLAMEGRAPH_TABLE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_EXPERIMENTAL_FLAMEGRAPH_TABLE_H_
-
-#include "src/trace_processor/sqlite/db_sqlite_table.h"
-
-#include "src/trace_processor/storage/trace_storage.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class TraceProcessorContext;
-
-class SqliteExperimentalFlamegraphTable : public SqliteTable {
- public:
- struct InputValues {
- int64_t ts;
- UniquePid upid;
- std::string profile_type;
- };
-
- class Cursor : public DbSqliteTable::Cursor {
- public:
- Cursor(SqliteTable*, TraceProcessorContext*);
-
- int Filter(const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory) override;
-
- private:
- TraceProcessorContext* context_ = nullptr;
-
- std::unique_ptr<Table> table_;
- InputValues values_ = {};
- };
-
- SqliteExperimentalFlamegraphTable(sqlite3*, TraceProcessorContext*);
- ~SqliteExperimentalFlamegraphTable() override;
-
- static void RegisterTable(sqlite3* db, TraceProcessorContext* storage);
-
- // SqliteTable implementation.
- util::Status Init(int,
- const char* const*,
- SqliteTable::Schema*) override final;
- std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
- int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
-
- private:
- friend class Cursor;
-
- TraceProcessorContext* context_;
-};
-
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_SQLITE_EXPERIMENTAL_FLAMEGRAPH_TABLE_H_
diff --git a/src/trace_processor/sqlite_raw_table.cc b/src/trace_processor/sqlite_raw_table.cc
index 1bf2ead..0997657 100644
--- a/src/trace_processor/sqlite_raw_table.cc
+++ b/src/trace_processor/sqlite_raw_table.cc
@@ -53,9 +53,10 @@
} // namespace
SqliteRawTable::SqliteRawTable(sqlite3* db, Context context)
- : DbSqliteTable(db,
- {context.cache, tables::RawTable::Schema(),
- &context.storage->raw_table()}),
+ : DbSqliteTable(
+ db,
+ {context.cache, tables::RawTable::Schema(), TableComputation::kStatic,
+ &context.storage->raw_table(), nullptr}),
storage_(context.storage) {
auto fn = [](sqlite3_context* ctx, int argc, sqlite3_value** argv) {
auto* thiz = static_cast<SqliteRawTable*>(sqlite3_user_data(ctx));
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index e521e4a..60b41c7 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -234,7 +234,9 @@
schema.columns.emplace_back(Table::Schema::Column{ \
#name, TypedColumn<type>::SqlValueType(), false, \
static_cast<bool>(FlagsForColumn(ColumnIndex::name) & \
- Column::Flag::kSorted)});
+ Column::Flag::kSorted), \
+ static_cast<bool>(FlagsForColumn(ColumnIndex::name) & \
+ Column::Flag::kHidden)});
// Defines the accessors for a column.
#define PERFETTO_TP_TABLE_COL_ACCESSOR(type, name, ...) \
@@ -374,10 +376,10 @@
\
static Table::Schema Schema() { \
Table::Schema schema; \
- schema.columns.emplace_back( \
- Table::Schema::Column{"id", SqlValue::Type::kLong, true, true}); \
schema.columns.emplace_back(Table::Schema::Column{ \
- "type", SqlValue::Type::kString, false, false}); \
+ "id", SqlValue::Type::kLong, true, true, false}); \
+ schema.columns.emplace_back(Table::Schema::Column{ \
+ "type", SqlValue::Type::kString, false, false, false}); \
PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_COLUMN_SCHEMA); \
return schema; \
} \
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
index 0ac47a9..1fe5d9a 100644
--- a/src/trace_processor/tables/profiler_tables.h
+++ b/src/trace_processor/tables/profiler_tables.h
@@ -88,9 +88,9 @@
#define PERFETTO_TP_EXPERIMENTAL_FLAMEGRAPH_NODES(NAME, PARENT, C) \
NAME(ExperimentalFlamegraphNodesTable, "experimental_flamegraph_nodes") \
PERFETTO_TP_ROOT_TABLE(PARENT, C) \
- C(int64_t, ts, Column::Flag::kSorted) \
- C(uint32_t, upid) \
- C(StringPool::Id, profile_type) \
+ C(int64_t, ts, Column::Flag::kSorted | Column::Flag::kHidden) \
+ C(uint32_t, upid, Column::Flag::kHidden) \
+ C(StringPool::Id, profile_type, Column::Flag::kHidden) \
C(uint32_t, depth) \
C(StringPool::Id, name) \
C(StringPool::Id, map_name) \
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 4a43cb9..7b6164a 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -24,6 +24,8 @@
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/additional_modules.h"
+#include "src/trace_processor/experimental_counter_dur_generator.h"
+#include "src/trace_processor/experimental_flamegraph_generator.h"
#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
#include "src/trace_processor/metadata_tracker.h"
#include "src/trace_processor/sql_stats_table.h"
@@ -32,8 +34,6 @@
#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/sqlite/window_operator_table.h"
-#include "src/trace_processor/sqlite_experimental_counter_dur_table.h"
-#include "src/trace_processor/sqlite_experimental_flamegraph_table.h"
#include "src/trace_processor/sqlite_raw_table.h"
#include "src/trace_processor/stats_table.h"
#include "src/trace_processor/types/variadic.h"
@@ -417,11 +417,14 @@
WindowOperatorTable::RegisterTable(*db_, storage);
// New style tables but with some custom logic.
- SqliteExperimentalFlamegraphTable::RegisterTable(*db_, &context_);
SqliteRawTable::RegisterTable(*db_, query_cache_.get(),
context_.storage.get());
- SqliteExperimentalCounterDurTable::RegisterTable(*db_, query_cache_.get(),
- storage->counter_table());
+
+ // Tables dynamically generated at query time.
+ RegisterDynamicTable(std::unique_ptr<ExperimentalFlamegraphGenerator>(
+ new ExperimentalFlamegraphGenerator(&context_)));
+ RegisterDynamicTable(std::unique_ptr<ExperimentalCounterDurGenerator>(
+ new ExperimentalCounterDurGenerator(storage->counter_table())));
// New style db-backed tables.
RegisterDbTable(storage->arg_table());
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index afacd55..6d0967d 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -81,6 +81,12 @@
&table, table.table_name());
}
+ void RegisterDynamicTable(
+ std::unique_ptr<DbSqliteTable::DynamicTableGenerator> generator) {
+ DbSqliteTable::RegisterTable(*db_, query_cache_.get(),
+ std::move(generator));
+ }
+
ScopedDb db_;
std::unique_ptr<QueryCache> query_cache_;
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 5a93e5f..f3917dc 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -428,11 +428,11 @@
if (ds_configs_.empty()) {
PERFETTO_DCHECK(active_configs_.empty());
- PERFETTO_DCHECK(!current_state_.tracing_on);
-
// If someone outside of perfetto is using ftrace give up now.
- if (is_ftrace_enabled)
+ if (is_ftrace_enabled) {
+ PERFETTO_ELOG("ftrace in use by non-Perfetto.");
return 0;
+ }
// Setup ftrace, without starting it. Setting buffers can be quite slow
// (up to hundreds of ms).
@@ -440,8 +440,10 @@
SetupBufferSize(request);
} else {
// Did someone turn ftrace off behind our back? If so give up.
- if (!active_configs_.empty() && !is_ftrace_enabled)
+ if (!active_configs_.empty() && !is_ftrace_enabled) {
+ PERFETTO_ELOG("ftrace disabled by non-Perfetto.");
return 0;
+ }
}
std::set<GroupAndName> events = GetFtraceEvents(request, table_);
@@ -493,20 +495,19 @@
return false;
}
+ if (active_configs_.empty()) {
+ if (ftrace_->IsTracingEnabled()) {
+ // If someone outside of perfetto is using ftrace give up now.
+ PERFETTO_ELOG("ftrace in use by non-Perfetto.");
+ return false;
+ }
+ if (!ftrace_->EnableTracing()) {
+ PERFETTO_ELOG("Failed to enable ftrace.");
+ return false;
+ }
+ }
+
active_configs_.insert(id);
- if (active_configs_.size() > 1) {
- PERFETTO_DCHECK(current_state_.tracing_on);
- return true; // We are not the first, ftrace is already enabled. All done.
- }
-
- PERFETTO_DCHECK(!current_state_.tracing_on);
- if (ftrace_->IsTracingEnabled()) {
- // If someone outside of perfetto is using ftrace give up now.
- return false;
- }
-
- ftrace_->EnableTracing();
- current_state_.tracing_on = true;
return true;
}
@@ -554,12 +555,11 @@
// If there aren't any more active configs, disable ftrace.
auto active_it = active_configs_.find(config_id);
if (active_it != active_configs_.end()) {
- PERFETTO_DCHECK(current_state_.tracing_on);
active_configs_.erase(active_it);
if (active_configs_.empty()) {
// This was the last active config, disable ftrace.
- ftrace_->DisableTracing();
- current_state_.tracing_on = false;
+ if (!ftrace_->DisableTracing())
+ PERFETTO_ELOG("Failed to disable ftrace.");
}
}
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index 16883c4..71100e9 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -121,7 +121,6 @@
std::vector<std::string> atrace_apps;
std::vector<std::string> atrace_categories;
size_t cpu_buffer_size_pages = 0;
- bool tracing_on = false;
bool atrace_on = false;
};
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index a66ed05..ac960e7 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -105,6 +105,8 @@
// We don't know what state the rest of the system and process is so as far
// as possible avoid allocations.
void HardResetFtraceState() {
+ PERFETTO_LOG("Hard resetting ftrace state.");
+
WriteToFile("/sys/kernel/debug/tracing/tracing_on", "0");
WriteToFile("/sys/kernel/debug/tracing/buffer_size_kb", "4");
WriteToFile("/sys/kernel/debug/tracing/events/enable", "0");
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index 060f4cd..ddccbe1 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -157,12 +157,14 @@
bool FtraceProcfs::EnableTracing() {
KernelLogWrite("perfetto: enabled ftrace\n");
+ PERFETTO_LOG("enabled ftrace");
std::string path = root_ + "tracing_on";
return WriteToFile(path, "1");
}
bool FtraceProcfs::DisableTracing() {
KernelLogWrite("perfetto: disabled ftrace\n");
+ PERFETTO_LOG("disabled ftrace");
std::string path = root_ + "tracing_on";
return WriteToFile(path, "0");
}
@@ -173,7 +175,10 @@
bool FtraceProcfs::IsTracingEnabled() {
std::string path = root_ + "tracing_on";
- return ReadOneCharFromFile(path) == '1';
+ char tracing_on = ReadOneCharFromFile(path);
+ if (tracing_on == '\0')
+ PERFETTO_PLOG("Failed to read %s", path.c_str());
+ return tracing_on == '1';
}
bool FtraceProcfs::SetClock(const std::string& clock_name) {
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index 036fb0c..1d79eae 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -261,9 +261,7 @@
ftrace_->GetWeakPtr(), session_id, std::move(ftrace_config),
endpoint_->CreateTraceWriter(buffer_id)));
if (!ftrace_->AddDataSource(data_source.get())) {
- PERFETTO_ELOG(
- "Failed to setup tracing (too many concurrent sessions or ftrace is "
- "already in use)");
+ PERFETTO_ELOG("Failed to setup ftrace");
return nullptr;
}
return std::unique_ptr<ProbesDataSource>(std::move(data_source));
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index 7f14ed3..a6ea494 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -151,3 +151,16 @@
]
sources = [ "internal/in_process_tracing_backend.cc" ]
}
+
+if (enable_perfetto_benchmarks) {
+ source_set("benchmarks") {
+ testonly = true
+ deps = [
+ ":platform_posix",
+ "../..:libperfetto_client_experimental",
+ "../../../../../gn:benchmark",
+ "../../../../../gn:default_deps",
+ ]
+ sources = [ "api_benchmark.cc" ]
+ }
+}
diff --git a/src/tracing/api_benchmark.cc b/src/tracing/api_benchmark.cc
new file mode 100644
index 0000000..88d725e
--- /dev/null
+++ b/src/tracing/api_benchmark.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <benchmark/benchmark.h>
+
+#include "perfetto/tracing.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
+#include "protos/perfetto/trace/track_event/log_message.pbzero.h"
+
+PERFETTO_DEFINE_CATEGORIES(perfetto::Category("benchmark"));
+PERFETTO_TRACK_EVENT_STATIC_STORAGE();
+
+namespace {
+
+class BenchmarkDataSource : public perfetto::DataSource<BenchmarkDataSource> {
+ public:
+ void OnSetup(const SetupArgs&) override {}
+ void OnStart(const StartArgs&) override {}
+ void OnStop(const StopArgs&) override {}
+};
+
+static void BM_TracingDataSourceDisabled(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ BenchmarkDataSource::Trace([&](BenchmarkDataSource::TraceContext) {});
+ benchmark::ClobberMemory();
+ }
+}
+
+std::unique_ptr<perfetto::TracingSession> StartTracing(
+ const std::string& data_source_name) {
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kInProcessBackend;
+ perfetto::Tracing::Initialize(args);
+
+ perfetto::DataSourceDescriptor dsd;
+ dsd.set_name("benchmark");
+ BenchmarkDataSource::Register(dsd);
+ perfetto::TrackEvent::Register();
+
+ perfetto::TraceConfig cfg;
+ cfg.add_buffers()->set_size_kb(1024);
+ auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ ds_cfg->set_name(data_source_name);
+ auto tracing_session =
+ perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+ tracing_session->Setup(cfg);
+ tracing_session->StartBlocking();
+ return tracing_session;
+}
+
+static void BM_TracingDataSourceLambda(benchmark::State& state) {
+ auto tracing_session = StartTracing("benchmark");
+
+ while (state.KeepRunning()) {
+ BenchmarkDataSource::Trace([&](BenchmarkDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp(42);
+ packet->set_for_testing()->set_str("benchmark");
+ });
+ benchmark::ClobberMemory();
+ }
+
+ tracing_session->StopBlocking();
+ PERFETTO_CHECK(!tracing_session->ReadTraceBlocking().empty());
+}
+
+static void BM_TracingTrackEventDisabled(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ TRACE_EVENT_BEGIN("benchmark", "DisabledEvent");
+ benchmark::ClobberMemory();
+ }
+}
+
+static void BM_TracingTrackEventBasic(benchmark::State& state) {
+ auto tracing_session = StartTracing("track_event");
+
+ while (state.KeepRunning()) {
+ TRACE_EVENT_BEGIN("benchmark", "Event");
+ benchmark::ClobberMemory();
+ }
+
+ tracing_session->StopBlocking();
+ PERFETTO_CHECK(!tracing_session->ReadTraceBlocking().empty());
+}
+
+static void BM_TracingTrackEventDebugAnnotations(benchmark::State& state) {
+ auto tracing_session = StartTracing("track_event");
+
+ while (state.KeepRunning()) {
+ TRACE_EVENT_BEGIN("benchmark", "Event", "value", 42);
+ benchmark::ClobberMemory();
+ }
+
+ tracing_session->StopBlocking();
+ PERFETTO_CHECK(!tracing_session->ReadTraceBlocking().empty());
+}
+
+static void BM_TracingTrackEventLambda(benchmark::State& state) {
+ auto tracing_session = StartTracing("track_event");
+
+ while (state.KeepRunning()) {
+ TRACE_EVENT_BEGIN("benchmark", "Event", [&](perfetto::EventContext ctx) {
+ auto* log = ctx.event()->set_log_message();
+ log->set_source_location_iid(42);
+ log->set_body_iid(1234);
+ });
+ benchmark::ClobberMemory();
+ }
+
+ tracing_session->StopBlocking();
+ PERFETTO_CHECK(!tracing_session->ReadTraceBlocking().empty());
+}
+
+} // namespace
+
+BENCHMARK(BM_TracingDataSourceDisabled);
+BENCHMARK(BM_TracingDataSourceLambda);
+BENCHMARK(BM_TracingTrackEventBasic);
+BENCHMARK(BM_TracingTrackEventDebugAnnotations);
+BENCHMARK(BM_TracingTrackEventDisabled);
+BENCHMARK(BM_TracingTrackEventLambda);
diff --git a/src/tracing/core/shared_memory_arbiter_impl.cc b/src/tracing/core/shared_memory_arbiter_impl.cc
index 9ea626b..f4ee8b3 100644
--- a/src/tracing/core/shared_memory_arbiter_impl.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl.cc
@@ -257,12 +257,13 @@
if (!commit_data_req_) {
commit_data_req_.reset(new CommitDataRequest());
- weak_this = weak_ptr_factory_.GetWeakPtr();
// Flushing the commit is only supported while we're |fully_bound_|. If we
// aren't, we'll flush when |fully_bound_| is updated.
- if (fully_bound_)
+ if (fully_bound_) {
+ weak_this = weak_ptr_factory_.GetWeakPtr();
task_runner_to_post_callback_on = task_runner_;
+ }
}
// If a valid chunk is specified, return it and attach it to the request.
@@ -418,6 +419,11 @@
producer_endpoint_ = producer_endpoint;
task_runner_ = task_runner;
+ // Now that we're bound to a task runner, also reset the WeakPtrFactory to
+ // it. Because this code runs on the task runner, the factory's weak
+ // pointers will be valid on it.
+ weak_ptr_factory_.Reset(this);
+
// All writers registered so far should be startup trace writers, since
// the producer cannot feasibly know the target buffer for any future
// session yet.
diff --git a/src/tracing/core/trace_buffer.cc b/src/tracing/core/trace_buffer.cc
index fe7ff48..2227870 100644
--- a/src/tracing/core/trace_buffer.cc
+++ b/src/tracing/core/trace_buffer.cc
@@ -880,6 +880,12 @@
chunk_meta->is_complete())) {
stats_.set_chunks_read(stats_.chunks_read() + 1);
stats_.set_bytes_read(stats_.bytes_read() + chunk_meta->chunk_record->size);
+ } else {
+ // We have at least one more packet to parse. It should be within the chunk.
+ if (chunk_meta->cur_fragment_offset + sizeof(ChunkRecord) >=
+ chunk_meta->chunk_record->size) {
+ PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+ }
}
chunk_meta->set_last_read_packet_skipped(false);
diff --git a/src/tracing/core/trace_writer_impl.cc b/src/tracing/core/trace_writer_impl.cc
index d34470f..6c3fd06 100644
--- a/src/tracing/core/trace_writer_impl.cc
+++ b/src/tracing/core/trace_writer_impl.cc
@@ -48,7 +48,8 @@
id_(id),
target_buffer_(target_buffer),
buffer_exhausted_policy_(buffer_exhausted_policy),
- protobuf_stream_writer_(this) {
+ protobuf_stream_writer_(this),
+ process_id_(base::GetProcessId()) {
// TODO(primiano): we could handle the case of running out of TraceWriterID(s)
// more gracefully and always return a no-op TracePacket in NewTracePacket().
PERFETTO_CHECK(id_ != 0);
@@ -88,6 +89,11 @@
// If we hit this, the caller is calling NewTracePacket() without having
// finalized the previous packet.
PERFETTO_CHECK(cur_packet_->is_finalized());
+ // If we hit this, this trace writer was created in a different process. This
+ // likely means that the process forked while tracing was active, and the
+ // forked child process tried to emit a trace event. This is not supported, as
+ // it would lead to two processes writing to the same tracing SMB.
+ PERFETTO_DCHECK(process_id_ == base::GetProcessId());
fragmenting_packet_ = false;
diff --git a/src/tracing/core/trace_writer_impl.h b/src/tracing/core/trace_writer_impl.h
index 500f3c9..f3a53be 100644
--- a/src/tracing/core/trace_writer_impl.h
+++ b/src/tracing/core/trace_writer_impl.h
@@ -17,6 +17,7 @@
#ifndef SRC_TRACING_CORE_TRACE_WRITER_IMPL_H_
#define SRC_TRACING_CORE_TRACE_WRITER_IMPL_H_
+#include "perfetto/base/proc_utils.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/shared_memory_abi.h"
#include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
@@ -129,6 +130,10 @@
// later sent out-of-band to the tracing service, who will patch the required
// chunks, if they are still around.
PatchList patch_list_;
+
+ // PID of the process that created the trace writer. Used for a DCHECK that
+ // aims to detect unsupported process forks while tracing.
+ const base::PlatformProcessId process_id_;
};
} // namespace perfetto
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 0f249a9..2451baa 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -683,10 +683,12 @@
tracing_session->state = TracingSession::CONFIGURED;
PERFETTO_LOG(
- "Configured tracing, #sources:%zu, duration:%d ms, #buffers:%d, total "
- "buffer size:%zu KB, total sessions:%zu session name: %s",
- cfg.data_sources().size(), tracing_session->config.duration_ms(),
+ "Configured tracing session %" PRIu64
+ ", #sources:%zu, duration:%d ms, #buffers:%d, total "
+ "buffer size:%zu KB, total sessions:%zu, uid:%d session name: \"%s\"",
+ tsid, cfg.data_sources().size(), tracing_session->config.duration_ms(),
cfg.buffers_size(), total_buf_size_kb, tracing_sessions_.size(),
+ static_cast<unsigned int>(consumer->uid_),
cfg.unique_session_name().c_str());
// Start the data sources, unless this is a case of early setup + fast
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index d27b9ef..eb62498 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -39,6 +39,9 @@
namespace {
std::atomic<perfetto::base::PlatformThreadId> g_main_thread;
+static constexpr const char kLegacySlowPrefix[] = "disabled-by-default-";
+static constexpr const char kSlowTag[] = "slow";
+static constexpr const char kDebugTag[] = "debug";
struct InternedEventCategory
: public TrackEventInternedDataIndex<
@@ -48,10 +51,11 @@
SmallInternedDataTraits> {
static void Add(protos::pbzero::InternedData* interned_data,
size_t iid,
- const char* value) {
+ const char* value,
+ size_t length) {
auto category = interned_data->add_event_categories();
category->set_iid(iid);
- category->set_name(value);
+ category->set_name(value, length);
}
};
@@ -95,22 +99,28 @@
#endif
}
-bool NameMatchesPattern(const std::string& pattern, const std::string& name) {
+enum class MatchType { kExact, kPattern };
+
+bool NameMatchesPattern(const std::string& pattern,
+ const std::string& name,
+ MatchType match_type) {
// To avoid pulling in all of std::regex, for now we only support a single "*"
// wildcard at the end of the pattern.
- // TODO(skyostil): Support comma-separated categories.
size_t i = pattern.find('*');
if (i != std::string::npos) {
PERFETTO_DCHECK(i == pattern.size() - 1);
+ if (match_type != MatchType::kPattern)
+ return false;
return name.substr(0, i) == pattern.substr(0, i);
}
return name == pattern;
}
bool NameMatchesPatternList(const std::vector<std::string>& patterns,
- const std::string& name) {
+ const std::string& name,
+ MatchType match_type) {
for (const auto& pattern : patterns) {
- if (NameMatchesPattern(pattern, name))
+ if (NameMatchesPattern(pattern, name, match_type))
return true;
}
return false;
@@ -131,9 +141,20 @@
protozero::HeapBuffered<protos::pbzero::TrackEventDescriptor> ted;
for (size_t i = 0; i < registry.category_count(); i++) {
auto category = registry.GetCategory(i);
+ // Don't register group categories.
+ if (category->IsGroup())
+ continue;
auto cat = ted->add_available_categories();
cat->set_name(category->name);
- // TODO(skyostil): Advertise category tags and descriptions.
+ if (category->description)
+ cat->set_description(category->description);
+ for (const auto& tag : category->tags) {
+ if (tag)
+ cat->add_tags(tag);
+ }
+ // Disabled-by-default categories get a "slow" tag.
+ if (!strncmp(category->name, kLegacySlowPrefix, strlen(kLegacySlowPrefix)))
+ cat->add_tags(kSlowTag);
}
dsd.set_track_event_descriptor_raw(ted.SerializeAsString());
@@ -146,7 +167,7 @@
const protos::gen::TrackEventConfig& config,
uint32_t instance_index) {
for (size_t i = 0; i < registry.category_count(); i++) {
- if (IsCategoryEnabled(config, *registry.GetCategory(i)))
+ if (IsCategoryEnabled(registry, config, *registry.GetCategory(i)))
registry.EnableCategoryForInstance(i, instance_index);
}
}
@@ -161,11 +182,91 @@
// static
bool TrackEventInternal::IsCategoryEnabled(
+ const TrackEventCategoryRegistry& registry,
const protos::gen::TrackEventConfig& config,
- const TrackEventCategory& category) {
- if (NameMatchesPatternList(config.disabled_categories(), category.name))
- return NameMatchesPatternList(config.enabled_categories(), category.name);
- // TODO(skyostil): Support tag-based category configs.
+ const Category& category) {
+ // If this is a group category, check if any of its constituent categories are
+ // enabled. If so, then this one is enabled too.
+ if (category.IsGroup()) {
+ bool result = false;
+ category.ForEachGroupMember([&](const char* member_name, size_t name_size) {
+ for (size_t i = 0; i < registry.category_count(); i++) {
+ const auto ref_category = registry.GetCategory(i);
+ // Groups can't refer to other groups.
+ if (ref_category->IsGroup())
+ continue;
+ // Require an exact match.
+ if (ref_category->name_size() != name_size ||
+ strncmp(ref_category->name, member_name, name_size)) {
+ continue;
+ }
+ if (IsCategoryEnabled(registry, config, *ref_category)) {
+ result = true;
+ // Break ForEachGroupMember() loop.
+ return false;
+ }
+ break;
+ }
+ // No match found => keep iterating.
+ return true;
+ });
+ return result;
+ }
+
+ auto has_matching_tag = [&](std::function<bool(const char*)> matcher) {
+ for (const auto& tag : category.tags) {
+ if (!tag)
+ break;
+ if (matcher(tag))
+ return true;
+ }
+ // Legacy "disabled-by-default" categories automatically get the "slow" tag.
+ if (!strncmp(category.name, kLegacySlowPrefix, strlen(kLegacySlowPrefix)) &&
+ matcher(kSlowTag)) {
+ return true;
+ }
+ return false;
+ };
+
+ // First try exact matches, then pattern matches.
+ const std::array<MatchType, 2> match_types = {
+ {MatchType::kExact, MatchType::kPattern}};
+ for (auto match_type : match_types) {
+ // 1. Enabled categories.
+ if (NameMatchesPatternList(config.enabled_categories(), category.name,
+ match_type)) {
+ return true;
+ }
+
+ // 2. Enabled tags.
+ if (has_matching_tag([&](const char* tag) {
+ return NameMatchesPatternList(config.enabled_tags(), tag, match_type);
+ })) {
+ return true;
+ }
+
+ // 3. Disabled categories.
+ if (NameMatchesPatternList(config.disabled_categories(), category.name,
+ match_type)) {
+ return false;
+ }
+
+ // 4. Disabled tags.
+ if (has_matching_tag([&](const char* tag) {
+ if (config.disabled_tags_size()) {
+ return NameMatchesPatternList(config.disabled_tags(), tag,
+ match_type);
+ } else {
+ // The "slow" and "debug" tags are disabled by default.
+ return NameMatchesPattern(kSlowTag, tag, match_type) ||
+ NameMatchesPattern(kDebugTag, tag, match_type);
+ }
+ })) {
+ return false;
+ }
+ }
+
+ // If nothing matched, enable the category by default.
return true;
}
@@ -224,11 +325,10 @@
EventContext TrackEventInternal::WriteEvent(
TraceWriterBase* trace_writer,
TrackEventIncrementalState* incr_state,
- const char* category,
+ const Category* category,
const char* name,
perfetto::protos::pbzero::TrackEvent::Type type,
uint64_t timestamp) {
- PERFETTO_DCHECK(category);
PERFETTO_DCHECK(g_main_thread);
if (incr_state->was_cleared) {
@@ -244,10 +344,14 @@
// We assume that |category| and |name| point to strings with static lifetime.
// This means we can use their addresses as interning keys.
- if (type != protos::pbzero::TrackEvent::TYPE_SLICE_END) {
- // TODO(skyostil): Handle multiple categories.
- size_t category_iid = InternedEventCategory::Get(&ctx, category);
- track_event->add_category_iids(category_iid);
+ if (category && type != protos::pbzero::TrackEvent::TYPE_SLICE_END) {
+ category->ForEachGroupMember(
+ [&](const char* member_name, size_t name_size) {
+ size_t category_iid =
+ InternedEventCategory::Get(&ctx, member_name, name_size);
+ track_event->add_category_iids(category_iid);
+ return true;
+ });
}
if (name) {
size_t name_iid = InternedEventName::Get(&ctx, name);
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
index a5d94c1..0730ed5 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
@@ -46,12 +46,14 @@
TracingService::ProducerSMBScrapingMode smb_scraping_mode,
size_t shared_memory_size_hint_bytes,
size_t shared_memory_page_size_hint_bytes,
- std::unique_ptr<SharedMemory> shm) {
+ std::unique_ptr<SharedMemory> shm,
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter) {
return std::unique_ptr<TracingService::ProducerEndpoint>(
- new ProducerIPCClientImpl(
- service_sock_name, producer, producer_name, task_runner,
- smb_scraping_mode, shared_memory_size_hint_bytes,
- shared_memory_page_size_hint_bytes, std::move(shm)));
+ new ProducerIPCClientImpl(service_sock_name, producer, producer_name,
+ task_runner, smb_scraping_mode,
+ shared_memory_size_hint_bytes,
+ shared_memory_page_size_hint_bytes,
+ std::move(shm), std::move(shm_arbiter)));
}
ProducerIPCClientImpl::ProducerIPCClientImpl(
@@ -62,16 +64,30 @@
TracingService::ProducerSMBScrapingMode smb_scraping_mode,
size_t shared_memory_size_hint_bytes,
size_t shared_memory_page_size_hint_bytes,
- std::unique_ptr<SharedMemory> shm)
+ std::unique_ptr<SharedMemory> shm,
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter)
: producer_(producer),
task_runner_(task_runner),
ipc_channel_(ipc::Client::CreateInstance(service_sock_name, task_runner)),
producer_port_(this /* event_listener */),
shared_memory_(std::move(shm)),
+ shared_memory_arbiter_(std::move(shm_arbiter)),
name_(producer_name),
shared_memory_page_size_hint_bytes_(shared_memory_page_size_hint_bytes),
shared_memory_size_hint_bytes_(shared_memory_size_hint_bytes),
smb_scraping_mode_(smb_scraping_mode) {
+ // Check for producer-provided SMB (used by Chrome for startup tracing).
+ if (shared_memory_) {
+ // We also expect a valid (unbound) arbiter. Bind it to this endpoint now.
+ PERFETTO_CHECK(shared_memory_arbiter_);
+ shared_memory_arbiter_->BindToProducerEndpoint(this, task_runner_);
+
+ // If the service accepts our SMB, then it must match our requested page
+ // layout. The protocol doesn't allow the service to change the size and
+ // layout when the SMB is provided by the producer.
+ shared_buffer_page_size_kb_ = shared_memory_page_size_hint_bytes_ / 1024;
+ }
+
ipc_channel_->BindService(producer_port_.GetWeakPtr());
PERFETTO_DCHECK_THREAD(thread_checker_);
}
@@ -215,19 +231,14 @@
/*require_seals_if_supported=*/false);
shared_buffer_page_size_kb_ =
cmd.setup_tracing().shared_buffer_page_size_kb();
+ shared_memory_arbiter_ = SharedMemoryArbiter::CreateInstance(
+ shared_memory_.get(), shared_buffer_page_size_kb_ * 1024, this,
+ task_runner_);
} else {
// Producer-provided SMB (used by Chrome for startup tracing).
- PERFETTO_CHECK(is_shmem_provided_by_producer_ && shared_memory_);
- // If the service accepted our SMB, then it must match our requested page
- // layout. The protocol doesn't allow the sservice to change the size and
- // layout when the SMB is provided by the producer.
- shared_buffer_page_size_kb_ = shared_memory_page_size_hint_bytes_ / 1024;
- // TODO(eseckler): In case of a procucer-provided SMB, create the
- // SharedMemoryArbiter even earlier to support startup tracing.
+ PERFETTO_CHECK(is_shmem_provided_by_producer_ && shared_memory_ &&
+ shared_memory_arbiter_);
}
- shared_memory_arbiter_ = SharedMemoryArbiter::CreateInstance(
- shared_memory_.get(), shared_buffer_page_size_kb_ * 1024, this,
- task_runner_);
producer_->OnTracingSetup();
return;
}
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.h b/src/tracing/ipc/producer/producer_ipc_client_impl.h
index 816a82bc..244a4f9 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.h
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.h
@@ -51,14 +51,16 @@
class ProducerIPCClientImpl : public TracingService::ProducerEndpoint,
public ipc::ServiceProxy::EventListener {
public:
- ProducerIPCClientImpl(const char* service_sock_name,
- Producer*,
- const std::string& producer_name,
- base::TaskRunner*,
- TracingService::ProducerSMBScrapingMode,
- size_t shared_memory_size_hint_bytes = 0,
- size_t shared_memory_page_size_hint_bytes = 0,
- std::unique_ptr<SharedMemory> shm = nullptr);
+ ProducerIPCClientImpl(
+ const char* service_sock_name,
+ Producer*,
+ const std::string& producer_name,
+ base::TaskRunner*,
+ TracingService::ProducerSMBScrapingMode,
+ size_t shared_memory_size_hint_bytes = 0,
+ size_t shared_memory_page_size_hint_bytes = 0,
+ std::unique_ptr<SharedMemory> shm = nullptr,
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
~ProducerIPCClientImpl() override;
// TracingService::ProducerEndpoint implementation.
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 3605e4e..76ce6ab 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -76,13 +76,19 @@
PERFETTO_DEFINE_TEST_CATEGORY_PREFIXES("dynamic");
// Trace categories used in the tests.
-PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORY(test),
- PERFETTO_CATEGORY(foo),
- PERFETTO_CATEGORY(bar),
- PERFETTO_CATEGORY(cat),
- // TODO(skyostil): Figure out how to represent
- // disabled-by-default categories
- {TRACE_DISABLED_BY_DEFAULT("cat")});
+PERFETTO_DEFINE_CATEGORIES(
+ perfetto::Category("test")
+ .SetDescription("This is a test category")
+ .SetTags("tag"),
+ perfetto::Category("foo"),
+ perfetto::Category("bar"),
+ perfetto::Category("cat").SetTags("slow"),
+ perfetto::Category("cat.verbose").SetTags("debug"),
+ perfetto::Category::Group("foo,bar"),
+ perfetto::Category::Group("baz,bar,quux"),
+ perfetto::Category::Group("red,green,blue,foo"),
+ perfetto::Category::Group("red,green,blue,yellow"),
+ perfetto::Category(TRACE_DISABLED_BY_DEFAULT("cat")));
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
// For testing interning of complex objects.
@@ -520,8 +526,11 @@
id << "(tid_override=" << legacy_event.tid_override() << ")";
slice += id.str();
}
- if (!track_event.category_iids().empty())
- slice += ":" + categories[track_event.category_iids()[0]];
+ size_t category_count = 0;
+ for (const auto& it : track_event.category_iids())
+ slice += (category_count++ ? "," : ":") + categories[it];
+ for (const auto& it : track_event.categories())
+ slice += (category_count++ ? ",$" : ":$") + it;
if (track_event.has_name_iid())
slice += "." + event_names[track_event.name_iid()];
@@ -808,12 +817,19 @@
// Check that the advertised categories match PERFETTO_DEFINE_CATEGORIES (see
// above).
- EXPECT_EQ(5, desc.available_categories_size());
+ EXPECT_EQ(6, desc.available_categories_size());
EXPECT_EQ("test", desc.available_categories()[0].name());
+ EXPECT_EQ("This is a test category",
+ desc.available_categories()[0].description());
+ EXPECT_EQ("tag", desc.available_categories()[0].tags()[0]);
EXPECT_EQ("foo", desc.available_categories()[1].name());
EXPECT_EQ("bar", desc.available_categories()[2].name());
EXPECT_EQ("cat", desc.available_categories()[3].name());
- EXPECT_EQ("disabled-by-default-cat", desc.available_categories()[4].name());
+ EXPECT_EQ("slow", desc.available_categories()[3].tags()[0]);
+ EXPECT_EQ("cat.verbose", desc.available_categories()[4].name());
+ EXPECT_EQ("debug", desc.available_categories()[4].tags()[0]);
+ EXPECT_EQ("disabled-by-default-cat", desc.available_categories()[5].name());
+ EXPECT_EQ("slow", desc.available_categories()[5].tags()[0]);
}
TEST_F(PerfettoApiTest, TrackEventSharedIncrementalState) {
@@ -1653,17 +1669,35 @@
TRACE_EVENT_BEGIN("foo", "FooEvent");
TRACE_EVENT_BEGIN("bar", "BarEvent");
+ TRACE_EVENT_BEGIN("foo,bar", "MultiFooBar");
+ TRACE_EVENT_BEGIN("baz,bar,quux", "MultiBar");
+ TRACE_EVENT_BEGIN("red,green,blue,foo", "MultiFoo");
+ TRACE_EVENT_BEGIN("red,green,blue,yellow", "MultiNone");
+ TRACE_EVENT_BEGIN("cat", "SlowEvent");
+ TRACE_EVENT_BEGIN("cat.verbose", "DebugEvent");
+ TRACE_EVENT_BEGIN("test", "TagEvent");
+ TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("cat"), "SlowDisabledEvent");
+ TRACE_EVENT_BEGIN("dynamic,foo", "DynamicGroupFooEvent");
+ perfetto::DynamicCategory dyn{"dynamic,bar"};
+ TRACE_EVENT_BEGIN(dyn, "DynamicGroupBarEvent");
perfetto::TrackEvent::Flush();
tracing_session->get()->StopBlocking();
- return ReadSlicesFromTrace(tracing_session->get());
+ auto slices = ReadSlicesFromTrace(tracing_session->get());
+ tracing_session->session.reset();
+ return slices;
};
- // Empty config should enable all categories.
+ // Empty config should enable all categories except slow ones.
{
perfetto::protos::gen::TrackEventConfig te_cfg;
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:bar.BarEvent"));
+ EXPECT_THAT(
+ slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+ "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+ "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
// Enable exactly one category.
@@ -1672,7 +1706,9 @@
te_cfg.add_disabled_categories("*");
te_cfg.add_enabled_categories("foo");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent"));
+ EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:foo,bar.MultiFooBar",
+ "B:red,green,blue,foo.MultiFoo",
+ "B:$dynamic,$foo.DynamicGroupFooEvent"));
}
// Enable two categories.
@@ -1683,17 +1719,25 @@
te_cfg.add_enabled_categories("baz");
te_cfg.add_enabled_categories("bar");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:bar.BarEvent"));
+ EXPECT_THAT(
+ slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+ "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+ "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
- // Enable overrides disable.
+ // Enabling all categories with a pattern doesn't enable slow ones.
{
perfetto::protos::gen::TrackEventConfig te_cfg;
- te_cfg.add_disabled_categories("foo");
- te_cfg.add_disabled_categories("bar");
te_cfg.add_enabled_categories("*");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:bar.BarEvent"));
+ EXPECT_THAT(
+ slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+ "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+ "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
// Enable with a pattern.
@@ -1702,7 +1746,46 @@
te_cfg.add_disabled_categories("*");
te_cfg.add_enabled_categories("fo*");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent"));
+ EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:foo,bar.MultiFooBar",
+ "B:red,green,blue,foo.MultiFoo",
+ "B:$dynamic,$foo.DynamicGroupFooEvent"));
+ }
+
+ // Enable with a tag.
+ {
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_disabled_categories("*");
+ te_cfg.add_enabled_tags("tag");
+ auto slices = check_config(te_cfg);
+ EXPECT_THAT(slices, ElementsAre("B:test.TagEvent"));
+ }
+
+ // Enable just slow categories.
+ {
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_disabled_categories("*");
+ te_cfg.add_enabled_tags("slow");
+ auto slices = check_config(te_cfg);
+ EXPECT_THAT(slices,
+ ElementsAre("B:cat.SlowEvent",
+ "B:disabled-by-default-cat.SlowDisabledEvent"));
+ }
+
+ // Enable everything including slow/debug categories.
+ {
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_enabled_categories("*");
+ te_cfg.add_enabled_tags("slow");
+ te_cfg.add_enabled_tags("debug");
+ auto slices = check_config(te_cfg);
+ EXPECT_THAT(slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent",
+ "B:foo,bar.MultiFooBar", "B:baz,bar,quux.MultiBar",
+ "B:red,green,blue,foo.MultiFoo", "B:cat.SlowEvent",
+ "B:cat.verbose.DebugEvent", "B:test.TagEvent",
+ "B:disabled-by-default-cat.SlowDisabledEvent",
+ "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
}
@@ -2071,15 +2154,9 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEvents) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
// Create a new trace session.
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session =
+ NewTraceWithCategories({"cat", TRACE_DISABLED_BY_DEFAULT("cat")});
tracing_session->get()->StartBlocking();
// Basic events.
@@ -2131,15 +2208,8 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEventsWithCustomAnnotation) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
// Create a new trace session.
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
MyDebugAnnotation annotation;
@@ -2160,17 +2230,10 @@
// Make sure that a uniquely owned debug annotation can be written into
// multiple concurrent tracing sessions.
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
- auto* tracing_session2 = NewTrace(cfg);
+ auto* tracing_session2 = NewTraceWithCategories({"cat"});
tracing_session2->get()->StartBlocking();
std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation());
@@ -2189,14 +2252,7 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEventsWithId) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
TRACE_EVENT_ASYNC_BEGIN0("cat", "UnscopedId", 0x1000);
@@ -2217,14 +2273,7 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEventsWithFlow) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
const uint64_t flow_id = 1234;
diff --git a/src/tracing/test/tracing_module_categories.h b/src/tracing/test/tracing_module_categories.h
index b492db8..03c0425 100644
--- a/src/tracing/test/tracing_module_categories.h
+++ b/src/tracing/test/tracing_module_categories.h
@@ -27,6 +27,7 @@
#include "perfetto/tracing.h"
+// Note: Using the old syntax here to ensure backwards compatibility.
PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORY(cat1),
PERFETTO_CATEGORY(cat2),
PERFETTO_CATEGORY(cat3),
diff --git a/src/tracing/track_event_category_registry.cc b/src/tracing/track_event_category_registry.cc
index 5bab490..6d9a649 100644
--- a/src/tracing/track_event_category_registry.cc
+++ b/src/tracing/track_event_category_registry.cc
@@ -17,14 +17,31 @@
#include "perfetto/tracing/track_event_category_registry.h"
namespace perfetto {
+
+// static
+Category Category::FromDynamicCategory(const char* name) {
+ if (GetNthNameSize(1, name, name)) {
+ Category group(Group(name));
+ PERFETTO_DCHECK(group.name);
+ return group;
+ }
+ Category category(name);
+ PERFETTO_DCHECK(category.name);
+ return category;
+}
+
+Category Category::FromDynamicCategory(
+ const DynamicCategory& dynamic_category) {
+ return FromDynamicCategory(dynamic_category.name.c_str());
+}
+
namespace internal {
perfetto::DynamicCategory NullCategory(const perfetto::DynamicCategory&) {
return perfetto::DynamicCategory{};
}
-const TrackEventCategory* TrackEventCategoryRegistry::GetCategory(
- size_t index) const {
+const Category* TrackEventCategoryRegistry::GetCategory(size_t index) const {
PERFETTO_DCHECK(index < category_count_);
return &categories_[index];
}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 09b10b7..f9629b3 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -144,3 +144,17 @@
sources = [ "benchmark_main.cc" ]
}
} # if (enable_perfetto_benchmarks)
+
+if (perfetto_build_with_android || (is_android && perfetto_build_standalone)) {
+ # This is used only in-tree builds. It's built in standalone builds just to
+ # get build coverage.
+ static_library("perfetto_gtest_logcat_printer") {
+ testonly = true
+ complete_static_lib = true
+ sources = [ "gtest_logcat_printer.cc" ]
+ deps = [
+ "../gn:default_deps",
+ "../gn:gtest_and_gmock",
+ ]
+ }
+}
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index 4871ebb..7628fb3 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -3,8 +3,9 @@
srcs: [
"device_feature_test_cts.cc",
"end_to_end_integrationtest_cts.cc",
- "heapprofd_test_cts.cc",
"heapprofd_java_test_cts.cc",
+ "heapprofd_test_cts.cc",
+ "traced_perf_test_cts.cc",
"utils.cc",
":perfetto_protos_perfetto_config_cpp_gen",
],
@@ -18,6 +19,9 @@
"perfetto_cts_deps",
"perfetto_trace_protos",
],
+ whole_static_libs: [
+ "perfetto_gtest_logcat_printer",
+ ],
shared_libs: [
"libandroid",
"liblog",
diff --git a/test/cts/AndroidTest.xml b/test/cts/AndroidTest.xml
index 6dfeac1..3d40593 100644
--- a/test/cts/AndroidTest.xml
+++ b/test/cts/AndroidTest.xml
@@ -34,6 +34,7 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="setprop persist.heapprofd.enable 1" />
+ <option name="run-command" value="setprop persist.traced_perf.enable 1" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
diff --git a/test/cts/BUILD.gn b/test/cts/BUILD.gn
index 84aba0c..84a7860 100644
--- a/test/cts/BUILD.gn
+++ b/test/cts/BUILD.gn
@@ -37,6 +37,7 @@
"end_to_end_integrationtest_cts.cc",
"heapprofd_java_test_cts.cc",
"heapprofd_test_cts.cc",
+ "traced_perf_test_cts.cc",
"utils.cc",
]
}
diff --git a/test/cts/producer/jni/fake_producer_jni.cc b/test/cts/producer/jni/fake_producer_jni.cc
index ab587a8..7eb470e 100644
--- a/test/cts/producer/jni/fake_producer_jni.cc
+++ b/test/cts/producer/jni/fake_producer_jni.cc
@@ -56,8 +56,9 @@
lock.unlock();
});
- FakeProducer producer(name);
- producer.Connect(GetProducerSocket(), &task_runner, [] {}, [] {});
+ FakeProducer producer(name, &task_runner);
+ producer.Connect(
+ GetProducerSocket(), [] {}, [] {});
task_runner.Run();
// Cleanup the task runner again to remove outside visibilty so we can
diff --git a/test/cts/heapprofd_test_apps/Android.bp b/test/cts/test_apps/Android.bp
similarity index 93%
rename from test/cts/heapprofd_test_apps/Android.bp
rename to test/cts/test_apps/Android.bp
index bc2b323..543c1f6 100644
--- a/test/cts/heapprofd_test_apps/Android.bp
+++ b/test/cts/test_apps/Android.bp
@@ -27,7 +27,7 @@
srcs: ["src/**/*.java"],
sdk_version: "current",
jni_libs: [
- "libperfettocts_heapprofdtarget",
+ "libperfettocts_native",
"libnativehelper_compat_libc++",
],
}
@@ -47,7 +47,7 @@
srcs: ["src/**/*.java"],
sdk_version: "current",
jni_libs: [
- "libperfettocts_heapprofdtarget",
+ "libperfettocts_native",
"libnativehelper_compat_libc++",
],
}
@@ -67,7 +67,7 @@
srcs: ["src/**/*.java"],
sdk_version: "current",
jni_libs: [
- "libperfettocts_heapprofdtarget",
+ "libperfettocts_native",
"libnativehelper_compat_libc++",
],
}
diff --git a/test/cts/heapprofd_test_apps/AndroidManifest_debuggable.xml b/test/cts/test_apps/AndroidManifest_debuggable.xml
similarity index 71%
rename from test/cts/heapprofd_test_apps/AndroidManifest_debuggable.xml
rename to test/cts/test_apps/AndroidManifest_debuggable.xml
index 999541e..c85ab05 100755
--- a/test/cts/heapprofd_test_apps/AndroidManifest_debuggable.xml
+++ b/test/cts/test_apps/AndroidManifest_debuggable.xml
@@ -31,6 +31,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.BusyWaitActivity"
+ android:exported="false">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.debuggable.BusyWaitActivity"
+ android:targetActivity="android.perfetto.cts.app.BusyWaitActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
</application>
</manifest>
diff --git a/test/cts/heapprofd_test_apps/AndroidManifest_profileable.xml b/test/cts/test_apps/AndroidManifest_profileable.xml
similarity index 71%
rename from test/cts/heapprofd_test_apps/AndroidManifest_profileable.xml
rename to test/cts/test_apps/AndroidManifest_profileable.xml
index 99ceda2..129e922 100755
--- a/test/cts/heapprofd_test_apps/AndroidManifest_profileable.xml
+++ b/test/cts/test_apps/AndroidManifest_profileable.xml
@@ -32,6 +32,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.BusyWaitActivity"
+ android:exported="false">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.profileable.BusyWaitActivity"
+ android:targetActivity="android.perfetto.cts.app.BusyWaitActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
</application>
</manifest>
diff --git a/test/cts/heapprofd_test_apps/AndroidManifest_release.xml b/test/cts/test_apps/AndroidManifest_release.xml
similarity index 71%
rename from test/cts/heapprofd_test_apps/AndroidManifest_release.xml
rename to test/cts/test_apps/AndroidManifest_release.xml
index b2dfd25..5b64a94 100755
--- a/test/cts/heapprofd_test_apps/AndroidManifest_release.xml
+++ b/test/cts/test_apps/AndroidManifest_release.xml
@@ -31,6 +31,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity
+ android:name="android.perfetto.cts.app.BusyWaitActivity"
+ android:exported="false">
+ </activity>
+ <activity-alias
+ android:name="android.perfetto.cts.app.release.BusyWaitActivity"
+ android:targetActivity="android.perfetto.cts.app.BusyWaitActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
</application>
</manifest>
diff --git a/test/cts/heapprofd_test_apps/jni/Android.bp b/test/cts/test_apps/jni/Android.bp
similarity index 95%
rename from test/cts/heapprofd_test_apps/jni/Android.bp
rename to test/cts/test_apps/jni/Android.bp
index de090d2..3f50e72 100644
--- a/test/cts/heapprofd_test_apps/jni/Android.bp
+++ b/test/cts/test_apps/jni/Android.bp
@@ -14,7 +14,7 @@
// limitations under the License.
cc_library_shared {
- name: "libperfettocts_heapprofdtarget",
+ name: "libperfettocts_native",
srcs: [
"target.cc",
],
diff --git a/test/cts/heapprofd_test_apps/jni/target.cc b/test/cts/test_apps/jni/target.cc
similarity index 77%
rename from test/cts/heapprofd_test_apps/jni/target.cc
rename to test/cts/test_apps/jni/target.cc
index 6bedf84..8f847c4 100644
--- a/test/cts/heapprofd_test_apps/jni/target.cc
+++ b/test/cts/test_apps/jni/target.cc
@@ -36,9 +36,21 @@
}
}
+// Runs continuously as a target for the sampling perf profiler tests.
+__attribute__((noreturn)) void perfetto_busy_wait() {
+ for (volatile unsigned i = 0;; i++) {
+ }
+}
+
} // namespace
extern "C" JNIEXPORT void JNICALL
Java_android_perfetto_cts_app_MainActivity_runNative(JNIEnv*, jclass) {
perfetto_test_allocations();
}
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_perfetto_cts_app_BusyWaitActivity_runNativeBusyWait(JNIEnv*,
+ jclass) {
+ perfetto_busy_wait();
+}
diff --git a/test/cts/heapprofd_test_apps/src/android/perfetto/cts/app/MainActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/BusyWaitActivity.java
similarity index 82%
copy from test/cts/heapprofd_test_apps/src/android/perfetto/cts/app/MainActivity.java
copy to test/cts/test_apps/src/android/perfetto/cts/app/BusyWaitActivity.java
index 0e26bfc..689c98a 100644
--- a/test/cts/heapprofd_test_apps/src/android/perfetto/cts/app/MainActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/BusyWaitActivity.java
@@ -19,9 +19,9 @@
import android.app.Activity;
import android.os.Bundle;
-public class MainActivity extends Activity {
+public class BusyWaitActivity extends Activity {
static {
- System.loadLibrary("perfettocts_heapprofdtarget");
+ System.loadLibrary("perfettocts_native");
}
@Override
@@ -31,14 +31,13 @@
new Thread(new Runnable() {
public void run() {
try {
- runNative();
+ runNativeBusyWait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
- })
- .start();
+ }).start();
}
- private static native void runNative();
+ private static native void runNativeBusyWait();
}
diff --git a/test/cts/heapprofd_test_apps/src/android/perfetto/cts/app/MainActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
similarity index 92%
rename from test/cts/heapprofd_test_apps/src/android/perfetto/cts/app/MainActivity.java
rename to test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
index 0e26bfc..0cabd16 100644
--- a/test/cts/heapprofd_test_apps/src/android/perfetto/cts/app/MainActivity.java
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/MainActivity.java
@@ -21,7 +21,7 @@
public class MainActivity extends Activity {
static {
- System.loadLibrary("perfettocts_heapprofdtarget");
+ System.loadLibrary("perfettocts_native");
}
@Override
@@ -36,8 +36,7 @@
ex.printStackTrace();
}
}
- })
- .start();
+ }).start();
}
private static native void runNative();
diff --git a/test/cts/traced_perf_test_cts.cc b/test/cts/traced_perf_test_cts.cc
new file mode 100644
index 0000000..d6745fc
--- /dev/null
+++ b/test/cts/traced_perf_test_cts.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/tracing/core/data_source_config.h"
+#include "src/base/test/test_task_runner.h"
+#include "test/cts/utils.h"
+#include "test/gtest_and_gmock.h"
+#include "test/test_helper.h"
+
+#include "protos/perfetto/config/profiling/perf_event_config.gen.h"
+#include "protos/perfetto/trace/profiling/profile_common.gen.h"
+#include "protos/perfetto/trace/profiling/profile_packet.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto {
+namespace {
+
+std::vector<protos::gen::TracePacket> ProfileSystemWide(std::string app_name) {
+ base::TestTaskRunner task_runner;
+
+ // (re)start the target app's main activity
+ if (IsAppRunning(app_name)) {
+ StopApp(app_name, "old.app.stopped", &task_runner);
+ task_runner.RunUntilCheckpoint("old.app.stopped", 1000 /*ms*/);
+ }
+ StartAppActivity(app_name, "BusyWaitActivity", "target.app.running",
+ &task_runner,
+ /*delay_ms=*/100);
+ task_runner.RunUntilCheckpoint("target.app.running", 1000 /*ms*/);
+
+ // set up tracing
+ TestHelper helper(&task_runner);
+ helper.ConnectConsumer();
+ helper.WaitForConsumerConnect();
+
+ TraceConfig trace_config;
+ trace_config.add_buffers()->set_size_kb(10 * 1024);
+ trace_config.set_duration_ms(2000);
+
+ auto* ds_config = trace_config.add_data_sources()->mutable_config();
+ ds_config->set_name("linux.perf");
+ ds_config->set_target_buffer(0);
+
+ protos::gen::PerfEventConfig perf_config;
+
+ perf_config.set_all_cpus(true);
+ perf_config.set_sampling_frequency(10); // Hz
+ ds_config->set_perf_event_config_raw(perf_config.SerializeAsString());
+
+ // start tracing
+ helper.StartTracing(trace_config);
+ helper.WaitForTracingDisabled(10000 /*ms*/);
+ helper.ReadData();
+ helper.WaitForReadData();
+
+ return helper.trace();
+}
+
+void AssertHasSampledStacksForPid(std::vector<protos::gen::TracePacket> packets,
+ int target_pid) {
+ ASSERT_GT(packets.size(), 0u);
+
+ int samples_found = 0;
+ for (const auto& packet : packets) {
+ if (!packet.has_perf_sample())
+ continue;
+
+ EXPECT_GT(packet.timestamp(), 0u) << "all samples should have a timestamp";
+ const auto& sample = packet.perf_sample();
+ if (sample.pid() != static_cast<uint32_t>(target_pid))
+ continue;
+
+ // TODO(rsavitski): include |sample.has_sample_skipped_reason| once that is
+ // merged.
+ if (sample.has_kernel_records_lost())
+ continue;
+
+ // A full sample
+ EXPECT_GT(sample.tid(), 0u);
+ EXPECT_GT(sample.callstack_iid(), 0u);
+ samples_found += 1;
+ }
+ EXPECT_GT(samples_found, 0);
+}
+
+void AssertNoStacksForPid(std::vector<protos::gen::TracePacket> packets,
+ int target_pid) {
+ for (const auto& packet : packets) {
+ if (packet.perf_sample().pid() == static_cast<uint32_t>(target_pid)) {
+ EXPECT_EQ(packet.perf_sample().callstack_iid(), 0u);
+ }
+ }
+}
+
+TEST(TracedPerfCtsTest, SystemWideDebuggableApp) {
+ std::string app_name = "android.perfetto.cts.app.debuggable";
+ const auto& packets = ProfileSystemWide(app_name);
+ int app_pid = PidForProcessName(app_name);
+ ASSERT_GT(app_pid, 0) << "failed to find pid for target process";
+
+ AssertHasSampledStacksForPid(packets, app_pid);
+ StopApp(app_name);
+}
+
+TEST(TracedPerfCtsTest, SystemWideProfileableApp) {
+ std::string app_name = "android.perfetto.cts.app.profileable";
+ const auto& packets = ProfileSystemWide(app_name);
+ int app_pid = PidForProcessName(app_name);
+ ASSERT_GT(app_pid, 0) << "failed to find pid for target process";
+
+ AssertHasSampledStacksForPid(packets, app_pid);
+ StopApp(app_name);
+}
+
+TEST(TracedPerfCtsTest, SystemWideReleaseApp) {
+ std::string app_name = "android.perfetto.cts.app.release";
+ const auto& packets = ProfileSystemWide(app_name);
+ int app_pid = PidForProcessName(app_name);
+ ASSERT_GT(app_pid, 0) << "failed to find pid for target process";
+
+ if (IsDebuggableBuild())
+ AssertHasSampledStacksForPid(packets, app_pid);
+ else
+ AssertNoStacksForPid(packets, app_pid);
+
+ StopApp(app_name);
+}
+
+} // namespace
+} // namespace perfetto
diff --git a/test/cts/utils.cc b/test/cts/utils.cc
index 4ea9309..b11cddf 100644
--- a/test/cts/utils.cc
+++ b/test/cts/utils.cc
@@ -20,6 +20,7 @@
#include <sys/system_properties.h>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
@@ -48,10 +49,7 @@
char buf[PROP_VALUE_MAX + 1] = {};
int ret = __system_property_get("ro.debuggable", buf);
PERFETTO_CHECK(ret >= 0);
- std::string debuggable(buf);
- if (debuggable == "1")
- return true;
- return false;
+ return std::string(buf) == "1";
}
// note: cannot use gtest macros due to return type
@@ -67,6 +65,25 @@
PERFETTO_FATAL("unexpected exit status from system(pgrep): %d", exit_status);
}
+int PidForProcessName(const std::string& name) {
+ // quirk: need to exclude ourselves from the result as the pgrep's cmdline
+ // matches itself when invoked via popen.
+ std::string cmd = "pgrep -f " + name + " | grep -v $$";
+ FILE* fp = popen(cmd.c_str(), "re");
+ if (!fp)
+ return -1;
+
+ std::string out;
+ base::ReadFileStream(fp, &out);
+ pclose(fp);
+
+ char* endptr = nullptr;
+ int pid = static_cast<int>(strtol(out.c_str(), &endptr, 10));
+ if (*endptr != '\0' && *endptr != '\n')
+ return -1;
+ return pid;
+}
+
void WaitForProcess(const std::string& process,
const std::string& checkpoint_name,
base::TestTaskRunner* task_runner,
diff --git a/test/cts/utils.h b/test/cts/utils.h
index e2796ef..3fd5a3e 100644
--- a/test/cts/utils.h
+++ b/test/cts/utils.h
@@ -27,6 +27,9 @@
bool IsAppRunning(const std::string& name);
+// returns -1 if the process wasn't found
+int PidForProcessName(const std::string& name);
+
void WaitForProcess(const std::string& process,
const std::string& checkpoint_name,
base::TestTaskRunner* task_runner,
diff --git a/test/end_to_end_integrationtest.cc b/test/end_to_end_integrationtest.cc
index 12cfa9c..7dc37b1 100644
--- a/test/end_to_end_integrationtest.cc
+++ b/test/end_to_end_integrationtest.cc
@@ -268,14 +268,6 @@
while (!ftrace_procfs_ && kTracingPaths[index]) {
ftrace_procfs_ = FtraceProcfs::Create(kTracingPaths[index++]);
}
- if (!ftrace_procfs_)
- return;
- ftrace_procfs_->SetTracingOn(false);
- }
-
- void TearDown() override {
- if (ftrace_procfs_)
- ftrace_procfs_->SetTracingOn(false);
}
std::unique_ptr<FtraceProcfs> ftrace_procfs_;
@@ -348,10 +340,8 @@
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
#define TreeHuggerOnly(x) x
-#define ExcludeTreeHugger(x) DISABLED_##x
#else
#define TreeHuggerOnly(x) DISABLED_##x
-#define ExcludeTreeHugger(x) x
#endif
// TODO(b/73453011): reenable on more platforms (including standalone Android).
@@ -689,13 +679,21 @@
EXPECT_FALSE(helper.AttachConsumer("key"));
}
-// TODO(b/148841422): Reenable on treehugger once selinux rules are in place.
-TEST_F(PerfettoTest, ExcludeTreeHugger(TestProducerProvidedSMB)) {
+TEST_F(PerfettoTest, TestProducerProvidedSMB) {
base::TestTaskRunner task_runner;
TestHelper helper(&task_runner);
helper.CreateProducerProvidedSmb();
+ protos::gen::TestConfig test_config;
+ test_config.set_seed(42);
+ test_config.set_message_count(1);
+ test_config.set_message_size(1024);
+ test_config.set_send_batch_on_register(true);
+
+ // Write a first batch before connection.
+ helper.ProduceStartupEventBatch(test_config);
+
helper.StartServiceIfRequired();
helper.ConnectFakeProducer();
helper.ConnectConsumer();
@@ -708,12 +706,10 @@
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.perfetto.FakeProducer");
ds_config->set_target_buffer(0);
+ *ds_config->mutable_for_testing() = test_config;
- ds_config->mutable_for_testing()->set_seed(42);
- ds_config->mutable_for_testing()->set_message_count(1);
- ds_config->mutable_for_testing()->set_message_size(1024);
- ds_config->mutable_for_testing()->set_send_batch_on_register(true);
-
+ // The data source is configured to emit another batch when it is started via
+ // send_batch_on_register in the TestConfig.
helper.StartTracing(trace_config);
helper.WaitForTracingDisabled();
@@ -723,8 +719,11 @@
helper.WaitForReadData();
const auto& packets = helper.trace();
- ASSERT_EQ(packets.size(), 1u);
+ // We should have produced two batches, one before the producer connected and
+ // another one when the data source was started.
+ ASSERT_EQ(packets.size(), 2u);
ASSERT_TRUE(packets[0].has_for_testing());
+ ASSERT_TRUE(packets[1].has_for_testing());
}
// Disable cmdline tests on sanitizets because they use fork() and that messes
diff --git a/test/fake_producer.cc b/test/fake_producer.cc
index dd5c0e7..784b711 100644
--- a/test/fake_producer.cc
+++ b/test/fake_producer.cc
@@ -22,6 +22,7 @@
#include "perfetto/base/time.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/traced/traced.h"
+#include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "perfetto/tracing/core/data_source_config.h"
@@ -32,21 +33,27 @@
namespace perfetto {
-FakeProducer::FakeProducer(const std::string& name) : name_(name) {}
+namespace {
+const MaybeUnboundBufferID kStartupTargetBufferReservationId = 1;
+} // namespace
+
+FakeProducer::FakeProducer(const std::string& name,
+ base::TaskRunner* task_runner)
+ : name_(name), task_runner_(task_runner) {}
FakeProducer::~FakeProducer() = default;
void FakeProducer::Connect(const char* socket_name,
- base::TaskRunner* task_runner,
std::function<void()> on_setup_data_source_instance,
std::function<void()> on_create_data_source_instance,
- std::unique_ptr<SharedMemory> shm) {
+ std::unique_ptr<SharedMemory> shm,
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter) {
PERFETTO_DCHECK_THREAD(thread_checker_);
- task_runner_ = task_runner;
endpoint_ = ProducerIPCClient::Connect(
- socket_name, this, "android.perfetto.FakeProducer", task_runner,
+ socket_name, this, "android.perfetto.FakeProducer", task_runner_,
TracingService::ProducerSMBScrapingMode::kDefault,
/*shared_memory_size_hint_bytes=*/0,
- /*shared_memory_page_size_hint_bytes=*/base::kPageSize, std::move(shm));
+ /*shared_memory_page_size_hint_bytes=*/base::kPageSize, std::move(shm),
+ std::move(shm_arbiter));
on_setup_data_source_instance_ = std::move(on_setup_data_source_instance);
on_create_data_source_instance_ = std::move(on_create_data_source_instance);
}
@@ -71,13 +78,17 @@
void FakeProducer::StartDataSource(DataSourceInstanceID,
const DataSourceConfig& source_config) {
PERFETTO_DCHECK_THREAD(thread_checker_);
- trace_writer_ = endpoint_->CreateTraceWriter(
- static_cast<BufferID>(source_config.target_buffer()));
- rnd_engine_ = std::minstd_rand0(source_config.for_testing().seed());
- message_count_ = source_config.for_testing().message_count();
- message_size_ = source_config.for_testing().message_size();
- max_messages_per_second_ =
- source_config.for_testing().max_messages_per_second();
+ if (trace_writer_) {
+ // Startup tracing was already active, just bind the target buffer.
+ endpoint_->MaybeSharedMemoryArbiter()->BindStartupTargetBuffer(
+ kStartupTargetBufferReservationId,
+ static_cast<BufferID>(source_config.target_buffer()));
+ } else {
+ // Common case: Start tracing now.
+ trace_writer_ = endpoint_->CreateTraceWriter(
+ static_cast<BufferID>(source_config.target_buffer()));
+ SetupFromConfig(source_config.for_testing());
+ }
if (source_config.for_testing().send_batch_on_register()) {
ProduceEventBatch(on_create_data_source_instance_);
} else {
@@ -91,49 +102,29 @@
}
// Note: this can be called on a different thread.
-void FakeProducer::ProduceEventBatch(std::function<void()> callback) {
- task_runner_->PostTask([this, callback] {
- PERFETTO_CHECK(trace_writer_);
- PERFETTO_CHECK(message_size_ > 1);
- std::unique_ptr<char, base::FreeDeleter> payload(
- static_cast<char*>(malloc(message_size_)));
- memset(payload.get(), '.', message_size_);
- payload.get()[message_size_ - 1] = 0;
+void FakeProducer::ProduceStartupEventBatch(
+ const protos::gen::TestConfig& config,
+ SharedMemoryArbiter* arbiter,
+ std::function<void()> callback) {
+ task_runner_->PostTask([this, config, arbiter, callback] {
+ SetupFromConfig(config);
- base::TimeMillis start = base::GetWallTimeMs();
- int64_t iterations = 0;
- uint32_t messages_to_emit = message_count_;
- while (messages_to_emit > 0) {
- uint32_t messages_in_minibatch =
- max_messages_per_second_ == 0
- ? messages_to_emit
- : std::min(max_messages_per_second_, messages_to_emit);
- PERFETTO_DCHECK(messages_to_emit >= messages_in_minibatch);
+ PERFETTO_CHECK(!trace_writer_);
+ trace_writer_ =
+ arbiter->CreateStartupTraceWriter(kStartupTargetBufferReservationId);
- for (uint32_t i = 0; i < messages_in_minibatch; i++) {
- auto handle = trace_writer_->NewTracePacket();
- handle->set_for_testing()->set_seq_value(
- static_cast<uint32_t>(rnd_engine_()));
- handle->set_for_testing()->set_str(payload.get(), message_size_);
- }
- messages_to_emit -= messages_in_minibatch;
- iterations++;
+ EmitEventBatchOnTaskRunner({});
- // Pause until the second boundary to make sure that we are adhering to
- // the speed limitation.
- if (max_messages_per_second_ > 0) {
- int64_t expected_time_taken = iterations * 1000;
- base::TimeMillis time_taken = base::GetWallTimeMs() - start;
- while (time_taken.count() < expected_time_taken) {
- usleep(static_cast<useconds_t>(
- (expected_time_taken - time_taken.count()) * 1000));
- time_taken = base::GetWallTimeMs() - start;
- }
- }
- trace_writer_->Flush(messages_to_emit > 0 ? [] {} : callback);
- }
+ // Issue callback right after writing - cannot wait for flush yet because
+ // we're not connected yet.
+ callback();
});
}
+// Note: this can be called on a different thread.
+void FakeProducer::ProduceEventBatch(std::function<void()> callback) {
+ task_runner_->PostTask(
+ [this, callback] { EmitEventBatchOnTaskRunner(callback); });
+}
void FakeProducer::OnTracingSetup() {}
@@ -146,4 +137,53 @@
endpoint_->NotifyFlushComplete(flush_request_id);
}
+void FakeProducer::SetupFromConfig(const protos::gen::TestConfig& config) {
+ rnd_engine_ = std::minstd_rand0(config.seed());
+ message_count_ = config.message_count();
+ message_size_ = config.message_size();
+ max_messages_per_second_ = config.max_messages_per_second();
+}
+
+void FakeProducer::EmitEventBatchOnTaskRunner(std::function<void()> callback) {
+ PERFETTO_CHECK(trace_writer_);
+ PERFETTO_CHECK(message_size_ > 1);
+ std::unique_ptr<char, base::FreeDeleter> payload(
+ static_cast<char*>(malloc(message_size_)));
+ memset(payload.get(), '.', message_size_);
+ payload.get()[message_size_ - 1] = 0;
+
+ base::TimeMillis start = base::GetWallTimeMs();
+ int64_t iterations = 0;
+ uint32_t messages_to_emit = message_count_;
+ while (messages_to_emit > 0) {
+ uint32_t messages_in_minibatch =
+ max_messages_per_second_ == 0
+ ? messages_to_emit
+ : std::min(max_messages_per_second_, messages_to_emit);
+ PERFETTO_DCHECK(messages_to_emit >= messages_in_minibatch);
+
+ for (uint32_t i = 0; i < messages_in_minibatch; i++) {
+ auto handle = trace_writer_->NewTracePacket();
+ handle->set_for_testing()->set_seq_value(
+ static_cast<uint32_t>(rnd_engine_()));
+ handle->set_for_testing()->set_str(payload.get(), message_size_);
+ }
+ messages_to_emit -= messages_in_minibatch;
+ iterations++;
+
+ // Pause until the second boundary to make sure that we are adhering to
+ // the speed limitation.
+ if (max_messages_per_second_ > 0) {
+ int64_t expected_time_taken = iterations * 1000;
+ base::TimeMillis time_taken = base::GetWallTimeMs() - start;
+ while (time_taken.count() < expected_time_taken) {
+ usleep(static_cast<useconds_t>(
+ (expected_time_taken - time_taken.count()) * 1000));
+ time_taken = base::GetWallTimeMs() - start;
+ }
+ }
+ trace_writer_->Flush(messages_to_emit > 0 ? [] {} : callback);
+ }
+}
+
} // namespace perfetto
diff --git a/test/fake_producer.h b/test/fake_producer.h
index 46af80e..f71d90e 100644
--- a/test/fake_producer.h
+++ b/test/fake_producer.h
@@ -30,16 +30,30 @@
namespace perfetto {
+namespace protos {
+namespace gen {
+class TestConfig;
+} // namespace gen
+} // namespace protos
+
class FakeProducer : public Producer {
public:
- explicit FakeProducer(const std::string& name);
+ explicit FakeProducer(const std::string& name, base::TaskRunner* task_runner);
~FakeProducer() override;
void Connect(const char* socket_name,
- base::TaskRunner* task_runner,
std::function<void()> on_setup_data_source_instance,
std::function<void()> on_create_data_source_instance,
- std::unique_ptr<SharedMemory> shm = nullptr);
+ std::unique_ptr<SharedMemory> shm = nullptr,
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
+
+ // Produces a batch of events (as configured by the passed config) before the
+ // producer is connected to the service using the provided unbound arbiter.
+ // Posts |callback| once the data was written. May only be called once.
+ void ProduceStartupEventBatch(
+ const protos::gen::TestConfig& config,
+ SharedMemoryArbiter* arbiter,
+ std::function<void()> callback = [] {});
// Produces a batch of events (as configured in the DataSourceConfig) and
// posts a callback when the service acknowledges the commit.
@@ -63,11 +77,12 @@
size_t /*num_data_sources*/) override {}
private:
- void Shutdown();
+ void SetupFromConfig(const protos::gen::TestConfig& config);
+ void EmitEventBatchOnTaskRunner(std::function<void()> callback);
base::ThreadChecker thread_checker_;
- base::TaskRunner* task_runner_ = nullptr;
std::string name_;
+ base::TaskRunner* task_runner_ = nullptr;
std::minstd_rand0 rnd_engine_;
uint32_t message_size_ = 0;
uint32_t message_count_ = 0;
diff --git a/test/gtest_logcat_printer.cc b/test/gtest_logcat_printer.cc
new file mode 100644
index 0000000..f88e01b
--- /dev/null
+++ b/test/gtest_logcat_printer.cc
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file registers a GTest listener that logs test begin/end in logcat.
+// This is to avoid problems like b/149852934 where the test output cannot be
+// correlated with the prouction code's logcat logs because they use different
+// clock sources.
+
+#include <android/log.h>
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace test {
+
+namespace {
+
+#define PERFETTO_TEST_LOG(...) \
+ __android_log_print(ANDROID_LOG_DEBUG, "perfetto", ##__VA_ARGS__)
+
+class LogcatPrinter : public testing::EmptyTestEventListener {
+ public:
+ LogcatPrinter();
+ ~LogcatPrinter() override;
+
+ // testing::EmptyTestEventListener:
+ void OnTestCaseStart(const testing::TestCase& test_case) override;
+ void OnTestStart(const testing::TestInfo& test_info) override;
+ void OnTestEnd(const testing::TestInfo& test_info) override;
+ void OnTestCaseEnd(const testing::TestCase& test_case) override;
+};
+
+LogcatPrinter::LogcatPrinter() = default;
+LogcatPrinter::~LogcatPrinter() = default;
+
+void LogcatPrinter::OnTestCaseStart(const testing::TestCase& test_case) {
+ PERFETTO_TEST_LOG("Test case start: %s", test_case.name());
+}
+
+void LogcatPrinter::OnTestStart(const testing::TestInfo& test_info) {
+ PERFETTO_TEST_LOG("Test start: %s.%s", test_info.test_case_name(),
+ test_info.name());
+}
+
+void LogcatPrinter::OnTestEnd(const testing::TestInfo& test_info) {
+ const auto* result = test_info.result();
+ const char* state = "N/A";
+ if (result)
+ state = result->Passed() ? "PASS" : "FAIL";
+ PERFETTO_TEST_LOG("Test end: %s.%s [%s]", test_info.test_case_name(),
+ test_info.name(), state);
+}
+
+void LogcatPrinter::OnTestCaseEnd(const testing::TestCase& test_case) {
+ PERFETTO_TEST_LOG("Test case end: %s. succeeded=%d, failed=%d",
+ test_case.name(), test_case.successful_test_count(),
+ test_case.failed_test_count());
+}
+
+// This static initializer
+void __attribute__((constructor)) __attribute__((visibility("default")))
+SetupGtestLogcatPrinter() {
+ static LogcatPrinter* instance = new LogcatPrinter();
+ auto& listeners = testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(instance);
+}
+
+} // namespace
+} // namespace test
+} // namespace perfetto
diff --git a/test/metrics/java_heap_stats.out b/test/metrics/java_heap_stats.out
index 3314b44..d5421f7 100644
--- a/test/metrics/java_heap_stats.out
+++ b/test/metrics/java_heap_stats.out
@@ -7,7 +7,7 @@
}
samples {
ts: 10
- heap_size: 736
+ heap_size: 992
reachable_heap_size: 352
}
}
diff --git a/test/metrics/obfuscated_heap_graph.textproto b/test/metrics/obfuscated_heap_graph.textproto
index b6654fe..4eb9ecf 100644
--- a/test/metrics/obfuscated_heap_graph.textproto
+++ b/test/metrics/obfuscated_heap_graph.textproto
@@ -37,6 +37,11 @@
type_id: 2
self_size: 32
}
+ objects {
+ id: 0x03
+ type_id: 3
+ self_size: 32
+ }
continued: true
index: 1
}
@@ -53,6 +58,10 @@
iid: 2
str: "a"
}
+ type_names {
+ iid: 3
+ str: "java.lang.Class<abc[]>"
+ }
field_names {
iid: 1
str: "FactoryProducerDelegateImplActor.a"
diff --git a/test/metrics/unmapped_java_symbols.out b/test/metrics/unmapped_java_symbols.out
index 5888642..4628dd9 100644
--- a/test/metrics/unmapped_java_symbols.out
+++ b/test/metrics/unmapped_java_symbols.out
@@ -6,6 +6,7 @@
}
type_name: "FactoryProducerDelegateImplActor"
type_name: "a"
+ type_name: "abc"
field {
field_name: "FactoryProducerDelegateImplActor.a"
}
diff --git a/test/test_helper.cc b/test/test_helper.cc
index ced1f96..0e16645 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -122,6 +122,14 @@
return fake_producer_thread_.producer()->IsShmemProvidedByProducer();
}
+void TestHelper::ProduceStartupEventBatch(
+ const protos::gen::TestConfig& config) {
+ auto on_data_written = CreateCheckpoint("startup_data_written");
+ fake_producer_thread_.ProduceStartupEventBatch(config,
+ WrapTask(on_data_written));
+ RunUntilCheckpoint("startup_data_written");
+}
+
void TestHelper::StartTracing(const TraceConfig& config,
base::ScopedFile file) {
trace_.clear();
diff --git a/test/test_helper.h b/test/test_helper.h
index 46ae6de..8617466 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -20,6 +20,7 @@
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/thread_task_runner.h"
#include "perfetto/ext/tracing/core/consumer.h"
+#include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
#include "perfetto/ext/tracing/ipc/service_ipc_host.h"
@@ -103,21 +104,23 @@
std::function<void()> start_callback)
: producer_socket_(producer_socket),
setup_callback_(std::move(setup_callback)),
- start_callback_(std::move(start_callback)) {}
+ start_callback_(std::move(start_callback)) {
+ runner_ = base::ThreadTaskRunner::CreateAndStart("perfetto.prd.fake");
+ runner_->PostTaskAndWaitForTesting([this]() {
+ producer_.reset(
+ new FakeProducer("android.perfetto.FakeProducer", runner_->get()));
+ });
+ }
~FakeProducerThread() {
- if (!runner_)
- return;
runner_->PostTaskAndWaitForTesting([this]() { producer_.reset(); });
}
void Connect() {
- runner_ = base::ThreadTaskRunner::CreateAndStart("perfetto.prd.fake");
runner_->PostTaskAndWaitForTesting([this]() {
- producer_.reset(new FakeProducer("android.perfetto.FakeProducer"));
- producer_->Connect(producer_socket_.c_str(), runner_->get(),
- std::move(setup_callback_), std::move(start_callback_),
- std::move(shm_));
+ producer_->Connect(producer_socket_.c_str(), std::move(setup_callback_),
+ std::move(start_callback_), std::move(shm_),
+ std::move(shm_arbiter_));
});
}
@@ -128,6 +131,14 @@
void CreateProducerProvidedSmb() {
PosixSharedMemory::Factory factory;
shm_ = factory.CreateSharedMemory(1024 * 1024);
+ shm_arbiter_ =
+ SharedMemoryArbiter::CreateUnboundInstance(shm_.get(), base::kPageSize);
+ }
+
+ void ProduceStartupEventBatch(const protos::gen::TestConfig& config,
+ std::function<void()> callback) {
+ PERFETTO_CHECK(shm_arbiter_);
+ producer_->ProduceStartupEventBatch(config, shm_arbiter_.get(), callback);
}
private:
@@ -138,6 +149,7 @@
std::function<void()> setup_callback_;
std::function<void()> start_callback_;
std::unique_ptr<SharedMemory> shm_;
+ std::unique_ptr<SharedMemoryArbiter> shm_arbiter_;
};
class TestHelper : public Consumer {
@@ -169,6 +181,7 @@
bool AttachConsumer(const std::string& key);
void CreateProducerProvidedSmb();
bool IsShmemProvidedByProducer();
+ void ProduceStartupEventBatch(const protos::gen::TestConfig& config);
void WaitForConsumerConnect();
void WaitForProducerSetup();
diff --git a/test/trace_processor/heap_graph.textproto b/test/trace_processor/heap_graph.textproto
index 44b99f6..de4d5b4 100644
--- a/test/trace_processor/heap_graph.textproto
+++ b/test/trace_processor/heap_graph.textproto
@@ -55,6 +55,11 @@
type_id: 4
self_size: 256
}
+ objects {
+ id: 0x06
+ type_id: 5
+ self_size: 256
+ }
continued: true
index: 0
}
@@ -79,6 +84,10 @@
iid: 4
str: "a[]"
}
+ type_names {
+ iid: 5
+ str: "java.lang.Class<a[]>"
+ }
field_names {
iid: 1
str: "FactoryProducerDelegateImplActor.foo"
diff --git a/test/trace_processor/heap_graph_object.out b/test/trace_processor/heap_graph_object.out
index b2cf862..a5cf29e 100644
--- a/test/trace_processor/heap_graph_object.out
+++ b/test/trace_processor/heap_graph_object.out
@@ -4,3 +4,4 @@
2,"heap_graph_object",2,10,3,128,-1,-1,2,0,"Foo","[NULL]","[NULL]"
3,"heap_graph_object",2,10,4,256,-1,-1,2,0,"a","DeobfuscatedA","[NULL]"
4,"heap_graph_object",2,10,5,256,-1,-1,3,1,"a[]","DeobfuscatedA[]","ROOT_JAVA_FRAME"
+5,"heap_graph_object",2,10,6,256,-1,-1,3,0,"java.lang.Class<a[]>","java.lang.Class<DeobfuscatedA[]>","[NULL]"
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 59d5a11..95a4c39 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -63,6 +63,7 @@
'//src/traced/service:traced',
'//test/cts:perfetto_cts_deps',
'//test/cts:perfetto_cts_jni_deps',
+ '//test:perfetto_gtest_logcat_printer',
]
# Host targets
@@ -184,6 +185,8 @@
def enable_gtest_and_gmock(module):
module.static_libs.add('libgmock')
module.static_libs.add('libgtest')
+ if module.name != 'perfetto_gtest_logcat_printer':
+ module.whole_static_libs.add('perfetto_gtest_logcat_printer')
def enable_protobuf_full(module):
@@ -310,12 +313,14 @@
self.name = name
self.shared_libs = set()
self.static_libs = set()
+ self.whole_static_libs = set()
self.cflags = set()
def to_string(self, output):
nested_out = []
self._output_field(nested_out, 'shared_libs')
self._output_field(nested_out, 'static_libs')
+ self._output_field(nested_out, 'whole_static_libs')
self._output_field(nested_out, 'cflags')
if nested_out:
@@ -340,6 +345,7 @@
self.comment = 'GN: ' + gn_utils.label_without_toolchain(gn_target)
self.shared_libs = set()
self.static_libs = set()
+ self.whole_static_libs = set()
self.tools = set()
self.cmd = None
self.host_supported = False
@@ -375,6 +381,7 @@
self._output_field(output, 'srcs')
self._output_field(output, 'shared_libs')
self._output_field(output, 'static_libs')
+ self._output_field(output, 'whole_static_libs')
self._output_field(output, 'tools')
self._output_field(output, 'cmd', sort=False)
self._output_field(output, 'host_supported')
diff --git a/traced_perf.rc b/traced_perf.rc
index b38b7c4..b4edcfa 100644
--- a/traced_perf.rc
+++ b/traced_perf.rc
@@ -14,7 +14,6 @@
service traced_perf /system/bin/traced_perf
class late_start
- disabled
# socket for receiving /proc/pid/{maps,mem} file descriptors
socket traced_perf stream 0666 root root
user nobody
diff --git a/ui/index.html b/ui/index.html
index 5a9190f..a893d30 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -2,6 +2,8 @@
<html lang="en-us">
<head>
<title>Perfetto UI</title>
+ <!-- See b/149573396 for CSP rationale -->
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; connect-src 'self' http://127.0.0.1:9001 https://*.googleapis.com; navigate-to https://*.perfetto.dev;">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<!-- WebComponents V0 origin trial token for https://ui.perfetto.dev Expires 17 Dec 2020.
See https://crbug.com/1021137. -->
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 13d1e39..ffd33eb 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,9 +15,14 @@
import {Draft} from 'immer';
import {assertExists} from '../base/logging';
-import {Area, CallsiteInfo} from '../common/state';
+import {
+ Area,
+ CallsiteInfo,
+ HeapProfileFlamegraphViewingOption
+} from '../common/state';
import {ConvertTrace, ConvertTraceToPprof} from '../controller/trace_converter';
+import {DEFAULT_VIEWING_OPTION} from './flamegraph_util';
import {
AdbRecordingTarget,
createEmptyState,
@@ -461,6 +466,7 @@
upid: args.upid,
ts: args.ts,
type: args.type,
+ viewingOption: DEFAULT_VIEWING_OPTION,
};
},
@@ -471,7 +477,8 @@
},
changeViewHeapProfileFlamegraph(
- state: StateDraft, args: {viewingOption: string}): void {
+ state: StateDraft,
+ args: {viewingOption: HeapProfileFlamegraphViewingOption}): void {
if (state.currentHeapProfileFlamegraph === null) return;
state.currentHeapProfileFlamegraph.viewingOption = args.viewingOption;
},
diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts
index 4f134c5..a94eddd 100644
--- a/ui/src/common/flamegraph_util.ts
+++ b/ui/src/common/flamegraph_util.ts
@@ -14,10 +14,10 @@
import {CallsiteInfo} from '../common/state';
-export const SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'space';
-export const ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'alloc_space';
-export const OBJECTS_ALLOCATED_NOT_FREED_KEY = 'objects';
-export const OBJECTS_ALLOCATED_KEY = 'alloc_objects';
+export const SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE';
+export const ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE';
+export const OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS';
+export const OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS';
export const DEFAULT_VIEWING_OPTION = SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY;
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index a761961..5bd331a 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -48,6 +48,9 @@
export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM';
+export type HeapProfileFlamegraphViewingOption =
+ 'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS';
+
export interface CallsiteInfo {
id: number;
parentId: number;
@@ -182,8 +185,8 @@
upid: number;
ts: number;
type: string;
+ viewingOption: HeapProfileFlamegraphViewingOption;
expandedCallsite?: CallsiteInfo;
- viewingOption?: string;
}
export interface ChromeSliceSelection {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 4607a32..327a411 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -664,15 +664,33 @@
const name =
this.getTrackName(utid, processName, pid, threadName, tid, upid);
- addTrackGroupActions.push(Actions.addTrackGroup({
+ const addTrackGroup = Actions.addTrackGroup({
engineId: this.engineId,
summaryTrackId,
name,
id: pUuid,
collapsed: !(upid !== null && heapUpids.has(upid)),
- }));
+ });
+
+ // If the track group contains a heap profile, it should be before all
+ // other processes.
+ if (upid !== null && heapUpids.has(upid)) {
+ addTrackGroupActions.unshift(addTrackGroup);
+ } else {
+ addTrackGroupActions.push(addTrackGroup);
+ }
if (upid !== null) {
+ if (heapUpids.has(upid)) {
+ tracksToAdd.push({
+ engineId: this.engineId,
+ kind: HEAP_PROFILE_TRACK_KIND,
+ name: `Heap Profile`,
+ trackGroup: pUuid,
+ config: {upid}
+ });
+ }
+
const counterNames = counterUpids.get(upid);
if (counterNames !== undefined) {
counterNames.forEach(element => {
@@ -691,16 +709,6 @@
});
}
- if (heapUpids.has(upid)) {
- tracksToAdd.push({
- engineId: this.engineId,
- kind: HEAP_PROFILE_TRACK_KIND,
- name: `Heap Profile`,
- trackGroup: pUuid,
- config: {upid}
- });
- }
-
if (upidToProcessTracks.has(upid)) {
for (const track of upidToProcessTracks.get(upid)) {
tracksToAdd.push(Object.assign(track, {trackGroup: pUuid}));
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 57f95c9..a966a45 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -328,6 +328,7 @@
const endSec = Math.min(ts.end, globals.state.traceTime.endSec);
this.visibleWindowTime = new TimeSpan(startSec, endSec);
this.timeScale.setTimeBounds(this.visibleWindowTime);
+ this.updateResolution(this.timeScale.startPx, this.timeScale.endPx);
}
// We lock an area selection by adding an area note. When we select the note
@@ -361,6 +362,7 @@
updateResolution(pxStart: number, pxEnd: number) {
this.timeScale.setLimitsPx(pxStart, pxEnd);
+ this._visibleState.lastUpdate = Date.now() / 1000;
this._visibleState.resolution = globals.getCurResolution();
this.ratelimitedUpdateVisible();
}
diff --git a/ui/src/frontend/heap_profile_panel.ts b/ui/src/frontend/heap_profile_panel.ts
index a210a9e..85b8b7a 100644
--- a/ui/src/frontend/heap_profile_panel.ts
+++ b/ui/src/frontend/heap_profile_panel.ts
@@ -17,11 +17,11 @@
import {Actions} from '../common/actions';
import {
ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
- DEFAULT_VIEWING_OPTION,
OBJECTS_ALLOCATED_KEY,
OBJECTS_ALLOCATED_NOT_FREED_KEY,
- SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY
+ SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
} from '../common/flamegraph_util';
+import {HeapProfileFlamegraphViewingOption} from '../common/state';
import {timeToCode} from '../common/time';
import {Flamegraph} from './flamegraph';
@@ -37,7 +37,6 @@
private ts = 0;
private pid = 0;
private flamegraph: Flamegraph = new Flamegraph([]);
- private currentViewingOption = DEFAULT_VIEWING_OPTION;
view() {
const heapDumpInfo = globals.heapProfileDetails;
@@ -106,8 +105,11 @@
}
}
- getButtonsClass(viewingOption = DEFAULT_VIEWING_OPTION): string {
- return this.currentViewingOption === viewingOption ? '.chosen' : '';
+ getButtonsClass(button: HeapProfileFlamegraphViewingOption): string {
+ if (globals.state.currentHeapProfileFlamegraph === null) return '';
+ return globals.state.currentHeapProfileFlamegraph.viewingOption === button ?
+ '.chosen' :
+ '';
}
getViewingOptionButtons(): m.Children {
@@ -126,7 +128,7 @@
this.changeViewingOption(ALLOC_SPACE_MEMORY_ALLOCATED_KEY);
}
},
- 'alloc_space'),
+ 'alloc space'),
m(`button${this.getButtonsClass(OBJECTS_ALLOCATED_NOT_FREED_KEY)}`,
{
onclick: () => {
@@ -140,11 +142,10 @@
this.changeViewingOption(OBJECTS_ALLOCATED_KEY);
}
},
- 'alloc_objects'));
+ 'alloc objects'));
}
- changeViewingOption(viewingOption: string) {
- this.currentViewingOption = viewingOption;
+ changeViewingOption(viewingOption: HeapProfileFlamegraphViewingOption) {
globals.dispatch(Actions.changeViewHeapProfileFlamegraph({viewingOption}));
}
@@ -164,9 +165,11 @@
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
this.changeFlamegraphData();
+ const current = globals.state.currentHeapProfileFlamegraph;
+ if (current === null) return;
const unit =
- this.currentViewingOption === SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY ||
- this.currentViewingOption === ALLOC_SPACE_MEMORY_ALLOCATED_KEY ?
+ current.viewingOption === SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY ||
+ current.viewingOption === ALLOC_SPACE_MEMORY_ALLOCATED_KEY ?
'B' :
'';
this.flamegraph.draw(ctx, size.width, size.height, 0, HEADER_HEIGHT, unit);