-
Notifications
You must be signed in to change notification settings - Fork 35
/
GatlingAwsMojo.java
271 lines (218 loc) · 11.3 KB
/
GatlingAwsMojo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/**
* Copyright (C) 2016 Electronic Arts Inc. All rights reserved.
*/
package com.ea.gatling;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Tag;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.FileUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Runs gatling script on remote EC2 instances.
*/
@Mojo(name = "execute")
public class GatlingAwsMojo extends BaseAwsMojo {
/**
* The time in milliseconds between checking for the termination of all executors running load tests.
*/
private static final int SLEEP_TIME_TERMINATION_MS = 1000;
@Parameter(property = "ssh.private.key", defaultValue = "${user.home}/gatling-private-key.pem")
private File sshPrivateKey;
@Parameter(property = "ssh.user", defaultValue = "ec2-user")
private String sshUser;
@Parameter(property = "debug.output.enabled", defaultValue = "false")
private boolean debugOutputEnabled = false;
@Parameter(property = "gatling.install.script", defaultValue = "${project.basedir}/src/test/resources/scripts/install-gatling.sh")
private File installScript;
@Parameter(defaultValue = "${project.basedir}/src/test/scala")
private File gatlingSourceDir;
@Parameter(property = "gatling.simulation", defaultValue = "Simulation")
private String gatlingSimulation;
@Parameter(defaultValue = "${project.basedir}/src/test/resources")
private File gatlingResourcesDir;
@Parameter(property = "gatling.test.name", defaultValue = "")
private String testName = "";
@Parameter(property = "path.config.file", defaultValue = "${project.basedir}/src/test/resources/config.properties")
private File simulationConfig;
@Parameter(property = "gatling.local.results", defaultValue = "${project.build.directory}/gatling/results")
private File gatlingLocalResultsDir;
@Parameter(property = "gatling.local.home", defaultValue = "${user.home}/gatling/gatling-charts-highcharts-bundle-2.1.4/bin/gatling.sh")
private String gatlingLocalHome;
@Parameter(property = "gatling.root", defaultValue = "gatling-charts-highcharts-bundle-2.1.4")
private String gatlingRoot;
/**
* The JAVA_OPTS used when launching Gatling on the remote load generator. This allows users of the plugin to increase the heap space or change any other JVM settings.
*/
@Parameter(property = "gatling.java.opts", defaultValue = "-Xms1g -Xmx6g")
private String gatlingJavaOpts;
@Parameter(property = "files")
private List<String> files;
@Parameter(property = "s3.upload.enabled", defaultValue = "false")
private boolean s3UploadEnabled;
@Parameter(property = "s3.region", defaultValue = "us-west-1")
private String s3Region;
@Parameter(property = "s3.bucket", defaultValue = "loadtest-results")
private String s3Bucket;
@Parameter(property = "s3.subfolder", defaultValue = "")
private String s3Subfolder;
@Parameter(property = "propagate.gatling.failure", defaultValue = "false")
private boolean propagateGatlingFailure;
@Parameter(property = "prefer.private.ip.hostnames", defaultValue = "false")
private boolean preferPrivateIpHostnames;
/**
* When true, this will run Gatling detached, and disconnect from SSH while Gatling is running. Leaves a
* file called 'gatling.pid' with the pid of the java process in it.
* ec2.keep.alive ignored when set to true
* ec2.force.termination ignored when set to true
* All output turned off
*/
@Parameter(property = "ec2.execute.detached", defaultValue = "false")
private boolean ec2ExecuteDetached = false;
public void execute() throws MojoExecutionException {
final AwsGatlingRunner runner = new AwsGatlingRunner(this.ec2EndPoint);
runner.setInstanceTag(new Tag(this.ec2TagName, this.ec2TagValue));
final Map<String, Instance> instances = this.ec2SecurityGroupId != null
? runner.launchEC2Instances(this.instanceType, this.instanceCount, this.ec2KeyPairName, this.ec2SecurityGroupId, this.ec2SubnetId, this.ec2AmiId, true)
: runner.launchEC2Instances(this.instanceType, this.instanceCount, this.ec2KeyPairName, this.ec2SecurityGroup, this.ec2AmiId, true);
final ConcurrentHashMap<String, Integer> completedHosts = new ConcurrentHashMap<>();
// launch all tests in parallel
final ExecutorService executor = Executors.newFixedThreadPool(this.instanceCount);
final long timeStamp = System.currentTimeMillis();
this.testName = this.testName.equals("") ? this.gatlingSimulation.toLowerCase() + "-" + timeStamp : this.testName + "-" + timeStamp;
final File resultsDir = new File(this.gatlingLocalResultsDir, this.testName);
final boolean success = resultsDir.mkdirs();
System.out.format("created result dir %s: %s%n", resultsDir.getAbsolutePath(), success);
final Collection<Instance> values = instances.values();
int numInstance = 0;
for (final Instance instance : values) {
final String host = this.getPreferredHostName(instance);
final Runnable worker = new AwsGatlingExecutor(
host,
this.sshUser,
this.sshPrivateKey,
this.testName,
this.installScript,
this.gatlingSourceDir,
this.gatlingSimulation,
this.simulationConfig,
this.gatlingResourcesDir,
this.gatlingLocalResultsDir,
this.files,
numInstance++,
this.instanceCount,
completedHosts,
this.gatlingRoot,
this.gatlingJavaOpts,
this.debugOutputEnabled,
this.ec2ExecuteDetached);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
try {
Thread.sleep(SLEEP_TIME_TERMINATION_MS);
} catch (final InterruptedException e) {
}
}
System.out.println("Finished all threads");
final int failedInstancesCount = this.listFailedInstances(instances, completedHosts);
// If the ec2KeepAlive value is true then we need to skip terminating.
if ((failedInstancesCount == 0 || this.ec2ForceTermination) && !this.ec2KeepAlive && !this.ec2ExecuteDetached) {
runner.terminateInstances(instances.keySet());
} else if (this.ec2KeepAlive) {
// Send a message out stating the machines are still running
System.out.println("EC2 instances are still running for the next load test");
} else if (this.ec2ExecuteDetached) {
System.out.println("EC2 instances are running detached");
}
if (!this.ec2ExecuteDetached) {
// Build report
final String reportCommand = String.format("%s -ro %s/%s", this.gatlingLocalHome, this.gatlingLocalResultsDir, this.testName);
System.out.format("Report command: %s%n", reportCommand);
System.out.println(this.executeCommand(reportCommand));
// Upload report to S3
if (this.s3UploadEnabled) {
System.out.format("Trying to upload simulation to S3 location %s/%s/%s%n", this.s3Bucket, this.s3Subfolder,
this.testName);
runner.uploadToS3(this.s3Bucket, this.s3Subfolder + "/" + this.testName,
new File(this.gatlingLocalResultsDir + File.separator + this.testName));
final String url = this.getS3Url();
System.out.format("Results are on %s%n", url);
try {
// Write the results URL into a file. This provides the URL to external tools which might want to link to the results.
FileUtils.fileWrite("results.txt", url);
} catch (final IOException e) {
System.err.println("Can't write result address: " + e);
}
} else {
System.out.println("Skipping upload to S3.");
}
} else {
System.out.println("Running detached, no reports available.");
}
if (this.propagateGatlingFailure && failedInstancesCount > 0) {
throw new MojoExecutionException("Some gatling simulation failed: " + failedInstancesCount);
}
}
private String getS3Url() {
if ("us-east-1".equalsIgnoreCase(this.s3Region)) {
// us-east-1 has no prefix - http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
return String.format("https://s3.amazonaws.com/%s/%s/%s/index.html", this.s3Bucket, this.s3Subfolder, this.testName);
}
return String.format("https://s3-%s.amazonaws.com/%s/%s/%s/index.html", this.s3Region, this.s3Bucket, this.s3Subfolder, this.testName);
}
private String executeCommand(final String command) {
final StringBuffer output = new StringBuffer();
try {
final Process process = Runtime.getRuntime().exec(command);
final int exitCode = process.waitFor();
SshClient.printExitCode(exitCode);
output.append(this.read(new BufferedReader(new InputStreamReader(process.getInputStream()))));
output.append(this.read(new BufferedReader(new InputStreamReader(process.getErrorStream()))));
} catch (final Exception e) {
e.printStackTrace();
}
return output.toString();
}
private StringBuffer read(final BufferedReader reader) throws IOException {
final StringBuffer output = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
output.append(line);
output.append('\n');
}
return output;
}
private int listFailedInstances(final Map<String, Instance> instances, final ConcurrentHashMap<String, Integer> completedHosts) {
int failedInstancesCount = instances.size() - completedHosts.size();
for (final Instance instance : instances.values()) {
final String host = this.getPreferredHostName(instance);
if (!completedHosts.containsKey(host)) {
System.out.format("No result collected from hostname: %s%n", host);
} else if (completedHosts.get(host) != 0) {
System.out.format("Unsuccessful result code: %d on hostname: %s%n", completedHosts.get(host), host);
failedInstancesCount++;
}
}
System.out.format("Load generators were unsuccessful. Failed instances count: %d%n", failedInstancesCount);
System.out.println();
return failedInstancesCount;
}
private String getPreferredHostName(final Instance instance) {
if (this.preferPrivateIpHostnames) {
return instance.getPrivateIpAddress();
}
return instance.getPublicDnsName();
}
}