Browse Source

Merge branch 'master' of https://github.com/TechEmpower/FrameworkBenchmarks

Simon Oberhammer 12 years ago
parent
commit
34019602a5
53 changed files with 494 additions and 355 deletions
  1. 15 1
      benchmarker.py
  2. 30 1
      config/create.js
  3. 21 0
      config/create.sql
  4. 29 0
      framework_test.py
  5. 1 1
      gemini/.project
  6. 0 15
      gemini/Docroot/WEB-INF/GeminiHello-Base.conf
  7. 0 7
      gemini/Docroot/WEB-INF/GeminiHello-Dev.conf
  8. 5 3
      gemini/Docroot/WEB-INF/GeminiHello-Inverness.conf
  9. 2 16
      gemini/Docroot/WEB-INF/GeminiHello-Prod.conf
  10. 1 16
      gemini/Docroot/WEB-INF/GeminiHello.conf
  11. BIN
      gemini/Docroot/WEB-INF/lib/techempower.jar
  12. 16 0
      gemini/Docroot/WEB-INF/mustache/fortunes.mustache
  13. 9 0
      gemini/Docroot/WEB-INF/mustache/layout.mustache
  14. 0 10
      gemini/Docroot/WEB-INF/web.xml
  15. 1 1
      gemini/Source/hello/GhForm.java
  16. 1 1
      gemini/Source/hello/GhStore.java
  17. 65 0
      gemini/Source/hello/home/entity/Fortune.java
  18. 14 0
      gemini/Source/hello/home/handler/HelloHandler.java
  19. 1 0
      gemini/benchmark_config
  20. 32 21
      openresty/app.lua
  21. 2 1
      openresty/nginx.conf
  22. 6 0
      rails/app/controllers/hello_world_controller.rb
  23. 4 0
      rails/app/models/fortune.rb
  24. 12 0
      rails/app/views/hello_world/fortune.html.erb
  25. 1 3
      rails/app/views/layouts/application.html.erb
  26. 2 0
      rails/benchmark_config
  27. 1 0
      rails/config/routes.rb
  28. 1 1
      run-tests.py
  29. 9 6
      servlet/.classpath
  30. 3 0
      servlet/.gitignore
  31. 12 15
      servlet/.project
  32. 5 0
      servlet/.settings/org.eclipse.jdt.core.prefs
  33. 2 1
      servlet/benchmark_config
  34. 9 2
      servlet/pom.xml
  35. 30 0
      servlet/src/main/java/hello/Common.java
  36. 10 19
      servlet/src/main/java/hello/DbPoolServlet.java
  37. 38 0
      servlet/src/main/java/hello/Fortune.java
  38. 69 0
      servlet/src/main/java/hello/FortunesServlet.java
  39. 3 10
      servlet/src/main/java/hello/JsonServlet.java
  40. 24 0
      servlet/src/main/webapp/WEB-INF/jsp/fortunes.jsp
  41. 1 1
      servlet/src/main/webapp/WEB-INF/resin-web.xml
  42. 6 0
      servlet/src/main/webapp/WEB-INF/web.xml
  43. 0 56
      yesod/bench/Application.hs
  44. 0 39
      yesod/bench/Foundation.hs
  45. 0 27
      yesod/bench/Import.hs
  46. 0 12
      yesod/bench/Model.hs
  47. 0 16
      yesod/bench/Settings.hs
  48. 0 8
      yesod/bench/app/main.hs
  49. 0 2
      yesod/bench/config/models
  50. 0 21
      yesod/bench/config/mysql.yml
  51. 0 3
      yesod/bench/config/routes
  52. 0 18
      yesod/bench/config/settings.yml
  53. 1 1
      yesod/setup.py

+ 15 - 1
benchmarker.py

@@ -182,7 +182,10 @@ class Benchmarker:
     except ValueError:
       framework_id = str(framework.sort)
       
-    
+    if test not in self.results['rawData'].keys():
+      self.results['rawData'][test] = dict()
+      self.results['weighttpData'][test] = dict()
+
     self.results['rawData'][test][framework_id] = results
     self.results['weighttpData'][test][framework_id] = dict()
     self.results['weighttpData'][test][framework_id]['latency'] = latency
@@ -421,6 +424,15 @@ class Benchmarker:
         framework = self.results['frameworks'][int(key)]
         writer.writerow([framework] + value)
 
+    # Fortune CSV
+    with open(os.path.join(self.full_results_directory(), "fortune.csv"), 'wb') as csvfile:
+      writer = csv.writer(csvfile)
+      writer.writerow(["Framework"] + self.query_intervals)
+      if 'fortune' in self.results['rawData'].keys():
+        for key, value in self.results['rawData']['fortune'].iteritems():
+          framework = self.results['frameworks'][int(key)]
+          writer.writerow([framework] + value)
+
   ############################################################
   # End __parse_results
   ############################################################
@@ -504,10 +516,12 @@ class Benchmarker:
       self.results['rawData']['json'] = dict()
       self.results['rawData']['db'] = dict()
       self.results['rawData']['query'] = dict()
+      self.results['rawData']['fortune'] = dict()
       self.results['weighttpData'] = dict()
       self.results['weighttpData']['json'] = dict()
       self.results['weighttpData']['db'] = dict()
       self.results['weighttpData']['query'] = dict()
+      self.results['weighttpData']['fortune'] = dict()
     else:
       for x in self.__gather_tests():
         if x.name not in self.results['frameworks']:

+ 30 - 1
config/create.js

@@ -5,4 +5,33 @@ for (var i = 1; i <= 10000; i++) {
 }
 
 // http://docs.mongodb.org/manual/applications/optimization/
