Files
linux-scripts/redis-metrics-exporter.sh
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

332 lines
10 KiB
Bash

#!/bin/bash
################################################
#### Redis Metrics Collector ####
#### for Prometheus node_exporter textfile ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### Version: 1.0.0.20260309 ####
################################################
set -o pipefail
SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_NAME
# Default configuration
readonly DEFAULT_NODE_DIR="/var/lib/node_exporter"
readonly DEFAULT_COLLECTION_INTERVAL=60
readonly DEFAULT_TOP_N_KEYS=10
# Configuration variables (can be overridden by environment)
REDIS_HOST=${REDIS_HOST:-127.0.0.1}
REDIS_PORT=${REDIS_PORT:-6379}
REDIS_PASSWORD=${REDIS_PASSWORD:-}
REDIS_CLI=${REDIS_CLI:-}
NODE_DIR=${NODE_DIR:-$DEFAULT_NODE_DIR}
COLLECTION_INTERVAL=${COLLECTION_INTERVAL:-$DEFAULT_COLLECTION_INTERVAL}
TOP_N_KEYS=${TOP_N_KEYS:-$DEFAULT_TOP_N_KEYS}
DEBUG=${DEBUG:-}
# Runtime flags
RUN_MODE="once"
# Error tracking
ERRORS_TOTAL=0
handle_error() {
local exit_code=$1
local line_number=$2
echo "Error: $SCRIPT_NAME failed at line $line_number with exit code $exit_code" >&2
exit "$exit_code"
}
trap 'handle_error $? $LINENO' ERR
debug_echo() {
if [[ -n "$DEBUG" ]]; then
echo "[DEBUG] $*" >&2
fi
}
show_help() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Redis metrics collector for Prometheus node_exporter textfile directory.
Collects connected clients, memory usage, keyspace hit/miss ratios, per-database
key counts, slowlog analysis, big key detection, persistence status, replication
info, and uptime from Redis via redis-cli.
OPTIONS:
--once Run collection once and exit (default)
--daemon Run continuously at COLLECTION_INTERVAL
--help, -h Show this help message
ENVIRONMENT VARIABLES:
REDIS_HOST Redis server hostname (default: 127.0.0.1)
REDIS_PORT Redis server port (default: 6379)
REDIS_PASSWORD Redis AUTH password (default: empty)
REDIS_CLI Path to redis-cli binary (default: auto-detect)
NODE_DIR Node exporter textfile directory (default: $DEFAULT_NODE_DIR)
COLLECTION_INTERVAL Seconds between collections in daemon mode (default: $DEFAULT_COLLECTION_INTERVAL)
TOP_N_KEYS Number of largest keys to report (default: $DEFAULT_TOP_N_KEYS)
DEBUG Enable debug output
EXAMPLES:
$SCRIPT_NAME --once
REDIS_PASSWORD=secret $SCRIPT_NAME --daemon
REDIS_HOST=redis.example.com REDIS_PORT=6380 $SCRIPT_NAME
OUTPUT:
Writes metrics to \$NODE_DIR/textfile_collector/redis_metrics.prom
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--once) RUN_MODE="once"; shift ;;
--daemon) RUN_MODE="daemon"; shift ;;
--help|-h) show_help ;;
*) echo "Unknown option: $1" >&2; show_help ;;
esac
done
# Auto-detect redis-cli
detect_redis_cli() {
if [[ -n "$REDIS_CLI" ]]; then
if [[ ! -x "$REDIS_CLI" ]]; then
echo "Error: REDIS_CLI not found or not executable: $REDIS_CLI" >&2
exit 1
fi
return
fi
REDIS_CLI=$(command -v redis-cli 2>/dev/null) || true
if [[ -z "$REDIS_CLI" ]]; then
echo "Error: redis-cli not found in PATH" >&2
exit 1
fi
}
# Validate configuration
validate_config() {
detect_redis_cli
local textfile_dir="${NODE_DIR}/textfile_collector"
if [[ ! -d "$textfile_dir" ]]; then
echo "Error: Textfile collector directory not found: $textfile_dir" >&2
echo "Create it: sudo mkdir -p $textfile_dir" >&2
exit 1
fi
}
# Run a redis-cli command
redis_cmd() {
local cmd="$*"
local args=(-h "$REDIS_HOST" -p "$REDIS_PORT")
if [[ -n "$REDIS_PASSWORD" ]]; then
args+=(-a "$REDIS_PASSWORD" --no-auth-warning)
fi
debug_echo "redis-cli ${args[*]} $cmd"
$REDIS_CLI "${args[@]}" $cmd 2>/dev/null
}
# Collect metrics from INFO command
collect_info_metrics() {
debug_echo "Collecting INFO metrics..."
local info
info=$(redis_cmd INFO) || { ERRORS_TOTAL=$((ERRORS_TOTAL + 1)); return 1; }
local connected_clients used_memory keyspace_hits keyspace_misses
local rdb_last_save_time aof_enabled connected_slaves uptime_in_seconds
connected_clients=$(echo "$info" | grep -oP '^connected_clients:\K[0-9]+' || echo "0")
used_memory=$(echo "$info" | grep -oP '^used_memory:\K[0-9]+' || echo "0")
keyspace_hits=$(echo "$info" | grep -oP '^keyspace_hits:\K[0-9]+' || echo "0")
keyspace_misses=$(echo "$info" | grep -oP '^keyspace_misses:\K[0-9]+' || echo "0")
rdb_last_save_time=$(echo "$info" | grep -oP '^rdb_last_save_time:\K[0-9]+' || echo "0")
aof_enabled=$(echo "$info" | grep -oP '^aof_enabled:\K[0-9]+' || echo "0")
connected_slaves=$(echo "$info" | grep -oP '^connected_slaves:\K[0-9]+' || echo "0")
uptime_in_seconds=$(echo "$info" | grep -oP '^uptime_in_seconds:\K[0-9]+' || echo "0")
cat <<EOF
# HELP redis_connected_clients Number of client connections.
# TYPE redis_connected_clients gauge
redis_connected_clients $connected_clients
# HELP redis_used_memory_bytes Total number of bytes allocated by Redis.
# TYPE redis_used_memory_bytes gauge
redis_used_memory_bytes $used_memory
# HELP redis_keyspace_hits_total Number of successful lookups of keys in the main dictionary.
# TYPE redis_keyspace_hits_total counter
redis_keyspace_hits_total $keyspace_hits
# HELP redis_keyspace_misses_total Number of failed lookups of keys in the main dictionary.
# TYPE redis_keyspace_misses_total counter
redis_keyspace_misses_total $keyspace_misses
# HELP redis_last_rdb_save_timestamp Unix timestamp of last successful RDB save.
# TYPE redis_last_rdb_save_timestamp gauge
redis_last_rdb_save_timestamp $rdb_last_save_time
# HELP redis_aof_enabled Whether AOF persistence is enabled (1=yes, 0=no).
# TYPE redis_aof_enabled gauge
redis_aof_enabled $aof_enabled
# HELP redis_connected_slaves Number of connected replicas.
# TYPE redis_connected_slaves gauge
redis_connected_slaves $connected_slaves
# HELP redis_uptime_seconds Number of seconds since Redis server start.
# TYPE redis_uptime_seconds gauge
redis_uptime_seconds $uptime_in_seconds
EOF
}
# Collect per-database key counts
collect_dbsize_metrics() {
debug_echo "Collecting per-database key counts..."
echo "# HELP redis_db_keys Number of keys in each Redis database."
echo "# TYPE redis_db_keys gauge"
for db in $(seq 0 15); do
local dbsize
dbsize=$(redis_cmd -n "$db" DBSIZE 2>/dev/null | grep -oP '[0-9]+' || echo "0")
echo "redis_db_keys{db=\"$db\"} $dbsize"
done
echo ""
}
# Collect slowlog metrics
collect_slowlog_metrics() {
debug_echo "Collecting slowlog metrics..."
local slowlog_len
slowlog_len=$(redis_cmd SLOWLOG LEN 2>/dev/null | grep -oP '[0-9]+' || echo "0")
local slowlog_latest_duration=0
if [[ "$slowlog_len" -gt 0 ]]; then
local slowlog_entry
slowlog_entry=$(redis_cmd SLOWLOG GET 1 2>/dev/null)
if [[ -n "$slowlog_entry" ]]; then
slowlog_latest_duration=$(echo "$slowlog_entry" | awk 'NR==4 { gsub(/[^0-9]/, ""); print }' || echo "0")
[[ -z "$slowlog_latest_duration" ]] && slowlog_latest_duration=0
fi
fi
cat <<EOF
# HELP redis_slowlog_length Total number of entries in the slowlog.
# TYPE redis_slowlog_length gauge
redis_slowlog_length $slowlog_len
# HELP redis_slowlog_latest_duration_microseconds Duration of the latest slowlog entry in microseconds.
# TYPE redis_slowlog_latest_duration_microseconds gauge
redis_slowlog_latest_duration_microseconds $slowlog_latest_duration
EOF
}
# Collect big key metrics via RANDOMKEY sampling
collect_big_keys() {
debug_echo "Collecting big key metrics (sampling $TOP_N_KEYS keys)..."
local -A key_sizes
local sample_count=$((TOP_N_KEYS * 10))
for ((i = 0; i < sample_count; i++)); do
local key
key=$(redis_cmd RANDOMKEY 2>/dev/null)
[[ -z "$key" || "$key" == "(nil)" ]] && continue
local size
size=$(redis_cmd MEMORY USAGE "$key" 2>/dev/null | grep -oP '[0-9]+' || echo "0")
[[ "$size" -gt 0 ]] && key_sizes["$key"]=$size
done
if [[ ${#key_sizes[@]} -gt 0 ]]; then
echo "# HELP redis_big_key_bytes Memory usage in bytes of sampled large keys."
echo "# TYPE redis_big_key_bytes gauge"
for key in "${!key_sizes[@]}"; do
echo "$key ${key_sizes[$key]}"
done | sort -t' ' -k2 -rn | head -n "$TOP_N_KEYS" | while IFS=' ' read -r k v; do
local safe_key
safe_key=$(echo "$k" | sed 's/["\\]/\\&/g')
echo "redis_big_key_bytes{key=\"$safe_key\"} $v"
done
echo ""
fi
}
# Write collection metadata
collect_metadata() {
cat <<EOF
# HELP redis_exporter_errors_total Total number of errors during collection.
# TYPE redis_exporter_errors_total counter
redis_exporter_errors_total $ERRORS_TOTAL
# HELP redis_exporter_last_success_timestamp Unix timestamp of last successful collection.
# TYPE redis_exporter_last_success_timestamp gauge
redis_exporter_last_success_timestamp $(date +%s)
EOF
}
# Main collection function
collect_all() {
ERRORS_TOTAL=0
local output_dir="${NODE_DIR}/textfile_collector"
local output_file="${output_dir}/redis_metrics.prom"
local temp_file
temp_file=$(mktemp "${output_file}.XXXXXX")
debug_echo "Starting collection..."
{
collect_info_metrics
collect_dbsize_metrics
collect_slowlog_metrics
collect_big_keys
collect_metadata
} > "$temp_file" 2>/dev/null
mv "$temp_file" "$output_file"
debug_echo "Collection complete. Wrote to $output_file (errors: $ERRORS_TOTAL)"
}
# Main
main() {
validate_config
case "$RUN_MODE" in
once)
collect_all
;;
daemon)
echo "$SCRIPT_NAME running in daemon mode (interval: ${COLLECTION_INTERVAL}s)"
while true; do
collect_all
sleep "$COLLECTION_INTERVAL"
done
;;
esac
}
main