a1a17e81a1
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.
332 lines
10 KiB
Bash
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
|