itsi 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Cargo.lock +76 -74
  4. data/crates/itsi_acme/Cargo.toml +1 -1
  5. data/crates/itsi_scheduler/Cargo.toml +1 -1
  6. data/crates/itsi_scheduler/extconf.rb +3 -1
  7. data/crates/itsi_server/Cargo.lock +1 -1
  8. data/crates/itsi_server/Cargo.toml +3 -1
  9. data/crates/itsi_server/extconf.rb +3 -1
  10. data/crates/itsi_server/src/lib.rs +7 -1
  11. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
  12. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +6 -6
  13. data/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
  14. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +71 -42
  15. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
  16. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
  17. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +32 -6
  18. data/crates/itsi_server/src/ruby_types/itsi_server.rs +1 -1
  19. data/crates/itsi_server/src/server/binds/listener.rs +49 -8
  20. data/crates/itsi_server/src/server/frame_stream.rs +142 -0
  21. data/crates/itsi_server/src/server/http_message_types.rs +143 -10
  22. data/crates/itsi_server/src/server/io_stream.rs +28 -5
  23. data/crates/itsi_server/src/server/lifecycle_event.rs +1 -1
  24. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
  25. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
  26. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
  27. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
  28. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -58
  29. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +6 -9
  30. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +27 -42
  31. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +65 -14
  32. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +1 -1
  33. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +8 -11
  34. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +21 -8
  35. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
  36. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +1 -5
  37. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
  38. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +13 -6
  39. data/crates/itsi_server/src/server/mod.rs +1 -0
  40. data/crates/itsi_server/src/server/process_worker.rs +5 -5
  41. data/crates/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
  42. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
  43. data/crates/itsi_server/src/server/serve_strategy/mod.rs +1 -0
  44. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +166 -206
  45. data/crates/itsi_server/src/server/signal.rs +37 -9
  46. data/crates/itsi_server/src/server/thread_worker.rs +92 -70
  47. data/crates/itsi_server/src/services/itsi_http_service.rs +67 -62
  48. data/crates/itsi_server/src/services/mime_types.rs +185 -183
  49. data/crates/itsi_server/src/services/rate_limiter.rs +16 -34
  50. data/crates/itsi_server/src/services/static_file_server.rs +35 -60
  51. data/docs/benchmark-dashboard/.gitignore +27 -0
  52. data/docs/benchmark-dashboard/app/api/benchmarks/route.ts +22 -0
  53. data/docs/benchmark-dashboard/app/globals.css +94 -0
  54. data/docs/benchmark-dashboard/app/layout.tsx +20 -0
  55. data/docs/benchmark-dashboard/app/page.tsx +252 -0
  56. data/docs/benchmark-dashboard/components/benchmark-dashboard.tsx +1663 -0
  57. data/docs/benchmark-dashboard/components/theme-provider.tsx +11 -0
  58. data/docs/benchmark-dashboard/components/ui/accordion.tsx +58 -0
  59. data/docs/benchmark-dashboard/components/ui/alert-dialog.tsx +141 -0
  60. data/docs/benchmark-dashboard/components/ui/alert.tsx +59 -0
  61. data/docs/benchmark-dashboard/components/ui/aspect-ratio.tsx +7 -0
  62. data/docs/benchmark-dashboard/components/ui/avatar.tsx +50 -0
  63. data/docs/benchmark-dashboard/components/ui/badge.tsx +36 -0
  64. data/docs/benchmark-dashboard/components/ui/breadcrumb.tsx +115 -0
  65. data/docs/benchmark-dashboard/components/ui/button.tsx +56 -0
  66. data/docs/benchmark-dashboard/components/ui/calendar.tsx +66 -0
  67. data/docs/benchmark-dashboard/components/ui/card.tsx +79 -0
  68. data/docs/benchmark-dashboard/components/ui/carousel.tsx +262 -0
  69. data/docs/benchmark-dashboard/components/ui/chart.tsx +365 -0
  70. data/docs/benchmark-dashboard/components/ui/checkbox.tsx +30 -0
  71. data/docs/benchmark-dashboard/components/ui/collapsible.tsx +11 -0
  72. data/docs/benchmark-dashboard/components/ui/command.tsx +153 -0
  73. data/docs/benchmark-dashboard/components/ui/context-menu.tsx +200 -0
  74. data/docs/benchmark-dashboard/components/ui/dialog.tsx +122 -0
  75. data/docs/benchmark-dashboard/components/ui/drawer.tsx +118 -0
  76. data/docs/benchmark-dashboard/components/ui/dropdown-menu.tsx +200 -0
  77. data/docs/benchmark-dashboard/components/ui/form.tsx +178 -0
  78. data/docs/benchmark-dashboard/components/ui/hover-card.tsx +29 -0
  79. data/docs/benchmark-dashboard/components/ui/input-otp.tsx +71 -0
  80. data/docs/benchmark-dashboard/components/ui/input.tsx +22 -0
  81. data/docs/benchmark-dashboard/components/ui/label.tsx +26 -0
  82. data/docs/benchmark-dashboard/components/ui/loading-spinner.tsx +12 -0
  83. data/docs/benchmark-dashboard/components/ui/menubar.tsx +236 -0
  84. data/docs/benchmark-dashboard/components/ui/navigation-menu.tsx +128 -0
  85. data/docs/benchmark-dashboard/components/ui/pagination.tsx +117 -0
  86. data/docs/benchmark-dashboard/components/ui/popover.tsx +31 -0
  87. data/docs/benchmark-dashboard/components/ui/progress.tsx +28 -0
  88. data/docs/benchmark-dashboard/components/ui/radio-group.tsx +44 -0
  89. data/docs/benchmark-dashboard/components/ui/resizable.tsx +45 -0
  90. data/docs/benchmark-dashboard/components/ui/scroll-area.tsx +48 -0
  91. data/docs/benchmark-dashboard/components/ui/select.tsx +160 -0
  92. data/docs/benchmark-dashboard/components/ui/separator.tsx +31 -0
  93. data/docs/benchmark-dashboard/components/ui/sheet.tsx +140 -0
  94. data/docs/benchmark-dashboard/components/ui/sidebar.tsx +763 -0
  95. data/docs/benchmark-dashboard/components/ui/skeleton.tsx +15 -0
  96. data/docs/benchmark-dashboard/components/ui/slider.tsx +28 -0
  97. data/docs/benchmark-dashboard/components/ui/sonner.tsx +31 -0
  98. data/docs/benchmark-dashboard/components/ui/switch.tsx +29 -0
  99. data/docs/benchmark-dashboard/components/ui/table.tsx +117 -0
  100. data/docs/benchmark-dashboard/components/ui/tabs.tsx +55 -0
  101. data/docs/benchmark-dashboard/components/ui/textarea.tsx +22 -0
  102. data/docs/benchmark-dashboard/components/ui/toast.tsx +129 -0
  103. data/docs/benchmark-dashboard/components/ui/toaster.tsx +35 -0
  104. data/docs/benchmark-dashboard/components/ui/toggle-group.tsx +61 -0
  105. data/docs/benchmark-dashboard/components/ui/toggle.tsx +45 -0
  106. data/docs/benchmark-dashboard/components/ui/tooltip.tsx +30 -0
  107. data/docs/benchmark-dashboard/components/ui/use-mobile.tsx +19 -0
  108. data/docs/benchmark-dashboard/components/ui/use-toast.ts +194 -0
  109. data/docs/benchmark-dashboard/components.json +21 -0
  110. data/docs/benchmark-dashboard/dist/benchmark-dashboard.css +1 -0
  111. data/docs/benchmark-dashboard/dist/benchmark-dashboard.iife.js +211 -0
  112. data/docs/benchmark-dashboard/dist/placeholder-logo.png +0 -0
  113. data/docs/benchmark-dashboard/dist/placeholder-logo.svg +1 -0
  114. data/docs/benchmark-dashboard/dist/placeholder-user.jpg +0 -0
  115. data/docs/benchmark-dashboard/dist/placeholder.jpg +0 -0
  116. data/docs/benchmark-dashboard/dist/placeholder.svg +1 -0
  117. data/docs/benchmark-dashboard/embed.tsx +13 -0
  118. data/docs/benchmark-dashboard/hooks/use-mobile.tsx +19 -0
  119. data/docs/benchmark-dashboard/hooks/use-toast.ts +194 -0
  120. data/docs/benchmark-dashboard/lib/benchmark-utils.ts +54 -0
  121. data/docs/benchmark-dashboard/lib/utils.ts +6 -0
  122. data/docs/benchmark-dashboard/next.config.mjs +14 -0
  123. data/docs/benchmark-dashboard/package-lock.json +5859 -0
  124. data/docs/benchmark-dashboard/package.json +72 -0
  125. data/docs/benchmark-dashboard/pnpm-lock.yaml +5 -0
  126. data/docs/benchmark-dashboard/postcss.config.mjs +8 -0
  127. data/docs/benchmark-dashboard/styles/globals.css +94 -0
  128. data/docs/benchmark-dashboard/tailwind.config.ts +96 -0
  129. data/docs/benchmark-dashboard/tsconfig.json +27 -0
  130. data/docs/benchmark-dashboard/vite.config.ts +24 -0
  131. data/docs/build.rb +52 -0
  132. data/docs/content/benchmarks/index.md +96 -0
  133. data/docs/content/features/_index.md +1 -1
  134. data/docs/content/getting_started/_index.md +76 -46
  135. data/docs/hugo.yaml +3 -0
  136. data/docs/static/results.json +1 -0
  137. data/docs/static/scripts/benchmark-dashboard.iife.js +211 -0
  138. data/docs/static/styles/benchmark-dashboard.css +1 -0
  139. data/examples/rails_with_static_assets/Gemfile.lock +1 -1
  140. data/examples/rails_with_static_assets/Itsi.rb +4 -1
  141. data/gems/scheduler/Cargo.lock +15 -15
  142. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  143. data/gems/server/Cargo.lock +75 -73
  144. data/gems/server/exe/itsi +6 -1
  145. data/gems/server/lib/itsi/http_request.rb +31 -39
  146. data/gems/server/lib/itsi/http_response.rb +5 -0
  147. data/gems/server/lib/itsi/rack_env_pool.rb +59 -0
  148. data/gems/server/lib/itsi/server/config/config_helpers.rb +1 -2
  149. data/gems/server/lib/itsi/server/config/dsl.rb +5 -4
  150. data/gems/server/lib/itsi/server/config/middleware/etag.md +3 -7
  151. data/gems/server/lib/itsi/server/config/middleware/etag.rb +2 -4
  152. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +1 -1
  153. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
  154. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
  155. data/gems/server/lib/itsi/server/config/options/include.rb +5 -2
  156. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +1 -1
  157. data/gems/server/lib/itsi/server/config/options/pipeline_flush.md +16 -0
  158. data/gems/server/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
  159. data/gems/server/lib/itsi/server/config/options/send_buffer_size.md +15 -0
  160. data/gems/server/lib/itsi/server/config/options/send_buffer_size.rb +19 -0
  161. data/gems/server/lib/itsi/server/config/options/writev.md +25 -0
  162. data/gems/server/lib/itsi/server/config/options/writev.rb +19 -0
  163. data/gems/server/lib/itsi/server/config.rb +43 -31
  164. data/gems/server/lib/itsi/server/default_config/Itsi.rb +1 -4
  165. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +2 -0
  166. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +2 -2
  167. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -1
  168. data/gems/server/lib/itsi/server/rack_interface.rb +17 -12
  169. data/gems/server/lib/itsi/server/route_tester.rb +1 -1
  170. data/gems/server/lib/itsi/server/scheduler_interface.rb +2 -0
  171. data/gems/server/lib/itsi/server/version.rb +1 -1
  172. data/gems/server/lib/itsi/server.rb +1 -0
  173. data/gems/server/lib/ruby_lsp/itsi/addon.rb +12 -13
  174. data/gems/server/test/helpers/test_helper.rb +12 -13
  175. data/gems/server/test/middleware/etag.rb +3 -3
  176. data/gems/server/test/middleware/grpc/grpc.rb +13 -14
  177. data/gems/server/test/middleware/grpc/test_service_impl.rb +3 -3
  178. data/gems/server/test/middleware/proxy.rb +262 -268
  179. data/gems/server/test/options/ruby_thread_request_backlog_size.rb +2 -3
  180. data/lib/itsi/version.rb +1 -1
  181. metadata +99 -6
  182. data/tasks.txt +0 -27
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "my-v0-project",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "vite build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@hookform/resolvers": "^3.9.1",
13
+ "@radix-ui/react-accordion": "1.2.2",
14
+ "@radix-ui/react-alert-dialog": "1.1.4",
15
+ "@radix-ui/react-aspect-ratio": "1.1.1",
16
+ "@radix-ui/react-avatar": "1.1.2",
17
+ "@radix-ui/react-checkbox": "1.1.3",
18
+ "@radix-ui/react-collapsible": "1.1.2",
19
+ "@radix-ui/react-context-menu": "2.2.4",
20
+ "@radix-ui/react-dialog": "1.1.4",
21
+ "@radix-ui/react-dropdown-menu": "2.1.4",
22
+ "@radix-ui/react-hover-card": "1.1.4",
23
+ "@radix-ui/react-label": "2.1.1",
24
+ "@radix-ui/react-menubar": "1.1.4",
25
+ "@radix-ui/react-navigation-menu": "1.2.3",
26
+ "@radix-ui/react-popover": "1.1.4",
27
+ "@radix-ui/react-progress": "1.1.1",
28
+ "@radix-ui/react-radio-group": "1.2.2",
29
+ "@radix-ui/react-scroll-area": "1.2.2",
30
+ "@radix-ui/react-select": "2.1.4",
31
+ "@radix-ui/react-separator": "1.1.1",
32
+ "@radix-ui/react-slider": "1.2.2",
33
+ "@radix-ui/react-slot": "1.1.1",
34
+ "@radix-ui/react-switch": "1.1.2",
35
+ "@radix-ui/react-tabs": "1.1.2",
36
+ "@radix-ui/react-toast": "1.2.4",
37
+ "@radix-ui/react-toggle": "1.1.1",
38
+ "@radix-ui/react-toggle-group": "1.1.1",
39
+ "@radix-ui/react-tooltip": "1.1.6",
40
+ "@vitejs/plugin-react": "^4.5.0",
41
+ "autoprefixer": "^10.4.20",
42
+ "class-variance-authority": "^0.7.1",
43
+ "clsx": "^2.1.1",
44
+ "cmdk": "1.0.4",
45
+ "date-fns": "4.1.0",
46
+ "embla-carousel-react": "8.5.1",
47
+ "input-otp": "1.4.1",
48
+ "lucide-react": "^0.454.0",
49
+ "next": "15.2.4",
50
+ "next-themes": "^0.4.4",
51
+ "react": "^19",
52
+ "react-day-picker": "8.10.1",
53
+ "react-dom": "^19",
54
+ "react-hook-form": "^7.54.1",
55
+ "react-resizable-panels": "^2.1.7",
56
+ "recharts": "latest",
57
+ "sonner": "^1.7.1",
58
+ "tailwind-merge": "^2.5.5",
59
+ "tailwindcss-animate": "^1.0.7",
60
+ "vaul": "^0.9.6",
61
+ "vite": "^6.3.5",
62
+ "zod": "^3.24.1"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22",
66
+ "@types/react": "^19",
67
+ "@types/react-dom": "^19",
68
+ "postcss": "^8",
69
+ "tailwindcss": "^3.4.17",
70
+ "typescript": "^5"
71
+ }
72
+ }
@@ -0,0 +1,5 @@
1
+ lockfileVersion: '9.0'
2
+
3
+ settings:
4
+ autoInstallPeers: true
5
+ excludeLinksFromLockfile: false
@@ -0,0 +1,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
@@ -0,0 +1,94 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ body {
6
+ font-family: Arial, Helvetica, sans-serif;
7
+ }
8
+
9
+ @layer utilities {
10
+ .text-balance {
11
+ text-wrap: balance;
12
+ }
13
+ }
14
+
15
+ @layer base {
16
+ :root {
17
+ --background: 0 0% 100%;
18
+ --foreground: 0 0% 3.9%;
19
+ --card: 0 0% 100%;
20
+ --card-foreground: 0 0% 3.9%;
21
+ --popover: 0 0% 100%;
22
+ --popover-foreground: 0 0% 3.9%;
23
+ --primary: 0 0% 9%;
24
+ --primary-foreground: 0 0% 98%;
25
+ --secondary: 0 0% 96.1%;
26
+ --secondary-foreground: 0 0% 9%;
27
+ --muted: 0 0% 96.1%;
28
+ --muted-foreground: 0 0% 45.1%;
29
+ --accent: 0 0% 96.1%;
30
+ --accent-foreground: 0 0% 9%;
31
+ --destructive: 0 84.2% 60.2%;
32
+ --destructive-foreground: 0 0% 98%;
33
+ --border: 0 0% 89.8%;
34
+ --input: 0 0% 89.8%;
35
+ --ring: 0 0% 3.9%;
36
+ --chart-1: 12 76% 61%;
37
+ --chart-2: 173 58% 39%;
38
+ --chart-3: 197 37% 24%;
39
+ --chart-4: 43 74% 66%;
40
+ --chart-5: 27 87% 67%;
41
+ --radius: 0.5rem;
42
+ --sidebar-background: 0 0% 98%;
43
+ --sidebar-foreground: 240 5.3% 26.1%;
44
+ --sidebar-primary: 240 5.9% 10%;
45
+ --sidebar-primary-foreground: 0 0% 98%;
46
+ --sidebar-accent: 240 4.8% 95.9%;
47
+ --sidebar-accent-foreground: 240 5.9% 10%;
48
+ --sidebar-border: 220 13% 91%;
49
+ --sidebar-ring: 217.2 91.2% 59.8%;
50
+ }
51
+ .dark {
52
+ --background: 0 0% 3.9%;
53
+ --foreground: 0 0% 98%;
54
+ --card: 0 0% 3.9%;
55
+ --card-foreground: 0 0% 98%;
56
+ --popover: 0 0% 3.9%;
57
+ --popover-foreground: 0 0% 98%;
58
+ --primary: 0 0% 98%;
59
+ --primary-foreground: 0 0% 9%;
60
+ --secondary: 0 0% 14.9%;
61
+ --secondary-foreground: 0 0% 98%;
62
+ --muted: 0 0% 14.9%;
63
+ --muted-foreground: 0 0% 63.9%;
64
+ --accent: 0 0% 14.9%;
65
+ --accent-foreground: 0 0% 98%;
66
+ --destructive: 0 62.8% 30.6%;
67
+ --destructive-foreground: 0 0% 98%;
68
+ --border: 0 0% 14.9%;
69
+ --input: 0 0% 14.9%;
70
+ --ring: 0 0% 83.1%;
71
+ --chart-1: 220 70% 50%;
72
+ --chart-2: 160 60% 45%;
73
+ --chart-3: 30 80% 55%;
74
+ --chart-4: 280 65% 60%;
75
+ --chart-5: 340 75% 55%;
76
+ --sidebar-background: 240 5.9% 10%;
77
+ --sidebar-foreground: 240 4.8% 95.9%;
78
+ --sidebar-primary: 224.3 76.3% 48%;
79
+ --sidebar-primary-foreground: 0 0% 100%;
80
+ --sidebar-accent: 240 3.7% 15.9%;
81
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
82
+ --sidebar-border: 240 3.7% 15.9%;
83
+ --sidebar-ring: 217.2 91.2% 59.8%;
84
+ }
85
+ }
86
+
87
+ @layer base {
88
+ * {
89
+ @apply border-border;
90
+ }
91
+ body {
92
+ @apply bg-background text-foreground;
93
+ }
94
+ }
@@ -0,0 +1,96 @@
1
+ import type { Config } from "tailwindcss";
2
+
3
+ const config: Config = {
4
+ darkMode: ["class"],
5
+ content: [
6
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
7
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
8
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
9
+ "*.{js,ts,jsx,tsx,mdx}"
10
+ ],
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ background: 'hsl(var(--background))',
15
+ foreground: 'hsl(var(--foreground))',
16
+ card: {
17
+ DEFAULT: 'hsl(var(--card))',
18
+ foreground: 'hsl(var(--card-foreground))'
19
+ },
20
+ popover: {
21
+ DEFAULT: 'hsl(var(--popover))',
22
+ foreground: 'hsl(var(--popover-foreground))'
23
+ },
24
+ primary: {
25
+ DEFAULT: 'hsl(var(--primary))',
26
+ foreground: 'hsl(var(--primary-foreground))'
27
+ },
28
+ secondary: {
29
+ DEFAULT: 'hsl(var(--secondary))',
30
+ foreground: 'hsl(var(--secondary-foreground))'
31
+ },
32
+ muted: {
33
+ DEFAULT: 'hsl(var(--muted))',
34
+ foreground: 'hsl(var(--muted-foreground))'
35
+ },
36
+ accent: {
37
+ DEFAULT: 'hsl(var(--accent))',
38
+ foreground: 'hsl(var(--accent-foreground))'
39
+ },
40
+ destructive: {
41
+ DEFAULT: 'hsl(var(--destructive))',
42
+ foreground: 'hsl(var(--destructive-foreground))'
43
+ },
44
+ border: 'hsl(var(--border))',
45
+ input: 'hsl(var(--input))',
46
+ ring: 'hsl(var(--ring))',
47
+ chart: {
48
+ '1': 'hsl(var(--chart-1))',
49
+ '2': 'hsl(var(--chart-2))',
50
+ '3': 'hsl(var(--chart-3))',
51
+ '4': 'hsl(var(--chart-4))',
52
+ '5': 'hsl(var(--chart-5))'
53
+ },
54
+ sidebar: {
55
+ DEFAULT: 'hsl(var(--sidebar-background))',
56
+ foreground: 'hsl(var(--sidebar-foreground))',
57
+ primary: 'hsl(var(--sidebar-primary))',
58
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
59
+ accent: 'hsl(var(--sidebar-accent))',
60
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
61
+ border: 'hsl(var(--sidebar-border))',
62
+ ring: 'hsl(var(--sidebar-ring))'
63
+ }
64
+ },
65
+ borderRadius: {
66
+ lg: 'var(--radius)',
67
+ md: 'calc(var(--radius) - 2px)',
68
+ sm: 'calc(var(--radius) - 4px)'
69
+ },
70
+ keyframes: {
71
+ 'accordion-down': {
72
+ from: {
73
+ height: '0'
74
+ },
75
+ to: {
76
+ height: 'var(--radix-accordion-content-height)'
77
+ }
78
+ },
79
+ 'accordion-up': {
80
+ from: {
81
+ height: 'var(--radix-accordion-content-height)'
82
+ },
83
+ to: {
84
+ height: '0'
85
+ }
86
+ }
87
+ },
88
+ animation: {
89
+ 'accordion-down': 'accordion-down 0.2s ease-out',
90
+ 'accordion-up': 'accordion-up 0.2s ease-out'
91
+ }
92
+ }
93
+ },
94
+ plugins: [require("tailwindcss-animate")],
95
+ };
96
+ export default config;
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "target": "ES6",
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import path from "node:path";
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ "@": path.resolve(__dirname, "./"),
10
+ },
11
+ },
12
+ define: {
13
+ "process.env.NODE_ENV": JSON.stringify("production"),
14
+ },
15
+ build: {
16
+ lib: {
17
+ entry: "./embed.tsx",
18
+ name: "MyApp",
19
+ fileName: "benchmark-dashboard",
20
+ formats: ["iife"],
21
+ },
22
+ outDir: "dist",
23
+ },
24
+ });
data/docs/build.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+
3
+ def replace_nans(obj)
4
+ case obj
5
+ when Array
6
+ obj.map { |e| replace_nans(e) }
7
+ when Hash
8
+ obj.transform_values { |v| replace_nans(v) }
9
+ when Float
10
+ obj.nan? ? 0.0 : obj
11
+ else
12
+ obj
13
+ end
14
+ end
15
+
16
+ system('cd benchmark-dashboard && '\
17
+ 'npm run build && '\
18
+ 'cp dist/benchmark-dashboard.iife.js ../static/scripts &&'\
19
+ 'cp dist/benchmark-dashboard.css ../static/styles')
20
+
21
+ combined = replace_nans(
22
+ Dir['../benchmarks/results/*'].map do |pth|
23
+ {
24
+ cpu: File.basename(pth),
25
+ groups: Dir["#{pth}/*"].map do |f|
26
+ {
27
+ group: File.basename(f),
28
+ tests: Dir["#{f}/*"].map do |f|
29
+ {
30
+ test: File.basename(f),
31
+ servers: Dir["#{f}/*.json"].map do |s|
32
+ {
33
+ server: File.basename(s, '.json'),
34
+ results: JSON.parse(IO.read(s), allow_nan: true)
35
+ }
36
+ end
37
+ }
38
+ end
39
+ }
40
+ end
41
+ }
42
+ end
43
+ )
44
+
45
+ IO.write(
46
+ './static/results.json',
47
+ combined.to_json
48
+ )
49
+ IO.write(
50
+ './public/results.json',
51
+ combined.to_json
52
+ )
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: "Benchmarks"
3
+ description: "Performance benchmarks for Itsi across different devices and configurations"
4
+ type: docs
5
+ weight: 100
6
+ toc: false
7
+ sidebar:
8
+ exclude: true
9
+ ---
10
+
11
+ <br/>
12
+ <div id="root" style="min-height: 550px;">
13
+ </div>
14
+
15
+ <style>
16
+ .hextra-toc{
17
+ width: 0 !important;
18
+ }
19
+
20
+ html{
21
+ scrollbar-gutter: stable;
22
+ }
23
+ </style>
24
+
25
+
26
+ <link rel="stylesheet" href="/styles/benchmark-dashboard.css"/>
27
+
28
+ <script>
29
+ window.addEventListener("load", () => {
30
+ const script = document.createElement("script");
31
+ script.type = "text/javascript";
32
+ script.src="/scripts/benchmark-dashboard.iife.js";
33
+ document.body.appendChild(script);
34
+ })
35
+ </script>
36
+
37
+ ## Intro
38
+ This benchmark suite tests a variety of the most widely used Rack servers, file servers, reverse proxies, and Ruby gRPC servers across diverse workloads on CPUs that vary significantly in their capabilities. Each benchmark includes error rates and p95 request latency.
39
+ * AMD Ryzen 5600 (6 Core / 12 Thread). AMD64
40
+ * Apple M1 Pro (6P cores + 2E cores). ARM64
41
+ * Intel N97 (4 Cores). AMD64
42
+
43
+ All tests were run on Ruby 3.4.3 with YJIT enabled.
44
+
45
+
46
+ ### Disclaimer
47
+ All source code for these benchmarks is accessible [for review and reproduction](https://github.com/wouterken/itsi-server-benchmarks).
48
+
49
+ While the intention is to fairly represent all software being tested, some results may not accurately reflect the best performance due to non-optimal configuration or errors in the test methodology. I welcome PRs to correct any such misconfiguration.
50
+
51
+ Before using performance to rationalize a server switch, always measure first and confirm that server performance is actually a bottleneck for your workload.
52
+
53
+ The following known caveats apply to these results:
54
+ * **No isolated testing environment**: Benchmarks were run on personal development machines without measures to isolate CPU, memory, or disk I/O from background processes. This may introduce variability or skew in some results.
55
+ * **Test clients and server were run on the same device**: Running both the load generator and the server on the same host introduces contention for CPU and memory resources. All network traffic occurs over the loopback interface, which does not reflect real-world network latency or congestion.
56
+ * **No cooldown between concurrency sweeps**: Concurrency tiers were tested sequentially on a single persistent server process, without a cooldown period between runs. Servers overloaded at high concurrency levels may exhibit degraded performance in subsequent tiers due to lingering backlog or thread exhaustion.
57
+ * **Short test duration**: Each benchmark ran for only 3 seconds. This favors servers that perform well under cold start or burst conditions and may disadvantage those that rely on longer warm-up phases. This tradeoff was made intentionally to reduce the total benchmarking time and device occupation when generating large result sets.
58
+
59
+
60
+ ## Summary
61
+ ### Rack - Fast Handlers + Small Response Bodies
62
+ These tests demonstrate the largest observed difference between pure-Ruby servers and native alternatives, because lightweight endpoints mean native servers spend almost all their time in optimized native code. However, this is rarely the case in true production workloads and is not a realistic indication of the performance impact you might see when switching to a more dynamic workload.
63
+ Examples:
64
+ * [Empty Response](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=empty_response&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
65
+ * [Hello World](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=hello_world&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
66
+
67
+ ### Rack - Non-blocking
68
+ Servers that support non-blocking IO (e.g., Falcon, Itsi) provide the best throughput for test cases that contain blocking operations, as they allow many more simultaneous requests to be multiplexed across fewer threads.
69
+ As expected, performance differences narrow as we increase thread counts and workers for blocking servers.
70
+ * [Chunked](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=chunked&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
71
+ * [IO heavy](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=io_heavy&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
72
+ * [Non Blocking Big Delay](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=nonblocking_big_delay&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
73
+ * [Non Blocking Small Delay](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=nonblocking_many_small_delay&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
74
+
75
+ ### Rack - Heavy CPU
76
+ The above differences all but disappear on CPU-heavy workloads, as most time is spent bottlenecked in CPU-intensive code that behaves identically in all tests regardless of server implementation.
77
+ * [CPU Heavy](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=cpu_heavy&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
78
+
79
+ ### Rack - Large Response Bodies
80
+ When dealing with large bodies, much less time is spent in server code or Ruby handler code, and most performance impact results from how efficiently the server can flush bytes onto the network. Here, techniques like buffering and using vectored operations can reflect a throughput advantage.
81
+ * [Response Size Large](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=response_size_large&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
82
+
83
+ ### Mixed Workload (Static + Dynamic)
84
+ A mixed workload test, requesting an image, a static HTML file, and a dynamic endpoint simultaneously aiming to represent more realistic and varied production workloads. This benchmark demonstrates the advantage of the typical production Ruby app server deployment, fronted by a reverse proxy, as an efficient alternative to serving static assets directly from an app server, where static asset requests are subject to head-of-line blocking. This test also compares this traditional setup to an alternative: using an all-in-one solution like Itsi, which serves static assets from an separate asynchronous reactor, isolated from Ruby worker threads to provide equivalent or better performance, without needing to spin up a dedicated proxy process.
85
+ * [Static Dynamic Mixed](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=static_dynamic_mixed&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
86
+
87
+ ### File Serving
88
+ Native servers and reverse proxies offer the best file-serving performance as they can do this at incredible speeds without contending for the GVL.
89
+ * [Static Large](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=static_large&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
90
+ * [Static Small](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=static_small&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
91
+
92
+ ### gRPC
93
+ The server bundled with the Ruby [gRPC gem](https://github.com/grpc/grpc/tree/master/src/ruby) is the de facto standard for gRPC services in the Ruby ecosystem. However, it doesn't utilize a Fiber scheduler and therefore is more prone to thread exhaustion errors, particularly for streaming calls, than Itsi.
94
+ * [Echo Stream](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=echo_stream&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
95
+ * [Process Payment](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=process_payment&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
96
+ * [Echo Collect](?cpu=amd_ryzen_5_5600x_6_core_processor&testCase=echo_collect&threads=1&workers=1&concurrency=10&http2=all&xAxis=concurrency&metric=rps&visibleServers=agoo%2Cfalcon%2Citsi%2Cpuma%2Cpuma__caddy%2Cpuma__h2o%2Cpuma__itsi%2Cpuma__nginx%2Cpuma__thrust%2Cunicorn%2Ciodine%2Ccaddy%2Ch2o%2Cnginx%2Cgrpc_server.rb)
@@ -35,7 +35,7 @@ Pick and choose **just** the features that make sense for you.
35
35
  {{% /details %}}
36
36
 
37
37
  {{% details title="ETag and Cache Control" closed="true" %}}
38
- * Weak and Strong eTag support.
38
+ * Weak and Strong ETag support.
39
39
  * `If-None-Match` and `If-Modified-Since` support.
40
40
  * Automated etag generation for dynamic content (or forwarding of existing `etags` if present)
41
41
  * See <a target="_blank" href="/middleware/etag">etag</a> and <a target="_blank" href="/middleware/cache_control">cache_control</a>
@@ -15,67 +15,97 @@ Install Ruby
15
15
 
16
16
  ### Step 2 - Install Itsi
17
17
 
18
- {{< tabs items="Linux,Mac,Windows" >}}
18
+ {{< tabs items="Linux,Mac,Windows,FreeBSD" >}}
19
19
  {{< tab >}}
20
20
  **Prerequisites**
21
21
 
22
- You'll need at least `build-essential` and `libclang-dev` installed to build Itsi on Linux.
23
- E.g.
24
- ```bash
25
- apt-get install build-essential libclang-dev
26
- ```
27
22
 
28
- Then use `gem` to install Itsi, or its components based on your Ruby version.
29
-
30
- **For Ruby >= 3.0**:
31
- ```bash
32
- gem install itsi
33
- ```
34
- *(Installs both `itsi-server` and `itsi-scheduler`)*
35
-
36
- **For Ruby 2.7**:
37
- ```bash
38
- gem install itsi-server
39
- ```
40
- *(Installs `itsi-server` only; `itsi-scheduler` is not supported on Ruby 2.7)*
41
-
42
- {{< callout type="info" >}}
43
- Itsi (**server + scheduler**) requires **Ruby >= 3.0**.
44
-
45
- Itsi **server** supports **Ruby >= 2.7**.
46
-
47
- If you wish to use either the scheduler or server independently:
48
- - `gem install itsi-server`
49
- - `gem install itsi-scheduler` (Ruby >= 3.0 only)
50
- {{< /callout >}}
23
+ You'll need at least a C/C++ build environment and `clang` and `curl` (for running `rustup`) installed.
24
+
25
+ #### For Ubuntu / Debian:
26
+ ```bash
27
+ apt-get install build-essential libclang-dev curl
28
+ ```
29
+ #### For Fedora / RHEL / Rocky / AlmaLinux:
30
+ ```bash
31
+ dnf groupinstall "Development Tools"
32
+ dnf install clang curl
33
+ ```
34
+ #### For Arch Linux / Manjaro:
35
+ ```bash
36
+ pacman -S base-devel clang curl
37
+ ```
38
+ #### For Alpine Linux:
39
+ ```bash
40
+ apk add build-base clang curl
41
+ ```
42
+
43
+ Then use `gem` to install Itsi, or its components based on your Ruby version.
44
+
45
+ **For Ruby >= 3.0**:
46
+ ```bash
47
+ gem install itsi
48
+ ```
49
+ *(Installs both `itsi-server` and `itsi-scheduler`)*
50
+
51
+ **For Ruby 2.7**:
52
+ ```bash
53
+ gem install itsi-server
54
+ ```
55
+ *(Installs `itsi-server` only; `itsi-scheduler` is not supported on Ruby 2.7)*
56
+
57
+ > ℹ️ **Itsi (`server` + `scheduler`) requires Ruby >= 3.0**
58
+ > Itsi `server` supports Ruby >= 2.7
59
+ > You can install components individually:
60
+ > `gem install itsi-server`
61
+ > `gem install itsi-scheduler` (Ruby >= 3.0 only)
51
62
 
52
63
  {{< /tab >}}
53
64
  {{< tab >}}
54
65
  **Mac**:
55
66
  **For Ruby >= 3.0**:
56
- ```bash
57
- gem install itsi
58
- ```
67
+ ```bash
68
+ gem install itsi
69
+ ```
70
+ *(Installs both `itsi-server` and `itsi-scheduler`)*
71
+
72
+ **For Ruby 2.7**:
73
+ ```bash
74
+ gem install itsi-server
75
+ ```
76
+ *(Installs `itsi-server` only; `itsi-scheduler` is not supported on Ruby 2.7)*
77
+
78
+ > ℹ️ **Itsi (`server` + `scheduler`) requires Ruby >= 3.0**
79
+ > Itsi `server` supports Ruby >= 2.7
80
+ > You can install components individually:
81
+ > `gem install itsi-server`
82
+ > `gem install itsi-scheduler` (Ruby >= 3.0 only)
83
+ {{< /tab >}}
84
+ {{< tab >}}**Windows**: Itsi currently doesn't support native Windows builds, but it runs well on [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
59
85
 
60
- **For Ruby 2.7**:
61
- ```bash
62
- gem install itsi-server
63
- ```
86
+ Follow the linked instructions to install a Linux distribution like Ubuntu or Debian, and then follow the instructions in the Linux tab.
87
+ {{< /tab >}}
88
+ {{< tab >}}
89
+ **FreeBSD**
64
90
 
65
- {{< callout type="info" >}}
66
- Itsi (**server + scheduler**) requires **Ruby >= 3.0**.
91
+ On FreeBSD you'll need to install a few build tools manually:
92
+ ```bash
93
+ pkg install gmake cmake curl llvm
94
+ ```
67
95
 
68
- Itsi **server** supports **Ruby >= 2.7**.
96
+ Then install Itsi with GNU make to avoid build errors:
97
+ ```bash
98
+ MAKE=gmake gem install itsi
99
+ ```
69
100
 
70
- You can install components individually:
71
- - `gem install itsi-server`
72
- - `gem install itsi-scheduler` (Ruby >= 3.0 only)
73
- {{< /callout >}}
101
+ *(Installs both `itsi-server` and `itsi-scheduler`)*
74
102
 
75
- {{< /tab >}}
76
- {{< tab >}}**Windows**: Itsi currently doesn't support native Windows builds, but it runs well on [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
103
+ **For Ruby 2.7**:
104
+ ```bash
105
+ MAKE=gmake gem install itsi-server
106
+ ```
107
+ *(Installs `itsi-server` only; `itsi-scheduler` is not supported on Ruby 2.7)*
77
108
 
78
- Follow the linked instructions to install a Linux distribution like Ubuntu or Debian, and then follow the instructions in the Linux tab.
79
109
  {{< /tab >}}
80
110
 
81
111
  {{< /tabs >}}
data/docs/hugo.yaml CHANGED
@@ -55,6 +55,9 @@ menu:
55
55
  - name: "Itsi Scheduler"
56
56
  pageRef: "/itsi_scheduler"
57
57
  weight: 2
58
+ - name: "Server Benchmarks"
59
+ pageRef: "/benchmarks"
60
+ weight: 2
58
61
  - name: "Github ↗"
59
62
  url: "https://github.com/wouterken/itsi"
60
63
  weight: 4