| // Copyright 2019 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 UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_ |
| #define UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_ |
| |
| #include <atomic> |
| #include <cstring> |
| #include <memory> |
| #include <stack> |
| #include <utility> |
| #include <vector> |
| |
| #include "platform/api/time.h" |
| #include "platform/base/error.h" |
| #include "platform/base/trace_logging_types.h" |
| #include "util/osp_logging.h" |
| |
| #if defined(ENABLE_TRACE_LOGGING) |
| |
| namespace openscreen { |
| namespace internal { |
| |
| // A base class for all trace logging objects which will create new entries in |
| // the Trace Hierarchy. |
| // 1) The sharing of all static and thread_local variables across template |
| // specializations. |
| // 2) Including all children in the same traces vector. |
| class ScopedTraceOperation { |
| public: |
| // Define the destructor to remove this item from the stack when it's |
| // destroyed. |
| virtual ~ScopedTraceOperation(); |
| |
| // Getters the current Trace Hierarchy. If the traces_ stack hasn't been |
| // created yet, return as if the empty root node is there. |
| static TraceId current_id() { |
| return traces_ == nullptr ? kEmptyTraceId : traces_->top()->trace_id_; |
| } |
| |
| static TraceId root_id() { |
| return traces_ == nullptr ? kEmptyTraceId : traces_->top()->root_id_; |
| } |
| |
| static TraceIdHierarchy hierarchy() { |
| if (traces_ == nullptr) { |
| return TraceIdHierarchy::Empty(); |
| } |
| |
| return traces_->top()->to_hierarchy(); |
| } |
| |
| // Static method to set the result of the most recent trace. |
| static void set_result(const Error& error) { set_result(error.code()); } |
| static void set_result(Error::Code error) { |
| if (traces_ == nullptr) { |
| return; |
| } |
| traces_->top()->SetTraceResult(error); |
| } |
| |
| // Traces the end of an asynchronous call. |
| // NOTE: This returns a bool rather than a void because it keeps the syntax of |
| // the ternary operator in the macros simpler. |
| static bool TraceAsyncEnd(const uint32_t line, |
| const char* file, |
| TraceId id, |
| Error::Code e); |
| |
| protected: |
| // Sets the result of this trace log. |
| // NOTE: this must be define in this class rather than TraceLogger so that it |
| // can be called on traces.back() without a potentially unsafe cast or type |
| // checking at runtime. |
| virtual void SetTraceResult(Error::Code error) = 0; |
| |
| // Constructor to set all trace id information. |
| ScopedTraceOperation(TraceId current_id = kUnsetTraceId, |
| TraceId parent_id = kUnsetTraceId, |
| TraceId root_id = kUnsetTraceId); |
| |
| // Current TraceId information. |
| TraceId trace_id_; |
| TraceId parent_id_; |
| TraceId root_id_; |
| |
| TraceIdHierarchy to_hierarchy() { return {trace_id_, parent_id_, root_id_}; } |
| |
| private: |
| // NOTE: A std::vector is used for backing the stack because it provides the |
| // best perf. Further perf improvement could be achieved later by swapping |
| // this out for a circular buffer once OSP supports that. Additional details |
| // can be found here: |
| // https://www.codeproject.com/Articles/1185449/Performance-of-a-Circular-Buffer-vs-Vector-Deque-a |
| using TraceStack = |
| std::stack<ScopedTraceOperation*, std::vector<ScopedTraceOperation*>>; |
| |
| // Counter to pick IDs when it is not provided. |
| static std::atomic<std::uint64_t> trace_id_counter_; |
| |
| // The LIFO stack of TraceLoggers currently being watched by this |
| // thread. |
| static thread_local TraceStack* traces_; |
| static thread_local ScopedTraceOperation* root_node_; |
| |
| OSP_DISALLOW_COPY_AND_ASSIGN(ScopedTraceOperation); |
| }; |
| |
| // The class which does actual trace logging. |
| class TraceLoggerBase : public ScopedTraceOperation { |
| public: |
| TraceLoggerBase(TraceCategory::Value category, |
| const char* name, |
| const char* file, |
| uint32_t line, |
| TraceId current = kUnsetTraceId, |
| TraceId parent = kUnsetTraceId, |
| TraceId root = kUnsetTraceId); |
| |
| TraceLoggerBase(TraceCategory::Value category, |
| const char* name, |
| const char* file, |
| uint32_t line, |
| TraceIdHierarchy ids); |
| |
| protected: |
| // Set the result. |
| void SetTraceResult(Error::Code error) override { result_ = error; } |
| |
| // Timestamp for when the object was created. |
| Clock::time_point start_time_; |
| |
| // Result of this operation. |
| Error::Code result_; |
| |
| // Name of this operation. |
| const char* name_; |
| |
| // Name of the file. |
| const char* file_name_; |
| |
| // Line number the log was generated from. |
| uint32_t line_number_; |
| |
| // Category of this trace log. |
| TraceCategory::Value category_; |
| |
| private: |
| OSP_DISALLOW_COPY_AND_ASSIGN(TraceLoggerBase); |
| }; |
| |
| class SynchronousTraceLogger : public TraceLoggerBase { |
| public: |
| using TraceLoggerBase::TraceLoggerBase; |
| |
| ~SynchronousTraceLogger() override; |
| |
| private: |
| OSP_DISALLOW_COPY_AND_ASSIGN(SynchronousTraceLogger); |
| }; |
| |
| class AsynchronousTraceLogger : public TraceLoggerBase { |
| public: |
| using TraceLoggerBase::TraceLoggerBase; |
| |
| ~AsynchronousTraceLogger() override; |
| |
| private: |
| OSP_DISALLOW_COPY_AND_ASSIGN(AsynchronousTraceLogger); |
| }; |
| |
| // Inserts a fake element into the ScopedTraceOperation stack to set |
| // the current TraceId Hierarchy manually. |
| class TraceIdSetter final : public ScopedTraceOperation { |
| public: |
| explicit TraceIdSetter(TraceIdHierarchy ids) |
| : ScopedTraceOperation(ids.current, ids.parent, ids.root) {} |
| ~TraceIdSetter() final; |
| |
| // Creates a new TraceIdSetter to set the full TraceId Hierarchy to default |
| // values and does not push it to the traces stack. |
| static TraceIdSetter* CreateStackRootNode(); |
| |
| private: |
| // Implement abstract method for use in Macros. |
| void SetTraceResult(Error::Code error) {} |
| |
| OSP_DISALLOW_COPY_AND_ASSIGN(TraceIdSetter); |
| }; |
| |
| // This helper object allows us to delete objects allocated on the stack in a |
| // unique_ptr. |
| template <class T> |
| class TraceInstanceHelper { |
| private: |
| class TraceOperationOnStackDeleter { |
| public: |
| void operator()(T* ptr) { ptr->~T(); } |
| }; |
| |
| using TraceInstanceWrapper = std::unique_ptr<T, TraceOperationOnStackDeleter>; |
| |
| public: |
| template <typename... Args> |
| static TraceInstanceWrapper Create(uint8_t storage[sizeof(T)], Args... args) { |
| return TraceInstanceWrapper(new (storage) T(std::forward<Args&&>(args)...)); |
| } |
| |
| static TraceInstanceWrapper Empty() { return TraceInstanceWrapper(); } |
| }; |
| |
| } // namespace internal |
| } // namespace openscreen |
| |
| #endif // defined(ENABLE_TRACE_LOGGING) |
| |
| #endif // UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_ |