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