Skip to content

Commit b4be7da

Browse files
committed
vm-tests: add pgbench PostgreSQL kernel A/B regression test
Add a pgbench-kernel-regression VM test that installs a base kernel, runs a PostgreSQL 16 pgbench read-only and read-write suite, reboots into a second (tip) kernel, re-runs the suite, and emits benchmark-base-*.csv and benchmark-tip-*.csv for the regression analyzer to compare (read-only/read-write TPS and average latency). Signed-off-by: Norbert Manthey <nmanthey@amazon.de>
1 parent 014a591 commit b4be7da

11 files changed

Lines changed: 392 additions & 0 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ examples/aws/credentials.json
4545
# Project-specific config (generated by setup configure)
4646
run-config.json
4747
demo-config.json
48+
pgbench-config.json
4849

4950
# Test kernel RPMs (large binary files)
5051
setup/test-kernel-rpms/

vm-tests/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ See the main [README](../README.md) for writing new tests, configuration, and th
1212
| `example-kernel-reboot-test` | 3 | yes | Installs two kernels with reboot between each |
1313
| `simple-unixbench` | 1 | no | Runs UnixBench on the default kernel |
1414
| `unixbench-kernel-regression` | 3 | yes | Installs two kernels, runs UnixBench on each, produces benchmark CSVs |
15+
| `pgbench-kernel-regression` | 3 | yes | Installs two kernels, runs PostgreSQL pgbench (read-only + read-write) on each, produces benchmark CSVs |
1516
| `simple-source-reboot` | 2 | yes | Installs kernel from source RPM, reboots, verifies |
1617

1718
## How Multi-Stage Tests Work

vm-tests/TODO-shared-lib.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ live only in the shared lib.
2929
All kernel tests now source the shared lib and keep only their test-specific
3030
functions in `common_lib.sh`:
3131

