Merge commit '09987d0f9d32e860f1391bb9c75b799501e2d141' as 'Subtrees/websocket'

This commit is contained in:
Vinnie Falco
2013-06-20 17:15:11 -07:00
255 changed files with 50217 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
BOOST_LIBS=boost_system boost_date_time boost_regex boost_thread boost_random boost_chrono boost_program_options
include ../common.mk
LDFLAGS := $(LDFLAGS) -lcrypto -lssl -lpthread
CFLAGS := -Wall -O3 $(CFLAGS)
OS=$(shell uname)
ifneq ($(OS), Darwin)
LDFLAGS := $(LDFLAGS) -lrt -lpthread
endif
wsperf: wsperf.o request.o case.o generic.o wscmd.o stress_aggregate.o stress_handler.o
$(CXX) $(CFLAGS) $^ -o $@ $(LDFLAGS)
%.o: %.cpp
$(CXX) -c $(CFLAGS) -o $@ $^
# cleanup by removing generated files
#
.PHONY: clean
clean:
rm -f *.o wsperf wsperf_client

View File

@@ -0,0 +1,29 @@
## wsperf
##
Import('env')
Import('boostlibs')
Import('wslib')
Import('platform_libs')
localenv = env.Clone ()
sources = ["wsperf.cpp",
"request.cpp",
"case.cpp",
"generic.cpp",
"stress_handler.cpp",
"stress_aggregate.cpp",
"wscmd.cpp"]
LIBS = [wslib] + boostlibs(['system',
'date_time',
'regex',
'thread',
'random',
'chrono',
'program_options']) + [platform_libs]
prg = localenv.Program('wsperf', sources, LIBS = LIBS)
Return('prg')

View File

