| /* | 
 |  *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | 
 |  * | 
 |  *  Use of this source code is governed by a BSD-style license | 
 |  *  that can be found in the LICENSE file in the root of the source | 
 |  *  tree. An additional intellectual property rights grant can be found | 
 |  *  in the file PATENTS.  All contributing project authors may | 
 |  *  be found in the AUTHORS file in the root of the source tree. | 
 |  */ | 
 |  | 
 | #include "rtc_base/event_tracer.h" | 
 |  | 
 | #include <stdio.h> | 
 |  | 
 | #include "rtc_base/trace_event.h" | 
 |  | 
 | #if defined(RTC_USE_PERFETTO) | 
 | #include "rtc_base/trace_categories.h" | 
 | #include "third_party/perfetto/include/perfetto/tracing/tracing.h" | 
 | #else | 
 | #include <inttypes.h> | 
 | #include <stdint.h> | 
 | #include <string.h> | 
 |  | 
 | #include <atomic> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "absl/strings/string_view.h" | 
 | #include "api/sequence_checker.h" | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/event.h" | 
 | #include "rtc_base/logging.h" | 
 | #include "rtc_base/platform_thread.h" | 
 | #include "rtc_base/platform_thread_types.h" | 
 | #include "rtc_base/synchronization/mutex.h" | 
 | #include "rtc_base/thread_annotations.h" | 
 | #include "rtc_base/time_utils.h" | 
 | #endif | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | namespace { | 
 |  | 
 | #if !defined(RTC_USE_PERFETTO) | 
 | GetCategoryEnabledPtr g_get_category_enabled_ptr = nullptr; | 
 | AddTraceEventPtr g_add_trace_event_ptr = nullptr; | 
 | #endif | 
 |  | 
 | }  // namespace | 
 |  | 
 | #if defined(RTC_USE_PERFETTO) | 
 | void RegisterPerfettoTrackEvents() { | 
 |   if (perfetto::Tracing::IsInitialized()) { | 
 |     webrtc::TrackEvent::Register(); | 
 |   } | 
 | } | 
 | #else | 
 |  | 
 | void SetupEventTracer(GetCategoryEnabledPtr get_category_enabled_ptr, | 
 |                       AddTraceEventPtr add_trace_event_ptr) { | 
 |   g_get_category_enabled_ptr = get_category_enabled_ptr; | 
 |   g_add_trace_event_ptr = add_trace_event_ptr; | 
 | } | 
 |  | 
 | const unsigned char* EventTracer::GetCategoryEnabled(const char* name) { | 
 |   if (g_get_category_enabled_ptr) | 
 |     return g_get_category_enabled_ptr(name); | 
 |  | 
 |   // A string with null terminator means category is disabled. | 
 |   return reinterpret_cast<const unsigned char*>("\0"); | 
 | } | 
 |  | 
 | // Arguments to this function (phase, etc.) are as defined in | 
 | // webrtc/rtc_base/trace_event.h. | 
 | void EventTracer::AddTraceEvent(char phase, | 
 |                                 const unsigned char* category_enabled, | 
 |                                 const char* name, | 
 |                                 unsigned long long id, | 
 |                                 int num_args, | 
 |                                 const char** arg_names, | 
 |                                 const unsigned char* arg_types, | 
 |                                 const unsigned long long* arg_values, | 
 |                                 unsigned char flags) { | 
 |   if (g_add_trace_event_ptr) { | 
 |     g_add_trace_event_ptr(phase, category_enabled, name, id, num_args, | 
 |                           arg_names, arg_types, arg_values, flags); | 
 |   } | 
 | } | 
 | #endif | 
 |  | 
 | }  // namespace webrtc | 
 |  | 
 | #if defined(RTC_USE_PERFETTO) | 
 | // TODO(bugs.webrtc.org/15917): Implement for perfetto. | 
 | namespace rtc::tracing { | 
 | void SetupInternalTracer(bool enable_all_categories) {} | 
 | bool StartInternalCapture(absl::string_view filename) { | 
 |   return false; | 
 | } | 
 | void StartInternalCaptureToFile(FILE* file) {} | 
 | void StopInternalCapture() {} | 
 | void ShutdownInternalTracer() {} | 
 |  | 
 | }  // namespace rtc::tracing | 
 | #else | 
 |  | 
 | // This is a guesstimate that should be enough in most cases. | 
 | static const size_t kEventLoggerArgsStrBufferInitialSize = 256; | 
 | static const size_t kTraceArgBufferLength = 32; | 
 |  | 
 | namespace rtc { | 
 | namespace tracing { | 
 | namespace { | 
 |  | 
 | // Atomic-int fast path for avoiding logging when disabled. | 
 | static std::atomic<int> g_event_logging_active(0); | 
 |  | 
 | // TODO(pbos): Log metadata for all threads, etc. | 
 | class EventLogger final { | 
 |  public: | 
 |   ~EventLogger() { RTC_DCHECK(thread_checker_.IsCurrent()); } | 
 |  | 
 |   void AddTraceEvent(const char* name, | 
 |                      const unsigned char* category_enabled, | 
 |                      char phase, | 
 |                      int num_args, | 
 |                      const char** arg_names, | 
 |                      const unsigned char* arg_types, | 
 |                      const unsigned long long* arg_values, | 
 |                      uint64_t timestamp, | 
 |                      int pid, | 
 |                      rtc::PlatformThreadId thread_id) { | 
 |     std::vector<TraceArg> args(num_args); | 
 |     for (int i = 0; i < num_args; ++i) { | 
 |       TraceArg& arg = args[i]; | 
 |       arg.name = arg_names[i]; | 
 |       arg.type = arg_types[i]; | 
 |       arg.value.as_uint = arg_values[i]; | 
 |  | 
 |       // Value is a pointer to a temporary string, so we have to make a copy. | 
 |       if (arg.type == TRACE_VALUE_TYPE_COPY_STRING) { | 
 |         // Space for the string and for the terminating null character. | 
 |         size_t str_length = strlen(arg.value.as_string) + 1; | 
 |         char* str_copy = new char[str_length]; | 
 |         memcpy(str_copy, arg.value.as_string, str_length); | 
 |         arg.value.as_string = str_copy; | 
 |       } | 
 |     } | 
 |     webrtc::MutexLock lock(&mutex_); | 
 |     trace_events_.push_back( | 
 |         {name, category_enabled, phase, args, timestamp, 1, thread_id}); | 
 |   } | 
 |  | 
 |   // The TraceEvent format is documented here: | 
 |   // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview | 
 |   void Log() { | 
 |     RTC_DCHECK(output_file_); | 
 |     static constexpr webrtc::TimeDelta kLoggingInterval = | 
 |         webrtc::TimeDelta::Millis(100); | 
 |     fprintf(output_file_, "{ \"traceEvents\": [\n"); | 
 |     bool has_logged_event = false; | 
 |     while (true) { | 
 |       bool shutting_down = shutdown_event_.Wait(kLoggingInterval); | 
 |       std::vector<TraceEvent> events; | 
 |       { | 
 |         webrtc::MutexLock lock(&mutex_); | 
 |         trace_events_.swap(events); | 
 |       } | 
 |       std::string args_str; | 
 |       args_str.reserve(kEventLoggerArgsStrBufferInitialSize); | 
 |       for (TraceEvent& e : events) { | 
 |         args_str.clear(); | 
 |         if (!e.args.empty()) { | 
 |           args_str += ", \"args\": {"; | 
 |           bool is_first_argument = true; | 
 |           for (TraceArg& arg : e.args) { | 
 |             if (!is_first_argument) | 
 |               args_str += ","; | 
 |             is_first_argument = false; | 
 |             args_str += " \""; | 
 |             args_str += arg.name; | 
 |             args_str += "\": "; | 
 |             args_str += TraceArgValueAsString(arg); | 
 |  | 
 |             // Delete our copy of the string. | 
 |             if (arg.type == TRACE_VALUE_TYPE_COPY_STRING) { | 
 |               delete[] arg.value.as_string; | 
 |               arg.value.as_string = nullptr; | 
 |             } | 
 |           } | 
 |           args_str += " }"; | 
 |         } | 
 |         fprintf(output_file_, | 
 |                 "%s{ \"name\": \"%s\"" | 
 |                 ", \"cat\": \"%s\"" | 
 |                 ", \"ph\": \"%c\"" | 
 |                 ", \"ts\": %" PRIu64 | 
 |                 ", \"pid\": %d" | 
 | #if defined(WEBRTC_WIN) | 
 |                 ", \"tid\": %lu" | 
 | #else | 
 |                 ", \"tid\": %d" | 
 | #endif  // defined(WEBRTC_WIN) | 
 |                 "%s" | 
 |                 "}\n", | 
 |                 has_logged_event ? "," : " ", e.name, e.category_enabled, | 
 |                 e.phase, e.timestamp, e.pid, e.tid, args_str.c_str()); | 
 |         has_logged_event = true; | 
 |       } | 
 |       if (shutting_down) | 
 |         break; | 
 |     } | 
 |     fprintf(output_file_, "]}\n"); | 
 |     if (output_file_owned_) | 
 |       fclose(output_file_); | 
 |     output_file_ = nullptr; | 
 |   } | 
 |  | 
 |   void Start(FILE* file, bool owned) { | 
 |     RTC_DCHECK(thread_checker_.IsCurrent()); | 
 |     RTC_DCHECK(file); | 
 |     RTC_DCHECK(!output_file_); | 
 |     output_file_ = file; | 
 |     output_file_owned_ = owned; | 
 |     { | 
 |       webrtc::MutexLock lock(&mutex_); | 
 |       // Since the atomic fast-path for adding events to the queue can be | 
 |       // bypassed while the logging thread is shutting down there may be some | 
 |       // stale events in the queue, hence the vector needs to be cleared to not | 
 |       // log events from a previous logging session (which may be days old). | 
 |       trace_events_.clear(); | 
 |     } | 
 |     // Enable event logging (fast-path). This should be disabled since starting | 
 |     // shouldn't be done twice. | 
 |     int zero = 0; | 
 |     RTC_CHECK(g_event_logging_active.compare_exchange_strong(zero, 1)); | 
 |  | 
 |     // Finally start, everything should be set up now. | 
 |     logging_thread_ = | 
 |         PlatformThread::SpawnJoinable([this] { Log(); }, "EventTracingThread"); | 
 |     TRACE_EVENT_INSTANT0("webrtc", "EventLogger::Start", | 
 |                          TRACE_EVENT_SCOPE_GLOBAL); | 
 |   } | 
 |  | 
 |   void Stop() { | 
 |     RTC_DCHECK(thread_checker_.IsCurrent()); | 
 |     TRACE_EVENT_INSTANT0("webrtc", "EventLogger::Stop", | 
 |                          TRACE_EVENT_SCOPE_GLOBAL); | 
 |     // Try to stop. Abort if we're not currently logging. | 
 |     int one = 1; | 
 |     if (g_event_logging_active.compare_exchange_strong(one, 0)) | 
 |       return; | 
 |  | 
 |     // Wake up logging thread to finish writing. | 
 |     shutdown_event_.Set(); | 
 |     // Join the logging thread. | 
 |     logging_thread_.Finalize(); | 
 |   } | 
 |  | 
 |  private: | 
 |   struct TraceArg { | 
 |     const char* name; | 
 |     unsigned char type; | 
 |     // Copied from webrtc/rtc_base/trace_event.h TraceValueUnion. | 
 |     union TraceArgValue { | 
 |       bool as_bool; | 
 |       unsigned long long as_uint; | 
 |       long long as_int; | 
 |       double as_double; | 
 |       const void* as_pointer; | 
 |       const char* as_string; | 
 |     } value; | 
 |  | 
 |     // Assert that the size of the union is equal to the size of the as_uint | 
 |     // field since we are assigning to arbitrary types using it. | 
 |     static_assert(sizeof(TraceArgValue) == sizeof(unsigned long long), | 
 |                   "Size of TraceArg value union is not equal to the size of " | 
 |                   "the uint field of that union."); | 
 |   }; | 
 |  | 
 |   struct TraceEvent { | 
 |     const char* name; | 
 |     const unsigned char* category_enabled; | 
 |     char phase; | 
 |     std::vector<TraceArg> args; | 
 |     uint64_t timestamp; | 
 |     int pid; | 
 |     rtc::PlatformThreadId tid; | 
 |   }; | 
 |  | 
 |   static std::string TraceArgValueAsString(TraceArg arg) { | 
 |     std::string output; | 
 |  | 
 |     if (arg.type == TRACE_VALUE_TYPE_STRING || | 
 |         arg.type == TRACE_VALUE_TYPE_COPY_STRING) { | 
 |       // Space for every character to be an espaced character + two for | 
 |       // quatation marks. | 
 |       output.reserve(strlen(arg.value.as_string) * 2 + 2); | 
 |       output += '\"'; | 
 |       const char* c = arg.value.as_string; | 
 |       do { | 
 |         if (*c == '"' || *c == '\\') { | 
 |           output += '\\'; | 
 |           output += *c; | 
 |         } else { | 
 |           output += *c; | 
 |         } | 
 |       } while (*++c); | 
 |       output += '\"'; | 
 |     } else { | 
 |       output.resize(kTraceArgBufferLength); | 
 |       size_t print_length = 0; | 
 |       switch (arg.type) { | 
 |         case TRACE_VALUE_TYPE_BOOL: | 
 |           if (arg.value.as_bool) { | 
 |             strcpy(&output[0], "true"); | 
 |             print_length = 4; | 
 |           } else { | 
 |             strcpy(&output[0], "false"); | 
 |             print_length = 5; | 
 |           } | 
 |           break; | 
 |         case TRACE_VALUE_TYPE_UINT: | 
 |           print_length = snprintf(&output[0], kTraceArgBufferLength, "%llu", | 
 |                                   arg.value.as_uint); | 
 |           break; | 
 |         case TRACE_VALUE_TYPE_INT: | 
 |           print_length = snprintf(&output[0], kTraceArgBufferLength, "%lld", | 
 |                                   arg.value.as_int); | 
 |           break; | 
 |         case TRACE_VALUE_TYPE_DOUBLE: | 
 |           print_length = snprintf(&output[0], kTraceArgBufferLength, "%f", | 
 |                                   arg.value.as_double); | 
 |           break; | 
 |         case TRACE_VALUE_TYPE_POINTER: | 
 |           print_length = snprintf(&output[0], kTraceArgBufferLength, "\"%p\"", | 
 |                                   arg.value.as_pointer); | 
 |           break; | 
 |       } | 
 |       size_t output_length = print_length < kTraceArgBufferLength | 
 |                                  ? print_length | 
 |                                  : kTraceArgBufferLength - 1; | 
 |       // This will hopefully be very close to nop. On most implementations, it | 
 |       // just writes null byte and sets the length field of the string. | 
 |       output.resize(output_length); | 
 |     } | 
 |  | 
 |     return output; | 
 |   } | 
 |  | 
 |   webrtc::Mutex mutex_; | 
 |   std::vector<TraceEvent> trace_events_ RTC_GUARDED_BY(mutex_); | 
 |   rtc::PlatformThread logging_thread_; | 
 |   rtc::Event shutdown_event_; | 
 |   webrtc::SequenceChecker thread_checker_; | 
 |   FILE* output_file_ = nullptr; | 
 |   bool output_file_owned_ = false; | 
 | }; | 
 |  | 
 | static std::atomic<EventLogger*> g_event_logger(nullptr); | 
 | static const char* const kDisabledTracePrefix = TRACE_DISABLED_BY_DEFAULT(""); | 
 | const unsigned char* InternalGetCategoryEnabled(const char* name) { | 
 |   const char* prefix_ptr = &kDisabledTracePrefix[0]; | 
 |   const char* name_ptr = name; | 
 |   // Check whether name contains the default-disabled prefix. | 
 |   while (*prefix_ptr == *name_ptr && *prefix_ptr != '\0') { | 
 |     ++prefix_ptr; | 
 |     ++name_ptr; | 
 |   } | 
 |   return reinterpret_cast<const unsigned char*>(*prefix_ptr == '\0' ? "" | 
 |                                                                     : name); | 
 | } | 
 |  | 
 | const unsigned char* InternalEnableAllCategories(const char* name) { | 
 |   return reinterpret_cast<const unsigned char*>(name); | 
 | } | 
 |  | 
 | void InternalAddTraceEvent(char phase, | 
 |                            const unsigned char* category_enabled, | 
 |                            const char* name, | 
 |                            unsigned long long id, | 
 |                            int num_args, | 
 |                            const char** arg_names, | 
 |                            const unsigned char* arg_types, | 
 |                            const unsigned long long* arg_values, | 
 |                            unsigned char flags) { | 
 |   // Fast path for when event tracing is inactive. | 
 |   if (g_event_logging_active.load() == 0) | 
 |     return; | 
 |  | 
 |   g_event_logger.load()->AddTraceEvent( | 
 |       name, category_enabled, phase, num_args, arg_names, arg_types, arg_values, | 
 |       rtc::TimeMicros(), 1, rtc::CurrentThreadId()); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void SetupInternalTracer(bool enable_all_categories) { | 
 |   EventLogger* null_logger = nullptr; | 
 |   RTC_CHECK( | 
 |       g_event_logger.compare_exchange_strong(null_logger, new EventLogger())); | 
 |   webrtc::SetupEventTracer(enable_all_categories ? InternalEnableAllCategories | 
 |                                                  : InternalGetCategoryEnabled, | 
 |                            InternalAddTraceEvent); | 
 | } | 
 |  | 
 | void StartInternalCaptureToFile(FILE* file) { | 
 |   EventLogger* event_logger = g_event_logger.load(); | 
 |   if (event_logger) { | 
 |     event_logger->Start(file, false); | 
 |   } | 
 | } | 
 |  | 
 | bool StartInternalCapture(absl::string_view filename) { | 
 |   EventLogger* event_logger = g_event_logger.load(); | 
 |   if (!event_logger) | 
 |     return false; | 
 |  | 
 |   FILE* file = fopen(std::string(filename).c_str(), "w"); | 
 |   if (!file) { | 
 |     RTC_LOG(LS_ERROR) << "Failed to open trace file '" << filename | 
 |                       << "' for writing."; | 
 |     return false; | 
 |   } | 
 |   event_logger->Start(file, true); | 
 |   return true; | 
 | } | 
 |  | 
 | void StopInternalCapture() { | 
 |   EventLogger* event_logger = g_event_logger.load(); | 
 |   if (event_logger) { | 
 |     event_logger->Stop(); | 
 |   } | 
 | } | 
 |  | 
 | void ShutdownInternalTracer() { | 
 |   StopInternalCapture(); | 
 |   EventLogger* old_logger = g_event_logger.load(std::memory_order_acquire); | 
 |   RTC_DCHECK(old_logger); | 
 |   RTC_CHECK(g_event_logger.compare_exchange_strong(old_logger, nullptr)); | 
 |   delete old_logger; | 
 |   webrtc::SetupEventTracer(nullptr, nullptr); | 
 | } | 
 |  | 
 | }  // namespace tracing | 
 | }  // namespace rtc | 
 |  | 
 | #endif  // defined(RTC_USE_PERFETTO) |