1// Copyright 2016 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8 9#include "include/libplatform/v8-tracing.h" 10#include "src/base/atomicops.h" 11#include "src/base/platform/mutex.h" 12#include "src/base/platform/time.h" 13#include "src/base/platform/wrappers.h" 14 15#ifdef V8_USE_PERFETTO 16#include "perfetto/ext/trace_processor/export_json.h" 17#include "perfetto/trace_processor/trace_processor.h" 18#include "perfetto/tracing/tracing.h" 19#include "protos/perfetto/config/data_source_config.gen.h" 20#include "protos/perfetto/config/trace_config.gen.h" 21#include "protos/perfetto/config/track_event/track_event_config.gen.h" 22#include "src/base/platform/platform.h" 23#include "src/base/platform/semaphore.h" 24#include "src/libplatform/tracing/trace-event-listener.h" 25#endif // V8_USE_PERFETTO 26 27#ifdef V8_USE_PERFETTO 28class JsonOutputWriter : public perfetto::trace_processor::json::OutputWriter { 29 public: 30 explicit JsonOutputWriter(std::ostream* stream) : stream_(stream) {} 31 32 perfetto::trace_processor::util::Status AppendString( 33 const std::string& string) override { 34 *stream_ << string; 35 return perfetto::trace_processor::util::OkStatus(); 36 } 37 38 private: 39 std::ostream* stream_; 40}; 41#endif // V8_USE_PERFETTO 42 43namespace v8 { 44namespace platform { 45namespace tracing { 46 47#if !defined(V8_USE_PERFETTO) 48static const size_t kMaxCategoryGroups = 200; 49 50// Parallel arrays g_category_groups and g_category_group_enabled are separate 51// so that a pointer to a member of g_category_group_enabled can be easily 52// converted to an index into g_category_groups. This allows macros to deal 53// only with char enabled pointers from g_category_group_enabled, and we can 54// convert internally to determine the category name from the char enabled 55// pointer. 56const char* g_category_groups[kMaxCategoryGroups] = { 57 "toplevel", 58 "tracing categories exhausted; must increase kMaxCategoryGroups", 59 "__metadata"}; 60 61// The enabled flag is char instead of bool so that the API can be used from C. 62unsigned char g_category_group_enabled[kMaxCategoryGroups] = {0}; 63// Indexes here have to match the g_category_groups array indexes above. 64const int g_category_categories_exhausted = 1; 65// Metadata category not used in V8. 66// const int g_category_metadata = 2; 67const int g_num_builtin_categories = 3; 68 69// Skip default categories. 70v8::base::AtomicWord g_category_index = g_num_builtin_categories; 71#endif // !defined(V8_USE_PERFETTO) 72 73TracingController::TracingController() { mutex_.reset(new base::Mutex()); } 74 75TracingController::~TracingController() { 76 StopTracing(); 77 78#if !defined(V8_USE_PERFETTO) 79 { 80 // Free memory for category group names allocated via strdup. 81 base::MutexGuard lock(mutex_.get()); 82 for (size_t i = g_category_index - 1; i >= g_num_builtin_categories; --i) { 83 const char* group = g_category_groups[i]; 84 g_category_groups[i] = nullptr; 85 free(const_cast<char*>(group)); 86 } 87 g_category_index = g_num_builtin_categories; 88 } 89#endif // !defined(V8_USE_PERFETTO) 90} 91 92#ifdef V8_USE_PERFETTO 93void TracingController::InitializeForPerfetto(std::ostream* output_stream) { 94 output_stream_ = output_stream; 95 DCHECK_NOT_NULL(output_stream); 96 DCHECK(output_stream->good()); 97} 98 99void TracingController::SetTraceEventListenerForTesting( 100 TraceEventListener* listener) { 101 listener_for_testing_ = listener; 102} 103#else // !V8_USE_PERFETTO 104void TracingController::Initialize(TraceBuffer* trace_buffer) { 105 trace_buffer_.reset(trace_buffer); 106} 107 108int64_t TracingController::CurrentTimestampMicroseconds() { 109 return base::TimeTicks::Now().ToInternalValue(); 110} 111 112int64_t TracingController::CurrentCpuTimestampMicroseconds() { 113 return base::ThreadTicks::Now().ToInternalValue(); 114} 115 116uint64_t TracingController::AddTraceEvent( 117 char phase, const uint8_t* category_enabled_flag, const char* name, 118 const char* scope, uint64_t id, uint64_t bind_id, int num_args, 119 const char** arg_names, const uint8_t* arg_types, 120 const uint64_t* arg_values, 121 std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables, 122 unsigned int flags) { 123 int64_t now_us = CurrentTimestampMicroseconds(); 124 125 return AddTraceEventWithTimestamp( 126 phase, category_enabled_flag, name, scope, id, bind_id, num_args, 127 arg_names, arg_types, arg_values, arg_convertables, flags, now_us); 128} 129 130uint64_t TracingController::AddTraceEventWithTimestamp( 131 char phase, const uint8_t* category_enabled_flag, const char* name, 132 const char* scope, uint64_t id, uint64_t bind_id, int num_args, 133 const char** arg_names, const uint8_t* arg_types, 134 const uint64_t* arg_values, 135 std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables, 136 unsigned int flags, int64_t timestamp) { 137 int64_t cpu_now_us = CurrentCpuTimestampMicroseconds(); 138 139 uint64_t handle = 0; 140 if (recording_.load(std::memory_order_acquire)) { 141 TraceObject* trace_object = trace_buffer_->AddTraceEvent(&handle); 142 if (trace_object) { 143 { 144 base::MutexGuard lock(mutex_.get()); 145 trace_object->Initialize(phase, category_enabled_flag, name, scope, id, 146 bind_id, num_args, arg_names, arg_types, 147 arg_values, arg_convertables, flags, timestamp, 148 cpu_now_us); 149 } 150 } 151 } 152 return handle; 153} 154 155void TracingController::UpdateTraceEventDuration( 156 const uint8_t* category_enabled_flag, const char* name, uint64_t handle) { 157 int64_t now_us = CurrentTimestampMicroseconds(); 158 int64_t cpu_now_us = CurrentCpuTimestampMicroseconds(); 159 160 TraceObject* trace_object = trace_buffer_->GetEventByHandle(handle); 161 if (!trace_object) return; 162 trace_object->UpdateDuration(now_us, cpu_now_us); 163} 164 165const char* TracingController::GetCategoryGroupName( 166 const uint8_t* category_group_enabled) { 167 // Calculate the index of the category group by finding 168 // category_group_enabled in g_category_group_enabled array. 169 uintptr_t category_begin = 170 reinterpret_cast<uintptr_t>(g_category_group_enabled); 171 uintptr_t category_ptr = reinterpret_cast<uintptr_t>(category_group_enabled); 172 // Check for out of bounds category pointers. 173 DCHECK(category_ptr >= category_begin && 174 category_ptr < reinterpret_cast<uintptr_t>(g_category_group_enabled + 175 kMaxCategoryGroups)); 176 uintptr_t category_index = 177 (category_ptr - category_begin) / sizeof(g_category_group_enabled[0]); 178 return g_category_groups[category_index]; 179} 180#endif // !defined(V8_USE_PERFETTO) 181 182void TracingController::StartTracing(TraceConfig* trace_config) { 183#ifdef V8_USE_PERFETTO 184 DCHECK_NOT_NULL(output_stream_); 185 DCHECK(output_stream_->good()); 186 perfetto::trace_processor::Config processor_config; 187 trace_processor_ = 188 perfetto::trace_processor::TraceProcessorStorage::CreateInstance( 189 processor_config); 190 191 ::perfetto::TraceConfig perfetto_trace_config; 192 perfetto_trace_config.add_buffers()->set_size_kb(4096); 193 auto ds_config = perfetto_trace_config.add_data_sources()->mutable_config(); 194 ds_config->set_name("track_event"); 195 perfetto::protos::gen::TrackEventConfig te_config; 196 te_config.add_disabled_categories("*"); 197 for (const auto& category : trace_config->GetEnabledCategories()) 198 te_config.add_enabled_categories(category); 199 ds_config->set_track_event_config_raw(te_config.SerializeAsString()); 200 201 tracing_session_ = 202 perfetto::Tracing::NewTrace(perfetto::BackendType::kUnspecifiedBackend); 203 tracing_session_->Setup(perfetto_trace_config); 204 tracing_session_->StartBlocking(); 205 206#endif // V8_USE_PERFETTO 207 208 trace_config_.reset(trace_config); 209 std::unordered_set<v8::TracingController::TraceStateObserver*> observers_copy; 210 { 211 base::MutexGuard lock(mutex_.get()); 212 recording_.store(true, std::memory_order_release); 213#ifndef V8_USE_PERFETTO 214 UpdateCategoryGroupEnabledFlags(); 215#endif 216 observers_copy = observers_; 217 } 218 for (auto o : observers_copy) { 219 o->OnTraceEnabled(); 220 } 221} 222 223void TracingController::StopTracing() { 224 bool expected = true; 225 if (!recording_.compare_exchange_strong(expected, false)) { 226 return; 227 } 228#ifndef V8_USE_PERFETTO 229 UpdateCategoryGroupEnabledFlags(); 230#endif 231 std::unordered_set<v8::TracingController::TraceStateObserver*> observers_copy; 232 { 233 base::MutexGuard lock(mutex_.get()); 234 observers_copy = observers_; 235 } 236 for (auto o : observers_copy) { 237 o->OnTraceDisabled(); 238 } 239 240#ifdef V8_USE_PERFETTO 241 tracing_session_->StopBlocking(); 242 243 std::vector<char> trace = tracing_session_->ReadTraceBlocking(); 244 std::unique_ptr<uint8_t[]> trace_bytes(new uint8_t[trace.size()]); 245 std::copy(&trace[0], &trace[0] + trace.size(), &trace_bytes[0]); 246 trace_processor_->Parse(std::move(trace_bytes), trace.size()); 247 trace_processor_->NotifyEndOfFile(); 248 JsonOutputWriter output_writer(output_stream_); 249 auto status = perfetto::trace_processor::json::ExportJson( 250 trace_processor_.get(), &output_writer, nullptr, nullptr, nullptr); 251 DCHECK(status.ok()); 252 253 if (listener_for_testing_) listener_for_testing_->ParseFromArray(trace); 254 255 trace_processor_.reset(); 256#else 257 258 { 259 base::MutexGuard lock(mutex_.get()); 260 DCHECK(trace_buffer_); 261 trace_buffer_->Flush(); 262 } 263#endif // V8_USE_PERFETTO 264} 265 266#if !defined(V8_USE_PERFETTO) 267void TracingController::UpdateCategoryGroupEnabledFlag(size_t category_index) { 268 unsigned char enabled_flag = 0; 269 const char* category_group = g_category_groups[category_index]; 270 if (recording_.load(std::memory_order_acquire) && 271 trace_config_->IsCategoryGroupEnabled(category_group)) { 272 enabled_flag |= ENABLED_FOR_RECORDING; 273 } 274 275 // TODO(fmeawad): EventCallback and ETW modes are not yet supported in V8. 276 // TODO(primiano): this is a temporary workaround for catapult:#2341, 277 // to guarantee that metadata events are always added even if the category 278 // filter is "-*". See crbug.com/618054 for more details and long-term fix. 279 if (recording_.load(std::memory_order_acquire) && 280 !strcmp(category_group, "__metadata")) { 281 enabled_flag |= ENABLED_FOR_RECORDING; 282 } 283 284 base::Relaxed_Store(reinterpret_cast<base::Atomic8*>( 285 g_category_group_enabled + category_index), 286 enabled_flag); 287} 288 289void TracingController::UpdateCategoryGroupEnabledFlags() { 290 size_t category_index = base::Acquire_Load(&g_category_index); 291 for (size_t i = 0; i < category_index; i++) UpdateCategoryGroupEnabledFlag(i); 292} 293 294const uint8_t* TracingController::GetCategoryGroupEnabled( 295 const char* category_group) { 296 // Check that category group does not contain double quote 297 DCHECK(!strchr(category_group, '"')); 298 299 // The g_category_groups is append only, avoid using a lock for the fast path. 300 size_t category_index = base::Acquire_Load(&g_category_index); 301 302 // Search for pre-existing category group. 303 for (size_t i = 0; i < category_index; ++i) { 304 if (strcmp(g_category_groups[i], category_group) == 0) { 305 return &g_category_group_enabled[i]; 306 } 307 } 308 309 // Slow path. Grab the lock. 310 base::MutexGuard lock(mutex_.get()); 311 312 // Check the list again with lock in hand. 313 unsigned char* category_group_enabled = nullptr; 314 category_index = base::Acquire_Load(&g_category_index); 315 for (size_t i = 0; i < category_index; ++i) { 316 if (strcmp(g_category_groups[i], category_group) == 0) { 317 return &g_category_group_enabled[i]; 318 } 319 } 320 321 // Create a new category group. 322 // Check that there is a slot for the new category_group. 323 DCHECK(category_index < kMaxCategoryGroups); 324 if (category_index < kMaxCategoryGroups) { 325 // Don't hold on to the category_group pointer, so that we can create 326 // category groups with strings not known at compile time (this is 327 // required by SetWatchEvent). 328 const char* new_group = base::Strdup(category_group); 329 g_category_groups[category_index] = new_group; 330 DCHECK(!g_category_group_enabled[category_index]); 331 // Note that if both included and excluded patterns in the 332 // TraceConfig are empty, we exclude nothing, 333 // thereby enabling this category group. 334 UpdateCategoryGroupEnabledFlag(category_index); 335 category_group_enabled = &g_category_group_enabled[category_index]; 336 // Update the max index now. 337 base::Release_Store(&g_category_index, category_index + 1); 338 } else { 339 category_group_enabled = 340 &g_category_group_enabled[g_category_categories_exhausted]; 341 } 342 return category_group_enabled; 343} 344#endif // !defined(V8_USE_PERFETTO) 345 346void TracingController::AddTraceStateObserver( 347 v8::TracingController::TraceStateObserver* observer) { 348 { 349 base::MutexGuard lock(mutex_.get()); 350 observers_.insert(observer); 351 if (!recording_.load(std::memory_order_acquire)) return; 352 } 353 // Fire the observer if recording is already in progress. 354 observer->OnTraceEnabled(); 355} 356 357void TracingController::RemoveTraceStateObserver( 358 v8::TracingController::TraceStateObserver* observer) { 359 base::MutexGuard lock(mutex_.get()); 360 DCHECK(observers_.find(observer) != observers_.end()); 361 observers_.erase(observer); 362} 363 364} // namespace tracing 365} // namespace platform 366} // namespace v8 367