1 /++
2     A POSIX application wrapping getrusage(2) for simple benchmarks.
3 
4     SYNOPSIS:
5     `getr <#runs> <command> [<args> ...]`
6 
7     OUTPUT:
8     ---
9     $ getr 1000 ./fizzbuzz >/dev/null
10     User time      : 0 s, 894347 us
11     System time    : 0 s, 673832 us
12     Time           : 1568.179 ms (1.568 ms/per)
13     Max RSS        : 952 kB
14     Page reclaims  : 239344
15     Page faults    : 1
16     Block inputs   : 0
17     Block outputs  : 0
18     vol ctx switches   : 0
19     invol ctx switches : 1298
20     ---
21 
22     DESCRIPTION:
23     getr is a simple wrapper around the `getrusage`(2) syscall, which can be
24     relied on for basic resource usage reports under Linux, OpenBSD, and macOS
25     (among others). A child command is repeatedly spawned and waited for, and
26     then a `RUSAGE_CHILDREN` report is generated. This program was created as
27     the author was used to very simple bash loops to test performance, which
28     was then found didn't work at all under ksh on OpenBSD.  getr is just as
29     easy and just as simple.
30 
31     EXIT_STATUS:
32     getr exits with status 1 if it fails to spawn a process, or if its own
33     arguments aren't understood. It exits with status 0 in all other cases,
34     including if the spawned program returns a nonzero exit status.
35 
36     EXAMPLES:
37     `getr 1000 ./fizzbuzz > /dev/null`
38 
39     `fizzbuzz` is invoked 1000 times, with no arguments, and with getr's own
40     (and therefore `fizzbuzz`'s) standard output piped to /dev/null. The
41     resulting usage report would still be printed to standard error.
42 
43     `getr 100 rdmd -c ''`
44 
45     `rdmd` in PATH is asked, 100 times, to evaluate the empty string.
46 
47     SEE_ALSO:
48     `getrusage`(2), `which`(1), `time`(1), `perf`(1), `valgrind`(1).
49 +/
50 
51 module getr;
52 
53 extern (C) int getrusage(int who, RUsage* usage);
54 
55 enum RUSAGE_CHILDREN = -1;
56 
57 struct Timeval {
58     long tv_sec;
59     long tv_usec;
60 }
61 
62 struct RUsage {
63     Timeval ru_utime;
64     Timeval ru_stime;
65     long ru_maxrss;
66     long ru_ixrss;
67     long ru_idrss;
68     long ru_isrss;
69     long ru_minflt;
70     long ru_majflt;
71     long ru_nswap;
72     long ru_inblock;
73     long ru_oublock;
74     long ru_msgsnd;
75     long ru_msgrcv;
76     long ru_nsignals;
77     long ru_nvcsw;
78     long ru_nivcsw;
79 }
80 
81 void report(int times) {
82     import std.stdio : stderr;
83 
84     RUsage usage = void;
85 
86     getrusage(RUSAGE_CHILDREN, &usage);
87     auto seconds = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec;
88     auto microseconds = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
89     auto milliseconds = seconds * 1000 + microseconds / 1000;
90     auto ms_per = cast(double) milliseconds / cast(double) times;
91 
92     stderr.writef!q"REPORT
93 User time      : %d s, %d us
94 System time    : %d s, %d us
95 Time           : %d ms (%.3f ms/per)
96 Max RSS        : %s
97 Page reclaims  : %d
98 Page faults    : %d
99 Block inputs   : %d
100 Block outputs  : %d
101 vol ctx switches   : %d
102 invol ctx switches : %d
103 REPORT"(usage.ru_utime.tv_sec, usage.ru_utime.tv_usec,
104             usage.ru_stime.tv_sec, usage.ru_stime.tv_usec, milliseconds, ms_per,
105             readable_rss(cast(real) usage.ru_maxrss), usage.ru_minflt, usage.ru_majflt,
106             usage.ru_inblock, usage.ru_oublock, usage.ru_nvcsw, usage.ru_nivcsw);
107 }
108 
109 string readable_rss(real kb) {
110     import std.format : format;
111 
112     if (kb < 1024.0)
113         return format!"%.0f kB"(kb);
114     else if (kb < 1024.0 * 1024.0)
115         return format!"%.1f MB"(kb / 1024.0);
116     else
117         return format!"%.2f GB"(kb / (1024.0 * 1024.0));
118 }
119 
120 void main(string[] args) {
121     import std.stdio : stderr;
122     import core.stdc.stdlib : exit;
123     import std.exception : ifThrown;
124     import std.conv : to;
125     import std.process : spawnProcess, wait;
126 
127     void usage() {
128         stderr.writeln("usage: ", args[0], " <#runs> <command> [<args> ...]");
129         exit(1);
130     }
131 
132     if (args.length < 3)
133         usage();
134 
135     immutable int count = to!int(args[1]).ifThrown(0);
136 
137     if (count < 1)
138         usage();
139 
140     foreach (i; 0 .. count) {
141         wait(spawnProcess(args[2 .. $]));
142     }
143 
144     report(count);
145 }