Skip to content

Commit aa5190b

Browse files
Use loadtest4j 0.16.0 (#42)
Support decimal response time percentile queries.
1 parent 0ad469e commit aa5190b

File tree

17 files changed

+205
-88
lines changed

17 files changed

+205
-88
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<dependency>
4242
<groupId>org.loadtest4j</groupId>
4343
<artifactId>loadtest4j</artifactId>
44-
<version>0.15.0</version>
44+
<version>0.16.0</version>
4545
</dependency>
4646
<dependency>
4747
<groupId>com.fasterxml.jackson.core</groupId>

src/main/java/org/loadtest4j/drivers/wrk/Wrk.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
import org.loadtest4j.drivers.wrk.utils.*;
1010
import org.loadtest4j.drivers.wrk.utils.Process;
1111

12-
import java.io.IOException;
13-
import java.io.InputStream;
14-
import java.io.InputStreamReader;
15-
import java.io.Reader;
12+
import java.io.*;
1613
import java.nio.charset.StandardCharsets;
1714
import java.nio.file.Path;
1815
import java.time.Duration;
1916
import java.util.Collection;
17+
import java.util.Collections;
2018
import java.util.List;
2119
import java.util.Map;
2220
import java.util.stream.Collectors;
@@ -61,12 +59,17 @@ private static Path createInput(List<DriverRequest> requests) {
6159
final List<Req> wrkRequests = wrkRequests(requests);
6260
final Input input = new Input(wrkRequests);
6361
final Path inputPath = FileUtils.createTempFile("loadtest4j-wrk", ".json");
64-
Json.serialize(inputPath.toFile(), input);
62+
try {
63+
Json.serialize(inputPath.toFile(), input);
64+
} catch (IOException e) {
65+
throw new LoadTesterException(e);
66+
}
6567
return inputPath;
6668
}
6769

6870
private DriverResult runWrkViaShell(Path input) {
6971
final Path luaScript = createLuaScript();
72+
final Path luaOutput = FileUtils.createTempFile("loadtest4j-output", ".json");
7073

7174
final List<String> arguments = new ArgumentBuilder()
7275
.addNamedArgument("--connections", valueOf(connections))
@@ -77,25 +80,38 @@ private DriverResult runWrkViaShell(Path input) {
7780
.addArgument(input.toString())
7881
.build();
7982

80-
final Command command = new Command(arguments, executable);
83+
final Map<String, String> env = Collections.singletonMap("WRK_OUTPUT", luaOutput.toString());
84+
85+
final Command command = new Command(arguments, env, executable);
8186

8287
final Process process = new Shell().start(command);
8388

89+
// Reduce sleep/wakeup cycles waiting for process by doing the sleep ourselves
90+
try {
91+
Thread.sleep(duration.toMillis());
92+
} catch (InterruptedException e) {
93+
// It was worth a try - just fall back to process.waitFor()
94+
}
95+
8496
final int exitStatus = process.waitFor();
8597

8698
if (exitStatus != 0) {
8799
final String error = StreamReader.streamToString(process.getStderr());
88100
throw new LoadTesterException("Wrk error:\n\n" + error);
89101
}
90102

91-
try (Reader reader = new InputStreamReader(process.getStderr(), StandardCharsets.UTF_8)) {
92-
return toDriverResult(reader);
103+
try {
104+
return toDriverResult(luaOutput.toAbsolutePath());
93105
} catch (IOException e) {
94106
throw new LoadTesterException(e);
95107
}
96108
}
97109

98-
protected static DriverResult toDriverResult(Reader report) {
110+
private static DriverResult toDriverResult(Path path) throws IOException {
111+
return toDriverResult(new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8));
112+
}
113+
114+
protected static DriverResult toDriverResult(Reader report) throws IOException {
99115
final Output output = Json.parse(report, Output.class);
100116
return toDriverResult(output);
101117
}

src/main/java/org/loadtest4j/drivers/wrk/WrkResponseTime.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@
22

33
import org.loadtest4j.driver.DriverResponseTime;
44

5+
import java.math.BigDecimal;
56
import java.time.Duration;
6-
import java.util.Map;
7+
import java.util.TreeMap;
78

89
class WrkResponseTime implements DriverResponseTime {
910

10-
private final Map<Integer, Long> percentiles;
11+
private static final int DECIMAL_PLACES = 3;
1112

12-
WrkResponseTime(Map<Integer, Long> percentiles) {
13+
private final TreeMap<BigDecimal, Long> percentiles;
14+
15+
WrkResponseTime(TreeMap<BigDecimal, Long> percentiles) {
1316
this.percentiles = percentiles;
1417
}
1518

1619
@Override
17-
public Duration getPercentile(int i) {
20+
public Duration getPercentile(double i) {
1821
if (i < 0 || i > 100) {
1922
throw new IllegalArgumentException("A percentile must be between 0 and 100");
2023
}
2124

22-
final long durationMicroseconds = percentiles.get(i);
25+
final BigDecimal decimalI = BigDecimal.valueOf(i);
26+
27+
if (decimalI.scale() > DECIMAL_PLACES) {
28+
throw new IllegalArgumentException(String.format("The Wrk driver only supports percentile queries up to %d decimal places.", DECIMAL_PLACES));
29+
}
30+
31+
final long durationMicroseconds;
32+
try {
33+
durationMicroseconds = percentiles.get(decimalI);
34+
} catch (NullPointerException e) {
35+
throw new IllegalArgumentException("The Wrk driver could not find a response time value for that percentile.");
36+
}
2337

2438
return Duration.ofNanos(durationMicroseconds * 1000);
2539
}

src/main/java/org/loadtest4j/drivers/wrk/WrkResult.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.loadtest4j.driver.DriverResult;
55

66
import java.time.Duration;
7-
import java.util.Optional;
87

98
class WrkResult implements DriverResult {
109

@@ -39,9 +38,4 @@ public Duration getActualDuration() {
3938
public DriverResponseTime getResponseTime() {
4039
return responseTime;
4140
}
42-
43-
@Override
44-
public Optional<String> getReportUrl() {
45-
return Optional.empty();
46-
}
4741
}

src/main/java/org/loadtest4j/drivers/wrk/dto/Latency.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
44
import com.fasterxml.jackson.annotation.JsonProperty;
55

6-
import java.util.Map;
6+
import java.math.BigDecimal;
7+
import java.util.TreeMap;
78

89
@JsonIgnoreProperties(ignoreUnknown = true)
910
public class Latency {
11+
/**
12+
* A mapping of (the percentile, decimal) to (the percentile value).
13+
*/
1014
@JsonProperty
11-
private Map<Integer, Long> percentiles;
15+
private TreeMap<BigDecimal, Long> percentiles;
1216

13-
public Map<Integer, Long> getPercentiles() {
17+
public TreeMap<BigDecimal, Long> getPercentiles() {
1418
return percentiles;
1519
}
1620
}

src/main/java/org/loadtest4j/drivers/wrk/utils/Command.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,28 @@
22

33
import java.util.Collections;
44
import java.util.List;
5+
import java.util.Map;
56

67
public class Command {
78

89
private final List<String> arguments;
10+
private final Map<String, String> env;
911
private final String launchPath;
1012

11-
public Command(List<String> arguments, String launchPath) {
13+
public Command(List<String> arguments, Map<String, String> env, String launchPath) {
1214
this.arguments = arguments;
15+
this.env = env;
1316
this.launchPath = launchPath;
1417
}
1518

1619
public List<String> getArguments() {
1720
return Collections.unmodifiableList(arguments);
1821
}
1922

23+
public Map<String, String> getEnv() {
24+
return env;
25+
}
26+
2027
public String getLaunchPath() {
2128
return launchPath;
2229
}

src/main/java/org/loadtest4j/drivers/wrk/utils/FileUtils.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010

1111
public class FileUtils {
1212
public static Path createTempFile(String prefix, String suffix) {
13+
final Path p;
1314
try {
14-
return Files.createTempFile(prefix, suffix);
15+
p = Files.createTempFile(prefix, suffix);
1516
} catch (IOException e) {
1617
throw new LoadTesterException(e);
1718
}
19+
20+
p.toFile().deleteOnExit();
21+
22+
return p;
1823
}
1924

2025
public static void copy(InputStream content, Path target) {
Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.loadtest4j.drivers.wrk.utils;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import org.loadtest4j.LoadTesterException;
54

65
import java.io.File;
76
import java.io.IOException;
@@ -11,19 +10,11 @@ public class Json {
1110

1211
private static final ObjectMapper MAPPER = new ObjectMapper();
1312

14-
public static void serialize(File resultFile, Object value) {
15-
try {
16-
MAPPER.writeValue(resultFile, value);
17-
} catch (IOException e) {
18-
throw new LoadTesterException(e);
19-
}
13+
public static void serialize(File resultFile, Object value) throws IOException {
14+
MAPPER.writeValue(resultFile, value);
2015
}
2116

22-
public static <T> T parse(Reader src, Class<T> valueType) {
23-
try {
24-
return MAPPER.readValue(src, valueType);
25-
} catch (IOException e) {
26-
throw new LoadTesterException(e);
27-
}
17+
public static <T> T parse(Reader src, Class<T> valueType) throws IOException {
18+
return MAPPER.readValue(src, valueType);
2819
}
2920
}

src/main/java/org/loadtest4j/drivers/wrk/utils/Shell.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ public org.loadtest4j.drivers.wrk.utils.Process start(Command command) {
1313
cmd.add(command.getLaunchPath());
1414
cmd.addAll(command.getArguments());
1515

16+
final ProcessBuilder pb = new ProcessBuilder(cmd)
17+
.redirectInput(ProcessBuilder.Redirect.PIPE);
18+
pb.environment().putAll(command.getEnv());
1619
try {
17-
return new org.loadtest4j.drivers.wrk.utils.Process(new ProcessBuilder(cmd).redirectInput(ProcessBuilder.Redirect.PIPE).start());
20+
return new org.loadtest4j.drivers.wrk.utils.Process(pb.start());
1821
} catch (IOException e) {
1922
throw new LoadTesterException(e);
2023
}

src/main/resources/loadtest4j-wrk.lua

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,40 @@ done = function(summary, latency, requests)
4040
}
4141
}
4242

43-
for p = 0, 99
43+
-- WARNING: decimalPlaces has a connascence with granularity. If you change one you MUST change the other.
44+
local granularity = 0.001
45+
local decimalPlaces = 3
46+
47+
for p = 0, 100, granularity
4448
do
45-
json["latency"]["percentiles"][p] = latency:percentile(p)
49+
-- We must round to the desired decimal places to stop p losing accuracy (e.g. the p95.9 becomes p95.8999999999)
50+
local roundedP = round(p, decimalPlaces)
51+
json["latency"]["percentiles"][roundedP] = latency:percentile(roundedP)
4652
end
53+
4754
-- p100 is not available so use max instead
4855
json["latency"]["percentiles"][100] = latency.max
4956

50-
io.stderr:write(encodeJson(json))
57+
printReport(encodeJson(json))
58+
end
59+
60+
function printReport(report)
61+
local outputFile = os.getenv("WRK_OUTPUT")
62+
if outputFile == nil then
63+
io.stderr:write(report)
64+
else
65+
local fho, _ = io.open(outputFile, "w")
66+
fho:write(report)
67+
fho:close()
68+
end
69+
end
70+
71+
-- From https://stackoverflow.com/a/37792884/1475135
72+
function round(x, n)
73+
n = math.pow(10, n)
74+
x = x * n
75+
if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end
76+
return x / n
5177
end
5278

5379
function readFile(file)

0 commit comments

Comments
 (0)