/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 |