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