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