46 using steady_clock = std::chrono::steady_clock;
48 class LinuxProcStats final :
public ProfileTask
50 static constexpr
bool debug =
false;
53 explicit LinuxProcStats(JsonLogger& logger) : logger_(logger) {
55 sc_pagesize_ = sysconf(_SC_PAGESIZE);
57 file_stat_.open(
"/proc/stat");
58 file_net_dev_.open(
"/proc/net/dev");
59 file_diskstats_.open(
"/proc/diskstats");
60 file_meminfo_.open(
"/proc/meminfo");
62 pid_t mypid = getpid();
66 read_sys_block_devices();
70 void read_sys_block_devices();
73 static double perc(
unsigned long long prev,
unsigned long long curr,
74 unsigned long long base) {
78 return static_cast<double>(curr - prev)
79 / static_cast<double>(base) * 100.0;
83 JsonLine& prepare_out(JsonLine& out);
86 void read_stat(JsonLine& out);
89 void read_pid_stat(JsonLine& out);
92 void read_net_dev(
const steady_clock::time_point& tp, JsonLine& out);
95 void read_pid_io(
const steady_clock::time_point& tp, JsonLine& out);
98 void read_diskstats(JsonLine& out);
101 void read_meminfo(JsonLine& out);
103 void RunTask(
const steady_clock::time_point& tp)
final {
106 JsonLine out = logger_.line();
110 read_net_dev(tp, out);
111 read_pid_io(tp, out);
123 std::ifstream file_stat_;
125 std::ifstream file_net_dev_;
127 std::ifstream file_pid_stat_;
129 std::ifstream file_pid_io_;
131 std::ifstream file_diskstats_;
133 std::ifstream file_meminfo_;
136 steady_clock::time_point tp_last_;
142 unsigned long long user = 0;
143 unsigned long long nice = 0;
144 unsigned long long sys = 0;
145 unsigned long long idle = 0;
146 unsigned long long iowait = 0;
147 unsigned long long steal = 0;
148 unsigned long long hardirq = 0;
149 unsigned long long softirq = 0;
150 unsigned long long guest = 0;
151 unsigned long long guest_nice = 0;
154 unsigned long long uptime()
const {
155 return user + nice + sys + idle
156 + iowait + hardirq + steal + softirq;
159 unsigned long long user_plain()
const {
return user - guest; }
161 unsigned long long nice_plain()
const {
return nice - guest_nice; }
165 unsigned long long check_pid = 0;
166 unsigned long long utime = 0;
167 unsigned long long stime = 0;
168 unsigned long long cutime = 0;
169 unsigned long long cstime = 0;
170 unsigned long long num_threads = 0;
171 unsigned long long vsize = 0;
172 unsigned long long rss = 0;
177 unsigned long long rx_pkts = 0;
178 unsigned long long tx_pkts = 0;
179 unsigned long long rx_bytes = 0;
180 unsigned long long tx_bytes = 0;
184 unsigned long long read_bytes = 0;
185 unsigned long long write_bytes = 0;
191 unsigned long long rd_ios = 0;
193 unsigned long long rd_merged = 0;
195 unsigned long long rd_sectors = 0;
197 unsigned long long rd_time = 0;
200 unsigned long long wr_ios = 0;
202 unsigned long long wr_merged = 0;
204 unsigned long long wr_sectors = 0;
206 unsigned long long wr_time = 0;
209 unsigned long long ios_progr = 0;
211 unsigned long long total_time = 0;
213 unsigned long long rq_time = 0;
218 unsigned long long jiffies_delta_ = 0;
224 std::vector<CpuStat> cpu_core_prev_;
227 PidStat pid_stat_prev_;
230 std::vector<NetDevStat> net_dev_prev_;
233 NetDevStat& find_net_dev(
const std::string& if_name);
236 PidIoStat pid_io_prev_;
239 DiskStats * find_diskstats(
const char* dev_name);
242 std::vector<DiskStats> diskstats_prev_;
245 static bool parse_meminfo(
const char* str,
size_t& size);
248 JsonLine& LinuxProcStats::prepare_out(JsonLine& out) {
249 if (out.items() == 2) {
250 out <<
"class" <<
"LinuxProcStats" 251 <<
"event" <<
"profile";
256 void LinuxProcStats::read_stat(JsonLine& out) {
257 if (!file_stat_.is_open())
return;
261 if (!file_stat_.good())
return;
266 const double kNaN = 0;
268 std::vector<double> cores_user, cores_nice, cores_sys, cores_idle,
269 cores_iowait, cores_hardirq, cores_softirq,
270 cores_steal, cores_guest, cores_guest_nice;
273 while (std::getline(file_stat_, line)) {
279 "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
291 if (ret < 4)
die(
"/proc/stat returned too few values");
293 CpuStat& prev = cpu_prev_;
301 jiffies_delta_ = curr.uptime() - prev.uptime();
302 unsigned long long base = jiffies_delta_;
305 <<
"delta" << jiffies_delta_
306 <<
"user" << perc(prev.user, curr.user, base)
307 <<
"nice" << perc(prev.nice, curr.nice, base)
308 <<
"sys" << perc(prev.sys, curr.sys, base)
309 <<
"iowait" << (ret >= 5 ? perc(prev.iowait, curr.iowait, base) : kNaN)
310 <<
"hardirq" << (ret >= 6 ? perc(prev.hardirq, curr.hardirq, base) : kNaN)
311 <<
"softirq" << (ret >= 7 ? perc(prev.softirq, curr.softirq, base) : kNaN)
312 <<
"steal" << (ret >= 8 ? perc(prev.steal, curr.steal, base) : kNaN)
313 <<
"guest" << (ret >= 9 ? perc(prev.guest, curr.guest, base) : kNaN)
314 <<
"guest_nice" << (ret >= 10 ? perc(prev.guest_nice, curr.guest_nice, base) : kNaN)
315 <<
"idle" << perc(prev.idle, curr.idle, base);
318 <<
"cpu_user" << perc(prev.user, curr.user, base)
319 <<
"cpu_nice" << perc(prev.nice, curr.nice, base)
320 <<
"cpu_sys" << perc(prev.sys, curr.sys, base)
321 <<
"cpu_idle" << perc(prev.idle, curr.idle, base)
322 <<
"cpu_iowait" << (ret >= 5 ? perc(prev.iowait, curr.iowait, base) : kNaN)
323 <<
"cpu_hardirq" << (ret >= 6 ? perc(prev.hardirq, curr.hardirq, base) : kNaN)
324 <<
"cpu_softirq" << (ret >= 7 ? perc(prev.softirq, curr.softirq, base) : kNaN)
325 <<
"cpu_steal" << (ret >= 8 ? perc(prev.steal, curr.steal, base) : kNaN)
326 <<
"cpu_guest" << (ret >= 9 ? perc(prev.guest, curr.guest, base) : kNaN)
327 <<
"cpu_guest_nice" << (ret >= 10 ? perc(prev.guest_nice, curr.guest_nice, base) : kNaN);
337 "%u %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
350 if (ret < 5)
die(
"/proc/stat returned too few values");
352 if (cpu_core_prev_.size() < core_id + 1)
353 cpu_core_prev_.resize(core_id + 1);
355 CpuStat& prev = cpu_core_prev_[core_id];
363 jiffies_delta_ = curr.uptime() - prev.uptime();
364 unsigned long long base = jiffies_delta_;
366 sLOG <<
"core" << core_id
367 <<
"delta" << jiffies_delta_
368 <<
"user" << perc(prev.user, curr.user, base)
369 <<
"nice" << perc(prev.nice, curr.nice, base)
370 <<
"sys" << perc(prev.sys, curr.sys, base)
371 <<
"iowait" << (ret >= 6 ? perc(prev.iowait, curr.iowait, base) : kNaN)
372 <<
"hardirq" << (ret >= 7 ? perc(prev.hardirq, curr.hardirq, base) : kNaN)
373 <<
"softirq" << (ret >= 8 ? perc(prev.softirq, curr.softirq, base) : kNaN)
374 <<
"steal" << (ret >= 9 ? perc(prev.steal, curr.steal, base) : kNaN)
375 <<
"guest" << (ret >= 10 ? perc(prev.guest, curr.guest, base) : kNaN)
376 <<
"guest_nice" << (ret >= 11 ? perc(prev.guest_nice, curr.guest_nice, base) : kNaN)
377 <<
"idle" << perc(prev.idle, curr.idle, base);
379 cores_user.emplace_back(perc(prev.user, curr.user, base));
380 cores_nice.emplace_back(perc(prev.nice, curr.nice, base));
381 cores_sys.emplace_back(perc(prev.sys, curr.sys, base));
382 cores_idle.emplace_back(perc(prev.idle, curr.idle, base));
383 cores_iowait.push_back(ret >= 6 ? perc(prev.iowait, curr.iowait, base) : kNaN);
384 cores_hardirq.push_back(ret >= 7 ? perc(prev.hardirq, curr.hardirq, base) : kNaN);
385 cores_softirq.push_back(ret >= 8 ? perc(prev.softirq, curr.softirq, base) : kNaN);
386 cores_steal.push_back(ret >= 9 ? perc(prev.steal, curr.steal, base) : kNaN);
387 cores_guest.push_back(ret >= 10 ? perc(prev.guest, curr.guest, base) : kNaN);
388 cores_guest_nice.push_back(ret >= 11 ? perc(prev.guest_nice, curr.guest_nice, base) : kNaN);
394 if (!cores_user.empty()) {
396 <<
"cores_user" << cores_user
397 <<
"cores_nice" << cores_nice
398 <<
"cores_sys" << cores_sys
399 <<
"cores_idle" << cores_idle
400 <<
"cores_iowait" << cores_iowait
401 <<
"cores_hardirq" << cores_hardirq
402 <<
"cores_softirq" << cores_softirq
403 <<
"cores_steal" << cores_steal
404 <<
"cores_guest" << cores_guest
405 <<
"cores_guest_nice" << cores_guest_nice;
409 void LinuxProcStats::read_pid_stat(JsonLine& out) {
410 if (!file_pid_stat_.is_open())
return;
412 file_pid_stat_.clear();
413 file_pid_stat_.seekg(0);
414 if (!file_pid_stat_.good())
return;
417 std::getline(file_pid_stat_, line);
452 "%llu %*s %*s %*u %*u %*u %*u %*u %*u " 455 "%*u %*u %*u %*u %llu %llu %llu %llu %*u %*u " 458 "%llu %*u %*u %llu %llu",
461 &curr.utime, &curr.stime, &curr.cutime, &curr.cstime,
462 &curr.num_threads, &curr.vsize, &curr.rss);
466 if (!pid_stat_prev_.check_pid) {
467 pid_stat_prev_ = curr;
470 unsigned long long base = jiffies_delta_;
473 <<
"utime" << perc(pid_stat_prev_.utime, curr.utime, base)
474 <<
"stime" << perc(pid_stat_prev_.stime, curr.stime, base)
475 <<
"cutime" << perc(pid_stat_prev_.cutime, curr.cutime, base)
476 <<
"cstime" << perc(pid_stat_prev_.cstime, curr.cstime, base)
477 <<
"num_threads" << curr.num_threads
478 <<
"vsize" << curr.vsize
479 <<
"rss" << curr.rss * sc_pagesize_;
482 <<
"pr_user" << perc(pid_stat_prev_.utime, curr.utime, base)
483 <<
"pr_sys" << perc(pid_stat_prev_.stime, curr.stime, base)
484 <<
"pr_nthreads" << curr.num_threads
485 <<
"pr_vsize" << curr.vsize
486 <<
"pr_rss" << curr.rss * sc_pagesize_;
488 pid_stat_prev_ = curr;
491 LinuxProcStats::NetDevStat&
492 LinuxProcStats::find_net_dev(
const std::string& if_name) {
493 for (NetDevStat& i : net_dev_prev_) {
494 if (i.if_name == if_name)
return i;
496 net_dev_prev_.emplace_back();
497 net_dev_prev_.back().if_name = if_name;
498 return net_dev_prev_.back();
501 void LinuxProcStats::read_net_dev(
502 const steady_clock::time_point& tp, JsonLine& out) {
503 if (!file_net_dev_.is_open())
return;
505 file_net_dev_.clear();
506 file_net_dev_.seekg(0);
507 if (!file_net_dev_.good())
return;
509 double elapsed =
static_cast<double>(
510 std::chrono::duration_cast<std::chrono::microseconds>(
511 tp - tp_last_).count()) / 1e6;
514 bool sum_output =
false;
517 while (std::getline(file_net_dev_, line)) {
518 std::string::size_type colonpos = line.find(
':');
519 if (colonpos == std::string::npos)
continue;
525 int ret = sscanf(line.data() + colonpos + 1,
526 "%llu %llu %*u %*u %*u %*u %*u %*u %llu %llu",
527 &curr.rx_bytes, &curr.rx_pkts,
528 &curr.tx_bytes, &curr.tx_pkts);
531 curr.if_name = if_name;
532 NetDevStat& prev = find_net_dev(if_name);
534 if (prev.rx_bytes == 0) {
540 sLOG <<
"net" << if_name
541 <<
"rx_bytes" << curr.rx_bytes - prev.rx_bytes
542 <<
"tx_bytes" << curr.tx_bytes - prev.tx_bytes
543 <<
"rx_pkts" << curr.rx_pkts - prev.rx_pkts
544 <<
"tx_pkts" << curr.tx_pkts - prev.tx_pkts
546 <<
static_cast<double>(curr.rx_bytes - prev.rx_bytes) / elapsed
548 << static_cast<double>(curr.tx_bytes - prev.tx_bytes) / elapsed;
550 sum.rx_bytes += curr.rx_bytes - prev.rx_bytes;
551 sum.tx_bytes += curr.tx_bytes - prev.tx_bytes;
552 sum.rx_pkts += curr.rx_pkts - prev.rx_pkts;
553 sum.tx_pkts += curr.tx_pkts - prev.tx_pkts;
562 sLOG <<
"net" <<
"(all)" 563 <<
"rx_bytes" << sum.rx_bytes
564 <<
"tx_bytes" << sum.tx_bytes
565 <<
"rx_pkts" << sum.rx_pkts
566 <<
"tx_pkts" << sum.tx_pkts
567 <<
"rx_speed" <<
static_cast<double>(sum.rx_bytes) / elapsed
568 <<
"tx_speed" << static_cast<double>(sum.tx_bytes) / elapsed;
571 <<
"net_rx_bytes" << sum.rx_bytes
572 <<
"net_tx_bytes" << sum.tx_bytes
573 <<
"net_rx_pkts" << sum.rx_pkts
574 <<
"net_tx_pkts" << sum.tx_pkts
575 <<
"net_rx_speed" <<
static_cast<double>(sum.rx_bytes) / elapsed
576 <<
"net_tx_speed" << static_cast<double>(sum.tx_bytes) / elapsed;
580 void LinuxProcStats::read_pid_io(
const steady_clock::time_point& tp, JsonLine& out) {
581 if (!file_pid_io_.is_open())
return;
583 file_pid_io_.clear();
584 file_pid_io_.seekg(0);
585 if (!file_pid_io_.good())
return;
590 while (std::getline(file_stat_, line)) {
592 int ret = sscanf(line.data() + 12,
"%llu", &curr.read_bytes);
596 int ret = sscanf(line.data() + 13,
"%llu", &curr.write_bytes);
601 if (!pid_io_prev_.read_bytes) {
607 double elapsed =
static_cast<double>(
608 std::chrono::duration_cast<std::chrono::microseconds>(
609 tp - tp_last_).count()) / 1e6;
611 PidIoStat& prev = pid_io_prev_;
614 <<
"read_bytes" << curr.read_bytes - prev.read_bytes
615 <<
"write_bytes" << curr.write_bytes - prev.write_bytes
617 <<
static_cast<double>(curr.read_bytes - prev.read_bytes) / elapsed
619 << static_cast<double>(curr.write_bytes - prev.write_bytes) / elapsed;
622 <<
"pr_io_read_bytes" << curr.read_bytes - prev.read_bytes
623 <<
"pr_io_write_bytes" << curr.read_bytes - prev.read_bytes
624 <<
"pr_io_read_speed" 625 <<
static_cast<double>(curr.read_bytes - prev.read_bytes) / elapsed
626 <<
"pr_io_write_speed" 627 << static_cast<double>(curr.write_bytes - prev.write_bytes) / elapsed;
632 LinuxProcStats::DiskStats*
633 LinuxProcStats::find_diskstats(
const char* dev_name) {
634 for (DiskStats& i : diskstats_prev_) {
635 if (strcmp(i.dev_name.c_str(), dev_name) == 0)
return &i;
640 void LinuxProcStats::read_sys_block_devices() {
641 DIR* dirp = opendir(
"/sys/block");
646 if (de->d_name[0] ==
'.')
continue;
648 diskstats_prev_.emplace_back();
649 diskstats_prev_.back().dev_name = de->d_name;
654 void LinuxProcStats::read_diskstats(JsonLine& out) {
655 if (!file_diskstats_.is_open())
return;
657 file_diskstats_.clear();
658 file_diskstats_.seekg(0);
659 if (!file_diskstats_.good())
return;
662 bool sum_valid =
false;
663 JsonLine disks = prepare_out(out).sub(
"disks");
666 while (std::getline(file_diskstats_, line)) {
672 "%*u %*u %31s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
674 &curr.rd_ios, &curr.rd_merged, &curr.rd_sectors, &curr.rd_time,
675 &curr.wr_ios, &curr.wr_merged, &curr.wr_sectors, &curr.wr_time,
676 &curr.ios_progr, &curr.total_time, &curr.rq_time);
679 DiskStats* ptr_prev = find_diskstats(dev_name);
680 if (!ptr_prev)
continue;
682 DiskStats& prev = *ptr_prev;
683 curr.dev_name = dev_name;
685 if (!prev.rd_ios && !prev.wr_ios && !prev.ios_progr) {
694 <<
"rd_ios" << curr.rd_ios - prev.rd_ios
695 <<
"rd_merged" << curr.rd_merged - prev.rd_merged
696 <<
"rd_bytes" << (curr.rd_sectors - prev.rd_sectors) * 512
697 <<
"rd_time" <<
double(curr.rd_time - prev.rd_time) / 1e3
698 <<
"wr_ios" << curr.wr_ios - prev.wr_ios
699 <<
"wr_merged" << curr.wr_merged - prev.wr_merged
700 <<
"wr_bytes" << (curr.wr_sectors - prev.wr_sectors) * 512
701 <<
"wr_time" <<
double(curr.wr_time - prev.wr_time) / 1e3
702 <<
"ios_progr" << curr.ios_progr
703 <<
"total_time" << double(curr.total_time - prev.total_time) / 1e3
704 <<
"rq_time" << double(curr.rq_time - prev.rq_time) / 1e3;
707 <<
"rd_ios" << curr.rd_ios - prev.rd_ios
708 <<
"rd_merged" << curr.rd_merged - prev.rd_merged
709 <<
"rd_bytes" << (curr.rd_sectors - prev.rd_sectors) * 512
710 <<
"rd_time" <<
double(curr.rd_time - prev.rd_time) / 1e3
711 <<
"wr_ios" << curr.wr_ios - prev.wr_ios
712 <<
"wr_merged" << curr.wr_merged - prev.wr_merged
713 <<
"wr_bytes" << (curr.wr_sectors - prev.wr_sectors) * 512
714 <<
"wr_time" <<
double(curr.wr_time - prev.wr_time) / 1e3
715 <<
"ios_progr" << curr.ios_progr
716 <<
"total_time" << double(curr.total_time - prev.total_time) / 1e3
717 <<
"rq_time" << double(curr.rq_time - prev.rq_time) / 1e3;
719 sum.rd_ios += curr.rd_ios - prev.rd_ios;
720 sum.rd_merged += curr.rd_merged - prev.rd_merged;
721 sum.rd_sectors += curr.rd_sectors - prev.rd_sectors;
722 sum.rd_time += curr.rd_time - prev.rd_time;
723 sum.wr_ios += curr.wr_ios - prev.wr_ios;
724 sum.wr_merged += curr.wr_merged - prev.wr_merged;
725 sum.wr_sectors += curr.wr_sectors - prev.wr_sectors;
726 sum.wr_time += curr.wr_time - prev.wr_time;
727 sum.ios_progr += curr.ios_progr;
728 sum.total_time += curr.total_time - prev.total_time;
729 sum.rq_time += curr.rq_time - prev.rq_time;
738 prepare_out(out).sub(
"diskstats")
739 <<
"rd_ios" << sum.rd_ios
740 <<
"rd_merged" << sum.rd_merged
741 <<
"rd_bytes" << sum.rd_sectors * 512
742 <<
"rd_time" << double(sum.rd_time) / 1e3
743 <<
"wr_ios" << sum.wr_ios
744 <<
"wr_merged" << sum.wr_merged
745 <<
"wr_bytes" << sum.wr_sectors * 512
746 <<
"wr_time" << double(sum.wr_time) / 1e3
747 <<
"ios_progr" << sum.ios_progr
748 <<
"total_time" << double(sum.total_time) / 1e3
749 <<
"rq_time" << double(sum.rq_time) / 1e3;
754 bool LinuxProcStats::parse_meminfo(
const char* str,
size_t& size) {
756 size = strtoul(str, &endptr, 10);
758 if (!endptr)
return false;
761 while (*endptr ==
' ') ++endptr;
764 if (*endptr ==
'k' || *endptr ==
'K')
765 size *= 1024, ++endptr;
766 else if (*endptr ==
'm' || *endptr ==
'M')
767 size *= 1024 * 1024, ++endptr;
768 else if (*endptr ==
'g' || *endptr ==
'G')
769 size *= 1024 * 1024 * 1024llu, ++endptr;
772 if (*endptr ==
'b' || *endptr ==
'B') {
777 while (*endptr ==
' ') ++endptr;
779 return (*endptr == 0);
782 void LinuxProcStats::read_meminfo(JsonLine& out) {
783 if (!file_meminfo_.is_open())
return;
785 file_meminfo_.clear();
786 file_meminfo_.seekg(0);
787 if (!file_meminfo_.good())
return;
789 JsonLine mem = prepare_out(out).sub(
"meminfo");
791 size_t swap_total = 0, swap_free = 0;
794 while (std::getline(file_meminfo_, line)) {
795 std::string::size_type colonpos = line.find(
':');
796 if (colonpos == std::string::npos)
continue;
802 if (key ==
"MemTotal") {
803 if (parse_meminfo(line.data() + colonpos + 1, size))
804 mem <<
"total" << size;
806 else if (key ==
"MemFree") {
807 if (parse_meminfo(line.data() + colonpos + 1, size))
808 mem <<
"free" << size;
810 else if (key ==
"MemAvailable") {
811 if (parse_meminfo(line.data() + colonpos + 1, size))
812 mem <<
"available" << size;
814 else if (key ==
"Buffers") {
815 if (parse_meminfo(line.data() + colonpos + 1, size))
816 mem <<
"buffers" << size;
818 else if (key ==
"Cached") {
819 if (parse_meminfo(line.data() + colonpos + 1, size))
820 mem <<
"cached" << size;
822 else if (key ==
"Mapped") {
823 if (parse_meminfo(line.data() + colonpos + 1, size))
824 mem <<
"mapped" << size;
826 else if (key ==
"Shmem") {
827 if (parse_meminfo(line.data() + colonpos + 1, size))
828 mem <<
"shmem" << size;
830 else if (key ==
"SwapTotal") {
831 if (parse_meminfo(line.data() + colonpos + 1, size)) {
832 mem <<
"swap_total" << size;
834 if (swap_total && swap_free) {
835 mem <<
"swap_used" << swap_total - swap_free;
836 swap_total = swap_free = 0;
840 else if (key ==
"SwapFree") {
841 if (parse_meminfo(line.data() + colonpos + 1, size)) {
842 mem <<
"swap_free" << size;
844 if (swap_total && swap_free) {
845 mem <<
"swap_used" << swap_total - swap_free;
846 swap_total = swap_free = 0;
854 sched.Add(std::chrono::seconds(1),
855 new LinuxProcStats(logger),
true);
#define sLOG
Default logging method: output if the local debug variable is true.
std::string & trim(std::string *str)
Trims the given string in-place on the left and right.
bool starts_with(const char *str, const char *match)
Checks if the given match string is located at the start of this string.
#define die(msg)
Instead of std::terminate(), throw the output the message via an exception.
static by_string to_string(int val)
convert to string
#define die_unequal(X, Y)
void StartLinuxProcStatsProfiler(ProfileThread &, JsonLogger &)
launch profiler task
struct dirent * ts_readdir(DIR *dirp)
mutex-locked readdir() call
std::basic_string< char, std::char_traits< char >, Allocator< char > > string
string with Manager tracking
static constexpr bool debug
StringView is a reference to a part of a string, consisting of only a char pointer and a length...
JsonLogger is a receiver of JSON output objects for logging.