-db.world.ensureIndex({id: 1})
+db.world.ensureIndex({id: 1})
+
+db.fortune.drop()
+
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+db.fortune.save( {id: 1, message: 'fortune: No such file or directory'} );
+
+db.fortune.ensureIndex({id: 1})
+
+INSERT INTO fortune (message) VALUES ('A computer scientist is someone who fixes things that aren''t broken.');
+INSERT INTO fortune (message) VALUES ('After enough decimal places, nobody gives a damn.');
+INSERT INTO fortune (message) VALUES ('A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1');
+INSERT INTO fortune (message) VALUES ('A computer program does what you tell it to do, not what you want it to do.');
+INSERT INTO fortune (message) VALUES ('Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen');
+INSERT INTO fortune (message) VALUES ('Any program that runs right is obsolete.');
+INSERT INTO fortune (message) VALUES ('A list is only as strong as its weakest link. — Donald Knuth');
+INSERT INTO fortune (message) VALUES ('Feature: A bug with seniority.');
+INSERT INTO fortune (message) VALUES ('Computers make very fast, very accurate mistakes.');
+INSERT INTO fortune (message) VALUES ('<script>alert("This should not be displayed in a browser alert box.");</script>');
+INSERT INTO fortune (message) VALUES ('フレームワークのベンチマーク');

+ 21 - 0
config/create.sql

@@ -35,3 +35,24 @@ END #
 DELIMITER ;
 
 CALL load_data();
+
+DROP TABLE IF EXISTS Fortune;
+CREATE TABLE  Fortune (
+  id int(10) unsigned NOT NULL auto_increment,
+  message varchar(2048) CHARACTER SET 'utf8' NOT NULL,
+  PRIMARY KEY  (id)
+)
+ENGINE=INNODB;
+
+INSERT INTO fortune (message) VALUES ('fortune: No such file or directory');
+INSERT INTO fortune (message) VALUES ('A computer scientist is someone who fixes things that aren''t broken.');
+INSERT INTO fortune (message) VALUES ('After enough decimal places, nobody gives a damn.');
+INSERT INTO fortune (message) VALUES ('A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1');
+INSERT INTO fortune (message) VALUES ('A computer program does what you tell it to do, not what you want it to do.');
+INSERT INTO fortune (message) VALUES ('Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen');
+INSERT INTO fortune (message) VALUES ('Any program that runs right is obsolete.');
+INSERT INTO fortune (message) VALUES ('A list is only as strong as its weakest link. — Donald Knuth');
+INSERT INTO fortune (message) VALUES ('Feature: A bug with seniority.');
+INSERT INTO fortune (message) VALUES ('Computers make very fast, very accurate mistakes.');
+INSERT INTO fortune (message) VALUES ('<script>alert("This should not be displayed in a browser alert box.");</script>');
+INSERT INTO fortune (message) VALUES ('フレームワークのベンチマーク');

+ 29 - 0
framework_test.py

@@ -141,6 +141,16 @@ class FrameworkTest:
       self.query_url_passed = True
     except (AttributeError, subprocess.CalledProcessError) as e:
       self.query_url_passed = False
+
+    # Fortune
+    try:
+      print "VERIFYING Fortune (" + self.fortune_url + ") ..."
+      url = self.benchmarker.generate_url(self.fortune_url, self.port)
+      subprocess.check_call(["curl", "-f", url])
+      print ""
+      self.fortune_url_passed = True
+    except (AttributeError, subprocess.CalledProcessError) as e:
+      self.fortune_url_passed = False
   ############################################################
   # End verify_urls
   ############################################################
@@ -191,6 +201,19 @@ class FrameworkTest:
         print "Complete"
     except AttributeError:
       pass
+
+    # fortune
+    try:
+      if self.fortune_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "fortune"):
+        sys.stdout.write("BENCHMARKING Fortune ... ") 
+        remote_script = self.__generate_concurrency_script(self.fortune_url, self.port)
+        self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'fortune'))
+        results = self.__parse_test('fortune')
+        self.benchmarker.report_results(framework=self, test="fortune", requests=results['requests'], latency=results['latency'],
+          results=results['results'], total_time=results['total_time'], errors=results['errors'], total_requests=results['totalRequests'])
+        print "Complete"
+    except AttributeError:
+      pass
   ############################################################
   # End benchmark
   ############################################################
@@ -217,6 +240,12 @@ class FrameworkTest:
       results = self.__parse_test('query')
       self.benchmarker.report_results(framework=self, test="query", requests=results['requests'], latency=results['latency'],
         results=results['results'], total_time=results['total_time'], errors=results['errors'], total_requests=results['totalRequests'])
+
+    # Query
+    if os.path.exists(self.benchmarker.output_file(self.name, 'fortune')):
+      results = self.__parse_test('fortune')
+      self.benchmarker.report_results(framework=self, test="fortune", requests=results['requests'], latency=results['latency'],
+        results=results['results'], total_time=results['total_time'], errors=results['errors'], total_requests=results['totalRequests'])
   ############################################################
   # End parse_all
   ############################################################

+ 1 - 1
gemini/.project

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
-	<name>GeminiHello</name>
+	<name>FrameworkBenchmarks-Gemini</name>
 	<comment></comment>
 	<projects>
 		<project>TechEmpower</project>

+ 0 - 15
gemini/Docroot/WEB-INF/GeminiHello-Base.conf

@@ -414,21 +414,6 @@ MailServerCount = 1
 
 EmailerThreadsDaemon = yes
 
-# StartupMailAuthor
-#   Specifies the email address to use when sending out the startup
-#   email.  This just needs to be specified so that the mail server
-#   won't reject it due to anti-spam measures.
-
-StartupMailAuthor = [email protected]
-
-# StartupMailRecipients
-#   When this Gemini application starts, Gemini will send an e-mail notice
-#   to the provided comma-separated list of recipients.  This notice can
-#   be useful for tracking when the site has been restarted.  If set to
-#   empty string, this functionality is disabled.
-
-StartupMailRecipients =
-
 # Mail Server blocks (where 'X' is a sequential ID of the mail servers
 # used by the application).
 #

