Coverage Report

Created: 2026-01-21 08:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/src/tracing/agent.cc
Line
Count
Source
1
#include "tracing/agent.h"
2
3
#include <string>
4
#include "trace_event.h"
5
#include "tracing/node_trace_buffer.h"
6
#include "debug_utils-inl.h"
7
#include "env-inl.h"
8
9
namespace node {
10
namespace tracing {
11
12
class Agent::ScopedSuspendTracing {
13
 public:
14
  ScopedSuspendTracing(TracingController* controller, Agent* agent,
15
                       bool do_suspend = true)
16
0
    : controller_(controller), agent_(do_suspend ? agent : nullptr) {
17
0
    if (do_suspend) {
18
0
      CHECK(agent_->started_);
19
0
      controller->StopTracing();
20
0
    }
21
0
  }
22
23
0
  ~ScopedSuspendTracing() {
24
0
    if (agent_ == nullptr) return;
25
0
    TraceConfig* config = agent_->CreateTraceConfig();
26
0
    if (config != nullptr) {
27
0
      controller_->StartTracing(config);
28
0
    }
29
0
  }
30
31
 private:
32
  TracingController* controller_;
33
  Agent* agent_;
34
};
35
36
namespace {
37
38
std::set<std::string> flatten(
39
0
    const std::unordered_map<int, std::multiset<std::string>>& map) {
40
0
  std::set<std::string> result;
41
0
  for (const auto& id_value : map)
42
0
    result.insert(id_value.second.begin(), id_value.second.end());
43
0
  return result;
44
0
}
45
46
}  // namespace
47
48
using v8::platform::tracing::TraceConfig;
49
using v8::platform::tracing::TraceWriter;
50
using std::string;
51
52
0
Agent::Agent() : tracing_controller_(new TracingController()) {
53
0
  tracing_controller_->Initialize(nullptr);
54
55
0
  CHECK_EQ(uv_loop_init(&tracing_loop_), 0);
56
0
  CHECK_EQ(uv_async_init(&tracing_loop_,
57
0
                         &initialize_writer_async_,
58
0
                         [](uv_async_t* async) {
59
0
    Agent* agent = ContainerOf(&Agent::initialize_writer_async_, async);
60
0
    agent->InitializeWritersOnThread();
61
0
  }), 0);
62
0
  uv_unref(reinterpret_cast<uv_handle_t*>(&initialize_writer_async_));
63
0
}
64
65
0
void Agent::InitializeWritersOnThread() {
66
0
  Mutex::ScopedLock lock(initialize_writer_mutex_);
67
0
  while (!to_be_initialized_.empty()) {
68
0
    AsyncTraceWriter* head = *to_be_initialized_.begin();
69
0
    head->InitializeOnThread(&tracing_loop_);
70
0
    to_be_initialized_.erase(head);
71
0
  }
72
0
  initialize_writer_condvar_.Broadcast(lock);
73
0
}
74
75
0
Agent::~Agent() {
76
0
  categories_.clear();
77
0
  writers_.clear();
78
79
0
  StopTracing();
80
81
0
  uv_close(reinterpret_cast<uv_handle_t*>(&initialize_writer_async_), nullptr);
82
0
  uv_run(&tracing_loop_, UV_RUN_ONCE);
83
0
  CheckedUvLoopClose(&tracing_loop_);
84
0
}
85
86
0
void Agent::Start() {
87
0
  if (started_)
88
0
    return;
89
90
0
  NodeTraceBuffer* trace_buffer_ = new NodeTraceBuffer(
91
0
      NodeTraceBuffer::kBufferChunks, this, &tracing_loop_);
92
0
  tracing_controller_->Initialize(trace_buffer_);
93
94
  // This thread should be created *after* async handles are created
95
  // (within NodeTraceWriter and NodeTraceBuffer constructors).
96
  // Otherwise the thread could shut down prematurely.
97
0
  CHECK_EQ(0,
98
0
           uv_thread_create(
99
0
               &thread_,
100
0
               [](void* arg) {
101
0
                 uv_thread_setname("TraceEventWorker");
102
0
                 Agent* agent = static_cast<Agent*>(arg);
103
0
                 uv_run(&agent->tracing_loop_, UV_RUN_DEFAULT);
104
0
               },
105
0
               this));
106
0
  started_ = true;
107
0
}
108
109
AgentWriterHandle Agent::AddClient(
110
    const std::set<std::string>& categories,
111
    std::unique_ptr<AsyncTraceWriter> writer,
112
0
    enum UseDefaultCategoryMode mode) {
113
0
  Start();
114
115
0
  const std::set<std::string>* use_categories = &categories;
116
117
0
  std::set<std::string> categories_with_default;
118
0
  if (mode == kUseDefaultCategories) {
119
0
    categories_with_default.insert(categories.begin(), categories.end());
120
0
    categories_with_default.insert(categories_[kDefaultHandleId].begin(),
121
0
                                   categories_[kDefaultHandleId].end());
122
0
    use_categories = &categories_with_default;
123
0
  }
124
125
0
  ScopedSuspendTracing suspend(tracing_controller_.get(), this);
126
0
  int id = next_writer_id_++;
127
0
  AsyncTraceWriter* raw = writer.get();
128
0
  writers_[id] = std::move(writer);
129
0
  categories_[id] = { use_categories->begin(), use_categories->end() };
130
131
0
  {
132
0
    Mutex::ScopedLock lock(initialize_writer_mutex_);
133
0
    to_be_initialized_.insert(raw);
134
0
    uv_async_send(&initialize_writer_async_);
135
0
    while (to_be_initialized_.count(raw) > 0)
136
0
      initialize_writer_condvar_.Wait(lock);
137
0
  }
138
139
0
  return AgentWriterHandle(this, id);
140
0
}
141
142
0
AgentWriterHandle Agent::DefaultHandle() {
143
0
  return AgentWriterHandle(this, kDefaultHandleId);
144
0
}
145
146
0
void Agent::StopTracing() {
147
0
  if (!started_)
148
0
    return;
149
  // Perform final Flush on TraceBuffer. We don't want the tracing controller
150
  // to flush the buffer again on destruction of the V8::Platform.
151
0
  tracing_controller_->StopTracing();
152
0
  tracing_controller_->Initialize(nullptr);
153
0
  started_ = false;
154
155
  // Thread should finish when the tracing loop is stopped.
156
0
  uv_thread_join(&thread_);
157
0
}
158
159
0
void Agent::Disconnect(int client) {
160
0
  if (client == kDefaultHandleId) return;
161
0
  {
162
0
    Mutex::ScopedLock lock(initialize_writer_mutex_);
163
0
    to_be_initialized_.erase(writers_[client].get());
164
0
  }
165
0
  ScopedSuspendTracing suspend(tracing_controller_.get(), this);
166
0
  writers_.erase(client);
167
0
  categories_.erase(client);
168
0
}
169
170
0
void Agent::Enable(int id, const std::set<std::string>& categories) {
171
0
  if (categories.empty())
172
0
    return;
173
174
0
  ScopedSuspendTracing suspend(tracing_controller_.get(), this,
175
0
                               id != kDefaultHandleId);
176
0
  categories_[id].insert(categories.begin(), categories.end());
177
0
}
178
179
0
void Agent::Disable(int id, const std::set<std::string>& categories) {
180
0
  ScopedSuspendTracing suspend(tracing_controller_.get(), this,
181
0
                               id != kDefaultHandleId);
182
0
  std::multiset<std::string>& writer_categories = categories_[id];
183
0
  for (const std::string& category : categories) {
184
0
    auto it = writer_categories.find(category);
185
0
    if (it != writer_categories.end())
186
0
      writer_categories.erase(it);
187
0
  }
188
0
}
189
190
0
TraceConfig* Agent::CreateTraceConfig() const {
191
0
  if (categories_.empty())
192
0
    return nullptr;
193
0
  TraceConfig* trace_config = new TraceConfig();
194
0
  for (const auto& category : flatten(categories_)) {
195
0
    trace_config->AddIncludedCategory(category.c_str());
196
0
  }
197
0
  return trace_config;
198
0
}
199
200
0
std::string Agent::GetEnabledCategories() const {
201
0
  std::string categories;
202
0
  for (const std::string& category : flatten(categories_)) {
203
0
    if (!categories.empty())
204
0
      categories += ',';
205
0
    categories += category;
206
0
  }
207
0
  return categories;
208
0
}
209
210
0
void Agent::AppendTraceEvent(TraceObject* trace_event) {
211
0
  for (const auto& id_writer : writers_)
212
0
    id_writer.second->AppendTraceEvent(trace_event);
213
0
}
214
215
0
void Agent::AddMetadataEvent(std::unique_ptr<TraceObject> event) {
216
0
  Mutex::ScopedLock lock(metadata_events_mutex_);
217
0
  metadata_events_.push_back(std::move(event));
218
0
}
219
220
0
void Agent::Flush(bool blocking) {
221
0
  {
222
0
    Mutex::ScopedLock lock(metadata_events_mutex_);
223
0
    for (const auto& event : metadata_events_)
224
0
      AppendTraceEvent(event.get());
225
0
  }
226
227
0
  for (const auto& id_writer : writers_)
228
0
    id_writer.second->Flush(blocking);
229
0
}
230
231
void TracingController::AddMetadataEvent(
232
    const unsigned char* category_group_enabled,
233
    const char* name,
234
    int num_args,
235
    const char** arg_names,
236
    const unsigned char* arg_types,
237
    const uint64_t* arg_values,
238
    std::unique_ptr<v8::ConvertableToTraceFormat>* convertable_values,
239
0
    unsigned int flags) {
240
0
  std::unique_ptr<TraceObject> trace_event(new TraceObject);
241
0
  trace_event->Initialize(
242
0
      TRACE_EVENT_PHASE_METADATA, category_group_enabled, name,
243
0
      node::tracing::kGlobalScope,  // scope
244
0
      node::tracing::kNoId,         // id
245
0
      node::tracing::kNoId,         // bind_id
246
0
      num_args, arg_names, arg_types, arg_values, convertable_values,
247
0
      TRACE_EVENT_FLAG_NONE,
248
0
      CurrentTimestampMicroseconds(),
249
0
      CurrentCpuTimestampMicroseconds());
250
0
  Agent* node_agent = node::tracing::TraceEventHelper::GetAgent();
251
0
  if (node_agent != nullptr)
252
0
    node_agent->AddMetadataEvent(std::move(trace_event));
253
0
}
254
255
}  // namespace tracing
256
}  // namespace node