| // Copyright 2016 the V8 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. |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "include/libplatform/v8-tracing.h" |
| |
| #include "src/base/atomicops.h" |
| #include "src/base/platform/mutex.h" |
| #include "src/base/platform/time.h" |
| |
| #if V8_OS_STARBOARD |
| #include "src/poems.h" |
| #endif |
| |
| #ifdef V8_USE_PERFETTO |
| #include "base/trace_event/common/trace_event_common.h" |
| #include "perfetto/trace/chrome/chrome_trace_event.pbzero.h" |
| #include "perfetto/trace/trace_packet.pbzero.h" |
| #include "perfetto/tracing.h" |
| #include "src/base/platform/platform.h" |
| #include "src/base/platform/semaphore.h" |
| #include "src/libplatform/tracing/json-trace-event-listener.h" |
| #endif // V8_USE_PERFETTO |
| |
| #ifdef V8_USE_PERFETTO |
| class V8DataSource : public perfetto::DataSource<V8DataSource> { |
| public: |
| void OnSetup(const SetupArgs&) override {} |
| void OnStart(const StartArgs&) override { started_.Signal(); } |
| void OnStop(const StopArgs&) override {} |
| |
| static v8::base::Semaphore started_; |
| }; |
| |
| v8::base::Semaphore V8DataSource::started_{0}; |
| |
| PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(V8DataSource); |
| #endif // V8_USE_PERFETTO |
| |
| namespace v8 { |
| namespace platform { |
| namespace tracing { |
| |
| static const size_t kMaxCategoryGroups = 200; |
| |
| // Parallel arrays g_category_groups and g_category_group_enabled are separate |
| // so that a pointer to a member of g_category_group_enabled can be easily |
| // converted to an index into g_category_groups. This allows macros to deal |
| // only with char enabled pointers from g_category_group_enabled, and we can |
| // convert internally to determine the category name from the char enabled |
| // pointer. |
| const char* g_category_groups[kMaxCategoryGroups] = { |
| "toplevel", |
| "tracing categories exhausted; must increase kMaxCategoryGroups", |
| "__metadata"}; |
| |
| // The enabled flag is char instead of bool so that the API can be used from C. |
| unsigned char g_category_group_enabled[kMaxCategoryGroups] = {0}; |
| // Indexes here have to match the g_category_groups array indexes above. |
| const int g_category_categories_exhausted = 1; |
| // Metadata category not used in V8. |
| // const int g_category_metadata = 2; |
| const int g_num_builtin_categories = 3; |
| |
| // Skip default categories. |
| v8::base::AtomicWord g_category_index = g_num_builtin_categories; |
| |
| TracingController::TracingController() = default; |
| |
| TracingController::~TracingController() { |
| StopTracing(); |
| |
| { |
| // Free memory for category group names allocated via strdup. |
| base::MutexGuard lock(mutex_.get()); |
| for (size_t i = g_category_index - 1; i >= g_num_builtin_categories; --i) { |
| const char* group = g_category_groups[i]; |
| g_category_groups[i] = nullptr; |
| free(const_cast<char*>(group)); |
| } |
| g_category_index = g_num_builtin_categories; |
| } |
| } |
| |
| void TracingController::Initialize(TraceBuffer* trace_buffer) { |
| trace_buffer_.reset(trace_buffer); |
| mutex_.reset(new base::Mutex()); |
| } |
| |
| #ifdef V8_USE_PERFETTO |
| void TracingController::InitializeForPerfetto(std::ostream* output_stream) { |
| output_stream_ = output_stream; |
| DCHECK_NOT_NULL(output_stream); |
| DCHECK(output_stream->good()); |
| mutex_.reset(new base::Mutex()); |
| } |
| |
| void TracingController::SetTraceEventListenerForTesting( |
| TraceEventListener* listener) { |
| listener_for_testing_ = listener; |
| } |
| #endif |
| |
| int64_t TracingController::CurrentTimestampMicroseconds() { |
| return base::TimeTicks::HighResolutionNow().ToInternalValue(); |
| } |
| |
| int64_t TracingController::CurrentCpuTimestampMicroseconds() { |
| return base::ThreadTicks::Now().ToInternalValue(); |
| } |
| |
| namespace { |
| |
| #ifdef V8_USE_PERFETTO |
| void AddArgsToTraceProto( |
| ::perfetto::protos::pbzero::ChromeTraceEvent* event, int num_args, |
| const char** arg_names, const uint8_t* arg_types, |
| const uint64_t* arg_values, |
| std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables) { |
| for (int i = 0; i < num_args; i++) { |
| ::perfetto::protos::pbzero::ChromeTraceEvent_Arg* arg = event->add_args(); |
| // TODO(petermarshall): Set name_index instead if need be. |
| arg->set_name(arg_names[i]); |
| |
| TraceObject::ArgValue arg_value; |
| arg_value.as_uint = arg_values[i]; |
| switch (arg_types[i]) { |
| case TRACE_VALUE_TYPE_CONVERTABLE: { |
| // TODO(petermarshall): Support AppendToProto for Convertables. |
| std::string json_value; |
| arg_convertables[i]->AppendAsTraceFormat(&json_value); |
| arg->set_json_value(json_value.c_str()); |
| break; |
| } |
| case TRACE_VALUE_TYPE_BOOL: |
| arg->set_bool_value(arg_value.as_bool); |
| break; |
| case TRACE_VALUE_TYPE_UINT: |
| arg->set_uint_value(arg_value.as_uint); |
| break; |
| case TRACE_VALUE_TYPE_INT: |
| arg->set_int_value(arg_value.as_int); |
| break; |
| case TRACE_VALUE_TYPE_DOUBLE: |
| arg->set_double_value(arg_value.as_double); |
| break; |
| case TRACE_VALUE_TYPE_POINTER: |
| arg->set_pointer_value(arg_value.as_uint); |
| break; |
| // There is no difference between copy strings and regular strings for |
| // Perfetto; the set_string_value(const char*) API will copy the string |
| // into the protobuf by default. |
| case TRACE_VALUE_TYPE_COPY_STRING: |
| case TRACE_VALUE_TYPE_STRING: |
| arg->set_string_value(arg_value.as_string); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| } |
| #endif // V8_USE_PERFETTO |
| |
| } // namespace |
| |
| uint64_t TracingController::AddTraceEvent( |
| char phase, const uint8_t* category_enabled_flag, const char* name, |
| const char* scope, uint64_t id, uint64_t bind_id, int num_args, |
| const char** arg_names, const uint8_t* arg_types, |
| const uint64_t* arg_values, |
| std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables, |
| unsigned int flags) { |
| int64_t now_us = CurrentTimestampMicroseconds(); |
| |
| return AddTraceEventWithTimestamp( |
| phase, category_enabled_flag, name, scope, id, bind_id, num_args, |
| arg_names, arg_types, arg_values, arg_convertables, flags, now_us); |
| } |
| |
| uint64_t TracingController::AddTraceEventWithTimestamp( |
| char phase, const uint8_t* category_enabled_flag, const char* name, |
| const char* scope, uint64_t id, uint64_t bind_id, int num_args, |
| const char** arg_names, const uint8_t* arg_types, |
| const uint64_t* arg_values, |
| std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables, |
| unsigned int flags, int64_t timestamp) { |
| int64_t cpu_now_us = CurrentCpuTimestampMicroseconds(); |
| |
| #ifdef V8_USE_PERFETTO |
| // Don't use COMPLETE events with perfetto - instead transform them into |
| // BEGIN/END pairs. This avoids the need for a thread-local stack of pending |
| // trace events as perfetto does not support handles into the trace buffer. |
| if (phase == TRACE_EVENT_PHASE_COMPLETE) phase = TRACE_EVENT_PHASE_BEGIN; |
| |
| V8DataSource::Trace([&](V8DataSource::TraceContext ctx) { |
| auto packet = ctx.NewTracePacket(); |
| auto* trace_event_bundle = packet->set_chrome_events(); |
| auto* trace_event = trace_event_bundle->add_trace_events(); |
| |
| trace_event->set_name(name); |
| trace_event->set_timestamp(timestamp); |
| trace_event->set_phase(phase); |
| trace_event->set_thread_id(base::OS::GetCurrentThreadId()); |
| trace_event->set_duration(0); |
| trace_event->set_thread_duration(0); |
| if (scope) trace_event->set_scope(scope); |
| trace_event->set_id(id); |
| trace_event->set_flags(flags); |
| if (category_enabled_flag) { |
| const char* category_group_name = |
| GetCategoryGroupName(category_enabled_flag); |
| DCHECK_NOT_NULL(category_group_name); |
| trace_event->set_category_group_name(category_group_name); |
| } |
| trace_event->set_process_id(base::OS::GetCurrentProcessId()); |
| trace_event->set_thread_timestamp(cpu_now_us); |
| trace_event->set_bind_id(bind_id); |
| |
| AddArgsToTraceProto(trace_event, num_args, arg_names, arg_types, |
| arg_values, arg_convertables); |
| }); |
| return 0; |
| #else |
| |
| uint64_t handle = 0; |
| if (recording_.load(std::memory_order_acquire)) { |
| TraceObject* trace_object = trace_buffer_->AddTraceEvent(&handle); |
| if (trace_object) { |
| { |
| base::MutexGuard lock(mutex_.get()); |
| trace_object->Initialize(phase, category_enabled_flag, name, scope, id, |
| bind_id, num_args, arg_names, arg_types, |
| arg_values, arg_convertables, flags, timestamp, |
| cpu_now_us); |
| } |
| } |
| } |
| return handle; |
| #endif // V8_USE_PERFETTO |
| } |
| |
| void TracingController::UpdateTraceEventDuration( |
| const uint8_t* category_enabled_flag, const char* name, uint64_t handle) { |
| int64_t now_us = CurrentTimestampMicroseconds(); |
| int64_t cpu_now_us = CurrentCpuTimestampMicroseconds(); |
| |
| #ifdef V8_USE_PERFETTO |
| V8DataSource::Trace([&](V8DataSource::TraceContext ctx) { |
| auto packet = ctx.NewTracePacket(); |
| auto* trace_event_bundle = packet->set_chrome_events(); |
| auto* trace_event = trace_event_bundle->add_trace_events(); |
| |
| trace_event->set_phase(TRACE_EVENT_PHASE_END); |
| trace_event->set_thread_id(base::OS::GetCurrentThreadId()); |
| trace_event->set_timestamp(now_us); |
| trace_event->set_process_id(base::OS::GetCurrentProcessId()); |
| trace_event->set_thread_timestamp(cpu_now_us); |
| }); |
| #else |
| |
| TraceObject* trace_object = trace_buffer_->GetEventByHandle(handle); |
| if (!trace_object) return; |
| trace_object->UpdateDuration(now_us, cpu_now_us); |
| #endif // V8_USE_PERFETTO |
| } |
| |
| const char* TracingController::GetCategoryGroupName( |
| const uint8_t* category_group_enabled) { |
| // Calculate the index of the category group by finding |
| // category_group_enabled in g_category_group_enabled array. |
| uintptr_t category_begin = |
| reinterpret_cast<uintptr_t>(g_category_group_enabled); |
| uintptr_t category_ptr = reinterpret_cast<uintptr_t>(category_group_enabled); |
| // Check for out of bounds category pointers. |
| DCHECK(category_ptr >= category_begin && |
| category_ptr < reinterpret_cast<uintptr_t>(g_category_group_enabled + |
| kMaxCategoryGroups)); |
| uintptr_t category_index = |
| (category_ptr - category_begin) / sizeof(g_category_group_enabled[0]); |
| return g_category_groups[category_index]; |
| } |
| |
| void TracingController::StartTracing(TraceConfig* trace_config) { |
| #ifdef V8_USE_PERFETTO |
| DCHECK_NOT_NULL(output_stream_); |
| DCHECK(output_stream_->good()); |
| json_listener_ = base::make_unique<JSONTraceEventListener>(output_stream_); |
| |
| // TODO(petermarshall): Set other the params for the config. |
| ::perfetto::TraceConfig perfetto_trace_config; |
| perfetto_trace_config.add_buffers()->set_size_kb(4096); |
| auto* ds_config = perfetto_trace_config.add_data_sources()->mutable_config(); |
| ds_config->set_name("v8.trace_events"); |
| |
| perfetto::DataSourceDescriptor dsd; |
| dsd.set_name("v8.trace_events"); |
| V8DataSource::Register(dsd); |
| |
| tracing_session_ = |
| perfetto::Tracing::NewTrace(perfetto::BackendType::kUnspecifiedBackend); |
| tracing_session_->Setup(perfetto_trace_config); |
| // TODO(petermarshall): Switch to StartBlocking when available. |
| tracing_session_->Start(); |
| V8DataSource::started_.Wait(); |
| |
| #endif // V8_USE_PERFETTO |
| |
| trace_config_.reset(trace_config); |
| std::unordered_set<v8::TracingController::TraceStateObserver*> observers_copy; |
| { |
| base::MutexGuard lock(mutex_.get()); |
| recording_.store(true, std::memory_order_release); |
| UpdateCategoryGroupEnabledFlags(); |
| observers_copy = observers_; |
| } |
| for (auto o : observers_copy) { |
| o->OnTraceEnabled(); |
| } |
| } |
| |
| void TracingController::StopTracing() { |
| bool expected = true; |
| if (!recording_.compare_exchange_strong(expected, false)) { |
| return; |
| } |
| UpdateCategoryGroupEnabledFlags(); |
| std::unordered_set<v8::TracingController::TraceStateObserver*> observers_copy; |
| { |
| base::MutexGuard lock(mutex_.get()); |
| observers_copy = observers_; |
| } |
| for (auto o : observers_copy) { |
| o->OnTraceDisabled(); |
| } |
| |
| #ifdef V8_USE_PERFETTO |
| base::Semaphore stopped_{0}; |
| tracing_session_->SetOnStopCallback([&stopped_]() { stopped_.Signal(); }); |
| tracing_session_->Stop(); |
| stopped_.Wait(); |
| |
| std::vector<char> trace = tracing_session_->ReadTraceBlocking(); |
| json_listener_->ParseFromArray(trace); |
| if (listener_for_testing_) listener_for_testing_->ParseFromArray(trace); |
| |
| json_listener_.reset(); |
| #else |
| |
| { |
| base::MutexGuard lock(mutex_.get()); |
| DCHECK(trace_buffer_); |
| trace_buffer_->Flush(); |
| } |
| #endif // V8_USE_PERFETTO |
| } |
| |
| void TracingController::UpdateCategoryGroupEnabledFlag(size_t category_index) { |
| unsigned char enabled_flag = 0; |
| const char* category_group = g_category_groups[category_index]; |
| if (recording_.load(std::memory_order_acquire) && |
| trace_config_->IsCategoryGroupEnabled(category_group)) { |
| enabled_flag |= ENABLED_FOR_RECORDING; |
| } |
| |
| // TODO(fmeawad): EventCallback and ETW modes are not yet supported in V8. |
| // TODO(primiano): this is a temporary workaround for catapult:#2341, |
| // to guarantee that metadata events are always added even if the category |
| // filter is "-*". See crbug.com/618054 for more details and long-term fix. |
| if (recording_.load(std::memory_order_acquire) && |
| !strcmp(category_group, "__metadata")) { |
| enabled_flag |= ENABLED_FOR_RECORDING; |
| } |
| |
| base::Relaxed_Store(reinterpret_cast<base::Atomic8*>( |
| g_category_group_enabled + category_index), |
| enabled_flag); |
| } |
| |
| void TracingController::UpdateCategoryGroupEnabledFlags() { |
| size_t category_index = base::Acquire_Load(&g_category_index); |
| for (size_t i = 0; i < category_index; i++) UpdateCategoryGroupEnabledFlag(i); |
| } |
| |
| const uint8_t* TracingController::GetCategoryGroupEnabled( |
| const char* category_group) { |
| // Check that category group does not contain double quote |
| DCHECK(!strchr(category_group, '"')); |
| |
| // The g_category_groups is append only, avoid using a lock for the fast path. |
| size_t category_index = base::Acquire_Load(&g_category_index); |
| |
| // Search for pre-existing category group. |
| for (size_t i = 0; i < category_index; ++i) { |
| if (strcmp(g_category_groups[i], category_group) == 0) { |
| return &g_category_group_enabled[i]; |
| } |
| } |
| |
| // Slow path. Grab the lock. |
| base::MutexGuard lock(mutex_.get()); |
| |
| // Check the list again with lock in hand. |
| unsigned char* category_group_enabled = nullptr; |
| category_index = base::Acquire_Load(&g_category_index); |
| for (size_t i = 0; i < category_index; ++i) { |
| if (strcmp(g_category_groups[i], category_group) == 0) { |
| return &g_category_group_enabled[i]; |
| } |
| } |
| |
| // Create a new category group. |
| // Check that there is a slot for the new category_group. |
| DCHECK(category_index < kMaxCategoryGroups); |
| if (category_index < kMaxCategoryGroups) { |
| // Don't hold on to the category_group pointer, so that we can create |
| // category groups with strings not known at compile time (this is |
| // required by SetWatchEvent). |
| const char* new_group = strdup(category_group); |
| g_category_groups[category_index] = new_group; |
| DCHECK(!g_category_group_enabled[category_index]); |
| // Note that if both included and excluded patterns in the |
| // TraceConfig are empty, we exclude nothing, |
| // thereby enabling this category group. |
| UpdateCategoryGroupEnabledFlag(category_index); |
| category_group_enabled = &g_category_group_enabled[category_index]; |
| // Update the max index now. |
| base::Release_Store(&g_category_index, category_index + 1); |
| } else { |
| category_group_enabled = |
| &g_category_group_enabled[g_category_categories_exhausted]; |
| } |
| return category_group_enabled; |
| } |
| |
| void TracingController::AddTraceStateObserver( |
| v8::TracingController::TraceStateObserver* observer) { |
| { |
| base::MutexGuard lock(mutex_.get()); |
| observers_.insert(observer); |
| if (!recording_.load(std::memory_order_acquire)) return; |
| } |
| // Fire the observer if recording is already in progress. |
| observer->OnTraceEnabled(); |
| } |
| |
| void TracingController::RemoveTraceStateObserver( |
| v8::TracingController::TraceStateObserver* observer) { |
| base::MutexGuard lock(mutex_.get()); |
| DCHECK(observers_.find(observer) != observers_.end()); |
| observers_.erase(observer); |
| } |
| |
| } // namespace tracing |
| } // namespace platform |
| } // namespace v8 |