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/StringUtilities.h>
7#include <xrpl/basics/chrono.h>
8#include <xrpl/beast/core/CurrentThreadName.h>
9#include <xrpl/beast/utility/Journal.h>
10#include <xrpl/beast/utility/instrumentation.h>
11#include <xrpl/config/BasicConfig.h>
12#include <xrpl/config/Constants.h>
13#include <xrpl/core/Job.h>
14#include <xrpl/core/JobTypes.h>
15#include <xrpl/core/PerfLog.h>
16#include <xrpl/json/json_value.h>
17#include <xrpl/json/json_writer.h>
18#include <xrpl/nodestore/Database.h>
19#include <xrpl/protocol/jss.h>
20#include <xrpl/server/NetworkOPs.h>
21
22#include <chrono>
23#include <cstdint>
24#include <filesystem>
25#include <functional>
26#include <ios>
27#include <memory>
28#include <mutex>
29#include <ostream>
30#include <span>
31#include <string>
32#include <string_view>
33#include <system_error>
34#include <unordered_map>
35#include <utility>
36#include <vector>
37
38namespace xrpl::perf {
39
42 JobTypes const& jobTypes)
43{
44 // Only a name that got a counter is kept, so labels and rpc hold the same set
45 // and countersJson() reports each counter once. Keeping a repeated name would
46 // add its counter to the totals twice, because the assertion below is compiled
47 // out of a release build.
48 labels.reserve(methodNames.size());
49 rpc.reserve(methodNames.size());
50 for (auto const& name : methodNames)
51 {
52 auto const inserted = rpc.try_emplace(name).second;
53 if (!inserted)
54 {
55 // LCOV_EXCL_START
56 UNREACHABLE("xrpl::perf::PerfLogImp::Counters::Counters : method name is unique");
57 continue;
58 // LCOV_EXCL_STOP
59 }
60 labels.push_back(name);
61 }
62
63 jq.reserve(jobTypes.size());
64 for (auto const& [jobType, _] : jobTypes)
65 {
66 auto const inserted = jq.emplace(jobType, Jq()).second;
67 if (!inserted)
68 {
69 // Nothing else inserts into jq, so a job type cannot repeat.
70 // LCOV_EXCL_START
71 UNREACHABLE("xrpl::perf::PerfLogImp::Counters::Counters : failed to insert job type");
72 // LCOV_EXCL_STOP
73 }
74 }
75}
76
79{
81 // totalRpc represents all rpc methods. All that started, finished, etc.
82 Rpc totalRpc;
83 // Walked by label rather than by map entry, so that each key can be reported
84 // as a C string. The constructor gives rpc an entry per label, so the lookup
85 // succeeds; it is a find rather than an at() because this runs on the logging
86 // thread, where a throw would end the process.
87 for (auto const& label : labels)
88 {
89 auto const entry = rpc.find(label);
90 if (entry == rpc.end())
91 {
92 // LCOV_EXCL_START
93 UNREACHABLE("xrpl::perf::PerfLogImp::Counters::countersJson : label has a counter");
94 continue;
95 // LCOV_EXCL_STOP
96 }
97 auto const& counter = entry->second;
98
99 Rpc value;
100 {
101 std::scoped_lock const lock(counter.mutex);
102 if ((counter.value.started == 0u) && (counter.value.finished == 0u) &&
103 (counter.value.errored == 0u))
104 {
105 continue;
106 }
107 value = counter.value;
108 }
109
111 p[jss::started] = std::to_string(value.started);
112 totalRpc.started += value.started;
113 p[jss::finished] = std::to_string(value.finished);
114 totalRpc.finished += value.finished;
115 p[jss::errored] = std::to_string(value.errored);
116 totalRpc.errored += value.errored;
117 p[jss::duration_us] = std::to_string(value.duration.count());
118 totalRpc.duration += value.duration;
119 rpcobj[json::StaticString{label.asCString()}] = p;
120 }
121
122 if (totalRpc.started != 0u)
123 {
125 totalRpcJson[jss::started] = std::to_string(totalRpc.started);
126 totalRpcJson[jss::finished] = std::to_string(totalRpc.finished);
127 totalRpcJson[jss::errored] = std::to_string(totalRpc.errored);
128 totalRpcJson[jss::duration_us] = std::to_string(totalRpc.duration.count());
129 rpcobj[jss::total] = totalRpcJson;
130 }
131
133 // totalJq represents all jobs. All enqueued, started, finished, etc.
134 Jq totalJq;
135 for (auto const& proc : jq)
136 {
137 Jq value;
138 {
139 std::scoped_lock const lock(proc.second.mutex);
140 if ((proc.second.value.queued == 0u) && (proc.second.value.started == 0u) &&
141 (proc.second.value.finished == 0u))
142 {
143 continue;
144 }
145 value = proc.second.value;
146 }
147
149 j[jss::queued] = std::to_string(value.queued);
150 totalJq.queued += value.queued;
151 j[jss::started] = std::to_string(value.started);
152 totalJq.started += value.started;
153 j[jss::finished] = std::to_string(value.finished);
154 totalJq.finished += value.finished;
155 j[jss::queued_duration_us] = std::to_string(value.queuedDuration.count());
156 totalJq.queuedDuration += value.queuedDuration;
157 j[jss::running_duration_us] = std::to_string(value.runningDuration.count());
158 totalJq.runningDuration += value.runningDuration;
159 jobQueueObj[JobTypes::name(proc.first)] = j;
160 }
161
162 if (totalJq.queued != 0u)
163 {
165 totalJqJson[jss::queued] = std::to_string(totalJq.queued);
166 totalJqJson[jss::started] = std::to_string(totalJq.started);
167 totalJqJson[jss::finished] = std::to_string(totalJq.finished);
168 totalJqJson[jss::queued_duration_us] = std::to_string(totalJq.queuedDuration.count());
169 totalJqJson[jss::running_duration_us] = std::to_string(totalJq.runningDuration.count());
170 jobQueueObj[jss::total] = totalJqJson;
171 }
172
174 // Be kind to reporting tools and let them expect rpc and jq objects
175 // even if empty.
176 counters[jss::rpc] = rpcobj;
177 counters[jss::job_queue] = jobQueueObj;
178 return counters;
179}
180
183{
184 auto const present = SteadyClock::now();
185
187 auto const jobs = [this] {
188 std::scoped_lock const lock(jobsMutex);
189 return this->jobs;
190 }();
191
192 for (auto const& j : jobs)
193 {
194 if (j.first == JtInvalid)
195 continue;
197 jobj[jss::job] = JobTypes::name(j.first);
198 jobj[jss::duration_us] =
199 std::to_string(std::chrono::duration_cast<Microseconds>(present - j.second).count());
200 jobsArray.append(jobj);
201 }
202
205 {
206 std::scoped_lock const lock(methodsMutex);
207 methods.reserve(this->methods.size());
208 for (auto const& m : this->methods)
209 methods.push_back(m.second);
210 }
211 for (auto m : methods)
212 {
214 // A key of rpc, per methods' declaration, so borrowed as above.
215 // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
216 methodobj[jss::method] = json::StaticString{m.first.data()};
217 methodobj[jss::duration_us] =
218 std::to_string(std::chrono::duration_cast<Microseconds>(present - m.second).count());
219 methodsArray.append(methodobj);
220 }
221
223 current[jss::jobs] = jobsArray;
224 current[jss::methods] = methodsArray;
225 return current;
226}
227
228//-----------------------------------------------------------------------------
229
230void
232{
233 if (setup_.perfLog.empty())
234 return;
235
236 if (logFile_.is_open())
237 logFile_.close();
238
239 auto logDir = setup_.perfLog.parent_path();
241 {
244 if (ec)
245 {
246 JLOG(j_.fatal()) << "Unable to create performance log "
247 "directory "
248 << logDir << ": " << ec.message();
249 signalStop_();
250 return;
251 }
252 }
253
254 logFile_.open(setup_.perfLog.c_str(), std::ios::out | std::ios::app);
255
256 if (!logFile_)
257 {
258 JLOG(j_.fatal()) << "Unable to open performance log " << setup_.perfLog << ".";
259 signalStop_();
260 }
261}
262
263void
265{
268
269 while (true)
270 {
271 {
273 if (cond_.wait_until(lock, lastLog_ + setup_.logInterval, [&] { return stop_; }))
274 {
275 return;
276 }
277 if (rotate_)
278 {
279 openLog();
280 rotate_ = false;
281 }
282 }
283 report();
284 }
285}
286
287void
289{
290 if (!logFile_)
291 {
292 // If logFile_ is not writable do no further work.
293 return;
294 }
295
296 auto const present = SystemClock::now();
297 if (present < lastLog_ + setup_.logInterval)
298 return;
299 lastLog_ = present;
300
302 report[jss::time] = to_string(std::chrono::floor<Microseconds>(present));
303 {
304 std::scoped_lock const lock{counters_.jobsMutex};
305 report[jss::workers] = static_cast<unsigned int>(counters_.jobs.size());
306 }
307 report[jss::hostid] = hostname_;
308 report[jss::counters] = counters_.countersJson();
309 report[jss::nodestore] = json::ValueType::Object;
310 app_.getNodeStore().getCountsJson(report[jss::nodestore]);
311 report[jss::current_activities] = counters_.currentJson();
312 app_.getOPs().stateAccounting(report);
313
314 logFile_ << json::Compact{std::move(report)} << std::endl;
315}
316
318 Setup setup,
319 Application& app,
321 beast::Journal journal,
322 std::function<void()>&& signalStop)
323 : setup_(std::move(setup))
324 , app_(app)
325 , j_(journal)
326 , signalStop_(std::move(signalStop))
327 , counters_(methodNames, JobTypes::instance())
328{
329 openLog();
330}
331
333{
334 stop();
335}
336
337void
339{
340 auto counter = counters_.rpc.find(method);
341 if (counter == counters_.rpc.end())
342 {
343 // LCOV_EXCL_START
344 UNREACHABLE("xrpl::perf::PerfLogImp::rpcStart : valid method input");
345 return;
346 // LCOV_EXCL_STOP
347 }
348
349 {
350 std::scoped_lock const lock(counter->second.mutex);
351 ++counter->second.value.started;
352 }
353 std::scoped_lock const lock(counters_.methodsMutex);
354 // The key, not the method argument: what is stored has to outlive the call.
355 counters_.methods[requestId] = {counter->first, SteadyClock::now()};
356}
357
358void
359PerfLogImp::rpcEnd(std::string_view method, std::uint64_t const requestId, bool finish)
360{
361 auto counter = counters_.rpc.find(method);
362 if (counter == counters_.rpc.end())
363 {
364 // LCOV_EXCL_START
365 UNREACHABLE("xrpl::perf::PerfLogImp::rpcEnd : valid method input");
366 return;
367 // LCOV_EXCL_STOP
368 }
369 SteadyTimePoint startTime;
370 {
371 std::scoped_lock const lock(counters_.methodsMutex);
372 auto const e = counters_.methods.find(requestId);
373 if (e != counters_.methods.end())
374 {
375 startTime = e->second.second;
376 counters_.methods.erase(e);
377 }
378 else
379 {
380 // LCOV_EXCL_START
381 UNREACHABLE("xrpl::perf::PerfLogImp::rpcEnd : valid requestId input");
382 // LCOV_EXCL_STOP
383 }
384 }
385 std::scoped_lock const lock(counter->second.mutex);
386 if (finish)
387 {
388 ++counter->second.value.finished;
389 }
390 else
391 {
392 ++counter->second.value.errored;
393 }
394 counter->second.value.duration +=
396}
397
398void
400{
401 auto counter = counters_.jq.find(type);
402 if (counter == counters_.jq.end())
403 {
404 // LCOV_EXCL_START
405 UNREACHABLE("xrpl::perf::PerfLogImp::jobQueue : valid job type input");
406 return;
407 // LCOV_EXCL_STOP
408 }
409 std::scoped_lock const lock(counter->second.mutex);
410 ++counter->second.value.queued;
411}
412
413void
414PerfLogImp::jobStart(JobType const type, Microseconds dur, SteadyTimePoint startTime, int instance)
415{
416 auto counter = counters_.jq.find(type);
417 if (counter == counters_.jq.end())
418 {
419 // LCOV_EXCL_START
420 UNREACHABLE("xrpl::perf::PerfLogImp::jobStart : valid job type input");
421 return;
422 // LCOV_EXCL_STOP
423 }
424
425 {
426 std::scoped_lock const lock(counter->second.mutex);
427 ++counter->second.value.started;
428 counter->second.value.queuedDuration += dur;
429 }
430 std::scoped_lock const lock(counters_.jobsMutex);
431 if (instance >= 0 && instance < counters_.jobs.size())
432 counters_.jobs[instance] = {type, startTime};
433}
434
435void
436PerfLogImp::jobFinish(JobType const type, Microseconds dur, int instance)
437{
438 auto counter = counters_.jq.find(type);
439 if (counter == counters_.jq.end())
440 {
441 // LCOV_EXCL_START
442 UNREACHABLE("xrpl::perf::PerfLogImp::jobFinish : valid job type input");
443 return;
444 // LCOV_EXCL_STOP
445 }
446
447 {
448 std::scoped_lock const lock(counter->second.mutex);
449 ++counter->second.value.finished;
450 counter->second.value.runningDuration += dur;
451 }
452 std::scoped_lock const lock(counters_.jobsMutex);
453 if (instance >= 0 && instance < counters_.jobs.size())
454 counters_.jobs[instance] = {JtInvalid, SteadyTimePoint()};
455}
456
457void
458PerfLogImp::resizeJobs(int const resize)
459{
460 std::scoped_lock const lock(counters_.jobsMutex);
461 if (resize > counters_.jobs.size())
462 counters_.jobs.resize(resize, {JtInvalid, SteadyTimePoint()});
463}
464
465void
467{
468 if (setup_.perfLog.empty())
469 return;
470
471 std::scoped_lock const lock(mutex_);
472 rotate_ = true;
473 cond_.notify_one();
474}
475
476void
478{
479 if (!setup_.perfLog.empty())
481}
482
483void
485{
486 if (thread_.joinable())
487 {
488 {
489 std::scoped_lock const lock(mutex_);
490 stop_ = true;
491 cond_.notify_one();
492 }
493 thread_.join();
494 }
495}
496
497//-----------------------------------------------------------------------------
498
500setupPerfLog(Section const& section, std::filesystem::path const& configDir)
501{
502 PerfLog::Setup setup;
503 std::string perfLog;
504 set(perfLog, "perf_log", section);
505 if (!perfLog.empty())
506 {
507 setup.perfLog = std::filesystem::path(perfLog);
508 if (setup.perfLog.is_relative())
509 {
510 setup.perfLog = std::filesystem::absolute(configDir / setup.perfLog);
511 }
512 }
513
514 std::uint64_t logInterval = 0;
515 if (getIfExists(section, Keys::kLogInterval, logInterval))
516 setup.logInterval = std::chrono::seconds(logInterval);
517 return setup;
518}
519
522 PerfLog::Setup const& setup,
523 Application& app,
525 beast::Journal journal,
526 std::function<void()>&& signalStop)
527{
528 return std::make_unique<PerfLogImp>(setup, app, methodNames, journal, std::move(signalStop));
529}
530
531} // namespace xrpl::perf
T absolute(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Decorator for streaming out compact json.
Lightweight wrapper to tag static string.
Definition json_value.h:48
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:28
PerfLogImp(Setup setup, Application &app, std::span< NullTerminatedView const > methodNames, beast::Journal journal, std::function< void()> &&signalStop)
std::condition_variable cond_
Definition PerfLogImp.h:130
void start() override
std::ofstream logFile_
Definition PerfLogImp.h:127
void rpcEnd(std::string_view method, std::uint64_t const requestId, bool finish)
void jobQueue(JobType const type) override
Log queued job.
void resizeJobs(int const resize) override
Ensure enough room to store each currently executing job.
void jobStart(JobType const type, Microseconds dur, SteadyTimePoint startTime, int instance) override
Log job executing.
beast::Journal const j_
Definition PerfLogImp.h:124
SystemTimePoint lastLog_
Definition PerfLogImp.h:131
void stop() override
void rotate() override
Rotate perf log file.
std::function< void()> const signalStop_
Definition PerfLogImp.h:125
void rpcStart(std::string_view method, std::uint64_t const requestId) override
Log start of RPC call.
void jobFinish(JobType const type, Microseconds dur, int instance) override
Log job finishing.
std::string const hostname_
Definition PerfLogImp.h:132
std::chrono::microseconds Microseconds
Definition PerfLog.h:41
std::chrono::time_point< SteadyClock > SteadyTimePoint
Definition PerfLog.h:37
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, std::span< NullTerminatedView const > methodNames, 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:657
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:82
RPC performance counters.
Definition PerfLogImp.h:68
std::vector< NullTerminatedView > labels
Definition PerfLogImp.h:106
std::unordered_map< std::string_view, Locked< Rpc > > rpc
Definition PerfLogImp.h:99
json::Value countersJson() const
json::Value currentJson() const
std::vector< std::pair< JobType, SteadyTimePoint > > jobs
Definition PerfLogImp.h:108
std::unordered_map< JobType, Locked< Jq > > jq
Definition PerfLogImp.h:107
std::unordered_map< std::uint64_t, MethodStart > methods
Definition PerfLogImp.h:112
Counters(std::span< NullTerminatedView const > methodNames, JobTypes const &jobTypes)
Configuration from [perf] section of xrpld.cfg.
Definition PerfLog.h:47
std::filesystem::path perfLog
Definition PerfLog.h:48
Milliseconds logInterval
Definition PerfLog.h:50
T to_string(T... args)