ethon 0.5.12 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -1
- data/Guardfile +9 -0
- data/ethon.gemspec +26 -0
- data/lib/ethon/curl.rb +0 -12
- data/lib/ethon/curls/constants.rb +6 -22
- data/lib/ethon/curls/functions.rb +38 -41
- data/lib/ethon/curls/infos.rb +19 -0
- data/lib/ethon/curls/options.rb +416 -219
- data/lib/ethon/curls/settings.rb +1 -0
- data/lib/ethon/easy.rb +12 -18
- data/lib/ethon/easy/callbacks.rb +40 -6
- data/lib/ethon/easy/debug_info.rb +46 -0
- data/lib/ethon/easy/mirror.rb +39 -0
- data/lib/ethon/easy/options.rb +17 -1235
- data/lib/ethon/easy/queryable.rb +6 -8
- data/lib/ethon/easy/response_callbacks.rb +1 -1
- data/lib/ethon/version.rb +1 -1
- data/profile/benchmarks.rb +137 -0
- data/profile/memory_leaks.rb +113 -0
- data/profile/perf_spec_helper.rb +36 -0
- data/profile/support/memory_test_helpers.rb +75 -0
- data/profile/support/os_memory_leak_tracker.rb +47 -0
- data/profile/support/ruby_object_leak_tracker.rb +48 -0
- data/spec/ethon/curl_spec.rb +27 -0
- data/spec/ethon/easy/callbacks_spec.rb +31 -0
- data/spec/ethon/easy/debug_info_spec.rb +52 -0
- data/spec/ethon/easy/form_spec.rb +76 -0
- data/spec/ethon/easy/header_spec.rb +78 -0
- data/spec/ethon/easy/http/custom_spec.rb +176 -0
- data/spec/ethon/easy/http/delete_spec.rb +20 -0
- data/spec/ethon/easy/http/get_spec.rb +89 -0
- data/spec/ethon/easy/http/head_spec.rb +79 -0
- data/spec/ethon/easy/http/options_spec.rb +50 -0
- data/spec/ethon/easy/http/patch_spec.rb +50 -0
- data/spec/ethon/easy/http/post_spec.rb +220 -0
- data/spec/ethon/easy/http/put_spec.rb +124 -0
- data/spec/ethon/easy/http_spec.rb +44 -0
- data/spec/ethon/easy/informations_spec.rb +82 -0
- data/spec/ethon/easy/mirror_spec.rb +39 -0
- data/spec/ethon/easy/operations_spec.rb +251 -0
- data/spec/ethon/easy/options_spec.rb +135 -0
- data/spec/ethon/easy/queryable_spec.rb +188 -0
- data/spec/ethon/easy/response_callbacks_spec.rb +50 -0
- data/spec/ethon/easy/util_spec.rb +27 -0
- data/spec/ethon/easy_spec.rb +105 -0
- data/spec/ethon/libc_spec.rb +13 -0
- data/spec/ethon/loggable_spec.rb +21 -0
- data/spec/ethon/multi/operations_spec.rb +297 -0
- data/spec/ethon/multi/options_spec.rb +70 -0
- data/spec/ethon/multi/stack_spec.rb +79 -0
- data/spec/ethon/multi_spec.rb +21 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/localhost_server.rb +94 -0
- data/spec/support/server.rb +114 -0
- metadata +91 -31
- data/lib/ethon/curls/auth_types.rb +0 -25
- data/lib/ethon/curls/http_versions.rb +0 -22
- data/lib/ethon/curls/postredir.rb +0 -15
- data/lib/ethon/curls/protocols.rb +0 -36
- data/lib/ethon/curls/proxy_types.rb +0 -25
- data/lib/ethon/curls/ssl_versions.rb +0 -23
data/lib/ethon/easy/queryable.rb
CHANGED
@@ -10,12 +10,12 @@ module Ethon
|
|
10
10
|
base.send(:attr_accessor, :escape)
|
11
11
|
end
|
12
12
|
|
13
|
-
# Return wether there are elements in
|
13
|
+
# Return wether there are elements in params or not.
|
14
14
|
#
|
15
|
-
# @example Return if
|
15
|
+
# @example Return if params is empty.
|
16
16
|
# form.empty?
|
17
17
|
#
|
18
|
-
# @return [ Boolean ] True if
|
18
|
+
# @return [ Boolean ] True if params is empty, else false.
|
19
19
|
def empty?
|
20
20
|
@params.empty?
|
21
21
|
end
|
@@ -102,14 +102,12 @@ module Ethon
|
|
102
102
|
|
103
103
|
def pairs_for(v, key, pairs)
|
104
104
|
case v
|
105
|
-
when Hash
|
106
|
-
recursively_generate_pairs(v, key, pairs)
|
107
|
-
when Array
|
105
|
+
when Hash, Array
|
108
106
|
recursively_generate_pairs(v, key, pairs)
|
109
107
|
when File, Tempfile
|
110
|
-
pairs << [
|
108
|
+
pairs << [key, file_info(v)]
|
111
109
|
else
|
112
|
-
pairs << [
|
110
|
+
pairs << [key, v]
|
113
111
|
end
|
114
112
|
end
|
115
113
|
end
|
data/lib/ethon/version.rb
CHANGED
@@ -0,0 +1,137 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'ethon'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'patron'
|
5
|
+
require 'curb'
|
6
|
+
require "net/http"
|
7
|
+
require 'cgi'
|
8
|
+
require 'benchmark'
|
9
|
+
|
10
|
+
Benchmark.bm do |bm|
|
11
|
+
|
12
|
+
[100_000].each do |i|
|
13
|
+
puts "[ #{i} Creations]"
|
14
|
+
|
15
|
+
bm.report("String.new ") do
|
16
|
+
i.times { String.new }
|
17
|
+
end
|
18
|
+
|
19
|
+
bm.report("Easy.new ") do
|
20
|
+
i.times { Ethon::Easy.new }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
GC.start
|
25
|
+
|
26
|
+
[100_000].each do |i|
|
27
|
+
puts "[ #{i} Escapes]"
|
28
|
+
|
29
|
+
bm.report("CGI::escape ") do
|
30
|
+
i.times { CGI::escape("まつもと") }
|
31
|
+
end
|
32
|
+
|
33
|
+
bm.report("Easy.escape ") do
|
34
|
+
e = Ethon::Easy.new
|
35
|
+
i.times { e.escape("まつもと") }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
GC.start
|
40
|
+
|
41
|
+
[1000].each do |i|
|
42
|
+
puts "[ #{i} Requests]"
|
43
|
+
|
44
|
+
bm.report("net/http ") do
|
45
|
+
uri = URI.parse("http://localhost:3001/")
|
46
|
+
i.times { Net::HTTP.get_response(uri) }
|
47
|
+
end
|
48
|
+
|
49
|
+
bm.report("open-uri ") do
|
50
|
+
i.times { open "http://localhost:3001/" }
|
51
|
+
end
|
52
|
+
|
53
|
+
bm.report("patron ") do
|
54
|
+
sess = Patron::Session.new
|
55
|
+
i.times do
|
56
|
+
sess.base_url = "http://localhost:3001"
|
57
|
+
sess.get("/")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
bm.report("patron reuse ") do
|
62
|
+
sess = Patron::Session.new
|
63
|
+
sess.base_url = "http://localhost:3001"
|
64
|
+
i.times do
|
65
|
+
sess.get("/")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
bm.report("curb reuse ") do
|
70
|
+
easy = Curl::Easy.new("http://localhost:3001")
|
71
|
+
i.times do
|
72
|
+
easy.perform
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
bm.report("Easy.perform ") do
|
77
|
+
easy = Ethon::Easy.new
|
78
|
+
i.times do
|
79
|
+
easy.url = "http://localhost:3001/"
|
80
|
+
easy.prepare
|
81
|
+
easy.perform
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
bm.report("Easy.perform reuse") do
|
86
|
+
easy = Ethon::Easy.new
|
87
|
+
easy.url = "http://localhost:3001/"
|
88
|
+
easy.prepare
|
89
|
+
i.times { easy.perform }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
GC.start
|
94
|
+
|
95
|
+
puts "[ 4 delayed Requests ]"
|
96
|
+
|
97
|
+
bm.report("net/http ") do
|
98
|
+
3.times do |i|
|
99
|
+
uri = URI.parse("http://localhost:300#{i}/?delay=1")
|
100
|
+
Net::HTTP.get_response(uri)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
bm.report("open-uri ") do
|
105
|
+
3.times do |i|
|
106
|
+
open("http://localhost:300#{i}/?delay=1")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
bm.report("patron ") do
|
111
|
+
sess = Patron::Session.new
|
112
|
+
3.times do |i|
|
113
|
+
sess.base_url = "http://localhost:300#{i}/?delay=1"
|
114
|
+
sess.get("/")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
bm.report("Easy.perform ") do
|
119
|
+
easy = Ethon::Easy.new
|
120
|
+
3.times do |i|
|
121
|
+
easy.url = "http://localhost:300#{i}/?delay=1"
|
122
|
+
easy.prepare
|
123
|
+
easy.perform
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
bm.report("Multi.perform ") do
|
128
|
+
multi = Ethon::Multi.new
|
129
|
+
3.times do |i|
|
130
|
+
easy = Ethon::Easy.new
|
131
|
+
easy.url = "http://localhost:300#{i}/?delay=1"
|
132
|
+
easy.prepare
|
133
|
+
multi.add(easy)
|
134
|
+
end
|
135
|
+
multi.perform
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'ethon'
|
2
|
+
require 'ethon/easy'
|
3
|
+
|
4
|
+
require_relative 'perf_spec_helper'
|
5
|
+
require 'rspec/autorun'
|
6
|
+
|
7
|
+
describe "low-level interactions with libcurl" do
|
8
|
+
describe Ethon::Multi do
|
9
|
+
memory_leak_test("init") do
|
10
|
+
Ethon::Multi.new
|
11
|
+
end
|
12
|
+
|
13
|
+
memory_leak_test("handle") do
|
14
|
+
Ethon::Multi.new.handle
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Ethon::Easy do
|
19
|
+
memory_leak_test("init") do
|
20
|
+
Ethon::Easy.new
|
21
|
+
end
|
22
|
+
|
23
|
+
memory_leak_test("handle") do
|
24
|
+
Ethon::Easy.new.handle
|
25
|
+
end
|
26
|
+
|
27
|
+
memory_leak_test("headers") do
|
28
|
+
Ethon::Easy.new.headers = { "a" => 1, "b" => 2, "c" => 3, "d" => 4}
|
29
|
+
end
|
30
|
+
|
31
|
+
memory_leak_test("escape") do
|
32
|
+
Ethon::Easy.new.escape("the_sky&is_blue")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe Ethon::Easy::Form do
|
38
|
+
memory_leak_test("init") do
|
39
|
+
Ethon::Easy::Form.new(nil, {})
|
40
|
+
end
|
41
|
+
|
42
|
+
memory_leak_test("first") do
|
43
|
+
Ethon::Easy::Form.new(nil, {}).first
|
44
|
+
end
|
45
|
+
|
46
|
+
memory_leak_test("last") do
|
47
|
+
Ethon::Easy::Form.new(nil, {}).last
|
48
|
+
end
|
49
|
+
|
50
|
+
memory_leak_test("materialized with some params") do
|
51
|
+
form = Ethon::Easy::Form.new(nil, { "a" => "1" })
|
52
|
+
form.materialize
|
53
|
+
end
|
54
|
+
|
55
|
+
memory_leak_test("materialized with a file") do
|
56
|
+
File.open(__FILE__, "r") do |file|
|
57
|
+
form = Ethon::Easy::Form.new(nil, { "a" => file })
|
58
|
+
form.materialize
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "higher level operations" do
|
65
|
+
memory_leak_test("a simple request") do
|
66
|
+
Ethon::Easy.new(:url => "http://localhost:3001/",
|
67
|
+
:forbid_reuse => true).perform
|
68
|
+
end
|
69
|
+
|
70
|
+
memory_leak_test("a request with headers") do
|
71
|
+
Ethon::Easy.new(:url => "http://localhost:3001/",
|
72
|
+
:headers => { "Content-Type" => "application/json",
|
73
|
+
"Something" => "1",
|
74
|
+
"Else" => "qwerty",
|
75
|
+
"Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"},
|
76
|
+
:forbid_reuse => true).perform
|
77
|
+
end
|
78
|
+
|
79
|
+
memory_leak_test("a request with headers and params") do
|
80
|
+
easy = Ethon::Easy.new(:url => "http://localhost:3001/",
|
81
|
+
:headers => { "Content-Type" => "application/json",
|
82
|
+
"Something" => "1",
|
83
|
+
"Else" => "qwerty",
|
84
|
+
"Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"},
|
85
|
+
:forbid_reuse => true)
|
86
|
+
easy.http_request("http://localhost:3001/",
|
87
|
+
:get,
|
88
|
+
:params => { "param1" => "value1",
|
89
|
+
"param2" => "value2",
|
90
|
+
"param3" => "value3",
|
91
|
+
"param4" => "value4"})
|
92
|
+
end
|
93
|
+
|
94
|
+
memory_leak_test("a request with headers, params, and body") do
|
95
|
+
easy = Ethon::Easy.new(:url => "http://localhost:3001/",
|
96
|
+
:headers => { "Content-Type" => "application/json",
|
97
|
+
"Something" => "1",
|
98
|
+
"Else" => "qwerty",
|
99
|
+
"Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"},
|
100
|
+
:forbid_reuse => true)
|
101
|
+
easy.http_request("http://localhost:3001/",
|
102
|
+
:get,
|
103
|
+
:params => { "param1" => "value1",
|
104
|
+
"param2" => "value2",
|
105
|
+
"param3" => "value3",
|
106
|
+
"param4" => "value4"},
|
107
|
+
:body => {
|
108
|
+
"body1" => "value1",
|
109
|
+
"body2" => "value2",
|
110
|
+
"body3" => "value3"
|
111
|
+
})
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#### SETUP
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
require 'support/localhost_server'
|
7
|
+
require 'support/server'
|
8
|
+
require_relative 'support/memory_test_helpers'
|
9
|
+
|
10
|
+
require 'logger'
|
11
|
+
|
12
|
+
if ENV['VERBOSE']
|
13
|
+
Ethon.logger = Logger.new($stdout)
|
14
|
+
Ethon.logger.level = Logger::DEBUG
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.before(:suite) do
|
19
|
+
LocalhostServer.new(TESTSERVER.new, 3001)
|
20
|
+
end
|
21
|
+
config.include(MemoryTestHelpers)
|
22
|
+
config.extend(MemoryTestHelpers::TestMethods)
|
23
|
+
end
|
24
|
+
|
25
|
+
MemoryTestHelpers.setup
|
26
|
+
MemoryTestHelpers.logger = Logger.new($stdout)
|
27
|
+
MemoryTestHelpers.logger.level = Logger::INFO
|
28
|
+
MemoryTestHelpers.logger.formatter = proc do |severity, datetime, progname, msg|
|
29
|
+
"\t\t#{msg}\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
if ENV['VERBOSE']
|
33
|
+
MemoryTestHelpers.logger.level = Logger::DEBUG
|
34
|
+
end
|
35
|
+
|
36
|
+
MemoryTestHelpers.iterations = ENV.fetch("ITERATIONS", 10_000).to_i
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative 'ruby_object_leak_tracker'
|
2
|
+
require_relative 'os_memory_leak_tracker'
|
3
|
+
|
4
|
+
module MemoryTestHelpers
|
5
|
+
class << self
|
6
|
+
attr_accessor :gc_proc, :iterations, :logger
|
7
|
+
|
8
|
+
def setup
|
9
|
+
if RUBY_PLATFORM == "java"
|
10
|
+
# for leak detection
|
11
|
+
JRuby.objectspace = true if defined?(JRuby)
|
12
|
+
# for gc
|
13
|
+
require 'java'
|
14
|
+
java_import 'java.lang.System'
|
15
|
+
self.gc_proc = proc { System.gc }
|
16
|
+
else
|
17
|
+
self.gc_proc = proc { GC.start }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module TestMethods
|
23
|
+
def memory_leak_test(description, &block)
|
24
|
+
context(description) do
|
25
|
+
it "doesn't leak ruby objects" do
|
26
|
+
object_leak_tracker = RubyObjectLeakTracker.new
|
27
|
+
track_memory_usage(object_leak_tracker, &block)
|
28
|
+
object_leak_tracker.total_difference_between_runs.should be <= 10
|
29
|
+
end
|
30
|
+
|
31
|
+
it "doesn't leak OS memory (C interop check)" do
|
32
|
+
os_memory_leak_tracker = OSMemoryLeakTracker.new
|
33
|
+
track_memory_usage(os_memory_leak_tracker, &block)
|
34
|
+
os_memory_leak_tracker.total_difference_between_runs.should be <= 10
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def track_memory_usage(tracker)
|
41
|
+
# Intentionally do all this setup before we do any testing
|
42
|
+
logger = MemoryTestHelpers.logger
|
43
|
+
iterations = MemoryTestHelpers.iterations
|
44
|
+
|
45
|
+
checkpoint_frequency = (iterations / 10.0).to_i
|
46
|
+
gc_frequency = 20
|
47
|
+
|
48
|
+
warmup_iterations = [(iterations / 3.0).to_i, 500].min
|
49
|
+
logger.info "Performing #{warmup_iterations} warmup iterations"
|
50
|
+
warmup_iterations.times do
|
51
|
+
yield
|
52
|
+
MemoryTestHelpers.gc_proc.call
|
53
|
+
end
|
54
|
+
tracker.capture_initial_memory_usage
|
55
|
+
|
56
|
+
logger.info "Performing #{iterations} iterations (checkpoint every #{checkpoint_frequency})"
|
57
|
+
|
58
|
+
iterations.times do |i|
|
59
|
+
yield
|
60
|
+
|
61
|
+
last_iteration = (i == iterations - 1)
|
62
|
+
checkpoint = last_iteration || (i % checkpoint_frequency == 0)
|
63
|
+
|
64
|
+
if checkpoint || (i % gc_frequency == 0)
|
65
|
+
MemoryTestHelpers.gc_proc.call
|
66
|
+
end
|
67
|
+
|
68
|
+
if checkpoint
|
69
|
+
logger.info "Iteration #{i} checkpoint"
|
70
|
+
tracker.capture_memory_usage
|
71
|
+
tracker.dump_status(logger)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class OSMemoryLeakTracker
|
2
|
+
attr_reader :current_run
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@previous_run = @current_run = 0
|
6
|
+
end
|
7
|
+
|
8
|
+
def difference_between_runs(basis=@previous_run)
|
9
|
+
@current_run - basis
|
10
|
+
end
|
11
|
+
|
12
|
+
def total_difference_between_runs
|
13
|
+
difference_between_runs(@initial_count_run)
|
14
|
+
end
|
15
|
+
|
16
|
+
def capture_initial_memory_usage
|
17
|
+
capture_memory_usage
|
18
|
+
@initial_count_run = @current_run
|
19
|
+
end
|
20
|
+
|
21
|
+
def capture_memory_usage
|
22
|
+
@previous_run = @current_run
|
23
|
+
@current_run = rss_bytes
|
24
|
+
end
|
25
|
+
|
26
|
+
def dump_status(logger)
|
27
|
+
delta = difference_between_runs
|
28
|
+
logger.add(log_level(delta), sprintf("\tTotal memory usage (kb): %d (%+d)", current_run, delta))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
# amount of memory the current process "is using", in RAM
|
33
|
+
# (doesn't include any swap memory that it may be using, just that in actual RAM)
|
34
|
+
# Code loosely based on https://github.com/rdp/os/blob/master/lib/os.rb
|
35
|
+
# returns 0 on windows
|
36
|
+
def rss_bytes
|
37
|
+
if ENV['OS'] == 'Windows_NT'
|
38
|
+
0
|
39
|
+
else
|
40
|
+
`ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def log_level(delta)
|
45
|
+
delta > 0 ? Logger::WARN : Logger::DEBUG
|
46
|
+
end
|
47
|
+
end
|