-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #83 from AikidoSec/AIK-3345
Add benchmarking/fix some performance issues
- Loading branch information
Showing
35 changed files
with
457 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Benchmark | ||
on: | ||
push: {} | ||
workflow_call: {} | ||
jobs: | ||
benchmark: | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 10 | ||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v2 | ||
|
||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v1 | ||
- name: Run Docker Compose | ||
working-directory: ./sample-apps/flask-mysql | ||
run: | | ||
docker compose -f docker-compose.yml -f docker-compose.benchmark.yml up --build -d | ||
- name: Install K6 | ||
uses: grafana/setup-k6-action@v1 | ||
- name: Run flask-mysql k6 Benchmark | ||
run: | | ||
k6 run -q ./benchmarks/flask-mysql-benchmarks.js | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
""" | ||
Helper function file, see function docstring | ||
""" | ||
|
||
import os | ||
|
||
|
||
def get_temp_dir(): | ||
""" | ||
Checks the environment variable "AIKIDO_TMP_DIR" | ||
""" | ||
aikido_temp_dir_env = os.getenv("AIKIDO_TMP_DIR") | ||
if aikido_temp_dir_env is not None: | ||
return aikido_temp_dir_env | ||
return "/tmp" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import http from 'k6/http'; | ||
import { check, sleep, fail } from 'k6'; | ||
import exec from 'k6/execution'; | ||
import { Trend } from 'k6/metrics'; | ||
|
||
const BASE_URL_8086 = 'http://localhost:8086'; | ||
const BASE_URL_8087 = 'http://localhost:8087'; | ||
|
||
export const options = { | ||
vus: 1, // Number of virtual users | ||
thresholds: { | ||
test_40mb_payload: [{ | ||
threshold: "avg<2000", // Temporary exagurated threshold until optimizations are finished | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_multiple_queries: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_multiple_queries_with_big_body: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_create_with_big_body: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_normal_route: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_id_route: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_open_file: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
test_execute_shell: [{ | ||
threshold: "avg<2000", | ||
abortOnFail: true, | ||
delayAbortEval: '10s', | ||
}], | ||
|
||
}, | ||
}; | ||
const default_headers = { | ||
"User-Agent": | ||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", | ||
}; | ||
|
||
const default_payload = { | ||
dog_name: "Pops", | ||
other_dogs: Array(2000).fill("Lorem Ipsum"), | ||
other_dogs2: Array.from({length: 5000}, () => Math.floor(Math.random() * 99999999)), | ||
text_message: "Lorem ipsum dolor sit amet".repeat(3000) | ||
|
||
}; | ||
function generateLargeJson(sizeInMB) { | ||
const sizeInBytes = sizeInMB * 1024; // Convert MB to Kilobytes | ||
let long_text = "b".repeat(sizeInBytes) | ||
return { | ||
dog_name: "test", | ||
long_texts: new Array(1024).fill(long_text) | ||
} | ||
} | ||
|
||
function measureRequest(url, method = 'GET', payload, status_code=200, headers=default_headers) { | ||
let res; | ||
if (method === 'POST') { | ||
res = http.post(url, payload, { | ||
headers: headers | ||
} | ||
); | ||
} else { | ||
res = http.get(url, { | ||
headers: headers | ||
}); | ||
} | ||
check(res, { | ||
'status is correct': (r) => r.status === status_code, | ||
}); | ||
return res.timings.duration; // Return the duration of the request | ||
} | ||
|
||
function route_test(trend, amount, route, method="GET", data=default_payload, status=200) { | ||
for (let i = 0; i < amount; i++) { | ||
let time_with_fw = measureRequest(BASE_URL_8086 + route, method, data, status) | ||
let time_without_fw = measureRequest(BASE_URL_8087 + route, method, data, status) | ||
trend.add(time_with_fw - time_without_fw) | ||
} | ||
} | ||
|
||
export function handleSummary(data) { | ||
for (const [metricName, metricValue] of Object.entries(data.metrics)) { | ||
if(!metricName.startsWith('test_') || metricValue.values.avg == 0) { | ||
continue | ||
} | ||
let values = metricValue.values | ||
console.log(`\x1b[35m 🚅 ${metricName}\x1b[0m: ΔAverage is \x1b[4m${values.avg.toFixed(2)}ms\x1b[0m | ΔMedian is \x1b[4m${values.med.toFixed(2)}ms\x1b[0m`); | ||
} | ||
return {stdout: ""}; | ||
} | ||
|
||
let test_40mb_payload = new Trend('test_40mb_payload') | ||
let test_multiple_queries = new Trend("test_multiple_queries") | ||
let test_multiple_queries_with_big_body = new Trend("test_multiple_queries_with_big_body") | ||
let test_create_with_big_body = new Trend("test_create_with_big_body") | ||
let test_normal_route = new Trend("test_normal_route") | ||
let test_id_route = new Trend("test_id_route") | ||
let test_open_file = new Trend("test_open_file") | ||
let test_execute_shell = new Trend("test_execute_shell") | ||
export default function () { | ||
route_test(test_40mb_payload, 30, "/create", "POST", generateLargeJson(40)) // 40 Megabytes | ||
route_test(test_multiple_queries, 50, "/multiple_queries", "POST", {dog_name: "W"}) | ||
route_test(test_multiple_queries_with_big_body, 50, "/multiple_queries", "POST") | ||
route_test(test_create_with_big_body, 500, "/create", "POST") | ||
route_test(test_normal_route, 500, "/") | ||
route_test(test_id_route, 500, "/dogpage/1") | ||
route_test(test_open_file, 500, "/open_file", 'POST', { filepath: '.env.example' }) | ||
route_test(test_execute_shell, 500, "/shell", "POST", { command: 'xyzwh'}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,20 @@ | ||
# Aikido's sample apps | ||
Overview : | ||
- `django-mysql/` is a Django app using MySQL. | ||
- `django-mysql/` is a Django app using MySQL. | ||
- It runs **multi-threaded** | ||
- Runs on 8080. Without Aikido runs on 8081 | ||
- `django-mysql-gunicorn/` is a Django app using MySQL and runnin with a Gunicorn backend. | ||
- it runs 4 processes, called workers, (**multi-process**) which handle requests using 2 threads (**multi-threaded**) | ||
- Runs on 8082. Without Aikido runs on 8083 | ||
- `flask-mongo/` is a Flask app using MongoDB. | ||
- It runs **multi-threaded** | ||
- Runs on 8084. Without Aikido runs on 8085 | ||
- `flask-mysql/` is a Flask app using MySQL. | ||
- It runs **single-threaded** | ||
- Runs on 8086. Without Aikido runs on 8087 | ||
- `flask-mysql-uwsgi/` is a Flask app using Mysql and running with a uWSGI backend. | ||
- It runs 4 processes (**multi-process**) which handle requests **multi-threaded** | ||
- Runs on 8088. Without aikido runs on 8089 | ||
- `flask-postres/` is a Flask app using Postgres | ||
- It runs **multi-threaded** | ||
- Runs on 8090. Without aikido runs on 8091 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
sample-apps/django-mysql-gunicorn/docker-compose.benchmark.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
version: "3" | ||
services: | ||
backend_firewall_disabled: | ||
build: | ||
context: ./../../ | ||
dockerfile: ./sample-apps/django-mysql-gunicorn/Dockerfile | ||
command: sh -c "python manage.py migrate && gunicorn -c gunicorn_config.py --workers 4 --threads 2 --log-level debug --access-logfile '-' --error-logfile '-' --bind 0.0.0.0:8000 sample-django-mysql-gunicorn-app.wsgi" | ||
restart: always | ||
volumes: | ||
- .:/app | ||
ports: | ||
- "8083:8000" | ||
env_file: | ||
- .env | ||
depends_on: | ||
db: | ||
condition: service_healthy | ||
extra_hosts: | ||
- "app.local.aikido.io:host-gateway" | ||
environment: | ||
- FIREWALL_DISABLED=1 | ||
backend: | ||
environment: | ||
- AIKIDO_TOKEN="test_aikido_token" |
Oops, something went wrong.