david 0.3.0 → 0.4.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.
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