+ 0 - 7
gemini/Docroot/WEB-INF/GeminiHello-Dev.conf

@@ -10,9 +10,6 @@
 # Extend the baseline configuration.
 Extends = GeminiHello-Base.conf
 
-# TODO: Edit these settings according to the particulars of the
-# Development environments.
-
 DeploymentDescription = Development/${Servlet.MachineName}
 
 # Database connectivity for Development.
@@ -23,8 +20,4 @@ db.LoginPass = root
 # Disable outbound e-mail from the Development environment.
 OutboundMailEnabled = no
 
-# You may want to disable the last login update on Development.
-#BasicSecurity.UpdateLastLogin = no
-
-
 

+ 5 - 3
gemini/Docroot/WEB-INF/GeminiHello-Inverness.conf

@@ -9,7 +9,9 @@
 
 # Extend the development configuration, which in turn extends the
 # baseline configuration.
-Extends = GeminiHello-Dev.conf
+Extends = GeminiHello-Prod.conf
+
+DeploymentDescription = Production/${Servlet.MachineName}
 
 # Now set any attributes that are specific to this machine.
 
@@ -18,5 +20,5 @@ db.ConnectString = 172.16.98.98:3306/hello_world?jdbcCompliantTruncation=false&e
 Log.Console.On = yes
 Log.Console.LogDebugThreshold = 50
 
-#db.Driver.Pooling = 30
-#db.Driver.MaxPooling = 30
+db.Driver.Pooling = 30
+db.Driver.MaxPooling = 30

+ 2 - 16
gemini/Docroot/WEB-INF/GeminiHello-Prod.conf

@@ -10,9 +10,6 @@
 # Extend the baseline configuration.
 Extends = GeminiHello-Base.conf
 
-# TODO: Edit these settings according to the particulars of the
-# Production environment.
-
 DeploymentDescription = Production/${Servlet.MachineName}
 
 # Database connectivity for Production.
@@ -20,19 +17,8 @@ db.ConnectString = localhost:3306/gemini?jdbcCompliantTruncation=false&cachePrep
 db.LoginName = hello
 db.LoginPass = hello
 
-# Mail server definition for the production environment.  TODO: Most
-# likely you shouldn't be using the TechEmpower mail server in 
-# Production, so change this.
-MailServerCount = 1
-MailServer1.ServerAddress = mail.techempower.com
-MailServer1.SmtpPort = 25
-MailServer1.PopPort = 110
-MailServer1.Username = mhixson
-MailServer1.Password = password
-MailServer1.ServerRole = Outbound
-
-# In production, we'll want to have the email exception handler enabled.
-EmailExceptionHandler.Enabled = true
+# For this application, we are not sending any outbound e-mail.
+OutboundMailEnabled = no
 
 # In production, refer to all the static assets via URLs with version strings to
 # allow us to perform aggressive caching.

+ 1 - 16
gemini/Docroot/WEB-INF/GeminiHello.conf

@@ -76,7 +76,7 @@ ApplicationRoot = ${Servlet.ApplicationRoot}
 #   description is used to identify the installation in some system-
 #   generated messages such as exception report e-mails.
 
-DeploymentDescription = Unspecified
+DeploymentDescription = Production
 
 
 # -----------------------------------------------------------------------
@@ -413,21 +413,6 @@ MailServerCount = 1
 
 EmailerThreadsDaemon = yes
 
-# StartupMailAuthor
-#   Specifies the email address to use when sending out the startup
-#   email.  This just needs to be specified so that the mail server
-#   won't reject it due to anti-spam measures.
-
-StartupMailAuthor = [email protected]
-
-# StartupMailRecipients
-#   When this Gemini application starts, Gemini will send an e-mail notice
-#   to the provided comma-separated list of recipients.  This notice can
-#   be useful for tracking when the site has been restarted.  If set to
-#   empty string, this functionality is disabled.
-
-StartupMailRecipients =
-
 # Mail Server blocks (where 'X' is a sequential ID of the mail servers
 # used by the application).
 #

BIN
gemini/Docroot/WEB-INF/lib/techempower.jar


+ 16 - 0
gemini/Docroot/WEB-INF/mustache/fortunes.mustache

