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