@@ -0,0 +1,271 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "case.hpp"
using wsperf::case_handler;
// Construct a message_test from a wscmd command
/* Reads values from the wscmd object into member variables. The cmd object is
* passed to the parent constructor for extracting values common to all test
* cases.
*
* Any of the constructors may throw a `case_exception` if required parameters
* are not found or default values don't make sense.
*
* Values that message_test checks for:
*
* uri=[string];
* Example: uri=ws://localhost:9000;
* URI of the server to connect to
*
* token=[string];
* Example: token=foo;
* String value that will be returned in the `token` field of all test related
* messages. A separate token should be sent for each unique test.
*
* quantile_count=[integer];
* Example: quantile_count=10;
* How many histogram quantiles to return in the test results
*
* rtts=[bool];
* Example: rtts:true;
* Whether or not to return the full list of round trip times for each message
* primarily useful for debugging.
*/
case_handler::case_handler(wscmd::cmd& cmd)
: m_uri(extract_string(cmd,"uri")),
m_token(extract_string(cmd,"token")),
m_quantile_count(extract_number<size_t>(cmd,"quantile_count")),
m_rtts(extract_bool(cmd,"rtts")),
m_pass(RUNNING),
m_timeout(0),
m_bytes(0) {}
/// Starts a test by starting the timeout timer and marking the start time
void case_handler::start(connection_ptr con, uint64_t timeout) {
if (timeout > 0) {
m_timer.reset(new boost::asio::deadline_timer(
con->get_io_service(),
boost::posix_time::seconds(0))
);
m_timeout = timeout;
m_timer->expires_from_now(boost::posix_time::milliseconds(m_timeout));
m_timer->async_wait(
boost::bind(
&type::on_timer,
this,
con,
boost::asio::placeholders::error
)
);
}
m_start = boost::chrono::steady_clock::now();
}
/// Marks an incremental time point
void case_handler::mark() {
m_end.push_back(boost::chrono::steady_clock::now());
}
/// Ends a test
/* Ends a test by canceling the timeout timer, marking the end time, generating
* statistics and closing the websocket connection.
*/
void case_handler::end(connection_ptr con) {
std::vector<double> avgs;
avgs.resize(m_quantile_count, 0);
std::vector<double> quantiles;
quantiles.resize(m_quantile_count, 0);
double avg = 0;
double stddev = 0;
double total = 0;
double seconds = 0;
if (m_timeout > 0) {
m_timer->cancel();
}
// TODO: handle weird sizes and error conditions better
if (m_end.size() > m_quantile_count) {
boost::chrono::steady_clock::time_point last = m_start;
// convert RTTs to microsecs
//
std::vector<boost::chrono::steady_clock::time_point>::iterator it;
for (it = m_end.begin(); it != m_end.end(); ++it) {
boost::chrono::nanoseconds dur = *it - last;
m_times.push_back(static_cast<double> (dur.count()) / 1000.);
last = *it;
}
std::sort(m_times.begin(), m_times.end());
size_t samples_per_quantile = m_times.size() / m_quantile_count;
// quantiles
for (size_t i = 0; i < m_quantile_count; ++i) {
quantiles[i] = m_times[((i + 1) * samples_per_quantile) - 1];
}
// total average and quantile averages
for (size_t i = 0; i < m_times.size(); ++i) {
avg += m_times[i];
avgs[i / samples_per_quantile]
+= m_times[i] / static_cast<double>(samples_per_quantile);
}
avg /= static_cast<double> (m_times.size());
// standard dev corrected for estimation from sample
for (size_t i = 0; i < m_times.size(); ++i) {
stddev += (m_times[i] - avg) * (m_times[i] - avg);
}
// Bessel's correction
stddev /= static_cast<double> (m_times.size() - 1);
stddev = std::sqrt(stddev);
} else {
m_times.push_back(0);
}
boost::chrono::nanoseconds total_dur = m_end[m_end.size()-1] - m_start;
total = static_cast<double> (total_dur.count()) / 1000.; // microsec
seconds = total / 10000000.;
std::stringstream s;
std::string outcome;
switch (m_pass) {
case FAIL:
outcome = "fail";
break;
case PASS:
outcome = "pass";
break;
case TIME_OUT:
outcome = "time_out";
break;
case RUNNING:
throw case_exception("end() called from RUNNING state");
break;
}
s << "{\"result\":\"" << outcome
<< "\",\"min\":" << m_times[0]
<< ",\"max\":" << m_times[m_times.size()-1]
<< ",\"median\":" << m_times[(m_times.size()-1)/2]
<< ",\"avg\":" << avg
<< ",\"stddev\":" << stddev
<< ",\"total\":" << total
<< ",\"bytes\":" << m_bytes
<< ",\"quantiles\":[";
for (size_t i = 0; i < m_quantile_count; i++) {
s << (i > 0 ? "," : "");
s << "[";
s << avgs[i] << "," << quantiles[i];
s << "]";
}
s << "]";
if (m_rtts) {
s << ",\"rtts\":[";
for (size_t i = 0; i < m_times.size(); i++) {
s << (i > 0 ? "," : "") << m_times[i];
}
s << "]";
};
s << "}";
m_data = s.str();
con->close(websocketpp::close::status::NORMAL,"");
}
/// Fills a buffer with utf8 bytes
void case_handler::fill_utf8(std::string& data,size_t size,bool random) {
if (random) {
uint32_t val;
for (size_t i = 0; i < size; i++) {
if (i%4 == 0) {
val = uint32_t(rand());
}
data.push_back(char(((reinterpret_cast<uint8_t*>(&val)[i%4])%95)+32));
}
} else {
data.assign(size,'*');
}
}
/// Fills a buffer with bytes
void case_handler::fill_binary(std::string& data,size_t size,bool random) {
if (random) {
int32_t val;
for (size_t i = 0; i < size; i++) {
if (i%4 == 0) {
val = rand();
}
data.push_back((reinterpret_cast<char*>(&val))[i%4]);
}
} else {
data.assign(size,'*');
}
}
void case_handler::on_timer(connection_ptr con,const boost::system::error_code& error) {
if (error == boost::system::errc::operation_canceled) {
return; // timer was canceled because test finished
}
// time out
mark();
m_pass = TIME_OUT;
this->end(con);
}
void case_handler::on_fail(connection_ptr con) {
m_data = "{\"result\":\"connection_failed\"}";
}
const std::string& case_handler::get_data() const {
return m_data;
}
const std::string& case_handler::get_token() const {
return m_token;
}
const std::string& case_handler::get_uri() const {
return m_uri;
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WSPERF_CASE_HPP
#define WSPERF_CASE_HPP
#include "wscmd.hpp"
#include "../../src/roles/client.hpp"
#include "../../src/websocketpp.hpp"
#include <boost/chrono.hpp>
#include <exception>
using websocketpp::client;
namespace wsperf {
class case_exception : public std::exception {
public:
case_exception(const std::string& msg) : m_msg(msg) {}
~case_exception() throw() {}
virtual const char* what() const throw() {
return m_msg.c_str();
}
std::string m_msg;
};
class case_handler : public client::handler {
public:
typedef case_handler type;
/// Construct a message test from a wscmd command
explicit case_handler(wscmd::cmd& cmd);
void start(connection_ptr con, uint64_t timeout);
void end(connection_ptr con);
void mark();
// Just does random ascii right now. True random UTF8 with multi-byte stuff
// would probably be better
void fill_utf8(std::string& data,size_t size,bool random = true);
void fill_binary(std::string& data,size_t size,bool random = true);
void on_timer(connection_ptr con,const boost::system::error_code& error);
void on_close(connection_ptr con) {
con->alog()->at(websocketpp::log::alevel::DEVEL)
<< "case_handler::on_close"
<< websocketpp::log::endl;
}
void on_fail(connection_ptr con);
const std::string& get_data() const;
const std::string& get_token() const;
const std::string& get_uri() const;
// TODO: refactor these three extract methods into wscmd
std::string extract_string(wscmd::cmd command,std::string key) {
if (command.args[key] != "") {
return command.args[key];
} else {
throw case_exception("Invalid " + key + " parameter.");
}
}
template <typename T>
T extract_number(wscmd::cmd command,std::string key) {
if (command.args[key] != "") {
std::istringstream buf(command.args[key]);
T val;
buf >> val;
if (buf) {return val;}
}
throw case_exception("Invalid " + key + " parameter.");
}
bool extract_bool(wscmd::cmd command,std::string key) {
if (command.args[key] != "") {
if (command.args[key] == "true") {
return true;
} else if (command.args[key] == "false") {
return false;
}
}
throw case_exception("Invalid " + key + " parameter.");
}
protected:
enum status {
FAIL = 0,
PASS = 1,
TIME_OUT = 2,
RUNNING = 3
};
std::string m_uri;
std::string m_token;
size_t m_quantile_count;
bool m_rtts;
std::string m_data;
status m_pass;
uint64_t m_timeout;
boost::shared_ptr<boost::asio::deadline_timer> m_timer;
boost::chrono::steady_clock::time_point m_start;
std::vector<boost::chrono::steady_clock::time_point> m_end;
std::vector<double> m_times;
uint64_t m_bytes;
};
typedef boost::shared_ptr<case_handler> case_handler_ptr;
} // namespace wsperf
#endif // WSPERF_CASE_HPP

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "generic.hpp"
using wsperf::message_test;
// Construct a message_test from a wscmd command
/* Reads values from the wscmd object into member variables. The cmd object is
* passed to the parent constructor for extracting values common to all test
* cases.
*
* Any of the constructors may throw a `case_exception` if required parameters
* are not found or default values don't make sense.
*
* Values that message_test checks for:
*
* size=[interger];
* Example: size=4096;
* Size of messages to send in bytes. Valid values 0 - max size_t
*
* count=[integer];
* Example: count=1000;
* Number of test messages to send. Valid values 0-2^64
*
* timeout=[integer];
* Example: timeout=10000;
* How long to wait (in ms) for a response before failing the test.
*
* binary=[bool];
* Example: binary=true;
* Whether or not to use binary websocket frames. (true=binary, false=utf8)
*
* sync=[bool];
* Example: sync=true;
* Syncronize messages. When sync is on wsperf will wait for a response before
* sending the next message. When sync is off, messages will be sent as quickly
* as possible.
*
* correctness=[string];
* Example: correctness=exact;
* Example: correctness=length;
* How to evaluate the correctness of responses. Exact checks each response for
* exact correctness. Length checks only that the response has the correct
* length. Length mode is faster but won't catch invalid implimentations. Length
* mode can be used to test situations where you deliberately return incorrect
* bytes in order to compare performance (ex: performance with/without masking)
*/
message_test::message_test(wscmd::cmd& cmd)
: case_handler(cmd),
m_message_size(extract_number<uint64_t>(cmd,"size")),
m_message_count(extract_number<uint64_t>(cmd,"count")),
m_timeout(extract_number<uint64_t>(cmd,"timeout")),
m_binary(extract_bool(cmd,"binary")),
m_sync(extract_bool(cmd,"sync")),
m_acks(0)
{
if (cmd.args["correctness"] == "exact") {
m_mode = EXACT;
} else if (cmd.args["correctness"] == "length") {
m_mode = LENGTH;
} else {
throw case_exception("Invalid correctness parameter.");
}
}
void message_test::on_open(connection_ptr con) {
con->alog()->at(websocketpp::log::alevel::DEVEL)
<< "message_test::on_open" << websocketpp::log::endl;
m_msg = con->get_data_message();
m_data.reserve(static_cast<size_t>(m_message_size));
if (!m_binary) {
fill_utf8(m_data,static_cast<size_t>(m_message_size),true);
m_msg->reset(websocketpp::frame::opcode::TEXT);
} else {
fill_binary(m_data,static_cast<size_t>(m_message_size),true);
m_msg->reset(websocketpp::frame::opcode::BINARY);
}
m_msg->set_payload(m_data);
start(con,m_timeout);
if (m_sync) {
con->send(m_msg);
} else {
for (uint64_t i = 0; i < m_message_count; i++) {
con->send(m_msg);
}
}
}
void message_test::on_message(connection_ptr con,websocketpp::message::data_ptr msg) {
if ((m_mode == LENGTH && msg->get_payload().size() == m_data.size()) ||
(m_mode == EXACT && msg->get_payload() == m_data))
{
m_acks++;
m_bytes += m_message_size;
mark();
} else {
mark();
m_msg.reset();
m_pass = FAIL;
this->end(con);
}
if (m_acks == m_message_count) {
m_pass = PASS;
m_msg.reset();
this->end(con);
} else if (m_sync && m_pass == RUNNING) {
con->send(m_msg);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WSPERF_CASE_GENERIC_HPP
#define WSPERF_CASE_GENERIC_HPP
#include "case.hpp"
#include "wscmd.hpp"
namespace wsperf {
enum correctness_mode {
EXACT = 0,
LENGTH = 1
};
class message_test : public case_handler {
public:
/// Construct a message test from a wscmd command
explicit message_test(wscmd::cmd& cmd);
void on_open(connection_ptr con);
void on_message(connection_ptr con,websocketpp::message::data_ptr msg);
private:
// Simulation Parameters
uint64_t m_message_size;
uint64_t m_message_count;
uint64_t m_timeout;
bool m_binary;
bool m_sync;
correctness_mode m_mode;
// Simulation temporaries
std::string m_data;
message_ptr m_msg;
uint64_t m_acks;
};
} // namespace wsperf
#endif // WSPERF_CASE_GENERIC_HPP

View File

@@ -0,0 +1,277 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var ws;
var url;
function connect() {
url = document.getElementById("server_url").value;
console.log(url);
if ("WebSocket" in window) {
ws = new WebSocket(url);
} else if ("MozWebSocket" in window) {
ws = new MozWebSocket(url);
} else {
write_error("This Browser does not support WebSockets.");
return;
}
ws.onopen = function(e) {
document.getElementById("server_url").disabled = true;
document.getElementById("toggle_connect").innerHTML = "Disconnect";
};
ws.onerror = function(e) {
write_error("Client: An error occured, see console log for more details.");
console.log(e);
};
ws.onclose = function(e) {
document.getElementById("server_url").disabled = false;
document.getElementById("toggle_connect").innerHTML = "Connect";
document.getElementById("server_details").innerHTML = "";
};
ws.onmessage = function(e) {
var data = JSON.parse(e.data);
if (data.type == "test_welcome") {
var o = "";
o += "<strong>Version:</strong> "+data.version+" ";
o += "<strong>Ident:</strong> "+data.ident+" ";
o += "<strong>num_workers:</strong> "+data.num_workers+" ";
document.getElementById("server_details").innerHTML = o;
} else if (data.type == "test_start") {
$("#result-"+data.token).removeClass("pass fail error");
if ($("#result-"+data.token).length == 0) {
var o = "";
o += "<tr id='result-"+data.token+"'>";
o += "<td class='token'>"+data.token+"</td>";
o += "<td class='status'>Running</td>";
o += "<td class='result'></td>";
o += "<td class='total'></td>";
o += "<td class='bytes'></td>";
o += "<td class='min'></td>";
o += "<td class='median'></td>";
o += "<td class='mean'></td>";
o += "<td class='max'></td>";
o += "<td class='stddev'></td>";
o += "<td class='MBps'></td>";
o += "</tr>";
o += document.getElementById("results-body").innerHTML;
document.getElementById("results-body").innerHTML = o;
//$("#results-body").html(o);
} else {
var o = "<td>"+data.token+"</td><td>Running</td>";
o += "<td class='result'></td>";
o += "<td class='total'></td>";
o += "<td class='bytes'></td>";
o += "<td class='min'></td>";
o += "<td class='median'></td>";
o += "<td class='mean'></td>";
o += "<td class='max'></td>";
o += "<td class='stddev'></td>";
o += "<td class='MBps'></td>";
$("#result-"+data.token).html(o);
}
} else if (data.type == "test_complete") {
$("#result-"+data.token+" .status").html("Complete");
} else if (data.type == "test_data") {
if (data.data.current_connections != null) {
var foo = "";
foo += "Current Connections: "+data.data.current_connections+"<br />";
foo += "Max Connections: "+data.data.max_connections+"<br />";
foo += "Total Connections: "+data.data.total_connections+"<br />";
foo += "Failed Connections: "+data.data.failed_connections+"<br />";
$("#result-"+data.token+" .result").html(foo);
}
$("#result-"+data.token+" .result").html(data.data.result);
$("#result-"+data.token+" .total").html(data.data.total);
$("#result-"+data.token+" .bytes").html(data.data.bytes);
$("#result-"+data.token+" .min").html(data.data.min);
$("#result-"+data.token+" .median").html(data.data.median);
$("#result-"+data.token+" .mean").html(data.data.avg);
$("#result-"+data.token+" .max").html(data.data.max);
$("#result-"+data.token+" .stddev").html(data.data.stddev);
$("#result-"+data.token+" .MBps").html((data.data.bytes/data.data.total).toFixed(4));
$("#result-"+data.token).addClass(data.data.result);
} else if (data.type == "error") {
$("#result-"+data.token+" .status").html("Error");
$("#messages").html(data.data+"<br />");
} else {
console.log(data);
}
};
}
function write_error(msg) {
$("#messages").css("display","block").html(msg);
}
function disconnect() {
ws.close();
$("#server_url").attr("disabled","disabled");
$("#toggle_connect").html("Connect");
}
function toggle_connect() {
if ($("#server_url").attr("disabled") == "disabled") {
disconnect();
} else {
connect();
}
}
function send() {
$("#messages").css("display","none");
if (ws === undefined || ws.readyState != 1) {
write_error("Websocket is not avaliable for writing");
return;
}
ws.send($("#msg").val());
}
</script>
<style>
body,html {
margin: 0px;
padding: 0px;
font-family: Tahoma,Arial,Verdana,sans-serif;
background-color: #F4F4F4;
}
#server_url {
width: 200px;
}
h2 {
font-size: 18px;
margin: 20px 20px;
}
#controls {
/*float:right;*/
background-color: #333;
color: white;
font-size: 14px;
padding: 4px;
}
#msg {
width: 100%;
box-sizing: border-box;
-webkit-box-sizing:border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
display: block;
height: 5em;
}
#message_input button {
display: block;
}
#message_input {
margin: 0px 20px;
}
#results {
margin: 0px 20px;
}
#results table {
width: 100%;
border: 1px solid white;
color: white;
border-collapse: collapse;
border-spacing: 0px;
}
#messages {
display: none;
margin: 20px;
padding: 4px;
color: white;
}
th,td {
font-weight: normal;
padding: 6px;
font-size: 0.8em;
border: 1px solid white;
}
thead tr {
background-color: #048;
}
tbody tr {
background-color: #666;
}
.pass {
background-color: #0A0;
}
.fail {
background-color: #900;
}
.error {
background-color: #9A0;
}
</style>
<div id="controls">
<div id="server">
<input type="text" name="server_url" id="server_url" value="ws://localhost:9050" />
<button id="toggle_connect" onclick="toggle_connect();">Connect</button>
<span id="server_details"></span>
</div>
</div>
<h2>Run Test</h2>
<div id="message_input">
<textarea type="text" id="msg">message_test:uri=ws://localhost:9002;size=10;count=1;timeout=10000;binary=true;sync=true;correctness=exact;token=foo;quantile_count=10;rtts=false;</textarea><br />
<button onclick="send();">Run Test</button>
</div>
<h2>Test Results</h2>
<div id="messages" class='fail'></div>
<div id="results">
<table>
<thead>
<tr>
<th>Token</th>
<th>Status</th>
<th>Result</th>
<th>Time (µs)</th>
<th>Size (bytes)</th>
<th>Min</th>
<th>Median</th>
<th>Mean</th>
<th>Max</th>
<th>Stddev</th>
<th>MB/sec</th>
</tr>
</thead>
<tbody id="results-body">
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "request.hpp"
#include "stress_aggregate.hpp"
#include <algorithm>
#include <sstream>
using wsperf::request;
void request::process(unsigned int id) {
case_handler_ptr test;
stress_handler_ptr shandler;
std::string uri;
//size_t connections_opened = 0;
size_t connection_count;
wscmd::cmd command = wscmd::parse(req);
try {
if (command.command == "message_test") {
test = case_handler_ptr(new message_test(command));
token = test->get_token();
uri = test->get_uri();
} else if (command.command == "stress_test") {
shandler = stress_handler_ptr(new stress_aggregate(command));
// todo make sure this isn't 0
if(!wscmd::extract_number<size_t>(command, "connection_count",connection_count)) {
connection_count = 1;
}
if (command.args["token"] != "") {
token = command.args["token"];
} else {
throw case_exception("Invalid token parameter.");
}
if (command.args["uri"] != "") {
uri = command.args["uri"];
} else {
throw case_exception("Invalid uri parameter.");
}
} else {
writer->write(prepare_response("error","Invalid Command"));
return;
}
std::stringstream o;
o << "{\"worker_id\":" << id << "}";
writer->write(prepare_response_object("test_start",o.str()));
if (command.command == "message_test") {
client e(test);
e.alog().set_level(websocketpp::log::alevel::ALL);
e.elog().set_level(websocketpp::log::elevel::ALL);
//e.alog().unset_level(websocketpp::log::alevel::ALL);
//e.elog().unset_level(websocketpp::log::elevel::ALL);
//e.elog().set_level(websocketpp::log::elevel::RERROR);
//e.elog().set_level(websocketpp::log::elevel::FATAL);
e.connect(uri);
e.run();
writer->write(prepare_response_object("test_data",test->get_data()));
} else if (command.command == "stress_test") {
client e(shandler);
e.alog().unset_level(websocketpp::log::alevel::ALL);
e.elog().unset_level(websocketpp::log::elevel::ALL);
/*client::connection_ptr con;
con = e.get_connection(uri);
shandler->on_connect(con);
e.connect(con);*/
boost::thread t(boost::bind(&client::run, &e, true));
size_t handshake_delay;
if(!wscmd::extract_number<size_t>(command, "handshake_delay",handshake_delay)) {
handshake_delay = 10;
}
// open n connections
for (size_t i = 0; i < connection_count; i++) {
client::connection_ptr con;
con = e.get_connection(uri);
shandler->on_connect(con);
e.connect(con);
boost::this_thread::sleep(boost::posix_time::milliseconds(handshake_delay));
}
// start sending messages
shandler->start_message_test();
e.end_perpetual();
t.join();
std::cout << "writing data" << std::endl;
writer->write(prepare_response_object("test_data",shandler->get_data()));
/*for (;;) {
// tell the handler to perform its event loop
bool quit = false;
if (connections_opened == connection_count) {
std::cout << "maintenance loop" << std::endl;
quit = shandler->maintenance();
}
// check for done-ness
if (quit) {
// send update to command
std::cout << "writing data" << std::endl;
writer->write(prepare_response_object("test_data",shandler->get_data()));
break;
}
// unless we know we have something to do, sleep for a bit.
if (connections_opened < connection_count) {
continue;
}
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
}*/
//e.end_perpetual();
}
writer->write(prepare_response("test_complete",""));
} catch (case_exception& e) {
std::cout << "case_exception: " << e.what() << std::endl;
writer->write(prepare_response("error",e.what()));
return;
} catch (websocketpp::exception& e) {
std::cout << "websocketpp::exception: " << e.what() << std::endl;
writer->write(prepare_response("error",e.what()));
return;
} catch (websocketpp::uri_exception& e) {
std::cout << "websocketpp::uri_exception: " << e.what() << std::endl;
writer->write(prepare_response("error",e.what()));
return;
}
}
std::string request::prepare_response(std::string type,std::string data) {
return "{\"type\":\"" + type
+ "\",\"token\":\"" + token + "\",\"data\":\"" + data + "\"}";
}
std::string request::prepare_response_object(std::string type,std::string data){
return "{\"type\":\"" + type
+ "\",\"token\":\"" + token + "\",\"data\":" + data + "}";
}
void wsperf::process_requests(request_coordinator* coordinator,unsigned int id) {
request r;
while (1) {
coordinator->get_request(r);
if (r.type == PERF_TEST) {
r.process(id);
} else {
break;
}
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WSPERF_REQUEST_HPP
#define WSPERF_REQUEST_HPP
#include "case.hpp"
#include "generic.hpp"
#include "wscmd.hpp"
#include "../../src/roles/client.hpp"
#include "../../src/websocketpp.hpp"
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
using websocketpp::client;
using websocketpp::server;
namespace wsperf {
enum request_type {
PERF_TEST = 0,
END_WORKER = 1
};
class writer {
public:
virtual ~writer() {}
virtual void write(std::string msg) = 0;
};
typedef boost::shared_ptr<writer> writer_ptr;
template <typename endpoint_type>
class ws_writer : public writer {
public:
ws_writer(typename endpoint_type::handler::connection_ptr con)
: m_con(con) {}
void write(std::string msg) {
m_con->send(msg);
}
private:
typename endpoint_type::handler::connection_ptr m_con;
};
// A request encapsulates all of the information necesssary to perform a request
// the coordinator will fill in this information from the websocket connection
// and add it to the processing queue. Sleeping in this example is a placeholder
// for any long serial task.
struct request {
writer_ptr writer;
request_type type;
std::string req; // The raw request
std::string token; // Parsed test token. Return in all results
/// Run a test and return JSON result
void process(unsigned int id);
// Simple json generation
std::string prepare_response(std::string type,std::string data);
std::string prepare_response_object(std::string type,std::string data);
};
// The coordinator is a simple wrapper around an STL queue. add_request inserts
// a new request. get_request returns the next available request and blocks
// (using condition variables) in the case that the queue is empty.
class request_coordinator {
public:
void add_request(const request& r) {
{
boost::unique_lock<boost::mutex> lock(m_lock);
m_requests.push(r);
}
m_cond.notify_one();
}
void get_request(request& value) {
boost::unique_lock<boost::mutex> lock(m_lock);
while (m_requests.empty()) {
m_cond.wait(lock);
}
value = m_requests.front();
m_requests.pop();
}
void reset() {
boost::unique_lock<boost::mutex> lock(m_lock);
while (!m_requests.empty()) {
m_requests.pop();
}
}
private:
std::queue<request> m_requests;
boost::mutex m_lock;
boost::condition_variable m_cond;
};
/// Handler that reads requests off the wire and dispatches them to a request queue
template <typename endpoint_type>
class concurrent_handler : public endpoint_type::handler {
public:
typedef typename endpoint_type::handler::connection_ptr connection_ptr;
typedef typename endpoint_type::handler::message_ptr message_ptr;
concurrent_handler(request_coordinator& c,
std::string ident,
std::string ua,
unsigned int num_workers)
: m_coordinator(c),
m_ident(ident),
m_ua(ua),
m_num_workers(num_workers),
m_blocking(num_workers == 0) {}
void on_open(connection_ptr con) {
std::stringstream o;
o << "{"
<< "\"type\":\"test_welcome\","
<< "\"version\":\"" << m_ua << "\","
<< "\"ident\":\"" << m_ident << "\","
<< "\"num_workers\":" << m_num_workers
<< "}";
con->send(o.str());
}
void on_message(connection_ptr con, message_ptr msg) {
request r;
r.type = PERF_TEST;
r.writer = writer_ptr(new ws_writer<endpoint_type>(con));
r.req = msg->get_payload();
if (m_blocking) {
r.process(0);
} else {
m_coordinator.add_request(r);
}
}
void on_fail(connection_ptr con) {
std::cout << "A command connection failed." << std::endl;
}
void on_close(connection_ptr con) {
std::cout << "A command connection closed." << std::endl;
}
private:
request_coordinator& m_coordinator;
std::string m_ident;
std::string m_ua;
unsigned int m_num_workers;
bool m_blocking;
};
// process_requests is the body function for a processing thread. It loops
// forever reading requests, processing them serially, then reading another
// request. A request with type END_WORKER will stop the processing loop.
void process_requests(request_coordinator* coordinator, unsigned int id);
} // namespace wsperf
#endif // WSPERF_REQUEST_HPP

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "stress_aggregate.hpp"
using wsperf::stress_aggregate;
// Construct a message_test from a wscmd command
/* Reads values from the wscmd object into member variables. The cmd object is
* passed to the parent constructor for extracting values common to all test
* cases.
*
* Any of the constructors may throw a `case_exception` if required parameters
* are not found or default values don't make sense.
*
* Values that message_test checks for:
*
* uri=[string];
* Example: uri=ws://localhost:9000;
* URI of the server to connect to
*
* token=[string];
* Example: token=foo;
* String value that will be returned in the `token` field of all test related
* messages. A separate token should be sent for each unique test.
*
* quantile_count=[integer];
* Example: quantile_count=10;
* How many histogram quantiles to return in the test results
*
* rtts=[bool];
* Example: rtts:true;
* Whether or not to return the full list of round trip times for each message
* primarily useful for debugging.
*/
stress_aggregate::stress_aggregate(wscmd::cmd& cmd)
: stress_handler(cmd)
{}
void stress_aggregate::start(connection_ptr con) {
}
/*void stress_aggregate::on_message(connection_ptr con,websocketpp::message::data_ptr msg) {
std::string hash = websocketpp::md5_hash_hex(msg->get_payload());
boost::lock_guard<boost::mutex> lock(m_lock);
m_msg_stats[hash]++;
}*/
/*std::string stress_aggregate::get_data() const {
std::stringstream data;
std::string sep = "";
data << "{";
std::map<std::string,size_t>::iterator it;
{
boost::lock_guard<boost::mutex> lock(m_lock);
for (it = m_msg_stats.begin(); it != m_msg_stats.end(); it++) {
data << sep << "\"" << (*it)->first << "\":" << (*it)->second;
sep = ",";
}
}
data << "}";
return data;
}*/

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WSPERF_STRESS_AGGREGATE_HPP
#define WSPERF_STRESS_AGGREGATE_HPP
#include "stress_handler.hpp"
namespace wsperf {
class stress_aggregate : public stress_handler {
public:
typedef stress_aggregate type;
/// Construct a stress test from a wscmd command
explicit stress_aggregate(wscmd::cmd& cmd);
//void on_message(connection_ptr con,websocketpp::message::data_ptr msg);
void start(connection_ptr con);
void end();
const std::string get_data() const;
protected:
std::map<std::string,size_t> m_msg_stats;
};
typedef boost::shared_ptr<stress_aggregate> stress_aggregate_ptr;
} // namespace wsperf
#endif // WSPERF_STRESS_AGGREGATE_HPP

View File

@@ -0,0 +1,358 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "stress_handler.hpp"
using wsperf::stress_handler;
// Construct a message_test from a wscmd command
/* Reads values from the wscmd object into member variables. The cmd object is
* passed to the parent constructor for extracting values common to all test
* cases.
*
* Any of the constructors may throw a `case_exception` if required parameters
* are not found or default values don't make sense.
*
* Values that message_test checks for:
*
* uri=[string];
* Example: uri=ws://localhost:9000;
* URI of the server to connect to
*
* token=[string];
* Example: token=foo;
* String value that will be returned in the `token` field of all test related
* messages. A separate token should be sent for each unique test.
*
* quantile_count=[integer];
* Example: quantile_count=10;
* How many histogram quantiles to return in the test results
*
* rtts=[bool];
* Example: rtts:true;
* Whether or not to return the full list of round trip times for each message
* primarily useful for debugging.
*/
stress_handler::stress_handler(wscmd::cmd& cmd)
: m_current_connections(0)
, m_max_connections(0)
, m_total_connections(0)
, m_failed_connections(0)
, m_next_con_id(0)
, m_init(boost::chrono::steady_clock::now())
, m_next_msg_id(0)
, m_con_sync(false)
{
if (!wscmd::extract_number<size_t>(cmd,"msg_count",m_msg_count)) {
m_msg_count = 0;
}
if (!wscmd::extract_number<size_t>(cmd,"msg_size",m_msg_size)) {
m_msg_size = 0;
}
std::string mode;
if (wscmd::extract_string(cmd,"msg_mode",mode)) {
if (mode == "fixed") {
m_msg_mode = msg_mode::FIXED;
} else if (mode == "infinite") {
m_msg_mode = msg_mode::UNLIMITED;
} else {
m_msg_mode = msg_mode::NONE;
}
} else {
m_msg_mode = msg_mode::NONE;
}
if (wscmd::extract_string(cmd,"con_lifetime",mode)) {
if (mode == "random") {
m_con_lifetime = con_lifetime::RANDOM;
} else if (mode == "infinite") {
m_con_lifetime = con_lifetime::UNLIMITED;
} else {
m_con_lifetime = con_lifetime::FIXED;
}
} else {
m_con_lifetime = con_lifetime::FIXED;
}
if (m_con_lifetime == con_lifetime::FIXED) {
if (!wscmd::extract_number<size_t>(cmd,"con_duration",m_con_duration)) {
m_con_duration = 5000;
}
} else if (m_con_lifetime == con_lifetime::RANDOM) {
size_t max_dur;
if (!wscmd::extract_number<size_t>(cmd,"con_duration",max_dur)) {
max_dur = 5000;
}
// TODO: choose random number between 0 and max_dur
m_con_duration = max_dur;
}
}
void stress_handler::on_connect(connection_ptr con) {
boost::lock_guard<boost::mutex> lock(m_lock);
m_con_data[con] = con_data(m_next_con_id++, m_init);
m_con_data[con].start = boost::chrono::steady_clock::now();
}
void stress_handler::on_handshake_init(connection_ptr con) {
boost::lock_guard<boost::mutex> lock(m_lock);
m_con_data[con].tcp_established = boost::chrono::steady_clock::now();
// TODO: log close reason?
}
void stress_handler::start_message_test() {
m_msg.reset(new std::string());
m_msg->assign(m_msg_size,'*');
// for each connection send the first message
std::map<connection_ptr,con_data>::iterator it;
for (it = m_con_data.begin(); it != m_con_data.end(); it++) {
connection_ptr con = (*it).first;
con_data& data = (*it).second;
/*data.msg = con->get_data_message();
std::string msg;
msg.assign(m_msg_size,'*');
data.msg->set_payload(msg);
data.msg->reset(websocketpp::frame::opcode::BINARY);*/
boost::lock_guard<boost::mutex> lock(m_lock);
msg_data foo;
foo.msg_id = m_next_msg_id++;
foo.send_time = boost::chrono::steady_clock::now();
data.messages.push_back(foo);
con->send(*m_msg);
}
}
void stress_handler::on_message(connection_ptr con,websocketpp::message::data_ptr msg) {
time_point mark = boost::chrono::steady_clock::now();
boost::lock_guard<boost::mutex> lock(m_lock);
std::map<connection_ptr,con_data>::iterator element = m_con_data.find(con);
if (element == m_con_data.end()) {
std::cout << "Bad Bad Bad" << std::endl;
return;
}
con_data& data = (*element).second;
data.messages.back().recv_time = mark;
if (data.messages.size() < m_msg_count) {
msg_data foo;
foo.msg_id = m_next_msg_id++;
foo.send_time = boost::chrono::steady_clock::now();
data.messages.push_back(foo);
con->send(*m_msg);
} else {
close(con);
}
}
void stress_handler::on_open(connection_ptr con) {
{
boost::lock_guard<boost::mutex> lock(m_lock);
m_current_connections++;
m_total_connections++;
if (m_current_connections > m_max_connections) {
m_max_connections = m_current_connections;
}
m_con_data[con].on_open = boost::chrono::steady_clock::now();
m_con_data[con].status = "Open";
}
start(con);
}
void stress_handler::on_close(connection_ptr con) {
boost::lock_guard<boost::mutex> lock(m_lock);
m_current_connections--;
m_con_data[con].on_close = boost::chrono::steady_clock::now();
m_con_data[con].status = "Closed";
// TODO: log close reason?
}
void stress_handler::on_fail(connection_ptr con) {
boost::lock_guard<boost::mutex> lock(m_lock);
m_failed_connections++;
m_con_data[con].on_fail = boost::chrono::steady_clock::now();
m_con_data[con].status = "Failed";
// TODO: log failure reason
}
void stress_handler::start(connection_ptr con) {
//close(con);
}
void stress_handler::close(connection_ptr con) {
//boost::lock_guard<boost::mutex> lock(m_lock);
m_con_data[con].close_sent = boost::chrono::steady_clock::now();
m_con_data[con].status = "Closing";
con->close(websocketpp::close::status::NORMAL);
// TODO: log close reason?
}
std::string stress_handler::get_data() const {
std::stringstream data;
data << "{";
{
boost::lock_guard<boost::mutex> lock(m_lock);
data << "\"current_connections\":" << m_current_connections;
data << ",\"max_connections\":" << m_max_connections;
data << ",\"total_connections\":" << m_total_connections;
data << ",\"failed_connections\":" << m_failed_connections;
data << ",\"connection_data\":[";
// for each item in m_dirty
std::string sep = "";
std::map<connection_ptr,con_data>::const_iterator it;
for (it = m_con_data.begin(); it != m_con_data.end(); it++) {
data << sep << (*it).second.print();
sep = ",";
}
data << "]";
}
data << "}";
return data.str();
}
bool stress_handler::maintenance() {
std::cout << "locking..." << std::endl;
boost::lock_guard<boost::mutex> lock(m_lock);
bool quit = true;
time_point now = boost::chrono::steady_clock::now();
std::cout << "found " << m_con_data.size() << " connections" << std::endl;
std::map<connection_ptr,con_data>::iterator it;
for (it = m_con_data.begin(); it != m_con_data.end(); it++) {
if ((*it).first->get_state() != websocketpp::session::state::CLOSED) {
quit = false;
}
connection_ptr con = (*it).first;
con_data& data = (*it).second;
std::cout << "processing " << data.id << "...";
// check the connection state
if (con->get_state() != websocketpp::session::state::OPEN) {
std::cout << "ignored" << std::endl;
continue;
}
boost::chrono::nanoseconds dur = now - data.on_open;
size_t milliseconds = static_cast<size_t>(dur.count()) / 1000000;
if (milliseconds > m_con_duration) {
close(con);
}
std::cout << "closed" << std::endl;
}
if (quit) {
return true;
}
/*std::cout << "found " << to_process.size() << " connections" << std::endl;
std::list<connection_ptr>::iterator it2;
for (it2 = to_process.begin(); it2 != to_process.end(); it++) {
connection_ptr con = (*it2);
std::map<connection_ptr,con_data>::iterator element;
element = m_con_data.find(con);
if (element == m_con_data.end()) {
continue;
}
con_data& data = element->second;
// check the connection state
if (con->get_state() != websocketpp::session::state::OPEN) {
std::cout << "ignored" << std::endl;
continue;
}
boost::chrono::nanoseconds dur = now - data.on_open;
size_t milliseconds = static_cast<size_t>(dur.count()) / 1000000;
if (milliseconds > m_con_duration) {
close(con);
}
std::cout << "closed" << std::endl;
}*/
return false;
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WSPERF_STRESS_HANDLER_HPP
#define WSPERF_STRESS_HANDLER_HPP
#include "wscmd.hpp"
#include "../../src/roles/client.hpp"
#include "../../src/websocketpp.hpp"
#include <boost/chrono.hpp>
#include <boost/thread/mutex.hpp>
#include <exception>
using websocketpp::client;
namespace wsperf {
namespace con_lifetime {
enum value {
FIXED = 0,
RANDOM = 1,
UNLIMITED = 2
};
}
namespace msg_mode {
enum value {
NONE = 0,
FIXED = 1,
UNLIMITED = 2
};
}
struct msg_data {
typedef boost::chrono::steady_clock::time_point time_point;
size_t msg_id;
time_point send_time;
time_point recv_time;
//size_t payload_len;
};
struct con_data {
typedef boost::chrono::steady_clock::time_point time_point;
con_data() {}
con_data(size_t id,time_point init)
: id(id)
, init(init)
, start(init)
, tcp_established(init)
, on_open(init)
, on_fail(init)
, close_sent(init)
, on_close(init)
, status("Connecting")
{
}
std::string print() const {
std::stringstream o;
o << "{";
o << "\"id\":" << id;
o << ",\"status\":\"" << status << "\"";
o << ",\"start\":" << get_rel_microseconds(start);
o << ",\"tcp\":" << get_rel_microseconds(tcp_established);
o << ",\"open\":" << get_rel_microseconds(on_open);
o << ",\"fail\":" << get_rel_microseconds(on_fail);
o << ",\"close_sent\":" << get_rel_microseconds(close_sent);
o << ",\"close\":" << get_rel_microseconds(on_close);
o << ",\"messages\":[";
std::string sep = "";
std::vector<msg_data>::const_iterator it;
for (it = messages.begin(); it != messages.end(); it++) {
o << sep << "["
<< get_rel_microseconds((*it).send_time) << ","
<< get_rel_microseconds((*it).recv_time)
<< "]";
sep = ",";
}
o << "]";
o << "}";
return o.str();
}
double get_rel_microseconds(time_point t) const {
boost::chrono::nanoseconds dur = t - init;
return static_cast<double> (dur.count()) / 1000.;
}
size_t id;
time_point init;
time_point start;
time_point tcp_established;
time_point on_open;
time_point on_fail;
time_point close_sent;
time_point on_close;
std::string status;
std::vector<msg_data> messages;
//stress_handler::message_ptr msg;
};
class stress_handler : public client::handler {
public:
typedef stress_handler type;
typedef boost::chrono::steady_clock::time_point time_point;
typedef std::map<connection_ptr,time_point> time_map;
/// Construct a stress test from a wscmd command
explicit stress_handler(wscmd::cmd& cmd);
void on_connect(connection_ptr con);
void on_message(connection_ptr con,websocketpp::message::data_ptr msg);
void on_handshake_init(connection_ptr con);
void on_open(connection_ptr con);
void on_close(connection_ptr con);
void on_fail(connection_ptr con);
void start(connection_ptr con);
void close(connection_ptr con);
void end();
std::string get_data() const;
virtual bool maintenance();
void start_message_test();
protected:
size_t m_current_connections;
size_t m_max_connections;
size_t m_total_connections;
size_t m_failed_connections;
size_t m_next_con_id;
time_point m_init;
// connection related timestamps
std::map<connection_ptr,con_data> m_con_data;
mutable std::list<connection_ptr> m_dirty;
// Stats update timer
size_t m_timeout;
boost::shared_ptr<boost::asio::deadline_timer> m_timer;
size_t m_next_msg_id;
boost::shared_ptr<std::string> m_msg;
// test settings pulled from the command
con_lifetime::value m_con_lifetime;
size_t m_con_duration;
bool m_con_sync;
msg_mode::value m_msg_mode;
size_t m_msg_count;
size_t m_msg_size;
mutable boost::mutex m_lock;
};
typedef boost::shared_ptr<stress_handler> stress_handler_ptr;
} // namespace wsperf
#endif // WSPERF_STRESS_HANDLER_HPP

View File

@@ -0,0 +1,492 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var ws;
var url;
var testdata = {};
var show_individual_results = true;
function format(val) {
if (val < 1000) {
return val + "µs";
} else if (val >= 1000) {
return val/1000.0 + "ms";
}
}
function reset_stats() {
test_data = {};
$("#token").html("");
$("#status").html("");
$("#current_connections").html("");
$("#max_connections").html("");
$("#total_connections").html("");
$("#failed_connections").html("");
$("#open_min").html("");
$("#open_med").html("");
$("#open_avg").html("");
$("#open_max").html("");
$("#msg_min").html("");
$("#msg_med").html("");
$("#msg_avg").html("");
$("#msg_max").html("");
$("#close_min").html("");
$("#close_med").html("");
$("#close_avg").html("");
$("#close_max").html("");
$("#results-body").html("");
}
function connect() {
url = document.getElementById("server_url").value;
console.log(url);
if ("WebSocket" in window) {
ws = new WebSocket(url);
} else if ("MozWebSocket" in window) {
ws = new MozWebSocket(url);
} else {
write_error("This Browser does not support WebSockets.");
return;
}
ws.onopen = function(e) {
document.getElementById("server_url").disabled = true;
document.getElementById("toggle_connect").innerHTML = "Disconnect";
};
ws.onerror = function(e) {
write_error("Client: An error occured, see console log for more details.");
console.log(e);
};
ws.onclose = function(e) {
document.getElementById("server_url").disabled = false;
document.getElementById("toggle_connect").innerHTML = "Connect";
document.getElementById("server_details").innerHTML = "";
};
ws.onmessage = function(e) {
//console.log(e);
var data = JSON.parse(e.data);
console.log(data);
if (data.type == "test_welcome") {
var o = "";
o += "<strong>Version:</strong> "+data.version+" ";
o += "<strong>Ident:</strong> "+data.ident+" ";
o += "<strong>num_workers:</strong> "+data.num_workers+" ";
document.getElementById("server_details").innerHTML = o;
} else if (data.type == "test_start") {
$("#token").html(data.token);
$("#status").html("Started");
reset_stats();
} else if (data.type == "test_complete") {
$("#status").html("Completed");
open_min = 0;
open_median = [];
open_mean = 0;
open_max = 0;
msg_min = 0;
msg_median = [];
msg_mean = 0;
msg_max = 0;
close_min = 0;
close_median = [];
close_mean = 0;
close_max = 0;
for (var i in testdata) {
open = Math.max(Math.round(testdata[i].open-testdata[i].tcp,2),0);
fail = Math.max(Math.round(testdata[i].fail-testdata[i].tcp,2),0);
close = Math.max(Math.round(testdata[i].close-testdata[i].close_sent,2),0);
open = Math.max(fail,open);
if (open < open_min || open_min == 0) {
open_min = open;
}
if (close < close_min || close_min == 0) {
close_min = close;
}
open_median.push(open);
close_median.push(close);
open_mean += open;
close_mean += close;
if (open > open_max) {
open_max = open;
}
if (close > close_max) {
close_max = close;
}
for (var j in testdata[i].messages) {
net = Math.round(testdata[i].messages[j][1]-testdata[i].messages[j][0],2)
if (net < msg_min || msg_min == 0) {
msg_min = net;
}
if (net > msg_max) {
msg_max = net;
}
msg_median.push(net);
msg_mean += net;
}
}
open_mean = Math.round(open_mean / open_median.length);
msg_mean = Math.round(msg_mean / msg_median.length);
close_mean = Math.round(close_mean / close_median.length);
open_median.sort();
msg_median.sort();
close_median.sort();
var half = Math.floor(open_median.length/2);
if (open_median.length % 2) {
open_median = open_median[half];
close_median = close_median[half];
} else {
open_median = (open_median[half-1]+open_median[half])/2.0;
close_median = (close_median[half-1]+close_median[half])/2.0;
}
half = Math.floor(msg_median.length/2);
if (msg_median.length % 2) {
msg_median = msg_median[half];
} else {
msg_median = (msg_median[half-1]+msg_median[half])/2.0;
}
$("#open_min").html(format(open_min));
$("#open_med").html(format(open_median));
$("#open_avg").html(format(open_mean));
$("#open_max").html(format(open_max));
$("#msg_min").html(format(msg_min));
$("#msg_med").html(format(msg_median));
$("#msg_avg").html(format(msg_mean));
$("#msg_max").html(format(msg_max));
$("#close_min").html(format(close_min));
$("#close_med").html(format(close_median));
$("#close_avg").html(format(close_mean));
$("#close_max").html(format(close_max));
} else if (data.type == "test_data") {
$("#current_connections").html(data.data.current_connections);
$("#max_connections").html(data.data.max_connections);
$("#total_connections").html(data.data.total_connections);
$("#failed_connections").html(data.data.failed_connections);
if (data.data.connection_data.length > 0) {
for (var i in data.data.connection_data) {
foo = data.data.connection_data[i];
testdata[foo.id] = foo;
if (show_individual_results) {
if ($("#result-"+foo.id).length == 0) {
$("#result-"+foo.id+" .result").html(data.data.result);
var o = "";
o += "<tr id='result-"+foo.id+"'>";
o += "<td class='id'>"+foo.id+"</td>";
o += "<td class='status'>"+foo.status+"</td>";
o += "<td class='start'>"+foo.start+"</td>";
o += "<td class='tcp'>"+Math.max(Math.round(foo.tcp-foo.start,2),0)+"µs</td>";
open = Math.max(Math.round(foo.open-foo.tcp,2),0);
fail = Math.max(Math.round(foo.fail-foo.tcp,2),0);
close = Math.max(Math.round(foo.close-foo.close_sent,2),0);
if (open == 0) {
o += "<td class='open'>"+fail+"µs</td>";
} else {
o += "<td class='open'>"+open+"µs</td>";
}
o += "<td class='close_sent'>"+foo.close_sent+"µs</td>";
o += "<td class='close'>"+close+"µs</td>";
o += "</tr>";
o += document.getElementById("results-body").innerHTML;
document.getElementById("results-body").innerHTML = o;
} else {
temp = foo.tcp-foo.start
$("#result-"+foo.id+" .id").html(foo.id);
$("#result-"+foo.id+" .status").html(foo.status);
$("#result-"+foo.id+" .start").html(foo.start+"µs");
$("#result-"+foo.id+" .tcp").html(Math.max(Math.round(foo.tcp-foo.start,2),0)+"µs");
open = Math.max(Math.round(foo.open-foo.tcp,2),0);
fail = Math.max(Math.round(foo.fail-foo.tcp,2),0);
close = Math.max(Math.round(foo.close-foo.close_sent,2),0);
if (open == 0) {
$("#result-"+foo.id+" .open").html(fail+"µs");
} else {
$("#result-"+foo.id+" .open").html(open+"µs");
}
$("#result-"+foo.id+" .close_sent").html(foo.close_sent+"µs");
$("#result-"+foo.id+" .close").html(close+"µs");
}
}
}
}
$("#result-"+data.token).addClass(data.data.result);
} else if (data.type == "error") {
$("#result-"+data.token+" .status").html("Error");
$("#messages").html(data.data+"<br />");
} else {
console.log(data);
}
};
}
function write_error(msg) {
$("#messages").css("display","block").html(msg);
}
function disconnect() {
ws.close();
$("#server_url").attr("disabled","disabled");
$("#toggle_connect").html("Connect");
}
function toggle_connect() {
if ($("#server_url").attr("disabled") == "disabled") {
disconnect();
} else {
connect();
}
}
function send(ind) {
$("#messages").css("display","none");
if (ws === undefined || ws.readyState != 1) {
write_error("Websocket is not avaliable for writing");
return;
}
show_individual_results = ind;
ws.send($("#msg").val());
}
</script>
<style>
body,html {
margin: 0px;
padding: 0px;
font-family: Tahoma,Arial,Verdana,sans-serif;
background-color: #F4F4F4;
}
#server_url {
width: 200px;
}
h2 {
font-size: 18px;
margin: 20px 20px;
}
#controls {
/*float:right;*/
background-color: #333;
color: white;
font-size: 14px;
padding: 4px;
}
#msg {
width: 100%;
box-sizing: border-box;
-webkit-box-sizing:border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
display: block;
height: 5em;
}
#message_input button {
display: inline-block;
}
#message_input {
margin: 0px 20px;
}
#results {
margin: 0px 20px;
}
#results table {
width: 100%;
border: 1px solid white;
color: white;
border-collapse: collapse;
border-spacing: 0px;
margin-top: 10px;
}
#messages {
display: none;
margin: 20px;
padding: 4px;
color: white;
}
th,td {
font-weight: normal;
padding: 6px;
font-size: 0.8em;
border: 1px solid white;
}
thead tr {
background-color: #048;
}
tbody tr {
background-color: #666;
}
.pass {
background-color: #0A0;
}
.fail {
background-color: #900;
}
.error {
background-color: #9A0;
}
</style>
<div id="controls">
<div id="server">
<input type="text" name="server_url" id="server_url" value="ws://localhost:9050" />
<button id="toggle_connect" onclick="toggle_connect();">Connect</button>
<span id="server_details"></span>
</div>
</div>
<h2>Run Test</h2>
<div id="message_input">
<textarea type="text" id="msg">stress_test:uri=ws://localhost:9002;token=stress;connection_count=5;handshake_delay=10;</textarea><br />
<button onclick="send(true);">Run Test</button> <button onclick="send(false);">Run Test (aggregate results only)</button>
</div>
<h2>Test Results</h2>
<div id="messages" class='fail'></div>
<div id="results">
<!--<div>
Token: <span id="token"></span><br />
Status: <span id="status">N/A</span><br />
Current Connections: <span id="current_connections"></span><br />
Max Connections: <span id="max_connections"></span><br />
Total Connections: <span id="total_connections"></span><br />
Failed Connections: <span id="failed_connections"></span><br />
</div>-->
<table>
<thead>
<tr>
<th rowspan="2">Token</th>
<th rowspan="2">Status</th>
<th colspan="4">Connections</th>
<th colspan="4">Open</th>
<th colspan="4">Messages</th>
<th colspan="4">Close</th>
</tr>
<tr>
<th>Current</th>
<th>Max</th>
<th>Total</th>
<th>Failed</th>
<th>Min</th>
<th>Median</th>
<th>Mean</th>
<th>Max</th>
<th>Min</th>
<th>Median</th>
<th>Mean</th>
<th>Max</th>
<th>Min</th>
<th>Median</th>
<th>Mean</th>
<th>Max</th>
</tr>
</thead>
<tbody>
<tr>
<td><span id="token"></span></td>
<td><span id="status">N/A</span></td>
<td><span id="current_connections"></span></td>
<td><span id="max_connections"></span></td>
<td><span id="total_connections"></span></td>
<td><span id="failed_connections"></span></td>
<td><span id="open_min"></span></td>
<td><span id="open_med"></span></td>
<td><span id="open_avg"></span></td>
<td><span id="open_max"></span></td>
<td><span id="msg_min"></span></td>
<td><span id="msg_med"></span></td>
<td><span id="msg_avg"></span></td>
<td><span id="msg_max"></span></td>
<td><span id="close_min"></span></td>
<td><span id="close_med"></span></td>
<td><span id="close_avg"></span></td>
<td><span id="close_max"></span></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Start</th>
<th>TCP</th>
<th>Open/Fail</th>
<th>Close sent</th>
<th>Closed</th>
</tr>
</thead>
<tbody id="results-body">
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,84 @@
// A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that.
// Generate four random hex digits.
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
// Generate a pseudo-GUID by concatenating random hexadecimal.
function guid() {
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
};
// Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table.
var Store = function(name) {
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};
_.extend(Store.prototype, {
// Save the current state of the **Store** to *localStorage*.
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
},
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
},
// Update a model by replacing its copy in `this.data`.
update: function(model) {
this.data[model.id] = model;
this.save();
return model;
},
// Retrieve a model from `this.data` by id.
find: function(model) {
return this.data[model.id];
},
// Return the array of all models currently in storage.
findAll: function() {
return _.values(this.data);
},
// Delete a model from `this.data`, returning it.
destroy: function(model) {
delete this.data[model.id];
this.save();
return model;
}
});
// Override `Backbone.sync` to use delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`.
Backbone.sync = function(method, model, options) {
var resp;
var store = model.localStorage || model.collection.localStorage;
switch (method) {
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
case "create": resp = store.create(model); break;
case "update": resp = store.update(model); break;
case "delete": resp = store.destroy(model); break;
}
if (resp) {
options.success(resp);
} else {
options.error("Record not found");
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,999 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.1';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
if (index == 0) {
shuffled[0] = value;
} else {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
}
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var result = [];
_.reduce(initial, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
memo[memo.length] = el;
result[result.length] = array[i];
}
return memo;
}, []);
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
func.apply(context, args);
}
whenDone();
throttling = true;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.escape || noMatch, function(match, code) {
return "',_.escape(" + unescape(code) + "),'";
})
.replace(c.interpolate || noMatch, function(match, code) {
return "'," + unescape(code) + ",'";
})
.replace(c.evaluate || noMatch, function(match, code) {
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', '_', tmpl);
if (data) return func(data, _);
return function(data) {
return func.call(this, data, _);
};
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "wscmd.hpp"
#include <map>
#include <string>
wscmd::cmd wscmd::parse(const std::string& m) {
cmd command;
std::string::size_type start;
std::string::size_type end;
start = m.find(":",0);
if (start != std::string::npos) {
command.command = m.substr(0,start);
start++; // skip the colon
end = m.find(";",start);
// find all semicolons
while (end != std::string::npos) {
std::string arg;
std::string val;
std::string::size_type sep = m.find("=",start);
if (sep != std::string::npos) {
arg = m.substr(start,sep-start);
val = m.substr(sep+1,end-sep-1);
} else {
arg = m.substr(start,end-start);
val = "";
}
command.args[arg] = val;
start = end+1;
end = m.find(";",start);
}
}
return command;
}
bool wscmd::extract_string(wscmd::cmd command,const std::string& key,std::string& val) {
if (command.args[key] != "") {
val = command.args[key];
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef WSCMD_HPP
#define WSCMD_HPP
#include <map>
#include <stdexcept>
#include <string>
#include <sstream>
namespace wscmd {
// Parses a wscmd string.
// command structure
// command:arg1=val1;arg2=val2;arg3=val3;
// commands
// ack: messages to ack
// example: `ack:e3458d0aceff8b70a3e5c0afec632881=38;e3458d0aceff8b70a3e5c0afec632881=42;`
// send: [vals]
// message; opcode=X; payload="X"
// frame; [fuzzer stuff]
// close:code=1000;reason=msg;
// (instructs the opposite end to close with given optional code/msg)
typedef std::map<std::string,std::string> arg_list;
struct cmd {
// TODO: move semantics
std::string command;
arg_list args;
};
wscmd::cmd parse(const std::string& m);
template <typename T>
bool extract_number(wscmd::cmd command,const std::string& key,T& val) {
if (command.args[key] != "") {
std::istringstream buf(command.args[key]);
buf >> val;
if(buf) {return true;}
}
return false;
}
bool extract_string(wscmd::cmd command,const std::string& key,std::string& val);
} // namespace wscmd
#endif // WSCMD_HPP

View File

@@ -0,0 +1,18 @@
##
## wsperf config file
##
## MODE (choose one)
server = 1
#client = 1
## GLOBAL OPTIONS
num_threads = 2
#ident = "ident string to report to commander"
## OPTIONS FOR SERVER
port = 9501
## OPTIONS FOR CLIENT
## uri of command server
#uri = "ws://localhost:9005"

View File

@@ -0,0 +1,294 @@
/*
* Copyright (c) 2011, Peter Thorson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the WebSocket++ Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "request.hpp"
#include "../../src/roles/client.hpp"
#include "../../src/websocketpp.hpp"
#include <boost/program_options.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <cstring>
#include <sstream>
#include <fstream>
// This default will only work on unix systems.
// Windows systems should set this as a compile flag to an appropriate value
#ifndef WSPERF_CONFIG
#define WSPERF_CONFIG "~/.wsperf"
#endif
static const std::string user_agent = "wsperf/0.2.0dev "+websocketpp::USER_AGENT;
using websocketpp::client;
namespace po = boost::program_options;
int start_server(po::variables_map& vm);
int start_client(po::variables_map& vm);
int start_server(po::variables_map& vm) {
unsigned short port = vm["port"].as<unsigned short>();
unsigned int num_threads = vm["num_threads"].as<unsigned int>();
std::string ident = vm["ident"].as<std::string>();
bool silent = (vm.count("silent") && vm["silent"].as<int>() == 1);
std::list< boost::shared_ptr<boost::thread> > threads;
wsperf::request_coordinator rc;
server::handler::ptr h;
h = server::handler::ptr(
new wsperf::concurrent_handler<server>(
rc,
ident,
user_agent,
num_threads
)
);
if (!silent) {
std::cout << "Starting wsperf server on port " << port << " with " << num_threads << " processing threads." << std::endl;
}
// Start worker threads
for (unsigned int i = 0; i < num_threads; i++) {
threads.push_back(boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&wsperf::process_requests, &rc, i))));
}
// Start WebSocket++
server endpoint(h);
endpoint.alog().unset_level(websocketpp::log::alevel::ALL);
endpoint.elog().unset_level(websocketpp::log::elevel::ALL);
if (!silent) {
endpoint.alog().set_level(websocketpp::log::alevel::CONNECT);
endpoint.alog().set_level(websocketpp::log::alevel::DISCONNECT);
endpoint.elog().set_level(websocketpp::log::elevel::RERROR);
endpoint.elog().set_level(websocketpp::log::elevel::FATAL);
}
endpoint.listen(port);
return 0;
}
int start_client(po::variables_map& vm) {
if (!vm.count("uri")) {
std::cerr << "client mode requires uri" << std::endl;
return 1;
}
bool silent = (vm.count("silent") && vm["silent"].as<int>() == 1);
unsigned int reconnect = vm["reconnect"].as<unsigned int>();
std::string uri = vm["uri"].as<std::string>();
unsigned int num_threads = vm["num_threads"].as<unsigned int>();
std::string ident = vm["ident"].as<std::string>();
// Start wsperf
std::list< boost::shared_ptr<boost::thread> > threads;
std::list< boost::shared_ptr<boost::thread> >::iterator thread_it;
wsperf::request_coordinator rc;
client::handler::ptr h;
h = client::handler::ptr(
new wsperf::concurrent_handler<client>(
rc,
ident,
user_agent,
num_threads
)
);
if (!silent) {
std::cout << "Starting wsperf client connecting to " << uri << " with " << num_threads << " processing threads." << std::endl;
}
for (unsigned int i = 0; i < num_threads; i++) {
threads.push_back(boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&wsperf::process_requests, &rc, i))));
}
while(1) {
client endpoint(h);
endpoint.alog().unset_level(websocketpp::log::alevel::ALL);
endpoint.elog().unset_level(websocketpp::log::elevel::ALL);
if (!silent) {
endpoint.alog().set_level(websocketpp::log::alevel::CONNECT);
endpoint.alog().set_level(websocketpp::log::alevel::DISCONNECT);
endpoint.elog().set_level(websocketpp::log::elevel::RERROR);
endpoint.elog().set_level(websocketpp::log::elevel::FATAL);
}
client::connection_ptr con = endpoint.get_connection(uri);
con->add_request_header("User-Agent",user_agent);
con->add_subprotocol("wsperf");
endpoint.connect(con);
// This will block until there is an error or the websocket closes
endpoint.run();
rc.reset();
if (!reconnect) {
break;
} else {
boost::this_thread::sleep(boost::posix_time::seconds(reconnect));
}
}
// Add a "stop work" request for each outstanding worker thread
for (thread_it = threads.begin(); thread_it != threads.end(); ++thread_it) {
wsperf::request r;
r.type = wsperf::END_WORKER;
rc.add_request(r);
}
// Wait for worker threads to finish quitting.
for (thread_it = threads.begin(); thread_it != threads.end(); ++thread_it) {
(*thread_it)->join();
}
return 0;
}
int main(int argc, char* argv[]) {
try {
// 12288 is max OS X limit without changing kernal settings
/*const rlim_t ideal_size = 10000;
rlim_t old_size;
rlim_t old_max;
struct rlimit rl;
int result;
result = getrlimit(RLIMIT_NOFILE, &rl);
if (result == 0) {
//std::cout << "System FD limits: " << rl.rlim_cur << " max: " << rl.rlim_max << std::endl;
old_size = rl.rlim_cur;
old_max = rl.rlim_max;
if (rl.rlim_cur < ideal_size) {
std::cout << "Attempting to raise system file descriptor limit from " << rl.rlim_cur << " to " << ideal_size << std::endl;
rl.rlim_cur = ideal_size;
if (rl.rlim_max < ideal_size) {
rl.rlim_max = ideal_size;
}
result = setrlimit(RLIMIT_NOFILE, &rl);
if (result == 0) {
std::cout << "Success" << std::endl;
} else if (result == EPERM) {
std::cout << "Failed. This server will be limited to " << old_size << " concurrent connections. Error code: Insufficient permissions. Try running process as root. system max: " << old_max << std::endl;
} else {
std::cout << "Failed. This server will be limited to " << old_size << " concurrent connections. Error code: " << errno << " system max: " << old_max << std::endl;
}
}
}*/
std::string config_file;
// Read and Process Command Line Options
po::options_description generic("Generic");
generic.add_options()
("help", "produce this help message")
("version,v", po::value<int>()->implicit_value(1), "Print version information")
("config", po::value<std::string>(&config_file)->default_value(WSPERF_CONFIG),
"Configuration file to use.")
;
po::options_description config("Configuration");
config.add_options()
("server,s", po::value<int>()->implicit_value(1), "Run in server mode")
("client,c", po::value<int>()->implicit_value(1), "Run in client mode")
("port,p", po::value<unsigned short>()->default_value(9050), "Port to listen on in server mode")
("uri,u", po::value<std::string>(), "URI to connect to in client mode")
("reconnect,r", po::value<unsigned int>()->default_value(0), "Auto-reconnect delay (in seconds) after a connection ends or fails in client mode. Zero indicates do not reconnect.")
("num_threads", po::value<unsigned int>()->default_value(2), "Number of worker threads to use")
("silent", po::value<int>()->implicit_value(1), "Silent mode. Will not print errors to stdout")
("ident,i", po::value<std::string>()->default_value("Unspecified"), "Implimentation identification string reported by this agent.")
;
po::options_description cmdline_options;
cmdline_options.add(generic).add(config);
po::options_description config_file_options;
config_file_options.add(config);
po::options_description visible("Allowed options");
visible.add(generic).add(config);
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, cmdline_options), vm);
po::notify(vm);
std::ifstream ifs(config_file.c_str());
if (ifs) {
store(parse_config_file(ifs, config_file_options), vm);
notify(vm);
}
if (vm.count("help")) {
std::cout << cmdline_options << std::endl;
return 1;
}
if (vm.count("version")) {
std::cout << user_agent << std::endl;
return 1;
}
if (vm.count("server") && vm["server"].as<int>() == 1) {
return start_server(vm);
} else if (vm.count("client") && vm["client"].as<int>() == 1) {
return start_client(vm);
} else {
std::cerr << "You must choose either client or server mode. See wsperf --help for more information" << std::endl;
return 1;
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,387 @@
<!doctype html>
<html>
<head>
<!--[if lte IE 8]><script language="javascript" type="text/javascript" src="flot/excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="vendor/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="vendor/underscore.js"></script>
<script language="javascript" type="text/javascript" src="vendor/backbone.js"></script>
<script language="javascript" type="text/javascript" src="vendor/backbone-localstorage.js"></script>
</head>
<body>
<script type="text/javascript">
var ws;
var url;
var tests = {"9.1.1":1,"9.1.2":2,"9.1.3":3,"9.1.4":4,"9.1.5":5,"9.1.6":6};
var d2 = [["9.1.1",6021],["9.1.2",22146]];
var raw = {};
var d = [{data:[[0,6021],[2,22146]], label:"Autobahn",bars: { show: true }},{label:"WebSocket++",bars: { show: true },data:[[1,5243],[3,20042]]}];
var ws;
function connect() {
url = wsperf.get('server');
if ("WebSocket" in window) {
ws = new WebSocket(url);
} else if ("MozWebSocket" in window) {
ws = new MozWebSocket(url);
} else {
document.getElementById("messages").innerHTML += "This Browser does not support WebSockets<br />";
return;
}
ws.onopen = function(e) {
console.log("Client: A connection to "+ws.URL+" has been opened.");
wsperf.set('connected',true);
};
ws.onerror = function(e) {
document.getElementById("messages").innerHTML += "Client: An error occured, see console log for more details.<br />";
console.log(e);
wsperf.set('connected',false);
};
ws.onclose = function(e) {
console.log("Client: The connection to "+url+" was closed.");
wsperf.set('connected',false);
};
ws.onmessage = function(e) {
console.log(e.data);
data = JSON.parse(e.data);
if (data.type == "message") {
console.log("Server: "+data.data);
} else {
if (raw[data.target] === undefined) {
raw[data.target] = [data.data];
} else {
raw[data.target].push(data.data);
}
process();
}
};
}
function disconnect() {
ws.close();
}
function send() {
if (ws === undefined || ws.readyState != 1) {
document.getElementById("messages").innerHTML += "Client: Websocket is not avaliable for writing<br />";
return;
}
ws.send(document.getElementById("msg").value);
}
function process() {
d = [];
k = 0;
for (i in raw) {
o = {};
o.label = i;
o.bars = {show: true}
o.data = [];
for (j in raw[i]) {
p = [k++,raw[i][j].microseconds];
o.data.push(p);
}
d.push(o);
}
$.plot($("#placeholder"), d, {xaxis:[{tickFormatter: euroFormatter}]});
}
function euroFormatter(v, axis) {
return tests[v];
}
$(function () {
//$.plot($("#placeholder"), d, {xaxis:[{tickFormatter: euroFormatter}]});
window.WSPerf = Backbone.Model.extend({
defaults: function() {
return {server:"ws://localhost:9003",connected:false};
}
});
window.Server = Backbone.Model.extend({
defaults: function() {
return {enabled:true};
}
});
window.ServerList = Backbone.Collection.extend({
model: Server,
localStorage: new Store("wsperf_servers"),
});
window.Servers = new ServerList;
window.wsperf = new WSPerf;
window.ServerView = Backbone.View.extend({
tagName: "tr",
template: _.template($('#server-template').html()),
events: {
"click td.clear" : "clear",
"click td.server-enabled input" : "toggle_enabled"
},
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setText();
return this;
},
setText: function() {
var ua = this.model.get('ua');
var uri = this.model.get('uri');
this.$('.server-ua').text(ua);
this.$('.server-uri').text(uri);
},
remove: function() {
console.log("remove");
$(this.el).remove();
},
clear: function() {
console.log("destroy");
this.model.destroy();
},
toggle_enabled: function() {
this.model.set('enabled',!this.model.get('enabled'));
}
});
window.WSPerfSettingsView = Backbone.View.extend({
tagName: "div",
template: _.template($('#settings-template').html()),
events: {
"click #add-target-server": "addTargetServer",
"click #target-server-table .clear-all": "clearAll",
"click #toggle_connect": "toggle_connect"
},
initialize: function() {
//this.model.bind('change', this.render, this);
//this.model.bind('destroy', this.remove, this);
wsperf.bind('change', this.render, this);
Servers.bind('add', this.addOne, this);
Servers.bind('reset', this.addAll, this);
Servers.bind('all', this.render, this);
},
render: function() {
$(this.el).html(this.template(wsperf.toJSON()));
this.inputua = this.$("#add-target-server-ua");
this.inputuri = this.$("#add-target-server-uri");
this.addAll();
return this;
},
addOne: function(server) {
var view = new ServerView({model: server});
$("#target-server-list").append(view.render().el);
},
addAll: function() {
Servers.each(this.addOne);
},
addTargetServer: function() {
var ua = this.inputua.val();
var uri = this.inputuri.val();
console.log("bar "+ua+" "+uri);
if (!ua || !uri) {
return;
}
Servers.create({ua: ua, uri: uri});
this.inputua.val('');
this.inputuri.val('');
},
clearAll: function() {
console.log("foo");
Servers.each(function(server) {server.destroy(); });
return false;
},
toggle_connect: function() {
if (wsperf.get('connected')) {
disconnect();
} else {
connect();
}
}
});
window.WSPerfDataView = Backbone.View.extend({
tagName: "div",
template: _.template($('#data-template').html()),
events: {
//"click td.clear" : "clear"
},
initialize: function() {
//this.model.bind('change', this.render, this);
//this.model.bind('destroy', this.remove, this);
},
render: function() {
$(this.el).html(this.template());
return this;
},
});
/*window.WSPerfCommanderView = Backbone.View.extend({
el: $("#wsperf-commander"),
events: {
//"click #add-target-server": "addTargetServer",
//"click #target-server-table .clear-all": "clearAll",
//"click #menu-server-settings": "renderServerSettings",
//"click #menu-server-settings": "renderServerSettings",
//"click #menu-server-settings": "renderServerSettings",
},
initialize: function() {
Servers.fetch();
},
render: function() {},
});*/
var WSPerfCommanderRouter = Backbone.Router.extend({
initialize: function() {
Servers.fetch();
settingsView = new WSPerfSettingsView({el: "#content"});
dataView = new WSPerfDataView({el: "#content"});
},
routes: {
"/settings": "settings",
"/data": "data",
"*actions": "defaultRoute"
},
defaultRoute: function( actions ) {
console.log(actions);
},
settings: function() {
settingsView.render();
},
data: function() {
dataView.render();
}
});
var app_router = new WSPerfCommanderRouter;
Backbone.history.start();
//window.App = new WSPerfCommanderView;
});
</script>
<style>
body,html {
margin: 0px;
padding: 0px;
}
#controls {
float:left;
width:200px;
background-color: #999;
}
#content {
margin-left: 200px;
}
</style>
<div id="wsperf-commander">
<div id="controls">
<ul>
<li id="menu-server-settings"><a href="#/settings">Server Settings</a></li>
<li id="menu-data-table"><a href="#/data">Data Table</a></li>
<li id="menu-charts"><a href="#action">Charts</a></li>
</ul>
<div id="message_input"><input type="text" name="msg" id="msg" value="ws://localhost:9004" />
<button onclick="send();">Send</button></div>
</div>
<div id="content">
<div id="placeholder" style="width:600px;height:300px"></div>
<div id="messages"></div>
</div>
</div>
<!-- Templates -->
<script type="text/template" id="server-template">
<td class="server-enabled"><input type="checkbox" <%= (enabled ? "checked" : "") %> /></td>
<td class="server-ua"><%= ua %></td>
<td class="server-uri"><%= uri %></td>
<td class="clear">X</td>
</script>
<script type="text/template" id="settings-template">
<h2>wsperf Server</h2>
<div id="server">
<input type="text" name="server_url" id="server_url" value="<%= server %>" <%= (connected ? "disabled" : "") %> />
<button id="toggle_connect"><%= (connected ? "Disconnect" : "Connect") %></button>
</div>
<h2>Target Servers</h2>
<table id="target-server-table">
<thead>
<th>Enabled</th><th>User Agent</th><th>URI</th><th class="clear-all">Remove</th>
</thead>
<tbody id="target-server-list"></tbody>
</table>
<div>
User Agent: <input type="text" id="add-target-server-ua" /><br />
URI: <input type="text" id="add-target-server-uri" /><br />
<button id="add-target-server">Add Target Server</button>
</div>
</script>
<script type="text/template" id="data-template">
<div>data</div>
</script>
</body>
</html>