From fad1aa69991a3995ce69dfd7ae32e23085111f1a Mon Sep 17 00:00:00 2001 From: Wouter Coppieters Date: Mon, 12 May 2025 17:35:40 +1200 Subject: [PATCH 1/6] 0.2.17 WIP - Adding embedded benchmark suite --- CHANGELOG.md | 11 + Cargo.lock | 28 +- Gemfile.lock | 10 +- benchmarks/Gemfile | 13 + benchmarks/Gemfile.lock | 182 +++++++ benchmarks/README.md | 19 + benchmarks/apps/big_delay.ru | 8 + benchmarks/apps/chunked.ru | 14 + benchmarks/apps/echo_service/echo.proto | 57 ++ benchmarks/apps/echo_service/echo_service.rb | 128 +++++ .../echo_service/google/protobuf/any.proto | 162 ++++++ .../google/protobuf/timestamp.proto | 144 +++++ .../apps/echo_service/google/type/money.proto | 42 ++ benchmarks/apps/empty.ru | 8 + benchmarks/apps/full_hijack.ru | 18 + benchmarks/apps/hello_world.ru | 7 + benchmarks/apps/large.ru | 9 + benchmarks/apps/many_small_delay.ru | 10 + benchmarks/apps/medium.ru | 9 + benchmarks/apps/public/image.png | Bin 0 -> 9230 bytes benchmarks/apps/public/index.html | 9 + benchmarks/apps/sinatra.ru | 13 + benchmarks/apps/small.ru | 9 + benchmarks/apps/static.ru | 12 + benchmarks/apps/streaming_response_large.ru | 17 + benchmarks/apps/streaming_response_small.ru | 17 + benchmarks/flamegraph.svg | 491 ++++++++++++++++++ benchmarks/grpc_server.rb | 12 + benchmarks/lib/benchmark_case.rb | 59 +++ benchmarks/lib/server.rb | 94 ++++ benchmarks/lib/util.rb | 45 ++ benchmarks/rack_bench.rb | 277 ++++++++++ benchmarks/server_configurations/h2o.conf | 16 + benchmarks/server_configurations/itsi.rb | 4 + benchmarks/server_configurations/nginx.conf | 26 + benchmarks/servers.rb | 71 +++ .../test_cases/framework/sinatra_get.rb | 3 + .../test_cases/framework/sinatra_post.rb | 5 + benchmarks/test_cases/grpc/echo.rb | 13 + .../test_cases/grpc/echo_bidirectional.rb | 13 + benchmarks/test_cases/grpc/echo_collect.rb | 13 + benchmarks/test_cases/grpc/echo_stream.rb | 13 + benchmarks/test_cases/grpc/process_payment.rb | 13 + .../grpc/server_configurations/itsi.rb | 6 + .../nonblocking/nonblocking_big_delay.rb | 5 + .../nonblocking_many_small_delay.rb | 5 + .../response_size/empty_response.rb | 2 + .../response_size/response_size_large.rb | 2 + .../response_size/response_size_medium.rb | 2 + .../response_size/response_size_small.rb | 2 + .../static_file/server_configurations/itsi.rb | 8 + .../static_file/static_dynamic_mixed.rb | 9 + .../test_cases/static_file/static_large.rb | 7 + .../test_cases/static_file/static_small.rb | 7 + .../test_cases/streaming_response/chunked.rb | 5 + .../streaming_response/full_hijack.rb | 3 + .../streaming_response_large.rb | 4 + .../streaming_response_small.rb | 4 + .../streaming_response_small_http2.rb | 4 + .../test_cases/throughput/hello_world.rb | 1 + crates/itsi_scheduler/Cargo.toml | 2 +- crates/itsi_server/Cargo.toml | 4 +- crates/itsi_server/src/lib.rs | 5 + .../src/ruby_types/itsi_body_proxy/mod.rs | 2 + .../src/ruby_types/itsi_http_request.rs | 89 ++-- .../src/ruby_types/itsi_http_response.rs | 284 +++++----- .../ruby_types/itsi_server/file_watcher.rs | 21 +- crates/itsi_server/src/server/io_stream.rs | 10 +- .../middlewares/static_assets.rs | 3 +- .../itsi_server/src/server/process_worker.rs | 2 +- .../src/server/serve_strategy/acceptor.rs | 25 +- .../src/server/serve_strategy/single_mode.rs | 161 +++--- .../itsi_server/src/server/thread_worker.rs | 100 ++-- .../src/services/itsi_http_service.rs | 79 ++- gems/scheduler/Cargo.lock | 2 +- gems/scheduler/lib/itsi/scheduler/version.rb | 2 +- gems/server/Cargo.lock | 26 +- gems/server/exe/itsi | 7 +- gems/server/lib/itsi/http_request.rb | 70 ++- gems/server/lib/itsi/http_response.rb | 1 + gems/server/lib/itsi/rack_env_pool.rb | 59 +++ gems/server/lib/itsi/server.rb | 1 + gems/server/lib/itsi/server/config.rb | 16 +- .../config/options/auto_reload_config.rb | 8 +- .../lib/itsi/server/rack/handler/itsi.rb | 3 +- gems/server/lib/itsi/server/rack_interface.rb | 29 +- gems/server/lib/itsi/server/version.rb | 2 +- gems/server/lib/ruby_lsp/itsi/addon.rb | 4 +- itsi.gemspec | 4 +- lib/itsi/version.rb | 2 +- 90 files changed, 2778 insertions(+), 445 deletions(-) create mode 100644 benchmarks/Gemfile create mode 100644 benchmarks/Gemfile.lock create mode 100644 benchmarks/README.md create mode 100644 benchmarks/apps/big_delay.ru create mode 100644 benchmarks/apps/chunked.ru create mode 100644 benchmarks/apps/echo_service/echo.proto create mode 100644 benchmarks/apps/echo_service/echo_service.rb create mode 100644 benchmarks/apps/echo_service/google/protobuf/any.proto create mode 100644 benchmarks/apps/echo_service/google/protobuf/timestamp.proto create mode 100644 benchmarks/apps/echo_service/google/type/money.proto create mode 100644 benchmarks/apps/empty.ru create mode 100644 benchmarks/apps/full_hijack.ru create mode 100644 benchmarks/apps/hello_world.ru create mode 100644 benchmarks/apps/large.ru create mode 100644 benchmarks/apps/many_small_delay.ru create mode 100644 benchmarks/apps/medium.ru create mode 100644 benchmarks/apps/public/image.png create mode 100644 benchmarks/apps/public/index.html create mode 100644 benchmarks/apps/sinatra.ru create mode 100644 benchmarks/apps/small.ru create mode 100644 benchmarks/apps/static.ru create mode 100644 benchmarks/apps/streaming_response_large.ru create mode 100644 benchmarks/apps/streaming_response_small.ru create mode 100644 benchmarks/flamegraph.svg create mode 100644 benchmarks/grpc_server.rb create mode 100644 benchmarks/lib/benchmark_case.rb create mode 100644 benchmarks/lib/server.rb create mode 100644 benchmarks/lib/util.rb create mode 100644 benchmarks/rack_bench.rb create mode 100644 benchmarks/server_configurations/h2o.conf create mode 100644 benchmarks/server_configurations/itsi.rb create mode 100644 benchmarks/server_configurations/nginx.conf create mode 100644 benchmarks/servers.rb create mode 100644 benchmarks/test_cases/framework/sinatra_get.rb create mode 100644 benchmarks/test_cases/framework/sinatra_post.rb create mode 100644 benchmarks/test_cases/grpc/echo.rb create mode 100644 benchmarks/test_cases/grpc/echo_bidirectional.rb create mode 100644 benchmarks/test_cases/grpc/echo_collect.rb create mode 100644 benchmarks/test_cases/grpc/echo_stream.rb create mode 100644 benchmarks/test_cases/grpc/process_payment.rb create mode 100644 benchmarks/test_cases/grpc/server_configurations/itsi.rb create mode 100644 benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb create mode 100644 benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb create mode 100644 benchmarks/test_cases/response_size/empty_response.rb create mode 100644 benchmarks/test_cases/response_size/response_size_large.rb create mode 100644 benchmarks/test_cases/response_size/response_size_medium.rb create mode 100644 benchmarks/test_cases/response_size/response_size_small.rb create mode 100644 benchmarks/test_cases/static_file/server_configurations/itsi.rb create mode 100644 benchmarks/test_cases/static_file/static_dynamic_mixed.rb create mode 100644 benchmarks/test_cases/static_file/static_large.rb create mode 100644 benchmarks/test_cases/static_file/static_small.rb create mode 100644 benchmarks/test_cases/streaming_response/chunked.rb create mode 100644 benchmarks/test_cases/streaming_response/full_hijack.rb create mode 100644 benchmarks/test_cases/streaming_response/streaming_response_large.rb create mode 100644 benchmarks/test_cases/streaming_response/streaming_response_small.rb create mode 100644 benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb create mode 100644 benchmarks/test_cases/throughput/hello_world.rb create mode 100644 gems/server/lib/itsi/rack_env_pool.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 37917b88..bf0480d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [0.2.17] - Unreleased +- Add 5 threads by default in rack/handler +- Reserve header size ahead of time in rack interface +- Avoid intermediate array allocation when populating Rack env headers. +- Rewrite synchronous thread worker to avoid excessive GVL acquisition +- Revert to default write_ev behaviour for http1 +- Switch to service_fn from service struct to avoid one additional pinned future +- Worker pinning accepts ruby workers too +- Fixed ordering incomaptibility in etag forwarding from static file server +- Added embedded benchmark suite + ## [0.2.16] - 2025-05-02 - Optimized static error responses - Optimized rate limit middleware diff --git a/Cargo.lock b/Cargo.lock index 76796d73..521027d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,28 @@ dependencies = [ "zstd-safe", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -1644,7 +1666,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "itsi-scheduler" -version = "0.2.16" +version = "0.2.17" dependencies = [ "bytes", "derive_more", @@ -1662,11 +1684,12 @@ dependencies = [ [[package]] name = "itsi-server" -version = "0.2.16" +version = "0.2.17" dependencies = [ "argon2", "async-channel", "async-compression", + "async-stream", "async-trait", "base64 0.22.1", "bcrypt", @@ -1713,6 +1736,7 @@ dependencies = [ "serde_magnus", "sha-crypt", "sha2", + "smallvec", "socket2", "sysinfo", "tempfile", diff --git a/Gemfile.lock b/Gemfile.lock index fa660d67..3b727690 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,20 +1,20 @@ PATH remote: . specs: - itsi (0.2.16) - itsi-scheduler (~> 0.2.16) - itsi-server (~> 0.2.16) + itsi (0.2.17) + itsi-scheduler (~> 0.2.17) + itsi-server (~> 0.2.17) PATH remote: gems/scheduler specs: - itsi-scheduler (0.2.16) + itsi-scheduler (0.2.17) rb_sys (~> 0.9.91) PATH remote: gems/server specs: - itsi-server (0.2.16) + itsi-server (0.2.17) json (~> 2) prism (~> 1.4) rack (>= 1.6) diff --git a/benchmarks/Gemfile b/benchmarks/Gemfile new file mode 100644 index 00000000..b809d96b --- /dev/null +++ b/benchmarks/Gemfile @@ -0,0 +1,13 @@ +source 'https://rubygems.org' +gem 'debug' +gem 'agoo' +gem 'falcon' +gem 'iodine' +gem 'itsi-server', path: '../gems/server' +gem 'itsi-scheduler', path: '../gems/scheduler' +gem 'puma' +gem 'unicorn' +gem 'paint' +gem 'sinatra' +gem 'sys-proctable' +gem 'grpc' diff --git a/benchmarks/Gemfile.lock b/benchmarks/Gemfile.lock new file mode 100644 index 00000000..86088db4 --- /dev/null +++ b/benchmarks/Gemfile.lock @@ -0,0 +1,182 @@ +PATH + remote: ../gems/scheduler + specs: + itsi-scheduler (0.2.17) + rb_sys (~> 0.9.91) + +PATH + remote: ../gems/server + specs: + itsi-server (0.2.17) + json (~> 2) + prism (~> 1.4) + rack (>= 1.6) + rb_sys (~> 0.9.91) + +GEM + remote: https://rubygems.org/ + specs: + agoo (2.15.13) + async (2.24.0) + console (~> 1.29) + fiber-annotation + io-event (~> 1.9) + metrics (~> 0.12) + traces (~> 0.15) + async-container (0.24.0) + async (~> 2.22) + async-container-supervisor (0.5.1) + async-container (~> 0.22) + async-service + io-endpoint + memory-leak (~> 0.5) + async-http (0.87.0) + async (>= 2.10.2) + async-pool (~> 0.9) + io-endpoint (~> 0.14) + io-stream (~> 0.6) + metrics (~> 0.12) + protocol-http (~> 0.49) + protocol-http1 (~> 0.30) + protocol-http2 (~> 0.22) + traces (~> 0.10) + async-http-cache (0.4.5) + async-http (~> 0.56) + async-pool (0.10.3) + async (>= 1.25) + async-service (0.13.0) + async + async-container (~> 0.16) + base64 (0.2.0) + bigdecimal (3.1.9) + console (1.30.2) + fiber-annotation + fiber-local (~> 1.1) + json + date (3.4.1) + debug (1.10.0) + irb (~> 1.10) + reline (>= 0.3.8) + falcon (0.51.1) + async + async-container (~> 0.20) + async-container-supervisor (~> 0.5.0) + async-http (~> 0.75) + async-http-cache (~> 0.4) + async-service (~> 0.10) + bundler + localhost (~> 1.1) + openssl (~> 3.0) + protocol-http (~> 0.31) + protocol-rack (~> 0.7) + samovar (~> 2.3) + ffi (1.17.2) + ffi (1.17.2-arm64-darwin) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.1) + google-protobuf (4.30.2-arm64-darwin) + bigdecimal + rake (>= 13) + googleapis-common-protos-types (1.19.0) + google-protobuf (>= 3.18, < 5.a) + grpc (1.71.0-arm64-darwin) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + io-console (0.8.0) + io-endpoint (0.15.2) + io-event (1.10.0) + io-stream (0.6.1) + iodine (0.7.58) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.11.3) + kgio (2.11.4) + localhost (1.5.0) + logger (1.7.0) + mapping (1.1.3) + memory-leak (0.5.2) + metrics (0.12.2) + mustermann (3.0.3) + ruby2_keywords (~> 0.0.1) + nio4r (2.7.4) + openssl (3.3.0) + paint (2.3.0) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + protocol-hpack (1.5.1) + protocol-http (0.50.1) + protocol-http1 (0.34.0) + protocol-http (~> 0.22) + protocol-http2 (0.22.1) + protocol-hpack (~> 1.4) + protocol-http (~> 0.47) + protocol-rack (0.12.0) + protocol-http (~> 0.43) + rack (>= 1.0) + psych (5.2.4) + date + stringio + puma (6.6.0) + nio4r (~> 2.0) + rack (3.1.14) + rack-protection (4.1.1) + base64 (>= 0.1.0) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) + rack-session (2.1.0) + base64 (>= 0.1.0) + rack (>= 3.0.0) + raindrops (0.20.1) + rake (13.2.1) + rake-compiler-dock (1.9.1) + rb_sys (0.9.111) + rake-compiler-dock (= 1.9.1) + rdoc (6.13.1) + psych (>= 4.0.0) + reline (0.6.1) + io-console (~> 0.5) + ruby2_keywords (0.0.5) + samovar (2.3.0) + console (~> 1.0) + mapping (~> 1.0) + sinatra (4.1.1) + logger (>= 1.6.0) + mustermann (~> 3.0) + rack (>= 3.0.0, < 4) + rack-protection (= 4.1.1) + rack-session (>= 2.0.0, < 3) + tilt (~> 2.0) + stringio (3.1.7) + sys-proctable (1.3.0) + ffi (~> 1.1) + tilt (2.6.0) + traces (0.15.2) + unicorn (6.1.0) + kgio (~> 2.6) + raindrops (~> 0.7) + +PLATFORMS + arm64-darwin-23 + +DEPENDENCIES + agoo + debug + falcon + grpc + iodine + itsi-scheduler! + itsi-server! + paint + puma + sinatra + sys-proctable + unicorn + +BUNDLED WITH + 2.6.7 diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..1f8f47b0 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,19 @@ +# Rack Server Benchmarks + +A systematic benchmarking suite for Ruby Rack-compatible servers. + +## Targets + +- Itsi +- Puma +- Unicorn +- Agoo +- Iodine +- Falcon + +## How to Run + +```bash +bundle install +bundle exec ruby rack_bench +``` diff --git a/benchmarks/apps/big_delay.ru b/benchmarks/apps/big_delay.ru new file mode 100644 index 00000000..39a99571 --- /dev/null +++ b/benchmarks/apps/big_delay.ru @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +run( + proc do + sleep 1 + [200, { 'content-type' => 'text/plain' }, ['hello world']] + end +) diff --git a/benchmarks/apps/chunked.ru b/benchmarks/apps/chunked.ru new file mode 100644 index 00000000..4b2be250 --- /dev/null +++ b/benchmarks/apps/chunked.ru @@ -0,0 +1,14 @@ +run Proc.new { |env| + body = Enumerator.new do |yielder| + 5.times do |i| + yielder << "Chunk #{i + 1}\n" + sleep 0.001 + end + end + + [ + 200, + { 'Content-Type' => 'text/plain' }, + body + ] +} diff --git a/benchmarks/apps/echo_service/echo.proto b/benchmarks/apps/echo_service/echo.proto new file mode 100644 index 00000000..54886b6c --- /dev/null +++ b/benchmarks/apps/echo_service/echo.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package echo; + +import "google/type/money.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/any.proto"; + +service EchoService { + // Simple unary method + rpc Echo(EchoRequest) returns (EchoResponse) {} + + // Server streaming method + rpc EchoStream(EchoRequest) returns (stream EchoResponse) {} + + // Client streaming method + rpc EchoCollect(stream EchoRequest) returns (EchoResponse) {} + + // Bidirectional streaming method + rpc EchoBidirectional(stream EchoRequest) returns (stream EchoResponse) {} + + // New endpoint demonstrating Google's common protobuf types + rpc ProcessPayment(PaymentRequest) returns (PaymentResponse) {} +} + +message EchoRequest { + string message = 1; +} + +message EchoResponse { + string message = 1; + int32 count = 2; +} + +// New messages demonstrating Google's common protobuf types +message PaymentRequest { + string customer_id = 1; + google.type.Money amount = 2; + google.protobuf.Timestamp payment_time = 3; + // Optional field using google.protobuf.Any for additional metadata + google.protobuf.Any metadata = 4; +} + +message PaymentResponse { + string transaction_id = 1; + google.protobuf.Timestamp processed_at = 2; + PaymentStatus status = 3; + // Optional error message + optional string error_message = 4; +} + +enum PaymentStatus { + PAYMENT_STATUS_UNSPECIFIED = 0; + PAYMENT_STATUS_SUCCESS = 1; + PAYMENT_STATUS_FAILED = 2; + PAYMENT_STATUS_PENDING = 3; +} \ No newline at end of file diff --git a/benchmarks/apps/echo_service/echo_service.rb b/benchmarks/apps/echo_service/echo_service.rb new file mode 100644 index 00000000..3781d6ad --- /dev/null +++ b/benchmarks/apps/echo_service/echo_service.rb @@ -0,0 +1,128 @@ +# Implementation of the Echo service +require 'grpc' +require 'google/protobuf' +require 'google/type/money_pb' +require 'google/protobuf/timestamp_pb' +require 'google/protobuf/any_pb' + +descriptor_data = "\n\necho.proto\x12\x04\x65\x63ho\x1a\x17google/type/money.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\"\x1e\n\x0b\x45\x63hoRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\".\n\x0c\x45\x63hoResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\"\xa3\x01\n\x0ePaymentRequest\x12\x13\n\x0b\x63ustomer_id\x18\x01 \x01(\t\x12\"\n\x06\x61mount\x18\x02 \x01(\x0b\x32\x12.google.type.Money\x12\x30\n\x0cpayment_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x08metadata\x18\x04 \x01(\x0b\x32\x14.google.protobuf.Any\"\xae\x01\n\x0fPaymentResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x30\n\x0cprocessed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.echo.PaymentStatus\x12\x1a\n\rerror_message\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_error_message*\x82\x01\n\rPaymentStatus\x12\x1e\n\x1aPAYMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16PAYMENT_STATUS_SUCCESS\x10\x01\x12\x19\n\x15PAYMENT_STATUS_FAILED\x10\x02\x12\x1a\n\x16PAYMENT_STATUS_PENDING\x10\x03\x32\xb4\x02\n\x0b\x45\x63hoService\x12/\n\x04\x45\x63ho\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x12\x37\n\nEchoStream\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x30\x01\x12\x38\n\x0b\x45\x63hoCollect\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x12@\n\x11\x45\x63hoBidirectional\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x30\x01\x12?\n\x0eProcessPayment\x12\x14.echo.PaymentRequest\x1a\x15.echo.PaymentResponse\"\x00\x62\x06proto3" + +pool = Google::Protobuf::DescriptorPool.generated_pool +pool.add_serialized_file(descriptor_data) + +module Echo + EchoRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.EchoRequest').msgclass + EchoResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.EchoResponse').msgclass + PaymentRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.PaymentRequest').msgclass + PaymentResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.PaymentResponse').msgclass + PaymentStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.PaymentStatus').enummodule +end + +module Echo + module EchoService + class Service + include ::GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'echo.EchoService' + + # Simple unary method + rpc :Echo, ::Echo::EchoRequest, ::Echo::EchoResponse + # Server streaming method + rpc :EchoStream, ::Echo::EchoRequest, stream(::Echo::EchoResponse) + # Client streaming method + rpc :EchoCollect, stream(::Echo::EchoRequest), ::Echo::EchoResponse + # Bidirectional streaming method + rpc :EchoBidirectional, stream(::Echo::EchoRequest), stream(::Echo::EchoResponse) + # Payment processing method + rpc :ProcessPayment, ::Echo::PaymentRequest, ::Echo::PaymentResponse + end + + Stub = Service.rpc_stub_class + end +end + +class EchoServiceImpl < Echo::EchoService::Service + def echo(request, _call) + @counter ||= 1 + # sleep 0.05 + Echo::EchoResponse.new(message: "Echo: #{request.message}", count: @counter += 1) + end + + def echo_stream(request, _call) + # Stream 3 responses back + + 3.times do |i| + response = Echo::EchoResponse.new( + message: "Echo[#{i}]: #{request.message}", + count: i + 1 + ) + yield response + sleep 0.1 + end + end + + def echo_collect(requests, _call) + messages = [] + count = 0 + + # Collect all messages from the client + requests.each do |req| + messages << req.message + count += 1 + end + + # Return a single response with all messages concatenated + Echo::EchoResponse.new( + message: "Collected: #{messages.join(', ')}", + count: count + ) + end + + def echo_bidirectional(requests, _call) + count = 0 + + # For each request received, send a response immediately + thr = Thread.new do + requests.each do |_| + count += 1 + sleep 0.1 + end + end + + Enumerator.new do |e| + 10.times do |i| + e << Echo::EchoResponse.new( + message: "Here's a response\n", + count: count + ) + end + thr.join + end + end + + def process_payment(request, _call) + # Create a response with current timestamp + # + processed_at = Google::Protobuf::Timestamp.new(seconds: Time.now.to_i) + + # Generate a mock transaction ID + transaction_id = "txn_#{Time.now.to_i}_#{rand(1000..9999)}" + + # Simulate payment processing + status = if request.amount.units > 0 + Echo::PaymentStatus::PAYMENT_STATUS_SUCCESS + else + Echo::PaymentStatus::PAYMENT_STATUS_FAILED + end + + # Create the response + Echo::PaymentResponse.new( + transaction_id: transaction_id, + processed_at: processed_at, + status: status, + error_message: status == Echo::PaymentStatus::PAYMENT_STATUS_FAILED ? 'Invalid amount' : nil + ) + end +end diff --git a/benchmarks/apps/echo_service/google/protobuf/any.proto b/benchmarks/apps/echo_service/google/protobuf/any.proto new file mode 100644 index 00000000..eff44e50 --- /dev/null +++ b/benchmarks/apps/echo_service/google/protobuf/any.proto @@ -0,0 +1,162 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/benchmarks/apps/echo_service/google/protobuf/timestamp.proto b/benchmarks/apps/echo_service/google/protobuf/timestamp.proto new file mode 100644 index 00000000..fd0bc07d --- /dev/null +++ b/benchmarks/apps/echo_service/google/protobuf/timestamp.proto @@ -0,0 +1,144 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/benchmarks/apps/echo_service/google/type/money.proto b/benchmarks/apps/echo_service/google/type/money.proto new file mode 100644 index 00000000..f67aa51f --- /dev/null +++ b/benchmarks/apps/echo_service/google/type/money.proto @@ -0,0 +1,42 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.type; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/type/money;money"; +option java_multiple_files = true; +option java_outer_classname = "MoneyProto"; +option java_package = "com.google.type"; +option objc_class_prefix = "GTP"; + +// Represents an amount of money with its currency type. +message Money { + // The three-letter currency code defined in ISO 4217. + string currency_code = 1; + + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + int64 units = 2; + + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + int32 nanos = 3; +} diff --git a/benchmarks/apps/empty.ru b/benchmarks/apps/empty.ru new file mode 100644 index 00000000..b04ccc31 --- /dev/null +++ b/benchmarks/apps/empty.ru @@ -0,0 +1,8 @@ +# frozen_string_literal: true +EMPTY = [200, { }, ['']].freeze + +run( + proc do + EMPTY + end +) diff --git a/benchmarks/apps/full_hijack.ru b/benchmarks/apps/full_hijack.ru new file mode 100644 index 00000000..ac05bd57 --- /dev/null +++ b/benchmarks/apps/full_hijack.ru @@ -0,0 +1,18 @@ +run Proc.new { |env| + if env['rack.hijack'] + io = env['rack.hijack'].call + + # Write raw HTTP/1.1 response headers + io.write "HTTP/1.1 200 OK\r\n" + io.write "Content-Type: text/plain\r\n" + io.write "Connection: close\r\n" + io.write "\r\n" + + # Write the response body + io.write "Hello from full hijack!\n" + + io.close + end + + [-1, {}, []] +} diff --git a/benchmarks/apps/hello_world.ru b/benchmarks/apps/hello_world.ru new file mode 100644 index 00000000..0b739f8a --- /dev/null +++ b/benchmarks/apps/hello_world.ru @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +run( + proc do + [200, { 'content-type' => 'text/plain' }, ['hello world']] + end +) diff --git a/benchmarks/apps/large.ru b/benchmarks/apps/large.ru new file mode 100644 index 00000000..2c7bf8f8 --- /dev/null +++ b/benchmarks/apps/large.ru @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +large = `ruby --help` * 1000 # 3MB + +run( + proc do + [200, {"content-type" => "text/plain"}, [large]] + end +) diff --git a/benchmarks/apps/many_small_delay.ru b/benchmarks/apps/many_small_delay.ru new file mode 100644 index 00000000..330f7d78 --- /dev/null +++ b/benchmarks/apps/many_small_delay.ru @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +run( + proc do + 100.times do + sleep 0.01 + end + [200, { 'content-type' => 'text/plain' }, ['hello world']] + end +) diff --git a/benchmarks/apps/medium.ru b/benchmarks/apps/medium.ru new file mode 100644 index 00000000..dd94ad62 --- /dev/null +++ b/benchmarks/apps/medium.ru @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +medium = `ruby --help` * 100 # 300kb + +run( + proc do + [200, {"content-type" => "text/plain"}, [medium]] + end +) diff --git a/benchmarks/apps/public/image.png b/benchmarks/apps/public/image.png new file mode 100644 index 0000000000000000000000000000000000000000..5c0cfdfeb0a2acb9954f6b39a52769b44574a89d GIT binary patch literal 9230 zcmbVybyyrpxA)-g?gS6+46Z?fLx2H->jYv)ZI{#pS~gd8Iw;0 z(MDQT8USca#JDpLu z7(CJ6P4TJ!#{5YU3gQ0oSP1{OwPhi~-@MyzSf?37&l5p&R@8?802l&JfH#1r<2YW||h?h9s9~L4{{BJQA z9qk_`Zg%2yAXN=oStnO3T0u^3PHs90bXr}|yu2Jw790?7M>jJs4o3+6pHBYcN6rdj;R<$k13Nj={`PBT z?&R(!PDl4U(BJLPJl(+7|3q?x{8iRdfn2{eTs)lIT>lfy$_xCzz_z&b?;(wz+|LFe-^WVaM1Jqr? zR!=|VA9?ZomDYdD{>%P%bs`#IFDrYH9N59i5%Rkh0e%57uK!W{SiE6GW}@`5`w!ti=E*Rsl&f)b4Q77z>wM#N;t}U5Y_U21YM6e9ObPyZmUfdyv96 zGG=*}nKZxTY<=8JV^{le-q^SP9w_>f@pT9S8a4wkXvbeU9E_3)YOehcGsldSuYot$&|>hcYiDaKEsgn~U_(_gB~Q@vq%{$b_)( zOJ(K#j$@JsKAAP8(`70jXazxQoqal2F z$Tl`M>d0H;+o^=;#ShDnNe6O)H?Ii#{BJJ>Qr^CeySuxabuehxhHSmQ&qvA+ZN*$( zx^G?^`}C=d`-id59%FNB>qLc^K3L*>%!E`gCnv|Q7{kyZr2sc-JgV+ePIT%;Jn#@Y zTLoD5yO>tEF9VYw*^J<1AZA4zRl^6=srKt>vG7Z8Z48fDiWO6lwjtn3anXr9-ktC1 z(?=0$@s&*``AsEBM7?b|)GjossuqNm@wZfa^W@4!WpEsApPgGY~NFuE~&3B763JSmDL>PqXt2z-Y}%0}*?UK7RR zve{pwE^)o>D;t6NL4c1Bvc6joSZ9_L4a;0=w$a2WBpWHFipPqtYO2yQD_rm%!6cJJ z$H35eYU913D0=ba0s+^n9}E1!HpM5x9XPvD3U&d$VY?9nXP-T0@X-=Q12e)!*>GDy z)99${IX|(mx><%NY1-@-IPr&LNPjIu5e%>|xPF*_`8B1%AY>!n~m~5t3GEu?I zOE1_|N=Hwx+F~f7viPI#T%=(uDGsM0Ie!S#o-(zNWA2y0^&m;%=X=tq-^&rPch_5u z!-EM+=19II&z7m~xA^`FmhLq@#I^k%^dKy}_YhH5)80^^wUtg<70{&^3=50y zVpOI+QOoDmf3~{y)90&E^4Xbd3wV~P#e4vLE6;Nkt*pmPOpDY;f?Uk&q-82wyhoiJ zdMV5y%u##not5QpU3e=xxvLCY9;A%%19P*l7p0L#h)EQvlOt{QMA4#(C#*|!cf0sn zS^Zq2ASYj~t*@6HcKfOcsSqfyQJxKhUMz{|7_SaP)~UqrFJ|?%)z0AA^y>K4b@MTK z?0A?+drf<|LrjtKq96JF$-)wCXFv7%^(Hmh&JD(Lbtzt1`rR)!T9DQ4N!m`8o>1ul zX34oECq_qa5smVqm6*<3{8;$k;DYCH*3iC=U| zsvMJ^o>o=BVOU1(d*G>R3#dFG=4;~p7Dp;#4p9P+%yjMfJB2LeA{%5Dgd@mCVrn;Y z73`3ifWDKZ3yp=AQY3BaRDRSV;#Uw5SUWY!cC9snIyqd2y+)t8+Wk$lptket!gWy8Ty+2B5+YQF-jE)Zu%2&6% z3!jZfhGqgh7!n$w^HYJ_$qK{vk}ZG$fVe)RU&J-PTKC0F56$D1!qa#>l)r9tMS#$% zVoZsu4Sn!uB6oLp9}Gr7iEFW2KqBF|ffIOXhc7S~<{X3P$wz&-S;j#w%}{V(PR?U4 z2bZlcpzAOaR92qv^O6iZ(ehI|#Qw(_(&Z zt`!!GipOdi<`#eCsD+V-x=u;T{UQG8YF!mO?u0D02(0ro{*bz>mzI)UpH(7pMC#aG z*n^nv*)!1xwQeLU(6~o=>I_%Lb(kl^(s>2=Xtg8;J6!I5=yjVH3?I=suldI&FJsEu z5uJbKWLU|2Z$)aw!p|3k5}K=xJ)d_9!+2EoE9TjTYi~~R;#8od>fo*pOk22mP{atD zJCm)Mis7E3D{X0%ag=YVG1cp~d;K}{t7C_%q1fO=o@i+Q+?>HnKiq`bVQeZ9OLpVV zOfL$v!$u5xN#d^u$HYa$3>?f{POp4?MCQ^F5pk$Q*yY-z58r%K2q_PT9U(%8A+xGN z!dpl!bXrRZgMAMeU@yCe)(Agb3@kPR%tw42Dqn(fh2%`mhvY^C?gA*v>0pbUNpskA z0uKUGd#aKFOXh~C%u<6{==v0x$|?Zr>bNNCaZpHWxlY4FSFjOjzuJs9^n!&hZftoB z4+#m0|L53#eFtZd`*Ypp?Yn2>r>81g-=(fH+sO1x#MQ%G`>=$_TU!+NlzlpFr$V0Q zN*tHbMc4=RH*b!SS~WJ!{YMs)Q&O6p$1-{B;Z&VI{!G7^%P#cNS;Sh{^tVIaKi!DnuI)s_}#aJ^Bo_xSf`mr1|ArP>yyTk+>f)G+j*p8Flznege=2k?;D*~cSY zBgw|%5KEf((L0hn*n*17_j>P$eT7S0ZZ8iDt9{2`Z^mAJuLEzDsj9vIc|DP*Ro1~S zHAU+uEh});b?cbGEUkvq5F$2b-T2MQErGs86-&C{>Su8=oq)#yX<_D2mcG?+CUf~Y_QJmGKM79e&wcG$FvR&55!ZgX3`M6$c0TeWAD^O={Djf|eEuHBuI;Abb^y_8p z;}W6b5~;M37vr<=O!BB0(JPR#EWSu?O#7&(`FlVgM5_quW@%GyqNfenR8UNy zdd3^un(KeLGy)8~#ZxiTnjC60HVAuH`hs=#@KpU_PpPr(AWQse2jsBakjk(38tS<% z6t7dQO9nKwUUgr{LvF2O?50S-7BUyGc({8^+VL4vl0ZC$#Gxn-YFzX z;9wLz6h%_M?Csg!!J}RZ{B}e&fIAZK#4r11!mg<0 zjDJXQ*AjIu5-4C)mp|dFMX5hxcu#e@YjrJTk z&6_#@Ep;R%i^Z}}$mPu%0<|}bp+|vRdslV7k=3$SUp#P~M+9VOD2nHHAlGyu^R_Xb zM&DO>zoxlMAyd0dfh zBlaxjhsDIiOg4yDulgQ~TMYE!prE@TDvT+Vd@&L<#GuArw8gV!LsjyOb8t?0Gox}5 zjjizwnu$B+^-N}IL4Ne|rW2h*3TX^uqR8O+1Cr zna*pq3EBar!><$%btfD#XB$w4St0wy!n$0(pM$D12u9Zj=5Vi<7{ z!<)nP*oh(kI~O3JiQx8;beXHNx-MWQgz$QS1DBH?vTmG{m-q87nt@ZB>4x>NeyS1vyYPR-%jvF^h;6S+zk=rFk4Q`MDnrXUF-7Ir76xH|H znJ6wtD$K_(swJyJgS}0~rVeU|Cv?O4^xjNn z_=a!Zz3=+}C) z&0>~|LZEnbhPE1zb~RJriHTohwnQF8a>jHk zL#UckKcbHm#s;$LOHK{lWuYEy6t&xtDFTf=%c}o)`Grt%Ay!)Hj4sYeG`o)-A2a=0 zx6bb)u}mjvM~svI^-jiMBu&8ZQLijtR;{wA2OnZT@xVLa{u-#>`n8~}k@rp-dgRgP znNsDsgQaF7Y~n(zU#*BC@We&wdG-68FTVA$QMXDxo1i|p z<}A!ibW8p~n1F>~?HHPs&J_8Jk;(!gu`L@Yq6{Z$L_KP!0AzpsX6Tfiq1OE>o{e&M z<@#EUb7n@coK9|z5KG~fp=rhLnuTvdB01Q5l!9_eDkCoSSt$M1&%O)Mqqfl6fgl08 zhY-Tll0XZSe5@nxmAIT5kO4a_6<|3}L@S zQVAH8ot}Oyl5g0B8F)9s%znIUZed_B_&RJ)1ZMbL(ZTFI;YKA#P_aWD6#NA46#__PMjb%f81SkrSc#b(~v+c-ZeJB45Zu4)!d(-G&jJbbHG88zMjh z7Po1^@U`on!;T4LqGfoY9j{>4J|OEx-s~%z0)JgTSdc%L;lNSM5WW4D;C_S9WbOKk zSYonf>v3MwR8kBQPX5(^TL(&nCTzeP5sM;(w6vSz9DWy05TdJ(*@lU|t{_fT<7unx z2e}^&sdbG)Q1~1nr-ZggHRXtVF7XNwuiMdqjzvY&8earPk+dcR^_FWN?L*%SVKuYPy7aBb7gS+yp zLt!Z)vz-8(^m-^BQL3^hB5d-zsMZvpD>Kz1)*3#? z?~|wN(#9S|>)tV^&UO0vV{QgtDsz^Ou7!1~<~+t>&6tj^4=Wte-PsmRwQ&_NG!w;b zfX5j)3N+HYdfTs5b;!%(w2?9BW(NNLCb)sox#tE~ndoWFI=G+k?3!)!U2v?bI}lC5 ze?QyLhi#uk4#ae^Ie>9r9k^vU-f?IKS*UfTgO7gMav%6@^Br3H8Ia^2#y~=oo%JyK zrMXRqlWuurZA`LmW=kb`?nnuM7LmxJ-lS12I+27wU>S;6Bip2;9I4Q-qO}(OX@5Q% z6tB2=)wPz9U8%K~%A3z{9d(#8p5CHh+~UN3Fz9?U*BmSgTn|Ku68L#oUREez{biKN-xm0g3%N1FXt zm3l_*Ij`5Pe-XxLLr6k-*le4V3FLo! zq?dO1P={*?q%`(@56`yq#%uM2_I-b2h+j_LLZ!so)^l9gZckdsd7X*5g)M8kR@FJi zneTXxP`%6bnF%X9+pt_*E{TH+56JK}B7XHgyR0bdaO-!3*CSfME{&Ck!>{4H+>Ujm zk^y47$X|HV#7>D(lbxv|t8-{d%W;4No|cC&e3>7x@L2foFwEBjy<&8vH|c|6{N(aO z+h5FWZIFlw@XUDk>awz3M=q4b$jJQfKs96iB;7~H1f`aqs9{kosoV4l*av+!2sVc+ z9lwzLdb8t7iD(2%?<60uCqrMncrg>#_PH>FDop0ijfnm$e&!$vSAkw+uHX&-WC)Vy z$a-|UvBMTVJ&Uo|yIyxJ(=NlOi7fF<4jKJ~hZ@w>-%~yg`q3)kaCD%Bj<{4=nmLia z3!^jZy#D5c-jgZhS=ZUuPy}!`>~AB#>N}Z=c6G~$0I}pqng$~g`=KXWn7Xh>>igj< zqvxY!a0+m>t;4$y5b3cjT4vBEZ*V)~HFyovvFBUb8C&Wc_M+Z0*-3XY~3X#Zr zDhDON&!0nEzpF({MP<%sfJ{Ng!qM*a;a8j|1aEMD`_uWIcHhPsqOf9?4vy;@4a@#ctIHVLkKQ-*`fQ@k2Efflc zIEXtE-Dm(CE!PxcWeRk-sB>ic!P(*XRWg#N4WT{2;8DWP({EiiMQh#M6p zUbWV!BVBq`Qc0cs^ZIfxubeq&db~~?o=Omg@QL*c_v3nQnslOiGD)e=z*$BYT#a8^ zGXsmzh=UbFh9s^J+xQJ9n}eVOY`d!pIPkNWqfq6sFTN@qtNVIW0=MP&l%oMs)`L?Q-S|4A|W}9i8KK z4OxzgmrcjG$u9*`$RBY@!2))Lu906uvvyc;+c^y1R~RjFj1LkYg`E;Gs3ocjT&3q@ zmq{d+>W*Lp_=HH=g0Iz5)6z_zo-4z|pWbtJHX}ks@66mHsTK(cXvf*w0 z?>Q#Ym8KbS#0pQmg~L^tT__JWIy)WWz*SE9{`e3}Zr|5U#SM~^>w@8?~g0Py2=#5XqN3pd+))vKP} zXSv3A`T+r5?kG`dMMLPav-P(#f$vegLJ*?PulhQ))-@`Ni!Gv9TYJyHs>&475QXe_Em$b_Z{Jik*~;*DRu{CfD)D>$gJ2|W_67-iR7 zd|4rC032-pVA^cZYEQPLpd6{r&rDv-x9mE~apmP+)7j3i$~^na)PCz~LsfZSC!Par zSJ>QP=s5`L6W6DOQ9{S=YcS^nJMTnw8qwwWaBK50&o>gP&2vQpX-?#2L3dF}?iu?s zQ)zQwu>$DSw6fbkQm4R~o?OcCZscB_FN#K`(K?1j1lhibCncvn3!!2mhY)dN+6pxu zY%s-@?sR1Ls@3|97^U+2n`2v@Ir!dmZdF^F_RIvP!9Kk`Jp)9qaXRsG%p8B@O^V)_50lxhid?7mw9RXa-!yo_a zm<`_&_X=*X&poC*kJWx7@oPOQB1b1WLK9{>@C8@ef;Ij%5tb-dRIfNwgXu@~bEKta zg#cv(UFq}%F(5KRaq6@}!pmwKZg#(Qt87?inTLMhc?``4p^{}7uZKZSK2zCvTscdq zTP3J)QTDFGV+22Zw&2}dN7jB)1)24GH=2p3&7fDQBZvf9i{ap*Ge6+s>?39(ObIVI z)`@PwCim+eSjAl@{)6L+h!bmFa>z*^+OiRQnX@`c6m%+PYsj-fu)g!G%1;}{tzt@M zKXR9jGXt8iczlj#Y}#61vYjcY^x_<~&%?6)1P*?6uuW*qZ*+7Xhj~G{CBlj7RYh%W za?>)O&B7;mXR~ly#+Li9vq*GfY;0_SB);gbSO;algc@P4Df}q&&N%JVA0~*IL2?v5 zH5`I$#pFDo?KO~wM&a*mh=H^qc9^$18)ZXh*j^iJQb=T{iZL5CQEPm+YCT6srz&72 z8IGo&_}xtN)SJk9KOZZj#{>H~3~C%ntiGNf;0mXc^~tWPFYN}Cnkpw(zr+G=bJh^A z7M4xZ7&lU{@#CHyTrL6(-$d1gcz1SoBJRfRZq&RwYIm<+z@ZRKy*t&QxCb`LQU;Y` zr>C>NzH-{+`Yd36EQv&+sR{-!Nmf=@Z))P>y+yv6{I2|t4CuTFe%g6u8x#wxo%KfE z{e~EUGl|i|kBvD&B{v#v@N93nc@TI)lJk5ferdxwA9}LA4TVf2odVy6B5bUm;PoMZ z{p{)y-n%%018nb@{zky?1{Ow4E5)1B{|M%?XhY-ExtzD-kAz z&9F6*_}q>+nwpra-@FO1(Bsbbu%$+&ww*(jTTaaDQZC}~e!6XZwt_eL9e&cq_P}xK z1lZo6CK#k1!Rmna8eAO3JLZmS28V46CFDRUhw&M2VrvCg`L^Ua7bK6AszvN-Kzx zg5unxWO^Vb@>&MUObjdD*+XrdUU#=47l^pnkK{--yE)ieImhbs7{<$|zzMchrA<&o jhClz;cD!Gj@X6yLz#MCkg-`VNzg(1FsL9pIn1=j6Z)M)q literal 0 HcmV?d00001 diff --git a/benchmarks/apps/public/index.html b/benchmarks/apps/public/index.html new file mode 100644 index 00000000..8fcc4387 --- /dev/null +++ b/benchmarks/apps/public/index.html @@ -0,0 +1,9 @@ + + + + Hello World + + +

Hello, World!

+ + diff --git a/benchmarks/apps/sinatra.ru b/benchmarks/apps/sinatra.ru new file mode 100644 index 00000000..7c585767 --- /dev/null +++ b/benchmarks/apps/sinatra.ru @@ -0,0 +1,13 @@ +require 'sinatra/base' + +class MyApp < Sinatra::Base + get '/get' do + "Hello, world!" + end + + post '/post' do + "You posted: #{request.body.read}" + end +end + +run MyApp diff --git a/benchmarks/apps/small.ru b/benchmarks/apps/small.ru new file mode 100644 index 00000000..26b7ba33 --- /dev/null +++ b/benchmarks/apps/small.ru @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +small = `ruby --help` # ~3kb + +run( + proc do + [200, {'content-type' => 'text/plain'}, [small]] + end +) diff --git a/benchmarks/apps/static.ru b/benchmarks/apps/static.ru new file mode 100644 index 00000000..caf0ef7c --- /dev/null +++ b/benchmarks/apps/static.ru @@ -0,0 +1,12 @@ +# Rack static handler. Fallback for servers that do not have built-in static asset serving. +# * Do have static asset serving: nginx, caddy, h2o, itsi, agoo, iodine +# * Don't have static asset serving: pume, falcon, unicorn +use Rack::Static, urls: ["/public"], root: File.expand_path("public", __dir__), index: 'index.html' + +# Simulate static assets and slower Ruby endpoints working together +endpoint_delay = 0.01 + +run lambda { |env| + sleep endpoint_delay + [200, { "Content-Type" => "text/plain" }, ["Busy Endpoint: #{env['PATH_INFO']}"]] +} diff --git a/benchmarks/apps/streaming_response_large.ru b/benchmarks/apps/streaming_response_large.ru new file mode 100644 index 00000000..c25612bd --- /dev/null +++ b/benchmarks/apps/streaming_response_large.ru @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +chunk_large = 100.times.map{|i| "data #{i}"}.join(', ') << "\n" + +run( + proc do |_env| + [ + 200, {'content-type' => 'text/plain'}, lambda do |stream| + 1000.times do + stream << chunk_large + stream.flush + end + stream.close + end + ] + end +) diff --git a/benchmarks/apps/streaming_response_small.ru b/benchmarks/apps/streaming_response_small.ru new file mode 100644 index 00000000..2ace4281 --- /dev/null +++ b/benchmarks/apps/streaming_response_small.ru @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +chunk_small = 10.times.map{|i| "data #{i}"}.join(', ') << "\n" + +run( + proc do |_env| + [ + 200, {'content-type' => 'text/plain'}, lambda do |stream| + 5.times do + stream << chunk_small + stream.flush + end + stream.close + end + ] + end +) diff --git a/benchmarks/flamegraph.svg b/benchmarks/flamegraph.svg new file mode 100644 index 00000000..7e900f87 --- /dev/null +++ b/benchmarks/flamegraph.svg @@ -0,0 +1,491 @@ +Flame Graph Reset ZoomSearch itsi_server.bundle`<tokio::task::coop::with_budget::ResetGuard as core::ops::drop::Drop>::drop (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::driver::Driver::park_timeout (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (1 samples, 0.01%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)libsystem_kernel.dylib`mach_absolute_time (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::wake (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::context::with_scheduler (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::Context::park (19 samples, 0.28%)itsi_server.bundle`tokio::runtime::time::Driver::park_internal (19 samples, 0.28%)itsi_server.bundle`tokio::runtime::io::driver::Driver::turn (18 samples, 0.26%)libsystem_kernel.dylib`kevent (16 samples, 0.24%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (2 samples, 0.03%)libsystem_c.dylib`clock_gettime (2 samples, 0.03%)libsystem_kernel.dylib`mach_absolute_time (1 samples, 0.01%)itsi_server.bundle`std::time::Instant::duration_since (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::context::with_scheduler (5 samples, 0.07%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::wake (10 samples, 0.15%)itsi_server.bundle`tokio::runtime::task::waker::wake_by_val (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::driver::Driver::turn (200 samples, 2.94%)it..libsystem_kernel.dylib`kevent (187 samples, 2.75%)li..itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::wake (1 samples, 0.01%)libsystem_c.dylib`DYLD-STUB$$mach_timebase_info (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (4 samples, 0.06%)libsystem_c.dylib`clock_gettime (4 samples, 0.06%)libsystem_kernel.dylib`mach_absolute_time (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::process (5 samples, 0.07%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::process_at_sharded_time (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::time::wheel::Wheel::poll (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::Context::park_yield (211 samples, 3.10%)its..itsi_server.bundle`tokio::runtime::time::Driver::park_internal (211 samples, 3.10%)its..itsi_server.bundle`tokio::runtime::time::wheel::level::Level::next_expiration (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::Core::next_task (2 samples, 0.03%)itsi_server.bundle`itsi_server::server::serve_strategy::acceptor::Acceptor::serve_connection::_{{closure}}::_{{closure}} (2 samples, 0.03%)itsi_server.bundle`<tokio::runtime::task::core::TaskIdGuard as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`<hyper::server::conn::http1::UpgradeableConnection<I,S> as core::future::future::Future>::poll (1 samples, 0.01%)itsi_server.bundle`<alloc::collections::vec_deque::iter::Iter<T> as core::iter::traits::iterator::Iterator>::fold (2 samples, 0.03%)itsi_server.bundle`<http_body_util::full::Full<D> as http_body::Body>::poll_frame (1 samples, 0.01%)itsi_server.bundle`<hyper::proto::h1::dispatch::Server<S,hyper::body::incoming::Incoming> as hyper::proto::h1::dispatch::Dispatch>::recv_msg (1 samples, 0.01%)itsi_server.bundle`__rdl_alloc (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::services::itsi_http_service::ItsiHttpService::handle_request::{{closure}}> (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::insert (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::maybe_notify (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::uri::Uri> (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_with_capacity (3 samples, 0.04%)itsi_server.bundle`<hyper::proto::h1::dispatch::Server<S,hyper::body::incoming::Incoming> as hyper::proto::h1::dispatch::Dispatch>::recv_msg (15 samples, 0.22%)itsi_server.bundle`http::request::Parts::new (4 samples, 0.06%)itsi_server.bundle`<hyper_util::rt::tokio::TokioSleep as core::future::future::Future>::poll (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after (6 samples, 0.09%)itsi_server.bundle`http::header::name::HdrName::from_bytes (8 samples, 0.12%)itsi_server.bundle`http::header::name::parse_hdr (4 samples, 0.06%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after::_{{closure}} (13 samples, 0.19%)itsi_server.bundle`http::header::name::parse_hdr (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after (5 samples, 0.07%)libsystem_malloc.dylib`szone_malloc_should_clear (5 samples, 0.07%)libsystem_malloc.dylib`small_malloc_should_clear (4 samples, 0.06%)libsystem_malloc.dylib`small_malloc_from_free_list (3 samples, 0.04%)libsystem_malloc.dylib`small_free_list_add_ptr (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libsystem_malloc.dylib`free_small (3 samples, 0.04%)itsi_server.bundle`<itsi_server::server::middleware_stack::middleware::Middleware as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after::_{{closure}} (37 samples, 0.54%)libsystem_platform.dylib`_platform_memmove (4 samples, 0.06%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$pthread_mutex_unlock (3 samples, 0.04%)itsi_server.bundle`__rdl_alloc (1 samples, 0.01%)itsi_server.bundle`event_listener::Event<T>::inner (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$pthread_mutex_lock (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::Inner<T>::notify (43 samples, 0.63%)itsi_server.bundle`event_listener::Task::wake (43 samples, 0.63%)itsi_server.bundle`parking::Inner::unpark (43 samples, 0.63%)libsystem_kernel.dylib`__psynch_cvsignal (43 samples, 0.63%)itsi_server.bundle`async_channel::Sender<T>::try_send (56 samples, 0.82%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::notify (52 samples, 0.76%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (7 samples, 0.10%)libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow (2 samples, 0.03%)libsystem_kernel.dylib`__psynch_mutexwait (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::build::{{closure}}> (4 samples, 0.06%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (1 samples, 0.01%)libsystem_malloc.dylib`free_small (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (3 samples, 0.04%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::server::size_limited_incoming::SizeLimitedIncoming<hyper::body::incoming::Incoming>> (1 samples, 0.01%)itsi_server.bundle`event_listener::Event<T>::inner (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (6 samples, 0.09%)itsi_server.bundle`http::response::Parts::new (1 samples, 0.01%)itsi_server.bundle`http_body_util::combinators::box_body::BoxBody<D,E>::new (9 samples, 0.13%)itsi_server.bundle`regex_automata::dfa::onepass::DFA::try_search_slots (2 samples, 0.03%)itsi_server.bundle`regex_automata::dfa::onepass::DFA::try_search_slots_imp (3 samples, 0.04%)itsi_server.bundle`DYLD-STUB$$bzero (1 samples, 0.01%)itsi_server.bundle`regex_automata::dfa::onepass::Cache::explicit_slots (1 samples, 0.01%)itsi_server.bundle`regex_automata::dfa::onepass::DFA::try_search_slots_imp (14 samples, 0.21%)itsi_server.bundle`<regex_automata::meta::strategy::Core as regex_automata::meta::strategy::Strategy>::search_slots (22 samples, 0.32%)itsi_server.bundle`regex_automata::meta::strategy::Core::search_slots_nofail (17 samples, 0.25%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (1 samples, 0.01%)itsi_server.bundle`regex_automata::meta::regex::Regex::create_captures (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`regex::regex::string::Regex::captures_at (40 samples, 0.59%)libsystem_platform.dylib`_platform_memset (4 samples, 0.06%)itsi_server.bundle`regex_automata::util::captures::Captures::all (1 samples, 0.01%)itsi_server.bundle`<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (3 samples, 0.04%)itsi_server.bundle`regex_automata::util::captures::Captures::get_group_by_name (4 samples, 0.06%)itsi_server.bundle`core::hash::BuildHasher::hash_one (1 samples, 0.01%)libsystem_c.dylib`DYLD-STUB$$mach_absolute_time (1 samples, 0.01%)libsystem_c.dylib`clock_gettime_nsec_np (1 samples, 0.01%)libsystem_kernel.dylib`mach_timebase_info (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (14 samples, 0.21%)libsystem_c.dylib`clock_gettime (14 samples, 0.21%)libsystem_kernel.dylib`mach_absolute_time (12 samples, 0.18%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (18 samples, 0.26%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libsystem_malloc.dylib`DYLD-STUB$$_platform_bzero (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (6 samples, 0.09%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (10 samples, 0.15%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (7 samples, 0.10%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before::_{{closure}} (302 samples, 4.44%)itsi_..libsystem_platform.dylib`_platform_memset (2 samples, 0.03%)libsystem_malloc.dylib`small_malloc_should_clear (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (2 samples, 0.03%)libsystem_malloc.dylib`small_free_list_add_ptr (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before (6 samples, 0.09%)libsystem_malloc.dylib`szone_malloc_should_clear (5 samples, 0.07%)libsystem_malloc.dylib`small_malloc_should_clear (3 samples, 0.04%)libsystem_malloc.dylib`small_malloc_from_free_list (3 samples, 0.04%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (5 samples, 0.07%)itsi_server.bundle`DYLD-STUB$$memcpy (3 samples, 0.04%)itsi_server.bundle`__rdl_alloc (2 samples, 0.03%)itsi_server.bundle`__rdl_dealloc (4 samples, 0.06%)itsi_server.bundle`async_channel::Sender<T>::try_send (1 samples, 0.01%)itsi_server.bundle`regex_automata::util::captures::Captures::get_group_by_name (1 samples, 0.01%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (3 samples, 0.04%)libsystem_kernel.dylib`__ulock_wake (1 samples, 0.01%)libsystem_malloc.dylib`_free (2 samples, 0.03%)libsystem_malloc.dylib`_malloc_zone_malloc (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (6 samples, 0.09%)libsystem_malloc.dylib`free_small (18 samples, 0.26%)libsystem_malloc.dylib`small_free_list_find_by_ptr (6 samples, 0.09%)libsystem_malloc.dylib`nanov2_malloc (6 samples, 0.09%)itsi_server.bundle`<itsi_server::server::middleware_stack::middleware::Middleware as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before::_{{closure}} (455 samples, 6.69%)itsi_serv..libsystem_platform.dylib`_platform_memmove (90 samples, 1.32%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after::_{{closure}} (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before::_{{closure}} (1 samples, 0.01%)itsi_server.bundle`<rand_chacha::chacha::ChaCha12Core as rand_core::block::BlockRngCore>::generate (2 samples, 0.03%)itsi_server.bundle`<regex::regexset::bytes::SetMatchesIter as core::iter::traits::iterator::Iterator>::next (2 samples, 0.03%)itsi_server.bundle`<regex_automata::meta::strategy::Core as regex_automata::meta::strategy::Strategy>::which_overlapping_matches (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$memcpy (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::uri::Uri> (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<hyper_util::rt::tokio::TokioSleep> (1 samples, 0.01%)itsi_server.bundle`http::extensions::Extensions::remove (2 samples, 0.03%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_insert_entry (2 samples, 0.03%)itsi_server.bundle`alloc::raw_vec::finish_grow (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_realloc (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_pointer_size (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_allocate_outlined (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_realloc (8 samples, 0.12%)libsystem_platform.dylib`_platform_memset (2 samples, 0.03%)libsystem_malloc.dylib`_malloc_zone_realloc (17 samples, 0.25%)libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libsystem_malloc.dylib`_realloc (22 samples, 0.32%)libsystem_malloc.dylib`default_zone_realloc (2 samples, 0.03%)itsi_server.bundle`alloc::raw_vec::finish_grow (30 samples, 0.44%)libsystem_malloc.dylib`nanov2_size (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_grow (36 samples, 0.53%)libsystem_malloc.dylib`_realloc (4 samples, 0.06%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (43 samples, 0.63%)libsystem_malloc.dylib`nanov2_try_free_default (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::insert (51 samples, 0.75%)itsi_server.bundle`http::header::name::HdrName::from_static (50 samples, 0.73%)libsystem_platform.dylib`_platform_memmove (4 samples, 0.06%)itsi_server.bundle`http::header::name::StandardHeader::from_bytes (2 samples, 0.03%)itsi_server.bundle`http::header::name::HdrName::from_bytes (6 samples, 0.09%)itsi_server.bundle`http::header::name::parse_hdr (2 samples, 0.03%)libsystem_kernel.dylib`__commpage_gettimeofday_internal (1 samples, 0.01%)libsystem_c.dylib`gettimeofday (29 samples, 0.43%)libsystem_kernel.dylib`mach_absolute_time (28 samples, 0.41%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (31 samples, 0.46%)libsystem_c.dylib`clock_gettime (31 samples, 0.46%)libsystem_kernel.dylib`__commpage_gettimeofday_internal (1 samples, 0.01%)itsi_server.bundle`std::time::SystemTime::now (19 samples, 0.28%)itsi_server.bundle`hyper::common::date::update (52 samples, 0.76%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)itsi_server.bundle`<hyper::proto::h1::role::Server as hyper::proto::h1::Http1Transaction>::parse (2 samples, 0.03%)itsi_server.bundle`<hyper_util::rt::tokio::TokioTimer as hyper::rt::timer::Timer>::sleep_until (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)itsi_server.bundle`<tokio::runtime::time::entry::TimerEntry as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::reregister (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::inner (1 samples, 0.01%)itsi_server.bundle`<std::time::Instant as core::ops::arith::Add<core::time::Duration>>::add (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::driver::Handle::unpark (3 samples, 0.04%)libsystem_kernel.dylib`kevent (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::reregister (7 samples, 0.10%)itsi_server.bundle`tokio::runtime::time::wheel::Wheel::insert (2 samples, 0.03%)itsi_server.bundle`std::time::Instant::duration_since (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::sub_timespec (1 samples, 0.01%)itsi_server.bundle`<tokio::time::sleep::Sleep as core::future::future::Future>::poll (20 samples, 0.29%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::poll_elapsed (18 samples, 0.26%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::reset (10 samples, 0.15%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::inner (6 samples, 0.09%)libdyld.dylib`tlv_get_addr (4 samples, 0.06%)itsi_server.bundle`core::ptr::drop_in_place<hyper_util::rt::tokio::TokioSleep> (9 samples, 0.13%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::clear_entry (8 samples, 0.12%)itsi_server.bundle`tokio::runtime::time::wheel::Wheel::remove (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::BytesMut::advance_unchecked (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::BytesMut::split_to (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_allocate_outlined (4 samples, 0.06%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_clone (2 samples, 0.03%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (2 samples, 0.03%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_append2 (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve (10 samples, 0.15%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (1 samples, 0.01%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (1 samples, 0.01%)itsi_server.bundle`http::header::name::StandardHeader::from_bytes (2 samples, 0.03%)itsi_server.bundle`core::str::converts::from_utf8 (1 samples, 0.01%)itsi_server.bundle`httparse::Request::parse_with_config_and_uninit_headers (12 samples, 0.18%)itsi_server.bundle`httparse::parse_headers_iter_uninit (4 samples, 0.06%)itsi_server.bundle`httparse::Request::parse_with_uninit_headers (13 samples, 0.19%)itsi_server.bundle`httparse::parse_version (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`<hyper::proto::h1::role::Server as hyper::proto::h1::Http1Transaction>::parse (61 samples, 0.90%)libsystem_malloc.dylib`nanov2_malloc (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_append2 (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve (2 samples, 0.03%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (1 samples, 0.01%)itsi_server.bundle`http::method::Method::from_bytes (1 samples, 0.01%)itsi_server.bundle`http::uri::Uri::from_shared (1 samples, 0.01%)itsi_server.bundle`<&mio::net::tcp::stream::TcpStream as std::io::Read>::read (1,081 samples, 15.88%)itsi_server.bundle`<&mio..libsystem_kernel.dylib`__recvfrom (1,080 samples, 15.87%)libsystem_kernel.dylib`_..itsi_server.bundle`<&std::net::tcp::TcpStream as std::io::Read>::read (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (12 samples, 0.18%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (4 samples, 0.06%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (1 samples, 0.01%)itsi_server.bundle`tokio::net::tcp::stream::TcpStream::poll_read_priv (1,100 samples, 16.16%)itsi_server.bundle`tokio:..libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (1,106 samples, 16.25%)itsi_server.bundle`hyper:..itsi_server.bundle`<hyper_util::common::rewind::Rewind<T> as hyper::rt::io::Read>::poll_read (1,105 samples, 16.24%)itsi_server.bundle`<hyper..itsi_server.bundle`<hyper_util::rt::tokio::TokioIo<T> as hyper::rt::io::Read>::poll_read (1,104 samples, 16.22%)itsi_server.bundle`<hyper..itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (3 samples, 0.04%)itsi_server.bundle`hyper::proto::h1::io::ReadStrategy::record (2 samples, 0.03%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::parse (1,187 samples, 17.44%)itsi_server.bundle`hyper::p..libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (1 samples, 0.01%)libsystem_c.dylib`clock_gettime (9 samples, 0.13%)libsystem_kernel.dylib`mach_absolute_time (9 samples, 0.13%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (10 samples, 0.15%)libsystem_c.dylib`clock_gettime_nsec_np (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (3 samples, 0.04%)libsystem_malloc.dylib`DYLD-STUB$$_platform_bzero (2 samples, 0.03%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_malloc (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::poll_read_head (1,254 samples, 18.42%)itsi_server.bundle`hyper::pro..libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`<hyper_util::common::rewind::Rewind<T> as hyper::rt::io::Read>::poll_read (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (4 samples, 0.06%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (4 samples, 0.06%)itsi_server.bundle`<hyper_util::common::rewind::Rewind<T> as hyper::rt::io::Read>::poll_read (15 samples, 0.22%)itsi_server.bundle`<hyper_util::rt::tokio::TokioIo<T> as hyper::rt::io::Read>::poll_read (15 samples, 0.22%)itsi_server.bundle`tokio::net::tcp::stream::TcpStream::poll_read_priv (15 samples, 0.22%)libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<hyper_util::rt::tokio::TokioIo<T> as hyper::rt::io::Read>::poll_read (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (31 samples, 0.46%)libsystem_malloc.dylib`small_free_list_remove_ptr (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::BytesMut::reserve_inner (38 samples, 0.56%)libsystem_malloc.dylib`szone_malloc_should_clear (38 samples, 0.56%)libsystem_malloc.dylib`small_malloc_should_clear (37 samples, 0.54%)libsystem_malloc.dylib`small_malloc_from_free_list (34 samples, 0.50%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::poll_read_keep_alive (60 samples, 0.88%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (57 samples, 0.84%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::WriteBuf<B>::buffer (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::Cursor<alloc::vec::Vec<u8>>::maybe_unshift (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::encode::Encoder::encode_and_end (4 samples, 0.06%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::write_body_and_end (6 samples, 0.09%)itsi_server.bundle`hyper::proto::h1::io::WriteBuf<B>::buffer (1 samples, 0.01%)itsi_server.bundle`<http::header::map::Drain<T> as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`<http::header::map::Drain<T> as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::shared_drop (8 samples, 0.12%)libsystem_malloc.dylib`_nanov2_free (7 samples, 0.10%)itsi_server.bundle`hyper::common::date::extend (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (3 samples, 0.04%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (6 samples, 0.09%)itsi_server.bundle`<hyper::proto::h1::role::Server as hyper::proto::h1::Http1Transaction>::encode (73 samples, 1.07%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`http::header::name::StandardHeader::as_str (4 samples, 0.06%)libsystem_malloc.dylib`_free (12 samples, 0.18%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::write_head (99 samples, 1.45%)libsystem_platform.dylib`_platform_memmove (8 samples, 0.12%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::parse (1 samples, 0.01%)itsi_server.bundle`<&mio::net::tcp::stream::TcpStream as std::io::Write>::write (1,064 samples, 15.63%)itsi_server.bundle`<&mio..libsystem_kernel.dylib`__sendto (1,063 samples, 15.62%)libsystem_kernel.dylib`_..itsi_server.bundle`<&std::net::tcp::TcpStream as std::io::Write>::write (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (2 samples, 0.03%)itsi_server.bundle`tokio::net::tcp::stream::TcpStream::poll_write_priv (1,078 samples, 15.84%)itsi_server.bundle`tokio..itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (5 samples, 0.07%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_flush (1,084 samples, 15.93%)itsi_server.bundle`hyper..itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (1 samples, 0.01%)itsi_server.bundle`<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (3 samples, 0.04%)itsi_server.bundle`<regex_automata::meta::strategy::Core as regex_automata::meta::strategy::Strategy>::which_overlapping_matches (18 samples, 0.26%)itsi_server.bundle`regex_automata::meta::wrappers::HybridEngine::try_which_overlapping_matches (18 samples, 0.26%)itsi_server.bundle`regex_automata::hybrid::search::find_overlapping_fwd (7 samples, 0.10%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (2 samples, 0.03%)itsi_server.bundle`regex_automata::util::search::PatternSet::new (4 samples, 0.06%)libsystem_malloc.dylib`nanov2_calloc (4 samples, 0.06%)libsystem_malloc.dylib`_malloc_zone_calloc (1 samples, 0.01%)itsi_server.bundle`itsi_server::server::middleware_stack::MiddlewareSet::stack_for (39 samples, 0.57%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)itsi_server.bundle`DYLD-STUB$$clock_gettime (1 samples, 0.01%)libsystem_c.dylib`DYLD-STUB$$mach_timebase_info (4 samples, 0.06%)libsystem_kernel.dylib`mach_absolute_time (20 samples, 0.29%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (29 samples, 0.43%)libsystem_c.dylib`clock_gettime (29 samples, 0.43%)libsystem_kernel.dylib`mach_timebase_info (3 samples, 0.04%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 0.01%)libsystem_malloc.dylib`set_tiny_meta_header_in_use (4 samples, 0.06%)libsystem_malloc.dylib`_tiny_check_and_zero_inline_meta_from_freelist (1 samples, 0.01%)itsi_server.bundle`itsi_server::services::itsi_http_service::HttpRequestContext::new (49 samples, 0.72%)libsystem_malloc.dylib`szone_malloc_should_clear (18 samples, 0.26%)libsystem_malloc.dylib`tiny_malloc_should_clear (16 samples, 0.24%)libsystem_malloc.dylib`tiny_malloc_from_free_list (10 samples, 0.15%)libsystem_malloc.dylib`tiny_free_list_add_ptr (2 samples, 0.03%)itsi_server.bundle`regex_automata::util::search::PatternSet::new (2 samples, 0.03%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (9 samples, 0.13%)libsystem_malloc.dylib`DYLD-STUB$$_platform_bzero (1 samples, 0.01%)libsystem_malloc.dylib`_free (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)libsystem_malloc.dylib`_szone_free (2 samples, 0.03%)libsystem_malloc.dylib`free (1 samples, 0.01%)libsystem_malloc.dylib`get_tiny_previous_free_msize (2 samples, 0.03%)libsystem_malloc.dylib`tiny_free_list_add_ptr (4 samples, 0.06%)libsystem_malloc.dylib`free_tiny (21 samples, 0.31%)libsystem_malloc.dylib`tiny_free_no_lock (11 samples, 0.16%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)libsystem_malloc.dylib`set_tiny_meta_header_in_use (10 samples, 0.15%)libsystem_malloc.dylib`_tiny_check_and_zero_inline_meta_from_freelist (2 samples, 0.03%)libsystem_malloc.dylib`szone_malloc_should_clear (27 samples, 0.40%)libsystem_malloc.dylib`tiny_malloc_should_clear (26 samples, 0.38%)libsystem_malloc.dylib`tiny_malloc_from_free_list (14 samples, 0.21%)libsystem_malloc.dylib`tiny_free_list_add_ptr (4 samples, 0.06%)libsystem_platform.dylib`_platform_memmove (11 samples, 0.16%)itsi_server.bundle`hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_catch (3,404 samples, 50.01%)itsi_server.bundle`hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_catchlibsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_malloc.dylib`_free (3 samples, 0.04%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)libsystem_malloc.dylib`malloc (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`<hyper::server::conn::http1::UpgradeableConnection<I,S> as core::future::future::Future>::poll (3,452 samples, 50.72%)itsi_server.bundle`<hyper::server::conn::http1::UpgradeableConnection<I,S> as core:..libsystem_platform.dylib`_platform_memmove (29 samples, 0.43%)itsi_server.bundle`<hyper_util::server::conn::auto::UpgradeableConnection<I,S,E> as core::future::future::Future>::poll (3,460 samples, 50.84%)itsi_server.bundle`<hyper_util::server::conn::auto::UpgradeableConnection<I,S,E> as ..itsi_server.bundle`hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_catch (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::context::thread_rng_n (1 samples, 0.01%)itsi_server.bundle`<tokio::task::coop::RestoreOnPending as core::ops::drop::Drop>::drop (6 samples, 0.09%)itsi_server.bundle`<tokio::task::coop::Coop<F> as core::future::future::Future>::poll (19 samples, 0.28%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (11 samples, 0.16%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (2 samples, 0.03%)itsi_server.bundle`tokio::sync::watch::Receiver<T>::changed::_{{closure}} (26 samples, 0.38%)libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<core::future::poll_fn::PollFn<F> as core::future::future::Future>::poll (3,505 samples, 51.50%)itsi_server.bundle`<core::future::poll_fn::PollFn<F> as core::future::future::Future>..libdyld.dylib`tlv_get_addr (10 samples, 0.15%)itsi_server.bundle`<hyper_util::server::conn::auto::UpgradeableConnection<I,S,E> as core::future::future::Future>::poll (2 samples, 0.03%)itsi_server.bundle`tokio::sync::watch::Receiver<T>::changed::_{{closure}} (1 samples, 0.01%)itsi_server.bundle`itsi_server::server::serve_strategy::acceptor::Acceptor::serve_connection::_{{closure}}::_{{closure}} (3,517 samples, 51.67%)itsi_server.bundle`itsi_server::server::serve_strategy::acceptor::Acceptor::serve_con..libdyld.dylib`tlv_get_addr (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::task::core::Core<T,S>::poll (3,527 samples, 51.82%)itsi_server.bundle`tokio::runtime::task::core::Core<T,S>::polllibdyld.dylib`tlv_get_addr (6 samples, 0.09%)itsi_server.bundle`tokio::runtime::task::core::TaskIdGuard::enter (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::task::harness::Harness<T,S>::poll (3,535 samples, 51.94%)itsi_server.bundle`tokio::runtime::task::harness::Harness<T,S>::pollitsi_server.bundle`tokio::runtime::task::state::State::transition_to_idle (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::CoreGuard::block_on (3,777 samples, 55.50%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::CoreGuard::block_onlibdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`tokio::runtime::scheduler::defer::Defer::is_empty (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::task::raw::RawTask::poll (1 samples, 0.01%)dyld`start (3,784 samples, 55.60%)dyld`startruby`main (3,784 samples, 55.60%)ruby`mainlibruby.3.4.dylib`ruby_run_node (3,784 samples, 55.60%)libruby.3.4.dylib`ruby_run_nodelibruby.3.4.dylib`rb_ec_exec_node (3,784 samples, 55.60%)libruby.3.4.dylib`rb_ec_exec_nodelibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libruby.3.4.dylib`rb_f_load (3,784 samples, 55.60%)libruby.3.4.dylib`rb_f_loadlibruby.3.4.dylib`rb_load_internal (3,784 samples, 55.60%)libruby.3.4.dylib`rb_load_internallibruby.3.4.dylib`load_iseq_eval (3,784 samples, 55.60%)libruby.3.4.dylib`load_iseq_evallibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libruby.3.4.dylib`rb_f_load (3,784 samples, 55.60%)libruby.3.4.dylib`rb_f_loadlibruby.3.4.dylib`rb_load_internal (3,784 samples, 55.60%)libruby.3.4.dylib`rb_load_internallibruby.3.4.dylib`load_iseq_eval (3,784 samples, 55.60%)libruby.3.4.dylib`load_iseq_evallibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libruby.3.4.dylib`rb_f_load (3,784 samples, 55.60%)libruby.3.4.dylib`rb_f_loadlibruby.3.4.dylib`rb_load_internal (3,784 samples, 55.60%)libruby.3.4.dylib`rb_load_internallibruby.3.4.dylib`load_iseq_eval (3,784 samples, 55.60%)libruby.3.4.dylib`load_iseq_evallibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_itsi_server.bundle`itsi_server::init::anon (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::init::anonitsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::start (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::startitsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::build_and_run_strategy (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::build_and_run_strategylibruby.3.4.dylib`rb_nogvl (3,784 samples, 55.60%)libruby.3.4.dylib`rb_nogvlitsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampoline (3,784 samples, 55.60%)itsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampolineitsi_server.bundle`itsi_server::server::serve_strategy::single_mode::SingleMode::run (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::server::serve_strategy::single_mode::SingleMode::runitsi_server.bundle`tokio::runtime::runtime::Runtime::block_on (3,784 samples, 55.60%)itsi_server.bundle`tokio::runtime::runtime::Runtime::block_onitsi_server.bundle`tokio::runtime::context::runtime::enter_runtime (3,784 samples, 55.60%)itsi_server.bundle`tokio::runtime::context::runtime::enter_runtimelibdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<event_listener_strategy::Blocking as event_listener_strategy::Strategy>::poll (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<event_listener::InnerListener<(),alloc::sync::Arc<event_listener::Inner<()>>>> (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::unlock (1 samples, 0.01%)itsi_server.bundle`event_listener::TaskRef::into_task (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::Inner<T>::remove (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::with_inner (4 samples, 0.06%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (1 samples, 0.01%)libsystem_pthread.dylib`pthread_mutex_lock (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (1 samples, 0.01%)libsystem_kernel.dylib`__psynch_cvwait (75 samples, 1.10%)itsi_server.bundle`<event_listener_strategy::Blocking as event_listener_strategy::Strategy>::poll (88 samples, 1.29%)itsi_server.bundle`parking::Inner::park (81 samples, 1.19%)libsystem_pthread.dylib`_pthread_cond_wait (1 samples, 0.01%)libsystem_pthread.dylib`pthread_testcancel (1 samples, 0.01%)itsi_server.bundle`async_channel::Receiver<T>::try_recv (3 samples, 0.04%)libsystem_pthread.dylib`pthread_mutex_unlock (1 samples, 0.01%)itsi_server.bundle`event_listener::Event<T>::listen (1 samples, 0.01%)libsystem_pthread.dylib`pthread_mutex_unlock (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::with_inner (1 samples, 0.01%)itsi_server.bundle`<magnus::value::LazyId as core::ops::deref::Deref>::deref (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::Inner<T>::notify (7 samples, 0.10%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::notify (4 samples, 0.06%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (3 samples, 0.04%)libsystem_pthread.dylib`pthread_mutex_lock (2 samples, 0.03%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::unlock (20 samples, 0.29%)itsi_server.bundle`async_channel::Receiver<T>::try_recv (36 samples, 0.53%)libsystem_pthread.dylib`pthread_mutex_unlock (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::notify (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$clock_gettime (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (12 samples, 0.18%)libsystem_c.dylib`clock_gettime (12 samples, 0.18%)libsystem_c.dylib`gettimeofday (12 samples, 0.18%)libsystem_kernel.dylib`mach_absolute_time (12 samples, 0.18%)itsi_server.bundle`std::time::SystemTime::duration_since (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::sub_timespec (1 samples, 0.01%)itsi_server.bundle`std::time::SystemTime::now (4 samples, 0.06%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_funcallv (1 samples, 0.01%)itsi_server.bundle`magnus::api::Ruby::get (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)libruby.3.4.dylib`rb_current_ec (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (8 samples, 0.12%)libruby.3.4.dylib`rb_current_ec (11 samples, 0.16%)libruby.3.4.dylib`CALLER_SETUP_ARG (1 samples, 0.01%)libruby.3.4.dylib`rb_ec_ary_new_from_values (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_aset (5 samples, 0.07%)libruby.3.4.dylib`rb_hash_bulk_insert (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_opt_getconstant_path (4 samples, 0.06%)libruby.3.4.dylib`vm_call_cfunc_with_frame_ (10 samples, 0.15%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start (1 samples, 0.01%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start_0params_0locals (1 samples, 0.01%)libruby.3.4.dylib`vm_caller_setup_arg_block (1 samples, 0.01%)itsi_server.bundle`itsi_server::init::anon (2 samples, 0.03%)libruby.3.4.dylib`DYLD-STUB$$memset_pattern16 (3 samples, 0.04%)libruby.3.4.dylib`ary_memcpy0 (1 samples, 0.01%)libruby.3.4.dylib`call_cfunc_0 (10 samples, 0.15%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)libruby.3.4.dylib`obj_method (1 samples, 0.01%)libruby.3.4.dylib`ractor_safe_call_cfunc_m1 (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_pop (2 samples, 0.03%)libruby.3.4.dylib`ary_ensure_room_for_push (2 samples, 0.03%)libruby.3.4.dylib`rb_ary_push (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`ary_memcpy0 (4 samples, 0.06%)libruby.3.4.dylib`rb_gc_writebarrier (3 samples, 0.04%)libruby.3.4.dylib`rb_ec_ary_new_from_values (10 samples, 0.15%)libruby.3.4.dylib`newobj_of (5 samples, 0.07%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_free (1 samples, 0.01%)itsi_server.bundle`__rdl_dealloc (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)itsi_server.bundle`<itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::services::itsi_http_service::RequestContextInner> (2 samples, 0.03%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (6 samples, 0.09%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes> (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (2 samples, 0.03%)libsystem_kernel.dylib`__ulock_wait2 (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (2 samples, 0.03%)libsystem_malloc.dylib`small_free_scan_madvise_free (9 samples, 0.13%)libsystem_kernel.dylib`madvise (8 samples, 0.12%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (24 samples, 0.35%)libsystem_malloc.dylib`free_small (21 samples, 0.31%)libsystem_malloc.dylib`small_madvise_free_range_no_lock (5 samples, 0.07%)libsystem_kernel.dylib`madvise (4 samples, 0.06%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (4 samples, 0.06%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (31 samples, 0.46%)libsystem_malloc.dylib`_szone_free (2 samples, 0.03%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (36 samples, 0.53%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (36 samples, 0.53%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (36 samples, 0.53%)libsystem_malloc.dylib`nanov2_madvise_block (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_madvise_block_locked (2 samples, 0.03%)libsystem_kernel.dylib`madvise (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (47 samples, 0.69%)libsystem_platform.dylib`_platform_memset (2 samples, 0.03%)libsystem_malloc.dylib`_free (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (3 samples, 0.04%)libsystem_malloc.dylib`free_tiny (18 samples, 0.26%)libsystem_malloc.dylib`tiny_free_no_lock (16 samples, 0.24%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (6 samples, 0.09%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (82 samples, 1.20%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (85 samples, 1.25%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (106 samples, 1.56%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (11 samples, 0.16%)libruby.3.4.dylib`rb_str_free (3 samples, 0.04%)libruby.3.4.dylib`gc_sweep_plane (137 samples, 2.01%)l..libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (2 samples, 0.03%)libruby.3.4.dylib`gc_sweep_step (142 samples, 2.09%)l..libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (3 samples, 0.04%)libruby.3.4.dylib`gc_continue (144 samples, 2.12%)l..libsystem_kernel.dylib`getrusage (2 samples, 0.03%)libruby.3.4.dylib`each_location (2 samples, 0.03%)libruby.3.4.dylib`gc_mark_maybe_internal (2 samples, 0.03%)libsystem_c.dylib`bsearch (1 samples, 0.01%)libruby.3.4.dylib`cont_mark (3 samples, 0.04%)libruby.3.4.dylib`rb_execution_context_mark (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_mark_machine_context (3 samples, 0.04%)libruby.3.4.dylib`gc_mark_maybe_internal (1 samples, 0.01%)libruby.3.4.dylib`gc_marks_rest (4 samples, 0.06%)libruby.3.4.dylib`thread_mark (1 samples, 0.01%)libruby.3.4.dylib`rb_execution_context_mark (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_vm_stack_values (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_plane (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::services::itsi_http_service::RequestContextInner> (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (1 samples, 0.01%)libsystem_malloc.dylib`free_small (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_find_by_ptr (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (3 samples, 0.04%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (3 samples, 0.04%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (4 samples, 0.06%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (4 samples, 0.06%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (5 samples, 0.07%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (11 samples, 0.16%)libsystem_malloc.dylib`free_tiny (3 samples, 0.04%)libsystem_malloc.dylib`tiny_free_no_lock (2 samples, 0.03%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (12 samples, 0.18%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (1 samples, 0.01%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (22 samples, 0.32%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (5 samples, 0.07%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_plane (62 samples, 0.91%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_step (67 samples, 0.98%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (2 samples, 0.03%)libruby.3.4.dylib`gc_sweep (70 samples, 1.03%)libsystem_kernel.dylib`getrusage (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_children (2 samples, 0.03%)libruby.3.4.dylib`rb_id_table_foreach_values (2 samples, 0.03%)libruby.3.4.dylib`mark_const_table_i (2 samples, 0.03%)libruby.3.4.dylib`gc_mark_maybe_internal (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_roots (5 samples, 0.07%)libruby.3.4.dylib`rb_vm_mark (4 samples, 0.06%)libruby.3.4.dylib`rb_id_table_foreach_values (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_vm_stack_values (3 samples, 0.04%)libruby.3.4.dylib`gc_mark (2 samples, 0.03%)libruby.3.4.dylib`newobj_of (231 samples, 3.39%)lib..libruby.3.4.dylib`newobj_cache_miss (230 samples, 3.38%)lib..libruby.3.4.dylib`gc_start (86 samples, 1.26%)libruby.3.4.dylib`rb_st_foreach (1 samples, 0.01%)libruby.3.4.dylib`st_general_foreach (1 samples, 0.01%)libruby.3.4.dylib`gc_mark_tbl_no_pin_i (1 samples, 0.01%)libruby.3.4.dylib`gc_mark (1 samples, 0.01%)libruby.3.4.dylib`rb_ivar_defined (4 samples, 0.06%)libruby.3.4.dylib`rb_shape_get_iv_index (2 samples, 0.03%)libruby.3.4.dylib`rb_ec_str_resurrect (241 samples, 3.54%)libr..libruby.3.4.dylib`rb_shape_obj_too_complex (2 samples, 0.03%)libruby.3.4.dylib`rb_gc_writebarrier (2 samples, 0.03%)libruby.3.4.dylib`rb_st_update (2 samples, 0.03%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`gc_writebarrier_generational (3 samples, 0.04%)libruby.3.4.dylib`rb_any_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (26 samples, 0.38%)libruby.3.4.dylib`gc_writebarrier_generational (9 samples, 0.13%)libruby.3.4.dylib`any_hash (13 samples, 0.19%)libruby.3.4.dylib`rb_str_hash (3 samples, 0.04%)libruby.3.4.dylib`hash_aset_str_insert (4 samples, 0.06%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_st_update (78 samples, 1.15%)libruby.3.4.dylib`tbl_update_modify (30 samples, 0.44%)libruby.3.4.dylib`rb_hash_aset (148 samples, 2.17%)l..libruby.3.4.dylib`tbl_update (130 samples, 1.91%)l..libruby.3.4.dylib`tbl_update_modify (7 samples, 0.10%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`ar_insert (5 samples, 0.07%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_bulk_insert (10 samples, 0.15%)libruby.3.4.dylib`rb_gc_writebarrier (3 samples, 0.04%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_new_with_size (4 samples, 0.06%)libruby.3.4.dylib`newobj_of (2 samples, 0.03%)libruby.3.4.dylib`rb_int_to_s (2 samples, 0.03%)libruby.3.4.dylib`rb_ivar_defined (2 samples, 0.03%)libruby.3.4.dylib`rb_obj_class (10 samples, 0.15%)libruby.3.4.dylib`rb_str_equal (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_opt_getconstant_path (8 samples, 0.12%)libruby.3.4.dylib`rb_wb_protected_newobj_of (2 samples, 0.03%)libruby.3.4.dylib`tbl_update (1 samples, 0.01%)itsi_server.bundle`<core::result::Result<T,E> as magnus::method::private::ReturnValue>::into_return_value (1 samples, 0.01%)itsi_server.bundle`<magnus::integer::Integer as magnus::try_convert::TryConvert>::try_convert (1 samples, 0.01%)itsi_server.bundle`<magnus::r_string::RString as magnus::try_convert::TryConvert>::try_convert (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_utf8_str_new (3 samples, 0.04%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes> (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (9 samples, 0.13%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (9 samples, 0.13%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (8 samples, 0.12%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (8 samples, 0.12%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (8 samples, 0.12%)libsystem_malloc.dylib`free_small (8 samples, 0.12%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (3 samples, 0.04%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (10 samples, 0.15%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_malloc.dylib`_free (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (2 samples, 0.03%)libsystem_malloc.dylib`free_tiny (7 samples, 0.10%)libsystem_malloc.dylib`tiny_free_no_lock (5 samples, 0.07%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (22 samples, 0.32%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (23 samples, 0.34%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_free (1 samples, 0.01%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (29 samples, 0.43%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (5 samples, 0.07%)libruby.3.4.dylib`gc_sweep_plane (43 samples, 0.63%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)libruby.3.4.dylib`newobj_of (46 samples, 0.68%)libruby.3.4.dylib`newobj_cache_miss (44 samples, 0.65%)libruby.3.4.dylib`gc_continue (44 samples, 0.65%)libruby.3.4.dylib`gc_sweep_step (44 samples, 0.65%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (1 samples, 0.01%)libruby.3.4.dylib`typed_data_alloc (2 samples, 0.03%)libruby.3.4.dylib`rb_get_alloc_func (1 samples, 0.01%)itsi_server.bundle`<core::result::Result<T,E> as magnus::method::private::ReturnValue>::into_return_value (50 samples, 0.73%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (3 samples, 0.04%)itsi_server.bundle`DYLD-STUB$$rb_protect (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_utf8_str_new (1 samples, 0.01%)itsi_server.bundle`core::str::_<impl str>::trim_end_matches (1 samples, 0.01%)itsi_server.bundle`core::str::pattern::StrSearcher::new (2 samples, 0.03%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve (4 samples, 0.06%)itsi_server.bundle`http::header::value::HeaderValue::to_str (2 samples, 0.03%)itsi_server.bundle`itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes::as_value (1 samples, 0.01%)itsi_server.bundle`itsi_server::ruby_types::itsi_body_proxy::ItsiBody::into_value (3 samples, 0.04%)libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libruby.3.4.dylib`rb_enc_raw_set (2 samples, 0.03%)libruby.3.4.dylib`rb_gc_size_allocatable_p (2 samples, 0.03%)itsi_server.bundle`magnus::error::protect::call (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`invoke_block_from_c_bh (1 samples, 0.01%)libruby.3.4.dylib`CALLER_SETUP_ARG (1 samples, 0.01%)libruby.3.4.dylib`rb_st_lookup (3 samples, 0.04%)libruby.3.4.dylib`any_hash (1 samples, 0.01%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`ruby_sip_hash13 (2 samples, 0.03%)libruby.3.4.dylib`rb_hash_aref (13 samples, 0.19%)libruby.3.4.dylib`rb_st_lookup (9 samples, 0.13%)libsystem_platform.dylib`_platform_memcmp (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`any_hash (1 samples, 0.01%)libruby.3.4.dylib`ruby_sip_hash13 (1 samples, 0.01%)libruby.3.4.dylib`hash_aset_str_insert (1 samples, 0.01%)libruby.3.4.dylib`rb_any_cmp (1 samples, 0.01%)libruby.3.4.dylib`rb_str_comparable (1 samples, 0.01%)libruby.3.4.dylib`str_do_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_st_update (8 samples, 0.12%)libsystem_platform.dylib`_platform_memcmp (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_aset (12 samples, 0.18%)libruby.3.4.dylib`tbl_update (11 samples, 0.16%)libruby.3.4.dylib`rb_str_hash_cmp (1 samples, 0.01%)libruby.3.4.dylib`ruby_sip_hash13 (4 samples, 0.06%)libruby.3.4.dylib`rb_vm_exec (49 samples, 0.72%)libruby.3.4.dylib`vm_exec_core (46 samples, 0.68%)libruby.3.4.dylib`rb_st_lookup (10 samples, 0.15%)libruby.3.4.dylib`str_do_hash (2 samples, 0.03%)libruby.3.4.dylib`rb_enc_dummy_p (1 samples, 0.01%)libruby.3.4.dylib`vm_exec_core (2 samples, 0.03%)libruby.3.4.dylib`invoke_block_from_c_bh (58 samples, 0.85%)libsystem_platform.dylib`_setjmp (1 samples, 0.01%)libruby.3.4.dylib`rb_protect (65 samples, 0.96%)libruby.3.4.dylib`rb_yield_values_kw (59 samples, 0.87%)libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)libruby.3.4.dylib`newobj_of (5 samples, 0.07%)libruby.3.4.dylib`str_enc_new (12 samples, 0.18%)libruby.3.4.dylib`rb_current_ec (2 samples, 0.03%)libsystem_platform.dylib`_platform_memmove (10 samples, 0.15%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest::each_header (95 samples, 1.40%)libsystem_platform.dylib`_setjmp (1 samples, 0.01%)itsi_server.bundle`core::str::_<impl str>::trim_end_matches (5 samples, 0.07%)itsi_server.bundle`core::str::pattern::StrSearcher::new (3 samples, 0.04%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest::path (6 samples, 0.09%)itsi_server.bundle`core::str::pattern::StrSearcher::new (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::BodyState> (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::context::with_scheduler (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::task::raw::schedule (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$kevent (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::scheduler::inject::Inject<T>::push (2 samples, 0.03%)itsi_server.bundle`parking_lot::raw_mutex::RawMutex::lock_slow (2 samples, 0.03%)libsystem_kernel.dylib`swtch_pri (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::context::with_scheduler (287 samples, 4.22%)itsi_..libsystem_kernel.dylib`kevent (284 samples, 4.17%)libsy..itsi_server.bundle`tokio::runtime::task::waker::wake_by_val (290 samples, 4.26%)itsi_..libdyld.dylib`tlv_get_addr (3 samples, 0.04%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::send_and_close (326 samples, 4.79%)itsi_s..itsi_server.bundle`tokio::sync::notify::Notify::notify_with_strategy (29 samples, 0.43%)itsi_server.bundle`tokio::sync::notify::notify_locked (28 samples, 0.41%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::set_status (2 samples, 0.03%)itsi_server.bundle`magnus::error::protect::call (7 samples, 0.10%)libruby.3.4.dylib`DYLD-STUB$$_setjmp (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_check_typeddata (3 samples, 0.04%)itsi_server.bundle`magnus::error::protect::call (16 samples, 0.24%)libruby.3.4.dylib`rb_check_typeddata (9 samples, 0.13%)libdyld.dylib`tlv_get_addr (11 samples, 0.16%)libruby.3.4.dylib`rb_check_typeddata (4 samples, 0.06%)libruby.3.4.dylib`rb_protect (65 samples, 0.96%)libruby.3.4.dylib`rb_current_ec (2 samples, 0.03%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (99 samples, 1.45%)libsystem_platform.dylib`_setjmp (10 samples, 0.15%)libruby.3.4.dylib`newobj_of (7 samples, 0.10%)libruby.3.4.dylib`rb_enc_raw_set (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_size_allocatable_p (2 samples, 0.03%)libruby.3.4.dylib`rb_protect (1 samples, 0.01%)libruby.3.4.dylib`rb_utf8_encoding (1 samples, 0.01%)libruby.3.4.dylib`rb_utf8_str_new (6 samples, 0.09%)libruby.3.4.dylib`rb_wb_protected_newobj_of (3 samples, 0.04%)libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (1 samples, 0.01%)libsystem_malloc.dylib`small_free_scan_madvise_free (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (4 samples, 0.06%)libsystem_malloc.dylib`free_small (4 samples, 0.06%)libsystem_malloc.dylib`small_madvise_free_range_no_lock (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (6 samples, 0.09%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (6 samples, 0.09%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (5 samples, 0.07%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.07%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (9 samples, 0.13%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_kernel.dylib`__ulock_wait2 (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 0.01%)libsystem_malloc.dylib`free_tiny (10 samples, 0.15%)libsystem_malloc.dylib`tiny_free_no_lock (8 samples, 0.12%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (3 samples, 0.04%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (21 samples, 0.31%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (2 samples, 0.03%)libsystem_malloc.dylib`_free (2 samples, 0.03%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (26 samples, 0.38%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (32 samples, 0.47%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (11 samples, 0.16%)libruby.3.4.dylib`gc_sweep_plane (56 samples, 0.82%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`newobj_of (64 samples, 0.94%)libruby.3.4.dylib`newobj_cache_miss (57 samples, 0.84%)libruby.3.4.dylib`gc_continue (57 samples, 0.84%)libruby.3.4.dylib`gc_sweep_step (57 samples, 0.84%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (1 samples, 0.01%)libruby.3.4.dylib`str_enc_new (100 samples, 1.47%)libruby.3.4.dylib`rb_current_ec (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (8 samples, 0.12%)libsystem_malloc.dylib`nanov2_malloc (10 samples, 0.15%)itsi_server.bundle`itsi_server::init::anon (754 samples, 11.08%)itsi_server.bund..libsystem_platform.dylib`_platform_memmove (4 samples, 0.06%)itsi_server.bundle`magnus::r_string::_<impl magnus::api::Ruby>::str_new (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (5 samples, 0.07%)libruby.3.4.dylib`ary_new (2 samples, 0.03%)libruby.3.4.dylib`basic_obj_respond_to (4 samples, 0.06%)libruby.3.4.dylib`call_cfunc_0 (1 samples, 0.01%)libruby.3.4.dylib`call_cfunc_1 (1 samples, 0.01%)libruby.3.4.dylib`mnew_internal (1 samples, 0.01%)libruby.3.4.dylib`callable_method_entry_or_negative (3 samples, 0.04%)libruby.3.4.dylib`callable_method_entry_refinements (7 samples, 0.10%)libruby.3.4.dylib`rb_id_table_lookup (2 samples, 0.03%)libruby.3.4.dylib`DYLD-STUB$$bzero (1 samples, 0.01%)libruby.3.4.dylib`rb_get_alloc_func (1 samples, 0.01%)libruby.3.4.dylib`rb_data_typed_object_zalloc (4 samples, 0.06%)libruby.3.4.dylib`typed_data_alloc (2 samples, 0.03%)libruby.3.4.dylib`mnew_internal (7 samples, 0.10%)libruby.3.4.dylib`rb_gc_writebarrier (2 samples, 0.03%)libruby.3.4.dylib`obj_method (16 samples, 0.24%)libruby.3.4.dylib`rb_gc_writebarrier (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (4 samples, 0.06%)libruby.3.4.dylib`callable_method_entry_or_negative (3 samples, 0.04%)libruby.3.4.dylib`callable_method_entry_or_negative (2 samples, 0.03%)libruby.3.4.dylib`basic_obj_respond_to (26 samples, 0.38%)libruby.3.4.dylib`callable_method_entry_refinements (16 samples, 0.24%)libruby.3.4.dylib`rb_id_table_lookup (8 samples, 0.12%)libruby.3.4.dylib`callable_method_entry_or_negative (1 samples, 0.01%)libruby.3.4.dylib`obj_respond_to (41 samples, 0.60%)libruby.3.4.dylib`rb_check_id (7 samples, 0.10%)libruby.3.4.dylib`invoke_block_from_c_bh (8 samples, 0.12%)libruby.3.4.dylib`rb_block_given_p (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_frame_block_handler (3 samples, 0.04%)libdyld.dylib`tlv_get_addr (11 samples, 0.16%)libruby.3.4.dylib`DYLD-STUB$$memcpy (2 samples, 0.03%)libruby.3.4.dylib`CALLER_SETUP_ARG (6 samples, 0.09%)libruby.3.4.dylib`DYLD-STUB$$_setjmp (3 samples, 0.04%)libruby.3.4.dylib`rb_hash_aset (3 samples, 0.04%)libruby.3.4.dylib`rb_st_lookup (12 samples, 0.18%)libruby.3.4.dylib`rb_gvar_get (1 samples, 0.01%)libruby.3.4.dylib`rb_global_entry (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`gc_writebarrier_generational (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (6 samples, 0.09%)libruby.3.4.dylib`gc_writebarrier_generational (2 samples, 0.03%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`hash_aset_str_insert (3 samples, 0.04%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_st_update (25 samples, 0.37%)libruby.3.4.dylib`tbl_update_modify (2 samples, 0.03%)libruby.3.4.dylib`rb_hash_aset (47 samples, 0.69%)libruby.3.4.dylib`tbl_update (41 samples, 0.60%)libruby.3.4.dylib`tbl_update_modify (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_stlike_lookup (4 samples, 0.06%)libruby.3.4.dylib`rb_iseq_cdhash_hash (2 samples, 0.03%)libruby.3.4.dylib`rb_obj_class (2 samples, 0.03%)libruby.3.4.dylib`rb_iseq_cdhash_hash (3 samples, 0.04%)libruby.3.4.dylib`rb_st_lookup (56 samples, 0.82%)libruby.3.4.dylib`rb_str_hash (7 samples, 0.10%)libruby.3.4.dylib`rb_str_hash (3 samples, 0.04%)libruby.3.4.dylib`rb_vm_exec (462 samples, 6.79%)libruby.3..libruby.3.4.dylib`vm_exec_core (411 samples, 6.04%)libruby...libruby.3.4.dylib`tbl_update (1 samples, 0.01%)libruby.3.4.dylib`vm_callee_setup_block_arg (18 samples, 0.26%)libruby.3.4.dylib`CALLER_SETUP_ARG (3 samples, 0.04%)libruby.3.4.dylib`vm_exec_core (15 samples, 0.22%)libruby.3.4.dylib`invoke_block_from_c_bh (564 samples, 8.29%)libruby.3.4...libsystem_platform.dylib`_setjmp (16 samples, 0.24%)libruby.3.4.dylib`rb_current_ec (5 samples, 0.07%)libruby.3.4.dylib`rb_ec_stack_check (3 samples, 0.04%)libruby.3.4.dylib`rb_vm_exec (5 samples, 0.07%)libruby.3.4.dylib`vm_callee_setup_block_arg (4 samples, 0.06%)libruby.3.4.dylib`rb_ary_each (639 samples, 9.39%)libruby.3.4.d..libruby.3.4.dylib`rb_yield (621 samples, 9.12%)libruby.3.4.d..libsystem_platform.dylib`_platform_memmove (13 samples, 0.19%)libruby.3.4.dylib`rb_ary_pop (3 samples, 0.04%)libruby.3.4.dylib`rb_block_given_p (1 samples, 0.01%)libruby.3.4.dylib`rb_block_pair_yield_optimizable (1 samples, 0.01%)libruby.3.4.dylib`rb_check_id (1 samples, 0.01%)libruby.3.4.dylib`ruby_sip_hash13 (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_delete_m (3 samples, 0.04%)libruby.3.4.dylib`rb_hash_stlike_delete (3 samples, 0.04%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`str_do_hash (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_block_pair_yield_optimizable (17 samples, 0.25%)libruby.3.4.dylib`rb_vm_block_min_max_arity (16 samples, 0.24%)libruby.3.4.dylib`rb_ensure (4 samples, 0.06%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`invoke_block_from_c_bh (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_new_from_args (2 samples, 0.03%)libruby.3.4.dylib`ary_new (1 samples, 0.01%)libruby.3.4.dylib`rb_assoc_new (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`DYLD-STUB$$memcpy (2 samples, 0.03%)libruby.3.4.dylib`DYLD-STUB$$_setjmp (1 samples, 0.01%)itsi_server.bundle`<magnus::r_string::RString as magnus::try_convert::TryConvert>::try_convert (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$memcpy (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_protect (1 samples, 0.01%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (2 samples, 0.03%)itsi_server.bundle`__rdl_dealloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::promotable_even_drop (4 samples, 0.06%)itsi_server.bundle`core::alloc::layout::Layout::is_size_align_valid (2 samples, 0.03%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::Bytes::copy_from_slice (1 samples, 0.01%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (14 samples, 0.21%)libsystem_malloc.dylib`nanov2_malloc (5 samples, 0.07%)itsi_server.bundle`http::header::name::StandardHeader::from_bytes (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::shallow_clone_vec (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`<http::header::name::HeaderName as core::convert::From<&http::header::name::HeaderName>>::from (8 samples, 0.12%)libsystem_malloc.dylib`nanov2_malloc (3 samples, 0.04%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::Bytes::slice (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_malloc (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_insert_entry (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (1 samples, 0.01%)itsi_server.bundle`http::header::map::hash_elem_using (10 samples, 0.15%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::insert_header (47 samples, 0.69%)libsystem_platform.dylib`_platform_memmove (8 samples, 0.12%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::add_header (74 samples, 1.09%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::insert_header (1 samples, 0.01%)itsi_server.bundle`magnus::r_string::RString::to_bytes (3 samples, 0.04%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`magnus::error::protect::call (3 samples, 0.04%)libruby.3.4.dylib`rb_protect (10 samples, 0.15%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (12 samples, 0.18%)libsystem_platform.dylib`_setjmp (2 samples, 0.03%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (4 samples, 0.06%)itsi_server.bundle`magnus::method::Method2::call_handle_error (110 samples, 1.62%)libsystem_malloc.dylib`nanov2_malloc (6 samples, 0.09%)libruby.3.4.dylib`vm_exec_core (148 samples, 2.17%)l..libruby.3.4.dylib`vm_call_cfunc_with_frame_ (115 samples, 1.69%)itsi_server.bundle`itsi_server::init::anon (112 samples, 1.65%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_exec (152 samples, 2.23%)l..libruby.3.4.dylib`vm_opt_length (1 samples, 0.01%)libruby.3.4.dylib`ary_new (1 samples, 0.01%)libruby.3.4.dylib`ary_memcpy0 (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (2 samples, 0.03%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (2 samples, 0.03%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (2 samples, 0.03%)libsystem_malloc.dylib`small_free_scan_madvise_free (2 samples, 0.03%)libsystem_kernel.dylib`madvise (2 samples, 0.03%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (15 samples, 0.22%)libsystem_malloc.dylib`free_small (13 samples, 0.19%)libsystem_malloc.dylib`small_madvise_free_range_no_lock (3 samples, 0.04%)libsystem_kernel.dylib`madvise (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (4 samples, 0.06%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (20 samples, 0.29%)libsystem_malloc.dylib`free_small (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (23 samples, 0.34%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (23 samples, 0.34%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (23 samples, 0.34%)libsystem_malloc.dylib`free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_madvise_block (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_madvise_block_locked (2 samples, 0.03%)libsystem_kernel.dylib`madvise (2 samples, 0.03%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (34 samples, 0.50%)libsystem_platform.dylib`_platform_memset (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (3 samples, 0.04%)libsystem_malloc.dylib`free_tiny (17 samples, 0.25%)libsystem_malloc.dylib`tiny_free_no_lock (16 samples, 0.24%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (5 samples, 0.07%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (55 samples, 0.81%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (57 samples, 0.84%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (2 samples, 0.03%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (78 samples, 1.15%)libsystem_malloc.dylib`_nanov2_free (7 samples, 0.10%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (13 samples, 0.19%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_plane (106 samples, 1.56%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (3 samples, 0.04%)libruby.3.4.dylib`gc_sweep_step (110 samples, 1.62%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (1 samples, 0.01%)libruby.3.4.dylib`setup_parameters_complex (122 samples, 1.79%)l..libruby.3.4.dylib`rb_ary_new_from_values (118 samples, 1.73%)libruby.3.4.dylib`ary_new (115 samples, 1.69%)libruby.3.4.dylib`newobj_of (113 samples, 1.66%)libruby.3.4.dylib`newobj_cache_miss (112 samples, 1.65%)libruby.3.4.dylib`gc_continue (112 samples, 1.65%)libsystem_kernel.dylib`getrusage (2 samples, 0.03%)libruby.3.4.dylib`vm_callee_setup_block_arg (1 samples, 0.01%)libruby.3.4.dylib`invoke_block_from_c_bh (280 samples, 4.11%)libr..libsystem_platform.dylib`_setjmp (2 samples, 0.03%)libruby.3.4.dylib`rb_vm_exec (2 samples, 0.03%)libruby.3.4.dylib`setup_parameters_complex (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_each_pair (317 samples, 4.66%)libru..libruby.3.4.dylib`rb_hash_foreach (296 samples, 4.35%)libru..libruby.3.4.dylib`rb_ensure (295 samples, 4.33%)libru..libruby.3.4.dylib`hash_foreach_call (292 samples, 4.29%)libru..libruby.3.4.dylib`each_pair_i (290 samples, 4.26%)libru..libruby.3.4.dylib`rb_yield (286 samples, 4.20%)libru..libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_foreach (1 samples, 0.01%)libruby.3.4.dylib`ary_new (3 samples, 0.04%)libruby.3.4.dylib`rb_current_ec (1 samples, 0.01%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_keys (5 samples, 0.07%)libruby.3.4.dylib`rb_ary_set_len (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_int2str (6 samples, 0.09%)libruby.3.4.dylib`str_enc_new (4 samples, 0.06%)libruby.3.4.dylib`newobj_of (2 samples, 0.03%)libruby.3.4.dylib`rb_st_keys (10 samples, 0.15%)libruby.3.4.dylib`rb_str_equal (3 samples, 0.04%)libruby.3.4.dylib`rb_utf8_str_new (3 samples, 0.04%)libruby.3.4.dylib`rb_yield (3 samples, 0.04%)libruby.3.4.dylib`vm_call_cfunc_with_frame_ (1,889 samples, 27.75%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libsystem_platform.dylib`_platform_memcmp (2 samples, 0.03%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start (2 samples, 0.03%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start_0params_4locals (1 samples, 0.01%)libruby.3.4.dylib`vm_call_ivar (3 samples, 0.04%)libruby.3.4.dylib`CALLER_SETUP_ARG (1 samples, 0.01%)libruby.3.4.dylib`vm_invoke_iseq_block (6 samples, 0.09%)libruby.3.4.dylib`vm_callee_setup_block_arg (2 samples, 0.03%)libsystem_platform.dylib`_platform_memmove (3 samples, 0.04%)libruby.3.4.dylib`vm_exec_core (2,699 samples, 39.66%)libruby.3.4.dylib`vm_exec_corelibsystem_platform.dylib`_platform_memset_pattern16 (3 samples, 0.04%)libruby.3.4.dylib`vm_invoke_block_opt_call (1 samples, 0.01%)libruby.3.4.dylib`vm_invoke_iseq_block (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_exec (2,729 samples, 40.10%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_opt_length (1 samples, 0.01%)libruby.3.4.dylib`vm_exec_core (1 samples, 0.01%)libruby.3.4.dylib`vm_invoke_proc (2,736 samples, 40.20%)libruby.3.4.dylib`vm_invoke_proclibsystem_platform.dylib`_setjmp (1 samples, 0.01%)libruby.3.4.dylib`rb_funcallv_scope (2,782 samples, 40.88%)libruby.3.4.dylib`rb_funcallv_scopelibruby.3.4.dylib`vm_call0_body (2,744 samples, 40.32%)libruby.3.4.dylib`vm_call0_bodylibsystem_platform.dylib`_platform_memset_pattern16 (1 samples, 0.01%)itsi_server.bundle`magnus::error::protect::call (2,791 samples, 41.01%)itsi_server.bundle`magnus::error::protect::calllibruby.3.4.dylib`vm_call0_body (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)libruby.3.4.dylib`rb_protect (2,814 samples, 41.35%)libruby.3.4.dylib`rb_protectlibruby.3.4.dylib`rb_funcallv_scope (2 samples, 0.03%)libruby.3.4.dylib`typed_data_alloc (2 samples, 0.03%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)itsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker::process_one (2,842 samples, 41.76%)itsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker:..libsystem_malloc.dylib`nanov2_allocate_outlined (4 samples, 0.06%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (3 samples, 0.04%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (2 samples, 0.03%)itsi_server.bundle`std::time::SystemTime::now (1 samples, 0.01%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)libruby.3.4.dylib`rb_data_typed_object_wrap (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (4 samples, 0.06%)itsi_server.bundle`itsi_rb_helpers::call_with_gvl::trampoline (2,904 samples, 42.67%)itsi_server.bundle`itsi_rb_helpers::call_with_gvl::trampolinelibsystem_platform.dylib`_platform_memmove (7 samples, 0.10%)libruby.3.4.dylib`blocking_region_end (1 samples, 0.01%)libsystem_pthread.dylib`pthread_mutex_unlock (1 samples, 0.01%)libruby.3.4.dylib`rb_thread_call_with_gvl (2,922 samples, 42.93%)libruby.3.4.dylib`rb_thread_call_with_gvllibsystem_platform.dylib`_platform_memmove (17 samples, 0.25%)libsystem_kernel.dylib`sigprocmask (4 samples, 0.06%)itsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampoline (3,020 samples, 44.37%)itsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampolinelibsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)all (6,806 samples, 100%)libsystem_pthread.dylib`thread_start (3,022 samples, 44.40%)libsystem_pthread.dylib`thread_startlibsystem_pthread.dylib`_pthread_start (3,022 samples, 44.40%)libsystem_pthread.dylib`_pthread_startlibruby.3.4.dylib`nt_start (3,022 samples, 44.40%)libruby.3.4.dylib`nt_startlibruby.3.4.dylib`thread_start_func_2 (3,022 samples, 44.40%)libruby.3.4.dylib`thread_start_func_2itsi_server.bundle`itsi_rb_helpers::create_ruby_thread::trampoline (3,022 samples, 44.40%)itsi_server.bundle`itsi_rb_helpers::create_ruby_thread::trampolineitsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker::accept_loop (3,022 samples, 44.40%)itsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker::acce..libruby.3.4.dylib`rb_nogvl (3,022 samples, 44.40%)libruby.3.4.dylib`rb_nogvllibsystem_platform.dylib`_platform_memmove (1 samples, 0.01%) \ No newline at end of file diff --git a/benchmarks/grpc_server.rb b/benchmarks/grpc_server.rb new file mode 100644 index 00000000..747292ed --- /dev/null +++ b/benchmarks/grpc_server.rb @@ -0,0 +1,12 @@ +require_relative "./apps/echo_service/echo_service" + +def main + thread_count = (ENV['THREADS'] || 30).to_i + + server = GRPC::RpcServer.new(pool_size: thread_count) + server.add_http2_port("0.0.0.0:#{ENV['PORT'] || 50051}", :this_port_is_insecure) + server.handle(EchoServiceImpl) + server.run_till_terminated +end + +main diff --git a/benchmarks/lib/benchmark_case.rb b/benchmarks/lib/benchmark_case.rb new file mode 100644 index 00000000..7a389b00 --- /dev/null +++ b/benchmarks/lib/benchmark_case.rb @@ -0,0 +1,59 @@ +# lib/benchmark_case.rb +class BenchmarkCase + + # Give the server some time to warm up before we start measuring. + RACK_BENCH_WARMUP_DURATION_SECONDS = ENV.fetch('RACK_BENCH_WARMUP_DURATION_SECONDS', 1).to_i + + # A default 3 is relatively low, but allows us to get through the entire test + # suite quickly. For a more robust benchmark, this should be higher. + RACK_BENCH_DURATION_SECONDS = ENV.fetch('RACK_BENCH_DURATION_SECONDS', 3).to_i + + %i[ + name description app method data path + workers threads warmup_duration concurrency_levels + duration https parallel_requests nonblocking + requires use_yjit static_files_root grpc call proto + ].each do |accessor| + define_method(accessor) do |value = self| + if value.eql?(self) + instance_variable_get("@#{accessor}") + else + instance_variable_set("@#{accessor}", value) + end + end + end + + def initialize(name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + @name = name + @description = '' + @method = 'GET' + @data = nil + @path = '/' + @proto = "" + @call = "" + @workers = 1 + @threads = [1, 5, 10, 20] + @workers = [1, 2, Etc.nprocessors].uniq + @duration = RACK_BENCH_DURATION_SECONDS + @warmup_duration = RACK_BENCH_WARMUP_DURATION_SECONDS + @concurrency_levels = [10, 50, 100, 250] + @static_files_root = nil + @https = false + @grpc = false + @parallel_requests = 16# frozen_string_literal: true + @nonblocking = false + @requires = %i[ruby] + @use_yjit = true + yield self if block_given? + end + + def method_missing(name, *args, **kwargs, &blk) + return super unless name.end_with?('?') + + @requires.include?(name[0...-1].to_sym) + end + + def respond_to_missing?(name, include_private = false) + @requires.include?(name[0...-1].to_sym) || super + end +end diff --git a/benchmarks/lib/server.rb b/benchmarks/lib/server.rb new file mode 100644 index 00000000..a8b8934b --- /dev/null +++ b/benchmarks/lib/server.rb @@ -0,0 +1,94 @@ +require 'sys/proctable' +include Sys + +class Server # rubocop:disable Style/Documentation + attr_reader :name, :cmd_template, :http2, :supports + + ALL = [] + + def initialize(name, cmd_template, http2: false, supports: [], **custom_args) + @name = name + @cmd_template = cmd_template + @http2 = http2 + @supports = supports + @custom_args = custom_args + ALL << self + end + + def supports?(feature) + @supports.include?(feature) + end + + def run!(server_config_file_path, test_case, threads, workers) + port = free_port + + @builder_args = { + base: "bundle exec #{@name}", + config: server_config_file_path, + scheme: test_case.https ? 'https' : 'http', + host: '0.0.0.0', + app_path: test_case.app&.path, + workers: workers, + threads: threads, + www: test_case.static_files_root, + port: port + } + + @builder_args.merge!(@custom_args.transform_values{ |v| v[test_case, @builder_args] }) + + cmd = cmd_template % @builder_args.to_h.transform_keys(&:to_sym) + puts Paint["\nStarting server:", :green, :bold] + puts Paint[cmd, :green] + + @pid = Process.spawn( + {"RUBY_YJIT_ENABLE" => "#{test_case.use_yjit}", "PORT" => port.to_s, "THREADS" => threads.to_s}, + cmd, + out: '/dev/null', + err: '/dev/null', + pgroup: true + ) + + begin + wait_for_port(port) + puts Paint["pid: #{@pid}", :yellow] + paths, methods, data = [Array(test_case.path), Array(test_case.method), Array(test_case.data)] + combinations = [paths, methods, data].map(&:length).max.times.map do |i| + [ + "http://127.0.0.1:#{port}/#{paths[[i, paths.size - 1].min].gsub(/^\/+/,'')}", + methods[[i, methods.size - 1].min], + data[[i, data.size - 1].min], + ] + end + result = yield combinations + rescue StandardError => e + puts Paint["Server failed to start: #{e.message}", :red, :bold] + + return false + end + + + return result + rescue StandardError => e + binding.b # rubocop:disable Lint/Debugger + ensure + stop! + end + + def exec_found? + return @exec_found if defined?(@exec_found) + + @exec_found = system("which #{name} > /dev/null") || system("ls #{name}") + end + + def rss + @pid ? ProcTable.ps(pid: @pid).rss : nil + rescue + nil + end + + def stop! + Process.kill('TERM', @pid) + Process.wait(@pid) + end + +end diff --git a/benchmarks/lib/util.rb b/benchmarks/lib/util.rb new file mode 100644 index 00000000..bdd46cae --- /dev/null +++ b/benchmarks/lib/util.rb @@ -0,0 +1,45 @@ + +def wait_for_port(port, timeout: 5) + Timeout.timeout(timeout) do + loop do + TCPSocket.new('127.0.0.1', port).close + break + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH + sleep 0.1 + end + end +rescue Timeout::Error + raise "Unable to connect to localhost:#{port}" +end + + +def free_port + server = TCPServer.new("0.0.0.0", 0) + port = server.addr[1] + server.close + port +end + +def parse_wrk_output(output) + metrics = {} + + if output =~ /Requests\/sec:\s+([\d.]+)/ + metrics[:requests_per_sec] = $1.to_f + end + + if output =~ /Transfer\/sec:\s+([\d.]+)([KMG]?B)/ + metrics[:transfer_per_sec] = { + value: $1.to_f, + unit: $2 + } + end + + if output =~ /Latency\s+([\d.]+)([a-z]+)/ + metrics[:latency_avg] = { + value: $1.to_f, + unit: $2 + } + end + + metrics +end diff --git a/benchmarks/rack_bench.rb b/benchmarks/rack_bench.rb new file mode 100644 index 00000000..f4c74d4d --- /dev/null +++ b/benchmarks/rack_bench.rb @@ -0,0 +1,277 @@ +require_relative 'lib/util' +require_relative 'lib/benchmark_case' +require_relative 'servers' +require 'etc' +require 'debug' +require 'json' +require 'fileutils' +require 'open3' +require 'socket' +require 'timeout' +require 'time' +require 'paint' +require 'uri' + +$LOAD_PATH.unshift(File.dirname(__FILE__)) +$interrupt_signal = Queue.new + +trap("INT") do + exit(0) if $interrupt_signal.length > 0 + puts "\n[DEBUG] Caught Ctrl+C. Pausing before next iteration. Press again to exit." + $interrupt_signal << true +end + +def interrupted? + $interrupt_signal.pop(true) +rescue + false +end + +def run_benchmark( + test_name, test_case, server_config_file_path, server, + threads:, workers:, http2: +) + server.run!(server_config_file_path, test_case, threads, workers) do |workloads| + puts Paint["\n=== Running #{test_name} on #{server.name}", :cyan, :bold] + warmup_cmds = workloads.map do |url, method, data| + if test_case.grpc? + "ghz --duration-stop=ignore --cpus=2 -z #{test_case.warmup_duration}s -c50 --call #{test_case.call} --stream-call-count=5 -d #{data} --insecure #{URI(url).host}:#{URI(url).port} --proto #{test_case.proto} -O json" + else + "oha --no-tui -z #{test_case.warmup_duration}s -c50 #{url} -m #{method} #{data ? %{-d "#{data}"} : ""} -j #{http2 ? '--http2 --insecure' : ''} #{http2 ? "-p #{test_case.parallel_requests}" : ''}" # rubocop:disable Layout/LineLength + end + end + + test_command_sets = test_case.concurrency_levels.map do |level| + [ + level, + workloads.map do |url, method, data| + if test_case.grpc? + "ghz --duration-stop=ignore --cpus=2 -z #{test_case.duration}s -c#{level} --call #{test_case.call} --stream-call-count=5 -d #{data} --insecure #{URI(url).host}:#{URI(url).port} --proto #{test_case.proto} -O json" + else + "oha #{test_case.nonblocking ? '' : '-w'} --no-tui -z #{test_case.duration}s -c#{level} #{url} -m #{method} #{data ? %{-d "#{data}"} : ""} -j #{http2 ? '--http2 --insecure' : ''} #{http2 ? "-p #{test_case.parallel_requests}" : ''}" # rubocop:disable Layout/LineLength + end + end + ] + end + + puts Paint["\nWarming up with:", :yellow, :bold] + warmup_cmds.each do |warmup_cmd| + puts Paint[warmup_cmd, :yellow] + `#{warmup_cmd}` + end + + test_command_sets.map do |concurrency, cmds| + puts Paint["\n[#{test_case.name}] #{server.name}(#{workers}x#{threads}). Concurrency #{concurrency}. #{http2 ? "HTTP/2.0": "HTTP/1.1"}", :blue, :bold] + result_outputs = cmds.map do |cmd| + puts Paint[cmd, :blue] + Thread.new{ `#{cmd}` } + end.map(&:value) + + result_outputs.map! do |result_output| + if test_case.grpc? + result_json = JSON.parse(result_output) + success_rate = ((result_json["statusCodeDistribution"]&.[]("OK") / result_json["count"].to_f).round(4) rescue 0) + + gross_rps = (result_json["rps"]).round(4) + failure_rate = (1 - success_rate) + net_rps = (gross_rps * (1 - failure_rate)).round(2) + failure = failure_rate.*(100.0).round(2) + + error_distribution = result_json['errorDistribution'] + + if failure.positive? && error_distribution + puts Paint["• Error breakdown:", :red, :bold] + max_key_length = error_distribution.keys.map(&:length).max + error_distribution.each do |err, count| + padded_key = err.ljust(max_key_length) + puts Paint[" #{padded_key} : #{count}", :red] + end + end + + puts Paint % ['RPS: %{rps}. Errors: %{failure}%', :bold, :cyan, + {rps: [net_rps.to_s, :green], failure: [failure, failure.positive? ? :red : :green]}] + { + "successRate" => success_rate, + "total" => (result_json["count"]), + "slowest" => (result_json["slowest"]).round(4), + "fastest" => (result_json["fastest"] == Float::INFINITY ? 0.0 : result_json["fastest"]).round(4), + "average" => result_json["average"].round(4), + "requestsPerSec" => net_rps, + "grossRequestsPerSec" => gross_rps, + "errorDistribution" => error_distribution, + "p95_latency" => result_json["latencyDistribution"].find{|ld| ld["percentage"] == 95 }["latency"] / (1000.0 ** 2) + } + else + result_json = JSON.parse(result_output) + summary = result_json['summary'] + p95_latency = result_json['latencyPercentiles']['p95'] + gross_rps = summary['requestsPerSec'].round(2) + failure_rate = (1 - summary['successRate']) + net_rps = (gross_rps * (1 - failure_rate)).round(2) + failure = failure_rate.*(100.0).round(2) + + puts Paint % ['RPS: %{rps}. Errors: %{failure}%', :bold, :cyan, + {rps: [net_rps.to_s, :green], failure: [failure, failure.positive? ? :red : :green]}] + + error_distribution = result_json['errorDistribution'] + summary['errorDistribution'] = error_distribution + summary["requestsPerSec"] => net_rps + summary["grossRequestsPerSec"] => gross_rps + + if failure.positive? && error_distribution + puts Paint["• Error breakdown:", :red, :bold] + max_key_length = error_distribution.keys.map(&:length).max + error_distribution.each do |err, count| + padded_key = err.ljust(max_key_length) + puts Paint[" #{padded_key} : #{count}", :red] + end + end + + summary.transform_values!{|v| v.is_a?(Numeric) ? v.round(2) : v } + summary['p95_latency'] = p95_latency + summary + end + end + + binding.b if interrupted? # rubocop:disable Lint/Debugger + + { + server: server.name, + test_case: test_name, + threads: threads, + workers: workers, + http2: http2, + concurrency: concurrency, + **(workers == 1 ? {rss_mb: (server.rss / 1024.0 / 1024.0).round(2) } : {}), + results: combine_results(result_outputs), + timestamp: Time.now.utc.iso8601 + } + end + end +end + +def combine_results(results) + return nil if results.empty? + + combined = { + "total" => 0.0, + "totalData" => 0, + "successRate_numerator" => 0.0, + "successRate_denominator" => 0.0, + "slowest" => 0.0, + "fastest" => Float::INFINITY, + "average_total" => 0.0, + "average_count" => 0, + "requestsPerSec_total" => 0.0, + "sizePerRequest_total" => 0, + "sizePerRequest_count" => 0, + "sizePerSec_total" => 0.0, + "p95_latencies" => [], + "errorDistribution" => Hash.new(0) + } + + results.each do |res| + total = res["total"].to_f + combined["total"] += total + + combined["totalData"] += res["totalData"].to_i + + combined["successRate_numerator"] += total * res["successRate"].to_f + combined["successRate_denominator"] += total + + combined["slowest"] = [combined["slowest"], res["slowest"].to_f].max + combined["fastest"] = [combined["fastest"], res["fastest"].to_f].min + + combined["average_total"] += res["average"].to_f * total + combined["average_count"] += total + + combined["requestsPerSec_total"] += res["requestsPerSec"].to_f + combined["sizePerRequest_total"] += res["sizePerRequest"].to_f + combined["sizePerRequest_count"] += 1 + combined["sizePerSec_total"] += res["sizePerSec"].to_f + + combined["p95_latencies"] << res["p95_latency"].to_f + + res["errorDistribution"].each do |err, count| + combined["errorDistribution"][err] += count.to_i + end + end + + { + "successRate" => (combined["successRate_numerator"] / combined["successRate_denominator"]).round(4), + "total" => (combined["total"]).round(4), + "slowest" => (combined["slowest"]).round(4), + "fastest" => (combined["fastest"] == Float::INFINITY ? 0.0 : combined["fastest"]).round(4), + "average" => (combined["average_total"] / combined["average_count"]).round(4), + "requestsPerSec" => (combined["requestsPerSec_total"]).round(4), + "totalData" => (combined["totalData"]).round(4), + "sizePerRequest" => (combined["sizePerRequest_total"] / combined["sizePerRequest_count"]).round(4), + "sizePerSec" => (combined["sizePerSec_total"]).round(4), + "errorDistribution" => combined["errorDistribution"], + "p95_latency" => combined["p95_latencies"].sort[(combined["p95_latencies"].size * 0.95).floor] || 0.0 + } +end + +def cpu_label + uname, = Open3.capture2('uname -m') + arch = uname.strip + + model = \ + case RUBY_PLATFORM + when /darwin/ + output, = Open3.capture2('sysctl -n machdep.cpu.brand_string') + output.strip + when /linux/ + model_name = File.read('/proc/cpuinfo')[/^model name\s+:\s+(.+)$/, 1] + model_name || arch + else + arch + end + + model.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/^_|_+$/, '') +end + +def save_result(result, test_name, server_name) + path_prefix = File.join("results", cpu_label, test_name) + FileUtils.mkdir_p(path_prefix) + path = File.join(path_prefix, "#{server_name}.json") + File.write(path, JSON.pretty_generate(result)) +end + +filters = ARGV.map(&Regexp.method(:new)) + +Dir.glob('test_cases/*/*.rb').each do |path| + test_name = File.basename(path, '.rb') + begin + src = IO.read(path).strip + next if src.empty? + + test_case = BenchmarkCase.new(test_name) + test_case.instance_eval(src) + + Server::ALL.each do |server| + results = [] + next if filters.any?{|filter| !(filter =~ server.name.to_s || filter =~ test_name.to_s) } + Array(test_case.threads).each do |threads| + Array(test_case.workers).each do |workers| + [true, false].each do |http2| + next unless server.exec_found? + next unless test_case.requires.all?{|r| server.supports?(r) } + next if http2 && !server.supports?(:http2) + next if !http2 && test_case.grpc? + + server_config_file_path = File.join(File.dirname(path), "server_configurations", "#{server.name}.rb") + server_config_file_path = "server_configurations/#{server.name}.rb" unless File.exist?(server_config_file_path) + + results.concat run_benchmark(test_name, test_case, server_config_file_path, server, threads: threads, workers: workers, http2: http2) + end + end + end + + save_result(results, test_name, server.name.to_s) if results.any? + end + rescue StandardError => e + puts "Error during test case #{path}. #{e}" + puts e.backtrace + end +end diff --git a/benchmarks/server_configurations/h2o.conf b/benchmarks/server_configurations/h2o.conf new file mode 100644 index 00000000..f9d01ac9 --- /dev/null +++ b/benchmarks/server_configurations/h2o.conf @@ -0,0 +1,16 @@ +listen: + port: %{port} + host: 0.0.0.0 + +file.custom-handler: + send-gzip: ON + +paths: + "/": + file.dir: %{www} + header.add: + cache-control: "public, max-age=31536000, immutable" + expires: "@365d" + +# Use multithreaded mode (defaults to CPU cores, can also set `num-threads`) +num-threads: %{workers} diff --git a/benchmarks/server_configurations/itsi.rb b/benchmarks/server_configurations/itsi.rb new file mode 100644 index 00000000..0cd3724a --- /dev/null +++ b/benchmarks/server_configurations/itsi.rb @@ -0,0 +1,4 @@ +# To make benchmarks fair. If we use too small a default, Itsi will start applying backpressure +# by returning 503s under heavy loads for distant queued requests to Ruby, +# causing an artificial increase in throughput +ruby_thread_request_backlog_size 100_000 diff --git a/benchmarks/server_configurations/nginx.conf b/benchmarks/server_configurations/nginx.conf new file mode 100644 index 00000000..3820e46c --- /dev/null +++ b/benchmarks/server_configurations/nginx.conf @@ -0,0 +1,26 @@ +worker_processes %{workers}; +events { worker_connections 1024; } + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + server { + listen 0.0.0.0:%{port} http2; + root %{www}; + location / { + open_file_cache max=1000 inactive=20s; + open_file_cache_valid 1s; + open_file_cache_min_uses 1; + open_file_cache_errors on; + + try_files $uri /index.html =404; + access_log off; + add_header Cache-Control "public, max-age=31536000, immutable"; + expires max; + } + } +} diff --git a/benchmarks/servers.rb b/benchmarks/servers.rb new file mode 100644 index 00000000..4e4e5027 --- /dev/null +++ b/benchmarks/servers.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require_relative 'lib/server' + +Server.new \ + :falcon, + '%s -b %s://%s:%s -c %s --hybrid --forks %s --threads %s', + supports: %i[http2 threads processes streaming_body static ruby] + +Server.new \ + :iodine, + '%{base} -p %{port} %{app_path} -w %{workers} -t %{threads} %{www}', + supports: %i[threads processes static ruby], + www: ->(test_case, _args){ test_case.static_files_root ? "-www #{test_case.static_files_root}" : ""} + +Server.new \ + :itsi, + '%{base} -C %{config} -b %{scheme}://%{host}:%{port} --rackup_file=%{app_path} -w %{workers} -t %{threads} %{scheduler_toggle}', + supports: %i[http2 threads processes streaming_body static ruby grpc], + scheduler_toggle: ->(test_case, _args){ test_case.nonblocking ? "-f" : "" } + +Server.new \ + :puma, + '%{base} %{config}-b tcp://%{host}:%{port} %{app_path} -w %{workers} -t %{threads}:%{threads}', + supports: %i[http1 threads processes streaming_body static ruby], + config: ->(_, args){ File.exist?(args[:config]) ? "-C #{args[:config]} " : "" } + +Server.new \ + :unicorn, + 'UNICORN_WORKERS=%{workers} %{base} %{config}-l %{host}:%{port} %{app_path}', + supports: %i[processes streaming_body static ruby], + config: ->(_, args){ File.exist?(args[:config]) ? "-c #{args[:config]} " : "" } + +Server.new \ + :agoo, + '(cd apps && %{base} -p %{port} %{app_path} -w %{workers} -t %{threads} %{www})', + supports: %i[threads processes streaming_body static ruby], + www: ->(test_case, _args){ test_case.static_files_root ? "-d #{test_case.static_files_root.gsub("./apps", "")}" : ""}, + app_path: ->(_test_case, args){ args[:app_path].gsub("apps/", "") } + +Server.new \ + :nginx, + "nginx -p \"#{Dir.pwd}\" -c %{config_file}", + supports: %i[static http2], + config_file: ->(_, args){ + temp_config = Tempfile.new(['nginx', '.conf']) + temp_config.write(IO.read('server_configurations/nginx.conf') % args) + temp_config.flush + temp_config.path + } + +Server.new \ + :caddy, + 'GOMAXPROCS=%{workers} caddy file-server --listen %{host}:%{port} --browse --root %{www}', + supports: %i[static http2] + +Server.new \ + :h2o, + 'h2o -c %{config_file}', + supports: %i[static http2], + config_file: ->(_, args){ + temp_config = Tempfile.new(['nginx', '.conf']) + temp_config.write(IO.read('server_configurations/nginx.conf') % args) + temp_config.flush + temp_config.path + } + +Server.new \ + :"grpc_server.rb", + 'bundle exec ruby ./grpc_server.rb', + supports: %i[grpc http2] diff --git a/benchmarks/test_cases/framework/sinatra_get.rb b/benchmarks/test_cases/framework/sinatra_get.rb new file mode 100644 index 00000000..2233c876 --- /dev/null +++ b/benchmarks/test_cases/framework/sinatra_get.rb @@ -0,0 +1,3 @@ +path "/get" + +app File.open('apps/sinatra.ru') diff --git a/benchmarks/test_cases/framework/sinatra_post.rb b/benchmarks/test_cases/framework/sinatra_post.rb new file mode 100644 index 00000000..76edf1e7 --- /dev/null +++ b/benchmarks/test_cases/framework/sinatra_post.rb @@ -0,0 +1,5 @@ +method 'POST' +path "/post" +data %{{"some":"json"}} + +app File.open('apps/sinatra.ru') diff --git a/benchmarks/test_cases/grpc/echo.rb b/benchmarks/test_cases/grpc/echo.rb new file mode 100644 index 00000000..eefee505 --- /dev/null +++ b/benchmarks/test_cases/grpc/echo.rb @@ -0,0 +1,13 @@ +workers 1 + +requires %i[grpc] + +proto "apps/echo_service/echo.proto" + +call "echo.EchoService/Echo" + +data "{}" + +nonblocking true + +app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/echo_bidirectional.rb b/benchmarks/test_cases/grpc/echo_bidirectional.rb new file mode 100644 index 00000000..cfcbdbd2 --- /dev/null +++ b/benchmarks/test_cases/grpc/echo_bidirectional.rb @@ -0,0 +1,13 @@ +workers 1 + +requires %i[grpc] + +proto "apps/echo_service/echo.proto" + +call "echo.EchoService/EchoBidirectional" + +data "{}" + +nonblocking true + +app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/echo_collect.rb b/benchmarks/test_cases/grpc/echo_collect.rb new file mode 100644 index 00000000..f5e55961 --- /dev/null +++ b/benchmarks/test_cases/grpc/echo_collect.rb @@ -0,0 +1,13 @@ +workers 1 + +requires %i[grpc] + +proto "apps/echo_service/echo.proto" + +call "echo.EchoService/EchoCollect" + +data "{}" + +nonblocking true + +app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/echo_stream.rb b/benchmarks/test_cases/grpc/echo_stream.rb new file mode 100644 index 00000000..b5e19eea --- /dev/null +++ b/benchmarks/test_cases/grpc/echo_stream.rb @@ -0,0 +1,13 @@ +workers 1 + +requires %i[grpc] + +proto "apps/echo_service/echo.proto" + +call "echo.EchoService/EchoStream" + +data "{}" + +nonblocking true + +app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/process_payment.rb b/benchmarks/test_cases/grpc/process_payment.rb new file mode 100644 index 00000000..3978936b --- /dev/null +++ b/benchmarks/test_cases/grpc/process_payment.rb @@ -0,0 +1,13 @@ +workers 1 + +requires %i[grpc] + +proto "apps/echo_service/echo.proto" + +call "echo.EchoService/ProcessPayment" + +data "{}" + +nonblocking true + +app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/server_configurations/itsi.rb b/benchmarks/test_cases/grpc/server_configurations/itsi.rb new file mode 100644 index 00000000..28ca1a50 --- /dev/null +++ b/benchmarks/test_cases/grpc/server_configurations/itsi.rb @@ -0,0 +1,6 @@ +# First attempt to serve incoming requests as static assets, +# falling through to our rack-mapp on not-found. + +require_relative "../../../apps/echo_service/echo_service" + +grpc EchoServiceImpl.new diff --git a/benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb b/benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb new file mode 100644 index 00000000..6ec2d1d7 --- /dev/null +++ b/benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +nonblocking true + +app File.open('apps/big_delay.ru') diff --git a/benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb b/benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb new file mode 100644 index 00000000..f0b69333 --- /dev/null +++ b/benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +nonblocking true + +app File.open('apps/many_small_delay.ru') diff --git a/benchmarks/test_cases/response_size/empty_response.rb b/benchmarks/test_cases/response_size/empty_response.rb new file mode 100644 index 00000000..947956e1 --- /dev/null +++ b/benchmarks/test_cases/response_size/empty_response.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: true +app File.open('apps/empty.ru') diff --git a/benchmarks/test_cases/response_size/response_size_large.rb b/benchmarks/test_cases/response_size/response_size_large.rb new file mode 100644 index 00000000..811107fb --- /dev/null +++ b/benchmarks/test_cases/response_size/response_size_large.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: true +app File.open('apps/large.ru') diff --git a/benchmarks/test_cases/response_size/response_size_medium.rb b/benchmarks/test_cases/response_size/response_size_medium.rb new file mode 100644 index 00000000..44c88e5d --- /dev/null +++ b/benchmarks/test_cases/response_size/response_size_medium.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: true +app File.open('apps/medium.ru') diff --git a/benchmarks/test_cases/response_size/response_size_small.rb b/benchmarks/test_cases/response_size/response_size_small.rb new file mode 100644 index 00000000..d8b7e3bf --- /dev/null +++ b/benchmarks/test_cases/response_size/response_size_small.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: true +app File.open('apps/small.ru') diff --git a/benchmarks/test_cases/static_file/server_configurations/itsi.rb b/benchmarks/test_cases/static_file/server_configurations/itsi.rb new file mode 100644 index 00000000..e2980596 --- /dev/null +++ b/benchmarks/test_cases/static_file/server_configurations/itsi.rb @@ -0,0 +1,8 @@ +# First attempt to serve incoming requests as static assets, +# falling through to our rack-mapp on not-found. +static_assets root_dir: './apps', not_found_behavior: 'fallthrough' + +# To make benchmarks fair. If we use too small a default, Itsi will start applying backpressure +# by returning 503s under heavy loads for distant queued requests to Ruby, +# causing an artificial increase in throughput +ruby_thread_request_backlog_size 100_000 diff --git a/benchmarks/test_cases/static_file/static_dynamic_mixed.rb b/benchmarks/test_cases/static_file/static_dynamic_mixed.rb new file mode 100644 index 00000000..7d3bc1bf --- /dev/null +++ b/benchmarks/test_cases/static_file/static_dynamic_mixed.rb @@ -0,0 +1,9 @@ +path(["public/image.png", "public/index.html", "dynamic"]) + +static_files_root "./apps" + +requires %i[static ruby] + +concurrency_levels([10, 25, 50, 75, 100]) + +app File.open('apps/static.ru') diff --git a/benchmarks/test_cases/static_file/static_large.rb b/benchmarks/test_cases/static_file/static_large.rb new file mode 100644 index 00000000..48da9e97 --- /dev/null +++ b/benchmarks/test_cases/static_file/static_large.rb @@ -0,0 +1,7 @@ +path "public/image.png" + +static_files_root "./apps" + +requires %i[static] + +app File.open('apps/static.ru') diff --git a/benchmarks/test_cases/static_file/static_small.rb b/benchmarks/test_cases/static_file/static_small.rb new file mode 100644 index 00000000..d9a765ef --- /dev/null +++ b/benchmarks/test_cases/static_file/static_small.rb @@ -0,0 +1,7 @@ +path "public" + +static_files_root "./apps" + +requires %i[static] + +app File.open('apps/static.ru') diff --git a/benchmarks/test_cases/streaming_response/chunked.rb b/benchmarks/test_cases/streaming_response/chunked.rb new file mode 100644 index 00000000..946a0c27 --- /dev/null +++ b/benchmarks/test_cases/streaming_response/chunked.rb @@ -0,0 +1,5 @@ +requires %i[streaming_body] + +nonblocking true + +app File.open('apps/chunked.ru') diff --git a/benchmarks/test_cases/streaming_response/full_hijack.rb b/benchmarks/test_cases/streaming_response/full_hijack.rb new file mode 100644 index 00000000..2e136336 --- /dev/null +++ b/benchmarks/test_cases/streaming_response/full_hijack.rb @@ -0,0 +1,3 @@ +requires %i[streaming_body] + +app File.open('apps/full_hijack.ru') diff --git a/benchmarks/test_cases/streaming_response/streaming_response_large.rb b/benchmarks/test_cases/streaming_response/streaming_response_large.rb new file mode 100644 index 00000000..31e2d70c --- /dev/null +++ b/benchmarks/test_cases/streaming_response/streaming_response_large.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +requires %i[streaming_body] + +app File.open('apps/streaming_response_large.ru') diff --git a/benchmarks/test_cases/streaming_response/streaming_response_small.rb b/benchmarks/test_cases/streaming_response/streaming_response_small.rb new file mode 100644 index 00000000..31e2d70c --- /dev/null +++ b/benchmarks/test_cases/streaming_response/streaming_response_small.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +requires %i[streaming_body] + +app File.open('apps/streaming_response_large.ru') diff --git a/benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb b/benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb new file mode 100644 index 00000000..7ac91893 --- /dev/null +++ b/benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +requires %i[http2 streaming_body] + +app File.open('apps/streaming_response_small.ru') diff --git a/benchmarks/test_cases/throughput/hello_world.rb b/benchmarks/test_cases/throughput/hello_world.rb new file mode 100644 index 00000000..194c6d27 --- /dev/null +++ b/benchmarks/test_cases/throughput/hello_world.rb @@ -0,0 +1 @@ +app File.open('apps/hello_world.ru') diff --git a/crates/itsi_scheduler/Cargo.toml b/crates/itsi_scheduler/Cargo.toml index 356af752..f6f7b438 100644 --- a/crates/itsi_scheduler/Cargo.toml +++ b/crates/itsi_scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itsi-scheduler" -version = "0.2.16" +version = "0.2.17" edition = "2021" authors = ["Wouter Coppieters "] license = "MIT" diff --git a/crates/itsi_server/Cargo.toml b/crates/itsi_server/Cargo.toml index ed9f793b..7c7f898e 100644 --- a/crates/itsi_server/Cargo.toml +++ b/crates/itsi_server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itsi-server" -version = "0.2.16" +version = "0.2.17" edition = "2021" authors = ["Wouter Coppieters "] license = "MIT" @@ -90,3 +90,5 @@ argon2 = "0.5.3" core_affinity = "0.8.3" memchr = "2.7.4" quick_cache = "0.6.13" +smallvec = "1.15.0" +async-stream = "0.3.6" diff --git a/crates/itsi_server/src/lib.rs b/crates/itsi_server/src/lib.rs index 7cc9940c..3b80d0aa 100644 --- a/crates/itsi_server/src/lib.rs +++ b/crates/itsi_server/src/lib.rs @@ -58,6 +58,7 @@ fn init(ruby: &Ruby) -> Result<()> { request.define_method("rack_protocol", method!(ItsiHttpRequest::rack_protocol, 0))?; request.define_method("host", method!(ItsiHttpRequest::host, 0))?; request.define_method("headers", method!(ItsiHttpRequest::headers, 0))?; + request.define_method("each_header", method!(ItsiHttpRequest::each_header, 0))?; request.define_method("uri", method!(ItsiHttpRequest::uri, 0))?; request.define_method("header", method!(ItsiHttpRequest::header, 1))?; request.define_method("[]", method!(ItsiHttpRequest::header, 1))?; @@ -80,6 +81,10 @@ fn init(ruby: &Ruby) -> Result<()> { let response = ruby.get_inner(&ITSI_RESPONSE); response.define_method("[]=", method!(ItsiHttpResponse::add_header, 2))?; + response.define_method( + "reserve_headers", + method!(ItsiHttpResponse::reserve_headers, 1), + )?; response.define_method("add_header", method!(ItsiHttpResponse::add_header, 2))?; response.define_method("add_headers", method!(ItsiHttpResponse::add_headers, 1))?; response.define_method("status=", method!(ItsiHttpResponse::set_status, 1))?; diff --git a/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs b/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs index 0877496f..5ce01d6c 100644 --- a/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +++ b/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs @@ -25,6 +25,7 @@ pub struct ItsiBodyProxy { pub enum ItsiBody { Buffered(BigBytes), Stream(ItsiBodyProxy), + Empty, } impl ItsiBody { @@ -32,6 +33,7 @@ impl ItsiBody { match self { ItsiBody::Buffered(bytes) => bytes.as_value(), ItsiBody::Stream(proxy) => Some(proxy.clone().into_value()), + ItsiBody::Empty => None, } } } diff --git a/crates/itsi_server/src/ruby_types/itsi_http_request.rs b/crates/itsi_server/src/ruby_types/itsi_http_request.rs index c52fd591..42eeffe4 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_request.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_request.rs @@ -1,30 +1,29 @@ use derive_more::Debug; use futures::StreamExt; -use http::{header::CONTENT_LENGTH, request::Parts, Response, StatusCode, Version}; +use http::{header::CONTENT_LENGTH, request::Parts, HeaderValue, Response, StatusCode, Version}; use http_body_util::{combinators::BoxBody, BodyExt, Empty}; use itsi_error::CLIENT_CONNECTION_CLOSED; -use itsi_rb_helpers::{print_rb_backtrace, HeapValue}; -use itsi_tracing::{debug, error}; +use itsi_rb_helpers::{funcall_no_ret, print_rb_backtrace, HeapValue}; +use itsi_tracing::debug; use magnus::{ - block::Proc, + block::{yield_values, Proc}, error::{ErrorType, Result as MagnusResult}, - Error, RHash, Symbol, + Error, IntoValue, RHash, Symbol, }; use magnus::{ value::{LazyId, ReprValue}, Ruby, Value, }; use std::{fmt, io::Write, sync::Arc, time::Instant}; -use tokio::sync::mpsc::{self}; +use tracing::error; use super::{ itsi_body_proxy::{big_bytes::BigBytes, ItsiBody, ItsiBodyProxy}, - itsi_http_response::ItsiHttpResponse, + itsi_http_response::{ItsiHttpResponse, ResponseFrame}, }; use crate::{ default_responses::{INTERNAL_SERVER_ERROR_RESPONSE, SERVICE_UNAVAILABLE_RESPONSE}, server::{ - byte_frame::ByteFrame, http_message_types::{HttpRequest, HttpResponse}, request_job::RequestJob, size_limited_incoming::MaxBodySizeReached, @@ -33,11 +32,13 @@ use crate::{ }; static ID_MESSAGE: LazyId = LazyId::new("message"); +static ID_CALL: LazyId = LazyId::new("call"); +static ZERO_HEADER_VALUE: HeaderValue = HeaderValue::from_static("0"); #[derive(Debug)] #[magnus::wrap(class = "Itsi::HttpRequest", free_immediately, size)] pub struct ItsiHttpRequest { - pub parts: Parts, + pub parts: Arc, #[debug(skip)] pub body: ItsiBody, pub version: Version, @@ -148,8 +149,10 @@ impl ItsiHttpRequest { pub fn process(self, ruby: &Ruby, app_proc: Arc>) -> magnus::error::Result<()> { let response = self.response.clone(); - let result = app_proc.call::<_, Value>((self,)); - if let Err(err) = result { + + if let Err(err) = + funcall_no_ret(app_proc.as_value(), *ID_CALL, [self.into_value_with(ruby)]) + { Self::internal_error(ruby, response, err); } Ok(()) @@ -158,7 +161,7 @@ impl ItsiHttpRequest { pub fn internal_error(ruby: &Ruby, response: ItsiHttpResponse, err: Error) { if Self::is_connection_closed_err(ruby, &err) { debug!("Connection closed by client"); - response.close(); + response.close().ok(); } else if let Some(rb_err) = err.value() { print_rb_backtrace(rb_err); response.internal_server_error(err.to_string()); @@ -179,9 +182,7 @@ impl ItsiHttpRequest { nonblocking: bool, ) -> itsi_error::Result { match ItsiHttpRequest::new(hyper_request, context, script_name).await { - Ok((request, mut receiver)) => { - let shutdown_channel = context.service.shutdown_receiver.clone(); - let response = request.response.clone(); + Ok((request, receiver)) => { let sender = if nonblocking { &context.nonblocking_sender } else { @@ -192,20 +193,30 @@ impl ItsiHttpRequest { async_channel::TrySendError::Full(_) => Ok(SERVICE_UNAVAILABLE_RESPONSE .to_http_response(context.accept) .await), - async_channel::TrySendError::Closed(err) => { - error!("Error occurred: {:?}", err); + async_channel::TrySendError::Closed(_) => { + error!("Channel closed while sending request job"); Ok(INTERNAL_SERVER_ERROR_RESPONSE .to_http_response(context.accept) .await) } }, - _ => match receiver.recv().await { - Some(first_frame) => Ok(response - .build(first_frame, receiver, shutdown_channel) - .await), - None => Ok(response - .build(ByteFrame::Empty, receiver, shutdown_channel) - .await), + Ok(_) => match receiver.await { + Ok(ResponseFrame::HttpResponse(response)) => Ok(response), + Ok(ResponseFrame::HijackedResponse(response)) => { + match response.process_hijacked_response().await { + Ok(result) => Ok(result), + Err(e) => { + error!("Error processing hijacked response: {}", e); + Ok(Response::new(BoxBody::new(Empty::new()))) + } + } + } + Err(_) => { + error!("Failed to receive response from receiver"); + Ok(INTERNAL_SERVER_ERROR_RESPONSE + .to_http_response(context.accept) + .await) + } }, } } @@ -217,9 +228,18 @@ impl ItsiHttpRequest { request: HttpRequest, context: &HttpRequestContext, script_name: String, - ) -> Result<(ItsiHttpRequest, mpsc::Receiver), HttpResponse> { + ) -> Result< + ( + ItsiHttpRequest, + tokio::sync::oneshot::Receiver, + ), + HttpResponse, + > { let (parts, body) = request.into_parts(); - let body = if context.server_params.streamable_body { + let parts = Arc::new(parts); + let body = if parts.headers.get(CONTENT_LENGTH) == Some(&ZERO_HEADER_VALUE) { + ItsiBody::Empty + } else if context.server_params.streamable_body { ItsiBody::Stream(ItsiBodyProxy::new(body)) } else { let mut body_bytes = BigBytes::new(); @@ -238,18 +258,22 @@ impl ItsiHttpRequest { } ItsiBody::Buffered(body_bytes) }; - let response_channel = mpsc::channel::(100); + let (sender, receiver) = tokio::sync::oneshot::channel::(); Ok(( Self { context: context.clone(), version: parts.version, - response: ItsiHttpResponse::new(parts.clone(), response_channel.0), + response: ItsiHttpResponse::new( + parts.clone(), + sender, + context.service.shutdown_receiver.clone(), + ), start: Instant::now(), script_name, body, parts, }, - response_channel.1, + receiver, )) } @@ -328,6 +352,13 @@ impl ItsiHttpRequest { .collect::>()) } + pub(crate) fn each_header(&self) -> MagnusResult<()> { + self.parts.headers.iter().for_each(|(hn, hv)| { + yield_values::<_, Value>((hn.as_str(), hv.to_str().unwrap_or(""))).ok(); + }); + Ok(()) + } + pub(crate) fn uri(&self) -> MagnusResult { Ok(self.parts.uri.to_string()) } diff --git a/crates/itsi_server/src/ruby_types/itsi_http_response.rs b/crates/itsi_server/src/ruby_types/itsi_http_response.rs index e8c17cba..648ed1b5 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_response.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_response.rs @@ -1,4 +1,4 @@ -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Buf, Bytes}; use derive_more::Debug; use futures::stream::{unfold, StreamExt}; use http::{ @@ -10,6 +10,7 @@ use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody}; use hyper::{body::Frame, upgrade::Upgraded}; use hyper_util::rt::TokioIo; use itsi_error::Result; +use itsi_rb_helpers::call_without_gvl; use itsi_tracing::error; use magnus::error::Result as MagnusResult; use memchr::{memchr, memchr_iter}; @@ -17,6 +18,7 @@ use parking_lot::RwLock; use std::{ collections::HashMap, io, + ops::Deref, os::{fd::FromRawFd, unix::net::UnixStream}, str::FromStr, sync::Arc, @@ -24,98 +26,60 @@ use std::{ use tokio::{ io::AsyncReadExt, net::UnixStream as TokioUnixStream, - sync::{ - mpsc::{self}, - watch, - }, + sync::{mpsc::Sender, oneshot::Sender as OneshotSender, watch}, }; -use tokio_stream::wrappers::ReceiverStream; + use tokio_util::io::ReaderStream; -use tracing::warn; +use tracing::{info, warn}; -use crate::server::{ - byte_frame::ByteFrame, http_message_types::HttpResponse, - serve_strategy::single_mode::RunningPhase, -}; +use crate::server::{http_message_types::HttpResponse, serve_strategy::single_mode::RunningPhase}; #[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)] #[derive(Debug, Clone)] pub struct ItsiHttpResponse { - pub data: Arc, + pub inner: Arc, +} + +impl Deref for ItsiHttpResponse { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.inner + } } #[derive(Debug)] -pub struct ResponseData { +pub struct ResponseInner { + pub frame_writer: RwLock>>, pub response: RwLock>, - pub response_writer: RwLock>>, - pub response_buffer: RwLock, pub hijacked_socket: RwLock>, - pub parts: Parts, + pub response_sender: RwLock>>, + pub shutdown_rx: watch::Receiver, + pub parts: Arc, +} + +#[derive(Debug)] +pub enum ResponseFrame { + HttpResponse(HttpResponse), + HijackedResponse(ItsiHttpResponse), } impl ItsiHttpResponse { - pub async fn build( - &self, - first_frame: ByteFrame, - receiver: mpsc::Receiver, + pub fn new( + parts: Arc, + response_sender: OneshotSender, shutdown_rx: watch::Receiver, - ) -> HttpResponse { - if self.is_hijacked() { - return match self.process_hijacked_response().await { - Ok(result) => result, - Err(e) => { - error!("Error processing hijacked response: {}", e); - Response::new(BoxBody::new(Empty::new())) - } - }; + ) -> Self { + Self { + inner: Arc::new(ResponseInner { + parts, + shutdown_rx, + response_sender: RwLock::new(Some(response_sender)), + frame_writer: RwLock::new(None), + response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))), + hijacked_socket: RwLock::new(None), + }), } - let mut response = self.data.response.write().take().unwrap(); - *response.body_mut() = if matches!(first_frame, ByteFrame::Empty) { - BoxBody::new(Empty::new()) - } else if matches!(first_frame, ByteFrame::End(_)) { - BoxBody::new(Full::new(first_frame.into())) - } else { - let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame)))); - let frame_stream = unfold( - (ReceiverStream::new(receiver), shutdown_rx), - |(mut receiver, mut shutdown_rx)| async move { - if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() { - return None; - } - loop { - tokio::select! { - maybe_bytes = receiver.next() => { - match maybe_bytes { - Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => { - return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx))); - } - _ => { - return None; - } - } - }, - _ = shutdown_rx.changed() => { - match *shutdown_rx.borrow() { - RunningPhase::ShutdownPending => { - warn!("Disconnecting streaming client."); - return None; - }, - _ => continue, - } - } - } - } - }, - ); - - let combined_stream = initial_frame.chain(frame_stream); - BoxBody::new(StreamBody::new(combined_stream)) - }; - response - } - - pub fn close(&self) { - self.data.response_writer.write().take(); } async fn two_way_bridge(upgraded: Upgraded, local: TokioUnixStream) -> io::Result<()> { @@ -163,8 +127,7 @@ impl ItsiHttpResponse { &self, ) -> Result<(HeaderMap, StatusCode, bool, TokioUnixStream)> { let hijacked_socket = - self.data - .hijacked_socket + self.hijacked_socket .write() .take() .ok_or(itsi_error::ItsiError::InvalidInput( @@ -191,9 +154,9 @@ impl ItsiHttpResponse { pub async fn process_hijacked_response(&self) -> Result { let (headers, status, requires_upgrade, reader) = self.read_hijacked_headers().await?; let mut response = if requires_upgrade { - let parts = self.data.parts.clone(); + let parts = self.parts.clone(); tokio::spawn(async move { - let mut req = Request::from_parts(parts, Empty::::new()); + let mut req = Request::from_parts((*parts).clone(), Empty::::new()); match hyper::upgrade::on(&mut req).await { Ok(upgraded) => { Self::two_way_bridge(upgraded, reader) @@ -259,14 +222,83 @@ impl ItsiHttpResponse { pub fn internal_server_error(&self, message: String) { error!(message); - self.data.response_writer.write().take(); - if let Some(ref mut response) = *self.data.response.write() { + self.close_write().ok(); + if let Some(mut response) = self.response.write().take() { *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + if let Some(sender) = self.response_sender.write().take() { + sender.send(ResponseFrame::HttpResponse(response)).ok(); + } } } pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> { - self.send_frame_into(ByteFrame::Data(frame), &self.data.response_writer) + { + if self.frame_writer.read().is_none() && self.response.read().is_some() { + if let Some(mut response) = self.response.write().take() { + let (writer, mut reader) = tokio::sync::mpsc::channel(5); + let mut shutdown_rx = self.shutdown_rx.clone(); + + let frame_stream = async_stream::stream! { + loop { + tokio::select! { + maybe_bytes = reader.recv() => { + match maybe_bytes { + Some(bytes) => { + yield Ok(Frame::data(bytes)); + } + _ => break, + } + }, + _ = shutdown_rx.changed() => { + if *shutdown_rx.borrow() == RunningPhase::ShutdownPending { + reader.close(); + while let Some(bytes) = reader.recv().await{ + yield Ok(Frame::data(bytes)); + } + warn!("Disconnecting streaming client."); + break; + } + } + } + } + }; + + *response.body_mut() = BoxBody::new(StreamBody::new(frame_stream)); + self.frame_writer.write().replace(writer); + if let Some(sender) = self.response_sender.write().take() { + sender.send(ResponseFrame::HttpResponse(response)).ok(); + } + } else { + info!("No response!"); + } + } + } + if let Some(frame_writer) = self.frame_writer.read().as_ref() { + call_without_gvl(|| frame_writer.blocking_send(frame)) + .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?; + } + Ok(()) + } + + pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> { + if self.frame_writer.read().is_some() { + self.send_frame(frame)?; + self.close()?; + return Ok(()); + } + if let Some(mut response) = self.response.write().take() { + *response.body_mut() = BoxBody::new(Full::new(frame)); + if let Some(sender) = self.response_sender.write().take() { + sender.send(ResponseFrame::HttpResponse(response)).ok(); + } + } + + Ok(()) + } + + pub fn close_write(&self) -> MagnusResult { + self.frame_writer.write().take(); + Ok(true) } pub fn recv_frame(&self) { @@ -278,40 +310,21 @@ impl ItsiHttpResponse { } pub fn is_closed(&self) -> bool { - self.data.response_writer.write().is_none() - } - - pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> { - let result = self.send_frame_into(ByteFrame::End(frame), &self.data.response_writer); - self.data.response_writer.write().take(); - result - } - - pub fn send_frame_into( - &self, - frame: ByteFrame, - writer: &RwLock>>, - ) -> MagnusResult<()> { - if let Some(writer) = writer.write().as_ref() { - return Ok(writer - .blocking_send(frame) - .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?); - } - Ok(()) + self.response.read().is_none() && self.frame_writer.read().is_none() } pub fn is_hijacked(&self) -> bool { - self.data.hijacked_socket.read().is_some() + self.hijacked_socket.read().is_some() } - pub fn close_write(&self) -> MagnusResult { - self.data.response_writer.write().take(); - Ok(true) + pub fn close(&self) -> MagnusResult<()> { + self.close_write()?; + self.close_read()?; + Ok(()) } pub fn accept_str(&self) -> &str { - self.data - .parts + self.parts .headers .get(ACCEPT) .and_then(|hv| hv.to_str().ok()) // handle invalid utf-8 @@ -330,25 +343,9 @@ impl ItsiHttpResponse { Ok(true) } - pub fn new(parts: Parts, response_writer: mpsc::Sender) -> Self { - Self { - data: Arc::new(ResponseData { - response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))), - response_writer: RwLock::new(Some(response_writer)), - response_buffer: RwLock::new(BytesMut::new()), - hijacked_socket: RwLock::new(None), - parts, - }), - } - } - - pub fn add_header(&self, name: Bytes, value: Bytes) -> MagnusResult<()> { - let header_name: HeaderName = HeaderName::from_bytes(&name).map_err(|e| { - itsi_error::ItsiError::InvalidInput(format!("Invalid header name {:?}: {:?}", name, e)) - })?; - if let Some(ref mut resp) = *self.data.response.write() { - let headers_mut = resp.headers_mut(); - self.insert_header(headers_mut, &header_name, value); + pub fn reserve_headers(&self, header_count: usize) -> MagnusResult<()> { + if let Some(ref mut resp) = *self.response.write() { + resp.headers_mut().try_reserve(header_count).ok(); } Ok(()) } @@ -394,8 +391,22 @@ impl ItsiHttpResponse { } } + pub fn add_header(&self, header_name: Bytes, value: Bytes) -> MagnusResult<()> { + if let Some(ref mut resp) = *self.response.write() { + let headers_mut = resp.headers_mut(); + let header_name = HeaderName::from_bytes(&header_name).map_err(|e| { + itsi_error::ItsiError::InvalidInput(format!( + "Invalid header name {:?}: {:?}", + header_name, e + )) + })?; + self.insert_header(headers_mut, &header_name, value); + } + Ok(()) + } + pub fn add_headers(&self, headers: HashMap>) -> MagnusResult<()> { - if let Some(ref mut resp) = *self.data.response.write() { + if let Some(ref mut resp) = *self.response.write() { let headers_mut = resp.headers_mut(); for (name, values) in headers { let header_name = HeaderName::from_bytes(&name).map_err(|e| { @@ -414,7 +425,7 @@ impl ItsiHttpResponse { } pub fn set_status(&self, status: u16) -> MagnusResult<()> { - if let Some(ref mut resp) = *self.data.response.write() { + if let Some(ref mut resp) = *self.response.write() { *resp.status_mut() = StatusCode::from_u16(status).map_err(|e| { itsi_error::ItsiError::InvalidInput(format!( "Invalid status code {:?}: {:?}", @@ -428,13 +439,14 @@ impl ItsiHttpResponse { pub fn hijack(&self, fd: i32) -> MagnusResult<()> { let stream = unsafe { UnixStream::from_raw_fd(fd) }; - *self.data.hijacked_socket.write() = Some(stream); - if let Some(writer) = self.data.response_writer.write().as_ref() { - writer - .blocking_send(ByteFrame::Empty) - .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)? + *self.hijacked_socket.write() = Some(stream); + if let Some(sender) = self.response_sender.write().take() { + sender + .send(ResponseFrame::HijackedResponse(self.clone())) + .ok(); } - self.close(); + + self.close()?; Ok(()) } } diff --git a/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs b/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs index 8ceeb5f9..08219cc0 100644 --- a/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +++ b/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs @@ -32,18 +32,8 @@ struct PatternGroup { /// component that contains a wildcard character. fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf { if !(pattern.contains("*") || pattern.contains("?") || pattern.contains('[')) { - if let Ok(metadata) = fs::metadata(pattern) { - if metadata.is_dir() { - return fs::canonicalize(pattern).unwrap(); - } - if metadata.is_file() { - return fs::canonicalize(pattern) - .unwrap() - .parent() - .unwrap() - .to_path_buf(); - } - } + let base = PathBuf::from("."); + return fs::canonicalize(&base).unwrap_or(base); } let path = Path::new(pattern); @@ -63,12 +53,11 @@ fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf { base }; - // Canonicalize to get the absolute path. fs::canonicalize(&base).unwrap_or(base) } /// Minimum time between triggering the same pattern group (debounce time) -const DEBOUNCE_DURATION: Duration = Duration::from_millis(500); +const DEBOUNCE_DURATION: Duration = Duration::from_millis(2000); pub fn watch_groups(pattern_groups: Vec<(String, Vec>)>) -> Result> { let (r_fd, w_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| { @@ -145,13 +134,14 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec>)>) -> Result>)>) -> Result SockAddr { + pub fn addr(&self) -> String { match self { - IoStream::Tcp { addr, .. } => addr.clone(), - IoStream::TcpTls { addr, .. } => addr.clone(), - IoStream::Unix { addr, .. } => addr.clone(), - IoStream::UnixTls { addr, .. } => addr.clone(), + IoStream::Tcp { addr, .. } => addr.to_string(), + IoStream::TcpTls { addr, .. } => addr.to_string(), + IoStream::Unix { addr, .. } => addr.to_string(), + IoStream::UnixTls { addr, .. } => addr.to_string(), } } } diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs index fee00d0a..44d6e613 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs @@ -134,8 +134,7 @@ impl MiddlewareLayer for StaticAssets { let file_server = self.file_server.get().unwrap(); let encodings: &[HeaderValue] = context .supported_encoding_set() - .map(Vec::as_slice) - .unwrap_or(&[] as &[HeaderValue]); + .map_or(&[], |set| set.as_slice()); let response = file_server .serve( &req, diff --git a/crates/itsi_server/src/server/process_worker.rs b/crates/itsi_server/src/server/process_worker.rs index 11bae63d..60894e31 100644 --- a/crates/itsi_server/src/server/process_worker.rs +++ b/crates/itsi_server/src/server/process_worker.rs @@ -88,7 +88,7 @@ impl ProcessWorker { .pin_worker_cores { core_affinity::set_for_current( - CORE_IDS[self.worker_id % CORE_IDS.len()], + CORE_IDS[(self.worker_id + 1) % CORE_IDS.len()], ); } Arc::new(single_mode).run().ok(); diff --git a/crates/itsi_server/src/server/serve_strategy/acceptor.rs b/crates/itsi_server/src/server/serve_strategy/acceptor.rs index 371d0409..effca6d5 100644 --- a/crates/itsi_server/src/server/serve_strategy/acceptor.rs +++ b/crates/itsi_server/src/server/serve_strategy/acceptor.rs @@ -1,6 +1,5 @@ -use std::{ops::Deref, pin::Pin, sync::Arc, time::Duration}; - use hyper_util::rt::TokioIo; +use std::{ops::Deref, pin::Pin, sync::Arc, time::Duration}; use tokio::task::JoinSet; use tracing::debug; @@ -40,17 +39,21 @@ impl Acceptor { let io: TokioIo>> = TokioIo::new(Box::pin(stream)); let mut shutdown_channel = self.shutdown_receiver.clone(); let acceptor_args = self.acceptor_args.clone(); + let service = ItsiHttpService { + inner: Arc::new(ItsiHttpServiceInner { + acceptor_args: acceptor_args.clone(), + addr, + }), + }; + self.join_set.spawn(async move { let executor = &acceptor_args.strategy.executor; - let mut serve = Box::pin(executor.serve_connection_with_upgrades( - io, - ItsiHttpService { - inner: Arc::new(ItsiHttpServiceInner { - acceptor_args: acceptor_args.clone(), - addr: addr.to_string(), - }), - }, - )); + let svc = hyper::service::service_fn(move |req| { + let service = service.clone(); + async move { service.handle_request(req).await } + }); + + let mut serve = Box::pin(executor.serve_connection_with_upgrades(io, svc)); tokio::select! { // Await the connection finishing naturally. diff --git a/crates/itsi_server/src/server/serve_strategy/single_mode.rs b/crates/itsi_server/src/server/serve_strategy/single_mode.rs index 1eb6df9b..331ebb5a 100644 --- a/crates/itsi_server/src/server/serve_strategy/single_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/single_mode.rs @@ -45,6 +45,7 @@ pub struct SingleMode { pub status: RwLock>, } +#[derive(PartialEq, Debug)] pub enum RunningPhase { Running, ShutdownPending, @@ -59,7 +60,7 @@ impl SingleMode { executor .http1() .header_read_timeout(server_config.server_params.read().header_read_timeout) - .writev(true) + .pipeline_flush(true) .timer(TokioTimer::new()); executor .http2() @@ -253,91 +254,95 @@ impl SingleMode { return Ok(()); } let runtime = self.build_runtime(); - let result = runtime.block_on( - async { - let mut listener_task_set = JoinSet::new(); - let server_params = self.server_config.server_params.read().clone(); - if let Err(err) = server_params.initialize_middleware().await { - error!("Failed to initialize middleware: {}", err); - return Err(ItsiError::new("Failed to initialize middleware")) - } - let tokio_listeners = server_params.listeners.lock() - .drain(..) - .map(|list| { - Arc::new(list.into_tokio_listener()) - }) - .collect::>(); + let result = runtime.block_on(async { + let mut listener_task_set = JoinSet::new(); + let server_params = self.server_config.server_params.read().clone(); + if let Err(err) = server_params.initialize_middleware().await { + error!("Failed to initialize middleware: {}", err); + return Err(ItsiError::new("Failed to initialize middleware")); + } + let tokio_listeners = server_params + .listeners + .lock() + .drain(..) + .map(|list| Arc::new(list.into_tokio_listener())) + .collect::>(); - tokio_listeners.iter().cloned().for_each(|listener| { - let shutdown_sender = shutdown_sender.clone(); - let job_sender = job_sender.clone(); - let nonblocking_sender = nonblocking_sender.clone(); + tokio_listeners.iter().cloned().for_each(|listener| { + let shutdown_sender = shutdown_sender.clone(); + let job_sender = job_sender.clone(); + let nonblocking_sender = nonblocking_sender.clone(); - let mut lifecycle_rx = self.lifecycle_channel.subscribe(); - let mut shutdown_receiver = shutdown_sender.subscribe(); - let mut acceptor = Acceptor{ - acceptor_args: Arc::new( - AcceptorArgs{ - strategy: self.clone(), - listener_info: listener.listener_info(), - shutdown_receiver: shutdown_sender.subscribe(), - job_sender: job_sender.clone(), - nonblocking_sender: nonblocking_sender.clone(), - server_params: server_params.clone() - } - ), - join_set: JoinSet::new() - }; + let mut lifecycle_rx = self.lifecycle_channel.subscribe(); + let mut shutdown_receiver = shutdown_sender.subscribe(); + let mut acceptor = Acceptor { + acceptor_args: Arc::new(AcceptorArgs { + strategy: self.clone(), + listener_info: listener.listener_info(), + shutdown_receiver: shutdown_sender.subscribe(), + job_sender: job_sender.clone(), + nonblocking_sender: nonblocking_sender.clone(), + server_params: server_params.clone(), + }), + join_set: JoinSet::new(), + }; - let shutdown_rx_for_acme_task = shutdown_receiver.clone(); - let acme_task_listener_clone = listener.clone(); - listener_task_set.spawn(async move { - acme_task_listener_clone.spawn_acme_event_task(shutdown_rx_for_acme_task).await; - }); + let shutdown_rx_for_acme_task = shutdown_receiver.clone(); + let acme_task_listener_clone = listener.clone(); + // let after_accept_wait = if server_params.workers > 1{ + // Some(Duration::from_micros(10 * server_params.workers as u64))} + // else{ + // None + // }; + listener_task_set.spawn(async move { + acme_task_listener_clone + .spawn_acme_event_task(shutdown_rx_for_acme_task) + .await; + }); - listener_task_set.spawn(async move { - loop { - tokio::select! { - accept_result = listener.accept() => { - match accept_result { - Ok(accepted) => acceptor.serve_connection(accepted).await, - Err(e) => debug!("Listener.accept failed: {:?}", e) - } - }, - _ = shutdown_receiver.changed() => { - debug!("Shutdown requested via receiver"); - break; - }, - lifecycle_event = lifecycle_rx.recv() => { - match lifecycle_event { - Ok(LifecycleEvent::Shutdown) => { - debug!("Received LifecycleEvent::Shutdown"); - let _ = shutdown_sender.send(RunningPhase::ShutdownPending); - for _ in 0..worker_count { - let _ = job_sender.send(RequestJob::Shutdown).await; - let _ = nonblocking_sender.send(RequestJob::Shutdown).await; - } - break; - }, - Err(e) => error!("Error receiving lifecycle event: {:?}", e), - _ => () - } - } - } - } - acceptor.join().await; - }); - }); + listener_task_set.spawn(async move { + loop { + tokio::select! { + accept_result = listener.accept() => { + match accept_result { + Ok(accepted) => acceptor.serve_connection(accepted).await, + Err(e) => debug!("Listener.accept failed: {:?}", e) + } + }, + _ = shutdown_receiver.changed() => { + debug!("Shutdown requested via receiver"); + break; + }, + lifecycle_event = lifecycle_rx.recv() => { + match lifecycle_event { + Ok(LifecycleEvent::Shutdown) => { + debug!("Received LifecycleEvent::Shutdown"); + let _ = shutdown_sender.send(RunningPhase::ShutdownPending); + for _ in 0..worker_count { + let _ = job_sender.send_blocking(RequestJob::Shutdown); + let _ = nonblocking_sender.send_blocking(RequestJob::Shutdown); + } + break; + }, + Err(e) => error!("Error receiving lifecycle event: {:?}", e), + _ => () + } + } + } + } + acceptor.join().await; + }); + }); - if self.is_single_mode() { + if self.is_single_mode() { self.invoke_hook("after_start"); - } + } - while let Some(_res) = listener_task_set.join_next().await {} - drop(tokio_listeners); + while let Some(_res) = listener_task_set.join_next().await {} + drop(tokio_listeners); - Ok::<(), ItsiError>(()) - }); + Ok::<(), ItsiError>(()) + }); debug!("Single mode runtime exited."); diff --git a/crates/itsi_server/src/server/thread_worker.rs b/crates/itsi_server/src/server/thread_worker.rs index ad177636..7f333226 100644 --- a/crates/itsi_server/src/server/thread_worker.rs +++ b/crates/itsi_server/src/server/thread_worker.rs @@ -17,8 +17,7 @@ use std::{ atomic::{AtomicBool, AtomicU64, Ordering}, Arc, }, - thread, - time::{Duration, Instant, SystemTime, UNIX_EPOCH}, + time::{Instant, SystemTime, UNIX_EPOCH}, }; use tokio::{runtime::Builder as RuntimeBuilder, sync::watch}; use tracing::instrument; @@ -430,60 +429,61 @@ impl ThreadWorker { receiver: Arc>, terminated: Arc, ) { - let ruby = Ruby::get().unwrap(); let mut idle_counter = 0; - let self_ref = self.clone(); call_without_gvl(|| loop { - if receiver.is_empty() { - if let Some(oob_gc_threshold) = params.oob_gc_responses_threshold { - idle_counter = (idle_counter + 1) % oob_gc_threshold; - if idle_counter == 0 { - call_with_gvl(|_ruby| { - ruby.gc_start(); - }); - } - }; - } match receiver.recv_blocking() { - Ok(RequestJob::ProcessHttpRequest(request, app_proc)) => { - self_ref.request_id.fetch_add(1, Ordering::Relaxed); - self_ref.current_request_start.store( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - Ordering::Relaxed, - ); - call_with_gvl(|_ruby| { - request.process(&ruby, app_proc).ok(); - }); - if terminated.load(Ordering::Relaxed) { - break; + Err(_) => break, + Ok(RequestJob::Shutdown) => break, + Ok(request_job) => call_with_gvl(|ruby| { + self.process_one(&ruby, request_job); + while let Ok(request_job) = receiver.try_recv() { + if matches!(request_job, RequestJob::Shutdown) { + terminated.store(true, Ordering::Relaxed); + break; + } + self.process_one(&ruby, request_job); } - } - Ok(RequestJob::ProcessGrpcRequest(request, app_proc)) => { - self_ref.request_id.fetch_add(1, Ordering::Relaxed); - self_ref.current_request_start.store( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - Ordering::Relaxed, - ); - call_with_gvl(|_ruby| { - request.process(&ruby, app_proc).ok(); - }); - if terminated.load(Ordering::Relaxed) { - break; + if let Some(thresh) = params.oob_gc_responses_threshold { + idle_counter = (idle_counter + 1) % thresh; + if idle_counter == 0 { + ruby.gc_start(); + } } - } - Ok(RequestJob::Shutdown) => { - break; - } - Err(_) => { - thread::sleep(Duration::from_micros(1)); - } + }), + }; + if terminated.load(Ordering::Relaxed) { + break; } }); } + + fn process_one(self: &Arc, ruby: &Ruby, job: RequestJob) { + match job { + RequestJob::ProcessHttpRequest(request, app_proc) => { + self.request_id.fetch_add(1, Ordering::Relaxed); + self.current_request_start.store( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + Ordering::Relaxed, + ); + request.process(ruby, app_proc).ok(); + } + + RequestJob::ProcessGrpcRequest(request, app_proc) => { + self.request_id.fetch_add(1, Ordering::Relaxed); + self.current_request_start.store( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + Ordering::Relaxed, + ); + request.process(ruby, app_proc).ok(); + } + + RequestJob::Shutdown => unreachable!(), + } + } } diff --git a/crates/itsi_server/src/services/itsi_http_service.rs b/crates/itsi_server/src/services/itsi_http_service.rs index a641d984..fa089699 100644 --- a/crates/itsi_server/src/services/itsi_http_service.rs +++ b/crates/itsi_server/src/services/itsi_http_service.rs @@ -12,16 +12,14 @@ use either::Either; use http::header::ACCEPT_ENCODING; use http::{HeaderValue, Request}; use hyper::body::Incoming; -use hyper::service::Service; -use itsi_error::ItsiError; use regex::Regex; +use smallvec::SmallVec; +use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use std::time::{Duration, Instant}; -use tracing::error; - -use std::{future::Future, ops::Deref, pin::Pin, sync::Arc}; use tokio::time::timeout; +use tracing::error; #[derive(Clone)] pub struct ItsiHttpService { @@ -80,12 +78,14 @@ pub struct RequestContextInner { pub request_start_time: OnceLock>, pub start_instant: Instant, pub if_none_match: OnceLock>, - pub supported_encoding_set: OnceLock>, + pub supported_encoding_set: OnceLock, pub is_ruby_request: Arc, } +type AcceptEncodingSet = SmallVec<[HeaderValue; 2]>; + impl HttpRequestContext { - fn new( + pub fn new( service: ItsiHttpService, matching_pattern: Option>, accept: ResponseFormat, @@ -109,12 +109,14 @@ impl HttpRequestContext { } pub fn set_supported_encoding_set(&self, req: &HttpRequest) { - self.inner.supported_encoding_set.get_or_init(move || { - req.headers() - .get_all(ACCEPT_ENCODING) - .into_iter() - .cloned() - .collect::>() + self.inner.supported_encoding_set.get_or_init(|| { + let mut set: AcceptEncodingSet = SmallVec::new(); + + for hv in req.headers().get_all(ACCEPT_ENCODING) { + set.push(hv.clone()); // clone ≈ 16 B struct copy + } + + set }); } @@ -164,7 +166,7 @@ impl HttpRequestContext { self.inner.response_format.get().unwrap() } - pub fn supported_encoding_set(&self) -> Option<&Vec> { + pub fn supported_encoding_set(&self) -> Option<&AcceptEncodingSet> { self.inner.supported_encoding_set.get() } } @@ -173,13 +175,8 @@ const SERVER_TOKEN_VERSION: HeaderValue = HeaderValue::from_static(concat!("Itsi/", env!("CARGO_PKG_VERSION"))); const SERVER_TOKEN_NAME: HeaderValue = HeaderValue::from_static("Itsi"); -impl Service> for ItsiHttpService { - type Response = HttpResponse; - type Error = ItsiError; - type Future = Pin> + Send>>; - - fn call(&self, req: Request) -> Self::Future { - let self_clone = self.clone(); +impl ItsiHttpService { + pub async fn handle_request(&self, req: Request) -> itsi_error::Result { let mut req = req.limit(); let accept: ResponseFormat = req.accept().into(); let is_single_mode = self.server_params.workers == 1; @@ -191,7 +188,7 @@ impl Service> for ItsiHttpService { let token_preference = self.server_params.itsi_server_token_preference; let service_future = async move { - let middleware_stack = self_clone + let middleware_stack = self .server_params .middleware .get() @@ -202,7 +199,7 @@ impl Service> for ItsiHttpService { let mut resp: Option = None; let mut context = - HttpRequestContext::new(self_clone.clone(), matching_pattern, accept, irr_clone); + HttpRequestContext::new(self.clone(), matching_pattern, accept, irr_clone); let mut depth = 0; for (index, elm) in stack.iter().enumerate() { @@ -243,28 +240,26 @@ impl Service> for ItsiHttpService { }; if let Some(timeout_duration) = request_timeout { - Box::pin(async move { - match timeout(timeout_duration, service_future).await { - Ok(result) => result, - Err(_) => { - // If we're still running Ruby at this point, we can't just kill the - // thread as it might be in a critical section. - // Instead we must ask the worker to hot restart. - if is_ruby_request.load(Ordering::Relaxed) { - if is_single_mode { - // If we're in single mode, re-exec the whole process - send_lifecycle_event(LifecycleEvent::Restart); - } else { - // Otherwise we can shutdown the worker and rely on the master to restart it - send_lifecycle_event(LifecycleEvent::Shutdown); - } + match timeout(timeout_duration, service_future).await { + Ok(result) => result, + Err(_) => { + // If we're still running Ruby at this point, we can't just kill the + // thread as it might be in a critical section. + // Instead we must ask the worker to hot restart. + if is_ruby_request.load(Ordering::Relaxed) { + if is_single_mode { + // If we're in single mode, re-exec the whole process + send_lifecycle_event(LifecycleEvent::Restart); + } else { + // Otherwise we can shutdown the worker and rely on the master to restart it + send_lifecycle_event(LifecycleEvent::Shutdown); } - Ok(TIMEOUT_RESPONSE.to_http_response(accept).await) } + Ok(TIMEOUT_RESPONSE.to_http_response(accept).await) } - }) + } } else { - Box::pin(service_future) + service_future.await } } } diff --git a/gems/scheduler/Cargo.lock b/gems/scheduler/Cargo.lock index 52353ac7..dcf8f3a8 100644 --- a/gems/scheduler/Cargo.lock +++ b/gems/scheduler/Cargo.lock @@ -213,7 +213,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "itsi-scheduler" -version = "0.2.16" +version = "0.2.17" dependencies = [ "bytes", "derive_more", diff --git a/gems/scheduler/lib/itsi/scheduler/version.rb b/gems/scheduler/lib/itsi/scheduler/version.rb index dbc2cc2b..d747b526 100644 --- a/gems/scheduler/lib/itsi/scheduler/version.rb +++ b/gems/scheduler/lib/itsi/scheduler/version.rb @@ -2,6 +2,6 @@ module Itsi class Scheduler - VERSION = "0.2.16" + VERSION = "0.2.17" end end diff --git a/gems/server/Cargo.lock b/gems/server/Cargo.lock index 37585e51..69137711 100644 --- a/gems/server/Cargo.lock +++ b/gems/server/Cargo.lock @@ -220,6 +220,28 @@ dependencies = [ "zstd-safe", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -1644,11 +1666,12 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "itsi-server" -version = "0.2.16" +version = "0.2.17" dependencies = [ "argon2", "async-channel", "async-compression", + "async-stream", "async-trait", "base64 0.22.1", "bcrypt", @@ -1695,6 +1718,7 @@ dependencies = [ "serde_magnus", "sha-crypt", "sha2", + "smallvec", "socket2", "sysinfo", "tempfile", diff --git a/gems/server/exe/itsi b/gems/server/exe/itsi index 45188658..25c75c88 100755 --- a/gems/server/exe/itsi +++ b/gems/server/exe/itsi @@ -164,7 +164,12 @@ if ENV['COMP_LINE'] || ARGV.include?('--completion') exit end -parser.parse! +begin + parser.parse! +rescue StandardError => e + puts e.message + exit +end case (command = ARGV.shift) when *COMMANDS.keys diff --git a/gems/server/lib/itsi/http_request.rb b/gems/server/lib/itsi/http_request.rb index 1cc42fd8..5ca2e53b 100644 --- a/gems/server/lib/itsi/http_request.rb +++ b/gems/server/lib/itsi/http_request.rb @@ -14,50 +14,35 @@ class HttpRequest EMPTY_IO = StringIO.new("").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) } RACK_HEADER_MAP = StandardHeaders::ALL.map do |header| - rack_form = if header == "content-type" - "CONTENT_TYPE" - elsif header == "content-length" - "CONTENT_LENGTH" - else - "HTTP_#{header.upcase.gsub(/-/, "_")}" - end + rack_form = \ + if header == "content-type" + "CONTENT_TYPE" + elsif header == "content-length" + "CONTENT_LENGTH" + else + "HTTP_#{header.upcase.gsub(/-/, "_")}" + end [header, rack_form] - end.to_h.tap do |hm| - hm.default_proc = proc { |_, key| "HTTP_#{key.upcase.gsub(/-/, "_")}" } - end + end.to_h - RACK_ENV_TEMPLATE = { - "SERVER_SOFTWARE" => "Itsi", - "rack.errors" => $stderr, - "rack.multithread" => true, - "rack.multiprocess" => true, - "rack.run_once" => false, - "rack.hijack?" => true, - "rack.multipart.buffer_size" => 16_384, - "SCRIPT_NAME" => "", - "REQUEST_METHOD" => "", - "PATH_INFO" => "", - "REQUEST_PATH" => "", - "QUERY_STRING" => "", - "REMOTE_ADDR" => "", - "SERVER_PORT" => "", - "SERVER_NAME" => "", - "SERVER_PROTOCOL" => "", - "HTTP_HOST" => "", - "HTTP_VERSION" => "", - "itsi.request" => "", - "itsi.response" => "", - "rack.version" => nil, - "rack.url_scheme" => "", - "rack.input" => "", - "rack.hijack" => "" - }.freeze + RACK_HEADER_MAP.default_proc = proc { |_, key| "HTTP_#{key.upcase.gsub(/-/, "_")}" } + + HTTP_09 = "HTTP/0.9" + HTTP_09_ARR = ["HTTP/0.9"].freeze + HTTP_10 = "HTTP/1.0" + HTTP_10_ARR = ["HTTP/1.0"].freeze + HTTP_11 = "HTTP/1.1" + HTTP_11_ARR = ["HTTP/1.1"].freeze + HTTP_20 = "HTTP/2.0" + HTTP_20_ARR = ["HTTP/2.0"].freeze + HTTP_30 = "HTTP/3.0" + HTTP_30_ARR = ["HTTP/3.0"].freeze def to_rack_env path = self.path host = self.host version = self.version - env = RACK_ENV_TEMPLATE.dup + env = RackEnvPool.checkout env["SCRIPT_NAME"] = script_name env["REQUEST_METHOD"] = request_method env["REQUEST_PATH"] = env["PATH_INFO"] = path @@ -68,11 +53,18 @@ def to_rack_env env["HTTP_VERSION"] = env["SERVER_PROTOCOL"] = version env["itsi.request"] = self env["itsi.response"] = response - env["rack.version"] = [version] + env["rack.version"] = \ + case version + when HTTP_09 then HTTP_09_ARR + when HTTP_10 then HTTP_10_ARR + when HTTP_11 then HTTP_11_ARR + when HTTP_20 then HTTP_20_ARR + when HTTP_30 then HTTP_30_ARR + end env["rack.url_scheme"] = scheme env["rack.input"] = build_input_io env["rack.hijack"] = method(:hijack) - headers.each do |(k, v)| + each_header do |k, v| env[case k when "content-type" then "CONTENT_TYPE" when "content-length" then "CONTENT_LENGTH" diff --git a/gems/server/lib/itsi/http_response.rb b/gems/server/lib/itsi/http_response.rb index 55d84b6d..230789fd 100644 --- a/gems/server/lib/itsi/http_response.rb +++ b/gems/server/lib/itsi/http_response.rb @@ -16,6 +16,7 @@ def respond( body = body.to_s unless body.is_a?(String) if headers + reserve_headers(headers.size) headers.each do |key, value| if value.is_a?(Array) value.each { |v| add_header(key, v) } diff --git a/gems/server/lib/itsi/rack_env_pool.rb b/gems/server/lib/itsi/rack_env_pool.rb new file mode 100644 index 00000000..eac64735 --- /dev/null +++ b/gems/server/lib/itsi/rack_env_pool.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Itsi + module RackEnvPool + + RACK_ENV_TEMPLATE = { + "SERVER_SOFTWARE" => "Itsi", + "rack.errors" => $stderr, + "rack.multithread" => true, + "rack.multiprocess" => true, + "rack.run_once" => false, + "rack.hijack?" => true, + "rack.multipart.buffer_size" => 16_384, + "SCRIPT_NAME" => "", + "REQUEST_METHOD" => "", + "PATH_INFO" => "", + "REQUEST_PATH" => "", + "QUERY_STRING" => "", + "REMOTE_ADDR" => "", + "SERVER_PORT" => "", + "SERVER_NAME" => "", + "SERVER_PROTOCOL" => "", + "HTTP_HOST" => "", + "HTTP_VERSION" => "", + "itsi.request" => "", + "itsi.response" => "", + "rack.version" => nil, + "rack.url_scheme" => "", + "rack.input" => "", + "rack.hijack" => "" + }.freeze + + POOL = [] + + def self.checkout # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength + POOL.pop&.tap do |recycled| + recycled.keys.each do |key| + case key + when "SERVER_SOFTWARE" then recycled[key] = "Itsi" + when "rack.errors" then recycled[key] = $stderr + when "rack.multithread", "rack.multiprocess", "rack.hijack?" then recycled[key] = true + when "rack.run_once" then recycled[key] = false + when "rack.multipart.buffer_size" then recycled[key] = 16_384 + when "SCRIPT_NAME", "REQUEST_METHOD", "PATH_INFO", "REQUEST_PATH", "QUERY_STRING", "REMOTE_ADDR", + "SERVER_PORT", "SERVER_NAME", "SERVER_PROTOCOL", "HTTP_HOST", "HTTP_VERSION", "itsi.request", + "itsi.response", "rack.version", "rack.url_scheme", "rack.input", "rack.hijack" + nil + else recycled.delete(key) + end + end + end || RACK_ENV_TEMPLATE.dup + end + + def self.checkin(env) + POOL << env + end + end + +end diff --git a/gems/server/lib/itsi/server.rb b/gems/server/lib/itsi/server.rb index bf8dd0ea..11a9d379 100644 --- a/gems/server/lib/itsi/server.rb +++ b/gems/server/lib/itsi/server.rb @@ -12,6 +12,7 @@ require_relative "server/config" require_relative "server/typed_handlers" require_relative "standard_headers" +require_relative "rack_env_pool" require_relative "http_request" require_relative "http_response" require_relative "passfile" diff --git a/gems/server/lib/itsi/server/config.rb b/gems/server/lib/itsi/server/config.rb index ca44e390..12580784 100644 --- a/gems/server/lib/itsi/server/config.rb +++ b/gems/server/lib/itsi/server/config.rb @@ -65,14 +65,19 @@ def self.build_config(args, config_file_path, builder_proc = nil) } end elsif File.exist?(config_file_path.to_s) - DSL.evaluate(config_file_path) + DSL.evaluate do + include config_file_path.gsub(".rb", "") + rackup_file args[:rackup_file], script_name: "/" if args.key?(:rackup_file) + end elsif File.exist?("./config.ru") DSL.evaluate do preload true - rackup_file args.fetch(:rackup_file, "./config.ru") + rackup_file args.fetch(:rackup_file, "./config.ru"), script_name: "/" end else - DSL.evaluate {} + DSL.evaluate do + rackup_file args[:rackup_file], script_name: "/" if args.key?(:rackup_file) + end end itsifile_config.transform_keys!(&:to_sym) @@ -222,6 +227,11 @@ def self.reload_exec(listener_info) # Find config file path, if it exists. def self.config_file_path(config_file_path = nil) + + if config_file_path && !File.exist?(config_file_path) + raise "Config file #{config_file_path} does not exist" + end + config_file_path ||= \ if File.exist?(ITSI_DEFAULT_CONFIG_FILE) ITSI_DEFAULT_CONFIG_FILE diff --git a/gems/server/lib/itsi/server/config/options/auto_reload_config.rb b/gems/server/lib/itsi/server/config/options/auto_reload_config.rb index 00a6787a..eaf31642 100644 --- a/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +++ b/gems/server/lib/itsi/server/config/options/auto_reload_config.rb @@ -14,11 +14,15 @@ def self.option_name end def build! + return if @auto_reloading + src = caller.find{|l| !(l =~ /lib\/itsi\/server\/config/) }.split(":").first + location.instance_eval do return if @auto_reloading if @included @included.each do |file| + next if "#{file}.rb" == src if ENV["BUNDLE_BIN_PATH"] watch "#{file}.rb", [%w[bundle exec itsi restart]] else @@ -29,9 +33,9 @@ def build! @auto_reloading = true if ENV["BUNDLE_BIN_PATH"] - watch "Itsi.rb", [%w[bundle exec itsi restart]] + watch src, [%w[bundle exec itsi restart]] else - watch "Itsi.rb", [%w[itsi restart]] + watch src, [%w[itsi restart]] end end end diff --git a/gems/server/lib/itsi/server/rack/handler/itsi.rb b/gems/server/lib/itsi/server/rack/handler/itsi.rb index cee1a370..696cc8d4 100644 --- a/gems/server/lib/itsi/server/rack/handler/itsi.rb +++ b/gems/server/lib/itsi/server/rack/handler/itsi.rb @@ -8,7 +8,8 @@ def self.run(app, options = {}) port = options.fetch(:Port, 3001) ::Itsi::Server.start( { - binds: ["http://#{host}:#{port}"] + binds: ["http://#{host}:#{port}"], + threads: 5 } ) do run app diff --git a/gems/server/lib/itsi/server/rack_interface.rb b/gems/server/lib/itsi/server/rack_interface.rb index 5a1d2095..68f474cd 100644 --- a/gems/server/lib/itsi/server/rack_interface.rb +++ b/gems/server/lib/itsi/server/rack_interface.rb @@ -12,7 +12,7 @@ def self.for(app) end end lambda do |request| - Server.respond(request, app.call(request.to_rack_env)) + Server.respond(request, app.call(env = request.to_rack_env), env) end end @@ -20,14 +20,14 @@ def self.for(app) # Here we build the env, and invoke the Rack app's call method. # We then turn the Rack response into something Itsi server understands. def call(app, request) - respond request, app.call(request.to_rack_env) + respond request, app.call(env = request.to_rack_env), env end # Itsi responses are asynchronous and can be streamed. # Response chunks are sent using response.send_frame # and the response is finished using response.close_write. # If only a single chunk is written, you can use the #send_and_close method. - def respond(request, (status, headers, body)) + def respond(request, (status, headers, body), env) response = request.response # Don't try and respond if we've been hijacked. @@ -39,13 +39,16 @@ def respond(request, (status, headers, body)) # 2. Set Headers body_streamer = streaming_body?(body) ? body : headers.delete("rack.hijack") - headers.each do |key, value| - if value.is_a?(Array) + + response.reserve_headers(headers.size) + + for key, value in headers + case value + when String then response[key] = value + when Array value.each do |v| response[key] = v end - elsif value.is_a?(String) - response[key] = value end end @@ -53,15 +56,15 @@ def respond(request, (status, headers, body)) # As soon as we start setting the response # the server will begin to stream it to the client. - # If we're partially hijacked or returned a streaming body, - # stream this response. if body_streamer + # If we're partially hijacked or returned a streaming body, + # stream this response. body_streamer.call(response) - # If we're enumerable with more than one chunk - # also stream, otherwise write in a single chunk elsif body.respond_to?(:each) || body.respond_to?(:to_ary) + # If we're enumerable with more than one chunk + # also stream, otherwise write in a single chunk unless body.respond_to?(:each) body = body.to_ary raise "Body #to_ary didn't return an array" unless body.is_a?(Array) @@ -78,8 +81,10 @@ def respond(request, (status, headers, body)) else response.send_and_close(body.to_s) end + rescue EOFError + response.close ensure - response.close_write + RackEnvPool.checkin(env) body.close if body.respond_to?(:close) end diff --git a/gems/server/lib/itsi/server/version.rb b/gems/server/lib/itsi/server/version.rb index 3d1116ad..ee5359f2 100644 --- a/gems/server/lib/itsi/server/version.rb +++ b/gems/server/lib/itsi/server/version.rb @@ -2,6 +2,6 @@ module Itsi class Server - VERSION = "0.2.16" + VERSION = "0.2.17" end end diff --git a/gems/server/lib/ruby_lsp/itsi/addon.rb b/gems/server/lib/ruby_lsp/itsi/addon.rb index f82359e3..d29d31f6 100644 --- a/gems/server/lib/ruby_lsp/itsi/addon.rb +++ b/gems/server/lib/ruby_lsp/itsi/addon.rb @@ -22,14 +22,14 @@ def version def create_completion_listener(response_builder, node_context, dispatcher, uri) - return unless uri.to_s.end_with?("Itsi.rb") + return unless uri.to_s =~ /itsi.rb$/i @in_itsi_file = true CompletionListener.new(response_builder, node_context, dispatcher, uri) end def create_hover_listener(response_builder, node_context, dispatcher) hl = dispatcher.listeners[:on_call_node_enter].find { |c| c.is_a?(RubyLsp::Listeners::Hover) } - return unless hl.instance_variable_get("@path").to_s.end_with?("Itsi.rb") + return unless hl.instance_variable_get("@path").to_s =~ /itsi.rb$/i HoverListener.new(response_builder, node_context, dispatcher) end end diff --git a/itsi.gemspec b/itsi.gemspec index ccdaa9f4..9aa15f43 100644 --- a/itsi.gemspec +++ b/itsi.gemspec @@ -33,6 +33,6 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] - spec.add_dependency 'itsi-scheduler', '~> 0.2.16' - spec.add_dependency 'itsi-server', '~> 0.2.16' + spec.add_dependency 'itsi-scheduler', '~> 0.2.17' + spec.add_dependency 'itsi-server', '~> 0.2.17' end diff --git a/lib/itsi/version.rb b/lib/itsi/version.rb index 53c9bcbb..21b43b8c 100644 --- a/lib/itsi/version.rb +++ b/lib/itsi/version.rb @@ -1,3 +1,3 @@ module Itsi - VERSION = '0.2.16' + VERSION = '0.2.17' end From b469220ddff7fab6e59b92f5d8fbdac5dd425d91 Mon Sep 17 00:00:00 2001 From: Wouter Coppieters Date: Tue, 13 May 2025 19:51:55 +1200 Subject: [PATCH 2/6] Restore after accept wait --- .../src/server/serve_strategy/single_mode.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/itsi_server/src/server/serve_strategy/single_mode.rs b/crates/itsi_server/src/server/serve_strategy/single_mode.rs index 331ebb5a..923f50e7 100644 --- a/crates/itsi_server/src/server/serve_strategy/single_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/single_mode.rs @@ -289,11 +289,11 @@ impl SingleMode { let shutdown_rx_for_acme_task = shutdown_receiver.clone(); let acme_task_listener_clone = listener.clone(); - // let after_accept_wait = if server_params.workers > 1{ - // Some(Duration::from_micros(10 * server_params.workers as u64))} - // else{ - // None - // }; + let after_accept_wait = if server_params.workers > 1{ + Some(Duration::from_nanos(10 * server_params.workers as u64))} + else{ + None + }; listener_task_set.spawn(async move { acme_task_listener_clone .spawn_acme_event_task(shutdown_rx_for_acme_task) @@ -308,6 +308,9 @@ impl SingleMode { Ok(accepted) => acceptor.serve_connection(accepted).await, Err(e) => debug!("Listener.accept failed: {:?}", e) } + if let Some(after_accept_wait) = after_accept_wait{ + tokio::time::sleep(after_accept_wait).await; + } }, _ = shutdown_receiver.changed() => { debug!("Shutdown requested via receiver"); From 9deac4cb3f6eb48f7313605927e3740357fc8e54 Mon Sep 17 00:00:00 2001 From: Wouter Coppieters Date: Wed, 14 May 2025 13:38:07 +1200 Subject: [PATCH 3/6] Test rebind on linux --- benchmarks/rack_bench.rb | 2 +- .../itsi_grpc_response_stream/mod.rs | 2 +- .../src/ruby_types/itsi_http_response.rs | 4 +- .../itsi_server/src/ruby_types/itsi_server.rs | 2 +- .../itsi_server/src/server/binds/listener.rs | 52 ++++++++++++++++--- .../itsi_server/src/server/process_worker.rs | 4 +- .../src/server/serve_strategy/cluster_mode.rs | 37 ++++++++++--- .../src/server/serve_strategy/single_mode.rs | 27 ++++++---- .../itsi_server/src/server/thread_worker.rs | 45 +++++++++------- 9 files changed, 126 insertions(+), 49 deletions(-) diff --git a/benchmarks/rack_bench.rb b/benchmarks/rack_bench.rb index f4c74d4d..8b8b0917 100644 --- a/benchmarks/rack_bench.rb +++ b/benchmarks/rack_bench.rb @@ -142,7 +142,7 @@ def run_benchmark( workers: workers, http2: http2, concurrency: concurrency, - **(workers == 1 ? {rss_mb: (server.rss / 1024.0 / 1024.0).round(2) } : {}), + **(workers == 1 ? {rss_mb: (server.rss / (1024.0 * 1024.0)).round(2) } : {}), results: combine_results(result_outputs), timestamp: Time.now.utc.iso8601 } diff --git a/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs b/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs index 9bdec5ed..59b7af54 100644 --- a/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +++ b/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs @@ -234,7 +234,7 @@ impl ItsiGrpcResponseStream { _ = shutdown_rx.changed() => { match *shutdown_rx.borrow() { RunningPhase::ShutdownPending => { - warn!("Disconnecting streaming client."); + debug!("Disconnecting streaming client."); return None; }, _ => continue, diff --git a/crates/itsi_server/src/ruby_types/itsi_http_response.rs b/crates/itsi_server/src/ruby_types/itsi_http_response.rs index 648ed1b5..f3f270c0 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_response.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_response.rs @@ -30,7 +30,7 @@ use tokio::{ }; use tokio_util::io::ReaderStream; -use tracing::{info, warn}; +use tracing::{debug, info, warn}; use crate::server::{http_message_types::HttpResponse, serve_strategy::single_mode::RunningPhase}; @@ -255,7 +255,7 @@ impl ItsiHttpResponse { while let Some(bytes) = reader.recv().await{ yield Ok(Frame::data(bytes)); } - warn!("Disconnecting streaming client."); + debug!("Disconnecting streaming client."); break; } } diff --git a/crates/itsi_server/src/ruby_types/itsi_server.rs b/crates/itsi_server/src/ruby_types/itsi_server.rs index 54e2c2aa..88b6c0ee 100644 --- a/crates/itsi_server/src/ruby_types/itsi_server.rs +++ b/crates/itsi_server/src/ruby_types/itsi_server.rs @@ -64,7 +64,7 @@ impl ItsiServer { Ok(if server_config.server_params.read().workers > 1 { ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config.clone()))) } else { - ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone())?)) + ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone(), 0)?)) }) } diff --git a/crates/itsi_server/src/server/binds/listener.rs b/crates/itsi_server/src/server/binds/listener.rs index 6129bbc3..7af10c89 100644 --- a/crates/itsi_server/src/server/binds/listener.rs +++ b/crates/itsi_server/src/server/binds/listener.rs @@ -9,7 +9,7 @@ use super::bind_protocol::BindProtocol; use super::tls::ItsiTlsAcceptor; use itsi_error::{ItsiError, Result}; use itsi_tracing::info; -use socket2::{Domain, Protocol, Socket, Type}; +use socket2::{Domain, Protocol, SockRef, Socket, Type}; use std::fmt::Display; use std::net::{IpAddr, SocketAddr, TcpListener}; use std::os::fd::{AsRawFd, FromRawFd, RawFd}; @@ -274,15 +274,53 @@ impl Display for Listener { } impl Listener { - pub fn into_tokio_listener(self) -> TokioListener { + pub fn rebind_listener(listener: TcpListener) -> TcpListener { + let sock = SockRef::from(&listener); + let (reuse_address, reuse_port) = ( + sock.reuse_address().unwrap_or(true), + sock.reuse_port().unwrap_or(true), + ); + + if !reuse_address || !reuse_port { + return listener; + } + + let (ip, port) = sock + .local_addr() + .unwrap() + .as_socket() + .map(|addr| (addr.ip(), addr.port())) + .unwrap(); + + let socket_opts = SocketOpts { + reuse_address: sock.reuse_address().unwrap_or(true), // default: true + reuse_port: sock.reuse_port().unwrap_or(false), // default: false + nodelay: sock.nodelay().unwrap_or(false), // default: false + recv_buffer_size: sock.recv_buffer_size().unwrap_or(0), + send_buffer_size: sock.send_buffer_size().unwrap_or(0), + listen_backlog: 1024, // cannot query – pick sane default + }; + + connect_tcp_socket(ip, port, &socket_opts).unwrap() + } + + pub fn into_tokio_listener(self, no_rebind: bool) -> TokioListener { match self { - Listener::Tcp(listener) => { + Listener::Tcp(mut listener) => { + if cfg!(target_os = "linux") && !no_rebind { + listener = Listener::rebind_listener(listener); + } TokioListener::Tcp(TokioTcpListener::from_std(listener).unwrap()) } - Listener::TcpTls((listener, acceptor)) => TokioListener::TcpTls( - TokioTcpListener::from_std(listener).unwrap(), - acceptor.clone(), - ), + Listener::TcpTls((mut listener, acceptor)) => { + if cfg!(target_os = "linux") && !no_rebind { + listener = Listener::rebind_listener(listener); + } + TokioListener::TcpTls( + TokioTcpListener::from_std(listener).unwrap(), + acceptor.clone(), + ) + } Listener::Unix(listener) => { TokioListener::Unix(TokioUnixListener::from_std(listener).unwrap()) } diff --git a/crates/itsi_server/src/server/process_worker.rs b/crates/itsi_server/src/server/process_worker.rs index 60894e31..693e5691 100644 --- a/crates/itsi_server/src/server/process_worker.rs +++ b/crates/itsi_server/src/server/process_worker.rs @@ -79,7 +79,7 @@ impl ProcessWorker { ) { error!("Failed to set process group ID: {}", e); } - match SingleMode::new(cluster_template.server_config.clone()) { + match SingleMode::new(cluster_template.server_config.clone(), self.worker_id) { Ok(single_mode) => { if cluster_template .server_config @@ -88,7 +88,7 @@ impl ProcessWorker { .pin_worker_cores { core_affinity::set_for_current( - CORE_IDS[(self.worker_id + 1) % CORE_IDS.len()], + CORE_IDS[self.worker_id % CORE_IDS.len()], ); } Arc::new(single_mode).run().ok(); diff --git a/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs b/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs index 06a76c6a..5c56673b 100644 --- a/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs @@ -8,7 +8,7 @@ use magnus::Value; use nix::{libc::exit, unistd::Pid}; use std::{ - sync::{atomic::AtomicUsize, Arc}, + sync::Arc, time::{Duration, Instant}, }; use tokio::{ @@ -23,15 +23,14 @@ pub(crate) struct ClusterMode { pub lifecycle_channel: broadcast::Sender, } -static WORKER_ID: AtomicUsize = AtomicUsize::new(0); static CHILD_SIGNAL_SENDER: parking_lot::Mutex>> = parking_lot::Mutex::new(None); impl ClusterMode { pub fn new(server_config: Arc) -> Self { let process_workers = (0..server_config.server_params.read().workers) - .map(|_| ProcessWorker { - worker_id: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + .map(|id| ProcessWorker { + worker_id: id as usize, ..Default::default() }) .collect(); @@ -60,6 +59,22 @@ impl ClusterMode { } } + fn next_worker_id(&self) -> usize { + let mut taken: Vec<_> = self + .process_workers + .lock() + .iter() + .map(|w| w.worker_id) + .collect(); + taken.sort_unstable(); + for (expected, &id) in taken.iter().enumerate() { + if id != expected { + return expected; + } + } + taken.len() + } + #[allow(clippy::await_holding_lock)] pub async fn handle_lifecycle_event( self: Arc, @@ -112,7 +127,7 @@ impl ClusterMode { while workers_to_load > 0 { let mut workers = self.process_workers.lock(); let worker = ProcessWorker { - worker_id: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + worker_id: self.next_worker_id(), ..Default::default() }; let worker_clone = worker.clone(); @@ -130,7 +145,7 @@ impl ClusterMode { LifecycleEvent::IncreaseWorkers => { let mut workers = self.process_workers.lock(); let worker = ProcessWorker { - worker_id: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + worker_id: self.next_worker_id(), ..Default::default() }; let worker_clone = worker.clone(); @@ -275,11 +290,21 @@ impl ClusterMode { pub fn run(self: Arc) -> Result<()> { info!("Starting in Cluster mode"); self.invoke_hook("before_fork"); + self.process_workers .lock() .iter() .try_for_each(|worker| worker.boot(Arc::clone(&self)))?; + if cfg!(target_os = "linux") { + self.server_config + .server_params + .write() + .listeners + .lock() + .drain(..); + }; + let (sender, mut receiver) = watch::channel(()); *CHILD_SIGNAL_SENDER.lock() = Some(sender); diff --git a/crates/itsi_server/src/server/serve_strategy/single_mode.rs b/crates/itsi_server/src/server/serve_strategy/single_mode.rs index 923f50e7..808cb579 100644 --- a/crates/itsi_server/src/server/serve_strategy/single_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/single_mode.rs @@ -38,6 +38,7 @@ use tokio::{ use tracing::instrument; pub struct SingleMode { + pub worker_id: usize, pub executor: Builder, pub server_config: Arc, pub(crate) lifecycle_channel: broadcast::Sender, @@ -54,7 +55,7 @@ pub enum RunningPhase { impl SingleMode { #[instrument(parent=None, skip_all)] - pub fn new(server_config: Arc) -> Result { + pub fn new(server_config: Arc, worker_id: usize) -> Result { server_config.server_params.read().preload_ruby()?; let mut executor = Builder::new(TokioExecutor::new()); executor @@ -71,6 +72,7 @@ impl SingleMode { .max_send_buf_size(16 * 1024 * 1024); Ok(Self { + worker_id, executor, server_config, lifecycle_channel: SIGNAL_HANDLER_CHANNEL.0.clone(), @@ -79,6 +81,10 @@ impl SingleMode { }) } + pub fn is_zero_worker(&self) -> bool { + self.worker_id == 0 + } + pub fn build_runtime(&self) -> Runtime { let mut builder: RuntimeBuilder = if self .server_config @@ -228,13 +234,15 @@ impl SingleMode { #[instrument(name="worker", parent=None, skip(self), fields(pid=format!("{}", Pid::this())))] pub fn run(self: Arc) -> Result<()> { - let (thread_workers, job_sender, nonblocking_sender) = - build_thread_workers(self.server_config.server_params.read().clone(), Pid::this()) - .inspect_err(|e| { - if let Some(err_val) = e.value() { - print_rb_backtrace(err_val); - } - })?; + let (thread_workers, job_sender, nonblocking_sender) = build_thread_workers( + self.server_config.server_params.read().clone(), + self.worker_id, + ) + .inspect_err(|e| { + if let Some(err_val) = e.value() { + print_rb_backtrace(err_val); + } + })?; let worker_count = thread_workers.len(); info!( @@ -245,6 +253,7 @@ impl SingleMode { let shutdown_timeout = self.server_config.server_params.read().shutdown_timeout; let (shutdown_sender, _) = watch::channel(RunningPhase::Running); let monitor_thread = self.clone().start_monitors(thread_workers.clone()); + let is_zero_worker = self.is_zero_worker(); if monitor_thread.is_none() { error!("Failed to start monitor thread"); return Err(ItsiError::new("Failed to start monitor thread")); @@ -265,7 +274,7 @@ impl SingleMode { .listeners .lock() .drain(..) - .map(|list| Arc::new(list.into_tokio_listener())) + .map(|list| Arc::new(list.into_tokio_listener(is_zero_worker))) .collect::>(); tokio_listeners.iter().cloned().for_each(|listener| { diff --git a/crates/itsi_server/src/server/thread_worker.rs b/crates/itsi_server/src/server/thread_worker.rs index 7f333226..c76444a7 100644 --- a/crates/itsi_server/src/server/thread_worker.rs +++ b/crates/itsi_server/src/server/thread_worker.rs @@ -3,13 +3,12 @@ use itsi_error::ItsiError; use itsi_rb_helpers::{ call_with_gvl, call_without_gvl, create_ruby_thread, kill_threads, HeapValue, }; -use itsi_tracing::{debug, error, warn}; +use itsi_tracing::{debug, error}; use magnus::{ error::Result, value::{InnerValue, Lazy, LazyId, Opaque, ReprValue}, Module, RClass, Ruby, Thread, Value, }; -use nix::unistd::Pid; use parking_lot::{Mutex, RwLock}; use std::{ ops::Deref, @@ -34,7 +33,7 @@ use super::request_job::RequestJob; pub struct ThreadWorker { pub params: Arc, pub id: u8, - pub name: String, + pub worker_id: usize, pub request_id: AtomicU64, pub current_request_start: AtomicU64, pub receiver: Arc>, @@ -63,8 +62,11 @@ type ThreadWorkerBuildResult = Result<( Sender, )>; -#[instrument(name = "boot", parent=None, skip(params, pid))] -pub fn build_thread_workers(params: Arc, pid: Pid) -> ThreadWorkerBuildResult { +#[instrument(name = "boot", parent=None, skip(params, worker_id))] +pub fn build_thread_workers( + params: Arc, + worker_id: usize, +) -> ThreadWorkerBuildResult { let blocking_thread_count = params.threads; let nonblocking_thread_count = params.scheduler_threads; let ruby_thread_request_backlog_size: usize = params @@ -82,7 +84,7 @@ pub fn build_thread_workers(params: Arc, pid: Pid) -> ThreadWorker ThreadWorker::new( params.clone(), id, - format!("{:?}#{:?}", pid, id), + worker_id, blocking_receiver_ref.clone(), blocking_sender_ref.clone(), if nonblocking_thread_count.is_some() { @@ -105,7 +107,7 @@ pub fn build_thread_workers(params: Arc, pid: Pid) -> ThreadWorker workers.push(ThreadWorker::new( params.clone(), id, - format!("{:?}#{:?}", pid, id), + worker_id, nonblocking_receiver_ref.clone(), nonblocking_sender_ref.clone(), Some(scheduler_class), @@ -140,7 +142,7 @@ impl ThreadWorker { pub fn new( params: Arc, id: u8, - name: String, + worker_id: usize, receiver: Arc>, sender: Sender, scheduler_class: Option>, @@ -148,9 +150,9 @@ impl ThreadWorker { let worker = Arc::new(Self { params, id, + worker_id, request_id: AtomicU64::new(0), current_request_start: AtomicU64::new(0), - name, receiver, sender, thread: RwLock::new(None), @@ -180,24 +182,22 @@ impl ThreadWorker { } pub fn run(self: Arc) -> Result<()> { - let name = self.name.clone(); let receiver = self.receiver.clone(); let terminated = self.terminated.clone(); let scheduler_class = self.scheduler_class; let params = self.params.clone(); let self_ref = self.clone(); - let id = self.id; + let worker_id = self.worker_id; call_with_gvl(|_| { *self.thread.write() = Some( create_ruby_thread(move || { if params.pin_worker_cores { - core_affinity::set_for_current(CORE_IDS[(id as usize) % CORE_IDS.len()]); + core_affinity::set_for_current(CORE_IDS[worker_id % CORE_IDS.len()]); } debug!("Ruby thread worker started"); if let Some(scheduler_class) = scheduler_class { if let Err(err) = self_ref.fiber_accept_loop( params, - name, receiver, scheduler_class, terminated, @@ -205,7 +205,7 @@ impl ThreadWorker { error!("Error in fiber_accept_loop: {:?}", err); } } else { - self_ref.accept_loop(params, name, receiver, terminated); + self_ref.accept_loop(params, receiver, terminated); } }) .ok_or_else(|| { @@ -261,9 +261,14 @@ impl ThreadWorker { } } } + for _ in 0..MAX_BATCH_SIZE { if let Ok(req) = receiver.try_recv() { + let should_break = matches!(req, RequestJob::Shutdown); batch.push(req); + if should_break { + break; + } } else { break; } @@ -306,7 +311,9 @@ impl ThreadWorker { ItsiGrpcCall::internal_error(ruby, response, err) } } - RequestJob::Shutdown => return true, + RequestJob::Shutdown => { + return true; + } } } false @@ -338,15 +345,14 @@ impl ThreadWorker { if yield_result.is_err() { break; } - }) + }); }) } - #[instrument(skip_all, fields(thread_worker=name))] + #[instrument(skip_all, fields(thread_worker=format!("{}:{}", self.id, self.worker_id)))] pub fn fiber_accept_loop( self: Arc, params: Arc, - name: String, receiver: Arc>, scheduler_class: Opaque, terminated: Arc, @@ -421,11 +427,10 @@ impl ThreadWorker { }); } - #[instrument(skip_all, fields(thread_worker=id))] + #[instrument(skip_all, fields(thread_worker=format!("{}:{}", self.id, self.worker_id)))] pub fn accept_loop( self: Arc, params: Arc, - id: String, receiver: Arc>, terminated: Arc, ) { From bc31be618bd3135ef76f1cd50090eff2bca5eeba Mon Sep 17 00:00:00 2001 From: Wouter Coppieters Date: Thu, 15 May 2025 13:33:05 +1200 Subject: [PATCH 4/6] Use non-boxed types for full and empty responses --- Cargo.lock | 1 + crates/itsi_server/Cargo.toml | 1 + .../src/ruby_types/itsi_grpc_call.rs | 8 +- .../itsi_grpc_response_stream/mod.rs | 25 +-- .../src/ruby_types/itsi_http_request.rs | 6 +- .../src/ruby_types/itsi_http_response.rs | 37 ++-- .../src/server/http_message_types.rs | 172 +++++++++++++++++- .../middlewares/auth_basic.rs | 5 +- .../middlewares/compression.rs | 18 +- .../middleware_stack/middlewares/cors.rs | 5 +- .../middleware_stack/middlewares/csp.rs | 6 +- .../middlewares/error_response.rs | 12 +- .../error_response/default_responses.rs | 111 ++++++----- .../middleware_stack/middlewares/etag.rs | 10 +- .../middleware_stack/middlewares/proxy.rs | 14 +- .../middleware_stack/middlewares/redirect.rs | 5 +- .../middlewares/static_response.rs | 10 +- .../src/services/static_file_server.rs | 75 +++----- gems/server/Cargo.lock | 1 + 19 files changed, 326 insertions(+), 196 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 521027d6..3f39eef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1701,6 +1701,7 @@ dependencies = [ "either", "fs2", "futures", + "futures-util", "globset", "http 1.3.1", "http-body-util", diff --git a/crates/itsi_server/Cargo.toml b/crates/itsi_server/Cargo.toml index 7c7f898e..b308a3a5 100644 --- a/crates/itsi_server/Cargo.toml +++ b/crates/itsi_server/Cargo.toml @@ -92,3 +92,4 @@ memchr = "2.7.4" quick_cache = "0.6.13" smallvec = "1.15.0" async-stream = "0.3.6" +futures-util = "0.3.31" diff --git a/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs b/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs index 23370a29..508bd981 100644 --- a/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +++ b/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs @@ -1,6 +1,6 @@ use super::itsi_grpc_response_stream::ItsiGrpcResponseStream; use crate::prelude::*; -use crate::server::http_message_types::{HttpRequest, HttpResponse}; +use crate::server::http_message_types::{HttpBody, HttpRequest, HttpResponse}; use crate::server::{byte_frame::ByteFrame, request_job::RequestJob}; use crate::services::itsi_http_service::HttpRequestContext; use async_compression::futures::bufread::{GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder}; @@ -8,7 +8,7 @@ use bytes::Bytes; use derive_more::Debug; use futures::{executor::block_on, io::Cursor, AsyncReadExt}; use http::{request::Parts, Response, StatusCode}; -use http_body_util::{combinators::BoxBody, BodyExt, Empty}; +use http_body_util::BodyExt; use itsi_error::CLIENT_CONNECTION_CLOSED; use itsi_rb_helpers::{print_rb_backtrace, HeapValue}; use itsi_tracing::debug; @@ -139,7 +139,7 @@ impl ItsiGrpcCall { { Err(err) => { error!("Error occurred: {}", err); - let mut response = Response::new(BoxBody::new(Empty::new())); + let mut response = Response::new(HttpBody::empty()); *response.status_mut() = StatusCode::BAD_REQUEST; Ok(response) } @@ -147,7 +147,7 @@ impl ItsiGrpcCall { Some(first_frame) => Ok(response_stream .build_response(first_frame, receiver, shutdown_channel) .await), - None => Ok(Response::new(BoxBody::new(Empty::new()))), + None => Ok(Response::new(HttpBody::empty())), }, } } diff --git a/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs b/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs index 59b7af54..1aeb084b 100644 --- a/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +++ b/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs @@ -1,6 +1,6 @@ use super::itsi_grpc_call::CompressionAlgorithm; use crate::prelude::*; -use crate::server::http_message_types::HttpResponse; +use crate::server::http_message_types::{HttpBody, HttpResponse}; use crate::server::size_limited_incoming::SizeLimitedIncoming; use crate::server::{byte_frame::ByteFrame, serve_strategy::single_mode::RunningPhase}; use bytes::Bytes; @@ -11,8 +11,8 @@ use http::{ header::{HeaderName, HeaderValue}, HeaderMap, Response, }; -use http_body_util::{combinators::BoxBody, BodyDataStream, BodyExt, Empty, Full, StreamBody}; -use hyper::body::{Frame, Incoming}; +use http_body_util::{BodyDataStream, BodyExt}; +use hyper::body::Incoming; use magnus::error::Result as MagnusResult; use nix::unistd::pipe; use parking_lot::Mutex; @@ -161,7 +161,7 @@ impl ItsiGrpcResponseStream { response_headers, incoming_reader: Some(pipe_read), response_sender, - response: Some(Response::new(BoxBody::new(Empty::new()))), + response: Some(Response::new(HttpBody::empty())), trailer_tx, trailer_rx: Some(trailer_rx), })), @@ -207,12 +207,12 @@ impl ItsiGrpcResponseStream { let rx = self.inner.lock().trailer_rx.take().unwrap(); *response.version_mut() = Version::HTTP_2; *response.headers_mut() = self.inner.lock().response_headers.clone(); - *response.body_mut() = if matches!(first_frame, ByteFrame::Empty) { - BoxBody::new(Empty::new()) + let body_with_trailers = if matches!(first_frame, ByteFrame::Empty) { + HttpBody::empty() } else if matches!(first_frame, ByteFrame::End(_)) { - BoxBody::new(Full::new(first_frame.into())) + HttpBody::full(first_frame.into()) } else { - let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame)))); + let initial_frame = tokio_stream::once(Ok(Bytes::from(first_frame))); let frame_stream = unfold( (ReceiverStream::new(receiver), shutdown_rx), |(mut receiver, mut shutdown_rx)| async move { @@ -224,7 +224,7 @@ impl ItsiGrpcResponseStream { maybe_bytes = receiver.next() => { match maybe_bytes { Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => { - return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx))); + return Some((Ok(bytes), (receiver, shutdown_rx))); } _ => { return None; @@ -246,15 +246,16 @@ impl ItsiGrpcResponseStream { ); let combined_stream = initial_frame.chain(frame_stream); - BoxBody::new(StreamBody::new(combined_stream)) + HttpBody::stream(combined_stream) } .with_trailers(async move { match rx.await { Ok(trailers) => Some(Ok(trailers)), Err(_err) => None, } - }) - .boxed(); + }); + + *response.body_mut() = body_with_trailers; response } diff --git a/crates/itsi_server/src/ruby_types/itsi_http_request.rs b/crates/itsi_server/src/ruby_types/itsi_http_request.rs index 42eeffe4..b8d179c5 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_request.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_request.rs @@ -24,7 +24,7 @@ use super::{ use crate::{ default_responses::{INTERNAL_SERVER_ERROR_RESPONSE, SERVICE_UNAVAILABLE_RESPONSE}, server::{ - http_message_types::{HttpRequest, HttpResponse}, + http_message_types::{HttpBody, HttpRequest, HttpResponse}, request_job::RequestJob, size_limited_incoming::MaxBodySizeReached, }, @@ -207,7 +207,7 @@ impl ItsiHttpRequest { Ok(result) => Ok(result), Err(e) => { error!("Error processing hijacked response: {}", e); - Ok(Response::new(BoxBody::new(Empty::new()))) + Ok(Response::new(HttpBody::empty())) } } } @@ -248,7 +248,7 @@ impl ItsiHttpRequest { match chunk { Ok(byte_array) => body_bytes.write_all(&byte_array).unwrap(), Err(e) => { - let mut err_resp = Response::new(BoxBody::new(Empty::new())); + let mut err_resp = Response::new(HttpBody::empty()); if e.downcast_ref::().is_some() { *err_resp.status_mut() = StatusCode::PAYLOAD_TOO_LARGE; } diff --git a/crates/itsi_server/src/ruby_types/itsi_http_response.rs b/crates/itsi_server/src/ruby_types/itsi_http_response.rs index f3f270c0..20d4cff4 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_response.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_response.rs @@ -32,7 +32,10 @@ use tokio::{ use tokio_util::io::ReaderStream; use tracing::{debug, info, warn}; -use crate::server::{http_message_types::HttpResponse, serve_strategy::single_mode::RunningPhase}; +use crate::server::{ + http_message_types::{HttpBody, HttpResponse}, + serve_strategy::single_mode::RunningPhase, +}; #[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)] #[derive(Debug, Clone)] @@ -76,7 +79,7 @@ impl ItsiHttpResponse { shutdown_rx, response_sender: RwLock::new(Some(response_sender)), frame_writer: RwLock::new(None), - response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))), + response: RwLock::new(Some(Response::new(HttpBody::empty()))), hijacked_socket: RwLock::new(None), }), } @@ -166,14 +169,14 @@ impl ItsiHttpResponse { Err(e) => eprintln!("upgrade error: {:?}", e), } }); - Response::new(BoxBody::new(Empty::new())) + Response::new(HttpBody::empty()) } else { let stream = ReaderStream::new(reader); let boxed_body = if headers .get(TRANSFER_ENCODING) .is_some_and(|h| h == "chunked") { - BoxBody::new(StreamBody::new(unfold( + HttpBody::stream(unfold( (stream, Vec::new()), |(mut stream, mut buf)| async move { loop { @@ -194,7 +197,7 @@ impl ItsiHttpResponse { if buf.starts_with(b"\r\n") { buf.drain(..2); } - return Some((Ok(Frame::data(Bytes::from(data))), (stream, buf))); + return Some((Ok(Bytes::from(data)), (stream, buf))); } match stream.next().await { Some(Ok(chunk)) => buf.extend_from_slice(&chunk), @@ -202,15 +205,11 @@ impl ItsiHttpResponse { } } }, - ))) + )) } else { - BoxBody::new(StreamBody::new(stream.map( - |result: std::result::Result| { - result - .map(Frame::data) - .map_err(|e| unreachable!("unexpected io error: {:?}", e)) - }, - ))) + HttpBody::stream(stream.map(|result: std::result::Result| { + result.map_err(|e| unreachable!("unexpected io error: {:?}", e)) + })) }; Response::new(boxed_body) }; @@ -244,7 +243,7 @@ impl ItsiHttpResponse { maybe_bytes = reader.recv() => { match maybe_bytes { Some(bytes) => { - yield Ok(Frame::data(bytes)); + yield Ok(bytes); } _ => break, } @@ -253,7 +252,7 @@ impl ItsiHttpResponse { if *shutdown_rx.borrow() == RunningPhase::ShutdownPending { reader.close(); while let Some(bytes) = reader.recv().await{ - yield Ok(Frame::data(bytes)); + yield Ok(bytes); } debug!("Disconnecting streaming client."); break; @@ -263,7 +262,7 @@ impl ItsiHttpResponse { } }; - *response.body_mut() = BoxBody::new(StreamBody::new(frame_stream)); + *response.body_mut() = HttpBody::stream(frame_stream); self.frame_writer.write().replace(writer); if let Some(sender) = self.response_sender.write().take() { sender.send(ResponseFrame::HttpResponse(response)).ok(); @@ -287,7 +286,11 @@ impl ItsiHttpResponse { return Ok(()); } if let Some(mut response) = self.response.write().take() { - *response.body_mut() = BoxBody::new(Full::new(frame)); + if frame.is_empty() { + *response.body_mut() = HttpBody::empty(); + } else { + *response.body_mut() = HttpBody::full(frame); + } if let Some(sender) = self.response_sender.write().take() { sender.send(ResponseFrame::HttpResponse(response)).ok(); } diff --git a/crates/itsi_server/src/server/http_message_types.rs b/crates/itsi_server/src/server/http_message_types.rs index 43e4cc88..acc3299b 100644 --- a/crates/itsi_server/src/server/http_message_types.rs +++ b/crates/itsi_server/src/server/http_message_types.rs @@ -1,13 +1,163 @@ -use std::convert::Infallible; - use bytes::Bytes; -use http::{Request, Response}; -use http_body_util::combinators::BoxBody; -use hyper::body::Incoming; +use core::fmt; +use futures::Stream; +use futures_util::TryStreamExt; +use http::Request; +use http_body_util::{combinators::WithTrailers, BodyExt, Either, Empty, Full, StreamBody}; +use hyper::body::{Body, Frame, Incoming, SizeHint}; +use std::{ + convert::Infallible, + pin::Pin, + task::{Context, Poll}, +}; use super::size_limited_incoming::SizeLimitedIncoming; -pub type HttpResponse = Response>; +/* ------------------------------------------------------------------------- + * Low‑level concrete body without trailers + * ---------------------------------------------------------------------- */ + +type Inner = Either, Empty>; + +type BoxStream = + Pin, Infallible>> + Send + Sync + 'static>>; + +struct PlainBody(Either, Inner>); + +impl fmt::Debug for PlainBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Either::Left(_) => f.write_str("PlainBody::Stream(..)"), + Either::Right(inner) => match inner { + Either::Left(full) => f.debug_tuple("PlainBody::Full").field(full).finish(), + Either::Right(_) => f.write_str("PlainBody::Empty"), + }, + } + } +} +type DynErr = Box; + +impl Body for PlainBody { + type Data = Bytes; + type Error = DynErr; + + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + unsafe { self.map_unchecked_mut(|s| &mut s.0) }.poll_frame(cx) + } + + fn size_hint(&self) -> SizeHint { + self.0.size_hint() + } +} + +impl PlainBody { + fn stream(s: S) -> Self + where + S: Stream> + Send + Sync + 'static, + { + let boxed: BoxStream = Box::pin(s.map_ok(Frame::data)); + Self(Either::Left(StreamBody::new(boxed))) + } + + fn full(bytes: Bytes) -> Self { + Self(Either::Right(Either::Left(Full::new(bytes)))) + } + + fn empty() -> Self { + Self(Either::Right(Either::Right(Empty::new()))) + } +} + +/* ------------------------------------------------------------------------- + * Public body type used at API boundaries (plain or with trailers) + * ---------------------------------------------------------------------- */ + +type BoxTrailers = Pin< + Box>> + Send + Sync>, +>; + +pub enum HttpBody { + Plain(PlainBody), + WithT(WithTrailers), +} + +impl fmt::Debug for HttpBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HttpBody::Plain(b) => f.debug_tuple("HttpBody::Plain").field(b).finish(), + HttpBody::WithT(_) => f.write_str("HttpBody::WithT(..)"), + } + } +} + +impl Body for HttpBody { + type Data = Bytes; + type Error = DynErr; + + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + unsafe { + match self.get_unchecked_mut() { + HttpBody::Plain(b) => Pin::new_unchecked(b).poll_frame(cx), + HttpBody::WithT(b) => Pin::new_unchecked(b).poll_frame(cx), + } + } + } + + fn size_hint(&self) -> SizeHint { + match self { + HttpBody::Plain(b) => b.size_hint(), + HttpBody::WithT(b) => b.size_hint(), + } + } +} + +/* ------------------------------------------------------------------------- + * Constructors & helpers (public API) + * ---------------------------------------------------------------------- */ + +impl HttpBody { + pub fn stream(s: S) -> Self + where + S: Stream> + Send + Sync + 'static, + { + HttpBody::Plain(PlainBody::stream(s)) + } + + pub fn full(bytes: Bytes) -> Self { + HttpBody::Plain(PlainBody::full(bytes)) + } + + pub fn empty() -> Self { + HttpBody::Plain(PlainBody::empty()) + } + + pub fn with_trailers(self, fut: Fut) -> Self + where + Fut: std::future::Future>> + + Send + + Sync + + 'static, + { + let boxed: BoxTrailers = Box::pin(fut); + match self { + HttpBody::Plain(p) => HttpBody::WithT(p.with_trailers(boxed)), + already @ HttpBody::WithT(_) => already, + } + } +} + +/* ------------------------------------------------------------------------- + * Aliases & request helpers + * ---------------------------------------------------------------------- */ + +pub type HttpResponse = http::Response; + pub type HttpRequest = Request>; pub trait ConversionExt { @@ -21,6 +171,10 @@ impl ConversionExt for Request { } } +/* ------------------------------------------------------------------------- + * Misc unchanged helpers + * ---------------------------------------------------------------------- */ + pub trait RequestExt { fn content_type(&self) -> Option<&str>; fn accept(&self) -> Option<&str>; @@ -64,7 +218,7 @@ impl PathExt for str { if self == "/" { self } else { - self.trim_end_matches("/") + self.trim_end_matches('/') } } } @@ -91,7 +245,7 @@ impl RequestExt for HttpRequest { fn query_param(&self, query_name: &str) -> Option<&str> { self.uri() .query() - .and_then(|query| query.split('&').find(|param| param.starts_with(query_name))) - .map(|param| param.split('=').nth(1).unwrap_or("")) + .and_then(|q| q.split('&').find(|p| p.starts_with(query_name))) + .map(|p| p.split('=').nth(1).unwrap_or("")) } } diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs index 91e773c1..ae59b64c 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs @@ -3,7 +3,6 @@ use base64::{engine::general_purpose, Engine}; use bytes::Bytes; use either::Either; use http::{Response, StatusCode}; -use http_body_util::{combinators::BoxBody, Full}; use magnus::error::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -11,7 +10,7 @@ use std::str; use tracing::debug; use crate::{ - server::http_message_types::{HttpRequest, HttpResponse, RequestExt}, + server::http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt}, services::{itsi_http_service::HttpRequestContext, password_hasher::verify_password_hash}, }; @@ -34,7 +33,7 @@ impl AuthBasic { "WWW-Authenticate", format!("Basic realm=\"{}\"", self.realm), ) - .body(BoxBody::new(Full::new(Bytes::from("Unauthorized")))) + .body(HttpBody::full(Bytes::from("Unauthorized"))) .unwrap() } } diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs index d55f0c3e..fd690c3f 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs @@ -1,5 +1,5 @@ use crate::{ - server::http_message_types::{HttpRequest, HttpResponse}, + server::http_message_types::{HttpBody, HttpRequest, HttpResponse}, services::itsi_http_service::HttpRequestContext, }; @@ -20,8 +20,8 @@ use http::{ header::{GetAll, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE}, HeaderValue, Response, }; -use http_body_util::{combinators::BoxBody, BodyExt, Full, StreamBody}; -use hyper::body::{Body, Frame}; +use http_body_util::{BodyExt, StreamBody}; +use hyper::body::Body; use magnus::error::Result; use serde::{Deserialize, Serialize}; use std::convert::Infallible; @@ -126,15 +126,13 @@ impl MimeType { } } -fn stream_encode(encoder: R) -> BoxBody +fn stream_encode(encoder: R) -> HttpBody where R: AsyncRead + Unpin + Sync + Send + 'static, { - let encoded_stream = ReaderStream::new(encoder).map(|res| { - res.map(Frame::data) - .map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }) - }); - BoxBody::new(StreamBody::new(encoded_stream)) + let encoded_stream = ReaderStream::new(encoder) + .map(|res| res.map_err(|_| -> Infallible { unreachable!("We handle IO errors above") })); + HttpBody::stream(StreamBody::new(encoded_stream)) } fn update_content_encoding(parts: &mut http::response::Parts, new_encoding: HeaderValue) { @@ -293,7 +291,7 @@ impl MiddlewareLayer for Compression { } CompressionAlgorithm::Identity => unreachable!(), }; - BoxBody::new(Full::new(Bytes::from(compressed_bytes))) + HttpBody::full(Bytes::from(compressed_bytes)) } else { let stream = body .into_data_stream() diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs index 0e649f8f..152fecca 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs @@ -1,12 +1,11 @@ use super::{FromValue, MiddlewareLayer}; use crate::{ - server::http_message_types::{HttpRequest, HttpResponse, RequestExt}, + server::http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt}, services::itsi_http_service::HttpRequestContext, }; use async_trait::async_trait; use http::{HeaderMap, Method, Response}; -use http_body_util::{combinators::BoxBody, Empty}; use itsi_error::ItsiError; use magnus::error::Result; use serde::Deserialize; @@ -273,7 +272,7 @@ impl MiddlewareLayer for Cors { let mut response_builder = Response::builder().status(204); *response_builder.headers_mut().unwrap() = headers; let response = response_builder - .body(BoxBody::new(Empty::new())) + .body(HttpBody::empty()) .map_err(ItsiError::new)?; return Ok(either::Either::Right(response)); } diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs index 000b203e..523efaa9 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs @@ -1,6 +1,6 @@ use super::FromValue; use crate::{ - server::http_message_types::{HttpRequest, HttpResponse}, + server::http_message_types::{HttpBody, HttpRequest, HttpResponse}, services::itsi_http_service::HttpRequestContext, }; use async_trait::async_trait; @@ -8,7 +8,7 @@ use bytes::{Bytes, BytesMut}; use either::Either; use futures::TryStreamExt; use http::{HeaderValue, StatusCode}; -use http_body_util::{combinators::BoxBody, BodyExt, Empty}; +use http_body_util::BodyExt; use itsi_error::ItsiError; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -164,7 +164,7 @@ impl super::MiddlewareLayer for Csp { } } - let mut resp = HttpResponse::new(BoxBody::new(Empty::new())); + let mut resp = HttpResponse::new(HttpBody::empty()); *resp.status_mut() = StatusCode::NO_CONTENT; return Ok(Either::Right(resp)); } diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs index c9654540..851a2bc9 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs @@ -1,13 +1,11 @@ use bytes::Bytes; use http::header::CONTENT_TYPE; use http::Response; -use http_body_util::{combinators::BoxBody, Full}; use serde::{Deserialize, Deserializer}; -use std::convert::Infallible; use std::path::PathBuf; use tracing::warn; -use crate::server::http_message_types::{HttpResponse, ResponseFormat}; +use crate::server::http_message_types::{HttpBody, HttpResponse, ResponseFormat}; use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER; mod default_responses; @@ -19,7 +17,7 @@ pub enum ContentSource { File(PathBuf), #[serde(rename(deserialize = "static"))] #[serde(skip_deserializing)] - Static(Full), + Static(Bytes), } #[derive(Debug, Clone, Deserialize, Default)] @@ -144,13 +142,13 @@ impl ErrorResponse { code: u16, source: &Option, accept: ResponseFormat, - ) -> BoxBody { + ) -> HttpBody { match source { Some(ContentSource::Inline(text)) => { - return BoxBody::new(Full::new(Bytes::from(text.clone()))); + return HttpBody::full(Bytes::from(text.clone())); } Some(ContentSource::Static(text)) => { - return BoxBody::new(text.clone()); + return HttpBody::full(text.clone()); } Some(ContentSource::File(path)) => { // Convert the PathBuf to a &str (assumes valid UTF-8). diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs index 187530ad..0f476e52 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs @@ -1,91 +1,90 @@ use super::{ContentSource, DefaultFormat, ErrorResponse}; -use crate::server::http_message_types::ResponseFormat; +use crate::server::http_message_types::{HttpBody, ResponseFormat}; use bytes::Bytes; -use http_body_util::{combinators::BoxBody, Full}; -use std::convert::Infallible; +use http_body_util::Full; impl DefaultFormat { pub fn response_for_code(&self, code: u16) -> ContentSource { match self { DefaultFormat::Plaintext => match code { - 500 => ContentSource::Static(Full::new("500 Internal Error".into())), - 404 => ContentSource::Static(Full::new("404 Not Found".into())), - 401 => ContentSource::Static(Full::new("401 Unauthorized".into())), - 403 => ContentSource::Static(Full::new("403 Forbidden".into())), - 413 => ContentSource::Static(Full::new("413 Payload Too Large".into())), - 429 => ContentSource::Static(Full::new("429 Too Many Requests".into())), - 502 => ContentSource::Static(Full::new("502 Bad Gateway".into())), - 503 => ContentSource::Static(Full::new("503 Service Unavailable".into())), - 504 => ContentSource::Static(Full::new("504 Gateway Timeout".into())), - _ => ContentSource::Static(Full::new("Unexpected Error".into())), + 500 => ContentSource::Static("500 Internal Error".into()), + 404 => ContentSource::Static("404 Not Found".into()), + 401 => ContentSource::Static("401 Unauthorized".into()), + 403 => ContentSource::Static("403 Forbidden".into()), + 413 => ContentSource::Static("413 Payload Too Large".into()), + 429 => ContentSource::Static("429 Too Many Requests".into()), + 502 => ContentSource::Static("502 Bad Gateway".into()), + 503 => ContentSource::Static("503 Service Unavailable".into()), + 504 => ContentSource::Static("504 Gateway Timeout".into()), + _ => ContentSource::Static("Unexpected Error".into()), }, DefaultFormat::Html => match code { - 500 => ContentSource::Static(Full::new( + 500 => ContentSource::Static( include_str!("../../../../default_responses/html/500.html").into(), - )), - 404 => ContentSource::Static(Full::new( + ), + 404 => ContentSource::Static( include_str!("../../../../default_responses/html/404.html").into(), - )), - 401 => ContentSource::Static(Full::new( + ), + 401 => ContentSource::Static( include_str!("../../../../default_responses/html/401.html").into(), - )), - 403 => ContentSource::Static(Full::new( + ), + 403 => ContentSource::Static( include_str!("../../../../default_responses/html/403.html").into(), - )), - 413 => ContentSource::Static(Full::new( + ), + 413 => ContentSource::Static( include_str!("../../../../default_responses/html/413.html").into(), - )), - 429 => ContentSource::Static(Full::new( + ), + 429 => ContentSource::Static( include_str!("../../../../default_responses/html/429.html").into(), - )), - 502 => ContentSource::Static(Full::new( + ), + 502 => ContentSource::Static( include_str!("../../../../default_responses/html/502.html").into(), - )), - 503 => ContentSource::Static(Full::new( + ), + 503 => ContentSource::Static( include_str!("../../../../default_responses/html/503.html").into(), - )), - 504 => ContentSource::Static(Full::new( + ), + 504 => ContentSource::Static( include_str!("../../../../default_responses/html/504.html").into(), - )), - _ => ContentSource::Static(Full::new( + ), + _ => ContentSource::Static( include_str!("../../../../default_responses/html/500.html").into(), - )), + ), }, DefaultFormat::Json => match code { - 500 => ContentSource::Static(Full::new( + 500 => ContentSource::Static( include_str!("../../../../default_responses/json/500.json").into(), - )), - 404 => ContentSource::Static(Full::new( + ), + 404 => ContentSource::Static( include_str!("../../../../default_responses/json/404.json").into(), - )), - 401 => ContentSource::Static(Full::new( + ), + 401 => ContentSource::Static( include_str!("../../../../default_responses/json/401.json").into(), - )), - 403 => ContentSource::Static(Full::new( + ), + 403 => ContentSource::Static( include_str!("../../../../default_responses/json/403.json").into(), - )), - 413 => ContentSource::Static(Full::new( + ), + 413 => ContentSource::Static( include_str!("../../../../default_responses/json/413.json").into(), - )), - 429 => ContentSource::Static(Full::new( + ), + 429 => ContentSource::Static( include_str!("../../../../default_responses/json/429.json").into(), - )), - 502 => ContentSource::Static(Full::new( + ), + 502 => ContentSource::Static( include_str!("../../../../default_responses/json/502.json").into(), - )), - 503 => ContentSource::Static(Full::new( + ), + 503 => ContentSource::Static( include_str!("../../../../default_responses/json/503.json").into(), - )), - 504 => ContentSource::Static(Full::new( + ), + 504 => ContentSource::Static( include_str!("../../../../default_responses/json/504.json").into(), - )), - _ => ContentSource::Static(Full::new("Unexpected Error".into())), + ), + _ => ContentSource::Static("Unexpected Error".into()), }, } } } impl ErrorResponse { - pub fn fallback_body_for(code: u16, accept: ResponseFormat) -> BoxBody { + pub fn fallback_body_for(code: u16, accept: ResponseFormat) -> HttpBody { let source = match accept { ResponseFormat::TEXT => DefaultFormat::Plaintext.response_for_code(code), ResponseFormat::HTML => DefaultFormat::Html.response_for_code(code), @@ -93,9 +92,9 @@ impl ErrorResponse { ResponseFormat::UNKNOWN => ContentSource::Inline("Unexpected Error".to_owned()), }; match source { - ContentSource::Inline(bytes) => BoxBody::new(Full::new(Bytes::from(bytes))), - ContentSource::Static(bytes) => BoxBody::new(bytes), - ContentSource::File(_) => BoxBody::new(Full::new(Bytes::from("Unexpected error"))), + ContentSource::Inline(bytes) => HttpBody::full(Bytes::from(bytes)), + ContentSource::Static(bytes) => HttpBody::full(bytes), + ContentSource::File(_) => HttpBody::full(Bytes::from("Unexpected error")), } } pub fn internal_server_error() -> Self { diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs index 74242fad..6921098f 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs @@ -1,5 +1,5 @@ use crate::{ - server::http_message_types::{HttpRequest, HttpResponse}, + server::http_message_types::{HttpBody, HttpRequest, HttpResponse}, services::itsi_http_service::HttpRequestContext, }; @@ -10,7 +10,7 @@ use bytes::{Bytes, BytesMut}; use either::Either; use futures::TryStreamExt; use http::{header, HeaderValue, Response, StatusCode}; -use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; +use http_body_util::BodyExt; use hyper::body::Body; use magnus::error::Result; use serde::Deserialize; @@ -113,7 +113,7 @@ impl MiddlewareLayer for ETag { .await { Ok(bytes_mut) => bytes_mut.freeze(), - Err(_) => return Response::from_parts(parts, BoxBody::new(Empty::new())), + Err(_) => return Response::from_parts(parts, HttpBody::empty()), }; let computed_etag = match self.algorithm { @@ -139,14 +139,14 @@ impl MiddlewareLayer for ETag { parts.headers.insert(header::ETAG, value); } - body = Full::new(full_bytes).boxed(); + body = HttpBody::full(full_bytes); formatted_etag }; if let Some(if_none_match) = context.get_if_none_match() { if if_none_match == etag_value || if_none_match == "*" { // Return 304 Not Modified without the body - let mut not_modified = Response::new(BoxBody::new(Empty::new())); + let mut not_modified = Response::new(HttpBody::empty()); *not_modified.status_mut() = StatusCode::NOT_MODIFIED; // Copy headers we want to preserve for (name, value) in parts.headers.iter() { diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs index e2e8f2a3..2bb55413 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs @@ -14,7 +14,7 @@ use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareL use crate::{ server::{ binds::bind::{Bind, BindAddress}, - http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat}, + http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt, ResponseFormat}, size_limited_incoming::MaxBodySizeReached, }, services::itsi_http_service::HttpRequestContext, @@ -373,19 +373,17 @@ impl MiddlewareLayer for Proxy { for (hn, hv) in response.headers() { builder = builder.header(hn, hv); } - let response = builder.body(BoxBody::new(StreamBody::new( - response - .bytes_stream() - .map_ok(Frame::data) - .map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }), - ))); + let response = + builder.body(HttpBody::stream(response.bytes_stream().map_err( + |_| -> Infallible { unreachable!("We handle IO errors above") }, + ))); response.unwrap_or(error_response) } Err(e) => { debug!(target: "middleware::proxy", "Error {:?} received", e); if let Some(inner) = e.source() { if inner.downcast_ref::().is_some() { - let mut max_body_response = Response::new(BoxBody::new(Empty::new())); + let mut max_body_response = Response::new(HttpBody::empty()); *max_body_response.status_mut() = StatusCode::PAYLOAD_TOO_LARGE; return Ok(Either::Right(max_body_response)); } diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs index f790e120..e5a899ec 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs @@ -1,7 +1,7 @@ use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer}; use crate::{ server::{ - http_message_types::{HttpRequest, HttpResponse}, + http_message_types::{HttpBody, HttpRequest, HttpResponse}, redirect_type::RedirectType, }, services::itsi_http_service::HttpRequestContext, @@ -9,7 +9,6 @@ use crate::{ use async_trait::async_trait; use either::Either; use http::Response; -use http_body_util::{combinators::BoxBody, Empty}; use magnus::error::Result; use serde::Deserialize; use tracing::debug; @@ -39,7 +38,7 @@ impl Redirect { req: &HttpRequest, context: &mut HttpRequestContext, ) -> Result { - let mut response = Response::new(BoxBody::new(Empty::new())); + let mut response = Response::new(HttpBody::empty()); *response.status_mut() = self.redirect_type.status_code(); let destination = self.to.rewrite_request(req, context).parse().map_err(|e| { magnus::Error::new( diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs index ec32fa1e..b5131c3f 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs @@ -1,15 +1,13 @@ use std::sync::OnceLock; use super::{FromValue, MiddlewareLayer}; -use crate::server::http_message_types::{HttpRequest, HttpResponse}; +use crate::server::http_message_types::{HttpBody, HttpRequest, HttpResponse}; use crate::services::itsi_http_service::HttpRequestContext; use async_trait::async_trait; use bytes::Bytes; use derive_more::Debug; use either::Either; use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode}; -use http_body_util::combinators::BoxBody; -use http_body_util::Full; use itsi_error::ItsiError; use magnus::error::Result; use serde::Deserialize; @@ -22,7 +20,7 @@ pub struct StaticResponse { #[serde(skip)] header_map: OnceLock, #[serde(skip)] - body_bytes: OnceLock>, + body_bytes: OnceLock, #[serde(skip)] status_code: OnceLock, } @@ -40,7 +38,7 @@ impl MiddlewareLayer for StaticResponse { .set(header_map) .map_err(|_| ItsiError::new("Failed to set headers"))?; self.body_bytes - .set(Full::new(Bytes::from(self.body.clone()))) + .set(Bytes::from(self.body.clone())) .map_err(|_| ItsiError::new("Failed to set body bytes"))?; self.status_code .set(StatusCode::from_u16(self.code).unwrap_or(StatusCode::OK)) @@ -53,7 +51,7 @@ impl MiddlewareLayer for StaticResponse { _req: HttpRequest, _context: &mut HttpRequestContext, ) -> Result> { - let mut resp = Response::new(BoxBody::new(self.body_bytes.get().unwrap().clone())); + let mut resp = Response::new(HttpBody::full(self.body_bytes.get().unwrap().clone())); *resp.status_mut() = *self.status_code.get().unwrap(); *resp.headers_mut() = self.header_map.get().unwrap().clone(); diff --git a/crates/itsi_server/src/services/static_file_server.rs b/crates/itsi_server/src/services/static_file_server.rs index ad00447d..1dda1cf2 100644 --- a/crates/itsi_server/src/services/static_file_server.rs +++ b/crates/itsi_server/src/services/static_file_server.rs @@ -2,7 +2,7 @@ use crate::{ default_responses::NOT_FOUND_RESPONSE, prelude::*, server::{ - http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat}, + http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt, ResponseFormat}, middleware_stack::ErrorResponse, redirect_type::RedirectType, }, @@ -16,7 +16,6 @@ use http::{ }, HeaderName, HeaderValue, Response, StatusCode, }; -use http_body_util::{combinators::BoxBody, Full}; use itsi_error::Result; use parking_lot::{Mutex, RwLock}; use percent_encoding::percent_decode_str; @@ -28,7 +27,6 @@ use std::{ borrow::Cow, cmp::Ordering, collections::HashMap, - convert::Infallible, fs::Metadata, ops::Deref, path::{Path, PathBuf}, @@ -324,7 +322,7 @@ impl StaticFileServer { }) => Response::builder() .status(StatusCode::MOVED_PERMANENTLY) .header(header::LOCATION, redirect_to) - .body(BoxBody::new(Full::new(Bytes::new()))) + .body(HttpBody::empty()) .unwrap(), Err(not_found_behavior) => match not_found_behavior { NotFoundBehavior::Error(error_response) => { @@ -340,7 +338,7 @@ impl StaticFileServer { NotFoundBehavior::Redirect(redirect) => Response::builder() .status(redirect.r#type.status_code()) .header(header::LOCATION, redirect.to) - .body(BoxBody::new(Full::new(Bytes::new()))) + .body(HttpBody::empty()) .unwrap(), }, }) @@ -407,7 +405,7 @@ impl StaticFileServer { Response::builder() .status(StatusCode::NOT_FOUND) - .body(BoxBody::new(Full::new(Bytes::new()))) + .body(HttpBody::empty()) .unwrap() } @@ -648,15 +646,8 @@ impl StaticFileServer { Err(nf) } - async fn stream_file_range( - &self, - path: PathBuf, - start: u64, - end: u64, - ) -> Option> { + async fn stream_file_range(&self, path: PathBuf, start: u64, end: u64) -> Option { use futures::TryStreamExt; - use http_body_util::StreamBody; - use hyper::body::Frame; use tokio::io::AsyncSeekExt; use tokio_util::io::ReaderStream; @@ -687,32 +678,25 @@ impl StaticFileServer { let range_length = end - start + 1; let limited_reader = tokio::io::AsyncReadExt::take(file, range_length); let path_clone = path.clone(); - let stream = ReaderStream::with_capacity(limited_reader, 64 * 1024) - .map_ok(Frame::data) - .map_err(move |e| { - warn!("Error streaming file {}: {}", path_clone.display(), e); - unreachable!("We handle IO errors above") - }); - - Some(BoxBody::new(StreamBody::new(stream))) + let stream = ReaderStream::with_capacity(limited_reader, 64 * 1024).map_err(move |e| { + warn!("Error streaming file {}: {}", path_clone.display(), e); + unreachable!("We handle IO errors above") + }); + Some(HttpBody::stream(stream)) } - async fn stream_file(&self, path: PathBuf) -> Option> { + async fn stream_file(&self, path: PathBuf) -> Option { use futures::TryStreamExt; - use http_body_util::StreamBody; - use hyper::body::Frame; use tokio_util::io::ReaderStream; match File::open(&path).await { Ok(file) => { let path_clone = path.clone(); - let stream = ReaderStream::with_capacity(file, 64 * 1024) - .map_ok(Frame::data) - .map_err(move |e| { - warn!("Error streaming file {}: {}", path_clone.display(), e); - unreachable!("We handle IO errors above") - }); - Some(BoxBody::new(StreamBody::new(stream))) + let stream = ReaderStream::with_capacity(file, 64 * 1024).map_err(move |e| { + warn!("Error streaming file {}: {}", path_clone.display(), e); + unreachable!("We handle IO errors above") + }); + Some(HttpBody::stream(stream)) } Err(e) => { warn!( @@ -749,7 +733,7 @@ impl StaticFileServer { return Response::builder() .status(StatusCode::RANGE_NOT_SATISFIABLE) .header("Content-Range", format!("bytes */{}", content_length)) - .body(BoxBody::new(Full::new(Bytes::new()))) + .body(HttpBody::empty()) .unwrap(); } @@ -795,7 +779,7 @@ impl StaticFileServer { builder = builder.header("Content-Range", range); } - return builder.body(BoxBody::new(Full::new(Bytes::new()))).unwrap(); + return builder.body(HttpBody::empty()).unwrap(); } // For GET requests, prepare the actual content @@ -829,10 +813,7 @@ impl StaticFileServer { } } - fn serve_cached_content( - &self, - serve_cache_args: ServeCacheArgs, - ) -> http::Response> { + fn serve_cached_content(&self, serve_cache_args: ServeCacheArgs) -> HttpResponse { let ServeCacheArgs( cache_entry, start, @@ -855,7 +836,7 @@ impl StaticFileServer { return Response::builder() .status(StatusCode::RANGE_NOT_SATISFIABLE) .header("Content-Range", format!("bytes */{}", content_length)) - .body(BoxBody::new(Full::new(Bytes::new()))) + .body(HttpBody::empty()) .unwrap(); } @@ -904,7 +885,7 @@ impl StaticFileServer { builder = builder.header("Content-Range", range); } - return builder.body(BoxBody::new(Full::new(Bytes::new()))).unwrap(); + return builder.body(HttpBody::empty()).unwrap(); } if is_range_request { @@ -920,7 +901,7 @@ impl StaticFileServer { cache_entry.last_modified_http_date.clone(), content_range, &self.headers, - BoxBody::new(Full::new(range_bytes)), + HttpBody::full(range_bytes), ) } else { // Return the full content @@ -987,15 +968,15 @@ fn format_http_date_header(time: SystemTime) -> HeaderValue { .unwrap() } -fn build_ok_body(bytes: Arc) -> BoxBody { - BoxBody::new(Full::new(bytes.as_ref().clone())) +fn build_ok_body(bytes: Arc) -> HttpBody { + HttpBody::full(bytes.as_ref().clone()) } // Helper function to handle not modified responses -fn build_not_modified_response() -> http::Response> { +fn build_not_modified_response() -> HttpResponse { Response::builder() .status(StatusCode::NOT_MODIFIED) - .body(BoxBody::new(Full::new(Bytes::new()))) + .body(HttpBody::empty()) .unwrap() } @@ -1009,8 +990,8 @@ fn build_file_response( last_modified_http_date: HeaderValue, range_header: Option, headers: &Option>, - body: BoxBody, -) -> http::Response> { + body: HttpBody, +) -> HttpResponse { let mut response = Response::new(body); *response.status_mut() = status; diff --git a/gems/server/Cargo.lock b/gems/server/Cargo.lock index 69137711..36f0b350 100644 --- a/gems/server/Cargo.lock +++ b/gems/server/Cargo.lock @@ -1683,6 +1683,7 @@ dependencies = [ "either", "fs2", "futures", + "futures-util", "globset", "http 1.3.1", "http-body-util", From deb438dc68815ad2b5ee80f3cc187f5464f4dd7c Mon Sep 17 00:00:00 2001 From: Wouter Coppieters Date: Fri, 23 May 2025 18:03:43 +1200 Subject: [PATCH 5/6] Added new HTTP/1.x connection configuration options, defer pushing to broadcast channel during signal handling if no current runtime --- benchmarks/Gemfile | 2 + benchmarks/Gemfile.lock | 34 + benchmarks/apps/chunked.ru | 4 +- benchmarks/apps/echo_service/echo_service.rb | 10 +- benchmarks/apps/io_party.ru | 54 + benchmarks/lib/benchmark_case.rb | 2 +- benchmarks/lib/server.rb | 8 +- benchmarks/rack_bench.rb | 23 +- benchmarks/server_configurations/itsi.rb | 2 +- benchmarks/servers.rb | 10 +- crates/itsi_acme/Cargo.toml | 2 +- crates/itsi_server/src/lib.rs | 1 + .../itsi_grpc_response_stream/mod.rs | 2 +- .../src/ruby_types/itsi_http_request.rs | 4 +- .../src/ruby_types/itsi_http_response.rs | 31 +- .../itsi_server/itsi_server_config.rs | 31 +- .../src/server/http_message_types.rs | 23 +- .../itsi_server/src/server/lifecycle_event.rs | 2 +- .../error_response/default_responses.rs | 1 - .../middleware_stack/middlewares/proxy.rs | 3 +- .../itsi_server/src/server/process_worker.rs | 5 +- .../src/server/serve_strategy/acceptor.rs | 3 +- .../src/server/serve_strategy/cluster_mode.rs | 91 +- .../src/server/serve_strategy/single_mode.rs | 121 +- crates/itsi_server/src/server/signal.rs | 45 +- .../itsi_server/src/server/thread_worker.rs | 18 +- .../src/services/itsi_http_service.rs | 8 +- gems/scheduler/Cargo.lock | 4535 +++++++++++++++-- gems/server/Cargo.lock | 27 + gems/server/lib/itsi/server/config.rb | 21 +- gems/server/lib/itsi/server/config/dsl.rb | 11 +- .../lib/itsi/server/config/options/include.rb | 7 +- .../server/config/options/pipeline_flush.md | 16 + .../server/config/options/pipeline_flush.rb | 19 + .../lib/itsi/server/config/options/writev.md | 25 + .../lib/itsi/server/config/options/writev.rb | 19 + .../lib/itsi/server/scheduler_interface.rb | 2 + gems/server/lib/ruby_lsp/itsi/addon.rb | 21 +- gems/server/test/helpers/test_helper.rb | 25 +- gems/server/test/middleware/proxy.rb | 530 +- 40 files changed, 4783 insertions(+), 1015 deletions(-) create mode 100644 benchmarks/apps/io_party.ru create mode 100644 gems/server/lib/itsi/server/config/options/pipeline_flush.md create mode 100644 gems/server/lib/itsi/server/config/options/pipeline_flush.rb create mode 100644 gems/server/lib/itsi/server/config/options/writev.md create mode 100644 gems/server/lib/itsi/server/config/options/writev.rb diff --git a/benchmarks/Gemfile b/benchmarks/Gemfile index b809d96b..293820da 100644 --- a/benchmarks/Gemfile +++ b/benchmarks/Gemfile @@ -11,3 +11,5 @@ gem 'paint' gem 'sinatra' gem 'sys-proctable' gem 'grpc' +gem 'sqlite3' +gem 'activerecord' diff --git a/benchmarks/Gemfile.lock b/benchmarks/Gemfile.lock index 86088db4..0daf7738 100644 --- a/benchmarks/Gemfile.lock +++ b/benchmarks/Gemfile.lock @@ -16,6 +16,25 @@ PATH GEM remote: https://rubygems.org/ specs: + activemodel (8.0.2) + activesupport (= 8.0.2) + activerecord (8.0.2) + activemodel (= 8.0.2) + activesupport (= 8.0.2) + timeout (>= 0.4.0) + activesupport (8.0.2) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) agoo (2.15.13) async (2.24.0) console (~> 1.29) @@ -48,7 +67,10 @@ GEM async async-container (~> 0.16) base64 (0.2.0) + benchmark (0.4.0) bigdecimal (3.1.9) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) console (1.30.2) fiber-annotation fiber-local (~> 1.1) @@ -57,6 +79,7 @@ GEM debug (1.10.0) irb (~> 1.10) reline (>= 0.3.8) + drb (2.2.1) falcon (0.51.1) async async-container (~> 0.20) @@ -84,6 +107,8 @@ GEM grpc (1.71.0-arm64-darwin) google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) io-console (0.8.0) io-endpoint (0.15.2) io-event (1.10.0) @@ -100,6 +125,7 @@ GEM mapping (1.1.3) memory-leak (0.5.2) metrics (0.12.2) + minitest (5.25.5) mustermann (3.0.3) ruby2_keywords (~> 0.0.1) nio4r (2.7.4) @@ -145,6 +171,7 @@ GEM samovar (2.3.0) console (~> 1.0) mapping (~> 1.0) + securerandom (0.4.1) sinatra (4.1.1) logger (>= 1.6.0) mustermann (~> 3.0) @@ -152,19 +179,25 @@ GEM rack-protection (= 4.1.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + sqlite3 (2.6.0-arm64-darwin) stringio (3.1.7) sys-proctable (1.3.0) ffi (~> 1.1) tilt (2.6.0) + timeout (0.4.3) traces (0.15.2) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unicorn (6.1.0) kgio (~> 2.6) raindrops (~> 0.7) + uri (1.0.3) PLATFORMS arm64-darwin-23 DEPENDENCIES + activerecord agoo debug falcon @@ -175,6 +208,7 @@ DEPENDENCIES paint puma sinatra + sqlite3 sys-proctable unicorn diff --git a/benchmarks/apps/chunked.ru b/benchmarks/apps/chunked.ru index 4b2be250..4aaa748e 100644 --- a/benchmarks/apps/chunked.ru +++ b/benchmarks/apps/chunked.ru @@ -1,4 +1,6 @@ -run Proc.new { |env| +# frozen_string_literal: true + +run proc { |_env| body = Enumerator.new do |yielder| 5.times do |i| yielder << "Chunk #{i + 1}\n" diff --git a/benchmarks/apps/echo_service/echo_service.rb b/benchmarks/apps/echo_service/echo_service.rb index 3781d6ad..c3b79dc4 100644 --- a/benchmarks/apps/echo_service/echo_service.rb +++ b/benchmarks/apps/echo_service/echo_service.rb @@ -5,7 +5,7 @@ require 'google/protobuf/timestamp_pb' require 'google/protobuf/any_pb' -descriptor_data = "\n\necho.proto\x12\x04\x65\x63ho\x1a\x17google/type/money.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\"\x1e\n\x0b\x45\x63hoRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\".\n\x0c\x45\x63hoResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\"\xa3\x01\n\x0ePaymentRequest\x12\x13\n\x0b\x63ustomer_id\x18\x01 \x01(\t\x12\"\n\x06\x61mount\x18\x02 \x01(\x0b\x32\x12.google.type.Money\x12\x30\n\x0cpayment_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x08metadata\x18\x04 \x01(\x0b\x32\x14.google.protobuf.Any\"\xae\x01\n\x0fPaymentResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x30\n\x0cprocessed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.echo.PaymentStatus\x12\x1a\n\rerror_message\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_error_message*\x82\x01\n\rPaymentStatus\x12\x1e\n\x1aPAYMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16PAYMENT_STATUS_SUCCESS\x10\x01\x12\x19\n\x15PAYMENT_STATUS_FAILED\x10\x02\x12\x1a\n\x16PAYMENT_STATUS_PENDING\x10\x03\x32\xb4\x02\n\x0b\x45\x63hoService\x12/\n\x04\x45\x63ho\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x12\x37\n\nEchoStream\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x30\x01\x12\x38\n\x0b\x45\x63hoCollect\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x12@\n\x11\x45\x63hoBidirectional\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x30\x01\x12?\n\x0eProcessPayment\x12\x14.echo.PaymentRequest\x1a\x15.echo.PaymentResponse\"\x00\x62\x06proto3" +descriptor_data = "\n\necho.proto\x12\x04\x65\x63ho\x1a\x17google/type/money.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\"\x1e\n\x0b\x45\x63hoRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\".\n\x0c\x45\x63hoResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\"\xa3\x01\n\x0ePaymentRequest\x12\x13\n\x0b\x63ustomer_id\x18\x01 \x01(\t\x12\"\n\x06\x61mount\x18\x02 \x01(\x0b\x32\x12.google.type.Money\x12\x30\n\x0cpayment_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x08metadata\x18\x04 \x01(\x0b\x32\x14.google.protobuf.Any\"\xae\x01\n\x0fPaymentResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x30\n\x0cprocessed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.echo.PaymentStatus\x12\x1a\n\rerror_message\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_error_message*\x82\x01\n\rPaymentStatus\x12\x1e\n\x1aPAYMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16PAYMENT_STATUS_SUCCESS\x10\x01\x12\x19\n\x15PAYMENT_STATUS_FAILED\x10\x02\x12\x1a\n\x16PAYMENT_STATUS_PENDING\x10\x03\x32\xb4\x02\n\x0b\x45\x63hoService\x12/\n\x04\x45\x63ho\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x12\x37\n\nEchoStream\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x30\x01\x12\x38\n\x0b\x45\x63hoCollect\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x12@\n\x11\x45\x63hoBidirectional\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x30\x01\x12?\n\x0eProcessPayment\x12\x14.echo.PaymentRequest\x1a\x15.echo.PaymentResponse\"\x00\x62\x06proto3" # rubocop:disable Layout/LineLength pool = Google::Protobuf::DescriptorPool.generated_pool pool.add_serialized_file(descriptor_data) @@ -20,7 +20,7 @@ module Echo module Echo module EchoService - class Service + class Service # rubocop:disable Style/Documentation include ::GRPC::GenericService self.marshal_class_method = :encode @@ -92,7 +92,7 @@ def echo_bidirectional(requests, _call) end Enumerator.new do |e| - 10.times do |i| + 10.times do |_i| e << Echo::EchoResponse.new( message: "Here's a response\n", count: count @@ -102,7 +102,7 @@ def echo_bidirectional(requests, _call) end end - def process_payment(request, _call) + def process_payment(request, _call) # rubocop:disable Metrics/MethodLength # Create a response with current timestamp # processed_at = Google::Protobuf::Timestamp.new(seconds: Time.now.to_i) @@ -111,7 +111,7 @@ def process_payment(request, _call) transaction_id = "txn_#{Time.now.to_i}_#{rand(1000..9999)}" # Simulate payment processing - status = if request.amount.units > 0 + status = if request.amount.units.positive? Echo::PaymentStatus::PAYMENT_STATUS_SUCCESS else Echo::PaymentStatus::PAYMENT_STATUS_FAILED diff --git a/benchmarks/apps/io_party.ru b/benchmarks/apps/io_party.ru new file mode 100644 index 00000000..d4fdb73d --- /dev/null +++ b/benchmarks/apps/io_party.ru @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'json' +require 'active_record' +require 'net/http' +require 'debug' + + +TMP_DB_DIR = Dir.mktmpdir('io_party_db') +TMP_DB_FILE = File.join(TMP_DB_DIR, 'test.sqlite3') + +ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: TMP_DB_FILE) + +ActiveRecord::Schema.define do + create_table :posts, force: true do |t| + t.string :name + t.text :body + t.timestamps + end +end + +class Post < ActiveRecord::Base; end + +run( + proc do |_| + post = Post.find_or_create_by(name: 'Hello World', body: 'I made a change. This is a test post') + ActiveRecord::Base.connection.execute('SELECT * FROM posts;') + sleep 0.0001 + + queue = Queue.new + Thread.new do + sleep 0.0001 + queue.push('done') + end.join + queue.pop + + Thread.new do + sleep 0.0001 + end.join + + post.update(name: 'I made a change. Hello World', body: 'Wow... I think it might be working') + [200, { 'content-type' => 'text/plain'}, [post.to_json]] + end +) + +at_exit do + ActiveRecord::Base.connection_pool.disconnect! + FileUtils.rm_f(TMP_DB_FILE) # remove database file + begin + Dir.rmdir(TMP_DB_DIR) + rescue StandardError + nil + end +end diff --git a/benchmarks/lib/benchmark_case.rb b/benchmarks/lib/benchmark_case.rb index 7a389b00..7dee832d 100644 --- a/benchmarks/lib/benchmark_case.rb +++ b/benchmarks/lib/benchmark_case.rb @@ -40,7 +40,7 @@ def initialize(name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength @static_files_root = nil @https = false @grpc = false - @parallel_requests = 16# frozen_string_literal: true + @parallel_requests = 16 @nonblocking = false @requires = %i[ruby] @use_yjit = true diff --git a/benchmarks/lib/server.rb b/benchmarks/lib/server.rb index a8b8934b..c29d6242 100644 --- a/benchmarks/lib/server.rb +++ b/benchmarks/lib/server.rb @@ -61,8 +61,8 @@ def run!(server_config_file_path, test_case, threads, workers) end result = yield combinations rescue StandardError => e - puts Paint["Server failed to start: #{e.message}", :red, :bold] - + puts Paint["Server failed to start: #{e.message}. `#{cmd}`", :red, :bold] + puts e return false end @@ -81,9 +81,9 @@ def exec_found? end def rss - @pid ? ProcTable.ps(pid: @pid).rss : nil + @pid ? ProcTable.ps(pid: @pid).rss.to_i : 0 rescue - nil + 0 end def stop! diff --git a/benchmarks/rack_bench.rb b/benchmarks/rack_bench.rb index 8b8b0917..ba85c7b8 100644 --- a/benchmarks/rack_bench.rb +++ b/benchmarks/rack_bench.rb @@ -33,6 +33,7 @@ def run_benchmark( ) server.run!(server_config_file_path, test_case, threads, workers) do |workloads| puts Paint["\n=== Running #{test_name} on #{server.name}", :cyan, :bold] + warmup_cmds = workloads.map do |url, method, data| if test_case.grpc? "ghz --duration-stop=ignore --cpus=2 -z #{test_case.warmup_duration}s -c50 --call #{test_case.call} --stream-call-count=5 -d #{data} --insecure #{URI(url).host}:#{URI(url).port} --proto #{test_case.proto} -O json" @@ -106,7 +107,7 @@ def run_benchmark( summary = result_json['summary'] p95_latency = result_json['latencyPercentiles']['p95'] gross_rps = summary['requestsPerSec'].round(2) - failure_rate = (1 - summary['successRate']) + failure_rate = (1 - summary['successRate'].to_f) net_rps = (gross_rps * (1 - failure_rate)).round(2) failure = failure_rate.*(100.0).round(2) @@ -238,6 +239,12 @@ def save_result(result, test_name, server_name) File.write(path, JSON.pretty_generate(result)) end +def result_exists?(test_name, server_name) + path_prefix = File.join("results", cpu_label, test_name) + FileUtils.mkdir_p(path_prefix) + File.exist? File.join(path_prefix, "#{server_name}.json") +end + filters = ARGV.map(&Regexp.method(:new)) Dir.glob('test_cases/*/*.rb').each do |path| @@ -249,12 +256,24 @@ def save_result(result, test_name, server_name) test_case = BenchmarkCase.new(test_name) test_case.instance_eval(src) + if test_case.grpc? && !system("which ghz > /dev/null") + puts Paint["Skipping gRPC test because exec `ghz` not found", :yellow] + next + elsif !system("which oha > /dev/null") + puts Paint["Skipping HTTP test because exec `oha` not found", :yellow] + next + end + Server::ALL.each do |server| results = [] next if filters.any?{|filter| !(filter =~ server.name.to_s || filter =~ test_name.to_s) } + if result_exists?(test_name, server.name.to_s) + puts "Results already exist for #{test_name}/#{server.name}" + next + end unless ENV['RACK_BENCH_OVERWRITE_RESULTS'] Array(test_case.threads).each do |threads| Array(test_case.workers).each do |workers| - [true, false].each do |http2| + [false, true].each do |http2| next unless server.exec_found? next unless test_case.requires.all?{|r| server.supports?(r) } next if http2 && !server.supports?(:http2) diff --git a/benchmarks/server_configurations/itsi.rb b/benchmarks/server_configurations/itsi.rb index 0cd3724a..a97015d9 100644 --- a/benchmarks/server_configurations/itsi.rb +++ b/benchmarks/server_configurations/itsi.rb @@ -1,4 +1,4 @@ # To make benchmarks fair. If we use too small a default, Itsi will start applying backpressure # by returning 503s under heavy loads for distant queued requests to Ruby, # causing an artificial increase in throughput -ruby_thread_request_backlog_size 100_000 +ruby_thread_request_backlog_size 10_000 diff --git a/benchmarks/servers.rb b/benchmarks/servers.rb index 4e4e5027..2232df37 100644 --- a/benchmarks/servers.rb +++ b/benchmarks/servers.rb @@ -9,7 +9,7 @@ Server.new \ :iodine, - '%{base} -p %{port} %{app_path} -w %{workers} -t %{threads} %{www}', + '%s -p %{port} %{app_path} -w %s -t %{threads} %{www}', supports: %i[threads processes static ruby], www: ->(test_case, _args){ test_case.static_files_root ? "-www #{test_case.static_files_root}" : ""} @@ -35,7 +35,7 @@ :agoo, '(cd apps && %{base} -p %{port} %{app_path} -w %{workers} -t %{threads} %{www})', supports: %i[threads processes streaming_body static ruby], - www: ->(test_case, _args){ test_case.static_files_root ? "-d #{test_case.static_files_root.gsub("./apps", "")}" : ""}, + www: ->(test_case, _args){ test_case.static_files_root ? "-d #{test_case.static_files_root}" : ""}, app_path: ->(_test_case, args){ args[:app_path].gsub("apps/", "") } Server.new \ @@ -51,14 +51,14 @@ Server.new \ :caddy, - 'GOMAXPROCS=%{workers} caddy file-server --listen %{host}:%{port} --browse --root %{www}', + 'GOMAXPROCS=%{workers} caddy file-server --listen %{host}:%{port} --browse --root %s', supports: %i[static http2] Server.new \ :h2o, - 'h2o -c %{config_file}', + 'h2o -c %s', supports: %i[static http2], - config_file: ->(_, args){ + config_file: lambda { |_, args| temp_config = Tempfile.new(['nginx', '.conf']) temp_config.write(IO.read('server_configurations/nginx.conf') % args) temp_config.flush diff --git a/crates/itsi_acme/Cargo.toml b/crates/itsi_acme/Cargo.toml index 7cc9c8a8..b79a3d76 100644 --- a/crates/itsi_acme/Cargo.toml +++ b/crates/itsi_acme/Cargo.toml @@ -2,7 +2,7 @@ name = "itsi_acme" version = "0.1.0" authors = [ - "wouterkem ", + "wouterken ", "dignifiedquire ", "Florian Uekermann ", ] diff --git a/crates/itsi_server/src/lib.rs b/crates/itsi_server/src/lib.rs index 3b80d0aa..0c79166e 100644 --- a/crates/itsi_server/src/lib.rs +++ b/crates/itsi_server/src/lib.rs @@ -72,6 +72,7 @@ fn init(ruby: &Ruby) -> Result<()> { request.define_method("url_encoded?", method!(ItsiHttpRequest::is_url_encoded, 0))?; request.define_method("multipart?", method!(ItsiHttpRequest::is_multipart, 0))?; request.define_method("url_params", method!(ItsiHttpRequest::url_params, 0))?; + request.define_method("server_error", method!(ItsiHttpRequest::error, 1))?; let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY); body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?; diff --git a/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs b/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs index 1aeb084b..f34a5c98 100644 --- a/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +++ b/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs @@ -11,7 +11,7 @@ use http::{ header::{HeaderName, HeaderValue}, HeaderMap, Response, }; -use http_body_util::{BodyDataStream, BodyExt}; +use http_body_util::BodyDataStream; use hyper::body::Incoming; use magnus::error::Result as MagnusResult; use nix::unistd::pipe; diff --git a/crates/itsi_server/src/ruby_types/itsi_http_request.rs b/crates/itsi_server/src/ruby_types/itsi_http_request.rs index b8d179c5..801102ce 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_request.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_request.rs @@ -1,7 +1,7 @@ use derive_more::Debug; use futures::StreamExt; use http::{header::CONTENT_LENGTH, request::Parts, HeaderValue, Response, StatusCode, Version}; -use http_body_util::{combinators::BoxBody, BodyExt, Empty}; +use http_body_util::BodyExt; use itsi_error::CLIENT_CONNECTION_CLOSED; use itsi_rb_helpers::{funcall_no_ret, print_rb_backtrace, HeapValue}; use itsi_tracing::debug; @@ -170,7 +170,7 @@ impl ItsiHttpRequest { } } - pub fn error(self, message: String) { + pub fn error(&self, message: String) { self.response.internal_server_error(message); } diff --git a/crates/itsi_server/src/ruby_types/itsi_http_response.rs b/crates/itsi_server/src/ruby_types/itsi_http_response.rs index 20d4cff4..774d7868 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_response.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_response.rs @@ -1,3 +1,7 @@ +use crate::server::{ + http_message_types::{HttpBody, HttpResponse}, + serve_strategy::single_mode::RunningPhase, +}; use bytes::{Buf, Bytes}; use derive_more::Debug; use futures::stream::{unfold, StreamExt}; @@ -6,8 +10,8 @@ use http::{ request::Parts, HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode, }; -use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody}; -use hyper::{body::Frame, upgrade::Upgraded}; +use http_body_util::Empty; +use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use itsi_error::Result; use itsi_rb_helpers::call_without_gvl; @@ -28,14 +32,8 @@ use tokio::{ net::UnixStream as TokioUnixStream, sync::{mpsc::Sender, oneshot::Sender as OneshotSender, watch}, }; - use tokio_util::io::ReaderStream; -use tracing::{debug, info, warn}; - -use crate::server::{ - http_message_types::{HttpBody, HttpResponse}, - serve_strategy::single_mode::RunningPhase, -}; +use tracing::{info, warn}; #[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)] #[derive(Debug, Clone)] @@ -230,11 +228,21 @@ impl ItsiHttpResponse { } } + pub fn service_unavailable(&self) { + self.close_write().ok(); + if let Some(mut response) = self.response.write().take() { + *response.status_mut() = StatusCode::SERVICE_UNAVAILABLE; + if let Some(sender) = self.response_sender.write().take() { + sender.send(ResponseFrame::HttpResponse(response)).ok(); + } + } + } + pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> { { if self.frame_writer.read().is_none() && self.response.read().is_some() { if let Some(mut response) = self.response.write().take() { - let (writer, mut reader) = tokio::sync::mpsc::channel(5); + let (writer, mut reader) = tokio::sync::mpsc::channel::(5); let mut shutdown_rx = self.shutdown_rx.clone(); let frame_stream = async_stream::stream! { @@ -251,10 +259,9 @@ impl ItsiHttpResponse { _ = shutdown_rx.changed() => { if *shutdown_rx.borrow() == RunningPhase::ShutdownPending { reader.close(); - while let Some(bytes) = reader.recv().await{ + while let Ok(bytes) = reader.try_recv() { yield Ok(bytes); } - debug!("Disconnecting streaming client."); break; } } diff --git a/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs b/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs index f57ad0bb..bd9d0aae 100644 --- a/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +++ b/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs @@ -72,6 +72,12 @@ pub struct ServerParams { pub ruby_thread_request_backlog_size: Option, pub middleware_loader: HeapValue, pub middleware: OnceLock, + pub pipeline_flush: bool, + pub writev: Option, + pub max_concurrent_streams: Option, + pub max_local_error_reset_streams: Option, + pub max_header_list_size: u32, + pub max_send_buf_size: usize, pub binds: Vec, #[debug(skip)] pub(crate) listeners: Mutex>, @@ -178,19 +184,19 @@ impl ServerParams { } fn from_rb_hash(rb_param_hash: RHash) -> Result { + let num_cpus = num_cpus::get_physical() as u8; let workers = rb_param_hash .fetch::<_, Option>("workers")? - .unwrap_or(num_cpus::get() as u8); + .unwrap_or(num_cpus); let worker_memory_limit: Option = rb_param_hash.fetch("worker_memory_limit")?; let silence: bool = rb_param_hash.fetch("silence")?; let multithreaded_reactor: bool = rb_param_hash .fetch::<_, Option>("multithreaded_reactor")? - .unwrap_or(workers == 1); + .unwrap_or(workers <= (num_cpus / 3)); let pin_worker_cores: bool = rb_param_hash .fetch::<_, Option>("pin_worker_cores")? - .unwrap_or(true); + .unwrap_or(false); let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?; - let hooks: Option = rb_param_hash.fetch("hooks")?; let hooks = hooks .map(|rhash| -> Result>> { @@ -281,6 +287,14 @@ impl ServerParams { set_target_filters(target_filters); } + let pipeline_flush: bool = rb_param_hash.fetch("pipeline_flush")?; + let writev: Option = rb_param_hash.fetch("writev")?; + let max_concurrent_streams: Option = rb_param_hash.fetch("max_concurrent_streams")?; + let max_local_error_reset_streams: Option = + rb_param_hash.fetch("max_local_error_reset_streams")?; + let max_header_list_size: u32 = rb_param_hash.fetch("max_header_list_size")?; + let max_send_buf_size: usize = rb_param_hash.fetch("max_send_buf_size")?; + let binds: Option> = rb_param_hash.fetch("binds")?; let binds = binds .unwrap_or_else(|| vec![DEFAULT_BIND.to_string()]) @@ -322,6 +336,12 @@ impl ServerParams { scheduler_class, ruby_thread_request_backlog_size, oob_gc_responses_threshold, + pipeline_flush, + writev, + max_concurrent_streams, + max_local_error_reset_streams, + max_header_list_size, + max_send_buf_size, binds, itsi_server_token_preference, socket_opts, @@ -437,7 +457,8 @@ impl ItsiServerConfig { let requires_exec = if !is_single_mode && !server_params.preload { // In cluster mode children are cycled during a reload // and if preload is disabled, will get a clean memory slate, - // so we don't need to exec. + // so we don't need to exec. We do need to rebind our listeners here. + server_params.setup_listeners()?; false } else { // In non-cluster mode, or when preloading is enabled, we shouldn't try to diff --git a/crates/itsi_server/src/server/http_message_types.rs b/crates/itsi_server/src/server/http_message_types.rs index acc3299b..363adfa1 100644 --- a/crates/itsi_server/src/server/http_message_types.rs +++ b/crates/itsi_server/src/server/http_message_types.rs @@ -13,16 +13,12 @@ use std::{ use super::size_limited_incoming::SizeLimitedIncoming; -/* ------------------------------------------------------------------------- - * Low‑level concrete body without trailers - * ---------------------------------------------------------------------- */ - type Inner = Either, Empty>; type BoxStream = Pin, Infallible>> + Send + Sync + 'static>>; -struct PlainBody(Either, Inner>); +pub struct PlainBody(Either, Inner>); impl fmt::Debug for PlainBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -71,10 +67,6 @@ impl PlainBody { } } -/* ------------------------------------------------------------------------- - * Public body type used at API boundaries (plain or with trailers) - * ---------------------------------------------------------------------- */ - type BoxTrailers = Pin< Box>> + Send + Sync>, >; @@ -117,10 +109,6 @@ impl Body for HttpBody { } } -/* ------------------------------------------------------------------------- - * Constructors & helpers (public API) - * ---------------------------------------------------------------------- */ - impl HttpBody { pub fn stream(s: S) -> Self where @@ -152,12 +140,7 @@ impl HttpBody { } } -/* ------------------------------------------------------------------------- - * Aliases & request helpers - * ---------------------------------------------------------------------- */ - pub type HttpResponse = http::Response; - pub type HttpRequest = Request>; pub trait ConversionExt { @@ -171,10 +154,6 @@ impl ConversionExt for Request { } } -/* ------------------------------------------------------------------------- - * Misc unchanged helpers - * ---------------------------------------------------------------------- */ - pub trait RequestExt { fn content_type(&self) -> Option<&str>; fn accept(&self) -> Option<&str>; diff --git a/crates/itsi_server/src/server/lifecycle_event.rs b/crates/itsi_server/src/server/lifecycle_event.rs index 18cb99ff..5a258f4a 100644 --- a/crates/itsi_server/src/server/lifecycle_event.rs +++ b/crates/itsi_server/src/server/lifecycle_event.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum LifecycleEvent { Start, Shutdown, diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs index 0f476e52..0329e8c3 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs @@ -1,7 +1,6 @@ use super::{ContentSource, DefaultFormat, ErrorResponse}; use crate::server::http_message_types::{HttpBody, ResponseFormat}; use bytes::Bytes; -use http_body_util::Full; impl DefaultFormat { pub fn response_for_code(&self, code: u16) -> ContentSource { diff --git a/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs b/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs index 2bb55413..c391a498 100644 --- a/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +++ b/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs @@ -24,8 +24,7 @@ use bytes::{Bytes, BytesMut}; use either::Either; use futures::TryStreamExt; use http::{HeaderMap, Method, Response, StatusCode}; -use http_body_util::{combinators::BoxBody, BodyExt, Empty, StreamBody}; -use hyper::body::Frame; +use http_body_util::BodyExt; use magnus::error::Result; use rand::Rng; use reqwest::{ diff --git a/crates/itsi_server/src/server/process_worker.rs b/crates/itsi_server/src/server/process_worker.rs index 693e5691..cfbc6198 100644 --- a/crates/itsi_server/src/server/process_worker.rs +++ b/crates/itsi_server/src/server/process_worker.rs @@ -88,7 +88,7 @@ impl ProcessWorker { .pin_worker_cores { core_affinity::set_for_current( - CORE_IDS[self.worker_id % CORE_IDS.len()], + CORE_IDS[(2 * self.worker_id) % CORE_IDS.len()], ); } Arc::new(single_mode).run().ok(); @@ -166,7 +166,7 @@ impl ProcessWorker { } pub(crate) fn boot_if_dead(&self, cluster_template: Arc) -> bool { - if !self.is_alive() { + if !self.is_alive() && self.child_pid.lock().is_some() { if self.just_started() { error!( "Worker in crash loop {:?}. Refusing to restart", @@ -202,7 +202,6 @@ impl ProcessWorker { let child_pid = *self.child_pid.lock(); if let Some(pid) = child_pid { if self.is_alive() { - info!("Worker still alive, sending SIGKILL {}", pid); if let Err(e) = kill(pid, SIGKILL) { error!("Failed to force kill process {}: {}", pid, e); } diff --git a/crates/itsi_server/src/server/serve_strategy/acceptor.rs b/crates/itsi_server/src/server/serve_strategy/acceptor.rs index effca6d5..e73f73a0 100644 --- a/crates/itsi_server/src/server/serve_strategy/acceptor.rs +++ b/crates/itsi_server/src/server/serve_strategy/acceptor.rs @@ -66,7 +66,6 @@ impl Acceptor { debug!("Connection closed abruptly: {:?}", res); } } - serve.as_mut().graceful_shutdown(); }, // A lifecycle event triggers shutdown. _ = shutdown_channel.changed() => { @@ -84,6 +83,7 @@ impl Acceptor { pub async fn join(&mut self) { // Join all acceptor tasks with timeout + let deadline = tokio::time::Instant::now() + Duration::from_secs_f64(self.server_params.shutdown_timeout); let sleep_until = tokio::time::sleep_until(deadline); @@ -92,6 +92,7 @@ impl Acceptor { while (self.join_set.join_next().await).is_some() {} } => {}, _ = sleep_until => { + self.join_set.abort_all(); debug!("Shutdown timeout reached; abandoning remaining acceptor tasks."); } } diff --git a/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs b/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs index 5c56673b..9b3b3b05 100644 --- a/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs @@ -1,5 +1,5 @@ use crate::ruby_types::itsi_server::itsi_server_config::ItsiServerConfig; -use crate::server::signal::SIGNAL_HANDLER_CHANNEL; +use crate::server::signal::{subscribe_runtime_to_signals, unsubscribe_runtime}; use crate::server::{lifecycle_event::LifecycleEvent, process_worker::ProcessWorker}; use itsi_error::{ItsiError, Result}; use itsi_rb_helpers::{call_with_gvl, call_without_gvl, create_ruby_thread}; @@ -7,25 +7,27 @@ use itsi_tracing::{error, info, warn}; use magnus::Value; use nix::{libc::exit, unistd::Pid}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::{ sync::Arc, time::{Duration, Instant}, }; use tokio::{ runtime::{Builder as RuntimeBuilder, Runtime}, - sync::{broadcast, watch, Mutex}, + sync::{watch, Mutex}, time::{self, sleep}, }; use tracing::{debug, instrument}; pub(crate) struct ClusterMode { pub server_config: Arc, pub process_workers: parking_lot::Mutex>, - pub lifecycle_channel: broadcast::Sender, } static CHILD_SIGNAL_SENDER: parking_lot::Mutex>> = parking_lot::Mutex::new(None); +static RELOAD_IN_PROGRESS: AtomicBool = AtomicBool::new(false); + impl ClusterMode { pub fn new(server_config: Arc) -> Self { let process_workers = (0..server_config.server_params.read().workers) @@ -38,7 +40,6 @@ impl ClusterMode { Self { server_config, process_workers: parking_lot::Mutex::new(process_workers), - lifecycle_channel: SIGNAL_HANDLER_CHANNEL.0.clone(), } } @@ -60,19 +61,23 @@ impl ClusterMode { } fn next_worker_id(&self) -> usize { - let mut taken: Vec<_> = self + let mut ids: Vec = self .process_workers .lock() .iter() .map(|w| w.worker_id) .collect(); - taken.sort_unstable(); - for (expected, &id) in taken.iter().enumerate() { + self.next_available_id_in(&mut ids) + } + + fn next_available_id_in(&self, list: &mut [usize]) -> usize { + list.sort_unstable(); + for (expected, &id) in list.iter().enumerate() { if id != expected { return expected; } } - taken.len() + list.len() } #[allow(clippy::await_holding_lock)] @@ -112,34 +117,50 @@ impl ClusterMode { self.shutdown().await.ok(); self.server_config.reload_exec()?; } - let mut workers_to_load = self.server_config.server_params.read().workers; - let mut next_workers = Vec::new(); - for worker in self.process_workers.lock().drain(..) { - if workers_to_load == 0 { - worker.graceful_shutdown(self.clone()).await - } else { - workers_to_load -= 1; - worker.reboot(self.clone()).await?; - next_workers.push(worker); - } + + if RELOAD_IN_PROGRESS + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + warn!("Reload already in progress, ignoring request"); + return Ok(()); } - self.process_workers.lock().extend(next_workers); - while workers_to_load > 0 { - let mut workers = self.process_workers.lock(); + let workers_to_load = self.server_config.server_params.read().workers; + let mut next_workers = Vec::new(); + let mut old_workers = self.process_workers.lock().drain(..).collect::>(); + + // Spawn new workers + for i in 0..workers_to_load { let worker = ProcessWorker { - worker_id: self.next_worker_id(), + worker_id: i as usize, ..Default::default() }; let worker_clone = worker.clone(); let self_clone = self.clone(); - create_ruby_thread(move || { - call_without_gvl(move || { - worker_clone.boot(self_clone).ok(); - }) + + call_with_gvl(|_| { + create_ruby_thread(move || { + call_without_gvl(move || match worker_clone.boot(self_clone) { + Err(err) => error!("Worker boot failed {:?}", err), + _ => {} + }) + }); }); - workers.push(worker); - workers_to_load -= 1 + + next_workers.push(worker); + + if let Some(old) = old_workers.pop() { + old.graceful_shutdown(self.clone()).await; + } + } + + for worker in old_workers { + worker.graceful_shutdown(self.clone()).await; } + + self.process_workers.lock().extend(next_workers); + RELOAD_IN_PROGRESS.store(false, Ordering::SeqCst); + Ok(()) } LifecycleEvent::IncreaseWorkers => { @@ -186,6 +207,10 @@ impl ClusterMode { unsafe { exit(0) }; } LifecycleEvent::ChildTerminated => { + if RELOAD_IN_PROGRESS.load(Ordering::SeqCst) { + warn!("Reload already in progress, ignoring child signal"); + return Ok(()); + } CHILD_SIGNAL_SENDER.lock().as_ref().inspect(|i| { i.send(()).ok(); }); @@ -308,10 +333,11 @@ impl ClusterMode { let (sender, mut receiver) = watch::channel(()); *CHILD_SIGNAL_SENDER.lock() = Some(sender); - let mut lifecycle_rx = self.lifecycle_channel.subscribe(); let self_ref = self.clone(); self.build_runtime().block_on(async { + let mut lifecycle_rx = subscribe_runtime_to_signals(); + let self_ref = self_ref.clone(); let memory_check_duration = if self_ref.server_config.server_params.read().worker_memory_limit.is_some(){ time::Duration::from_secs(15) @@ -363,11 +389,16 @@ impl ClusterMode { } }, - Err(e) => error!("Error receiving lifecycle_event: {:?}", e), + Err(e) => { + error!("Error receiving lifecycle_event: {:?}", e); + break + }, } } } }); + + unsubscribe_runtime(); self.server_config .server_params .write() diff --git a/crates/itsi_server/src/server/serve_strategy/single_mode.rs b/crates/itsi_server/src/server/serve_strategy/single_mode.rs index 808cb579..28e22de6 100644 --- a/crates/itsi_server/src/server/serve_strategy/single_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/single_mode.rs @@ -4,7 +4,7 @@ use crate::{ lifecycle_event::LifecycleEvent, request_job::RequestJob, serve_strategy::acceptor::{Acceptor, AcceptorArgs}, - signal::{SHUTDOWN_REQUESTED, SIGNAL_HANDLER_CHANNEL}, + signal::{send_lifecycle_event, subscribe_runtime_to_signals, SHUTDOWN_REQUESTED}, thread_worker::{build_thread_workers, ThreadWorker}, }, }; @@ -29,10 +29,7 @@ use std::{ }; use tokio::{ runtime::{Builder as RuntimeBuilder, Runtime}, - sync::{ - broadcast, - watch::{self}, - }, + sync::watch::{self}, task::JoinSet, }; use tracing::instrument; @@ -41,7 +38,6 @@ pub struct SingleMode { pub worker_id: usize, pub executor: Builder, pub server_config: Arc, - pub(crate) lifecycle_channel: broadcast::Sender, pub restart_requested: AtomicBool, pub status: RwLock>, } @@ -57,25 +53,34 @@ impl SingleMode { #[instrument(parent=None, skip_all)] pub fn new(server_config: Arc, worker_id: usize) -> Result { server_config.server_params.read().preload_ruby()?; - let mut executor = Builder::new(TokioExecutor::new()); - executor - .http1() - .header_read_timeout(server_config.server_params.read().header_read_timeout) - .pipeline_flush(true) - .timer(TokioTimer::new()); - executor - .http2() - .max_concurrent_streams(100) - .max_local_error_reset_streams(100) - .enable_connect_protocol() - .max_header_list_size(10 * 1024 * 1024) - .max_send_buf_size(16 * 1024 * 1024); + let executor = { + let mut executor = Builder::new(TokioExecutor::new()); + let server_params = server_config.server_params.read(); + let mut http1_executor = executor.http1(); + + http1_executor + .header_read_timeout(server_params.header_read_timeout) + .pipeline_flush(server_params.pipeline_flush) + .timer(TokioTimer::new()); + + if let Some(writev) = server_params.writev { + http1_executor.writev(writev); + } + + executor + .http2() + .max_concurrent_streams(server_params.max_concurrent_streams) + .max_local_error_reset_streams(server_params.max_local_error_reset_streams) + .max_header_list_size(server_params.max_header_list_size) + .max_send_buf_size(server_params.max_send_buf_size) + .enable_connect_protocol(); + executor + }; Ok(Self { worker_id, executor, server_config, - lifecycle_channel: SIGNAL_HANDLER_CHANNEL.0.clone(), restart_requested: AtomicBool::new(false), status: RwLock::new(HashMap::new()), }) @@ -110,7 +115,7 @@ impl SingleMode { pub fn stop(&self) -> Result<()> { SHUTDOWN_REQUESTED.store(true, std::sync::atomic::Ordering::SeqCst); - self.lifecycle_channel.send(LifecycleEvent::Shutdown).ok(); + send_lifecycle_event(LifecycleEvent::Shutdown); Ok(()) } @@ -189,7 +194,7 @@ impl SingleMode { .unwrap(); let receiver = self.clone(); monitor_runtime.block_on({ - let mut lifecycle_rx = receiver.lifecycle_channel.subscribe(); + let mut lifecycle_rx = subscribe_runtime_to_signals(); let receiver = receiver.clone(); let thread_workers = thread_workers.clone(); async move { @@ -208,12 +213,9 @@ impl SingleMode { } lifecycle_event = lifecycle_rx.recv() => { match lifecycle_event { - Ok(LifecycleEvent::Restart) => { + Ok(LifecycleEvent::Restart) | Ok(LifecycleEvent::Reload) => { receiver.restart().await.ok(); } - Ok(LifecycleEvent::Reload) => { - receiver.reload().await.ok(); - } Ok(LifecycleEvent::Shutdown) => { break; } @@ -282,7 +284,7 @@ impl SingleMode { let job_sender = job_sender.clone(); let nonblocking_sender = nonblocking_sender.clone(); - let mut lifecycle_rx = self.lifecycle_channel.subscribe(); + let mut lifecycle_rx = subscribe_runtime_to_signals(); let mut shutdown_receiver = shutdown_sender.subscribe(); let mut acceptor = Acceptor { acceptor_args: Arc::new(AcceptorArgs { @@ -298,11 +300,17 @@ impl SingleMode { let shutdown_rx_for_acme_task = shutdown_receiver.clone(); let acme_task_listener_clone = listener.clone(); - let after_accept_wait = if server_params.workers > 1{ - Some(Duration::from_nanos(10 * server_params.workers as u64))} - else{ - None + + let mut after_accept_wait: Option = None::; + + if cfg!(target_os = "macos") { + after_accept_wait = if server_params.workers > 1 { + Some(Duration::from_nanos(10 * server_params.workers as u64)) + } else { + None + }; }; + listener_task_set.spawn(async move { acme_task_listener_clone .spawn_acme_event_task(shutdown_rx_for_acme_task) @@ -311,14 +319,18 @@ impl SingleMode { listener_task_set.spawn(async move { loop { + // Process any pending signals before select tokio::select! { accept_result = listener.accept() => { + info!("New connection accepted"); match accept_result { Ok(accepted) => acceptor.serve_connection(accepted).await, Err(e) => debug!("Listener.accept failed: {:?}", e) } - if let Some(after_accept_wait) = after_accept_wait{ - tokio::time::sleep(after_accept_wait).await; + if cfg!(target_os = "macos") { + if let Some(after_accept_wait) = after_accept_wait{ + tokio::time::sleep(after_accept_wait).await; + } } }, _ = shutdown_receiver.changed() => { @@ -330,13 +342,12 @@ impl SingleMode { Ok(LifecycleEvent::Shutdown) => { debug!("Received LifecycleEvent::Shutdown"); let _ = shutdown_sender.send(RunningPhase::ShutdownPending); - for _ in 0..worker_count { - let _ = job_sender.send_blocking(RequestJob::Shutdown); - let _ = nonblocking_sender.send_blocking(RequestJob::Shutdown); - } break; }, - Err(e) => error!("Error receiving lifecycle event: {:?}", e), + Err(e) => { + error!("Error receiving lifecycle event: {:?}", e); + break + }, _ => () } } @@ -358,14 +369,14 @@ impl SingleMode { debug!("Single mode runtime exited."); + for _i in 0..thread_workers.len() { + job_sender.send_blocking(RequestJob::Shutdown).unwrap(); + nonblocking_sender + .send_blocking(RequestJob::Shutdown) + .unwrap(); + } if result.is_err() { - for _i in 0..thread_workers.len() { - job_sender.send_blocking(RequestJob::Shutdown).unwrap(); - nonblocking_sender - .send_blocking(RequestJob::Shutdown) - .unwrap(); - } - self.lifecycle_channel.send(LifecycleEvent::Shutdown).ok(); + send_lifecycle_event(LifecycleEvent::Shutdown); } shutdown_sender.send(RunningPhase::Shutdown).ok(); @@ -401,26 +412,6 @@ impl SingleMode { pub fn is_single_mode(&self) -> bool { self.server_config.server_params.read().workers == 1 } - /// Attempts to reload the config "live" - /// Not that when running in single mode this will not unload - /// old code. If you need a clean restart, use the `restart` (SIGHUP) method instead - pub async fn reload(&self) -> Result<()> { - if !self.server_config.check_config().await { - return Ok(()); - } - let should_reexec = self.server_config.clone().reload(false)?; - if should_reexec { - if self.is_single_mode() { - self.invoke_hook("before_restart"); - } - self.server_config.dup_fds()?; - self.server_config.reload_exec()?; - } - self.restart_requested.store(true, Ordering::SeqCst); - self.stop()?; - self.server_config.server_params.read().preload_ruby()?; - Ok(()) - } pub fn invoke_hook(&self, hook_name: &str) { if let Some(hook) = self.server_config.server_params.read().hooks.get(hook_name) { diff --git a/crates/itsi_server/src/server/signal.rs b/crates/itsi_server/src/server/signal.rs index 539cd16f..ccca9c76 100644 --- a/crates/itsi_server/src/server/signal.rs +++ b/crates/itsi_server/src/server/signal.rs @@ -1,22 +1,49 @@ -use std::sync::{ - atomic::{AtomicBool, AtomicI8}, - LazyLock, +use std::{ + collections::VecDeque, + sync::atomic::{AtomicBool, AtomicI8}, }; use nix::libc::{self, sighandler_t}; -use tokio::sync::{self, broadcast}; +use parking_lot::Mutex; +use tokio::sync::broadcast; use super::lifecycle_event::LifecycleEvent; pub static SIGINT_COUNT: AtomicI8 = AtomicI8::new(0); pub static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false); -pub static SIGNAL_HANDLER_CHANNEL: LazyLock<( - broadcast::Sender, - broadcast::Receiver, -)> = LazyLock::new(|| sync::broadcast::channel(5)); +pub static SIGNAL_HANDLER_CHANNEL: Mutex>> = + Mutex::new(None); + +pub static PENDING_QUEUE: Mutex> = Mutex::new(VecDeque::new()); + +pub fn subscribe_runtime_to_signals() -> broadcast::Receiver { + if let Some(sender) = SIGNAL_HANDLER_CHANNEL.lock().as_ref() { + return sender.subscribe(); + } + let (sender, receiver) = broadcast::channel(5); + let sender_clone = sender.clone(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(50)); + for event in PENDING_QUEUE.lock().drain(..) { + sender_clone.send(event).ok(); + } + }); + + SIGNAL_HANDLER_CHANNEL.lock().replace(sender); + + receiver +} + +pub fn unsubscribe_runtime() { + SIGNAL_HANDLER_CHANNEL.lock().take(); +} pub fn send_lifecycle_event(event: LifecycleEvent) { - SIGNAL_HANDLER_CHANNEL.0.send(event).ok(); + if let Some(sender) = SIGNAL_HANDLER_CHANNEL.lock().as_ref() { + sender.send(event).ok(); + } else { + PENDING_QUEUE.lock().push_back(event); + } } fn receive_signal(signum: i32, _: sighandler_t) { diff --git a/crates/itsi_server/src/server/thread_worker.rs b/crates/itsi_server/src/server/thread_worker.rs index c76444a7..0ae37e9b 100644 --- a/crates/itsi_server/src/server/thread_worker.rs +++ b/crates/itsi_server/src/server/thread_worker.rs @@ -192,7 +192,9 @@ impl ThreadWorker { *self.thread.write() = Some( create_ruby_thread(move || { if params.pin_worker_cores { - core_affinity::set_for_current(CORE_IDS[worker_id % CORE_IDS.len()]); + core_affinity::set_for_current( + CORE_IDS[((2 * worker_id) + 1) % CORE_IDS.len()], + ); } debug!("Ruby thread worker started"); if let Some(scheduler_class) = scheduler_class { @@ -440,13 +442,13 @@ impl ThreadWorker { Err(_) => break, Ok(RequestJob::Shutdown) => break, Ok(request_job) => call_with_gvl(|ruby| { - self.process_one(&ruby, request_job); + self.process_one(&ruby, request_job, &terminated); while let Ok(request_job) = receiver.try_recv() { if matches!(request_job, RequestJob::Shutdown) { terminated.store(true, Ordering::Relaxed); break; } - self.process_one(&ruby, request_job); + self.process_one(&ruby, request_job, &terminated); } if let Some(thresh) = params.oob_gc_responses_threshold { idle_counter = (idle_counter + 1) % thresh; @@ -462,9 +464,13 @@ impl ThreadWorker { }); } - fn process_one(self: &Arc, ruby: &Ruby, job: RequestJob) { + fn process_one(self: &Arc, ruby: &Ruby, job: RequestJob, terminated: &Arc) { match job { RequestJob::ProcessHttpRequest(request, app_proc) => { + if terminated.load(Ordering::Relaxed) { + request.response().unwrap().service_unavailable(); + return; + } self.request_id.fetch_add(1, Ordering::Relaxed); self.current_request_start.store( SystemTime::now() @@ -477,6 +483,10 @@ impl ThreadWorker { } RequestJob::ProcessGrpcRequest(request, app_proc) => { + if terminated.load(Ordering::Relaxed) { + request.stream().unwrap().close().ok(); + return; + } self.request_id.fetch_add(1, Ordering::Relaxed); self.current_request_start.store( SystemTime::now() diff --git a/crates/itsi_server/src/services/itsi_http_service.rs b/crates/itsi_server/src/services/itsi_http_service.rs index fa089699..07243a10 100644 --- a/crates/itsi_server/src/services/itsi_http_service.rs +++ b/crates/itsi_server/src/services/itsi_http_service.rs @@ -6,7 +6,7 @@ use crate::server::http_message_types::{ use crate::server::lifecycle_event::LifecycleEvent; use crate::server::middleware_stack::MiddlewareLayer; use crate::server::serve_strategy::acceptor::AcceptorArgs; -use crate::server::signal::send_lifecycle_event; +use crate::server::signal::{send_lifecycle_event, SHUTDOWN_REQUESTED}; use chrono::{self, DateTime, Local}; use either::Either; use http::header::ACCEPT_ENCODING; @@ -246,7 +246,11 @@ impl ItsiHttpService { // If we're still running Ruby at this point, we can't just kill the // thread as it might be in a critical section. // Instead we must ask the worker to hot restart. - if is_ruby_request.load(Ordering::Relaxed) { + // But only if we're not already shutting down + if is_ruby_request.load(Ordering::Relaxed) && + !SHUTDOWN_REQUESTED.load(Ordering::SeqCst) { + // When we've detected a timeout, use the safer send_lifecycle_event + // which will properly handle signal-safe state transitions if is_single_mode { // If we're in single mode, re-exec the whole process send_lifecycle_event(LifecycleEvent::Restart); diff --git a/gems/scheduler/Cargo.lock b/gems/scheduler/Cargo.lock index dcf8f3a8..3f39eef0 100644 --- a/gems/scheduler/Cargo.lock +++ b/gems/scheduler/Cargo.lock @@ -3,733 +3,887 @@ version = 4 [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "addr2line" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "memchr", + "gimli", ] [[package]] -name = "anyhow" -version = "1.0.98" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] -name = "atty" -version = "0.2.14" +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "cfg-if", + "getrandom 0.2.16", + "once_cell", + "version_check", + "zerocopy 0.7.35", ] [[package]] -name = "autocfg" -version = "1.4.0" +name = "aho-corasick" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] [[package]] -name = "base64" -version = "0.22.1" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] -name = "bindgen" -version = "0.69.5" +name = "alloc-stdlib" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.101", + "alloc-no-stdlib", ] [[package]] -name = "bitflags" -version = "2.9.0" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] -name = "bytes" -version = "1.10.1" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] -name = "cc" -version = "1.2.20" +name = "ansi_term" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "shlex", + "winapi", ] [[package]] -name = "cexpr" -version = "0.6.0" +name = "anstream" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ - "nom", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "anstyle" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] -name = "cfg_aliases" -version = "0.2.1" +name = "anstyle-parse" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] [[package]] -name = "clang-sys" -version = "1.8.1" +name = "anstyle-query" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "glob", - "libc", - "libloading", + "windows-sys 0.59.0", ] [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "anstyle-wincon" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ - "crossbeam-utils", + "anstyle", + "once_cell", + "windows-sys 0.59.0", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "anyhow" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] -name = "deranged" -version = "0.4.0" +name = "arc-swap" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ - "powerfmt", + "base64ct", + "blake2", + "cpufeatures", + "password-hash", ] [[package]] -name = "derive_more" -version = "2.0.1" +name = "asn1-rs" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "derive_more-impl", + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", ] [[package]] -name = "derive_more-impl" -version = "2.0.1" +name = "asn1-rs-derive" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", "syn 2.0.101", - "unicode-xid", + "synstructure", ] [[package]] -name = "either" -version = "1.15.0" +name = "asn1-rs-impl" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] [[package]] -name = "getrandom" -version = "0.2.16" +name = "async-channel" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ - "cfg-if", - "libc", - "wasi", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "glob" -version = "0.3.2" +name = "async-compression" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "futures-io", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "async-stream" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ - "libc", + "async-stream-impl", + "futures-core", + "pin-project-lite", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "async-stream-impl" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] [[package]] -name = "itertools" -version = "0.12.1" +name = "async-trait" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ - "either", + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "itsi-scheduler" -version = "0.2.17" +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "bytes", - "derive_more", - "itsi_error", - "itsi_instrument_entry", - "itsi_rb_helpers", - "itsi_tracing", - "magnus", - "mio", - "nix", - "parking_lot", - "rb-sys", - "tracing", + "hermit-abi 0.1.19", + "libc", + "winapi", ] [[package]] -name = "itsi_error" -version = "0.1.0" -dependencies = [ - "anyhow", - "httparse", - "magnus", - "nix", - "rcgen", - "thiserror 2.0.12", -] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "itsi_instrument_entry" -version = "0.1.0" +name = "aws-lc-rs" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "aws-lc-sys", + "zeroize", ] [[package]] -name = "itsi_rb_helpers" -version = "0.1.0" +name = "aws-lc-sys" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" dependencies = [ - "cfg-if", - "magnus", - "nix", - "rb-sys", - "serde", + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", ] [[package]] -name = "itsi_tracing" -version = "0.1.0" +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "atty", + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", "tracing", - "tracing-appender", - "tracing-attributes", - "tracing-subscriber", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "axum-core" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] [[package]] -name = "lazycell" -version = "1.3.0" +name = "axum-server" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab" +dependencies = [ + "bytes", + "fs-err", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "hyper-util", + "tokio", + "tokio-rustls", + "tower-service", +] [[package]] -name = "libc" -version = "0.2.172" +name = "backon" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "fd0b50b1b78dbadd44ab18b3c794e496f3a139abb9fbc27d9c94c4eebbb96496" +dependencies = [ + "fastrand", +] [[package]] -name = "libloading" -version = "0.8.6" +name = "backtrace" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ + "addr2line", "cfg-if", - "windows-targets", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] -name = "lock_api" -version = "0.4.12" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "log" -version = "0.4.27" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "magnus" -version = "0.7.1" +name = "base64ct" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d87ae53030f3a22e83879e666cb94e58a7bdf31706878a0ba48752994146dab" -dependencies = [ - "bytes", - "magnus-macros", - "rb-sys", - "rb-sys-env", - "seq-macro", +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + +[[package]] +name = "bcrypt" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f" +dependencies = [ + "base64 0.22.1", + "blowfish", + "getrandom 0.3.2", + "subtle", + "zeroize", ] [[package]] -name = "magnus-macros" -version = "0.6.0" +name = "bindgen" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ + "bitflags 2.9.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", "proc-macro2", "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", "syn 2.0.101", + "which", ] [[package]] -name = "matchers" -version = "0.1.0" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "memchr" -version = "2.7.4" +name = "bitflags" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] [[package]] -name = "mio" -version = "1.0.3" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", + "generic-array", ] [[package]] -name = "nix" -version = "0.29.0" +name = "blowfish" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", + "byteorder", + "cipher", ] [[package]] -name = "nom" -version = "7.1.3" +name = "brotli" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "cf19e729cdbd51af9a397fb9ef8ac8378007b797f8273cfbfdf45dcaa316167b" dependencies = [ - "memchr", - "minimal-lexical", + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ - "overload", - "winapi", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "bstr" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] [[package]] -name = "once_cell" -version = "1.21.3" +name = "bumpalo" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] -name = "overload" -version = "0.1.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "parking_lot" -version = "0.12.3" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ - "lock_api", - "parking_lot_core", + "jobserver", + "libc", + "shlex", ] [[package]] -name = "parking_lot_core" -version = "0.9.10" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", + "nom", ] [[package]] -name = "pem" -version = "3.0.5" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ - "base64", - "serde", + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] [[package]] -name = "proc-macro2" -version = "1.0.95" +name = "clap" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "unicode-ident", + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] -name = "quote" -version = "1.0.40" +name = "clap" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ - "proc-macro2", + "clap_builder", + "clap_derive", ] [[package]] -name = "rb-sys" -version = "0.9.111" +name = "clap_builder" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "becea799ce051c16fb140be80f5e7cf781070f99ca099332383c2b17861249af" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ - "rb-sys-build", + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", ] [[package]] -name = "rb-sys-build" -version = "0.9.111" +name = "clap_derive" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64691175abc704862f60a9ca8ef06174080cc50615f2bf1d4759f46db18b4d29" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ - "bindgen", - "lazy_static", + "heck 0.5.0", "proc-macro2", "quote", - "regex", - "shell-words", "syn 2.0.101", ] [[package]] -name = "rb-sys-env" -version = "0.1.2" +name = "clap_lex" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] -name = "rcgen" -version = "0.13.2" +name = "cmake" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", + "cc", ] [[package]] -name = "redox_syscall" -version = "0.5.11" +name = "colorchoice" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "bitflags", + "lazy_static", + "windows-sys 0.59.0", ] [[package]] -name = "regex" -version = "1.11.1" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "aho-corasick", + "bytes", + "futures-core", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "pin-project-lite", + "tokio", + "tokio-util", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "regex-syntax 0.6.29", + "crossbeam-utils", ] [[package]] -name = "regex-automata" -version = "0.4.9" +name = "core-foundation" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", + "core-foundation-sys", + "libc", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "regex-syntax" -version = "0.8.5" +name = "core_affinity" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "a034b3a7b624016c6e13f5df875747cc25f884156aad2abd12b6c46797971342" +dependencies = [ + "libc", + "num_cpus", + "winapi", +] [[package]] -name = "ring" -version = "0.17.14" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "cc", - "cfg-if", - "getrandom", "libc", - "untrusted", - "windows-sys", ] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "crc32fast" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] [[package]] -name = "rustls-pki-types" -version = "1.11.0" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "ryu" -version = "1.0.20" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "seq-macro" -version = "0.3.6" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "serde" -version = "1.0.219" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "serde_derive", + "generic-array", + "typenum", ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "data-encoding" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", ] [[package]] -name = "serde_json" -version = "1.0.140" +name = "deranged" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "powerfmt", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "derive_more" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "lazy_static", + "derive_more-impl", ] [[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "syn" -version = "1.0.109" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.101", + "unicode-xid", ] [[package]] -name = "syn" -version = "2.0.101" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "block-buffer", + "crypto-common", + "subtle", ] [[package]] -name = "thiserror" -version = "1.0.69" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "thiserror-impl 1.0.69", + "dirs-sys", ] [[package]] -name = "thiserror" -version = "2.0.12" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ - "thiserror-impl 2.0.12", + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", ] [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", @@ -737,249 +891,3364 @@ dependencies = [ ] [[package]] -name = "thiserror-impl" -version = "2.0.12" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "thread_local" -version = "1.1.8" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", - "once_cell", ] [[package]] -name = "time" -version = "0.3.41" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "time-core" -version = "0.1.4" +name = "errno" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] [[package]] -name = "time-macros" -version = "0.2.22" +name = "event-listener" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ - "num-conv", - "time-core", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "tracing" -version = "0.1.41" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ + "event-listener", "pin-project-lite", - "tracing-attributes", - "tracing-core", ] [[package]] -name = "tracing-appender" -version = "0.2.3" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ - "crossbeam-channel", - "thiserror 1.0.69", - "time", - "tracing-subscriber", + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", ] [[package]] -name = "tracing-attributes" -version = "0.1.28" +name = "flate2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "crc32fast", + "miniz_oxide", ] [[package]] -name = "tracing-core" -version = "0.1.33" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "once_cell", - "valuable", + "percent-encoding", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "fs-err" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa" dependencies = [ - "log", - "once_cell", - "tracing-core", + "autocfg", + "tokio", ] [[package]] -name = "tracing-serde" -version = "0.2.0" +name = "fs2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ - "serde", - "tracing-core", + "libc", + "winapi", ] [[package]] -name = "tracing-subscriber" -version = "0.3.19" +name = "fs_extra" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", + "libc", ] [[package]] -name = "unicode-ident" -version = "1.0.18" +name = "futures" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] [[package]] -name = "untrusted" -version = "0.9.0" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "valuable" -version = "0.1.1" +name = "futures-executor" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] -name = "winapi" -version = "0.3.9" +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "futures-sink" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "futures-task" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "futures-util" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "windows-targets", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "typenum", + "version_check", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "getrandom" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" +name = "getrandom" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] [[package]] -name = "windows_i686_gnu" -version = "0.52.6" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "glob" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "globset" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.9", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "itsi-scheduler" +version = "0.2.17" +dependencies = [ + "bytes", + "derive_more", + "itsi_error", + "itsi_instrument_entry", + "itsi_rb_helpers", + "itsi_tracing", + "magnus", + "mio", + "nix", + "parking_lot", + "rb-sys", + "tracing", +] + +[[package]] +name = "itsi-server" +version = "0.2.17" +dependencies = [ + "argon2", + "async-channel", + "async-compression", + "async-stream", + "async-trait", + "base64 0.22.1", + "bcrypt", + "bytes", + "chrono", + "core_affinity", + "derive_more", + "dirs", + "either", + "fs2", + "futures", + "futures-util", + "globset", + "http 1.3.1", + "http-body-util", + "httparse", + "httpdate", + "hyper 1.6.0", + "hyper-util", + "itsi_acme", + "itsi_error", + "itsi_rb_helpers", + "itsi_tracing", + "jsonwebtoken", + "magnus", + "md5", + "memchr", + "nix", + "notify", + "num_cpus", + "parking_lot", + "percent-encoding", + "phf", + "pin-project", + "quick_cache", + "rand 0.9.1", + "rcgen", + "redis", + "regex", + "reqwest", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_magnus", + "sha-crypt", + "sha2", + "smallvec", + "socket2", + "sysinfo", + "tempfile", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "itsi_acme" +version = "0.1.0" +dependencies = [ + "async-trait", + "axum", + "axum-server", + "base64 0.22.1", + "chrono", + "clap 4.5.37", + "futures", + "log", + "num-bigint", + "pem", + "proc-macro2", + "rcgen", + "reqwest", + "ring", + "rustls", + "serde", + "serde_json", + "simple_logger", + "structopt", + "thiserror 2.0.12", + "time", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-util", + "warp", + "webpki-roots", + "x509-parser", +] + +[[package]] +name = "itsi_error" +version = "0.1.0" +dependencies = [ + "anyhow", + "httparse", + "magnus", + "nix", + "rcgen", + "thiserror 2.0.12", +] + +[[package]] +name = "itsi_instrument_entry" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "itsi_rb_helpers" +version = "0.1.0" +dependencies = [ + "cfg-if", + "magnus", + "nix", + "rb-sys", + "serde", +] + +[[package]] +name = "itsi_tracing" +version = "0.1.0" +dependencies = [ + "atty", + "tracing", + "tracing-appender", + "tracing-attributes", + "tracing-subscriber", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "magnus" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d87ae53030f3a22e83879e666cb94e58a7bdf31706878a0ba48752994146dab" +dependencies = [ + "bytes", + "magnus-macros", + "rb-sys", + "rb-sys-env", + "seq-macro", +] + +[[package]] +name = "magnus-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +dependencies = [ + "bitflags 2.9.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.59.0", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.25", +] + +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn 2.0.101", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick_cache" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287e56aac5a2b4fb25a6fb050961d157635924c8696305a5c937a76f29841a0f" +dependencies = [ + "ahash", + "equivalent", + "hashbrown", + "parking_lot", +] + +[[package]] +name = "quinn" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "rand 0.9.1", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rb-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "becea799ce051c16fb140be80f5e7cf781070f99ca099332383c2b17861249af" +dependencies = [ + "rb-sys-build", +] + +[[package]] +name = "rb-sys-build" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64691175abc704862f60a9ca8ef06174080cc50615f2bf1d4759f46db18b4d29" +dependencies = [ + "bindgen", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "shell-words", + "syn 2.0.101", +] + +[[package]] +name = "rb-sys-env" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb" + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redis" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc42f3a12fd4408ce64d8efef67048a924e543bd35c6591c0447fda9054695f" +dependencies = [ + "arc-swap", + "backon", + "bytes", + "combine", + "futures-channel", + "futures-util", + "itoa", + "num-bigint", + "percent-encoding", + "pin-project-lite", + "r2d2", + "rustls", + "rustls-native-certs", + "ryu", + "sha1_smol", + "socket2", + "tokio", + "tokio-rustls", + "tokio-util", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_magnus" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b8b945a2dadb221f1c5490cfb411cab6c3821446b8eca50ee07e5a3893ec51" +dependencies = [ + "magnus", + "serde", + "tap", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-crypt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e79009728d8311d42d754f2f319a975f9e38f156fd5e422d2451486c78b286" +dependencies = [ + "base64ct", + "rand 0.8.5", + "sha2", + "subtle", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "simple_logger" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.48.0", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper 0.14.32", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.2", + "windows-strings 0.3.1", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -987,18 +4256,87 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "yasna" version = "0.5.2" @@ -1007,3 +4345,144 @@ checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ "time", ] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive 0.8.25", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/gems/server/Cargo.lock b/gems/server/Cargo.lock index 36f0b350..3f39eef0 100644 --- a/gems/server/Cargo.lock +++ b/gems/server/Cargo.lock @@ -1664,6 +1664,24 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "itsi-scheduler" +version = "0.2.17" +dependencies = [ + "bytes", + "derive_more", + "itsi_error", + "itsi_instrument_entry", + "itsi_rb_helpers", + "itsi_tracing", + "magnus", + "mio", + "nix", + "parking_lot", + "rb-sys", + "tracing", +] + [[package]] name = "itsi-server" version = "0.2.17" @@ -1777,6 +1795,15 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "itsi_instrument_entry" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "itsi_rb_helpers" version = "0.1.0" diff --git a/gems/server/lib/itsi/server/config.rb b/gems/server/lib/itsi/server/config.rb index 12580784..95a87460 100644 --- a/gems/server/lib/itsi/server/config.rb +++ b/gems/server/lib/itsi/server/config.rb @@ -44,9 +44,9 @@ def self.build_config(args, config_file_path, builder_proc = nil) rate_limit key: "address", store_config: "in_memory", requests: 5, seconds: 10 etag type: "strong", algorithm: "md5", min_body_size: 1024 * 1024 compress min_size: 1024 * 1024, level: "fastest", algorithms: %w[zstd gzip br deflate], - mime_types: %w[all], compress_streams: true + mime_types: %w[all], compress_streams: true log_requests before: { level: "DEBUG", format: "[{request_id}] {method} {path_and_query} - {addr} " }, - after: { level: "DEBUG", + after: { level: "DEBUG", format: "[{request_id}] └─ {status} in {response_time}" } nodelay false static_assets \ @@ -111,7 +111,7 @@ def self.build_config(args, config_file_path, builder_proc = nil) end srv_config = { - workers: args.fetch(:workers) { itsifile_config.fetch(:workers, 1) }, + workers: args.fetch(:workers) { itsifile_config.fetch(:workers, nil) }, worker_memory_limit: args.fetch(:worker_memory_limit) { itsifile_config.fetch(:worker_memory_limit, nil) }, silence: args.fetch(:silence) { itsifile_config.fetch(:silence, false) }, shutdown_timeout: args.fetch(:shutdown_timeout) { itsifile_config.fetch(:shutdown_timeout, 5) }, @@ -132,7 +132,7 @@ def self.build_config(args, config_file_path, builder_proc = nil) multithreaded_reactor: args.fetch(:multithreaded_reactor) do itsifile_config.fetch(:multithreaded_reactor, nil) end, - pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores, true) }, + pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores, false) }, scheduler_class: args.fetch(:scheduler_class) { itsifile_config.fetch(:scheduler_class, nil) }, oob_gc_responses_threshold: args.fetch(:oob_gc_responses_threshold) do itsifile_config.fetch(:oob_gc_responses_threshold, nil) @@ -144,9 +144,15 @@ def self.build_config(args, config_file_path, builder_proc = nil) log_format: args.fetch(:log_format) { itsifile_config.fetch(:log_format, nil) }, log_target: args.fetch(:log_target) { itsifile_config.fetch(:log_target, nil) }, log_target_filters: args.fetch(:log_target_filters) { itsifile_config.fetch(:log_target_filters, nil) }, + pipeline_flush: itsifile_config.fetch(:pipeline_flush, true), + writev: itsifile_config.fetch(:writev, false), + max_concurrent_streams: itsifile_config.fetch(:max_concurrent_streams, nil), + max_local_error_reset_streams: itsifile_config.fetch(:max_local_error_reset_streams, nil), + max_header_list_size: itsifile_config.fetch(:max_header_list_size, 2 * 1024 * 1024), + max_send_buf_size: itsifile_config.fetch(:max_send_buf_size, 400 * 1024), binds: args.fetch(:binds) { itsifile_config.fetch(:binds, ["http://0.0.0.0:3000"]) }, middleware_loader: middleware_loader, - listeners: args.fetch(:listeners) { nil }, + listeners: args.fetch(:listeners, nil), reuse_address: itsifile_config.fetch(:reuse_address, true), reuse_port: itsifile_config.fetch(:reuse_port, true), listen_backlog: itsifile_config.fetch(:listen_backlog, 1024), @@ -227,10 +233,7 @@ def self.reload_exec(listener_info) # Find config file path, if it exists. def self.config_file_path(config_file_path = nil) - - if config_file_path && !File.exist?(config_file_path) - raise "Config file #{config_file_path} does not exist" - end + raise "Config file #{config_file_path} does not exist" if config_file_path && !File.exist?(config_file_path) config_file_path ||= \ if File.exist?(ITSI_DEFAULT_CONFIG_FILE) diff --git a/gems/server/lib/itsi/server/config/dsl.rb b/gems/server/lib/itsi/server/config/dsl.rb index e9abb48f..a0780133 100644 --- a/gems/server/lib/itsi/server/config/dsl.rb +++ b/gems/server/lib/itsi/server/config/dsl.rb @@ -55,8 +55,11 @@ def initialize( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/P nested_locations: [], middleware_loader: lambda do @options[:nested_locations].each(&:call) - @middleware[:app] ||= {} - @middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call || DEFAULT_APP[] + if !(@middleware[:app] || @middleware[:static_assets]) + @middleware[:app] = { app_proc: DEFAULT_APP[]} + elsif @middleware[:app] + @middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call + end [flatten_routes, Config.errors_to_error_lines(errors)] end } @@ -74,7 +77,7 @@ def errors define_method(option_name) do |*args, **kwargs, &blk| option.new(self, *args, **kwargs, &blk).build! rescue Exception => e # rubocop:disable Lint/RescueException - @errors << [e, caller[1]] + @errors << [e, e.backtrace.find{|r| !(r =~ /server\/config/) }] end end @@ -85,7 +88,7 @@ def errors rescue Config::Endpoint::InvalidHandlerException => e @errors << [e, "#{e.backtrace[0]}:in #{e.message}"] rescue Exception => e # rubocop:disable Lint/RescueException - @errors << [e, caller[1]] + @errors << [e, e.backtrace.find{|r| !(r =~ /server\/config/) }] end end diff --git a/gems/server/lib/itsi/server/config/options/include.rb b/gems/server/lib/itsi/server/config/options/include.rb index d6a5925b..452aa2d9 100644 --- a/gems/server/lib/itsi/server/config/options/include.rb +++ b/gems/server/lib/itsi/server/config/options/include.rb @@ -26,8 +26,11 @@ def build! end end - code = IO.read("#{included_file}.rb") - location.instance_eval(code, "#{included_file}.rb", 1) + filename = File.expand_path("#{included_file}.rb") + + code = IO.read(filename) + location.instance_eval(code, filename, 1) + end end diff --git a/gems/server/lib/itsi/server/config/options/pipeline_flush.md b/gems/server/lib/itsi/server/config/options/pipeline_flush.md new file mode 100644 index 00000000..cae7916f --- /dev/null +++ b/gems/server/lib/itsi/server/config/options/pipeline_flush.md @@ -0,0 +1,16 @@ +--- +title: Pipeline Flush +url: /options/pipeline_flush +--- + +Aggregates flushes to better support pipelined responses. (HTTP1 only) +The default value is `false`. + +## Configuration +```ruby {filename=Itsi.rb} +pipeline_flush true +``` + +```ruby {filename=Itsi.rb} +pipeline_flush false +``` diff --git a/gems/server/lib/itsi/server/config/options/pipeline_flush.rb b/gems/server/lib/itsi/server/config/options/pipeline_flush.rb new file mode 100644 index 00000000..d01c0828 --- /dev/null +++ b/gems/server/lib/itsi/server/config/options/pipeline_flush.rb @@ -0,0 +1,19 @@ +module Itsi + class Server + module Config + class PipelineFlush < Option + + insert_text <<~SNIPPET + pipeline_flush ${1|true,false|} + SNIPPET + + detail "Aggregates flushes to better support pipelined responses. (HTTP1 only)." + + schema do + (Bool() & Required()).default(false) + end + + end + end + end +end diff --git a/gems/server/lib/itsi/server/config/options/writev.md b/gems/server/lib/itsi/server/config/options/writev.md new file mode 100644 index 00000000..1ebc57b1 --- /dev/null +++ b/gems/server/lib/itsi/server/config/options/writev.md @@ -0,0 +1,25 @@ +--- +title: Write Vectored +url: /options/writev +--- + +Set whether HTTP/1 connections should try to use vectored writes, +or always flatten into a single buffer. + +Note that setting this to false may mean more copies of body data, +but may also improve performance when an IO transport doesn't +support vectored writes well, such as most TLS implementations. + +Setting this to true will force hyper to use queued strategy +which may eliminate unnecessary cloning on some TLS backends + +Default is `nil` in which case hyper will try to guess which mode to use + +## Configuration +```ruby {filename=Itsi.rb} +writev true +``` + +```ruby {filename=Itsi.rb} +writev false +``` diff --git a/gems/server/lib/itsi/server/config/options/writev.rb b/gems/server/lib/itsi/server/config/options/writev.rb new file mode 100644 index 00000000..55178005 --- /dev/null +++ b/gems/server/lib/itsi/server/config/options/writev.rb @@ -0,0 +1,19 @@ +module Itsi + class Server + module Config + class Writev < Option + + insert_text <<~SNIPPET + writev ${1|true,false|} + SNIPPET + + detail "Set whether HTTP/1 connections should try to use vectored writes" + + schema do + (Bool() & Required()).default(false) + end + + end + end + end +end diff --git a/gems/server/lib/itsi/server/scheduler_interface.rb b/gems/server/lib/itsi/server/scheduler_interface.rb index b9cac629..c340bbfc 100644 --- a/gems/server/lib/itsi/server/scheduler_interface.rb +++ b/gems/server/lib/itsi/server/scheduler_interface.rb @@ -14,6 +14,8 @@ def start_scheduler_loop(scheduler_class, scheduler_task) def schedule(app, request) Fiber.schedule do app.call(request) + rescue StandardError => e + request.server_error(e.message) end end end diff --git a/gems/server/lib/ruby_lsp/itsi/addon.rb b/gems/server/lib/ruby_lsp/itsi/addon.rb index d29d31f6..eeda5221 100644 --- a/gems/server/lib/ruby_lsp/itsi/addon.rb +++ b/gems/server/lib/ruby_lsp/itsi/addon.rb @@ -5,8 +5,8 @@ module RubyLsp module Itsi - class Addon < ::RubyLsp::Addon - def activate(global_state, message_queue) + class Addon < ::RubyLsp::Addon # rubocop:disable Style/Documentation + def activate(_global_state, message_queue) @message_queue = message_queue end @@ -20,9 +20,9 @@ def version "0.1.0" end - def create_completion_listener(response_builder, node_context, dispatcher, uri) return unless uri.to_s =~ /itsi.rb$/i + @in_itsi_file = true CompletionListener.new(response_builder, node_context, dispatcher, uri) end @@ -30,23 +30,24 @@ def create_completion_listener(response_builder, node_context, dispatcher, uri) def create_hover_listener(response_builder, node_context, dispatcher) hl = dispatcher.listeners[:on_call_node_enter].find { |c| c.is_a?(RubyLsp::Listeners::Hover) } return unless hl.instance_variable_get("@path").to_s =~ /itsi.rb$/i + HoverListener.new(response_builder, node_context, dispatcher) end end - class HoverListener + class HoverListener # rubocop:disable Style/Documentation def initialize(response_builder, node_context, dispatcher) @response_builder = response_builder @node_context = node_context @dispatcher = dispatcher - @options_by_name = ::Itsi::Server::Config::Option.subclasses.group_by(&:option_name).map{|k,v| [k,v.first]}.to_h - @middlewares_by_name = ::Itsi::Server::Config::Middleware.subclasses.group_by(&:middleware_name).map{|k,v| [k,v.first]}.to_h + @options_by_name = ::Itsi::Server::Config::Option.subclasses.group_by(&:option_name).transform_values(&:first) + @middlewares_by_name = ::Itsi::Server::Config::Middleware.subclasses.group_by(&:middleware_name).transform_values(&:first) # Register for call nodes for hover events dispatcher.register(self, :on_call_node_enter) end - def on_call_node_enter(node) + def on_call_node_enter(node) # rubocop:disable Metrics/MethodLength if (matched_class = @options_by_name[node.message.to_sym]) @response_builder.push( matched_class.documentation, @@ -61,7 +62,7 @@ def on_call_node_enter(node) end end - class CompletionListener + class CompletionListener # rubocop:disable Style/Documentation def initialize(response_builder, node_context, dispatcher, uri) @response_builder = response_builder @node_context = node_context @@ -74,7 +75,7 @@ def initialize(response_builder, node_context, dispatcher, uri) dispatcher.register(self, :completion_item_resolve) end - def on_call_node_enter(node) + def on_call_node_enter(node) # rubocop:disable Metrics/AbcSize # Only handle method calls that are being typed (i.e. no arguments yet) return unless node.arguments.nil? @@ -122,8 +123,6 @@ def on_call_node_enter(node) end end end - - end end end diff --git a/gems/server/test/helpers/test_helper.rb b/gems/server/test/helpers/test_helper.rb index f057c258..4948534f 100644 --- a/gems/server/test/helpers/test_helper.rb +++ b/gems/server/test/helpers/test_helper.rb @@ -25,17 +25,10 @@ def free_bind(protocol = "http", unix_socket: false) end end -def server(app: nil, app_with_lint: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, cleanup: true, - timeout: 5, &blk) +def server( + app: nil, app_with_lint: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, cleanup: true, + &blk) app ||= Rack::Lint.new(app_with_lint) if app_with_lint - itsi_rb ||= lambda do - # Inline Itsi.rb - bind bind - workers 1 - threads 1 - log_level :warn - run app if app - end cli_params = {} cli_params[:binds] = [bind] if bind @@ -46,13 +39,18 @@ def server(app: nil, app_with_lint: nil, protocol: "http", bind: free_bind(proto sync.push(true) end - Itsi::Server.start_in_background_thread(cli_params, &itsi_rb) + Itsi::Server.start_in_background_thread(cli_params) do + bind bind + workers 1 + threads 1 + log_level :warn + run app if app + instance_exec(&itsi_rb) if itsi_rb + end sync.pop uri = URI(bind) - # Timeout.timeout(timeout) do RequestContext.new(uri, self).instance_exec(uri, &blk) - # end rescue StandardError => e puts e # puts e.message @@ -119,6 +117,7 @@ def delete(path, headers = {}) def patch(path, data = "", headers = {}) request = Net::HTTP::Patch.new(uri_for(path)) request.body = data + headers.each { |k, v| request[k] = v } client.request(request) end diff --git a/gems/server/test/middleware/proxy.rb b/gems/server/test/middleware/proxy.rb index b82670ca..b3871433 100644 --- a/gems/server/test/middleware/proxy.rb +++ b/gems/server/test/middleware/proxy.rb @@ -1,16 +1,14 @@ require_relative "../helpers/test_helper" class TestProxy < Minitest::Test - def test_successful_forwarding - backend_bind = free_bind server( itsi_rb: lambda do - log_requests before: { format: "GET {path_and_query}", level: "INFO"} - get("/foo") { |r| + log_requests before: { format: "GET {path_and_query}", level: "INFO" } + get("/foo") do |r| r.ok "backend success. #{r.query_params["bar"]}" - } + end end, bind: backend_bind ) do @@ -25,9 +23,9 @@ def test_successful_forwarding timeout: 30, tls_sni: false, error_response: "internal_server_error" - get("/foo") { |r| + get("/foo") do |r| r.ok "should not get here" - } + end end ) do res = get_resp("/foo?bar=baz") @@ -60,11 +58,10 @@ def test_invalid_target_url_returns_error end def test_overriding_headers - backend_bind = free_bind server( itsi_rb: lambda do - log_requests before: { format: "GET {path_and_query}", level: "INFO"} + log_requests before: { format: "GET {path_and_query}", level: "INFO" } get("/header-test") do |r| # Return the incoming header value. r.ok r.header("X-Forwarded-For").first @@ -72,7 +69,6 @@ def test_overriding_headers end, bind: backend_bind ) do - # Start proxy server. server( itsi_rb: lambda do @@ -96,278 +92,279 @@ def test_overriding_headers end def test_proxy_with_static_to_only - backend_bind = free_bind - server( - itsi_rb: lambda do - get("/static") { |r| r.ok "static response" } - end, - bind: backend_bind - ) do - server( - itsi_rb: lambda do - proxy \ - to: "#{backend_bind}{path}{query}", - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "internal_server_error" - get("/static") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/static") - assert_equal "200", res.code, "Expected success status from static 'to' URL" - assert_equal "static response", res.body - end - end - end + backend_bind = free_bind + server( + itsi_rb: lambda do + get("/static") { |r| r.ok "static response" } + end, + bind: backend_bind + ) do + server( + itsi_rb: lambda do + proxy \ + to: "#{backend_bind}{path}{query}", + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "internal_server_error" + get("/static") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/static") + assert_equal "200", res.code, "Expected success status from static 'to' URL" + assert_equal "static response", res.body + end + end + end - def test_proxy_with_backend_host_override - backend_bind1 = free_bind - backend_bind2 = free_bind - # Start two dummy backends that respond with their Host header. - thread1 = Thread.new do - server( - itsi_rb: lambda do - get("/host-test") { |r| r.ok r.header("Host").first } - end, - bind: backend_bind1 - ){ sleep 1 } - end - thread2 = Thread.new do - server( - itsi_rb: lambda do - get("/host-test") { |r| r.ok r.header("Host").first } - end, - bind: backend_bind2 - ){ sleep 1 } - end + def test_proxy_with_backend_host_override + backend_bind1 = free_bind + backend_bind2 = free_bind + # Start two dummy backends that respond with their Host header. + thread1 = Thread.new do + server( + itsi_rb: lambda do + get("/host-test") { |r| r.ok r.header("Host").first } + end, + bind: backend_bind1 + ) { sleep 1 } + end + thread2 = Thread.new do + server( + itsi_rb: lambda do + get("/host-test") { |r| r.ok r.header("Host").first } + end, + bind: backend_bind2 + ) { sleep 1 } + end - sleep 0.1 + sleep 0.1 - server( - itsi_rb: lambda do - proxy \ - to: "#{backend_bind1}{path}{query}", - backends: ["#{backend_bind1}", "#{backend_bind2}"], - backend_priority: "round_robin", - headers: { "Host" => "custom.backend.example.com" }, - verify_ssl: false, - timeout: 30, - tls_sni: true, - error_response: "internal_server_error" - get("/host-test") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/host-test") - assert_equal "200", res.code, "Expected successful response with host override" - assert_equal "custom.backend.example.com", res.body, "Expected the Host header to be overridden" - end + server( + itsi_rb: lambda do + proxy \ + to: "#{backend_bind1}{path}{query}", + backends: ["#{backend_bind1}", "#{backend_bind2}"], + backend_priority: "round_robin", + headers: { "Host" => "custom.backend.example.com" }, + verify_ssl: false, + timeout: 30, + tls_sni: true, + error_response: "internal_server_error" + get("/host-test") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/host-test") + assert_equal "200", res.code, "Expected successful response with host override" + assert_equal "custom.backend.example.com", res.body, "Expected the Host header to be overridden" + end - thread1.kill - thread2.kill - end + thread1.kill + thread2.kill + end - def test_proxy_timeout - backend_bind = free_bind - # Start a backend server that sleeps for 1 second before responding. - server( - itsi_rb: lambda do - get("/slow") do |r| - sleep 1 - r.ok "delayed response" - end - end, - bind: backend_bind - ) do - server( - itsi_rb: lambda do - # Set a very short timeout (0 seconds) to force a timeout. - proxy \ - to: "#{backend_bind}{path}{query}", - backends: ["#{backend_bind}"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 0, - tls_sni: false, - error_response: "gateway_timeout" - get("/slow") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/slow") - assert_equal "504", res.code, "Expected 504 Gateway Timeout when backend response exceeds timeout" - end - end - end + def test_proxy_timeout + backend_bind = free_bind + # Start a backend server that sleeps for 1 second before responding. + server( + itsi_rb: lambda do + get("/slow") do |r| + sleep 1 + r.ok "delayed response" + end + end, + bind: backend_bind + ) do + server( + itsi_rb: lambda do + # Set a very short timeout (0 seconds) to force a timeout. + proxy \ + to: "#{backend_bind}{path}{query}", + backends: ["#{backend_bind}"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 0, + tls_sni: false, + error_response: "gateway_timeout" + get("/slow") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/slow") + assert_equal "504", res.code, "Expected 504 Gateway Timeout when backend response exceeds timeout" + end + end + end - def test_error_response_internal_server_error - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "internal_server_error" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "500", res.code, "Expected error response code 500 for internal_server_error" - end - end + def test_error_response_internal_server_error + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "internal_server_error" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "500", res.code, "Expected error response code 500 for internal_server_error" + end + end - def test_error_response_not_found - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "not_found" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "404", res.code, "Expected error response code 404 for not_found" - end - end + def test_error_response_not_found + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "not_found" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "404", res.code, "Expected error response code 404 for not_found" + end + end - def test_error_response_unauthorized - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "unauthorized" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "401", res.code, "Expected error response code 401 for unauthorized" - end - end + def test_error_response_unauthorized + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "unauthorized" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "401", res.code, "Expected error response code 401 for unauthorized" + end + end - def test_error_response_forbidden - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "forbidden" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "403", res.code, "Expected error response code 403 for forbidden" - end - end + def test_error_response_forbidden + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "forbidden" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "403", res.code, "Expected error response code 403 for forbidden" + end + end - def test_error_response_payload_too_large - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "payload_too_large" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "413", res.code, "Expected error response code 413 for payload_too_large" - end - end + def test_error_response_payload_too_large + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "payload_too_large" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "413", res.code, "Expected error response code 413 for payload_too_large" + end + end - def test_error_response_too_many_requests - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "too_many_requests" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "429", res.code, "Expected error response code 429 for too_many_requests" - end - end + def test_error_response_too_many_requests + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "too_many_requests" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "429", res.code, "Expected error response code 429 for too_many_requests" + end + end - def test_error_response_service_unavailable - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "service_unavailable" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "503", res.code, "Expected error response code 503 for service_unavailable" - end - end + def test_error_response_service_unavailable + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "service_unavailable" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "503", res.code, "Expected error response code 503 for service_unavailable" + end + end - def test_error_response_gateway_timeout - server( - itsi_rb: lambda do - proxy \ - to: "invalid_url", - backends: ["127.0.0.1:3001"], - backend_priority: "round_robin", - headers: {}, - verify_ssl: false, - timeout: 30, - tls_sni: false, - error_response: "gateway_timeout" - get("/foo") { |r| r.ok "should not get here" } - end - ) do - res = get_resp("/foo") - assert_equal "504", res.code, "Expected error response code 504 for gateway_timeout" + def test_error_response_gateway_timeout + server( + itsi_rb: lambda do + proxy \ + to: "invalid_url", + backends: ["127.0.0.1:3001"], + backend_priority: "round_robin", + headers: {}, + verify_ssl: false, + timeout: 30, + tls_sni: false, + error_response: "gateway_timeout" + get("/foo") { |r| r.ok "should not get here" } + end + ) do + res = get_resp("/foo") + assert_equal "504", res.code, "Expected error response code 504 for gateway_timeout" end end - def test_failover_behavior + def test_failover_behavior # rubocop:disable Metrics/AbcSize,Metrics/MethodLength # Obtain two free bind addresses for backend servers. backend1_bind = free_bind backend2_bind = free_bind - backend2_server = Itsi::Server.start_in_background_thread(binds: [backend2_bind]) do + Itsi::Server.start_in_background_thread(binds: [backend2_bind]) do + workers 1 get("/failover") { |r| r.ok "backend2" } end @@ -376,14 +373,13 @@ def test_failover_behavior # # Allow the backend servers to start. sleep 0.2 - # Start the proxy server with an ordered backend selection. server( cleanup: false, itsi_rb: lambda do proxy \ to: "http://proxied_host.com{path}{query}", - backends: [backend1_bind[/\/\/(.*)/,1], backend2_bind[/\/\/(.*)/,1]], + backends: [backend1_bind[%r{//(.*)}, 1], backend2_bind[%r{//(.*)}, 1]], backend_priority: "ordered", headers: {}, verify_ssl: false, @@ -393,18 +389,16 @@ def test_failover_behavior get("/failover") { |r| r.ok "should not get here" } end ) do - - res = get_resp("/failover") assert_equal "200", res.code, "Expected success when fallback backend is available" assert_equal "backend2", res.body, "Expected response from backend2" - backend1_server = Itsi::Server.start_in_background_thread(binds: [backend1_bind]) do + Itsi::Server.start_in_background_thread(binds: [backend1_bind]) do + workers 1 get("/failover") { |r| r.ok "backend1" } end sleep 1 - res = get_resp("/failover") assert_equal "200", res.code, "Expected reversion when primary becomes available" assert_equal "backend1", res.body, "Expected response from backend1 with ordered priority" From 7e1e6cd85b64745713b4a4cdc6b104c959965b6b Mon Sep 17 00:00:00 2001 From: Wouter Coppieters Date: Mon, 26 May 2025 18:00:12 +1200 Subject: [PATCH 6/6] Finishing benchmarks --- .gitmodules | 3 + CHANGELOG.md | 6 +- Cargo.lock | 23 - benchmarks | 1 + benchmarks/Gemfile | 15 - benchmarks/Gemfile.lock | 216 - benchmarks/README.md | 19 - benchmarks/apps/big_delay.ru | 8 - benchmarks/apps/chunked.ru | 16 - benchmarks/apps/echo_service/echo.proto | 57 - benchmarks/apps/echo_service/echo_service.rb | 128 - .../echo_service/google/protobuf/any.proto | 162 - .../google/protobuf/timestamp.proto | 144 - .../apps/echo_service/google/type/money.proto | 42 - benchmarks/apps/empty.ru | 8 - benchmarks/apps/full_hijack.ru | 18 - benchmarks/apps/hello_world.ru | 7 - benchmarks/apps/io_party.ru | 54 - benchmarks/apps/large.ru | 9 - benchmarks/apps/many_small_delay.ru | 10 - benchmarks/apps/medium.ru | 9 - benchmarks/apps/public/image.png | Bin 9230 -> 0 bytes benchmarks/apps/public/index.html | 9 - benchmarks/apps/sinatra.ru | 13 - benchmarks/apps/small.ru | 9 - benchmarks/apps/static.ru | 12 - benchmarks/apps/streaming_response_large.ru | 17 - benchmarks/apps/streaming_response_small.ru | 17 - benchmarks/flamegraph.svg | 491 -- benchmarks/grpc_server.rb | 12 - benchmarks/lib/benchmark_case.rb | 59 - benchmarks/lib/server.rb | 94 - benchmarks/lib/util.rb | 45 - benchmarks/rack_bench.rb | 296 - benchmarks/server_configurations/h2o.conf | 16 - benchmarks/server_configurations/itsi.rb | 4 - benchmarks/server_configurations/nginx.conf | 26 - benchmarks/servers.rb | 71 - .../test_cases/framework/sinatra_get.rb | 3 - .../test_cases/framework/sinatra_post.rb | 5 - benchmarks/test_cases/grpc/echo.rb | 13 - .../test_cases/grpc/echo_bidirectional.rb | 13 - benchmarks/test_cases/grpc/echo_collect.rb | 13 - benchmarks/test_cases/grpc/echo_stream.rb | 13 - benchmarks/test_cases/grpc/process_payment.rb | 13 - .../grpc/server_configurations/itsi.rb | 6 - .../nonblocking/nonblocking_big_delay.rb | 5 - .../nonblocking_many_small_delay.rb | 5 - .../response_size/empty_response.rb | 2 - .../response_size/response_size_large.rb | 2 - .../response_size/response_size_medium.rb | 2 - .../response_size/response_size_small.rb | 2 - .../static_file/server_configurations/itsi.rb | 8 - .../static_file/static_dynamic_mixed.rb | 9 - .../test_cases/static_file/static_large.rb | 7 - .../test_cases/static_file/static_small.rb | 7 - .../test_cases/streaming_response/chunked.rb | 5 - .../streaming_response/full_hijack.rb | 3 - .../streaming_response_large.rb | 4 - .../streaming_response_small.rb | 4 - .../streaming_response_small_http2.rb | 4 - .../test_cases/throughput/hello_world.rb | 1 - crates/itsi_server/Cargo.toml | 1 - crates/itsi_server/src/lib.rs | 1 - .../src/ruby_types/itsi_http_response.rs | 39 +- crates/itsi_server/src/server/frame_stream.rs | 142 + crates/itsi_server/src/server/io_stream.rs | 23 + crates/itsi_server/src/server/mod.rs | 1 + .../src/server/serve_strategy/cluster_mode.rs | 2 +- .../src/server/serve_strategy/single_mode.rs | 14 +- crates/itsi_server/src/server/signal.rs | 5 +- .../src/services/itsi_http_service.rs | 5 +- docs/benchmark-dashboard/.gitignore | 27 + .../app/api/benchmarks/route.ts | 22 + docs/benchmark-dashboard/app/globals.css | 94 + docs/benchmark-dashboard/app/layout.tsx | 20 + docs/benchmark-dashboard/app/page.tsx | 252 + docs/benchmark-dashboard/components.json | 21 + .../components/benchmark-dashboard.tsx | 1663 +++++ .../components/theme-provider.tsx | 11 + .../components/ui/accordion.tsx | 58 + .../components/ui/alert-dialog.tsx | 141 + .../components/ui/alert.tsx | 59 + .../components/ui/aspect-ratio.tsx | 7 + .../components/ui/avatar.tsx | 50 + .../components/ui/badge.tsx | 36 + .../components/ui/breadcrumb.tsx | 115 + .../components/ui/button.tsx | 56 + .../components/ui/calendar.tsx | 66 + .../components/ui/card.tsx | 79 + .../components/ui/carousel.tsx | 262 + .../components/ui/chart.tsx | 365 + .../components/ui/checkbox.tsx | 30 + .../components/ui/collapsible.tsx | 11 + .../components/ui/command.tsx | 153 + .../components/ui/context-menu.tsx | 200 + .../components/ui/dialog.tsx | 122 + .../components/ui/drawer.tsx | 118 + .../components/ui/dropdown-menu.tsx | 200 + .../components/ui/form.tsx | 178 + .../components/ui/hover-card.tsx | 29 + .../components/ui/input-otp.tsx | 71 + .../components/ui/input.tsx | 22 + .../components/ui/label.tsx | 26 + .../components/ui/loading-spinner.tsx | 12 + .../components/ui/menubar.tsx | 236 + .../components/ui/navigation-menu.tsx | 128 + .../components/ui/pagination.tsx | 117 + .../components/ui/popover.tsx | 31 + .../components/ui/progress.tsx | 28 + .../components/ui/radio-group.tsx | 44 + .../components/ui/resizable.tsx | 45 + .../components/ui/scroll-area.tsx | 48 + .../components/ui/select.tsx | 160 + .../components/ui/separator.tsx | 31 + .../components/ui/sheet.tsx | 140 + .../components/ui/sidebar.tsx | 763 +++ .../components/ui/skeleton.tsx | 15 + .../components/ui/slider.tsx | 28 + .../components/ui/sonner.tsx | 31 + .../components/ui/switch.tsx | 29 + .../components/ui/table.tsx | 117 + .../components/ui/tabs.tsx | 55 + .../components/ui/textarea.tsx | 22 + .../components/ui/toast.tsx | 129 + .../components/ui/toaster.tsx | 35 + .../components/ui/toggle-group.tsx | 61 + .../components/ui/toggle.tsx | 45 + .../components/ui/tooltip.tsx | 30 + .../components/ui/use-mobile.tsx | 19 + .../components/ui/use-toast.ts | 194 + .../dist/benchmark-dashboard.css | 1 + .../dist/benchmark-dashboard.iife.js | 211 + .../dist/placeholder-logo.png | Bin 0 -> 958 bytes .../dist/placeholder-logo.svg | 1 + .../dist/placeholder-user.jpg | Bin 0 -> 2615 bytes docs/benchmark-dashboard/dist/placeholder.jpg | Bin 0 -> 1596 bytes docs/benchmark-dashboard/dist/placeholder.svg | 1 + docs/benchmark-dashboard/embed.tsx | 13 + docs/benchmark-dashboard/hooks/use-mobile.tsx | 19 + docs/benchmark-dashboard/hooks/use-toast.ts | 194 + .../lib/benchmark-utils.ts | 54 + docs/benchmark-dashboard/lib/utils.ts | 6 + docs/benchmark-dashboard/next.config.mjs | 14 + docs/benchmark-dashboard/package-lock.json | 5859 +++++++++++++++++ docs/benchmark-dashboard/package.json | 72 + docs/benchmark-dashboard/pnpm-lock.yaml | 5 + docs/benchmark-dashboard/postcss.config.mjs | 8 + docs/benchmark-dashboard/styles/globals.css | 94 + docs/benchmark-dashboard/tailwind.config.ts | 96 + docs/benchmark-dashboard/tsconfig.json | 27 + docs/benchmark-dashboard/vite.config.ts | 24 + docs/build.rb | 52 + docs/content/benchmarks/index.md | 96 + docs/hugo.yaml | 3 + docs/static/results.json | 1 + .../scripts/benchmark-dashboard.iife.js | 211 + docs/static/styles/benchmark-dashboard.css | 1 + gems/scheduler/Cargo.lock | 23 - gems/server/Cargo.lock | 23 - gems/server/lib/itsi/http_response.rb | 4 + gems/server/lib/itsi/server/config.rb | 2 +- gems/server/lib/itsi/server/config/dsl.rb | 2 - .../itsi/server/config/middleware/proxy.rb | 2 +- .../server/config/middleware/rackup_file.rb | 4 +- gems/server/lib/itsi/server/grpc/grpc_call.rb | 2 + .../lib/itsi/server/grpc/grpc_interface.rb | 4 +- .../lib/itsi/server/rack/handler/itsi.rb | 3 +- gems/server/test/middleware/grpc/grpc.rb | 27 +- .../test/middleware/grpc/test_service_impl.rb | 6 +- tasks.txt | 23 - 171 files changed, 14450 insertions(+), 2423 deletions(-) create mode 100644 .gitmodules create mode 160000 benchmarks delete mode 100644 benchmarks/Gemfile delete mode 100644 benchmarks/Gemfile.lock delete mode 100644 benchmarks/README.md delete mode 100644 benchmarks/apps/big_delay.ru delete mode 100644 benchmarks/apps/chunked.ru delete mode 100644 benchmarks/apps/echo_service/echo.proto delete mode 100644 benchmarks/apps/echo_service/echo_service.rb delete mode 100644 benchmarks/apps/echo_service/google/protobuf/any.proto delete mode 100644 benchmarks/apps/echo_service/google/protobuf/timestamp.proto delete mode 100644 benchmarks/apps/echo_service/google/type/money.proto delete mode 100644 benchmarks/apps/empty.ru delete mode 100644 benchmarks/apps/full_hijack.ru delete mode 100644 benchmarks/apps/hello_world.ru delete mode 100644 benchmarks/apps/io_party.ru delete mode 100644 benchmarks/apps/large.ru delete mode 100644 benchmarks/apps/many_small_delay.ru delete mode 100644 benchmarks/apps/medium.ru delete mode 100644 benchmarks/apps/public/image.png delete mode 100644 benchmarks/apps/public/index.html delete mode 100644 benchmarks/apps/sinatra.ru delete mode 100644 benchmarks/apps/small.ru delete mode 100644 benchmarks/apps/static.ru delete mode 100644 benchmarks/apps/streaming_response_large.ru delete mode 100644 benchmarks/apps/streaming_response_small.ru delete mode 100644 benchmarks/flamegraph.svg delete mode 100644 benchmarks/grpc_server.rb delete mode 100644 benchmarks/lib/benchmark_case.rb delete mode 100644 benchmarks/lib/server.rb delete mode 100644 benchmarks/lib/util.rb delete mode 100644 benchmarks/rack_bench.rb delete mode 100644 benchmarks/server_configurations/h2o.conf delete mode 100644 benchmarks/server_configurations/itsi.rb delete mode 100644 benchmarks/server_configurations/nginx.conf delete mode 100644 benchmarks/servers.rb delete mode 100644 benchmarks/test_cases/framework/sinatra_get.rb delete mode 100644 benchmarks/test_cases/framework/sinatra_post.rb delete mode 100644 benchmarks/test_cases/grpc/echo.rb delete mode 100644 benchmarks/test_cases/grpc/echo_bidirectional.rb delete mode 100644 benchmarks/test_cases/grpc/echo_collect.rb delete mode 100644 benchmarks/test_cases/grpc/echo_stream.rb delete mode 100644 benchmarks/test_cases/grpc/process_payment.rb delete mode 100644 benchmarks/test_cases/grpc/server_configurations/itsi.rb delete mode 100644 benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb delete mode 100644 benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb delete mode 100644 benchmarks/test_cases/response_size/empty_response.rb delete mode 100644 benchmarks/test_cases/response_size/response_size_large.rb delete mode 100644 benchmarks/test_cases/response_size/response_size_medium.rb delete mode 100644 benchmarks/test_cases/response_size/response_size_small.rb delete mode 100644 benchmarks/test_cases/static_file/server_configurations/itsi.rb delete mode 100644 benchmarks/test_cases/static_file/static_dynamic_mixed.rb delete mode 100644 benchmarks/test_cases/static_file/static_large.rb delete mode 100644 benchmarks/test_cases/static_file/static_small.rb delete mode 100644 benchmarks/test_cases/streaming_response/chunked.rb delete mode 100644 benchmarks/test_cases/streaming_response/full_hijack.rb delete mode 100644 benchmarks/test_cases/streaming_response/streaming_response_large.rb delete mode 100644 benchmarks/test_cases/streaming_response/streaming_response_small.rb delete mode 100644 benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb delete mode 100644 benchmarks/test_cases/throughput/hello_world.rb create mode 100644 crates/itsi_server/src/server/frame_stream.rs create mode 100644 docs/benchmark-dashboard/.gitignore create mode 100644 docs/benchmark-dashboard/app/api/benchmarks/route.ts create mode 100644 docs/benchmark-dashboard/app/globals.css create mode 100644 docs/benchmark-dashboard/app/layout.tsx create mode 100644 docs/benchmark-dashboard/app/page.tsx create mode 100644 docs/benchmark-dashboard/components.json create mode 100644 docs/benchmark-dashboard/components/benchmark-dashboard.tsx create mode 100644 docs/benchmark-dashboard/components/theme-provider.tsx create mode 100644 docs/benchmark-dashboard/components/ui/accordion.tsx create mode 100644 docs/benchmark-dashboard/components/ui/alert-dialog.tsx create mode 100644 docs/benchmark-dashboard/components/ui/alert.tsx create mode 100644 docs/benchmark-dashboard/components/ui/aspect-ratio.tsx create mode 100644 docs/benchmark-dashboard/components/ui/avatar.tsx create mode 100644 docs/benchmark-dashboard/components/ui/badge.tsx create mode 100644 docs/benchmark-dashboard/components/ui/breadcrumb.tsx create mode 100644 docs/benchmark-dashboard/components/ui/button.tsx create mode 100644 docs/benchmark-dashboard/components/ui/calendar.tsx create mode 100644 docs/benchmark-dashboard/components/ui/card.tsx create mode 100644 docs/benchmark-dashboard/components/ui/carousel.tsx create mode 100644 docs/benchmark-dashboard/components/ui/chart.tsx create mode 100644 docs/benchmark-dashboard/components/ui/checkbox.tsx create mode 100644 docs/benchmark-dashboard/components/ui/collapsible.tsx create mode 100644 docs/benchmark-dashboard/components/ui/command.tsx create mode 100644 docs/benchmark-dashboard/components/ui/context-menu.tsx create mode 100644 docs/benchmark-dashboard/components/ui/dialog.tsx create mode 100644 docs/benchmark-dashboard/components/ui/drawer.tsx create mode 100644 docs/benchmark-dashboard/components/ui/dropdown-menu.tsx create mode 100644 docs/benchmark-dashboard/components/ui/form.tsx create mode 100644 docs/benchmark-dashboard/components/ui/hover-card.tsx create mode 100644 docs/benchmark-dashboard/components/ui/input-otp.tsx create mode 100644 docs/benchmark-dashboard/components/ui/input.tsx create mode 100644 docs/benchmark-dashboard/components/ui/label.tsx create mode 100644 docs/benchmark-dashboard/components/ui/loading-spinner.tsx create mode 100644 docs/benchmark-dashboard/components/ui/menubar.tsx create mode 100644 docs/benchmark-dashboard/components/ui/navigation-menu.tsx create mode 100644 docs/benchmark-dashboard/components/ui/pagination.tsx create mode 100644 docs/benchmark-dashboard/components/ui/popover.tsx create mode 100644 docs/benchmark-dashboard/components/ui/progress.tsx create mode 100644 docs/benchmark-dashboard/components/ui/radio-group.tsx create mode 100644 docs/benchmark-dashboard/components/ui/resizable.tsx create mode 100644 docs/benchmark-dashboard/components/ui/scroll-area.tsx create mode 100644 docs/benchmark-dashboard/components/ui/select.tsx create mode 100644 docs/benchmark-dashboard/components/ui/separator.tsx create mode 100644 docs/benchmark-dashboard/components/ui/sheet.tsx create mode 100644 docs/benchmark-dashboard/components/ui/sidebar.tsx create mode 100644 docs/benchmark-dashboard/components/ui/skeleton.tsx create mode 100644 docs/benchmark-dashboard/components/ui/slider.tsx create mode 100644 docs/benchmark-dashboard/components/ui/sonner.tsx create mode 100644 docs/benchmark-dashboard/components/ui/switch.tsx create mode 100644 docs/benchmark-dashboard/components/ui/table.tsx create mode 100644 docs/benchmark-dashboard/components/ui/tabs.tsx create mode 100644 docs/benchmark-dashboard/components/ui/textarea.tsx create mode 100644 docs/benchmark-dashboard/components/ui/toast.tsx create mode 100644 docs/benchmark-dashboard/components/ui/toaster.tsx create mode 100644 docs/benchmark-dashboard/components/ui/toggle-group.tsx create mode 100644 docs/benchmark-dashboard/components/ui/toggle.tsx create mode 100644 docs/benchmark-dashboard/components/ui/tooltip.tsx create mode 100644 docs/benchmark-dashboard/components/ui/use-mobile.tsx create mode 100644 docs/benchmark-dashboard/components/ui/use-toast.ts create mode 100644 docs/benchmark-dashboard/dist/benchmark-dashboard.css create mode 100644 docs/benchmark-dashboard/dist/benchmark-dashboard.iife.js create mode 100644 docs/benchmark-dashboard/dist/placeholder-logo.png create mode 100644 docs/benchmark-dashboard/dist/placeholder-logo.svg create mode 100644 docs/benchmark-dashboard/dist/placeholder-user.jpg create mode 100644 docs/benchmark-dashboard/dist/placeholder.jpg create mode 100644 docs/benchmark-dashboard/dist/placeholder.svg create mode 100644 docs/benchmark-dashboard/embed.tsx create mode 100644 docs/benchmark-dashboard/hooks/use-mobile.tsx create mode 100644 docs/benchmark-dashboard/hooks/use-toast.ts create mode 100644 docs/benchmark-dashboard/lib/benchmark-utils.ts create mode 100644 docs/benchmark-dashboard/lib/utils.ts create mode 100644 docs/benchmark-dashboard/next.config.mjs create mode 100644 docs/benchmark-dashboard/package-lock.json create mode 100644 docs/benchmark-dashboard/package.json create mode 100644 docs/benchmark-dashboard/pnpm-lock.yaml create mode 100644 docs/benchmark-dashboard/postcss.config.mjs create mode 100644 docs/benchmark-dashboard/styles/globals.css create mode 100644 docs/benchmark-dashboard/tailwind.config.ts create mode 100644 docs/benchmark-dashboard/tsconfig.json create mode 100644 docs/benchmark-dashboard/vite.config.ts create mode 100755 docs/build.rb create mode 100644 docs/content/benchmarks/index.md create mode 100644 docs/static/results.json create mode 100644 docs/static/scripts/benchmark-dashboard.iife.js create mode 100644 docs/static/styles/benchmark-dashboard.css delete mode 100644 tasks.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3f4ac27e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "benchmarks"] + path = benchmarks + url = ../itsi-server-benchmarks diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0480d6..7326448c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -## [0.2.17] - Unreleased -- Add 5 threads by default in rack/handler +## [0.2.17] - 2025-05-31 +- Enabled vectorized writes in IoSteam +- Replaced all usage of heap-allocated BoxBody with HttpBody enums +- Add 5 threads as default for rack/handler - Reserve header size ahead of time in rack interface - Avoid intermediate array allocation when populating Rack env headers. - Rewrite synchronous thread worker to avoid excessive GVL acquisition diff --git a/Cargo.lock b/Cargo.lock index 3f39eef0..a086a600 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,28 +220,6 @@ dependencies = [ "zstd-safe", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "async-trait" version = "0.1.88" @@ -1689,7 +1667,6 @@ dependencies = [ "argon2", "async-channel", "async-compression", - "async-stream", "async-trait", "base64 0.22.1", "bcrypt", diff --git a/benchmarks b/benchmarks new file mode 160000 index 00000000..c99d6305 --- /dev/null +++ b/benchmarks @@ -0,0 +1 @@ +Subproject commit c99d63051dd96f533b9aa9976e3c112dd73ca52e diff --git a/benchmarks/Gemfile b/benchmarks/Gemfile deleted file mode 100644 index 293820da..00000000 --- a/benchmarks/Gemfile +++ /dev/null @@ -1,15 +0,0 @@ -source 'https://rubygems.org' -gem 'debug' -gem 'agoo' -gem 'falcon' -gem 'iodine' -gem 'itsi-server', path: '../gems/server' -gem 'itsi-scheduler', path: '../gems/scheduler' -gem 'puma' -gem 'unicorn' -gem 'paint' -gem 'sinatra' -gem 'sys-proctable' -gem 'grpc' -gem 'sqlite3' -gem 'activerecord' diff --git a/benchmarks/Gemfile.lock b/benchmarks/Gemfile.lock deleted file mode 100644 index 0daf7738..00000000 --- a/benchmarks/Gemfile.lock +++ /dev/null @@ -1,216 +0,0 @@ -PATH - remote: ../gems/scheduler - specs: - itsi-scheduler (0.2.17) - rb_sys (~> 0.9.91) - -PATH - remote: ../gems/server - specs: - itsi-server (0.2.17) - json (~> 2) - prism (~> 1.4) - rack (>= 1.6) - rb_sys (~> 0.9.91) - -GEM - remote: https://rubygems.org/ - specs: - activemodel (8.0.2) - activesupport (= 8.0.2) - activerecord (8.0.2) - activemodel (= 8.0.2) - activesupport (= 8.0.2) - timeout (>= 0.4.0) - activesupport (8.0.2) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) - agoo (2.15.13) - async (2.24.0) - console (~> 1.29) - fiber-annotation - io-event (~> 1.9) - metrics (~> 0.12) - traces (~> 0.15) - async-container (0.24.0) - async (~> 2.22) - async-container-supervisor (0.5.1) - async-container (~> 0.22) - async-service - io-endpoint - memory-leak (~> 0.5) - async-http (0.87.0) - async (>= 2.10.2) - async-pool (~> 0.9) - io-endpoint (~> 0.14) - io-stream (~> 0.6) - metrics (~> 0.12) - protocol-http (~> 0.49) - protocol-http1 (~> 0.30) - protocol-http2 (~> 0.22) - traces (~> 0.10) - async-http-cache (0.4.5) - async-http (~> 0.56) - async-pool (0.10.3) - async (>= 1.25) - async-service (0.13.0) - async - async-container (~> 0.16) - base64 (0.2.0) - benchmark (0.4.0) - bigdecimal (3.1.9) - concurrent-ruby (1.3.5) - connection_pool (2.5.3) - console (1.30.2) - fiber-annotation - fiber-local (~> 1.1) - json - date (3.4.1) - debug (1.10.0) - irb (~> 1.10) - reline (>= 0.3.8) - drb (2.2.1) - falcon (0.51.1) - async - async-container (~> 0.20) - async-container-supervisor (~> 0.5.0) - async-http (~> 0.75) - async-http-cache (~> 0.4) - async-service (~> 0.10) - bundler - localhost (~> 1.1) - openssl (~> 3.0) - protocol-http (~> 0.31) - protocol-rack (~> 0.7) - samovar (~> 2.3) - ffi (1.17.2) - ffi (1.17.2-arm64-darwin) - fiber-annotation (0.2.0) - fiber-local (1.1.0) - fiber-storage - fiber-storage (1.0.1) - google-protobuf (4.30.2-arm64-darwin) - bigdecimal - rake (>= 13) - googleapis-common-protos-types (1.19.0) - google-protobuf (>= 3.18, < 5.a) - grpc (1.71.0-arm64-darwin) - google-protobuf (>= 3.25, < 5.0) - googleapis-common-protos-types (~> 1.0) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - io-console (0.8.0) - io-endpoint (0.15.2) - io-event (1.10.0) - io-stream (0.6.1) - iodine (0.7.58) - irb (1.15.2) - pp (>= 0.6.0) - rdoc (>= 4.0.0) - reline (>= 0.4.2) - json (2.11.3) - kgio (2.11.4) - localhost (1.5.0) - logger (1.7.0) - mapping (1.1.3) - memory-leak (0.5.2) - metrics (0.12.2) - minitest (5.25.5) - mustermann (3.0.3) - ruby2_keywords (~> 0.0.1) - nio4r (2.7.4) - openssl (3.3.0) - paint (2.3.0) - pp (0.6.2) - prettyprint - prettyprint (0.2.0) - prism (1.4.0) - protocol-hpack (1.5.1) - protocol-http (0.50.1) - protocol-http1 (0.34.0) - protocol-http (~> 0.22) - protocol-http2 (0.22.1) - protocol-hpack (~> 1.4) - protocol-http (~> 0.47) - protocol-rack (0.12.0) - protocol-http (~> 0.43) - rack (>= 1.0) - psych (5.2.4) - date - stringio - puma (6.6.0) - nio4r (~> 2.0) - rack (3.1.14) - rack-protection (4.1.1) - base64 (>= 0.1.0) - logger (>= 1.6.0) - rack (>= 3.0.0, < 4) - rack-session (2.1.0) - base64 (>= 0.1.0) - rack (>= 3.0.0) - raindrops (0.20.1) - rake (13.2.1) - rake-compiler-dock (1.9.1) - rb_sys (0.9.111) - rake-compiler-dock (= 1.9.1) - rdoc (6.13.1) - psych (>= 4.0.0) - reline (0.6.1) - io-console (~> 0.5) - ruby2_keywords (0.0.5) - samovar (2.3.0) - console (~> 1.0) - mapping (~> 1.0) - securerandom (0.4.1) - sinatra (4.1.1) - logger (>= 1.6.0) - mustermann (~> 3.0) - rack (>= 3.0.0, < 4) - rack-protection (= 4.1.1) - rack-session (>= 2.0.0, < 3) - tilt (~> 2.0) - sqlite3 (2.6.0-arm64-darwin) - stringio (3.1.7) - sys-proctable (1.3.0) - ffi (~> 1.1) - tilt (2.6.0) - timeout (0.4.3) - traces (0.15.2) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicorn (6.1.0) - kgio (~> 2.6) - raindrops (~> 0.7) - uri (1.0.3) - -PLATFORMS - arm64-darwin-23 - -DEPENDENCIES - activerecord - agoo - debug - falcon - grpc - iodine - itsi-scheduler! - itsi-server! - paint - puma - sinatra - sqlite3 - sys-proctable - unicorn - -BUNDLED WITH - 2.6.7 diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index 1f8f47b0..00000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Rack Server Benchmarks - -A systematic benchmarking suite for Ruby Rack-compatible servers. - -## Targets - -- Itsi -- Puma -- Unicorn -- Agoo -- Iodine -- Falcon - -## How to Run - -```bash -bundle install -bundle exec ruby rack_bench -``` diff --git a/benchmarks/apps/big_delay.ru b/benchmarks/apps/big_delay.ru deleted file mode 100644 index 39a99571..00000000 --- a/benchmarks/apps/big_delay.ru +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -run( - proc do - sleep 1 - [200, { 'content-type' => 'text/plain' }, ['hello world']] - end -) diff --git a/benchmarks/apps/chunked.ru b/benchmarks/apps/chunked.ru deleted file mode 100644 index 4aaa748e..00000000 --- a/benchmarks/apps/chunked.ru +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -run proc { |_env| - body = Enumerator.new do |yielder| - 5.times do |i| - yielder << "Chunk #{i + 1}\n" - sleep 0.001 - end - end - - [ - 200, - { 'Content-Type' => 'text/plain' }, - body - ] -} diff --git a/benchmarks/apps/echo_service/echo.proto b/benchmarks/apps/echo_service/echo.proto deleted file mode 100644 index 54886b6c..00000000 --- a/benchmarks/apps/echo_service/echo.proto +++ /dev/null @@ -1,57 +0,0 @@ -syntax = "proto3"; - -package echo; - -import "google/type/money.proto"; -import "google/protobuf/timestamp.proto"; -import "google/protobuf/any.proto"; - -service EchoService { - // Simple unary method - rpc Echo(EchoRequest) returns (EchoResponse) {} - - // Server streaming method - rpc EchoStream(EchoRequest) returns (stream EchoResponse) {} - - // Client streaming method - rpc EchoCollect(stream EchoRequest) returns (EchoResponse) {} - - // Bidirectional streaming method - rpc EchoBidirectional(stream EchoRequest) returns (stream EchoResponse) {} - - // New endpoint demonstrating Google's common protobuf types - rpc ProcessPayment(PaymentRequest) returns (PaymentResponse) {} -} - -message EchoRequest { - string message = 1; -} - -message EchoResponse { - string message = 1; - int32 count = 2; -} - -// New messages demonstrating Google's common protobuf types -message PaymentRequest { - string customer_id = 1; - google.type.Money amount = 2; - google.protobuf.Timestamp payment_time = 3; - // Optional field using google.protobuf.Any for additional metadata - google.protobuf.Any metadata = 4; -} - -message PaymentResponse { - string transaction_id = 1; - google.protobuf.Timestamp processed_at = 2; - PaymentStatus status = 3; - // Optional error message - optional string error_message = 4; -} - -enum PaymentStatus { - PAYMENT_STATUS_UNSPECIFIED = 0; - PAYMENT_STATUS_SUCCESS = 1; - PAYMENT_STATUS_FAILED = 2; - PAYMENT_STATUS_PENDING = 3; -} \ No newline at end of file diff --git a/benchmarks/apps/echo_service/echo_service.rb b/benchmarks/apps/echo_service/echo_service.rb deleted file mode 100644 index c3b79dc4..00000000 --- a/benchmarks/apps/echo_service/echo_service.rb +++ /dev/null @@ -1,128 +0,0 @@ -# Implementation of the Echo service -require 'grpc' -require 'google/protobuf' -require 'google/type/money_pb' -require 'google/protobuf/timestamp_pb' -require 'google/protobuf/any_pb' - -descriptor_data = "\n\necho.proto\x12\x04\x65\x63ho\x1a\x17google/type/money.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\"\x1e\n\x0b\x45\x63hoRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\".\n\x0c\x45\x63hoResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\"\xa3\x01\n\x0ePaymentRequest\x12\x13\n\x0b\x63ustomer_id\x18\x01 \x01(\t\x12\"\n\x06\x61mount\x18\x02 \x01(\x0b\x32\x12.google.type.Money\x12\x30\n\x0cpayment_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x08metadata\x18\x04 \x01(\x0b\x32\x14.google.protobuf.Any\"\xae\x01\n\x0fPaymentResponse\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x30\n\x0cprocessed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.echo.PaymentStatus\x12\x1a\n\rerror_message\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_error_message*\x82\x01\n\rPaymentStatus\x12\x1e\n\x1aPAYMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16PAYMENT_STATUS_SUCCESS\x10\x01\x12\x19\n\x15PAYMENT_STATUS_FAILED\x10\x02\x12\x1a\n\x16PAYMENT_STATUS_PENDING\x10\x03\x32\xb4\x02\n\x0b\x45\x63hoService\x12/\n\x04\x45\x63ho\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x12\x37\n\nEchoStream\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00\x30\x01\x12\x38\n\x0b\x45\x63hoCollect\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x12@\n\x11\x45\x63hoBidirectional\x12\x11.echo.EchoRequest\x1a\x12.echo.EchoResponse\"\x00(\x01\x30\x01\x12?\n\x0eProcessPayment\x12\x14.echo.PaymentRequest\x1a\x15.echo.PaymentResponse\"\x00\x62\x06proto3" # rubocop:disable Layout/LineLength - -pool = Google::Protobuf::DescriptorPool.generated_pool -pool.add_serialized_file(descriptor_data) - -module Echo - EchoRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.EchoRequest').msgclass - EchoResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.EchoResponse').msgclass - PaymentRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.PaymentRequest').msgclass - PaymentResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.PaymentResponse').msgclass - PaymentStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup('echo.PaymentStatus').enummodule -end - -module Echo - module EchoService - class Service # rubocop:disable Style/Documentation - include ::GRPC::GenericService - - self.marshal_class_method = :encode - self.unmarshal_class_method = :decode - self.service_name = 'echo.EchoService' - - # Simple unary method - rpc :Echo, ::Echo::EchoRequest, ::Echo::EchoResponse - # Server streaming method - rpc :EchoStream, ::Echo::EchoRequest, stream(::Echo::EchoResponse) - # Client streaming method - rpc :EchoCollect, stream(::Echo::EchoRequest), ::Echo::EchoResponse - # Bidirectional streaming method - rpc :EchoBidirectional, stream(::Echo::EchoRequest), stream(::Echo::EchoResponse) - # Payment processing method - rpc :ProcessPayment, ::Echo::PaymentRequest, ::Echo::PaymentResponse - end - - Stub = Service.rpc_stub_class - end -end - -class EchoServiceImpl < Echo::EchoService::Service - def echo(request, _call) - @counter ||= 1 - # sleep 0.05 - Echo::EchoResponse.new(message: "Echo: #{request.message}", count: @counter += 1) - end - - def echo_stream(request, _call) - # Stream 3 responses back - - 3.times do |i| - response = Echo::EchoResponse.new( - message: "Echo[#{i}]: #{request.message}", - count: i + 1 - ) - yield response - sleep 0.1 - end - end - - def echo_collect(requests, _call) - messages = [] - count = 0 - - # Collect all messages from the client - requests.each do |req| - messages << req.message - count += 1 - end - - # Return a single response with all messages concatenated - Echo::EchoResponse.new( - message: "Collected: #{messages.join(', ')}", - count: count - ) - end - - def echo_bidirectional(requests, _call) - count = 0 - - # For each request received, send a response immediately - thr = Thread.new do - requests.each do |_| - count += 1 - sleep 0.1 - end - end - - Enumerator.new do |e| - 10.times do |_i| - e << Echo::EchoResponse.new( - message: "Here's a response\n", - count: count - ) - end - thr.join - end - end - - def process_payment(request, _call) # rubocop:disable Metrics/MethodLength - # Create a response with current timestamp - # - processed_at = Google::Protobuf::Timestamp.new(seconds: Time.now.to_i) - - # Generate a mock transaction ID - transaction_id = "txn_#{Time.now.to_i}_#{rand(1000..9999)}" - - # Simulate payment processing - status = if request.amount.units.positive? - Echo::PaymentStatus::PAYMENT_STATUS_SUCCESS - else - Echo::PaymentStatus::PAYMENT_STATUS_FAILED - end - - # Create the response - Echo::PaymentResponse.new( - transaction_id: transaction_id, - processed_at: processed_at, - status: status, - error_message: status == Echo::PaymentStatus::PAYMENT_STATUS_FAILED ? 'Invalid amount' : nil - ) - end -end diff --git a/benchmarks/apps/echo_service/google/protobuf/any.proto b/benchmarks/apps/echo_service/google/protobuf/any.proto deleted file mode 100644 index eff44e50..00000000 --- a/benchmarks/apps/echo_service/google/protobuf/any.proto +++ /dev/null @@ -1,162 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -syntax = "proto3"; - -package google.protobuf; - -option go_package = "google.golang.org/protobuf/types/known/anypb"; -option java_package = "com.google.protobuf"; -option java_outer_classname = "AnyProto"; -option java_multiple_files = true; -option objc_class_prefix = "GPB"; -option csharp_namespace = "Google.Protobuf.WellKnownTypes"; - -// `Any` contains an arbitrary serialized protocol buffer message along with a -// URL that describes the type of the serialized message. -// -// Protobuf library provides support to pack/unpack Any values in the form -// of utility functions or additional generated methods of the Any type. -// -// Example 1: Pack and unpack a message in C++. -// -// Foo foo = ...; -// Any any; -// any.PackFrom(foo); -// ... -// if (any.UnpackTo(&foo)) { -// ... -// } -// -// Example 2: Pack and unpack a message in Java. -// -// Foo foo = ...; -// Any any = Any.pack(foo); -// ... -// if (any.is(Foo.class)) { -// foo = any.unpack(Foo.class); -// } -// // or ... -// if (any.isSameTypeAs(Foo.getDefaultInstance())) { -// foo = any.unpack(Foo.getDefaultInstance()); -// } -// -// Example 3: Pack and unpack a message in Python. -// -// foo = Foo(...) -// any = Any() -// any.Pack(foo) -// ... -// if any.Is(Foo.DESCRIPTOR): -// any.Unpack(foo) -// ... -// -// Example 4: Pack and unpack a message in Go -// -// foo := &pb.Foo{...} -// any, err := anypb.New(foo) -// if err != nil { -// ... -// } -// ... -// foo := &pb.Foo{} -// if err := any.UnmarshalTo(foo); err != nil { -// ... -// } -// -// The pack methods provided by protobuf library will by default use -// 'type.googleapis.com/full.type.name' as the type URL and the unpack -// methods only use the fully qualified type name after the last '/' -// in the type URL, for example "foo.bar.com/x/y.z" will yield type -// name "y.z". -// -// JSON -// ==== -// The JSON representation of an `Any` value uses the regular -// representation of the deserialized, embedded message, with an -// additional field `@type` which contains the type URL. Example: -// -// package google.profile; -// message Person { -// string first_name = 1; -// string last_name = 2; -// } -// -// { -// "@type": "type.googleapis.com/google.profile.Person", -// "firstName": , -// "lastName": -// } -// -// If the embedded message type is well-known and has a custom JSON -// representation, that representation will be embedded adding a field -// `value` which holds the custom JSON in addition to the `@type` -// field. Example (for message [google.protobuf.Duration][]): -// -// { -// "@type": "type.googleapis.com/google.protobuf.Duration", -// "value": "1.212s" -// } -// -message Any { - // A URL/resource name that uniquely identifies the type of the serialized - // protocol buffer message. This string must contain at least - // one "/" character. The last segment of the URL's path must represent - // the fully qualified name of the type (as in - // `path/google.protobuf.Duration`). The name should be in a canonical form - // (e.g., leading "." is not accepted). - // - // In practice, teams usually precompile into the binary all types that they - // expect it to use in the context of Any. However, for URLs which use the - // scheme `http`, `https`, or no scheme, one can optionally set up a type - // server that maps type URLs to message definitions as follows: - // - // * If no scheme is provided, `https` is assumed. - // * An HTTP GET on the URL must yield a [google.protobuf.Type][] - // value in binary format, or produce an error. - // * Applications are allowed to cache lookup results based on the - // URL, or have them precompiled into a binary to avoid any - // lookup. Therefore, binary compatibility needs to be preserved - // on changes to types. (Use versioned type names to manage - // breaking changes.) - // - // Note: this functionality is not currently available in the official - // protobuf release, and it is not used for type URLs beginning with - // type.googleapis.com. As of May 2023, there are no widely used type server - // implementations and no plans to implement one. - // - // Schemes other than `http`, `https` (or the empty scheme) might be - // used with implementation specific semantics. - // - string type_url = 1; - - // Must be a valid serialized protocol buffer of the above specified type. - bytes value = 2; -} diff --git a/benchmarks/apps/echo_service/google/protobuf/timestamp.proto b/benchmarks/apps/echo_service/google/protobuf/timestamp.proto deleted file mode 100644 index fd0bc07d..00000000 --- a/benchmarks/apps/echo_service/google/protobuf/timestamp.proto +++ /dev/null @@ -1,144 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -syntax = "proto3"; - -package google.protobuf; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/protobuf/types/known/timestamppb"; -option java_package = "com.google.protobuf"; -option java_outer_classname = "TimestampProto"; -option java_multiple_files = true; -option objc_class_prefix = "GPB"; -option csharp_namespace = "Google.Protobuf.WellKnownTypes"; - -// A Timestamp represents a point in time independent of any time zone or local -// calendar, encoded as a count of seconds and fractions of seconds at -// nanosecond resolution. The count is relative to an epoch at UTC midnight on -// January 1, 1970, in the proleptic Gregorian calendar which extends the -// Gregorian calendar backwards to year one. -// -// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap -// second table is needed for interpretation, using a [24-hour linear -// smear](https://developers.google.com/time/smear). -// -// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By -// restricting to that range, we ensure that we can convert to and from [RFC -// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. -// -// # Examples -// -// Example 1: Compute Timestamp from POSIX `time()`. -// -// Timestamp timestamp; -// timestamp.set_seconds(time(NULL)); -// timestamp.set_nanos(0); -// -// Example 2: Compute Timestamp from POSIX `gettimeofday()`. -// -// struct timeval tv; -// gettimeofday(&tv, NULL); -// -// Timestamp timestamp; -// timestamp.set_seconds(tv.tv_sec); -// timestamp.set_nanos(tv.tv_usec * 1000); -// -// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. -// -// FILETIME ft; -// GetSystemTimeAsFileTime(&ft); -// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; -// -// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z -// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. -// Timestamp timestamp; -// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); -// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); -// -// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. -// -// long millis = System.currentTimeMillis(); -// -// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) -// .setNanos((int) ((millis % 1000) * 1000000)).build(); -// -// Example 5: Compute Timestamp from Java `Instant.now()`. -// -// Instant now = Instant.now(); -// -// Timestamp timestamp = -// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) -// .setNanos(now.getNano()).build(); -// -// Example 6: Compute Timestamp from current time in Python. -// -// timestamp = Timestamp() -// timestamp.GetCurrentTime() -// -// # JSON Mapping -// -// In JSON format, the Timestamp type is encoded as a string in the -// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the -// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" -// where {year} is always expressed using four digits while {month}, {day}, -// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional -// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), -// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone -// is required. A proto3 JSON serializer should always use UTC (as indicated by -// "Z") when printing the Timestamp type and a proto3 JSON parser should be -// able to accept both UTC and other timezones (as indicated by an offset). -// -// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past -// 01:30 UTC on January 15, 2017. -// -// In JavaScript, one can convert a Date object to this format using the -// standard -// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) -// method. In Python, a standard `datetime.datetime` object can be converted -// to this format using -// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with -// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use -// the Joda Time's [`ISODateTimeFormat.dateTime()`]( -// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() -// ) to obtain a formatter capable of generating timestamps in this format. -// -message Timestamp { - // Represents seconds of UTC time since Unix epoch - // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - // 9999-12-31T23:59:59Z inclusive. - int64 seconds = 1; - - // Non-negative fractions of a second at nanosecond resolution. Negative - // second values with fractions must still have non-negative nanos values - // that count forward in time. Must be from 0 to 999,999,999 - // inclusive. - int32 nanos = 2; -} diff --git a/benchmarks/apps/echo_service/google/type/money.proto b/benchmarks/apps/echo_service/google/type/money.proto deleted file mode 100644 index f67aa51f..00000000 --- a/benchmarks/apps/echo_service/google/type/money.proto +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.type; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/genproto/googleapis/type/money;money"; -option java_multiple_files = true; -option java_outer_classname = "MoneyProto"; -option java_package = "com.google.type"; -option objc_class_prefix = "GTP"; - -// Represents an amount of money with its currency type. -message Money { - // The three-letter currency code defined in ISO 4217. - string currency_code = 1; - - // The whole units of the amount. - // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. - int64 units = 2; - - // Number of nano (10^-9) units of the amount. - // The value must be between -999,999,999 and +999,999,999 inclusive. - // If `units` is positive, `nanos` must be positive or zero. - // If `units` is zero, `nanos` can be positive, zero, or negative. - // If `units` is negative, `nanos` must be negative or zero. - // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. - int32 nanos = 3; -} diff --git a/benchmarks/apps/empty.ru b/benchmarks/apps/empty.ru deleted file mode 100644 index b04ccc31..00000000 --- a/benchmarks/apps/empty.ru +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true -EMPTY = [200, { }, ['']].freeze - -run( - proc do - EMPTY - end -) diff --git a/benchmarks/apps/full_hijack.ru b/benchmarks/apps/full_hijack.ru deleted file mode 100644 index ac05bd57..00000000 --- a/benchmarks/apps/full_hijack.ru +++ /dev/null @@ -1,18 +0,0 @@ -run Proc.new { |env| - if env['rack.hijack'] - io = env['rack.hijack'].call - - # Write raw HTTP/1.1 response headers - io.write "HTTP/1.1 200 OK\r\n" - io.write "Content-Type: text/plain\r\n" - io.write "Connection: close\r\n" - io.write "\r\n" - - # Write the response body - io.write "Hello from full hijack!\n" - - io.close - end - - [-1, {}, []] -} diff --git a/benchmarks/apps/hello_world.ru b/benchmarks/apps/hello_world.ru deleted file mode 100644 index 0b739f8a..00000000 --- a/benchmarks/apps/hello_world.ru +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -run( - proc do - [200, { 'content-type' => 'text/plain' }, ['hello world']] - end -) diff --git a/benchmarks/apps/io_party.ru b/benchmarks/apps/io_party.ru deleted file mode 100644 index d4fdb73d..00000000 --- a/benchmarks/apps/io_party.ru +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'json' -require 'active_record' -require 'net/http' -require 'debug' - - -TMP_DB_DIR = Dir.mktmpdir('io_party_db') -TMP_DB_FILE = File.join(TMP_DB_DIR, 'test.sqlite3') - -ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: TMP_DB_FILE) - -ActiveRecord::Schema.define do - create_table :posts, force: true do |t| - t.string :name - t.text :body - t.timestamps - end -end - -class Post < ActiveRecord::Base; end - -run( - proc do |_| - post = Post.find_or_create_by(name: 'Hello World', body: 'I made a change. This is a test post') - ActiveRecord::Base.connection.execute('SELECT * FROM posts;') - sleep 0.0001 - - queue = Queue.new - Thread.new do - sleep 0.0001 - queue.push('done') - end.join - queue.pop - - Thread.new do - sleep 0.0001 - end.join - - post.update(name: 'I made a change. Hello World', body: 'Wow... I think it might be working') - [200, { 'content-type' => 'text/plain'}, [post.to_json]] - end -) - -at_exit do - ActiveRecord::Base.connection_pool.disconnect! - FileUtils.rm_f(TMP_DB_FILE) # remove database file - begin - Dir.rmdir(TMP_DB_DIR) - rescue StandardError - nil - end -end diff --git a/benchmarks/apps/large.ru b/benchmarks/apps/large.ru deleted file mode 100644 index 2c7bf8f8..00000000 --- a/benchmarks/apps/large.ru +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -large = `ruby --help` * 1000 # 3MB - -run( - proc do - [200, {"content-type" => "text/plain"}, [large]] - end -) diff --git a/benchmarks/apps/many_small_delay.ru b/benchmarks/apps/many_small_delay.ru deleted file mode 100644 index 330f7d78..00000000 --- a/benchmarks/apps/many_small_delay.ru +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -run( - proc do - 100.times do - sleep 0.01 - end - [200, { 'content-type' => 'text/plain' }, ['hello world']] - end -) diff --git a/benchmarks/apps/medium.ru b/benchmarks/apps/medium.ru deleted file mode 100644 index dd94ad62..00000000 --- a/benchmarks/apps/medium.ru +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -medium = `ruby --help` * 100 # 300kb - -run( - proc do - [200, {"content-type" => "text/plain"}, [medium]] - end -) diff --git a/benchmarks/apps/public/image.png b/benchmarks/apps/public/image.png deleted file mode 100644 index 5c0cfdfeb0a2acb9954f6b39a52769b44574a89d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9230 zcmbVybyyrpxA)-g?gS6+46Z?fLx2H->jYv)ZI{#pS~gd8Iw;0 z(MDQT8USca#JDpLu z7(CJ6P4TJ!#{5YU3gQ0oSP1{OwPhi~-@MyzSf?37&l5p&R@8?802l&JfH#1r<2YW||h?h9s9~L4{{BJQA z9qk_`Zg%2yAXN=oStnO3T0u^3PHs90bXr}|yu2Jw790?7M>jJs4o3+6pHBYcN6rdj;R<$k13Nj={`PBT z?&R(!PDl4U(BJLPJl(+7|3q?x{8iRdfn2{eTs)lIT>lfy$_xCzz_z&b?;(wz+|LFe-^WVaM1Jqr? zR!=|VA9?ZomDYdD{>%P%bs`#IFDrYH9N59i5%Rkh0e%57uK!W{SiE6GW}@`5`w!ti=E*Rsl&f)b4Q77z>wM#N;t}U5Y_U21YM6e9ObPyZmUfdyv96 zGG=*}nKZxTY<=8JV^{le-q^SP9w_>f@pT9S8a4wkXvbeU9E_3)YOehcGsldSuYot$&|>hcYiDaKEsgn~U_(_gB~Q@vq%{$b_)( zOJ(K#j$@JsKAAP8(`70jXazxQoqal2F z$Tl`M>d0H;+o^=;#ShDnNe6O)H?Ii#{BJJ>Qr^CeySuxabuehxhHSmQ&qvA+ZN*$( zx^G?^`}C=d`-id59%FNB>qLc^K3L*>%!E`gCnv|Q7{kyZr2sc-JgV+ePIT%;Jn#@Y zTLoD5yO>tEF9VYw*^J<1AZA4zRl^6=srKt>vG7Z8Z48fDiWO6lwjtn3anXr9-ktC1 z(?=0$@s&*``AsEBM7?b|)GjossuqNm@wZfa^W@4!WpEsApPgGY~NFuE~&3B763JSmDL>PqXt2z-Y}%0}*?UK7RR zve{pwE^)o>D;t6NL4c1Bvc6joSZ9_L4a;0=w$a2WBpWHFipPqtYO2yQD_rm%!6cJJ z$H35eYU913D0=ba0s+^n9}E1!HpM5x9XPvD3U&d$VY?9nXP-T0@X-=Q12e)!*>GDy z)99${IX|(mx><%NY1-@-IPr&LNPjIu5e%>|xPF*_`8B1%AY>!n~m~5t3GEu?I zOE1_|N=Hwx+F~f7viPI#T%=(uDGsM0Ie!S#o-(zNWA2y0^&m;%=X=tq-^&rPch_5u z!-EM+=19II&z7m~xA^`FmhLq@#I^k%^dKy}_YhH5)80^^wUtg<70{&^3=50y zVpOI+QOoDmf3~{y)90&E^4Xbd3wV~P#e4vLE6;Nkt*pmPOpDY;f?Uk&q-82wyhoiJ zdMV5y%u##not5QpU3e=xxvLCY9;A%%19P*l7p0L#h)EQvlOt{QMA4#(C#*|!cf0sn zS^Zq2ASYj~t*@6HcKfOcsSqfyQJxKhUMz{|7_SaP)~UqrFJ|?%)z0AA^y>K4b@MTK z?0A?+drf<|LrjtKq96JF$-)wCXFv7%^(Hmh&JD(Lbtzt1`rR)!T9DQ4N!m`8o>1ul zX34oECq_qa5smVqm6*<3{8;$k;DYCH*3iC=U| zsvMJ^o>o=BVOU1(d*G>R3#dFG=4;~p7Dp;#4p9P+%yjMfJB2LeA{%5Dgd@mCVrn;Y z73`3ifWDKZ3yp=AQY3BaRDRSV;#Uw5SUWY!cC9snIyqd2y+)t8+Wk$lptket!gWy8Ty+2B5+YQF-jE)Zu%2&6% z3!jZfhGqgh7!n$w^HYJ_$qK{vk}ZG$fVe)RU&J-PTKC0F56$D1!qa#>l)r9tMS#$% zVoZsu4Sn!uB6oLp9}Gr7iEFW2KqBF|ffIOXhc7S~<{X3P$wz&-S;j#w%}{V(PR?U4 z2bZlcpzAOaR92qv^O6iZ(ehI|#Qw(_(&Z zt`!!GipOdi<`#eCsD+V-x=u;T{UQG8YF!mO?u0D02(0ro{*bz>mzI)UpH(7pMC#aG z*n^nv*)!1xwQeLU(6~o=>I_%Lb(kl^(s>2=Xtg8;J6!I5=yjVH3?I=suldI&FJsEu z5uJbKWLU|2Z$)aw!p|3k5}K=xJ)d_9!+2EoE9TjTYi~~R;#8od>fo*pOk22mP{atD zJCm)Mis7E3D{X0%ag=YVG1cp~d;K}{t7C_%q1fO=o@i+Q+?>HnKiq`bVQeZ9OLpVV zOfL$v!$u5xN#d^u$HYa$3>?f{POp4?MCQ^F5pk$Q*yY-z58r%K2q_PT9U(%8A+xGN z!dpl!bXrRZgMAMeU@yCe)(Agb3@kPR%tw42Dqn(fh2%`mhvY^C?gA*v>0pbUNpskA z0uKUGd#aKFOXh~C%u<6{==v0x$|?Zr>bNNCaZpHWxlY4FSFjOjzuJs9^n!&hZftoB z4+#m0|L53#eFtZd`*Ypp?Yn2>r>81g-=(fH+sO1x#MQ%G`>=$_TU!+NlzlpFr$V0Q zN*tHbMc4=RH*b!SS~WJ!{YMs)Q&O6p$1-{B;Z&VI{!G7^%P#cNS;Sh{^tVIaKi!DnuI)s_}#aJ^Bo_xSf`mr1|ArP>yyTk+>f)G+j*p8Flznege=2k?;D*~cSY zBgw|%5KEf((L0hn*n*17_j>P$eT7S0ZZ8iDt9{2`Z^mAJuLEzDsj9vIc|DP*Ro1~S zHAU+uEh});b?cbGEUkvq5F$2b-T2MQErGs86-&C{>Su8=oq)#yX<_D2mcG?+CUf~Y_QJmGKM79e&wcG$FvR&55!ZgX3`M6$c0TeWAD^O={Djf|eEuHBuI;Abb^y_8p z;}W6b5~;M37vr<=O!BB0(JPR#EWSu?O#7&(`FlVgM5_quW@%GyqNfenR8UNy zdd3^un(KeLGy)8~#ZxiTnjC60HVAuH`hs=#@KpU_PpPr(AWQse2jsBakjk(38tS<% z6t7dQO9nKwUUgr{LvF2O?50S-7BUyGc({8^+VL4vl0ZC$#Gxn-YFzX z;9wLz6h%_M?Csg!!J}RZ{B}e&fIAZK#4r11!mg<0 zjDJXQ*AjIu5-4C)mp|dFMX5hxcu#e@YjrJTk z&6_#@Ep;R%i^Z}}$mPu%0<|}bp+|vRdslV7k=3$SUp#P~M+9VOD2nHHAlGyu^R_Xb zM&DO>zoxlMAyd0dfh zBlaxjhsDIiOg4yDulgQ~TMYE!prE@TDvT+Vd@&L<#GuArw8gV!LsjyOb8t?0Gox}5 zjjizwnu$B+^-N}IL4Ne|rW2h*3TX^uqR8O+1Cr zna*pq3EBar!><$%btfD#XB$w4St0wy!n$0(pM$D12u9Zj=5Vi<7{ z!<)nP*oh(kI~O3JiQx8;beXHNx-MWQgz$QS1DBH?vTmG{m-q87nt@ZB>4x>NeyS1vyYPR-%jvF^h;6S+zk=rFk4Q`MDnrXUF-7Ir76xH|H znJ6wtD$K_(swJyJgS}0~rVeU|Cv?O4^xjNn z_=a!Zz3=+}C) z&0>~|LZEnbhPE1zb~RJriHTohwnQF8a>jHk zL#UckKcbHm#s;$LOHK{lWuYEy6t&xtDFTf=%c}o)`Grt%Ay!)Hj4sYeG`o)-A2a=0 zx6bb)u}mjvM~svI^-jiMBu&8ZQLijtR;{wA2OnZT@xVLa{u-#>`n8~}k@rp-dgRgP znNsDsgQaF7Y~n(zU#*BC@We&wdG-68FTVA$QMXDxo1i|p z<}A!ibW8p~n1F>~?HHPs&J_8Jk;(!gu`L@Yq6{Z$L_KP!0AzpsX6Tfiq1OE>o{e&M z<@#EUb7n@coK9|z5KG~fp=rhLnuTvdB01Q5l!9_eDkCoSSt$M1&%O)Mqqfl6fgl08 zhY-Tll0XZSe5@nxmAIT5kO4a_6<|3}L@S zQVAH8ot}Oyl5g0B8F)9s%znIUZed_B_&RJ)1ZMbL(ZTFI;YKA#P_aWD6#NA46#__PMjb%f81SkrSc#b(~v+c-ZeJB45Zu4)!d(-G&jJbbHG88zMjh z7Po1^@U`on!;T4LqGfoY9j{>4J|OEx-s~%z0)JgTSdc%L;lNSM5WW4D;C_S9WbOKk zSYonf>v3MwR8kBQPX5(^TL(&nCTzeP5sM;(w6vSz9DWy05TdJ(*@lU|t{_fT<7unx z2e}^&sdbG)Q1~1nr-ZggHRXtVF7XNwuiMdqjzvY&8earPk+dcR^_FWN?L*%SVKuYPy7aBb7gS+yp zLt!Z)vz-8(^m-^BQL3^hB5d-zsMZvpD>Kz1)*3#? z?~|wN(#9S|>)tV^&UO0vV{QgtDsz^Ou7!1~<~+t>&6tj^4=Wte-PsmRwQ&_NG!w;b zfX5j)3N+HYdfTs5b;!%(w2?9BW(NNLCb)sox#tE~ndoWFI=G+k?3!)!U2v?bI}lC5 ze?QyLhi#uk4#ae^Ie>9r9k^vU-f?IKS*UfTgO7gMav%6@^Br3H8Ia^2#y~=oo%JyK zrMXRqlWuurZA`LmW=kb`?nnuM7LmxJ-lS12I+27wU>S;6Bip2;9I4Q-qO}(OX@5Q% z6tB2=)wPz9U8%K~%A3z{9d(#8p5CHh+~UN3Fz9?U*BmSgTn|Ku68L#oUREez{biKN-xm0g3%N1FXt zm3l_*Ij`5Pe-XxLLr6k-*le4V3FLo! zq?dO1P={*?q%`(@56`yq#%uM2_I-b2h+j_LLZ!so)^l9gZckdsd7X*5g)M8kR@FJi zneTXxP`%6bnF%X9+pt_*E{TH+56JK}B7XHgyR0bdaO-!3*CSfME{&Ck!>{4H+>Ujm zk^y47$X|HV#7>D(lbxv|t8-{d%W;4No|cC&e3>7x@L2foFwEBjy<&8vH|c|6{N(aO z+h5FWZIFlw@XUDk>awz3M=q4b$jJQfKs96iB;7~H1f`aqs9{kosoV4l*av+!2sVc+ z9lwzLdb8t7iD(2%?<60uCqrMncrg>#_PH>FDop0ijfnm$e&!$vSAkw+uHX&-WC)Vy z$a-|UvBMTVJ&Uo|yIyxJ(=NlOi7fF<4jKJ~hZ@w>-%~yg`q3)kaCD%Bj<{4=nmLia z3!^jZy#D5c-jgZhS=ZUuPy}!`>~AB#>N}Z=c6G~$0I}pqng$~g`=KXWn7Xh>>igj< zqvxY!a0+m>t;4$y5b3cjT4vBEZ*V)~HFyovvFBUb8C&Wc_M+Z0*-3XY~3X#Zr zDhDON&!0nEzpF({MP<%sfJ{Ng!qM*a;a8j|1aEMD`_uWIcHhPsqOf9?4vy;@4a@#ctIHVLkKQ-*`fQ@k2Efflc zIEXtE-Dm(CE!PxcWeRk-sB>ic!P(*XRWg#N4WT{2;8DWP({EiiMQh#M6p zUbWV!BVBq`Qc0cs^ZIfxubeq&db~~?o=Omg@QL*c_v3nQnslOiGD)e=z*$BYT#a8^ zGXsmzh=UbFh9s^J+xQJ9n}eVOY`d!pIPkNWqfq6sFTN@qtNVIW0=MP&l%oMs)`L?Q-S|4A|W}9i8KK z4OxzgmrcjG$u9*`$RBY@!2))Lu906uvvyc;+c^y1R~RjFj1LkYg`E;Gs3ocjT&3q@ zmq{d+>W*Lp_=HH=g0Iz5)6z_zo-4z|pWbtJHX}ks@66mHsTK(cXvf*w0 z?>Q#Ym8KbS#0pQmg~L^tT__JWIy)WWz*SE9{`e3}Zr|5U#SM~^>w@8?~g0Py2=#5XqN3pd+))vKP} zXSv3A`T+r5?kG`dMMLPav-P(#f$vegLJ*?PulhQ))-@`Ni!Gv9TYJyHs>&475QXe_Em$b_Z{Jik*~;*DRu{CfD)D>$gJ2|W_67-iR7 zd|4rC032-pVA^cZYEQPLpd6{r&rDv-x9mE~apmP+)7j3i$~^na)PCz~LsfZSC!Par zSJ>QP=s5`L6W6DOQ9{S=YcS^nJMTnw8qwwWaBK50&o>gP&2vQpX-?#2L3dF}?iu?s zQ)zQwu>$DSw6fbkQm4R~o?OcCZscB_FN#K`(K?1j1lhibCncvn3!!2mhY)dN+6pxu zY%s-@?sR1Ls@3|97^U+2n`2v@Ir!dmZdF^F_RIvP!9Kk`Jp)9qaXRsG%p8B@O^V)_50lxhid?7mw9RXa-!yo_a zm<`_&_X=*X&poC*kJWx7@oPOQB1b1WLK9{>@C8@ef;Ij%5tb-dRIfNwgXu@~bEKta zg#cv(UFq}%F(5KRaq6@}!pmwKZg#(Qt87?inTLMhc?``4p^{}7uZKZSK2zCvTscdq zTP3J)QTDFGV+22Zw&2}dN7jB)1)24GH=2p3&7fDQBZvf9i{ap*Ge6+s>?39(ObIVI z)`@PwCim+eSjAl@{)6L+h!bmFa>z*^+OiRQnX@`c6m%+PYsj-fu)g!G%1;}{tzt@M zKXR9jGXt8iczlj#Y}#61vYjcY^x_<~&%?6)1P*?6uuW*qZ*+7Xhj~G{CBlj7RYh%W za?>)O&B7;mXR~ly#+Li9vq*GfY;0_SB);gbSO;algc@P4Df}q&&N%JVA0~*IL2?v5 zH5`I$#pFDo?KO~wM&a*mh=H^qc9^$18)ZXh*j^iJQb=T{iZL5CQEPm+YCT6srz&72 z8IGo&_}xtN)SJk9KOZZj#{>H~3~C%ntiGNf;0mXc^~tWPFYN}Cnkpw(zr+G=bJh^A z7M4xZ7&lU{@#CHyTrL6(-$d1gcz1SoBJRfRZq&RwYIm<+z@ZRKy*t&QxCb`LQU;Y` zr>C>NzH-{+`Yd36EQv&+sR{-!Nmf=@Z))P>y+yv6{I2|t4CuTFe%g6u8x#wxo%KfE z{e~EUGl|i|kBvD&B{v#v@N93nc@TI)lJk5ferdxwA9}LA4TVf2odVy6B5bUm;PoMZ z{p{)y-n%%018nb@{zky?1{Ow4E5)1B{|M%?XhY-ExtzD-kAz z&9F6*_}q>+nwpra-@FO1(Bsbbu%$+&ww*(jTTaaDQZC}~e!6XZwt_eL9e&cq_P}xK z1lZo6CK#k1!Rmna8eAO3JLZmS28V46CFDRUhw&M2VrvCg`L^Ua7bK6AszvN-Kzx zg5unxWO^Vb@>&MUObjdD*+XrdUU#=47l^pnkK{--yE)ieImhbs7{<$|zzMchrA<&o jhClz;cD!Gj@X6yLz#MCkg-`VNzg(1FsL9pIn1=j6Z)M)q diff --git a/benchmarks/apps/public/index.html b/benchmarks/apps/public/index.html deleted file mode 100644 index 8fcc4387..00000000 --- a/benchmarks/apps/public/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - Hello World - - -

Hello, World!

- - diff --git a/benchmarks/apps/sinatra.ru b/benchmarks/apps/sinatra.ru deleted file mode 100644 index 7c585767..00000000 --- a/benchmarks/apps/sinatra.ru +++ /dev/null @@ -1,13 +0,0 @@ -require 'sinatra/base' - -class MyApp < Sinatra::Base - get '/get' do - "Hello, world!" - end - - post '/post' do - "You posted: #{request.body.read}" - end -end - -run MyApp diff --git a/benchmarks/apps/small.ru b/benchmarks/apps/small.ru deleted file mode 100644 index 26b7ba33..00000000 --- a/benchmarks/apps/small.ru +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -small = `ruby --help` # ~3kb - -run( - proc do - [200, {'content-type' => 'text/plain'}, [small]] - end -) diff --git a/benchmarks/apps/static.ru b/benchmarks/apps/static.ru deleted file mode 100644 index caf0ef7c..00000000 --- a/benchmarks/apps/static.ru +++ /dev/null @@ -1,12 +0,0 @@ -# Rack static handler. Fallback for servers that do not have built-in static asset serving. -# * Do have static asset serving: nginx, caddy, h2o, itsi, agoo, iodine -# * Don't have static asset serving: pume, falcon, unicorn -use Rack::Static, urls: ["/public"], root: File.expand_path("public", __dir__), index: 'index.html' - -# Simulate static assets and slower Ruby endpoints working together -endpoint_delay = 0.01 - -run lambda { |env| - sleep endpoint_delay - [200, { "Content-Type" => "text/plain" }, ["Busy Endpoint: #{env['PATH_INFO']}"]] -} diff --git a/benchmarks/apps/streaming_response_large.ru b/benchmarks/apps/streaming_response_large.ru deleted file mode 100644 index c25612bd..00000000 --- a/benchmarks/apps/streaming_response_large.ru +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -chunk_large = 100.times.map{|i| "data #{i}"}.join(', ') << "\n" - -run( - proc do |_env| - [ - 200, {'content-type' => 'text/plain'}, lambda do |stream| - 1000.times do - stream << chunk_large - stream.flush - end - stream.close - end - ] - end -) diff --git a/benchmarks/apps/streaming_response_small.ru b/benchmarks/apps/streaming_response_small.ru deleted file mode 100644 index 2ace4281..00000000 --- a/benchmarks/apps/streaming_response_small.ru +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -chunk_small = 10.times.map{|i| "data #{i}"}.join(', ') << "\n" - -run( - proc do |_env| - [ - 200, {'content-type' => 'text/plain'}, lambda do |stream| - 5.times do - stream << chunk_small - stream.flush - end - stream.close - end - ] - end -) diff --git a/benchmarks/flamegraph.svg b/benchmarks/flamegraph.svg deleted file mode 100644 index 7e900f87..00000000 --- a/benchmarks/flamegraph.svg +++ /dev/null @@ -1,491 +0,0 @@ -Flame Graph Reset ZoomSearch itsi_server.bundle`<tokio::task::coop::with_budget::ResetGuard as core::ops::drop::Drop>::drop (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::driver::Driver::park_timeout (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (1 samples, 0.01%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)libsystem_kernel.dylib`mach_absolute_time (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::wake (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::context::with_scheduler (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::Context::park (19 samples, 0.28%)itsi_server.bundle`tokio::runtime::time::Driver::park_internal (19 samples, 0.28%)itsi_server.bundle`tokio::runtime::io::driver::Driver::turn (18 samples, 0.26%)libsystem_kernel.dylib`kevent (16 samples, 0.24%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (2 samples, 0.03%)libsystem_c.dylib`clock_gettime (2 samples, 0.03%)libsystem_kernel.dylib`mach_absolute_time (1 samples, 0.01%)itsi_server.bundle`std::time::Instant::duration_since (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::context::with_scheduler (5 samples, 0.07%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::wake (10 samples, 0.15%)itsi_server.bundle`tokio::runtime::task::waker::wake_by_val (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::driver::Driver::turn (200 samples, 2.94%)it..libsystem_kernel.dylib`kevent (187 samples, 2.75%)li..itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::wake (1 samples, 0.01%)libsystem_c.dylib`DYLD-STUB$$mach_timebase_info (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (4 samples, 0.06%)libsystem_c.dylib`clock_gettime (4 samples, 0.06%)libsystem_kernel.dylib`mach_absolute_time (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::process (5 samples, 0.07%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::process_at_sharded_time (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::time::wheel::Wheel::poll (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::Context::park_yield (211 samples, 3.10%)its..itsi_server.bundle`tokio::runtime::time::Driver::park_internal (211 samples, 3.10%)its..itsi_server.bundle`tokio::runtime::time::wheel::level::Level::next_expiration (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::Core::next_task (2 samples, 0.03%)itsi_server.bundle`itsi_server::server::serve_strategy::acceptor::Acceptor::serve_connection::_{{closure}}::_{{closure}} (2 samples, 0.03%)itsi_server.bundle`<tokio::runtime::task::core::TaskIdGuard as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`<hyper::server::conn::http1::UpgradeableConnection<I,S> as core::future::future::Future>::poll (1 samples, 0.01%)itsi_server.bundle`<alloc::collections::vec_deque::iter::Iter<T> as core::iter::traits::iterator::Iterator>::fold (2 samples, 0.03%)itsi_server.bundle`<http_body_util::full::Full<D> as http_body::Body>::poll_frame (1 samples, 0.01%)itsi_server.bundle`<hyper::proto::h1::dispatch::Server<S,hyper::body::incoming::Incoming> as hyper::proto::h1::dispatch::Dispatch>::recv_msg (1 samples, 0.01%)itsi_server.bundle`__rdl_alloc (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::services::itsi_http_service::ItsiHttpService::handle_request::{{closure}}> (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::insert (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::maybe_notify (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::uri::Uri> (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_with_capacity (3 samples, 0.04%)itsi_server.bundle`<hyper::proto::h1::dispatch::Server<S,hyper::body::incoming::Incoming> as hyper::proto::h1::dispatch::Dispatch>::recv_msg (15 samples, 0.22%)itsi_server.bundle`http::request::Parts::new (4 samples, 0.06%)itsi_server.bundle`<hyper_util::rt::tokio::TokioSleep as core::future::future::Future>::poll (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after (6 samples, 0.09%)itsi_server.bundle`http::header::name::HdrName::from_bytes (8 samples, 0.12%)itsi_server.bundle`http::header::name::parse_hdr (4 samples, 0.06%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after::_{{closure}} (13 samples, 0.19%)itsi_server.bundle`http::header::name::parse_hdr (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after (5 samples, 0.07%)libsystem_malloc.dylib`szone_malloc_should_clear (5 samples, 0.07%)libsystem_malloc.dylib`small_malloc_should_clear (4 samples, 0.06%)libsystem_malloc.dylib`small_malloc_from_free_list (3 samples, 0.04%)libsystem_malloc.dylib`small_free_list_add_ptr (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libsystem_malloc.dylib`free_small (3 samples, 0.04%)itsi_server.bundle`<itsi_server::server::middleware_stack::middleware::Middleware as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after::_{{closure}} (37 samples, 0.54%)libsystem_platform.dylib`_platform_memmove (4 samples, 0.06%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$pthread_mutex_unlock (3 samples, 0.04%)itsi_server.bundle`__rdl_alloc (1 samples, 0.01%)itsi_server.bundle`event_listener::Event<T>::inner (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$pthread_mutex_lock (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::Inner<T>::notify (43 samples, 0.63%)itsi_server.bundle`event_listener::Task::wake (43 samples, 0.63%)itsi_server.bundle`parking::Inner::unpark (43 samples, 0.63%)libsystem_kernel.dylib`__psynch_cvsignal (43 samples, 0.63%)itsi_server.bundle`async_channel::Sender<T>::try_send (56 samples, 0.82%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::notify (52 samples, 0.76%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (7 samples, 0.10%)libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_slow (2 samples, 0.03%)libsystem_kernel.dylib`__psynch_mutexwait (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::build::{{closure}}> (4 samples, 0.06%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (1 samples, 0.01%)libsystem_malloc.dylib`free_small (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (3 samples, 0.04%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::server::size_limited_incoming::SizeLimitedIncoming<hyper::body::incoming::Incoming>> (1 samples, 0.01%)itsi_server.bundle`event_listener::Event<T>::inner (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (6 samples, 0.09%)itsi_server.bundle`http::response::Parts::new (1 samples, 0.01%)itsi_server.bundle`http_body_util::combinators::box_body::BoxBody<D,E>::new (9 samples, 0.13%)itsi_server.bundle`regex_automata::dfa::onepass::DFA::try_search_slots (2 samples, 0.03%)itsi_server.bundle`regex_automata::dfa::onepass::DFA::try_search_slots_imp (3 samples, 0.04%)itsi_server.bundle`DYLD-STUB$$bzero (1 samples, 0.01%)itsi_server.bundle`regex_automata::dfa::onepass::Cache::explicit_slots (1 samples, 0.01%)itsi_server.bundle`regex_automata::dfa::onepass::DFA::try_search_slots_imp (14 samples, 0.21%)itsi_server.bundle`<regex_automata::meta::strategy::Core as regex_automata::meta::strategy::Strategy>::search_slots (22 samples, 0.32%)itsi_server.bundle`regex_automata::meta::strategy::Core::search_slots_nofail (17 samples, 0.25%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (1 samples, 0.01%)itsi_server.bundle`regex_automata::meta::regex::Regex::create_captures (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`regex::regex::string::Regex::captures_at (40 samples, 0.59%)libsystem_platform.dylib`_platform_memset (4 samples, 0.06%)itsi_server.bundle`regex_automata::util::captures::Captures::all (1 samples, 0.01%)itsi_server.bundle`<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (3 samples, 0.04%)itsi_server.bundle`regex_automata::util::captures::Captures::get_group_by_name (4 samples, 0.06%)itsi_server.bundle`core::hash::BuildHasher::hash_one (1 samples, 0.01%)libsystem_c.dylib`DYLD-STUB$$mach_absolute_time (1 samples, 0.01%)libsystem_c.dylib`clock_gettime_nsec_np (1 samples, 0.01%)libsystem_kernel.dylib`mach_timebase_info (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (14 samples, 0.21%)libsystem_c.dylib`clock_gettime (14 samples, 0.21%)libsystem_kernel.dylib`mach_absolute_time (12 samples, 0.18%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (18 samples, 0.26%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libsystem_malloc.dylib`DYLD-STUB$$_platform_bzero (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (6 samples, 0.09%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (10 samples, 0.15%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (7 samples, 0.10%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before::_{{closure}} (302 samples, 4.44%)itsi_..libsystem_platform.dylib`_platform_memset (2 samples, 0.03%)libsystem_malloc.dylib`small_malloc_should_clear (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (2 samples, 0.03%)libsystem_malloc.dylib`small_free_list_add_ptr (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before (6 samples, 0.09%)libsystem_malloc.dylib`szone_malloc_should_clear (5 samples, 0.07%)libsystem_malloc.dylib`small_malloc_should_clear (3 samples, 0.04%)libsystem_malloc.dylib`small_malloc_from_free_list (3 samples, 0.04%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (5 samples, 0.07%)itsi_server.bundle`DYLD-STUB$$memcpy (3 samples, 0.04%)itsi_server.bundle`__rdl_alloc (2 samples, 0.03%)itsi_server.bundle`__rdl_dealloc (4 samples, 0.06%)itsi_server.bundle`async_channel::Sender<T>::try_send (1 samples, 0.01%)itsi_server.bundle`regex_automata::util::captures::Captures::get_group_by_name (1 samples, 0.01%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (3 samples, 0.04%)libsystem_kernel.dylib`__ulock_wake (1 samples, 0.01%)libsystem_malloc.dylib`_free (2 samples, 0.03%)libsystem_malloc.dylib`_malloc_zone_malloc (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (6 samples, 0.09%)libsystem_malloc.dylib`free_small (18 samples, 0.26%)libsystem_malloc.dylib`small_free_list_find_by_ptr (6 samples, 0.09%)libsystem_malloc.dylib`nanov2_malloc (6 samples, 0.09%)itsi_server.bundle`<itsi_server::server::middleware_stack::middleware::Middleware as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before::_{{closure}} (455 samples, 6.69%)itsi_serv..libsystem_platform.dylib`_platform_memmove (90 samples, 1.32%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::after::_{{closure}} (1 samples, 0.01%)itsi_server.bundle`<itsi_server::server::middleware_stack::middlewares::ruby_app::RubyApp as itsi_server::server::middleware_stack::middlewares::MiddlewareLayer>::before::_{{closure}} (1 samples, 0.01%)itsi_server.bundle`<rand_chacha::chacha::ChaCha12Core as rand_core::block::BlockRngCore>::generate (2 samples, 0.03%)itsi_server.bundle`<regex::regexset::bytes::SetMatchesIter as core::iter::traits::iterator::Iterator>::next (2 samples, 0.03%)itsi_server.bundle`<regex_automata::meta::strategy::Core as regex_automata::meta::strategy::Strategy>::which_overlapping_matches (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$memcpy (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::uri::Uri> (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<hyper_util::rt::tokio::TokioSleep> (1 samples, 0.01%)itsi_server.bundle`http::extensions::Extensions::remove (2 samples, 0.03%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_insert_entry (2 samples, 0.03%)itsi_server.bundle`alloc::raw_vec::finish_grow (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_realloc (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_pointer_size (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_allocate_outlined (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_realloc (8 samples, 0.12%)libsystem_platform.dylib`_platform_memset (2 samples, 0.03%)libsystem_malloc.dylib`_malloc_zone_realloc (17 samples, 0.25%)libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libsystem_malloc.dylib`_realloc (22 samples, 0.32%)libsystem_malloc.dylib`default_zone_realloc (2 samples, 0.03%)itsi_server.bundle`alloc::raw_vec::finish_grow (30 samples, 0.44%)libsystem_malloc.dylib`nanov2_size (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_grow (36 samples, 0.53%)libsystem_malloc.dylib`_realloc (4 samples, 0.06%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (43 samples, 0.63%)libsystem_malloc.dylib`nanov2_try_free_default (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::insert (51 samples, 0.75%)itsi_server.bundle`http::header::name::HdrName::from_static (50 samples, 0.73%)libsystem_platform.dylib`_platform_memmove (4 samples, 0.06%)itsi_server.bundle`http::header::name::StandardHeader::from_bytes (2 samples, 0.03%)itsi_server.bundle`http::header::name::HdrName::from_bytes (6 samples, 0.09%)itsi_server.bundle`http::header::name::parse_hdr (2 samples, 0.03%)libsystem_kernel.dylib`__commpage_gettimeofday_internal (1 samples, 0.01%)libsystem_c.dylib`gettimeofday (29 samples, 0.43%)libsystem_kernel.dylib`mach_absolute_time (28 samples, 0.41%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (31 samples, 0.46%)libsystem_c.dylib`clock_gettime (31 samples, 0.46%)libsystem_kernel.dylib`__commpage_gettimeofday_internal (1 samples, 0.01%)itsi_server.bundle`std::time::SystemTime::now (19 samples, 0.28%)itsi_server.bundle`hyper::common::date::update (52 samples, 0.76%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)itsi_server.bundle`<hyper::proto::h1::role::Server as hyper::proto::h1::Http1Transaction>::parse (2 samples, 0.03%)itsi_server.bundle`<hyper_util::rt::tokio::TokioTimer as hyper::rt::timer::Timer>::sleep_until (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)itsi_server.bundle`<tokio::runtime::time::entry::TimerEntry as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::reregister (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::inner (1 samples, 0.01%)itsi_server.bundle`<std::time::Instant as core::ops::arith::Add<core::time::Duration>>::add (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::driver::Handle::unpark (3 samples, 0.04%)libsystem_kernel.dylib`kevent (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::reregister (7 samples, 0.10%)itsi_server.bundle`tokio::runtime::time::wheel::Wheel::insert (2 samples, 0.03%)itsi_server.bundle`std::time::Instant::duration_since (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::sub_timespec (1 samples, 0.01%)itsi_server.bundle`<tokio::time::sleep::Sleep as core::future::future::Future>::poll (20 samples, 0.29%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::poll_elapsed (18 samples, 0.26%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::reset (10 samples, 0.15%)itsi_server.bundle`tokio::runtime::time::entry::TimerEntry::inner (6 samples, 0.09%)libdyld.dylib`tlv_get_addr (4 samples, 0.06%)itsi_server.bundle`core::ptr::drop_in_place<hyper_util::rt::tokio::TokioSleep> (9 samples, 0.13%)itsi_server.bundle`tokio::runtime::time::_<impl tokio::runtime::time::handle::Handle>::clear_entry (8 samples, 0.12%)itsi_server.bundle`tokio::runtime::time::wheel::Wheel::remove (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::BytesMut::advance_unchecked (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::BytesMut::split_to (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_allocate_outlined (4 samples, 0.06%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_clone (2 samples, 0.03%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (2 samples, 0.03%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_append2 (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve (10 samples, 0.15%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (1 samples, 0.01%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (1 samples, 0.01%)itsi_server.bundle`http::header::name::StandardHeader::from_bytes (2 samples, 0.03%)itsi_server.bundle`core::str::converts::from_utf8 (1 samples, 0.01%)itsi_server.bundle`httparse::Request::parse_with_config_and_uninit_headers (12 samples, 0.18%)itsi_server.bundle`httparse::parse_headers_iter_uninit (4 samples, 0.06%)itsi_server.bundle`httparse::Request::parse_with_uninit_headers (13 samples, 0.19%)itsi_server.bundle`httparse::parse_version (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`<hyper::proto::h1::role::Server as hyper::proto::h1::Http1Transaction>::parse (61 samples, 0.90%)libsystem_malloc.dylib`nanov2_malloc (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_append2 (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve (2 samples, 0.03%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (1 samples, 0.01%)itsi_server.bundle`http::method::Method::from_bytes (1 samples, 0.01%)itsi_server.bundle`http::uri::Uri::from_shared (1 samples, 0.01%)itsi_server.bundle`<&mio::net::tcp::stream::TcpStream as std::io::Read>::read (1,081 samples, 15.88%)itsi_server.bundle`<&mio..libsystem_kernel.dylib`__recvfrom (1,080 samples, 15.87%)libsystem_kernel.dylib`_..itsi_server.bundle`<&std::net::tcp::TcpStream as std::io::Read>::read (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (12 samples, 0.18%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (4 samples, 0.06%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (1 samples, 0.01%)itsi_server.bundle`tokio::net::tcp::stream::TcpStream::poll_read_priv (1,100 samples, 16.16%)itsi_server.bundle`tokio:..libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (1,106 samples, 16.25%)itsi_server.bundle`hyper:..itsi_server.bundle`<hyper_util::common::rewind::Rewind<T> as hyper::rt::io::Read>::poll_read (1,105 samples, 16.24%)itsi_server.bundle`<hyper..itsi_server.bundle`<hyper_util::rt::tokio::TokioIo<T> as hyper::rt::io::Read>::poll_read (1,104 samples, 16.22%)itsi_server.bundle`<hyper..itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (3 samples, 0.04%)itsi_server.bundle`hyper::proto::h1::io::ReadStrategy::record (2 samples, 0.03%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::parse (1,187 samples, 17.44%)itsi_server.bundle`hyper::p..libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (1 samples, 0.01%)libsystem_c.dylib`clock_gettime (9 samples, 0.13%)libsystem_kernel.dylib`mach_absolute_time (9 samples, 0.13%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (10 samples, 0.15%)libsystem_c.dylib`clock_gettime_nsec_np (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (3 samples, 0.04%)libsystem_malloc.dylib`DYLD-STUB$$_platform_bzero (2 samples, 0.03%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_malloc (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::poll_read_head (1,254 samples, 18.42%)itsi_server.bundle`hyper::pro..libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`<hyper_util::common::rewind::Rewind<T> as hyper::rt::io::Read>::poll_read (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (4 samples, 0.06%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (2 samples, 0.03%)itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (4 samples, 0.06%)itsi_server.bundle`<hyper_util::common::rewind::Rewind<T> as hyper::rt::io::Read>::poll_read (15 samples, 0.22%)itsi_server.bundle`<hyper_util::rt::tokio::TokioIo<T> as hyper::rt::io::Read>::poll_read (15 samples, 0.22%)itsi_server.bundle`tokio::net::tcp::stream::TcpStream::poll_read_priv (15 samples, 0.22%)libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<hyper_util::rt::tokio::TokioIo<T> as hyper::rt::io::Read>::poll_read (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (31 samples, 0.46%)libsystem_malloc.dylib`small_free_list_remove_ptr (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::BytesMut::reserve_inner (38 samples, 0.56%)libsystem_malloc.dylib`szone_malloc_should_clear (38 samples, 0.56%)libsystem_malloc.dylib`small_malloc_should_clear (37 samples, 0.54%)libsystem_malloc.dylib`small_malloc_from_free_list (34 samples, 0.50%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::poll_read_keep_alive (60 samples, 0.88%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (57 samples, 0.84%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::WriteBuf<B>::buffer (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::Cursor<alloc::vec::Vec<u8>>::maybe_unshift (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::encode::Encoder::encode_and_end (4 samples, 0.06%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::write_body_and_end (6 samples, 0.09%)itsi_server.bundle`hyper::proto::h1::io::WriteBuf<B>::buffer (1 samples, 0.01%)itsi_server.bundle`<http::header::map::Drain<T> as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`<http::header::map::Drain<T> as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::shared_drop (8 samples, 0.12%)libsystem_malloc.dylib`_nanov2_free (7 samples, 0.10%)itsi_server.bundle`hyper::common::date::extend (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (3 samples, 0.04%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (6 samples, 0.09%)itsi_server.bundle`<hyper::proto::h1::role::Server as hyper::proto::h1::Http1Transaction>::encode (73 samples, 1.07%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`http::header::name::StandardHeader::as_str (4 samples, 0.06%)libsystem_malloc.dylib`_free (12 samples, 0.18%)itsi_server.bundle`hyper::proto::h1::conn::Conn<I,B,T>::write_head (99 samples, 1.45%)libsystem_platform.dylib`_platform_memmove (8 samples, 0.12%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::parse (1 samples, 0.01%)itsi_server.bundle`<&mio::net::tcp::stream::TcpStream as std::io::Write>::write (1,064 samples, 15.63%)itsi_server.bundle`<&mio..libsystem_kernel.dylib`__sendto (1,063 samples, 15.62%)libsystem_kernel.dylib`_..itsi_server.bundle`<&std::net::tcp::TcpStream as std::io::Write>::write (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (2 samples, 0.03%)itsi_server.bundle`tokio::net::tcp::stream::TcpStream::poll_write_priv (1,078 samples, 15.84%)itsi_server.bundle`tokio..itsi_server.bundle`tokio::runtime::io::scheduled_io::ScheduledIo::poll_readiness (5 samples, 0.07%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_flush (1,084 samples, 15.93%)itsi_server.bundle`hyper..itsi_server.bundle`tokio::runtime::io::registration::Registration::poll_ready (1 samples, 0.01%)itsi_server.bundle`hyper::proto::h1::io::Buffered<T,B>::poll_read_from_io (1 samples, 0.01%)itsi_server.bundle`<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (3 samples, 0.04%)itsi_server.bundle`<regex_automata::meta::strategy::Core as regex_automata::meta::strategy::Strategy>::which_overlapping_matches (18 samples, 0.26%)itsi_server.bundle`regex_automata::meta::wrappers::HybridEngine::try_which_overlapping_matches (18 samples, 0.26%)itsi_server.bundle`regex_automata::hybrid::search::find_overlapping_fwd (7 samples, 0.10%)itsi_server.bundle`http::header::map::HeaderMap<T>::get (2 samples, 0.03%)itsi_server.bundle`regex_automata::util::search::PatternSet::new (4 samples, 0.06%)libsystem_malloc.dylib`nanov2_calloc (4 samples, 0.06%)libsystem_malloc.dylib`_malloc_zone_calloc (1 samples, 0.01%)itsi_server.bundle`itsi_server::server::middleware_stack::MiddlewareSet::stack_for (39 samples, 0.57%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)itsi_server.bundle`DYLD-STUB$$clock_gettime (1 samples, 0.01%)libsystem_c.dylib`DYLD-STUB$$mach_timebase_info (4 samples, 0.06%)libsystem_kernel.dylib`mach_absolute_time (20 samples, 0.29%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (29 samples, 0.43%)libsystem_c.dylib`clock_gettime (29 samples, 0.43%)libsystem_kernel.dylib`mach_timebase_info (3 samples, 0.04%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 0.01%)libsystem_malloc.dylib`set_tiny_meta_header_in_use (4 samples, 0.06%)libsystem_malloc.dylib`_tiny_check_and_zero_inline_meta_from_freelist (1 samples, 0.01%)itsi_server.bundle`itsi_server::services::itsi_http_service::HttpRequestContext::new (49 samples, 0.72%)libsystem_malloc.dylib`szone_malloc_should_clear (18 samples, 0.26%)libsystem_malloc.dylib`tiny_malloc_should_clear (16 samples, 0.24%)libsystem_malloc.dylib`tiny_malloc_from_free_list (10 samples, 0.15%)libsystem_malloc.dylib`tiny_free_list_add_ptr (2 samples, 0.03%)itsi_server.bundle`regex_automata::util::search::PatternSet::new (2 samples, 0.03%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (9 samples, 0.13%)libsystem_malloc.dylib`DYLD-STUB$$_platform_bzero (1 samples, 0.01%)libsystem_malloc.dylib`_free (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)libsystem_malloc.dylib`_szone_free (2 samples, 0.03%)libsystem_malloc.dylib`free (1 samples, 0.01%)libsystem_malloc.dylib`get_tiny_previous_free_msize (2 samples, 0.03%)libsystem_malloc.dylib`tiny_free_list_add_ptr (4 samples, 0.06%)libsystem_malloc.dylib`free_tiny (21 samples, 0.31%)libsystem_malloc.dylib`tiny_free_no_lock (11 samples, 0.16%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`rack_get_thread_index (1 samples, 0.01%)libsystem_malloc.dylib`set_tiny_meta_header_in_use (10 samples, 0.15%)libsystem_malloc.dylib`_tiny_check_and_zero_inline_meta_from_freelist (2 samples, 0.03%)libsystem_malloc.dylib`szone_malloc_should_clear (27 samples, 0.40%)libsystem_malloc.dylib`tiny_malloc_should_clear (26 samples, 0.38%)libsystem_malloc.dylib`tiny_malloc_from_free_list (14 samples, 0.21%)libsystem_malloc.dylib`tiny_free_list_add_ptr (4 samples, 0.06%)libsystem_platform.dylib`_platform_memmove (11 samples, 0.16%)itsi_server.bundle`hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_catch (3,404 samples, 50.01%)itsi_server.bundle`hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_catchlibsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_malloc.dylib`_free (3 samples, 0.04%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)libsystem_malloc.dylib`malloc (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (2 samples, 0.03%)itsi_server.bundle`<hyper::server::conn::http1::UpgradeableConnection<I,S> as core::future::future::Future>::poll (3,452 samples, 50.72%)itsi_server.bundle`<hyper::server::conn::http1::UpgradeableConnection<I,S> as core:..libsystem_platform.dylib`_platform_memmove (29 samples, 0.43%)itsi_server.bundle`<hyper_util::server::conn::auto::UpgradeableConnection<I,S,E> as core::future::future::Future>::poll (3,460 samples, 50.84%)itsi_server.bundle`<hyper_util::server::conn::auto::UpgradeableConnection<I,S,E> as ..itsi_server.bundle`hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_catch (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::context::thread_rng_n (1 samples, 0.01%)itsi_server.bundle`<tokio::task::coop::RestoreOnPending as core::ops::drop::Drop>::drop (6 samples, 0.09%)itsi_server.bundle`<tokio::task::coop::Coop<F> as core::future::future::Future>::poll (19 samples, 0.28%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (11 samples, 0.16%)itsi_server.bundle`tokio::sync::notify::Notified::poll_notified (2 samples, 0.03%)itsi_server.bundle`tokio::sync::watch::Receiver<T>::changed::_{{closure}} (26 samples, 0.38%)libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<core::future::poll_fn::PollFn<F> as core::future::future::Future>::poll (3,505 samples, 51.50%)itsi_server.bundle`<core::future::poll_fn::PollFn<F> as core::future::future::Future>..libdyld.dylib`tlv_get_addr (10 samples, 0.15%)itsi_server.bundle`<hyper_util::server::conn::auto::UpgradeableConnection<I,S,E> as core::future::future::Future>::poll (2 samples, 0.03%)itsi_server.bundle`tokio::sync::watch::Receiver<T>::changed::_{{closure}} (1 samples, 0.01%)itsi_server.bundle`itsi_server::server::serve_strategy::acceptor::Acceptor::serve_connection::_{{closure}}::_{{closure}} (3,517 samples, 51.67%)itsi_server.bundle`itsi_server::server::serve_strategy::acceptor::Acceptor::serve_con..libdyld.dylib`tlv_get_addr (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::task::core::Core<T,S>::poll (3,527 samples, 51.82%)itsi_server.bundle`tokio::runtime::task::core::Core<T,S>::polllibdyld.dylib`tlv_get_addr (6 samples, 0.09%)itsi_server.bundle`tokio::runtime::task::core::TaskIdGuard::enter (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::task::harness::Harness<T,S>::poll (3,535 samples, 51.94%)itsi_server.bundle`tokio::runtime::task::harness::Harness<T,S>::pollitsi_server.bundle`tokio::runtime::task::state::State::transition_to_idle (3 samples, 0.04%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::CoreGuard::block_on (3,777 samples, 55.50%)itsi_server.bundle`tokio::runtime::scheduler::current_thread::CoreGuard::block_onlibdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`tokio::runtime::scheduler::defer::Defer::is_empty (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::task::raw::RawTask::poll (1 samples, 0.01%)dyld`start (3,784 samples, 55.60%)dyld`startruby`main (3,784 samples, 55.60%)ruby`mainlibruby.3.4.dylib`ruby_run_node (3,784 samples, 55.60%)libruby.3.4.dylib`ruby_run_nodelibruby.3.4.dylib`rb_ec_exec_node (3,784 samples, 55.60%)libruby.3.4.dylib`rb_ec_exec_nodelibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libruby.3.4.dylib`rb_f_load (3,784 samples, 55.60%)libruby.3.4.dylib`rb_f_loadlibruby.3.4.dylib`rb_load_internal (3,784 samples, 55.60%)libruby.3.4.dylib`rb_load_internallibruby.3.4.dylib`load_iseq_eval (3,784 samples, 55.60%)libruby.3.4.dylib`load_iseq_evallibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libruby.3.4.dylib`rb_f_load (3,784 samples, 55.60%)libruby.3.4.dylib`rb_f_loadlibruby.3.4.dylib`rb_load_internal (3,784 samples, 55.60%)libruby.3.4.dylib`rb_load_internallibruby.3.4.dylib`load_iseq_eval (3,784 samples, 55.60%)libruby.3.4.dylib`load_iseq_evallibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libruby.3.4.dylib`rb_f_load (3,784 samples, 55.60%)libruby.3.4.dylib`rb_f_loadlibruby.3.4.dylib`rb_load_internal (3,784 samples, 55.60%)libruby.3.4.dylib`rb_load_internallibruby.3.4.dylib`load_iseq_eval (3,784 samples, 55.60%)libruby.3.4.dylib`load_iseq_evallibruby.3.4.dylib`rb_vm_exec (3,784 samples, 55.60%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_exec_core (3,784 samples, 55.60%)libruby.3.4.dylib`vm_exec_corelibruby.3.4.dylib`vm_call_cfunc_with_frame_ (3,784 samples, 55.60%)libruby.3.4.dylib`vm_call_cfunc_with_frame_itsi_server.bundle`itsi_server::init::anon (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::init::anonitsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::start (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::startitsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::build_and_run_strategy (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::ruby_types::itsi_server::ItsiServer::build_and_run_strategylibruby.3.4.dylib`rb_nogvl (3,784 samples, 55.60%)libruby.3.4.dylib`rb_nogvlitsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampoline (3,784 samples, 55.60%)itsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampolineitsi_server.bundle`itsi_server::server::serve_strategy::single_mode::SingleMode::run (3,784 samples, 55.60%)itsi_server.bundle`itsi_server::server::serve_strategy::single_mode::SingleMode::runitsi_server.bundle`tokio::runtime::runtime::Runtime::block_on (3,784 samples, 55.60%)itsi_server.bundle`tokio::runtime::runtime::Runtime::block_onitsi_server.bundle`tokio::runtime::context::runtime::enter_runtime (3,784 samples, 55.60%)itsi_server.bundle`tokio::runtime::context::runtime::enter_runtimelibdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<event_listener_strategy::Blocking as event_listener_strategy::Strategy>::poll (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<event_listener::InnerListener<(),alloc::sync::Arc<event_listener::Inner<()>>>> (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::unlock (1 samples, 0.01%)itsi_server.bundle`event_listener::TaskRef::into_task (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::Inner<T>::remove (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::with_inner (4 samples, 0.06%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (1 samples, 0.01%)libsystem_pthread.dylib`pthread_mutex_lock (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (1 samples, 0.01%)libsystem_kernel.dylib`__psynch_cvwait (75 samples, 1.10%)itsi_server.bundle`<event_listener_strategy::Blocking as event_listener_strategy::Strategy>::poll (88 samples, 1.29%)itsi_server.bundle`parking::Inner::park (81 samples, 1.19%)libsystem_pthread.dylib`_pthread_cond_wait (1 samples, 0.01%)libsystem_pthread.dylib`pthread_testcancel (1 samples, 0.01%)itsi_server.bundle`async_channel::Receiver<T>::try_recv (3 samples, 0.04%)libsystem_pthread.dylib`pthread_mutex_unlock (1 samples, 0.01%)itsi_server.bundle`event_listener::Event<T>::listen (1 samples, 0.01%)libsystem_pthread.dylib`pthread_mutex_unlock (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::with_inner (1 samples, 0.01%)itsi_server.bundle`<magnus::value::LazyId as core::ops::deref::Deref>::deref (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::Inner<T>::notify (7 samples, 0.10%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::notify (4 samples, 0.06%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::lock (3 samples, 0.04%)libsystem_pthread.dylib`pthread_mutex_lock (2 samples, 0.03%)itsi_server.bundle`std::sys::pal::unix::sync::mutex::Mutex::unlock (20 samples, 0.29%)itsi_server.bundle`async_channel::Receiver<T>::try_recv (36 samples, 0.53%)libsystem_pthread.dylib`pthread_mutex_unlock (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (1 samples, 0.01%)itsi_server.bundle`event_listener::sys::_<impl event_listener::Inner<T>>::notify (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$clock_gettime (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (12 samples, 0.18%)libsystem_c.dylib`clock_gettime (12 samples, 0.18%)libsystem_c.dylib`gettimeofday (12 samples, 0.18%)libsystem_kernel.dylib`mach_absolute_time (12 samples, 0.18%)itsi_server.bundle`std::time::SystemTime::duration_since (1 samples, 0.01%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::sub_timespec (1 samples, 0.01%)itsi_server.bundle`std::time::SystemTime::now (4 samples, 0.06%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_funcallv (1 samples, 0.01%)itsi_server.bundle`magnus::api::Ruby::get (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)libruby.3.4.dylib`rb_current_ec (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (8 samples, 0.12%)libruby.3.4.dylib`rb_current_ec (11 samples, 0.16%)libruby.3.4.dylib`CALLER_SETUP_ARG (1 samples, 0.01%)libruby.3.4.dylib`rb_ec_ary_new_from_values (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_aset (5 samples, 0.07%)libruby.3.4.dylib`rb_hash_bulk_insert (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_opt_getconstant_path (4 samples, 0.06%)libruby.3.4.dylib`vm_call_cfunc_with_frame_ (10 samples, 0.15%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start (1 samples, 0.01%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start_0params_0locals (1 samples, 0.01%)libruby.3.4.dylib`vm_caller_setup_arg_block (1 samples, 0.01%)itsi_server.bundle`itsi_server::init::anon (2 samples, 0.03%)libruby.3.4.dylib`DYLD-STUB$$memset_pattern16 (3 samples, 0.04%)libruby.3.4.dylib`ary_memcpy0 (1 samples, 0.01%)libruby.3.4.dylib`call_cfunc_0 (10 samples, 0.15%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)libruby.3.4.dylib`obj_method (1 samples, 0.01%)libruby.3.4.dylib`ractor_safe_call_cfunc_m1 (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_pop (2 samples, 0.03%)libruby.3.4.dylib`ary_ensure_room_for_push (2 samples, 0.03%)libruby.3.4.dylib`rb_ary_push (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`ary_memcpy0 (4 samples, 0.06%)libruby.3.4.dylib`rb_gc_writebarrier (3 samples, 0.04%)libruby.3.4.dylib`rb_ec_ary_new_from_values (10 samples, 0.15%)libruby.3.4.dylib`newobj_of (5 samples, 0.07%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_free (1 samples, 0.01%)itsi_server.bundle`__rdl_dealloc (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)itsi_server.bundle`<itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes as core::ops::drop::Drop>::drop (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::services::itsi_http_service::RequestContextInner> (2 samples, 0.03%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (6 samples, 0.09%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes> (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (2 samples, 0.03%)libsystem_kernel.dylib`__ulock_wait2 (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (2 samples, 0.03%)libsystem_malloc.dylib`small_free_scan_madvise_free (9 samples, 0.13%)libsystem_kernel.dylib`madvise (8 samples, 0.12%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (24 samples, 0.35%)libsystem_malloc.dylib`free_small (21 samples, 0.31%)libsystem_malloc.dylib`small_madvise_free_range_no_lock (5 samples, 0.07%)libsystem_kernel.dylib`madvise (4 samples, 0.06%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (4 samples, 0.06%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (31 samples, 0.46%)libsystem_malloc.dylib`_szone_free (2 samples, 0.03%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (36 samples, 0.53%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (36 samples, 0.53%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (36 samples, 0.53%)libsystem_malloc.dylib`nanov2_madvise_block (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_madvise_block_locked (2 samples, 0.03%)libsystem_kernel.dylib`madvise (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (47 samples, 0.69%)libsystem_platform.dylib`_platform_memset (2 samples, 0.03%)libsystem_malloc.dylib`_free (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (3 samples, 0.04%)libsystem_malloc.dylib`free_tiny (18 samples, 0.26%)libsystem_malloc.dylib`tiny_free_no_lock (16 samples, 0.24%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (6 samples, 0.09%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (82 samples, 1.20%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (85 samples, 1.25%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (106 samples, 1.56%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (11 samples, 0.16%)libruby.3.4.dylib`rb_str_free (3 samples, 0.04%)libruby.3.4.dylib`gc_sweep_plane (137 samples, 2.01%)l..libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (2 samples, 0.03%)libruby.3.4.dylib`gc_sweep_step (142 samples, 2.09%)l..libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (3 samples, 0.04%)libruby.3.4.dylib`gc_continue (144 samples, 2.12%)l..libsystem_kernel.dylib`getrusage (2 samples, 0.03%)libruby.3.4.dylib`each_location (2 samples, 0.03%)libruby.3.4.dylib`gc_mark_maybe_internal (2 samples, 0.03%)libsystem_c.dylib`bsearch (1 samples, 0.01%)libruby.3.4.dylib`cont_mark (3 samples, 0.04%)libruby.3.4.dylib`rb_execution_context_mark (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_mark_machine_context (3 samples, 0.04%)libruby.3.4.dylib`gc_mark_maybe_internal (1 samples, 0.01%)libruby.3.4.dylib`gc_marks_rest (4 samples, 0.06%)libruby.3.4.dylib`thread_mark (1 samples, 0.01%)libruby.3.4.dylib`rb_execution_context_mark (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_vm_stack_values (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_plane (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::services::itsi_http_service::RequestContextInner> (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (1 samples, 0.01%)libsystem_malloc.dylib`free_small (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_find_by_ptr (1 samples, 0.01%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (3 samples, 0.04%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (3 samples, 0.04%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (4 samples, 0.06%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (4 samples, 0.06%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (5 samples, 0.07%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (11 samples, 0.16%)libsystem_malloc.dylib`free_tiny (3 samples, 0.04%)libsystem_malloc.dylib`tiny_free_no_lock (2 samples, 0.03%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (12 samples, 0.18%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (1 samples, 0.01%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (22 samples, 0.32%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (5 samples, 0.07%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_plane (62 samples, 0.91%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_step (67 samples, 0.98%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (2 samples, 0.03%)libruby.3.4.dylib`gc_sweep (70 samples, 1.03%)libsystem_kernel.dylib`getrusage (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_children (2 samples, 0.03%)libruby.3.4.dylib`rb_id_table_foreach_values (2 samples, 0.03%)libruby.3.4.dylib`mark_const_table_i (2 samples, 0.03%)libruby.3.4.dylib`gc_mark_maybe_internal (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_roots (5 samples, 0.07%)libruby.3.4.dylib`rb_vm_mark (4 samples, 0.06%)libruby.3.4.dylib`rb_id_table_foreach_values (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_mark_vm_stack_values (3 samples, 0.04%)libruby.3.4.dylib`gc_mark (2 samples, 0.03%)libruby.3.4.dylib`newobj_of (231 samples, 3.39%)lib..libruby.3.4.dylib`newobj_cache_miss (230 samples, 3.38%)lib..libruby.3.4.dylib`gc_start (86 samples, 1.26%)libruby.3.4.dylib`rb_st_foreach (1 samples, 0.01%)libruby.3.4.dylib`st_general_foreach (1 samples, 0.01%)libruby.3.4.dylib`gc_mark_tbl_no_pin_i (1 samples, 0.01%)libruby.3.4.dylib`gc_mark (1 samples, 0.01%)libruby.3.4.dylib`rb_ivar_defined (4 samples, 0.06%)libruby.3.4.dylib`rb_shape_get_iv_index (2 samples, 0.03%)libruby.3.4.dylib`rb_ec_str_resurrect (241 samples, 3.54%)libr..libruby.3.4.dylib`rb_shape_obj_too_complex (2 samples, 0.03%)libruby.3.4.dylib`rb_gc_writebarrier (2 samples, 0.03%)libruby.3.4.dylib`rb_st_update (2 samples, 0.03%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`gc_writebarrier_generational (3 samples, 0.04%)libruby.3.4.dylib`rb_any_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (26 samples, 0.38%)libruby.3.4.dylib`gc_writebarrier_generational (9 samples, 0.13%)libruby.3.4.dylib`any_hash (13 samples, 0.19%)libruby.3.4.dylib`rb_str_hash (3 samples, 0.04%)libruby.3.4.dylib`hash_aset_str_insert (4 samples, 0.06%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_st_update (78 samples, 1.15%)libruby.3.4.dylib`tbl_update_modify (30 samples, 0.44%)libruby.3.4.dylib`rb_hash_aset (148 samples, 2.17%)l..libruby.3.4.dylib`tbl_update (130 samples, 1.91%)l..libruby.3.4.dylib`tbl_update_modify (7 samples, 0.10%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`ar_insert (5 samples, 0.07%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_bulk_insert (10 samples, 0.15%)libruby.3.4.dylib`rb_gc_writebarrier (3 samples, 0.04%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_new_with_size (4 samples, 0.06%)libruby.3.4.dylib`newobj_of (2 samples, 0.03%)libruby.3.4.dylib`rb_int_to_s (2 samples, 0.03%)libruby.3.4.dylib`rb_ivar_defined (2 samples, 0.03%)libruby.3.4.dylib`rb_obj_class (10 samples, 0.15%)libruby.3.4.dylib`rb_str_equal (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_opt_getconstant_path (8 samples, 0.12%)libruby.3.4.dylib`rb_wb_protected_newobj_of (2 samples, 0.03%)libruby.3.4.dylib`tbl_update (1 samples, 0.01%)itsi_server.bundle`<core::result::Result<T,E> as magnus::method::private::ReturnValue>::into_return_value (1 samples, 0.01%)itsi_server.bundle`<magnus::integer::Integer as magnus::try_convert::TryConvert>::try_convert (1 samples, 0.01%)itsi_server.bundle`<magnus::r_string::RString as magnus::try_convert::TryConvert>::try_convert (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_utf8_str_new (3 samples, 0.04%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes> (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (9 samples, 0.13%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (9 samples, 0.13%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (8 samples, 0.12%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (8 samples, 0.12%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (8 samples, 0.12%)libsystem_malloc.dylib`free_small (8 samples, 0.12%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (3 samples, 0.04%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (10 samples, 0.15%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_malloc.dylib`_free (2 samples, 0.03%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (2 samples, 0.03%)libsystem_malloc.dylib`free_tiny (7 samples, 0.10%)libsystem_malloc.dylib`tiny_free_no_lock (5 samples, 0.07%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (2 samples, 0.03%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (22 samples, 0.32%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (23 samples, 0.34%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_free (1 samples, 0.01%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (29 samples, 0.43%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (5 samples, 0.07%)libruby.3.4.dylib`gc_sweep_plane (43 samples, 0.63%)libsystem_malloc.dylib`_nanov2_free (2 samples, 0.03%)libruby.3.4.dylib`newobj_of (46 samples, 0.68%)libruby.3.4.dylib`newobj_cache_miss (44 samples, 0.65%)libruby.3.4.dylib`gc_continue (44 samples, 0.65%)libruby.3.4.dylib`gc_sweep_step (44 samples, 0.65%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (1 samples, 0.01%)libruby.3.4.dylib`typed_data_alloc (2 samples, 0.03%)libruby.3.4.dylib`rb_get_alloc_func (1 samples, 0.01%)itsi_server.bundle`<core::result::Result<T,E> as magnus::method::private::ReturnValue>::into_return_value (50 samples, 0.73%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (3 samples, 0.04%)itsi_server.bundle`DYLD-STUB$$rb_protect (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_utf8_str_new (1 samples, 0.01%)itsi_server.bundle`core::str::_<impl str>::trim_end_matches (1 samples, 0.01%)itsi_server.bundle`core::str::pattern::StrSearcher::new (2 samples, 0.03%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve (4 samples, 0.06%)itsi_server.bundle`http::header::value::HeaderValue::to_str (2 samples, 0.03%)itsi_server.bundle`itsi_server::ruby_types::itsi_body_proxy::big_bytes::BigBytes::as_value (1 samples, 0.01%)itsi_server.bundle`itsi_server::ruby_types::itsi_body_proxy::ItsiBody::into_value (3 samples, 0.04%)libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libruby.3.4.dylib`rb_enc_raw_set (2 samples, 0.03%)libruby.3.4.dylib`rb_gc_size_allocatable_p (2 samples, 0.03%)itsi_server.bundle`magnus::error::protect::call (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`invoke_block_from_c_bh (1 samples, 0.01%)libruby.3.4.dylib`CALLER_SETUP_ARG (1 samples, 0.01%)libruby.3.4.dylib`rb_st_lookup (3 samples, 0.04%)libruby.3.4.dylib`any_hash (1 samples, 0.01%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`ruby_sip_hash13 (2 samples, 0.03%)libruby.3.4.dylib`rb_hash_aref (13 samples, 0.19%)libruby.3.4.dylib`rb_st_lookup (9 samples, 0.13%)libsystem_platform.dylib`_platform_memcmp (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`any_hash (1 samples, 0.01%)libruby.3.4.dylib`ruby_sip_hash13 (1 samples, 0.01%)libruby.3.4.dylib`hash_aset_str_insert (1 samples, 0.01%)libruby.3.4.dylib`rb_any_cmp (1 samples, 0.01%)libruby.3.4.dylib`rb_str_comparable (1 samples, 0.01%)libruby.3.4.dylib`str_do_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_st_update (8 samples, 0.12%)libsystem_platform.dylib`_platform_memcmp (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_aset (12 samples, 0.18%)libruby.3.4.dylib`tbl_update (11 samples, 0.16%)libruby.3.4.dylib`rb_str_hash_cmp (1 samples, 0.01%)libruby.3.4.dylib`ruby_sip_hash13 (4 samples, 0.06%)libruby.3.4.dylib`rb_vm_exec (49 samples, 0.72%)libruby.3.4.dylib`vm_exec_core (46 samples, 0.68%)libruby.3.4.dylib`rb_st_lookup (10 samples, 0.15%)libruby.3.4.dylib`str_do_hash (2 samples, 0.03%)libruby.3.4.dylib`rb_enc_dummy_p (1 samples, 0.01%)libruby.3.4.dylib`vm_exec_core (2 samples, 0.03%)libruby.3.4.dylib`invoke_block_from_c_bh (58 samples, 0.85%)libsystem_platform.dylib`_setjmp (1 samples, 0.01%)libruby.3.4.dylib`rb_protect (65 samples, 0.96%)libruby.3.4.dylib`rb_yield_values_kw (59 samples, 0.87%)libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)libruby.3.4.dylib`newobj_of (5 samples, 0.07%)libruby.3.4.dylib`str_enc_new (12 samples, 0.18%)libruby.3.4.dylib`rb_current_ec (2 samples, 0.03%)libsystem_platform.dylib`_platform_memmove (10 samples, 0.15%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest::each_header (95 samples, 1.40%)libsystem_platform.dylib`_setjmp (1 samples, 0.01%)itsi_server.bundle`core::str::_<impl str>::trim_end_matches (5 samples, 0.07%)itsi_server.bundle`core::str::pattern::StrSearcher::new (3 samples, 0.04%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest::path (6 samples, 0.09%)itsi_server.bundle`core::str::pattern::StrSearcher::new (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::BodyState> (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::context::with_scheduler (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::task::raw::schedule (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$kevent (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::scheduler::inject::Inject<T>::push (2 samples, 0.03%)itsi_server.bundle`parking_lot::raw_mutex::RawMutex::lock_slow (2 samples, 0.03%)libsystem_kernel.dylib`swtch_pri (1 samples, 0.01%)itsi_server.bundle`tokio::runtime::context::with_scheduler (287 samples, 4.22%)itsi_..libsystem_kernel.dylib`kevent (284 samples, 4.17%)libsy..itsi_server.bundle`tokio::runtime::task::waker::wake_by_val (290 samples, 4.26%)itsi_..libdyld.dylib`tlv_get_addr (3 samples, 0.04%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::send_and_close (326 samples, 4.79%)itsi_s..itsi_server.bundle`tokio::sync::notify::Notify::notify_with_strategy (29 samples, 0.43%)itsi_server.bundle`tokio::sync::notify::notify_locked (28 samples, 0.41%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::set_status (2 samples, 0.03%)itsi_server.bundle`magnus::error::protect::call (7 samples, 0.10%)libruby.3.4.dylib`DYLD-STUB$$_setjmp (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_check_typeddata (3 samples, 0.04%)itsi_server.bundle`magnus::error::protect::call (16 samples, 0.24%)libruby.3.4.dylib`rb_check_typeddata (9 samples, 0.13%)libdyld.dylib`tlv_get_addr (11 samples, 0.16%)libruby.3.4.dylib`rb_check_typeddata (4 samples, 0.06%)libruby.3.4.dylib`rb_protect (65 samples, 0.96%)libruby.3.4.dylib`rb_current_ec (2 samples, 0.03%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (99 samples, 1.45%)libsystem_platform.dylib`_setjmp (10 samples, 0.15%)libruby.3.4.dylib`newobj_of (7 samples, 0.10%)libruby.3.4.dylib`rb_enc_raw_set (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_size_allocatable_p (2 samples, 0.03%)libruby.3.4.dylib`rb_protect (1 samples, 0.01%)libruby.3.4.dylib`rb_utf8_encoding (1 samples, 0.01%)libruby.3.4.dylib`rb_utf8_str_new (6 samples, 0.09%)libruby.3.4.dylib`rb_wb_protected_newobj_of (3 samples, 0.04%)libdyld.dylib`tlv_get_addr (5 samples, 0.07%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (1 samples, 0.01%)libsystem_malloc.dylib`small_free_scan_madvise_free (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (4 samples, 0.06%)libsystem_malloc.dylib`free_small (4 samples, 0.06%)libsystem_malloc.dylib`small_madvise_free_range_no_lock (1 samples, 0.01%)libsystem_kernel.dylib`madvise (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (6 samples, 0.09%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (6 samples, 0.09%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (5 samples, 0.07%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.07%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_madvise_block_locked (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (9 samples, 0.13%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_kernel.dylib`__ulock_wait2 (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 0.01%)libsystem_malloc.dylib`free_tiny (10 samples, 0.15%)libsystem_malloc.dylib`tiny_free_no_lock (8 samples, 0.12%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (3 samples, 0.04%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (21 samples, 0.31%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (2 samples, 0.03%)libsystem_malloc.dylib`_free (2 samples, 0.03%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (26 samples, 0.38%)libsystem_malloc.dylib`_szone_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (32 samples, 0.47%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (11 samples, 0.16%)libruby.3.4.dylib`gc_sweep_plane (56 samples, 0.82%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libruby.3.4.dylib`newobj_of (64 samples, 0.94%)libruby.3.4.dylib`newobj_cache_miss (57 samples, 0.84%)libruby.3.4.dylib`gc_continue (57 samples, 0.84%)libruby.3.4.dylib`gc_sweep_step (57 samples, 0.84%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (1 samples, 0.01%)libruby.3.4.dylib`str_enc_new (100 samples, 1.47%)libruby.3.4.dylib`rb_current_ec (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (8 samples, 0.12%)libsystem_malloc.dylib`nanov2_malloc (10 samples, 0.15%)itsi_server.bundle`itsi_server::init::anon (754 samples, 11.08%)itsi_server.bund..libsystem_platform.dylib`_platform_memmove (4 samples, 0.06%)itsi_server.bundle`magnus::r_string::_<impl magnus::api::Ruby>::str_new (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (5 samples, 0.07%)libruby.3.4.dylib`ary_new (2 samples, 0.03%)libruby.3.4.dylib`basic_obj_respond_to (4 samples, 0.06%)libruby.3.4.dylib`call_cfunc_0 (1 samples, 0.01%)libruby.3.4.dylib`call_cfunc_1 (1 samples, 0.01%)libruby.3.4.dylib`mnew_internal (1 samples, 0.01%)libruby.3.4.dylib`callable_method_entry_or_negative (3 samples, 0.04%)libruby.3.4.dylib`callable_method_entry_refinements (7 samples, 0.10%)libruby.3.4.dylib`rb_id_table_lookup (2 samples, 0.03%)libruby.3.4.dylib`DYLD-STUB$$bzero (1 samples, 0.01%)libruby.3.4.dylib`rb_get_alloc_func (1 samples, 0.01%)libruby.3.4.dylib`rb_data_typed_object_zalloc (4 samples, 0.06%)libruby.3.4.dylib`typed_data_alloc (2 samples, 0.03%)libruby.3.4.dylib`mnew_internal (7 samples, 0.10%)libruby.3.4.dylib`rb_gc_writebarrier (2 samples, 0.03%)libruby.3.4.dylib`obj_method (16 samples, 0.24%)libruby.3.4.dylib`rb_gc_writebarrier (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (4 samples, 0.06%)libruby.3.4.dylib`callable_method_entry_or_negative (3 samples, 0.04%)libruby.3.4.dylib`callable_method_entry_or_negative (2 samples, 0.03%)libruby.3.4.dylib`basic_obj_respond_to (26 samples, 0.38%)libruby.3.4.dylib`callable_method_entry_refinements (16 samples, 0.24%)libruby.3.4.dylib`rb_id_table_lookup (8 samples, 0.12%)libruby.3.4.dylib`callable_method_entry_or_negative (1 samples, 0.01%)libruby.3.4.dylib`obj_respond_to (41 samples, 0.60%)libruby.3.4.dylib`rb_check_id (7 samples, 0.10%)libruby.3.4.dylib`invoke_block_from_c_bh (8 samples, 0.12%)libruby.3.4.dylib`rb_block_given_p (2 samples, 0.03%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_frame_block_handler (3 samples, 0.04%)libdyld.dylib`tlv_get_addr (11 samples, 0.16%)libruby.3.4.dylib`DYLD-STUB$$memcpy (2 samples, 0.03%)libruby.3.4.dylib`CALLER_SETUP_ARG (6 samples, 0.09%)libruby.3.4.dylib`DYLD-STUB$$_setjmp (3 samples, 0.04%)libruby.3.4.dylib`rb_hash_aset (3 samples, 0.04%)libruby.3.4.dylib`rb_st_lookup (12 samples, 0.18%)libruby.3.4.dylib`rb_gvar_get (1 samples, 0.01%)libruby.3.4.dylib`rb_global_entry (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`gc_writebarrier_generational (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_writebarrier (6 samples, 0.09%)libruby.3.4.dylib`gc_writebarrier_generational (2 samples, 0.03%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`hash_aset_str_insert (3 samples, 0.04%)libruby.3.4.dylib`rb_str_hash (1 samples, 0.01%)libruby.3.4.dylib`rb_st_update (25 samples, 0.37%)libruby.3.4.dylib`tbl_update_modify (2 samples, 0.03%)libruby.3.4.dylib`rb_hash_aset (47 samples, 0.69%)libruby.3.4.dylib`tbl_update (41 samples, 0.60%)libruby.3.4.dylib`tbl_update_modify (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_stlike_lookup (4 samples, 0.06%)libruby.3.4.dylib`rb_iseq_cdhash_hash (2 samples, 0.03%)libruby.3.4.dylib`rb_obj_class (2 samples, 0.03%)libruby.3.4.dylib`rb_iseq_cdhash_hash (3 samples, 0.04%)libruby.3.4.dylib`rb_st_lookup (56 samples, 0.82%)libruby.3.4.dylib`rb_str_hash (7 samples, 0.10%)libruby.3.4.dylib`rb_str_hash (3 samples, 0.04%)libruby.3.4.dylib`rb_vm_exec (462 samples, 6.79%)libruby.3..libruby.3.4.dylib`vm_exec_core (411 samples, 6.04%)libruby...libruby.3.4.dylib`tbl_update (1 samples, 0.01%)libruby.3.4.dylib`vm_callee_setup_block_arg (18 samples, 0.26%)libruby.3.4.dylib`CALLER_SETUP_ARG (3 samples, 0.04%)libruby.3.4.dylib`vm_exec_core (15 samples, 0.22%)libruby.3.4.dylib`invoke_block_from_c_bh (564 samples, 8.29%)libruby.3.4...libsystem_platform.dylib`_setjmp (16 samples, 0.24%)libruby.3.4.dylib`rb_current_ec (5 samples, 0.07%)libruby.3.4.dylib`rb_ec_stack_check (3 samples, 0.04%)libruby.3.4.dylib`rb_vm_exec (5 samples, 0.07%)libruby.3.4.dylib`vm_callee_setup_block_arg (4 samples, 0.06%)libruby.3.4.dylib`rb_ary_each (639 samples, 9.39%)libruby.3.4.d..libruby.3.4.dylib`rb_yield (621 samples, 9.12%)libruby.3.4.d..libsystem_platform.dylib`_platform_memmove (13 samples, 0.19%)libruby.3.4.dylib`rb_ary_pop (3 samples, 0.04%)libruby.3.4.dylib`rb_block_given_p (1 samples, 0.01%)libruby.3.4.dylib`rb_block_pair_yield_optimizable (1 samples, 0.01%)libruby.3.4.dylib`rb_check_id (1 samples, 0.01%)libruby.3.4.dylib`ruby_sip_hash13 (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_delete_m (3 samples, 0.04%)libruby.3.4.dylib`rb_hash_stlike_delete (3 samples, 0.04%)libruby.3.4.dylib`any_hash (3 samples, 0.04%)libruby.3.4.dylib`str_do_hash (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_block_pair_yield_optimizable (17 samples, 0.25%)libruby.3.4.dylib`rb_vm_block_min_max_arity (16 samples, 0.24%)libruby.3.4.dylib`rb_ensure (4 samples, 0.06%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`invoke_block_from_c_bh (1 samples, 0.01%)libruby.3.4.dylib`rb_ary_new_from_args (2 samples, 0.03%)libruby.3.4.dylib`ary_new (1 samples, 0.01%)libruby.3.4.dylib`rb_assoc_new (3 samples, 0.04%)libruby.3.4.dylib`rb_gc_writebarrier (1 samples, 0.01%)libruby.3.4.dylib`DYLD-STUB$$memcpy (2 samples, 0.03%)libruby.3.4.dylib`DYLD-STUB$$_setjmp (1 samples, 0.01%)itsi_server.bundle`<magnus::r_string::RString as magnus::try_convert::TryConvert>::try_convert (2 samples, 0.03%)itsi_server.bundle`DYLD-STUB$$memcpy (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$rb_protect (1 samples, 0.01%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (2 samples, 0.03%)itsi_server.bundle`__rdl_dealloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::promotable_even_drop (4 samples, 0.06%)itsi_server.bundle`core::alloc::layout::Layout::is_size_align_valid (2 samples, 0.03%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::Bytes::copy_from_slice (1 samples, 0.01%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`http::header::name::HeaderName::from_bytes (14 samples, 0.21%)libsystem_malloc.dylib`nanov2_malloc (5 samples, 0.07%)itsi_server.bundle`http::header::name::StandardHeader::from_bytes (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$malloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::shallow_clone_vec (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_from_block (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`<http::header::name::HeaderName as core::convert::From<&http::header::name::HeaderName>>::from (8 samples, 0.12%)libsystem_malloc.dylib`nanov2_malloc (3 samples, 0.04%)libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 0.01%)itsi_server.bundle`bytes::bytes::Bytes::slice (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_malloc (3 samples, 0.04%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_insert_entry (1 samples, 0.01%)itsi_server.bundle`http::header::map::HeaderMap<T>::try_reserve_one (1 samples, 0.01%)itsi_server.bundle`http::header::map::hash_elem_using (10 samples, 0.15%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::insert_header (47 samples, 0.69%)libsystem_platform.dylib`_platform_memmove (8 samples, 0.12%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::add_header (74 samples, 1.09%)libsystem_malloc.dylib`_nanov2_free (3 samples, 0.04%)itsi_server.bundle`itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse::insert_header (1 samples, 0.01%)itsi_server.bundle`magnus::r_string::RString::to_bytes (3 samples, 0.04%)itsi_server.bundle`<bytes::bytes::Bytes as core::convert::From<alloc::vec::Vec<u8>>>::from (1 samples, 0.01%)itsi_server.bundle`magnus::error::protect::call (3 samples, 0.04%)libruby.3.4.dylib`rb_protect (10 samples, 0.15%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (12 samples, 0.18%)libsystem_platform.dylib`_setjmp (2 samples, 0.03%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libsystem_malloc.dylib`_malloc_zone_malloc (4 samples, 0.06%)itsi_server.bundle`magnus::method::Method2::call_handle_error (110 samples, 1.62%)libsystem_malloc.dylib`nanov2_malloc (6 samples, 0.09%)libruby.3.4.dylib`vm_exec_core (148 samples, 2.17%)l..libruby.3.4.dylib`vm_call_cfunc_with_frame_ (115 samples, 1.69%)itsi_server.bundle`itsi_server::init::anon (112 samples, 1.65%)itsi_server.bundle`magnus::typed_data::_<impl magnus::try_convert::TryConvert for &T>::try_convert (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_exec (152 samples, 2.23%)l..libruby.3.4.dylib`vm_opt_length (1 samples, 0.01%)libruby.3.4.dylib`ary_new (1 samples, 0.01%)libruby.3.4.dylib`ary_memcpy0 (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (2 samples, 0.03%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)libsystem_malloc.dylib`small_free_list_add_ptr (2 samples, 0.03%)libsystem_malloc.dylib`small_free_list_remove_ptr_no_clear (2 samples, 0.03%)libsystem_malloc.dylib`small_free_scan_madvise_free (2 samples, 0.03%)libsystem_kernel.dylib`madvise (2 samples, 0.03%)itsi_server.bundle`bytes::bytes_mut::shared_v_drop (15 samples, 0.22%)libsystem_malloc.dylib`free_small (13 samples, 0.19%)libsystem_malloc.dylib`small_madvise_free_range_no_lock (3 samples, 0.04%)libsystem_kernel.dylib`madvise (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (4 samples, 0.06%)itsi_server.bundle`<alloc::vec::Vec<T,A> as core::ops::drop::Drop>::drop (20 samples, 0.29%)libsystem_malloc.dylib`free_small (1 samples, 0.01%)itsi_server.bundle`DYLD-STUB$$free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)itsi_server.bundle`alloc::sync::Arc<T,A>::drop_slow (23 samples, 0.34%)itsi_server.bundle`core::ptr::drop_in_place<http::request::Parts> (23 samples, 0.34%)itsi_server.bundle`core::ptr::drop_in_place<http::header::map::HeaderMap> (23 samples, 0.34%)libsystem_malloc.dylib`free (1 samples, 0.01%)libsystem_malloc.dylib`_nanov2_free (5 samples, 0.07%)libsystem_malloc.dylib`nanov2_madvise_block (2 samples, 0.03%)libsystem_malloc.dylib`nanov2_madvise_block_locked (2 samples, 0.03%)libsystem_kernel.dylib`madvise (2 samples, 0.03%)libsystem_platform.dylib`__bzero (1 samples, 0.01%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (34 samples, 0.50%)libsystem_platform.dylib`_platform_memset (3 samples, 0.04%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libsystem_malloc.dylib`tiny_free_list_add_ptr (3 samples, 0.04%)libsystem_malloc.dylib`free_tiny (17 samples, 0.25%)libsystem_malloc.dylib`tiny_free_no_lock (16 samples, 0.24%)libsystem_malloc.dylib`tiny_free_list_remove_ptr (5 samples, 0.07%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_request::ItsiHttpRequest> (55 samples, 0.81%)libsystem_platform.dylib`_platform_memset (1 samples, 0.01%)itsi_server.bundle`magnus::typed_data::DataTypeFunctions::extern_free (57 samples, 0.84%)itsi_server.bundle`core::ptr::drop_in_place<itsi_server::ruby_types::itsi_http_response::ItsiHttpResponse> (2 samples, 0.03%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (78 samples, 1.15%)libsystem_malloc.dylib`_nanov2_free (7 samples, 0.10%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (13 samples, 0.19%)libruby.3.4.dylib`rb_str_free (1 samples, 0.01%)libsystem_malloc.dylib`_free (1 samples, 0.01%)libruby.3.4.dylib`gc_sweep_plane (106 samples, 1.56%)libsystem_malloc.dylib`_nanov2_free (1 samples, 0.01%)libruby.3.4.dylib`rb_gc_obj_free (3 samples, 0.04%)libruby.3.4.dylib`gc_sweep_step (110 samples, 1.62%)libruby.3.4.dylib`rb_gc_obj_free_vm_weak_references (1 samples, 0.01%)libruby.3.4.dylib`setup_parameters_complex (122 samples, 1.79%)l..libruby.3.4.dylib`rb_ary_new_from_values (118 samples, 1.73%)libruby.3.4.dylib`ary_new (115 samples, 1.69%)libruby.3.4.dylib`newobj_of (113 samples, 1.66%)libruby.3.4.dylib`newobj_cache_miss (112 samples, 1.65%)libruby.3.4.dylib`gc_continue (112 samples, 1.65%)libsystem_kernel.dylib`getrusage (2 samples, 0.03%)libruby.3.4.dylib`vm_callee_setup_block_arg (1 samples, 0.01%)libruby.3.4.dylib`invoke_block_from_c_bh (280 samples, 4.11%)libr..libsystem_platform.dylib`_setjmp (2 samples, 0.03%)libruby.3.4.dylib`rb_vm_exec (2 samples, 0.03%)libruby.3.4.dylib`setup_parameters_complex (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_each_pair (317 samples, 4.66%)libru..libruby.3.4.dylib`rb_hash_foreach (296 samples, 4.35%)libru..libruby.3.4.dylib`rb_ensure (295 samples, 4.33%)libru..libruby.3.4.dylib`hash_foreach_call (292 samples, 4.29%)libru..libruby.3.4.dylib`each_pair_i (290 samples, 4.26%)libru..libruby.3.4.dylib`rb_yield (286 samples, 4.20%)libru..libsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_foreach (1 samples, 0.01%)libruby.3.4.dylib`ary_new (3 samples, 0.04%)libruby.3.4.dylib`rb_current_ec (1 samples, 0.01%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)libruby.3.4.dylib`rb_hash_keys (5 samples, 0.07%)libruby.3.4.dylib`rb_ary_set_len (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (1 samples, 0.01%)libruby.3.4.dylib`rb_int2str (6 samples, 0.09%)libruby.3.4.dylib`str_enc_new (4 samples, 0.06%)libruby.3.4.dylib`newobj_of (2 samples, 0.03%)libruby.3.4.dylib`rb_st_keys (10 samples, 0.15%)libruby.3.4.dylib`rb_str_equal (3 samples, 0.04%)libruby.3.4.dylib`rb_utf8_str_new (3 samples, 0.04%)libruby.3.4.dylib`rb_yield (3 samples, 0.04%)libruby.3.4.dylib`vm_call_cfunc_with_frame_ (1,889 samples, 27.75%)libruby.3.4.dylib`vm_call_cfunc_with_frame_libsystem_platform.dylib`_platform_memcmp (2 samples, 0.03%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start (2 samples, 0.03%)libruby.3.4.dylib`vm_call_iseq_setup_normal_0start_0params_4locals (1 samples, 0.01%)libruby.3.4.dylib`vm_call_ivar (3 samples, 0.04%)libruby.3.4.dylib`CALLER_SETUP_ARG (1 samples, 0.01%)libruby.3.4.dylib`vm_invoke_iseq_block (6 samples, 0.09%)libruby.3.4.dylib`vm_callee_setup_block_arg (2 samples, 0.03%)libsystem_platform.dylib`_platform_memmove (3 samples, 0.04%)libruby.3.4.dylib`vm_exec_core (2,699 samples, 39.66%)libruby.3.4.dylib`vm_exec_corelibsystem_platform.dylib`_platform_memset_pattern16 (3 samples, 0.04%)libruby.3.4.dylib`vm_invoke_block_opt_call (1 samples, 0.01%)libruby.3.4.dylib`vm_invoke_iseq_block (1 samples, 0.01%)libruby.3.4.dylib`rb_vm_exec (2,729 samples, 40.10%)libruby.3.4.dylib`rb_vm_execlibruby.3.4.dylib`vm_opt_length (1 samples, 0.01%)libruby.3.4.dylib`vm_exec_core (1 samples, 0.01%)libruby.3.4.dylib`vm_invoke_proc (2,736 samples, 40.20%)libruby.3.4.dylib`vm_invoke_proclibsystem_platform.dylib`_setjmp (1 samples, 0.01%)libruby.3.4.dylib`rb_funcallv_scope (2,782 samples, 40.88%)libruby.3.4.dylib`rb_funcallv_scopelibruby.3.4.dylib`vm_call0_body (2,744 samples, 40.32%)libruby.3.4.dylib`vm_call0_bodylibsystem_platform.dylib`_platform_memset_pattern16 (1 samples, 0.01%)itsi_server.bundle`magnus::error::protect::call (2,791 samples, 41.01%)itsi_server.bundle`magnus::error::protect::calllibruby.3.4.dylib`vm_call0_body (1 samples, 0.01%)libdyld.dylib`tlv_get_addr (2 samples, 0.03%)libruby.3.4.dylib`rb_protect (2,814 samples, 41.35%)libruby.3.4.dylib`rb_protectlibruby.3.4.dylib`rb_funcallv_scope (2 samples, 0.03%)libruby.3.4.dylib`typed_data_alloc (2 samples, 0.03%)libsystem_c.dylib`clock_gettime (1 samples, 0.01%)itsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker::process_one (2,842 samples, 41.76%)itsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker:..libsystem_malloc.dylib`nanov2_allocate_outlined (4 samples, 0.06%)libsystem_malloc.dylib`nanov2_find_block_and_allocate (3 samples, 0.04%)itsi_server.bundle`std::sys::pal::unix::time::Timespec::now (2 samples, 0.03%)itsi_server.bundle`std::time::SystemTime::now (1 samples, 0.01%)libruby.3.4.dylib`newobj_of (1 samples, 0.01%)libruby.3.4.dylib`rb_data_typed_object_wrap (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 0.01%)libsystem_malloc.dylib`nanov2_malloc (4 samples, 0.06%)itsi_server.bundle`itsi_rb_helpers::call_with_gvl::trampoline (2,904 samples, 42.67%)itsi_server.bundle`itsi_rb_helpers::call_with_gvl::trampolinelibsystem_platform.dylib`_platform_memmove (7 samples, 0.10%)libruby.3.4.dylib`blocking_region_end (1 samples, 0.01%)libsystem_pthread.dylib`pthread_mutex_unlock (1 samples, 0.01%)libruby.3.4.dylib`rb_thread_call_with_gvl (2,922 samples, 42.93%)libruby.3.4.dylib`rb_thread_call_with_gvllibsystem_platform.dylib`_platform_memmove (17 samples, 0.25%)libsystem_kernel.dylib`sigprocmask (4 samples, 0.06%)itsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampoline (3,020 samples, 44.37%)itsi_server.bundle`itsi_rb_helpers::call_without_gvl::trampolinelibsystem_platform.dylib`_platform_memmove (1 samples, 0.01%)all (6,806 samples, 100%)libsystem_pthread.dylib`thread_start (3,022 samples, 44.40%)libsystem_pthread.dylib`thread_startlibsystem_pthread.dylib`_pthread_start (3,022 samples, 44.40%)libsystem_pthread.dylib`_pthread_startlibruby.3.4.dylib`nt_start (3,022 samples, 44.40%)libruby.3.4.dylib`nt_startlibruby.3.4.dylib`thread_start_func_2 (3,022 samples, 44.40%)libruby.3.4.dylib`thread_start_func_2itsi_server.bundle`itsi_rb_helpers::create_ruby_thread::trampoline (3,022 samples, 44.40%)itsi_server.bundle`itsi_rb_helpers::create_ruby_thread::trampolineitsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker::accept_loop (3,022 samples, 44.40%)itsi_server.bundle`itsi_server::server::thread_worker::ThreadWorker::acce..libruby.3.4.dylib`rb_nogvl (3,022 samples, 44.40%)libruby.3.4.dylib`rb_nogvllibsystem_platform.dylib`_platform_memmove (1 samples, 0.01%) \ No newline at end of file diff --git a/benchmarks/grpc_server.rb b/benchmarks/grpc_server.rb deleted file mode 100644 index 747292ed..00000000 --- a/benchmarks/grpc_server.rb +++ /dev/null @@ -1,12 +0,0 @@ -require_relative "./apps/echo_service/echo_service" - -def main - thread_count = (ENV['THREADS'] || 30).to_i - - server = GRPC::RpcServer.new(pool_size: thread_count) - server.add_http2_port("0.0.0.0:#{ENV['PORT'] || 50051}", :this_port_is_insecure) - server.handle(EchoServiceImpl) - server.run_till_terminated -end - -main diff --git a/benchmarks/lib/benchmark_case.rb b/benchmarks/lib/benchmark_case.rb deleted file mode 100644 index 7dee832d..00000000 --- a/benchmarks/lib/benchmark_case.rb +++ /dev/null @@ -1,59 +0,0 @@ -# lib/benchmark_case.rb -class BenchmarkCase - - # Give the server some time to warm up before we start measuring. - RACK_BENCH_WARMUP_DURATION_SECONDS = ENV.fetch('RACK_BENCH_WARMUP_DURATION_SECONDS', 1).to_i - - # A default 3 is relatively low, but allows us to get through the entire test - # suite quickly. For a more robust benchmark, this should be higher. - RACK_BENCH_DURATION_SECONDS = ENV.fetch('RACK_BENCH_DURATION_SECONDS', 3).to_i - - %i[ - name description app method data path - workers threads warmup_duration concurrency_levels - duration https parallel_requests nonblocking - requires use_yjit static_files_root grpc call proto - ].each do |accessor| - define_method(accessor) do |value = self| - if value.eql?(self) - instance_variable_get("@#{accessor}") - else - instance_variable_set("@#{accessor}", value) - end - end - end - - def initialize(name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength - @name = name - @description = '' - @method = 'GET' - @data = nil - @path = '/' - @proto = "" - @call = "" - @workers = 1 - @threads = [1, 5, 10, 20] - @workers = [1, 2, Etc.nprocessors].uniq - @duration = RACK_BENCH_DURATION_SECONDS - @warmup_duration = RACK_BENCH_WARMUP_DURATION_SECONDS - @concurrency_levels = [10, 50, 100, 250] - @static_files_root = nil - @https = false - @grpc = false - @parallel_requests = 16 - @nonblocking = false - @requires = %i[ruby] - @use_yjit = true - yield self if block_given? - end - - def method_missing(name, *args, **kwargs, &blk) - return super unless name.end_with?('?') - - @requires.include?(name[0...-1].to_sym) - end - - def respond_to_missing?(name, include_private = false) - @requires.include?(name[0...-1].to_sym) || super - end -end diff --git a/benchmarks/lib/server.rb b/benchmarks/lib/server.rb deleted file mode 100644 index c29d6242..00000000 --- a/benchmarks/lib/server.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'sys/proctable' -include Sys - -class Server # rubocop:disable Style/Documentation - attr_reader :name, :cmd_template, :http2, :supports - - ALL = [] - - def initialize(name, cmd_template, http2: false, supports: [], **custom_args) - @name = name - @cmd_template = cmd_template - @http2 = http2 - @supports = supports - @custom_args = custom_args - ALL << self - end - - def supports?(feature) - @supports.include?(feature) - end - - def run!(server_config_file_path, test_case, threads, workers) - port = free_port - - @builder_args = { - base: "bundle exec #{@name}", - config: server_config_file_path, - scheme: test_case.https ? 'https' : 'http', - host: '0.0.0.0', - app_path: test_case.app&.path, - workers: workers, - threads: threads, - www: test_case.static_files_root, - port: port - } - - @builder_args.merge!(@custom_args.transform_values{ |v| v[test_case, @builder_args] }) - - cmd = cmd_template % @builder_args.to_h.transform_keys(&:to_sym) - puts Paint["\nStarting server:", :green, :bold] - puts Paint[cmd, :green] - - @pid = Process.spawn( - {"RUBY_YJIT_ENABLE" => "#{test_case.use_yjit}", "PORT" => port.to_s, "THREADS" => threads.to_s}, - cmd, - out: '/dev/null', - err: '/dev/null', - pgroup: true - ) - - begin - wait_for_port(port) - puts Paint["pid: #{@pid}", :yellow] - paths, methods, data = [Array(test_case.path), Array(test_case.method), Array(test_case.data)] - combinations = [paths, methods, data].map(&:length).max.times.map do |i| - [ - "http://127.0.0.1:#{port}/#{paths[[i, paths.size - 1].min].gsub(/^\/+/,'')}", - methods[[i, methods.size - 1].min], - data[[i, data.size - 1].min], - ] - end - result = yield combinations - rescue StandardError => e - puts Paint["Server failed to start: #{e.message}. `#{cmd}`", :red, :bold] - puts e - return false - end - - - return result - rescue StandardError => e - binding.b # rubocop:disable Lint/Debugger - ensure - stop! - end - - def exec_found? - return @exec_found if defined?(@exec_found) - - @exec_found = system("which #{name} > /dev/null") || system("ls #{name}") - end - - def rss - @pid ? ProcTable.ps(pid: @pid).rss.to_i : 0 - rescue - 0 - end - - def stop! - Process.kill('TERM', @pid) - Process.wait(@pid) - end - -end diff --git a/benchmarks/lib/util.rb b/benchmarks/lib/util.rb deleted file mode 100644 index bdd46cae..00000000 --- a/benchmarks/lib/util.rb +++ /dev/null @@ -1,45 +0,0 @@ - -def wait_for_port(port, timeout: 5) - Timeout.timeout(timeout) do - loop do - TCPSocket.new('127.0.0.1', port).close - break - rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH - sleep 0.1 - end - end -rescue Timeout::Error - raise "Unable to connect to localhost:#{port}" -end - - -def free_port - server = TCPServer.new("0.0.0.0", 0) - port = server.addr[1] - server.close - port -end - -def parse_wrk_output(output) - metrics = {} - - if output =~ /Requests\/sec:\s+([\d.]+)/ - metrics[:requests_per_sec] = $1.to_f - end - - if output =~ /Transfer\/sec:\s+([\d.]+)([KMG]?B)/ - metrics[:transfer_per_sec] = { - value: $1.to_f, - unit: $2 - } - end - - if output =~ /Latency\s+([\d.]+)([a-z]+)/ - metrics[:latency_avg] = { - value: $1.to_f, - unit: $2 - } - end - - metrics -end diff --git a/benchmarks/rack_bench.rb b/benchmarks/rack_bench.rb deleted file mode 100644 index ba85c7b8..00000000 --- a/benchmarks/rack_bench.rb +++ /dev/null @@ -1,296 +0,0 @@ -require_relative 'lib/util' -require_relative 'lib/benchmark_case' -require_relative 'servers' -require 'etc' -require 'debug' -require 'json' -require 'fileutils' -require 'open3' -require 'socket' -require 'timeout' -require 'time' -require 'paint' -require 'uri' - -$LOAD_PATH.unshift(File.dirname(__FILE__)) -$interrupt_signal = Queue.new - -trap("INT") do - exit(0) if $interrupt_signal.length > 0 - puts "\n[DEBUG] Caught Ctrl+C. Pausing before next iteration. Press again to exit." - $interrupt_signal << true -end - -def interrupted? - $interrupt_signal.pop(true) -rescue - false -end - -def run_benchmark( - test_name, test_case, server_config_file_path, server, - threads:, workers:, http2: -) - server.run!(server_config_file_path, test_case, threads, workers) do |workloads| - puts Paint["\n=== Running #{test_name} on #{server.name}", :cyan, :bold] - - warmup_cmds = workloads.map do |url, method, data| - if test_case.grpc? - "ghz --duration-stop=ignore --cpus=2 -z #{test_case.warmup_duration}s -c50 --call #{test_case.call} --stream-call-count=5 -d #{data} --insecure #{URI(url).host}:#{URI(url).port} --proto #{test_case.proto} -O json" - else - "oha --no-tui -z #{test_case.warmup_duration}s -c50 #{url} -m #{method} #{data ? %{-d "#{data}"} : ""} -j #{http2 ? '--http2 --insecure' : ''} #{http2 ? "-p #{test_case.parallel_requests}" : ''}" # rubocop:disable Layout/LineLength - end - end - - test_command_sets = test_case.concurrency_levels.map do |level| - [ - level, - workloads.map do |url, method, data| - if test_case.grpc? - "ghz --duration-stop=ignore --cpus=2 -z #{test_case.duration}s -c#{level} --call #{test_case.call} --stream-call-count=5 -d #{data} --insecure #{URI(url).host}:#{URI(url).port} --proto #{test_case.proto} -O json" - else - "oha #{test_case.nonblocking ? '' : '-w'} --no-tui -z #{test_case.duration}s -c#{level} #{url} -m #{method} #{data ? %{-d "#{data}"} : ""} -j #{http2 ? '--http2 --insecure' : ''} #{http2 ? "-p #{test_case.parallel_requests}" : ''}" # rubocop:disable Layout/LineLength - end - end - ] - end - - puts Paint["\nWarming up with:", :yellow, :bold] - warmup_cmds.each do |warmup_cmd| - puts Paint[warmup_cmd, :yellow] - `#{warmup_cmd}` - end - - test_command_sets.map do |concurrency, cmds| - puts Paint["\n[#{test_case.name}] #{server.name}(#{workers}x#{threads}). Concurrency #{concurrency}. #{http2 ? "HTTP/2.0": "HTTP/1.1"}", :blue, :bold] - result_outputs = cmds.map do |cmd| - puts Paint[cmd, :blue] - Thread.new{ `#{cmd}` } - end.map(&:value) - - result_outputs.map! do |result_output| - if test_case.grpc? - result_json = JSON.parse(result_output) - success_rate = ((result_json["statusCodeDistribution"]&.[]("OK") / result_json["count"].to_f).round(4) rescue 0) - - gross_rps = (result_json["rps"]).round(4) - failure_rate = (1 - success_rate) - net_rps = (gross_rps * (1 - failure_rate)).round(2) - failure = failure_rate.*(100.0).round(2) - - error_distribution = result_json['errorDistribution'] - - if failure.positive? && error_distribution - puts Paint["• Error breakdown:", :red, :bold] - max_key_length = error_distribution.keys.map(&:length).max - error_distribution.each do |err, count| - padded_key = err.ljust(max_key_length) - puts Paint[" #{padded_key} : #{count}", :red] - end - end - - puts Paint % ['RPS: %{rps}. Errors: %{failure}%', :bold, :cyan, - {rps: [net_rps.to_s, :green], failure: [failure, failure.positive? ? :red : :green]}] - { - "successRate" => success_rate, - "total" => (result_json["count"]), - "slowest" => (result_json["slowest"]).round(4), - "fastest" => (result_json["fastest"] == Float::INFINITY ? 0.0 : result_json["fastest"]).round(4), - "average" => result_json["average"].round(4), - "requestsPerSec" => net_rps, - "grossRequestsPerSec" => gross_rps, - "errorDistribution" => error_distribution, - "p95_latency" => result_json["latencyDistribution"].find{|ld| ld["percentage"] == 95 }["latency"] / (1000.0 ** 2) - } - else - result_json = JSON.parse(result_output) - summary = result_json['summary'] - p95_latency = result_json['latencyPercentiles']['p95'] - gross_rps = summary['requestsPerSec'].round(2) - failure_rate = (1 - summary['successRate'].to_f) - net_rps = (gross_rps * (1 - failure_rate)).round(2) - failure = failure_rate.*(100.0).round(2) - - puts Paint % ['RPS: %{rps}. Errors: %{failure}%', :bold, :cyan, - {rps: [net_rps.to_s, :green], failure: [failure, failure.positive? ? :red : :green]}] - - error_distribution = result_json['errorDistribution'] - summary['errorDistribution'] = error_distribution - summary["requestsPerSec"] => net_rps - summary["grossRequestsPerSec"] => gross_rps - - if failure.positive? && error_distribution - puts Paint["• Error breakdown:", :red, :bold] - max_key_length = error_distribution.keys.map(&:length).max - error_distribution.each do |err, count| - padded_key = err.ljust(max_key_length) - puts Paint[" #{padded_key} : #{count}", :red] - end - end - - summary.transform_values!{|v| v.is_a?(Numeric) ? v.round(2) : v } - summary['p95_latency'] = p95_latency - summary - end - end - - binding.b if interrupted? # rubocop:disable Lint/Debugger - - { - server: server.name, - test_case: test_name, - threads: threads, - workers: workers, - http2: http2, - concurrency: concurrency, - **(workers == 1 ? {rss_mb: (server.rss / (1024.0 * 1024.0)).round(2) } : {}), - results: combine_results(result_outputs), - timestamp: Time.now.utc.iso8601 - } - end - end -end - -def combine_results(results) - return nil if results.empty? - - combined = { - "total" => 0.0, - "totalData" => 0, - "successRate_numerator" => 0.0, - "successRate_denominator" => 0.0, - "slowest" => 0.0, - "fastest" => Float::INFINITY, - "average_total" => 0.0, - "average_count" => 0, - "requestsPerSec_total" => 0.0, - "sizePerRequest_total" => 0, - "sizePerRequest_count" => 0, - "sizePerSec_total" => 0.0, - "p95_latencies" => [], - "errorDistribution" => Hash.new(0) - } - - results.each do |res| - total = res["total"].to_f - combined["total"] += total - - combined["totalData"] += res["totalData"].to_i - - combined["successRate_numerator"] += total * res["successRate"].to_f - combined["successRate_denominator"] += total - - combined["slowest"] = [combined["slowest"], res["slowest"].to_f].max - combined["fastest"] = [combined["fastest"], res["fastest"].to_f].min - - combined["average_total"] += res["average"].to_f * total - combined["average_count"] += total - - combined["requestsPerSec_total"] += res["requestsPerSec"].to_f - combined["sizePerRequest_total"] += res["sizePerRequest"].to_f - combined["sizePerRequest_count"] += 1 - combined["sizePerSec_total"] += res["sizePerSec"].to_f - - combined["p95_latencies"] << res["p95_latency"].to_f - - res["errorDistribution"].each do |err, count| - combined["errorDistribution"][err] += count.to_i - end - end - - { - "successRate" => (combined["successRate_numerator"] / combined["successRate_denominator"]).round(4), - "total" => (combined["total"]).round(4), - "slowest" => (combined["slowest"]).round(4), - "fastest" => (combined["fastest"] == Float::INFINITY ? 0.0 : combined["fastest"]).round(4), - "average" => (combined["average_total"] / combined["average_count"]).round(4), - "requestsPerSec" => (combined["requestsPerSec_total"]).round(4), - "totalData" => (combined["totalData"]).round(4), - "sizePerRequest" => (combined["sizePerRequest_total"] / combined["sizePerRequest_count"]).round(4), - "sizePerSec" => (combined["sizePerSec_total"]).round(4), - "errorDistribution" => combined["errorDistribution"], - "p95_latency" => combined["p95_latencies"].sort[(combined["p95_latencies"].size * 0.95).floor] || 0.0 - } -end - -def cpu_label - uname, = Open3.capture2('uname -m') - arch = uname.strip - - model = \ - case RUBY_PLATFORM - when /darwin/ - output, = Open3.capture2('sysctl -n machdep.cpu.brand_string') - output.strip - when /linux/ - model_name = File.read('/proc/cpuinfo')[/^model name\s+:\s+(.+)$/, 1] - model_name || arch - else - arch - end - - model.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/^_|_+$/, '') -end - -def save_result(result, test_name, server_name) - path_prefix = File.join("results", cpu_label, test_name) - FileUtils.mkdir_p(path_prefix) - path = File.join(path_prefix, "#{server_name}.json") - File.write(path, JSON.pretty_generate(result)) -end - -def result_exists?(test_name, server_name) - path_prefix = File.join("results", cpu_label, test_name) - FileUtils.mkdir_p(path_prefix) - File.exist? File.join(path_prefix, "#{server_name}.json") -end - -filters = ARGV.map(&Regexp.method(:new)) - -Dir.glob('test_cases/*/*.rb').each do |path| - test_name = File.basename(path, '.rb') - begin - src = IO.read(path).strip - next if src.empty? - - test_case = BenchmarkCase.new(test_name) - test_case.instance_eval(src) - - if test_case.grpc? && !system("which ghz > /dev/null") - puts Paint["Skipping gRPC test because exec `ghz` not found", :yellow] - next - elsif !system("which oha > /dev/null") - puts Paint["Skipping HTTP test because exec `oha` not found", :yellow] - next - end - - Server::ALL.each do |server| - results = [] - next if filters.any?{|filter| !(filter =~ server.name.to_s || filter =~ test_name.to_s) } - if result_exists?(test_name, server.name.to_s) - puts "Results already exist for #{test_name}/#{server.name}" - next - end unless ENV['RACK_BENCH_OVERWRITE_RESULTS'] - Array(test_case.threads).each do |threads| - Array(test_case.workers).each do |workers| - [false, true].each do |http2| - next unless server.exec_found? - next unless test_case.requires.all?{|r| server.supports?(r) } - next if http2 && !server.supports?(:http2) - next if !http2 && test_case.grpc? - - server_config_file_path = File.join(File.dirname(path), "server_configurations", "#{server.name}.rb") - server_config_file_path = "server_configurations/#{server.name}.rb" unless File.exist?(server_config_file_path) - - results.concat run_benchmark(test_name, test_case, server_config_file_path, server, threads: threads, workers: workers, http2: http2) - end - end - end - - save_result(results, test_name, server.name.to_s) if results.any? - end - rescue StandardError => e - puts "Error during test case #{path}. #{e}" - puts e.backtrace - end -end diff --git a/benchmarks/server_configurations/h2o.conf b/benchmarks/server_configurations/h2o.conf deleted file mode 100644 index f9d01ac9..00000000 --- a/benchmarks/server_configurations/h2o.conf +++ /dev/null @@ -1,16 +0,0 @@ -listen: - port: %{port} - host: 0.0.0.0 - -file.custom-handler: - send-gzip: ON - -paths: - "/": - file.dir: %{www} - header.add: - cache-control: "public, max-age=31536000, immutable" - expires: "@365d" - -# Use multithreaded mode (defaults to CPU cores, can also set `num-threads`) -num-threads: %{workers} diff --git a/benchmarks/server_configurations/itsi.rb b/benchmarks/server_configurations/itsi.rb deleted file mode 100644 index a97015d9..00000000 --- a/benchmarks/server_configurations/itsi.rb +++ /dev/null @@ -1,4 +0,0 @@ -# To make benchmarks fair. If we use too small a default, Itsi will start applying backpressure -# by returning 503s under heavy loads for distant queued requests to Ruby, -# causing an artificial increase in throughput -ruby_thread_request_backlog_size 10_000 diff --git a/benchmarks/server_configurations/nginx.conf b/benchmarks/server_configurations/nginx.conf deleted file mode 100644 index 3820e46c..00000000 --- a/benchmarks/server_configurations/nginx.conf +++ /dev/null @@ -1,26 +0,0 @@ -worker_processes %{workers}; -events { worker_connections 1024; } - -http { - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - server { - listen 0.0.0.0:%{port} http2; - root %{www}; - location / { - open_file_cache max=1000 inactive=20s; - open_file_cache_valid 1s; - open_file_cache_min_uses 1; - open_file_cache_errors on; - - try_files $uri /index.html =404; - access_log off; - add_header Cache-Control "public, max-age=31536000, immutable"; - expires max; - } - } -} diff --git a/benchmarks/servers.rb b/benchmarks/servers.rb deleted file mode 100644 index 2232df37..00000000 --- a/benchmarks/servers.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require_relative 'lib/server' - -Server.new \ - :falcon, - '%s -b %s://%s:%s -c %s --hybrid --forks %s --threads %s', - supports: %i[http2 threads processes streaming_body static ruby] - -Server.new \ - :iodine, - '%s -p %{port} %{app_path} -w %s -t %{threads} %{www}', - supports: %i[threads processes static ruby], - www: ->(test_case, _args){ test_case.static_files_root ? "-www #{test_case.static_files_root}" : ""} - -Server.new \ - :itsi, - '%{base} -C %{config} -b %{scheme}://%{host}:%{port} --rackup_file=%{app_path} -w %{workers} -t %{threads} %{scheduler_toggle}', - supports: %i[http2 threads processes streaming_body static ruby grpc], - scheduler_toggle: ->(test_case, _args){ test_case.nonblocking ? "-f" : "" } - -Server.new \ - :puma, - '%{base} %{config}-b tcp://%{host}:%{port} %{app_path} -w %{workers} -t %{threads}:%{threads}', - supports: %i[http1 threads processes streaming_body static ruby], - config: ->(_, args){ File.exist?(args[:config]) ? "-C #{args[:config]} " : "" } - -Server.new \ - :unicorn, - 'UNICORN_WORKERS=%{workers} %{base} %{config}-l %{host}:%{port} %{app_path}', - supports: %i[processes streaming_body static ruby], - config: ->(_, args){ File.exist?(args[:config]) ? "-c #{args[:config]} " : "" } - -Server.new \ - :agoo, - '(cd apps && %{base} -p %{port} %{app_path} -w %{workers} -t %{threads} %{www})', - supports: %i[threads processes streaming_body static ruby], - www: ->(test_case, _args){ test_case.static_files_root ? "-d #{test_case.static_files_root}" : ""}, - app_path: ->(_test_case, args){ args[:app_path].gsub("apps/", "") } - -Server.new \ - :nginx, - "nginx -p \"#{Dir.pwd}\" -c %{config_file}", - supports: %i[static http2], - config_file: ->(_, args){ - temp_config = Tempfile.new(['nginx', '.conf']) - temp_config.write(IO.read('server_configurations/nginx.conf') % args) - temp_config.flush - temp_config.path - } - -Server.new \ - :caddy, - 'GOMAXPROCS=%{workers} caddy file-server --listen %{host}:%{port} --browse --root %s', - supports: %i[static http2] - -Server.new \ - :h2o, - 'h2o -c %s', - supports: %i[static http2], - config_file: lambda { |_, args| - temp_config = Tempfile.new(['nginx', '.conf']) - temp_config.write(IO.read('server_configurations/nginx.conf') % args) - temp_config.flush - temp_config.path - } - -Server.new \ - :"grpc_server.rb", - 'bundle exec ruby ./grpc_server.rb', - supports: %i[grpc http2] diff --git a/benchmarks/test_cases/framework/sinatra_get.rb b/benchmarks/test_cases/framework/sinatra_get.rb deleted file mode 100644 index 2233c876..00000000 --- a/benchmarks/test_cases/framework/sinatra_get.rb +++ /dev/null @@ -1,3 +0,0 @@ -path "/get" - -app File.open('apps/sinatra.ru') diff --git a/benchmarks/test_cases/framework/sinatra_post.rb b/benchmarks/test_cases/framework/sinatra_post.rb deleted file mode 100644 index 76edf1e7..00000000 --- a/benchmarks/test_cases/framework/sinatra_post.rb +++ /dev/null @@ -1,5 +0,0 @@ -method 'POST' -path "/post" -data %{{"some":"json"}} - -app File.open('apps/sinatra.ru') diff --git a/benchmarks/test_cases/grpc/echo.rb b/benchmarks/test_cases/grpc/echo.rb deleted file mode 100644 index eefee505..00000000 --- a/benchmarks/test_cases/grpc/echo.rb +++ /dev/null @@ -1,13 +0,0 @@ -workers 1 - -requires %i[grpc] - -proto "apps/echo_service/echo.proto" - -call "echo.EchoService/Echo" - -data "{}" - -nonblocking true - -app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/echo_bidirectional.rb b/benchmarks/test_cases/grpc/echo_bidirectional.rb deleted file mode 100644 index cfcbdbd2..00000000 --- a/benchmarks/test_cases/grpc/echo_bidirectional.rb +++ /dev/null @@ -1,13 +0,0 @@ -workers 1 - -requires %i[grpc] - -proto "apps/echo_service/echo.proto" - -call "echo.EchoService/EchoBidirectional" - -data "{}" - -nonblocking true - -app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/echo_collect.rb b/benchmarks/test_cases/grpc/echo_collect.rb deleted file mode 100644 index f5e55961..00000000 --- a/benchmarks/test_cases/grpc/echo_collect.rb +++ /dev/null @@ -1,13 +0,0 @@ -workers 1 - -requires %i[grpc] - -proto "apps/echo_service/echo.proto" - -call "echo.EchoService/EchoCollect" - -data "{}" - -nonblocking true - -app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/echo_stream.rb b/benchmarks/test_cases/grpc/echo_stream.rb deleted file mode 100644 index b5e19eea..00000000 --- a/benchmarks/test_cases/grpc/echo_stream.rb +++ /dev/null @@ -1,13 +0,0 @@ -workers 1 - -requires %i[grpc] - -proto "apps/echo_service/echo.proto" - -call "echo.EchoService/EchoStream" - -data "{}" - -nonblocking true - -app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/process_payment.rb b/benchmarks/test_cases/grpc/process_payment.rb deleted file mode 100644 index 3978936b..00000000 --- a/benchmarks/test_cases/grpc/process_payment.rb +++ /dev/null @@ -1,13 +0,0 @@ -workers 1 - -requires %i[grpc] - -proto "apps/echo_service/echo.proto" - -call "echo.EchoService/ProcessPayment" - -data "{}" - -nonblocking true - -app File.open("apps/hello_world.ru") diff --git a/benchmarks/test_cases/grpc/server_configurations/itsi.rb b/benchmarks/test_cases/grpc/server_configurations/itsi.rb deleted file mode 100644 index 28ca1a50..00000000 --- a/benchmarks/test_cases/grpc/server_configurations/itsi.rb +++ /dev/null @@ -1,6 +0,0 @@ -# First attempt to serve incoming requests as static assets, -# falling through to our rack-mapp on not-found. - -require_relative "../../../apps/echo_service/echo_service" - -grpc EchoServiceImpl.new diff --git a/benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb b/benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb deleted file mode 100644 index 6ec2d1d7..00000000 --- a/benchmarks/test_cases/nonblocking/nonblocking_big_delay.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -nonblocking true - -app File.open('apps/big_delay.ru') diff --git a/benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb b/benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb deleted file mode 100644 index f0b69333..00000000 --- a/benchmarks/test_cases/nonblocking/nonblocking_many_small_delay.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -nonblocking true - -app File.open('apps/many_small_delay.ru') diff --git a/benchmarks/test_cases/response_size/empty_response.rb b/benchmarks/test_cases/response_size/empty_response.rb deleted file mode 100644 index 947956e1..00000000 --- a/benchmarks/test_cases/response_size/empty_response.rb +++ /dev/null @@ -1,2 +0,0 @@ -# frozen_string_literal: true -app File.open('apps/empty.ru') diff --git a/benchmarks/test_cases/response_size/response_size_large.rb b/benchmarks/test_cases/response_size/response_size_large.rb deleted file mode 100644 index 811107fb..00000000 --- a/benchmarks/test_cases/response_size/response_size_large.rb +++ /dev/null @@ -1,2 +0,0 @@ -# frozen_string_literal: true -app File.open('apps/large.ru') diff --git a/benchmarks/test_cases/response_size/response_size_medium.rb b/benchmarks/test_cases/response_size/response_size_medium.rb deleted file mode 100644 index 44c88e5d..00000000 --- a/benchmarks/test_cases/response_size/response_size_medium.rb +++ /dev/null @@ -1,2 +0,0 @@ -# frozen_string_literal: true -app File.open('apps/medium.ru') diff --git a/benchmarks/test_cases/response_size/response_size_small.rb b/benchmarks/test_cases/response_size/response_size_small.rb deleted file mode 100644 index d8b7e3bf..00000000 --- a/benchmarks/test_cases/response_size/response_size_small.rb +++ /dev/null @@ -1,2 +0,0 @@ -# frozen_string_literal: true -app File.open('apps/small.ru') diff --git a/benchmarks/test_cases/static_file/server_configurations/itsi.rb b/benchmarks/test_cases/static_file/server_configurations/itsi.rb deleted file mode 100644 index e2980596..00000000 --- a/benchmarks/test_cases/static_file/server_configurations/itsi.rb +++ /dev/null @@ -1,8 +0,0 @@ -# First attempt to serve incoming requests as static assets, -# falling through to our rack-mapp on not-found. -static_assets root_dir: './apps', not_found_behavior: 'fallthrough' - -# To make benchmarks fair. If we use too small a default, Itsi will start applying backpressure -# by returning 503s under heavy loads for distant queued requests to Ruby, -# causing an artificial increase in throughput -ruby_thread_request_backlog_size 100_000 diff --git a/benchmarks/test_cases/static_file/static_dynamic_mixed.rb b/benchmarks/test_cases/static_file/static_dynamic_mixed.rb deleted file mode 100644 index 7d3bc1bf..00000000 --- a/benchmarks/test_cases/static_file/static_dynamic_mixed.rb +++ /dev/null @@ -1,9 +0,0 @@ -path(["public/image.png", "public/index.html", "dynamic"]) - -static_files_root "./apps" - -requires %i[static ruby] - -concurrency_levels([10, 25, 50, 75, 100]) - -app File.open('apps/static.ru') diff --git a/benchmarks/test_cases/static_file/static_large.rb b/benchmarks/test_cases/static_file/static_large.rb deleted file mode 100644 index 48da9e97..00000000 --- a/benchmarks/test_cases/static_file/static_large.rb +++ /dev/null @@ -1,7 +0,0 @@ -path "public/image.png" - -static_files_root "./apps" - -requires %i[static] - -app File.open('apps/static.ru') diff --git a/benchmarks/test_cases/static_file/static_small.rb b/benchmarks/test_cases/static_file/static_small.rb deleted file mode 100644 index d9a765ef..00000000 --- a/benchmarks/test_cases/static_file/static_small.rb +++ /dev/null @@ -1,7 +0,0 @@ -path "public" - -static_files_root "./apps" - -requires %i[static] - -app File.open('apps/static.ru') diff --git a/benchmarks/test_cases/streaming_response/chunked.rb b/benchmarks/test_cases/streaming_response/chunked.rb deleted file mode 100644 index 946a0c27..00000000 --- a/benchmarks/test_cases/streaming_response/chunked.rb +++ /dev/null @@ -1,5 +0,0 @@ -requires %i[streaming_body] - -nonblocking true - -app File.open('apps/chunked.ru') diff --git a/benchmarks/test_cases/streaming_response/full_hijack.rb b/benchmarks/test_cases/streaming_response/full_hijack.rb deleted file mode 100644 index 2e136336..00000000 --- a/benchmarks/test_cases/streaming_response/full_hijack.rb +++ /dev/null @@ -1,3 +0,0 @@ -requires %i[streaming_body] - -app File.open('apps/full_hijack.ru') diff --git a/benchmarks/test_cases/streaming_response/streaming_response_large.rb b/benchmarks/test_cases/streaming_response/streaming_response_large.rb deleted file mode 100644 index 31e2d70c..00000000 --- a/benchmarks/test_cases/streaming_response/streaming_response_large.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true -requires %i[streaming_body] - -app File.open('apps/streaming_response_large.ru') diff --git a/benchmarks/test_cases/streaming_response/streaming_response_small.rb b/benchmarks/test_cases/streaming_response/streaming_response_small.rb deleted file mode 100644 index 31e2d70c..00000000 --- a/benchmarks/test_cases/streaming_response/streaming_response_small.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true -requires %i[streaming_body] - -app File.open('apps/streaming_response_large.ru') diff --git a/benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb b/benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb deleted file mode 100644 index 7ac91893..00000000 --- a/benchmarks/test_cases/streaming_response/streaming_response_small_http2.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true -requires %i[http2 streaming_body] - -app File.open('apps/streaming_response_small.ru') diff --git a/benchmarks/test_cases/throughput/hello_world.rb b/benchmarks/test_cases/throughput/hello_world.rb deleted file mode 100644 index 194c6d27..00000000 --- a/benchmarks/test_cases/throughput/hello_world.rb +++ /dev/null @@ -1 +0,0 @@ -app File.open('apps/hello_world.ru') diff --git a/crates/itsi_server/Cargo.toml b/crates/itsi_server/Cargo.toml index b308a3a5..ed6fbb8b 100644 --- a/crates/itsi_server/Cargo.toml +++ b/crates/itsi_server/Cargo.toml @@ -91,5 +91,4 @@ core_affinity = "0.8.3" memchr = "2.7.4" quick_cache = "0.6.13" smallvec = "1.15.0" -async-stream = "0.3.6" futures-util = "0.3.31" diff --git a/crates/itsi_server/src/lib.rs b/crates/itsi_server/src/lib.rs index 0c79166e..d9567b7b 100644 --- a/crates/itsi_server/src/lib.rs +++ b/crates/itsi_server/src/lib.rs @@ -93,7 +93,6 @@ fn init(ruby: &Ruby) -> Result<()> { response.define_method("<<", method!(ItsiHttpResponse::send_frame, 1))?; response.define_method("write", method!(ItsiHttpResponse::send_frame, 1))?; response.define_method("read", method!(ItsiHttpResponse::recv_frame, 0))?; - response.define_method("flush", method!(ItsiHttpResponse::flush, 0))?; response.define_method("closed?", method!(ItsiHttpResponse::is_closed, 0))?; response.define_method( "send_and_close", diff --git a/crates/itsi_server/src/ruby_types/itsi_http_response.rs b/crates/itsi_server/src/ruby_types/itsi_http_response.rs index 774d7868..5bfa32f5 100644 --- a/crates/itsi_server/src/ruby_types/itsi_http_response.rs +++ b/crates/itsi_server/src/ruby_types/itsi_http_response.rs @@ -1,4 +1,5 @@ use crate::server::{ + frame_stream::{BufferedStream, FrameStream}, http_message_types::{HttpBody, HttpResponse}, serve_strategy::single_mode::RunningPhase, }; @@ -26,6 +27,7 @@ use std::{ os::{fd::FromRawFd, unix::net::UnixStream}, str::FromStr, sync::Arc, + time::Duration, }; use tokio::{ io::AsyncReadExt, @@ -242,34 +244,13 @@ impl ItsiHttpResponse { { if self.frame_writer.read().is_none() && self.response.read().is_some() { if let Some(mut response) = self.response.write().take() { - let (writer, mut reader) = tokio::sync::mpsc::channel::(5); - let mut shutdown_rx = self.shutdown_rx.clone(); + let (writer, reader) = tokio::sync::mpsc::channel::(256); + let shutdown_rx = self.shutdown_rx.clone(); + let frame_stream = FrameStream::new(reader, shutdown_rx.clone()); - let frame_stream = async_stream::stream! { - loop { - tokio::select! { - maybe_bytes = reader.recv() => { - match maybe_bytes { - Some(bytes) => { - yield Ok(bytes); - } - _ => break, - } - }, - _ = shutdown_rx.changed() => { - if *shutdown_rx.borrow() == RunningPhase::ShutdownPending { - reader.close(); - while let Ok(bytes) = reader.try_recv() { - yield Ok(bytes); - } - break; - } - } - } - } - }; - - *response.body_mut() = HttpBody::stream(frame_stream); + let buffered = + BufferedStream::new(frame_stream, 32 * 1024, Duration::from_millis(10)); + *response.body_mut() = HttpBody::stream(buffered); self.frame_writer.write().replace(writer); if let Some(sender) = self.response_sender.write().take() { sender.send(ResponseFrame::HttpResponse(response)).ok(); @@ -315,10 +296,6 @@ impl ItsiHttpResponse { // not implemented } - pub fn flush(&self) { - // no-op - } - pub fn is_closed(&self) -> bool { self.response.read().is_none() && self.frame_writer.read().is_none() } diff --git a/crates/itsi_server/src/server/frame_stream.rs b/crates/itsi_server/src/server/frame_stream.rs new file mode 100644 index 00000000..e5afa38e --- /dev/null +++ b/crates/itsi_server/src/server/frame_stream.rs @@ -0,0 +1,142 @@ +use bytes::{Bytes, BytesMut}; +use futures::Stream; +use std::convert::Infallible; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; +use tokio::sync::mpsc::Receiver; +use tokio::sync::watch; +use tokio::time::{sleep, Sleep}; + +use super::serve_strategy::single_mode::RunningPhase; + +#[derive(Debug)] +pub struct FrameStream { + receiver: Receiver, + shutdown_rx: watch::Receiver, + drained: bool, +} + +impl FrameStream { + pub fn new(receiver: Receiver, shutdown_rx: watch::Receiver) -> Self { + Self { + receiver, + shutdown_rx, + drained: false, + } + } +} + +impl Stream for FrameStream { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + if this.drained { + return Poll::Ready(None); + } + + match Pin::new(&mut this.receiver).poll_recv(cx) { + Poll::Ready(Some(bytes)) => Poll::Ready(Some(Ok(bytes))), + Poll::Ready(None) => { + this.drained = true; + Poll::Ready(None) + } + Poll::Pending => { + if this.shutdown_rx.has_changed().unwrap_or(false) + && *this.shutdown_rx.borrow() == RunningPhase::ShutdownPending + { + while let Ok(bytes) = this.receiver.try_recv() { + return Poll::Ready(Some(Ok(bytes))); + } + this.drained = true; + return Poll::Ready(None); + } + + Poll::Pending + } + } + } +} + +/// BufferedStream wraps a stream of Bytes and coalesces chunks into a larger buffer, +/// flushing either after `max_flush_bytes` is reached or `max_flush_interval` elapses. +pub struct BufferedStream { + inner: S, + buffer: BytesMut, + max_flush_bytes: usize, + max_flush_interval: Duration, + flush_deadline: Option>>, +} + +impl BufferedStream { + pub fn new(inner: S, max_flush_bytes: usize, max_flush_interval: Duration) -> Self { + Self { + inner, + buffer: BytesMut::with_capacity(max_flush_bytes), + max_flush_bytes, + max_flush_interval, + flush_deadline: None, + } + } +} + +impl Stream for BufferedStream +where + S: Stream> + Unpin, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + loop { + // Flush on timer if needed + if let Some(deadline) = &mut this.flush_deadline { + if Pin::new(deadline).poll(cx).is_ready() && !this.buffer.is_empty() { + let flushed = this.buffer.split().freeze(); + this.flush_deadline = None; + return Poll::Ready(Some(Ok(flushed))); + } + } + + match Pin::new(&mut this.inner).poll_next(cx) { + Poll::Ready(Some(Ok(bytes))) => { + this.buffer.extend_from_slice(&bytes); + + if bytes.is_empty() || this.buffer.len() >= this.max_flush_bytes { + let flushed = this.buffer.split().freeze(); + this.flush_deadline = None; + return Poll::Ready(Some(Ok(flushed))); + } + + if this.flush_deadline.is_none() { + this.flush_deadline = Some(Box::pin(sleep(this.max_flush_interval))); + } + } + Poll::Ready(None) => { + if this.buffer.is_empty() { + return Poll::Ready(None); + } else { + let flushed = this.buffer.split().freeze(); + this.flush_deadline = None; + return Poll::Ready(Some(Ok(flushed))); + } + } + Poll::Pending => { + if let Some(deadline) = &mut this.flush_deadline { + let deadline = deadline.as_mut(); + if deadline.poll(cx).is_ready() && !this.buffer.is_empty() { + let flushed = this.buffer.split().freeze(); + this.flush_deadline = None; + return Poll::Ready(Some(Ok(flushed))); + } + } + return Poll::Pending; + } + } + } + } +} diff --git a/crates/itsi_server/src/server/io_stream.rs b/crates/itsi_server/src/server/io_stream.rs index ef059c4f..ea073f97 100644 --- a/crates/itsi_server/src/server/io_stream.rs +++ b/crates/itsi_server/src/server/io_stream.rs @@ -2,6 +2,7 @@ use pin_project::pin_project; use tokio::net::{TcpStream, UnixStream}; use tokio_rustls::server::TlsStream; +use std::io::{self, IoSlice}; use std::os::unix::io::{AsRawFd, RawFd}; use std::pin::Pin; use std::task::{Context, Poll}; @@ -90,6 +91,28 @@ impl AsyncWrite for IoStream { IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_shutdown(cx), } } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + match self.project() { + IoStreamEnumProj::Tcp { stream, .. } => stream.poll_write_vectored(cx, bufs), + IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_write_vectored(cx, bufs), + IoStreamEnumProj::Unix { stream, .. } => stream.poll_write_vectored(cx, bufs), + IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_write_vectored(cx, bufs), + } + } + + fn is_write_vectored(&self) -> bool { + match self { + IoStream::Tcp { stream, .. } => stream.is_write_vectored(), + IoStream::TcpTls { stream, .. } => stream.is_write_vectored(), + IoStream::Unix { stream, .. } => stream.is_write_vectored(), + IoStream::UnixTls { stream, .. } => stream.is_write_vectored(), + } + } } impl AsRawFd for IoStream { diff --git a/crates/itsi_server/src/server/mod.rs b/crates/itsi_server/src/server/mod.rs index 02fa6e0a..d27af45c 100644 --- a/crates/itsi_server/src/server/mod.rs +++ b/crates/itsi_server/src/server/mod.rs @@ -1,5 +1,6 @@ pub mod binds; pub mod byte_frame; +pub mod frame_stream; pub mod http_message_types; pub mod io_stream; pub mod lifecycle_event; diff --git a/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs b/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs index 9b3b3b05..dfb8ad65 100644 --- a/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs @@ -390,7 +390,7 @@ impl ClusterMode { }, Err(e) => { - error!("Error receiving lifecycle_event: {:?}", e); + debug!("Lifecycle channel closed: {:?}, exiting cluster monitor loop", e); break }, } diff --git a/crates/itsi_server/src/server/serve_strategy/single_mode.rs b/crates/itsi_server/src/server/serve_strategy/single_mode.rs index 28e22de6..2595dfaf 100644 --- a/crates/itsi_server/src/server/serve_strategy/single_mode.rs +++ b/crates/itsi_server/src/server/serve_strategy/single_mode.rs @@ -4,7 +4,10 @@ use crate::{ lifecycle_event::LifecycleEvent, request_job::RequestJob, serve_strategy::acceptor::{Acceptor, AcceptorArgs}, - signal::{send_lifecycle_event, subscribe_runtime_to_signals, SHUTDOWN_REQUESTED}, + signal::{ + send_lifecycle_event, subscribe_runtime_to_signals, unsubscribe_runtime, + SHUTDOWN_REQUESTED, + }, thread_worker::{build_thread_workers, ThreadWorker}, }, }; @@ -222,6 +225,10 @@ impl SingleMode { Ok(LifecycleEvent::PrintInfo) => { receiver.print_info(thread_workers.clone()).await.ok(); } + Err(e) => { + debug!("Lifecycle channel closed: {:?}, exiting single mode monitor loop", e); + break; + } _ => {} } } @@ -322,7 +329,6 @@ impl SingleMode { // Process any pending signals before select tokio::select! { accept_result = listener.accept() => { - info!("New connection accepted"); match accept_result { Ok(accepted) => acceptor.serve_connection(accepted).await, Err(e) => debug!("Listener.accept failed: {:?}", e) @@ -345,7 +351,7 @@ impl SingleMode { break; }, Err(e) => { - error!("Error receiving lifecycle event: {:?}", e); + debug!("Lifecycle channel closed: {:?}, exiting accept loop", e); break }, _ => () @@ -381,6 +387,8 @@ impl SingleMode { shutdown_sender.send(RunningPhase::Shutdown).ok(); runtime.shutdown_timeout(Duration::from_millis(100)); + unsubscribe_runtime(); + debug!("Shutdown timeout finished."); let deadline = Instant::now() + Duration::from_secs_f64(shutdown_timeout); diff --git a/crates/itsi_server/src/server/signal.rs b/crates/itsi_server/src/server/signal.rs index ccca9c76..a1bf07e5 100644 --- a/crates/itsi_server/src/server/signal.rs +++ b/crates/itsi_server/src/server/signal.rs @@ -17,7 +17,8 @@ pub static SIGNAL_HANDLER_CHANNEL: Mutex> = Mutex::new(VecDeque::new()); pub fn subscribe_runtime_to_signals() -> broadcast::Receiver { - if let Some(sender) = SIGNAL_HANDLER_CHANNEL.lock().as_ref() { + let mut guard = SIGNAL_HANDLER_CHANNEL.lock(); + if let Some(sender) = guard.as_ref() { return sender.subscribe(); } let (sender, receiver) = broadcast::channel(5); @@ -29,7 +30,7 @@ pub fn subscribe_runtime_to_signals() -> broadcast::Receiver { } }); - SIGNAL_HANDLER_CHANNEL.lock().replace(sender); + guard.replace(sender); receiver } diff --git a/crates/itsi_server/src/services/itsi_http_service.rs b/crates/itsi_server/src/services/itsi_http_service.rs index 07243a10..ce7a6fbb 100644 --- a/crates/itsi_server/src/services/itsi_http_service.rs +++ b/crates/itsi_server/src/services/itsi_http_service.rs @@ -247,8 +247,9 @@ impl ItsiHttpService { // thread as it might be in a critical section. // Instead we must ask the worker to hot restart. // But only if we're not already shutting down - if is_ruby_request.load(Ordering::Relaxed) && - !SHUTDOWN_REQUESTED.load(Ordering::SeqCst) { + if is_ruby_request.load(Ordering::Relaxed) + && !SHUTDOWN_REQUESTED.load(Ordering::SeqCst) + { // When we've detected a timeout, use the safer send_lifecycle_event // which will properly handle signal-safe state transitions if is_single_mode { diff --git a/docs/benchmark-dashboard/.gitignore b/docs/benchmark-dashboard/.gitignore new file mode 100644 index 00000000..f650315f --- /dev/null +++ b/docs/benchmark-dashboard/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/docs/benchmark-dashboard/app/api/benchmarks/route.ts b/docs/benchmark-dashboard/app/api/benchmarks/route.ts new file mode 100644 index 00000000..17468956 --- /dev/null +++ b/docs/benchmark-dashboard/app/api/benchmarks/route.ts @@ -0,0 +1,22 @@ +import { NextResponse } from "next/server" + +// This would be your actual API endpoint to fetch benchmark data +export async function GET() { + try { + // In a real implementation, you would: + // 1. Scan the directory structure + // 2. Read and parse the JSON files + // 3. Return the aggregated data + + // For demo purposes, we're returning a mock response + return NextResponse.json({ + success: true, + data: [ + // Your benchmark data would go here + ], + }) + } catch (error) { + console.error("Error fetching benchmark data:", error) + return NextResponse.json({ success: false, error: "Failed to fetch benchmark data" }, { status: 500 }) + } +} diff --git a/docs/benchmark-dashboard/app/globals.css b/docs/benchmark-dashboard/app/globals.css new file mode 100644 index 00000000..ac684423 --- /dev/null +++ b/docs/benchmark-dashboard/app/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/docs/benchmark-dashboard/app/layout.tsx b/docs/benchmark-dashboard/app/layout.tsx new file mode 100644 index 00000000..17b2ce8c --- /dev/null +++ b/docs/benchmark-dashboard/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'v0 App', + description: 'Created with v0', + generator: 'v0.dev', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/docs/benchmark-dashboard/app/page.tsx b/docs/benchmark-dashboard/app/page.tsx new file mode 100644 index 00000000..ba99c42f --- /dev/null +++ b/docs/benchmark-dashboard/app/page.tsx @@ -0,0 +1,252 @@ +"use client" + +import { useState, useEffect } from "react" +import { BenchmarkDashboard } from "@/components/benchmark-dashboard" +import { LoadingSpinner } from "@/components/ui/loading-spinner" + +// Updated sample data with the new group hierarchy and longer version strings +const sampleHierarchicalData = [ + { + cpu: "apple_m1_pro", + groups: [ + { + group: "rack", + tests: [ + { + test: "chunked", + servers: [ + { + server: "agoo", + results: [ + { + server: "agoo", + test_case: "chunked", + version: "Agoo v1.2.3 (Ruby 3.2.0) with experimental HTTP/2 support", + threads: 1, + workers: 1, + http2: false, + concurrency: 10, + rss_mb: 1.77, + results: { + successRate: 1.0, + total: 20000.0, + slowest: 5000000.0, + fastest: 200000.0, + average: 2500000.0, + requestsPerSec: 6500.0, + totalData: 0, + sizePerRequest: 0.0, + sizePerSec: 0.0, + errorDistribution: {}, + p95_latency: 4.8, + }, + timestamp: "2025-05-24T10:41:17Z", + }, + { + server: "agoo", + test_case: "chunked", + version: "Agoo v1.2.3 (Ruby 3.2.0) with experimental HTTP/2 support", + threads: 1, + workers: 1, + http2: false, + concurrency: 50, + rss_mb: 1.77, + results: { + successRate: 0.95, + total: 28000.0, + slowest: 12000000.0, + fastest: 400000.0, + average: 6000000.0, + requestsPerSec: 9500.0, + totalData: 0, + sizePerRequest: 0.0, + sizePerSec: 0.0, + errorDistribution: { + timeout: 50, + }, + p95_latency: 10.2, + }, + timestamp: "2025-05-24T10:41:17Z", + }, + ], + }, + { + server: "puma", + results: [ + { + server: "puma", + test_case: "chunked", + version: + "Puma version 6.6.0+h2o version 2.3.0-DEV@87e2aa634 (Ruby 3.2.2) with HTTP/2 support enabled", + threads: 1, + workers: 1, + http2: true, + concurrency: 10, + results: { + successRate: 1.0, + total: 15000.0, + slowest: 4000000.0, + fastest: 200000.0, + average: 2000000.0, + requestsPerSec: 5000.0, + totalData: 150000000, + sizePerRequest: 10000.0, + sizePerSec: 50000000.0, + errorDistribution: {}, + p95_latency: 3.8, + }, + }, + { + server: "puma", + test_case: "chunked", + version: + "Puma version 6.6.0+h2o version 2.3.0-DEV@87e2aa634 (Ruby 3.2.2) with HTTP/2 support enabled", + threads: 1, + workers: 1, + http2: true, + concurrency: 50, + results: { + successRate: 1.0, + total: 22000.0, + slowest: 12000000.0, + fastest: 400000.0, + average: 6000000.0, + requestsPerSec: 7500.0, + totalData: 220000000, + sizePerRequest: 10000.0, + sizePerSec: 75000000.0, + errorDistribution: {}, + p95_latency: 11.2, + }, + }, + ], + }, + ], + }, + { + test: "io_party", + servers: [ + { + server: "itsi", + results: [ + { + server: "itsi", + test_case: "io_party", + version: + "ITSI v0.9.1-beta.3 (Experimental) with advanced IO processing and HTTP/2 multiplexing support", + threads: 1, + workers: 1, + http2: true, + concurrency: 10, + rss_mb: 64.11, + results: { + successRate: 1.0, + total: 48591.0, + slowest: 2850375.0, + fastest: 116292.0, + average: 486975.0, + requestsPerSec: 16196.49, + totalData: 0, + sizePerRequest: 0.0, + sizePerSec: 0.0, + errorDistribution: {}, + p95_latency: 0.977999, + }, + timestamp: "2025-05-15T03:43:50Z", + }, + ], + }, + ], + }, + ], + }, + { + group: "sinatra", + tests: [ + { + test: "hello_world", + servers: [ + { + server: "falcon", + results: [ + { + server: "falcon", + test_case: "hello_world", + version: + "Falcon v0.51.1 (Ruby 3.3.0-preview1) with HTTP/2 and WebSocket support, running on Async::HTTP::Protocol::HTTP2 implementation", + threads: 2, + workers: 2, + http2: true, + concurrency: 10, + results: { + successRate: 1.0, + total: 30000.0, + slowest: 10000000.0, + fastest: 400000.0, + average: 5000000.0, + requestsPerSec: 10000.0, + totalData: 0, + sizePerRequest: 0.0, + sizePerSec: 0.0, + errorDistribution: {}, + p95_latency: 8.5, + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, +] + +export default function Home() { + const [isLoading, setIsLoading] = useState(true) + const [benchmarkData, setBenchmarkData] = useState([]) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchData = async () => { + try { + // In a real app, this would fetch from your API endpoint + // For demo purposes, we're using the sample hierarchical data + setBenchmarkData(sampleHierarchicalData) + setIsLoading(false) + } catch (err) { + console.error("Error fetching benchmark data:", err) + setError("Failed to load benchmark data. Please try again.") + setIsLoading(false) + } + } + + fetchData() + }, []) + + if (isLoading) { + return ( +
+ + Loading benchmark data... +
+ ) + } + + if (error) { + return ( +
+
+

Error

+

{error}

+
+
+ ) + } + + return ( +
+ +
+ ) +} diff --git a/docs/benchmark-dashboard/components.json b/docs/benchmark-dashboard/components.json new file mode 100644 index 00000000..d9ef0ae5 --- /dev/null +++ b/docs/benchmark-dashboard/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/docs/benchmark-dashboard/components/benchmark-dashboard.tsx b/docs/benchmark-dashboard/components/benchmark-dashboard.tsx new file mode 100644 index 00000000..65d4bdb9 --- /dev/null +++ b/docs/benchmark-dashboard/components/benchmark-dashboard.tsx @@ -0,0 +1,1663 @@ +"use client"; + +import type React from "react"; + +import { useState, useMemo, useCallback, useEffect } from "react"; +import { + Bar, + XAxis, + YAxis, + CartesianGrid, + ResponsiveContainer, + BarChart, +} from "recharts"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + SelectGroup, + SelectLabel, +} from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + InfoIcon, + TrendingUp, + TrendingDown, + TagIcon, + TrophyIcon, +} from "lucide-react"; + +// Updated types to match the new hierarchical structure with groups +type BenchmarkResult = { + server: string; + version?: string; + test_case: string; + threads: number; + workers: number; + http2: boolean; + concurrency: number; + rss_mb?: number; + results: { + successRate: number; + total: number; + slowest: number; + fastest: number; + average: number; + requestsPerSec: number; + totalData: number; + sizePerRequest: number; + sizePerSec: number; + errorDistribution: Record; + p95_latency: number; + }; + timestamp?: string; +}; + +type ServerData = { + server: string; + results: BenchmarkResult[]; +}; + +type TestData = { + test: string; + servers: ServerData[]; +}; + +type GroupData = { + group: string; + tests: TestData[]; +}; + +type CpuData = { + cpu: string; + groups: GroupData[]; +}; + +type HierarchicalBenchmarkData = CpuData[]; + +type FilterOptions = { + cpus: string[]; + testCases: string[]; + servers: string[]; + threads: number[]; + workers: number[]; + concurrencyLevels: number[]; + http2Options: (boolean | "all")[]; +}; + +type FilterState = { + cpu: string; + testCase: string; + threads: number; + workers: number; + concurrency: number; + http2: boolean | "all"; + xAxis: string; + metric: string; + visibleServers: string[]; +}; + +type BenchmarkDashboardProps = { + data: HierarchicalBenchmarkData; +}; + +export function BenchmarkDashboard({ data }: BenchmarkDashboardProps) { + // Helper function to format server names (replace __ with +) + const formatServerName = useCallback((serverName: string): string => { + return serverName.replace(/__/g, "+"); + }, []); + + // Flatten the hierarchical data into the format we need for processing + const flattenedData = useMemo(() => { + const flattened: BenchmarkResult[] = []; + + if (!data || !Array.isArray(data)) { + return flattened; + } + + data.forEach((cpuData) => { + if (!cpuData?.groups || !Array.isArray(cpuData.groups)) { + return; + } + + cpuData.groups.forEach((groupData) => { + if (!groupData?.tests || !Array.isArray(groupData.tests)) { + return; + } + + groupData.tests.forEach((testData) => { + if (!testData?.servers || !Array.isArray(testData.servers)) { + return; + } + + testData.servers.forEach((serverData) => { + if (!serverData?.results || !Array.isArray(serverData.results)) { + return; + } + + serverData.results.forEach((result) => { + // Add CPU and group information to each result + flattened.push({ + ...result, + cpu: cpuData.cpu, + group: groupData.group, + } as BenchmarkResult & { cpu: string; group: string }); + }); + }); + }); + }); + }); + + return flattened; + }, [data]); + + // Extract all possible filter options from hierarchical data + const allFilterOptions: FilterOptions = useMemo(() => { + const defaultOptions: FilterOptions = { + cpus: [], + testCases: [], + servers: [], + threads: [], + workers: [], + concurrencyLevels: [], + http2Options: ["all"], + }; + + if (!data || !Array.isArray(data) || data.length === 0) { + return defaultOptions; + } + + return { + cpus: data.map((cpuData) => cpuData.cpu).filter(Boolean), + testCases: [ + ...new Set(flattenedData.map((item) => item.test_case).filter(Boolean)), + ], + servers: [ + ...new Set(flattenedData.map((item) => item.server).filter(Boolean)), + ], + threads: [ + ...new Set( + flattenedData + .map((item) => item.threads) + .filter((t) => typeof t === "number"), + ), + ].sort((a, b) => a - b), + workers: [ + ...new Set( + flattenedData + .map((item) => item.workers) + .filter((w) => typeof w === "number"), + ), + ].sort((a, b) => a - b), + concurrencyLevels: [ + ...new Set( + flattenedData + .map((item) => item.concurrency) + .filter((c) => typeof c === "number"), + ), + ].sort((a, b) => a - b), + http2Options: [ + "all", + ...new Set( + flattenedData + .map((item) => item.http2) + .filter((h) => typeof h === "boolean"), + ), + ], + }; + }, [data, flattenedData]); + + // Generate consistent colors for all servers upfront + const serverColors = useMemo(() => { + const itsiColor = "#ff7f0e"; + + const palette = [ + "#1f77b4", // blue + "#2ca02c", // green + "#d62728", // red (not orange) + "#9467bd", // purple + "#8c564b", // brown + "#e377c2", // pink + "#7f7f7f", // gray + "#bcbd22", // lime + "#17becf", // cyan + "#393b79", // indigo + "#a55194", // magenta + ]; + + const colorMap: Record = {}; + const servers = [...allFilterOptions.servers]; + + const itsiIndex = servers.findIndex((s) => s === "itsi"); + if (itsiIndex !== -1) { + colorMap["itsi"] = itsiColor; + servers.splice(itsiIndex, 1); + } + + servers.forEach((server, index) => { + colorMap[server] = palette[index % palette.length]; + }); + + return colorMap; + }, [allFilterOptions.servers]); + + // Helper function to parse URL search parameters + const parseUrlParams = useCallback((): Partial | null => { + if (typeof window === "undefined") return null; + + try { + const urlParams = new URLSearchParams(window.location.search); + const filters: Partial = {}; + + // Parse each parameter + const cpu = urlParams.get("cpu"); + const testCase = urlParams.get("testCase"); + const threads = urlParams.get("threads"); + const workers = urlParams.get("workers"); + const concurrency = urlParams.get("concurrency"); + const http2 = urlParams.get("http2"); + const xAxis = urlParams.get("xAxis"); + const metric = urlParams.get("metric"); + + if (cpu) filters.cpu = cpu; + if (testCase) filters.testCase = testCase; + if (threads) filters.threads = Number.parseInt(threads); + if (workers) filters.workers = Number.parseInt(workers); + if (concurrency) filters.concurrency = Number.parseInt(concurrency); + if (http2) { + if (http2 === "all") { + filters.http2 = "all"; + } else { + filters.http2 = http2 === "true"; + } + } + if (xAxis) filters.xAxis = xAxis; + if (metric) filters.metric = metric; + + const visibleServersParam = urlParams.get("visibleServers"); + if (visibleServersParam) { + try { + filters.visibleServers = visibleServersParam + .split(",") + .filter(Boolean); + } catch (e) { + // Ignore parsing errors + } + } + + return Object.keys(filters).length > 0 ? filters : null; + } catch (error) { + console.warn("Failed to parse URL parameters:", error); + } + + return null; + }, []); + + // Helper function to update URL search parameters + const updateUrlParams = useCallback((filters: FilterState) => { + if (typeof window === "undefined") return; + + try { + const url = new URL(window.location.href); + + // Set each parameter + url.searchParams.set("cpu", filters.cpu); + url.searchParams.set("testCase", filters.testCase); + url.searchParams.set("threads", filters.threads.toString()); + url.searchParams.set("workers", filters.workers.toString()); + url.searchParams.set("concurrency", filters.concurrency.toString()); + url.searchParams.set("http2", filters.http2.toString()); + url.searchParams.set("xAxis", filters.xAxis); + url.searchParams.set("metric", filters.metric); + + // Set visible servers as comma-separated list + if (filters.visibleServers && filters.visibleServers.length > 0) { + url.searchParams.set( + "visibleServers", + filters.visibleServers.join(","), + ); + } else { + url.searchParams.delete("visibleServers"); + } + + // Update the URL without triggering a page reload + window.history.replaceState(null, "", url.toString()); + } catch (error) { + console.warn("Failed to update URL parameters:", error); + } + }, []); + + // Helper function to validate and sanitize filter state from URL + const validateAndSanitizeFilters = useCallback( + (urlFilters: Partial): FilterState => { + const defaultFilters: FilterState = { + cpu: allFilterOptions.cpus[0] || "", + testCase: allFilterOptions.testCases[0] || "", + threads: allFilterOptions.threads[0] || 1, + workers: allFilterOptions.workers[0] || 1, + concurrency: allFilterOptions.concurrencyLevels[0] || 10, + http2: "all", // Default to "all" + xAxis: "concurrency", + metric: "rps", + visibleServers: allFilterOptions.servers, // Default to all servers visible + }; + + // Validate each field and fall back to defaults if invalid + const validatedFilters: FilterState = { + cpu: allFilterOptions.cpus.includes(urlFilters.cpu || "") + ? urlFilters.cpu! + : defaultFilters.cpu, + testCase: allFilterOptions.testCases.includes(urlFilters.testCase || "") + ? urlFilters.testCase! + : defaultFilters.testCase, + threads: allFilterOptions.threads.includes(urlFilters.threads || 0) + ? urlFilters.threads! + : defaultFilters.threads, + workers: allFilterOptions.workers.includes(urlFilters.workers || 0) + ? urlFilters.workers! + : defaultFilters.workers, + concurrency: allFilterOptions.concurrencyLevels.includes( + urlFilters.concurrency || 0, + ) + ? urlFilters.concurrency! + : defaultFilters.concurrency, + http2: allFilterOptions.http2Options.includes(urlFilters.http2 as any) + ? (urlFilters.http2 as boolean | "all") + : defaultFilters.http2, + xAxis: ["concurrency", "threads", "workers"].includes( + urlFilters.xAxis || "", + ) + ? urlFilters.xAxis! + : defaultFilters.xAxis, + metric: ["rps", "p95_latency", "errorRate"].includes( + urlFilters.metric || "", + ) + ? urlFilters.metric! + : defaultFilters.metric, + visibleServers: Array.isArray(urlFilters.visibleServers) + ? urlFilters.visibleServers.filter((server) => + allFilterOptions.servers.includes(server), + ) + : defaultFilters.visibleServers, + }; + + return validatedFilters; + }, + [allFilterOptions], + ); + + // Initialize filter state with URL parameters or defaults + const [filters, setFilters] = useState(() => { + const urlFilters = parseUrlParams(); + if (urlFilters && allFilterOptions.cpus.length > 0) { + return validateAndSanitizeFilters(urlFilters); + } + + const preferredTestCase = allFilterOptions.testCases.includes("hello_world") + ? "hello_world" + : allFilterOptions.testCases[0] || ""; + + return { + cpu: allFilterOptions.cpus[0] || "", + testCase: preferredTestCase || "", + threads: allFilterOptions.threads[0] || 1, + workers: allFilterOptions.workers[0] || 1, + concurrency: allFilterOptions.concurrencyLevels[0] || 10, + http2: "all", // Default to "all" + xAxis: "concurrency", + metric: "rps", + visibleServers: allFilterOptions.servers, + }; + }); + + // Track which servers are visible + const [visibleServers, setVisibleServers] = useState>( + () => { + const initialVisibleServers: Record = {}; + allFilterOptions.servers.forEach((server) => { + initialVisibleServers[server] = true; + }); + + const urlFilters = parseUrlParams(); + if ( + urlFilters?.visibleServers && + Array.isArray(urlFilters.visibleServers) + ) { + allFilterOptions.servers.forEach((server) => { + initialVisibleServers[server] = + urlFilters.visibleServers!.includes(server); + }); + } + + return initialVisibleServers; + }, + ); + + // Currently hovered data point + const [hoveredPoint, setHoveredPoint] = useState( + null, + ); + const [activeDataKey, setActiveDataKey] = useState(null); + + // Track legend interactions to disable animations + // const [isLegendInteracting, setIsLegendInteracting] = useState(false) + + // Update URL parameters when filters change + useEffect(() => { + updateUrlParams(filters); + }, [filters, updateUrlParams]); + + // Handle initial load from URL parameters after data is available + useEffect(() => { + if (allFilterOptions.cpus.length > 0) { + const urlFilters = parseUrlParams(); + if (urlFilters) { + const validatedFilters = validateAndSanitizeFilters(urlFilters); + setFilters(validatedFilters); + + // Update visibleServers based on URL params after filters are set + const initialVisibleServers: Record = {}; + allFilterOptions.servers.forEach((server) => { + initialVisibleServers[server] = true; + }); + + if ( + urlFilters?.visibleServers && + Array.isArray(urlFilters.visibleServers) + ) { + allFilterOptions.servers.forEach((server) => { + initialVisibleServers[server] = + urlFilters.visibleServers!.includes(server); + }); + } + setVisibleServers(initialVisibleServers); + } + } + }, [allFilterOptions, parseUrlParams, validateAndSanitizeFilters]); + + // Get dynamic filter options based on selected CPU and test case + const filterOptions = useMemo(() => { + // First filter by CPU + const cpuFilteredData = flattenedData.filter( + (item) => (item as any).cpu === filters.cpu, + ); + + // Get available test cases for the selected CPU + const availableTestCases = [ + ...new Set(cpuFilteredData.map((item) => item.test_case).filter(Boolean)), + ]; + + // Then filter by test case to get the remaining filter options + const testCaseFilteredData = cpuFilteredData.filter( + (item) => item.test_case === filters.testCase, + ); + + return { + cpus: allFilterOptions.cpus, + testCases: availableTestCases, + servers: [ + ...new Set( + testCaseFilteredData.map((item) => item.server).filter(Boolean), + ), + ], + threads: [ + ...new Set( + testCaseFilteredData + .map((item) => item.threads) + .filter((t) => typeof t === "number"), + ), + ].sort((a, b) => a - b), + workers: [ + ...new Set( + testCaseFilteredData + .map((item) => item.workers) + .filter((w) => typeof w === "number"), + ), + ].sort((a, b) => a - b), + concurrencyLevels: [ + ...new Set( + testCaseFilteredData + .map((item) => item.concurrency) + .filter((c) => typeof c === "number"), + ), + ].sort((a, b) => a - b), + http2Options: [ + "all", + ...new Set( + testCaseFilteredData + .map((item) => item.http2) + .filter((h) => typeof h === "boolean"), + ), + ], + }; + }, [flattenedData, filters.cpu, filters.testCase, allFilterOptions.cpus]); + + // Get grouped test cases for the dropdown + const groupedTestCases = useMemo(() => { + // First filter by CPU to get available data + const cpuFilteredData = flattenedData.filter( + (item) => (item as any).cpu === filters.cpu, + ); + + // Group test cases by their group + const groupedTests: Record = {}; + cpuFilteredData.forEach((item) => { + const group = (item as any).group; + if (!group || !item.test_case) return; + + if (!groupedTests[group]) { + groupedTests[group] = []; + } + if (!groupedTests[group].includes(item.test_case)) { + groupedTests[group].push(item.test_case); + } + }); + + // Sort test cases within each group + Object.keys(groupedTests).forEach((group) => { + groupedTests[group].sort(); + }); + + // Sort groups: "rack" first, then alphabetically + const sortedGroups = Object.keys(groupedTests).sort((a, b) => { + if (a === "rack") return -1; + if (b === "rack") return 1; + return a.localeCompare(b); + }); + + return { groupedTests, sortedGroups }; + }, [flattenedData, filters.cpu]); + + // Update filters when CPU or test case changes to ensure valid selections + useEffect(() => { + setFilters((prev) => { + const newFilters = { ...prev }; + + // If test case is not available for selected CPU, select first available + if (!filterOptions.testCases.includes(prev.testCase)) { + newFilters.testCase = filterOptions.testCases[0] || ""; + } + + // Update other filters with first available value if current value is not valid + // BUT skip the filter that matches the current X-axis + if ( + prev.xAxis !== "threads" && + !filterOptions.threads.includes(prev.threads) + ) { + newFilters.threads = filterOptions.threads[0] || 1; + } + + if ( + prev.xAxis !== "workers" && + !filterOptions.workers.includes(prev.workers) + ) { + newFilters.workers = filterOptions.workers[0] || 1; + } + + if ( + prev.xAxis !== "concurrency" && + !filterOptions.concurrencyLevels.includes(prev.concurrency) + ) { + newFilters.concurrency = filterOptions.concurrencyLevels[0] || 10; + } + + if (!filterOptions.http2Options.includes(prev.http2)) { + newFilters.http2 = filterOptions.http2Options[0] || "all"; + } + + return newFilters; + }); + }, [filters.cpu, filterOptions]); + + // Apply filters to flattened data + const filteredData = useMemo(() => { + return flattenedData.filter((item) => { + const itemWithCpu = item as any; + if (itemWithCpu.cpu !== filters.cpu) return false; + if (item.test_case !== filters.testCase) return false; + + // Don't filter by the parameter that's being used as X-axis + if (filters.xAxis !== "threads" && item.threads !== filters.threads) + return false; + if (filters.xAxis !== "workers" && item.workers !== filters.workers) + return false; + if ( + filters.xAxis !== "concurrency" && + item.concurrency !== filters.concurrency + ) + return false; + + // Handle "all" protocol option + if (filters.http2 !== "all" && item.http2 !== filters.http2) return false; + + return true; + }); + }, [flattenedData, filters]); + + // Prepare data for chart based on selected x-axis + const chartData = useMemo(() => { + // Group data by the selected x-axis + const groupedByXAxis: Record = {}; + + filteredData.forEach((item) => { + const xAxisValue = String(item[filters.xAxis as keyof BenchmarkResult]); + if (!groupedByXAxis[xAxisValue]) { + groupedByXAxis[xAxisValue] = []; + } + groupedByXAxis[xAxisValue].push(item); + }); + + // Convert to format suitable for chart + return Object.entries(groupedByXAxis) + .map(([xAxisValue, items]) => { + const point: Record = { [filters.xAxis]: xAxisValue }; + + // Group items by server and protocol when "all" is selected + items.forEach((item) => { + if (visibleServers[item.server]) { + // Single metric based on selection + let metricValue: number; + switch (filters.metric) { + case "rps": + metricValue = item.results.requestsPerSec; + break; + case "p95_latency": + metricValue = item.results.p95_latency; + break; + case "errorRate": + metricValue = 1 - item.results.successRate; + break; + default: + metricValue = item.results.requestsPerSec; + } + + // Create unique key for server+protocol combination when showing all protocols + let dataKey: string; + if (filters.http2 === "all") { + const protocolSuffix = item.http2 ? " (HTTP/2)" : " (HTTP/1.1)"; + dataKey = `${formatServerName(item.server)}${protocolSuffix}`; + } else { + dataKey = formatServerName(item.server); + } + + point[dataKey] = metricValue; + // Store the full item for hover details + point[`${dataKey}_data`] = item; + } + }); + + return point; + }) + .sort((a, b) => { + // Sort numerically if the x-axis is a number + const aVal = a[filters.xAxis]; + const bVal = b[filters.xAxis]; + if (!isNaN(Number(aVal)) && !isNaN(Number(bVal))) { + return Number(aVal) - Number(bVal); + } + // Otherwise sort alphabetically + return String(aVal).localeCompare(String(bVal)); + }); + }, [filteredData, filters, visibleServers, formatServerName]); + + // Get metric info for display + const metricInfo = useMemo(() => { + // Helper function to format large numbers compactly + const formatCompact = (value: number, decimals = 1): string => { + if (value >= 1000000) { + return `${(value / 1000000).toFixed(decimals)}M`; + } else if (value >= 1000) { + return `${(value / 1000).toFixed(decimals)}K`; + } + return value.toFixed(decimals); + }; + + switch (filters.metric) { + case "rps": + return { + label: "Requests per Second", + isBetter: "higher", + icon: TrendingUp, + formatter: (value: number) => formatCompact(value, 1), + }; + case "p95_latency": + return { + label: "P95 Latency (ms)", + isBetter: "lower", + icon: TrendingDown, + formatter: (value: number) => + value >= 1000 ? formatCompact(value, 1) : value.toFixed(2), + }; + case "errorRate": + return { + label: "Error Rate", + isBetter: "lower", + icon: TrendingDown, + formatter: (value: number) => `${(value * 100).toFixed(1)}%`, + }; + default: + return { + label: "Requests per Second", + isBetter: "higher", + icon: TrendingUp, + formatter: (value: number) => formatCompact(value, 1), + }; + } + }, [filters.metric]); + + // Handle filter changes + const handleFilterChange = (key: keyof FilterState, value: any) => { + setFilters((prev) => ({ ...prev, [key]: value })); + }; + + // Toggle server visibility when clicking on legend + const handleLegendClick = useCallback( + (server: string, event?: React.MouseEvent) => { + // Disable animations during legend interaction + // setIsLegendInteracting(true) + + // Check if Ctrl (Windows/Linux) or Cmd (Mac) key is pressed + const isExclusiveMode = event?.ctrlKey || event?.metaKey; + + if (isExclusiveMode) { + // Ctrl/Cmd + click: Show only this server (hide all others) + const newVisibleServers: Record = {}; + allFilterOptions.servers.forEach((s) => { + newVisibleServers[s] = s === server; + }); + setVisibleServers(newVisibleServers); + } else { + // Normal click: Toggle this server + setVisibleServers((prev) => ({ + ...prev, + [server]: !prev[server], + })); + } + + // Re-enable animations after a short delay + // setTimeout(() => { + // setIsLegendInteracting(false) + // }, 100) + }, + [allFilterOptions.servers], + ); + + // Calculate summary statistics + const summaryStats = useMemo(() => { + if (filteredData.length === 0) return null; + + const values = filteredData.map((item) => { + switch (filters.metric) { + case "rps": + return item.results.requestsPerSec; + case "p95_latency": + return item.results.p95_latency; + case "errorRate": + return 1 - item.results.successRate; + default: + return item.results.requestsPerSec; + } + }); + + return { + count: filteredData.length, + min: Math.min(...values), + max: Math.max(...values), + avg: values.reduce((sum, val) => sum + val, 0) / values.length, + }; + }, [filteredData, filters.metric]); + + const topPerformers = useMemo(() => { + if (filteredData.length === 0) return []; + + const currentGroup = (filteredData[0] as any).group; + if (!currentGroup) return []; + + const performanceMap: Record< + string, + Record> + > = {}; + + flattenedData + .filter((item) => { + const itemWithCpu = item as any; + return ( + itemWithCpu.cpu === filters.cpu && itemWithCpu.group === currentGroup + ); + }) + .forEach((item) => { + const key = `${item.test_case}_${item.threads}_${item.workers}_${item.concurrency}`; + + if (!performanceMap[key]) { + performanceMap[key] = {}; + } + + if (!performanceMap[key][item.server]) { + performanceMap[key][item.server] = { true: [], false: [] }; + } + + performanceMap[key][item.server][item.http2].push( + item.results.requestsPerSec, + ); + }); + + const winCounts: Record = {}; + + Object.values(performanceMap).forEach((serverResults) => { + const allScores: [string, number][] = []; + + for (const [server, variants] of Object.entries(serverResults)) { + for (const [http2, values] of Object.entries(variants)) { + const parsedHttp2 = http2 === "true"; // keys are string + if (values.length > 0) { + const avg = values.reduce((sum, v) => sum + v, 0) / values.length; + allScores.push([`${server}___${parsedHttp2}`, avg]); + } + } + } + + if (allScores.length === 0) return; + + const bestScore = Math.max(...allScores.map(([, v]) => v)); + const winningServers = new Set( + allScores + .filter(([, v]) => v === bestScore) + .map(([k]) => k.split("___")[0]), // extract server + ); + + for (const server of winningServers) { + winCounts[server] = (winCounts[server] || 0) + 1; + } + }); + + return Object.entries(winCounts) + .sort(([, a], [, b]) => b - a) + .slice(0, 8) + .map(([server, count]) => ({ server, count })); + }, [flattenedData, filters.cpu, filteredData]); + + // Handle chart hover + const handleChartHover = (props: any) => { + if (props.activePayload && props.activePayload.length > 0) { + const { dataKey, payload } = props.activePayload[0]; + const serverData = payload[`${dataKey}_data`]; + + if (serverData) { + setHoveredPoint(serverData); + setActiveDataKey(dataKey); + return; + } + } + + setHoveredPoint(null); + setActiveDataKey(null); + }; + + // Get visible servers and their data keys for the chart + const visibleDataKeys = useMemo(() => { + const keys: string[] = []; + + filterOptions.servers.forEach((server) => { + if (visibleServers[server]) { + if (filters.http2 === "all") { + // Check if this server has both HTTP/1.1 and HTTP/2 data + const serverData = filteredData.filter( + (item) => item.server === server, + ); + const hasHttp1 = serverData.some((item) => !item.http2); + const hasHttp2 = serverData.some((item) => item.http2); + + if (hasHttp1) keys.push(`${formatServerName(server)} (HTTP/1.1)`); + if (hasHttp2) keys.push(`${formatServerName(server)} (HTTP/2)`); + } else { + keys.push(formatServerName(server)); + } + } + }); + + return keys; + }, [ + filterOptions.servers, + visibleServers, + filters.http2, + filteredData, + formatServerName, + ]); + + // Get protocol display text + const getProtocolDisplay = () => { + if (filters.http2 === "all") return "All Protocols"; + return filters.http2 ? "HTTP/2" : "HTTP/1.1"; + }; + + // Show loading state if no data + const visibleServersForUrl = useMemo(() => { + return Object.keys(visibleServers).filter( + (server) => visibleServers[server], + ); + }, [visibleServers]); + + // Update URL parameters when visible servers change + useEffect(() => { + updateUrlParams({ ...filters, visibleServers: visibleServersForUrl }); + }, [visibleServersForUrl, filters, updateUrlParams]); + + if (!data || !Array.isArray(data) || data.length === 0) { + return ( +
+

No benchmark data available

+
+ ); + } + + return ( +
+
+ {/* Left column: Filters */} +
+ + + Filters + + +
+ {/* CPU filter */} +
+ + +
+ + {/* Test case filter with groups */} +
+ + +
+ +
+ {/* Threads filter - disabled if xAxis is threads */} +
+ + +
+ + {/* Workers filter - disabled if xAxis is workers */} +
+ + +
+
+ +
+ {/* Concurrency filter - disabled if xAxis is concurrency */} +
+ + +
+ + {/* HTTP2 filter */} +
+ + +
+
+ + {/* X-Axis selection */} +
+ + +
+ + {/* Metric selection */} +
+ + + handleFilterChange("metric", value) + } + className="w-full" + > + + + RPS + + + P95 + + + Errors + + + + + {/* Better indicator */} +
+ + {metricInfo.isBetter} is better +
+
+ + {/* Compact Summary Stats */} + {summaryStats && ( +
+
+ Results: + {summaryStats.count} +
+
+ Min: + + {metricInfo.formatter(summaryStats.min)} + +
+
+ Max: + + {metricInfo.formatter(summaryStats.max)} + +
+
+ Avg: + + {metricInfo.formatter(summaryStats.avg)} + +
+
+ )} +
+
+
+
+ + {/* Right column: Chart and Error Distribution */} +
+
+ {/* Main Chart */} + + + + {metricInfo.label}: {filters.cpu} - {filters.testCase} + + {filters.xAxis !== "threads" && + `Threads: ${filters.threads}, `} + {filters.xAxis !== "workers" && + `Workers: ${filters.workers}, `} + {filters.xAxis !== "concurrency" && + `Concurrency: ${filters.concurrency}, `} + Protocol: {getProtocolDisplay()} + + + + + {chartData.length === 0 ? ( +
+

+ No data available for the selected filters +

+
+ ) : ( + <> +
+ + + + + + + + {/* Render bars for each visible data key */} + {visibleDataKeys.map((dataKey, index) => ( + + handleChartHover({ + activePayload: [ + { dataKey, payload: data.payload }, + ], + }) + } + onMouseLeave={() => { + setHoveredPoint(null); + setActiveDataKey(null); + }} + /> + ))} + + {/* Add invisible bars for zero values to enable hovering */} + {visibleDataKeys.map((dataKey, index) => ( + { + if (data.payload[dataKey] === 0) { + handleChartHover({ + activePayload: [ + { dataKey, payload: data.payload }, + ], + }); + } + }} + onMouseLeave={() => { + setHoveredPoint(null); + setActiveDataKey(null); + }} + /> + ))} + + +
+ + {/* Server Toggle Legend */} +
+ {filterOptions.servers.map((server) => ( +
handleLegendClick(server, event)} + title={`Click to toggle, ${navigator.platform.includes("Mac") ? "Cmd" : "Ctrl"}+click to show only this server`} + > +
+ + {formatServerName(server)} + +
+ ))} +
+ + )} + + + + {/* Top Performers and Benchmark Details in horizontal layout */} +
+
0 ? "lg:col-span-7" : "lg:col-span-12" + } + > + + + + {hoveredPoint ? "Benchmark Details" : "Hover Details"} + {!hoveredPoint && ( + + + Hover over chart bars to see details + + )} + + + + {hoveredPoint ? ( +
+ {/* Header with server and test case */} +
+ +
+ {formatServerName(hoveredPoint.server)} + + + {hoveredPoint.test_case} + + + {filters.xAxis}:{" "} + {String( + hoveredPoint[ + filters.xAxis as keyof BenchmarkResult + ], + )} + + + {hoveredPoint.http2 ? "HTTP/2" : "HTTP/1.1"} + +
+ + {/* Version information in a separate row */} + {hoveredPoint.version && ( +
+ + + {hoveredPoint.version} + +
+ )} + + {/* Performance metrics */} +
+
+ RPS: + + {hoveredPoint.results.requestsPerSec.toFixed(2)} + +
+
+ + Success Rate: + + + {(hoveredPoint.results.successRate * 100).toFixed( + 2, + )} + % + +
+
+
+
+
+ + P95 Latency: + + + {hoveredPoint.results.p95_latency != null + ? `${hoveredPoint.results.p95_latency.toFixed(2)} ms` + : "N/A"} + +
+
+ + Avg Latency: + + + {hoveredPoint.results.average != null + ? `${hoveredPoint.results.average.toFixed(2)} ms` + : "N/A"} + +
+
+ + {/* Error distribution */} + {Object.keys( + hoveredPoint.results.errorDistribution || {}, + ).length > 0 ? ( +
+
+ Error Distribution: +
+
+ {Object.entries( + hoveredPoint.results.errorDistribution || {}, + ).map(([errorType, count]) => ( +
+ {errorType} + + {count} + +
+ ))} +
+
+ ) : ( +

+ No errors reported +

+ )} +
+ ) : ( +

+ Hover over a data point to see benchmark details +

+ )} + + +
+ {topPerformers.length > 0 && ( +
+ + + + Top Performers{" "} + {filteredData[0] && (filteredData[0] as any).group && ( + + ({(filteredData[0] as any).group}) + + )} + + + +
+ {topPerformers.map(({ server, count }) => ( +
+
+
+ + {formatServerName(server)} + +
+ + {count} + +
+ ))} +
+ {(filteredData[0] as any).group == "rack" && ( +

+ Note: Some servers (e.g. Unicorn, Agoo) don’t + participate in multi-threaded test cases so will + appear less frequently in these results. +

+ )} + + +
+ )} +
+
+
+
+
+ ); +} diff --git a/docs/benchmark-dashboard/components/theme-provider.tsx b/docs/benchmark-dashboard/components/theme-provider.tsx new file mode 100644 index 00000000..55c2f6eb --- /dev/null +++ b/docs/benchmark-dashboard/components/theme-provider.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as React from 'react' +import { + ThemeProvider as NextThemesProvider, + type ThemeProviderProps, +} from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/docs/benchmark-dashboard/components/ui/accordion.tsx b/docs/benchmark-dashboard/components/ui/accordion.tsx new file mode 100644 index 00000000..24c788c2 --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/docs/benchmark-dashboard/components/ui/alert-dialog.tsx b/docs/benchmark-dashboard/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..25e7b474 --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/docs/benchmark-dashboard/components/ui/alert.tsx b/docs/benchmark-dashboard/components/ui/alert.tsx new file mode 100644 index 00000000..41fa7e05 --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/docs/benchmark-dashboard/components/ui/aspect-ratio.tsx b/docs/benchmark-dashboard/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..d6a5226f --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/aspect-ratio.tsx @@ -0,0 +1,7 @@ +"use client" + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/docs/benchmark-dashboard/components/ui/avatar.tsx b/docs/benchmark-dashboard/components/ui/avatar.tsx new file mode 100644 index 00000000..51e507ba --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/docs/benchmark-dashboard/components/ui/badge.tsx b/docs/benchmark-dashboard/components/ui/badge.tsx new file mode 100644 index 00000000..f000e3ef --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/docs/benchmark-dashboard/components/ui/breadcrumb.tsx b/docs/benchmark-dashboard/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..60e6c96f --- /dev/null +++ b/docs/benchmark-dashboard/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>