@@ -0,0 +1,16 @@
+{{<layout}}
+{{$body}}
+<table>
+<tr>
+<th>id</th>
+<th>message</th>
+</tr>
+{{#.}}
+<tr>
+<td>{{id}}</td>
+<td>{{message}}</td>
+</tr>
+{{/.}}
+</table>
+{{/body}}
+{{/layout}}

+ 9 - 0
gemini/Docroot/WEB-INF/mustache/layout.mustache

@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Fortunes</title>
+</head>
+<body>
+{{$body}}{{/body}}
+</body>
+</html>

+ 0 - 10
gemini/Docroot/WEB-INF/web.xml

@@ -50,14 +50,4 @@
   <!-- Use UTF-8 for everything. -->
   <character-encoding>UTF-8</character-encoding>
 
-  <!-- Enable GZIP compression. -->
-  <!--
-  <filter filter-name="gzip" filter-class="com.caucho.filters.GzipFilter">
-    <init>
-      <use-vary>true</use-vary>
-    </init>
-  </filter>
-  <filter-mapping url-pattern='*' filter-name="gzip" />
-  -->
-
 </web-app>

+ 1 - 1
gemini/Source/hello/GhForm.java

@@ -59,7 +59,7 @@ public class GhForm
   @Override
   protected void onValidlySubmitted()
   {
-    // Does nothing.    
+    // Does nothing.
   }
 
 }   // End GhForm.

+ 1 - 1
gemini/Source/hello/GhStore.java

@@ -5,7 +5,6 @@ import hello.home.entity.*;
 import com.techempower.*;
 import com.techempower.cache.*;
 import com.techempower.data.*;
-import com.techempower.gemini.cluster.client.handler.*;
 import com.techempower.log.*;
 
 /**
@@ -48,6 +47,7 @@ public class GhStore
     
     // Use EntityGroup rather than CacheGroup to ensure World entities are not cached.
     register(EntityGroup.of(World.class));
+    register(EntityGroup.of(Fortune.class));
 
     // Register relationships.
     // We have no relationships in this application.

+ 65 - 0
gemini/Source/hello/home/entity/Fortune.java

@@ -0,0 +1,65 @@
+package hello.home.entity;
+
+import com.techempower.js.*;
+
+import hello.*;
+
+/**
+ * A fortune entity.
+ */
+public class   Fortune
+    extends    GhDataEntity
+    implements Comparable<Fortune>
+{
+
+  private String message;
+  
+  /**
+   * Default Constructor.
+   */
+  public Fortune()
+  {
+    // Does nothing.
+  }
+  
+  /**
+   * Set the message.  
+   */
+  public Fortune setMessage(String message)
+  {
+    this.message = message;
+    return this;
+  }
+  
+  /**
+   * Get the message.
+   */
+  public String getMessage()
+  {
+    return this.message;
+  }
+
+  /**
+   * A visitor factory used to map this class to JSON.
+   */
+  public static final VisitorFactory<Fortune> VISITOR_FACTORY = new VisitorFactory<Fortune>()
+  {
+    @Override
+    public Visitor visitor(Fortune fortune)
+    {
+      return Visitors.map(
+          "id", fortune.getId(),
+          "message", fortune.getMessage());
+    }
+  };
+
+  /**
+   * For our purposes, Fortunes sort by their message text. 
+   */
+  @Override
+  public int compareTo(Fortune other)
+  {
+    return getMessage().compareTo(other.getMessage());
+  }
+  
+}

+ 14 - 0
gemini/Source/hello/home/handler/HelloHandler.java

@@ -59,5 +59,19 @@ public class HelloHandler
     
     return json(worlds);
   }
+  
+  /**
+   * Fetch the full list of Fortunes from the database, sort them by the
+   * fortune message text, and then render the results to simple HTML using a 
+   * server-side template.
+   */
+  @PathSegment
+  public boolean fortunes()
+  {
+    final List<Fortune> fortunes = store.list(Fortune.class);
+    fortunes.add(new Fortune().setMessage("Additional fortune added at request time."));
+    Collections.sort(fortunes);
+    return mustache("fortunes", fortunes);
+  }
 
 }

+ 1 - 0
gemini/benchmark_config

@@ -6,6 +6,7 @@
       "json_url": "/",
       "db_url": "/db",
       "query_url": "/db?queries=",
+      "fortune_url": "/fortunes",
       "port": 8080,
       "sort": 0
     }

+ 32 - 21
openresty/app.lua

@@ -1,30 +1,41 @@
+local _M = {}
+
 local cjson = require "cjson"
 local mysql = require "resty.mysql"
 local math = require "math"
 
-local mysqlconn = {
-    host = "DBHOSTNAME",
-    port = 3306,
-    database = "hello_world",
-    user = "benchmarkdbuser",
-    password = "benchmarkdbpass"
-}
+local encode = cjson.encode
+local random = math.random
+local insert = table.insert
+
 
-ngx.header.content_type = 'application/json'
+function _M.handler(ngx)
+    ngx.header.content_type = 'application/json'
+    
+    if ngx.var.uri == '/json' then
+        local resp = {message = "Hello, World!"}
+        ngx.print( encode(resp) )
+    elseif ngx.var.uri == '/db' then
 
-if ngx.var.uri == '/json' then
-    local resp = {message = "Hello, World!"}
-    ngx.print( cjson.encode(resp) )
+        local mysqlconn = {
+            host = "DBHOSTNAME",
+            port = 3306,
+            database = "hello_world",
+            user = "benchmarkdbuser",
+            password = "benchmarkdbpass"
+        }
 
-elseif ngx.var.uri == '/db' then
-    local db, err = mysql:new()
-    local ok, err = db:connect(mysqlconn)
-    local num_queries = tonumber(ngx.req.get_uri_args()["queries"]) or 1
-    local worlds = {}
-    for i=1, num_queries do
-        local wid = math.random(1, 10000)
-        table.insert(worlds, db:query('SELECT * FROM World WHERE id = '..wid)[1])
+        local db, err = mysql:new()
+        local ok, err = db:connect(mysqlconn)
+        local num_queries = tonumber(ngx.var.arg_queries) or 1
+        local worlds = {}
+        for i=1, num_queries do
+            local wid = random(1, 10000)
+            insert(worlds, db:query('SELECT * FROM World WHERE id = '..wid)[1])
+        end
+        ngx.print( encode(worlds) )
+        local ok, err = db:set_keepalive(0, 256)
     end
-    ngx.print( cjson.encode(worlds) )
-    local ok, err = db:set_keepalive(0, 256)
 end
+
+return _M

+ 2 - 1
openresty/nginx.conf

