1 // Performs multiple runs of weighttp tool
2 // varying the concurency range in steps
3 // Inspired by GWAN benchmark tool (ab.c)
4 
5 module http_load;
6 
7 import core.cpuid, core.thread;
8 import std.getopt, std.array, std.algorithm, std.numeric,
9     std.conv, std.datetime, std.exception,
10     std.stdio, std.process, std.regex;
11 
12 void usage() {
13     stderr.writeln("Usage: http_load START-STOP:STEP TIMExRUNS http://server[:port]/path");
14 }
15 
16 long fromMetric(string num) {
17     assert(num.length > 0);
18     long mult;
19     switch(num[$-1]){
20     case 'm':
21         mult = 1_000_000;
22         num = num[0..$-1];
23         break;
24     case 'k':
25         mult = 1_000;
26         num = num[0..$-1];
27         break;
28     default:
29         mult = 1;
30     }
31     return mult * num.to!long;
32 }
33 
34 int main(string[] argv) { 
35     bool trace = false;
36     getopt(argv,
37         "v", &trace
38     );
39     if (argv.length < 4) {
40         usage();
41         return 1;
42     }
43     auto m = matchFirst(argv[1], `^(\d+(?:[km]?))-(\d+(?:[km]?)):(\d+(?:[km]?))$`);
44     if (!m) {
45         stderr.writeln("Can't parse 'range' argument:", argv[1]);
46         usage();
47         return 1;
48     }
49     string url = argv[3];
50     long start = fromMetric(m[1]);
51     long stop = fromMetric(m[2]);
52     long step = fromMetric(m[3]);
53     auto m2 = matchFirst(argv[2], `^(\d+)x(\d+)$`);
54     if (!m2) {
55         stderr.writeln("Can't parse 'runs' argument:", argv[2]);
56         usage();
57         return 1;
58     }
59     int numThreads = threadsPerCPU;
60     int time = m2[1].to!int;
61     int runs = m2[2].to!int;
62     writefln("time,concurrency,RPS(min),RPS(avg),RPS(max),errors(max),lat(75%%),lat(99%%)");
63     for(long c = start; c <= stop; c += step) {
64         c = c / step * step; // truncate to step
65         if (c < numThreads) c = numThreads;     
66         auto dt = Clock.currTime();   
67         double[] rps = new double[runs];
68         double[] perc75 = new double[runs];
69         double[] perc99 = new double[runs];
70         long[] errors = new long[runs];
71         foreach(r; 0..runs) {
72             double multiplier(const(char)[] s){
73                 if (s == "") return 1;
74                 else if(s == "u") return 1e-6;
75                 else if(s == "m") return 1e-3;
76                 else throw new Exception("Unknown multiplier " ~ s.to!string);
77             }
78             auto cmd = ["wrk", "--latency", "-c", c.to!string, "-t", numThreads.to!string, "-d", time.to!string, url];
79             if(trace) stderr.writeln(cmd.join(" "));
80             auto pipes = pipeProcess(cmd);
81             foreach (line; pipes.stdout.byLine) {
82                 auto result = matchFirst(line, `Socket errors: connect (\d+), read (\d+), write (\d+), timeout (\d+)`);
83                 if(result) {
84                     errors[r] = result[1].to!long + result[2].to!long + result[3].to!long + result[4].to!long;
85                 }
86                 result = matchFirst(line, `Requests/sec:\s*([\d.]+)`);
87                 if (result) {
88                     rps[r] = result[1].to!double;
89                 }
90                 result = matchFirst(line, `75%\s*([\d.]+)([mu])?s`);
91                 if (result) {
92                     perc75[r] = result[1].to!double * multiplier(result[2]);
93                 }
94                 result = matchFirst(line, `99%\s*([\d.]+)([mu])?s`);
95                 if (result) {
96                     perc99[r] = result[1].to!double * multiplier(result[2]);
97                 }
98             }
99             if (wait(pipes.pid)) {
100                 stderr.writeln("wrk failed, stopping benchmark");
101                 return 1;
102             }
103         }
104         writefln("%s,%d,%f,%f,%f,%d,%f,%f",
105             dt.toISOExtString, c, reduce!(min)(rps), mean(rps), reduce!max(rps), 
106             reduce!max(errors), mean(perc75), mean(perc99));
107         stdout.flush();
108         Thread.sleep(100.msecs);
109     }
110     return 0;
111 }