tipi 0.40 → 0.45

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +3 -1
  4. data/.gitignore +5 -1
  5. data/CHANGELOG.md +35 -0
  6. data/Gemfile +7 -1
  7. data/Gemfile.lock +55 -29
  8. data/README.md +184 -8
  9. data/Rakefile +1 -3
  10. data/benchmarks/bm_http1_parser.rb +85 -0
  11. data/bin/benchmark +37 -0
  12. data/bin/h1pd +6 -0
  13. data/bin/tipi +3 -21
  14. data/bm.png +0 -0
  15. data/df/agent.rb +1 -1
  16. data/df/sample_agent.rb +2 -2
  17. data/df/server.rb +16 -102
  18. data/df/server_utils.rb +175 -0
  19. data/examples/full_service.rb +13 -0
  20. data/examples/hello.rb +5 -0
  21. data/examples/http1_parser.rb +55 -0
  22. data/examples/http_server.js +1 -1
  23. data/examples/http_server.rb +15 -3
  24. data/examples/http_server_graceful.rb +1 -1
  25. data/examples/http_server_static.rb +6 -18
  26. data/examples/https_server.rb +41 -15
  27. data/examples/rack_server_forked.rb +26 -0
  28. data/examples/rack_server_https_forked.rb +1 -1
  29. data/examples/servername_cb.rb +37 -0
  30. data/examples/websocket_demo.rb +1 -1
  31. data/lib/tipi/acme.rb +315 -0
  32. data/lib/tipi/cli.rb +93 -0
  33. data/lib/tipi/config_dsl.rb +13 -13
  34. data/lib/tipi/configuration.rb +2 -2
  35. data/{e → lib/tipi/controller/bare_polyphony.rb} +0 -0
  36. data/lib/tipi/controller/bare_stock.rb +10 -0
  37. data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
  38. data/lib/tipi/controller/web_polyphony.rb +351 -0
  39. data/lib/tipi/controller/web_stock.rb +631 -0
  40. data/lib/tipi/controller.rb +12 -0
  41. data/lib/tipi/digital_fabric/agent.rb +10 -8
  42. data/lib/tipi/digital_fabric/agent_proxy.rb +26 -12
  43. data/lib/tipi/digital_fabric/executive.rb +7 -3
  44. data/lib/tipi/digital_fabric/protocol.rb +19 -4
  45. data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
  46. data/lib/tipi/digital_fabric/service.rb +84 -56
  47. data/lib/tipi/handler.rb +2 -2
  48. data/lib/tipi/http1_adapter.rb +86 -125
  49. data/lib/tipi/http2_adapter.rb +29 -16
  50. data/lib/tipi/http2_stream.rb +52 -56
  51. data/lib/tipi/rack_adapter.rb +2 -53
  52. data/lib/tipi/response_extensions.rb +2 -2
  53. data/lib/tipi/supervisor.rb +75 -0
  54. data/lib/tipi/version.rb +1 -1
  55. data/lib/tipi/websocket.rb +3 -3
  56. data/lib/tipi.rb +8 -5
  57. data/test/coverage.rb +2 -2
  58. data/test/helper.rb +60 -12
  59. data/test/test_http_server.rb +14 -41
  60. data/test/test_request.rb +2 -29
  61. data/tipi.gemspec +12 -8
  62. metadata +88 -28
  63. data/examples/automatic_certificate.rb +0 -193
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e78bf3b20c7ec6704c26869694cf69e389ae1c0c2f2e44c82d0104d2ecaded9
4
- data.tar.gz: dac8b0014cbc3a6ee5b146f3645bd38f3d55b21d5c17d969eecaa62c1e8b9306
3
+ metadata.gz: e6abb7798a69adc36e18dda822f7ff025f9f9e286eba50ccaf3905a74f29ef32
4
+ data.tar.gz: 41ee73bf589d2d4bd764aa58cb71e43477bc52caae708bd05ae4ae6372275415
5
5
  SHA512:
6
- metadata.gz: fa53cedcd271ba9dbc37821315d27bf14b1eac17451c912eb3dcaa3ceae3b0b738f5d10678cf390f0534edd9d352008e71dec95c46566cad513e3b2d783ea000
7
- data.tar.gz: 3cf95c22b05c8eaebdf646963d930977ddb010c3ca9174f081cee674e315f5ac672059b31f833ec337c78e3ec43770f72f6bba145ad00e2bd8c1ed8d0acb2174
6
+ metadata.gz: b23c0bfe27b9e6ff203508ab1a24ecbf10d954996ce9af2045a7d427ccec5746e9ef9df146353e4c134a56c12c4f2518141b46fe342123613c3da713a086a90f
7
+ data.tar.gz: a2f0fdc533ab39a23dad733535a9371a44a5809485c04114d0e054f4cb4bcb47b0cb27a266d5d58a7e01b8db06daf79a3aa944151de50e61da33f824e4651e43
@@ -0,0 +1 @@
1
+ github: ciconia
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest]
11
- ruby: [2.6, 2.7]
11
+ ruby: [2.6, 2.7, 3.0]
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
@@ -23,5 +23,7 @@ jobs:
23
23
  run: |
24
24
  gem install bundler
25
25
  POLYPHONY_USE_LIBEV=1 bundle install
26
+ - name: Show Linux kernel version
27
+ run: uname -r
26
28
  - name: Run tests
27
29
  run: bundle exec rake test
data/.gitignore CHANGED
@@ -54,4 +54,8 @@ build-iPhoneSimulator/
54
54
 
55
55
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
56
  # .rubocop-https?--*
57
- log
57
+ log
58
+ log.*
59
+
60
+ lib/tipi_ext*
61
+ examples/certificate_store.db
data/CHANGELOG.md CHANGED
@@ -1,3 +1,38 @@
1
+ ## 0.45 2021-10-25
2
+
3
+ - Remove `http_parser.rb` dependency (#14) - thanks @SwagDevOps
4
+ - Use `argv` argument in `Tipi.opts_from_argv` (#13) - thanks @SwagDevOps
5
+ - Ignore `ArgumentError` in `#parse_headers`
6
+
7
+ ## 0.44 2021-09-29
8
+
9
+ - Implement compatibility mode for HTTP/1 (WIP)
10
+ - Add option parsing for CLI tool
11
+ - Implement supervisor-controller-worker model in CLI tool
12
+
13
+ ## 0.43 2021-08-20
14
+
15
+ - Extract HTTP/1 parser into a separate gem:
16
+ [H1P](https://github.com/digital-fabric/h1p)
17
+
18
+ ## 0.42 2021-08-16
19
+
20
+ - HTTP/1 parser: disable UTF-8 parsing for all but header values
21
+ - Add support for parsing HTTP/1 from callable source
22
+ - Introduce full_service API for automatic HTTPS
23
+ - Introduce automatic SSL certificate provisioning
24
+ - Improve handling of exceptions
25
+ - Various fixes to DF service and agent pxoy
26
+ - Fix upgrading to HTTP2 with a request body
27
+ - Switch to new HTTP/1 parser
28
+
29
+ ## 0.41 2021-07-26
30
+
31
+ - Fix Rack adapter (#11)
32
+ - Introduce experimental HTTP/1 parser
33
+ - More work on DF server
34
+ - Allow setting chunk size in `#respond_from_io`
35
+
1
36
  ## 0.40 2021-06-24
2
37
 
3
38
  - Implement serving static files using splice_chunks (nice performance boost for
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec
4
+
5
+ # for local development
6
+ %w{polyphony ever qeweney h1p}.each do |dep|
7
+ dir = "../#{dep}"
8
+ gem(dep, path: dir) if File.directory?(dir)
9
+ end
data/Gemfile.lock CHANGED
@@ -1,57 +1,79 @@
1
+ PATH
2
+ remote: ../ever
3
+ specs:
4
+ ever (0.1)
5
+
6
+ PATH
7
+ remote: ../h1p
8
+ specs:
9
+ h1p (0.2)
10
+
11
+ PATH
12
+ remote: ../polyphony
13
+ specs:
14
+ polyphony (0.71)
15
+
16
+ PATH
17
+ remote: ../qeweney
18
+ specs:
19
+ qeweney (0.14)
20
+ escape_utils (~> 1.2.1)
21
+
1
22
  PATH
2
23
  remote: .
3
24
  specs:
4
- tipi (0.40)
25
+ tipi (0.45)
5
26
  acme-client (~> 2.0.8)
6
- http-2 (~> 0.10.0)
7
- http_parser.rb (~> 0.6.0)
27
+ ever (~> 0.1)
28
+ extralite (~> 1.2)
29
+ h1p (~> 0.2)
30
+ http-2 (~> 0.11)
31
+ localhost (~> 1.1.4)
8
32
  msgpack (~> 1.4.2)
9
- polyphony (~> 0.57.0)
10
- qeweney (~> 0.10.0)
33
+ polyphony (~> 0.71)
34
+ qeweney (~> 0.14)
11
35
  rack (>= 2.0.8, < 2.3.0)
12
36
  websocket (~> 1.2.8)
13
37
 
14
38
  GEM
15
39
  remote: https://rubygems.org/
16
40
  specs:
17
- acme-client (2.0.8)
41
+ acme-client (2.0.9)
18
42
  faraday (>= 0.17, < 2.0.0)
19
- ansi (1.5.0)
20
- builder (3.2.4)
21
- docile (1.3.2)
43
+ cuba (3.9.3)
44
+ rack (>= 1.6.0)
45
+ docile (1.4.0)
22
46
  escape_utils (1.2.1)
23
- faraday (1.4.3)
47
+ extralite (1.4)
48
+ faraday (1.8.0)
24
49
  faraday-em_http (~> 1.0)
25
50
  faraday-em_synchrony (~> 1.0)
26
51
  faraday-excon (~> 1.1)
52
+ faraday-httpclient (~> 1.0.1)
27
53
  faraday-net_http (~> 1.0)
28
54
  faraday-net_http_persistent (~> 1.1)
55
+ faraday-patron (~> 1.0)
56
+ faraday-rack (~> 1.0)
29
57
  multipart-post (>= 1.2, < 3)
30
58
  ruby2_keywords (>= 0.0.4)
31
59
  faraday-em_http (1.0.0)
32
60
  faraday-em_synchrony (1.0.0)
33
61
  faraday-excon (1.1.0)
62
+ faraday-httpclient (1.0.1)
34
63
  faraday-net_http (1.0.1)
35
- faraday-net_http_persistent (1.1.0)
36
- http-2 (0.10.2)
37
- http_parser.rb (0.6.0)
38
- json (2.3.1)
39
- localhost (1.1.4)
64
+ faraday-net_http_persistent (1.2.0)
65
+ faraday-patron (1.0.0)
66
+ faraday-rack (1.0.0)
67
+ http-2 (0.11.0)
68
+ json (2.5.1)
69
+ localhost (1.1.9)
70
+ memory_profiler (1.0.0)
40
71
  minitest (5.11.3)
41
- minitest-reporters (1.4.2)
42
- ansi
43
- builder
44
- minitest (>= 5.0)
45
- ruby-progressbar
46
72
  msgpack (1.4.2)
47
73
  multipart-post (2.1.1)
48
- polyphony (0.57.0)
49
- qeweney (0.10)
50
- escape_utils (~> 1.2.1)
51
74
  rack (2.2.3)
52
- rake (12.3.3)
53
- ruby-progressbar (1.10.1)
54
- ruby2_keywords (0.0.4)
75
+ rake (13.0.6)
76
+ ruby2_keywords (0.0.5)
55
77
  simplecov (0.17.1)
56
78
  docile (~> 1.1)
57
79
  json (>= 1.8, < 3)
@@ -63,10 +85,14 @@ PLATFORMS
63
85
  ruby
64
86
 
65
87
  DEPENDENCIES
66
- localhost (~> 1.1.4)
88
+ cuba (~> 3.9.3)
89
+ ever!
90
+ h1p!
91
+ memory_profiler (~> 1.0.0)
67
92
  minitest (~> 5.11.3)
68
- minitest-reporters (~> 1.4.2)
69
- rake (~> 12.3.3)
93
+ polyphony!
94
+ qeweney!
95
+ rake (~> 13.0.6)
70
96
  simplecov (~> 0.17.1)
71
97
  tipi!
72
98
 
data/README.md CHANGED
@@ -3,27 +3,203 @@
3
3
  # Tipi - the All-in-one Web Server for Ruby Apps
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/tipi.svg)](http://rubygems.org/gems/tipi)
6
- [![Modulation Test](https://github.com/digital-fabric/tipi/workflows/Tests/badge.svg)](https://github.com/digital-fabric/tipi/actions?query=workflow%3ATests)
6
+ [![Tipi Test](https://github.com/digital-fabric/tipi/workflows/Tests/badge.svg)](https://github.com/digital-fabric/tipi/actions?query=workflow%3ATests)
7
7
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/tipi/blob/master/LICENSE)
8
8
 
9
9
  ## What is Tipi?
10
10
 
11
11
  Tipi is an integrated, feature-complete HTTP/S server for Ruby applications.
12
- Tipi is built on Polyphony, a robust, high-performance library for building
13
- highly-concurrent applications in Ruby. Tipi can be used to serve any Rack
14
- application or set of static files directly without having to employ a
15
- reverse-proxy such as Nginx.
12
+ Tipi is built on top of
13
+ [Polyphony](https://github.com/digital-fabric/polyphony), a robust,
14
+ high-performance library for building highly-concurrent applications in Ruby.
15
+ Tipi can be used to serve any Rack application or set of static files directly
16
+ without having to employ a reverse-proxy such as Nginx.
16
17
 
17
18
  ## Features
18
19
 
19
20
  * High-performance, highly concurrent web server based on
20
21
  [Polyphony](https://github.com/digital-fabric/polyphony)
21
- * Full support for HTTP, HTTP/2, WebSocket protocols
22
+ * Full support for HTTP/1, HTTP/2, WebSocket protocols
22
23
  * Built-in SSL termination for secure, encrypted connections
23
- * Automatic ALPN protocol selection
24
+ * **Automatic SSL certificates** using ACME providers such as Let's Encrypt (WIP)
25
+ * Automatic ALPN protocol selection for serving HTTP/2
24
26
  * Request and response body streaming for efficient downloads and uploads
25
27
  * Full support for Rack-based apps
26
28
 
29
+ ## Benchmarks
30
+
31
+ > Caveat emptor: the following results were obtained with an ad-hoc, manual
32
+ > process. I am not really familiar with the servers I compared Tipi against,
33
+ > and I ran them in their default configuration (apart from setting the number
34
+ > of workers). Take these results with a bunch of salt.
35
+
36
+ <img src="bm.png" style="width: 480px">
37
+
38
+ | |Tipi|Puma|Falcon|Unicorn|
39
+ |-|---:|---:|-----:|------:|
40
+ |HTTP/1.1|138629|34573|40714|7438|
41
+ |HTTPS/2|56762|n/a|34226|n/a|
42
+
43
+ ### Methodology
44
+
45
+ - All servers ran the same "Hello world" [Rack
46
+ application](https://github.com/digital-fabric/tipi/blob/master/examples/hello.ru)
47
+ - Each server was run with 4 forked worker processes:
48
+ - Tipi: `tipi -w4 -flocalhost:10080:10443 examples/hello.ru`
49
+ - [Puma](https://github.com/puma/puma): `puma -w 4 examples/hello.ru`
50
+ - [Falcon](https://github.com/socketry/falcon/): `falcon -n 4 -b http://localhost:9292/ -c examples/hello.ru`
51
+ - [Unicorn](https://yhbt.net/unicorn/): `unicorn -c u.conf examples/hello.ru`
52
+ with the configuration file containing the directive `worker_processes 4`
53
+ - The benchmark results were obtained using `wrk -d60 -t4 -c64 <url>`
54
+ - All servers were run on Ruby 2.7.2p137
55
+ - Machine specs: i5-8350U@1.7GHzx8 CPU, 8GB of RAM, running Linux kernel version 5.13.7
56
+ - Puma does not support HTTP/2.
57
+ - As far as I could tell Unicorn does not support SSL termination.
58
+
59
+ ## Running Tipi
60
+
61
+ To run Tipi, run the included `tipi` command. Alternatively you can add tipi as
62
+ a dependency to your Gemfile, then run `bundle exec tipi`. By default
63
+
64
+ Tipi can be used to drive Rack apps or alternatively any app using the
65
+ [Qeweney](https://github.com/digital-fabric/qeweney) request-response interface.
66
+
67
+ ### Running Rack apps
68
+
69
+ Use the `tipi` command to start your app:
70
+
71
+ ```bash
72
+ $ bundle exec tipi myapp.ru
73
+ ```
74
+
75
+ ### Running Qeweney apps
76
+
77
+ ```bash
78
+ $ bundle exec tipi myapp.rb
79
+ ```
80
+
81
+ The app script file should define an `app` method that returns a proc/lambda
82
+ taking a single `Qeweney::Request` argument. Here's an example:
83
+
84
+ ```ruby
85
+ # frozen_string_literal: true
86
+
87
+ def app
88
+ ->(req) { req.respond('Hello, world!', 'Content-Type' => 'text/plain') }
89
+ end
90
+ ```
91
+
92
+ ## Setting server listening options
93
+
94
+ By default, Tipi serves plain HTTP on port 1234, but you can easily change that
95
+ by providing command line options as follows:
96
+
97
+ ### HTTP
98
+
99
+ To listen for plain HTTP, use the `-l`/`--listen` option and specify a port
100
+ number:
101
+
102
+ ```bash
103
+ $ bundle exec tipi -l9292 myapp.ru
104
+ ```
105
+
106
+ ### HTTPS
107
+
108
+ To listen for HTTPS connections, use the `-s`/`--secure` option and specify a
109
+ host name and a port:
110
+
111
+ ```bash
112
+ $ bundle exec tipi -sexample.com:9292 myapp.ru
113
+ ```
114
+
115
+ ### Full service listening
116
+
117
+ The Tipi full service listens for both HTTP and HTTPS and supports automatic
118
+ certificate provisioning. To use the full service, use the `-f`/`--full` option,
119
+ and specify the domain name, the HTTP port, and the HTTPS port, e.g.:
120
+
121
+ ```bash
122
+ $ bundle exec tipi -fmysite.org:10080:10443 myapp.ru
123
+
124
+ #If serving multiple domains, you can use * as place holder
125
+ $ bundle exec tipi -f*:10080:10443 myapp.ru
126
+ ```
127
+
128
+ If `localhost` is specified as the domain, Tipi will automatically generate a
129
+ localhost certificate.
130
+
131
+ ## Concurrency settings
132
+
133
+ By default, the `tipi` command starts a single controller and uses
134
+ [Polyphony](https://github.com/digital-fabric/polyphony) to run each connection
135
+ on its own fiber. This means that you will have a single process running on a
136
+ single thread (on a single CPU core). In order to parallelize your app and
137
+ employ multiple CPU cores, you can tell Tipi to fork multiple worker processes
138
+ to run your app. The number of workers is controlled using the `-w`/`--workers`
139
+ option:
140
+
141
+ ```bash
142
+ # fork 4 worker processes
143
+ $ bundle exec tipi -w4 myapp.ru
144
+ ```
145
+
146
+ You can also set Tipi to spawn multiple threads in each worker when in
147
+ compatibility mode (see below.)
148
+
149
+ ## Compatibility mode
150
+
151
+ > Note: compatibility mode is still being developed, and currently only supports
152
+ > HTTP/1 connections.
153
+
154
+ In some apps, using Polyphony is not possible, due to incompatibilities between
155
+ it and other third-party dependencies. In order to be able to run these apps,
156
+ Tipi provides a compatibility mode that does not use Polyphony for concurrency,
157
+ but instead uses a thread-per-connection concurrency model. You can also fork
158
+ multiple workers, each running multiple threads, if so desired. Note that the
159
+ concurrency level is the maximum number workers multiplied by the number of
160
+ threads per worker:
161
+
162
+ ```
163
+ concurrency = worker_count * threads_per_worker
164
+ ```
165
+
166
+ To run Tipi in compatibility mode, use the `-c`/`--compatibility` option, e.g.:
167
+
168
+ ```bash
169
+ # 4 workers * 8 threads = 32 max concurrency
170
+ $ bundle exec tipi -c -w4 -t8 myapp.ru
171
+ ```
172
+
173
+ ## Worker process supervision
174
+
175
+ Tipi employs a supervisor-controller-worker process supervision model, which
176
+ minimizes the memory consumption of forked workers, and which facilitates
177
+ graceful reloading after updating the application code.
178
+
179
+ This supervision model is made of three levels:
180
+
181
+ - Supervisor - Starts and stops the controller process
182
+ - Controller - loads the application code and forks workers
183
+ - Worker - listens for connections, handles incoming requests
184
+
185
+ (If the worker count is 1, the Controller and Worker roles are merged into a
186
+ single process.)
187
+
188
+ This model allows Tipi to fork workers after loading the app code, and use a
189
+ much simpler way to perform graceful restarts:
190
+
191
+ - The supervisor starts a new controller process (which may fork one or more
192
+ worker processes).
193
+ - Sleep for a certain amount of time (currently 1 second.)
194
+ - Stop the old controller process.
195
+ - Each worker process is gracefully stopped and allowed to finish all pending
196
+ requests, then shutdown all open connections.
197
+
198
+ ## Performing a graceful restart
199
+
200
+ A graceful restart performed by sending `SIGUSR2` to the supervisor process.
201
+
27
202
  ## Documentation
28
203
 
29
- Documentation for Tipi is coming soon...
204
+ Documentation for Tipi's API is coming soon...
205
+
data/Rakefile CHANGED
@@ -3,10 +3,8 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/clean"
5
5
 
6
- # frozen_string_literal: true
7
-
8
6
  task :default => [:test]
7
+
9
8
  task :test do
10
9
  exec 'ruby test/run.rb'
11
10
  end
12
-
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
6
+
7
+ def measure_time_and_allocs
8
+ 4.times { GC.start }
9
+ GC.disable
10
+
11
+ t0 = Time.now
12
+ a0 = object_count
13
+ yield
14
+ t1 = Time.now
15
+ a1 = object_count
16
+ [t1 - t0, a1 - a0]
17
+ ensure
18
+ GC.enable
19
+ end
20
+
21
+ def object_count
22
+ count = ObjectSpace.count_objects
23
+ count[:TOTAL] - count[:FREE]
24
+ end
25
+
26
+ def benchmark_other_http1_parser(iterations)
27
+ STDOUT << "http_parser.rb: "
28
+ require 'http_parser.rb'
29
+
30
+ i, o = IO.pipe
31
+ parser = Http::Parser.new
32
+ done = false
33
+ headers = nil
34
+ parser.on_headers_complete = proc do |h|
35
+ headers = h
36
+ headers[':method'] = parser.http_method
37
+ headers[':path'] = parser.request_url
38
+ end
39
+ parser.on_message_complete = proc { done = true }
40
+
41
+ elapsed, allocated = measure_time_and_allocs do
42
+ iterations.times do
43
+ o << HTTP_REQUEST
44
+ done = false
45
+ while !done
46
+ msg = i.readpartial(4096)
47
+ parser << msg
48
+ end
49
+ end
50
+ end
51
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
52
+ end
53
+
54
+ def benchmark_tipi_http1_parser(iterations)
55
+ STDOUT << "tipi parser: "
56
+ require_relative '../lib/tipi_ext'
57
+ i, o = IO.pipe
58
+ reader = proc { |len| i.readpartial(len) }
59
+ parser = Tipi::HTTP1Parser.new(reader)
60
+
61
+ elapsed, allocated = measure_time_and_allocs do
62
+ iterations.times do
63
+ o << HTTP_REQUEST
64
+ headers = parser.parse_headers
65
+ end
66
+ end
67
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
68
+ end
69
+
70
+ def fork_benchmark(method, iterations)
71
+ pid = fork do
72
+ send(method, iterations)
73
+ rescue Exception => e
74
+ p e
75
+ p e.backtrace
76
+ exit!
77
+ end
78
+ Process.wait(pid)
79
+ end
80
+
81
+ x = 500000
82
+ # fork_benchmark(:benchmark_other_http1_parser, x)
83
+ # fork_benchmark(:benchmark_tipi_http1_parser, x)
84
+
85
+ benchmark_tipi_http1_parser(x)
data/bin/benchmark ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ def parse_latency(latency)
7
+ m = latency.match(/^([\d\.]+)(us|ms|s)$/)
8
+ return nil unless m
9
+
10
+ value = m[1].to_f
11
+ case m[2]
12
+ when 's' then value
13
+ when 'ms' then value / 1000
14
+ when 'us' then value / 1000000
15
+ end
16
+ end
17
+
18
+ def parse_wrk_results(results)
19
+ lines = results.lines
20
+ latencies = lines[3].strip.split(/\s+/)
21
+ throughput = lines[6].strip.split(/\s+/)
22
+
23
+ {
24
+ latency_avg: parse_latency(latencies[1]),
25
+ latency_max: parse_latency(latencies[3]),
26
+ rate: throughput[1].to_f
27
+ }
28
+ end
29
+
30
+ def run_wrk(duration: 10, threads: 2, connections: 10, url: )
31
+ `wrk -d#{duration} -t#{threads} -c#{connections} #{url}`
32
+ end
33
+
34
+ [8, 64, 256, 512].each do |c|
35
+ puts "connections: #{c}"
36
+ p parse_wrk_results(run_wrk(duration: 10, threads: 4, connections: c, url: "http://localhost:10080/"))
37
+ end
data/bin/h1pd ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+ rake compile
5
+ ruby test/test_http1_parser.rb
6
+ ruby benchmarks/bm_http1_parser.rb
data/bin/tipi CHANGED
@@ -1,26 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
5
- require File.expand_path('../lib/tipi/configuration', __dir__)
4
+ require 'tipi/cli'
6
5
 
7
- config = {}
8
- #config[:forked] = 4
6
+ trap('SIGINT') { exit }
9
7
 
10
- puts DATA.read
11
- puts
12
-
13
- configuration_manager = spin { Tipi::Configuration.supervise_config }
14
-
15
- configuration_manager << config
16
- configuration_manager.await
17
-
18
- __END__
19
-
20
- ooo
21
- oo
22
- o
23
- \|/
24
- / \ Tipi - A better web server for a better world
25
- /___\
26
-
8
+ Tipi::CLI.start
data/bm.png ADDED
Binary file
data/df/agent.rb CHANGED
@@ -30,7 +30,7 @@ class SampleAgent < DigitalFabric::Agent
30
30
  do_some_activity
31
31
  req.send_chunk({ id: @id, time: Time.now.to_i }.to_json)
32
32
  end
33
-
33
+
34
34
  req.finish
35
35
  rescue Polyphony::Terminate
36
36
  req.respond(' * shutting down *') if Fiber.current.graceful_shutdown?
data/df/sample_agent.rb CHANGED
@@ -46,7 +46,7 @@ class SampleAgent < DigitalFabric::Agent
46
46
  true
47
47
  ))
48
48
  end
49
-
49
+
50
50
  end
51
51
 
52
52
  def ws_request(req)
@@ -82,7 +82,7 @@ class SampleAgent < DigitalFabric::Agent
82
82
  true
83
83
  ))
84
84
  end
85
-
85
+
86
86
  end
87
87
 
88
88
  agent = SampleAgent.new('127.0.0.1', 4411, { path: '/agent' })