• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "perfetto/tracing/traced_value.h"
24 
25 namespace perfetto {
26 class EventContext;
27 namespace internal {
28 template <typename FieldMetadata,
29           bool is_message,
30           protozero::proto_utils::RepetitionType repetition_type>
31 struct TypedProtoWriterImpl;
32 }
33 
34 // A Wrapper around a protozero message to allow C++ classes to specify how it
35 // should be serialised into the trace:
36 //
37 // class Foo {
38 //  public:
39 //   void WriteIntoTrace(perfetto::TracedProto<pbzero::Foo> message) {
40 //     message->set_int_field(int_field_);
41 //   }
42 // };
43 //
44 // This class also exposes EventContext, e.g. to enable data interning.
45 //
46 // NOTE: the functionality below is not ready yet.
47 // TODO(altimin): Make the interop below possible.
48 // TracedProto also provides a seamless integration with writing untyped
49 // values via TracedValue / TracedDictionary / TracedArray:
50 //
51 // - TracedValue can be converted to a TracedProto, either by calling
52 //   TracedValue::WriteProto<T>() or implicitly.
53 // - If a proto message has a repeating DebugAnnotation debug_annotations
54 //   field, it can be filled using the TracedDictionary obtained from
55 //   TracedProto::AddDebugAnnotations.
56 template <typename MessageType>
57 class TracedProto {
58  public:
59   // implicit
TracedProto(TracedValue && value)60   TracedProto(TracedValue&& value)
61       : TracedProto(std::move(value).WriteProto<MessageType>()) {}
62   ~TracedProto() = default;
63 
64   TracedProto(const TracedProto&) = delete;
65   TracedProto& operator=(const TracedProto&) = delete;
66   TracedProto& operator=(TracedProto&&) = delete;
67   TracedProto(TracedProto&&) = default;
68 
69   MessageType* operator->() const { return message_; }
70 
message()71   MessageType* message() { return message_; }
72 
73   // Write additional untyped values into the same context, which is useful
74   // when a given C++ class has a typed representation, but also either has
75   // members which can only be written into an untyped context (e.g. they are
76   // autogenerated) or it's desirable to have a way to quickly extend the
77   // trace representation of this class (e.g. for debugging).
78   //
79   // The usage of the returned TracedDictionary should not be interleaved with
80   // writing into |message| as this results in an inefficient proto layout. To
81   // enforce this, AddDebugAnnotations should be called on TracedProto&&, i.e.
82   // std::move(message).AddDebugAnnotations().
83   //
84   // This requires a 'repeated DebugAnnotations debug_annotations' field in
85   // MessageType.
86   template <typename Check = void>
AddDebugAnnotations()87   TracedDictionary AddDebugAnnotations() && {
88     static_assert(
89         std::is_base_of<
90             protozero::proto_utils::FieldMetadataBase,
91             typename MessageType::FieldMetadata_DebugAnnotations>::value,
92         "This message does not have a |debug_annotations| field. Please add a"
93         "'repeated perfetto.protos.DebugAnnotation debug_annnotations = N;' "
94         "field to your message.");
95     return TracedDictionary(message_, MessageType::kDebugAnnotations, context_,
96                             nullptr);
97   }
98 
99   // Start writing a single entry corresponding to the given |field| and return
100   // TracedProto should be used to populate this further.
101   // This method requires |field|'s type to be a nested message, but both
102   // repeated and non-repeated complex fields are supported.
103   template <typename FieldMetadata>
WriteNestedMessage(protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>)104   TracedProto<typename FieldMetadata::cpp_field_type> WriteNestedMessage(
105       protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>) {
106     static_assert(std::is_base_of<MessageType,
107                                   typename FieldMetadata::message_type>::value,
108                   "Field should belong to the current message");
109     static_assert(
110         FieldMetadata::kProtoFieldType ==
111             protozero::proto_utils::ProtoSchemaType::kMessage,
112         "AddItem() can be used only for nested message fields. To write a "
113         "primitive field, use traced_proto->set_field() or traced_proto.Set()");
114     return Wrap(
115         message_->template BeginNestedMessage<
116             typename FieldMetadata::cpp_field_type>(FieldMetadata::kFieldId));
117   }
118 
119   // Write a given |value| into proto  as a new |field| of the current message.
120   // This method supports both nested messages and primitive types (i.e. int or
121   // string), but requires the |field| to be non-repeateable (i.e. optional).
122   // For repeatable fields, AppendValue or AppendFrom should be used.
123   template <typename FieldMetadata, typename ValueType>
Set(protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,ValueType && value)124   void Set(protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
125            ValueType&& value) {
126     static_assert(std::is_base_of<MessageType,
127                                   typename FieldMetadata::message_type>::value,
128                   "Field should belong to the current message");
129     static_assert(
130         FieldMetadata::kRepetitionType ==
131             protozero::proto_utils::RepetitionType::kNotRepeated,
132         "Set() can't be used with repeated fields due to ambiguity between "
133         "writing |value| as a single entry or treating |value| as a container "
134         "and writing all contained items as multiple entries. Please use "
135         "dedicated AppendValue() or AppendFrom() methods to differentiate "
136         "between "
137         "these two situations");
138 
139     internal::TypedProtoWriterImpl<
140         FieldMetadata,
141         FieldMetadata::kProtoFieldType ==
142             protozero::proto_utils::ProtoSchemaType::kMessage,
143         protozero::proto_utils::RepetitionType::kNotRepeated>::
144         Write(*this, std::forward<ValueType>(value));
145   }
146 
147   // Write a given |value| a single entry into the repeated |field| of the
148   // current message. If the field is not repeated, Set() should be used
149   // instead.
150   template <typename FieldMetadata, typename ValueType>
AppendValue(protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,ValueType && value)151   void AppendValue(
152       protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
153       ValueType&& value) {
154     static_assert(std::is_base_of<MessageType,
155                                   typename FieldMetadata::message_type>::value,
156                   "Field should belong to the current message");
157     static_assert(
158         FieldMetadata::kRepetitionType ==
159             protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
160         "Append*() methods can be used only with repeated fields. "
161         "Please use Set() for non-repeated");
162 
163     // Write a single value into a given repeated field by explicitly passing
164     // "kNotRepeated" to the TypedProtoWriterImpl.
165     internal::TypedProtoWriterImpl<
166         FieldMetadata,
167         FieldMetadata::kProtoFieldType ==
168             protozero::proto_utils::ProtoSchemaType::kMessage,
169         protozero::proto_utils::RepetitionType::kNotRepeated>::
170         Write(*this, std::forward<ValueType>(value));
171   }
172 
173   // Write a given |value| as a set of entries into the repeated |field| of the
174   // current message. If the field is not repeated, Set() should be used
175   // instead.
176   template <typename FieldMetadata, typename ValueType>
AppendFrom(protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,ValueType && value)177   void AppendFrom(
178       protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
179       ValueType&& value) {
180     static_assert(std::is_base_of<MessageType,
181                                   typename FieldMetadata::message_type>::value,
182                   "Field should belong to the current message");
183     static_assert(
184         FieldMetadata::kRepetitionType ==
185             protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
186         "Append*() methods can be used only with repeated fields. "
187         "Please use Set() for non-repeated");
188 
189     internal::TypedProtoWriterImpl<
190         FieldMetadata,
191         FieldMetadata::kProtoFieldType ==
192             protozero::proto_utils::ProtoSchemaType::kMessage,
193         protozero::proto_utils::RepetitionType::kRepeatedNotPacked>::
194         Write(*this, std::forward<ValueType>(value));
195   }
196 
197   // Write a nested message into a field according to the provided metadata.
198   // TODO(altimin): Replace the current usages in Chrome with the functions
199   // above and make these methods private.
200   template <typename FieldMetadata>
WriteNestedMessage()201   TracedProto<typename FieldMetadata::cpp_field_type> WriteNestedMessage() {
202     return WriteNestedMessage(
203         protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>());
204   }
205 
206  private:
207   friend class EventContext;
208   friend class TracedValue;
209   // Allow TracedProto<Foo> to create TracedProto<Bar>.
210   template <typename T>
211   friend class TracedProto;
212 
213   // Wraps a raw protozero message using the same context as the current object.
214   template <typename ChildMessageType>
Wrap(ChildMessageType * message)215   TracedProto<ChildMessageType> Wrap(ChildMessageType* message) {
216     return TracedProto<ChildMessageType>(message, context_);
217   }
218 
219   // Context might be null here when writing typed message which is
220   // nested into untyped legacy trace event macro argument.
221   // TODO(altimin): Turn this into EventContext& when this case is eliminated
222   // and expose it in public API.
context()223   EventContext* context() const { return context_; }
224 
TracedProto(MessageType * message,EventContext * context)225   TracedProto(MessageType* message, EventContext* context)
226       : message_(message), context_(context) {}
227 
228   MessageType* const message_;
229   EventContext* context_;
230 };
231 
232 template <typename MessageType, typename ValueType>
233 void WriteIntoTracedProto(TracedProto<MessageType> message, ValueType&& value);
234 
235 namespace internal {
236 
237 template <typename FieldMetadata,
238           bool is_message,
239           protozero::proto_utils::RepetitionType repetition_type>
240 struct TypedProtoWriterImpl;
241 
242 // Simple non-repeated field.
243 template <typename FieldMetadata>
244 struct TypedProtoWriterImpl<
245     FieldMetadata,
246     /*is_message=*/false,
247     protozero::proto_utils::RepetitionType::kNotRepeated> {
248   template <typename Proto, typename ValueType>
249   static void Write(TracedProto<Proto>& context, ValueType&& value) {
250     protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
251         *context.message(), FieldMetadata::kFieldId, value);
252   }
253 };
254 
255 // Simple repeated non-packed field.
256 template <typename FieldMetadata>
257 struct TypedProtoWriterImpl<
258     FieldMetadata,
259     /*is_message=*/false,
260     protozero::proto_utils::RepetitionType::kRepeatedNotPacked> {
261   template <typename Proto, typename ValueType>
262   static void Write(TracedProto<Proto>& context, ValueType&& value) {
263     for (auto&& item : value) {
264       protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
265           *context.message(), FieldMetadata::kFieldId, item);
266     }
267   }
268 };
269 
270 // Nested repeated non-packed field.
271 template <typename FieldMetadata>
272 struct TypedProtoWriterImpl<
273     FieldMetadata,
274     /*is_message=*/true,
275     protozero::proto_utils::RepetitionType::kNotRepeated> {
276   template <typename Proto, typename ValueType>
277   static void Write(TracedProto<Proto>& context, ValueType&& value) {
278     WriteIntoTracedProto(context.template WriteNestedMessage<FieldMetadata>(),
279                          std::forward<ValueType>(value));
280   }
281 };
282 
283 // Nested repeated non-packed field.
284 template <typename FieldMetadata>
285 struct TypedProtoWriterImpl<
286     FieldMetadata,
287     /*is_message=*/true,
288     protozero::proto_utils::RepetitionType::kRepeatedNotPacked> {
289   template <typename Proto, typename ValueType>
290   static void Write(TracedProto<Proto>& context, ValueType&& value) {
291     for (auto&& item : value) {
292       WriteIntoTracedProto(context.template WriteNestedMessage<FieldMetadata>(),
293                            item);
294     }
295   }
296 };
297 
298 constexpr int kMaxWriteTracedProtoImplPriority = 1;
299 
300 // If perfetto::TraceFormatTraits<T>::WriteIntoTrace(TracedProto<MessageType>,
301 // T) is available, use it.
302 template <typename MessageType, typename T>
303 decltype(TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace(
304              std::declval<TracedProto<MessageType>>(),
305              std::declval<T>()),
306          void())
307 WriteIntoTracedProtoImpl(base::priority_tag<1>,
308                          TracedProto<MessageType> message,
309                          T&& value) {
310   TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace(
311       std::move(message), std::forward<T>(value));
312 }
313 
314 // If T has WriteIntoTrace(TracedProto<MessageType>) method, use it.
315 template <typename MessageType, typename T>
316 decltype(
317     std::declval<T>().WriteIntoTrace(std::declval<TracedProto<MessageType>>()),
318     void())
319 WriteIntoTracedProtoImpl(base::priority_tag<0>,
320                          TracedProto<MessageType> message,
321                          T&& value) {
322   value.WriteIntoTrace(std::move(message));
323 }
324 
325 // TypedProtoWriter takes the protozero message (TracedProto<MessageType>),
326 // field description (FieldMetadata) and value and writes the given value
327 // into the given field of the given protozero message.
328 //
329 // This is primarily used for inline writing of typed messages:
330 // TRACE_EVENT(..., pbzero::Message:kField, value);
331 //
332 // Ideally we would use a function here and not a struct, but passing template
333 // arguments directly to the function (e.g. foo<void>()) isn't supported until
334 // C++20, so we have to use a helper struct here.
335 template <typename FieldMetadata>
336 struct TypedProtoWriter {
337  private:
338   using ProtoSchemaType = protozero::proto_utils::ProtoSchemaType;
339   using RepetitionType = protozero::proto_utils::RepetitionType;
340 
341   static_assert(FieldMetadata::kRepetitionType !=
342                     RepetitionType::kRepeatedPacked,
343                 "writing packed fields isn't supported yet");
344 
345   template <bool is_message, RepetitionType repetition_type>
346   struct Writer;
347 
348  public:
349   template <typename Proto, typename ValueType>
350   static void Write(TracedProto<Proto>& context, ValueType&& value) {
351     TypedProtoWriterImpl<
352         FieldMetadata,
353         FieldMetadata::kProtoFieldType == ProtoSchemaType::kMessage,
354         FieldMetadata::kRepetitionType>::Write(context,
355                                                std::forward<ValueType>(value));
356   }
357 };
358 
359 }  // namespace internal
360 
361 // Helper template to determine if a given type can be passed to
362 // perfetto::WriteIntoTracedProto. These templates will fail to resolve if the
363 // class does not have necesary support, so they are useful for SFINAE and for
364 // producing helpful compiler error messages.
365 template <typename MessageType, typename ValueType, typename Result = void>
366 using check_traced_proto_support_t =
367     decltype(internal::WriteIntoTracedProtoImpl(
368         std::declval<
369             base::priority_tag<internal::kMaxWriteTracedProtoImplPriority>>(),
370         std::declval<TracedProto<MessageType>>(),
371         std::declval<ValueType>()));
372 
373 // check_traced_proto_support<MessageType, T, V>::type is defined (and equal to
374 // V) iff T supports being passed to WriteIntoTracedProto together with
375 // TracedProto<MessageType>. See the comment in traced_value_forward.h for more
376 // details.
377 template <typename MessageType, typename ValueType, class Result>
378 struct check_traced_proto_support<
379     MessageType,
380     ValueType,
381     Result,
382     check_traced_proto_support_t<MessageType, ValueType, Result>> {
383   static constexpr bool value = true;
384   using type = Result;
385 };
386 
387 template <typename MessageType, typename ValueType>
388 void WriteIntoTracedProto(TracedProto<MessageType> message, ValueType&& value) {
389   // TODO(altimin): Add a URL to the documentation and a list of common failure
390   // patterns.
391   static_assert(
392       std::is_same<check_traced_proto_support_t<MessageType, ValueType>,
393                    void>::value,
394       "The provided type does not support being serialised into the "
395       "provided protozero message. Please see the comment in traced_proto.h "
396       "for more details.");
397 
398   internal::WriteIntoTracedProtoImpl(
399       base::priority_tag<internal::kMaxWriteTracedProtoImplPriority>(),
400       std::move(message), std::forward<ValueType>(value));
401 }
402 
403 template <typename MessageType, typename FieldMetadataType, typename ValueType>
404 void WriteTracedProtoField(
405     TracedProto<MessageType>& message,
406     protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadataType>,
407     ValueType&& value) {
408   static_assert(
409       std::is_base_of<protozero::proto_utils::FieldMetadataBase,
410                       FieldMetadataType>::value,
411       "Field name should be a protozero::internal::FieldMetadata<...>");
412   static_assert(
413       std::is_base_of<MessageType,
414                       typename FieldMetadataType::message_type>::value,
415       "Field's parent type should match the context.");
416 
417   internal::TypedProtoWriter<FieldMetadataType>::Write(
418       message, std::forward<ValueType>(value));
419 }
420 
421 }  // namespace perfetto
422 
423 #endif  // INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
424