// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_TRACE_EVENT_TRACE_ARGUMENTS_H_ #define BASE_TRACE_EVENT_TRACE_ARGUMENTS_H_ #include #include #include #include #include #include "base/base_export.h" #include "base/macros.h" #include "base/trace_event/common/trace_event_common.h" // Trace macro can have one or two optional arguments, each one of them // identified by a name (a C string literal) and a value, which can be an // integer, enum, floating point, boolean, string pointer or reference, or // std::unique_ptr compatible values. Additionally, // custom data types need to be supported, like time values or WTF::CString. // // TraceArguments is a helper class used to store 0 to 2 named arguments // corresponding to an individual trace macro call. As efficiently as possible, // and with the minimal amount of generated machine code (since this affects // any TRACE macro call). Each argument has: // // - A name (C string literal, e.g "dumps") // - An 8-bit type value, corresponding to the TRACE_VALUE_TYPE_XXX macros. // - A value, stored in a TraceValue union // // IMPORTANT: For a TRACE_VALUE_TYPE_CONVERTABLE types, the TraceArguments // instance owns the pointed ConvertableToTraceFormat object, i.e. it will // delete it automatically on destruction. // // TraceArguments instances should be built using one of specialized // constructors declared below. One cannot modify an instance once it has // been built, except for move operations, Reset() and destruction. Examples: // // TraceArguments args; // No arguments. // // args.size() == 0 // // TraceArguments("foo", 100); // // args.size() == 1 // // args.types()[0] == TRACE_VALUE_TYPE_INT // // args.names()[0] == "foo" // // args.values()[0].as_int == 100 // // TraceArguments("bar", 1ULL); // // args.size() == 1 // // args.types()[0] == TRACE_VALUE_TYPE_UINT // // args.names()[0] == "bar" // // args.values()[0].as_uint == 100 // // TraceArguments("foo", "Hello", "bar", "World"); // // args.size() == 2 // // args.types()[0] == TRACE_VALUE_TYPE_STRING // // args.types()[1] == TRACE_VALUE_TYPE_STRING // // args.names()[0] == "foo" // // args.names()[1] == "bar" // // args.values()[0].as_string == "Hello" // // args.values()[1].as_string == "World" // // std::string some_string = ...; // TraceArguments("str1", some_string); // // args.size() == 1 // // args.types()[0] == TRACE_VALUE_TYPE_COPY_STRING // // args.names()[0] == "str1" // // args.values()[0].as_string == some_string.c_str() // // Note that TRACE_VALUE_TYPE_COPY_STRING corresponds to string pointers // that point to temporary values that may disappear soon. The // TraceArguments::CopyStringTo() method can be used to copy their content // into a StringStorage memory block, and update the |as_string| value pointers // to it to avoid keeping any dangling pointers. This is used by TraceEvent // to keep copies of such strings in the log after their initialization values // have disappeared. // // The TraceStringWithCopy helper class can be used to initialize a value // from a regular string pointer with TRACE_VALUE_TYPE_COPY_STRING too, as in: // // const char str[] = "...."; // TraceArguments("foo", str, "bar", TraceStringWithCopy(str)); // // args.size() == 2 // // args.types()[0] == TRACE_VALUE_TYPE_STRING // // args.types()[1] == TRACE_VALUE_TYPE_COPY_STRING // // args.names()[0] == "foo" // // args.names()[1] == "bar" // // args.values()[0].as_string == str // // args.values()[1].as_string == str // // StringStorage storage; // args.CopyStringTo(&storage, false, nullptr, nullptr); // // args.size() == 2 // // args.types()[0] == TRACE_VALUE_TYPE_STRING // // args.types()[1] == TRACE_VALUE_TYPE_COPY_STRING // // args.names()[0] == "foo" // // args.names()[1] == "bar" // // args.values()[0].as_string == str // // args.values()[1].as_string == Address inside |storage|. // // Initialization from a std::unique_ptr // is supported but will move ownership of the pointer objects to the // TraceArguments instance: // // class MyConvertableType : // public base::trace_event::AsConvertableToTraceFormat { // ... // }; // // { // TraceArguments args("foo" , std::make_unique(...)); // // args.size() == 1 // // args.values()[0].as_convertable == address of MyConvertable object. // } // Calls |args| destructor, which will delete the object too. // // Finally, it is possible to support initialization from custom values by // specializing the TraceValue::Helper<> template struct as described below. // // This is how values of custom types like WTF::CString can be passed directly // to trace macros. namespace base { class Time; class TimeTicks; class ThreadTicks; namespace trace_event { class TraceEventMemoryOverhead; // For any argument of type TRACE_VALUE_TYPE_CONVERTABLE the provided // class must implement this interface. Note that unlike other values, // these objects will be owned by the TraceArguments instance that points // to them. class BASE_EXPORT ConvertableToTraceFormat { public: ConvertableToTraceFormat() = default; virtual ~ConvertableToTraceFormat() = default; // Append the class info to the provided |out| string. The appended // data must be a valid JSON object. Strings must be properly quoted, and // escaped. There is no processing applied to the content after it is // appended. virtual void AppendAsTraceFormat(std::string* out) const = 0; // Append the class info directly into the Perfetto-defined proto // format; this is attempted first and if this returns true, // AppendAsTraceFormat is not called. The ProtoAppender interface // acts as a bridge to avoid proto/Perfetto dependencies in base. class BASE_EXPORT ProtoAppender { public: virtual ~ProtoAppender() = default; virtual void AddBuffer(uint8_t* begin, uint8_t* end) = 0; // Copy all of the previous buffers registered with AddBuffer // into the proto, with the given |field_id|. virtual size_t Finalize(uint32_t field_id) = 0; }; virtual bool AppendToProto(ProtoAppender* appender); virtual void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead); private: DISALLOW_COPY_AND_ASSIGN(ConvertableToTraceFormat); }; const int kTraceMaxNumArgs = 2; // A union used to hold the values of individual trace arguments. // // This is a POD union for performance reason. Initialization from an // explicit C++ trace argument should be performed with the Init() // templated method described below. // // Initialization from custom types is possible by implementing a custom // TraceValue::Helper<> instantiation as described below. // // IMPORTANT: Pointer storage inside a TraceUnion follows specific rules: // // - |as_pointer| is for raw pointers that should be treated as a simple // address and will never be dereferenced. Associated with the // TRACE_VALUE_TYPE_POINTER type. // // - |as_string| is for C-string pointers, associated with both // TRACE_VALUE_TYPE_STRING and TRACE_VALUE_TYPE_COPY_STRING. The former // indicates that the string pointer is persistent (e.g. a C string // literal), while the second indicates that the pointer belongs to a // temporary variable that may disappear soon. The TraceArguments class // provides a CopyStringTo() method to copy these strings into a // StringStorage instance, which is useful if the instance needs to // survive longer than the temporaries. // // - |as_convertable| is equivalent to // std::unique_ptr, except that it is a pointer // to keep this union POD and avoid un-necessary declarations and potential // code generation. This means that its ownership is passed to the // TraceValue instance when Init(std::unique_ptr) // is called, and that it will be deleted by the containing TraceArguments // destructor, or Reset() method. // union BASE_EXPORT TraceValue { bool as_bool; unsigned long long as_uint; long long as_int; double as_double; const void* as_pointer; const char* as_string; ConvertableToTraceFormat* as_convertable; // There is no constructor to keep this structure POD intentionally. // This avoids un-needed initialization when only 0 or 1 arguments are // used to construct a TraceArguments instance. Use Init() instead to // perform explicit initialization from a given C++ value. // Initialize TraceValue instance from a C++ trace value. // This relies on the proper specialization of TraceValue::Helper<> // described below. Usage is simply: // // TraceValue v; // v.Init(); // // NOTE: For ConvertableToTraceFormat values, see the note above and // the one for TraceValue::Helper for CONVERTABLE_TYPE below. template void Init(T&& value) { using ValueType = typename InnerType::type; Helper::SetValue(this, std::forward(value)); } // Static method to create a new TraceValue instance from a given // initialization value. Note that this deduces the TRACE_VALUE_TYPE_XXX // type but doesn't return it, use ForType::value for this. // // Usage example: // auto v = TraceValue::Make(100); // auto v2 = TraceValue::Make("Some text string"); // // IMPORTANT: Experience shows that the compiler generates worse code when // using this method rather than calling Init() directly on an existing // TraceValue union :-( // template static TraceValue Make(T&& value) { TraceValue ret; ret.Init(std::forward(value)); return ret; } // Output current value as a JSON string. |type| must be a valid // TRACE_VALUE_TYPE_XXX value. void AppendAsJSON(unsigned char type, std::string* out) const; // Output current value as a string. If the output string is to be used // in a JSON format use AppendAsJSON instead. |type| must be valid // TRACE_VALUE_TYPE_XXX value. void AppendAsString(unsigned char type, std::string* out) const; private: void Append(unsigned char type, bool as_json, std::string* out) const; // InnerType::type removes reference, cv-qualifications and decays // function and arrays into pointers. Only used internally. template struct InnerType { using type = typename std::remove_cv::type>::type>::type; }; public: // TraceValue::Helper is used to provide information about initialization // value types and an initialization function. It is a struct that should // provide the following for supported initialization value types: // // - kType: is a static TRACE_VALUE_TYPE_XXX constant. // // - SetValue(TraceValue*, T): is a static inline method that sets // TraceValue value from a given T value. Second parameter type // can also be const T& or T&& to restrict uses. // // IMPORTANT: The type T must be InnerType, where Q is the real C++ // argument type. I.e. you should not have to deal with reference types // in your specialization. // // Specializations are defined for integers, enums, floating point, pointers, // constant C string literals and pointers, std::string, time values below. // // Specializations for custom types are possible provided that there exists // a corresponding Helper specialization, for example: // // template <> // struct base::trace_event::TraceValue::Helper { // static constexpr unsigned char kTypes = TRACE_VALUE_TYPE_COPY_STRING; // static inline void SetValue(TraceValue* v, const Foo& value) { // v->as_string = value.c_str(); // } // }; // // Will allow code like: // // Foo foo = ...; // auto v = TraceValue::Make(foo); // // Or even: // Foo foo = ...; // TraceArguments args("foo_arg1", foo); // template struct Helper {}; // TraceValue::TypeFor::value returns the TRACE_VALUE_TYPE_XXX // corresponding to initialization values of type T. template struct TypeFor { using ValueType = typename InnerType::type; static const unsigned char value = Helper::kType; }; // TraceValue::TypeCheck::value is only defined iff T can be used to // initialize a TraceValue instance. This is useful to restrict template // instantiation to only the appropriate type (see TraceArguments // constructors below). template ::type>::kType)> struct TypeCheck { static const bool value = true; }; }; // TraceValue::Helper for integers and enums. template struct TraceValue::Helper< T, typename std::enable_if::value || std::is_enum::value>::type> { static constexpr unsigned char kType = std::is_signed::value ? TRACE_VALUE_TYPE_INT : TRACE_VALUE_TYPE_UINT; static inline void SetValue(TraceValue* v, T value) { v->as_uint = static_cast(value); } }; // TraceValue::Helper for floating-point types template struct TraceValue:: Helper::value>::type> { static constexpr unsigned char kType = TRACE_VALUE_TYPE_DOUBLE; static inline void SetValue(TraceValue* v, T value) { v->as_double = value; } }; // TraceValue::Helper for bool. template <> struct TraceValue::Helper { static constexpr unsigned char kType = TRACE_VALUE_TYPE_BOOL; static inline void SetValue(TraceValue* v, bool value) { v->as_bool = value; } }; // TraceValue::Helper for generic pointer types. template struct TraceValue::Helper { static constexpr unsigned char kType = TRACE_VALUE_TYPE_POINTER; static inline void SetValue(TraceValue* v, const typename std::decay::type* value) { v->as_pointer = value; } }; // TraceValue::Helper for raw persistent C strings. template <> struct TraceValue::Helper { static constexpr unsigned char kType = TRACE_VALUE_TYPE_STRING; static inline void SetValue(TraceValue* v, const char* value) { v->as_string = value; } }; // TraceValue::Helper for std::string values. template <> struct TraceValue::Helper { static constexpr unsigned char kType = TRACE_VALUE_TYPE_COPY_STRING; static inline void SetValue(TraceValue* v, const std::string& value) { v->as_string = value.c_str(); } }; // Special case for scoped pointers to convertables to trace format. // |CONVERTABLE_TYPE| must be a type whose pointers can be converted to a // ConvertableToTraceFormat* pointer as well (e.g. a derived class). // IMPORTANT: This takes an std::unique_ptr value, and takes // ownership of the pointed object! template struct TraceValue::Helper, typename std::enable_if::value>::type> { static constexpr unsigned char kType = TRACE_VALUE_TYPE_CONVERTABLE; static inline void SetValue(TraceValue* v, std::unique_ptr value) { v->as_convertable = value.release(); } }; // Specialization for time-based values like base::Time, which provide a // a ToInternalValue() method. template struct TraceValue::Helper< T, typename std::enable_if::value || std::is_same::value || std::is_same::value>::type> { static constexpr unsigned char kType = TRACE_VALUE_TYPE_INT; static inline void SetValue(TraceValue* v, const T& value) { v->as_int = value.ToInternalValue(); } }; // Simple container for const char* that should be copied instead of retained. // The goal is to indicate that the C string is copyable, unlike the default // Init(const char*) implementation. Usage is: // // const char* str = ...; // v.Init(TraceStringWithCopy(str)); // // Which will mark the string as TRACE_VALUE_TYPE_COPY_STRING, instead of // TRACE_VALUE_TYPE_STRING. // class TraceStringWithCopy { public: explicit TraceStringWithCopy(const char* str) : str_(str) {} const char* str() const { return str_; } private: const char* str_; }; template <> struct TraceValue::Helper { static constexpr unsigned char kType = TRACE_VALUE_TYPE_COPY_STRING; static inline void SetValue(TraceValue* v, const TraceStringWithCopy& value) { v->as_string = value.str(); } }; class TraceArguments; // A small class used to store a copy of all strings from a given // TraceArguments instance (see below). When empty, this should only // take the size of a pointer. Otherwise, this will point to a heap // allocated block containing a size_t value followed by all characters // in the storage area. For most cases, this is more efficient // than using a std::unique_ptr or an std::vector. class BASE_EXPORT StringStorage { public: constexpr StringStorage() = default; explicit StringStorage(size_t alloc_size) { Reset(alloc_size); } ~StringStorage() { if (data_) ::free(data_); } StringStorage(StringStorage&& other) noexcept : data_(other.data_) { other.data_ = nullptr; } StringStorage& operator=(StringStorage&& other) noexcept { if (this != &other) { if (data_) ::free(data_); data_ = other.data_; other.data_ = nullptr; } return *this; } // Reset storage area to new allocation size. Existing content might not // be preserved. If |alloc_size| is 0, this will free the storage area // as well. void Reset(size_t alloc_size = 0); // Accessors. constexpr size_t size() const { return data_ ? data_->size : 0u; } constexpr const char* data() const { return data_ ? data_->chars : nullptr; } constexpr char* data() { return data_ ? data_->chars : nullptr; } constexpr const char* begin() const { return data(); } constexpr const char* end() const { return data() + size(); } inline char* begin() { return data(); } inline char* end() { return data() + size(); } // True iff storage is empty. constexpr bool empty() const { return size() == 0; } // Returns true if |ptr| is inside the storage area, false otherwise. // Used during unit-testing. constexpr bool Contains(const void* ptr) const { const char* char_ptr = static_cast(ptr); return (char_ptr >= begin() && char_ptr < end()); } // Returns true if all string pointers in |args| are contained in this // storage area. bool Contains(const TraceArguments& args) const; // Return an estimate of the memory overhead of this instance. This doesn't // count the size of |data_| itself. constexpr size_t EstimateTraceMemoryOverhead() const { return data_ ? sizeof(size_t) + data_->size : 0u; } private: // Heap allocated data block (variable size), made of: // // - size: a size_t field, giving the size of the following |chars| array. // - chars: an array of |size| characters, holding all zero-terminated // strings referenced from a TraceArguments instance. struct Data { size_t size = 0; char chars[1]; // really |size| character items in storage. }; // This is an owning pointer. Normally, using a std::unique_ptr<> would be // enough, but the compiler will then complaing about inlined constructors // and destructors being too complex (!), resulting in larger code for no // good reason. Data* data_ = nullptr; }; // TraceArguments models an array of kMaxSize trace-related items, // each one of them having: // - a name, which is a constant char array literal. // - a type, as described by TRACE_VALUE_TYPE_XXX macros. // - a value, stored in a TraceValue union. // // IMPORTANT: For TRACE_VALUE_TYPE_CONVERTABLE, the value holds an owning // pointer to an AsConvertableToTraceFormat instance, which will // be destroyed with the array (or moved out of it when passed // to a TraceEvent instance). // // For TRACE_VALUE_TYPE_COPY_STRING, the value holds a const char* pointer // whose content will be copied when creating a TraceEvent instance. // // IMPORTANT: Most constructors and the destructor are all inlined // intentionally, in order to let the compiler remove un-necessary operations // and reduce machine code. // class BASE_EXPORT TraceArguments { public: // Maximum number of arguments held by this structure. static constexpr size_t kMaxSize = 2; // Default constructor, no arguments. TraceArguments() : size_(0) {} // Constructor for a single argument. template ::value)> TraceArguments(const char* arg1_name, T&& arg1_value) : size_(1) { types_[0] = TraceValue::TypeFor::value; names_[0] = arg1_name; values_[0].Init(std::forward(arg1_value)); } // Constructor for two arguments. template ::value && TraceValue::TypeCheck::value)> TraceArguments(const char* arg1_name, T1&& arg1_value, const char* arg2_name, T2&& arg2_value) : size_(2) { types_[0] = TraceValue::TypeFor::value; types_[1] = TraceValue::TypeFor::value; names_[0] = arg1_name; names_[1] = arg2_name; values_[0].Init(std::forward(arg1_value)); values_[1].Init(std::forward(arg2_value)); } // Constructor used to convert a legacy set of arguments when there // are no convertable values at all. TraceArguments(int num_args, const char* const* arg_names, const unsigned char* arg_types, const unsigned long long* arg_values); // Constructor used to convert legacy set of arguments, where the // convertable values are also provided by an array of CONVERTABLE_TYPE. template TraceArguments(int num_args, const char* const* arg_names, const unsigned char* arg_types, const unsigned long long* arg_values, CONVERTABLE_TYPE* arg_convertables) { static int max_args = static_cast(kMaxSize); if (num_args > max_args) num_args = max_args; size_ = static_cast(num_args); for (size_t n = 0; n < size_; ++n) { types_[n] = arg_types[n]; names_[n] = arg_names[n]; if (arg_types[n] == TRACE_VALUE_TYPE_CONVERTABLE) { values_[n].Init( std::forward(std::move(arg_convertables[n]))); } else { values_[n].as_uint = arg_values[n]; } } } // Destructor. NOTE: Intentionally inlined (see note above). ~TraceArguments() { for (size_t n = 0; n < size_; ++n) { if (types_[n] == TRACE_VALUE_TYPE_CONVERTABLE) delete values_[n].as_convertable; } } // Disallow copy operations. TraceArguments(const TraceArguments&) = delete; TraceArguments& operator=(const TraceArguments&) = delete; // Allow move operations. TraceArguments(TraceArguments&& other) noexcept { ::memcpy(this, &other, sizeof(*this)); // All owning pointers were copied to |this|. Setting |other.size_| will // mask the pointer values still in |other|. other.size_ = 0; } TraceArguments& operator=(TraceArguments&&) noexcept; // Accessors size_t size() const { return size_; } const unsigned char* types() const { return types_; } const char* const* names() const { return names_; } const TraceValue* values() const { return values_; } // Reset to empty arguments list. void Reset(); // Use |storage| to copy all copyable strings. // If |copy_all_strings| is false, then only the TRACE_VALUE_TYPE_COPY_STRING // values will be copied into storage. If it is true, then argument names are // also copied to storage, as well as the strings pointed to by // |*extra_string1| and |*extra_string2|. // NOTE: If there are no strings to copy, |*storage| is left untouched. void CopyStringsTo(StringStorage* storage, bool copy_all_strings, const char** extra_string1, const char** extra_string2); // Append debug string representation to |*out|. void AppendDebugString(std::string* out); private: unsigned char size_; unsigned char types_[kMaxSize]; const char* names_[kMaxSize]; TraceValue values_[kMaxSize]; }; } // namespace trace_event } // namespace base #endif // BASE_TRACE_EVENT_TRACE_ARGUMENTS_H_