|
| 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 | +} |
0 commit comments