Files
linux-scripts/speedtest-metrics.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

654 lines
24 KiB
Bash
Executable File

#!/bin/bash
################################################################################
# Script Name: speedtest-metrics.sh
# Description: Prometheus exporter for internet and LAN speed metrics
#
# Collects internet speed tests via Ookla Speedtest CLI against multiple
# servers and local network tests via iperf3. Outputs Prometheus-formatted
# metrics with per-server labels.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
# Version: 3.0
#
# Usage:
# # Output to stdout
# ./speedtest-metrics.sh
#
# # Textfile collector mode (atomic write)
# ./speedtest-metrics.sh --textfile
#
# # Custom output file
# ./speedtest-metrics.sh -o /path/to/metrics.prom
#
# # HTTP server mode
# ./speedtest-metrics.sh --http
#
################################################################################
# ============================================================================
# CONFIGURATION VARIABLES
# ============================================================================
LISTEN_PORT="${SPEEDTEST_EXPORTER_PORT:-9196}"
TEXTFILE_DIR="/var/lib/node_exporter"
OUTPUT_FILE=""
HTTP_MODE=false
HOSTNAME=$(hostname)
# ============================================================================
# ARGUMENT PARSING
# ============================================================================
show_help() {
cat <<EOF
Speedtest Metrics Exporter for Prometheus
Usage: $0 [OPTIONS]
MODES:
--textfile Write to node_exporter textfile collector
--http Run HTTP server on port $LISTEN_PORT
(no flag) Output to stdout (default)
OPTIONS:
-p, --port PORT HTTP port (default: 9196)
-o, --output PATH Output file path
--help Show this help
ENVIRONMENT VARIABLES:
SPEEDTEST_SERVERS Comma-separated Ookla server IDs or "auto" (default: auto)
IPERF_SERVER Local iperf3 server IP (default: 192.168.1.100)
IPERF_PORT iperf3 port (default: 9182)
SPEEDTEST_EXPORTER_PORT Same as --port
EXAMPLES:
$0 # One-shot to stdout
$0 --textfile # Write to textfile collector
$0 --http --port 9196 # Run HTTP server
$0 -o /tmp/speedtest.prom # Write to custom file
EOF
}
handle_request() {
local request
read -r request || true
local path
path=$(echo "$request" | awk '{print $2}')
case "$path" in
/metrics)
local metrics
metrics=$(collect_metrics)
local content_length=${#metrics}
printf "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4; charset=utf-8\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" "$content_length" "$metrics"
;;
/)
local body="<html><body><h1>Speedtest Metrics Exporter</h1><p><a href='/metrics'>Metrics</a></p></body></html>"
local content_length=${#body}
printf "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" "$content_length" "$body"
;;
*)
printf "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\nConnection: close\r\n\r\nNot Found"
;;
esac
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--textfile)
OUTPUT_FILE="$TEXTFILE_DIR/speedtest.prom"
shift
;;
--http)
HTTP_MODE=true
shift
;;
--port|-p)
LISTEN_PORT="$2"
HTTP_MODE=true
shift 2
;;
--output|-o)
OUTPUT_FILE="$2"
shift 2
;;
--handle-request)
handle_request
exit 0
;;
--help|-h)
show_help
exit 0
;;
*)
echo "Unknown option: $1" >&2
show_help >&2
exit 1
;;
esac
done
}
parse_args "$@"
# ============================================================================
# METRICS COLLECTION
# ============================================================================
TEMP_FILE="/tmp/speedtest_$$"
cleanup() {
rm -f "$TEMP_FILE"
}
trap cleanup EXIT
collect_metrics() {
# Configuration
IPERF_SERVER="${IPERF_SERVER:-192.168.1.100}" # Set to your local iperf3 server IP
IPERF_PORT="${IPERF_PORT:-9182}" # iperf3 port
# Multiple speedtest servers - add/remove server IDs as needed
# Common server IDs for major cities:
# Dallas/DFW: 5029 (AT&T), 12190 (Spectrum), 26847 (Verizon)
# New York: 3737 (Verizon), 11570 (Optimum), 17395 (Spectrum)
SPEEDTEST_SERVERS="${SPEEDTEST_SERVERS:-auto}" # Comma-separated server IDs or "auto"
# Record script start time
SCRIPT_START_TIME=$(date +%s.%N)
# Internet Speed Test - Multiple Servers
echo "# Running internet speedtest on multiple servers..." >&2
# Initialize arrays to store results for all servers
declare -a SERVER_IDS=()
declare -a PING_LATENCIES=()
declare -a PING_JITTERS=()
declare -a PING_LOWS=()
declare -a PING_HIGHS=()
declare -a DOWNLOAD_MBPS=()
declare -a UPLOAD_MBPS=()
declare -a PACKET_LOSSES=()
declare -a EXTERNAL_IPS=()
declare -a TEST_TIMESTAMPS=()
declare -a SERVER_NAMES=()
declare -a SERVER_LOCATIONS=()
declare -a SERVER_COUNTRIES=()
declare -a ISPS=()
declare -a RESULT_URLS=()
declare -a DOWNLOAD_SIZES=()
declare -a UPLOAD_SIZES=()
declare -a SUCCESSES=()
# Convert comma-separated servers to array
IFS=',' read -ra SERVERS <<< "$SPEEDTEST_SERVERS"
# Test each server
for server_id in "${SERVERS[@]}"; do
server_id=$(echo "$server_id" | xargs) # Trim whitespace
echo "# Testing server $server_id..." >&2
TEMP_SERVER_FILE="/tmp/speedtest_${server_id}_$$"
# Handle auto server selection vs specific server ID
if [[ "$server_id" == "auto" ]]; then
speedtest_cmd="speedtest --format=json"
else
speedtest_cmd="speedtest -s $server_id --format=json"
fi
if $speedtest_cmd --accept-license --accept-gdpr > "$TEMP_SERVER_FILE" 2>/dev/null; then
echo "# Server $server_id: SUCCESS" >&2
# Parse results for this server
ping_latency=$(jq -r '.ping.latency // "0"' "$TEMP_SERVER_FILE")
ping_jitter=$(jq -r '.ping.jitter // "0"' "$TEMP_SERVER_FILE")
ping_low=$(jq -r '.ping.low // "0"' "$TEMP_SERVER_FILE")
ping_high=$(jq -r '.ping.high // "0"' "$TEMP_SERVER_FILE")
download_bandwidth=$(jq -r '.download.bandwidth // "0"' "$TEMP_SERVER_FILE")
upload_bandwidth=$(jq -r '.upload.bandwidth // "0"' "$TEMP_SERVER_FILE")
packet_loss=$(jq -r '.packetLoss // "0"' "$TEMP_SERVER_FILE")
external_ip=$(jq -r '.interface.externalIp // "unknown"' "$TEMP_SERVER_FILE")
# Handle timestamp conversion
test_timestamp_raw=$(jq -r '.timestamp // "0"' "$TEMP_SERVER_FILE")
if [[ "$test_timestamp_raw" != "0" ]] && [[ "$test_timestamp_raw" != "unknown" ]]; then
test_timestamp=$(date -d "$test_timestamp_raw" +%s 2>/dev/null || echo "0")
else
test_timestamp=0
fi
server_name=$(jq -r '.server.name // "unknown"' "$TEMP_SERVER_FILE")
server_location=$(jq -r '.server.location // "unknown"' "$TEMP_SERVER_FILE")
server_country=$(jq -r '.server.country // "unknown"' "$TEMP_SERVER_FILE")
isp=$(jq -r '.isp // "unknown"' "$TEMP_SERVER_FILE")
result_url=$(jq -r '.result.url // "unknown"' "$TEMP_SERVER_FILE")
download_size=$(jq -r '.download.bytes // "0"' "$TEMP_SERVER_FILE")
upload_size=$(jq -r '.upload.bytes // "0"' "$TEMP_SERVER_FILE")
# Convert from bits to Mbps (fallback to awk if bc unavailable)
download_mbps=$(echo "scale=2; $download_bandwidth / 125000" | bc -l 2>/dev/null || echo "$download_bandwidth" | awk '{printf "%.2f", $1/125000}')
upload_mbps=$(echo "scale=2; $upload_bandwidth / 125000" | bc -l 2>/dev/null || echo "$upload_bandwidth" | awk '{printf "%.2f", $1/125000}')
success=1
else
echo "# Server $server_id: FAILED" >&2
# Set default values for failed test
ping_latency=0; ping_jitter=0; ping_low=0; ping_high=0
download_mbps=0; upload_mbps=0; packet_loss=0
external_ip="unknown"; test_timestamp=0; server_name="unknown"
server_location="unknown"; server_country="unknown"; isp="unknown"
result_url="unknown"; download_size=0; upload_size=0
success=0
fi
# Store results in arrays
SERVER_IDS+=("$server_id")
PING_LATENCIES+=("$ping_latency")
PING_JITTERS+=("$ping_jitter")
PING_LOWS+=("$ping_low")
PING_HIGHS+=("$ping_high")
DOWNLOAD_MBPS+=("$download_mbps")
UPLOAD_MBPS+=("$upload_mbps")
PACKET_LOSSES+=("$packet_loss")
EXTERNAL_IPS+=("$external_ip")
TEST_TIMESTAMPS+=("$test_timestamp")
SERVER_NAMES+=("$server_name")
SERVER_LOCATIONS+=("$server_location")
SERVER_COUNTRIES+=("$server_country")
ISPS+=("$isp")
RESULT_URLS+=("$result_url")
DOWNLOAD_SIZES+=("$download_size")
UPLOAD_SIZES+=("$upload_size")
SUCCESSES+=("$success")
# Cleanup temp file
rm -f "$TEMP_SERVER_FILE"
done
# Local Network Speed Test (iperf3) - Enhanced with additional metrics
echo "# Testing local network speed..." >&2
if command -v iperf3 >/dev/null 2>&1; then
# Test download from local server (we are client)
if local_down=$(timeout 10 iperf3 -c "$IPERF_SERVER" -p "$IPERF_PORT" -t 5 -J 2>/dev/null); then
local_download_mbps=$(echo "$local_down" | jq -r '.end.sum_received.bits_per_second // "0"' | awk '{printf "%.2f", $1/1000000}')
local_download_bytes=$(echo "$local_down" | jq -r '.end.sum_received.bytes // "0"')
local_download_retransmits=$(echo "$local_down" | jq -r '.end.sum_sent.retransmits // "0"')
local_download_rtt=$(echo "$local_down" | jq -r '.end.streams[0].sender.mean_rtt // "0"' | awk '{printf "%.3f", $1/1000}') # Convert to ms
local_download_rtt_var=$(echo "$local_down" | jq -r '.end.streams[0].sender.rtt_variance // "0"' | awk '{printf "%.3f", $1/1000}')
local_download_cpu_local=$(echo "$local_down" | jq -r '.end.cpu_utilization_percent.host_total // "0"')
local_download_cpu_remote=$(echo "$local_down" | jq -r '.end.cpu_utilization_percent.remote_total // "0"')
local_download_congestion_window=$(echo "$local_down" | jq -r '.end.streams[0].sender.max_snd_cwnd // "0"')
local_download_success=1
else
local_download_mbps=0; local_download_bytes=0; local_download_retransmits=0
local_download_rtt=0; local_download_rtt_var=0; local_download_cpu_local=0
local_download_cpu_remote=0; local_download_congestion_window=0; local_download_success=0
fi
# Test upload to local server (we are client, reverse mode)
if local_up=$(timeout 10 iperf3 -c "$IPERF_SERVER" -p "$IPERF_PORT" -t 5 -R -J 2>/dev/null); then
local_upload_mbps=$(echo "$local_up" | jq -r '.end.sum_sent.bits_per_second // "0"' | awk '{printf "%.2f", $1/1000000}')
local_upload_bytes=$(echo "$local_up" | jq -r '.end.sum_sent.bytes // "0"')
local_upload_retransmits=$(echo "$local_up" | jq -r '.end.sum_received.retransmits // "0"')
local_upload_rtt=$(echo "$local_up" | jq -r '.end.streams[0].receiver.mean_rtt // "0"' | awk '{printf "%.3f", $1/1000}')
local_upload_rtt_var=$(echo "$local_up" | jq -r '.end.streams[0].receiver.rtt_variance // "0"' | awk '{printf "%.3f", $1/1000}')
local_upload_cpu_local=$(echo "$local_up" | jq -r '.end.cpu_utilization_percent.host_total // "0"')
local_upload_cpu_remote=$(echo "$local_up" | jq -r '.end.cpu_utilization_percent.remote_total // "0"')
local_upload_congestion_window=$(echo "$local_up" | jq -r '.end.streams[0].receiver.max_snd_cwnd // "0"')
local_upload_success=1
else
local_upload_mbps=0; local_upload_bytes=0; local_upload_retransmits=0
local_upload_rtt=0; local_upload_rtt_var=0; local_upload_cpu_local=0
local_upload_cpu_remote=0; local_upload_congestion_window=0; local_upload_success=0
fi
else
echo "# iperf3 not installed, skipping local network test" >&2
local_download_mbps=0; local_upload_mbps=0; local_download_bytes=0; local_upload_bytes=0
local_download_retransmits=0; local_upload_retransmits=0; local_download_rtt=0; local_upload_rtt=0
local_download_rtt_var=0; local_upload_rtt_var=0; local_download_cpu_local=0; local_upload_cpu_local=0
local_download_cpu_remote=0; local_upload_cpu_remote=0; local_download_congestion_window=0; local_upload_congestion_window=0
local_download_success=0; local_upload_success=0
fi
# Calculate script runtime
SCRIPT_END_TIME=$(date +%s.%N)
SCRIPT_RUNTIME=$(echo "$SCRIPT_END_TIME - $SCRIPT_START_TIME" | bc -l 2>/dev/null || echo "$SCRIPT_END_TIME $SCRIPT_START_TIME" | awk '{printf "%.3f", $1-$2}')
# Output Prometheus metrics
cat <<EOF
# HELP internet_speedtest_latency Internet connection latency in milliseconds
# TYPE internet_speedtest_latency gauge
EOF
# Generate metrics for each server
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
isp="${ISPS[$i]}"
external_ip="${EXTERNAL_IPS[$i]}"
result_url="${RESULT_URLS[$i]}"
cat <<EOF
internet_speedtest_latency{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${PING_LATENCIES[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_latency_low Internet connection minimum latency in milliseconds
# TYPE internet_speedtest_latency_low gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_latency_low{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${PING_LOWS[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_latency_high Internet connection maximum latency in milliseconds
# TYPE internet_speedtest_latency_high gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_latency_high{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${PING_HIGHS[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_jitter Internet connection jitter in milliseconds
# TYPE internet_speedtest_jitter gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_jitter{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${PING_JITTERS[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_download Internet download speed in Mbps
# TYPE internet_speedtest_download gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_download{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${DOWNLOAD_MBPS[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_download_size Internet download test data size in bytes
# TYPE internet_speedtest_download_size gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_download_size{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${DOWNLOAD_SIZES[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_upload Internet upload speed in Mbps
# TYPE internet_speedtest_upload gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_upload{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${UPLOAD_MBPS[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_upload_size Internet upload test data size in bytes
# TYPE internet_speedtest_upload_size gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_upload_size{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${UPLOAD_SIZES[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_packet_loss Internet connection packet loss percentage
# TYPE internet_speedtest_packet_loss gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_packet_loss{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${PACKET_LOSSES[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_timestamp Unix timestamp when test was performed
# TYPE internet_speedtest_timestamp gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_timestamp{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${TEST_TIMESTAMPS[$i]}
EOF
done
cat <<EOF
# HELP internet_speedtest_info Internet speedtest metadata
# TYPE internet_speedtest_info gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
external_ip="${EXTERNAL_IPS[$i]}"
isp="${ISPS[$i]}"
result_url="${RESULT_URLS[$i]}"
cat <<EOF
internet_speedtest_info{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country",external_ip="$external_ip",isp="$isp",result_url="$result_url"} 1
EOF
done
cat <<EOF
# HELP internet_speedtest_success Whether internet speedtest succeeded (1=success, 0=failed)
# TYPE internet_speedtest_success gauge
EOF
for i in "${!SERVER_IDS[@]}"; do
server_id="${SERVER_IDS[$i]}"
server_name="${SERVER_NAMES[$i]}"
server_location="${SERVER_LOCATIONS[$i]}"
server_country="${SERVER_COUNTRIES[$i]}"
cat <<EOF
internet_speedtest_success{hostname="$HOSTNAME",server_id="$server_id",server_name="$server_name",server_location="$server_location",server_country="$server_country"} ${SUCCESSES[$i]}
EOF
done
cat <<EOF
# HELP local_network_download Local network download speed in Mbps
# TYPE local_network_download gauge
local_network_download{hostname="$HOSTNAME"} $local_download_mbps
# HELP local_network_upload Local network upload speed in Mbps
# TYPE local_network_upload gauge
local_network_upload{hostname="$HOSTNAME"} $local_upload_mbps
# HELP local_network_download_bytes Total bytes downloaded in local network test
# TYPE local_network_download_bytes gauge
local_network_download_bytes{hostname="$HOSTNAME"} $local_download_bytes
# HELP local_network_upload_bytes Total bytes uploaded in local network test
# TYPE local_network_upload_bytes gauge
local_network_upload_bytes{hostname="$HOSTNAME"} $local_upload_bytes
# HELP local_network_download_retransmits TCP retransmissions during download test
# TYPE local_network_download_retransmits gauge
local_network_download_retransmits{hostname="$HOSTNAME"} $local_download_retransmits
# HELP local_network_upload_retransmits TCP retransmissions during upload test
# TYPE local_network_upload_retransmits gauge
local_network_upload_retransmits{hostname="$HOSTNAME"} $local_upload_retransmits
# HELP local_network_download_rtt Mean round-trip time during download test in milliseconds
# TYPE local_network_download_rtt gauge
local_network_download_rtt{hostname="$HOSTNAME"} $local_download_rtt
# HELP local_network_upload_rtt Mean round-trip time during upload test in milliseconds
# TYPE local_network_upload_rtt gauge
local_network_upload_rtt{hostname="$HOSTNAME"} $local_upload_rtt
# HELP local_network_download_rtt_variance RTT variance during download test in milliseconds
# TYPE local_network_download_rtt_variance gauge
local_network_download_rtt_variance{hostname="$HOSTNAME"} $local_download_rtt_var
# HELP local_network_upload_rtt_variance RTT variance during upload test in milliseconds
# TYPE local_network_upload_rtt_variance gauge
local_network_upload_rtt_variance{hostname="$HOSTNAME"} $local_upload_rtt_var
# HELP local_network_download_cpu_local Local CPU utilization during download test (percentage)
# TYPE local_network_download_cpu_local gauge
local_network_download_cpu_local{hostname="$HOSTNAME"} $local_download_cpu_local
# HELP local_network_upload_cpu_local Local CPU utilization during upload test (percentage)
# TYPE local_network_upload_cpu_local gauge
local_network_upload_cpu_local{hostname="$HOSTNAME"} $local_upload_cpu_local
# HELP local_network_download_cpu_remote Remote CPU utilization during download test (percentage)
# TYPE local_network_download_cpu_remote gauge
local_network_download_cpu_remote{hostname="$HOSTNAME"} $local_download_cpu_remote
# HELP local_network_upload_cpu_remote Remote CPU utilization during upload test (percentage)
# TYPE local_network_upload_cpu_remote gauge
local_network_upload_cpu_remote{hostname="$HOSTNAME"} $local_upload_cpu_remote
# HELP local_network_download_congestion_window Maximum TCP congestion window size during download test in bytes
# TYPE local_network_download_congestion_window gauge
local_network_download_congestion_window{hostname="$HOSTNAME"} $local_download_congestion_window
# HELP local_network_upload_congestion_window Maximum TCP congestion window size during upload test in bytes
# TYPE local_network_upload_congestion_window gauge
local_network_upload_congestion_window{hostname="$HOSTNAME"} $local_upload_congestion_window
# HELP local_network_test_success Whether local network test succeeded (1=success, 0=failed)
# TYPE local_network_test_success gauge
local_network_test_success{hostname="$HOSTNAME"} $((local_download_success && local_upload_success))
# HELP speedtest_script_runtime_seconds Total script execution time in seconds
# TYPE speedtest_script_runtime_seconds gauge
speedtest_script_runtime_seconds{hostname="$HOSTNAME"} $SCRIPT_RUNTIME
EOF
}
# ============================================================================
# OUTPUT HANDLING
# ============================================================================
write_output() {
local metrics
metrics=$(collect_metrics)
if [[ -n "$OUTPUT_FILE" ]]; then
mkdir -p "$(dirname "$OUTPUT_FILE")"
local tmp_file="${OUTPUT_FILE}.$$"
echo "$metrics" > "$tmp_file"
mv "$tmp_file" "$OUTPUT_FILE"
echo "Metrics written to $OUTPUT_FILE" >&2
else
echo "$metrics"
fi
}
start_server() {
if ! command -v socat >/dev/null 2>&1; then
echo "socat is required for HTTP mode. Install it first." >&2
exit 1
fi
echo "Starting Speedtest Metrics Exporter on port $LISTEN_PORT" >&2
echo "Metrics available at http://localhost:$LISTEN_PORT/metrics" >&2
while true; do
socat TCP-LISTEN:"$LISTEN_PORT",reuseaddr,fork EXEC:"$0 --handle-request" 2>/dev/null || {
echo "Server error, restarting in 5 seconds..." >&2
sleep 5
}
done
}
# Main execution
if [[ "$HTTP_MODE" == true ]]; then
start_server
elif [[ -n "$OUTPUT_FILE" ]]; then
write_output
else
collect_metrics
fi