Add all 44 scripts, update CI: error severity baseline, PowerShell validation, multi-distro testing
Amp-Thread-ID: https://ampcode.com/threads/T-019cc404-c628-759e-a50b-f5eeea35b91f Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env bash
|
||||
# directory-size-exporter.sh — Prometheus exporter for directory sizes
|
||||
#
|
||||
# Monitors directory disk usage that node_exporter can't see.
|
||||
# Node exporter only reports mounted filesystem totals — this script
|
||||
# tracks individual directories like /var/log, /home, /opt, or any
|
||||
# path you care about.
|
||||
#
|
||||
# Author: Phil Connor
|
||||
# Contact: contact@mylinux.work
|
||||
# License: MIT
|
||||
# Version: 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
EXPORTER_NAME="directory_size"
|
||||
DEFAULT_PORT=9101
|
||||
OUTPUT_MODE="stdout"
|
||||
OUTPUT_FILE=""
|
||||
PORT="${DIRECTORY_SIZE_PORT:-$DEFAULT_PORT}"
|
||||
TIMEOUT="${DIRECTORY_SIZE_TIMEOUT:-300}"
|
||||
VERBOSE=false
|
||||
QUIET=false
|
||||
DRY_RUN=false
|
||||
TARGET_DIRECTORIES=()
|
||||
|
||||
# ── Metrics Collection ──────────────────────────────────────────────
|
||||
|
||||
log_verbose() {
|
||||
[[ "$VERBOSE" == true ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
|
||||
}
|
||||
|
||||
log_info() {
|
||||
[[ "$QUIET" == false ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
|
||||
}
|
||||
|
||||
collect_metrics() {
|
||||
local start_time
|
||||
start_time=$(date +%s%N)
|
||||
|
||||
echo "# HELP node_directory_size_bytes Disk space used by directory"
|
||||
echo "# TYPE node_directory_size_bytes gauge"
|
||||
echo "# HELP node_directory_filesystem_usage_percent Filesystem usage percentage for the directory mount point"
|
||||
echo "# TYPE node_directory_filesystem_usage_percent gauge"
|
||||
|
||||
local success=1
|
||||
|
||||
for directory in "${TARGET_DIRECTORIES[@]}"; do
|
||||
log_verbose "Running du for: $directory"
|
||||
|
||||
# Get directory size in bytes
|
||||
local du_output
|
||||
du_output=$(timeout "$TIMEOUT" du --block-size=1 --summarize "$directory" 2>/dev/null) || {
|
||||
log_info "WARNING: du failed for $directory"
|
||||
success=0
|
||||
continue
|
||||
}
|
||||
|
||||
local size_bytes
|
||||
size_bytes=$(echo "$du_output" | awk '{print $1}')
|
||||
echo "node_directory_size_bytes{directory=\"${directory}\"} ${size_bytes}"
|
||||
|
||||
# Get filesystem usage percentage for the mount point
|
||||
local pct
|
||||
pct=$(df --output=pcent "$directory" 2>/dev/null | tail -n 1 | tr -d ' %')
|
||||
if [[ "$pct" =~ ^[0-9]+$ ]]; then
|
||||
echo "node_directory_filesystem_usage_percent{directory=\"${directory}\"} ${pct}"
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Script runtime ──
|
||||
local end_time runtime
|
||||
end_time=$(date +%s%N)
|
||||
runtime=$(awk "BEGIN {printf \"%.3f\", ($end_time - $start_time) / 1000000000}")
|
||||
|
||||
echo ""
|
||||
echo "# HELP ${EXPORTER_NAME}_duration_seconds Script execution time"
|
||||
echo "# TYPE ${EXPORTER_NAME}_duration_seconds gauge"
|
||||
echo "${EXPORTER_NAME}_duration_seconds ${runtime}"
|
||||
|
||||
echo "# HELP ${EXPORTER_NAME}_last_run_timestamp Last successful run"
|
||||
echo "# TYPE ${EXPORTER_NAME}_last_run_timestamp gauge"
|
||||
echo "${EXPORTER_NAME}_last_run_timestamp $(date +%s)"
|
||||
|
||||
echo "# HELP ${EXPORTER_NAME}_success Whether the exporter ran successfully"
|
||||
echo "# TYPE ${EXPORTER_NAME}_success gauge"
|
||||
echo "${EXPORTER_NAME}_success ${success}"
|
||||
}
|
||||
|
||||
# ── HTTP Request Handler ────────────────────────────────────────────
|
||||
|
||||
handle_request() {
|
||||
read -r method path version
|
||||
|
||||
while IFS= read -r header; do
|
||||
[[ "$header" == $'\r' || -z "$header" ]] && break
|
||||
done
|
||||
|
||||
if [[ "$path" == "/metrics" ]]; then
|
||||
local metrics length
|
||||
metrics=$(collect_metrics)
|
||||
length=${#metrics}
|
||||
|
||||
printf "HTTP/1.1 200 OK\r\n"
|
||||
printf "Content-Type: text/plain; version=0.0.4; charset=utf-8\r\n"
|
||||
printf "Content-Length: %d\r\n" "$length"
|
||||
printf "Connection: close\r\n"
|
||||
printf "\r\n"
|
||||
printf "%s" "$metrics"
|
||||
else
|
||||
local body="404 Not Found"
|
||||
printf "HTTP/1.1 404 Not Found\r\n"
|
||||
printf "Content-Type: text/plain\r\n"
|
||||
printf "Content-Length: %d\r\n" "${#body}"
|
||||
printf "Connection: close\r\n"
|
||||
printf "\r\n"
|
||||
printf "%s" "$body"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Help ─────────────────────────────────────────────────────────────
|
||||
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
Usage: $0 [OPTIONS] <directory> [directory2 ...]
|
||||
|
||||
Monitor directory sizes for Prometheus. Node exporter only reports
|
||||
mounted filesystem totals — this script tracks individual directories.
|
||||
|
||||
Output modes:
|
||||
(default) Print metrics to stdout
|
||||
--textfile Write to node_exporter textfile collector
|
||||
-o FILE Write to a specific file
|
||||
--http Run as HTTP server (default port: ${DEFAULT_PORT})
|
||||
|
||||
Options:
|
||||
--port PORT HTTP listen port (default: ${DEFAULT_PORT})
|
||||
--timeout SECS du command timeout (default: 300)
|
||||
--dry-run Show what would be written without writing
|
||||
--verbose, -v Enable verbose debug output
|
||||
--quiet, -q Suppress non-error output
|
||||
-h, --help Show this help message
|
||||
|
||||
Environment variables:
|
||||
DIRECTORY_SIZE_PORT HTTP listen port (default: ${DEFAULT_PORT})
|
||||
DIRECTORY_SIZE_TIMEOUT du command timeout in seconds (default: 300)
|
||||
|
||||
Examples:
|
||||
$0 /var/log /home /opt
|
||||
$0 --textfile /var/log /var/lib/mysql
|
||||
$0 --http --port 9101 /var/log /home
|
||||
$0 -o /tmp/dir_sizes.prom /var/log
|
||||
EOF
|
||||
}
|
||||
|
||||
# ── Argument Parsing ────────────────────────────────────────────────
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--textfile)
|
||||
OUTPUT_MODE="textfile"
|
||||
shift
|
||||
;;
|
||||
-o)
|
||||
OUTPUT_MODE="file"
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--http)
|
||||
OUTPUT_MODE="http"
|
||||
shift
|
||||
;;
|
||||
--port)
|
||||
PORT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--timeout)
|
||||
TIMEOUT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--verbose|-v)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
--quiet|-q)
|
||||
QUIET=true
|
||||
shift
|
||||
;;
|
||||
--handle-request)
|
||||
handle_request
|
||||
exit 0
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
TARGET_DIRECTORIES+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate directories
|
||||
if [[ ${#TARGET_DIRECTORIES[@]} -eq 0 ]]; then
|
||||
echo "Error: at least one directory argument is required" >&2
|
||||
echo "Run with --help for usage" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for dir in "${TARGET_DIRECTORIES[@]}"; do
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
echo "Error: directory does not exist: $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -r "$dir" ]]; then
|
||||
echo "Error: directory is not readable: $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Output ──────────────────────────────────────────────────────────
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log_info "DRY RUN — metrics that would be written:"
|
||||
collect_metrics
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$OUTPUT_MODE" in
|
||||
stdout)
|
||||
collect_metrics
|
||||
;;
|
||||
textfile)
|
||||
output_dir="/var/lib/node_exporter"
|
||||
OUTPUT_FILE="${output_dir}/${EXPORTER_NAME}.prom"
|
||||
mkdir -p "$output_dir"
|
||||
temp_file=$(mktemp "${output_dir}/.${EXPORTER_NAME}.XXXXXX")
|
||||
collect_metrics > "$temp_file"
|
||||
chmod 644 "$temp_file"
|
||||
mv -f "$temp_file" "$OUTPUT_FILE"
|
||||
;;
|
||||
file)
|
||||
temp_file=$(mktemp "${OUTPUT_FILE}.XXXXXX")
|
||||
collect_metrics > "$temp_file"
|
||||
chmod 644 "$temp_file"
|
||||
mv -f "$temp_file" "$OUTPUT_FILE"
|
||||
;;
|
||||
http)
|
||||
if ! command -v socat &>/dev/null; then
|
||||
echo "ERROR: socat is required for --http mode" >&2
|
||||
echo "Install it: apt install socat or dnf install socat" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${EXPORTER_NAME} listening on port ${PORT}..."
|
||||
echo "Monitoring directories: ${TARGET_DIRECTORIES[*]}"
|
||||
socat TCP-LISTEN:"$PORT",reuseaddr,fork EXEC:"$0 --handle-request"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user