Browse Source

Merge branch 'round-14' of https://github.com/TechEmpower/FrameworkBenchmarks into round-14

Keith Newman 9 years ago
parent
commit
f1f52f105a

+ 2 - 1
frameworks/Crystal/crystal-raw/server.cr

@@ -3,7 +3,8 @@ require "json"
 
 server = HTTP::Server.new(8080) do |context|
   response = context.response
-  response.headers.merge!({"Server": "Crystal", "Date": Time.utc_now.to_s})
+  response.headers["Server"] = "Crystal"
+  response.headers["Date"] = Time.utc_now.to_s
   case context.request.path
   when "/json"
     response.status_code = 200

+ 1 - 2
frameworks/Crystal/kemal/.gitignore

@@ -1,5 +1,4 @@
-.deps
-.deps.lock
 libs/
 .crystal
 .shards
+!shard.lock

+ 24 - 1
frameworks/Crystal/kemal/benchmark_config.json

@@ -20,7 +20,30 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "Kemal",
+      "display_name": "Kemal (PostgreSQL)",
+      "notes": "",
+      "versus": "ruby"
+    },
+    "redis": {
+      "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 (Redis)",
       "notes": "",
       "versus": "ruby"
     }

+ 37 - 47
frameworks/Crystal/kemal/server-postgres.cr

@@ -11,23 +11,23 @@ DB = ConnectionPool.new(capacity: 25, timeout: 0.01) do
 end
 
 class CONTENT
-  UTF8 = "; charset=UTF-8"
-  JSON = "application/json"
+  UTF8  = "; charset=UTF-8"
+  JSON  = "application/json"
   PLAIN = "text/plain"
-  HTML = "text/html" + UTF8
+  HTML  = "text/html" + UTF8
 end
 
 ID_MAXIMUM = 10_000
 
-private def randomWorld
+private def random_world
   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]}
+  {id: result[0], randomNumber: result[1]}
 end
 
-private def setWorld(world)
+private def set_world(world)
   conn = DB.checkout
   result = conn.exec("UPDATE world set randomNumber = $1 where id = $2", [world[:randomNumber], world[:id]])
   DB.checkin(conn)
@@ -35,23 +35,18 @@ private def setWorld(world)
 end
 
 private def fortunes
-  data = [] of  Hash(Symbol, (String | Int32))
+  data = [] of NamedTuple(id: Int32, message: String)
 
   DB.connection.exec({Int32, String}, "select id, message from Fortune").rows.each do |row|
-    data.push({:id => row[0], :message => row[1]})
+    data.push({id: row[0], message: row[1]})
   end
   data
 end
 
