浏览代码

Adding ES4X to the JavaScript frameworks (#4039)

Paulo Lopes 6 年之前
父节点
当前提交
cffabe9d29

+ 41 - 0
frameworks/JavaScript/es4x/README.md

@@ -0,0 +1,41 @@
+# ES4X Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](index.js#L24-L30)
+* [PLAINTEXT](index.js#L233-L239)
+* [DB](index.js#L50-L71)
+* [QUERY](index.js#L78-L108)
+* [UPDATE](index.js#L171-L225)
+* [FORTUNES](index.js#L114-L164)
+
+## Important Libraries
+The tests were run with:
+* [Eclipse Vert.x](https://vertx.io/)
+* [GraalVM 1.0.0 rc6](https://graalvm.org/)
+* [ES4X](https://reactiverse.io/es4x/)
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/queries?queries=
+
+### UPDATE
+
+http://localhost:8080/updates?queries=
+
+### FORTUNES
+
+http://localhost:8080/fortunes

+ 30 - 0
frameworks/JavaScript/es4x/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+  "framework": "es4x",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "ES4X",
+        "language": "JavaScript",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Vert.x",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ES4X",
+        "notes": "",
+        "versus": "nodejs"  
+      }
+    }
+  ]
+}

+ 48 - 0
frameworks/JavaScript/es4x/es4x.dockerfile

@@ -0,0 +1,48 @@
+FROM ubuntu:18.04
+
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN apt-get update && \
+    apt-get -y install curl && \
+    rm -rf /var/lib/apt/lists/*
+
+ENV GRAALVM_VERSION=1.0.0-rc6
+
+# Get GraalVM CE
+RUN echo "Pulling graalvm ${GRAALVM_VERSION} binary from Github." \
+    && curl -sSLf https://github.com/oracle/graal/releases/download/vm-${GRAALVM_VERSION}/graalvm-ce-${GRAALVM_VERSION}-linux-amd64.tar.gz > /tmp/graalvm-ce-${GRAALVM_VERSION}-linux-amd64.tar.gz \
+    && mkdir -p /opt/java \
+    && tar -zxf /tmp/graalvm-ce-${GRAALVM_VERSION}-linux-amd64.tar.gz -C /opt/java \
+    && rm /tmp/graalvm-ce-${GRAALVM_VERSION}-linux-amd64.tar.gz
+
+ENV GRAALVM_HOME=/opt/java/graalvm-ce-${GRAALVM_VERSION}
+ENV JAVA_HOME=${GRAALVM_HOME}
+ENV PATH=${PATH}:${JAVA_HOME}/bin
+
+# Set working dir
+RUN mkdir /app
+WORKDIR /app
+
+COPY ./ /app
+
+# Get dependencies
+RUN npm --unsafe-perm install
+# Generate a runtime blog
+RUN npm run package
+
+CMD ${GRAALVM_HOME}/bin/java \
+    -server                                           \
+    -XX:+UseNUMA                                      \
+    -XX:+UseParallelGC                                \
+    -XX:+AggressiveOpts                               \
+    -Dvertx.disableMetrics=true                       \
+    -Dvertx.disableH2c=true                           \
+    -Dvertx.disableWebsockets=true                    \
+    -Dvertx.flashPolicyHandler=false                  \
+    -Dvertx.threadChecks=false                        \
+    -Dvertx.disableContextTimings=true                \
+    -Dvertx.disableTCCL=true                          \
+    -jar                                              \
+    target/es4x-0.0.1-fat.jar                         \
+    --instances                                       \
+    `grep --count ^processor /proc/cpuinfo`

+ 246 - 0
frameworks/JavaScript/es4x/index.js

@@ -0,0 +1,246 @@
+/// <reference types="@vertx/core/runtime" />
+// @ts-check
+
+import {Router} from '@vertx/web';
+
+import {PgClient, Tuple} from '@reactiverse/reactive-pg-client';
+import {PgPoolOptions} from '@reactiverse/reactive-pg-client/options';
+
+const util = require('./util');
+const HandlebarsTemplateEngine = Java.type('io.vertx.ext.web.templ.HandlebarsTemplateEngine');
+
+const SERVER = 'vertx.js';
+
+const app = Router.router(vertx);
+const template = HandlebarsTemplateEngine.create();
+let date = new Date().toString();
+
+vertx.setPeriodic(1000, t => date = new Date().toUTCString());
+
+/*
+ * This test exercises the framework fundamentals including keep-alive support, request routing, request header
+ * parsing, object instantiation, JSON serialization, response header generation, and request count throughput.
+ */
+app.get("/json").handler(ctx => {
+  ctx.response()
+    .putHeader("Server", SERVER)
+    .putHeader("Date", date)
+    .putHeader("Content-Type", "application/json")
+    .end(JSON.stringify({message: 'Hello, World!'}));
+});
+
+const UPDATE_WORLD = "UPDATE world SET randomnumber=$1 WHERE id=$2";
+const SELECT_WORLD = "SELECT id, randomnumber from WORLD where id=$1";
+const SELECT_FORTUNE = "SELECT id, message from FORTUNE";
+
+let client = PgClient.pool(
+  vertx,
+  new PgPoolOptions()
+    .setCachePreparedStatements(true)
+    .setMaxSize(1)
+    .setHost('tfb-database')
+    .setUser('benchmarkdbuser')
+    .setPassword('benchmarkdbpass')
+    .setDatabase('hello_world'));
+
+/*
+ * This test exercises the framework's object-relational mapper (ORM), random number generator, database driver,
+ * and database connection pool.
+ */
+app.get("/db").handler(ctx => {
+  client.preparedQuery(SELECT_WORLD, Tuple.of(util.randomWorld()), res => {
+    if (res.succeeded()) {
+      let resultSet = res.result().iterator();
+
+      if (!resultSet.hasNext()) {
+        ctx.fail(404);
+        return;
+      }
+
+      let row = resultSet.next();
+
+      ctx.response()
+        .putHeader("Server", SERVER)
+        .putHeader("Date", date)
+        .putHeader("Content-Type", "application/json")
+        .end(JSON.stringify({id: row.getInteger(0), randomNumber: row.getInteger(1)}));
+    } else {
+      ctx.fail(res.cause());
+    }
+  })
+});
+
+/*
+ * This test is a variation of Test #2 and also uses the World table. Multiple rows are fetched to more dramatically
+ * punish the database driver and connection pool. At the highest queries-per-request tested (20), this test
+ * demonstrates all frameworks' convergence toward zero requests-per-second as database activity increases.
+ */
+app.get("/queries").handler(ctx => {
+  let failed = false;
+  let worlds = [];
+
+  const queries = util.getQueries(ctx.request());
+  
+  for (let i = 0; i < queries; i++) {
+    client.preparedQuery(SELECT_WORLD, Tuple.of(util.randomWorld()), ar => {
+      if (!failed) {
+        if (ar.failed()) {
+          failed = true;
+          ctx.fail(ar.cause());
+          return;
+        }
+
+        // we need a final reference
+        const row = ar.result().iterator().next();
+        worlds.push({id: row.getInteger(0), randomNumber: row.getInteger(1)});
+
+        // stop condition
+        if (worlds.length === queries) {
+          ctx.response()
+            .putHeader("Server", SERVER)
+            .putHeader("Date", date)
+            .putHeader("Content-Type", "application/json")
+            .end(JSON.stringify(worlds));
+        }
+      }
+    });
+  }
+});
+
+/*
+ * This test exercises the ORM, database connectivity, dynamic-size collections, sorting, server-side templates,
+ * XSS countermeasures, and character encoding.
+ */
+app.get("/fortunes").handler(ctx => {
+  client.preparedQuery(SELECT_FORTUNE, ar => {
+
+    if (ar.failed()) {
+      ctx.fail(ar.cause());
+      return;
+    }
+
+    let fortunes = [];
+    let resultSet = ar.result().iterator();
+
+    if (!resultSet.hasNext()) {
+      ctx.fail(404);
+      return;
+    }
+
+    while (resultSet.hasNext()) {
+      let row = resultSet.next();
+      fortunes.push({id: row.getInteger(0), message: row.getString(1)});
+    }
+
+    fortunes.push({id: 0, message: "Additional fortune added at request time."});
+
+    fortunes.sort((a, b) => {
+      let messageA = a.message;
+      let messageB = b.message;
+      if (messageA < messageB) {
+        return -1;
+      }
+      if (messageA > messageB) {
+        return 1;
+      }
+      return 0;
+    });
+
+    ctx.put("fortunes", fortunes);
+
+    // and now delegate to the engine to render it.
+    template.render(ctx, "templates", "/fortunes.hbs", res => {
+      if (res.succeeded()) {
+        ctx.response()
+          .putHeader("Server", SERVER)
+          .putHeader("Date", date)
+          .putHeader("Content-Type", "text/html; charset=UTF-8")
+          .end(res.result());
+      } else {
+        ctx.fail(res.cause());
+      }
+    });
+  });
+});
+
+/*
+ * This test is a variation of Test #3 that exercises the ORM's persistence of objects and the database driver's
+ * performance at running UPDATE statements or similar. The spirit of this test is to exercise a variable number of
+ * read-then-write style database operations.
+ */
+app.route("/updates").handler(ctx => {
+  let failed = false;
+  let queryCount = 0;
+  let worlds = [];
+
+  const queries = util.getQueries(ctx.request());
+
+  for (let i = 0; i < queries; i++) {
+    const id = util.randomWorld();
+    const index = i;
+
+    client.preparedQuery(SELECT_WORLD, Tuple.of(id), ar => {
+      if (!failed) {
+        if (ar.failed()) {
+          failed = true;
+          ctx.fail(ar.cause());
+          return;
+        }
+
+        const row = ar.result().iterator().next();
+
+        worlds[index] = {id: row.getInteger(0), randomNumber: util.randomWorld()};
+        if (++queryCount === queries) {
+          worlds.sort((a, b) => {
+            return a.id - b.id;
+          });
+
+          let batch = [];
+
+          worlds.forEach(world => {
+            batch.push(Tuple.of(world.randomNumber, world.id));
+          });
+
+          client.preparedBatch(UPDATE_WORLD, batch, ar => {
+            if (ar.failed()) {
+              ctx.fail(ar.cause());
+              return;
+            }
+
+            let json = [];
+            worlds.forEach(world => {
+              json.push({id: world.id, randomNumber: world.randomNumber});
+            });
+
+            ctx.response()
+              .putHeader("Server", SERVER)
+              .putHeader("Date", date)
+              .putHeader("Content-Type", "application/json")
+              .end(JSON.stringify(json));
+          });
+        }
+      }
+    });
+  }
+});
+
+/*
+ * This test is an exercise of the request-routing fundamentals only, designed to demonstrate the capacity of
+ * high-performance platforms in particular. Requests will be sent using HTTP pipelining. The response payload is
+ * still small, meaning good performance is still necessary in order to saturate the gigabit Ethernet of the test
+ * environment.
+ */
+app.get("/plaintext").handler(ctx => {
+  ctx.response()
+    .putHeader("Server", SERVER)
+    .putHeader("Date", date)
+    .putHeader("Content-Type", "text/plain")
+    .end('Hello, World!');
+});
+
+vertx
+  .createHttpServer()
+  .requestHandler(req => app.accept(req))
+  .listen(8080);
+
+console.log('Server listening at: http://localhost:8080/');

+ 26 - 0
frameworks/JavaScript/es4x/package.json

@@ -0,0 +1,26 @@
+{
+  "name": "es4x",
+  "version": "0.0.1",
+  "private": true,
+  "main": "index.js",
+  "scripts": {
+    "postinstall": "vertx-scripts init",
+    "package": "vertx-scripts package"
+  },
+  "devDependencies": {
+    "vertx-scripts": "latest",
+    "@vertx/unit": "latest"
+  },
+  "dependencies": {
+    "@vertx/core": "3.5.3",
+    "@vertx/web": "3.5.3",
+    "@reactiverse/reactive-pg-client": "0.10.3"
+  },
+  "mvnDependencies": {
+    "io.vertx:vertx-web-templ-handlebars": "3.5.3"
+  },
+  "files": [
+    "util.js",
+    "templates/fortunes.hbs"
+  ]
+}

+ 15 - 0
frameworks/JavaScript/es4x/templates/fortunes.hbs

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+  <tr>
+    <th>id</th>
+    <th>message</th>
+  </tr> {{#each fortunes}}
+  <tr>
+    <td>{{id}}</td>
+    <td>{{message}}</td>
+  </tr> {{/each}} </table>
+</body>
+</html>

+ 25 - 0
frameworks/JavaScript/es4x/util.js

@@ -0,0 +1,25 @@
+const ThreadLocalRandom = Java.type('java.util.concurrent.ThreadLocalRandom');
+
+module.exports = {
+  randomWorld: () => {
+    return 1 + ThreadLocalRandom.current().nextInt(10000)
+  },
+
+  /**
+   * Returns the value of the "queries" getRequest parameter, which is an integer
+   * bound between 1 and 500 with a default value of 1.
+   *
+   * @param request the current HTTP request
+   * @return the value of the "queries" parameter
+   */
+  getQueries: (request) => {
+    let param = request.getParam("queries");
+
+    if (param == null) {
+      return 1;
+    }
+
+    // invalid params are converted to 1
+    return Math.min(500, parseInt(param, 10) || 1);
+  }
+};