├── .gitignore ├── .rvmrc ├── Gemfile ├── Gemfile.lock ├── README.md ├── c ├── Makefile ├── hellod.c └── tryit.sh ├── clj-aleph ├── project.clj └── src │ └── hellod │ └── core.clj ├── clj-jetty ├── project.clj └── src │ └── hellod │ └── core.clj ├── erlang ├── rebar.config ├── shell └── src │ ├── hello.app.src │ ├── hello_app.erl │ ├── hello_server.erl │ └── hello_sup.erl ├── go ├── Makefile └── server.go ├── hellod ├── hellod.rb ├── java-netty ├── compile ├── hellod ├── lib │ └── netty-3.2.6.Final.jar └── src │ ├── HelloDServer.java │ ├── HelloHttpRequestHandler.java │ ├── HttpRequestHandler.java │ └── HttpServerPipelineFactory.java ├── java-nio ├── compile ├── hellod └── src │ └── HelloDServer.java ├── node └── server.js ├── results.md ├── ruby └── server.rb └── test-all.sh /.gitignore: -------------------------------------------------------------------------------- 1 | go/_* 2 | go/hellod 3 | clj-*/classes 4 | clj-*/lib 5 | *.[oa] 6 | c/hellod 7 | c/libtask/primes 8 | c/libtask/testdelay 9 | out 10 | .idea 11 | *.iml 12 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm --create 1.9.2@hellod 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'eventmachine' 4 | gem 'http_parser.rb' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | eventmachine (0.12.10) 5 | eventmachine (0.12.10-java) 6 | http_parser.rb (0.5.2) 7 | http_parser.rb (0.5.2-java) 8 | 9 | PLATFORMS 10 | java 11 | ruby 12 | 13 | DEPENDENCIES 14 | eventmachine 15 | http_parser.rb 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HelloD 2 | ==================== 3 | 4 | Testing high performance HTTP "Hello World" servers. 5 | 6 | Ruby - port 8080 7 | ----------- 8 | 9 | gem install eventmachine http_parser.rb 10 | ruby ruby/server.rb 11 | 12 | Node - port 8081 13 | ----------- 14 | 15 | brew install node 16 | node node/server.js 17 | 18 | Go - port 8082 19 | ----------- 20 | 21 | easy_install mercurial 22 | brew install go 23 | export GOROOT=/usr/local/Cellar/go/r60.1 24 | cd go 25 | make 26 | ./hellod 27 | 28 | Clojure - port 8083 29 | ----------- 30 | 31 | Install Leiningen from https://github.com/technomancy/leiningen 32 | cd clj ; lein run 33 | 34 | Erlang - port 8084 35 | ----------- 36 | 37 | brew install -v erlang --use-gcc 38 | brew install rebar 39 | cd erlang; ./shell 40 | # in shell 41 | > application:start(hello). 42 | 43 | NOTE: you need the trailing '.' in the shell. 44 | 45 | C / libev - port 8085 46 | ----------- 47 | 48 | brew install libev (or apt-get install libev libev-dev) 49 | make 50 | ./hellod 51 | 52 | Java / Simple Java NIO - port 8086 53 | ----------- 54 | 55 | cd java-nio 56 | ./compile 57 | ./hellod 58 | 59 | Java / JBoss Netty (w/ NIO) - port 8087 60 | ----------- 61 | 62 | cd java-netty 63 | ./compile 64 | ./hellod 65 | 66 | Benchmark 67 | ----------- 68 | ab -n 10000 -c 100 http://localhost:8080/ 69 | 70 | or 8081 or 8082... 71 | -------------------------------------------------------------------------------- /c/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS= 3 | LIBS=-lev 4 | SRCS=hellod.c 5 | TARG=hellod 6 | 7 | .PHONY: all 8 | all: $(TARG) 9 | 10 | $(TARG): 11 | $(CC) $(CFLAGS) $(LIBS) $(SRCS) -o $(TARG) 12 | 13 | clean: 14 | rm -f *.o $(TARG) 15 | -------------------------------------------------------------------------------- /c/hellod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /* 12 | * Thanks to: 13 | * 14 | * http://codefundas.blogspot.com/2010/09/create-tcp-echo-server-using-libev.html 15 | * http://github.com/coolaj86/libev-examples 16 | * 17 | * and especially 18 | * 19 | * http://comments.gmane.org/gmane.comp.lib.ev/1424 20 | * where Brandon Black says: 21 | * 22 | * The most generic answer is 23 | * this: create both reader and writer watchers for the socket 24 | * separately, but only activate the read watcher when you need to 25 | * receive traffic, and only active the write watcher when you have 26 | * buffered data ready to write to the socket. You can enable and 27 | * disable them independently with ev_io_start() and ev_io_stop(). e.g. 28 | * for a simple HTTP server, you might start with only the read watcher 29 | * enabled. Once you've processed a request and have a response ready to 30 | * send, you turn on the write watcher to drain the buffered data, then 31 | * turn it off again when the write buffer is empty. 32 | * 33 | */ 34 | 35 | /** 36 | * TYPEDEFS / FORWARD DECLARATIONS 37 | */ 38 | 39 | typedef struct Stack *Stack; 40 | typedef struct Node *Node; 41 | typedef struct Connection *Connection; 42 | typedef struct Buffer *Buffer; 43 | 44 | struct Connection { 45 | int fd; 46 | struct ev_io *read_watcher; 47 | struct ev_io *write_watcher; 48 | Buffer in; 49 | Buffer out; 50 | }; 51 | 52 | struct Buffer { 53 | char *data; 54 | int capacity; 55 | int len; 56 | int loc; 57 | }; 58 | 59 | struct Node { 60 | void *value; 61 | Node next; 62 | }; 63 | 64 | struct Stack { 65 | Node top; 66 | }; 67 | 68 | Node node_init(); 69 | void node_destroy(Node node); 70 | 71 | Stack stack_init(); 72 | void stack_destroy(Stack stack); 73 | Node stack_pop(Stack stack); 74 | void stack_push(Stack stack, Node node); 75 | 76 | Connection conn_init(); 77 | void conn_destroy(Connection conn); 78 | Connection conn_start(int client_sd); 79 | void conn_close(Connection conn); 80 | void conn_write(Connection conn, char *to_write, int to_write_len); 81 | void conn_read_callback(struct ev_loop *loop, struct ev_io *watcher, int revents); 82 | void conn_write_callback(struct ev_loop *loop, struct ev_io *watcher, int revents); 83 | 84 | Buffer buffer_init(); 85 | void buffer_reset(Buffer buffer); 86 | void buffer_destroy(Buffer buffer); 87 | 88 | void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents); 89 | 90 | /** 91 | * GLOBALS 92 | */ 93 | 94 | #define CONNECTION_POOL_SIZE 256 95 | #define BUFFER_SIZE 1024 96 | 97 | #define HEADER_TEXT "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n" 98 | #define BODY_TEXT "

Hello World

