mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 16:52:52 +08:00
284 lines
9.0 KiB
Bash
Executable File
284 lines
9.0 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck shell=ash
|
|
|
|
# Variables are populated by sourced SafeShield helpers and ss_load_config().
|
|
# shellcheck disable=SC2154
|
|
LC_ALL=C
|
|
export LC_ALL
|
|
umask 077
|
|
|
|
SS_STATSD_FUNCTIONS_LIB="${SS_STATSD_FUNCTIONS_LIB:-/lib/functions.sh}"
|
|
SS_STATSD_CORE_LIB="${SS_STATSD_CORE_LIB:-/usr/lib/safeshield/core.sh}"
|
|
SS_STATSD_AWK_DIR="${SS_STATSD_AWK_DIR:-/usr/lib/safeshield/statistics}"
|
|
SS_STATSD_POLL_COMMAND="${SS_STATSD_POLL_COMMAND:-/usr/libexec/safeshield-stats-poll}"
|
|
SS_STATSD_IDENTITY_COMMAND="${SS_STATSD_IDENTITY_COMMAND:-/usr/libexec/safeshield-stats-identities}"
|
|
SS_STATSD_PERSISTENT_STATE_FILE="${SS_STATSD_PERSISTENT_STATE_FILE:-/etc/safeshield/statistics-state.tsv}"
|
|
SS_STATSD_PERSISTENT_JOURNAL_FILE="${SS_STATSD_PERSISTENT_JOURNAL_FILE:-/etc/safeshield/statistics-journal.tsv}"
|
|
SS_STATSD_SEQUENCE_FILE="${SS_STATSD_SEQUENCE_FILE:-/etc/safeshield/statistics-sequence.tsv}"
|
|
SS_STATSD_SEQUENCE_RESERVATION_SIZE="${SS_STATSD_SEQUENCE_RESERVATION_SIZE:-4096}"
|
|
SS_STATSD_UPLOAD_WINDOW_HOURS="${SS_STATSD_UPLOAD_WINDOW_HOURS:-2}"
|
|
SS_STATSD_PERSISTENT_INTERVAL_S="${SS_STATSD_PERSISTENT_INTERVAL_S:-3600}"
|
|
SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S="${SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S:-604800}"
|
|
|
|
# shellcheck disable=SC1090
|
|
. "$SS_STATSD_FUNCTIONS_LIB"
|
|
# shellcheck disable=SC1090
|
|
. "$SS_STATSD_CORE_LIB"
|
|
|
|
stats_poller_pid=''
|
|
stats_awk_pid=''
|
|
stats_fifo=''
|
|
stats_lock_dir=''
|
|
stats_lock_owned=0
|
|
|
|
stats_new_generation_id() {
|
|
local generation_id="${SS_STATSD_GENERATION_ID:-}"
|
|
|
|
if [ -n "$generation_id" ]; then
|
|
printf '%s\n' "$generation_id"
|
|
return 0
|
|
fi
|
|
|
|
if [ -r /proc/sys/kernel/random/uuid ]; then
|
|
IFS= read -r generation_id </proc/sys/kernel/random/uuid || generation_id=''
|
|
fi
|
|
|
|
if [ -z "$generation_id" ]; then
|
|
generation_id="fallback-$(date +%s 2>/dev/null || printf '0')-$$"
|
|
fi
|
|
|
|
printf '%s\n' "$generation_id"
|
|
}
|
|
|
|
stats_process_running() {
|
|
local pid="${1:-}"
|
|
[ -n "$pid" ] || return 1
|
|
kill -0 "$pid" 2>/dev/null
|
|
}
|
|
|
|
stats_cleanup() {
|
|
trap - EXIT HUP INT TERM
|
|
|
|
# Stop the low-frequency UBus poller first. awk then receives EOF and runs
|
|
# its END block, which performs the final snapshot and persistence flush.
|
|
if stats_process_running "$stats_poller_pid"; then
|
|
kill "$stats_poller_pid" 2>/dev/null || true
|
|
fi
|
|
|
|
[ -n "$stats_poller_pid" ] && wait "$stats_poller_pid" 2>/dev/null || true
|
|
stats_poller_pid=''
|
|
|
|
[ -n "$stats_awk_pid" ] && wait "$stats_awk_pid" 2>/dev/null || true
|
|
stats_awk_pid=''
|
|
|
|
[ -n "$stats_fifo" ] && rm -f "$stats_fifo"
|
|
|
|
if [ "$stats_lock_owned" -eq 1 ] && [ -n "$stats_lock_dir" ]; then
|
|
rm -rf "$stats_lock_dir"
|
|
fi
|
|
}
|
|
|
|
stats_terminate() {
|
|
local rc="${1:-0}"
|
|
stats_cleanup
|
|
exit "$rc"
|
|
}
|
|
|
|
stats_acquire_lock() {
|
|
local owner_pid=''
|
|
|
|
stats_lock_dir="${SS_STATISTICS_DIR}/collector.lock"
|
|
|
|
if mkdir "$stats_lock_dir" 2>/dev/null; then
|
|
stats_lock_owned=1
|
|
printf '%s\n' "$$" >"${stats_lock_dir}/pid"
|
|
return 0
|
|
fi
|
|
|
|
if [ -r "${stats_lock_dir}/pid" ]; then
|
|
owner_pid="$(cat "${stats_lock_dir}/pid" 2>/dev/null || true)"
|
|
fi
|
|
|
|
if stats_process_running "$owner_pid"; then
|
|
log_warn "Statistics collector is already running (pid=${owner_pid})"
|
|
return 1
|
|
fi
|
|
|
|
log_warn 'Removing stale statistics collector lock'
|
|
rm -rf "$stats_lock_dir" || return 1
|
|
mkdir "$stats_lock_dir" || return 1
|
|
stats_lock_owned=1
|
|
printf '%s\n' "$$" >"${stats_lock_dir}/pid"
|
|
return 0
|
|
}
|
|
|
|
stats_poll_loop() {
|
|
local snapshot_interval="$1"
|
|
local poll_command="$2"
|
|
local sleep_pid=''
|
|
|
|
trap 'if [ -n "$sleep_pid" ]; then kill "$sleep_pid" 2>/dev/null || true; fi; exit 0' HUP INT TERM
|
|
|
|
while :; do
|
|
if ! "$poll_command"; then
|
|
printf 'error\tdnsmasq_ubus_poll_failed\n'
|
|
fi
|
|
|
|
sleep "$snapshot_interval" &
|
|
sleep_pid=$!
|
|
wait "$sleep_pid" 2>/dev/null || true
|
|
sleep_pid=''
|
|
done
|
|
}
|
|
|
|
stats_run_collector() {
|
|
local snapshot_interval="$1"
|
|
local retention_hours="$2"
|
|
local lease_file="$3"
|
|
local persistent_state_file="$4"
|
|
local persistent_journal_file="$5"
|
|
local sequence_file="$6"
|
|
local generation_id ipv6_neigh_command='' ipv6_identity_command='' ipv6_identity_file="${SS_STATSD_IPV6_IDENTITY_FILE:-}" force_rebaseline=0 rc=0
|
|
|
|
if [ -f "${SS_STATISTICS_REBASELINE_FILE}" ]; then
|
|
force_rebaseline=1
|
|
fi
|
|
|
|
# This is only a candidate for a brand-new dataset. The collector modules keep
|
|
# the generation restored from tmpfs or persistent state when one already exists.
|
|
generation_id="$(stats_new_generation_id)"
|
|
if command_exists ip; then
|
|
ipv6_neigh_command='ip -6 neigh show 2>/dev/null'
|
|
fi
|
|
if [ -z "$ipv6_identity_file" ] && [ -x "$SS_STATSD_IDENTITY_COMMAND" ]; then
|
|
ipv6_identity_command="$SS_STATSD_IDENTITY_COMMAND 2>/dev/null"
|
|
fi
|
|
|
|
stats_fifo="${SS_STATISTICS_DIR}/events.$$"
|
|
rm -f "$stats_fifo"
|
|
mkfifo "$stats_fifo" || {
|
|
log_error "Failed to create statistics FIFO: ${stats_fifo}"
|
|
return 1
|
|
}
|
|
|
|
stats_poll_loop "$snapshot_interval" "$SS_STATSD_POLL_COMMAND" >"$stats_fifo" &
|
|
stats_poller_pid=$!
|
|
|
|
awk \
|
|
-v state_file="${SS_STATISTICS_STATE_FILE}" \
|
|
-v json_file="${SS_STATISTICS_JSON_FILE}" \
|
|
-v upload_json_file="${SS_STATISTICS_UPLOAD_JSON_FILE}" \
|
|
-v upload_window_hours="${SS_STATSD_UPLOAD_WINDOW_HOURS}" \
|
|
-v persistent_state_file="${persistent_state_file}" \
|
|
-v persistent_journal_file="${persistent_journal_file}" \
|
|
-v sequence_file="${sequence_file}" \
|
|
-v snapshot_seq_reservation_size="${SS_STATSD_SEQUENCE_RESERVATION_SIZE}" \
|
|
-v persistent_interval="${SS_STATSD_PERSISTENT_INTERVAL_S}" \
|
|
-v persistent_compact_interval="${SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S}" \
|
|
-v snapshot_interval="${snapshot_interval}" \
|
|
-v retention_hours="${retention_hours}" \
|
|
-v lease_file="${lease_file}" \
|
|
-v ipv6_neigh_command="${ipv6_neigh_command}" \
|
|
-v ipv6_identity_file="${ipv6_identity_file}" \
|
|
-v ipv6_identity_command="${ipv6_identity_command}" \
|
|
-v lease_refresh_interval=60 \
|
|
-v identity_cache_ttl=60 \
|
|
-v max_devices=128 \
|
|
-v generation_seed="${generation_id}" \
|
|
-v force_rebaseline="${force_rebaseline}" \
|
|
-v rebaseline_file="${SS_STATISTICS_REBASELINE_FILE}" \
|
|
-f "$SS_STATSD_AWK_DIR/00-common.awk" \
|
|
-f "$SS_STATSD_AWK_DIR/10-recovery.awk" \
|
|
-f "$SS_STATSD_AWK_DIR/20-identity.awk" \
|
|
-f "$SS_STATSD_AWK_DIR/30-aggregate.awk" \
|
|
-f "$SS_STATSD_AWK_DIR/40-persistence.awk" \
|
|
-f "$SS_STATSD_AWK_DIR/50-output.awk" \
|
|
-f "$SS_STATSD_AWK_DIR/90-main.awk" \
|
|
<"$stats_fifo" &
|
|
stats_awk_pid=$!
|
|
|
|
wait "$stats_awk_pid" || rc=$?
|
|
stats_awk_pid=''
|
|
|
|
if stats_process_running "$stats_poller_pid"; then
|
|
kill "$stats_poller_pid" 2>/dev/null || true
|
|
fi
|
|
wait "$stats_poller_pid" 2>/dev/null || true
|
|
stats_poller_pid=''
|
|
|
|
return "$rc"
|
|
}
|
|
|
|
main() {
|
|
local snapshot_interval configured_snapshot_interval retention_hours lease_file configured_lease_file
|
|
local persistent_state_file persistent_journal_file sequence_file persistence_mode rc=0
|
|
|
|
ss_load_config || return 1
|
|
[ "${ss_enabled}" = "1" ] || return 0
|
|
[ "${ss_statistics_enabled}" = "1" ] || return 0
|
|
|
|
if ! ss_dnsmasq_safeshield_block_supported; then
|
|
log_warn 'SafeShield statistics collector is disabled because dnsmasq does not provide the SafeShield extension'
|
|
return 0
|
|
fi
|
|
|
|
[ -x "$SS_STATSD_POLL_COMMAND" ] || {
|
|
log_error "SafeShield statistics poll command is unavailable: ${SS_STATSD_POLL_COMMAND}"
|
|
return 1
|
|
}
|
|
command_exists mkfifo || {
|
|
log_error 'mkfifo is required for SafeShield statistics'
|
|
return 1
|
|
}
|
|
|
|
persistent_state_file="${SS_STATSD_PERSISTENT_STATE_FILE}"
|
|
persistent_journal_file="${SS_STATSD_PERSISTENT_JOURNAL_FILE}"
|
|
sequence_file="${SS_STATSD_SEQUENCE_FILE}"
|
|
persistence_mode='journal'
|
|
if ! ss_statistics_persistence_enabled; then
|
|
persistent_state_file=''
|
|
persistent_journal_file=''
|
|
sequence_file=''
|
|
persistence_mode='volatile'
|
|
fi
|
|
|
|
mkdir -p "${SS_STATISTICS_DIR}" || return 1
|
|
if [ -n "$persistent_state_file" ]; then
|
|
mkdir -p "${persistent_state_file%/*}" || return 1
|
|
fi
|
|
if [ -n "$persistent_journal_file" ]; then
|
|
mkdir -p "${persistent_journal_file%/*}" || return 1
|
|
fi
|
|
if [ -n "$sequence_file" ]; then
|
|
mkdir -p "${sequence_file%/*}" || return 1
|
|
fi
|
|
stats_acquire_lock || return 0
|
|
|
|
trap 'stats_cleanup' EXIT
|
|
trap 'stats_terminate 129' HUP
|
|
trap 'stats_terminate 130' INT
|
|
trap 'stats_terminate 143' TERM
|
|
|
|
configured_snapshot_interval="${ss_statistics_snapshot_interval_s}"
|
|
snapshot_interval="$(ss_statistics_effective_snapshot_interval "$configured_snapshot_interval")"
|
|
retention_hours="${ss_statistics_retention_hours}"
|
|
lease_file="${SS_STATSD_DHCP_LEASE_FILE:-/tmp/dhcp.leases}"
|
|
if [ -z "${SS_STATSD_DHCP_LEASE_FILE:-}" ] && command_exists uci; then
|
|
configured_lease_file="$(uci -q get dhcp.@dnsmasq[0].leasefile 2>/dev/null || true)"
|
|
[ -n "$configured_lease_file" ] && lease_file="$configured_lease_file"
|
|
fi
|
|
|
|
log_info "Starting statistics collector (source=dnsmasq-ubus snapshot=${snapshot_interval}s configured_snapshot=${configured_snapshot_interval}s retention=${retention_hours}h persistence=${persistence_mode} journal=${SS_STATSD_PERSISTENT_INTERVAL_S}s compact=${SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S}s)"
|
|
|
|
stats_run_collector \
|
|
"$snapshot_interval" \
|
|
"$retention_hours" \
|
|
"$lease_file" \
|
|
"$persistent_state_file" \
|
|
"$persistent_journal_file" \
|
|
"$sequence_file" || rc=$?
|
|
return "$rc"
|
|
}
|
|
|
|
main "$@"
|