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