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