\r\n" 99 | 100 | // Our global event loop. 101 | struct ev_loop *global_loop; 102 | 103 | // Connection pool 104 | Stack connection_pool; 105 | 106 | // Precomputed outbound payload 107 | char *body; 108 | 109 | int port = 8085; 110 | int total_clients = 0; 111 | 112 | /** 113 | * DATA STRUCTURE & "OBJECT" IMPLEMENTATIONS 114 | */ 115 | 116 | Node node_init() 117 | { 118 | Node node = malloc(sizeof(struct Node)); 119 | node->value = NULL; 120 | node->next = NULL; 121 | return node; 122 | } 123 | 124 | void node_destroy(Node node) 125 | { 126 | free(node); 127 | } 128 | 129 | Stack stack_init() 130 | { 131 | Stack stack = malloc(sizeof(struct Stack)); 132 | return stack; 133 | } 134 | 135 | void stack_destroy(Stack stack) 136 | { 137 | free(stack); 138 | } 139 | 140 | Node 141 | stack_pop(Stack stack) 142 | { 143 | if (stack->top == NULL) 144 | return NULL; 145 | 146 | Node ret = stack->top; 147 | stack->top = ret->next; 148 | return ret; 149 | } 150 | 151 | void 152 | stack_push(Stack stack, Node node) 153 | { 154 | node->next = stack->top; 155 | stack->top = node; 156 | } 157 | 158 | Connection 159 | conn_init() 160 | { 161 | Connection conn; 162 | conn = malloc(sizeof(struct Connection)); 163 | conn->read_watcher = (struct ev_io*) malloc(sizeof(struct ev_io)); 164 | conn->read_watcher->data = (void*)conn; 165 | conn->write_watcher = (struct ev_io*) malloc(sizeof(struct ev_io)); 166 | conn->write_watcher->data = (void*)conn; 167 | ev_init(conn->read_watcher, conn_read_callback); 168 | ev_init(conn->write_watcher, conn_write_callback); 169 | conn->in = buffer_init(BUFFER_SIZE); 170 | conn->out = buffer_init(BUFFER_SIZE); 171 | return conn; 172 | } 173 | 174 | Connection 175 | conn_start(int fd) 176 | { 177 | Node node; 178 | Connection conn; 179 | 180 | node = stack_pop(connection_pool); 181 | if (node == NULL) { 182 | puts("Connection pool miss. conn_init().\n"); 183 | conn = conn_init(); 184 | } else { 185 | conn = (Connection) node->value; 186 | } 187 | 188 | total_clients++; 189 | //puts("Succcessfully connected with client."); 190 | //printf("%d client(s) connected.\n", total_clients); 191 | 192 | conn->fd = fd; 193 | ev_io_set(conn->read_watcher, conn->fd, EV_READ); 194 | ev_io_set(conn->write_watcher, conn->fd, EV_WRITE); 195 | ev_io_start(global_loop, conn->read_watcher); 196 | 197 | return conn; 198 | } 199 | 200 | void 201 | conn_destroy(Connection conn) 202 | { 203 | buffer_destroy(conn->in); 204 | buffer_destroy(conn->out); 205 | free(conn); 206 | } 207 | 208 | void 209 | conn_close(Connection conn) 210 | { 211 | Node node; 212 | 213 | total_clients--; 214 | //printf("%d client(s) connected.\n", total_clients); 215 | 216 | ev_io_stop(global_loop, conn->read_watcher); 217 | ev_io_stop(global_loop, conn->write_watcher); 218 | shutdown(conn->fd, SHUT_RDWR); 219 | close(conn->fd); 220 | 221 | buffer_reset(conn->in); 222 | buffer_reset(conn->out); 223 | 224 | node = node_init(); 225 | node->value = (void *)conn; 226 | stack_push(connection_pool, node); 227 | } 228 | 229 | void 230 | conn_write(Connection conn, char *to_write, int to_write_len) 231 | { 232 | char *buffer_address = conn->out->data + conn->out->len; 233 | if (to_write_len > (conn->out->capacity - conn->out->len)) { 234 | perror("conn_write(): would overflow"); 235 | return; 236 | } 237 | memmove(buffer_address, to_write, to_write_len); 238 | conn->out->len += to_write_len; 239 | } 240 | 241 | Buffer 242 | buffer_init(int size) 243 | { 244 | Buffer buffer; 245 | buffer = malloc(sizeof(struct Buffer)); 246 | buffer->data = calloc(size, sizeof(char)); 247 | buffer->capacity = size; 248 | buffer->len = 0; 249 | buffer->loc = 0; 250 | return buffer; 251 | } 252 | 253 | void 254 | buffer_reset(Buffer buffer) 255 | { 256 | bzero(buffer->data, buffer->capacity); 257 | buffer->len = 0; 258 | buffer->loc = 0; 259 | } 260 | 261 | void 262 | buffer_destroy(Buffer buffer) 263 | { 264 | free(buffer->data); 265 | free(buffer); 266 | } 267 | 268 | /** 269 | * NETWORKING 270 | */ 271 | 272 | void 273 | setnonblock(int fd) 274 | { 275 | fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); 276 | } 277 | 278 | int 279 | bind_server_socket() 280 | { 281 | int sd; 282 | struct sockaddr_in addr; 283 | 284 | if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 285 | perror("socket error"); 286 | return -1; 287 | } 288 | int flags = 1; 289 | setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); 290 | bzero(&addr, sizeof(addr)); 291 | addr.sin_family = AF_INET; 292 | addr.sin_port = htons(port); 293 | addr.sin_addr.s_addr = INADDR_ANY; 294 | if (bind(sd, (struct sockaddr*) &addr, sizeof(addr)) != 0) { 295 | perror("bind"); 296 | } 297 | return sd; 298 | } 299 | 300 | void 301 | check_error(Connection conn) 302 | { 303 | if (errno == EAGAIN) return; 304 | if (errno == EINTR) return; 305 | if (errno == EWOULDBLOCK) return; 306 | 307 | perror("io error"); 308 | conn_close(conn); 309 | return; 310 | } 311 | 312 | /** 313 | * EVENT LOOP CALLBACKS 314 | */ 315 | 316 | void 317 | conn_read_callback(struct ev_loop *loop, struct ev_io *watcher, int revents) 318 | { 319 | Connection conn; 320 | ssize_t bytes_read; 321 | 322 | if (EV_ERROR & revents) { 323 | perror("got invalid event"); 324 | } 325 | 326 | conn = (Connection)watcher->data; 327 | bytes_read = recv(watcher->fd, conn->in->data, BUFFER_SIZE, 0); 328 | // error 329 | if (bytes_read < 0) { 330 | check_error(conn); 331 | return; 332 | } 333 | // connection closing 334 | if (bytes_read == 0) { 335 | conn_close(conn); 336 | return; 337 | } 338 | // read some stuff - right now we cheat and just assume it is a request :) 339 | ev_io_stop(loop, conn->read_watcher); 340 | conn_write(conn, body, strlen(body)); 341 | ev_io_start(loop, conn->write_watcher); 342 | } 343 | 344 | void 345 | conn_write_callback(struct ev_loop *loop, struct ev_io *watcher, int revents) 346 | { 347 | Connection conn; 348 | ssize_t bytes_written; 349 | char *buffer_location; 350 | int buffer_length; 351 | int new_loc; 352 | 353 | conn = (Connection)watcher->data; 354 | buffer_location = conn->out->data + conn->out->loc; 355 | buffer_length = conn->out->len - conn->out->loc; 356 | 357 | bytes_written = send(conn->fd, buffer_location, buffer_length, 0); 358 | if (bytes_written < 0) { 359 | check_error(conn); 360 | return; 361 | } 362 | new_loc = conn->out->loc + bytes_written; 363 | if (new_loc > buffer_length) { 364 | perror("conn_write_callback(): wrote > buffer_length?!"); 365 | return; 366 | } 367 | // if we haven't drained the buffer just move its loc so the next pass through the event loop can 368 | conn->out->loc = new_loc; 369 | // if we've drained our buffer, GTFO 370 | if (conn->out->loc >= conn->out->len) { 371 | //printf("Done writing. Closing connection %i.\n", total_clients); 372 | conn_close(conn); 373 | } 374 | } 375 | 376 | void 377 | accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) 378 | { 379 | int client_sd; 380 | struct sockaddr_in client_addr; 381 | socklen_t client_len = sizeof(client_addr); 382 | Connection conn; 383 | 384 | if (EV_ERROR & revents) { 385 | perror("got invalid event"); 386 | return; 387 | } 388 | 389 | while ((client_sd = accept(w->fd, (struct sockaddr *)&client_addr, &client_len)) > 0) { 390 | setnonblock(client_sd); 391 | conn = conn_start(client_sd); 392 | } 393 | 394 | if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) { 395 | perror("accept error"); 396 | return; 397 | } 398 | } 399 | 400 | /** 401 | * HELLOD 402 | */ 403 | 404 | void 405 | accept_connections() 406 | { 407 | int sd; 408 | ev_io accept_watcher; 409 | 410 | sd = bind_server_socket(); 411 | setnonblock(sd); 412 | listen(sd, 256); 413 | 414 | ev_io_init(&accept_watcher, accept_cb, sd, EV_READ); 415 | ev_io_start(global_loop, &accept_watcher); 416 | ev_loop(global_loop, 0); 417 | 418 | // We don't get here. 419 | close(sd); 420 | } 421 | 422 | void 423 | setup_event_loop() 424 | { 425 | global_loop = ev_default_loop(0); 426 | } 427 | 428 | void 429 | prep_content_buffers() 430 | { 431 | char *headers = calloc(strlen(HEADER_TEXT) + 16, sizeof(char)); 432 | sprintf(headers, HEADER_TEXT, strlen(BODY_TEXT)); 433 | body = calloc(strlen(HEADER_TEXT) + 16 + strlen(BODY_TEXT), sizeof(char)); 434 | memmove(body, headers, strlen(headers)); 435 | memmove(body+strlen(headers), BODY_TEXT, strlen(BODY_TEXT)); 436 | } 437 | 438 | void 439 | setup_connection_pool() 440 | { 441 | int i; 442 | Node node; 443 | Connection conn; 444 | 445 | connection_pool = stack_init(); 446 | for (i = 0; i < CONNECTION_POOL_SIZE; i++) { 447 | node = node_init(); 448 | conn = conn_init(); 449 | node->value = (void *) conn; 450 | stack_push(connection_pool, node); 451 | } 452 | } 453 | 454 | int 455 | main(void) 456 | { 457 | puts("started."); 458 | prep_content_buffers(); 459 | setup_connection_pool(); 460 | setup_event_loop(); 461 | accept_connections(); 462 | exit(0); 463 | } 464 | 465 | -------------------------------------------------------------------------------- /c/tryit.sh: -------------------------------------------------------------------------------- 1 | echo "GET / HTTP/1.1\r\n" | nc localhost 8000 2 | -------------------------------------------------------------------------------- /clj-aleph/project.clj: -------------------------------------------------------------------------------- 1 | (defproject hellod "0.0.1" 2 | :description "High performance hello world server for Clojure" 3 | :url "http://blog.carbonfive.com" 4 | :tasks [] 5 | :main hellod.core 6 | :dependencies [[clojure "1.2.1"] 7 | [aleph "0.2.0-beta2"]]) 8 | 9 | -------------------------------------------------------------------------------- /clj-aleph/src/hellod/core.clj: -------------------------------------------------------------------------------- 1 | (ns hellod.core) 2 | (use 'lamina.core 'aleph.http) 3 | 4 | (defn hello-world [channel request] 5 | (enqueue channel 6 | {:status 200 7 | :headers {"content-type" "text/html"} 8 | :body "

