david 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +2 -2
  4. data/Gemfile +8 -2
  5. data/Gemfile.lock +84 -44
  6. data/README.md +87 -5
  7. data/benchmarks/Gemfile +1 -0
  8. data/benchmarks/Gemfile.lock +3 -1
  9. data/benchmarks/coapbench.sh +20 -0
  10. data/benchmarks/quick.sh +2 -0
  11. data/benchmarks/rackup/Gemfile +10 -0
  12. data/benchmarks/rackup/Gemfile.lock +159 -0
  13. data/benchmarks/rackup/grape.ru +18 -0
  14. data/benchmarks/rackup/rack.ru +7 -0
  15. data/benchmarks/rackup/rails.ru +14 -0
  16. data/benchmarks/rps.rb +14 -2
  17. data/benchmarks/stress.sh +17 -0
  18. data/david.gemspec +0 -3
  19. data/experiments/Gemfile +6 -0
  20. data/experiments/concurrency/Gemfile +3 -0
  21. data/experiments/concurrency/stub.rb +88 -0
  22. data/experiments/hash_key.rb +64 -0
  23. data/experiments/string_concat.rb +21 -0
  24. data/experiments/symbol_to_proc.rb +15 -0
  25. data/experiments/thread_safe.rb +40 -0
  26. data/lib/david.rb +8 -4
  27. data/lib/david/actor.rb +1 -11
  28. data/lib/david/app_config.rb +112 -0
  29. data/lib/david/exchange.rb +124 -0
  30. data/lib/david/fake_logger.rb +11 -0
  31. data/lib/david/garbage_collector.rb +9 -17
  32. data/lib/david/guerilla/rack/utils.rb +18 -0
  33. data/lib/david/interop.rb +4 -0
  34. data/lib/david/interop/mandatory_etsi.rb +4 -0
  35. data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
  36. data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
  37. data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
  38. data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
  39. data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
  40. data/lib/david/observe.rb +24 -29
  41. data/lib/david/rails/action_controller/base.rb +9 -7
  42. data/lib/david/railties/config.rb +4 -4
  43. data/lib/david/registry.rb +22 -0
  44. data/lib/david/server.rb +56 -56
  45. data/lib/david/server/constants.rb +1 -0
  46. data/lib/david/server/mapping.rb +43 -11
  47. data/lib/david/server/mid_cache.rb +29 -0
  48. data/lib/david/server/multicast.rb +7 -5
  49. data/lib/david/server/respond.rb +53 -43
  50. data/lib/david/server/utility.rb +2 -1
  51. data/lib/david/show_exceptions.rb +12 -8
  52. data/lib/david/transmitter.rb +44 -0
  53. data/lib/david/trap.rb +10 -0
  54. data/lib/david/version.rb +1 -1
  55. data/lib/rack/handler/david.rb +6 -10
  56. data/lib/rack/hello_world.rb +25 -0
  57. data/spec/app_config_spec.rb +56 -0
  58. data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
  59. data/spec/dummy/app/controllers/tests_controller.rb +9 -0
  60. data/spec/dummy/config/application.rb +4 -6
  61. data/spec/dummy/config/environments/development.rb +2 -2
  62. data/spec/dummy/config/environments/test.rb +2 -2
  63. data/spec/dummy/config/routes.rb +13 -53
  64. data/spec/interop/mandatory_spec.rb +100 -0
  65. data/spec/observe_spec.rb +8 -7
  66. data/spec/resource_discovery_spec.rb +3 -3
  67. data/spec/server_spec.rb +60 -15
  68. data/spec/spec_helper.rb +21 -2
  69. metadata +40 -33
  70. data/lib/david/request.rb +0 -80
  71. data/lib/david/server/deduplication.rb +0 -21
  72. 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,7 @@
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
+ run Rack::HelloWorld.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
- response = CoAP::Client.new.get_by_uri(uri)
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
@@ -26,7 +26,4 @@ Gem::Specification.new do |s|
26
26
 
27
27
  s.add_development_dependency 'rake'
28
28
  s.add_development_dependency 'rspec', '~> 3.1'
29
-
30
- s.add_development_dependency 'rspec-rails', '~> 3.1'
31
- s.add_development_dependency 'rails', '~> 4.2'
32
29
  end
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'benchmark-ips'
4
+ gem 'celluloid'
5
+ gem 'celluloid-io'
6
+ gem 'thread_safe'
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'david', path: '../..'
@@ -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,15 @@
1
+ require 'benchmark/ips'
2
+
3
+ Benchmark.ips do |x|
4
+ a = [0..99999]
5
+
6
+ x.report('map') do
7
+ a.map { |e| e.to_s }
8
+ end
9
+
10
+ x.report('symbol to proc') do
11
+ a.map(&:to_s)
12
+ end
13
+
14
+ x.compare!
15
+ 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? JRuby
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? Rails
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
- end
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