#include "tracing/agent_legacy.h"
#include <ranges>
#include <string>
#include <string_view>
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node_metadata.h"
#include "node_options.h"
#include "trace_event.h"
#include "tracing/node_trace_buffer.h"
#include "tracing/node_trace_writer.h"
#include "tracing/traced_value.h"
namespace node {
namespace tracing {
class LegacyTracingAgent::ScopedSuspendTracing {
public:
ScopedSuspendTracing(TracingController* controller,
LegacyTracingAgent* agent,
bool do_suspend = true)
: controller_(controller), agent_(do_suspend ? agent : nullptr) {
if (do_suspend) {
CHECK(agent_->started_);
controller->StopTracing();
}
}
~ScopedSuspendTracing() {
if (agent_ == nullptr) return;
TraceConfig* config = agent_->CreateTraceConfig();
if (config != nullptr) {
controller_->StartTracing(config);
}
}
private:
TracingController* controller_;
LegacyTracingAgent* agent_;
};
namespace {
std::set<std::string> flatten(
const std::unordered_map<int, std::multiset<std::string>>& map) {
std::set<std::string> result;
for (const auto& id_value : map)
result.insert(id_value.second.begin(), id_value.second.end());
return result;
}
} // namespace
using std::string;
using v8::platform::tracing::TraceConfig;
using v8::platform::tracing::TraceWriter;
void NodeTraceStateObserver::OnTraceEnabled() {
std::string title = GetProcessTitle("");
if (!title.empty()) {
TRACE_EVENT_METADATA1(
"__metadata", "process_name", "name", TRACE_STR_COPY(title.c_str()));
}
TRACE_EVENT_METADATA1("__metadata",
"version",
"node",
per_process::metadata.versions.node.c_str());
TRACE_EVENT_METADATA1(
"__metadata", "thread_name", "name", "JavaScriptMainThread");
ProcessMeta trace_process;
TRACE_EVENT_METADATA1(
"__metadata", "node", "process", CastTracedValue(trace_process));
// This only runs the first time tracing is enabled
controller_->RemoveTraceStateObserver(this);
}
LegacyTracingAgent::LegacyTracingAgent()
: tracing_controller_(new TracingController()) {
tracing_controller_->Initialize(nullptr);
trace_state_observer_ =
std::make_unique<NodeTraceStateObserver>(tracing_controller_.get());
tracing_controller_->AddTraceStateObserver(trace_state_observer_.get());
tracing_file_writer_ = AgentWriterHandle(this, kDefaultHandleId);
CHECK_EQ(uv_loop_init(&tracing_loop_), 0);
CHECK_EQ(uv_async_init(&tracing_loop_,
&initialize_writer_async_,
[](uv_async_t* async) {
LegacyTracingAgent* agent = ContainerOf(
&LegacyTracingAgent::initialize_writer_async_,
async);
agent->InitializeWritersOnThread();
}),
0);
uv_unref(reinterpret_cast<uv_handle_t*>(&initialize_writer_async_));
}
void LegacyTracingAgent::InitializeWritersOnThread() {
Mutex::ScopedLock lock(initialize_writer_mutex_);
while (!to_be_initialized_.empty()) {
AsyncTraceWriter* head = *to_be_initialized_.begin();
head->InitializeOnThread(&tracing_loop_);
to_be_initialized_.erase(head);
}
initialize_writer_condvar_.Broadcast(lock);
}
LegacyTracingAgent::~LegacyTracingAgent() {
StopTracing();
categories_.clear();
writers_.clear();
Stop();
uv_close(reinterpret_cast<uv_handle_t*>(&initialize_writer_async_), nullptr);
uv_run(&tracing_loop_, UV_RUN_ONCE);
CheckedUvLoopClose(&tracing_loop_);
}
void LegacyTracingAgent::Start() {
if (started_) return;
NodeTraceBuffer* trace_buffer_ =
new NodeTraceBuffer(NodeTraceBuffer::kBufferChunks, this, &tracing_loop_);
tracing_controller_->Initialize(trace_buffer_);
// This thread should be created *after* async handles are created
// (within NodeTraceWriter and NodeTraceBuffer constructors).
// Otherwise the thread could shut down prematurely.
CHECK_EQ(0,
uv_thread_create(
&thread_,
[](void* arg) {
uv_thread_setname("TraceEventWorker");
LegacyTracingAgent* agent =
static_cast<LegacyTracingAgent*>(arg);
uv_run(&agent->tracing_loop_, UV_RUN_DEFAULT);
},
this));
started_ = true;
}
AgentWriterHandle LegacyTracingAgent::AddClient(
const std::set<std::string>& categories,
std::unique_ptr<AsyncTraceWriter> writer,
enum UseDefaultCategoryMode mode) {
Start();
const std::set<std::string>* use_categories = &categories;
std::set<std::string> categories_with_default;
if (mode == kUseDefaultCategories) {
categories_with_default.insert(categories.begin(), categories.end());
categories_with_default.insert(categories_[kDefaultHandleId].begin(),
categories_[kDefaultHandleId].end());
use_categories = &categories_with_default;
}
ScopedSuspendTracing suspend(tracing_controller_.get(), this);
int id = next_writer_id_++;
AsyncTraceWriter* raw = writer.get();
writers_[id] = std::move(writer);
categories_[id] = {use_categories->begin(), use_categories->end()};
{
Mutex::ScopedLock lock(initialize_writer_mutex_);
to_be_initialized_.insert(raw);
uv_async_send(&initialize_writer_async_);
while (to_be_initialized_.count(raw) > 0)
initialize_writer_condvar_.Wait(lock);
}
return AgentWriterHandle(this, id);
}
void LegacyTracingAgent::Stop() {
if (!started_) return;
// Perform final Flush on TraceBuffer. We don't want the tracing controller
// to flush the buffer again on destruction of the V8::Platform.
tracing_controller_->StopTracing();
tracing_controller_->Initialize(nullptr);
started_ = false;
// Thread should finish when the tracing loop is stopped.
uv_thread_join(&thread_);
}
void LegacyTracingAgent::Disconnect(int client) {
if (client == kDefaultHandleId) return;
{
Mutex::ScopedLock lock(initialize_writer_mutex_);
to_be_initialized_.erase(writers_[client].get());
}
ScopedSuspendTracing suspend(tracing_controller_.get(), this);
writers_.erase(client);
categories_.erase(client);
}
void LegacyTracingAgent::Enable(int id,
const std::set<std::string>& categories) {
if (categories.empty()) return;
ScopedSuspendTracing suspend(
tracing_controller_.get(), this, id != kDefaultHandleId);
categories_[id].insert(categories.begin(), categories.end());
}
void LegacyTracingAgent::Disable(int id,
const std::set<std::string>& categories) {
ScopedSuspendTracing suspend(
tracing_controller_.get(), this, id != kDefaultHandleId);
std::multiset<std::string>& writer_categories = categories_[id];
for (const std::string& category : categories) {
auto it = writer_categories.find(category);
if (it != writer_categories.end()) writer_categories.erase(it);
}
}
TraceConfig* LegacyTracingAgent::CreateTraceConfig() const {
if (categories_.empty()) return nullptr;
TraceConfig* trace_config = new TraceConfig();
for (const auto& category : flatten(categories_)) {
trace_config->AddIncludedCategory(category.c_str());
}
return trace_config;
}
std::string LegacyTracingAgent::GetEnabledCategories() const {
std::string categories;
for (const std::string& category : flatten(categories_)) {
if (!categories.empty()) categories += ',';
categories += category;
}
return categories;
}
void LegacyTracingAgent::AppendTraceEvent(TraceObject* trace_event) {
for (const auto& id_writer : writers_)
id_writer.second->AppendTraceEvent(trace_event);
}
void LegacyTracingAgent::AddMetadataEvent(std::unique_ptr<TraceObject> event) {
Mutex::ScopedLock lock(metadata_events_mutex_);
metadata_events_.push_back(std::move(event));
}
void LegacyTracingAgent::Flush(bool blocking) {
{
Mutex::ScopedLock lock(metadata_events_mutex_);
for (const auto& event : metadata_events_) AppendTraceEvent(event.get());
}
for (const auto& id_writer : writers_) id_writer.second->Flush(blocking);
}
void LegacyTracingAgent::StartTracing(const std::string& categories) {
// Attach a new NodeTraceWriter only if this function hasn't been called
// before.
if (tracing_file_writer_.id_ != LegacyTracingAgent::kDefaultHandleId) return;
using std::operator""sv;
auto parts = std::views::split(categories, ","sv);
std::set<std::string> categories_set;
for (const auto& s : parts) {
categories_set.emplace(std::string(s.data(), s.size()));
}
tracing_file_writer_ =
AddClient(categories_set,
std::unique_ptr<AsyncTraceWriter>(new NodeTraceWriter(
per_process::cli_options->trace_event_file_pattern)),
kUseDefaultCategories);
}
void LegacyTracingAgent::StopTracing() {
tracing_file_writer_.reset();
tracing_file_writer_ = AgentWriterHandle(this, kDefaultHandleId);
}
AgentWriterHandle* LegacyTracingAgent::GetDefaultWriterHandle() {
return &tracing_file_writer_;
}
void TracingController::AddMetadataEvent(
const unsigned char* category_group_enabled,
const char* name,
int num_args,
const char** arg_names,
const unsigned char* arg_types,
const uint64_t* arg_values,
std::unique_ptr<v8::ConvertableToTraceFormat>* convertable_values,
unsigned int flags) {
std::unique_ptr<TraceObject> trace_event(new TraceObject);
trace_event->Initialize(TRACE_EVENT_PHASE_METADATA,
category_group_enabled,
name,
node::tracing::kGlobalScope, // scope
node::tracing::kNoId, // id
node::tracing::kNoId, // bind_id
num_args,
arg_names,
arg_types,
arg_values,
convertable_values,
TRACE_EVENT_FLAG_NONE,
CurrentTimestampMicroseconds(),
CurrentCpuTimestampMicroseconds());
LegacyTracingAgent* node_agent =
static_cast<LegacyTracingAgent*>(Agent::GetInstance());
if (node_agent != nullptr)
node_agent->AddMetadataEvent(std::move(trace_event));
}
} // namespace tracing
} // namespace node