Skip to content

Instantly share code, notes, and snippets.

@hbin
Last active August 29, 2015 13:57
Show Gist options
  • Save hbin/9776582 to your computer and use it in GitHub Desktop.
Save hbin/9776582 to your computer and use it in GitHub Desktop.
Net HTTP Benchmark(net/http, open-uri, curb, typhoeus)
require 'rack'
require 'rack/handler/webrick'
require 'net/http'
# The code for this is inspired by Capybara's server:
# http://github.com/jnicklas/capybara/blob/0.3.9/lib/capybara/server.rb
class LocalhostServer
READY_MESSAGE = "Server ready"
class Identify
def initialize(app)
@app = app
end
def call(env)
if env["PATH_INFO"] == "/__identify__"
[200, {}, [LocalhostServer::READY_MESSAGE]]
else
@app.call(env)
end
end
end
attr_reader :port
def initialize(rack_app, port = nil)
@port = port || find_available_port
@rack_app = rack_app
concurrently { boot }
wait_until(10, "Boot failed.") { booted? }
end
private
def find_available_port
server = TCPServer.new('127.0.0.1', 0)
server.addr[1]
ensure
server.close if server
end
def boot
# Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters.
options = { :Port => port }
options.merge!(:AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new)) unless ENV['VERBOSE_SERVER']
Rack::Handler::WEBrick.run(Identify.new(@rack_app), options)
end
def booted?
res = ::Net::HTTP.get_response("localhost", '/__identify__', port)
if res.is_a?(::Net::HTTPSuccess) or res.is_a?(::Net::HTTPRedirection)
return res.body == READY_MESSAGE
end
rescue Errno::ECONNREFUSED, Errno::EBADF
return false
end
def concurrently
if should_use_subprocess?
pid = Process.fork do
trap(:INT) { ::Rack::Handler::WEBrick.shutdown }
yield
exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet.
end
at_exit do
Process.kill('INT', pid)
begin
Process.wait(pid)
rescue Errno::ECHILD
# ignore this error...I think it means the child process has already exited.
end
end
else
Thread.new { yield }
end
end
def should_use_subprocess?
# !ENV['THREADED']
false
end
def wait_until(timeout, error_message, &block)
start_time = Time.now
while true
return if yield
raise TimeoutError.new(error_message) if (Time.now - start_time) > timeout
sleep(0.05)
end
end
end
#!/usr/bin/env ruby
require 'json'
require 'zlib'
require 'sinatra/base'
require 'rack/typhoeus'
TESTSERVER = Sinatra.new do
set :logging, false
use Rack::Typhoeus::Middleware::ParamsDecoder
fail_count = 0
post '/file' do
{
'content-type' => params[:file][:type],
'filename' => params[:file][:filename],
'content' => params[:file][:tempfile].read,
'request-content-type' => request.env['CONTENT_TYPE']
}.to_json
end
get '/multiple-headers' do
[200, { 'Set-Cookie' => %w[ foo bar ], 'Content-Type' => 'text/plain' }, ['']]
end
get '/fail/:number' do
if fail_count >= params[:number].to_i
"ok"
else
fail_count += 1
error 500, "oh noes!"
end
end
get '/fail_forever' do
error 500, "oh noes!"
end
get '/redirect' do
redirect '/'
end
get '/bad_redirect' do
redirect '/bad_redirect'
end
get '/auth_basic/:username/:password' do
@auth ||= Rack::Auth::Basic::Request.new(request.env)
# Check that we've got a basic auth, and that it's credentials match the ones
# provided in the request
if @auth.provided? && @auth.basic? && @auth.credentials == [ params[:username], params[:password] ]
# auth is valid - confirm it
true
else
# invalid auth - request the authentication
response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth")
throw(:halt, [401, "Not authorized\n"])
end
end
get '/auth_ntlm' do
# we're just checking for the existence if NTLM auth header here. It's validation
# is too troublesome and really doesn't bother is much, it's up to libcurl to make
# it valid
response['WWW-Authenticate'] = 'NTLM'
is_ntlm_auth = /^NTLM/ =~ request.env['HTTP_AUTHORIZATION']
true if is_ntlm_auth
throw(:halt, [401, "Not authorized\n"]) if !is_ntlm_auth
end
get '/gzipped' do
req_env = request.env.to_json
z = Zlib::Deflate.new
gzipped_env = z.deflate(req_env, Zlib::FINISH)
z.close
response['Content-Encoding'] = 'gzip'
gzipped_env
end
get '/**' do
sleep params["delay"].to_i if params.has_key?("delay")
request.env.merge!(:body => request.body.read).to_json
end
head '/**' do
sleep params["delay"].to_i if params.has_key?("delay")
end
put '/**' do
request.env.merge!(:body => request.body.read).to_json
end
post '/**' do
request.env.merge!(:body => request.body.read).to_json
end
delete '/**' do
request.env.merge!(:body => request.body.read).to_json
end
patch '/**' do
request.env.merge!(:body => request.body.read).to_json
end
options '/**' do
request.env.merge!(:body => request.body.read).to_json
end
end
require 'curb'
require 'typhoeus'
require 'net/http'
require 'open-uri'
require 'benchmark'
URL = "http://localhost:300"
hydra = Typhoeus::Hydra.new(max_concurrency: 3)
if defined? require_relative
require_relative 'localhost_server.rb'
require_relative 'server.rb'
else
require 'localhost_server.rb'
require 'server.rb'
end
LocalhostServer.new(TESTSERVER.new, 3000)
LocalhostServer.new(TESTSERVER.new, 3001)
LocalhostServer.new(TESTSERVER.new, 3002)
def url_for(i)
"#{URL}#{i%3}/"
end
Benchmark.bm do |bm|
[1000].each do |calls|
puts "[ #{calls} requests ]"
bm.report("net/http ") do
calls.times do |i|
uri = URI.parse(url_for(i))
Net::HTTP.get_response(uri)
end
end
bm.report("open ") do
calls.times do |i|
open(url_for(i))
end
end
bm.report("curb ") do
calls.times do |i|
Curl.get(url_for(i))
end
end
bm.report("request ") do
calls.times do |i|
Typhoeus::Request.get(url_for(i))
end
end
bm.report("hydra ") do
calls.times do |i|
hydra.queue(Typhoeus::Request.new(url_for(i)))
end
hydra.run
end
bm.report("hydra memoize ") do
Typhoeus::Config.memoize = true
calls.times do |i|
hydra.queue(Typhoeus::Request.new(url_for(i)))
end
hydra.run
Typhoeus::Config.memoize = false
end
end
end
@hbin
Copy link
Author

hbin commented Mar 26, 2014

Ubuntu 13.10 64-bit, Intel® Core™ i5-2450M CPU @ 2.50GHz × 4, 8 GB Memory, SSD

➜ ~ ruby vs_nethttp.rb
user system total real
[ 1000 requests ]
net/http 6.860000 0.990000 7.850000 ( 7.383645)
open 7.610000 0.930000 8.540000 ( 8.034777)
curb 8.370000 2.690000 11.060000 ( 90.985541)
request 6.140000 0.380000 6.520000 ( 33.269623)
hydra 7.260000 0.830000 8.090000 ( 14.778413)
hydra memoize 0.120000 0.010000 0.130000 ( 0.166089)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment