Skip to content

Commit 797ed9c

Browse files
committed
Get Tomcat setup
1 parent a0691f0 commit 797ed9c

File tree

11 files changed

+179
-65
lines changed

11 files changed

+179
-65
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ We are looking for Java developers that are interested in helping us build the c
222222
```bash
223223
$ mkdir ~/savant
224224
$ cd ~/savant
225-
$ wget http://savant.inversoft.org/org/savantbuild/savant-core/2.0.0-RC.6/savant-2.0.0-RC.7.tar.gz
226-
$ tar xvfz savant-2.0.0-RC.7.tar.gz
227-
$ ln -s ./savant-2.0.0-RC.7 current
225+
$ wget http://savant.inversoft.org/org/savantbuild/savant-core/2.0.0/savant-2.0.0.tar.gz
226+
$ tar xvfz savant-2.0.0.tar.gz
227+
$ ln -s ./savant-2.0.0 current
228228
$ export PATH=$PATH:~/savant/current/bin/
229229
```

build.savant

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ project(group: "io.fusionauth", name: "java-http", version: "0.4.0-RC.7", licens
6363
}
6464

6565
// Plugins
66-
dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:2.0.0-RC.7")
67-
java = loadPlugin(id: "org.savantbuild.plugin:java:2.0.0-RC.6")
68-
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:2.0.0-RC.6")
69-
idea = loadPlugin(id: "org.savantbuild.plugin:idea:2.0.0-RC.7")
70-
release = loadPlugin(id: "org.savantbuild.plugin:release-git:2.0.0-RC.6")
71-
pom = loadPlugin(id: "org.savantbuild.plugin:pom:2.0.0-RC.6")
66+
dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:2.0.0")
67+
java = loadPlugin(id: "org.savantbuild.plugin:java:2.0.0")
68+
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:2.1.0")
69+
idea = loadPlugin(id: "org.savantbuild.plugin:idea:2.0.0")
70+
release = loadPlugin(id: "org.savantbuild.plugin:release-git:2.0.0")
71+
pom = loadPlugin(id: "org.savantbuild.plugin:pom:2.0.0")
7272

7373
java.settings.javaVersion = "21"
7474
java.settings.compilerArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED -XDignore.symbol.file"

load-tests/benchmarks/LinuxConfig - Ultimate Web Server Benchmark.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ ab -n 100000 -c 100 http://localhost:8080/
2929
Test run on:
3030
- MacBook Pro 16-inch 2021, Apple M1 Max, 64 GB
3131

32-
| Server | Requests per second (RPS) | Latency (ms) | Normalized Performance (%) |
33-
| --------- |---------------------------|--------------| -------------------------- |
34-
| java-http | 20,265 | 2.681 | 0 |
32+
| Server | Requests per second (RPS) | Latency (ms) | Normalized Performance (%) |
33+
|---------------|---------------------------|--------------|----------------------------|
34+
| java-http | 20,265 | 2.681 | - |
35+
| apache tomcat | 19,142 | 5.224 | - |
3536

3637
Note that increasing the worker count to 100 produces very similar RPS with a higher latency.
3738

@@ -40,9 +41,10 @@ Note that I believe the results found at the above link were generated w/out usi
4041
For fun, here is the same test using the `-k` parameter. In practice this may be the better test because most web services sit behind an HTTP proxy that will almost certainly use persistent connections in a pool to the HTTP server even if the connection to the client is not using Keep Alive.
4142

4243

43-
| Server | Requests per second (RPS) | Latency (ms) | Normalized Performance (%) |
44-
| --------- |---------------------------|--------------| -------------------------- |
45-
| java-http | 82,443 | 0.606 | 0 |
44+
| Server | Requests per second (RPS) | Latency (ms) | Normalized Performance (%) |
45+
|---------------|---------------------------|--------------|----------------------------|
46+
| java-http | 82,443 | 0.606 | - |
47+
| apache tomcat | 75,516 | 1.324 | - |
4648

4749
## Test 2: Large File Transfers
4850

@@ -66,17 +68,22 @@ ab -n 500 -c 10 http://localhost:8080/file?size=10485760
6668
Test run on:
6769
- MacBook Pro 16-inch 2021, Apple M1 Max, 64 GB
6870

69-
| Server | Requests per second (RPS) | Latency (ms) | Transfer Rate (MB/sec) | Normalized Performance (%) |
70-
| --------- |---------------------------|--------------|------------------------|----------------------------|
71-
| java-http | 625 | 15.998 | 6,251 | |
71+
| Server | Requests per second (RPS) | Latency (ms) | Transfer Rate (MB/sec) | Normalized Performance (%) |
72+
|---------------|---------------------------|--------------|------------------------|----------------------------|
73+
| java-http | 625 | 15.998 | 6,251 | |
74+
| apache tomcat | 503 | 19.857 | 5,036 | |
75+
76+
Note to calculate the Transfer Rate in MB/sec, I am taking the `ab` result of `Kbytes/sec` and dividing by `1024`.
7277