32+
- [x] `pgbench-kernel-regression` — pgbench/PostgreSQL functions local.
3233
- [x] `example-kernel-reboot-test` — no test-specific functions; just sources
3334
the shared lib.
3435
- [x] `simple-source-reboot` — source-RPM build helpers
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# PostgreSQL pgbench Kernel Regression Test
2+
3+
Kernel A/B performance regression test using PostgreSQL's `pgbench`. It installs
4+
two kernels in turn on a **single VM** and runs the same read-only and
5+
read-write `pgbench` workload against each, so the pipeline's benchmark analyzer
6+
can flag database-performance regressions between kernel versions. It follows
7+
the same three-stage pattern as `unixbench-kernel-regression`.
8+
9+
- Dependencies are installed from `dependencies.txt` at run time (not from test
10+
metadata).
11+
- Results are emitted as `benchmark-*.csv` in the schema the pipeline already
12+
supports
13+
14+
## Test Flow
15+
16+
1. **run-01-setup-kernel-A.sh** — install PostgreSQL packages, record the running
17+
kernel, install the first (lowest-version) kernel RPM from the shared
18+
`kernel-rpms` area, then reboot.
19+
2. **run-02-run-pgbench-setup-kernel-B.sh** — confirm the kernel changed, run
20+
`pgbench` (read-only + read-write) on the base kernel, then install the second
21+
(highest-version) kernel and reboot.
22+
3. **run-03-run-second-pgbench.sh** — confirm the kernel changed, run `pgbench`
23+
on the tip kernel.
24+
25+
Single VM, CPU-pinned: PostgreSQL is pinned to the first half of the cores and
26+
the `pgbench` client to the second half, to reduce client/server interference.
27+
28+
## Output
29+
30+
- `benchmark-base-<kernel>.csv` — metrics for the first (base) kernel.
31+
- `benchmark-tip-<kernel>.csv` — metrics for the second (tip) kernel.
32+
33+
CSV columns: `metric,unit,value,more_is_better,kernel_version,instance_id,instance_type,arch`
34+
35+
Metrics captured (per kernel):
36+
- `postgresql.readonly.tps` / `postgresql.readwrite.tps`: transactions/sec (more is better).
37+
- `postgresql.readonly.latency_avg` / `postgresql.readwrite.latency_avg`: average latency in ms (less is better).
38+
39+
The pipeline's benchmark analyzer compares the base and tip CSVs and reports
40+
regressions.
41+
42+
## Requirements
43+
44+
- x86_64 or aarch64 instance with at least 4 vCPUs (CPU pinning splits
45+
server/client across the two halves); `c8i.4xlarge` recommended, `us-west-2`
46+
preferred to raise the chance of landing on the same hardware for both kernels.
47+
- Two kernel RPMs uploaded to the shared kernel-rpms area
48+
(`external_requirements.json` sets `kernel-rpms/binary: true`). The lowest
49+
version becomes the base, the highest becomes the tip.
50+
- System packages from `dependencies.txt` (`postgresql16-server`,
51+
`postgresql16-contrib`, `postgresql16`) are installed in run-01.
52+
53+
## Configuration (environment overrides)
54+
55+
| Variable | Default | Purpose |
56+
|---|---|---|
57+
| `PGBENCH_DURATION` | `240` | Duration in seconds of each pgbench run. |
58+
| `PGBENCH_SCALING_FACTOR` | `100` | Database size multiplier passed to `pgbench -i -s`. |
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Authors: Norbert Manthey <nmanthey@amazon.de>
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Common library for the pgbench PostgreSQL kernel A/B benchmark.
6+
#
7+
# The kernel-management half (install first/last kernel from the shared
8+
# kernel-rpms area, reboot between stages, assert the running kernel changed)
9+
# is reused from the unixbench-kernel-regression test so behaviour stays
10+
# consistent across the benchmark suites. The PostgreSQL specifics are in this
11+
# file. Results are written as the same benchmark-*.csv the pipeline's benchmark
12+
# analyzer supports.
13+
14+
# ---------------------------------------------------------------------------
15+
# Configuration (overridable via environment)
16+
# ---------------------------------------------------------------------------
17+
PGBENCH_DURATION="${PGBENCH_DURATION:-240}"
18+
PGBENCH_SCALING_FACTOR="${PGBENCH_SCALING_FACTOR:-100}"
19+
20+
PGDATA="/tmp/pgdata"
21+
PGPORT=5432
22+
PGDATABASE="pgbench"
23+
export PGDATA PGPORT
24+
25+
NUM_CPUS=$(nproc)
26+
HALF_CPUS=$((NUM_CPUS / 2))
27+
# Single VM with CPU pinning: PostgreSQL on the first half of the cores,
28+
# pgbench client on the second half, to reduce client/server interference.
29+
SERVER_CPUS="0-$((HALF_CPUS - 1))"
30+
CLIENT_CPUS="${HALF_CPUS}-$((NUM_CPUS - 1))"
31+
32+
# ---------------------------------------------------------------------------
33+
# Kernel management (shared across kernel A/B tests)
34+
# ---------------------------------------------------------------------------
35+
# Sets RESULTS_BUCKET/ARCH/KERNEL_RPM_DIR/KERNEL_FILE, validates the pipeline
36+
# environment, and defines the kernel install/reboot helpers. SOURCE_DIR must
37+
# already be set by the run script before this file is sourced.
38+
source "${SOURCE_DIR}/kernel_helpers.sh"
39+
40+
# ---------------------------------------------------------------------------
41+
# PostgreSQL / pgbench (test-specific)
42+
# ---------------------------------------------------------------------------
43+
run_as_postgres()
44+
{
45+
if [ "$(id -u)" -eq 0 ]; then
46+
sudo -u postgres "$@"
47+
else
48+
"$@"
49+
fi
50+
}
51+
52+
# Shared-buffer size: 25% of RAM, capped at 4GB, floored at 128MB.
53+
get_shared_buffer_size()
54+
{
55+
local mem_kb buffer_mb
56+
mem_kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo)
57+
buffer_mb=$((mem_kb / 1024 / 4))
58+
[ "$buffer_mb" -gt 4096 ] && buffer_mb=4096
59+
[ "$buffer_mb" -lt 128 ] && buffer_mb=128
60+
echo "$buffer_mb"
61+
}
62+
63+
setup_postgresql()
64+
{
65+
echo "Initializing PostgreSQL database cluster..."
66+
rm -rf "$PGDATA"
67+
mkdir -p "$PGDATA"
68+
69+
if [ "$(id -u)" -eq 0 ]; then
70+
id postgres &>/dev/null || sudo useradd -r postgres
71+
sudo chown -R postgres:postgres "$PGDATA"
72+
fi
73+
74+
run_as_postgres /usr/bin/initdb -D "$PGDATA" --encoding=SQL_ASCII --locale=C
75+
76+
local shared_buffers max_connections
77+
shared_buffers=$(get_shared_buffer_size)
78+
max_connections=$((NUM_CPUS * 4 + 100))
79+
80+
cat >>"$PGDATA/postgresql.conf" <<EOF
81+
listen_addresses = 'localhost'
82+
port = $PGPORT
83+
max_connections = $max_connections
84+
shared_buffers = ${shared_buffers}MB
85+
work_mem = 64MB
86+
maintenance_work_mem = 256MB
87+
synchronous_commit = off
88+
wal_level = minimal
89+
max_wal_senders = 0
90+
fsync = off
91+
full_page_writes = off
92+
logging_collector = off
93+
unix_socket_directories = '$PGDATA'
94+
dynamic_shared_memory_type = sysv
95+
max_parallel_workers_per_gather = 0
96+
EOF
97+
98+
cat >"$PGDATA/pg_hba.conf" <<EOF
99+
local all all trust
100+
host all all 127.0.0.1/32 trust
101+
host all all ::1/128 trust
102+
EOF
103+
104+
[ "$(id -u)" -eq 0 ] && sudo chown -R postgres:postgres "$PGDATA"
105+
106+
echo "Starting PostgreSQL (pinned to CPUs $SERVER_CPUS)..."
107+
run_as_postgres taskset -c "$SERVER_CPUS" /usr/bin/postgres -D "$PGDATA" >>"$PGDATA/logfile" 2>&1 &
108+
109+
local i
110+
for i in {1..30}; do
111+
sleep 1
112+
/usr/bin/pg_isready -h localhost -p "$PGPORT" && break
113+
done
114+
if ! /usr/bin/pg_isready -h localhost -p "$PGPORT"; then
115+
echo "ERROR: PostgreSQL failed to start:" >&2
116+
cat "$PGDATA/logfile" >&2
117+
return 1
118+
fi
119+
120+
run_as_postgres /usr/bin/createdb -h localhost -p "$PGPORT" "$PGDATABASE"
121+
echo "PostgreSQL started"
122+
}
123+
124+
init_pgbench()
125+
{
126+
local scaling_factor="${1:-$PGBENCH_SCALING_FACTOR}"
127+
echo "Initializing pgbench tables (scaling factor: $scaling_factor)..."
128+
run_as_postgres /usr/bin/pgbench -h localhost -p "$PGPORT" -i -s "$scaling_factor" "$PGDATABASE"
129+
}
130+
131+
# Run one pgbench mode (readonly|readwrite) into output_file.
132+
run_pgbench()
133+
{
134+
local mode="$1"
135+
local output_file="$2"
136+
local duration="${3:-$PGBENCH_DURATION}"
137+
138+
local clients threads mode_flag=""
139+
[ "$mode" = "readonly" ] && mode_flag="-S"
140+
clients=$((HALF_CPUS * 2))
141+
threads=$HALF_CPUS
142+
143+
echo "Running pgbench $mode (clients=$clients, threads=$threads, duration=${duration}s)"
144+
run_as_postgres taskset -c "$CLIENT_CPUS" /usr/bin/pgbench \
145+
-h localhost -p "$PGPORT" --protocol=prepared \
146+
-c "$clients" -j "$threads" -T "$duration" -r $mode_flag \
147+
"$PGDATABASE" >"$output_file" 2>&1
148+
}
149+
150+
stop_postgresql()
151+
{
152+
echo "Stopping PostgreSQL..."
153+
run_as_postgres /usr/bin/pg_ctl -D "$PGDATA" stop -m fast 2>/dev/null || true
154+
}
155+
156+
# Set up PostgreSQL, run the read-only and read-write benchmarks into
157+
# results_dir, then stop PostgreSQL. Requires at least 4 CPUs.
158+
run_pgbench_suite()
159+
{
160+
local results_dir="$1"
161+
if [ "$(nproc)" -lt 4 ]; then
162+
echo "ERROR: pgbench benchmark requires at least 4 CPUs" >&2
163+
return 1
164+
fi
165+
mkdir -p "$results_dir"
166+
167+
setup_postgresql
168+
init_pgbench "$PGBENCH_SCALING_FACTOR"
169+
170+
local mode output
171+
for mode in readonly readwrite; do
172+
echo "=== Running $mode benchmark ==="
173+
output="$results_dir/pgbench_${mode}.txt"
174+
run_pgbench "$mode" "$output"
175+
cat "$output"
176+
done
177+
178+
stop_postgresql
179+
}
180+
181+
# Parse pgbench read-only and read-write output into a benchmark CSV that the
182+
# pipeline's benchmark analyzer consumes (same schema as the unixbench test):
183+
# metric,unit,value,more_is_better,kernel_version,instance_id,instance_type,arch
184+
summarize_pgbench_output()
185+
{
186+
local readonly_file="$1"
187+
local readwrite_file="$2"
188+
local output_csv_file="$3"
189+
190+
local kernel_version instance_id instance_type arch
191+
kernel_version=$(uname -r)
192+
instance_id=$(ec2-metadata --instance-id 2>/dev/null | cut -d" " -f2 || hostname || echo "unknown")
193+
instance_type=$(ec2-metadata --instance-type 2>/dev/null | cut -d" " -f2 || echo "unknown")
194+
arch=$(uname -m)
195+
196+
echo "metric,unit,value,more_is_better,kernel_version,instance_id,instance_type,arch" >"$output_csv_file"
197+
198+
local mode file tps latency
199+
for mode in readonly readwrite; do
200+
[ "$mode" = "readonly" ] && file="$readonly_file" || file="$readwrite_file"
201+
[ -f "$file" ] || { echo "WARNING: $file not found, skipping $mode" >&2; continue; }
202+
203+
# "tps = NNN (without initial connection time)" / "(excluding connections establishing)"
204+
tps=$(grep "tps = " "$file" | grep -E "(excluding|without)" | awk '{print $3}' | head -1 || true)
205+
# "latency average = NNN ms"
206+
latency=$(grep "latency average" "$file" | awk '{print $4}' | head -1 || true)
207+
208+
[ -n "$tps" ] && \
209+
echo "postgresql.${mode}.tps,TPS,${tps},true,${kernel_version},${instance_id},${instance_type},${arch}" >>"$output_csv_file"
210+
[ -n "$latency" ] && \
211+
echo "postgresql.${mode}.latency_avg,ms,${latency},false,${kernel_version},${instance_id},${instance_type},${arch}" >>"$output_csv_file"
212+
done
213+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Packages required to run the pgbench PostgreSQL benchmark.
2+
# Provides initdb, postgres, pgbench, pg_isready, createdb and pg_ctl in /usr/bin.
3+
postgresql16-server
4+
postgresql16-contrib
5+
postgresql16
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"kernel-rpms/src": false,
3+
"kernel-rpms/binary": true
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../lib/kernel_helpers.sh
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
3+
# Authors: Norbert Manthey <nmanthey@amazon.de>
4+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
#
8+
# First run: install PostgreSQL dependencies and the first (lower-version)
9+
# kernel to test. The client reboots into it before run-02.
10+
11+
set -euxo pipefail
12+
13+
# Set source directory and source common library for functions and constants
14+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15+
source "${SOURCE_DIR}/common_lib.sh"
16+
17+
# Install PostgreSQL packages (from dependencies.txt, not test metadata).
18+
install_test_dependencies
19+
20+
# Save a build-level identity of the kernel running before we install kernel A.
21+
# get_running_kernel_id combines uname -r + uname -v + the booted vmlinuz hash,
22+
# so it detects a real kernel switch even when two builds share the same NVR
23+
# (e.g. compiler A/B kernels).
24+
kernel_before="$(get_running_kernel_id)"
25+
echo "Kernel before installation: $kernel_before"
26+
save_kernel_version "$kernel_before" "$KERNEL_FILE"
27+
28+
# Install the kernel with the lower version as the kernel to be used next.
29+
first_kernel=$(get_first_kernel_rpm_from_dir)
30+
install_specified_kernel_rpm "$first_kernel"
31+
32+
# Stop here; re-execution happens after reboot and continues in run-02-*.sh
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
# Authors: Norbert Manthey <nmanthey@amazon.de>
4+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
#
8+
# Second run: run pgbench on the first (base) kernel, then install the second
9+
# (higher-version) kernel to test.
10+
11+
set -euxo pipefail
12+
13+
# Set source directory and source common library for functions and constants
14+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15+
source "${SOURCE_DIR}/common_lib.sh"
16+
17+
# Confirm the kernel actually changed after the reboot from run-01. Use the
18+
# build-level identity so a same-NVR-but-different-build kernel still counts.
19+
kernel_after="$(get_running_kernel_id)"
20+
echo "Current running kernel: $(uname -r) (id: $kernel_after)"
21+
kernel_before="$(load_kernel_version "$KERNEL_FILE")"
22+
echo "Kernel before installation: $kernel_before"
23+
assert_kernel_changed "$kernel_before" "$kernel_after"
24+
save_kernel_version "$kernel_after" "$KERNEL_FILE"
25+
26+
# Make sure PostgreSQL is stopped even if the benchmark fails.
27+
trap stop_postgresql EXIT
28+
29+
# Run pgbench for the base kernel and record the benchmark CSV.
30+
RESULTS_DIR="${PWD}/results"
31+
run_pgbench_suite "$RESULTS_DIR"
32+
summarize_pgbench_output \
33+
"$RESULTS_DIR/pgbench_readonly.txt" \
34+
"$RESULTS_DIR/pgbench_readwrite.txt" \
35+
"benchmark-base-$(uname -r).csv"
36+
37+
# Install the kernel with the higher version as the kernel to be used next.
38+
last_kernel=$(get_last_kernel_rpm_from_dir)
39+
install_specified_kernel_rpm "$last_kernel"
40+
41+
# Stop here; re-execution happens after reboot and continues in run-03-*.sh

0 commit comments

Comments
 (0)