[Reland] Allow typed proto messages to be written into TracedValue.
This is a reland of r.android.com/1699768, caused by a lack of `const`
modifier on a WriteIntoTrace function in Chrome, which was fixed by
crrev.com/c/3500621.
=====
This patch adds TracedValue::WriteProto<proto::pbzero::MessageType>()
method, which can be used to write structured proto inside TracedValue.
Also TracedProto now gets an implicit constructor from TracedValue,
which means that if a C++ class implements
Foo::WriteIntoTrace(TracedProto<...>) method, it can be used in all
places where a class implementing Foo::WriteIntoTrace(TracedValue) can
be used, including:
- being passed as an untyped argument to TRACE_EVENT:
TRACE_EVENT(..., "arg", Foo());
- being passed to TracedDictionary::Add / TracedArray::Append.
This patch also adds MessageType::GetName() method to the protozero
bindings, which is needed for implementation of WriteProto method.
R=eseckler@google.com,skyostil@google.com
CC=primiano@google.com
Bug: b/184558843
Change-Id: I85956dd889780efd1ccf8fd943edee76ab99d820
diff --git a/CHANGELOG b/CHANGELOG
index 02ab1f8..95516cd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,7 +8,7 @@
* Added flow arrows between binder transaction pairs (request/reply
and async send/async recv).
SDK:
- *
+ * Added support for writing typed proto messages inside DebugAnnotations.
v24.2 - 2022-02-10:
diff --git a/include/perfetto/tracing/traced_proto.h b/include/perfetto/tracing/traced_proto.h
index a887d96..aa2aa64 100644
--- a/include/perfetto/tracing/traced_proto.h
+++ b/include/perfetto/tracing/traced_proto.h
@@ -50,11 +50,15 @@
template <typename MessageType>
class TracedProto {
public:
+ // implicit
+ TracedProto(TracedValue&& value)
+ : TracedProto(std::move(value).WriteProto<MessageType>()) {}
+ ~TracedProto() = default;
+
TracedProto(const TracedProto&) = delete;
TracedProto& operator=(const TracedProto&) = delete;
TracedProto& operator=(TracedProto&&) = delete;
TracedProto(TracedProto&&) = default;
- ~TracedProto() = default;
MessageType* operator->() const { return message_; }
@@ -91,10 +95,9 @@
static_assert(std::is_base_of<MessageType,
typename FieldMetadata::message_type>::value,
"Field should belong to the current message");
- return TracedProto<typename FieldMetadata::cpp_field_type>(
+ return Wrap(
message_->template BeginNestedMessage<
- typename FieldMetadata::cpp_field_type>(FieldMetadata::kFieldId),
- context_);
+ typename FieldMetadata::cpp_field_type>(FieldMetadata::kFieldId));
}
template <typename FieldMetadata>
@@ -105,6 +108,7 @@
private:
friend class EventContext;
+ friend class TracedValue;
// Allow TracedProto<Foo> to create TracedProto<Bar>.
template <typename T>
friend class TracedProto;
@@ -112,7 +116,7 @@
// Wraps a raw protozero message using the same context as the current object.
template <typename ChildMessageType>
TracedProto<ChildMessageType> Wrap(ChildMessageType* message) {
- return TracedProto(message, context_);
+ return TracedProto<ChildMessageType>(message, context_);
}
// Context might be null here when writing typed message which is
diff --git a/include/perfetto/tracing/traced_value.h b/include/perfetto/tracing/traced_value.h
index 53f1cb6..7bebd25 100644
--- a/include/perfetto/tracing/traced_value.h
+++ b/include/perfetto/tracing/traced_value.h
@@ -141,6 +141,8 @@
void WriteString(const char*, size_t len) &&;
void WriteString(const std::string&) &&;
void WritePointer(const void* value) &&;
+ template <typename MessageType>
+ TracedProto<MessageType> WriteProto() &&;
// Rules for writing nested dictionaries and arrays:
// - Only one scope (TracedArray, TracedDictionary or TracedValue) can be
@@ -172,6 +174,8 @@
internal::CheckedScope* parent_scope)
: context_(context), checked_scope_(parent_scope) {}
+ protozero::Message* WriteProtoInternal(const char* name);
+
// Temporary support for perfetto::DebugAnnotation C++ class before it's going
// to be replaced by TracedValue.
// TODO(altimin): Convert v8 to use TracedValue directly and delete it.
@@ -182,6 +186,13 @@
internal::CheckedScope checked_scope_;
};
+template <typename MessageType>
+TracedProto<MessageType> TracedValue::WriteProto() && {
+ return TracedProto<MessageType>(
+ static_cast<MessageType*>(WriteProtoInternal(MessageType::GetName())),
+ nullptr);
+}
+
class PERFETTO_EXPORT TracedArray {
public:
TracedArray(const TracedArray&) = delete;
diff --git a/include/perfetto/tracing/traced_value_forward.h b/include/perfetto/tracing/traced_value_forward.h
index 7a91712..a8cbe8e 100644
--- a/include/perfetto/tracing/traced_value_forward.h
+++ b/include/perfetto/tracing/traced_value_forward.h
@@ -22,6 +22,8 @@
class TracedValue;
class TracedArray;
class TracedDictionary;
+template <typename MessageType>
+class TracedProto;
template <typename T>
void WriteIntoTracedValue(TracedValue context, T&& value);
diff --git a/protos/perfetto/trace/interned_data/interned_data.proto b/protos/perfetto/trace/interned_data/interned_data.proto
index cc9c9e3..86b8a9f 100644
--- a/protos/perfetto/trace/interned_data/interned_data.proto
+++ b/protos/perfetto/trace/interned_data/interned_data.proto
@@ -53,7 +53,7 @@
// emitted proactively in advance of referring to them in later packets.
//
// Next reserved id: 8 (up to 15).
-// Next id: 27.
+// Next id: 28.
message InternedData {
// TODO(eseckler): Replace iid fields inside interned messages with
// map<iid, message> type fields in InternedData.
@@ -68,6 +68,7 @@
repeated EventCategory event_categories = 1;
repeated EventName event_names = 2;
repeated DebugAnnotationName debug_annotation_names = 3;
+ repeated DebugAnnotationValueTypeName debug_annotation_value_type_names = 27;
repeated SourceLocation source_locations = 4;
repeated LogMessageBody log_message_body = 20;
repeated HistogramName histogram_names = 25;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 39b2ad0..4dee1cf 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -6996,7 +6996,8 @@
// }
// }
//
-// Next ID: 13.
+// Next ID: 17.
+// Reserved ID: 15
message DebugAnnotation {
// Name fields are set only for dictionary entries.
oneof name_field {
@@ -7024,6 +7025,18 @@
string legacy_json_value = 9;
}
+ // Used to embed arbitrary proto messages (which are also typically used to
+ // represent typed TrackEvent arguments). |proto_type_name| or
+ // |proto_type_name_iid| are storing the full name of the proto messages (e.g.
+ // .perfetto.protos.DebugAnnotation) and |proto_value| contains the serialised
+ // proto messages. See |TracedValue::WriteProto| for more details.
+ oneof proto_type_descriptor {
+ string proto_type_name = 16;
+ // interned DebugAnnotationValueTypeName.
+ uint32 proto_type_name_iid = 13;
+ }
+ optional bytes proto_value = 14;
+
repeated DebugAnnotation dict_entries = 11;
repeated DebugAnnotation array_values = 12;
@@ -7058,6 +7071,12 @@
optional string name = 2;
}
+// See the |proto_type_descriptor| comment.
+message DebugAnnotationValueTypeName {
+ optional uint64 iid = 1;
+ optional string name = 2;
+}
+
// End of protos/perfetto/trace/track_event/debug_annotation.proto
// Begin of protos/perfetto/trace/track_event/log_message.proto
@@ -8109,7 +8128,7 @@
// emitted proactively in advance of referring to them in later packets.
//
// Next reserved id: 8 (up to 15).
-// Next id: 27.
+// Next id: 28.
message InternedData {
// TODO(eseckler): Replace iid fields inside interned messages with
// map<iid, message> type fields in InternedData.
@@ -8124,6 +8143,7 @@
repeated EventCategory event_categories = 1;
repeated EventName event_names = 2;
repeated DebugAnnotationName debug_annotation_names = 3;
+ repeated DebugAnnotationValueTypeName debug_annotation_value_type_names = 27;
repeated SourceLocation source_locations = 4;
repeated LogMessageBody log_message_body = 20;
repeated HistogramName histogram_names = 25;
diff --git a/protos/perfetto/trace/track_event/debug_annotation.proto b/protos/perfetto/trace/track_event/debug_annotation.proto
index d6766cb..8881955 100644
--- a/protos/perfetto/trace/track_event/debug_annotation.proto
+++ b/protos/perfetto/trace/track_event/debug_annotation.proto
@@ -57,7 +57,8 @@
// }
// }
//
-// Next ID: 13.
+// Next ID: 17.
+// Reserved ID: 15
message DebugAnnotation {
// Name fields are set only for dictionary entries.
oneof name_field {
@@ -85,6 +86,18 @@
string legacy_json_value = 9;
}
+ // Used to embed arbitrary proto messages (which are also typically used to
+ // represent typed TrackEvent arguments). |proto_type_name| or
+ // |proto_type_name_iid| are storing the full name of the proto messages (e.g.
+ // .perfetto.protos.DebugAnnotation) and |proto_value| contains the serialised
+ // proto messages. See |TracedValue::WriteProto| for more details.
+ oneof proto_type_descriptor {
+ string proto_type_name = 16;
+ // interned DebugAnnotationValueTypeName.
+ uint32 proto_type_name_iid = 13;
+ }
+ optional bytes proto_value = 14;
+
repeated DebugAnnotation dict_entries = 11;
repeated DebugAnnotation array_values = 12;
@@ -118,3 +131,9 @@
optional uint64 iid = 1;
optional string name = 2;
}
+
+// See the |proto_type_descriptor| comment.
+message DebugAnnotationValueTypeName {
+ optional uint64 iid = 1;
+ optional string name = 2;
+}
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 311672a..feabdf1 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -779,6 +779,10 @@
GenerateConstantsForMessageFields(message);
+ stub_h_->Print(
+ "static constexpr const char* GetName() { return \".$name$\"; }\n\n",
+ "name", message->full_name());
+
// Using statements for nested messages.
for (int i = 0; i < message->nested_type_count(); ++i) {
const Descriptor* nested_message = message->nested_type(i);
@@ -848,7 +852,7 @@
// It is declared as a function to keep protozero bindings header-only as
// inline constexpr variables are not available until C++17 (while inline
// functions are).
-// TODO(altimin): Use inline variable instead after adopting C++17.
+// TODO(altimin): Use inline variable instead after adopting C++17.
static constexpr $field_metadata_type$ $field_metadata_var$() { return {}; }
)";
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 82b2c7e..07c8d76 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -130,6 +130,7 @@
"../../../gn:default_deps",
"../../../gn:gtest_and_gmock",
"../../../protos/perfetto/common:zero",
+ "../../../protos/perfetto/trace:non_minimal_zero",
"../../../protos/perfetto/trace/track_event:zero",
"../../protozero",
"../../protozero:testing_messages_zero",
diff --git a/src/trace_processor/util/debug_annotation_parser.cc b/src/trace_processor/util/debug_annotation_parser.cc
index 7fa359e..7b49d2d 100644
--- a/src/trace_processor/util/debug_annotation_parser.cc
+++ b/src/trace_processor/util/debug_annotation_parser.cc
@@ -130,6 +130,25 @@
} else if (annotation.has_nested_value()) {
return ParseNestedValueArgs(annotation.nested_value(), context_name,
delegate);
+ } else if (annotation.has_proto_value()) {
+ std::string type_name;
+ if (annotation.has_proto_type_name()) {
+ type_name = annotation.proto_type_name().ToStdString();
+ } else if (annotation.has_proto_type_name_iid()) {
+ auto* interned_name = delegate.GetInternedMessage(
+ protos::pbzero::InternedData::kDebugAnnotationValueTypeNames,
+ annotation.proto_type_name_iid());
+ if (!interned_name)
+ return {base::ErrStatus("Interned proto type name not found"), false};
+ type_name = interned_name->name().ToStdString();
+ } else {
+ return {base::ErrStatus("DebugAnnotation has proto_value, but doesn't "
+ "have proto type name"),
+ false};
+ }
+ return {proto_to_args_parser_.ParseMessage(annotation.proto_value(),
+ type_name, nullptr, delegate),
+ true};
} else {
return {base::OkStatus(), /*added_entry=*/false};
}
diff --git a/src/trace_processor/util/debug_annotation_parser_unittest.cc b/src/trace_processor/util/debug_annotation_parser_unittest.cc
index d074e79..55a614f 100644
--- a/src/trace_processor/util/debug_annotation_parser_unittest.cc
+++ b/src/trace_processor/util/debug_annotation_parser_unittest.cc
@@ -20,6 +20,7 @@
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "protos/perfetto/common/descriptor.pbzero.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
#include "src/protozero/test/example_proto/test_messages.pbzero.h"
@@ -237,6 +238,33 @@
"root root[1][0] 3", "root root[1][1] 4"));
}
+TEST_F(DebugAnnotationParserTest, TypedMessageInsideUntyped) {
+ protozero::HeapBuffered<protos::pbzero::DebugAnnotation> msg;
+ msg->set_name("root");
+
+ protozero::HeapBuffered<protozero::test::protos::pbzero::EveryField> message;
+ message->set_field_string("value");
+
+ msg->set_proto_type_name(message->GetName());
+ msg->set_proto_value(message.SerializeAsString());
+
+ DescriptorPool pool;
+ auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
+ kTestMessagesDescriptor.size());
+ EXPECT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
+ << status.message();
+
+ ProtoToArgsParser args_parser(pool);
+ DebugAnnotationParser parser(args_parser);
+
+ status = ParseDebugAnnotation(parser, msg, *this);
+ EXPECT_TRUE(status.ok()) << "DebugAnnotationParser::Parse failed with error: "
+ << status.message();
+
+ EXPECT_THAT(args(), testing::ElementsAre(
+ "root.field_string root.field_string value"));
+}
+
} // namespace
} // namespace util
} // namespace trace_processor
diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc
index bd8b1ad..decaf5d 100644
--- a/src/tracing/traced_value.cc
+++ b/src/tracing/traced_value.cc
@@ -99,6 +99,14 @@
return TracedArray(context_, checked_scope_.parent_scope());
}
+protozero::Message* TracedValue::WriteProtoInternal(const char* name) {
+ // TODO(altimin): Plumb EventContext to TracedValue and support interning
+ // here.
+ context_->set_proto_type_name(name);
+ return context_->template BeginNestedMessage<protozero::Message>(
+ protos::pbzero::DebugAnnotation::kProtoValueFieldNumber);
+}
+
TracedValue TracedArray::AppendItem() {
PERFETTO_DCHECK(checked_scope_.is_active());
return TracedValue(context_->add_array_values(), &checked_scope_);
diff --git a/src/tracing/traced_value_unittest.cc b/src/tracing/traced_value_unittest.cc
index 66c86db..41f6402 100644
--- a/src/tracing/traced_value_unittest.cc
+++ b/src/tracing/traced_value_unittest.cc
@@ -32,6 +32,8 @@
#include "perfetto/test/traced_value_test_support.h"
#include "perfetto/tracing/debug_annotation.h"
#include "perfetto/tracing/track_event.h"
+#include "protos/perfetto/trace/test_event.pb.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
#include "protos/perfetto/trace/track_event/debug_annotation.gen.h"
#include "protos/perfetto/trace/track_event/debug_annotation.pb.h"
#include "test/gtest_and_gmock.h"
@@ -619,4 +621,43 @@
}));
}
+TEST(TracedValueTest, WriteTypedProto_Explicit) {
+ protozero::HeapBuffered<protos::pbzero::DebugAnnotation> message;
+ WriteIntoTracedValue(
+ internal::CreateTracedValueFromProto(message.get()),
+ [](perfetto::TracedValue context) {
+ perfetto::TracedProto<protos::pbzero::TestEvent::TestPayload> proto =
+ std::move(context)
+ .WriteProto<protos::pbzero::TestEvent::TestPayload>();
+ proto->set_single_string("payload");
+ });
+
+ protos::DebugAnnotation annotation;
+ annotation.ParseFromString(message.SerializeAsString());
+ EXPECT_EQ(annotation.proto_type_name(),
+ ".perfetto.protos.TestEvent.TestPayload");
+
+ protos::TestEvent::TestPayload payload;
+ payload.ParseFromString(annotation.proto_value());
+ EXPECT_EQ(payload.single_string(), "payload");
+}
+
+TEST(TracedValueTest, WriteTypedProto_Implicit) {
+ protozero::HeapBuffered<protos::pbzero::DebugAnnotation> message;
+ WriteIntoTracedValue(
+ internal::CreateTracedValueFromProto(message.get()),
+ [](perfetto::TracedProto<protos::pbzero::TestEvent::TestPayload> proto) {
+ proto->set_single_string("payload");
+ });
+
+ protos::DebugAnnotation annotation;
+ annotation.ParseFromString(message.SerializeAsString());
+ EXPECT_EQ(annotation.proto_type_name(),
+ ".perfetto.protos.TestEvent.TestPayload");
+
+ protos::TestEvent::TestPayload payload;
+ payload.ParseFromString(annotation.proto_value());
+ EXPECT_EQ(payload.single_string(), "payload");
+}
+
} // namespace perfetto