| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_ |
| #define INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_ |
| |
| #include "perfetto/base/compiler.h" |
| #include "perfetto/base/export.h" |
| #include "perfetto/base/template_util.h" |
| #include "perfetto/protozero/message.h" |
| #include "perfetto/protozero/proto_utils.h" |
| #include "perfetto/tracing/internal/checked_scope.h" |
| #include "perfetto/tracing/string_helpers.h" |
| #include "perfetto/tracing/traced_value_forward.h" |
| |
| #include <memory> |
| #include <type_traits> |
| #include <utility> |
| |
| namespace perfetto { |
| |
| namespace protos { |
| namespace pbzero { |
| class DebugAnnotation; |
| } |
| } // namespace protos |
| |
| class DebugAnnotation; |
| class EventContext; |
| |
| // These classes provide a JSON-inspired way to write structed data into traces. |
| // |
| // Each TracedValue can be consumed exactly once to write a value into a trace |
| // using one of the Write* methods. |
| // |
| // Write* methods fall into two categories: |
| // - Primitive types (int, string, bool, double, etc): they just write the |
| // provided value, consuming the TracedValue in the process. |
| // - Complex types (arrays and dicts): they consume the TracedValue and |
| // return a corresponding scoped object (TracedArray or TracedDictionary). |
| // This scope then can be used to write multiple items into the container: |
| // TracedArray::AppendItem and TracedDictionary::AddItem return a new |
| // TracedValue which then can be used to write an element of the |
| // dictionary or array. |
| // |
| // To define how a custom class should be written into the trace, users should |
| // define one of the two following functions: |
| // - Foo::WriteIntoTrace(TracedValue) const |
| // (preferred for code which depends on perfetto directly) |
| // - perfetto::TraceFormatTraits<T>::WriteIntoTrace( |
| // TracedValue, const T&); |
| // (should be used if T is defined in a library which doesn't know anything |
| // about tracing). |
| // |
| // |
| // After definiting a conversion method, the object can be used directly as a |
| // TRACE_EVENT argument: |
| // |
| // Foo foo; |
| // TRACE_EVENT("cat", "Event", "arg", foo); |
| // |
| // Examples: |
| // |
| // TRACE_EVENT("cat", "event", "params", [&](perfetto::TracedValue context) |
| // { |
| // auto dict = std::move(context).WriteDictionary(); |
| // dict->Add("param1", param1); |
| // dict->Add("param2", param2); |
| // ... |
| // dict->Add("paramN", paramN); |
| // |
| // { |
| // auto inner_array = dict->AddArray("inner"); |
| // inner_array->Append(value1); |
| // inner_array->Append(value2); |
| // } |
| // }); |
| // |
| // template <typename T> |
| // TraceFormatTraits<std::optional<T>>::WriteIntoTrace( |
| // TracedValue context, const std::optional<T>& value) { |
| // if (!value) { |
| // std::move(context).WritePointer(nullptr); |
| // return; |
| // } |
| // perfetto::WriteIntoTrace(std::move(context), *value); |
| // } |
| // |
| // template <typename T> |
| // TraceFormatTraits<std::vector<T>>::WriteIntoTrace( |
| // TracedValue context, const std::array<T>& value) { |
| // auto array = std::move(context).WriteArray(); |
| // for (const auto& item: value) { |
| // array_scope.Append(item); |
| // } |
| // } |
| // |
| // class Foo { |
| // void WriteIntoTrace(TracedValue context) const { |
| // auto dict = std::move(context).WriteDictionary(); |
| // dict->Set("key", 42); |
| // dict->Set("foo", "bar"); |
| // dict->Set("member", member_); |
| // } |
| // } |
| namespace internal { |
| // TODO(altimin): Currently EventContext can be null due the need to support |
| // TracedValue-based serialisation with the Chrome's TraceLog. After this is |
| // gone, the second parameter should be changed to EventContext&. |
| PERFETTO_EXPORT TracedValue |
| CreateTracedValueFromProto(protos::pbzero::DebugAnnotation*, |
| EventContext* = nullptr); |
| } |
| |
| class PERFETTO_EXPORT TracedValue { |
| public: |
| TracedValue(const TracedValue&) = delete; |
| TracedValue& operator=(const TracedValue&) = delete; |
| TracedValue& operator=(TracedValue&&) = delete; |
| TracedValue(TracedValue&&); |
| ~TracedValue(); |
| |
| // TracedValue represents a context into which a single value can be written |
| // (either by writing it directly for primitive types, or by creating a |
| // TracedArray or TracedDictionary for the complex types). This is enforced |
| // by allowing Write* methods to be called only on rvalue references. |
| |
| void WriteInt64(int64_t value) &&; |
| void WriteUInt64(uint64_t value) &&; |
| void WriteDouble(double value) &&; |
| void WriteBoolean(bool value) &&; |
| void WriteString(const char*) &&; |
| 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 |
| // active at the same time. It's only allowed to call methods on the active |
| // scope. |
| // - When a scope creates a nested scope, the new scope becomes active. |
| // - When a scope is destroyed, it's parent scope becomes active again. |
| // |
| // Typically users will have to create a scope only at the beginning of a |
| // conversion function and this scope should be destroyed at the end of it. |
| // TracedArray::Append and TracedDictionary::Add create, write and complete |
| // inner scopes automatically. |
| |
| // Scope which allows multiple values to be appended. |
| TracedArray WriteArray() && PERFETTO_WARN_UNUSED_RESULT; |
| |
| // Scope which allows multiple key-value pairs to be added. |
| TracedDictionary WriteDictionary() && PERFETTO_WARN_UNUSED_RESULT; |
| |
| private: |
| friend class TracedArray; |
| friend class TracedDictionary; |
| friend TracedValue internal::CreateTracedValueFromProto( |
| protos::pbzero::DebugAnnotation*, |
| EventContext*); |
| |
| static TracedValue CreateFromProto(protos::pbzero::DebugAnnotation* proto, |
| EventContext* event_context = nullptr); |
| |
| inline explicit TracedValue(protos::pbzero::DebugAnnotation* annotation, |
| EventContext* event_context, |
| internal::CheckedScope* parent_scope) |
| : annotation_(annotation), |
| event_context_(event_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. |
| friend class DebugAnnotation; |
| |
| protos::pbzero::DebugAnnotation* const annotation_ = nullptr; |
| EventContext* const event_context_ = nullptr; |
| |
| internal::CheckedScope checked_scope_; |
| }; |
| |
| template <typename MessageType> |
| TracedProto<MessageType> TracedValue::WriteProto() && { |
| return TracedProto<MessageType>( |
| static_cast<MessageType*>(WriteProtoInternal(MessageType::GetName())), |
| event_context_); |
| } |
| |
| class PERFETTO_EXPORT TracedArray { |
| public: |
| // implicit |
| TracedArray(TracedValue); |
| |
| TracedArray(const TracedArray&) = delete; |
| TracedArray& operator=(const TracedArray&) = delete; |
| TracedArray& operator=(TracedArray&&) = delete; |
| TracedArray(TracedArray&&) = default; |
| ~TracedArray() = default; |
| |
| TracedValue AppendItem(); |
| |
| template <typename T> |
| void Append(T&& value) { |
| WriteIntoTracedValue(AppendItem(), std::forward<T>(value)); |
| } |
| |
| TracedDictionary AppendDictionary() PERFETTO_WARN_UNUSED_RESULT; |
| TracedArray AppendArray(); |
| |
| private: |
| friend class TracedValue; |
| |
| inline explicit TracedArray(protos::pbzero::DebugAnnotation* annotation, |
| EventContext* event_context, |
| internal::CheckedScope* parent_scope) |
| : annotation_(annotation), |
| event_context_(event_context), |
| checked_scope_(parent_scope) {} |
| |
| protos::pbzero::DebugAnnotation* annotation_; |
| EventContext* const event_context_; |
| |
| internal::CheckedScope checked_scope_; |
| }; |
| |
| class PERFETTO_EXPORT TracedDictionary { |
| public: |
| // implicit |
| TracedDictionary(TracedValue); |
| |
| TracedDictionary(const TracedDictionary&) = delete; |
| TracedDictionary& operator=(const TracedDictionary&) = delete; |
| TracedDictionary& operator=(TracedDictionary&&) = delete; |
| TracedDictionary(TracedDictionary&&) = default; |
| ~TracedDictionary() = default; |
| |
| // There are two paths for writing dictionary keys: fast path for writing |
| // compile-time const, whose pointer is remains valid during the entire |
| // runtime of the program and the slow path for dynamic strings, which need to |
| // be copied. |
| // In the most common case, a string literal can be passed to `Add`/`AddItem`. |
| // In other cases, either StaticString or DynamicString declarations are |
| // needed. |
| |
| TracedValue AddItem(StaticString key); |
| TracedValue AddItem(DynamicString key); |
| |
| template <typename T> |
| void Add(StaticString key, T&& value) { |
| WriteIntoTracedValue(AddItem(key), std::forward<T>(value)); |
| } |
| |
| template <typename T> |
| void Add(DynamicString key, T&& value) { |
| WriteIntoTracedValue(AddItem(key), std::forward<T>(value)); |
| } |
| |
| TracedDictionary AddDictionary(StaticString key); |
| TracedDictionary AddDictionary(DynamicString key); |
| TracedArray AddArray(StaticString key); |
| TracedArray AddArray(DynamicString key); |
| |
| private: |
| friend class TracedValue; |
| template <typename T> |
| friend class TracedProto; |
| |
| // Create a |TracedDictionary| which will populate the given field of the |
| // given |message|. |
| template <typename MessageType, typename FieldMetadata> |
| inline explicit TracedDictionary( |
| MessageType* message, |
| protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>, |
| EventContext* event_context, |
| internal::CheckedScope* parent_scope) |
| : message_(message), |
| field_id_(FieldMetadata::kFieldId), |
| event_context_(event_context), |
| checked_scope_(parent_scope) { |
| static_assert(std::is_base_of<protozero::Message, MessageType>::value, |
| "Message should be a subclass of protozero::Message"); |
| static_assert(std::is_base_of<protozero::proto_utils::FieldMetadataBase, |
| FieldMetadata>::value, |
| "FieldMetadata should be a subclass of FieldMetadataBase"); |
| static_assert( |
| std::is_same<typename FieldMetadata::message_type, MessageType>::value, |
| "Field does not belong to this message"); |
| static_assert( |
| std::is_same<typename FieldMetadata::cpp_field_type, |
| ::perfetto::protos::pbzero::DebugAnnotation>::value, |
| "Field should be of DebugAnnotation type"); |
| static_assert( |
| FieldMetadata::kRepetitionType == |
| protozero::proto_utils::RepetitionType::kRepeatedNotPacked, |
| "Field should be non-packed repeated"); |
| } |
| |
| protozero::Message* const message_; |
| const uint32_t field_id_; |
| EventContext* event_context_; |
| |
| internal::CheckedScope checked_scope_; |
| }; |
| |
| namespace internal { |
| |
| // SFINAE helpers for finding a right overload to convert a given class to |
| // trace-friendly form, ordered from most to least preferred. |
| |
| constexpr int kMaxWriteImplPriority = 4; |
| |
| // If T has WriteIntoTracedValue member function, call it. |
| template <typename T> |
| decltype(std::declval<T>().WriteIntoTracedValue(std::declval<TracedValue>()), |
| void()) |
| WriteImpl(base::priority_tag<4>, TracedValue context, T&& value) { |
| value.WriteIntoTracedValue(std::move(context)); |
| } |
| |
| // If T has WriteIntoTrace member function, call it. |
| template <typename T> |
| decltype(std::declval<T>().WriteIntoTrace(std::declval<TracedValue>()), void()) |
| WriteImpl(base::priority_tag<4>, TracedValue context, T&& value) { |
| value.WriteIntoTrace(std::move(context)); |
| } |
| |
| // If perfetto::TraceFormatTraits<T>::WriteIntoTracedValue(TracedValue, const |
| // T&) is available, use it. |
| template <typename T> |
| decltype(TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTracedValue( |
| std::declval<TracedValue>(), |
| std::declval<T>()), |
| void()) |
| WriteImpl(base::priority_tag<3>, TracedValue context, T&& value) { |
| TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTracedValue( |
| std::move(context), std::forward<T>(value)); |
| } |
| |
| // If perfetto::TraceFormatTraits<T>::WriteIntoTrace(TracedValue, const T&) |
| // is available, use it. |
| template <typename T> |
| decltype(TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace( |
| std::declval<TracedValue>(), |
| std::declval<T>()), |
| void()) |
| WriteImpl(base::priority_tag<3>, TracedValue context, T&& value) { |
| TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace( |
| std::move(context), std::forward<T>(value)); |
| } |
| |
| // If T has operator(), which takes TracedValue, use it. |
| // Very useful for lambda resolutions. |
| template <typename T> |
| decltype(std::declval<T>()(std::declval<TracedValue>()), void()) |
| WriteImpl(base::priority_tag<2>, TracedValue context, T&& value) { |
| std::forward<T>(value)(std::move(context)); |
| } |
| |
| // If T is a container and its elements have tracing support, use it. |
| // |
| // Note: a reference to T should be passed to std::begin, otherwise |
| // for non-reference types const T& will be passed to std::begin, losing |
| // support for non-const WriteIntoTracedValue methods. |
| template <typename T> |
| typename check_traced_value_support< |
| decltype(*std::begin(std::declval<T&>()))>::type |
| WriteImpl(base::priority_tag<1>, TracedValue context, T&& value) { |
| auto array = std::move(context).WriteArray(); |
| for (auto&& item : value) { |
| array.Append(item); |
| } |
| } |
| |
| // std::underlying_type can't be used with non-enum types, so we need this |
| // indirection. |
| template <typename T, bool = std::is_enum<T>::value> |
| struct safe_underlying_type { |
| using type = typename std::underlying_type<T>::type; |
| }; |
| |
| template <typename T> |
| struct safe_underlying_type<T, false> { |
| using type = T; |
| }; |
| |
| template <typename T> |
| struct is_incomplete_type { |
| static constexpr bool value = sizeof(T) != 0; |
| }; |
| |
| // sizeof is not available for const char[], but it's still not considered to be |
| // an incomplete type for our purposes as the size can be determined at runtime |
| // due to strings being null-terminated. |
| template <> |
| struct is_incomplete_type<const char[]> { |
| static constexpr bool value = true; |
| }; |
| |
| } // namespace internal |
| |
| // Helper template to determine if a given type can be passed to |
| // perfetto::WriteIntoTracedValue. These templates will fail to resolve if the |
| // class does not have it support, so they are useful in SFINAE and in producing |
| // helpful compiler results. |
| template <typename T, class Result = void> |
| using check_traced_value_support_t = decltype( |
| internal::WriteImpl( |
| std::declval<base::priority_tag<internal::kMaxWriteImplPriority>>(), |
| std::declval<TracedValue>(), |
| std::declval<T>()), |
| std::declval<Result>()); |
| |
| // check_traced_value_support<T, V>::type is defined (and equal to V) iff T |
| // supports being passed to WriteIntoTracedValue. See the comment in |
| // traced_value_forward.h for more details. |
| template <typename T, class Result> |
| struct check_traced_value_support<T, |
| Result, |
| check_traced_value_support_t<T, Result>> { |
| static_assert( |
| internal::is_incomplete_type<T>::value, |
| "perfetto::TracedValue should not be used with incomplete types"); |
| |
| static constexpr bool value = true; |
| using type = Result; |
| }; |
| |
| namespace internal { |
| |
| // Helper class to check if a given type can be passed to |
| // perfetto::WriteIntoTracedValue. This template will always resolve (with |
| // |value| being set to either true or false depending on presence of the |
| // support, so this macro is useful in the situation when you want to e.g. OR |
| // the result with some other conditions. |
| // |
| // In this case, compiler will not give you the full deduction chain, so, for |
| // example, use check_traced_value_support for writing positive static_asserts |
| // and has_traced_value_support for writing negative. |
| template <typename T> |
| class has_traced_value_support { |
| using Yes = char[1]; |
| using No = char[2]; |
| |
| template <typename V> |
| static Yes& check_support(check_traced_value_support_t<V, int>); |
| template <typename V> |
| static No& check_support(...); |
| |
| public: |
| static constexpr bool value = sizeof(Yes) == sizeof(check_support<T>(0)); |
| }; |
| |
| } // namespace internal |
| |
| template <typename T> |
| void WriteIntoTracedValue(TracedValue context, T&& value) { |
| // TODO(altimin): Add a URL to documentation and a list of common failure |
| // patterns. |
| static_assert( |
| internal::has_traced_value_support<T>::value, |
| "The provided type (passed to TRACE_EVENT argument / TracedArray::Append " |
| "/ TracedDictionary::Add) does not support being written in a trace " |
| "format. Please see the comment in traced_value.h for more details."); |
| |
| // Should be kept in sync with check_traced_value_support_t! |
| internal::WriteImpl(base::priority_tag<internal::kMaxWriteImplPriority>(), |
| std::move(context), std::forward<T>(value)); |
| } |
| |
| // Helpers to write a given value into TracedValue even if the given type |
| // doesn't support conversion (in which case the provided fallback should be |
| // used). Useful for automatically generating conversions for autogenerated |
| // code, but otherwise shouldn't be used as non-autogenerated code is expected |
| // to define WriteIntoTracedValue convertor. |
| // See WriteWithFallback test in traced_value_unittest.cc for a concrete |
| // example. |
| template <typename T> |
| typename std::enable_if<internal::has_traced_value_support<T>::value>::type |
| WriteIntoTracedValueWithFallback(TracedValue context, |
| T&& value, |
| const std::string&) { |
| WriteIntoTracedValue(std::move(context), std::forward<T>(value)); |
| } |
| |
| template <typename T> |
| typename std::enable_if<!internal::has_traced_value_support<T>::value>::type |
| WriteIntoTracedValueWithFallback(TracedValue context, |
| T&&, |
| const std::string& fallback) { |
| std::move(context).WriteString(fallback); |
| } |
| |
| // TraceFormatTraits implementations for primitive types. |
| |
| // Specialisation for signed integer types (note: it excludes enums, which have |
| // their own explicit specialisation). |
| template <typename T> |
| struct TraceFormatTraits< |
| T, |
| typename std::enable_if<std::is_integral<T>::value && |
| !std::is_same<T, bool>::value && |
| std::is_signed<T>::value>::type> { |
| inline static void WriteIntoTrace(TracedValue context, T value) { |
| std::move(context).WriteInt64(value); |
| } |
| }; |
| |
| // Specialisation for unsigned integer types (note: it excludes enums, which |
| // have their own explicit specialisation). |
| template <typename T> |
| struct TraceFormatTraits< |
| T, |
| typename std::enable_if<std::is_integral<T>::value && |
| !std::is_same<T, bool>::value && |
| std::is_unsigned<T>::value>::type> { |
| inline static void WriteIntoTrace(TracedValue context, T value) { |
| std::move(context).WriteUInt64(value); |
| } |
| }; |
| |
| // Specialisation for bools. |
| template <> |
| struct TraceFormatTraits<bool> { |
| inline static void WriteIntoTrace(TracedValue context, bool value) { |
| std::move(context).WriteBoolean(value); |
| } |
| }; |
| |
| // Specialisation for floating point values. |
| template <typename T> |
| struct TraceFormatTraits< |
| T, |
| typename std::enable_if<std::is_floating_point<T>::value>::type> { |
| inline static void WriteIntoTrace(TracedValue context, T value) { |
| std::move(context).WriteDouble(static_cast<double>(value)); |
| } |
| }; |
| |
| // Specialisation for signed enums. |
| template <typename T> |
| struct TraceFormatTraits< |
| T, |
| typename std::enable_if< |
| std::is_enum<T>::value && |
| std::is_signed< |
| typename internal::safe_underlying_type<T>::type>::value>::type> { |
| inline static void WriteIntoTrace(TracedValue context, T value) { |
| std::move(context).WriteInt64(static_cast<int64_t>(value)); |
| } |
| }; |
| |
| // Specialisation for unsigned enums. |
| template <typename T> |
| struct TraceFormatTraits< |
| T, |
| typename std::enable_if< |
| std::is_enum<T>::value && |
| std::is_unsigned< |
| typename internal::safe_underlying_type<T>::type>::value>::type> { |
| inline static void WriteIntoTrace(TracedValue context, T value) { |
| std::move(context).WriteUInt64(static_cast<uint64_t>(value)); |
| } |
| }; |
| |
| // Specialisations for C-style strings. |
| template <> |
| struct TraceFormatTraits<const char*> { |
| inline static void WriteIntoTrace(TracedValue context, const char* value) { |
| std::move(context).WriteString(value); |
| } |
| }; |
| |
| template <> |
| struct TraceFormatTraits<char[]> { |
| inline static void WriteIntoTrace(TracedValue context, const char value[]) { |
| std::move(context).WriteString(value); |
| } |
| }; |
| |
| template <size_t N> |
| struct TraceFormatTraits<char[N]> { |
| inline static void WriteIntoTrace(TracedValue context, const char value[N]) { |
| std::move(context).WriteString(value); |
| } |
| }; |
| |
| // Specialisation for C++ strings. |
| template <> |
| struct TraceFormatTraits<std::string> { |
| inline static void WriteIntoTrace(TracedValue context, |
| const std::string& value) { |
| std::move(context).WriteString(value); |
| } |
| }; |
| |
| // Specialisation for (const) void*, which writes the pointer value. |
| template <> |
| struct TraceFormatTraits<void*> { |
| inline static void WriteIntoTrace(TracedValue context, void* value) { |
| std::move(context).WritePointer(value); |
| } |
| }; |
| |
| template <> |
| struct TraceFormatTraits<const void*> { |
| inline static void WriteIntoTrace(TracedValue context, const void* value) { |
| std::move(context).WritePointer(value); |
| } |
| }; |
| |
| // Specialisation for std::unique_ptr<>, which writes either nullptr or the |
| // object it points to. |
| template <typename T> |
| struct TraceFormatTraits<std::unique_ptr<T>, check_traced_value_support_t<T>> { |
| inline static void WriteIntoTrace(TracedValue context, |
| const std::unique_ptr<T>& value) { |
| ::perfetto::WriteIntoTracedValue(std::move(context), value.get()); |
| } |
| |
| template <typename MessageType> |
| inline static void WriteIntoTrace(TracedProto<MessageType> message, |
| const std::unique_ptr<T>& value) { |
| ::perfetto::WriteIntoTracedProto(std::move(message), value.get()); |
| } |
| }; |
| |
| // Specialisation for raw pointer, which writes either nullptr or the object it |
| // points to. |
| template <typename T> |
| struct TraceFormatTraits<T*, check_traced_value_support_t<T>> { |
| inline static void WriteIntoTrace(TracedValue context, T* value) { |
| if (!value) { |
| std::move(context).WritePointer(nullptr); |
| return; |
| } |
| ::perfetto::WriteIntoTracedValue(std::move(context), *value); |
| } |
| |
| template <typename MessageType> |
| inline static void WriteIntoTrace(TracedProto<MessageType> message, |
| T* value) { |
| if (!value) { |
| // Start the message, but do not write anything. TraceProcessor will emit |
| // a NULL value. |
| return; |
| } |
| |
| ::perfetto::WriteIntoTracedProto(std::move(message), *value); |
| } |
| }; |
| |
| // Specialisation for nullptr. |
| template <> |
| struct TraceFormatTraits<std::nullptr_t> { |
| inline static void WriteIntoTrace(TracedValue context, std::nullptr_t) { |
| std::move(context).WritePointer(nullptr); |
| } |
| |
| template <typename MessageType> |
| inline static void WriteIntoTrace(TracedProto<MessageType>, std::nullptr_t) { |
| // Start the message, but do not write anything. TraceProcessor will emit a |
| // NULL value. |
| } |
| }; |
| |
| } // namespace perfetto |
| |
| #endif // INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_ |