xrpld
Loading...
Searching...
No Matches
PerfLogImp.cpp
1#include <xrpld/perflog/detail/PerfLogImp.h>
2
3#include <xrpld/app/main/Application.h>
4
5#include <xrpl/basics/Log.h>
6#include <xrpl/basics/chrono.h>
7#include <xrpl/beast/core/CurrentThreadName.h>
8#include <xrpl/beast/utility/Journal.h>
9#include <xrpl/beast/utility/instrumentation.h>
10#include <xrpl/config/BasicConfig.h>
11#include <xrpl/config/Constants.h>
12#include <xrpl/core/Job.h>
13#include <xrpl/core/JobTypes.h>
14#include <xrpl/core/PerfLog.h>
15#include <xrpl/json/json_value.h>
16#include <xrpl/json/json_writer.h>
17#include <xrpl/nodestore/Database.h>
18#include <xrpl/protocol/jss.h>
19
20#include <chrono>
21#include <cstdint>
22#include <filesystem>
23#include <functional>
24#include <ios>
25#include <memory>
26#include <mutex>
27#include <ostream>
28#include <set>
29#include <string>
30#include <system_error>
31#include <unordered_map>
32#include <utility>
33#include <vector>
34
35namespace xrpl::perf {
36
38{
39 {
40 // populateRpc
41 rpc.reserve(labels.size());
42 for (std::string const label : labels)
43 {
44 auto const inserted = rpc.emplace(label, Rpc()).second;
45 if (!inserted)
46 {
47 // Ensure that no other function populates this entry.
48 // LCOV_EXCL_START
49 UNREACHABLE(
50 "xrpl::perf::PerfLogImp::Counters::Counters : failed to "
51 "insert label");
52 // LCOV_EXCL_STOP
53 }
54 }
55 }
56 {
57 // populateJq
58 jq.reserve(jobTypes.size());
59 for (auto const& [jobType, _] : jobTypes)
60 {
61 auto const inserted = jq.emplace(jobType, Jq()).second;
62 if (!inserted)
63 {
64 // Ensure that no other function populates this entry.
65 // LCOV_EXCL_START
66 UNREACHABLE(
67 "xrpl::perf::PerfLogImp::Counters::Counters : failed to "
68 "insert job type");
69 // LCOV_EXCL_STOP
70 }
71 }
72 }
73}
74
77{
79 // totalRpc represents all rpc methods. All that started, finished, etc.
80 Rpc totalRpc;
81 for (auto const& proc : rpc)
82 {
83 Rpc value;
84 {
85 std::scoped_lock const lock(proc.second.mutex);
86 if ((proc.second.value.started == 0u) && (proc.second.value.finished == 0u) &&
87 (proc.second.value.errored == 0u))
88 {
89 continue;
90 }
91 value = proc.second.value;
92 }
93
95 p[jss::started] = std::to_string(value.started);
96 totalRpc.started += value.started;
97 p[jss::finished] = std::to_string(value.finished);
98 totalRpc.finished += value.finished;
99 p[jss::errored] = std::to_string(value.errored);
100 totalRpc.errored += value.errored;
101 p[jss::duration_us] = std::to_string(value.duration.count());
102 totalRpc.duration += value.duration;
103 rpcobj[proc.first] = p;
104 }
105
106 if (totalRpc.started != 0u)
107 {
109 totalRpcJson[jss::started] = std::to_string(totalRpc.started);
110 totalRpcJson[jss::finished] = std::to_string(totalRpc.finished);
111 totalRpcJson[jss::errored] = std::to_string(totalRpc.errored);
112 totalRpcJson[jss::duration_us] = std::to_string(totalRpc.duration.count());
113 rpcobj[jss::total] = totalRpcJson;
114 }
115
117 // totalJq represents all jobs. All enqueued, started, finished, etc.
118 Jq totalJq;
119 for (auto const& proc : jq)
120 {
121 Jq value;
122 {
123 std::scoped_lock const lock(proc.second.mutex);
124 if ((proc.second.value.queued == 0u) && (proc.second.value.started == 0u) &&
125 (proc.second.value.finished == 0u))
126 {
127 continue;
128 }
129 value = proc.second.value;
130 }
131
133 j[jss::queued] = std::to_string(value.queued);
134 totalJq.queued += value.queued;
135 j[jss::started] = std::to_string(value.started);
136 totalJq.started += value.started;
137 j[jss::finished] = std::to_string(value.finished);
138 totalJq.finished += value.finished;
139 j[jss::queued_duration_us] = std::to_string(value.queuedDuration.count());
140 totalJq.queuedDuration += value.queuedDuration;
141 j[jss::running_duration_us] = std::to_string(value.runningDuration.count());
142 totalJq.runningDuration += value.runningDuration;
143 jobQueueObj[JobTypes::name(proc.first)] = j;
144 }
145
146 if (totalJq.queued != 0u)
147 {
149 totalJqJson[jss::queued] = std::to_string(totalJq.queued);
150 totalJqJson[jss::started] = std::to_string(totalJq.started);
151 totalJqJson[jss::finished] = std::to_string(totalJq.finished);
152 totalJqJson[jss::queued_duration_us] = std::to_string(totalJq.queuedDuration.count());
153 totalJqJson[jss::running_duration_us] = std::to_string(totalJq.runningDuration.count());
154 jobQueueObj[jss::total] = totalJqJson;
155 }
156
158 // Be kind to reporting tools and let them expect rpc and jq objects
159 // even if empty.
160 counters[jss::rpc] = rpcobj;
161 counters[jss::job_queue] = jobQueueObj;
162 return counters;
163}
164
167{
168 auto const present = steady_clock::now();
169
171 auto const jobs = [this] {
172 std::scoped_lock const lock(jobsMutex);
173 return this->jobs;
174 }();
175
176 for (auto const& j : jobs)
177 {
178 if (j.first == JtInvalid)
179 continue;
181 jobj[jss::job] = JobTypes::name(j.first);
182 jobj[jss::duration_us] =
183 std::to_string(std::chrono::duration_cast<microseconds>(present - j.second).count());
184 jobsArray.append(jobj);
185 }
186
189 {
190 std::scoped_lock const lock(methodsMutex);
191 methods.reserve(this->methods.size());
192 for (auto const& m : this->methods)
193 methods.push_back(m.second);
194 }
195 for (auto m : methods)
196 {
198 methodobj[jss::method] = m.first;
199 methodobj[jss::duration_us] =
200 std::to_string(std::chrono::duration_cast<microseconds>(present - m.second).count());
201 methodsArray.append(methodobj);
202 }
203
205 current[jss::jobs] = jobsArray;
206 current[jss::methods] = methodsArray;
207 return current;
208}
209
210//-----------------------------------------------------------------------------
211
212void
214{
215 if (setup_.perfLog.empty())
216 return;
217
218 if (logFile_.is_open())
219 logFile_.close();
220
221 auto logDir = setup_.perfLog.parent_path();
223 {
226 if (ec)
227 {
228 JLOG(j_.fatal()) << "Unable to create performance log "
229 "directory "
230 << logDir << ": " << ec.message();
231 signalStop_();
232 return;
233 }
234 }
235
236 logFile_.open(setup_.perfLog.c_str(), std::ios::out | std::ios::app);
237
238 if (!logFile_)
239 {
240 JLOG(j_.fatal()) << "Unable to open performance log " << setup_.perfLog << ".";
241 signalStop_();
242 }
243}
244
245void
247{
250
251 while (true)
252 {
253 {
255 if (cond_.wait_until(lock, lastLog_ + setup_.logInterval, [&] { return stop_; }))
256 {
257 return;
258 }
259 if (rotate_)
260 {
261 openLog();
262 rotate_ = false;
263 }
264 }
265 report();
266 }
267}
268
269void
271{
272 if (!logFile_)
273 {
274 // If logFile_ is not writable do no further work.
275 return;
276 }
277
278 auto const present = system_clock::now();
279 if (present < lastLog_ + setup_.logInterval)
280 return;
281 lastLog_ = present;
282
284 report[jss::time] = to_string(std::chrono::floor<microseconds>(present));
285 {
286 std::scoped_lock const lock{counters_.jobsMutex};
287 report[jss::workers] = static_cast<unsigned int>(counters_.jobs.size());
288 }
289 report[jss::hostid] = hostname_;
290 report[jss::counters] = counters_.countersJson();
291 report[jss::nodestore] = json::ValueType::Object;
292 app_.getNodeStore().getCountsJson(report[jss::nodestore]);
293 report[jss::current_activities] = counters_.currentJson();
294 app_.getOPs().stateAccounting(report);
295
296 logFile_ << json::Compact{std::move(report)} << std::endl;
297}
298
300 Setup setup,
301 Application& app,
302 beast::Journal journal,
303 std::function<void()>&& signalStop)
304 : setup_(std::move(setup)), app_(app), j_(journal), signalStop_(std::move(signalStop))
305{
306 openLog();
307}
308
310{
311 stop();
312}
313
314void
315PerfLogImp::rpcStart(std::string const& method, std::uint64_t const requestId)
316{
317 auto counter = counters_.rpc.find(method);
318 if (counter == counters_.rpc.end())
319 {
320 // LCOV_EXCL_START
321 UNREACHABLE("xrpl::perf::PerfLogImp::rpcStart : valid method input");
322 return;
323 // LCOV_EXCL_STOP
324 }
325
326 {
327 std::scoped_lock const lock(counter->second.mutex);
328 ++counter->second.value.started;
329 }
330 std::scoped_lock const lock(counters_.methodsMutex);
331 counters_.methods[requestId] = {counter->first.c_str(), steady_clock::now()};
332}
333
334void
335PerfLogImp::rpcEnd(std::string const& method, std::uint64_t const requestId, bool finish)
336{
337 auto counter = counters_.rpc.find(method);
338 if (counter == counters_.rpc.end())
339 {
340 // LCOV_EXCL_START
341 UNREACHABLE("xrpl::perf::PerfLogImp::rpcEnd : valid method input");
342 return;
343 // LCOV_EXCL_STOP
344 }
345 steady_time_point startTime;
346 {
347 std::scoped_lock const lock(counters_.methodsMutex);
348 auto const e = counters_.methods.find(requestId);
349 if (e != counters_.methods.end())
350 {
351 startTime = e->second.second;
352 counters_.methods.erase(e);
353 }
354 else
355 {
356 // LCOV_EXCL_START
357 UNREACHABLE("xrpl::perf::PerfLogImp::rpcEnd : valid requestId input");
358 // LCOV_EXCL_STOP
359 }
360 }
361 std::scoped_lock const lock(counter->second.mutex);
362 if (finish)
363 {
364 ++counter->second.value.finished;
365 }
366 else
367 {
368 ++counter->second.value.errored;
369 }
370 counter->second.value.duration +=
372}
373
374void
376{
377 auto counter = counters_.jq.find(type);
378 if (counter == counters_.jq.end())
379 {
380 // LCOV_EXCL_START
381 UNREACHABLE("xrpl::perf::PerfLogImp::jobQueue : valid job type input");
382 return;
383 // LCOV_EXCL_STOP
384 }
385 std::scoped_lock const lock(counter->second.mutex);
386 ++counter->second.value.queued;
387}
388
389void
391 JobType const type,
392 microseconds dur,
393 steady_time_point startTime,
394 int instance)
395{
396 auto counter = counters_.jq.find(type);
397 if (counter == counters_.jq.end())
398 {
399 // LCOV_EXCL_START
400 UNREACHABLE("xrpl::perf::PerfLogImp::jobStart : valid job type input");
401 return;
402 // LCOV_EXCL_STOP
403 }
404
405 {
406 std::scoped_lock const lock(counter->second.mutex);
407 ++counter->second.value.started;
408 counter->second.value.queuedDuration += dur;
409 }
410 std::scoped_lock const lock(counters_.jobsMutex);
411 if (instance >= 0 && instance < counters_.jobs.size())
412 counters_.jobs[instance] = {type, startTime};
413}
414
415void
416PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
417{
418 auto counter = counters_.jq.find(type);
419 if (counter == counters_.jq.end())
420 {
421 // LCOV_EXCL_START
422 UNREACHABLE("xrpl::perf::PerfLogImp::jobFinish : valid job type input");
423 return;
424 // LCOV_EXCL_STOP
425 }
426
427 {
428 std::scoped_lock const lock(counter->second.mutex);
429 ++counter->second.value.finished;
430 counter->second.value.runningDuration += dur;
431 }
432 std::scoped_lock const lock(counters_.jobsMutex);
433 if (instance >= 0 && instance < counters_.jobs.size())
434 counters_.jobs[instance] = {JtInvalid, steady_time_point()};
435}
436
437void
438PerfLogImp::resizeJobs(int const resize)
439{
440 std::scoped_lock const lock(counters_.jobsMutex);
441 if (resize > counters_.jobs.size())
442 counters_.jobs.resize(resize, {JtInvalid, steady_time_point()});
443}
444
445void
447{
448 if (setup_.perfLog.empty())
449 return;
450
451 std::scoped_lock const lock(mutex_);
452 rotate_ = true;
453 cond_.notify_one();
454}
455
456void
458{
459 if (!setup_.perfLog.empty())
461}
462
463void
465{
466 if (thread_.joinable())
467 {
468 {
469 std::scoped_lock const lock(mutex_);
470 stop_ = true;
471 cond_.notify_one();
472 }
473 thread_.join();
474 }
475}
476
477//-----------------------------------------------------------------------------
478
480setupPerfLog(Section const& section, std::filesystem::path const& configDir)
481{
482 PerfLog::Setup setup;
483 std::string perfLog;
484 set(perfLog, "perf_log", section);
485 if (!perfLog.empty())
486 {
487 setup.perfLog = std::filesystem::path(perfLog);
488 if (setup.perfLog.is_relative())
489 {
490 setup.perfLog = std::filesystem::absolute(configDir / setup.perfLog);
491 }
492 }
493
494 std::uint64_t logInterval = 0;
495 if (getIfExists(section, Keys::kLogInterval, logInterval))
496 setup.logInterval = std::chrono::seconds(logInterval);
497 return setup;
498}
499
502 PerfLog::Setup const& setup,
503 Application& app,
504 beast::Journal journal,
505 std::function<void()>&& signalStop)
506{
507 return std::make_unique<PerfLogImp>(setup, app, journal, std::move(signalStop));
508}
509
510} // namespace xrpl::perf
T absolute(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Decorator for streaming out compact json.
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
Map::size_type size() const
Definition JobTypes.h:137
static std::string const & name(JobType jt)
Definition JobTypes.h:113
Holds a collection of configuration values.
Definition BasicConfig.h:29
std::condition_variable cond_
Definition PerfLogImp.h:116
void jobFinish(JobType const type, microseconds dur, int instance) override
Log job finishing.
void start() override
PerfLogImp(Setup setup, Application &app, beast::Journal journal, std::function< void()> &&signalStop)
std::ofstream logFile_
Definition PerfLogImp.h:113
void rpcStart(std::string const &method, std::uint64_t const requestId) override
Log start of RPC call.
void jobQueue(JobType const type) override
Log queued job.
void resizeJobs(int const resize) override
Ensure enough room to store each currently executing job.
system_time_point lastLog_
Definition PerfLogImp.h:117
beast::Journal const j_
Definition PerfLogImp.h:110
void rpcEnd(std::string const &method, std::uint64_t const requestId, bool finish)
void stop() override
void rotate() override
Rotate perf log file.
std::function< void()> const signalStop_
Definition PerfLogImp.h:111
void jobStart(JobType const type, microseconds dur, steady_time_point startTime, int instance) override
Log job executing.
std::string const hostname_
Definition PerfLogImp.h:118
std::chrono::microseconds microseconds
Definition PerfLog.h:39
std::chrono::time_point< steady_clock > steady_time_point
Definition PerfLog.h:35
T create_directories(T... args)
T duration_cast(T... args)
T empty(T... args)
T endl(T... args)
T is_relative(T... args)
T is_directory(T... args)
T make_unique(T... args)
T message(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
STL namespace.
Dummy class for unit tests.
Definition Workers.h:14
std::unique_ptr< PerfLog > makePerfLog(PerfLog::Setup const &setup, Application &app, beast::Journal journal, std::function< void()> &&signalStop)
PerfLog::Setup setupPerfLog(Section const &section, std::filesystem::path const &configDir)
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
bool getIfExists(Section const &section, std::string const &name, T &v)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
JobType
Definition Job.h:21
@ JtInvalid
Definition Job.h:23
T size(T... args)
static constexpr auto kLogInterval
Definition Constants.h:119
Job Queue task performance counters.
Definition PerfLogImp.h:81
RPC performance counters.
Definition PerfLogImp.h:67
std::vector< std::pair< JobType, steady_time_point > > jobs
Definition PerfLogImp.h:96
std::unordered_map< std::string, Locked< Rpc > > rpc
Definition PerfLogImp.h:94
json::Value countersJson() const
json::Value currentJson() const
Counters(std::set< char const * > const &labels, JobTypes const &jobTypes)
std::unordered_map< JobType, Locked< Jq > > jq
Definition PerfLogImp.h:95
std::unordered_map< std::uint64_t, MethodStart > methods
Definition PerfLogImp.h:98
Configuration from [perf] section of xrpld.cfg.
Definition PerfLog.h:45
std::filesystem::path perfLog
Definition PerfLog.h:46
milliseconds logInterval
Definition PerfLog.h:48
T to_string(T... args)