/* * 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 <string> #include <string_view> #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 defining 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_COMPONENT TracedValue CreateTracedValueFromProto(protos::pbzero::DebugAnnotation*, EventContext* = nullptr); } class PERFETTO_EXPORT_COMPONENT TracedValue { … }; template <typename MessageType> TracedProto<MessageType> TracedValue::WriteProto() && { … } class PERFETTO_EXPORT_COMPONENT TracedArray { … }; class PERFETTO_EXPORT_COMPONENT TracedDictionary { … }; 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 = …; // 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) { … } // 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) { … } // 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) { … } // 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) { … } // 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) { … } // 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) { … } // 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 { … }; safe_underlying_type<T, false>; template <typename T> struct is_incomplete_type { … }; // 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[]> { … }; } // 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. check_traced_value_support_t; // 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. check_traced_value_support<T, Result, check_traced_value_support_t<T, 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 { … }; } // namespace internal template <typename T> void WriteIntoTracedValue(TracedValue context, 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&) { … } template <typename T> typename std::enable_if<!internal::has_traced_value_support<T>::value>::type WriteIntoTracedValueWithFallback(TracedValue context, T&&, const std::string& fallback) { … } // TraceFormatTraits implementations for primitive types. // Specialisation for signed integer types (note: it excludes enums, which have // their own explicit specialisation). TraceFormatTraits<T, typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value && std::is_signed<T>::value>::type>; // Specialisation for unsigned integer types (note: it excludes enums, which // have their own explicit specialisation). TraceFormatTraits<T, typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value && std::is_unsigned<T>::value>::type>; // Specialisation for bools. template <> struct TraceFormatTraits<bool> { … }; // Specialisation for floating point values. TraceFormatTraits<T, typename std::enable_if<std::is_floating_point<T>::value>::type>; // Specialisation for signed enums. TraceFormatTraits<T, typename std::enable_if<std::is_enum<T>::value && std::is_signed<typename internal::safe_underlying_type<T>::type>::value>::type>; // Specialisation for unsigned enums. TraceFormatTraits<T, typename std::enable_if<std::is_enum<T>::value && std::is_unsigned<typename internal::safe_underlying_type<T>::type>::value>::type>; // Specialisations for C-style strings. template <> struct TraceFormatTraits<const char*> { … }; template <> struct TraceFormatTraits<char[]> { … }; TraceFormatTraits<char[N]>; // Specialization for Perfetto strings. template <> struct TraceFormatTraits<perfetto::StaticString> { … }; template <> struct TraceFormatTraits<perfetto::DynamicString> { … }; // Specialisation for C++ strings. template <> struct TraceFormatTraits<std::string> { … }; // Specialisation for C++ string_views. template <> struct TraceFormatTraits<std::string_view> { … }; // Specialisation for (const) void*, which writes the pointer value. template <> struct TraceFormatTraits<void*> { … }; template <> struct TraceFormatTraits<const void*> { … }; // Specialisation for std::unique_ptr<>, which writes either nullptr or the // object it points to. TraceFormatTraits<std::unique_ptr<T>, check_traced_value_support_t<T>>; // Specialisation for raw pointer, which writes either nullptr or the object it // points to. TraceFormatTraits<T *, check_traced_value_support_t<T>>; // Specialisation for nullptr. template <> struct TraceFormatTraits<std::nullptr_t> { … }; } // namespace perfetto #endif // INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_