Hello World

"})) 9 | 10 | (defn -main [& args] 11 | (start-http-server hello-world {:port 8083})) 12 | 13 | -------------------------------------------------------------------------------- /clj-jetty/project.clj: -------------------------------------------------------------------------------- 1 | (defproject hellod "0.0.1" 2 | :description "High performance hello world server for Clojure" 3 | :url "http://blog.carbonfive.com" 4 | :tasks [] 5 | :main hellod.core 6 | :dependencies [[clojure "1.2.1"] 7 | [ring "1.0.0-beta2"]]) 8 | 9 | -------------------------------------------------------------------------------- /clj-jetty/src/hellod/core.clj: -------------------------------------------------------------------------------- 1 | (ns hellod.core) 2 | (use 'ring.adapter.jetty) 3 | 4 | (defn hello [req] 5 | {:status 200 6 | :headers {"Content-Type" "text/html"} 7 | :body "

Hello World

"}) 8 | 9 | (defn -main [& args] 10 | (run-jetty hello {:port 8084})) 11 | 12 | -------------------------------------------------------------------------------- /erlang/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [{i, "deps/proper/include"}, warnings_as_errors, debug_info, export_all]}. 2 | {deps, 3 | [ 4 | {misultin, "", {git, "https://github.com/ostinelli/misultin.git", {branch, "master"}}} 5 | ]}. -------------------------------------------------------------------------------- /erlang/shell: -------------------------------------------------------------------------------- 1 | rebar get-deps compile 2 | erl -pa `pwd`/ebin `pwd`/deps/*/ebin -sname test -boot start_sasl +P 134217727 -------------------------------------------------------------------------------- /erlang/src/hello.app.src: -------------------------------------------------------------------------------- 1 | {application, hello, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { hello_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /erlang/src/hello_app.erl: -------------------------------------------------------------------------------- 1 | -module(hello_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | hello_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /erlang/src/hello_server.erl: -------------------------------------------------------------------------------- 1 | -module(hello_server). 2 | -behaviour(gen_server). 3 | 4 | %% API 5 | -export([start_link/0]). 6 | 7 | %% gen_server callbacks 8 | -export([ 9 | init/1, 10 | handle_call/3, handle_cast/2, 11 | handle_info/2, 12 | terminate/2, code_change/3]). 13 | 14 | -record(state, {}). 15 | -define(SERVER, ?MODULE). 16 | -define(PORT, 8084). 17 | 18 | 19 | start_link() -> 20 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 21 | 22 | init([]) -> 23 | misultin:start_link([ 24 | {port, ?PORT}, {loop, fun(Req) -> handle_http(Req, ?PORT) end} 25 | ]), 26 | {ok, #state{}}. 27 | 28 | terminate(_Reason, _State) -> 29 | misultin:stop(), 30 | ok. 31 | 32 | code_change(_OldVsn, State, _Extra) -> 33 | {ok, State}. 34 | 35 | handle_info(_Info, State) -> 36 | {noreply, State}. 37 | 38 | handle_call(_Request, _From, State) -> 39 | {noreply, State}. 40 | 41 | handle_cast(_Request, State) -> 42 | {noreply, State}. 43 | 44 | % callback on request received 45 | handle_http(Req, Port) -> 46 | Req:ok([{"Content-Type", "text/html"}], 47 | ["

Hello World

