rippled
Loading...
Searching...
No Matches
PerfLogImp.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2018 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/core/JobTypes.h>
21#include <xrpld/perflog/detail/PerfLogImp.h>
22
23#include <xrpl/basics/BasicConfig.h>
24#include <xrpl/beast/core/CurrentThreadName.h>
25#include <xrpl/beast/utility/Journal.h>
26#include <xrpl/json/json_writer.h>
27
28#include <atomic>
29#include <cstdint>
30#include <cstdlib>
31#include <iterator>
32#include <mutex>
33#include <optional>
34#include <sstream>
35#include <stdexcept>
36#include <string>
37#include <unordered_map>
38#include <utility>
39
40namespace ripple {
41namespace perf {
42
44 std::set<char const*> const& labels,
45 JobTypes const& jobTypes)
46{
47 {
48 // populateRpc
49 rpc_.reserve(labels.size());
50 for (std::string const label : labels)
51 {
52 auto const inserted = rpc_.emplace(label, Rpc()).second;
53 if (!inserted)
54 {
55 // Ensure that no other function populates this entry.
56 // LCOV_EXCL_START
57 UNREACHABLE(
58 "ripple::perf::PerfLogImp::Counters::Counters : failed to "
59 "insert label");
60 // LCOV_EXCL_STOP
61 }
62 }
63 }
64 {
65 // populateJq
66 jq_.reserve(jobTypes.size());
67 for (auto const& [jobType, _] : jobTypes)
68 {
69 auto const inserted = jq_.emplace(jobType, Jq()).second;
70 if (!inserted)
71 {
72 // Ensure that no other function populates this entry.
73 // LCOV_EXCL_START
74 UNREACHABLE(
75 "ripple::perf::PerfLogImp::Counters::Counters : failed to "
76 "insert job type");
77 // LCOV_EXCL_STOP
78 }
79 }
80 }
81}
82
85{
87 // totalRpc represents all rpc methods. All that started, finished, etc.
88 Rpc totalRpc;
89 for (auto const& proc : rpc_)
90 {
91 Rpc value;
92 {
93 std::lock_guard lock(proc.second.mutex);
94 if (!proc.second.value.started && !proc.second.value.finished &&
95 !proc.second.value.errored)
96 {
97 continue;
98 }
99 value = proc.second.value;
100 }
101
103 p[jss::started] = std::to_string(value.started);
104 totalRpc.started += value.started;
105 p[jss::finished] = std::to_string(value.finished);
106 totalRpc.finished += value.finished;
107 p[jss::errored] = std::to_string(value.errored);
108 totalRpc.errored += value.errored;
109 p[jss::duration_us] = std::to_string(value.duration.count());
110 totalRpc.duration += value.duration;
111 rpcobj[proc.first] = p;
112 }
113
114 if (totalRpc.started)
115 {
116 Json::Value totalRpcJson(Json::objectValue);
117 totalRpcJson[jss::started] = std::to_string(totalRpc.started);
118 totalRpcJson[jss::finished] = std::to_string(totalRpc.finished);
119 totalRpcJson[jss::errored] = std::to_string(totalRpc.errored);
120 totalRpcJson[jss::duration_us] =
121 std::to_string(totalRpc.duration.count());
122 rpcobj[jss::total] = totalRpcJson;
123 }
124
126 // totalJq represents all jobs. All enqueued, started, finished, etc.
127 Jq totalJq;
128 for (auto const& proc : jq_)
129 {
130 Jq value;
131 {
132 std::lock_guard lock(proc.second.mutex);
133 if (!proc.second.value.queued && !proc.second.value.started &&
134 !proc.second.value.finished)
135 {
136 continue;
137 }
138 value = proc.second.value;
139 }
140
142 j[jss::queued] = std::to_string(value.queued);
143 totalJq.queued += value.queued;
144 j[jss::started] = std::to_string(value.started);
145 totalJq.started += value.started;
146 j[jss::finished] = std::to_string(value.finished);
147 totalJq.finished += value.finished;
148 j[jss::queued_duration_us] =
150 totalJq.queuedDuration += value.queuedDuration;
151 j[jss::running_duration_us] =
153 totalJq.runningDuration += value.runningDuration;
154 jqobj[JobTypes::name(proc.first)] = j;
155 }
156
157 if (totalJq.queued)
158 {
159 Json::Value totalJqJson(Json::objectValue);
160 totalJqJson[jss::queued] = std::to_string(totalJq.queued);
161 totalJqJson[jss::started] = std::to_string(totalJq.started);
162 totalJqJson[jss::finished] = std::to_string(totalJq.finished);
163 totalJqJson[jss::queued_duration_us] =
165 totalJqJson[jss::running_duration_us] =
167 jqobj[jss::total] = totalJqJson;
168 }
169
171 // Be kind to reporting tools and let them expect rpc and jq objects
172 // even if empty.
173 counters[jss::rpc] = rpcobj;
174 counters[jss::job_queue] = jqobj;
175 return counters;
176}
177
180{
181 auto const present = steady_clock::now();
182
183 Json::Value jobsArray(Json::arrayValue);
184 auto const jobs = [this] {
185 std::lock_guard lock(jobsMutex_);
186 return jobs_;
187 }();
188
189 for (auto const& j : jobs)
190 {
191 if (j.first == jtINVALID)
192 continue;
194 jobj[jss::job] = JobTypes::name(j.first);
195 jobj[jss::duration_us] = std::to_string(
196 std::chrono::duration_cast<microseconds>(present - j.second)
197 .count());
198 jobsArray.append(jobj);
199 }
200
201 Json::Value methodsArray(Json::arrayValue);
203 {
204 std::lock_guard lock(methodsMutex_);
205 methods.reserve(methods_.size());
206 for (auto const& m : methods_)
207 methods.push_back(m.second);
208 }
209 for (auto m : methods)
210 {
212 methodobj[jss::method] = m.first;
213 methodobj[jss::duration_us] = std::to_string(
214 std::chrono::duration_cast<microseconds>(present - m.second)
215 .count());
216 methodsArray.append(methodobj);
217 }
218
220 current[jss::jobs] = jobsArray;
221 current[jss::methods] = methodsArray;
222 return current;
223}
224
225//-----------------------------------------------------------------------------
226
227void
229{
230 if (setup_.perfLog.empty())
231 return;
232
233 if (logFile_.is_open())
234 logFile_.close();
235
236 auto logDir = setup_.perfLog.parent_path();
237 if (!boost::filesystem::is_directory(logDir))
238 {
239 boost::system::error_code ec;
240 boost::filesystem::create_directories(logDir, ec);
241 if (ec)
242 {
243 JLOG(j_.fatal()) << "Unable to create performance log "
244 "directory "
245 << logDir << ": " << ec.message();
246 signalStop_();
247 return;
248 }
249 }
250
251 logFile_.open(setup_.perfLog.c_str(), std::ios::out | std::ios::app);
252
253 if (!logFile_)
254 {
255 JLOG(j_.fatal()) << "Unable to open performance log " << setup_.perfLog
256 << ".";
257 signalStop_();
258 }
259}
260
261void
263{
266
267 while (true)
268 {
269 {
271 if (cond_.wait_until(
272 lock, lastLog_ + setup_.logInterval, [&] { return stop_; }))
273 {
274 return;
275 }
276 if (rotate_)
277 {
278 openLog();
279 rotate_ = false;
280 }
281 }
282 report();
283 }
284}
285
286void
288{
289 if (!logFile_)
290 // If logFile_ is not writable do no further work.
291 return;
292
293 auto const present = system_clock::now();
294 if (present < lastLog_ + setup_.logInterval)
295 return;
296 lastLog_ = present;
297
299 report[jss::time] = to_string(std::chrono::floor<microseconds>(present));
300 {
302 report[jss::workers] =
303 static_cast<unsigned int>(counters_.jobs_.size());
304 }
305 report[jss::hostid] = hostname_;
306 report[jss::counters] = counters_.countersJson();
307 report[jss::nodestore] = Json::objectValue;
308 app_.getNodeStore().getCountsJson(report[jss::nodestore]);
309 report[jss::current_activities] = counters_.currentJson();
311
312 logFile_ << Json::Compact{std::move(report)} << std::endl;
313}
314
316 Setup const& setup,
317 Application& app,
318 beast::Journal journal,
319 std::function<void()>&& signalStop)
320 : setup_(setup), app_(app), j_(journal), signalStop_(std::move(signalStop))
321{
322 openLog();
323}
324
326{
327 stop();
328}
329
330void
331PerfLogImp::rpcStart(std::string const& method, std::uint64_t const requestId)
332{
333 auto counter = counters_.rpc_.find(method);
334 if (counter == counters_.rpc_.end())
335 {
336 // LCOV_EXCL_START
337 UNREACHABLE("ripple::perf::PerfLogImp::rpcStart : valid method input");
338 return;
339 // LCOV_EXCL_STOP
340 }
341
342 {
343 std::lock_guard lock(counter->second.mutex);
344 ++counter->second.value.started;
345 }
347 counters_.methods_[requestId] = {
348 counter->first.c_str(), steady_clock::now()};
349}
350
351void
353 std::string const& method,
354 std::uint64_t const requestId,
355 bool finish)
356{
357 auto counter = counters_.rpc_.find(method);
358 if (counter == counters_.rpc_.end())
359 {
360 // LCOV_EXCL_START
361 UNREACHABLE("ripple::perf::PerfLogImp::rpcEnd : valid method input");
362 return;
363 // LCOV_EXCL_STOP
364 }
365 steady_time_point startTime;
366 {
368 auto const e = counters_.methods_.find(requestId);
369 if (e != counters_.methods_.end())
370 {
371 startTime = e->second.second;
372 counters_.methods_.erase(e);
373 }
374 else
375 {
376 // LCOV_EXCL_START
377 UNREACHABLE(
378 "ripple::perf::PerfLogImp::rpcEnd : valid requestId input");
379 // LCOV_EXCL_STOP
380 }
381 }
382 std::lock_guard lock(counter->second.mutex);
383 if (finish)
384 ++counter->second.value.finished;
385 else
386 ++counter->second.value.errored;
387 counter->second.value.duration += std::chrono::duration_cast<microseconds>(
388 steady_clock::now() - startTime);
389}
390
391void
393{
394 auto counter = counters_.jq_.find(type);
395 if (counter == counters_.jq_.end())
396 {
397 // LCOV_EXCL_START
398 UNREACHABLE(
399 "ripple::perf::PerfLogImp::jobQueue : valid job type input");
400 return;
401 // LCOV_EXCL_STOP
402 }
403 std::lock_guard lock(counter->second.mutex);
404 ++counter->second.value.queued;
405}
406
407void
409 JobType const type,
410 microseconds dur,
411 steady_time_point startTime,
412 int instance)
413{
414 auto counter = counters_.jq_.find(type);
415 if (counter == counters_.jq_.end())
416 {
417 // LCOV_EXCL_START
418 UNREACHABLE(
419 "ripple::perf::PerfLogImp::jobStart : valid job type input");
420 return;
421 // LCOV_EXCL_STOP
422 }
423
424 {
425 std::lock_guard lock(counter->second.mutex);
426 ++counter->second.value.started;
427 counter->second.value.queuedDuration += dur;
428 }
430 if (instance >= 0 && instance < counters_.jobs_.size())
431 counters_.jobs_[instance] = {type, startTime};
432}
433
434void
435PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
436{
437 auto counter = counters_.jq_.find(type);
438 if (counter == counters_.jq_.end())
439 {
440 // LCOV_EXCL_START
441 UNREACHABLE(
442 "ripple::perf::PerfLogImp::jobFinish : valid job type input");
443 return;
444 // LCOV_EXCL_STOP
445 }
446
447 {
448 std::lock_guard lock(counter->second.mutex);
449 ++counter->second.value.finished;
450 counter->second.value.runningDuration += dur;
451 }
453 if (instance >= 0 && instance < counters_.jobs_.size())
455}
456
457void
458PerfLogImp::resizeJobs(int const resize)
459{
461 if (resize > counters_.jobs_.size())
463}
464
465void
467{
468 if (setup_.perfLog.empty())
469 return;
470
472 rotate_ = true;
474}
475
476void
478{
479 if (setup_.perfLog.size())
481}
482
483void
485{
486 if (thread_.joinable())
487 {
488 {
490 stop_ = true;
492 }
493 thread_.join();
494 }
495}
496
497//-----------------------------------------------------------------------------
498
500setup_PerfLog(Section const& section, boost::filesystem::path const& configDir)
501{
502 PerfLog::Setup setup;
503 std::string perfLog;
504 set(perfLog, "perf_log", section);
505 if (perfLog.size())
506 {
507 setup.perfLog = boost::filesystem::path(perfLog);
508 if (setup.perfLog.is_relative())
509 {
510 setup.perfLog =
511 boost::filesystem::absolute(setup.perfLog, configDir);
512 }
513 }
514
515 std::uint64_t logInterval;
516 if (get_if_exists(section, "log_interval", logInterval))
517 setup.logInterval = std::chrono::seconds(logInterval);
518 return setup;
519}
520
523 PerfLog::Setup const& setup,
524 Application& app,
525 beast::Journal journal,
526 std::function<void()>&& signalStop)
527{
529 setup, app, journal, std::move(signalStop));
530}
531
532} // namespace perf
533} // namespace ripple
Decorator for streaming out compact json.
Represents a JSON value.
Definition json_value.h:149
Value & append(Value const &value)
Append value to array at the end.
A generic endpoint for log messages.
Definition Journal.h:60
Stream fatal() const
Definition Journal.h:352
virtual NodeStore::Database & getNodeStore()=0
virtual NetworkOPs & getOPs()=0
static std::string const & name(JobType jt)
Definition JobTypes.h:135
Map::size_type size() const
Definition JobTypes.h:159
virtual void stateAccounting(Json::Value &obj)=0
void getCountsJson(Json::Value &obj)
Definition Database.cpp:268
Holds a collection of configuration values.
Definition BasicConfig.h:45
std::string const hostname_
Definition PerfLogImp.h:131
void rpcEnd(std::string const &method, std::uint64_t const requestId, bool finish)
void resizeJobs(int const resize) override
Ensure enough room to store each currently executing job.
beast::Journal const j_
Definition PerfLogImp.h:123
std::function< void()> const signalStop_
Definition PerfLogImp.h:124
void jobQueue(JobType const type) override
Log queued job.
void jobStart(JobType const type, microseconds dur, steady_time_point startTime, int instance) override
Log job executing.
void rpcStart(std::string const &method, std::uint64_t const requestId) override
Log start of RPC call.
void rotate() override
Rotate perf log file.
std::condition_variable cond_
Definition PerfLogImp.h:129
PerfLogImp(Setup const &setup, Application &app, beast::Journal journal, std::function< void()> &&signalStop)
void jobFinish(JobType const type, microseconds dur, int instance) override
Log job finishing.
system_time_point lastLog_
Definition PerfLogImp.h:130
std::chrono::time_point< steady_clock > steady_time_point
Definition PerfLog.h:56
T close(T... args)
T endl(T... args)
T is_open(T... args)
T is_same_v
T join(T... args)
T joinable(T... args)
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
PerfLog::Setup setup_PerfLog(Section const &section, boost::filesystem::path const &configDir)
std::unique_ptr< PerfLog > make_PerfLog(PerfLog::Setup const &setup, Application &app, beast::Journal journal, std::function< void()> &&signalStop)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
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 get_if_exists(Section const &section, std::string const &name, T &v)
@ current
This was a new validation and was added.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
JobType
Definition Job.h:35
@ jtINVALID
Definition Job.h:37
STL namespace.
T open(T... args)
T push_back(T... args)
T reserve(T... args)
T resize(T... args)
T size(T... args)
Job Queue task performance counters.
Definition PerfLogImp.h:94
RPC performance counters.
Definition PerfLogImp.h:80
std::unordered_map< std::string, Locked< Rpc > > rpc_
Definition PerfLogImp.h:107
std::vector< std::pair< JobType, steady_time_point > > jobs_
Definition PerfLogImp.h:109
std::unordered_map< std::uint64_t, MethodStart > methods_
Definition PerfLogImp.h:111
Counters(std::set< char const * > const &labels, JobTypes const &jobTypes)
std::unordered_map< JobType, Locked< Jq > > jq_
Definition PerfLogImp.h:108
Configuration from [perf] section of rippled.cfg.
Definition PerfLog.h:66
boost::filesystem::path perfLog
Definition PerfLog.h:67
T to_string(T... args)