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 UNREACHABLE(
57 "ripple::perf::PerfLogImp::Counters::Counters : failed to "
58 "insert label");
59 }
60 }
61 }
62 {
63 // populateJq
64 jq_.reserve(jobTypes.size());
65 for (auto const& [jobType, _] : jobTypes)
66 {
67 auto const inserted = jq_.emplace(jobType, Jq()).second;
68 if (!inserted)
69 {
70 // Ensure that no other function populates this entry.
71 UNREACHABLE(
72 "ripple::perf::PerfLogImp::Counters::Counters : failed to "
73 "insert job type");
74 }
75 }
76 }
77}
78
81{
83 // totalRpc represents all rpc methods. All that started, finished, etc.
84 Rpc totalRpc;
85 for (auto const& proc : rpc_)
86 {
87 Rpc value;
88 {
89 std::lock_guard lock(proc.second.mutex);
90 if (!proc.second.value.started && !proc.second.value.finished &&
91 !proc.second.value.errored)
92 {
93 continue;
94 }
95 value = proc.second.value;
96 }
97
99 p[jss::started] = std::to_string(value.started);
100 totalRpc.started += value.started;
101 p[jss::finished] = std::to_string(value.finished);
102 totalRpc.finished += value.finished;
103 p[jss::errored] = std::to_string(value.errored);
104 totalRpc.errored += value.errored;
105 p[jss::duration_us] = std::to_string(value.duration.count());
106 totalRpc.duration += value.duration;
107 rpcobj[proc.first] = p;
108 }
109
110 if (totalRpc.started)
111 {
112 Json::Value totalRpcJson(Json::objectValue);
113 totalRpcJson[jss::started] = std::to_string(totalRpc.started);
114 totalRpcJson[jss::finished] = std::to_string(totalRpc.finished);
115 totalRpcJson[jss::errored] = std::to_string(totalRpc.errored);
116 totalRpcJson[jss::duration_us] =
117 std::to_string(totalRpc.duration.count());
118 rpcobj[jss::total] = totalRpcJson;
119 }
120
122 // totalJq represents all jobs. All enqueued, started, finished, etc.
123 Jq totalJq;
124 for (auto const& proc : jq_)
125 {
126 Jq value;
127 {
128 std::lock_guard lock(proc.second.mutex);
129 if (!proc.second.value.queued && !proc.second.value.started &&
130 !proc.second.value.finished)
131 {
132 continue;
133 }
134 value = proc.second.value;
135 }
136
138 j[jss::queued] = std::to_string(value.queued);
139 totalJq.queued += value.queued;
140 j[jss::started] = std::to_string(value.started);
141 totalJq.started += value.started;
142 j[jss::finished] = std::to_string(value.finished);
143 totalJq.finished += value.finished;
144 j[jss::queued_duration_us] =
146 totalJq.queuedDuration += value.queuedDuration;
147 j[jss::running_duration_us] =
149 totalJq.runningDuration += value.runningDuration;
150 jqobj[JobTypes::name(proc.first)] = j;
151 }
152
153 if (totalJq.queued)
154 {
155 Json::Value totalJqJson(Json::objectValue);
156 totalJqJson[jss::queued] = std::to_string(totalJq.queued);
157 totalJqJson[jss::started] = std::to_string(totalJq.started);
158 totalJqJson[jss::finished] = std::to_string(totalJq.finished);
159 totalJqJson[jss::queued_duration_us] =
161 totalJqJson[jss::running_duration_us] =
163 jqobj[jss::total] = totalJqJson;
164 }
165
167 // Be kind to reporting tools and let them expect rpc and jq objects
168 // even if empty.
169 counters[jss::rpc] = rpcobj;
170 counters[jss::job_queue] = jqobj;
171 return counters;
172}
173
176{
177 auto const present = steady_clock::now();
178
179 Json::Value jobsArray(Json::arrayValue);
180 auto const jobs = [this] {
181 std::lock_guard lock(jobsMutex_);
182 return jobs_;
183 }();
184
185 for (auto const& j : jobs)
186 {
187 if (j.first == jtINVALID)
188 continue;
190 jobj[jss::job] = JobTypes::name(j.first);
191 jobj[jss::duration_us] = std::to_string(
192 std::chrono::duration_cast<microseconds>(present - j.second)
193 .count());
194 jobsArray.append(jobj);
195 }
196
197 Json::Value methodsArray(Json::arrayValue);
199 {
200 std::lock_guard lock(methodsMutex_);
201 methods.reserve(methods_.size());
202 for (auto const& m : methods_)
203 methods.push_back(m.second);
204 }
205 for (auto m : methods)
206 {
208 methodobj[jss::method] = m.first;
209 methodobj[jss::duration_us] = std::to_string(
210 std::chrono::duration_cast<microseconds>(present - m.second)
211 .count());
212 methodsArray.append(methodobj);
213 }
214
216 current[jss::jobs] = jobsArray;
217 current[jss::methods] = methodsArray;
218 return current;
219}
220
221//-----------------------------------------------------------------------------
222
223void
225{
226 if (setup_.perfLog.empty())
227 return;
228
229 if (logFile_.is_open())
230 logFile_.close();
231
232 auto logDir = setup_.perfLog.parent_path();
233 if (!boost::filesystem::is_directory(logDir))
234 {
235 boost::system::error_code ec;
236 boost::filesystem::create_directories(logDir, ec);
237 if (ec)
238 {
239 JLOG(j_.fatal()) << "Unable to create performance log "
240 "directory "
241 << logDir << ": " << ec.message();
242 signalStop_();
243 return;
244 }
245 }
246
247 logFile_.open(setup_.perfLog.c_str(), std::ios::out | std::ios::app);
248
249 if (!logFile_)
250 {
251 JLOG(j_.fatal()) << "Unable to open performance log " << setup_.perfLog
252 << ".";
253 signalStop_();
254 }
255}
256
257void
259{
262
263 while (true)
264 {
265 {
267 if (cond_.wait_until(
268 lock, lastLog_ + setup_.logInterval, [&] { return stop_; }))
269 {
270 return;
271 }
272 if (rotate_)
273 {
274 openLog();
275 rotate_ = false;
276 }
277 }
278 report();
279 }
280}
281
282void
284{
285 if (!logFile_)
286 // If logFile_ is not writable do no further work.
287 return;
288
289 auto const present = system_clock::now();
290 if (present < lastLog_ + setup_.logInterval)
291 return;
292 lastLog_ = present;
293
295 report[jss::time] = to_string(std::chrono::floor<microseconds>(present));
296 {
298 report[jss::workers] =
299 static_cast<unsigned int>(counters_.jobs_.size());
300 }
301 report[jss::hostid] = hostname_;
302 report[jss::counters] = counters_.countersJson();
303 report[jss::nodestore] = Json::objectValue;
304 app_.getNodeStore().getCountsJson(report[jss::nodestore]);
305 report[jss::current_activities] = counters_.currentJson();
307
308 logFile_ << Json::Compact{std::move(report)} << std::endl;
309}
310
312 Setup const& setup,
313 Application& app,
314 beast::Journal journal,
315 std::function<void()>&& signalStop)
316 : setup_(setup), app_(app), j_(journal), signalStop_(std::move(signalStop))
317{
318 openLog();
319}
320
322{
323 stop();
324}
325
326void
327PerfLogImp::rpcStart(std::string const& method, std::uint64_t const requestId)
328{
329 auto counter = counters_.rpc_.find(method);
330 if (counter == counters_.rpc_.end())
331 {
332 UNREACHABLE("ripple::perf::PerfLogImp::rpcStart : valid method input");
333 return;
334 }
335
336 {
337 std::lock_guard lock(counter->second.mutex);
338 ++counter->second.value.started;
339 }
341 counters_.methods_[requestId] = {
342 counter->first.c_str(), steady_clock::now()};
343}
344
345void
347 std::string const& method,
348 std::uint64_t const requestId,
349 bool finish)
350{
351 auto counter = counters_.rpc_.find(method);
352 if (counter == counters_.rpc_.end())
353 {
354 UNREACHABLE("ripple::perf::PerfLogImp::rpcEnd : valid method input");
355 return;
356 }
357 steady_time_point startTime;
358 {
360 auto const e = counters_.methods_.find(requestId);
361 if (e != counters_.methods_.end())
362 {
363 startTime = e->second.second;
364 counters_.methods_.erase(e);
365 }
366 else
367 {
368 UNREACHABLE(
369 "ripple::perf::PerfLogImp::rpcEnd : valid requestId input");
370 }
371 }
372 std::lock_guard lock(counter->second.mutex);
373 if (finish)
374 ++counter->second.value.finished;
375 else
376 ++counter->second.value.errored;
377 counter->second.value.duration += std::chrono::duration_cast<microseconds>(
378 steady_clock::now() - startTime);
379}
380
381void
383{
384 auto counter = counters_.jq_.find(type);
385 if (counter == counters_.jq_.end())
386 {
387 UNREACHABLE(
388 "ripple::perf::PerfLogImp::jobQueue : valid job type input");
389 return;
390 }
391 std::lock_guard lock(counter->second.mutex);
392 ++counter->second.value.queued;
393}
394
395void
397 JobType const type,
398 microseconds dur,
399 steady_time_point startTime,
400 int instance)
401{
402 auto counter = counters_.jq_.find(type);
403 if (counter == counters_.jq_.end())
404 {
405 UNREACHABLE(
406 "ripple::perf::PerfLogImp::jobStart : valid job type input");
407 return;
408 }
409 {
410 std::lock_guard lock(counter->second.mutex);
411 ++counter->second.value.started;
412 counter->second.value.queuedDuration += dur;
413 }
415 if (instance >= 0 && instance < counters_.jobs_.size())
416 counters_.jobs_[instance] = {type, startTime};
417}
418
419void
420PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
421{
422 auto counter = counters_.jq_.find(type);
423 if (counter == counters_.jq_.end())
424 {
425 UNREACHABLE(
426 "ripple::perf::PerfLogImp::jobFinish : valid job type input");
427 return;
428 }
429 {
430 std::lock_guard lock(counter->second.mutex);
431 ++counter->second.value.finished;
432 counter->second.value.runningDuration += dur;
433 }
435 if (instance >= 0 && instance < counters_.jobs_.size())
437}
438
439void
440PerfLogImp::resizeJobs(int const resize)
441{
443 if (resize > counters_.jobs_.size())
445}
446
447void
449{
450 if (setup_.perfLog.empty())
451 return;
452
454 rotate_ = true;
456}
457
458void
460{
461 if (setup_.perfLog.size())
463}
464
465void
467{
468 if (thread_.joinable())
469 {
470 {
472 stop_ = true;
474 }
475 thread_.join();
476 }
477}
478
479//-----------------------------------------------------------------------------
480
482setup_PerfLog(Section const& section, boost::filesystem::path const& configDir)
483{
484 PerfLog::Setup setup;
485 std::string perfLog;
486 set(perfLog, "perf_log", section);
487 if (perfLog.size())
488 {
489 setup.perfLog = boost::filesystem::path(perfLog);
490 if (setup.perfLog.is_relative())
491 {
492 setup.perfLog =
493 boost::filesystem::absolute(setup.perfLog, configDir);
494 }
495 }
496
497 std::uint64_t logInterval;
498 if (get_if_exists(section, "log_interval", logInterval))
499 setup.logInterval = std::chrono::seconds(logInterval);
500 return setup;
501}
502
505 PerfLog::Setup const& setup,
506 Application& app,
507 beast::Journal journal,
508 std::function<void()>&& signalStop)
509{
511 setup, app, journal, std::move(signalStop));
512}
513
514} // namespace perf
515} // 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)