\r\n"]). 48 | -------------------------------------------------------------------------------- /erlang/src/hello_sup.erl: -------------------------------------------------------------------------------- 1 | 2 | -module(hello_sup). 3 | 4 | -behaviour(supervisor). 5 | 6 | %% API 7 | -export([start_link/0]). 8 | 9 | %% Supervisor callbacks 10 | -export([init/1]). 11 | 12 | %% Helper macro for declaring children of supervisor 13 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 14 | 15 | %% =================================================================== 16 | %% API functions 17 | %% =================================================================== 18 | 19 | start_link() -> 20 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 21 | 22 | %% =================================================================== 23 | %% Supervisor callbacks 24 | %% =================================================================== 25 | 26 | init([]) -> 27 | RestartStrategy = one_for_one, 28 | MaxRestarts = 1000, 29 | MaxSecondsBetweenRestarts = 3600, 30 | 31 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, 32 | 33 | Restart = permanent, 34 | Shutdown = 2000, 35 | Type = worker, 36 | 37 | HelloServer = {hello_server, {hello_server, start_link, []}, 38 | Restart, Shutdown, Type, [hello_server]}, 39 | 40 | {ok, { SupFlags, [HelloServer] } }. 41 | 42 | -------------------------------------------------------------------------------- /go/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=hellod 4 | GOFILES=server.go 5 | 6 | include $(GOROOT)/src/Make.cmd 7 | -------------------------------------------------------------------------------- /go/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "http" 6 | "strconv" 7 | ) 8 | 9 | var html []byte = []byte("

Hello World

\r\n") 10 | var html_length int = len(html); 11 | 12 | func main() { 13 | http.HandleFunc("/", HelloServer) 14 | err := http.ListenAndServe(":8082", nil) 15 | if err != nil { 16 | fmt.Println("ListenAndServe: ", err.String()) 17 | } 18 | } 19 | 20 | func HelloServer(w http.ResponseWriter, req *http.Request) { 21 | // Emit JSON stringified request headers, only we can't. 22 | // Seems to be a bit of of a work-in progress; see http://golang.org/src/pkg/http/request.go 23 | w.Header().Set("Content-Type", "text/html") 24 | w.Header().Set("Content-Length", strconv.Itoa(html_length)) 25 | w.Write(html); 26 | } 27 | -------------------------------------------------------------------------------- /hellod: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH << File.dirname(__FILE__) 4 | require 'optparse' 5 | require 'hellod' 6 | 7 | config = { :number => 10000, :concurrent => 50 } 8 | operations = %w(start stop test) 9 | 10 | option_parser = OptionParser.new do |opts| 11 | opts.banner = "HelloD - high performance Hello World!\nUsage: #{__FILE__} [command] [options]" 12 | 13 | opts.on("-n", "--requests N", Integer, "Number of requests") { |n| config[:requests] = n } 14 | opts.on("-c", "--concurrent C", Integer, "Concurrent requests") { |c| config[:concurrent] = c } 15 | 16 | opts.separator <<-EOS 17 | 18 | Supported commands: 19 | 20 | start Start server(s) in the background 21 | stop Stop server(s) 22 | test -n -c Run performance test(s) 23 | 24 | is one of: node, ruby, go, clj-aleph, clj-jetty, c, all 25 | 26 | Example: 27 | 28 | hellod start all 29 | hellod test all -n 10000 -c 50 30 | 31 | EOS 32 | end 33 | option_parser.parse! 34 | 35 | 36 | op = ARGV.shift 37 | if operations.include?(op) 38 | begin 39 | cli = Hellod.new config 40 | cli.send(op.to_sym, ARGV) 41 | rescue ArgumentError => ex 42 | puts ex.message 43 | rescue Exception => e 44 | puts "Uh oh, I didn't expect this:" 45 | puts e.message 46 | puts e.backtrace.join("\n") 47 | end 48 | else 49 | puts option_parser.help 50 | end 51 | -------------------------------------------------------------------------------- /hellod.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | class Hellod 4 | 5 | PORTS = { 'ruby' => 8080, 6 | 'node' => 8081, 7 | 'go' => 8082, 8 | 'clj-aleph' => '8083', 9 | 'clj-jetty' => '8084', 10 | 'c' => '8085', 11 | 'java-nio' => '8086', 12 | 'java-netty' => '8087'} 13 | CMDS = { 'ruby' => 'ruby ruby/server.rb', 14 | 'node' => 'node node/server.js', 15 | 'go' => 'GOMAXPROCS=6; ./go/hellod', 16 | 'clj-aleph' => 'cd clj-aleph; lein run', 17 | 'clj-jetty' => 'cd clj-jetty; lein run', 18 | 'c' => './c/hellod', 19 | 'java-nio' => 'cd java-nio; ./hellod', 20 | 'java-netty' => 'cd java-netty; ./hellod' } 21 | 22 | def initialize(opts = {}) 23 | opts = { :requests => 10000, :concurrent => 50 }.merge(opts) 24 | @n = opts[:requests] 25 | @c = opts[:concurrent] 26 | @pids = {} 27 | end 28 | 29 | def start(flavor) 30 | read 31 | ports(flavor[0]).each do |f, p| 32 | print "Starting #{f} server on port #{p}..." 33 | if @pids[f] 34 | puts " already running with pid #{@pids[f]}" 35 | else 36 | begin 37 | pipe = IO.popen CMDS[f], 'r' 38 | puts " pid #{pipe.pid}" 39 | @pids[f] = pipe.pid 40 | rescue 41 | puts " failed" 42 | end 43 | end 44 | end 45 | write 46 | end 47 | 48 | def stop(flavor) 49 | read 50 | ports(flavor[0]).each do |f, p| 51 | pid = @pids[f] 52 | print "Stopping #{f} server with pid #{pid}..." 53 | if pid 54 | begin 55 | Process.kill 'HUP', pid 56 | Process.kill 'HUP', (pid + 2) if f =~ /^clj/ # crazy ass hack -mike 57 | puts " done" 58 | rescue 59 | puts " not running" 60 | end 61 | @pids.delete f 62 | else 63 | puts " not running" 64 | end 65 | end 66 | write 67 | end 68 | 69 | def test(flavor) 70 | read 71 | ports(flavor[0]).each do |f, p| 72 | puts "\tTesting #{f} with -n #{@n} -c #{@c}" 73 | if @pids[f] 74 | test_run p, true 75 | 5.times { test_run p } 76 | else 77 | puts "Not started" 78 | end 79 | puts 80 | end 81 | end 82 | 83 | private 84 | 85 | def ports(flavor) 86 | if flavor == 'all' 87 | PORTS 88 | else 89 | { flavor => PORTS[flavor] } 90 | end 91 | end 92 | 93 | def config_file 94 | "#{ENV['HOME']}/.hellod" 95 | end 96 | 97 | def write 98 | File.open config_file, 'w' do |f| 99 | f.write @pids.to_yaml 100 | end 101 | end 102 | 103 | def read 104 | @pids = YAML.load( IO.read(config_file) ) if File.exists? config_file 105 | @pids = {} unless @pids 106 | end 107 | 108 | def test_run(port, header = false) 109 | h = header ? 0 : 1 110 | failures = 0 111 | values = [] 112 | IO.popen "ab -r -n #{@n} -c #{@c} http://localhost:#{port}/ 2>&1", 'r' do |io| 113 | io.readlines.each do |line| 114 | failures = line.split(":")[1].strip.to_i / 2 if line =~ /^Failed requests:/ 115 | values << line.split[h] if line =~ /\d+%/ 116 | end 117 | end 118 | printf "\t" 119 | values.each do |val| 120 | print sprintf("%6s", val) 121 | end 122 | printf " (#{failures} failures)" if failures > 0 && ! header 123 | puts 124 | end 125 | 126 | end 127 | -------------------------------------------------------------------------------- /java-netty/compile: -------------------------------------------------------------------------------- 1 | rm -fr out 2 | mkdir out 3 | javac -cp lib/netty-3.2.6.Final.jar -d out src/HelloHttpRequestHandler.java src/HttpRequestHandler.java src/HttpServerPipelineFactory.java src/HelloDServer.java 4 | -------------------------------------------------------------------------------- /java-netty/hellod: -------------------------------------------------------------------------------- 1 | java -classpath "out/:lib/netty-3.2.6.Final.jar" HelloDServer 2 | -------------------------------------------------------------------------------- /java-netty/lib/netty-3.2.6.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carbonfive/hellod/01423b6118177dcc7a44388543775369780da6e7/java-netty/lib/netty-3.2.6.Final.jar -------------------------------------------------------------------------------- /java-netty/src/HelloDServer.java: -------------------------------------------------------------------------------- 1 | import java.net.InetSocketAddress; 2 | import java.util.concurrent.Executors; 3 | 4 | import org.jboss.netty.bootstrap.ServerBootstrap; 5 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; 6 | 7 | public class HelloDServer { 8 | public static void main(String[] args) { 9 | // Configure the server. 10 | ServerBootstrap bootstrap = new ServerBootstrap( 11 | new NioServerSocketChannelFactory( 12 | Executors.newCachedThreadPool(), 13 | Executors.newCachedThreadPool())); 14 | 15 | 16 | bootstrap.setOption("tcpNoDelay", true); 17 | bootstrap.setOption("reuseAddress", true); 18 | bootstrap.setOption("child.reuseAddress", true); 19 | 20 | // Set up the event pipeline factory. 21 | bootstrap.setPipelineFactory(new HttpServerPipelineFactory()); 22 | 23 | // Bind and start to accept incoming connections. 24 | bootstrap.bind(new InetSocketAddress(8087)); 25 | } 26 | } -------------------------------------------------------------------------------- /java-netty/src/HelloHttpRequestHandler.java: -------------------------------------------------------------------------------- 1 | import static org.jboss.netty.handler.codec.http.HttpHeaders.*; 2 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; 3 | import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*; 4 | import static org.jboss.netty.handler.codec.http.HttpVersion.*; 5 | 6 | import org.jboss.netty.buffer.ChannelBuffer; 7 | import org.jboss.netty.buffer.ChannelBuffers; 8 | import org.jboss.netty.channel.*; 9 | import org.jboss.netty.handler.codec.http.*; 10 | import org.jboss.netty.util.CharsetUtil; 11 | 12 | public class HelloHttpRequestHandler extends org.jboss.netty.channel.SimpleChannelUpstreamHandler { 13 | 14 | private static final String BODY_TEXT = "

