who_need_help/scripts/scale-readonly-load.sh

162 lines
4.8 KiB
Bash
Executable File

#!/bin/sh
set -eu
umask 077
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
# shellcheck source=scripts/scale-common.sh
. "$ROOT/scripts/scale-common.sh"
scale_load_environment
K6_IMAGE="grafana/k6@sha256:65c920dc067d5e2e00befbf982af6ad6ad0117034e8b1c65817c7975c52d4669"
STATE_FILE=${WNH_SCALE_BROWSER_STATE:-"$ROOT/output/runtime/scale-browser-state.json"}
VUS=${WNH_SCALE_READONLY_VUS:?WNH_SCALE_READONLY_VUS is required}
DURATION=${WNH_SCALE_READONLY_DURATION:?WNH_SCALE_READONLY_DURATION is required}
THINK_SECONDS=${WNH_SCALE_READONLY_THINK_SECONDS:?WNH_SCALE_READONLY_THINK_SECONDS is required}
LABEL=${WNH_SCALE_READONLY_LABEL:-"scale-readonly-$(date -u +%Y%m%dT%H%M%SZ)"}
OUTPUT_DIR="$ROOT/output/performance/$LABEL"
case "$VUS" in
''|*[!0-9]*|0)
echo "WNH_SCALE_READONLY_VUS must be a positive integer." >&2
exit 2
;;
esac
if ! printf '%s\n' "$DURATION" | grep -Eq '^[1-9][0-9]*(ms|s|m|h)$'; then
echo "WNH_SCALE_READONLY_DURATION must be a positive k6 duration." >&2
exit 2
fi
if ! printf '%s\n' "$THINK_SECONDS" |
grep -Eq '^([0-9]+([.][0-9]+)?|[.][0-9]+)$'; then
echo "WNH_SCALE_READONLY_THINK_SECONDS must be a non-negative number." >&2
exit 2
fi
case "$LABEL" in
''|*[!A-Za-z0-9._-]*)
echo "WNH_SCALE_READONLY_LABEL contains unsupported characters." >&2
exit 2
;;
esac
if [ ! -f "$STATE_FILE" ]; then
echo "Missing authenticated scale browser state: $STATE_FILE" >&2
exit 2
fi
session_cookie=$(
jq -r '
.cookies[]
| select(.domain == "scale.localhost" and .name == "_who_need_help_key")
| "\(.name)=\(.value)"
' "$STATE_FILE"
)
if [ -z "$session_cookie" ]; then
echo "The browser state does not contain the scale session cookie." >&2
exit 2
fi
web_id=$(scale_compose ps -q web)
worker_id=$(scale_compose ps -q worker)
db_id=$(scale_compose ps -q db)
for pair in "web:$web_id" "worker:$worker_id" "db:$db_id"; do
service=${pair%%:*}
container=${pair#*:}
if [ -z "$container" ] || [ "$(docker inspect --format '{{.State.Status}}' "$container")" != "running" ]; then
echo "Scale service $service is not running." >&2
exit 2
fi
done
actual_database=$(
scale_compose exec -T db \
psql -XAt -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
-c 'SELECT current_database()'
)
if [ "$actual_database" != "$WNH_SCALE_EXPECTED_DATABASE" ]; then
echo "Refusing unexpected database: $actual_database" >&2
exit 2
fi
if [ -e "$OUTPUT_DIR" ]; then
echo "Output path already exists: $OUTPUT_DIR" >&2
exit 2
fi
mkdir -p "$OUTPUT_DIR"
chmod 700 "$ROOT/output" "$ROOT/output/performance" "$OUTPUT_DIR"
snapshot_counts() {
destination=$1
scale_compose exec -T db \
psql -XAt -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
-c "
SELECT json_build_object(
'captured_at', clock_timestamp(),
'database', current_database(),
'users', (SELECT count(*) FROM users),
'users_tokens', (SELECT count(*) FROM users_tokens),
'help_requests', (SELECT count(*) FROM help_requests),
'activities', (SELECT count(*) FROM activities),
'help_assignments', (SELECT count(*) FROM help_assignments),
'activity_participants', (SELECT count(*) FROM activity_participants),
'messages', (SELECT count(*) FROM messages),
'activity_messages', (SELECT count(*) FROM activity_messages),
'rate_limit_buckets', (SELECT count(*) FROM rate_limit_buckets)
)
" >"$destination"
}
snapshot_counts "$OUTPUT_DIR/counts-before.json"
cat >"$OUTPUT_DIR/scope.txt" <<EOF
target=http://web:4000
host_header=scale.localhost
compose_project=$LOAD_PROJECT
database=$actual_database
database_volume=${LOAD_PROJECT}_postgres_data
cpuset=$CPU_REPLAY_CPUSET
vus=$VUS
duration=$DURATION
think_seconds=$THINK_SECONDS
scope=authenticated GET-only static LiveView shells; discovery SQL is measured separately by scripts/scale-verify.sh
mutating_routes=false
test_or_production_targets=false
latency_or_capacity_thresholds_applied=false
EOF
docker run --rm \
--user "$(id -u):$(id -g)" \
--cpuset-cpus "$CPU_REPLAY_CPUSET" \
--network "${LOAD_PROJECT}_internal" \
--env BASE_URL=http://web:4000 \
--env "SESSION_COOKIE=$session_cookie" \
--env "VUS=$VUS" \
--env "DURATION=$DURATION" \
--env "THINK_SECONDS=$THINK_SECONDS" \
--volume "$ROOT/load/k6:/scripts:ro" \
--volume "$OUTPUT_DIR:/output" \
"$K6_IMAGE" run \
--summary-export /output/k6-summary.json \
/scripts/scale-readonly.js
snapshot_counts "$OUTPUT_DIR/counts-after.json"
jq -e '
del(.captured_at) as $before
| $before == (input | del(.captured_at))
' "$OUTPUT_DIR/counts-before.json" "$OUTPUT_DIR/counts-after.json" >/dev/null
docker run --rm \
--volume "$OUTPUT_DIR:/output" \
alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 \
chown -R "$(id -u):$(id -g)" /output
printf 'Read-only scale result: %s\n' "$OUTPUT_DIR"