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