mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
Merge commit '09987d0f9d32e860f1391bb9c75b799501e2d141' as 'Subtrees/websocket'
This commit is contained in:
24
Subtrees/websocket/examples/wsperf/Makefile
Normal file
24
Subtrees/websocket/examples/wsperf/Makefile
Normal 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
|
||||
29
Subtrees/websocket/examples/wsperf/SConscript
Normal file
29
Subtrees/websocket/examples/wsperf/SConscript
Normal 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')
|
||||
271
Subtrees/websocket/examples/wsperf/case.cpp
Normal file
271
Subtrees/websocket/examples/wsperf/case.cpp
Normal 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;
|
||||
}
|
||||
148
Subtrees/websocket/examples/wsperf/case.hpp
Normal file
148
Subtrees/websocket/examples/wsperf/case.hpp
Normal 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
|
||||
142
Subtrees/websocket/examples/wsperf/generic.cpp
Normal file
142
Subtrees/websocket/examples/wsperf/generic.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
65
Subtrees/websocket/examples/wsperf/generic.hpp
Normal file
65
Subtrees/websocket/examples/wsperf/generic.hpp
Normal 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
|
||||
277
Subtrees/websocket/examples/wsperf/message_test.html
Normal file
277
Subtrees/websocket/examples/wsperf/message_test.html
Normal 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>
|
||||
200
Subtrees/websocket/examples/wsperf/request.cpp
Normal file
200
Subtrees/websocket/examples/wsperf/request.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
194
Subtrees/websocket/examples/wsperf/request.hpp
Normal file
194
Subtrees/websocket/examples/wsperf/request.hpp
Normal 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
|
||||
96
Subtrees/websocket/examples/wsperf/stress_aggregate.cpp
Normal file
96
Subtrees/websocket/examples/wsperf/stress_aggregate.cpp
Normal 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;
|
||||
}*/
|
||||
|
||||
59
Subtrees/websocket/examples/wsperf/stress_aggregate.hpp
Normal file
59
Subtrees/websocket/examples/wsperf/stress_aggregate.hpp
Normal 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
|
||||
358
Subtrees/websocket/examples/wsperf/stress_handler.cpp
Normal file
358
Subtrees/websocket/examples/wsperf/stress_handler.cpp
Normal 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;
|
||||
}
|
||||
204
Subtrees/websocket/examples/wsperf/stress_handler.hpp
Normal file
204
Subtrees/websocket/examples/wsperf/stress_handler.hpp
Normal 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
|
||||
492
Subtrees/websocket/examples/wsperf/stress_test.html
Normal file
492
Subtrees/websocket/examples/wsperf/stress_test.html
Normal 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>
|
||||
84
Subtrees/websocket/examples/wsperf/vendor/backbone-localstorage.js
vendored
Normal file
84
Subtrees/websocket/examples/wsperf/vendor/backbone-localstorage.js
vendored
Normal 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");
|
||||
}
|
||||
};
|
||||
1290
Subtrees/websocket/examples/wsperf/vendor/backbone.js
vendored
Normal file
1290
Subtrees/websocket/examples/wsperf/vendor/backbone.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
Subtrees/websocket/examples/wsperf/vendor/jquery.min.js
vendored
Normal file
4
Subtrees/websocket/examples/wsperf/vendor/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
999
Subtrees/websocket/examples/wsperf/vendor/underscore.js
vendored
Normal file
999
Subtrees/websocket/examples/wsperf/vendor/underscore.js
vendored
Normal 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
|
||||
};
|
||||
|
||||
// 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);
|
||||
78
Subtrees/websocket/examples/wsperf/wscmd.cpp
Normal file
78
Subtrees/websocket/examples/wsperf/wscmd.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
77
Subtrees/websocket/examples/wsperf/wscmd.hpp
Normal file
77
Subtrees/websocket/examples/wsperf/wscmd.hpp
Normal 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
|
||||
18
Subtrees/websocket/examples/wsperf/wsperf.cfg
Normal file
18
Subtrees/websocket/examples/wsperf/wsperf.cfg
Normal 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"
|
||||
294
Subtrees/websocket/examples/wsperf/wsperf.cpp
Normal file
294
Subtrees/websocket/examples/wsperf/wsperf.cpp
Normal 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;
|
||||
}
|
||||
387
Subtrees/websocket/examples/wsperf/wsperf_commander.html
Normal file
387
Subtrees/websocket/examples/wsperf/wsperf_commander.html
Normal 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>
|
||||
Reference in New Issue
Block a user