@@ -8,10 +8,11 @@ events {
 
 http {
     access_log off;
+    lua_package_path 'CWD/openresty/?.lua;;';	
     server {
         listen       8080;
         location / {
-            content_by_lua_file 'CWD/openresty/app.lua';
+            content_by_lua 'require("app").handler(ngx)';
         }
     }
 }

+ 6 - 0
rails/app/controllers/hello_world_controller.rb

@@ -14,4 +14,10 @@ class HelloWorldController < ApplicationController
     end
     render :json => results
   end
+  
+  def fortune
+    @fortunes = Fortune.all
+    @fortunes << Fortune.new(:message => "Additional fortune added at request time.")
+    @fortunes = @fortunes.sort { |x, y| x.message <=> y.message }
+  end
 end

+ 4 - 0
rails/app/models/fortune.rb

@@ -0,0 +1,4 @@
+class Fortune < ActiveRecord::Base
+  self.table_name = "Fortune"
+  attr_accessible :message
+end

+ 12 - 0
rails/app/views/hello_world/fortune.html.erb

@@ -0,0 +1,12 @@
+<table>
+<tr>
+<th>id</th>
+<th>message</th>
+</tr>
+<% @fortunes.each do |fortune| %>
+<tr>
+<td><%= fortune.id %></td>
+<td><%= fortune.message %></td>
+</tr>
+<% end %>
+</table>

+ 1 - 3
rails/app/views/layouts/application.html.erb

@@ -1,11 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <title>Hello World</title>
+<title>Fortunes</title>
 </head>
 <body>
-
 <%= yield %>
-
 </body>
 </html>

+ 2 - 0
rails/benchmark_config

@@ -6,6 +6,7 @@
       "json_url": "/hello_world/json",
       "db_url": "/hello_world/db",
       "query_url": "/hello_world/db?queries=",
+      "fortune_url": "/fortune",
       "port": 8080,
       "sort": 16
     },
@@ -14,6 +15,7 @@
       "json_url": "/rails/hello_world/json",
       "db_url": "/rails/hello_world/db",
       "query_url": "/rails/hello_world/db?queries=",
+      "fortune_url": "/fortune",
       "port": 8080,
       "sort": 17
     }

+ 1 - 0
rails/config/routes.rb

@@ -1,6 +1,7 @@
 Hello::Application.routes.draw do
   get "hello_world/json"
   get "hello_world/db"
+  get "fortune" => "hello_world#fortune"
 
 
   # The priority is based upon order of creation:

+ 1 - 1
run-tests.py

@@ -19,7 +19,7 @@ parser.add_argument('-p', dest='password_prompt', action='store_true')
 parser.add_argument('--install-software', action='store_true', help='runs the installation script before running the rest of the commands')
 parser.add_argument('--test', nargs='+', help='names of tests to run')
 parser.add_argument('--exclude', nargs='+', help='names of tests to exclude')
-parser.add_argument('--type', choices=['all', 'json', 'db', 'query'], default='all', help='which type of test to run')
+parser.add_argument('--type', choices=['all', 'json', 'db', 'query', 'fortune'], default='all', help='which type of test to run')
 parser.add_argument('-m', '--mode', choices=['benchmark', 'verify'], default='benchmark', help='verify mode will only start up the tests, curl the urls and shutdown')
 parser.add_argument('--list-tests', action='store_true', default=False, help='lists all the known tests that can run')
 parser.add_argument('--next-sort', action='store_true', default=False, help='displatys the next value that can be used as a sort value')

+ 9 - 6
servlet/.classpath

@@ -1,10 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="lib" path="docroot/WEB-INF/lib/jackson-annotations-2.1.1.jar"/>
-	<classpathentry kind="lib" path="docroot/WEB-INF/lib/jackson-core-2.1.1.jar"/>
-	<classpathentry kind="lib" path="docroot/WEB-INF/lib/jackson-databind-2.1.1.jar"/>
+	<classpathentry including="**/*.java" kind="src" path="src/main/java"/>
+	<classpathentry kind="var" path="M2_REPO/javax/inject/javax.inject/1/javax.inject-1.jar"/>
+	<classpathentry kind="var" path="M2_REPO/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="var" path="Resin4"/>
-	<classpathentry kind="output" path="docroot/WEB-INF/classes"/>
+	<classpathentry kind="var" path="M2_REPO/mysql/mysql-connector-java/5.1.23/mysql-connector-java-5.1.23.jar"/>
+	<classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.1.2/jackson-databind-2.1.2.jar"/>
+	<classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-annotations/2.1.1/jackson-annotations-2.1.1.jar"/>
+	<classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.1.1/jackson-core-2.1.1.jar"/>
+	<classpathentry kind="var" path="M2_REPO/org/apache/commons/commons-lang3/3.1/commons-lang3-3.1.jar"/>
+	<classpathentry kind="output" path="src/main/webapp/WEB-INF/classes"/>
 </classpath>

+ 3 - 0
servlet/.gitignore

@@ -0,0 +1,3 @@
+lib/
+work/
+*.bat

+ 12 - 15
servlet/.project

@@ -1,17 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
-	<name>ServletHello</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
+  <name>world</name>
+  <comment>NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
+  <projects/>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>

+ 5 - 0
servlet/.settings/org.eclipse.jdt.core.prefs

@@ -0,0 +1,5 @@
+#Thu Apr 11 13:42:30 PDT 2013
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7

+ 2 - 1
servlet/benchmark_config

@@ -11,8 +11,9 @@
       "setup_file": "setup",
       "db_url": "/servlet/db",
       "query_url": "/servlet/db?queries=",
+      "fortune_url": "/servlet//fortunes",
       "port": 8080,
       "sort": 19
     }
   }]
-}
+}

+ 9 - 2
servlet/pom.xml

@@ -22,7 +22,7 @@
             <artifactId>jackson-databind</artifactId>
             <version>2.1.2</version>
         </dependency>
- 
+
         <!-- @Inject -->
         <dependency>
             <groupId>javax.inject</groupId>
@@ -37,7 +37,14 @@
             <version>2.5</version>
             <scope>provided</scope>
         </dependency>
- 
+
+        <!-- Apache Commons Lang -->
+        <dependency>
+          	<groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.1</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 30 - 0
servlet/src/main/java/hello/Common.java

