Browse Source

Added tests for postgres node module with expressjs (#8561)

srisaiswaroop 1 year ago
parent
commit
5b2ead28c2

+ 25 - 2
frameworks/JavaScript/express/benchmark_config.json

@@ -71,7 +71,7 @@
       "orm": "Full",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "express",
+      "display_name": "express[mysql-sequelize]",
       "notes": "",
       "versus": "nodejs"
     },
@@ -92,7 +92,30 @@
       "orm": "Full",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "express",
+      "display_name": "express[postgres-sequelize]",
+      "notes": "",
+      "versus": "nodejs"
+    },
+    "postgresjs": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "update_url": "/updates?queries=",
+      "fortune_url": "/fortunes",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "express",
+      "language": "JavaScript",
+      "flavor": "NodeJS",
+      "platform": "nodejs",
+      "webserver": "None",
+      "orm": "Full",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "express[postgres.js]",
       "notes": "",
       "versus": "nodejs"
     }

+ 12 - 0
frameworks/JavaScript/express/express-postgresjs.dockerfile

@@ -0,0 +1,12 @@
+FROM node:21.1.0-slim
+
+COPY ./ ./
+
+RUN npm install
+
+ENV NODE_ENV production
+ENV DATABASE postgres
+
+EXPOSE 8080
+
+CMD ["node", "src/clustered.mjs"]

+ 3 - 1
frameworks/JavaScript/express/package.json

@@ -6,12 +6,14 @@
     "body-parser": "1.19.0",
     "dateformat": "3.0.3",
     "escape-html": "1.0.3",
-    "express": "4.17.3",
+    "express": "4.18.2",
     "mongoose": "5.13.20",
     "mysql2": "2.2.5",
     "pg": "8.5.0",
     "pg-promise": "10.7.3",
     "pug": "2.0.1",
+    "postgres": "^3.4.3",
+    "slow-json-stringify": "^2.0.1",
     "sequelize": "6.29.0"
   }
 }

+ 25 - 0
frameworks/JavaScript/express/src/clustered.mjs

@@ -0,0 +1,25 @@
+import cluster from "node:cluster";
+import os from "node:os";
+import process from "node:process";
+
+process.env.DATABASE = "postgres";
+
+if (cluster.isPrimary) {
+  // Master Node
+  console.log(`Primary ${process.pid} is running`);
+
+  // Fork workers
+  const numCPUs = os.availableParallelism();
+  for (let i = 0; i < numCPUs; i++) {
+    cluster.fork();
+  }
+
+  cluster.on("exit", (worker) => {
+    console.log(`worker ${worker.process.pid} died`);
+    process.exit(1);
+  });
+} else {
+  // Cluster Node
+  await import("./server.mjs");
+  console.log(`Worker ${process.pid} started`);
+}

+ 27 - 0
frameworks/JavaScript/express/src/database/postgres.mjs

@@ -0,0 +1,27 @@
+import postgres from "postgres";
+
+const sql = postgres({
+  host: "tfb-database",
+  user: "benchmarkdbuser",
+  password: "benchmarkdbpass",
+  database: "hello_world",
+  max: 1,
+});
+
+export const fortunes = async () => await sql`SELECT id, message FROM fortune`;
+
+export const find = async (id) =>
+  await sql`SELECT id, randomNumber FROM world WHERE id = ${id}`.then(
+    (arr) => arr[0]
+  );
+
+export const bulkUpdate = async (worlds) => {
+  const sorted = sql(
+    worlds
+      .map((world) => [world.id, world.randomNumber])
+      .sort((a, b) => (a[0] < b[0] ? -1 : 1))
+  );
+  await sql`UPDATE world SET randomNumber = (update_data.randomNumber)::int
+  FROM (VALUES ${sorted}) AS update_data (id, randomNumber)
+  WHERE world.id = (update_data.id)::int`;
+};

+ 105 - 0
frameworks/JavaScript/express/src/server.mjs

