rippled
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 <ripple/basics/BasicConfig.h>
21 #include <ripple/basics/impl/PerfLogImp.h>
22 #include <ripple/beast/core/CurrentThreadName.h>
23 #include <ripple/beast/utility/Journal.h>
24 #include <ripple/core/JobTypes.h>
25 #include <ripple/json/json_writer.h>
26 #include <ripple/json/to_string.h>
27 #include <ripple/nodestore/DatabaseShard.h>
28 #include <atomic>
29 #include <cstdint>
30 #include <cstdlib>
31 #include <iostream>
32 #include <iterator>
33 #include <mutex>
34 #include <optional>
35 #include <sstream>
36 #include <stdexcept>
37 #include <string>
38 #include <unordered_map>
39 #include <utility>
40 
41 namespace ripple {
42 namespace perf {
43 
45  std::vector<char const*> const& labels,
46  JobTypes const& jobTypes)
47 {
48  {
49  // populateRpc
50  rpc_.reserve(labels.size());
51  for (std::string const label : labels)
52  {
53  auto const inserted = rpc_.emplace(label, Rpc()).second;
54  if (!inserted)
55  {
56  // Ensure that no other function populates this entry.
57  assert(false);
58  }
59  }
60  }
61  {
62  // populateJq
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  // Ensure that no other function populates this entry.
70  assert(false);
71  }
72  }
73  }
74 }
75 
78 {
80  // totalRpc represents all rpc methods. All that started, finished, etc.
81  Rpc totalRpc;
82  for (auto const& proc : rpc_)
83  {
84  Rpc value;
85  {
86  std::lock_guard lock(proc.second.mutex);
87  if (!proc.second.value.started && !proc.second.value.finished &&
88  !proc.second.value.errored)
89  {
90  continue;
91  }
92  value = proc.second.value;
93  }
94 
96  p[jss::started] = std::to_string(value.started);
97  totalRpc.started += value.started;
98  p[jss::finished] = std::to_string(value.finished);
99  totalRpc.finished += value.finished;
100  p[jss::errored] = std::to_string(value.errored);
101  totalRpc.errored += value.errored;
102  p[jss::duration_us] = std::to_string(value.duration.count());
103  totalRpc.duration += value.duration;
104  rpcobj[proc.first] = p;
105  }
106 
107  if (totalRpc.started)
108  {
109  Json::Value totalRpcJson(Json::objectValue);
110  totalRpcJson[jss::started] = std::to_string(totalRpc.started);
111  totalRpcJson[jss::finished] = std::to_string(totalRpc.finished);
112  totalRpcJson[jss::errored] = std::to_string(totalRpc.errored);
113  totalRpcJson[jss::duration_us] =
114  std::to_string(totalRpc.duration.count());
115  rpcobj[jss::total] = totalRpcJson;
116  }
117 
119  // totalJq represents all jobs. All enqueued, started, finished, etc.
120  Jq totalJq;
121  for (auto const& proc : jq_)
122  {
123  Jq value;
124  {
125  std::lock_guard lock(proc.second.mutex);
126  if (!proc.second.value.queued && !proc.second.value.started &&
127  !proc.second.value.finished)
128  {
129  continue;
130  }
131  value = proc.second.value;
132  }
133 
135  j[jss::queued] = std::to_string(value.queued);
136  totalJq.queued += value.queued;
137  j[jss::started] = std::to_string(value.started);
138  totalJq.started += value.started;
139  j[jss::finished] = std::to_string(value.finished);
140  totalJq.finished += value.finished;
141  j[jss::queued_duration_us] =
143  totalJq.queuedDuration += value.queuedDuration;
144  j[jss::running_duration_us] =
146  totalJq.runningDuration += value.runningDuration;
147  jqobj[JobTypes::name(proc.first)] = j;
148  }
149 
150  if (totalJq.queued)
151  {
152  Json::Value totalJqJson(Json::objectValue);
153  totalJqJson[jss::queued] = std::to_string(totalJq.queued);
154  totalJqJson[jss::started] = std::to_string(totalJq.started);
155  totalJqJson[jss::finished] = std::to_string(totalJq.finished);
156  totalJqJson[jss::queued_duration_us] =
158  totalJqJson[jss::running_duration_us] =
160  jqobj[jss::total] = totalJqJson;
161  }
162 
163  Json::Value counters(Json::objectValue);
164  // Be kind to reporting tools and let them expect rpc and jq objects
165  // even if empty.
166  counters[jss::rpc] = rpcobj;
167  counters[jss::job_queue] = jqobj;
168  return counters;
169 }
170 
173 {
174  auto const present = steady_clock::now();
175 
176  Json::Value jobsArray(Json::arrayValue);
177  auto const jobs = [this] {
178  std::lock_guard lock(jobsMutex_);
179  return jobs_;
180  }();
181 
182  for (auto const& j : jobs)
183  {
184  if (j.first == jtINVALID)
185  continue;
187  jobj[jss::job] = JobTypes::name(j.first);
188  jobj[jss::duration_us] = std::to_string(
189  std::chrono::duration_cast<microseconds>(present - j.second)
190  .count());
191  jobsArray.append(jobj);
192  }
193 
194  Json::Value methodsArray(Json::arrayValue);
195  std::vector<MethodStart> methods;
196  {
197  std::lock_guard lock(methodsMutex_);
198  methods.reserve(methods_.size());
199  for (auto const& m : methods_)
200  methods.push_back(m.second);
201  }
202  for (auto m : methods)
203  {
204  Json::Value methodobj(Json::objectValue);
205  methodobj[jss::method] = m.first;
206  methodobj[jss::duration_us] = std::to_string(
207  std::chrono::duration_cast<microseconds>(present - m.second)
208  .count());
209  methodsArray.append(methodobj);
210  }
211 
213  current[jss::jobs] = jobsArray;
214  current[jss::methods] = methodsArray;
215  return current;
216 }
217 
218 //-----------------------------------------------------------------------------
219 
220 void
222 {
223  if (setup_.perfLog.empty())
224  return;
225 
226  if (logFile_.is_open())
227  logFile_.close();
228 
229  auto logDir = setup_.perfLog.parent_path();
230  if (!boost::filesystem::is_directory(logDir))
231  {
232  boost::system::error_code ec;
233  boost::filesystem::create_directories(logDir, ec);
234  if (ec)
235  {
236  JLOG(j_.fatal()) << "Unable to create performance log "
237  "directory "
238  << logDir << ": " << ec.message();
239  signalStop_();
240  return;
241  }
242  }
243 
244  logFile_.open(setup_.perfLog.c_str(), std::ios::out | std::ios::app);
245 
246  if (!logFile_)
247  {
248  JLOG(j_.fatal()) << "Unable to open performance log " << setup_.perfLog
249  << ".";
250  signalStop_();
251  }
252 }
253 
254 void
256 {
257  beast::setCurrentThreadName("perflog");
259 
260  while (true)
261  {
262  {
264  if (cond_.wait_until(
265  lock, lastLog_ + setup_.logInterval, [&] { return stop_; }))
266  {
267  return;
268  }
269  if (rotate_)
270  {
271  openLog();
272  rotate_ = false;
273  }
274  }
275  report();
276  }
277 }
278 
279 void
281 {
282  if (!logFile_)
283  // If logFile_ is not writable do no further work.
284  return;
285 
286  auto const present = system_clock::now();
287  if (present < lastLog_ + setup_.logInterval)
288  return;
289  lastLog_ = present;
290 
292  report[jss::time] = to_string(std::chrono::floor<microseconds>(present));
293  {
295  report[jss::workers] =
296  static_cast<unsigned int>(counters_.jobs_.size());
297  }
298  report[jss::hostid] = hostname_;
299  report[jss::counters] = counters_.countersJson();
300  report[jss::nodestore] = Json::objectValue;
301  if (app_.getShardStore())
302  app_.getShardStore()->getCountsJson(report[jss::nodestore]);
303  else
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 
326 void
327 PerfLogImp::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  assert(false);
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 
345 void
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  assert(false);
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  assert(false);
369  }
370  }
371  std::lock_guard lock(counter->second.mutex);
372  if (finish)
373  ++counter->second.value.finished;
374  else
375  ++counter->second.value.errored;
376  counter->second.value.duration += std::chrono::duration_cast<microseconds>(
377  steady_clock::now() - startTime);
378 }
379 
380 void
382 {
383  auto counter = counters_.jq_.find(type);
384  if (counter == counters_.jq_.end())
385  {
386  assert(false);
387  return;
388  }
389  std::lock_guard lock(counter->second.mutex);
390  ++counter->second.value.queued;
391 }
392 
393 void
395  JobType const type,
396  microseconds dur,
397  steady_time_point startTime,
398  int instance)
399 {
400  auto counter = counters_.jq_.find(type);
401  if (counter == counters_.jq_.end())
402  {
403  assert(false);
404  return;
405  }
406  {
407  std::lock_guard lock(counter->second.mutex);
408  ++counter->second.value.started;
409  counter->second.value.queuedDuration += dur;
410  }
412  if (instance >= 0 && instance < counters_.jobs_.size())
413  counters_.jobs_[instance] = {type, startTime};
414 }
415 
416 void
417 PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
418 {
419  auto counter = counters_.jq_.find(type);
420  if (counter == counters_.jq_.end())
421  {
422  assert(false);
423  return;
424  }
425  {
426  std::lock_guard lock(counter->second.mutex);
427  ++counter->second.value.finished;
428  counter->second.value.runningDuration += dur;
429  }
431  if (instance >= 0 && instance < counters_.jobs_.size())
432  counters_.jobs_[instance] = {jtINVALID, steady_time_point()};
433 }
434 
435 void
436 PerfLogImp::resizeJobs(int const resize)
437 {
439  if (resize > counters_.jobs_.size())
441 }
442 
443 void
445 {
446  if (setup_.perfLog.empty())
447  return;
448 
449  std::lock_guard lock(mutex_);
450  rotate_ = true;
451  cond_.notify_one();
452 }
453 
454 void
456 {
457  if (setup_.perfLog.size())
459 }
460 
461 void
463 {
464  if (thread_.joinable())
465  {
466  {
467  std::lock_guard lock(mutex_);
468  stop_ = true;
469  cond_.notify_one();
470  }
471  thread_.join();
472  }
473 }
474 
475 //-----------------------------------------------------------------------------
476 
478 setup_PerfLog(Section const& section, boost::filesystem::path const& configDir)
479 {
480  PerfLog::Setup setup;
481  std::string perfLog;
482  set(perfLog, "perf_log", section);
483  if (perfLog.size())
484  {
485  setup.perfLog = boost::filesystem::path(perfLog);
486  if (setup.perfLog.is_relative())
487  {
488  setup.perfLog =
489  boost::filesystem::absolute(setup.perfLog, configDir);
490  }
491  }
492 
493  std::uint64_t logInterval;
494  if (get_if_exists(section, "log_interval", logInterval))
495  setup.logInterval = std::chrono::seconds(logInterval);
496  return setup;
497 }
498 
501  PerfLog::Setup const& setup,
502  Application& app,
503  beast::Journal journal,
504  std::function<void()>&& signalStop)
505 {
506  return std::make_unique<PerfLogImp>(
507  setup, app, journal, std::move(signalStop));
508 }
509 
510 } // namespace perf
511 } // namespace ripple
beast::Journal::fatal
Stream fatal() const
Definition: Journal.h:339
ripple::Section
Holds a collection of configuration values.
Definition: BasicConfig.h:42
ripple::Application
Definition: Application.h:115
sstream
std::vector::resize
T resize(T... args)
ripple::perf::PerfLogImp::Counters::Jq::runningDuration
microseconds runningDuration
Definition: PerfLogImp.h:104
ripple::perf::PerfLogImp::Counters::Rpc::started
std::uint64_t started
Definition: PerfLogImp.h:85
std::string
STL class.
ripple::perf::PerfLogImp::Counters::Jq::finished
std::uint64_t finished
Definition: PerfLogImp.h:101
ripple::perf::PerfLogImp::lastLog_
system_time_point lastLog_
Definition: PerfLogImp.h:134
utility
ripple::JobTypes
Definition: JobTypes.h:32
ripple::perf::PerfLogImp::Counters::Jq::queued
std::uint64_t queued
Definition: PerfLogImp.h:99
ripple::perf::PerfLogImp::openLog
void openLog()
Definition: PerfLogImp.cpp:221
ripple::perf::PerfLog::Setup::perfLog
boost::filesystem::path perfLog
Definition: PerfLog.h:64
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::perf::PerfLogImp::Counters::jobsMutex_
std::mutex jobsMutex_
Definition: PerfLogImp.h:112
std::vector::reserve
T reserve(T... args)
ripple::perf::PerfLogImp::PerfLogImp
PerfLogImp(Setup const &setup, Application &app, beast::Journal journal, std::function< void()> &&signalStop)
Definition: PerfLogImp.cpp:311
Json::Compact
Decorator for streaming out compact json.
Definition: json_writer.h:316
ripple::perf::PerfLogImp::Counters::methodsMutex_
std::mutex methodsMutex_
Definition: PerfLogImp.h:114
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::perf::PerfLogImp::jobFinish
void jobFinish(JobType const type, microseconds dur, int instance) override
Log job finishing.
Definition: PerfLogImp.cpp:417
ripple::perf::PerfLogImp::j_
const beast::Journal j_
Definition: PerfLogImp.h:127
ripple::perf::PerfLogImp::rotate
void rotate() override
Rotate perf log file.
Definition: PerfLogImp.cpp:444
std::chrono::microseconds
ripple::perf::PerfLogImp::cond_
std::condition_variable cond_
Definition: PerfLogImp.h:133
iterator
ripple::perf::PerfLogImp::Counters::Rpc::finished
std::uint64_t finished
Definition: PerfLogImp.h:86
ripple::perf::make_PerfLog
std::unique_ptr< PerfLog > make_PerfLog(PerfLog::Setup const &setup, Application &app, beast::Journal journal, std::function< void()> &&signalStop)
Definition: PerfLogImp.cpp:500
std::lock_guard
STL class.
ripple::perf::PerfLogImp::Counters::Jq::started
std::uint64_t started
Definition: PerfLogImp.h:100
ripple::Application::getShardStore
virtual NodeStore::DatabaseShard * getShardStore()=0
ripple::perf::PerfLogImp::Counters::methods_
std::unordered_map< std::uint64_t, MethodStart > methods_
Definition: PerfLogImp.h:113
std::function
ripple::perf::PerfLogImp::Counters::Rpc::errored
std::uint64_t errored
Definition: PerfLogImp.h:87
iostream
ripple::NetworkOPs::stateAccounting
virtual void stateAccounting(Json::Value &obj)=0
ripple::perf::PerfLogImp::hostname_
const std::string hostname_
Definition: PerfLogImp.h:135
ripple::Application::getOPs
virtual NetworkOPs & getOPs()=0
ripple::perf::PerfLog::Setup
Configuration from [perf] section of rippled.cfg.
Definition: PerfLog.h:62
ripple::perf::PerfLogImp::jobStart
void jobStart(JobType const type, microseconds dur, steady_time_point startTime, int instance) override
Log job executing.
Definition: PerfLogImp.cpp:394
ripple::get_if_exists
bool get_if_exists(Section const &section, std::string const &name, T &v)
Definition: BasicConfig.h:384
std::vector::push_back
T push_back(T... args)
stdexcept
ripple::perf::PerfLogImp::rotate_
bool rotate_
Definition: PerfLogImp.h:137
std::thread::joinable
T joinable(T... args)
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
std::thread
STL class.
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::perf::PerfLogImp::mutex_
std::mutex mutex_
Definition: PerfLogImp.h:132
ripple::perf::PerfLogImp::Counters::jobs_
std::vector< std::pair< JobType, steady_time_point > > jobs_
Definition: PerfLogImp.h:111
ripple::perf::PerfLogImp::Counters::jq_
std::unordered_map< JobType, Locked< Jq > > jq_
Definition: PerfLogImp.h:110
std::unique_lock
STL class.
std::to_string
T to_string(T... args)
ripple::jtINVALID
@ jtINVALID
Definition: Job.h:36
ripple::set
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,...
Definition: BasicConfig.h:313
ripple::JobTypes::name
static std::string const & name(JobType jt)
Definition: JobTypes.h:131
std::ofstream::close
T close(T... args)
std::chrono::time_point
cstdint
ripple::perf::PerfLogImp::thread_
std::thread thread_
Definition: PerfLogImp.h:131
ripple::ValStatus::current
@ current
This was a new validation and was added.
std::ofstream::open
T open(T... args)
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint64_t
atomic
ripple::perf::PerfLogImp::signalStop_
const std::function< void()> signalStop_
Definition: PerfLogImp.h:128
ripple::perf::PerfLogImp::Counters::rpc_
std::unordered_map< std::string, Locked< Rpc > > rpc_
Definition: PerfLogImp.h:109
ripple::perf::PerfLogImp::Counters::Jq
Job Queue task performance counters.
Definition: PerfLogImp.h:95
ripple::perf::PerfLogImp::~PerfLogImp
~PerfLogImp() override
Definition: PerfLogImp.cpp:321
std::condition_variable::notify_one
T notify_one(T... args)
ripple::perf::setup_PerfLog
PerfLog::Setup setup_PerfLog(Section const &section, boost::filesystem::path const &configDir)
Definition: PerfLogImp.cpp:478
ripple::perf::PerfLogImp::Counters::Jq::queuedDuration
microseconds queuedDuration
Definition: PerfLogImp.h:103
beast::setCurrentThreadName
void setCurrentThreadName(std::string_view name)
Changes the name of the caller thread.
Definition: CurrentThreadName.cpp:119
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::perf::PerfLog::steady_time_point
std::chrono::time_point< steady_clock > steady_time_point
Definition: PerfLog.h:53
ripple::Application::getNodeStore
virtual NodeStore::Database & getNodeStore()=0
cstdlib
std::endl
T endl(T... args)
ripple::perf::PerfLogImp::stop_
bool stop_
Definition: PerfLogImp.h:136
ripple::perf::PerfLogImp::Counters::Rpc
RPC performance counters.
Definition: PerfLogImp.h:81
std::condition_variable::wait_until
T wait_until(T... args)
std
STL namespace.
ripple::perf::PerfLogImp::rpcEnd
void rpcEnd(std::string const &method, std::uint64_t const requestId, bool finish)
Definition: PerfLogImp.cpp:346
ripple::perf::PerfLogImp::rpcStart
void rpcStart(std::string const &method, std::uint64_t const requestId) override
Log start of RPC call.
Definition: PerfLogImp.cpp:327
ripple::NodeStore::Database::getCountsJson
void getCountsJson(Json::Value &obj)
Definition: Database.cpp:331
ripple::perf::PerfLogImp::Counters::currentJson
Json::Value currentJson() const
Definition: PerfLogImp.cpp:172
std::chrono::microseconds::count
T count(T... args)
ripple::perf::PerfLogImp::run
void run()
Definition: PerfLogImp.cpp:255
optional
mutex
ripple::JobType
JobType
Definition: Job.h:34
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:38
ripple::perf::PerfLogImp::setup_
const Setup setup_
Definition: PerfLogImp.h:125
ripple::perf::PerfLogImp::stop
void stop() override
Definition: PerfLogImp.cpp:462
ripple::perf::PerfLogImp::app_
Application & app_
Definition: PerfLogImp.h:126
ripple::perf::PerfLogImp::Counters::Counters
Counters(std::vector< char const * > const &labels, JobTypes const &jobTypes)
Definition: PerfLogImp.cpp:44
ripple::perf::PerfLogImp::logFile_
std::ofstream logFile_
Definition: PerfLogImp.h:130
ripple::perf::PerfLogImp::counters_
Counters counters_
Definition: PerfLogImp.h:129
std::unique_ptr
STL class.
ripple::perf::PerfLogImp::Counters::Rpc::duration
microseconds duration
Definition: PerfLogImp.h:89
ripple::perf::PerfLogImp::jobQueue
void jobQueue(JobType const type) override
Log queued job.
Definition: PerfLogImp.cpp:381
unordered_map
ripple::perf::PerfLog::Setup::logInterval
milliseconds logInterval
Definition: PerfLog.h:66
ripple::perf::PerfLogImp::start
void start() override
Definition: PerfLogImp.cpp:455
ripple::JobTypes::size
Map::size_type size() const
Definition: JobTypes.h:155
std::ofstream::is_open
T is_open(T... args)
ripple::perf::PerfLogImp::resizeJobs
void resizeJobs(int const resize) override
Ensure enough room to store each currently executing job.
Definition: PerfLogImp.cpp:436
ripple::perf::PerfLogImp::Counters::countersJson
Json::Value countersJson() const
Definition: PerfLogImp.cpp:77
ripple::perf::PerfLogImp::report
void report()
Definition: PerfLogImp.cpp:280
std::thread::join
T join(T... args)
Json::Value
Represents a JSON value.
Definition: json_value.h:145
string
std::chrono::steady_clock::now
T now(T... args)