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 private:
54 
55 extern (C) int getrusage(int who, RUsage* usage);
56 
57 enum RUSAGE_CHILDREN = -1;
58 
59 struct Timeval {
60     long tv_sec;
61     long tv_usec;
62 }
63 
64 struct RUsage {
65     Timeval ru_utime;
66     Timeval ru_stime;
67     long ru_maxrss;
68     long ru_ixrss;
69     long ru_idrss;
70     long ru_isrss;
71     long ru_minflt;
72     long ru_majflt;
73     long ru_nswap;
74     long ru_inblock;
75     long ru_oublock;
76     long ru_msgsnd;
77     long ru_msgrcv;
78     long ru_nsignals;
79     long ru_nvcsw;
80     long ru_nivcsw;
81 }
82 
83 void report(int times) {
84     import std.stdio : stderr;
85 
86     RUsage usage = void;
87 
88     getrusage(RUSAGE_CHILDREN, &usage);
89     immutable long seconds = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec,
90         microseconds = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec,
91         milliseconds = seconds * 1000 + microseconds / 1000;
92     immutable double ms_per = cast(double) milliseconds / cast(double) times;
93 
94     stderr.writef!q"REPORT
95 User time      : %d s, %d us
96 System time    : %d s, %d us
97 Time           : %d ms (%.3f ms/per)
98 Max RSS        : %s
99 Page reclaims  : %d
100 Page faults    : %d
101 Block inputs   : %d
102 Block outputs  : %d
103 vol ctx switches   : %d
104 invol ctx switches : %d
105 REPORT"(usage.ru_utime.tv_sec, usage.ru_utime.tv_usec,
106             usage.ru_stime.tv_sec, usage.ru_stime.tv_usec, milliseconds, ms_per,
107             readable_rss(cast(real) usage.ru_maxrss), usage.ru_minflt, usage.ru_majflt,
108             usage.ru_inblock, usage.ru_oublock, usage.ru_nvcsw, usage.ru_nivcsw);
109 }
110 
111 string readable_rss(real kb) {
112     import std.format : format;
113 
114     if (kb < 1024.0)
115         return format!"%.0f kB"(kb);
116     else if (kb < 1024.0 * 1024.0)
117         return format!"%.1f MB"(kb / 1024.0);
118     else
119         return format!"%.2f GB"(kb / (1024.0 * 1024.0));
120 }
121 
122 void main(string[] args) {
123     import std.stdio : stderr;
124     import core.stdc.stdlib : exit;
125     import std.exception : ifThrown;
126     import std.conv : to;
127     import std.process : spawnProcess, wait;
128 
129     void usage() {
130         stderr.writeln("usage: ", args[0], " <#runs> <command> [<args> ...]");
131         exit(1);
132     }
133 
134     if (args.length < 3)
135         usage();
136 
137     immutable int count = to!int(args[1]).ifThrown(0);
138 
139     if (count < 1)
140         usage();
141 
142     foreach (i; 0 .. count) {
143         wait(spawnProcess(args[2 .. $]));
144     }
145 
146     report(count);
147 }