DWR framework.Performance Test.Take 2
As a part of evaluation of presentation layer frameworks, we performed load tests for the DWR framework. We downloaded DWR Version 1.1.beta2.We deployed dwr.war to Tomcat Server. We recorded test scripts using Grinder TCPProxy.
Test modifications
1. We removed from the test script all but the simplest requests. Particularly, we removed requests like testBeanSetParam and testBeanListParam, which caused errors in the Tomcat log file.
2. We disabled DWR logging.
3. We ignored first 100 samples (sample interval=1000 ms).
4. We used fixed time/snapshot method.
5. We used 30 runs.
Performance Test Results
Summary
200 Users
Errors: 560
AART, ms: 100
AART Standard Deviation: 85.1
Max ART: 166
Quality: 0.851
Max CPU utilization, %: 32
160 Users
Errors: 0
AART, ms: 102
AART Standard Deviation: 84.2
Max ART: 166
Quality: 0.825
Max CPU utilization, %: 10
Conclusions
1. Using the hardware and software (described in the previous posts), [the subset of] DWR framework can handle approximately 160 simultaneous active users with a maximum response time of 166 milliseconds.
2. The quality of the test is far beyond the limit, which means that further investigation is necessary (including Java code profiling).
3. For 200 users, CPU utilization increases until the Tomcat server starts refusing connections.
4. Logging – disabling the DWR logging improved the AART by 5.5%.
5. We tested only a small subset of the DWR framework functionality.
Grinder properties
grinder.processes=1
grinder.threads=200
grinder.runs=30
grinder.consoleHost=localhost
grinder.receiveConsoleSignals=true
grinder.reportToConsole=true
grinder.logDirectory=log
grinder.appendLog=true
grinder.numberOfOldLogs=0
grinder.initialSleepTime=1000
grinder.sleepTimeFactor=1
grinder.sleepTimeVariation=0.2
grinder.script=httpscript.py
url=http://X.X.X.X:8080
Log files
Excerpts from the Grinder log files (200 users):
3/1/06 12:58:29 PM (thread 32 run 4): Aborted run, script threw class java.net.BindException: Address already in use: connect
java.net.BindException: Address already in use: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:364)
at java.net.Socket.connect(Socket.java:507)
at java.net.Socket.connect(Socket.java:457)
at java.net.Socket.<init>(Socket.java:365)
at java.net.Socket.<init>(Socket.java:207)
at HTTPClient.HTTPConnection.getSocket(HTTPConnection.java:3264)
at HTTPClient.HTTPConnection.sendRequest(HTTPConnection.java:2994)
at HTTPClient.HTTPConnection.handleRequest(HTTPConnection.java:2801)
at HTTPClient.HTTPConnection.setupRequest(HTTPConnection.java:2593)
at HTTPClient.HTTPConnection.Post(HTTPConnection.java:1095)
at net.grinder.plugin.http.HTTPRequest$6.doRequest(HTTPRequest.java:647)
at net.grinder.plugin.http.HTTPRequest$AbstractRequest.getHTTPResponse(HTTPRequest.java:827)
at net.grinder.plugin.http.HTTPRequest.POST(HTTPRequest.java:644)
at net.grinder.plugin.http.HTTPRequest.POST(HTTPRequest.java:623)
at sun.reflect.GeneratedMethodAccessor7.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java)
at org.python.core.PyMethod.__call__(PyMethod.java)
at org.python.core.PyObject.__call__(PyObject.java)
at org.python.core.PyInstance.invoke(PyInstance.java)
at net.grinder.engine.process.TestPyJavaInstance.access$201(TestPyJavaInstance.java:35)
at net.grinder.engine.process.TestPyJavaInstance$3.call(TestPyJavaInstance.java:88)
at net.grinder.engine.process.ThreadContextImplementation.invokeTest(ThreadContextImplementation.java:155)
at net.grinder.engine.process.TestData.dispatch(TestData.java:79)
at net.grinder.engine.process.TestPyJavaInstance.dispatch(TestPyJavaInstance.java:48)
at net.grinder.engine.process.TestPyJavaInstance.invoke(TestPyJavaInstance.java:85)
at org.python.pycode._pyx0.__call__$2(httpscript.py:57)
at org.python.pycode._pyx0.call_function(httpscript.py)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyFunction.__call__(PyFunction.java)
at org.python.core.PyMethod.__call__(PyMethod.java)
at org.python.core.PyObject.invoke(PyObject.java)
at org.python.core.PyInstance.__call__(PyInstance.java)
at org.python.core.PyObject.__call__(PyObject.java)
at net.grinder.engine.process.JythonScript$JythonRunnable.run(JythonScript.java:144)
at net.grinder.engine.process.GrinderThread.run(GrinderThread.java:128)
at java.lang.Thread.run(Thread.java:595)
Test Scripts
httpscript.py
#
# The Grinder version 3.0-beta26
#
# Script recorded by the TCPProxy at Jan 13, 2006 11:06:03 AM
#
from httpscript_tests import *
from net.grinder.script.Grinder import grinder
url = grinder.properties.get("url")
class TestRunner:
def __call__(self):
grinder.sleep(62)
tests[1].GET(url + '/dwr/')
grinder.sleep(47)
tests[2].GET(url + '/dwr/dwr/interface/Test.js')
grinder.sleep(78)
tests[3].GET(url + '/dwr/dwr/engine.js')
grinder.sleep(125)
tests[4].GET(url + '/dwr/dwr/util.js')
grinder.sleep(140)
tests[5].POST(url + '/dwr/dwr/exec/Test.getInsert', data5)
grinder.sleep(4563)
tests[6].GET(url + '/dwr/test.html')
tests[7].GET(url + '/dwr/dwr/interface/Test.js')
grinder.sleep(78)
tests[8].GET(url + '/dwr/testsuite.js')
grinder.sleep(3750)
tests[10].POST(url + '/dwr/dwr/exec/Test.booleanParam', data10)
grinder.sleep(2937)
tests[11].POST(url + '/dwr/dwr/exec/Test.byteParam', data11)
grinder.sleep(2140)
tests[12].POST(url + '/dwr/dwr/exec/Test.shortParam', data12)
grinder.sleep(2875)
tests[13].POST(url + '/dwr/dwr/exec/Test.intParam', data13)
grinder.sleep(1875)
tests[14].POST(url + '/dwr/dwr/exec/Test.longParam', data14)
grinder.sleep(3579)
tests[15].POST(url + '/dwr/dwr/exec/Test.floatParam', data15)
grinder.sleep(2547)
tests[16].POST(url + '/dwr/dwr/exec/Test.doubleParam', data16)
grinder.sleep(2578)
tests[17].POST(url + '/dwr/dwr/exec/Test.bigDecimalParam', data17)
grinder.sleep(4250)
tests[18].POST(url + '/dwr/dwr/exec/Test.bigIntegerParam', data18)
grinder.sleep(6938)
tests[19].POST(url + '/dwr/dwr/exec/Test.charParam', data19)
grinder.sleep(9390)
tests[20].POST(url + '/dwr/dwr/exec/Test.stringParam', data20)
