commit 2ad6788a51e5e9b4ce69b7078f96b23990c9b66f Author: Pascal Date: Tue Feb 16 18:33:47 2021 -0500 [add] initial commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..062aa27 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM debian:buster-slim + +ARG USER_ID=9999 +ARG GROUP_ID=9999 +ENV OUTPUT_PATH=/service/data + +RUN apt-get update \ + && apt-get install -y iputils-ping + +COPY ./files/inet_stat.sh /entrypoint.sh + +RUN mkdir -p $OUTPUT_PATH \ + && chown -R $USER_ID:$GROUP_ID $OUTPUT_PATH + +USER $USER_ID:$GROUP_ID + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..44ed57a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: '3.7' +services: + pinger: + build: + context: . + args: + - USER_ID=${EUID:-1000} + - GROUP_ID=${EUID:-1000} + environment: + - TZ=America/New_York + # file with server list + - SERVER_FILE=/service/servers.env + # list of servers (takes precedence over file) + #- SERVERS=127.0.0.1 + # 0.0.0.0 + # localhost + # use iso-8601 instead of epoch timestamp [default: -+%s] + #- DATE_FORMAT=-Iseconds + # set the cycle at which servers are pinged [default: 60s] + #- CYCLE=60s + # set amount of times servers are pinged on each cycle [default: 10] + #- PING_COUNT=10 + # set output file name [default: ping.csv] + #- OUTPUT_FILE=ping.csv + restart: always + image: pinger + container_name: pinger + volumes: + - $PWD/data:/service/data + - $PWD/servers.env:/service/servers.env diff --git a/files/inet_stat.sh b/files/inet_stat.sh new file mode 100755 index 0000000..aad6535 --- /dev/null +++ b/files/inet_stat.sh @@ -0,0 +1,134 @@ +#!/bin/bash +DATE_FORMAT=${DATE_FORMAT:-+%s} # +%s = epoch, -Iseconds = iso8601 +CYCLE=${CYCLE:-60s} +OUTPUT_FILE=${OUTPUT_FILE:-ping.csv} +PING_COUNT=${PING_COUNT:-10} +# directories are set relative from the scripts path +script_path=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd) +OUTPUT_PATH=${OUTPUT_PATH:-"$script_path/../data"} +SERVER_FILE=${SERVER_FILE:-"$script_path/../servers.env"} + +# helper functions +function log() +{ + local time=$(date +%F_%T) + local prefix="LOG" + case $1 in + ERR | ERROR) + prefix="ERR" + shift + ;; + INF | INFO) + prefix="INF" + shift + ;; + WAR | WARNING) + prefix="WAR" + shift + ;; + esac + local func=$1; shift + # remove any \n + #local text=$(echo "$@"| tr '\n' '#' | sed 's|#|\\n |g') + case "$log_file" in + "") + echo "[$time]:[$prefix]:[$0][$func]: $@" 2>&1 + ;; + *) + echo "[$time]:[$prefix]:[$0][$func]: $@" >> $log_file + ;; + esac +} +function sleep_wait() +{ + # waiting on sleep to be able to handle SIGTERM + sleep $1 & + wait $! +} + +function exit_handler() +{ + echo "stopping pinger" + exit +} + +trap "exit_handler" SIGINT +trap "exit_handler" SIGTERM + +# main functions + +# this function assumes the following ping output format: +## valid server +# --- 192.168.0.1 ping statistics --- +# 10 packets transmitted, 10 received, 0% packet loss, time 1806ms +# rtt min/avg/max/mdev = 0.278/0.361/1.010/0.216 ms +## valid server 100% package loss +# --- 10.0.0.2 ping statistics --- +# 10 packets transmitted, 0 received, 100% packet loss, time 1861ms +# [empty line] +## invalid server +# [no output] +function ping_server() +{ + local server="$1" + local packet_loss="" + + ifs_old=$IFS + IFS=$'\n' + ping_output=( $(ping $server -c$PING_COUNT -i0.2 -q 2>/dev/null| tail -n2) ) + IFS=$ifs_old + + packet_loss=$(echo "${ping_output[0]}" | grep -Eo "[.[:digit:]]{1,10}%") + satistic="$(echo "${ping_output[1]}" | cut -d' ' -f4 | sed -E 's/\//,/g')" + + # Name or service not known + if [ "${#packet_loss}" -eq 0 ]; then + packet_loss="-1" + fi + + # 100% package loss + if [ "${#satistic}" -eq 0 ]; then + satistic="-1,-1,-1,-1" + fi + + echo "$(date $DATE_FORMAT),$server,$packet_loss,$satistic" +} + +function load_server_file() +{ + local result="" + if [ -f "$SERVER_FILE" ]; then + local server_file="$(cat $SERVER_FILE)" + for server in ${server_file[@]}; do + result="$result $(echo "$server" | cut -f1 -d":")" + done + fi + echo "$result" +} + +main() +{ + log INFO main DO NOT PING SERVERS YOU DO NOT HAVE PERMISSION TO DO SO! + # Environment SERVERS > arguments $@ > file $SERVER_FILE + local servers=( ${SERVERS:-${@:-$(load_server_file)}} ) + local output_file="$OUTPUT_PATH/$OUTPUT_FILE" + log main "writing to $output_file" + while true; do + if [ ! -d $OUTPUT_PATH ]; then + mkdir -p $OUTPUT_PATH + fi + if [ ! -f $output_file ]; then + echo "Epoch,Server,PacketLoss,Min,Average,Max,Deviation" > $output_file + fi + log main "pinging ${servers[@]}" + for server in "${servers[@]}"; do + # run in subshell to catch SIGTERM + ping_server "$server" >> $output_file & + wait $! + done + log main "sleeping $CYCLE" + sleep_wait $CYCLE + done +} + +main "$@" diff --git a/plotter/pinger.gnuplot b/plotter/pinger.gnuplot new file mode 100644 index 0000000..a83ddc5 --- /dev/null +++ b/plotter/pinger.gnuplot @@ -0,0 +1,94 @@ +# config +csv_file="../data/ping.csv" +# space seperates list of SERVER:LABEL where SERVER is the filter for col_server +server_list=" \ + 127.0.0.1:my_pc \ + 0.0.0.0.:still_my_pc \ + localhost:my_other_local \ + example.com:example \ + " +# timezone offset in seconds +tz=0 + +# table setup +col_time=1 +col_server=2 +col_packet_loss=3 +col_min=4 +col_average=5 +col_max=6 +col_deviation=7 + +if(exists("TZ")) tz=TZ*3600 +# gnuplot does not like spaces in -e so we recreate them here via system call to sed +if(exists("SERVERS")) server_list=system("echo '".SERVERS."' | sed 's|;| |g'") +if(exists("CSV_FILE")) csv_file=CSV_FILE + +# helper function +server(n) = word(server_list ,n) +# csv settings +set datafile separator ',' + +# output settings +#set terminal pngcairo size 800,600 enhanced font 'Segoe UI,10' # pngcairo cleaner than png +set output 'ping.png' +set encoding utf8 + +# data settings +set xdata time # tells gnuplot the x axis is time data +set timefmt "%s" # for iso 8601 use: "%Y-%m-%dT%H:%M:%S%:z" +set format x "%d.%m.%y - %H:%M" +set key autotitle columnhead + +# styling +## legend +set key box lt rgb "#000000" +set key width 1.5 +set key height 0.5 +set key samplen 0.1 +set key center top +## axis +set y2tics # enable second axis +set ytics nomirror # dont show the tics on that side +set y2range [5:100] +if (exists("Y_MAX")) set yrange [*:Y_MAX] +if (exists("X_MAX")) set xrange [*:X_MAX+tz] +if (exists("X_MIN")) set xrange [X_MIN+tz:*] +if (exists("X_MIN") && exists("X_MAX")) set xrange [X_MIN+tz:X_MAX+tz] +#set format y2 "%s perc" +## labels +set xlabel sprintf("Time (UTC: %+03d:00)", tz/3600) +set ylabel "Latency (ms)" +set y2label "Package Loss (%)" # label for second axis +set xtics rotate # rotate labels on the x axis +## grid +set style line 100 lt 1 lc rgb "grey" lw 0.5 # linestyle for the grid +set grid ls 100 # enable grid with specific linestyle +## lines and points +set style line 1 lt rgb "#1e88e5" +set style line 2 lt rgb "#43a047" +set style line 3 lt rgb "#d81b60" +set style line 4 lt rgb "#f57c00" + +#plot for [server in server_list] csv_file using 1:(stringcolumn(3) eq server[1:strstrt(server, ":")-1] ? column(7) : NaN) ti server + +# [ping] + [packet loss] + [packet loss border] + [packet loss legend] +plot for [i=1:words(server_list)] \ + csv_file using (column(col_time) + tz):(stringcolumn(col_server) eq server(i)[1:strstrt(server(i), ":")-1] ? column(col_average) : NaN) \ + ti server(i)[strstrt(server(i), ":")+1:*] \ + ls i \ + pointtype 7 ps 0.5, \ + for [i=1:words(server_list)] \ + csv_file using (column(col_time) + tz):(stringcolumn(col_server) eq server(i)[1:strstrt(server(i), ":")-1] ? column(col_packet_loss) : NaN) \ + notitle \ + axis x1y2 \ + ls i lt rgb "#000000" \ + pointtype 13 lw 0.5, \ + for [i=1:words(server_list)] \ + csv_file using (column(col_time) + tz):(stringcolumn(col_server) eq server(i)[1:strstrt(server(i), ":")-1] ? column(col_packet_loss) : NaN) \ + notitle \ + axis x1y2 \ + ls i \ + pointtype 13 ps 0.65, \ + NaN with points pt 12 lc rgb "black" title "Packet Loss" + diff --git a/plotter/plot.sh b/plotter/plot.sh new file mode 100755 index 0000000..9ff5656 --- /dev/null +++ b/plotter/plot.sh @@ -0,0 +1,241 @@ +#!/bin/bash + +function help() { cat; }</dev/null 2>&1 && pwd) + +plot_file="$script_path/pinger.gnuplot" +log_file="" # leave empty for stdout + +# defaults +default_csv="$script_path/../data/ping.csv" +default_ymax=250 +default_start_date=$(date --date="1 week ago" -Is) +default_end_date=$(date -Is) +default_hours=24 +default_tz=$(date +%-:::z) + +# end configuration +############################## + +# variables +declare -A plot_env + +#functions +function log() +{ + local time=$(date +%F_%T) + local prefix="LOG" + case $1 in + ERR | ERROR) + prefix="ERR" + shift + ;; + WAR | WARNING) + prefix="WAR" + shift + ;; + esac + local func=$1; shift + # remove any \n + #local text=$(echo "$@"| tr '\n' '#' | sed 's|#|\\n |g') + case "$log_file" in + "") + echo "[$time]:[$prefix]:[$0][$func]: $@" 2>&1 + ;; + *) + echo "[$time]:[$prefix]:[$0][$func]: $@" >> $log_file + ;; + esac +} + +function date_to_epoch() +{ + echo $(date --date="$1" +"%s") +} + +function load_server_env() +{ + if [ -f "$script_path/../servers.env" ]; then + echo $(cat "$script_path/../servers.env" 2>/dev/null) | sed 's|\s|;|g' + elif [ -f "$script_path/servers.env" ]; then + echo $(cat "$script_path/servers.env" 2>/dev/null) | sed 's|\s|;|g' + else + echo '' + fi +} + +# set X_MIN relative to X_MAX is set +function set_relative_X_MIN() +{ + local hours=$1 + if [[ -z "${plot_env[X_MAX]}" ]]; then + plot_env[X_MIN]=$(date_to_epoch $(date --date="$hours hours ago" -Is)) + else + plot_env[X_MIN]=$(( ${plot_env[X_MAX]} - $hours * 3600 )) + fi +} + +function create_env() +{ + local env="" + for env_var in "${!plot_env[@]}"; do + env+=" -e $env_var=${plot_env[$env_var]}" + done + echo "$env" +} + +main() +{ + local unknown_args=() + local servers=$(load_server_env) + local csv_file="$default_csv" limit time_zone="$default_tz" start_date end_date hours + + # if servers passed as args reset list and parse + [[ $# -gt 0 ]] && [[ $1 != -* ]] && servers="" + while [[ $# -gt 0 ]] && [[ $1 != -* ]]; do + servers="$servers;$1" + shift + done + + # parse args + while [[ $# -gt 0 ]]; do + key="$1" + + case $key in + --file|-f) + if [[ $2 == -* ]] || [[ -z "$2" ]]; then + echo "log WAR main Invalid csv file, using default: $csv_file" + shift # past argument, no value + else + csv_file=$2 + shift # past argument + shift # past value + fi + ;; + --limit-y|-l) + if [[ $2 == -* ]] || [[ -z "$2" ]]; then + limit=$default_ymax + shift # past argument, no value + else + limit=$2 + shift # past argument + shift # past value + fi + ;; + --time-zone|-t) + if [[ $2 == -* ]] || [[ -z "$2" ]]; then + time_zone='' + shift # past argument, no value + else + time_zone=$2 + shift # past argument + shift # past value + fi + ;; + --start-date|-S) + if [[ $2 == -* ]] || [[ -z "$2" ]]; then + start_date=$(date_to_epoch $default_start_date) + shift # past argument, no value + else + start_date=$(date_to_epoch "$2") + shift # past argument + shift # past value + fi + ;; + --end-date|-E) + if [[ $2 == -* ]] || [[ -z "$2" ]]; then + end_date=$(date_to_epoch $default_end_date) + shift # past argument, no value + else + end_date=$(date_to_epoch "$2") + shift # past argument + shift # past value + fi + ;; + --show-hours|-H) + if [[ $2 == -* ]] || [[ -z "$2" ]]; then + hours=$default_hours + shift # past argument, no value + else + hours=$2 + shift # past argument + shift # past value + fi + ;; + --help|-h) + help + exit + ;; + *) # unknown option + unknown_args+=("$1") # save it in an array for later + shift # past argument + ;; + esac + done + + # exit with error if no servers to plot + if [[ -z "$servers" ]]; then + log ERR main no servers to plot + exit 1 + fi + + # assign env in order to set relative X_MIN + [[ ! -z "$servers" ]] && plot_env[SERVERS]=\"$servers\" + [[ ! -z "$csv_file" ]] && plot_env[CSV_FILE]=\"$csv_file\" + [[ ! -z "$limit" ]] && plot_env[Y_MAX]=$limit + [[ ! -z "$time_zone" ]] && plot_env[TZ]=$time_zone + [[ ! -z "$start_date" ]] && plot_env[X_MIN]=$start_date + [[ ! -z "$end_date" ]] && plot_env[X_MAX]=$end_date + [[ ! -z "$hours" ]] && set_relative_X_MIN $hours + + [[ ${#unknown_args[@]} -ne 0 ]] && log main WARNING "ignored unknown args: ${unknown_args[@]}" + + log main "plot ARGS: $(create_env)" + gnuplot $(create_env) -p $plot_file + +} + +main "$@" diff --git a/servers.env b/servers.env new file mode 100644 index 0000000..5ee3eb6 --- /dev/null +++ b/servers.env @@ -0,0 +1,3 @@ +127.0.0.1:localhost(1) +0.0.0.0:localhost(2) +localhost:localhost(3)