Преглед изворни кода

Merge pull request #1938 from Jesterovskiy/add_kemal_crystal

Add Kemal Crystal framework
Mike Smith пре 9 година
родитељ
комит
db2bf9ec0c

+ 1 - 0
.travis.yml

@@ -40,6 +40,7 @@ env:
     - "TESTDIR=Clojure/aleph"
     - "TESTDIR=Crystal/crystal-raw"
     - "TESTDIR=Crystal/moonshine"
+    - "TESTDIR=Crystal/kemal"
     - "TESTDIR=D/vibed"
     - "TESTDIR=Dart/dart-raw"
     - "TESTDIR=Dart/redstone"

+ 5 - 0
frameworks/Crystal/kemal/.gitignore

@@ -0,0 +1,5 @@
+.deps
+.deps.lock
+libs/
+.crystal
+.shards

+ 5 - 0
frameworks/Crystal/kemal/README.md

@@ -0,0 +1,5 @@
+# Crystal-Kemal
+
+This is the [Kemal](https://github.com/sdogruyol/kemal) test of the Framework Benchmarks. Crystal is a new language that closely resembles Ruby with a goal of removing typed variables and parameters (instead inferencing), whilst maintaining top speed through bindings into C.
+
+Kemal is a fast and simple web framework for Crystal language. Inspired by Sinatra but with superior performance and built-in WebSocket support.

+ 51 - 0
frameworks/Crystal/kemal/benchmark_config.json

@@ -0,0 +1,51 @@
+{
+  "framework": "kemal",
+  "tests": [{
+    "default": {
+      "setup_file": "setup-redis",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 3000,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Redis",
+      "framework": "kemal",
+      "language": "Crystal",
+      "orm": "micro",
+      "platform": "Crystal",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kemal",
+      "notes": "",
+      "versus": "ruby"
+    },
+    "postgres": {
+      "setup_file": "setup-postgres",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 3000,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "kemal",
+      "language": "Crystal",
+      "orm": "micro",
+      "platform": "Crystal",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kemal",
+      "notes": "",
+      "versus": "ruby"
+    }
+  }]
+}

+ 151 - 0
frameworks/Crystal/kemal/server-postgres.cr

@@ -0,0 +1,151 @@
+require "kemal"
+require "pg"
+require "pool/connection"
+require "html_builder"
+
+# Compose Objects (like Hash) to have a to_json method
+require "json/to_json"
+
+DB = ConnectionPool.new(capacity: 25, timeout: 0.01) do
+  PG.connect("postgres://benchmarkdbuser:benchmarkdbpass@#{ENV["DBHOST"]? || "127.0.0.1"}/hello_world")
+end
+
+class CONTENT
+  UTF8 = "; charset=UTF-8"
+  JSON = "application/json"
+  PLAIN = "text/plain"
+  HTML = "text/html" + UTF8
+end
+
+ID_MAXIMUM = 10_000
+
+private def randomWorld
+  id = rand(1..ID_MAXIMUM)
+  conn = DB.checkout
+  result = conn.exec({Int32, Int32}, "SELECT id, randomNumber FROM world WHERE id = $1", [id]).rows.first
+  DB.checkin(conn)
+  {:id => result[0], :randomNumber => result[1]}
+end
+
+private def setWorld(world)
+  conn = DB.checkout
+  result = conn.exec("UPDATE world set randomNumber = $1 where id = $2", [world[:randomNumber], world[:id]])
+  DB.checkin(conn)
+  world
+end
+
+private def fortunes
+  data = [] of  Hash(Symbol, (String | Int32))
+
+  DB.connection.exec({Int32, String}, "select id, message from Fortune").rows.each do |row|
+    data.push({:id => row[0], :message => row[1]})
+  end
+  data
+end
+
+private def sanitizedQueryCount(request)
+  queries = request.params["queries"] as String
+  return 1 if queries.empty? || queries.to_i?.nil?
+  if queries.to_i > 500
+    queries = 500
+  elsif queries.to_i < 1
+    queries = 1
+  end
+  queries.to_i
+end
+
+before_all do |env|
+  env.response.headers["Server"] = "Kemal"
+  env.response.headers["Date"] = Time.now.to_s
+end
+
+#
+# Basic Tests
+#
+
+# Test 1: JSON Serialization
+get "/json", do |env|
+  env.response.content_type = CONTENT::JSON
+  { :message => "Hello, World!" }.to_json
+end
+
+# Test 6: Plaintext
+get "/plaintext", do |env|
+  env.response.content_type = CONTENT::PLAIN
+  "Hello, World!"
+end
+
+#
+# Postgres DatabaseTests
+#
+
+# Postgres Test 2: Single database query
+get "/db", do |env|
+  env.response.content_type = CONTENT::JSON
+  randomWorld.to_json
+end
+
+# Postgres Test 3: Multiple database query
+get "/queries", do |env|
+  results = (1..sanitizedQueryCount(env)).map do
+    randomWorld
+  end
+
+  env.response.content_type = CONTENT::JSON
+  results.to_json
+end
+
+# Postgres Test 4: Fortunes
+get "/fortunes", do |env|
+  data = fortunes
+
+  additional_fortune = {
+    :id => 0,
+    :message => "Additional fortune added at request time."
+  }
+  data.push(additional_fortune)
+
+  data.sort! do |a, b|
+    a[:message].to_s <=> b[:message].to_s
+  end
+
+  # New builder for each request!
+  html = HTML::Builder.new.build do
+    html {
+      head {
+        title { text "Fortunes" }
+      }
+      body {
+        table {
+          tr {
+            thead { text "id" }
+            thead { text "message" }
+          }
+          data.each { |e|
+            tr {
+              td { text e[:id].to_s }
+              td { text e[:message].to_s }
+            }
+          }
+        }
+      }
+    }
+  end
+
+  # Doctype not available in builder
+  # builder only supports `thead`, tests need to see `th`
+  env.response.content_type = CONTENT::HTML
+  "<!doctype html>" + html.gsub("thead", "th")
+end
+
+# Postgres Test 5: Database Updates
+get "/updates", do |env|
+  updated = (1..sanitizedQueryCount(env)).map do
+    world = randomWorld
+    world[:randomNumber] = rand(1..ID_MAXIMUM)
+    setWorld(world)
+  end
+
+  env.response.content_type = CONTENT::JSON
+  updated.to_json
+end

+ 145 - 0
frameworks/Crystal/kemal/server-redis.cr

@@ -0,0 +1,145 @@
+require "kemal"
+require "redis"
+require "html_builder"
+
+# Compose Objects (like Hash) to have a to_json method
+require "json/to_json"
+
+REDIS = Redis.new
+
+class CONTENT
+  UTF8 = "; charset=UTF-8"
+  JSON = "application/json"
+  PLAIN = "text/plain"
+  HTML = "text/html" + UTF8
+end
+
+ID_MAXIMUM = 10_000
+
+private def randomWorld
+  id = rand(1..ID_MAXIMUM)
+  num = REDIS.get("world:" + id.to_s)
+  { :id => id, :randomNumber => num }
+end
+
+private def setWorld(world)
+  id = "world:" + world[:id].to_s
+  REDIS.set(id, world[:randomNumber])
+  world
+end
+
+private def fortunes
+  data = [] of  Hash(Symbol, (String | Int32))
+
+  REDIS.lrange("fortunes", 0, -1).each_with_index do |e, i|
+    data.push({:id => i + 1, :message => e.to_s})
+  end
+  data
+end
+
+private def sanitizedQueryCount(request)
+  queries = request.params["queries"] as String
+  return 1 if queries.empty? || queries.to_i?.nil?
+  if queries.to_i > 500
+    queries = 500
+  elsif queries.to_i < 1
+    queries = 1
+  end
+  queries.to_i
+end
+
+before_all do |env|
+  env.response.headers["Server"] = "Kemal"
+  env.response.headers["Date"] = Time.now.to_s
+end
+
+#
+# Basic Tests
+#
+
+# Test 1: JSON Serialization
+get "/json", do |env|
+  env.response.content_type = CONTENT::JSON
+  { :message => "Hello, World!" }.to_json
+end
+
+# Test 6: Plaintext
+get "/plaintext", do |env|
+  env.response.content_type = CONTENT::PLAIN
+  "Hello, World!"
+end
+
+#
+# Redis DatabaseTests
+#
+
+# Redis Test 2: Single database query
+get "/db", do |env|
+  env.response.content_type = CONTENT::JSON
+  randomWorld.to_json
+end
+
+# Redis Test 3: Multiple database query
+get "/queries", do |env|
+  results = (1..sanitizedQueryCount(env)).map do
+    randomWorld
+  end
+
+  env.response.content_type = CONTENT::JSON
+  results.to_json
+end
+
+# Redis Test 4: Fortunes
+get "/fortunes", do |env|
+  data = fortunes
+
+  additional_fortune = {
+    :id => 0,
+    :message => "Additional fortune added at request time."
+  }
+  data.push(additional_fortune)
+
+  data.sort! do |a, b|
+    a[:message].to_s <=> b[:message].to_s
+  end
+
+  # New builder for each request!
+  html = HTML::Builder.new.build do
+    html {
+      head {
+        title { text "Fortunes" }
+      }
+      body {
+        table {
+          tr {
+            thead { text "id" }
+            thead { text "message" }
+          }
+          data.each { |e|
+            tr {
+              td { text e[:id].to_s }
+              td { text e[:message].to_s }
+            }
+          }
+        }
+      }
+    }
+  end
+
+  # Doctype not available in builder
+  # builder only supports `thead`, tests need to see `th`
+  env.response.content_type = CONTENT::HTML
+  "<!doctype html>" + html.gsub("thead", "th")
+end
+
+# Redis Test 5: Database Updates
+get "/updates", do |env|
+  updated = (1..sanitizedQueryCount(env)).map do
+    world = randomWorld
+    world[:randomNumber] = rand(1..ID_MAXIMUM)
+    setWorld(world)
+  end
+
+  env.response.content_type = CONTENT::JSON
+  updated.to_json
+end

+ 7 - 0
frameworks/Crystal/kemal/setup-postgres.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends crystal
+
+crystal deps install
+
+crystal server-postgres.cr &

+ 7 - 0
frameworks/Crystal/kemal/setup-redis.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends crystal
+
+crystal deps install
+
+crystal server-redis.cr &

+ 19 - 0
frameworks/Crystal/kemal/shard.yml

@@ -0,0 +1,19 @@
+name: "kemal"
+version: "0.0.1"
+
+dependencies:
+  pg:
+    github: will/crystal-pg
+    branch: master
+  pool:
+    github: ysbaddaden/pool
+    branch: master
+  kemal:
+    github: "sdogruyol/kemal"
+    branch: "master"
+  redis:
+    github: "stefanwille/crystal-redis"
+    version: "~> 1.3.1"
+  html_builder:
+    github: crystal-lang/html_builder
+license: MIT

+ 2 - 2
toolset/setup/linux/languages/crystal.sh

@@ -7,7 +7,7 @@ RETCODE=$(fw_exists ${IROOT}/crystal.installed)
 
 # install crystal
 
-VERSION="0.9.1"
+VERSION="0.12.0"
 
 SAVE_AS=crystal-$VERSION-1-linux-x86_64.tar.gz
 URL=https://github.com/manastech/crystal/releases/download/$VERSION/crystal-$VERSION-1-linux-x86_64.tar.gz
@@ -18,7 +18,7 @@ fw_untar ${SAVE_AS}
 
 # install shards
 
-SVERSION="0.5.1"
+SVERSION="0.6.1"
 SAVE_AS=shards-${SVERSION}_linux_x86_64
 URL=https://github.com/ysbaddaden/shards/releases/download/v${SVERSION}/shards-${SVERSION}_linux_x86_64.gz