@@ -0,0 +1,30 @@
+package hello;
+
+import org.apache.commons.lang3.*;
+
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Some common functionality and constants used by the Servlet tests.
+ */
+public class Common
+{
+
+  // Constants for setting the content type.
+  protected static final String HEADER_CONTENT_TYPE    = "Content-Type";
+  protected static final String CONTENT_TYPE_JSON      = "application/json";
+  protected static final String CONTENT_TYPE_HTML      = "text/html";
+
+  // Jackson encoder, reused for each response.
+  protected static final ObjectMapper MAPPER = new ObjectMapper();
+
+  /**
+   * Use the OWASP ESAPI HTML encoder to process an untrusted String into a
+   * form suitable for rendering in output HTML.
+   */
+  public static String render(String input)
+  {
+    return StringEscapeUtils.escapeHtml4(input);        
+  }
+  
+}

+ 10 - 19
servlet/src/main/java/hello/DbPoolServlet.java

@@ -10,37 +10,28 @@ import javax.servlet.*;
 import javax.servlet.http.*;
 import javax.sql.*;
 
-import com.fasterxml.jackson.databind.*;
-
 /**
- * Database connectivity (with a Servlet-container managed pool) test
+ * Database connectivity (with a Servlet-container managed pool) test.
  */
 @SuppressWarnings("serial")
 public class DbPoolServlet extends HttpServlet
 {
-  // Constants for setting the content type.
-  private static final String HEADER_CONTENT_TYPE    = "Content-Type";
-  private static final String CONTENT_TYPE_JSON      = "application/json";
-  
+  // Database details.
+  private static final String DB_QUERY = "SELECT * FROM World WHERE id = ?";
+  private static final int    DB_ROWS  = 10000;
+
+  // Database connection pool.
   @Resource(name="jdbc/hello_world")
   private DataSource mysqlDataSource;
-
-  // Jackson encoder, reused for each response.
-  private final ObjectMapper mapper = new ObjectMapper();
-  
-  // Database details.
-  private static final String DB_QUERY               = "SELECT * FROM World WHERE id = ?";
-  private static final int    DB_ROWS                = 10000;
-  
+    
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse res)
       throws ServletException, IOException
   {
     // Set content type to JSON
-    res.setHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON);
+    res.setHeader(Common.HEADER_CONTENT_TYPE, Common.CONTENT_TYPE_JSON);
 
-    // Select the MySQL source by default, but allow the option of selecting
-    // Postgres by providing a parameter named "postgres".
+    // Reference the data source.
     final DataSource source = mysqlDataSource;
     
     // Get the count of queries to run.
@@ -97,7 +88,7 @@ public class DbPoolServlet extends HttpServlet
     // Write JSON encoded message to the response.
     try
     {
-      mapper.writeValue(res.getOutputStream(), worlds);
+      Common.MAPPER.writeValue(res.getOutputStream(), worlds);
     }
     catch (IOException ioe) 
     {

+ 38 - 0
servlet/src/main/java/hello/Fortune.java

@@ -0,0 +1,38 @@
+package hello;
+
+/**
+ * Simple Fortune cookie entity.
+ */
+public class Fortune
+  implements Comparable<Fortune>
+{
+
+  private final int    id;
+  private final String message;
+  
+  public Fortune(int id, String message)
+  {
+    this.id = id;
+    this.message = message;
+  }
+  
+  public int getId()
+  {
+    return this.id;
+  }
+  
+  public String getMessage()
+  {
+    return this.message;
+  }      
+
+  /**
+   * For our purposes, Fortunes sort by their message text. 
+   */
+  @Override
+  public int compareTo(Fortune other)
+  {
+    return getMessage().compareTo(other.getMessage());
+  }
+  
+}

+ 69 - 0
servlet/src/main/java/hello/FortunesServlet.java

@@ -0,0 +1,69 @@
+package hello;
+
+import java.io.*;
+import java.sql.*;
+import java.util.*;
+
+import javax.annotation.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import javax.sql.*;
+
+/**
+ * Fortunes test, returns a list of fortune cookie messages fetched from
+ * a database table and then composed by server-side templates.
+ */
+@SuppressWarnings("serial")
+public class FortunesServlet extends HttpServlet
+{
+  
+  // Database details.
+  private static final String DB_QUERY = "SELECT * FROM Fortune";
+
+  // Database connection pool.
+  @Resource(name="jdbc/hello_world")
+  private DataSource mysqlDataSource;
+  
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse res)
+      throws ServletException, IOException
+  {
+    // Set content type to JSON
+    res.setHeader(Common.HEADER_CONTENT_TYPE, Common.CONTENT_TYPE_HTML);
+
+    // Reference the data source.
+    final DataSource source = mysqlDataSource;
+
+    List<Fortune> fortunes = new ArrayList<>();
+    
+    try (Connection conn = source.getConnection())
+    {
+      try (PreparedStatement statement = conn.prepareStatement(DB_QUERY, 
+          ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY))
+      {
+        try (ResultSet results = statement.executeQuery())
+        {
+          while (results.next())
+          {
+            fortunes.add(new Fortune(results.getInt("id"), results.getString("message")));
+          }
+        }
+      }
+    }
+    catch (SQLException sqlex)
+    {
+      System.err.println("SQL Exception: " + sqlex);
+    }
+    
+    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+    Collections.sort(fortunes);
+    
+    // Set the list of Fortunes as an attribute of the request, making it
+    // available to the JSP.
+    req.setAttribute("fortunes", fortunes);
+    
+    // Dispatch to the JSP.
+    RequestDispatcher disp = req.getRequestDispatcher("/WEB-INF/jsp/fortunes.jsp");
+    disp.forward(req, res);
+  }
+}

+ 3 - 10
servlet/src/main/java/hello/JsonServlet.java

@@ -5,20 +5,12 @@ import java.io.*;
 import javax.servlet.*;
 import javax.servlet.http.*;
 
