Files
Thomas Vincent 8221a941a7 test(performance): [develop] add disposable browser and SQL/RRD profiling (#8336)
* perf: add disposable browser and SQL/RRD profiling

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs: record profiling harness in the changelog

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-14 00:01:32 -07:00

72 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later.
set -euo pipefail
umask 077
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"
export CACTI_PROFILE_OUTPUT="${CACTI_PROFILE_OUTPUT:-$(mktemp -d "${TMPDIR:-/tmp}/cacti-profile.XXXXXX")}"
mkdir -p "$CACTI_PROFILE_OUTPUT"
CACTI_PROFILE_OUTPUT="$(cd "$CACTI_PROFILE_OUTPUT" && pwd)"
export CACTI_PROFILE_OUTPUT
# Do not combine old browser timings with a fresh server profile.
if [[ -e "$CACTI_PROFILE_OUTPUT/metadata.json" || -e "$CACTI_PROFILE_OUTPUT/fixture.json" ]]; then
echo 'Select a new output directory for each run.' >&2
exit 1
fi
export HOST_PORT="${HOST_PORT:-18093}"
export CACTI_BASE_URL="http://127.0.0.1:$HOST_PORT/"
export CACTI_PROFILE_DISPOSABLE=1
PROJECT="cacti_profile_$$"
COMPOSE=(docker compose -p "$PROJECT" -f tests/e2e/docker-compose.yml -f tests/performance/compose.profile.yml)
cleanup() { "${COMPOSE[@]}" down -v --remove-orphans >/dev/null 2>&1 || true; }
trap cleanup EXIT
mise exec node@22 -- npm ci --prefix tests/e2e --ignore-scripts
if ! mise exec node@22 -- node -e 'const {chromium}=require("./tests/e2e/node_modules/@playwright/test"); process.exit(require("node:fs").existsSync(chromium.executablePath()) ? 0 : 1);'; then
mise exec node@22 -- tests/e2e/node_modules/.bin/playwright install chromium
fi
"${COMPOSE[@]}" build cacti
"${COMPOSE[@]}" up -d --wait --wait-timeout 180
"${COMPOSE[@]}" exec -T cacti touch /tmp/cacti-profile-disposable
"${COMPOSE[@]}" exec -T cacti php tests/performance/profile-seed.php
"${COMPOSE[@]}" exec -T cacti chown -R www-data:www-data rra cache
# No authenticated browser storage, HTTP bodies or credentials go into metadata.
mise exec python@3.12 -- python - "$CACTI_PROFILE_OUTPUT" <<'PY'
import hashlib
import json
import platform
import subprocess
import sys
from pathlib import Path
root = Path.cwd()
digest = hashlib.sha256()
for directory in ('include', 'lib', 'tests/performance', 'tests/e2e'):
for path in sorted((root / directory).rglob('*')):
if path.is_file() and not any(part in ('vendor', 'node_modules', '__pycache__') for part in path.parts) and path.suffix in ('.php', '.js', '.mjs', '.css', '.py', '.sh', '.yml'):
digest.update(str(path.relative_to(root)).encode())
digest.update(path.read_bytes())
for path in sorted(root.glob('*.php')):
digest.update(path.name.encode())
digest.update(path.read_bytes())
metadata = {'revision': subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip(),
'source_sha256': digest.hexdigest(), 'machine': platform.node(), 'platform': platform.platform(),
'load_average': list(__import__('os').getloadavg()),
'diagnostic_only': True}
(Path(sys.argv[1]) / 'metadata.json').write_text(json.dumps(metadata, indent=2) + '\n')
PY
for scenario in render boost; do
"${COMPOSE[@]}" exec -T cacti php \
-d xdebug.mode=profile -d xdebug.start_with_request=yes \
-d xdebug.output_dir=/profiles -d "xdebug.profiler_output_name=cachegrind.out.$scenario" \
tests/performance/profile-graphs.php "$scenario" > "$CACTI_PROFILE_OUTPUT/$scenario.json"
done
# Browser runs use real authentication, AJAX navigation, CSS and RRD images.
# The realtime transport uses controlled response delays/errors, without SNMP.
mise exec node@22 -- node tests/e2e/profile-client.mjs
mise exec python@3.12 -- python tests/performance/profile-report.py "$CACTI_PROFILE_OUTPUT"
printf 'Profiling results: %s\n' "$CACTI_PROFILE_OUTPUT"