@@ -0,0 +1,105 @@
+import express from "express";
+import {
+  generateRandomNumber,
+  getQueriesCount,
+  handleError,
+  escape,
+  jsonSerializer,
+  worldObjectSerializer,
+  sortByMessage,
+  writeResponse,
+  headerTypes,
+  GREETING,
+} from "./utils.mjs";
+
+let db;
+const { DATABASE } = process.env;
+if (DATABASE) db = await import(`./database/${DATABASE}.mjs`);
+
+const extra = { id: 0, message: "Additional fortune added at request time." };
+
+const app = express();
+
+app.get("/plaintext", (req, res) => {
+  writeResponse(res, GREETING, headerTypes["plain"]);
+});
+
+app.get("/json", (req, res) => {
+  writeResponse(res, jsonSerializer({ message: GREETING }));
+});
+
+if (db) {
+  app.get("/db", async (req, res) => {
+    try {
+      const row = await db.find(generateRandomNumber());
+      writeResponse(res, worldObjectSerializer(row));
+    } catch (error) {
+      handleError(error, res);
+    }
+  });
+
+  app.get("/queries", async (req, res) => {
+    try {
+      const queriesCount = getQueriesCount(req);
+      const databaseJobs = new Array(queriesCount)
+        .fill()
+        .map(() => db.find(generateRandomNumber()));
+      const worldObjects = await Promise.all(databaseJobs);
+      writeResponse(res, JSON.stringify(worldObjects));
+    } catch (error) {
+      handleError(error, res);
+    }
+  });
+
+  app.get("/fortunes", async (req, res) => {
+    try {
+      const rows = [extra, ...(await db.fortunes())];
+      sortByMessage(rows);
+      const n = rows.length;
+      let html = "",
+        i = 0;
+      for (; i < n; i++) {
+        html += `<tr><td>${rows[i].id}</td><td>${escape(
+          rows[i].message
+        )}</td></tr>`;
+      }
+
+      writeResponse(
+        res,
+        `<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`,
+        headerTypes["html"]
+      );
+    } catch (error) {
+      handleError(error, res);
+    }
+  });
+
+  app.get("/updates", async (req, res) => {
+    try {
+      const queriesCount = getQueriesCount(req);
+      const databaseJobs = new Array(queriesCount);
+      for (let i = 0; i < queriesCount; i++) {
+        databaseJobs[i] = db.find(generateRandomNumber());
+      }
+      const worldObjects = await Promise.all(databaseJobs);
+
+      for (let i = 0; i < queriesCount; i++) {
+        worldObjects[i].randomNumber = generateRandomNumber();
+      }
+      await db.bulkUpdate(worldObjects);
+      writeResponse(res, JSON.stringify(worldObjects));
+    } catch (error) {
+      handleError(error, res);
+    }
+  });
+}
+
+app.all("*", (req, res) => {
+  res.status(404).send("Not Found");
+});
+
+const host = process.env.HOST || "0.0.0.0";
+const port = parseInt(process.env.PORT || "8080");
+app.listen(port, host, () => {
+  console.log(`Server running at http://${host}:${port}/`);
+});

+ 68 - 0
frameworks/JavaScript/express/src/utils.mjs

@@ -0,0 +1,68 @@
+import { sjs, attr } from "slow-json-stringify";
+
+export const GREETING = "Hello, World!";
+
+export const headerTypes = {
+  plain: "text/plain",
+  json: "application/json",
+  html: "text/html; charset=UTF-8",
+};
+
+export function writeResponse(res, text, type = headerTypes["json"]) {
+  res.writeHead(200, {
+    "content-type": type,
+    server: "Express",
+  });
+  res.end(text);
+}
+
+export function handleError(error, response) {
+  console.error(error);
+  response.end("Internal Server Error");
+}
+
+export function getQueriesCount(request) {
+  return Math.min(parseInt(request.query["queries"]) || 1, 500);
+}
+
+export function generateRandomNumber() {
+  return Math.ceil(Math.random() * 10000);
+}
+
+const escapeHTMLRules = {
+  "&": "&#38;",
+  "<": "&#60;",
+  ">": "&#62;",
+  '"': "&#34;",
+  "'": "&#39;",
+  "/": "&#47;",
+};
+
+const unsafeHTMLMatcher = /[&<>"'\/]/g;
+
+export function escape(text) {
+  if (unsafeHTMLMatcher.test(text) === false) return text;
+  return text.replace(unsafeHTMLMatcher, function (m) {
+    return escapeHTMLRules[m] || m;
+  });
+}
+
+export const jsonSerializer = sjs({ message: attr("string") });
+export const worldObjectSerializer = sjs({
+  id: attr("number"),
+  randomnumber: attr("number"),
+});
+
+export function sortByMessage(arr) {
+  const n = arr.length;
+  for (let i = 1; i < n; i++) {
+    const c = arr[i];
+    let j = i - 1;
+    while (j > -1 && c.message < arr[j].message) {
+      arr[j + 1] = arr[j];
+      j--;
+    }
+    arr[j + 1] = c;
+  }
+  return arr;
+}