-import com.fasterxml.jackson.databind.*;
-
 /**
  * JSON Encoding Test
  */
 @SuppressWarnings("serial")
 public class JsonServlet extends HttpServlet
 {
-  // Constants for setting the content type.
-  private static final String HEADER_CONTENT_TYPE    = "Content-Type";
-  private static final String CONTENT_TYPE_JSON      = "application/json";
-
-  // Jackson encoder, reused for each response.
-  private final ObjectMapper mapper = new ObjectMapper();
 
   // Response message class.
   public static class HelloMessage {
@@ -30,16 +22,17 @@ public class JsonServlet extends HttpServlet
       throws ServletException, IOException
   {
     // Set content type to JSON
-    res.setHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON);
+    res.setHeader(Common.HEADER_CONTENT_TYPE, Common.CONTENT_TYPE_JSON);
 
     // Write JSON encoded message to the response.
     try
     {
-      mapper.writeValue(res.getOutputStream(), new HelloMessage());
+      Common.MAPPER.writeValue(res.getOutputStream(), new HelloMessage());
     }
     catch (IOException ioe) 
     {
       // do nothing
     }
   }
+  
 }

+ 24 - 0
servlet/src/main/webapp/WEB-INF/jsp/fortunes.jsp

@@ -0,0 +1,24 @@
+<%@ page import="hello.*,
+                 java.util.*" %><%@ page session="false" %><%
+
+List<Fortune> fortunes = (List)request.getAttribute("fortunes");
+
+%>
+<!DOCTYPE html>
+<html>
+<head>
+<title>Fortunes</title>
+</head>
+<body>
+<table>
+<tr>
+<th>id</th>
+<th>message</th>
+</tr>
+<% for (Fortune fortune : fortunes) { %><tr>
+<td><%= fortune.getId() %></td>
+<td><%= Common.render(fortune.getMessage()) %></td>
+</tr>
+<% } %>
+</table></body>
+</html>

+ 1 - 1
servlet/src/main/webapp/WEB-INF/resin-web.xml

@@ -3,7 +3,7 @@
 <database jndi-name='jdbc/hello_world'>
   <driver>
     <type>com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource</type>
-    <url>jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&amp;elideSetAutoCommits=true&amp;useLocalSessionState=true&amp;cachePrepStmts=true&amp;cacheCallableStmts=true&amp;alwaysSendSetIsolation=false&amp;prepStmtCacheSize=4096&amp;cacheServerConfiguration=true&amp;prepStmtCacheSqlLimit=2048&amp;zeroDateTimeBehavior=convertToNull&amp;traceProtocol=false&amp;useUnbufferedInput=false&amp;useReadAheadInput=false&amp;maintainTimeStats=false&amp;useServerPrepStmts&amp;cacheRSMetadata=true</url>
+    <url>jdbc:mysql://172.16.98.98:3306/hello_world?jdbcCompliantTruncation=false&amp;elideSetAutoCommits=true&amp;useLocalSessionState=true&amp;cachePrepStmts=true&amp;cacheCallableStmts=true&amp;alwaysSendSetIsolation=false&amp;prepStmtCacheSize=4096&amp;cacheServerConfiguration=true&amp;prepStmtCacheSqlLimit=2048&amp;zeroDateTimeBehavior=convertToNull&amp;traceProtocol=false&amp;useUnbufferedInput=false&amp;useReadAheadInput=false&amp;maintainTimeStats=false&amp;useServerPrepStmts&amp;cacheRSMetadata=true</url>
     <user>benchmarkdbuser</user>
     <password>benchmarkdbpass</password>
     <useUnicode/>

+ 6 - 0
servlet/src/main/webapp/WEB-INF/web.xml

@@ -11,4 +11,10 @@
     <load-on-startup/>
   </servlet>
   <servlet-mapping url-regexp='^/db$' servlet-name='db'/>
+  <servlet>
+    <servlet-name>fortunes</servlet-name>
+    <servlet-class>hello.FortunesServlet</servlet-class>
+    <load-on-startup/>
+  </servlet>
+  <servlet-mapping url-regexp='^/fortunes$' servlet-name='fortunes'/>
 </web-app>

+ 0 - 56
yesod/bench/Application.hs

@@ -1,56 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-module Application
-    ( makeApplication
-    , makeFoundation
-    ) where
-
-import Import
-import Control.Monad
-import Control.DeepSeq (force)
-import System.Random
-
-import qualified Database.Persist.Store
-import Database.Persist.Store (PersistValue (..))
-import Network.HTTP.Conduit (newManager, def)
-import Yesod.Default.Config
-
-
-import Settings
-
-getJsonR :: Handler RepJson
-getJsonR = jsonToRepJson $ object [("message", "Hello, World!" :: Text)]
-
-getDBR :: Handler RepJson
-getDBR = do
-    !i <- liftIO $ randomRIO (1, 10000)
-    Just o <- runDB $ get $ Key $ PersistInt64 i
-    jsonToRepJson $ object ["id" .= i, "randomNumber" .= worldRandomNumber o]
-
-getDB2R :: Int -> Handler RepJson
-getDB2R n = do
-    !is <- force . take n . randomRs (1, 10000) <$> liftIO newStdGen
-
-    ns <- runDB $
-        forM is $ \i -> do
-            Just o <- get $ Key $ PersistInt64 i
-            return (i, worldRandomNumber o)
-
-    jsonToRepJson $ array
-        [ object ["id" .= i, "randomNumber" .= rn] | (i, rn) <- ns ]
-
-mkYesodDispatch "App" resourcesApp
-
-makeApplication :: AppConfig DefaultEnv Extra -> IO Application
-makeApplication conf = makeFoundation conf >>= toWaiAppPlain
-
-makeFoundation :: AppConfig DefaultEnv Extra -> IO App
-makeFoundation conf = do
-    manager <- newManager def
-    dbconf <- withYamlEnvironment "config/mysql.yml" (appEnv conf)
-              Database.Persist.Store.loadConfig >>=
-              Database.Persist.Store.applyEnv
-    p <- Database.Persist.Store.createPoolConfig (dbconf :: Settings.PersistConfig)
-    let foundation = App conf p manager dbconf
-
-    return foundation

