From 39ca8845a9b09ee004ab56a3f2eb406ddc1ee71e Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Tue, 8 Feb 2022 11:08:28 -0300 Subject: [PATCH] PG-342: Add API compatibility verification script. Added a new script that automates the process of checking API compatibility between two pg_stat_monitor branches. --- api_check.sh | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100755 api_check.sh diff --git a/api_check.sh b/api_check.sh new file mode 100755 index 0000000..d288996 --- /dev/null +++ b/api_check.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# +# api_check.sh: Check API compatibility between two pg_stat_monitor branches. +# +# Usage: api_check.sh [source_branch] target_branch +# OR +# api_check.sh clean +# +# source_branch is optional, if omitted the script will use the current git branch +# as the source. +# +# If invoked as api_check.sh clean, it will remove all generated objects that could +# be used for further inspection. +# +# The script works by compiling the pg_stat_monitor extension on both source and +# target branches, then it (re)creates the extension on PostgreSQL, dump the +# database objects (views, functions) and compare the results. +# +# All the relevant log is output on console (stdout/1). +# +# Following exit codes are used: +# 0 (SUCCESS) API between branches hasn't changed. +# 1 (ERROR) Script failed to execute some step. +# 10 (API CHANGED) Target branch has either removed or modified some database +# object that was exported by the source branch. +# +# +# REQUIRES: Following environment variables must be properly set. +# PATH Must include path to the pg_config relative to the PostgreSQL instance to be used. +# PGDATA Must point to the database cluster directory to be used. + +function usage() { + echo "[*] Usage: $0 [source_branch/commit_id] target_branch/commit_id" + echo "[*] OR" + echo "[*] Usage: $0 clean" + echo + echo "$0 checks for API compatibility between the source_branch and"\ + "target_branch." + echo + echo "If invoked with clean argument, the script just removes all generated"\ + "files that could be used for further inspection." + exit 1 +} + +# Check number of command line arguments. +[ $# -eq 0 ] && +{ + usage +} + +[ "$1" = "-h" -o "$1" = "--help"] && +{ + usage +} + +if [ "$1" = "clean" ]; then + rm -vf api_pgsmview.* + rm -vf api_pgsmerrview.* + rm -vf api_histogramfn.* + rm -vf api_resetfn.* + rm -vf api_reseterrfn.* + exit 0 +fi + +# Get absolute directory path to the script. +# https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +# Change working dir to the project directory to be able to run git commands. +cd "${SCRIPTPATH}" + +# Save original git branch. +ORIG_BRANCH="`git rev-parse --abbrev-ref HEAD`" + +if [ $# -gt 1 ]; then + # Source git branch. + SOURCE_BRANCH="$1" + # Target branch to compare API with. + TARGET_BRANCH="$2" + # Check if source branch actually exists. + if ! git rev-parse --verify "${SOURCE_BRANCH}"; then + echo "[*] ERROR: Source branch do not exist: ${SOURCE_BRANCH}" + exit 1 + fi +else + # Current git branch. + SOURCE_BRANCH="`git rev-parse --abbrev-ref HEAD`" + # Target branch to compare API with. + TARGET_BRANCH="$1" +fi + +# Check that target branch actually exists. +if ! git rev-parse --verify "${TARGET_BRANCH}"; then + echo "[*] ERROR: Target branch do not exist: ${TARGET_BRANCH}" + exit 1 +fi + +# Check for pg_config, POSIX compatible way. +if ! command -v pg_config > /dev/null 2>&1; then + echo "[*] ERROR: Could not locate pg_config, please adjust PATH" + exit 1 +fi + +PG_BINDIR=`pg_config --bindir` +PGCTL="${PG_BINDIR}/pg_ctl" +PSQL="${PG_BINDIR}/psql" + +[ ! -x "${PGCTL}" ] && +{ + echo "[*] ERROR: pg_ctl not found/no +x permission: ${PGCTL}" + exit 1 +} + +[ ! -x "${PSQL}" ] && +{ + echo "[*] ERROR: psql not found/no +x permission: ${PSQL}" + exit 1 +} + +[ -z "$PGDATA" ] && +{ + echo "[*] ERROR: PGDATA environment must be set and point to your\ + PostgreSQL database directory" + exit 1 +} + +[ ! -d "$PGDATA" ] && +{ + echo "[*] ERROR: PGDATA directory not found: $PGDATA" + exit 1 +} + +# Remove temporary files when script exits. +function cleanup() { + rm -f err + if [ -f pgconf.orig ]; then + cp -f pgconf.orig "${PGDATA}/postgresql.conf" + rm -f pgconf.orig + fi + rm -f pgsm.conf +} + +trap cleanup EXIT + +PG_VER="`cat ${PGDATA}/PG_VERSION 2> /dev/null`" + +# Make a copy of original postgresql.conf. +cp -f "${PGDATA}"/postgresql.conf ./pgconf.orig +# Remove any shared_preload_libraries configuration. +grep -v shared_preload_libraries pgconf.orig > pgsm.conf +# Add pg_stat_monitor extension to the config. +echo "shared_preload_libraries = 'pg_stat_monitor'" >> pgsm.conf +# Overwrite postgresql configuration. +cp pgsm.conf "${PGDATA}/postgresql.conf" + +function restart_postgres() { + echo "[*] Restarting PostgreSQL ${PG_VER}..." + cd "$PGDATA" + if ! $PGCTL -D "$PGDATA" restart 2> err > /dev/null; then + echo "[*] ERROR: Failed to restart PostgreSQL ${PG_VER}: `cat err`" + exit 1 + fi + cd - +} + +# Dump information about views, functions, or any object used as API. +# 1. Compile and install pg_stat_monitor. +# 2. Restart PostgreSQL. +# 3. Create the extension. +# 4. Dump pg_stat_monitor views, functions, etc... +function dump_pgsm_api() { + BRANCH="$1" + echo "[*] Compiling pg_stat_monitor ($BRANCH)" + make USE_PGXS=1 clean + if ! make USE_PGXS=1 install; then + echo "[*] Compilation failed, aborting..." + exit 1 + fi + restart_postgres + "$PSQL" -c 'DROP EXTENSION IF EXISTS pg_stat_monitor;' 2> /dev/null + "$PSQL" -c 'CREATE EXTENSION pg_stat_monitor;' 2> /dev/null + "$PSQL" -c '\d pg_stat_monitor' 2> /dev/null > api_pgsmview."${BRANCH}" + "$PSQL" -c '\d pg_stat_monitor_errors' 2> /dev/null > api_pgsmerrview."${BRANCH}" + "$PSQL" -c '\sf histogram' 2> /dev/null > api_histogramfn."${BRANCH}" + "$PSQL" -c '\sf pg_stat_monitor_reset' 2> /dev/null > api_resetfn."${BRANCH}" + "$PSQL" -c '\sf pg_stat_monitor_reset_errors' 2> /dev/null > api_reseterrfn."${BRANCH}" +} + +# Checkout source branch. +# If source branch was not given on command line, use current +# git branch (skip checkout). +TMP_SRC_BRANCH="" +if [ $# -gt 1 ]; then + RANDSTR=`echo $RANDOM | md5sum | head -c 20` + TMP_SRC_BRANCH="source_${RANDSTR}_${SOURCE_BRANCH}" + echo "[*] Checking out ${SOURCE_BRANCH} on temporary branch ${TMP_SRC_BRANCH}" + + if ! git checkout -b "${TMP_SRC_BRANCH}" "${SOURCE_BRANCH}" 2> /dev/null; then + echo "[*] ERROR: Failed to checkout branch ${SOURCE_BRANCH}" + exit 1 + fi +fi + +echo "[*] Dumping API (${SOURCE_BRANCH})..." +dump_pgsm_api "${SOURCE_BRANCH}" +make USE_PGXS=1 clean + +RANDSTR=`echo $RANDOM | md5sum | head -c 20` +TMP_DST_BRANCH="target_${RANDSTR}_${TARGET_BRANCH}"1 +echo "[*] Checking out ${TARGET_BRANCH} on temporary branch ${TMP_DST_BRANCH}" + +if ! git checkout -b "${TMP_DST_BRANCH}" "${TARGET_BRANCH}" 2> /dev/null; +then + echo "[*] ERROR: Failed to checkout branch ${TARGET_BRANCH}" + exit 1 +fi + +# Remove temporary source branch (if created). +[ -z "${TMP_SRC_BRANCH}"] || git branch -D "${TMP_SRC_BRANCH}" + +echo "[*] Dumping API (${TARGET_BRANCH})..." +dump_pgsm_api "${TARGET_BRANCH}" +make USE_PGXS=1 clean + +# Restore original branch and remove temporary one. +git checkout "${ORIG_BRANCH}" +git branch -D "${TMP_DST_BRANCH}" + +echo +echo "[*] ------ API comparison results ------" +echo "[*]" + +# Input files +INPUT=(api_pgsmview api_pgsmerrview api_histogramfn api_resetfn api_reseterrfn) +# Real API object names. +API_OBJECTS=("pg_stat_monitor" "pg_stat_monitor_errors" "histogram" "pg_stat_monitor_reset" "pg_stat_monitor_reset_errors") + +STATUS=0 +i=0 +while [ $i -lt ${#API_OBJECTS[@]} ]; do + echo "[*] ${API_OBJECTS[$i]}:" + + # Compute input file sizes. + fs0=$(stat -c '%s' ${INPUT[$i]}."${SOURCE_BRANCH}") + fs1=$(stat -c '%s' ${INPUT[$i]}."${TARGET_BRANCH}") + + if [ $fs0 -eq 0 ]; then + echo -e "\tNot present in branch ${SOURCE_BRANCH}" + if [ $fs1 -gt 0 ]; then + echo -e "\tObject added on target branch ${TARGET_BRANCH}" + fi + fi + + if [ $fs1 -eq 0 ]; then + if [ $fs0 -gt 0 ]; then + # Object was present in original but removed on target branch. + echo -e "\tRemoved on branch ${TARGET_BRANCH}" + STATUS=10 + else + echo -e "\tNot present in branch ${TARGET_BRANCH}" + fi + fi + + if [ $fs0 -gt 0 -a $fs1 -gt 0 ]; then + if ! diff ${INPUT[$i]}."${SOURCE_BRANCH}" ${INPUT[$i]}."${TARGET_BRANCH}" > ${API_OBJECTS[$i]}.diff; then + echo -e "\tAPI is different, diff (${API_OBJECTS[$i]}.diff):" + cat ${API_OBJECTS[$i]}.diff + STATUS=10 + else + echo -e "\tAPI is compatible (OK)" + fi + fi + + echo "[*] -----------------------------" + + i=`expr $i + 1` +done + +exit $STATUS \ No newline at end of file