david 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +2 -2
- data/Gemfile +8 -2
- data/Gemfile.lock +84 -44
- data/README.md +87 -5
- data/benchmarks/Gemfile +1 -0
- data/benchmarks/Gemfile.lock +3 -1
- data/benchmarks/coapbench.sh +20 -0
- data/benchmarks/quick.sh +2 -0
- data/benchmarks/rackup/Gemfile +10 -0
- data/benchmarks/rackup/Gemfile.lock +159 -0
- data/benchmarks/rackup/grape.ru +18 -0
- data/benchmarks/rackup/rack.ru +7 -0
- data/benchmarks/rackup/rails.ru +14 -0
- data/benchmarks/rps.rb +14 -2
- data/benchmarks/stress.sh +17 -0
- data/david.gemspec +0 -3
- data/experiments/Gemfile +6 -0
- data/experiments/concurrency/Gemfile +3 -0
- data/experiments/concurrency/stub.rb +88 -0
- data/experiments/hash_key.rb +64 -0
- data/experiments/string_concat.rb +21 -0
- data/experiments/symbol_to_proc.rb +15 -0
- data/experiments/thread_safe.rb +40 -0
- data/lib/david.rb +8 -4
- data/lib/david/actor.rb +1 -11
- data/lib/david/app_config.rb +112 -0
- data/lib/david/exchange.rb +124 -0
- data/lib/david/fake_logger.rb +11 -0
- data/lib/david/garbage_collector.rb +9 -17
- data/lib/david/guerilla/rack/utils.rb +18 -0
- data/lib/david/interop.rb +4 -0
- data/lib/david/interop/mandatory_etsi.rb +4 -0
- data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
- data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
- data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
- data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
- data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
- data/lib/david/observe.rb +24 -29
- data/lib/david/rails/action_controller/base.rb +9 -7
- data/lib/david/railties/config.rb +4 -4
- data/lib/david/registry.rb +22 -0
- data/lib/david/server.rb +56 -56
- data/lib/david/server/constants.rb +1 -0
- data/lib/david/server/mapping.rb +43 -11
- data/lib/david/server/mid_cache.rb +29 -0
- data/lib/david/server/multicast.rb +7 -5
- data/lib/david/server/respond.rb +53 -43
- data/lib/david/server/utility.rb +2 -1
- data/lib/david/show_exceptions.rb +12 -8
- data/lib/david/transmitter.rb +44 -0
- data/lib/david/trap.rb +10 -0
- data/lib/david/version.rb +1 -1
- data/lib/rack/handler/david.rb +6 -10
- data/lib/rack/hello_world.rb +25 -0
- data/spec/app_config_spec.rb +56 -0
- data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
- data/spec/dummy/app/controllers/tests_controller.rb +9 -0
- data/spec/dummy/config/application.rb +4 -6
- data/spec/dummy/config/environments/development.rb +2 -2
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/config/routes.rb +13 -53
- data/spec/interop/mandatory_spec.rb +100 -0
- data/spec/observe_spec.rb +8 -7
- data/spec/resource_discovery_spec.rb +3 -3
- data/spec/server_spec.rb +60 -15
- data/spec/spec_helper.rb +21 -2
- metadata +40 -33
- data/lib/david/request.rb +0 -80
- data/lib/david/server/deduplication.rb +0 -21
- data/lib/david/server/options.rb +0 -79
@@ -0,0 +1,18 @@
|
|
1
|
+
#\ -o ::1 -p 5683 -O Block=false -O Multicast=false -O Observe=false -O Log=none -E none
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require 'david'
|
7
|
+
require 'grape'
|
8
|
+
|
9
|
+
class Dummy < Grape::API
|
10
|
+
content_type :txt, 'text/plain'
|
11
|
+
default_format :txt
|
12
|
+
|
13
|
+
get :hello do
|
14
|
+
'Hello World!'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
run Dummy.new
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#\ -o ::1 -p 5683 -O Block=false -O Multicast=false -O Observe=false -O Log=none -E none
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require_relative '../../spec/dummy/config/application'
|
7
|
+
|
8
|
+
module Dummy
|
9
|
+
class Application
|
10
|
+
config.middleware.delete(Rack::ETag)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
run Rails.application.initialize!
|
data/benchmarks/rps.rb
CHANGED
@@ -2,18 +2,30 @@
|
|
2
2
|
|
3
3
|
require 'benchmark/ips'
|
4
4
|
|
5
|
+
require 'celluloid'
|
6
|
+
Celluloid.logger = nil
|
7
|
+
|
5
8
|
require 'coap'
|
6
9
|
include CoRE
|
7
10
|
|
8
11
|
uri = ARGV[0] || 'coap://[::1]/hello'
|
12
|
+
n = (ARGV[1] || 100).to_i
|
13
|
+
|
14
|
+
class Tester
|
15
|
+
include Celluloid
|
16
|
+
|
17
|
+
def run(uri)
|
18
|
+
response = CoAP::Client.new.get_by_uri(uri)
|
19
|
+
raise unless response.mcode == [2, 5]
|
20
|
+
end
|
21
|
+
end
|
9
22
|
|
10
23
|
Benchmark.ips do |x|
|
11
24
|
x.warmup = 30
|
12
25
|
x.time = 30
|
13
26
|
|
14
27
|
x.report(uri) do
|
15
|
-
|
16
|
-
raise unless response.mcode == [2, 5]
|
28
|
+
Tester.pool(size: n).run(uri)
|
17
29
|
end
|
18
30
|
|
19
31
|
x.compare!
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# (60+(28*3*(30+15)))/60=64
|
3
|
+
|
4
|
+
uri="coap://[::1]:5683/hello"
|
5
|
+
|
6
|
+
./coapbench.sh -c 1000 -t 60 $uri > /dev/null 2>&1
|
7
|
+
|
8
|
+
seq="$(seq 10 10 90) $(seq 100 100 900) $(seq 1000 1000 10000)"
|
9
|
+
|
10
|
+
for c in $seq; do
|
11
|
+
for i in $(seq 1 3); do
|
12
|
+
./coapbench.sh -c $c -t 30 $uri 2>&1 | grep throughput
|
13
|
+
sleep 15
|
14
|
+
done
|
15
|
+
done
|
16
|
+
|
17
|
+
rm -f coapbench\(*
|
data/david.gemspec
CHANGED
data/experiments/Gemfile
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'coap'
|
3
|
+
require 'david'
|
4
|
+
|
5
|
+
class Listener
|
6
|
+
include David::Server::Respond
|
7
|
+
|
8
|
+
def initialize(mode, socket, cache)
|
9
|
+
@mode = mode
|
10
|
+
@socket = socket
|
11
|
+
@cache = cache
|
12
|
+
@app = Rack::HelloWorld.new
|
13
|
+
@block = true
|
14
|
+
@observe = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
loop do
|
19
|
+
if defined?(JRuby) || @mode == :prefork || @mode == :threaded
|
20
|
+
data, sender = @socket.recvfrom(1152)
|
21
|
+
port, _, host = sender[1..3]
|
22
|
+
else
|
23
|
+
begin
|
24
|
+
data, sender, _, anc = @socket.to_io.recvmsg_nonblock
|
25
|
+
rescue ::IO::WaitReadable
|
26
|
+
Celluloid::IO.wait_readable(@socket)
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
|
30
|
+
host, port = sender.ip_address, sender.ip_port
|
31
|
+
end
|
32
|
+
|
33
|
+
message = CoAP::Message.parse(data)
|
34
|
+
exchange = David::Exchange.new(host, port, message, anc)
|
35
|
+
|
36
|
+
return if !exchange.non? && exchange.multicast?
|
37
|
+
|
38
|
+
key = exchange.key
|
39
|
+
cached = @cache[key]
|
40
|
+
|
41
|
+
if exchange.ack? && !cached.nil?
|
42
|
+
@cache.delete(key)
|
43
|
+
elsif exchange.request?
|
44
|
+
if exchange.con? && !cached.nil?
|
45
|
+
response = cached[0]
|
46
|
+
else
|
47
|
+
response, _ = respond(exchange)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
unless response.nil?
|
52
|
+
@socket.send(response.to_wire, 0, host, port)
|
53
|
+
|
54
|
+
if !cached.nil?
|
55
|
+
cached[1] = Time.now.to_i
|
56
|
+
elsif exchange.reliable?
|
57
|
+
@cache[[host, response.mid]] = [response, Time.now.to_i]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
trap('EXIT') { socket.close }
|
66
|
+
|
67
|
+
cache = {}
|
68
|
+
|
69
|
+
case ARGV[0]
|
70
|
+
when 'prefork'
|
71
|
+
# ~33000
|
72
|
+
socket = UDPSocket.new(Socket::AF_INET6)
|
73
|
+
socket.bind('::', 5683)
|
74
|
+
4.times { fork { Listener.new(:prefork, socket, cache).run } }
|
75
|
+
when 'threaded'
|
76
|
+
# ~16000
|
77
|
+
socket = UDPSocket.new(Socket::AF_INET6)
|
78
|
+
socket.bind('::', 5683)
|
79
|
+
Listener.send(:include, Celluloid)
|
80
|
+
Listener.pool(size: 8, args: [:threaded, socket, cache]).run
|
81
|
+
else
|
82
|
+
# ~14000
|
83
|
+
socket = Celluloid::IO::UDPSocket.new(Socket::AF_INET6)
|
84
|
+
socket.bind('::', 5683)
|
85
|
+
Listener.new(:sped, socket, cache).run
|
86
|
+
end
|
87
|
+
|
88
|
+
Process.waitall
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
|
3
|
+
max = 10000
|
4
|
+
|
5
|
+
keys1 = ([0]*max).map { rand(0..99999).to_s }
|
6
|
+
keys2 = ([0]*max).map { rand(0..99999) }
|
7
|
+
values = ([0]*max).map { rand(0..99999) }
|
8
|
+
|
9
|
+
a = {}
|
10
|
+
b = {}
|
11
|
+
|
12
|
+
Benchmark.ips do |x|
|
13
|
+
x.report('Array key (insert)') do
|
14
|
+
max.times do |i|
|
15
|
+
a[[keys1[i], keys2[i]]] = values[i]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
x.report('Nested Hash (insert)') do
|
20
|
+
max.times do |i|
|
21
|
+
if b[keys1[i]].nil?
|
22
|
+
b[keys1[i]] = {keys2[i] => values[i]}
|
23
|
+
else
|
24
|
+
b[keys1[i]].merge!(keys2[i] => values[i])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('Set key (insert)') do
|
30
|
+
max.times do |i|
|
31
|
+
key = Set.new
|
32
|
+
key << keys1[i]
|
33
|
+
key << keys2[i]
|
34
|
+
a[key] = values[i]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
x.compare!
|
39
|
+
end
|
40
|
+
|
41
|
+
Benchmark.ips do |x|
|
42
|
+
x.report('Array key (lookup)') do
|
43
|
+
max.times do |i|
|
44
|
+
v = a[[keys1[i], keys2[i]]]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
x.report('Nested Hash (lookup)') do
|
49
|
+
max.times do |i|
|
50
|
+
v = b[keys1[i]][keys2[i]]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
x.report('Set key (lookup)') do
|
55
|
+
max.times do |i|
|
56
|
+
key = Set.new
|
57
|
+
key << keys1[i]
|
58
|
+
key << keys2[i]
|
59
|
+
v = a[key]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
x.compare!
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
|
3
|
+
def r
|
4
|
+
rand(0..99999)
|
5
|
+
end
|
6
|
+
|
7
|
+
Benchmark.ips do |x|
|
8
|
+
x.report('interpolation') do
|
9
|
+
"foo #{r} #{r} #{r}"
|
10
|
+
end
|
11
|
+
|
12
|
+
x.report('addition') do
|
13
|
+
'foo ' + r.to_s + ' ' + r.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
x.report('concatenation') do
|
17
|
+
'foo ' << r.to_s << ' ' << r.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
x.compare!
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'celluloid'
|
3
|
+
require 'thread_safe'
|
4
|
+
|
5
|
+
class Stresser
|
6
|
+
include Celluloid
|
7
|
+
|
8
|
+
def run(&block)
|
9
|
+
block.call
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Benchmark.ips do |x|
|
14
|
+
x.time = 60
|
15
|
+
x.warmup = 60
|
16
|
+
|
17
|
+
a = ThreadSafe::Hash.new
|
18
|
+
b = Hash.new
|
19
|
+
c = ThreadSafe::Cache.new
|
20
|
+
|
21
|
+
p = Stresser.pool(size: 1000)
|
22
|
+
|
23
|
+
def r
|
24
|
+
rand(0..9999)
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report('ThreadSafe::Hash') do
|
28
|
+
p.run { a[[r.to_s, r]] = [r] }
|
29
|
+
end
|
30
|
+
|
31
|
+
x.report('Hash') do
|
32
|
+
p.run { b[[r.to_s, r]] = [r] }
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report('ThreadSafe::Cache') do
|
36
|
+
p.run { c[[r.to_s, r]] = [r] }
|
37
|
+
end
|
38
|
+
|
39
|
+
x.compare!
|
40
|
+
end
|
data/lib/david.rb
CHANGED
@@ -4,7 +4,7 @@ end
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
Bundler.require(:default, :cbor)
|
6
6
|
|
7
|
-
unless defined?
|
7
|
+
unless defined?(JRuby)
|
8
8
|
begin
|
9
9
|
require 'cbor'
|
10
10
|
rescue LoadError
|
@@ -28,15 +28,19 @@ require 'rack/handler/david'
|
|
28
28
|
require 'rack/handler/coap'
|
29
29
|
|
30
30
|
require 'david/guerilla/rack/handler'
|
31
|
+
require 'david/guerilla/rack/utils'
|
31
32
|
|
32
33
|
require 'david/actor'
|
34
|
+
require 'david/registry'
|
35
|
+
require 'david/exchange'
|
33
36
|
require 'david/garbage_collector'
|
37
|
+
require 'david/transmitter'
|
38
|
+
require 'david/version'
|
39
|
+
|
34
40
|
require 'david/observe'
|
35
|
-
require 'david/request'
|
36
41
|
require 'david/server'
|
37
|
-
require 'david/version'
|
38
42
|
|
39
|
-
if defined?
|
43
|
+
if defined?(Rails)
|
40
44
|
require 'david/rails/action_controller/base'
|
41
45
|
|
42
46
|
require 'david/railties/config'
|
data/lib/david/actor.rb
CHANGED
@@ -2,17 +2,7 @@ module David
|
|
2
2
|
module Actor
|
3
3
|
def self.included(base)
|
4
4
|
base.send(:include, Celluloid)
|
5
|
-
|
6
|
-
|
7
|
-
protected
|
8
|
-
|
9
|
-
def log
|
10
|
-
@log ||= Celluloid.logger
|
11
|
-
@log ||= ::Logger.new(nil)
|
12
|
-
end
|
13
|
-
|
14
|
-
def server
|
15
|
-
Celluloid::Actor[:server]
|
5
|
+
base.send(:include, Registry)
|
16
6
|
end
|
17
7
|
end
|
18
8
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'david/fake_logger'
|
2
|
+
|
3
|
+
module David
|
4
|
+
class AppConfig < Hash
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
:Block => true,
|
7
|
+
:CBOR => false,
|
8
|
+
:DefaultFormat => 'application/json',
|
9
|
+
:Host => ENV['RACK_ENV'] == 'development' ? '::1' : '::',
|
10
|
+
:Log => nil,
|
11
|
+
:MinimalMapping => false,
|
12
|
+
:Multicast => true,
|
13
|
+
:Observe => true,
|
14
|
+
:Port => ::CoAP::PORT
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(hash = {})
|
18
|
+
self.merge!(DEFAULT_OPTIONS)
|
19
|
+
self.merge!(hash)
|
20
|
+
|
21
|
+
(self.keys & DEFAULT_OPTIONS.keys).each do |key|
|
22
|
+
optionize!(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def choose_block(value)
|
29
|
+
default_to_true(:block, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def choose_cbor(value)
|
33
|
+
default_to_false(:cbor, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def choose_defaultformat(value)
|
37
|
+
value = from_rails(:default_format)
|
38
|
+
return nil if value.nil?
|
39
|
+
value
|
40
|
+
end
|
41
|
+
|
42
|
+
# Rails starts on 'localhost' since 4.2.0.beta1
|
43
|
+
# (Resolv class seems not to consider /etc/hosts)
|
44
|
+
def choose_host(value)
|
45
|
+
return nil if value.nil?
|
46
|
+
Socket::getaddrinfo(value, nil, nil, Socket::SOCK_STREAM)[0][3]
|
47
|
+
end
|
48
|
+
|
49
|
+
def choose_log(value)
|
50
|
+
return FakeLogger.new if value == 'none'
|
51
|
+
|
52
|
+
log = ::Logger.new($stderr)
|
53
|
+
|
54
|
+
log.level = ::Logger::INFO
|
55
|
+
log.level = ::Logger::DEBUG if value == 'debug'
|
56
|
+
|
57
|
+
log.formatter = proc do |sev, time, prog, msg|
|
58
|
+
"#{time.strftime('[%Y-%m-%d %H:%M:%S]')} #{sev} #{msg}\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
Celluloid.logger = log
|
62
|
+
|
63
|
+
log
|
64
|
+
end
|
65
|
+
|
66
|
+
def choose_minimalmapping(value)
|
67
|
+
value
|
68
|
+
end
|
69
|
+
|
70
|
+
def choose_multicast(value)
|
71
|
+
default_to_true(:multicast, value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def choose_observe(value)
|
75
|
+
default_to_true(:observe, value)
|
76
|
+
end
|
77
|
+
|
78
|
+
def choose_port(value)
|
79
|
+
value.nil? ? nil : value.to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
def default_to_false(key, value)
|
83
|
+
return true if value.to_s == 'true'
|
84
|
+
|
85
|
+
r = from_rails(key)
|
86
|
+
return r unless r.nil? || value == 'false'
|
87
|
+
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
def default_to_true(key, value)
|
92
|
+
return false if value.to_s == 'false'
|
93
|
+
|
94
|
+
r = from_rails(key)
|
95
|
+
return r unless r.nil? || value == 'true'
|
96
|
+
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def from_rails(key)
|
101
|
+
if defined?(Rails) && !Rails.application.nil?
|
102
|
+
Rails.application.config.coap.send(key)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def optionize!(key)
|
107
|
+
method = ('choose_' << key.to_s.downcase).to_sym
|
108
|
+
value = self.send(method, self[key])
|
109
|
+
self[key] = value unless value.nil?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|