+ 0 - 39
yesod/bench/Foundation.hs

@@ -1,39 +0,0 @@
-module Foundation where
-
-import Prelude
-import Yesod
-import Yesod.Default.Config
-import Network.HTTP.Conduit (Manager)
-import qualified Settings
-import qualified Database.Persist.Store
-import Database.Persist.GenericSql
-import Settings (Extra (..))
-
-data App = App
-    { settings :: AppConfig DefaultEnv Extra
-    , connPool :: Database.Persist.Store.PersistConfigPool Settings.PersistConfig -- ^ Database connection pool.
-    , httpManager :: Manager
-    , persistConfig :: Settings.PersistConfig
-    }
-
-mkYesodData "App" $(parseRoutesFile "config/routes")
-
-type Form x = Html -> MForm App App (FormResult x, Widget)
-
-instance Yesod App where
-    approot = ApprootMaster $ appRoot . settings
-    logLevel _ = LevelError
-    makeSessionBackend _ = return Nothing
-    shouldLog _ _ _ = False
-
-instance YesodPersist App where
-    type YesodPersistBackend App = SqlPersist
-    runDB f = do
-        master <- getYesod
-        Database.Persist.Store.runPool
-            (persistConfig master)
-            f
-            (connPool master)
-
-getExtra :: Handler Extra
-getExtra = fmap (appExtra . settings) getYesod

+ 0 - 27
yesod/bench/Import.hs

@@ -1,27 +0,0 @@
-module Import
-    ( module Import
-    ) where
-
-import           Prelude              as Import hiding (head, init, last,
-                                                 readFile, tail, writeFile)
-import           Yesod                as Import hiding (Route (..))
-
-import           Control.Applicative  as Import (pure, (<$>), (<*>))
-import           Data.Text            as Import (Text)
-
-import           Foundation           as Import
-import           Model                as Import
-import           Settings             as Import
-
-#if __GLASGOW_HASKELL__ >= 704
-import           Data.Monoid          as Import
-                                                 (Monoid (mappend, mempty, mconcat),
-                                                 (<>))
-#else
-import           Data.Monoid          as Import
-                                                 (Monoid (mappend, mempty, mconcat))
-
-infixr 5 <>
-(<>) :: Monoid m => m -> m -> m
-(<>) = mappend
-#endif

+ 0 - 12
yesod/bench/Model.hs

@@ -1,12 +0,0 @@
-module Model where
-
-import Prelude
-import Yesod
-import Database.Persist.Quasi
-
--- You can define all of your database entities in the entities file.
--- You can find more information on persistent and how to declare entities
--- at:
--- http://www.yesodweb.com/book/persistent/
-share [mkPersist sqlOnlySettings, mkMigrate "migrateAll"]
-    $(persistFileWith lowerCaseSettings "config/models")

+ 0 - 16
yesod/bench/Settings.hs

@@ -1,16 +0,0 @@
-module Settings where
-
-import Prelude
-import Database.Persist.MySQL (MySQLConf)
-import Yesod.Default.Config
-import Yesod.Default.Util
-import Data.Yaml
-
-type PersistConfig = MySQLConf
-
-data Extra = Extra
-    { 
-    } deriving Show
-
-parseExtra :: DefaultEnv -> Object -> Parser Extra
-parseExtra _ o = return Extra

+ 0 - 8
yesod/bench/app/main.hs

@@ -1,8 +0,0 @@
-import Prelude              (IO)
-import Yesod.Default.Config (fromArgs)
-import Yesod.Default.Main   (defaultMain)
-import Settings             (parseExtra)
-import Application          (makeApplication)
-
-main :: IO ()
-main = defaultMain (fromArgs parseExtra) makeApplication

+ 0 - 2
yesod/bench/config/models

@@ -1,2 +0,0 @@
-World
-    randomNumber Int sql=randomNumber

+ 0 - 21
yesod/bench/config/mysql.yml

@@ -1,21 +0,0 @@
-Default: &defaults
-  user: benchmarkdbuser
-  password: benchmarkdbpass
-  host: 127.0.0.1
-  port: 3306
-  database: hello_world
-  poolsize: 10
-
-Development:
-  <<: *defaults
-
-Testing:
-  <<: *defaults
-
-Staging:
-  poolsize: 100
-  <<: *defaults
-
-Production:
-  poolsize: 100
-  <<: *defaults

+ 0 - 3
yesod/bench/config/routes

@@ -1,3 +0,0 @@
-/json           JsonR   GET
-/db             DBR     GET
-/db2/#Int       DB2R    GET

+ 0 - 18
yesod/bench/config/settings.yml

@@ -1,18 +0,0 @@
-Default: &defaults
-  host: "*4" # any IPv4 host
-  port: 3000
-  approot: "http://localhost:3000"
-  #analytics: UA-YOURCODE
-
-Development:
-  <<: *defaults
-
-Testing:
-  <<: *defaults
-
-Staging:
-  <<: *defaults
-
-Production:
-  #approot: "http://www.example.com"
-  <<: *defaults

+ 1 - 1
yesod/setup.py

@@ -5,7 +5,7 @@ import setup_util
 import os
 
 def start(args):
-  setup_util.replace_text("yesod/bench/config/mysql.yml", "host: .*", "host: " + args.database_host)
+  #setup_util.replace_text("yesod/bench/config/mysql.yml", "host: .*", "host: " + args.database_host)
   
   subprocess.check_call("cabal update", shell=True, cwd="yesod/bench")
   subprocess.check_call("cabal install --only-dependencies", shell=True, cwd="yesod/bench")