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