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