Hello World

\r\n"; 15 | 16 | private HttpRequest request; 17 | /** Buffer that stores the response content */ 18 | //private final StringBuilder buf = new StringBuilder(); 19 | 20 | @Override 21 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { 22 | this.request = (HttpRequest) e.getMessage(); 23 | 24 | writeResponse(e); 25 | } 26 | 27 | private void writeResponse(MessageEvent e) { 28 | // Decide whether to close the connection or not. 29 | boolean keepAlive = isKeepAlive(request); 30 | 31 | // Build the response object. 32 | HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); 33 | //response.setContent(ChannelBuffers.copiedBuffer(buf.toString(), CharsetUtil.UTF_8)); 34 | response.setContent(ChannelBuffers.copiedBuffer(BODY_TEXT, CharsetUtil.UTF_8)); 35 | response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8"); 36 | if (!keepAlive) { 37 | response.setHeader(CONNECTION, "close"); 38 | } 39 | 40 | if (keepAlive) { 41 | // Add 'Content-Length' header only for a keep-alive connection. 42 | response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes()); 43 | } 44 | 45 | // Write the response. 46 | ChannelFuture future = e.getChannel().write(response); 47 | 48 | // Close the non-keep-alive connection after the write operation is done. 49 | if (!keepAlive) { 50 | future.addListener(ChannelFutureListener.CLOSE); 51 | } 52 | } 53 | 54 | @Override 55 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) 56 | throws Exception { 57 | e.getCause().printStackTrace(); 58 | e.getChannel().close(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /java-netty/src/HttpRequestHandler.java: -------------------------------------------------------------------------------- 1 | import static org.jboss.netty.handler.codec.http.HttpHeaders.*; 2 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; 3 | import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*; 4 | import static org.jboss.netty.handler.codec.http.HttpVersion.*; 5 | 6 | import org.jboss.netty.buffer.ChannelBuffer; 7 | import org.jboss.netty.buffer.ChannelBuffers; 8 | import org.jboss.netty.channel.*; 9 | import org.jboss.netty.handler.codec.http.*; 10 | import org.jboss.netty.util.CharsetUtil; 11 | 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class HttpRequestHandler extends org.jboss.netty.channel.SimpleChannelUpstreamHandler { 17 | 18 | private HttpRequest request; 19 | private boolean readingChunks; 20 | /** Buffer that stores the response content */ 21 | private final StringBuilder buf = new StringBuilder(); 22 | 23 | @Override 24 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { 25 | if (!readingChunks) { 26 | HttpRequest request = this.request = (HttpRequest) e.getMessage(); 27 | 28 | if (is100ContinueExpected(request)) { 29 | send100Continue(e); 30 | } 31 | 32 | buf.setLength(0); 33 | buf.append("WELCOME TO THE WILD WILD WEB SERVER\r\n"); 34 | buf.append("===================================\r\n"); 35 | 36 | buf.append("VERSION: " + request.getProtocolVersion() + "\r\n"); 37 | buf.append("HOSTNAME: " + getHost(request, "unknown") + "\r\n"); 38 | buf.append("REQUEST_URI: " + request.getUri() + "\r\n\r\n"); 39 | 40 | for (Map.Entry h: request.getHeaders()) { 41 | buf.append("HEADER: " + h.getKey() + " = " + h.getValue() + "\r\n"); 42 | } 43 | buf.append("\r\n"); 44 | 45 | QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri()); 46 | Map> params = queryStringDecoder.getParameters(); 47 | if (!params.isEmpty()) { 48 | for (Map.Entry> p: params.entrySet()) { 49 | String key = p.getKey(); 50 | List vals = p.getValue(); 51 | for (String val : vals) { 52 | buf.append("PARAM: " + key + " = " + val + "\r\n"); 53 | } 54 | } 55 | buf.append("\r\n"); 56 | } 57 | 58 | if (request.isChunked()) { 59 | readingChunks = true; 60 | } else { 61 | ChannelBuffer content = request.getContent(); 62 | if (content.readable()) { 63 | buf.append("CONTENT: " + content.toString(CharsetUtil.UTF_8) + "\r\n"); 64 | } 65 | writeResponse(e); 66 | } 67 | } else { 68 | HttpChunk chunk = (HttpChunk) e.getMessage(); 69 | if (chunk.isLast()) { 70 | readingChunks = false; 71 | buf.append("END OF CONTENT\r\n"); 72 | 73 | HttpChunkTrailer trailer = (HttpChunkTrailer) chunk; 74 | if (!trailer.getHeaderNames().isEmpty()) { 75 | buf.append("\r\n"); 76 | for (String name: trailer.getHeaderNames()) { 77 | for (String value: trailer.getHeaders(name)) { 78 | buf.append("TRAILING HEADER: " + name + " = " + value + "\r\n"); 79 | } 80 | } 81 | buf.append("\r\n"); 82 | } 83 | 84 | writeResponse(e); 85 | } else { 86 | buf.append("CHUNK: " + chunk.getContent().toString(CharsetUtil.UTF_8) + "\r\n"); 87 | } 88 | } 89 | } 90 | 91 | private void writeResponse(MessageEvent e) { 92 | // Decide whether to close the connection or not. 93 | boolean keepAlive = isKeepAlive(request); 94 | 95 | // Build the response object. 96 | HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); 97 | response.setContent(ChannelBuffers.copiedBuffer(buf.toString(), CharsetUtil.UTF_8)); 98 | response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8"); 99 | 100 | if (keepAlive) { 101 | // Add 'Content-Length' header only for a keep-alive connection. 102 | response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes()); 103 | } 104 | 105 | // Encode the cookie. 106 | String cookieString = request.getHeader(COOKIE); 107 | if (cookieString != null) { 108 | CookieDecoder cookieDecoder = new CookieDecoder(); 109 | Set cookies = cookieDecoder.decode(cookieString); 110 | if(!cookies.isEmpty()) { 111 | // Reset the cookies if necessary. 112 | CookieEncoder cookieEncoder = new CookieEncoder(true); 113 | for (Cookie cookie : cookies) { 114 | cookieEncoder.addCookie(cookie); 115 | } 116 | response.addHeader(SET_COOKIE, cookieEncoder.encode()); 117 | } 118 | } 119 | 120 | // Write the response. 121 | ChannelFuture future = e.getChannel().write(response); 122 | 123 | // Close the non-keep-alive connection after the write operation is done. 124 | if (!keepAlive) { 125 | future.addListener(ChannelFutureListener.CLOSE); 126 | } 127 | } 128 | 129 | private void send100Continue(MessageEvent e) { 130 | HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE); 131 | e.getChannel().write(response); 132 | } 133 | 134 | @Override 135 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) 136 | throws Exception { 137 | e.getCause().printStackTrace(); 138 | e.getChannel().close(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /java-netty/src/HttpServerPipelineFactory.java: -------------------------------------------------------------------------------- 1 | import org.jboss.netty.channel.ChannelPipeline; 2 | import org.jboss.netty.channel.ChannelPipelineFactory; 3 | import org.jboss.netty.handler.codec.http.HttpChunkAggregator; 4 | import org.jboss.netty.handler.codec.http.HttpContentCompressor; 5 | import org.jboss.netty.handler.codec.http.HttpRequestDecoder; 6 | import org.jboss.netty.handler.codec.http.HttpResponseEncoder; 7 | 8 | import static org.jboss.netty.channel.Channels.*; 9 | 10 | 11 | public class HttpServerPipelineFactory implements ChannelPipelineFactory { 12 | public ChannelPipeline getPipeline() throws Exception { 13 | // Create a default pipeline implementation. 14 | ChannelPipeline pipeline = pipeline(); 15 | 16 | // Uncomment the following line if you want HTTPS 17 | //SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); 18 | //engine.setUseClientMode(false); 19 | //pipeline.addLast("ssl", new SslHandler(engine)); 20 | 21 | pipeline.addLast("decoder", new HttpRequestDecoder()); 22 | // Uncomment the following line if you don't want to handle HttpChunks. 23 | pipeline.addLast("aggregator", new HttpChunkAggregator(1048576)); 24 | pipeline.addLast("encoder", new HttpResponseEncoder()); 25 | // Remove the following line if you don't want automatic content compression. 26 | pipeline.addLast("deflater", new HttpContentCompressor()); 27 | pipeline.addLast("handler", new HelloHttpRequestHandler()); 28 | return pipeline; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java-nio/compile: -------------------------------------------------------------------------------- 1 | rm -fr out 2 | mkdir out 3 | javac src/HelloDServer.java -d out 4 | -------------------------------------------------------------------------------- /java-nio/hellod: -------------------------------------------------------------------------------- 1 | java -cp out HelloDServer 2 | -------------------------------------------------------------------------------- /java-nio/src/HelloDServer.java: -------------------------------------------------------------------------------- 1 | import java.io.PrintWriter; 2 | import java.net.InetSocketAddress; 3 | import java.net.ServerSocket; 4 | import java.net.Socket; 5 | import java.nio.channels.SelectionKey; 6 | import java.nio.channels.Selector; 7 | import java.nio.channels.ServerSocketChannel; 8 | import java.util.Iterator; 9 | import java.util.Set; 10 | 11 | public class HelloDServer { 12 | private static int port = 8086; 13 | private static final String BODY_TEXT = "

Hello World

\r\n"; 14 | private static final String HEADER_TEXT = String.format("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\nContent-Length: %1$d\r\n\r\n", BODY_TEXT.getBytes().length); 15 | 16 | 17 | public static void main(String args[]) throws Exception { 18 | Selector selector = Selector.open(); 19 | 20 | ServerSocketChannel channel = ServerSocketChannel.open(); 21 | channel.configureBlocking(false); 22 | 23 | InetSocketAddress isa = new InetSocketAddress(port); 24 | channel.socket().bind(isa); 25 | 26 | // Register interest in connections 27 | channel.register(selector, SelectionKey.OP_ACCEPT); 28 | 29 | // Wait for something to happen 30 | while (selector.select() > 0) { 31 | //Get set of ready objects 32 | Set readyKeys = selector.selectedKeys(); 33 | Iterator readyIterator = readyKeys.iterator(); 34 | 35 | // Walk through set 36 | while(readyIterator.hasNext()) { 37 | 38 | // Get key from set 39 | SelectionKey key = readyIterator.next(); 40 | 41 | // Remove current entry 42 | readyIterator.remove(); 43 | 44 | if (key.isAcceptable()) { 45 | //Get channel 46 | ServerSocketChannel keyChannel = (ServerSocketChannel) key.channel(); 47 | 48 | // Get server socket 49 | ServerSocket serverSocket = keyChannel.socket(); 50 | serverSocket.setReuseAddress(true); 51 | 52 | // Accept request 53 | Socket socket = serverSocket.accept(); 54 | 55 | // Return canned message 56 | PrintWriter out = new PrintWriter(socket.getOutputStream(), true); 57 | out.print(HEADER_TEXT); 58 | out.print(BODY_TEXT); 59 | out.close(); 60 | } else { 61 | System.err.println("Oops"); 62 | } 63 | } 64 | } 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /node/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | http.createServer(function (req, res) { 3 | var content = '

Hello World

\r\n'; 4 | res.writeHead(200, {'Connection' : 'close', 'Content-Type': 'text/html', 'Content-Length' : content.length.toString() }); 5 | res.end(content); 6 | }).listen(8081); 7 | -------------------------------------------------------------------------------- /results.md: -------------------------------------------------------------------------------- 1 | HelloD 2 | ============ 3 | 4 | Performance results testing with ab. All tests run on a EC2 High CPU Extra Large, 7GB RAM, 20 EC2 Compute Units (8 virtual cores). 5 | 6 | Before each test, I start the server and I give it 5 seconds to start up. I think "warm it up" by running 10 runs of ab. Then I run the following tests with ab, and I collect the results. 7 | 8 | ab -n 10000 -c 50 http://localhost:808x/ 9 | ab -n 50000 -c 200 http://localhost:808x/ 10 | 11 | 12 | Ruby 1.9.2 13 | ------------- 14 | ruby -v 15 | ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux] 16 | 17 | Testing ruby with -n 10000 -c 50 18 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 19 | 4 4 5 7 7 8 9 11 14 20 | 4 4 4 4 7 7 8 8 8 21 | 4 4 4 4 7 7 8 8 8 22 | 4 4 4 4 8 8 9 9 10 23 | 4 4 4 4 7 7 8 8 8 24 | 25 | Testing ruby with -n 50000 -c 200 26 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 27 | 11 11 11 11 12 12 13 15 3015 28 | 10 11 11 11 12 12 13 14 3011 29 | 10 11 11 11 12 12 13 13 3756 30 | 10 11 11 11 11 12 12 13 3295 31 | 11 11 11 12 12 12 13 14 3012 32 | 33 | 34 | Ruby 1.9.3 35 | ------------- 36 | ruby -v 37 | ruby 1.9.3dev (2011-09-23 revision 33323) [x86_64-linux] 38 | 39 | Testing ruby with -n 10000 -c 50 40 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 41 | 4 4 4 4 4 4 4 4 5 42 | 4 4 4 4 4 4 4 4 5 43 | 4 4 4 4 4 4 4 5 5 44 | 4 4 4 4 4 4 4 4 5 45 | 4 4 4 4 4 4 4 5 6 46 | 47 | Testing ruby with -n 50000 -c 200 48 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 49 | 14 14 14 15 15 15 15 15 3014 50 | 14 15 15 15 15 15 15 15 18 51 | 14 14 14 14 15 15 15 15 3014 52 | 14 14 15 15 15 15 15 15 18 53 | 14 15 15 15 15 15 15 15 18 54 | 55 | 56 | JRuby 1.6.4 - Sun JDK 57 | ------------- 58 | ruby -v 59 | jruby 1.6.4 (ruby-1.8.7-p330) (2011-08-23 17ea768) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_26) [linux-amd64-java] 60 | 61 | java -version 62 | java version "1.6.0_26" 63 | Java(TM) SE Runtime Environment (build 1.6.0_26-b03) 64 | Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode) 65 | 66 | Testing ruby with -n 10000 -c 50 67 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 68 | 6 6 6 6 6 6 9 9 42 69 | 6 6 6 6 6 6 7 13 42 70 | 6 6 6 6 6 6 8 14 44 71 | 6 6 6 6 6 6 6 7 7 72 | 6 6 6 6 6 6 7 14 56 73 | 74 | Testing ruby with -n 50000 -c 200 75 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 76 | 10 10 10 10 11 11 12 20 3721 77 | 10 10 10 10 11 11 12 36 6595 (9 failures) 78 | 10 10 10 10 11 11 12 28 6598 (45 failures) 79 | 10 10 10 10 11 11 11 21 6598 (49 failures) 80 | 10 10 10 10 11 11 11 39 6596 (54 failures) 81 | 82 | 83 | JRuby 1.6.4 - OpenJDK 84 | ------------- 85 | ruby -v 86 | jruby 1.6.4 (ruby-1.8.7-p330) (2011-08-23 17ea768) (OpenJDK 64-Bit Server VM 1.6.0_20) [linux-amd64-java] 87 | 88 | java -version 89 | java version "1.6.0_20" 90 | OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2) 91 | OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode) 92 | 93 | Testing ruby with -n 10000 -c 50 94 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 95 | 6 6 6 6 6 6 6 7 54 96 | 6 6 6 6 6 6 7 14 49 97 | 6 6 6 6 6 6 6 6 7 98 | 6 6 6 6 6 6 6 6 37 99 | 6 6 6 6 6 6 6 7 52 100 | 101 | Testing ruby with -n 50000 -c 200 102 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 103 | 9 9 10 10 10 10 13 27 6225 104 | 9 9 10 10 10 10 10 19 6595 105 | 9 9 9 10 10 10 20 22 6598 (49 failures) 106 | 9 9 10 10 10 10 11 22 4689 107 | 9 9 10 10 10 10 11 21 4688 108 | 109 | 110 | Go 111 | ------------- 112 | 6g -V 113 | 6g version release.r60.1 9497 114 | 115 | Testing go with -n 10000 -c 50 116 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 117 | 12 12 12 12 14 16 17 17 18 118 | 12 12 12 12 13 14 17 17 18 119 | 12 12 12 12 12 12 15 16 17 120 | 11 12 12 12 12 12 16 17 18 121 | 11 12 12 12 12 12 12 12 13 122 | 123 | Testing go with -n 50000 -c 200 124 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 125 | 30 31 31 31 33 38 44 45 11682 126 | 29 30 30 31 31 32 40 43 11688 127 | 29 30 31 31 33 41 44 45 11706 128 | 30 31 31 31 35 42 44 45 11680 129 | 29 30 31 31 33 41 44 45 11684 130 | 131 | 132 | Node.js 133 | ------------- 134 | node -v 135 | v0.4.12 136 | 137 | Testing node with -n 10000 -c 50 138 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 139 | 8 10 11 12 13 14 20 22 26 140 | 8 10 11 12 13 14 20 22 27 141 | 8 11 12 12 14 15 23 26 30 142 | 8 10 12 12 14 14 21 24 30 143 | 8 10 12 12 14 15 21 24 30 144 | 145 | Testing node with -n 50000 -c 200 146 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 147 | 23 28 31 33 37 42 49 53 3270 148 | 23 28 31 33 36 40 48 51 3037 149 | 23 28 31 33 37 42 49 52 3050 150 | 23 29 32 34 37 43 50 54 9021 151 | 23 28 31 33 37 42 49 52 3038 152 | 153 | 154 | C / libev 155 | ------------- 156 | 157 | 158 | Testing c with -n 10000 -c 50 159 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 160 | 4 4 4 4 4 4 5 5 5 161 | 4 4 4 4 4 4 4 5 5 162 | 4 4 4 4 4 5 5 5 5 163 | 4 4 4 4 4 5 5 5 5 164 | 4 4 4 4 4 5 5 5 5 165 | 166 | Testing c with -n 50000 -c 200 167 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 168 | 16 17 17 17 18 18 19 19 21 169 | 16 17 17 17 17 18 18 19 21 170 | 16 16 17 17 18 18 18 19 22 171 | 17 17 17 17 18 18 19 19 21 172 | 17 17 17 17 18 18 19 19 20 173 | 174 | 175 | Clojure / Aleph - Sun JDK 176 | ------------- 177 | lein version 178 | Leiningen 1.6.1.1 on Java 1.6.0_26 Java HotSpot(TM) 64-Bit Server VM 179 | 180 | java -version 181 | java version "1.6.0_26" 182 | Java(TM) SE Runtime Environment (build 1.6.0_26-b03) 183 | Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode) 184 | 185 | Testing clj-aleph with -n 10000 -c 50 186 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 187 | 9 10 10 10 11 12 14 39 77 188 | 9 10 10 11 12 15 15 16 47 189 | 9 10 10 11 12 12 13 39 50 190 | 9 10 10 11 12 12 14 43 70 191 | 9 10 10 10 10 11 11 11 46 192 | 193 | Testing clj-aleph with -n 50000 -c 200 194 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 195 | 9 9 10 10 12 12 14 45 7213 196 | 9 9 10 10 12 12 14 42 7805 (99 failures) 197 | 9 10 11 12 13 14 16 45 7207 (69 failures) 198 | 10 10 11 12 12 13 19 52 7504 (46 failures) 199 | 9 10 11 11 12 14 16 53 11076 (46 failures) 200 | 201 | 202 | Clojure / Aleph - OpenJDK 203 | ------------- 204 | lein version 205 | Leiningen 1.6.1.1 on Java 1.6.0_20 OpenJDK 64-Bit Server VM 206 | 207 | java -version 208 | java version "1.6.0_20" 209 | OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2) 210 | OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode) 211 | 212 | Testing clj-aleph with -n 10000 -c 50 213 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 214 | 10 11 11 12 12 12 13 42 50 215 | 9 10 10 10 11 11 13 14 48 216 | 9 10 11 11 12 13 14 41 71 217 | 9 9 10 10 12 12 13 44 49 218 | 8 10 11 11 12 12 13 47 67 219 | 220 | Testing clj-aleph with -n 50000 -c 200 221 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 222 | 9 9 10 10 11 12 19 47 7767 (76 failures) 223 | 9 10 10 11 11 12 14 44 11677 (40 failures) 224 | 9 10 10 11 12 12 14 47 7209 (67 failures) 225 | 9 10 10 11 12 13 16 47 9837 (51 failures) 226 | 10 10 11 11 12 12 26 53 8608 (67 failures) 227 | 228 | 229 | Clojure / Ring - Sun JDK 230 | ------------- 231 | lein version 232 | Leiningen 1.6.1.1 on Java 1.6.0_26 Java HotSpot(TM) 64-Bit Server VM 233 | 234 | java -version 235 | java version "1.6.0_26" 236 | Java(TM) SE Runtime Environment (build 1.6.0_26-b03) 237 | Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode) 238 | 239 | Testing clj-jetty with -n 10000 -c 50 240 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 241 | 14 14 14 14 14 14 15 17 25 242 | 14 14 14 14 14 14 15 15 36 243 | 14 14 14 14 14 14 15 15 28 244 | 14 14 14 14 14 15 15 17 29 245 | 14 14 14 14 14 14 15 15 35 246 | 247 | Testing clj-jetty with -n 50000 -c 200 248 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 249 | 27 30 32 41 59 61 62 64 3051 250 | 39 53 56 57 59 61 63 63 3058 251 | 45 56 58 58 59 60 62 65 3063 252 | 34 40 43 47 52 59 62 63 3053 253 | 35 52 53 55 60 61 62 64 3063 254 | 255 | 256 | Clojure / Ring - OpenJDK 257 | ------------- 258 | lein version 259 | Leiningen 1.6.1.1 on Java 1.6.0_20 OpenJDK 64-Bit Server VM 260 | 261 | java -version 262 | java version "1.6.0_20" 263 | OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2) 264 | OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode) 265 | 266 | Testing clj-jetty with -n 10000 -c 50 267 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 268 | 14 14 14 14 14 14 15 15 39 269 | 13 14 14 14 14 14 15 15 35 270 | 14 14 14 14 14 15 15 16 29 271 | 14 14 14 14 14 14 15 15 34 272 | 14 14 14 14 14 14 15 15 34 273 | 274 | Testing clj-jetty with -n 50000 -c 200 275 | 50% 66% 75% 80% 90% 95% 98% 99% 100% 276 | 41 49 51 52 60 62 63 63 3051 277 | 41 46 49 49 51 58 60 62 3054 278 | 35 40 46 57 60 62 63 63 3058 279 | 26 35 54 57 60 61 62 66 3059 280 | 34 39 43 46 55 58 62 63 3041 281 | 282 | 283 | -------------------------------------------------------------------------------- /ruby/server.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'eventmachine' 3 | require 'http/parser' 4 | 5 | #GC.disable 6 | class Server < EM::Connection 7 | 8 | def post_init 9 | @parser = Http::Parser.new 10 | # @parser.on_headers_complete = proc do 11 | # end 12 | @parser.on_message_complete = method(:get_slash) 13 | end 14 | 15 | def get_slash 16 | body = "

Hello World

\r\n" 17 | send_data "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\nContent-Length: #{body.size}\r\n\r\n" 18 | send_data body 19 | close_connection_after_writing 20 | end 21 | 22 | def unbind 23 | end 24 | 25 | def receive_data(data) 26 | @parser << data 27 | end 28 | end 29 | 30 | EM.run do 31 | Signal.trap("INT") { EM.stop } 32 | Signal.trap("TERM") { EM.stop } 33 | 34 | EM.start_server("0.0.0.0", 8080, Server) 35 | end 36 | -------------------------------------------------------------------------------- /test-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "$HOME/.rvm/scripts/rvm" 4 | 5 | run() 6 | { 7 | local flavor=$1 8 | local desc=$2 9 | local version1=$3 10 | local version2=$4 11 | local rvm=$5 12 | local java=$6 13 | 14 | if [ "$rvm" != "" ]; then 15 | rvm $rvm 16 | fi 17 | 18 | if [ "$java" != "" ]; then 19 | sudo update-alternatives --set java $java > /dev/null 2>&1 20 | fi 21 | 22 | ./hellod start $flavor > /dev/null 23 | echo $desc 24 | echo "-------------" 25 | if [ "$version1" != ""]; then 26 | echo -e "\t$version1" 27 | $version1 2>&1 | sed s/^/\\t/ 28 | fi 29 | 30 | if [ "$version2" != "" ]; then 31 | echo "" 32 | echo -e "\t$version2" 33 | $version2 2>&1 | sed s/^/\\t/ 34 | fi 35 | 36 | echo "" 37 | sleep 5 38 | ./hellod test $flavor -n 10000 -c 50 > /dev/null 39 | ./hellod test $flavor -n 10000 -c 50 > /dev/null 40 | 41 | ./hellod test $flavor -n 10000 -c 50 42 | ./hellod test $flavor -n 50000 -c 200 43 | ./hellod stop $flavor > /dev/null 44 | 45 | echo "" 46 | } 47 | 48 | ./hellod stop all > /dev/null 49 | 50 | OPENJDK='/usr/lib/jvm/java-6-openjdk/jre/bin/java' 51 | SUNJDK='/usr/lib/jvm/java-6-sun/jre/bin/java' 52 | 53 | run 'ruby' 'Ruby 1.9.2' 'ruby -v' '' '1.9.2@hellod' '' 54 | run 'ruby' 'Ruby 1.9.3' 'ruby -v' '' '1.9.3@hellod' '' 55 | run 'ruby' 'JRuby 1.6.4 - Sun JDK' 'ruby -v' 'java -version' 'jruby@hellod' $SUNJDK 56 | run 'ruby' 'JRuby 1.6.4 - OpenJDK' 'ruby -v' 'java -version' 'jruby@hellod' $OPENJDK 57 | run 'go' 'Go' '6g -V' '' '' '' 58 | run 'node' 'Node.js' 'node -v' '' '' '' 59 | run 'c' 'C / libev' '' '' '' '' 60 | run 'clj-aleph' 'Clojure / Aleph - Sun JDK' 'lein version' 'java -version' '' $SUNJDK 61 | run 'clj-aleph' 'Clojure / Aleph - OpenJDK' 'lein version' 'java -version' '' $OPENJDK 62 | run 'clj-jetty' 'Clojure / Ring - Sun JDK' 'lein version' 'java -version' '' $SUNJDK 63 | run 'clj-jetty' 'Clojure / Ring - OpenJDK' 'lein version' 'java -version' '' $OPENJDK 64 | run 'java-nio' ' Java NIO - Sun JDK' '' 'java -version' '' $SUNJDK 65 | run 'java-nio' ' Java NIO - OpenJDK' '' 'java -version' '' $OPENJDK 66 | run 'java-netty' ' Java Netty - Sun JDK' '' 'java -version' '' $SUNJDK 67 | run 'java-netty' ' Java Netty - OpenJDK' '' 'java -version' '' $OPENJDK 68 | --------------------------------------------------------------------------------