#!/bin/bash ################################################################################ # Script Name: postgresql-exporter.sh # Version: 1.0 # Description: Prometheus textfile exporter for PostgreSQL — connections, TPS, # cache hit ratio, database sizes, dead tuples, locks, WAL # replication, and checkpoint stats via psql # # Author: Phil Connor # Contact: contact@mylinux.work # Website: https://mylinux.work # License: MIT # # Prerequisites: # - psql CLI client # - Credentials via ~/.pgpass or PGPASSWORD env # - Network access to PostgreSQL server # - netcat (nc) for HTTP mode # # Usage: # ./postgresql-exporter.sh # ./postgresql-exporter.sh --http -p 9208 # ./postgresql-exporter.sh --textfile # # Configuration: # Default HTTP port: 9208 # Textfile directory: /var/lib/node_exporter # ################################################################################ TEXTFILE_DIR="/var/lib/node_exporter" OUTPUT_FILE="" HTTP_MODE=false HTTP_PORT=9208 PGHOST="${PGHOST:-127.0.0.1}" PGPORT="${PGPORT:-5432}" PGUSER="${PGUSER:-}" PGDATABASE="${PGDATABASE:-postgres}" show_usage() { cat <&2; exit 1 ;; esac done } build_psql_cmd() { local cmd="psql -h $PGHOST -p $PGPORT" [ -n "$PGUSER" ] && cmd="$cmd -U $PGUSER" cmd="$cmd -d $PGDATABASE -t -A -F'|'" echo "$cmd" } check_postgresql() { if ! command -v psql >/dev/null 2>&1; then echo "ERROR: psql client not found" >&2 return 1 fi local psql_cmd psql_cmd=$(build_psql_cmd) if ! $psql_cmd -c "SELECT 1" >/dev/null 2>&1; then echo "ERROR: Cannot connect to PostgreSQL server" >&2 return 1 fi return 0 } psql_query() { local query="$1" local psql_cmd psql_cmd=$(build_psql_cmd) $psql_cmd -c "$query" 2>/dev/null } generate_metrics() { local script_start script_start=$(date +%s) if ! check_postgresql; then cat </dev/null | head -1) local temp_bytes temp_bytes=$(psql_query "SELECT sum(temp_bytes) FROM pg_stat_database" | head -1) local checkpoints checkpoints=$(psql_query "SELECT checkpoints_timed + checkpoints_req FROM pg_stat_bgwriter" | head -1) local checkpoint_write_time checkpoint_write_time=$(psql_query "SELECT checkpoint_write_time / 1000.0 FROM pg_stat_bgwriter" | head -1) cat <&2 if ! command -v nc >/dev/null 2>&1; then echo "ERROR: netcat (nc) required for HTTP mode" >&2 exit 1 fi while true; do { read -r request if [[ "$request" =~ ^GET\ /metrics ]]; then echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r" generate_metrics else echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r" echo "PostgreSQL Exporter

PostgreSQL Prometheus Exporter

Metrics

" fi } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null done } main() { parse_args "$@" if [ "$HTTP_MODE" = true ]; then run_http_server elif [ -n "$OUTPUT_FILE" ]; then local output_dir output_dir="$(dirname "$OUTPUT_FILE")" mkdir -p "$output_dir" local temp_file temp_file=$(mktemp "${output_dir}/.postgresql_metrics.XXXXXX") if ! generate_metrics > "$temp_file" 2>/dev/null; then rm -f "$temp_file" echo "ERROR: Failed to generate metrics" >&2 exit 1 fi local file_lines file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0) if [ "$file_lines" -lt 10 ]; then rm -f "$temp_file" echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2 exit 1 fi chmod 644 "$temp_file" mv -f "$temp_file" "$OUTPUT_FILE" echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2 else generate_metrics fi } main "$@"