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