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