7378
Same test with `-k` which assumes we are using an HTTP proxy with Keep Alive.
7479

7580

76-
| Server | Requests per second (RPS) | Latency (ms) | Transfer Rate (MB/sec) | Normalized Performance (%) |
77-
| --------- |---------------------------|--------------|------------------------|----------------------------|
78-
| java-http | 682 | 14.647 | 6,632 | |
81+
| Server | Requests per second (RPS) | Latency (ms) | Transfer Rate (MB/sec) | Normalized Performance (%) |
82+
|---------------|---------------------------|--------------|------------------------|----------------------------|
83+
| java-http | 682 | 14.647 | 6,632 | |
84+
| apache tomcat | 229 | 43.518 | 2,297 | |
7985

86+
It is unexpected that Apache Tomcat would perform worse with `-k` enabled. It is possible that Tomcat is not handling HTTP/1.0 keep alive properly which could cause the socket to timeout reducing throughput. This is only a guess.
8087

8188
## Test 2: High Concurrency Performance
8289

@@ -94,3 +101,5 @@ ab -n 20000 -c 1000 http://localhost:8080/
94101
- -n 20000 → Total number of requests (20,000)
95102
- -c 1000 → Number of concurrent users (1,000)
96103
- http://localhost:8080/ → URL of the test page
104+
105+
Note, I have been unable to run this command on macOS. I receive an error `socket: Too many open files (24)`. The default on macOS is `256` (`ulimit -n -S`). If I increase the local limit using `ulimit -n -S 16384`, I get past this error and receive another error. `apr_socket_recv: Connection reset by peer (54)`. My guess is the server cannot accept this many sockets connections all at once and the Server Socket is reaching the configured backlog and puking.

load-tests/self/src/main/java/io/fusionauth/http/load/LoadHandler.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@
2222
import java.util.Base64;
2323
import java.util.HashMap;
2424
import java.util.Map;
25+
import java.util.concurrent.ConcurrentHashMap;
2526

2627
import io.fusionauth.http.server.HTTPHandler;
2728
import io.fusionauth.http.server.HTTPRequest;
2829
import io.fusionauth.http.server.HTTPResponse;
2930

