rippled
PerfLog_test.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/PerfLog.h>
21 #include <ripple/basics/random.h>
22 #include <ripple/beast/unit_test.h>
23 #include <ripple/json/json_reader.h>
24 #include <ripple/protocol/jss.h>
25 #include <ripple/rpc/impl/Handler.h>
26 #include <atomic>
27 #include <chrono>
28 #include <random>
29 #include <string>
30 #include <test/jtx/Env.h>
31 #include <thread>
32 
33 //------------------------------------------------------------------------------
34 
35 namespace ripple {
36 
37 class PerfLog_test : public beast::unit_test::suite
38 {
39  enum class WithFile : bool { no = false, yes = true };
40 
41  using path = boost::filesystem::path;
42 
43  // We're only using Env for its Journal. That Journal gives better
44  // coverage in unit tests.
46  beast::Journal j_{env_.app().journal("PerfLog_test")};
47 
48  // A PerfLog needs a Parent that is a Stoppable and a function to
49  // call if it wants to shutdown the system. This class provides both.
50  struct PerfLogParent : public RootStoppable
51  {
52  bool stopSignaled{false};
54 
55  explicit PerfLogParent(beast::Journal const& j)
56  : RootStoppable("testRootStoppable"), j_(j)
57  {
58  }
59 
60  ~PerfLogParent() override
61  {
62  doStop();
64  }
65 
66  // Benign replacement for Application::signalStop().
67  void
69  {
70  stopSignaled = true;
71  }
72 
73  private:
74  // Interfaces for RootStoppable. Made private to encourage the use
75  // of doStart() and doStop().
76  void
77  onPrepare() override
78  {
79  }
80 
81  void
82  onStart() override
83  {
84  }
85 
86  void
87  onStop() override
88  {
89  if (areChildrenStopped())
90  stopped();
91  }
92 
93  void
94  onChildrenStopped() override
95  {
96  onStop();
97  }
98 
99  public:
100  // Test interfaces to start and stop the PerfLog
101  void
103  {
104  start();
105  }
106 
107  void
109  {
110  if (started())
111  stop(j_);
112  }
113 
114  // Interfaces for PerfLog file management
115  static path
117  {
118  using namespace boost::filesystem;
119  return temp_directory_path() / "perf_log_test_dir";
120  }
121 
122  static path
124  {
125  return {"perf_log.txt"};
126  }
127 
130  {
131  return std::chrono::milliseconds{10};
132  }
133 
134  static perf::PerfLog::Setup
135  getSetup(WithFile withFile)
136  {
137  return perf::PerfLog::Setup{
138  withFile == WithFile::no
139  ? ""
141  getLogInterval()};
142  }
143 
144  static void
146  {
147  using namespace boost::filesystem;
148 
149  auto const perfLogPath{getPerfLogPath()};
150  auto const fullPath = perfLogPath / getPerfLogFileName();
151  if (exists(fullPath))
152  remove(fullPath);
153 
154  if (!exists(perfLogPath) || !is_directory(perfLogPath) ||
155  !is_empty(perfLogPath))
156  {
157  return;
158  }
159  remove(perfLogPath);
160  }
161  };
162 
163  //------------------------------------------------------------------------------
164 
165  // Convenience function to return a PerfLog
167  getPerfLog(PerfLogParent& parent, WithFile withFile)
168  {
169  return perf::make_PerfLog(
170  parent.getSetup(withFile), parent, j_, [&parent]() {
171  return parent.signalStop();
172  });
173  }
174 
175  //------------------------------------------------------------------------------
176 
177  // Convenience function to return a uint64 given a Json::Value containing
178  // a string.
179  static std::uint64_t
180  jsonToUint64(Json::Value const& jsonUintAsString)
181  {
182  return std::stoull(jsonUintAsString.asString());
183  }
184 
185  //------------------------------------------------------------------------------
186 
187  // The PerfLog's current state is easier to sort by duration if the
188  // duration is converted from string to integer. The following struct
189  // is a way to think about the converted entry.
190  struct Cur
191  {
194 
195  Cur(std::uint64_t d, std::string n) : dur(d), name(std::move(n))
196  {
197  }
198  };
199 
200  // A convenience function to convert JSON to Cur and sort. The sort
201  // goes from longest to shortest duration. That way stuff that was started
202  // earlier goes to the front.
203  static std::vector<Cur>
204  getSortedCurrent(Json::Value const& currentJson)
205  {
206  std::vector<Cur> currents;
207  currents.reserve(currentJson.size());
208  for (Json::Value const& cur : currentJson)
209  {
210  currents.emplace_back(
211  jsonToUint64(cur[jss::duration_us]),
212  cur.isMember(jss::job) ? cur[jss::job].asString()
213  : cur[jss::method].asString());
214  }
215 
216  // Note that the longest durations should be at the front of the
217  // vector since they were started first.
218  std::sort(
219  currents.begin(),
220  currents.end(),
221  [](Cur const& lhs, Cur const& rhs) {
222  if (lhs.dur != rhs.dur)
223  return (rhs.dur < lhs.dur);
224  return (lhs.name < rhs.name);
225  });
226  return currents;
227  }
228 
229  //------------------------------------------------------------------------------
230 
231  // Helper function that checks the size of the PerfLog file and then
232  // returns when the file gets bigger. This indicates that the PerfLog
233  // has written new values to the file and _should_ have the latest
234  // update.
235  static void
237  {
238  using namespace boost::filesystem;
239 
240  auto const path = parent.getPerfLogPath() / parent.getPerfLogFileName();
241  if (!exists(path))
242  return;
243 
244  // We wait for the file to change size twice. The first file size
245  // change may have been in process while we arrived.
246  std::uintmax_t const firstSize{file_size(path)};
247  std::uintmax_t secondSize{firstSize};
248  do
249  {
251  secondSize = file_size(path);
252  } while (firstSize >= secondSize);
253 
254  do
255  {
257  } while (secondSize >= file_size(path));
258  }
259 
260  //------------------------------------------------------------------------------
261 
262 public:
263  void
265  {
266  using namespace boost::filesystem;
267 
268  auto const perfLogPath{PerfLogParent::getPerfLogPath()};
269  auto const fullPath = perfLogPath / PerfLogParent::getPerfLogFileName();
270  {
271  // Verify a PerfLog creates its file when constructed.
272  PerfLogParent parent{j_};
273  BEAST_EXPECT(!exists(perfLogPath));
274 
275  auto perfLog{getPerfLog(parent, WithFile::yes)};
276 
277  BEAST_EXPECT(parent.stopSignaled == false);
278  BEAST_EXPECT(exists(perfLogPath));
279  }
280  {
281  // Create a file where PerfLog wants to put its directory.
282  // Make sure that PerfLog tries to shutdown the server since it
283  // can't open its file.
284  PerfLogParent parent{j_};
285  if (!BEAST_EXPECT(!exists(perfLogPath)))
286  return;
287 
288  {
289  // Make a file that prevents PerfLog from creating its file.
290  std::ofstream nastyFile;
291  nastyFile.open(
292  perfLogPath.c_str(), std::ios::out | std::ios::app);
293  if (!BEAST_EXPECT(nastyFile))
294  return;
295  nastyFile.close();
296  }
297 
298  // Now construct a PerfLog. The PerfLog should attempt to shut
299  // down the server because it can't open its file.
300  BEAST_EXPECT(parent.stopSignaled == false);
301  auto perfLog{getPerfLog(parent, WithFile::yes)};
302  BEAST_EXPECT(parent.stopSignaled == true);
303 
304  // Start PerfLog and wait long enough for PerfLog::report()
305  // to not be able to write to its file. That should cause no
306  // problems.
307  parent.doStart();
308  std::this_thread::sleep_for(parent.getLogInterval() * 10);
309  parent.doStop();
310 
311  // Remove the file.
312  remove(perfLogPath);
313  }
314  {
315  // Put a write protected file where PerfLog wants to write its
316  // file. Make sure that PerfLog tries to shutdown the server
317  // since it can't open its file.
318  PerfLogParent parent{j_};
319  if (!BEAST_EXPECT(!exists(perfLogPath)))
320  return;
321 
322  // Construct and write protect a file to prevent PerfLog
323  // from creating its file.
324  boost::system::error_code ec;
325  boost::filesystem::create_directories(perfLogPath, ec);
326  if (!BEAST_EXPECT(!ec))
327  return;
328 
329  auto fileWriteable = [](boost::filesystem::path const& p) -> bool {
330  return std::ofstream{p.c_str(), std::ios::out | std::ios::app}
331  .is_open();
332  };
333 
334  if (!BEAST_EXPECT(fileWriteable(fullPath)))
335  return;
336 
337  boost::filesystem::permissions(
338  fullPath,
339  perms::remove_perms | perms::owner_write | perms::others_write |
340  perms::group_write);
341 
342  // If the test is running as root, then the write protect may have
343  // no effect. Make sure write protect worked before proceeding.
344  if (fileWriteable(fullPath))
345  {
346  log << "Unable to write protect file. Test skipped."
347  << std::endl;
348  return;
349  }
350 
351  // Now construct a PerfLog. The PerfLog should attempt to shut
352  // down the server because it can't open its file.
353  BEAST_EXPECT(parent.stopSignaled == false);
354  auto perfLog{getPerfLog(parent, WithFile::yes)};
355  BEAST_EXPECT(parent.stopSignaled == true);
356 
357  // Start PerfLog and wait long enough for PerfLog::report()
358  // to not be able to write to its file. That should cause no
359  // problems.
360  parent.doStart();
361  std::this_thread::sleep_for(parent.getLogInterval() * 10);
362  parent.doStop();
363 
364  // Fix file permissions so the file can be cleaned up.
365  boost::filesystem::permissions(
366  fullPath,
367  perms::add_perms | perms::owner_write | perms::others_write |
368  perms::group_write);
369  }
370  }
371 
372  void
373  testRPC(WithFile withFile)
374  {
375  // Exercise the rpc interfaces of PerfLog.
376  // Start up the PerfLog that we'll use for testing.
377  PerfLogParent parent{j_};
378  auto perfLog{getPerfLog(parent, withFile)};
379  parent.doStart();
380 
381  // Get the all the labels we can use for RPC interfaces without
382  // causing an assert.
384  std::shuffle(labels.begin(), labels.end(), default_prng());
385 
386  // Get two IDs to associate with each label. Errors tend to happen at
387  // boundaries, so we pick IDs starting from zero and ending at
388  // std::uint64_t>::max().
390  ids.reserve(labels.size() * 2);
392  std::back_inserter(ids),
393  labels.size(),
394  [i = std::numeric_limits<std::uint64_t>::min()]() mutable {
395  return i++;
396  });
398  std::back_inserter(ids),
399  labels.size(),
400  [i = std::numeric_limits<std::uint64_t>::max()]() mutable {
401  return i--;
402  });
403  std::shuffle(ids.begin(), ids.end(), default_prng());
404 
405  // Start all of the RPC commands twice to show they can all be tracked
406  // simultaneously.
407  for (int labelIndex = 0; labelIndex < labels.size(); ++labelIndex)
408  {
409  for (int idIndex = 0; idIndex < 2; ++idIndex)
410  {
412  perfLog->rpcStart(
413  labels[labelIndex], ids[(labelIndex * 2) + idIndex]);
414  }
415  }
416  {
417  // Examine current PerfLog::counterJson() values.
418  Json::Value const countersJson{perfLog->countersJson()["rpc"]};
419  BEAST_EXPECT(countersJson.size() == labels.size() + 1);
420  for (auto& label : labels)
421  {
422  // Expect every label in labels to have the same contents.
423  Json::Value const& counter{countersJson[label]};
424  BEAST_EXPECT(counter[jss::duration_us] == "0");
425  BEAST_EXPECT(counter[jss::errored] == "0");
426  BEAST_EXPECT(counter[jss::finished] == "0");
427  BEAST_EXPECT(counter[jss::started] == "2");
428  }
429  // Expect "total" to have a lot of "started"
430  Json::Value const& total{countersJson[jss::total]};
431  BEAST_EXPECT(total[jss::duration_us] == "0");
432  BEAST_EXPECT(total[jss::errored] == "0");
433  BEAST_EXPECT(total[jss::finished] == "0");
434  BEAST_EXPECT(jsonToUint64(total[jss::started]) == ids.size());
435  }
436  {
437  // Verify that every entry in labels appears twice in currents.
438  // If we sort by duration_us they should be in the order the
439  // rpcStart() call was made.
440  std::vector<Cur> const currents{
441  getSortedCurrent(perfLog->currentJson()[jss::methods])};
442  BEAST_EXPECT(currents.size() == labels.size() * 2);
443 
445  for (int i = 0; i < currents.size(); ++i)
446  {
447  BEAST_EXPECT(currents[i].name == labels[i / 2]);
448  BEAST_EXPECT(prevDur > currents[i].dur);
449  prevDur = currents[i].dur;
450  }
451  }
452 
453  // Finish all but the first RPC command in reverse order to show that
454  // the start and finish of the commands can interleave. Half of the
455  // commands finish correctly, the other half with errors.
456  for (int labelIndex = labels.size() - 1; labelIndex > 0; --labelIndex)
457  {
459  perfLog->rpcFinish(labels[labelIndex], ids[(labelIndex * 2) + 1]);
461  perfLog->rpcError(labels[labelIndex], ids[(labelIndex * 2) + 0]);
462  }
463  perfLog->rpcFinish(labels[0], ids[0 + 1]);
464  // Note that label[0] id[0] is intentionally left unfinished.
465 
466  auto validateFinalCounters = [this, &labels](
467  Json::Value const& countersJson) {
468  {
469  Json::Value const& jobQueue = countersJson[jss::job_queue];
470  BEAST_EXPECT(jobQueue.isObject());
471  BEAST_EXPECT(jobQueue.size() == 0);
472  }
473 
474  Json::Value const& rpc = countersJson[jss::rpc];
475  BEAST_EXPECT(rpc.size() == labels.size() + 1);
476 
477  // Verify that every entry in labels appears in rpc.
478  // If we access the entries by label we should be able to correlate
479  // their durations with the appropriate labels.
480  {
481  // The first label is special. It should have "errored" : "0".
482  Json::Value const& first = rpc[labels[0]];
483  BEAST_EXPECT(first[jss::duration_us] != "0");
484  BEAST_EXPECT(first[jss::errored] == "0");
485  BEAST_EXPECT(first[jss::finished] == "1");
486  BEAST_EXPECT(first[jss::started] == "2");
487  }
488 
489  // Check the rest of the labels.
491  for (int i = 1; i < labels.size(); ++i)
492  {
493  Json::Value const& counter{rpc[labels[i]]};
494  std::uint64_t const dur{
495  jsonToUint64(counter[jss::duration_us])};
496  BEAST_EXPECT(dur != 0 && dur < prevDur);
497  prevDur = dur;
498  BEAST_EXPECT(counter[jss::errored] == "1");
499  BEAST_EXPECT(counter[jss::finished] == "1");
500  BEAST_EXPECT(counter[jss::started] == "2");
501  }
502 
503  // Check "total"
504  Json::Value const& total{rpc[jss::total]};
505  BEAST_EXPECT(total[jss::duration_us] != "0");
506  BEAST_EXPECT(
507  jsonToUint64(total[jss::errored]) == labels.size() - 1);
508  BEAST_EXPECT(jsonToUint64(total[jss::finished]) == labels.size());
509  BEAST_EXPECT(
510  jsonToUint64(total[jss::started]) == labels.size() * 2);
511  };
512 
513  auto validateFinalCurrent = [this,
514  &labels](Json::Value const& currentJson) {
515  {
516  Json::Value const& job_queue = currentJson[jss::jobs];
517  BEAST_EXPECT(job_queue.isArray());
518  BEAST_EXPECT(job_queue.size() == 0);
519  }
520 
521  Json::Value const& methods = currentJson[jss::methods];
522  BEAST_EXPECT(methods.size() == 1);
523  BEAST_EXPECT(methods.isArray());
524 
525  Json::Value const& only = methods[0u];
526  BEAST_EXPECT(only.size() == 2);
527  BEAST_EXPECT(only.isObject());
528  BEAST_EXPECT(only[jss::duration_us] != "0");
529  BEAST_EXPECT(only[jss::method] == labels[0]);
530  };
531 
532  // Validate the final state of the PerfLog.
533  validateFinalCounters(perfLog->countersJson());
534  validateFinalCurrent(perfLog->currentJson());
535 
536  // Give the PerfLog enough time to flush it's state to the file.
537  waitForFileUpdate(parent);
538 
539  // Politely stop the PerfLog.
540  parent.doStop();
541 
542  auto const fullPath =
543  parent.getPerfLogPath() / parent.getPerfLogFileName();
544 
545  if (withFile == WithFile::no)
546  {
547  BEAST_EXPECT(!exists(fullPath));
548  }
549  else
550  {
551  // The last line in the log file should contain the same
552  // information that countersJson() and currentJson() returned.
553  // Verify that.
554 
555  // Get the last line of the log.
556  std::ifstream logStream(fullPath.c_str());
557  std::string lastLine;
558  for (std::string line; std::getline(logStream, line);)
559  {
560  if (!line.empty())
561  lastLine = std::move(line);
562  }
563 
564  Json::Value parsedLastLine;
565  Json::Reader().parse(lastLine, parsedLastLine);
566  if (!BEAST_EXPECT(!RPC::contains_error(parsedLastLine)))
567  // Avoid cascade of failures
568  return;
569 
570  // Validate the contents of the last line of the log.
571  validateFinalCounters(parsedLastLine[jss::counters]);
572  validateFinalCurrent(parsedLastLine[jss::current_activities]);
573  }
574  }
575 
576  void
577  testJobs(WithFile withFile)
578  {
579  using namespace std::chrono;
580 
581  // Exercise the jobs interfaces of PerfLog.
582  // Start up the PerfLog that we'll use for testing.
583  PerfLogParent parent{j_};
584  auto perfLog{getPerfLog(parent, withFile)};
585  parent.doStart();
586 
587  // Get the all the JobTypes we can use to call the jobs interfaces
588  // without causing an assert.
589  struct JobName
590  {
591  JobType type;
592  std::string typeName;
593 
594  JobName(JobType t, std::string name)
595  : type(t), typeName(std::move(name))
596  {
597  }
598  };
599 
601  {
602  auto const& jobTypes = JobTypes::instance();
603  jobs.reserve(jobTypes.size());
604  for (auto const& job : jobTypes)
605  {
606  jobs.emplace_back(job.first, job.second.name());
607  }
608  }
609  std::shuffle(jobs.begin(), jobs.end(), default_prng());
610 
611  // Walk through all of the jobs, enqueuing every job once. Check
612  // the jobs data with every addition.
613  for (int i = 0; i < jobs.size(); ++i)
614  {
615  perfLog->jobQueue(jobs[i].type);
616  Json::Value const jq_counters{
617  perfLog->countersJson()[jss::job_queue]};
618 
619  BEAST_EXPECT(jq_counters.size() == i + 2);
620  for (int j = 0; j <= i; ++j)
621  {
622  // Verify all expected counters are present and contain
623  // expected values.
624  Json::Value const& counter{jq_counters[jobs[j].typeName]};
625  BEAST_EXPECT(counter.size() == 5);
626  BEAST_EXPECT(counter[jss::queued] == "1");
627  BEAST_EXPECT(counter[jss::started] == "0");
628  BEAST_EXPECT(counter[jss::finished] == "0");
629  BEAST_EXPECT(counter[jss::queued_duration_us] == "0");
630  BEAST_EXPECT(counter[jss::running_duration_us] == "0");
631  }
632 
633  // Verify jss::total is present and has expected values.
634  Json::Value const& total{jq_counters[jss::total]};
635  BEAST_EXPECT(total.size() == 5);
636  BEAST_EXPECT(jsonToUint64(total[jss::queued]) == i + 1);
637  BEAST_EXPECT(total[jss::started] == "0");
638  BEAST_EXPECT(total[jss::finished] == "0");
639  BEAST_EXPECT(total[jss::queued_duration_us] == "0");
640  BEAST_EXPECT(total[jss::running_duration_us] == "0");
641  }
642 
643  // Even with jobs queued, the perfLog should report nothing current.
644  {
645  Json::Value current{perfLog->currentJson()};
646  BEAST_EXPECT(current.size() == 2);
647  BEAST_EXPECT(current.isMember(jss::jobs));
648  BEAST_EXPECT(current[jss::jobs].size() == 0);
649  BEAST_EXPECT(current.isMember(jss::methods));
650  BEAST_EXPECT(current[jss::methods].size() == 0);
651  }
652 
653  // Current jobs are tracked by Worker ID. Even though it's not
654  // realistic, crank up the number of workers so we can have many
655  // jobs in process simultaneously without problems.
656  perfLog->resizeJobs(jobs.size() * 2);
657 
658  // Start two instances of every job to show that the same job can run
659  // simultaneously (on different Worker threads). Admittedly, this
660  // will make the jss::queued count look a bit goofy since there will
661  // be half as many queued as started...
662  for (int i = 0; i < jobs.size(); ++i)
663  {
664  perfLog->jobStart(
665  jobs[i].type, microseconds{i + 1}, steady_clock::now(), i * 2);
667 
668  // Check each jobType counter entry.
669  Json::Value const jq_counters{
670  perfLog->countersJson()[jss::job_queue]};
671  for (int j = 0; j < jobs.size(); ++j)
672  {
673  Json::Value const& counter{jq_counters[jobs[j].typeName]};
674  std::uint64_t const queued_dur_us{
675  jsonToUint64(counter[jss::queued_duration_us])};
676  if (j < i)
677  {
678  BEAST_EXPECT(counter[jss::started] == "2");
679  BEAST_EXPECT(queued_dur_us == j + 1);
680  }
681  else if (j == i)
682  {
683  BEAST_EXPECT(counter[jss::started] == "1");
684  BEAST_EXPECT(queued_dur_us == j + 1);
685  }
686  else
687  {
688  BEAST_EXPECT(counter[jss::started] == "0");
689  BEAST_EXPECT(queued_dur_us == 0);
690  }
691 
692  BEAST_EXPECT(counter[jss::queued] == "1");
693  BEAST_EXPECT(counter[jss::finished] == "0");
694  BEAST_EXPECT(counter[jss::running_duration_us] == "0");
695  }
696  {
697  // Verify values in jss::total are what we expect.
698  Json::Value const& total{jq_counters[jss::total]};
699  BEAST_EXPECT(jsonToUint64(total[jss::queued]) == jobs.size());
700  BEAST_EXPECT(jsonToUint64(total[jss::started]) == (i * 2) + 1);
701  BEAST_EXPECT(total[jss::finished] == "0");
702 
703  // Total queued duration is triangle number of (i + 1).
704  BEAST_EXPECT(
705  jsonToUint64(total[jss::queued_duration_us]) ==
706  (((i * i) + 3 * i + 2) / 2));
707  BEAST_EXPECT(total[jss::running_duration_us] == "0");
708  }
709 
710  perfLog->jobStart(
711  jobs[i].type,
712  microseconds{0},
713  steady_clock::now(),
714  (i * 2) + 1);
716 
717  // Verify that every entry in jobs appears twice in currents.
718  // If we sort by duration_us they should be in the order the
719  // rpcStart() call was made.
720  std::vector<Cur> const currents{
721  getSortedCurrent(perfLog->currentJson()[jss::jobs])};
722  BEAST_EXPECT(currents.size() == (i + 1) * 2);
723 
725  for (int j = 0; j <= i; ++j)
726  {
727  BEAST_EXPECT(currents[j * 2].name == jobs[j].typeName);
728  BEAST_EXPECT(prevDur > currents[j * 2].dur);
729  prevDur = currents[j * 2].dur;
730 
731  BEAST_EXPECT(currents[(j * 2) + 1].name == jobs[j].typeName);
732  BEAST_EXPECT(prevDur > currents[(j * 2) + 1].dur);
733  prevDur = currents[(j * 2) + 1].dur;
734  }
735  }
736 
737  // Finish every job we started. Finish them in reverse.
738  for (int i = jobs.size() - 1; i >= 0; --i)
739  {
740  // A number of the computations in this loop care about the
741  // number of jobs that have finished. Make that available.
742  int const finished = ((jobs.size() - i) * 2) - 1;
743  perfLog->jobFinish(
744  jobs[i].type, microseconds(finished), (i * 2) + 1);
746 
747  Json::Value const jq_counters{
748  perfLog->countersJson()[jss::job_queue]};
749  for (int j = 0; j < jobs.size(); ++j)
750  {
751  Json::Value const& counter{jq_counters[jobs[j].typeName]};
752  std::uint64_t const running_dur_us{
753  jsonToUint64(counter[jss::running_duration_us])};
754  if (j < i)
755  {
756  BEAST_EXPECT(counter[jss::finished] == "0");
757  BEAST_EXPECT(running_dur_us == 0);
758  }
759  else if (j == i)
760  {
761  BEAST_EXPECT(counter[jss::finished] == "1");
762  BEAST_EXPECT(running_dur_us == ((jobs.size() - j) * 2) - 1);
763  }
764  else
765  {
766  BEAST_EXPECT(counter[jss::finished] == "2");
767  BEAST_EXPECT(running_dur_us == ((jobs.size() - j) * 4) - 1);
768  }
769 
770  std::uint64_t const queued_dur_us{
771  jsonToUint64(counter[jss::queued_duration_us])};
772  BEAST_EXPECT(queued_dur_us == j + 1);
773  BEAST_EXPECT(counter[jss::queued] == "1");
774  BEAST_EXPECT(counter[jss::started] == "2");
775  }
776  {
777  // Verify values in jss::total are what we expect.
778  Json::Value const& total{jq_counters[jss::total]};
779  BEAST_EXPECT(jsonToUint64(total[jss::queued]) == jobs.size());
780  BEAST_EXPECT(
781  jsonToUint64(total[jss::started]) == jobs.size() * 2);
782  BEAST_EXPECT(jsonToUint64(total[jss::finished]) == finished);
783 
784  // Total queued duration should be triangle number of
785  // jobs.size().
786  int const queuedDur = ((jobs.size() * (jobs.size() + 1)) / 2);
787  BEAST_EXPECT(
788  jsonToUint64(total[jss::queued_duration_us]) == queuedDur);
789 
790  // Total running duration should be triangle number of finished.
791  int const runningDur = ((finished * (finished + 1)) / 2);
792  BEAST_EXPECT(
793  jsonToUint64(total[jss::running_duration_us]) ==
794  runningDur);
795  }
796 
797  perfLog->jobFinish(
798  jobs[i].type, microseconds(finished + 1), (i * 2));
800 
801  // Verify that the two jobs we just finished no longer appear in
802  // currents.
803  std::vector<Cur> const currents{
804  getSortedCurrent(perfLog->currentJson()[jss::jobs])};
805  BEAST_EXPECT(currents.size() == i * 2);
806 
808  for (int j = 0; j < i; ++j)
809  {
810  BEAST_EXPECT(currents[j * 2].name == jobs[j].typeName);
811  BEAST_EXPECT(prevDur > currents[j * 2].dur);
812  prevDur = currents[j * 2].dur;
813 
814  BEAST_EXPECT(currents[(j * 2) + 1].name == jobs[j].typeName);
815  BEAST_EXPECT(prevDur > currents[(j * 2) + 1].dur);
816  prevDur = currents[(j * 2) + 1].dur;
817  }
818  }
819 
820  // Validate the final results.
821  auto validateFinalCounters = [this,
822  &jobs](Json::Value const& countersJson) {
823  {
824  Json::Value const& rpc = countersJson[jss::rpc];
825  BEAST_EXPECT(rpc.isObject());
826  BEAST_EXPECT(rpc.size() == 0);
827  }
828 
829  Json::Value const& jobQueue = countersJson[jss::job_queue];
830  for (int i = jobs.size() - 1; i >= 0; --i)
831  {
832  Json::Value const& counter{jobQueue[jobs[i].typeName]};
833  std::uint64_t const running_dur_us{
834  jsonToUint64(counter[jss::running_duration_us])};
835  BEAST_EXPECT(running_dur_us == ((jobs.size() - i) * 4) - 1);
836 
837  std::uint64_t const queued_dur_us{
838  jsonToUint64(counter[jss::queued_duration_us])};
839  BEAST_EXPECT(queued_dur_us == i + 1);
840 
841  BEAST_EXPECT(counter[jss::queued] == "1");
842  BEAST_EXPECT(counter[jss::started] == "2");
843  BEAST_EXPECT(counter[jss::finished] == "2");
844  }
845 
846  // Verify values in jss::total are what we expect.
847  Json::Value const& total{jobQueue[jss::total]};
848  const int finished = jobs.size() * 2;
849  BEAST_EXPECT(jsonToUint64(total[jss::queued]) == jobs.size());
850  BEAST_EXPECT(jsonToUint64(total[jss::started]) == finished);
851  BEAST_EXPECT(jsonToUint64(total[jss::finished]) == finished);
852 
853  // Total queued duration should be triangle number of
854  // jobs.size().
855  int const queuedDur = ((jobs.size() * (jobs.size() + 1)) / 2);
856  BEAST_EXPECT(
857  jsonToUint64(total[jss::queued_duration_us]) == queuedDur);
858 
859  // Total running duration should be triangle number of finished.
860  int const runningDur = ((finished * (finished + 1)) / 2);
861  BEAST_EXPECT(
862  jsonToUint64(total[jss::running_duration_us]) == runningDur);
863  };
864 
865  auto validateFinalCurrent = [this](Json::Value const& currentJson) {
866  {
867  Json::Value const& j = currentJson[jss::jobs];
868  BEAST_EXPECT(j.isArray());
869  BEAST_EXPECT(j.size() == 0);
870  }
871 
872  Json::Value const& methods = currentJson[jss::methods];
873  BEAST_EXPECT(methods.size() == 0);
874  BEAST_EXPECT(methods.isArray());
875  };
876 
877  // Validate the final state of the PerfLog.
878  validateFinalCounters(perfLog->countersJson());
879  validateFinalCurrent(perfLog->currentJson());
880 
881  // Give the PerfLog enough time to flush it's state to the file.
882  waitForFileUpdate(parent);
883 
884  // Politely stop the PerfLog.
885  parent.doStop();
886 
887  // Check file contents if that is appropriate.
888  auto const fullPath =
889  parent.getPerfLogPath() / parent.getPerfLogFileName();
890 
891  if (withFile == WithFile::no)
892  {
893  BEAST_EXPECT(!exists(fullPath));
894  }
895  else
896  {
897  // The last line in the log file should contain the same
898  // information that countersJson() and currentJson() returned.
899  // Verify that.
900 
901  // Get the last line of the log.
902  std::ifstream logStream(fullPath.c_str());
903  std::string lastLine;
904  for (std::string line; std::getline(logStream, line);)
905  {
906  if (!line.empty())
907  lastLine = std::move(line);
908  }
909 
910  Json::Value parsedLastLine;
911  Json::Reader().parse(lastLine, parsedLastLine);
912  if (!BEAST_EXPECT(!RPC::contains_error(parsedLastLine)))
913  // Avoid cascade of failures
914  return;
915 
916  // Validate the contents of the last line of the log.
917  validateFinalCounters(parsedLastLine[jss::counters]);
918  validateFinalCurrent(parsedLastLine[jss::current_activities]);
919  }
920  }
921 
922  void
924  {
925  using namespace std::chrono;
926 
927  // The Worker ID is used to identify jobs in progress. Show that
928  // the PerLog behaves as well as possible if an invalid ID is passed.
929 
930  // Start up the PerfLog that we'll use for testing.
931  PerfLogParent parent{j_};
932  auto perfLog{getPerfLog(parent, withFile)};
933  parent.doStart();
934 
935  // Randomly select a job type and its name.
936  JobType jobType;
937  std::string jobTypeName;
938  {
939  auto const& jobTypes = JobTypes::instance();
940 
941  std::uniform_int_distribution<> dis(0, jobTypes.size() - 1);
942  auto iter{jobTypes.begin()};
943  std::advance(iter, dis(default_prng()));
944 
945  jobType = iter->second.type();
946  jobTypeName = iter->second.name();
947  }
948 
949  // Say there's one worker thread.
950  perfLog->resizeJobs(1);
951 
952  // Lambda to validate countersJson for this test.
953  auto verifyCounters = [this, jobTypeName](
954  Json::Value const& countersJson,
955  int started,
956  int finished,
957  int queued_us,
958  int running_us) {
959  BEAST_EXPECT(countersJson.isObject());
960  BEAST_EXPECT(countersJson.size() == 2);
961 
962  BEAST_EXPECT(countersJson.isMember(jss::rpc));
963  BEAST_EXPECT(countersJson[jss::rpc].isObject());
964  BEAST_EXPECT(countersJson[jss::rpc].size() == 0);
965 
966  BEAST_EXPECT(countersJson.isMember(jss::job_queue));
967  BEAST_EXPECT(countersJson[jss::job_queue].isObject());
968  BEAST_EXPECT(countersJson[jss::job_queue].size() == 1);
969  {
970  Json::Value const& job{
971  countersJson[jss::job_queue][jobTypeName]};
972 
973  BEAST_EXPECT(job.isObject());
974  BEAST_EXPECT(jsonToUint64(job[jss::queued]) == 0);
975  BEAST_EXPECT(jsonToUint64(job[jss::started]) == started);
976  BEAST_EXPECT(jsonToUint64(job[jss::finished]) == finished);
977 
978  BEAST_EXPECT(
979  jsonToUint64(job[jss::queued_duration_us]) == queued_us);
980  BEAST_EXPECT(
981  jsonToUint64(job[jss::running_duration_us]) == running_us);
982  }
983  };
984 
985  // Lambda to validate currentJson (always empty) for this test.
986  auto verifyEmptyCurrent = [this](Json::Value const& currentJson) {
987  BEAST_EXPECT(currentJson.isObject());
988  BEAST_EXPECT(currentJson.size() == 2);
989 
990  BEAST_EXPECT(currentJson.isMember(jss::jobs));
991  BEAST_EXPECT(currentJson[jss::jobs].isArray());
992  BEAST_EXPECT(currentJson[jss::jobs].size() == 0);
993 
994  BEAST_EXPECT(currentJson.isMember(jss::methods));
995  BEAST_EXPECT(currentJson[jss::methods].isArray());
996  BEAST_EXPECT(currentJson[jss::methods].size() == 0);
997  };
998 
999  // Start an ID that's too large.
1000  perfLog->jobStart(jobType, microseconds{11}, steady_clock::now(), 2);
1002  verifyCounters(perfLog->countersJson(), 1, 0, 11, 0);
1003  verifyEmptyCurrent(perfLog->currentJson());
1004 
1005  // Start a negative ID
1006  perfLog->jobStart(jobType, microseconds{13}, steady_clock::now(), -1);
1008  verifyCounters(perfLog->countersJson(), 2, 0, 24, 0);
1009  verifyEmptyCurrent(perfLog->currentJson());
1010 
1011  // Finish the too large ID
1012  perfLog->jobFinish(jobType, microseconds{17}, 2);
1014  verifyCounters(perfLog->countersJson(), 2, 1, 24, 17);
1015  verifyEmptyCurrent(perfLog->currentJson());
1016 
1017  // Finish the negative ID
1018  perfLog->jobFinish(jobType, microseconds{19}, -1);
1020  verifyCounters(perfLog->countersJson(), 2, 2, 24, 36);
1021  verifyEmptyCurrent(perfLog->currentJson());
1022 
1023  // Give the PerfLog enough time to flush it's state to the file.
1024  waitForFileUpdate(parent);
1025 
1026  // Politely stop the PerfLog.
1027  parent.doStop();
1028 
1029  // Check file contents if that is appropriate.
1030  auto const fullPath =
1031  parent.getPerfLogPath() / parent.getPerfLogFileName();
1032 
1033  if (withFile == WithFile::no)
1034  {
1035  BEAST_EXPECT(!exists(fullPath));
1036  }
1037  else
1038  {
1039  // The last line in the log file should contain the same
1040  // information that countersJson() and currentJson() returned.
1041  // Verify that.
1042 
1043  // Get the last line of the log.
1044  std::ifstream logStream(fullPath.c_str());
1045  std::string lastLine;
1046  for (std::string line; std::getline(logStream, line);)
1047  {
1048  if (!line.empty())
1049  lastLine = std::move(line);
1050  }
1051 
1052  Json::Value parsedLastLine;
1053  Json::Reader().parse(lastLine, parsedLastLine);
1054  if (!BEAST_EXPECT(!RPC::contains_error(parsedLastLine)))
1055  // Avoid cascade of failures
1056  return;
1057 
1058  // Validate the contents of the last line of the log.
1059  verifyCounters(parsedLastLine[jss::counters], 2, 2, 24, 36);
1060  verifyEmptyCurrent(parsedLastLine[jss::current_activities]);
1061  }
1062  }
1063 
1064  void
1066  {
1067  // We can't fully test rotate because unit tests must run on Windows,
1068  // and Windows doesn't (may not?) support rotate. But at least call
1069  // the interface and see that it doesn't crash.
1070  using namespace boost::filesystem;
1071 
1072  auto const perfLogPath{PerfLogParent::getPerfLogPath()};
1073  auto const fullPath = perfLogPath / PerfLogParent::getPerfLogFileName();
1074 
1075  PerfLogParent parent{j_};
1076  BEAST_EXPECT(!exists(perfLogPath));
1077 
1078  auto perfLog{getPerfLog(parent, withFile)};
1079 
1080  BEAST_EXPECT(parent.stopSignaled == false);
1081  if (withFile == WithFile::no)
1082  {
1083  BEAST_EXPECT(!exists(perfLogPath));
1084  }
1085  else
1086  {
1087  BEAST_EXPECT(exists(fullPath));
1088  BEAST_EXPECT(file_size(fullPath) == 0);
1089  }
1090 
1091  // Start PerfLog and wait long enough for PerfLog::report()
1092  // to write to its file.
1093  parent.doStart();
1094  waitForFileUpdate(parent);
1095 
1096  decltype(file_size(fullPath)) firstFileSize{0};
1097  if (withFile == WithFile::no)
1098  {
1099  BEAST_EXPECT(!exists(perfLogPath));
1100  }
1101  else
1102  {
1103  firstFileSize = file_size(fullPath);
1104  BEAST_EXPECT(firstFileSize > 0);
1105  }
1106 
1107  // Rotate and then wait to make sure more stuff is written to the file.
1108  perfLog->rotate();
1109  waitForFileUpdate(parent);
1110 
1111  parent.doStop();
1112 
1113  if (withFile == WithFile::no)
1114  {
1115  BEAST_EXPECT(!exists(perfLogPath));
1116  }
1117  else
1118  {
1119  BEAST_EXPECT(file_size(fullPath) > firstFileSize);
1120  }
1121  }
1122 
1123  void
1124  run() override
1125  {
1126  testFileCreation();
1135  }
1136 };
1137 
1138 BEAST_DEFINE_TESTSUITE(PerfLog, basics, ripple);
1139 
1140 } // namespace ripple
ripple::PerfLog_test::PerfLogParent::cleanupPerfLogDir
static void cleanupPerfLogDir()
Definition: PerfLog_test.cpp:145
std::this_thread::sleep_for
T sleep_for(T... args)
std::generate_n
T generate_n(T... args)
Json::Value::isObject
bool isObject() const
Definition: json_value.cpp:1027
ripple::PerfLog_test::waitForFileUpdate
static void waitForFileUpdate(PerfLogParent const &parent)
Definition: PerfLog_test.cpp:236
std::string
STL class.
ripple::PerfLog_test::WithFile
WithFile
Definition: PerfLog_test.cpp:39
std::uniform_int_distribution
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::PerfLog_test::PerfLogParent::PerfLogParent
PerfLogParent(beast::Journal const &j)
Definition: PerfLog_test.cpp:55
std::stoull
T stoull(T... args)
ripple::Stoppable::stopped
void stopped()
Called by derived classes to indicate that the stoppable has stopped.
Definition: Stoppable.cpp:72
ripple::PerfLog_test::testRotate
void testRotate(WithFile withFile)
Definition: PerfLog_test.cpp:1065
ripple::PerfLog_test::PerfLogParent::onStop
void onStop() override
Override called when the stop notification is issued.
Definition: PerfLog_test.cpp:87
ripple::PerfLog_test::PerfLogParent::getLogInterval
static std::chrono::milliseconds getLogInterval()
Definition: PerfLog_test.cpp:129
std::vector::reserve
T reserve(T... args)
std::vector
STL class.
ripple::PerfLog_test::j_
beast::Journal j_
Definition: PerfLog_test.cpp:46
std::vector::size
T size(T... args)
ripple::PerfLog_test::testJobs
void testJobs(WithFile withFile)
Definition: PerfLog_test.cpp:577
std::back_inserter
T back_inserter(T... args)
std::chrono::milliseconds
ripple::PerfLog_test::PerfLogParent::getPerfLogFileName
static path getPerfLogFileName()
Definition: PerfLog_test.cpp:123
random
ripple::PerfLog_test::getSortedCurrent
static std::vector< Cur > getSortedCurrent(Json::Value const &currentJson)
Definition: PerfLog_test.cpp:204
ripple::PerfLog_test::PerfLogParent::stopSignaled
bool stopSignaled
Definition: PerfLog_test.cpp:52
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:240
ripple::PerfLog_test::jsonToUint64
static std::uint64_t jsonToUint64(Json::Value const &jsonUintAsString)
Definition: PerfLog_test.cpp:180
ripple::PerfLog_test::testFileCreation
void testFileCreation()
Definition: PerfLog_test.cpp:264
ripple::perf::make_PerfLog
std::unique_ptr< PerfLog > make_PerfLog(PerfLog::Setup const &setup, Stoppable &parent, beast::Journal journal, std::function< void()> &&signalStop)
Definition: PerfLogImp.cpp:513
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::PerfLog_test::Cur::dur
std::uint64_t dur
Definition: PerfLog_test.cpp:192
std::sort
T sort(T... args)
ripple::perf::PerfLog::Setup
Configuration from [perf] section of rippled.cfg.
Definition: PerfLog.h:60
ripple::PerfLog_test::PerfLogParent::getSetup
static perf::PerfLog::Setup getSetup(WithFile withFile)
Definition: PerfLog_test.cpp:135
ripple::PerfLog_test::Cur::Cur
Cur(std::uint64_t d, std::string n)
Definition: PerfLog_test.cpp:195
ripple::PerfLog_test::testRPC
void testRPC(WithFile withFile)
Definition: PerfLog_test.cpp:373
ripple::RootStoppable::stop
void stop(beast::Journal j)
Notify a root stoppable and children to stop, and block until stopped.
Definition: Stoppable.cpp:191
ripple::RootStoppable
Definition: Stoppable.h:354
thread
ripple::RPC::contains_error
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:225
ripple::PerfLog_test::Cur
Definition: PerfLog_test.cpp:190
ripple::PerfLog_test::path
boost::filesystem::path path
Definition: PerfLog_test.cpp:41
std::ofstream
STL class.
chrono
ripple::PerfLog_test::PerfLogParent::onStart
void onStart() override
Override called during start.
Definition: PerfLog_test.cpp:82
ripple::PerfLog_test::PerfLogParent::signalStop
void signalStop()
Definition: PerfLog_test.cpp:68
ripple::Stoppable::areChildrenStopped
bool areChildrenStopped() const
Returns true if all children have stopped.
Definition: Stoppable.cpp:66
ripple::PerfLog_test::run
void run() override
Definition: PerfLog_test.cpp:1124
ripple::JobTypes::instance
static JobTypes const & instance()
Definition: JobTypes.h:101
ripple::PerfLog_test::PerfLogParent::getPerfLogPath
static path getPerfLogPath()
Definition: PerfLog_test.cpp:116
std::ofstream::close
T close(T... args)
ripple::default_prng
beast::xor_shift_engine & default_prng()
Return the default random engine.
Definition: ripple/basics/random.h:65
ripple::PerfLog_test::PerfLogParent
Definition: PerfLog_test.cpp:50
Json::Value::size
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
ripple::ValStatus::current
@ current
This was a new validation and was added.
std::ofstream::open
T open(T... args)
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
ripple::PerfLog_test::WithFile::no
@ no
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::PerfLog_test::PerfLogParent::doStart
void doStart()
Definition: PerfLog_test.cpp:102
std::uint64_t
ripple::PerfLog_test
Definition: PerfLog_test.cpp:37
atomic
ripple::PerfLog_test::env_
test::jtx::Env env_
Definition: PerfLog_test.cpp:45
ripple::PerfLog_test::getPerfLog
std::unique_ptr< perf::PerfLog > getPerfLog(PerfLogParent &parent, WithFile withFile)
Definition: PerfLog_test.cpp:167
std::advance
T advance(T... args)
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1015
ripple::RootStoppable::start
void start()
Prepare and start all contained Stoppable objects.
Definition: Stoppable.cpp:181
std::vector::emplace_back
T emplace_back(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Application::journal
virtual beast::Journal journal(std::string const &name)=0
std::endl
T endl(T... args)
ripple::PerfLog_test::PerfLogParent::~PerfLogParent
~PerfLogParent() override
Definition: PerfLog_test.cpp:60
ripple::PerfLog_test::Cur::name
std::string name
Definition: PerfLog_test.cpp:193
std::vector::begin
T begin(T... args)
std::getline
T getline(T... args)
ripple::PerfLog_test::WithFile::yes
@ yes
std
STL namespace.
ripple::RootStoppable::started
bool started() const
Return true if start() was ever called.
Definition: Stoppable.h:387
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
ripple::PerfLog_test::PerfLogParent::onChildrenStopped
void onChildrenStopped() override
Override called when all children have stopped.
Definition: PerfLog_test.cpp:94
ripple::JobType
JobType
Definition: Job.h:33
ripple::PerfLog_test::testInvalidID
void testInvalidID(WithFile withFile)
Definition: PerfLog_test.cpp:923
std::vector::end
T end(T... args)
ripple::PerfLog_test::PerfLogParent::onPrepare
void onPrepare() override
Override called during preparation.
Definition: PerfLog_test.cpp:77
std::numeric_limits::max
T max(T... args)
ripple::RPC::getHandlerNames
std::vector< char const * > getHandlerNames()
Return names of all methods.
Definition: Handler.cpp:260
std::unique_ptr
STL class.
std::shuffle
T shuffle(T... args)
ripple::PerfLog_test::PerfLogParent::doStop
void doStop()
Definition: PerfLog_test.cpp:108
std::numeric_limits
std::ofstream::is_open
T is_open(T... args)
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
ripple::PerfLog_test::PerfLogParent::j_
beast::Journal j_
Definition: PerfLog_test.cpp:53
Json::Value
Represents a JSON value.
Definition: json_value.h:145
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
std::ifstream
STL class.
std::chrono
string