1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
18 #define INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
19 
20 #include "perfetto/base/template_util.h"
21 #include "perfetto/protozero/field_writer.h"
22 #include "perfetto/protozero/proto_utils.h"
23 
24 namespace perfetto {
25 class EventContext;
26 
27 // A Wrapper around a protozero message to allow C++ classes to specify how it
28 // should be serialised into the trace:
29 //
30 // class Foo {
31 //  public:
32 //   void WriteIntoTrace(perfetto::TracedProto<pbzero::Foo> message) {
33 //     message->set_int_field(int_field_);
34 //   }
35 // };
36 //
37 // This class also exposes EventContext, e.g. to enable data interning.
38 //
39 // NOTE: the functionality below is not ready yet.
40 // TODO(altimin): Make the interop below possible.
41 // TracedProto also provides a seamless integration with writing untyped
42 // values via TracedValue / TracedDictionary / TracedArray:
43 //
44 // - TracedValue can be converted to a TracedProto, either by calling
45 //   TracedValue::WriteProto<T>() or implicitly.
46 // - If a proto message has a repeating DebugAnnotation debug_annotations
47 //   field, it can be filled using the TracedDictionary obtained from
48 //   TracedProto::WriteDebugAnnotations.
49 template <typename MessageType>
50 class TracedProto {
51  public:
52   TracedProto(const TracedProto&) = delete;
53   TracedProto& operator=(const TracedProto&) = delete;
54   TracedProto& operator=(TracedProto&&) = delete;
55   TracedProto(TracedProto&&) = default;
56   ~TracedProto() = default;
57 
58   MessageType* operator->() const { return message_; }
59 
message()60   MessageType* message() { return message_; }
61 
context()62   EventContext& context() const { return context_; }
63 
64  private:
65   friend class EventContext;
66 
TracedProto(MessageType * message,EventContext & context)67   TracedProto(MessageType* message, EventContext& context)
68       : message_(message), context_(context) {}
69 
70   MessageType* const message_;
71   EventContext& context_;
72 };
73 
74 namespace internal {
75 
76 // TypedProtoWriter takes the protozero message (TracedProto<MessageType>),
77 // field description (FieldMetadata) and value and writes the given value
78 // into the given field of the given protozero message.
79 //
80 // This is primarily used for inline writing of typed messages:
81 // TRACE_EVENT(..., pbzero::Message:kField, value);
82 //
83 // Ideally we would use a function here and not a struct, but passing template
84 // arguments directly to the function (e.g. foo<void>()) isn't supported until
85 // C++20, so we have to use a helper struct here.
86 template <typename FieldMetadata>
87 struct TypedProtoWriter {
88  private:
89   using ProtoSchemaType = protozero::proto_utils::ProtoSchemaType;
90   using RepetitionType = protozero::proto_utils::RepetitionType;
91 
92   static_assert(FieldMetadata::kRepetitionType !=
93                     RepetitionType::kRepeatedPacked,
94                 "writing packed fields isn't supported yet");
95 
96  public:
97   // Implementation note: typename Check=void is used to ensure that SFINAE
98   // kicks in and the methods which do not match FieldMetadata do not fail
99   // to compile. std::is_same<Check,void> prevents early evaluation of the
100   // first enable_if_t argument.
101 
102   // Simple non-repeated field.
103   template <typename Proto, typename ValueType, typename Check = void>
104   static typename base::enable_if_t<
105       FieldMetadata::kProtoFieldType != ProtoSchemaType::kMessage &&
106       FieldMetadata::kRepetitionType == RepetitionType::kNotRepeated &&
107       std::is_same<Check, void>::value>
WriteTypedProtoWriter108   Write(TracedProto<Proto> context, ValueType&& value) {
109     protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
110         *context.message(), FieldMetadata::kFieldId, value);
111   }
112 
113   // Simple repeated non-packed field.
114   template <typename Proto, typename ValueType, typename Check = void>
115   static typename base::enable_if_t<
116       FieldMetadata::kProtoFieldType != ProtoSchemaType::kMessage &&
117       FieldMetadata::kRepetitionType == RepetitionType::kRepeatedNotPacked &&
118       std::is_same<Check, void>::value>
WriteTypedProtoWriter119   Write(TracedProto<Proto> context, ValueType&& value) {
120     for (auto&& item : value) {
121       protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
122           *context.message(), FieldMetadata::kFieldId, item);
123     }
124   }
125 
126   // Nested non-repeated field.
127   template <typename Proto, typename ValueType, typename Check = void>
128   static typename base::enable_if_t<
129       FieldMetadata::kProtoFieldType == ProtoSchemaType::kMessage &&
130       FieldMetadata::kRepetitionType == RepetitionType::kNotRepeated &&
131       std::is_same<Check, void>::value>
WriteTypedProtoWriter132   Write(TracedProto<Proto> context, ValueType&& value) {
133     // TODO(altimin): support TraceFormatTraits here.
134     value.WriteIntoTrace(
135         context.context().Wrap(context.message()
136                                    ->template BeginNestedMessage<
137                                        typename FieldMetadata::cpp_field_type>(
138                                        FieldMetadata::kFieldId)));
139   }
140 
141   // Nested repeated non-packed field.
142   template <typename Proto, typename ValueType, typename Check = void>
143   static typename base::enable_if_t<
144       FieldMetadata::kProtoFieldType == ProtoSchemaType::kMessage &&
145       FieldMetadata::kRepetitionType == RepetitionType::kRepeatedNotPacked &&
146       std::is_same<Check, void>::value>
WriteTypedProtoWriter147   Write(TracedProto<Proto> context, ValueType&& value) {
148     // TODO(altimin): support TraceFormatTraits here.
149     for (auto&& item : value) {
150       item.WriteIntoTrace(context.context().Wrap(
151           context.message()
152               ->template BeginNestedMessage<
153                   typename FieldMetadata::cpp_field_type>(
154                   FieldMetadata::kFieldId)));
155     }
156   }
157 };
158 
159 }  // namespace internal
160 
161 template <typename MessageType, typename FieldMetadataType, typename ValueType>
WriteIntoTracedProto(TracedProto<MessageType> message,protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadataType>,ValueType && value)162 void WriteIntoTracedProto(
163     TracedProto<MessageType> message,
164     protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadataType>,
165     ValueType&& value) {
166   static_assert(
167       std::is_base_of<protozero::proto_utils::FieldMetadataBase,
168                       FieldMetadataType>::value,
169       "Field name should be a protozero::internal::FieldMetadata<...>");
170   static_assert(
171       std::is_base_of<MessageType,
172                       typename FieldMetadataType::message_type>::value,
173       "Field's parent type should match the context.");
174 
175   internal::TypedProtoWriter<FieldMetadataType>::Write(
176       std::move(message), std::forward<ValueType>(value));
177 }
178 
179 }  // namespace perfetto
180 
181 #endif  // INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
182