3031
public class LoadHandler implements HTTPHandler {
31-
private final Map<Integer, byte[]> Blobs = new HashMap<>();
32+
private static final Map<Integer, byte[]> Blobs = new HashMap<>();
3233

3334
@Override
3435
public void handle(HTTPRequest req, HTTPResponse res) {
@@ -55,32 +56,31 @@ private void handleFailure(HTTPRequest req, HTTPResponse res) {
5556
}
5657

5758
private void handleFile(HTTPRequest req, HTTPResponse res) {
58-
System.out.println("/file");
5959
int size = 1024 * 1024;
6060
var requestedSize = req.getURLParameter("size");
6161
if (requestedSize != null) {
6262
size = Integer.parseInt(requestedSize);
63-
System.out.println("Requested size: " + size);
64-
} else {
65-
System.out.println("Default size: " + size);
6663
}
6764

65+
// Ensure we only build one file
6866
byte[] blob = Blobs.get(size);
6967
if (blob == null) {
70-
System.out.println("Build file with size : " + size);
71-
String s = "Lorem ipsum dolor sit amet";
72-
String body = s.repeat(size / s.length() + (size % s.length()));
73-
assert body.length() == size;
74-
Blobs.put(size, body.getBytes(StandardCharsets.UTF_8));
75-
blob = Blobs.get(size);
76-
assert blob != null;
77-
} else {
78-
System.out.println("Already built file with size: " + size);
68+
synchronized (Blobs) {
69+
blob = Blobs.get(size);
70+
if (blob == null) {
71+
System.out.println("Build file with size : " + size);
72+
String s = "Lorem ipsum dolor sit amet";
73+
String body = s.repeat(size / s.length() + (size % s.length()));
74+
assert body.length() == size;
75+
Blobs.put(size, body.getBytes(StandardCharsets.UTF_8));
76+
blob = Blobs.get(size);
77+
assert blob != null;
78+
}
79+
}
7980
}
8081

8182
res.setStatus(200);
8283
res.setContentType("application/octet-stream");
83-
System.out.println("Write back a file with [" + blob.length + "] bytes");
8484
res.setContentLength(blob.length);
8585

8686
try (OutputStream os = res.getOutputStream()) {

load-tests/tomcat/build.savant

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import java.nio.file.Paths
1515
* either express or implied. See the License for the specific
1616
* language governing permissions and limitations under the License.
1717
*/
18-
restifyVersion = "4.1.2"
19-
testngVersion = "7.6.1"
2018

2119
project(group: "io.fusionauth", name: "tomcat", version: "0.1.10", licenses: ["ApacheV2_0"]) {
2220
workflow {
@@ -50,7 +48,7 @@ project(group: "io.fusionauth", name: "tomcat", version: "0.1.10", licenses: ["A
5048
// Plugins
5149
java = loadPlugin(id: "org.savantbuild.plugin:java:2.0.0")
5250
idea = loadPlugin(id: "org.savantbuild.plugin:idea:2.0.0")
53-
tomcat = loadPlugin(id: "org.savantbuild.plugin:tomcat:2.0.0")
51+
tomcat = loadPlugin(id: "org.savantbuild.plugin:tomcat:2.0.2")
5452
webapp = loadPlugin(id: "org.savantbuild.plugin:webapp:2.0.0")
5553

5654
java.settings.javaVersion = "17"
@@ -73,6 +71,7 @@ target(name: "jar", description: "Builds the project JARs", dependsOn: ["compile
7371

7472
target(name: "war", description: "Builds the project WAR (exploded and JAR)", dependsOn: ["jar"]) {
7573
webapp.build()
74+
webapp.war()
7675
}
7776

7877
target(name: "tomcat", description: "Builds the Tomcat runtime", dependsOn: ["war"]) {
@@ -82,3 +81,9 @@ target(name: "tomcat", description: "Builds the Tomcat runtime", dependsOn: ["wa
8281
target(name: "idea", description: "Updates the IntelliJ IDEA module file") {
8382
idea.iml()
8483
}
84+
85+
target(name: "start", description: "Starts the Tomcat server for load testing!", dependsOn: ["tomcat"]) {
86+
if (new ProcessBuilder('./catalina.sh', 'run').directory(new File("build/dist/tomcat/apache-tomcat/bin")).inheritIO().start().waitFor() != 0) {
87+
fail("Unable to start the server!")
88+
}
89+
}

load-tests/tomcat/src/main/java/io/fusionauth/http/load/LoadServlet.java

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
import javax.servlet.http.HttpServlet;
1919
import javax.servlet.http.HttpServletRequest;
2020
import javax.servlet.http.HttpServletResponse;
21+
import java.io.IOException;
2122
import java.io.InputStream;
22-
import java.io.PrintWriter;
23+
import java.io.OutputStream;
24+
import java.nio.charset.StandardCharsets;
2325
import java.util.Base64;
26+
import java.util.HashMap;
27+
import java.util.Map;
2428

2529
public class LoadServlet extends HttpServlet {
30+
private static final Map<Integer, byte[]> Blobs = new HashMap<>();
31+
2632
@Override
2733
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
2834
doPost(req, res);
@@ -32,6 +38,15 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) {
3238
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
3339
// Note that this should be mostly the same between all load tests.
3440
// - See load-tests/self
41+
switch (req.getPathInfo()) {
42+
case "/" -> handleNoOp();
43+
case "/hello" -> handleHello(req, res);
44+
case "/file" -> handleFile(req, res);
45+
case "/load" -> handleLoad(req, res);
46+
default -> handleFailure(req, res);
47+
}
48+
49+
3550
try (InputStream is = req.getInputStream()) {
3651
byte[] body = is.readAllBytes();
3752
byte[] result = Base64.getEncoder().encode(body);
@@ -43,4 +58,86 @@ protected void doPost(HttpServletRequest req, HttpServletResponse res) {
4358
res.setStatus(500);
4459
}
4560
}
61+
62+
private void handleFailure(HttpServletRequest req, HttpServletResponse res) {
63+
// Path does not match handler.
64+
res.setStatus(400);
65+
byte[] response = ("Invalid path [" + req.getPathInfo() + "]. Supported paths include [/, /text, /file, /echo].").getBytes(StandardCharsets.UTF_8);
66+
res.setContentLength(response.length);
67+
res.setContentType("text/plain");
68+
try {
69+
res.getOutputStream().write(response);
70+
} catch (IOException e) {
71+
res.setStatus(500);
72+
}
73+
}
74+
75+
private void handleFile(HttpServletRequest req, HttpServletResponse res) {
76+
int size = 1024 * 1024;
77+
var requestedSize = req.getParameter("size");
78+
if (requestedSize != null) {
79+
size = Integer.parseInt(requestedSize);
80+
}
81+
82+
// Ensure we only build one file
83+
byte[] blob = Blobs.get(size);
84+
if (blob == null) {
85+
synchronized (Blobs) {
86+
blob = Blobs.get(size);
87+
if (blob == null) {
88+
System.out.println("Build file with size : " + size);
89+
String s = "Lorem ipsum dolor sit amet";
90+
String body = s.repeat(size / s.length() + (size % s.length()));
91+
assert body.length() == size;
92+
Blobs.put(size, body.getBytes(StandardCharsets.UTF_8));
93+
blob = Blobs.get(size);
94+
assert blob != null;
95+
}
96+
}
97+
}
98+
99+
res.setStatus(200);
100+
res.setContentType("application/octet-stream");
101+
res.setContentLength(blob.length);
102+
103+
try (OutputStream os = res.getOutputStream()) {
104+
os.write(blob);
105+
os.flush();
106+
} catch (Exception e) {
107+
res.setStatus(500);
108+
}
109+
}
110+
111+
private void handleHello(HttpServletRequest req, HttpServletResponse res) {
112+
// Hello world
113+
res.setStatus(200);
114+
res.setContentType("text/plain");
115+
byte[] response = "Hello world".getBytes(StandardCharsets.UTF_8);
116+
res.setContentLength(response.length);
117+
118+
try (OutputStream os = res.getOutputStream()) {
119+
os.write(response);
120+
os.flush();
121+
} catch (Exception e) {
122+
res.setStatus(500);
123+
}
124+
}
125+
126+
private void handleLoad(HttpServletRequest req, HttpServletResponse res) {
127+
// Note that this should be mostly the same between all load tests.
128+
// - See load-tests/tomcat
129+
try (InputStream is = req.getInputStream()) {
130+
byte[] body = is.readAllBytes();
131+
byte[] result = Base64.getEncoder().encode(body);
132+
res.setContentLength(result.length);
133+
res.setContentType("text/plain");
134+
res.setStatus(200);
135+
res.getOutputStream().write(result);
136+
} catch (Exception e) {
137+
res.setStatus(500);
138+
}
139+
}
140+
141+
private void handleNoOp() {
142+
}
46143
}

load-tests/tomcat/src/main/tomcat/bin/PLACEHOLDER

Lines changed: 0 additions & 1 deletion
This file was deleted.

load-tests/tomcat/src/main/tomcat/bin/gitkeep

Whitespace-only changes.

load-tests/tomcat/src/main/tomcat/conf/server.xml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,20 @@
1313
- maxParameterCount: A value of less than 0 means no limit.
1414
- maxPostSize: The limit can be disabled by setting this attribute to a value less than zero.
1515
-->
16-
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" compression="on" maxHttpHeaderSize="${cleanspeak-management-interface.http.max-header-size:-10240}" maxParameterCount="-1" maxPostSize="-1" relaxedPathChars="${cleanspeak-management-interface.http.relaxed-path-chars:-&#x5B;&#x5D;&#x7C;}" relaxedQueryChars="${cleanspeak-management-interface.http.relaxed-query-chars:-&#x5B;&#x5D;&#x7C;&#x7B;&#x7D;&#x5E;&#x5C;&#x60;&#x22;&#x3C;&#x3E;}" redirectPort="8443" URIEncoding="UTF-8"/>
16+
<Connector port="8080"
17+
protocol="HTTP/1.1"
18+
connectionTimeout="20000"
19+
compression="on"
20+
maxHttpHeaderSize="10240"
21+
maxParameterCount="-1"
22+
maxPostSize="-1"
23+
relaxedPathChars="&#x5B;&#x5D;&#x7C;"
24+
relaxedQueryChars="&#x5B;&#x5D;&#x7C;&#x7B;&#x7D;&#x5E;&#x5C;&#x60;&#x22;&#x3C;&#x3E;"
25+
redirectPort="8443"
26+
acceptCount="200"
27+
maxConnections="1000"
28+
maxThreads="200"
29+
URIEncoding="UTF-8"/>
1730
<Engine name="Catalina" defaultHost="localhost">
1831
<Host name="localhost" autoDeploy="false" unpackWARs="false">
1932
<Context path="" docBase="${catalina.base}/../web">
@@ -23,4 +36,4 @@
2336
</Host>
2437
</Engine>
2538
</Service>
26-
</Server>
39+
</Server>

load-tests/tomcat/web/WEB-INF/web.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
</servlet>
2222
<servlet-mapping>
2323
<servlet-name>load</servlet-name>
24-
<url-pattern>/load</url-pattern>
24+
<url-pattern>/*</url-pattern>
2525
</servlet-mapping>
2626
</web-app>

0 commit comments

Comments
 (0)