北京通网站建设/中国万网域名注册免费
前言: trace event 简单来说就是在代码里静态插入埋点,当开启 trace event 的时候,每次经过这些埋点,就会记录执行的相关数据,通过这些数据,我们可以对系统进行分析。Node.js 内核支持 trace event 的功能,并实现了对某些模块的 trace 能力。本文介绍 trace event 在 Node.js 中的实现。
在 Node.js 收集 trace_event 数据的方式有两种,第一种是在启动 Node.js 时通过命令行参数。
node --trace-event-categories v8 --trace-event-file-pattern '${pid}-${rotation}.log' server.js
命令行参数可以指定我们需要收集哪些模块的 trace 数据以及这些 trace 数据写到哪个文件中。第二种方式是通过动态的方式去控制想要收集哪些模块的 trace 数据。
const trace_events = require('trace_events');
const categories = ['node.perf', 'node.async_hooks'];
const tracing = trace_events.createTracing({ categories });
tracing.enable();
// do something
tracing.disable();
下面以第二种方式为例介绍具体的实现(第一种只不过是第二种的简单情况)。通过 createTracing 可以创建一个 trace 对象。
function createTracing(options) {return new Tracing(options.categories);
}class Tracing {constructor(categories) {this[kHandle] = new CategorySet(categories);this[kCategories] = categories;this[kEnabled] = false;}enable() {if (!this[kEnabled]) {this[kEnabled] = true;this[kHandle].enable();}}
}
新建了一个 CategorySet 对象然后调用其 enable 函数。可以看到,js 层只是对底层 CategorySet 的简单封装。接着看 C++ 层。
class NodeCategorySet : public BaseObject {public:static void New(const FunctionCallbackInfo<Value>& args);static void Enable(const FunctionCallbackInfo<Value>& args);static void Disable(const FunctionCallbackInfo<Value>& args);private:bool enabled_ = false;const std::set<std::string> categories_; // 对象关联的 trace 模块
};
接着看 enable 函数的逻辑。
void NodeCategorySet::Enable(const FunctionCallbackInfo<Value>& args) {NodeCategorySet* category_set;ASSIGN_OR_RETURN_UNWRAP(&category_set, args.Holder());const auto& categories = category_set->GetCategories();// 非空并且没有启动则启动if (!category_set->enabled_ && !categories.empty()) {// 启动 trace agent,如果已经启动则直接返回StartTracingAgent();// 通过 writer 注册需要 trace 的模块GetTracingAgentWriter()->Enable(categories);category_set->enabled_ = true;}
}
最终的核心逻辑在 StartTracingAgent 和 GetTracingAgentWriter()->Enable() 中,在分析这两个函数之前,首先来看一下 Node.js 初始化的过程中关于 trace agent 的逻辑。
struct V8Platform {bool initialized_ = false;inline void Initialize(int thread_pool_size) {// 创建一个 trace agent 对象tracing_agent_ = std::make_unique<tracing::Agent>();// 保存到某个地方,生产 trace 数据时使用node::tracing::TraceEventHelper::SetAgent(tracing_agent_.get());// 获取 agent 中的 controller,controller 负责管理 trace 数据的生产node::tracing::TracingController* controller = tracing_agent_->GetTracingController();// 创建一个 trace 观察者,在启动 trace 的时候被 V8 执行trace_state_observer_ = std::make_unique<NodeTraceStateObserver>(controller);// 保持到 controller 中controller->AddTraceStateObserver(trace_state_observer_.get());// tracing_file_writer_ 设置为默认值tracing_file_writer_ = tracing_agent_->DefaultHandle();// 通过命令行启动if (!per_process::cli_options->trace_event_categories.empty()) {StartTracingAgent();}}inline tracing::AgentWriterHandle* GetTracingAgentWriter() {return &tracing_file_writer_;}std::unique_ptr<NodeTraceStateObserver> trace_state_observer_;std::unique_ptr<tracing::Agent> tracing_agent_;tracing::AgentWriterHandle tracing_file_writer_;
};
下面来看一下 对象间的整体关系和大致实现。
1. Agent
Agent 是 Node.js 中 trace event 的总负责人。
class Agent {public:Agent();~Agent();// 获取 agent 中的 controllerTracingController* GetTracingController() {TracingController* controller = tracing_controller_.get();return controller;}// 注册消费者 writerAgentWriterHandle AddClient(...);// 默认 writerAgentWriterHandle DefaultHandle();// 生产者调用这些函数生产 trace 数据void AppendTraceEvent(TraceObject* trace_event);void AddMetadataEvent(std::unique_ptr<TraceObject> event);// 通过 writer flush 数据到文件void Flush(bool blocking);private:void Start();void Enable(int id, const std::set<std::string>& categories);uv_thread_t thread_;uv_loop_t tracing_loop_;bool started_ = false;class ScopedSuspendTracing;// 记录需要 trace 的模块std::unordered_map<int, std::multiset<std::string>> categories_;// 记录消费者std::unordered_map<int, std::unique_ptr<AsyncTraceWriter>> writers_;std::unique_ptr<TracingController> tracing_controller_;
};
从 Agent 的类结构我们可以大致了解到 Agent 的功能。我们可以通过 Agent 启动 trace 系统、注册消费者,提供接口给生产者生产数据并写到消费者中。接着看 agent 中的 tracing_controller_ 和 writers_ 对象。
2. TracingController
class TracingController : public v8::platform::tracing::TracingController {public:TracingController() : v8::platform::tracing::TracingController() {}int64_t CurrentTimestampMicroseconds() override {return uv_hrtime() / 1000;}void AddMetadataEvent(...);
};
TracingController 继承 V8 的 TracingController。
class V8_PLATFORM_EXPORT TracingController : public V8_PLATFORM_NON_EXPORTED_BASE(v8::TracingController) {public:// 设置 TraceBuffer,用于保持生产者数据void Initialize(TraceBuffer* trace_buffer);// 生产者调用uint64_t AddTraceEvent(...) override;uint64_t AddTraceEventWithTimestamp(...) override;// 注册观察者,启动 trace 时通知观察者void AddTraceStateObserver(...) override;void StartTracing(TraceConfig* trace_config);void StopTracing();private:std::unordered_set<v8::TracingController::TraceStateObserver*> observers_;std::unique_ptr<TraceBuffer> trace_buffer_;
};
TracingController 内部持有一个 TraceBuffer 对象,当产生 trace 数据时,生产者就会调用 TracingController 的 AddTraceEvent 函数把数据写入 TraceBuffer 中。另外 TracingController 还持有了一些观察者。这些观察者在启动 trace 时会被执行。
void TracingController::AddTraceStateObserver(v8::TracingController::TraceStateObserver* observer) {{base::MutexGuard lock(mutex_.get());observers_.insert(observer);// 如果还没启动 trace 则返回,等待启动时通知观察者if (!recording_.load(std::memory_order_acquire)) return;}// trace 已经开启,直接通知观察者observer->OnTraceEnabled();
}
3. NodeTraceStateObserver
刚才提到 TracingController 持有一些观察者。Node.js 中的观察者是 NodeTraceStateObserver。
class NodeTraceStateObserver: public v8::TracingController::TraceStateObserver {public://。trace 启动时被回调inline void OnTraceEnabled() override {// 省略部分代码trace_process->SetString("arch", per_process::metadata.arch.c_str());trace_process->SetString("platform", per_process::metadata.platform.c_str());trace_process->BeginDictionary("release");trace_process->SetString("name", per_process::metadata.release.name.c_str());// 产生 trace 数据TRACE_EVENT_METADATA1("__metadata", "node", "process", std::move(trace_process));}private:v8::TracingController* controller_;
};
NodeTraceStateObserver 的逻辑很简单,就是在 trace 系统启动时执行 OnTraceEnabled,在 OnTraceEnabled 中会产生一个 trace 的 meta 数据,最终通过 TRACE_EVENT_METADATA1 写入 trace 文件,TRACE_EVENT_METADATA1 是一个宏,最终展开后调用 node::tracing::AddMetadataEvent -> AddMetadataEventImpl -> agent->GetTracingController()->AddMetadataEvent。
了解了 Node.js 初始化 agent 的逻辑后,就可以继续分析之前留下的 StartTracingAgent() 和 GetTracingAgentWriter()->Enable(categories) 的逻辑。首先看 StartTracingAgent。
inline void StartTracingAgent() {if (tracing_file_writer_.IsDefaultHandle()) {// 解析出命令后设置的需要 trace 的模块std::vector<std::string> categories = SplitString(per_process::cli_options->trace_event_categories, ',');// 注册消费者 writer tracing_file_writer_ = tracing_agent_->AddClient(std::set<std::string>(std::make_move_iterator(categories.begin()),std::make_move_iterator(categories.end())),std::unique_ptr<tracing::AsyncTraceWriter>(new tracing::NodeTraceWriter(per_process::cli_options->trace_event_file_pattern)),tracing::Agent::kUseDefaultCategories);}
}
在 Node.js 初始化时,tracing_file_writer_ 为默认值,所以如果还没有调用过 StartTracingAgent,则 IsDefaultHandle 为 true,反之 tracing_file_writer_ 会被 AddClient 重新赋值,第二次调用 StartTracingAgent 就直接返回了。因为我们现在是第一次执行 StartTracingAgent。所以 IsDefaultHandle 为 true。接着首先解析出需要 trace 的模块,然后调用 agent 的 AddClient 函数注册消费者。看一下 AddClient。
AgentWriterHandle Agent::AddClient(const std::set<std::string>& categories,std::unique_ptr<AsyncTraceWriter> writer,enum UseDefaultCategoryMode mode) {// 启动 trace 子线程,如果还没有启动的话Start();const std::set<std::string>* use_categories = &categories;int id = next_writer_id_++;AsyncTraceWriter* raw = writer.get();// 记录 writer 和 trace 的模块writers_[id] = std::move(writer);categories_[id] = { use_categories->begin(), use_categories->end() };{Mutex::ScopedLock lock(initialize_writer_mutex_);// 记录待初始化的 writerto_be_initialized_.insert(raw);// 通知 trace 子线程uv_async_send(&initialize_writer_async_);while (to_be_initialized_.count(raw) > 0)initialize_writer_condvar_.Wait(lock);}return AgentWriterHandle(this, id);
}
trace 系统部分逻辑是跑在子线程的。注册 writer 时如果还没有启动 trace 子线程则启动它。
Agent::Agent() : tracing_controller_(new TracingController()) {tracing_controller_->Initialize(nullptr);uv_loop_init(&tracing_loop_), 0;// 注册 writer 时执行的回调uv_async_init(&tracing_loop_, &initialize_writer_async_, [](uv_async_t* async) {Agent* agent = ContainerOf(&Agent::initialize_writer_async_, async);agent->InitializeWritersOnThread();}), 0);uv_unref(reinterpret_cast<uv_handle_t*>(&initialize_writer_async_));
}void Agent::Start() {if (started_)return;NodeTraceBuffer* trace_buffer_ = new NodeTraceBuffer(NodeTraceBuffer::kBufferChunks, this, &tracing_loop_);tracing_controller_->Initialize(trace_buffer_);uv_thread_create(&thread_, [](void* arg) {Agent* agent = static_cast<Agent*>(arg);uv_run(&agent->tracing_loop_, UV_RUN_DEFAULT);}, this);started_ = true;
}
Agent::Start 首先初始化了和生产者相关的逻辑,否则注册消费者 writer 就毫无意义。这里新建了一个 NodeTraceBuffer 并设置到 controller 中。后面分析生产者时再分析这部分代码。接着创建了一个线程,trace 子线程中单独跑了一个事件循环,并且通过异步方式和主线程通信,所以每次注册 writer 的时候,主线程都通过 uv_async_send(&initialize_writer_async_) 通知 子线程。从而子线程执行回调 agent->InitializeWritersOnThread()。
void Agent::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);
}
InitializeWritersOnThread 遍历待注册的 writer 并执行它的 InitializeWritersOnThread 函数。这里以 Node.js 的 writer NodeTraceWriter 为例。
void NodeTraceWriter::InitializeOnThread(uv_loop_t* loop) {tracing_loop_ = loop;flush_signal_.data = this;int err = uv_async_init(tracing_loop_, &flush_signal_, [](uv_async_t* signal) {NodeTraceWriter* trace_writer = ContainerOf(&NodeTraceWriter::flush_signal_, signal);trace_writer->FlushPrivate();});
}
writer 往子线程事件循环中注册了一个异步回调,这个回调会在需要写入数据到文件里被执行,后面会分析。至此,StartTracingAgent 就分析完了。接着看 GetTracingAgentWriter()->Enable(categories)。GetTracingAgentWriter 返回到是一个 AgentWriterHandle 对象。
void AgentWriterHandle::Enable(const std::set<std::string>& categories) {if (agent_ != nullptr) agent_->Enable(id_, categories);
}void Agent::Enable(int id, const std::set<std::string>& categories) {categories_[id].insert(categories.begin(), categories.end());
}
这样就完成了 trace 系统的初始化和订阅了需要 trace 的模块。接下来分析生产者。以同步打开文件 API 为例。下面是 open 函数的 trace 埋点。
FS_SYNC_TRACE_BEGIN(open);
int result = SyncCall(env, args[4], &req_wrap_sync, "open",uv_fs_open, *path, flags, mode);
FS_SYNC_TRACE_END(open);
宏展开后
#define FS_SYNC_TRACE_BEGIN(syscall, ...) \if (GET_TRACE_ENABLED) \TRACE_EVENT_BEGIN(TRACING_CATEGORY_NODE2(fs, sync), TRACE_NAME(syscall), \##__VA_ARGS__);
继续
// 判断是否订阅了当前模块的 trace
if (*node::tracing::TraceEventHelper::GetCategoryGroupEnabled("node,node.fs,node.fs.sync") != 0) {// 通过 agent 的 controller 写入 trace 数据controller->AddTraceEvent(...);
}
AddTraceEvent 由 V8 实现。
uint64_t TracingController::AddTraceEvent(...) {int64_t now_us = CurrentTimestampMicroseconds();return AddTraceEventWithTimestamp(...);
}uint64_t TracingController::AddTraceEventWithTimestamp(...) {TraceObject* trace_object = trace_buffer_->AddTraceEvent(&handle);
}
通过层层调用,最终调用 TraceBuffer 的 AddTraceEvent,对应 Node.js 的 NodeTraceBuffer。
TraceObject* NodeTraceBuffer::AddTraceEvent(uint64_t* handle) {// buffer 是否已经满了,是则 flushif (!TryLoadAvailableBuffer()) {*handle = 0;return nullptr;}// 否则缓存return current_buf_.load()->AddTraceEvent(handle);
}
我们只需要看 TryLoadAvailableBuffer。
bool NodeTraceBuffer::TryLoadAvailableBuffer() {InternalTraceBuffer* prev_buf = current_buf_.load();if (prev_buf->IsFull()) {uv_async_send(&flush_signal_);}return true;
}
如果 buffer 满了,则通知 flush_signal_,那么 flush_signal_ 是什么呢?这是在 NodeTraceBuffer 初始化时设置的。
NodeTraceBuffer::NodeTraceBuffer(size_t max_chunks,Agent* agent, uv_loop_t* tracing_loop): tracing_loop_(tracing_loop),buffer1_(max_chunks, 0, agent),buffer2_(max_chunks, 1, agent) {flush_signal_.data = this;// 回调 NonBlockingFlushSignalCbint err = uv_async_init(tracing_loop_, &flush_signal_,NonBlockingFlushSignalCb);
}
可以看到 NodeTraceBuffer 在 trace 子线程中设置了一个回调,当主线程写入的 trace 数据满了则通知子线程处理。具体逻辑在 NonBlockingFlushSignalCb。
void NodeTraceBuffer::NonBlockingFlushSignalCb(uv_async_t* signal) {NodeTraceBuffer* buffer = static_cast<NodeTraceBuffer*>(signal->data);if (buffer->buffer1_.IsFull() && !buffer->buffer1_.IsFlushing()) {buffer->buffer1_.Flush(false);}if (buffer->buffer2_.IsFull() && !buffer->buffer2_.IsFlushing()) {buffer->buffer2_.Flush(false);}
}
NodeTraceBuffer 内部维护了几个内部 buffer 用于存储数据(InternalTraceBuffer 对象)。当 内部 buffer 满了则调用 Flush。
void InternalTraceBuffer::Flush(bool blocking) {{Mutex::ScopedLock scoped_lock(mutex_);if (total_chunks_ > 0) {flushing_ = true;for (size_t i = 0; i < total_chunks_; ++i) {auto& chunk = chunks_[i];for (size_t j = 0; j < chunk->size(); ++j) {TraceObject* trace_event = chunk->GetEventAt(j);if (trace_event->name()) {// 交给 agent 处理agent_->AppendTraceEvent(trace_event);}}}total_chunks_ = 0;flushing_ = false;}}agent_->Flush(blocking);
}
Flush 最终会通知 agent 进行数据的处理并调用 agent 的 Flush。
void Agent::AppendTraceEvent(TraceObject* trace_event) {for (const auto& id_writer : writers_)id_writer.second->AppendTraceEvent(trace_event);
}void Agent::Flush(bool blocking) {for (const auto& id_writer : writers_)id_writer.second->Flush(blocking);
}
agent 也只是简单调用 writer 进行数据的消费。
void NodeTraceWriter::AppendTraceEvent(TraceObject* trace_event) {Mutex::ScopedLock scoped_lock(stream_mutex_);if (total_traces_ == 0) {// 打开 trace 文件OpenNewFileForStreaming();json_trace_writer_.reset(TraceWriter::CreateJSONTraceWriter(stream_));}++total_traces_;// 缓存数据json_trace_writer_->AppendTraceEvent(trace_event);
}
AppendTraceEvent 只是把数据放到内存里。等待 Flush 时写到文件。
void NodeTraceWriter::Flush(bool blocking) {int err = uv_async_send(&flush_signal_);
}
和 buffer 一样,消费者进行数据处理时也是需要通知 trace 子线程。这个在介绍注册 writer 时已经分析过。具体处理函数是 trace_writer->FlushPrivate()。这个函数就是把数据写到 trace 文件,就不再具体分析。trace 系统整体架构和处理流程如下。
生成 trace 文件后,可以使用 chrome://tracing/ 进行分析。比如分析 openSync 函数。
对应 trace 文件的数据。