-private def sanitizedQueryCount(request)
-  queries = request.params.query["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
+private def sanitized_query_count(request)
+  queries = request.params.query["queries"].as(String)
+  queries = queries.to_i? || 1
+  queries.clamp(1..500)
 end
 
 before_all do |env|
@@ -64,13 +59,13 @@ end
 #
 
 # Test 1: JSON Serialization
-get "/json", do |env|
+get "/json" do |env|
   env.response.content_type = CONTENT::JSON
-  { :message => "Hello, World!" }.to_json
+  {message: "Hello, World!"}.to_json
 end
 
 # Test 6: Plaintext
-get "/plaintext", do |env|
+get "/plaintext" do |env|
   env.response.content_type = CONTENT::PLAIN
   "Hello, World!"
 end
@@ -80,15 +75,15 @@ end
 #
 
 # Postgres Test 2: Single database query
-get "/db", do |env|
+get "/db" do |env|
   env.response.content_type = CONTENT::JSON
-  randomWorld.to_json
+  random_world.to_json
 end
 
 # Postgres Test 3: Multiple database query
-get "/queries", do |env|
-  results = (1..sanitizedQueryCount(env)).map do
-    randomWorld
+get "/queries" do |env|
+  results = (1..sanitized_query_count(env)).map do
+    random_world
   end
 
   env.response.content_type = CONTENT::JSON
@@ -96,56 +91,51 @@ get "/queries", do |env|
 end
 
 # Postgres Test 4: Fortunes
-get "/fortunes", do |env|
+get "/fortunes" do |env|
   data = fortunes
 
   additional_fortune = {
-    :id => 0,
-    :message => "Additional fortune added at request time."
+    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
+  data.sort_by! {|fortune| fortune[:message] }
 
+  env.response.content_type = CONTENT::HTML
   # New builder for each request!
-  html = HTML::Builder.new.build do
+  HTML::Builder.new.build do
+    doctype
     html {
       head {
-        title { text "Fortunes" }
+        title { html "Fortunes" }
       }
       body {
         table {
           tr {
-            thead { text "id" }
-            thead { text "message" }
+            th { html "id" }
+            th { html "message" }
           }
           data.each { |e|
             tr {
-              td { text e[:id].to_s }
-              td { text e[:message].to_s }
+              td { html e[:id] }
+              td { text e[:message] }
             }
           }
         }
       }
     }
   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)
+get "/updates" do |env|
+  updated = (1..sanitized_query_count(env)).map do
+    set_world({id: random_world[:id], randomNumber: rand(1..ID_MAXIMUM)})
   end
 
   env.response.content_type = CONTENT::JSON
   updated.to_json
 end
+
+Kemal.run

+ 39 - 49
frameworks/Crystal/kemal/server-redis.cr

@@ -8,44 +8,39 @@ require "json/to_json"
 REDIS = Redis.new
 
 class CONTENT
-  UTF8 = "; charset=UTF-8"
-  JSON = "application/json"
+  UTF8  = "; charset=UTF-8"
+  JSON  = "application/json"
   PLAIN = "text/plain"
-  HTML = "text/html" + UTF8
+  HTML  = "text/html" + UTF8
 end
 
 ID_MAXIMUM = 10_000
 
-private def randomWorld
+private def random_world
   id = rand(1..ID_MAXIMUM)
-  num = REDIS.get("world:" + id.to_s)
-  { :id => id, :randomNumber => num }
+  num = REDIS.get("world:#{id}")
+  {id: id, randomNumber: num}
 end
 
-private def setWorld(world)
-  id = "world:" + world[:id].to_s
+private def set_world(world)
+  id = "world:#{world[:id]}"
   REDIS.set(id, world[:randomNumber])
   world
 end
 
 private def fortunes
-  data = [] of  Hash(Symbol, (String | Int32))
+  data = [] of NamedTuple(id: Int32, message: String)
 
   REDIS.lrange("fortunes", 0, -1).each_with_index do |e, i|
-    data.push({:id => i + 1, :message => e.to_s})
+    data.push({id: i + 1, message: e.to_s})
   end
   data
 end
 
-private def sanitizedQueryCount(request)
-  queries = request.params.query["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
+private def sanitized_query_count(request)
+  queries = request.params.query["queries"].as(String)
+  queries = queries.to_i? || 1
+  queries.clamp(1..500)
 end
 
 before_all do |env|
@@ -58,13 +53,13 @@ end
 #
 
 # Test 1: JSON Serialization
-get "/json", do |env|
+get "/json" do |env|
   env.response.content_type = CONTENT::JSON
-  { :message => "Hello, World!" }.to_json
+  {message: "Hello, World!"}.to_json
 end
 
 # Test 6: Plaintext
-get "/plaintext", do |env|
+get "/plaintext" do |env|
   env.response.content_type = CONTENT::PLAIN
   "Hello, World!"
 end
@@ -74,15 +69,15 @@ end
 #
 
 # Redis Test 2: Single database query
-get "/db", do |env|
+get "/db" do |env|
   env.response.content_type = CONTENT::JSON
-  randomWorld.to_json
+  random_world.to_json
 end
 
 # Redis Test 3: Multiple database query
-get "/queries", do |env|
-  results = (1..sanitizedQueryCount(env)).map do
-    randomWorld
+get "/queries" do |env|
+  results = (1..sanitized_query_count(env)).map do
+    random_world
   end
 
   env.response.content_type = CONTENT::JSON
@@ -90,56 +85,51 @@ get "/queries", do |env|
 end
 
 # Redis Test 4: Fortunes
-get "/fortunes", do |env|
+get "/fortunes" do |env|
   data = fortunes
 
   additional_fortune = {
-    :id => 0,
-    :message => "Additional fortune added at request time."
+    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
+  data.sort_by! { |fortune| fortune[:message] }
 
+  env.response.content_type = CONTENT::HTML
   # New builder for each request!
-  html = HTML::Builder.new.build do
+  HTML::Builder.new.build do
+    doctype
     html {
       head {
-        title { text "Fortunes" }
+        title { html "Fortunes" }
       }
       body {
         table {
           tr {
-            thead { text "id" }
-            thead { text "message" }
+            th { html "id" }
+            th { html "message" }
           }
           data.each { |e|
             tr {
-              td { text e[:id].to_s }
-              td { text e[:message].to_s }
+              td { html e[:id] }
+              td { text e[:message] }
             }
           }
         }
       }
     }
   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)
+get "/updates" do |env|
+  updated = (1..sanitized_query_count(env)).map do
+    set_world({id: random_world[:id], randomNumber: rand(1..ID_MAXIMUM)})
   end
 
   env.response.content_type = CONTENT::JSON
   updated.to_json
 end
+
+Kemal.run

+ 1 - 1
frameworks/Crystal/kemal/setup-postgres.sh

@@ -6,4 +6,4 @@ crystal deps install
 
 crystal build --release server-postgres.cr
 
-./server-postgres -e production &
+KEMAL_ENV=production ./server-postgres &

+ 1 - 1
frameworks/Crystal/kemal/setup-redis.sh

@@ -6,4 +6,4 @@ crystal deps install
 
 crystal build --release server-redis.cr
 
-./server-redis -e production &
+KEMAL_ENV=production ./server-redis &

+ 30 - 0
frameworks/Crystal/kemal/shard.lock

@@ -0,0 +1,30 @@
+version: 1.0
+shards:
+  html_builder:
+    github: crystal-lang/html_builder
+    version: 0.2.1
+
+  kemal:
+    github: sdogruyol/kemal
+    version: 0.14.1
+
+  kilt:
+    github: jeromegn/kilt
+    version: 0.3.3
+
+  pg:
+    github: will/crystal-pg
+    version: 0.9.0
+
+  pool:
+    github: ysbaddaden/pool
+    version: 0.2.3
+
+  radix:
+    github: luislavena/radix
+    version: 0.3.0
+
+  redis:
+    github: stefanwille/crystal-redis
+    version: 1.6.6
+

+ 5 - 4
frameworks/Crystal/kemal/shard.yml

@@ -4,16 +4,17 @@ version: "0.0.1"
 dependencies:
   pg:
     github: "will/crystal-pg"
-    version: "0.5.0"
+    version: "0.9.0"
   pool:
     github: "ysbaddaden/pool"
-    version: "0.2.1"
+    version: "0.2.3"
   kemal:
     github: "sdogruyol/kemal"
-    version: "0.10.0"
+    version: "0.14.1"
   redis:
     github: "stefanwille/crystal-redis"
-    version: "~> 1.3.1"
+    version: "1.6.6"
   html_builder:
     github: "crystal-lang/html_builder"
+    version: "0.2.1"
 license: "MIT"

+ 16 - 6
toolset/benchmark/fortune_html_parser.py

@@ -30,19 +30,19 @@ class FortuneHTMLParser(HTMLParser):
   # "DOCTYPE html", so we will surround it with "<!" and ">".
   def handle_decl(self, decl):
     # The spec says that for HTML this is case insensitive,
-    # and since we did not specify xml compliance (where 
+    # and since we did not specify xml compliance (where
     # incorrect casing would throw a syntax error), we must
     # allow all casings. We will lower for our normalization.
     self.body.append("<!{d}>".format(d=decl.lower()))
 
-  # This is called when an HTML character is parsed (i.e. 
-  # &quot;). There are a number of issues to be resolved 
+  # This is called when an HTML character is parsed (i.e.
+  # &quot;). There are a number of issues to be resolved
   # here. For instance, some tests choose to leave the
   # "+" character as-is, which should be fine as far as
   # character escaping goes, but others choose to use the
   # character reference of "&#43;", which is also fine.
   # Therefore, this method looks for all possible character
-  # references and normalizes them so that we can 
+  # references and normalizes them so that we can
   # validate the input against a single valid spec string.
   # Another example problem: "&quot;" is valid, but so is
   # "&#34;"
@@ -74,20 +74,30 @@ class FortuneHTMLParser(HTMLParser):
     # Not sure why some are escaping '/'
     if val == "47" or val == "047" or val == "x2f":
       self.body.append("/")
+    # "&#40;" is a valid escaping of "(", but
+    # it is not required, so we need to normalize for out
+    # final parse and equality check.
+    if val == "40" or val == "040" or val == "x28":
+      self.body.append("(")
+    # "&#41;" is a valid escaping of ")", but
+    # it is not required, so we need to normalize for out
+    # final parse and equality check.
+    if val == "41" or val == "041" or val == "x29":
+      self.body.append(")")
 
   def handle_entityref(self, name):
     # Again, "&mdash;" is a valid escaping of "—", but we
     # need to normalize to "—" for equality checking.
     if name == "mdash":
       self.body.append("—")
-    else:  
+    else:
       self.body.append("&{n};".format(n=name))
 
   # This is called every time a tag is opened. We append
   # each one wrapped in "<" and ">".
   def handle_starttag(self, tag, attrs):
     self.body.append("<{t}>".format(t=tag))
-    
+
     # Append a newline after the <table> and <html>
     if tag.lower() == 'table' or tag.lower() == 'html':
       self.body.append("\n")

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

@@ -7,10 +7,10 @@ RETCODE=$(fw_exists ${IROOT}/crystal.installed)
 
 # install crystal
 
-VERSION="0.12.0"
+VERSION="0.18.7"
 
 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
+URL=https://github.com/crystal-lang/crystal/releases/download/$VERSION/crystal-$VERSION-1-linux-x86_64.tar.gz
 
 fw_get -o $SAVE_AS $URL
 
@@ -18,9 +18,9 @@ fw_untar ${SAVE_AS}
 
 # install shards
 
-SVERSION="0.6.1"
+SVERSION="0.6.3"
 SAVE_AS=shards-${SVERSION}_linux_x86_64
-URL=https://github.com/ysbaddaden/shards/releases/download/v${SVERSION}/shards-${SVERSION}_linux_x86_64.gz
+URL=https://github.com/crystal-lang/shards/releases/download/v${SVERSION}/shards-${SVERSION}_linux_x86_64.gz
 
 fw_get -o ${SAVE_AS}.gz $URL