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 }