瀏覽代碼

Add Hanami framework (#8496)

Petrik de Heus 1 年之前
父節點
當前提交
27bd28a1be

+ 16 - 0
frameworks/Ruby/hanami/Gemfile

@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gem "hanami", "~> 2.0"
+gem "hanami-router", "~> 2.0"
+gem "hanami-controller", "~> 2.0"
+gem "hanami-validations", "~> 2.0"
+
+gem "dry-types", "~> 1.0", ">= 1.6.1"
+gem "puma"
+gem "rake"
+
+gem "rom", "~> 5.3"
+gem "rom-sql", "~> 3.6"
+gem "pg"

+ 1 - 0
frameworks/Ruby/hanami/README.md

@@ -0,0 +1 @@
+# Hanami

+ 3 - 0
frameworks/Ruby/hanami/Rakefile

@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+require "hanami/rake_tasks"

+ 9 - 0
frameworks/Ruby/hanami/app/action.rb

@@ -0,0 +1,9 @@
+# auto_register: false
+# frozen_string_literal: true
+
+require "hanami/action"
+
+module HelloWorld
+  class Action < Hanami::Action
+  end
+end

+ 0 - 0
frameworks/Ruby/hanami/app/actions/.keep


+ 24 - 0
frameworks/Ruby/hanami/app/actions/db/index.rb

@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module HelloWorld
+  module Actions
+    module Db
+      class Index < HelloWorld::Action
+        QUERY_RANGE = 1..10_000    # range of IDs in the Fortune DB
+        include Deps["persistence.rom"]
+
+        def handle(*, response)
+          world = rom.relations[:World].where(id: random_id).one
+          response.headers['Server'] = 'hanami'
+          response.headers['Date'] = Time.now.httpdate
+          response.format = :json
+          response.body = world.to_json
+        end
+
+        def random_id
+          Random.rand(QUERY_RANGE)
+        end
+      end
+    end
+  end
+end

+ 16 - 0
frameworks/Ruby/hanami/app/actions/json/index.rb

@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module HelloWorld
+  module Actions
+    module JSON
+      class Index < HelloWorld::Action
+        def handle(*, response)
+          response.headers['Server'] = 'hanami'
+          response.headers['Date'] = Time.now.httpdate
+          response.format = :json
+          response.body = { 'message' => 'Hello, World!' }.to_json
+        end
+      end
+    end
+  end
+end

+ 15 - 0
frameworks/Ruby/hanami/app/actions/plaintext/index.rb

@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module HelloWorld
+  module Actions
+    module Plaintext
+      class Index < HelloWorld::Action
+        def handle(*, response)
+          response.headers['Server'] = 'hanami'
+          response.headers['Date'] = Time.now.httpdate
+          response.body = 'Hello, World!'
+        end
+      end
+    end
+  end
+end

+ 24 - 0
frameworks/Ruby/hanami/benchmark_config.json

@@ -0,0 +1,24 @@
+{
+  "framework": "hanami",
+  "tests": [{
+    "default": {
+      "db_url": "/db",
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "hanami",
+      "language": "Ruby",
+      "orm": "Full",
+      "platform": "Rack",
+      "webserver": "Puma",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "hanami",
+      "notes": "",
+      "versus": ""
+    }
+  }]
+}

+ 5 - 0
frameworks/Ruby/hanami/config.ru

@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require "hanami/boot"
+
+run Hanami.app

+ 17 - 0
frameworks/Ruby/hanami/config.toml

@@ -0,0 +1,17 @@
+[framework]
+name = "hanami"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Rom"
+platform = "Rack"
+webserver = "Puma"
+versus = "None"
+

+ 11 - 0
frameworks/Ruby/hanami/config/app.rb

@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require "hanami"
+
+module HelloWorld
+  class App < Hanami::App
+    environment(:production) do
+      config.logger.level = :error
+    end
+  end
+end

+ 43 - 0
frameworks/Ruby/hanami/config/auto_tune.rb

@@ -0,0 +1,43 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+# Instantiate about one process per X MiB of available memory, scaling up to as
+# close to MAX_THREADS as possible while observing an upper bound based on the
+# number of virtual/logical CPUs. If there are fewer processes than
+# MAX_THREADS, add threads per process to reach MAX_THREADS.
+require 'etc'
+
+KB_PER_WORKER = 128 * 1_024 # average of peak PSS of single-threaded processes (watch smem -k)
+MIN_WORKERS = 15
+MAX_WORKERS_PER_VCPU = 1.25 # virtual/logical
+MIN_THREADS_PER_WORKER = 1
+MAX_THREADS = Integer(ENV['MAX_CONCURRENCY'] || 256)
+
+def meminfo(arg)
+  File.open('/proc/meminfo') do |f|
+    f.each_line do |line|
+      key, value = line.split(/:\s+/)
+      return value.split(/\s+/).first.to_i if key == arg
+    end
+  end
+
+  raise "Unable to find `#{arg}' in /proc/meminfo!"
+end
+
+def auto_tune
+  avail_mem = meminfo('MemAvailable') * 0.8 - MAX_THREADS * 1_024
+
+  workers = [
+    [(1.0 * avail_mem / KB_PER_WORKER).floor, MIN_WORKERS].max,
+    [(Etc.nprocessors * MAX_WORKERS_PER_VCPU).ceil, MIN_WORKERS].max
+  ].min
+
+  threads_per_worker = [
+    workers < MAX_THREADS ? (1.0 * MAX_THREADS / workers).ceil : -Float::INFINITY,
+    MIN_THREADS_PER_WORKER
+  ].max
+
+  [workers, threads_per_worker]
+end
+
+p auto_tune if $PROGRAM_NAME == __FILE__

+ 30 - 0
frameworks/Ruby/hanami/config/providers/persistence.rb

@@ -0,0 +1,30 @@
+Hanami.app.register_provider :persistence, namespace: true do
+  prepare do
+    require "rom"
+
+    require_relative '../auto_tune'
+    num_workers, num_threads = auto_tune
+
+    opts = {}
+
+    if (threads = num_threads) > 1
+      opts[:max_connections] = (2 * Math.log(threads)).floor
+      opts[:pool_timeout] = 10
+    end
+    config = ROM::Configuration.new(:sql, target["settings"].database_url, opts)
+
+    register "config", config
+    register "db", config.gateways[:default].connection
+  end
+
+  start do
+    config = target["persistence.config"]
+
+    config.auto_registration(
+      target.root.join("lib/hello_world/persistence"),
+      namespace: "HelloWorld::Persistence"
+    )
+
+    register "rom", ROM.container(config)
+  end
+end

+ 18 - 0
frameworks/Ruby/hanami/config/puma.rb

@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+require_relative 'auto_tune'
+
+# FWBM only... use the puma_auto_tune gem in production!
+num_workers, num_threads = auto_tune
+
+workers num_workers
+threads num_threads, num_threads
+
+port        ENV.fetch("HANAMI_PORT", 2300)
+environment ENV.fetch("HANAMI_ENV", "development")
+
+on_worker_boot do
+  Hanami.shutdown
+  Hanami.boot
+end
+
+preload_app!

+ 9 - 0
frameworks/Ruby/hanami/config/routes.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module HelloWorld
+  class Routes < Hanami::Routes
+    get "/db", to: "db.index"
+    get "/json", to: "json.index"
+    get "/plaintext", to: "plaintext.index"
+  end
+end

+ 10 - 0
frameworks/Ruby/hanami/config/settings.rb

@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module HelloWorld
+  class Settings < Hanami::Settings
+    # Define your app settings here, for example:
+    #
+    # setting :my_flag, default: false, constructor: Types::Params::Bool
+    setting :database_url, constructor: Types::String
+  end
+end

+ 20 - 0
frameworks/Ruby/hanami/hanami.dockerfile

@@ -0,0 +1,20 @@
+FROM ruby:3.3-rc
+
+ENV BUNDLE_FORCE_RUBY_PLATFORM=true
+ENV RUBY_YJIT_ENABLE=1
+
+WORKDIR /hanami
+
+COPY Gemfile  ./
+
+RUN bundle install --jobs=8
+
+COPY . .
+
+EXPOSE 8080
+
+ENV HANAMI_ENV=production
+ENV HANAMI_PORT=8080
+ENV DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world
+
+CMD bundle exec hanami server

+ 11 - 0
frameworks/Ruby/hanami/lib/hanami/types.rb

@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require "dry/types"
+
+module Hanami
+  Types = Dry.Types
+
+  module Types
+    # Define your custom types here
+  end
+end

+ 9 - 0
frameworks/Ruby/hanami/lib/hello_world/persistence/relations/world.rb

@@ -0,0 +1,9 @@
+module HelloWorld
+  module Persistence
+    module Relations
+      class World < ROM::Relation[:sql]
+        schema(:World, infer: true)
+      end
+    end
+  end
+end