├── .gitignore ├── Makefile ├── README.md ├── README.old.md ├── SConstruct ├── analyze.py ├── wsperf.cpp ├── wsperf.py └── wspp-config.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | wsperf 2 | .sconsign.dblite 3 | *.o 4 | *.json 5 | perf* 6 | *.py[co] 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | rm -f result*.json 3 | sudo rm -f perf* 4 | 5 | test_ab: 6 | pypy wsperf.py --wsuri ws://127.0.0.1:9000 --workers 4 --threads 0 --conns 50000 --lowmark 250 --highmark 500 7 | 8 | test_ws: 9 | pypy wsperf.py --wsuri ws://127.0.0.1:9002 --workers 4 --threads 0 --conns 50000 --lowmark 250 --highmark 500 10 | 11 | test_netty: 12 | pypy wsperf.py --wsuri ws://127.0.0.1:9999 --workers 4 --threads 0 --conns 50000 --lowmark 250 --highmark 500 13 | 14 | analyze: 15 | pypy wsperf.py --workers 4 --skiprun 16 | 17 | help: 18 | pypy wsperf.py --help 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wsperf 2 | 3 | **wsperf** is a WebSocket load testing probe that can be used for load testing and performance benchmarking of WebSocket servers: 4 | 5 | * comes as a command line tool that builds on [WebSocket++](http://www.zaphoyd.com/websocketpp), a high-performance C++/ASIO based WebSocket library. 6 | * designed to work stand-alone or being controlled from **wstest**, which comes as part of the [Autobahn WebSocket testsuite](http://autobahn.ws/testsuite/). 7 | 8 | ## Description 9 | 10 | The first test mode implemented is **WebSocket Handshaking**. 11 | 12 | In this mode, **wsperf** will: 13 | 14 | 1. open a TCP connection to a testee 15 | 2. perform a WebSocket opening handshake 16 | 3. perform a WebSocket closing handshake 17 | 4. close the TCP connection 18 | 19 | It can do so in parallel and using multiple threads. You can fine control exactly how it operates (see usage section below). 20 | 21 | It will create a detailed log file (JSON format) with high-precision timestamps for all states a connection goes through (and also of course if the connection was successful at all). 22 | 23 | The generated log file can then be post-processed with **wstest** to obtain statistics like in the following: 24 | 25 | Aggregate results (WebSocket Opening+Closing Handshake) 26 | 27 | Duration: 13.4 s 28 | Total: 200000 29 | Success: 200000 30 | Fail: 0 31 | Fail %: 0.00 32 | Handshakes/sec: 14883 33 | 34 | Min: 2.0 ms 35 | SD: 10.7 ms 36 | Avg: 67.6 ms 37 | Median: 67.3 ms 38 | q90 : 81.2 ms 39 | q95 : 85.2 ms 40 | q99 : 93.2 ms 41 | q99.9 : 104.9 ms 42 | q99.99: 108.3 ms 43 | Max: 109.2 ms 44 | 45 | 46 | Analyze done. 47 | 48 | 49 | 50 | ## Building 51 | 52 | **wsperf** is currently developed and tested on Unix like systems (I use Ubuntu 12.04 LTS x64). 53 | 54 | You will need a *decent* C++ compiler, currently at least *GCC 4.6* or *clang X.X*. 55 | 56 | The only dependencies of **wsperf** are: 57 | 58 | * [Boost](http://boost.org/) 59 | * [WebSocket++](https://github.com/zaphoyd/websocketpp) 60 | 61 | ### Boost 62 | 63 | Don't waste time on your distro's packaged Boost - likely too old. Build from the source, Luke;) 64 | 65 | Also see the [Boost Getting Started](http://www.boost.org/doc/libs/1_54_0/more/getting_started/unix-variants.html). 66 | 67 | Get Boost from [here](http://sourceforge.net/projects/boost/files/boost/1.55.0.beta.1/). 68 | 69 | cd ~/build 70 | tar xvjf ../tarballs/boost_1_55_0b1.tar.bz2 71 | cd boost_1_55_0b1 72 | ./bootstrap.sh --prefix=$HOME/boost_1_55_0b1 73 | ./b2 -j 4 install 74 | 75 | This will take a little time. 76 | 77 | 78 | ### WebSocket++ 79 | 80 | WebSocket++ is a header-only library. So all you need is: 81 | 82 | cd ~/scm 83 | git clone git@github.com:zaphoyd/websocketpp.git 84 | 85 | ### SCons 86 | 87 | **wsperf** is built using [SCons](http://scons.org/), a Python based build tool. 88 | 89 | So if you have Python installed, all you need is: 90 | 91 | easy_install scons 92 | 93 | ### wsperf 94 | 95 | To build **wsperf**, you will need to have 2 environment variables set: 96 | 97 | * `BOOST_ROOT` pointing to a Boost installation 98 | * `WSPP_ROOT` pointing to a WebSocket++ source distribution 99 | 100 | Like add the following to your `.bashrc`: 101 | 102 | export BOOST_ROOT=${HOME}/boost_1_55_0b1 103 | export WSPP_ROOT=${HOME}/scm/websocketpp 104 | 105 | Now get the source and build 106 | 107 | cd ~/scm 108 | git clone git@github.com:zaphoyd/wsperf.git 109 | cd wsperf 110 | scons 111 | 112 | When successful, this should produce a `wsperf` executable (optimized, statically linked and unstripped). 113 | 114 | To cleanup 115 | 116 | scons -uc 117 | 118 | ## Usage 119 | 120 | Basic usage of **wsperf**: 121 | 122 | wsperf 123 | 124 | like e.g. 125 | 126 | wsperf ws://127.0.0.1:9000 4 200000 1000 2000 results.json 127 | 128 | The `wsuri` the the WebSocket address of the testee. 129 | 130 | The `threads` parameter controls the number of background worker threads to be spawned. 131 | 132 | > It can also be `0` in which case the load is processed on the main thread. Note that ASIO will nevertheless create a background thread of asynchronous name resolution. So you see 2 threads for **wsperf** even if run with `threads==0`. 133 | > 134 | 135 | The `connections` is the total number of WebSocket connections that are opened to the testee - not concurrently, but in total. Also note that **wsperf** will currently not retry a failing connection. 136 | 137 | The `result_file` is the name of the log file to produce. 138 | 139 | The `low_watermark` and `high_watermark` control how many parallel connections will be in flight as follows: 140 | 141 | **wsperf** will open new TCP connections to the testee and perform WebSocket opening handshakes on those as fast as it can up till the `high_watermark` connections is reached. That is connections which have not yet again been closed. 142 | 143 | When that happens, it will stop trying to connect more. After some time, more WebSocket connections will get closed again (by performing a closing handshakes), and the outstanding number reaches the `low_watermark`, **wsperf** will start again connecting as fast as it can. 144 | 145 | So the watermarks limit the number of WebSocket connections that haven't yet reached the "open" state .. hence are still in flight. 146 | 147 | ## Postprocessing 148 | 149 | Writeme. 150 | -------------------------------------------------------------------------------- /README.old.md: -------------------------------------------------------------------------------- 1 | # wsperf 2 | 3 | **wsperf** is a WebSocket load testing probe. 4 | 5 | ## Building 6 | 7 | You will need to have 2 environment variables set: 8 | 9 | * `BOOSTROOT` pointing to a Boost installation 10 | * `WSPP_ROOT` pointing to a WebSocket++ source distribution 11 | 12 | Note that (for now), you will need the `flow_control2` branch with WebSocket++ checked out. 13 | 14 | To build: 15 | 16 | scons 17 | 18 | To cleanup 19 | 20 | scons -uc 21 | 22 | 23 | ## Usage 24 | 25 | time ./wsperf ws://127.0.0.1:9000 4 200000 1000 2000 > results.json 26 | 27 | 28 | ## Analyze 29 | 30 | Analyzing results can be done with `analyze.py` (a quick hack, needs more love). We need a streaming JSON parser, since results are getting big: 31 | 32 | git clone git@github.com:oberstet/ijson.git 33 | cd ijson 34 | ~/pypy-2.1/bin/easy_install ijson 35 | 36 | 37 | ## Results 38 | 39 | ### WebSocket++ 40 | 41 | Build and run the testee: 42 | 43 | $ cd ~/scm/websocketpp 44 | $ scons 45 | $./build/release/testee_server/testee_server 9002 4 46 | 47 | Run the test (from another shell): 48 | 49 | $ cd ~/scm/wsperf 50 | $ make test_ws 51 | pypy wsperf.py --wsuri ws://127.0.0.1:9002 --workers 4 --threads 0 --conns 50000 --lowmark 250 --highmark 500 52 | Loading wsperf result file result_0.json .. 53 | Loading wsperf result file result_1.json .. 54 | Loading wsperf result file result_2.json .. 55 | Loading wsperf result file result_3.json .. 56 | 57 | Aggregate results (WebSocket Opening+Closing Handshake) 58 | 59 | Duration: 13.5 s 60 | Total: 200000 61 | Success: 200000 62 | Fail: 0 63 | Fail %: 0.00 64 | Handshakes/sec: 14796 65 | 66 | Min: 7.6 ms 67 | SD: 11.2 ms 68 | Avg: 68.4 ms 69 | Median: 68.2 ms 70 | q90 : 82.8 ms 71 | q95 : 87.6 ms 72 | q99 : 96.5 ms 73 | q99.9 : 106.4 ms 74 | q99.99: 108.6 ms 75 | Max: 109.2 ms 76 | 77 | 78 | Analyze done. 79 | 80 | 81 | ### Autobahn Multicore 82 | 83 | Run the testee: 84 | 85 | $ cd ~/scm/AutobahnPython/examples/websocket/echo_multicore 86 | $ pypy server.py --wsuri ws://localhost:9000 --workers 4 --silence 87 | 88 | Run the test (from another shell .. make sure to run that test multiple times without restarting the testee to give the PyPy JIT compiler chance to warmup on the hotpaths): 89 | 90 | $ cd ~/scm/wsperf 91 | $ make test_ab 92 | pypy wsperf.py --wsuri ws://127.0.0.1:9000 --workers 4 --threads 0 --conns 50000 --lowmark 250 --highmark 500 93 | Loading wsperf result file result_0.json .. 94 | Loading wsperf result file result_1.json .. 95 | Loading wsperf result file result_2.json .. 96 | Loading wsperf result file result_3.json .. 97 | 98 | Aggregate results (WebSocket Opening+Closing Handshake) 99 | 100 | Duration: 11.7 s 101 | Total: 200000 102 | Success: 200000 103 | Fail: 0 104 | Fail %: 0.00 105 | Handshakes/sec: 17058 106 | 107 | Min: 1.0 ms 108 | SD: 16.9 ms 109 | Avg: 31.6 ms 110 | Median: 27.6 ms 111 | q90 : 52.2 ms 112 | q95 : 65.9 ms 113 | q99 : 95.8 ms 114 | q99.9 : 115.2 ms 115 | q99.99: 124.7 ms 116 | Max: 138.0 ms 117 | 118 | 119 | Analyze done. 120 | 121 | ### Netty 122 | 123 | Run the testee: 124 | 125 | cd ~/scm/netty 126 | mvn install 127 | java -cp ./all/target/netty-all-4.0.12.Final-SNAPSHOT.jar:./example/target/netty-example-4.0.12.Final-SNAPSHOT.jar io.netty.example.http.websocketx.autobahn.AutobahnServer 9999 128 | 129 | Run the test (from another shell .. make sure to run that test multiple times without restarting the testee to give the HotSpot JIT compiler chance to warmup on the hotpaths): 130 | 131 | $ cd ~/scm/wsperf 132 | $ make test_netty 133 | pypy wsperf.py --wsuri ws://127.0.0.1:9999 --workers 4 --threads 0 --conns 50000 --lowmark 250 --highmark 500 134 | Loading wsperf result file result_0.json .. 135 | Loading wsperf result file result_1.json .. 136 | Loading wsperf result file result_2.json .. 137 | Loading wsperf result file result_3.json .. 138 | 139 | Aggregate results (WebSocket Opening+Closing Handshake) 140 | 141 | Duration: 12.2 s 142 | Total: 200000 143 | Success: 200000 144 | Fail: 0 145 | Fail %: 0.00 146 | Handshakes/sec: 16397 147 | 148 | Min: 2.4 ms 149 | SD: 34.7 ms 150 | Avg: 29.3 ms 151 | Median: 25.2 ms 152 | q90 : 33.4 ms 153 | q95 : 36.9 ms 154 | q99 : 194.0 ms 155 | q99.9 : 420.2 ms 156 | q99.99: 466.5 ms 157 | Max: 477.3 ms 158 | 159 | 160 | Analyze done. 161 | 162 | ### Linux Perf 163 | 164 | Basic statistics: 165 | 166 | oberstet@corei7-ubuntu:~/scm/wsperf$ sudo perf stat ./wsperf ws://127.0.0.1:9000 8 200000 1000 2000 > results.json 167 | 168 | Performance counter stats for './wsperf ws://127.0.0.1:9000 8 200000 1000 2000': 169 | 170 | 56397,171142 task-clock # 3,576 CPUs utilized 171 | 177.411 context-switches # 0,003 M/sec 172 | 23.577 cpu-migrations # 0,418 K/sec 173 | 595.352 page-faults # 0,011 M/sec 174 | 186.462.389.466 cycles # 3,306 GHz [83,25%] 175 | 144.686.178.873 stalled-cycles-frontend # 77,60% frontend cycles idle [83,57%] 176 | 83.129.352.607 stalled-cycles-backend # 44,58% backend cycles idle [66,67%] 177 | 84.877.715.262 instructions # 0,46 insns per cycle 178 | # 1,70 stalled cycles per insn [83,35%] 179 | 16.967.531.711 branches # 300,858 M/sec [83,40%] 180 | 673.808.823 branch-misses # 3,97% of all branches [83,11%] 181 | 182 | 15,771390852 seconds time elapsed 183 | 184 | oberstet@corei7-ubuntu:~/scm/wsperf$ 185 | 186 | 187 | Detailed event recording: 188 | 189 | oberstet@corei7-ubuntu:~/scm/wsperf$ sudo perf record -e cycles,branch-misses,cache-misses ./wsperf ws://127.0.0.1:9000 8 200000 1000 2000 > results.json 190 | [ perf record: Woken up 119 times to write data ] 191 | [ perf record: Captured and wrote 30.653 MB perf.data (~1339248 samples) ] 192 | 193 | Reporting 194 | 195 | oberstet@corei7-ubuntu:~/scm/wsperf$ sudo perf report --stdio 196 | # ======== 197 | # captured on: Mon Nov 4 08:03:59 2013 198 | # hostname : corei7-ubuntu 199 | # os release : 3.8.0-32-generic 200 | # perf version : 3.8.13.10 201 | # arch : x86_64 202 | # nrcpus online : 8 203 | # nrcpus avail : 8 204 | # cpudesc : Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz 205 | # cpuid : GenuineIntel,6,26,4 206 | # total memory : 12296476 kB 207 | # cmdline : /usr/bin/perf_3.8.0-32 record -e cycles,branch-misses,cache-misses ./wsperf ws://127.0.0.1:9000 208 | # event : name = cycles, type = 0, config = 0x0, config1 = 0x0, config2 = 0x0, excl_usr = 0, excl_kern = 0, 209 | # event : name = branch-misses, type = 0, config = 0x5, config1 = 0x0, config2 = 0x0, excl_usr = 0, excl_ker 210 | # event : name = cache-misses, type = 0, config = 0x3, config1 = 0x0, config2 = 0x0, excl_usr = 0, excl_kern 211 | # HEADER_CPU_TOPOLOGY info available, use -I to display 212 | # HEADER_NUMA_TOPOLOGY info available, use -I to display 213 | # pmu mappings: cpu = 4, software = 1, tracepoint = 2, uncore = 6, breakpoint = 5 214 | # ======== 215 | # 216 | # Samples: 249K of event 'cycles' 217 | # Event count (approx.): 190156545668 218 | # 219 | # Overhead Command Shared Object 220 | # ........ ....... ................... .................................................................. 221 | # 222 | 4.68% wsperf libc-2.15.so [.] _int_malloc 223 | 3.94% wsperf libc-2.15.so [.] _int_free 224 | 3.20% wsperf [kernel.kallsyms] [k] __ticket_spin_lock 225 | 2.91% wsperf libc-2.15.so [.] malloc 226 | 1.86% wsperf wsperf [.] std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_cou 227 | 1.84% wsperf wsperf [.] std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_coun 228 | 1.61% wsperf libpthread-2.15.so [.] pthread_mutex_lock 229 | 1.28% wsperf libc-2.15.so [.] tolower 230 | 1.12% wsperf libc-2.15.so [.] __memcpy_ssse3_back 231 | 1.09% wsperf libstdc++.so.6.0.16 [.] __cxxabiv1::__vmi_class_type_info::__do_dyncast(long, __cxxabi 232 | 1.06% wsperf libc-2.15.so [.] free 233 | ... 234 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os, sys, commands 2 | env = Environment(ENV = os.environ) 3 | 4 | # figure out a better way to configure this 5 | if os.environ.has_key('CXX'): 6 | env['CXX'] = os.environ['CXX'] 7 | 8 | if os.environ.has_key('DEBUG'): 9 | env['DEBUG'] = os.environ['DEBUG'] 10 | 11 | if os.environ.has_key('CXXFLAGS'): 12 | env.Append(CXXFLAGS = os.environ['CXXFLAGS']) 13 | 14 | if os.environ.has_key('LINKFLAGS'): 15 | env.Append(LINKFLAGS = os.environ['LINKFLAGS']) 16 | 17 | 18 | ## WebSocket++ 19 | ## 20 | if not os.environ.has_key('WSPP_ROOT'): 21 | raise SCons.Errors.UserError, "WebSocket++ directory not set: missing WSPP_ROOT environment variable" 22 | 23 | 24 | ## Boost 25 | ## 26 | ## Note: You need to either set BOOSTROOT to the root of a stock Boost distribution 27 | ## or set BOOST_INCLUDES and BOOST_LIBS if Boost comes with your OS distro e.g. and 28 | ## needs BOOST_INCLUDES=/usr/include/boost and BOOST_LIBS=/usr/lib like Ubuntu. 29 | ## 30 | if os.environ.has_key('BOOSTROOT'): 31 | os.environ['BOOST_ROOT'] = os.environ['BOOSTROOT'] 32 | 33 | if os.environ.has_key('BOOST_ROOT'): 34 | env['BOOST_INCLUDES'] = os.path.join(os.environ['BOOST_ROOT'], 'include') 35 | env['BOOST_LIBS'] = os.path.join(os.environ['BOOST_ROOT'], 'lib') 36 | elif os.environ.has_key('BOOST_INCLUDES') and os.environ.has_key('BOOST_LIBS'): 37 | env['BOOST_INCLUDES'] = os.environ['BOOST_INCLUDES'] 38 | env['BOOST_LIBS'] = os.environ['BOOST_LIBS'] 39 | else: 40 | raise SCons.Errors.UserError, "Neither BOOST_ROOT, nor BOOST_INCLUDES + BOOST_LIBS was set!" 41 | 42 | env.Append(CPPPATH = [env['BOOST_INCLUDES']]) 43 | env.Append(LIBPATH = [env['BOOST_LIBS']]) 44 | 45 | if os.environ.has_key('WSPP_ENABLE_CPP11'): 46 | env['WSPP_ENABLE_CPP11'] = True 47 | else: 48 | env['WSPP_ENABLE_CPP11'] = False 49 | 50 | boost_linkshared = False 51 | 52 | def boostlibs(libnames,localenv): 53 | if localenv['PLATFORM'].startswith('win'): 54 | # Win/VC++ supports autolinking. nothing to do. 55 | # http://www.boost.org/doc/libs/1_49_0/more/getting_started/windows.html#auto-linking 56 | return [] 57 | else: 58 | libs = [] 59 | prefix = localenv['SHLIBPREFIX'] if boost_linkshared else localenv['LIBPREFIX'] 60 | suffix = localenv['SHLIBSUFFIX'] if boost_linkshared else localenv['LIBSUFFIX'] 61 | for name in libnames: 62 | lib = File(os.path.join(localenv['BOOST_LIBS'], '%sboost_%s%s' % (prefix, name, suffix))) 63 | libs.append(lib) 64 | return libs 65 | 66 | if env['PLATFORM'].startswith('win'): 67 | env.Append(CPPDEFINES = ['WIN32', 68 | 'NDEBUG', 69 | 'WIN32_LEAN_AND_MEAN', 70 | '_WIN32_WINNT=0x0600', 71 | '_CONSOLE', 72 | 'BOOST_TEST_DYN_LINK', 73 | 'NOMINMAX', 74 | '_WEBSOCKETPP_CPP11_MEMORY_', 75 | '_WEBSOCKETPP_CPP11_FUNCTIONAL_']) 76 | arch_flags = '/arch:SSE2' 77 | opt_flags = '/Ox /Oi /fp:fast' 78 | warn_flags = '/W3 /wd4996 /wd4995 /wd4355' 79 | env['CCFLAGS'] = '%s /EHsc /GR /GS- /MD /nologo %s %s' % (warn_flags, arch_flags, opt_flags) 80 | env['LINKFLAGS'] = '/INCREMENTAL:NO /MANIFEST /NOLOGO /OPT:REF /OPT:ICF /MACHINE:X86' 81 | elif env['PLATFORM'] == 'posix': 82 | if env.has_key('DEBUG'): 83 | env.Append(CCFLAGS = ['-g', '-O0']) 84 | else: 85 | env.Append(CPPDEFINES = ['NDEBUG']) 86 | env.Append(CCFLAGS = ['-O3']) 87 | #env.Append(CCFLAGS = ['-O3', '-fomit-frame-pointer']) 88 | env.Append(CCFLAGS = ['-Wall']) 89 | #env['LINKFLAGS'] = '' 90 | elif env['PLATFORM'] == 'darwin': 91 | if env.has_key('DEBUG'): 92 | env.Append(CCFLAGS = ['-g', '-O0']) 93 | else: 94 | env.Append(CPPDEFINES = ['NDEBUG']) 95 | env.Append(CCFLAGS = ['-O3']) 96 | #env.Append(CCFLAGS = ['-O3', '-fomit-frame-pointer']) 97 | env.Append(CCFLAGS = ['-Wall']) 98 | #env['LINKFLAGS'] = '' 99 | 100 | if env['PLATFORM'].startswith('win'): 101 | #env['LIBPATH'] = env['BOOST_LIBS'] 102 | pass 103 | else: 104 | env['LIBPATH'] = ['/usr/lib', 105 | '/usr/local/lib'] #, env['BOOST_LIBS'] 106 | 107 | # Compiler specific warning flags 108 | if env['CXX'].startswith('g++'): 109 | #env.Append(CCFLAGS = ['-Wconversion']) 110 | env.Append(CCFLAGS = ['-Wcast-align']) 111 | elif env['CXX'].startswith('clang++'): 112 | #env.Append(CCFLAGS = ['-Wcast-align']) 113 | #env.Append(CCFLAGS = ['-Wglobal-constructors']) 114 | #env.Append(CCFLAGS = ['-Wconversion']) 115 | env.Append(CCFLAGS = ['-Wno-padded']) 116 | 117 | # Wpadded 118 | # Wsign-conversion 119 | 120 | platform_libs = [] 121 | tls_libs = [] 122 | 123 | tls_build = False 124 | 125 | if env['PLATFORM'] == 'posix': 126 | platform_libs = ['pthread', 'rt'] 127 | tls_libs = ['ssl', 'crypto'] 128 | tls_build = True 129 | elif env['PLATFORM'] == 'darwin': 130 | tls_libs = ['ssl', 'crypto'] 131 | tls_build = True 132 | elif env['PLATFORM'].startswith('win'): 133 | # Win/VC++ supports autolinking. nothing to do. 134 | pass 135 | 136 | 137 | ##### Set up C++11 environment 138 | polyfill_libs = [] # boost libraries used as drop in replacements for incomplete 139 | # C++11 STL implementations 140 | env_cpp11 = env.Clone () 141 | 142 | if env_cpp11['CXX'].startswith('g++'): 143 | # TODO: check g++ version 144 | GCC_VERSION = commands.getoutput(env_cpp11['CXX'] + ' -dumpversion') 145 | 146 | if GCC_VERSION > "4.4.0": 147 | print "C++11 build environment partially enabled" 148 | env_cpp11.Append(WSPP_CPP11_ENABLED = "true",CXXFLAGS = ['-std=c++0x'],TOOLSET = ['g++'],CPPDEFINES = ['_WEBSOCKETPP_CPP11_STL_']) 149 | else: 150 | print "C++11 build environment is not supported on this version of G++" 151 | elif env_cpp11['CXX'].startswith('clang++'): 152 | print "C++11 build environment enabled" 153 | env_cpp11.Append(WSPP_CPP11_ENABLED = "true",CXXFLAGS = ['-std=c++0x','-stdlib=libc++'],LINKFLAGS = ['-stdlib=libc++'],TOOLSET = ['clang++'],CPPDEFINES = ['_WEBSOCKETPP_CPP11_STL_']) 154 | 155 | # look for optional second boostroot compiled with clang's libc++ STL library 156 | # this prevents warnings/errors when linking code built with two different 157 | # incompatible STL libraries. 158 | if os.environ.has_key('BOOST_ROOT_CPP11'): 159 | env_cpp11['BOOST_INCLUDES'] = os.environ['BOOST_ROOT_CPP11'] 160 | env_cpp11['BOOST_LIBS'] = os.path.join(os.environ['BOOST_ROOT_CPP11'], 'stage', 'lib') 161 | elif os.environ.has_key('BOOST_INCLUDES_CPP11') and os.environ.has_key('BOOST_LIBS_CPP11'): 162 | env_cpp11['BOOST_INCLUDES'] = os.environ['BOOST_INCLUDES_CPP11'] 163 | env_cpp11['BOOST_LIBS'] = os.environ['BOOST_LIBS_CPP11'] 164 | else: 165 | print "C++11 build environment disabled" 166 | 167 | # if the build system is known to allow the isystem modifier for library include 168 | # values then use it for the boost libraries. Otherwise just add them to the 169 | # regular CPPPATH values. 170 | if env['CXX'].startswith('g++') or env['CXX'].startswith('clang'): 171 | env.Append(CPPFLAGS = '-isystem ' + env['BOOST_INCLUDES']) 172 | else: 173 | env.Append(CPPPATH = [env['BOOST_INCLUDES']]) 174 | env.Append(LIBPATH = [env['BOOST_LIBS']]) 175 | 176 | # if the build system is known to allow the isystem modifier for library include 177 | # values then use it for the boost libraries. Otherwise just add them to the 178 | # regular CPPPATH values. 179 | if env_cpp11['CXX'].startswith('g++') or env_cpp11['CXX'].startswith('clang'): 180 | env_cpp11.Append(CPPFLAGS = '-isystem ' + env_cpp11['BOOST_INCLUDES']) 181 | else: 182 | env_cpp11.Append(CPPPATH = [env_cpp11['BOOST_INCLUDES']]) 183 | env_cpp11.Append(LIBPATH = [env_cpp11['BOOST_LIBS']]) 184 | 185 | 186 | env.Append(CPPPATH = [os.environ['WSPP_ROOT']]) 187 | env_cpp11.Append(CPPPATH = [os.environ['WSPP_ROOT']]) 188 | 189 | ## END OF CONFIG !! 190 | 191 | ## TARGETS: 192 | 193 | env = env.Clone () 194 | env_cpp11 = env_cpp11.Clone () 195 | 196 | ## strip executable (this remove symbol tables - but we may want it even for 197 | ## non-debug builds for Linux "perf") 198 | ## 199 | STRIP = False 200 | 201 | # if a C++11 environment is available build using that, otherwise use boost 202 | if env_cpp11.has_key('WSPP_CPP11_ENABLED'): 203 | 204 | ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + [tls_libs] 205 | wsperf = env_cpp11.Program('wsperf', ["wsperf.cpp"], LIBS = ALL_LIBS) 206 | 207 | else: 208 | 209 | ALL_LIBS = boostlibs(['system'],env) + [platform_libs] + [polyfill_libs] + [tls_libs] 210 | wsperf = env.Program('wsperf', ["wsperf.cpp"], LIBS = ALL_LIBS) 211 | 212 | 213 | if not env.has_key('DEBUG') and STRIP: 214 | env.AddPostAction (wsperf, env.Action('strip ' + str(wsperf[0]))) 215 | -------------------------------------------------------------------------------- /analyze.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import math 3 | 4 | import ijson.backends.python as ijson 5 | #import ijson.backends.yajl2 as ijson 6 | #import ijson.backends.yajl as ijson 7 | 8 | 9 | class Result: 10 | 11 | def __init__(self): 12 | self.success = 0 13 | self.fail = 0 14 | self.total = 0 15 | self.duration = 0 16 | 17 | ## Wallclock in us since epoch 18 | self.started = 0 19 | self.ended = 0 20 | 21 | ## Wallclock duration in us 22 | self.durationWallclock = 0 23 | 24 | self.openTimestamps = [] 25 | self.closeTimestamps = [] 26 | self.preInitMin = None 27 | self.preInitMax = None 28 | 29 | 30 | def joinResults(results): 31 | res = Result() 32 | for r in results: 33 | res.success += r.success 34 | res.fail += r.fail 35 | res.total += r.total 36 | res.duration += r.duration 37 | res.openTimestamps.extend(r.openTimestamps) 38 | res.closeTimestamps.extend(r.closeTimestamps) 39 | 40 | if res.started == 0 or r.started < res.started: 41 | res.started = r.started 42 | 43 | if res.ended == 0 or r.ended > res.ended: 44 | res.ended = r.ended 45 | 46 | res.durationWallclock = res.ended - res.started 47 | 48 | return res 49 | 50 | 51 | def printStats(timestamps): 52 | r = sorted(timestamps) 53 | r_cnt = len(timestamps) 54 | r_min = float(r[0]) 55 | r_median = float(r[len(r)/2]) 56 | r_avg = float(sum(timestamps)) / float(r_cnt) 57 | r_sd = math.sqrt(sum([((float(x) - r_avg)**2.) for x in timestamps]) / (float(r_cnt) - 1.)) 58 | r_q90 = float(r[-len(r)/10]) 59 | r_q95 = float(r[-len(r)/20]) 60 | r_q99 = float(r[-len(r)/100]) 61 | r_q999 = float(r[-len(r)/1000]) 62 | r_q9999 = float(r[-len(r)/10000]) 63 | r_max = float(r[-1]) 64 | 65 | print (" Min: %9.1f ms\n" + \ 66 | " SD: %9.1f ms\n" + \ 67 | " Avg: %9.1f ms\n" + \ 68 | " Median: %9.1f ms\n" + \ 69 | " q90 : %9.1f ms\n" + \ 70 | " q95 : %9.1f ms\n" + \ 71 | " q99 : %9.1f ms\n" + \ 72 | " q99.9 : %9.1f ms\n" + \ 73 | " q99.99: %9.1f ms\n" + \ 74 | " Max: %9.1f ms\n") % (r_min / 1000., 75 | r_sd / 1000., 76 | r_avg / 1000., 77 | r_median / 1000., 78 | r_q90 / 1000., 79 | r_q95 / 1000., 80 | r_q99 / 1000., 81 | r_q999 / 1000., 82 | r_q9999 / 1000., 83 | r_max / 1000.) 84 | 85 | 86 | def load(filename): 87 | 88 | res = Result() 89 | parser = ijson.parse(open(filename)) 90 | 91 | for prefix, event, value in parser: 92 | 93 | if prefix == 'total_duration': 94 | res.duration = value 95 | 96 | if prefix == 'started': 97 | res.started = value 98 | 99 | if prefix == 'ended': 100 | res.ended = value 101 | 102 | if prefix == 'connection_stats.item.tcp_pre_init': 103 | if res.preInitMin is None or value < res.preInitMin: 104 | res.preInitMin = value 105 | if res.preInitMax is None or value > res.preInitMax: 106 | res.preInitMax = value 107 | 108 | if prefix == 'connection_stats.item.failed': 109 | if value: 110 | res.fail += 1 111 | else: 112 | res.success += 1 113 | 114 | if prefix == 'connection_stats.item.open': 115 | res.openTimestamps.append(value) 116 | 117 | if prefix == 'connection_stats.item.close': 118 | res.closeTimestamps.append(value) 119 | 120 | res.total = res.success + res.fail 121 | res.durationWallclock = res.ended - res.started 122 | return res 123 | 124 | 125 | def analyze(res): 126 | 127 | print 128 | print "Aggregate results (WebSocket Opening+Closing Handshake)" 129 | print 130 | #print " Duration: %9d ms" % (float(res.duration) / 1000.) 131 | print " Duration: %9.1f s" % round(float(res.durationWallclock) / 1000000., 1) 132 | print " Total: %9d" % res.total 133 | print " Success: %9d" % res.success 134 | print " Fail: %9d" % res.fail 135 | print " Fail %%: %9.2f" % (100. * float(res.fail) / float(res.total)) 136 | print " Handshakes/sec: %9d" % int(round((float(res.success) / (float(res.durationWallclock) / 1000000.)))) 137 | print 138 | printStats(res.openTimestamps) 139 | print 140 | 141 | 142 | def printResults(files): 143 | results = [] 144 | for fn in files: 145 | print "Loading wsperf result file %s .." % fn 146 | res = load(fn) 147 | results.append(res) 148 | 149 | res = joinResults(results) 150 | analyze(res) 151 | print "Analyze done." 152 | 153 | 154 | if __name__ == '__main__': 155 | printResults(sys.argv[1:]) 156 | 157 | 158 | #pstat(res_close_timestamps) 159 | 160 | 161 | # start_array None 162 | # item start_map None 163 | # item map_key tcp_pre_init 164 | # item.tcp_pre_init number 20594 165 | # item map_key tcp_post_init 166 | # item.tcp_post_init number 2 167 | # item map_key open 168 | # item.open number 15831 169 | # item map_key close 170 | # item.close number 38388 171 | # item map_key failed 172 | # item.failed boolean False 173 | # item end_map None 174 | 175 | # prefix, event, value -------------------------------------------------------------------------------- /wsperf.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This example is presently used as a scratch space. It may or may not be broken 3 | * at any given time. 4 | */ 5 | 6 | #include "wspp-config.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct open_handshake_stats { 14 | std::chrono::high_resolution_clock::time_point s_start; 15 | std::chrono::high_resolution_clock::time_point s_tcp_pre_init; 16 | std::chrono::high_resolution_clock::time_point s_tcp_post_init; 17 | std::chrono::high_resolution_clock::time_point s_open; 18 | std::chrono::high_resolution_clock::time_point s_close; 19 | 20 | // some sort of status 21 | bool s_fail; 22 | }; 23 | 24 | void on_socket_init(websocketpp::connection_hdl hdl, boost::asio::ip::tcp::socket & s) { 25 | boost::asio::ip::tcp::no_delay option(true); 26 | s.set_option(option); 27 | } 28 | 29 | template 30 | class handshake_test { 31 | public: 32 | typedef handshake_test type; 33 | typedef std::chrono::duration dur_type; 34 | 35 | typedef typename client_type::connection_ptr connection_ptr; 36 | 37 | handshake_test () { 38 | // silence access/error messages 39 | m_endpoint.set_access_channels(websocketpp::log::alevel::none); 40 | m_endpoint.set_error_channels(websocketpp::log::elevel::none); 41 | 42 | // Initialize ASIO 43 | m_endpoint.init_asio(); 44 | 45 | // Register our handlers 46 | if (m_endpoint.is_secure()) { 47 | //m_endpoint.set_tls_init_handler(bind(&type::on_tls_init,this,::_1)); 48 | } 49 | 50 | m_endpoint.set_socket_init_handler(bind(&on_socket_init,::_1,::_2)); 51 | 52 | m_endpoint.set_tcp_pre_init_handler(bind(&type::on_tcp_pre_init,this,::_1)); 53 | m_endpoint.set_tcp_post_init_handler(bind(&type::on_tcp_post_init,this,::_1)); 54 | m_endpoint.set_open_handler(bind(&type::on_open,this,::_1)); 55 | m_endpoint.set_fail_handler(bind(&type::on_fail,this,::_1)); 56 | m_endpoint.set_close_handler(bind(&type::on_close,this,::_1)); 57 | } 58 | 59 | void start(std::string uri, size_t num_threads, size_t num_cons, size_t num_parallel_handshakes_low, size_t num_parallel_handshakes_high, std::string logfile) { 60 | m_stats_list.reserve(num_cons); 61 | m_uri = uri; 62 | m_logfile = logfile; 63 | m_connection_count = num_cons; 64 | m_max_handshakes_low = num_parallel_handshakes_low; 65 | m_max_handshakes_high = num_parallel_handshakes_high; 66 | m_cur_handshakes = 0; 67 | m_total_connections = 0; 68 | m_close_immediately = true; 69 | m_cur_connections = 0; 70 | 71 | m_low_water_mark_count = 0; 72 | m_high_water_mark_count = 0; 73 | 74 | m_test_start = std::chrono::high_resolution_clock::now(); 75 | m_test_start_wallclock = std::chrono::system_clock::now(); 76 | 77 | launch_more_connections(); 78 | 79 | if (num_threads > 0) { 80 | std::vector ts; 81 | for (size_t i = 0; i < num_threads; i++) { 82 | ts.push_back(std::thread(&client_type::run, &m_endpoint)); 83 | } 84 | for (auto & t : ts) { 85 | t.join(); 86 | } 87 | } else { 88 | m_endpoint.run(); 89 | } 90 | } 91 | 92 | void launch_more_connections() { 93 | if (m_total_connections < m_connection_count) { 94 | m_low_water_mark_count++; 95 | } 96 | while (m_total_connections < m_connection_count) { 97 | if (m_cur_handshakes == m_max_handshakes_high) { 98 | m_high_water_mark_count++; 99 | break; 100 | } 101 | launch_connection(m_uri); 102 | m_cur_handshakes++; 103 | m_total_connections++; 104 | } 105 | } 106 | 107 | void launch_connection(std::string uri) { 108 | websocketpp::lib::error_code ec; 109 | connection_ptr con = m_endpoint.get_connection(uri, ec); 110 | 111 | if (ec) { 112 | m_endpoint.get_alog().write(websocketpp::log::alevel::app,ec.message()); 113 | } 114 | 115 | m_endpoint.connect(con); 116 | con->s_start = std::chrono::high_resolution_clock::now(); 117 | } 118 | 119 | void on_tcp_pre_init(websocketpp::connection_hdl hdl) { 120 | connection_ptr con = m_endpoint.get_con_from_hdl(hdl); 121 | con->s_tcp_pre_init = std::chrono::high_resolution_clock::now(); 122 | } 123 | void on_tcp_post_init(websocketpp::connection_hdl hdl) { 124 | connection_ptr con = m_endpoint.get_con_from_hdl(hdl); 125 | con->s_tcp_post_init = std::chrono::high_resolution_clock::now(); 126 | } 127 | 128 | context_ptr on_tls_init(websocketpp::connection_hdl hdl) { 129 | context_ptr ctx(new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1)); 130 | 131 | try { 132 | ctx->set_options(boost::asio::ssl::context::default_workarounds | 133 | boost::asio::ssl::context::no_sslv2 | 134 | boost::asio::ssl::context::single_dh_use); 135 | } catch (std::exception& e) { 136 | std::cout << e.what() << std::endl; 137 | } 138 | return ctx; 139 | } 140 | 141 | void on_open(websocketpp::connection_hdl hdl) { 142 | connection_ptr con = m_endpoint.get_con_from_hdl(hdl); 143 | con->s_open = std::chrono::high_resolution_clock::now(); 144 | con->s_fail = false; 145 | 146 | if (m_close_immediately) { 147 | con->close(websocketpp::close::status::going_away,""); 148 | } 149 | 150 | std::lock_guard guard(m_stats_lock); 151 | m_cur_handshakes--; 152 | m_cur_connections++; 153 | 154 | // check if we need to launch more connections 155 | if (m_cur_handshakes < m_max_handshakes_low) { 156 | launch_more_connections(); 157 | } 158 | 159 | // check if we need to close any connections 160 | if (m_total_connections == m_connection_count) { 161 | // close connections 162 | } 163 | } 164 | 165 | void on_fail(websocketpp::connection_hdl hdl) { 166 | connection_ptr con = m_endpoint.get_con_from_hdl(hdl); 167 | con->s_open = std::chrono::high_resolution_clock::now(); 168 | con->s_close = con->s_open; 169 | con->s_fail = true; 170 | 171 | std::lock_guard guard(m_stats_lock); 172 | m_cur_handshakes--; 173 | 174 | // Add stats to the list 175 | m_stats_list.push_back(*websocketpp::lib::static_pointer_cast(con)); 176 | 177 | // check if we need to launch more connections 178 | if (m_cur_handshakes < m_max_handshakes_low) { 179 | launch_more_connections(); 180 | } 181 | 182 | // check if we are done 183 | if (m_stats_list.size() == m_connection_count) { 184 | test_complete(); 185 | } 186 | 187 | 188 | } 189 | 190 | void on_close(websocketpp::connection_hdl hdl) { 191 | connection_ptr con = m_endpoint.get_con_from_hdl(hdl); 192 | con->s_close = std::chrono::high_resolution_clock::now(); 193 | 194 | std::lock_guard guard(m_stats_lock); 195 | m_cur_connections--; 196 | 197 | // Add stats to the list 198 | m_stats_list.push_back(*websocketpp::lib::static_pointer_cast(con)); 199 | 200 | // Check it we are done 201 | if (m_stats_list.size() == m_connection_count) { 202 | test_complete(); 203 | } 204 | } 205 | 206 | void test_complete() { 207 | m_test_end = std::chrono::high_resolution_clock::now(); 208 | m_test_end_wallclock = std::chrono::system_clock::now(); 209 | 210 | std::ofstream logfile; 211 | logfile.open(m_logfile); 212 | 213 | logfile << "{\"total_duration\":" 214 | << std::chrono::duration_cast(m_test_end-m_test_start).count() 215 | << ",\"started\":" << m_test_start_wallclock.time_since_epoch().count() 216 | << ",\"ended\":" << m_test_end_wallclock.time_since_epoch().count() 217 | << ",\"handshake_throttle_count\":" << m_high_water_mark_count 218 | << ",\"handshake_resume_count\":" << m_low_water_mark_count 219 | << ",\"connection_stats\":["; 220 | bool first = true; 221 | for (auto i : m_stats_list) { 222 | logfile << (!first ? "," : "") << "{\"tcp_pre_init\":" 223 | << std::chrono::duration_cast(i.s_tcp_pre_init-i.s_start).count() 224 | << ",\"tcp_post_init\":" 225 | << std::chrono::duration_cast(i.s_tcp_post_init-i.s_tcp_pre_init).count() 226 | << ",\"open\":" 227 | << std::chrono::duration_cast(i.s_open-i.s_tcp_post_init).count() 228 | << ",\"close\":" 229 | << std::chrono::duration_cast(i.s_close-i.s_open).count() 230 | << ",\"failed\":" << (i.s_fail ? "true" : "false") 231 | << "}"; 232 | first = false; 233 | } 234 | logfile << "]}" << std::endl; 235 | 236 | logfile.close(); 237 | } 238 | private: 239 | client_type m_endpoint; 240 | 241 | std::string m_uri; 242 | std::string m_logfile; 243 | size_t m_connection_count; 244 | size_t m_max_handshakes_high; 245 | size_t m_max_handshakes_low; 246 | size_t m_cur_handshakes; 247 | size_t m_cur_connections; 248 | size_t m_total_connections; 249 | 250 | size_t m_high_water_mark_count; 251 | size_t m_low_water_mark_count; 252 | 253 | bool m_close_immediately; 254 | 255 | std::chrono::high_resolution_clock::time_point m_test_start; 256 | std::chrono::high_resolution_clock::time_point m_test_end; 257 | 258 | std::chrono::system_clock::time_point m_test_start_wallclock; 259 | std::chrono::system_clock::time_point m_test_end_wallclock; 260 | 261 | std::mutex m_stats_lock; 262 | std::vector m_stats_list; 263 | }; 264 | 265 | typedef websocketpp::client> client_tls; 266 | 267 | int main(int argc, char* argv[]) { 268 | std::string logfile; 269 | std::string uri; 270 | size_t num_threads; 271 | size_t num_cons; 272 | size_t max_parallel_handshakes_low; 273 | size_t max_parallel_handshakes_high; 274 | 275 | if (argc == 7) { 276 | uri = argv[1]; 277 | num_threads = atoi(argv[2]); 278 | num_cons = atoi(argv[3]); 279 | max_parallel_handshakes_low = atoi(argv[4]); 280 | max_parallel_handshakes_high = atoi(argv[5]); 281 | logfile = argv[6]; 282 | } else { 283 | std::cout << "Usage: wsperf serverurl num_threads num_connections max_parallel_handshakes_low max_parallel_handshakes_high" << std::endl; 284 | std::cout << "Example: wsperf ws://localhost:9002 4 50 25 50 result.json" << std::endl; 285 | return 1; 286 | } 287 | 288 | // some input sanity checking 289 | if (max_parallel_handshakes_low == 0 || max_parallel_handshakes_low > max_parallel_handshakes_high) { 290 | std::cout << "max_parallel_handshakes_low must be positive and less than max_parallel_handshakes_high" << std::endl; 291 | return 1; 292 | } 293 | 294 | try { 295 | if (uri.substr(0,3) == "wss") { 296 | //handshake_test> endpoint; 297 | //endpoint.start(uri); 298 | std::cout << "wss not supported at the moment" << std::endl; 299 | } else { 300 | handshake_test endpoint; 301 | endpoint.start(uri,num_threads,num_cons,max_parallel_handshakes_low,max_parallel_handshakes_high,logfile); 302 | } 303 | } catch (const std::exception & e) { 304 | std::cout << e.what() << std::endl; 305 | } catch (websocketpp::lib::error_code e) { 306 | std::cout << e.message() << std::endl; 307 | } catch (...) { 308 | std::cout << "other exception" << std::endl; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /wsperf.py: -------------------------------------------------------------------------------- 1 | import os, sys, argparse 2 | from twisted.internet import reactor 3 | from twisted.internet.utils import getProcessOutput, getProcessValue 4 | from twisted.internet.defer import DeferredList 5 | 6 | import analyze 7 | 8 | 9 | 10 | if __name__ == '__main__': 11 | 12 | default_wsperf = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wsperf') 13 | 14 | parser = argparse.ArgumentParser(description = 'wsperf test driver') 15 | 16 | parser.add_argument('--wsuri', dest = 'wsuri', type = str, default = 'ws://127.0.0.1:9000', help = 'The WebSocket URI the testee is listening on, e.g. ws://127.0.0.1:9000.') 17 | parser.add_argument('--workers', dest = 'workers', type = int, default = 4, help = 'Number of wsperf worker processes to spawn.') 18 | parser.add_argument('--threads', dest = 'threads', type = int, default = 0, help = 'Number of wsperf worker threads to spawn at each worker [0: run on main thread, >0: spawn that many background worker threads].') 19 | 20 | parser.add_argument('--conns', dest = 'conns', type = int, default = 50000, help = 'Number of WebSocket connections to open from each worker.') 21 | parser.add_argument('--lowmark', dest = 'lowmark', type = int, default = 250, help = 'Low watermark for each worker.') 22 | parser.add_argument('--highmark', dest = 'highmark', type = int, default = 500, help = 'High watermark for each worker.') 23 | 24 | parser.add_argument('--resultfile', dest = 'resultfile', type = str, default = r'result_%d.json', help = 'Result file pattern.') 25 | 26 | parser.add_argument('--wsperf', dest = 'wsperf', type = str, default = default_wsperf, help = 'Full path to wsperf executable.') 27 | 28 | parser.add_argument('--skiprun', dest = 'skiprun', action = "store_true", default = False, help = 'Skip test run.') 29 | parser.add_argument('--skipanalyze', dest = 'skipanalyze', action = "store_true", default = False, help = 'Skip analyze results.') 30 | 31 | options = parser.parse_args() 32 | 33 | resultfiles = [(options.resultfile % i) for i in xrange(options.workers)] 34 | 35 | if options.skiprun: 36 | ## here we don't start a reactor. 37 | 38 | if not options.skipanalyze: 39 | analyze.printResults(resultfiles) 40 | 41 | else: 42 | df = [] 43 | for i in range(options.workers): 44 | 45 | args = [options.wsuri, 46 | str(options.threads), 47 | str(options.conns), 48 | str(options.lowmark), 49 | str(options.highmark), 50 | options.resultfile % i] 51 | 52 | ## run wsperf executable 53 | d = getProcessOutput(options.wsperf, args, os.environ) 54 | 55 | ## accumulate any output 56 | df.append(d) 57 | 58 | d = DeferredList(df, consumeErrors = True) 59 | 60 | def onok(res): 61 | if not options.skipanalyze: 62 | analyze.printResults(resultfiles) 63 | reactor.stop() 64 | 65 | def onerr(err): 66 | print err 67 | reactor.stop() 68 | 69 | d.addCallbacks(onok, onerr) 70 | 71 | reactor.run() 72 | -------------------------------------------------------------------------------- /wspp-config.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This example is presently used as a scratch space. It may or may not be broken 3 | * at any given time. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | //#include 10 | 11 | template 12 | struct wsperf_config : public config_base { 13 | // pull default settings from our core config 14 | typedef config_base core; 15 | 16 | typedef typename core::concurrency_type concurrency_type; 17 | typedef typename core::request_type request_type; 18 | typedef typename core::response_type response_type; 19 | 20 | typedef typename core::message_type message_type; 21 | typedef typename core::con_msg_manager_type con_msg_manager_type; 22 | typedef typename core::endpoint_msg_manager_type endpoint_msg_manager_type; 23 | //typedef websocketpp::message_buffer::fixed::policy::message message_type; 24 | //typedef websocketpp::message_buffer::fixed::policy::con_msg_manager con_msg_manager_type; 25 | //typedef websocketpp::message_buffer::fixed::policy::con_msg_manager endpoint_msg_manager_type; 26 | //typedef websocketpp::message_buffer::fixed::policy::endpoint_msg_manager endpoint_msg_manager_type; 27 | 28 | typedef typename core::alog_type alog_type; 29 | typedef typename core::elog_type elog_type; 30 | typedef typename core::rng_type rng_type; 31 | typedef typename core::endpoint_base endpoint_base; 32 | 33 | static bool const enable_multithreading = true; 34 | 35 | struct transport_config : public core::transport_config { 36 | typedef typename core::concurrency_type concurrency_type; 37 | typedef typename core::elog_type elog_type; 38 | typedef typename core::alog_type alog_type; 39 | typedef typename core::request_type request_type; 40 | typedef typename core::response_type response_type; 41 | 42 | static bool const enable_multithreading = true; 43 | }; 44 | 45 | typedef websocketpp::transport::asio::endpoint 46 | transport_type; 47 | 48 | typedef con_base connection_base; 49 | 50 | static const websocketpp::log::level elog_level = 51 | websocketpp::log::elevel::none; 52 | static const websocketpp::log::level alog_level = 53 | websocketpp::log::alevel::none; 54 | }; 55 | 56 | /*template 57 | using client = websocketpp::client 58 | >; 59 | 60 | template 61 | using client_tls = websocketpp::client 62 | >;*/ 63 | 64 | // convenience typedefs 65 | using websocketpp::lib::placeholders::_1; 66 | using websocketpp::lib::placeholders::_2; 67 | using websocketpp::lib::bind; 68 | 69 | typedef websocketpp::lib::shared_ptr context_ptr; 70 | --------------------------------------------------------------------------------