Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds benchmark #833

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Benchmark

on:
pull_request:
branches:
- master

jobs:
benchmark:
runs-on: ubuntu-latest

steps:
# Step 1: Checkout the code
- name: Checkout repository
uses: actions/checkout@v3

# Step 2: Install Docker Compose
- name: Install Docker Compose
run: |
sudo apt-get update
sudo apt-get install -y docker-compose

# Step 3: Run the benchmark
- name: Run tests
run: npm run benchmark
12 changes: 10 additions & 2 deletions app/templates/folders/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const localPath = require("helper/localPath");
const sync = require("sync");
const fix = require("sync/fix");

const FOLDER_ACCOUNT_EMAIL = config.admin.email || "[email protected]";
const FOLDER_ACCOUNT_EMAIL = config.admin.email || "[email protected]";
const FOLDER_ACCOUNT_PASSWORD = config.session.secret || "password";

const updates = {
bjorn: {
Expand Down Expand Up @@ -129,7 +130,14 @@ function setupUser (_callback) {

if (user) return callback(null, user);

User.create(FOLDER_ACCOUNT_EMAIL, config.session.secret, {}, {}, callback);
User.hashPassword(FOLDER_ACCOUNT_PASSWORD, function (err, hash) {
if (err) return callback(err);

if (!hash) return callback(new Error("Password hash not generated"));

console.log("Creating user", FOLDER_ACCOUNT_EMAIL, "with password", FOLDER_ACCOUNT_PASSWORD);
User.create(FOLDER_ACCOUNT_EMAIL, hash, {}, {}, callback);
});
});
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"scripts": {
"test": "./tests/invoke.sh",
"benchmark": "./tests/benchmark/invoke.sh",
"start": "docker-compose up --build",
"folder": "docker exec blot-node-app-1 node app/templates/folders"
},
Expand Down
8 changes: 8 additions & 0 deletions tests/benchmark/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
npm-debug.log
docker-compose.yml
Dockerfile.benchmark
.dockerignore
.gitignore
**/data
.env
1 change: 1 addition & 0 deletions tests/benchmark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
23 changes: 23 additions & 0 deletions tests/benchmark/Dockerfile.benchmark
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Use a lightweight Node.js image as the base
FROM node:slim

# Set the working directory inside the container
WORKDIR /usr/src/benchmark

# Copy only the benchmark-related files into the container
COPY ./package*.json ./

# Install dependencies for the benchmark
RUN npm install

# Copy the rest of the benchmark folder into the container
COPY ./index.js ./
COPY ./wait-for-it.sh ./
COPY ./dashboard.yml ./
COPY ./blog.yml ./

# Make sure wait-for-it.sh is executable
RUN chmod +x ./wait-for-it.sh

# Default command (can be overridden by docker-compose.yml)
CMD ["sh", "-c", "node index.js"]
64 changes: 64 additions & 0 deletions tests/benchmark/blog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
config:
target: "http://benchmark-server:8080"
phases:
- duration: 30
arrivalRate: 10
rampTo: 50
defaults:
headers:
Host: "david.localhost"
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
X-Forwarded-For: "217.1.186.106"
X-Forwarded-Proto: "https"

scenarios:
- name: "Blog search"
flow:
- get:
url: "/search"
capture:
- selector: link[rel="stylesheet"][href^="/"]
as: "stylesheet"
attr: "href"
- selector: script[src^="/"]
as: "script"
attr: "src"
- parallel:
- get:
url: "{{ script }}"
- get:
url: "{{ stylesheet }}"
- get:
url: "/search?q=hello"
capture:
- selector: a.full-line
as: "post"
attr: "href"
- get:
url: "{{ post }}"

- name: "Blog homepage"
flow:
- get:
url: "/"
capture:
- selector: link[rel="stylesheet"][href^="/"]
as: "stylesheet"
attr: "href"
- selector: script[src^="/"]
as: "script"
attr: "src"
- parallel:
- get:
url: "{{ script }}"
- get:
url: "{{ stylesheet }}"
- get:
url: "/robots.txt"
- get:
url: "/feed.rss"

- name: "Blog broken link"
flow:
- get:
url: "/WOWOWO"
41 changes: 41 additions & 0 deletions tests/benchmark/dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
config:
target: "http://benchmark-server:8080"
phases:
- duration: 10
arrivalRate: 1
defaults:
headers:
Host: "benchmark-server"
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
X-Forwarded-For: "217.1.186.106"
X-Forwarded-Proto: "https"

before:
flow:
- post:
url: '/sites/log-in'
form:
email: '[email protected]'
password: 'password'
capture:
- as: "connectSid"
from: "headers['set-cookie']"
regex: "connect\\.sid=([^;]+);" # Regex to extract the value of connect.sid

scenarios:
- name: "Dashboard view sites"
flow:
- get:
url: '/sites'
headers:
Cookie: "connect.sid={{ connectSid }}" # Use the extracted cookie
redirect: false
capture:
- selector: a.blog-link
as: "site"
attr: "href"
- get:
url: "{{ site }}"
headers:
Cookie: "connect.sid={{ connectSid }}" # Use the extracted cookie
redirect: false
32 changes: 32 additions & 0 deletions tests/benchmark/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
services:

benchmark-redis:
image: "redis:alpine"
command: sh -c "redis-server" # add 'rm -f /data/dump.rdb &&' to reset the database

benchmark-server:
build:
context: ../../
dockerfile: Dockerfile
target: prod
stop_grace_period: 1s
depends_on:
- benchmark-redis
ports:
- "8080:8080"
environment:
- BLOT_REDIS_HOST=benchmark-redis
- BLOT_HOST=localhost
# rm -rf /usr/src/app/data && mkdir /usr/src/app/data && node -v && npm -v &&
command: >
sh -c "node app/setup.js && node app/templates/folders david && node app/index.js"

benchmark-runner:
build:
context: .
dockerfile: Dockerfile.benchmark
depends_on:
- benchmark-server
environment:
- BLOT_HOST=benchmark-server
- BLOT_PORT=8080
47 changes: 47 additions & 0 deletions tests/benchmark/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const path = require('path');
const fs = require('fs-extra');
const { execSync, spawn } = require('child_process');

console.log('BENCHMARK started');

// Check required environment variables
if (!process.env.BLOT_HOST || !process.env.BLOT_PORT) {
console.error('Error: BLOT_HOST and BLOT_PORT environment variables must be set.');
process.exit(1);
}

// Ensure wait-for-it.sh is executable
const waitForItPath = './wait-for-it.sh';
if (!fs.existsSync(waitForItPath)) {
console.error('Error: wait-for-it.sh script not found.');
process.exit(1);
}
fs.chmodSync(waitForItPath, '755'); // Ensure it's executable

// Wait for the web server to be ready
try {
console.log('Waiting for the web server to be ready...');
execSync(`${waitForItPath} ${process.env.BLOT_HOST}:${process.env.BLOT_PORT} -t 120`, { stdio: 'inherit' });
console.log('Web server is ready');
} catch (error) {
console.error('Error: Web server did not become ready within the timeout period.');
process.exit(1);
}

const artilleryPath = path.resolve(__dirname, 'node_modules/.bin/artillery');

// Spawn Artillery to run the benchmark
const artillery = spawn(artilleryPath, ['run', 'blog.yml'], { stdio: 'inherit' });

// Handle errors when spawning Artillery
artillery.on('error', (error) => {
console.error(`Failed to start Artillery: ${error.message}`);
process.exit(1);
});

// Handle Artillery process completion
artillery.on('close', (code) => {
console.log(`Artillery process exited with code ${code}`);
console.log('BENCHMARK finished');
process.exit(code);
});
41 changes: 41 additions & 0 deletions tests/benchmark/invoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Threshold in seconds (modify as needed), default to 1 hour
THRESHOLD={$1:-3600}

echo "Running benchmark with threshold $THRESHOLD seconds"

# Start time
START_TIME=$(date +%s.%N)

# Get the path to the directory which contains this script
TESTS_DIR=$(dirname "$0")

echo "Running tests in path: $TESTS_DIR"

# If the file tests.env does not existing the parent directory of the tests directory, then create it
if [ ! -f "$TESTS_DIR/../test.env" ]; then
echo "Creating test.env file in the parent directory of the tests directory..."
touch "$TESTS_DIR/../test.env"
fi

# Run the tests in the specified path using docker-compose
docker-compose -p blot-benchmark -f tests/benchmark/docker-compose.yml up --build --abort-on-container-exit --remove-orphans

# End time
END_TIME=$(date +%s.%N)

# Calculate elapsed time
ELAPSED_TIME=$(echo "$END_TIME - $START_TIME" | bc)

# Output elapsed time
echo "Elapsed time: $ELAPSED_TIME seconds"

# Check if the elapsed time exceeds the threshold
if (( $(echo "$ELAPSED_TIME > $THRESHOLD" | bc -l) )); then
echo "Benchmark failed: Elapsed time exceeds threshold of $THRESHOLD seconds"
exit 1
else
echo "Benchmark passed: Elapsed time is within threshold"
exit 0
fi
Loading