• 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(FieldMetadata)104   TracedProto<typename FieldMetadata::cpp_field_type> WriteNestedMessage(
105       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(FieldMetadata,ValueType && value)124   void Set(FieldMetadata, ValueType&& value) {
125     static_assert(std::is_base_of<MessageType,
126                                   typename FieldMetadata::message_type>::value,
127                   "Field should belong to the current message");
128     static_assert(
129         FieldMetadata::kRepetitionType ==
130             protozero::proto_utils::RepetitionType::kNotRepeated,
131         "Set() can't be used with repeated fields due to ambiguity between "
132         "writing |value| as a single entry or treating |value| as a container "
133         "and writing all contained items as multiple entries. Please use "
134         "dedicated AppendValue() or AppendFrom() methods to differentiate "
135         "between "
136         "these two situations");
137 
138     internal::TypedProtoWriterImpl<
139         FieldMetadata,
140         FieldMetadata::kProtoFieldType ==
141             protozero::proto_utils::ProtoSchemaType::kMessage,
142         protozero::proto_utils::RepetitionType::kNotRepeated>::
143         Write(*this, std::forward<ValueType>(value));
144   }
145 
146   // Write a given |value| a single entry into the repeated |field| of the
147   // current message. If the field is not repeated, Set() should be used
148   // instead.
149   template <typename FieldMetadata, typename ValueType>
AppendValue(FieldMetadata,ValueType && value)150   void AppendValue(FieldMetadata, ValueType&& value) {
151     static_assert(std::is_base_of<MessageType,
152                                   typename FieldMetadata::message_type>::value,
153                   "Field should belong to the current message");
154     static_assert(
155         FieldMetadata::kRepetitionType ==
156             protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
157         "Append*() methods can be used only with repeated fields. "
158         "Please use Set() for non-repeated");
159 
160     // Write a single value into a given repeated field by explicitly passing
161     // "kNotRepeated" to the TypedProtoWriterImpl.
162     internal::TypedProtoWriterImpl<
163         FieldMetadata,
164         FieldMetadata::kProtoFieldType ==
165             protozero::proto_utils::ProtoSchemaType::kMessage,
166         protozero::proto_utils::RepetitionType::kNotRepeated>::
167         Write(*this, std::forward<ValueType>(value));
168   }
169 
170   // Write a given |value| as a set of entries into the repeated |field| of the
171   // current message. If the field is not repeated, Set() should be used
172   // instead.
173   template <typename FieldMetadata, typename ValueType>
AppendFrom(FieldMetadata,ValueType && value)174   void AppendFrom(FieldMetadata, ValueType&& value) {
175     static_assert(std::is_base_of<MessageType,
176                                   typename FieldMetadata::message_type>::value,
177                   "Field should belong to the current message");
178     static_assert(
179         FieldMetadata::kRepetitionType ==
180             protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
181         "Append*() methods can be used only with repeated fields. "
182         "Please use Set() for non-repeated");
183 
184     internal::TypedProtoWriterImpl<
185         FieldMetadata,
186         FieldMetadata::kProtoFieldType ==
187             protozero::proto_utils::ProtoSchemaType::kMessage,
188         protozero::proto_utils::RepetitionType::kRepeatedNotPacked>::
189         Write(*this, std::forward<ValueType>(value));
190   }
191 
192   // Write a nested message into a field according to the provided metadata.
193   // TODO(altimin): Replace the current usages in Chrome with the functions
194   // above and make these methods private.
195   template <typename FieldMetadata>
WriteNestedMessage()196   TracedProto<typename FieldMetadata::cpp_field_type> WriteNestedMessage() {
197     return WriteNestedMessage(FieldMetadata());
198   }
199 
200  private:
201   friend class EventContext;
202   friend class TracedValue;
203   // Allow TracedProto<Foo> to create TracedProto<Bar>.
204   template <typename T>
205   friend class TracedProto;
206 
207   // Wraps a raw protozero message using the same context as the current object.
208   template <typename ChildMessageType>
Wrap(ChildMessageType * message)209   TracedProto<ChildMessageType> Wrap(ChildMessageType* message) {
210     return TracedProto<ChildMessageType>(message, context_);
211   }
212 
213   // Context might be null here when writing typed message which is
214   // nested into untyped legacy trace event macro argument.
215   // TODO(altimin): Turn this into EventContext& when this case is eliminated
216   // and expose it in public API.
context()217   EventContext* context() const { return context_; }
218 
TracedProto(MessageType * message,EventContext * context)219   TracedProto(MessageType* message, EventContext* context)
220       : message_(message), context_(context) {}
221 
222   MessageType* const message_;
223   EventContext* context_;
224 };
225 
226 template <typename MessageType, typename ValueType>
227 void WriteIntoTracedProto(TracedProto<MessageType> message, ValueType&& value);
228 
229 namespace internal {
230 
231 template <typename FieldMetadata,
232           bool is_message,
233           protozero::proto_utils::RepetitionType repetition_type>
234 struct TypedProtoWriterImpl;
235 
236 // Simple non-repeated field.
237 template <typename FieldMetadata>
238 struct TypedProtoWriterImpl<
239     FieldMetadata,
240     /*is_message=*/false,
241     protozero::proto_utils::RepetitionType::kNotRepeated> {
242   template <typename Proto, typename ValueType>
243   static void Write(TracedProto<Proto>& context, ValueType&& value) {
244     protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
245         *context.message(), FieldMetadata::kFieldId, value);
246   }
247 };
248 
249 // Simple repeated non-packed field.
250 template <typename FieldMetadata>
251 struct TypedProtoWriterImpl<
252     FieldMetadata,
253     /*is_message=*/false,
254     protozero::proto_utils::RepetitionType::kRepeatedNotPacked> {
255   template <typename Proto, typename ValueType>
256   static void Write(TracedProto<Proto>& context, ValueType&& value) {
257     for (auto&& item : value) {
258       protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
259           *context.message(), FieldMetadata::kFieldId, item);
260     }
261   }
262 };
263 
264 // Nested repeated non-packed field.
265 template <typename FieldMetadata>
266 struct TypedProtoWriterImpl<
267     FieldMetadata,
268     /*is_message=*/true,
269     protozero::proto_utils::RepetitionType::kNotRepeated> {
270   template <typename Proto, typename ValueType>
271   static void Write(TracedProto<Proto>& context, ValueType&& value) {
272     WriteIntoTracedProto(context.template WriteNestedMessage<FieldMetadata>(),
273                          std::forward<ValueType>(value));
274   }
275 };
276 
277 // Nested repeated non-packed field.
278 template <typename FieldMetadata>
279 struct TypedProtoWriterImpl<
280     FieldMetadata,
281     /*is_message=*/true,
282     protozero::proto_utils::RepetitionType::kRepeatedNotPacked> {
283   template <typename Proto, typename ValueType>
284   static void Write(TracedProto<Proto>& context, ValueType&& value) {
285     for (auto&& item : value) {
286       WriteIntoTracedProto(context.template WriteNestedMessage<FieldMetadata>(),
287                            item);
288     }
289   }
290 };
291 
292 constexpr int kMaxWriteTracedProtoImplPriority = 1;
293 
294 // If perfetto::TraceFormatTraits<T>::WriteIntoTrace(TracedProto<MessageType>,
295 // T) is available, use it.
296 template <typename MessageType, typename T>
297 decltype(TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace(
298              std::declval<TracedProto<MessageType>>(),
299              std::declval<T>()),
300          void())
301 WriteIntoTracedProtoImpl(base::priority_tag<1>,
302                          TracedProto<MessageType> message,
303                          T&& value) {
304   TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace(
305       std::move(message), std::forward<T>(value));
306 }
307 
308 // If T has WriteIntoTrace(TracedProto<MessageType>) method, use it.
309 template <typename MessageType, typename T>
310 decltype(
311     std::declval<T>().WriteIntoTrace(std::declval<TracedProto<MessageType>>()),
312     void())
313 WriteIntoTracedProtoImpl(base::priority_tag<0>,
314                          TracedProto<MessageType> message,
315                          T&& value) {
316   value.WriteIntoTrace(std::move(message));
317 }
318 
319 // TypedProtoWriter takes the protozero message (TracedProto<MessageType>),
320 // field description (FieldMetadata) and value and writes the given value
321 // into the given field of the given protozero message.
322 //
323 // This is primarily used for inline writing of typed messages:
324 // TRACE_EVENT(..., pbzero::Message:kField, value);
325 //
326 // Ideally we would use a function here and not a struct, but passing template
327 // arguments directly to the function (e.g. foo<void>()) isn't supported until
328 // C++20, so we have to use a helper struct here.
329 template <typename FieldMetadata>
330 struct TypedProtoWriter {
331  private:
332   using ProtoSchemaType = protozero::proto_utils::ProtoSchemaType;
333   using RepetitionType = protozero::proto_utils::RepetitionType;
334 
335   static_assert(FieldMetadata::kRepetitionType !=
336                     RepetitionType::kRepeatedPacked,
337                 "writing packed fields isn't supported yet");
338 
339   template <bool is_message, RepetitionType repetition_type>
340   struct Writer;
341 
342  public:
343   template <typename Proto, typename ValueType>
344   static void Write(TracedProto<Proto>& context, ValueType&& value) {
345     TypedProtoWriterImpl<
346         FieldMetadata,
347         FieldMetadata::kProtoFieldType == ProtoSchemaType::kMessage,
348         FieldMetadata::kRepetitionType>::Write(context,
349                                                std::forward<ValueType>(value));
350   }
351 };
352 
353 }  // namespace internal
354 
355 // Helper template to determine if a given type can be passed to
356 // perfetto::WriteIntoTracedProto. These templates will fail to resolve if the
357 // class does not have necesary support, so they are useful for SFINAE and for
358 // producing helpful compiler error messages.
359 template <typename MessageType, typename ValueType, typename Result = void>
360 using check_traced_proto_support_t =
361     decltype(internal::WriteIntoTracedProtoImpl(
362         std::declval<
363             base::priority_tag<internal::kMaxWriteTracedProtoImplPriority>>(),
364         std::declval<TracedProto<MessageType>>(),
365         std::declval<ValueType>()));
366 
367 // check_traced_proto_support<MessageType, T, V>::type is defined (and equal to
368 // V) iff T supports being passed to WriteIntoTracedProto together with
369 // TracedProto<MessageType>. See the comment in traced_value_forward.h for more
370 // details.
371 template <typename MessageType, typename ValueType, class Result>
372 struct check_traced_proto_support<
373     MessageType,
374     ValueType,
375     Result,
376     check_traced_proto_support_t<MessageType, ValueType, Result>> {
377   static constexpr bool value = true;
378   using type = Result;
379 };
380 
381 template <typename MessageType, typename ValueType>
382 void WriteIntoTracedProto(TracedProto<MessageType> message, ValueType&& value) {
383   // TODO(altimin): Add a URL to the documentation and a list of common failure
384   // patterns.
385   static_assert(
386       std::is_same<check_traced_proto_support_t<MessageType, ValueType>,
387                    void>::value,
388       "The provided type does not support being serialised into the "
389       "provided protozero message. Please see the comment in traced_proto.h "
390       "for more details.");
391 
392   internal::WriteIntoTracedProtoImpl(
393       base::priority_tag<internal::kMaxWriteTracedProtoImplPriority>(),
394       std::move(message), std::forward<ValueType>(value));
395 }
396 
397 template <typename MessageType, typename FieldMetadataType, typename ValueType>
398 void WriteTracedProtoField(TracedProto<MessageType>& message,
399                            FieldMetadataType,
400                            ValueType&& value) {
401   static_assert(
402       std::is_base_of<protozero::proto_utils::FieldMetadataBase,
403                       FieldMetadataType>::value,
404       "Field name should be a protozero::internal::FieldMetadata<...>");
405   static_assert(
406       std::is_base_of<MessageType,
407                       typename FieldMetadataType::message_type>::value,
408       "Field's parent type should match the context.");
409 
410   internal::TypedProtoWriter<FieldMetadataType>::Write(
411       message, std::forward<ValueType>(value));
412 }
413 
414 }  // namespace perfetto
415 
416 #endif  // INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
417