How to determine how long specified remote server or device was offline

Create shell script to determine how long specified remote server or device was offline. It is not designed to substitute monitoring solution, but to determine how long it will take to reboot server or device. It is using convert seconds to human readable time code to display results.

#!/bin/bash
# Determine how long remote server was offline

# initial sleep time
sleep_time=1

# ping timeout
timeout=5

if [ "$#" -ne "1" ]; then
  echo "Provide server/device address as a parameter"
  exit 1
fi

# server/device address is provided as a first parameter
address="$1"

# function to pretty print time
function pretty_time() {
  # calculate seconds for each time period
  seconds_in_year=$(  echo "(365.2425  * 24 * 60 * 60)/1" | bc)
  seconds_in_month=$( echo "(30.436875 * 24 * 60 * 60)/1" | bc)
  seconds_in_day=$(   echo "(            24 * 60 * 60)/1" | bc)
  seconds_in_hour=$(  echo "(                 60 * 60)/1" | bc)
  seconds_in_minute=60

  seconds_param="$1"

  time_string=""

  # take care of years
  if [ "$seconds_param" -ge "$seconds_in_year" ]; then
    years=$(expr $seconds_param \/ $seconds_in_year)
    seconds_param=$(expr $seconds_param - $years \* $seconds_in_year)
    if [ "$years" -gt "1" ]; then
      time_string="$years years"
    elif [ "$years" -eq "1" ]; then
      time_string="$years year"
    fi
  fi
  # take care of months
  if [ "$seconds_param" -ge "$seconds_in_month" ]; then
    months=$(expr $seconds_param \/ $seconds_in_month)
    seconds_param=$(expr $seconds_param - $months \* $seconds_in_month)
    if [ "$months" -gt "1" ]; then
      time_string="$time_string $months months"
    elif [ "$months" -eq "1" ]; then
      time_string="$time_string $months month"
    fi
  fi

  # take care of days
  if [ "$seconds_param" -ge "$seconds_in_day" ]; then
    days=$(expr $seconds_param \/ $seconds_in_day)
    seconds_param=$(expr $seconds_param - $days \* $seconds_in_day)
    if [ "$days" -gt "1" ]; then
      time_string="$time_string $days days"
    elif [ "$days" -eq "1" ]; then
      time_string="$time_string $days day"
    fi
  fi
  
  # take care of hours
  if [ "$seconds_param" -ge "$seconds_in_hour" ]; then
    hours=$(expr $seconds_param \/ $seconds_in_hour)
    seconds_param=$(expr $seconds_param - $hours \* $seconds_in_hour)
    if [ "$hours" -gt "1" ]; then
      time_string="$time_string $hours hours"
    elif [ "$hours" -eq "1" ]; then
      time_string="$time_string $hours hour"
    fi
  fi
  
  # take care of minutes
  if [ "$seconds_param" -ge "$seconds_in_minute" ]; then
    minutes=$(expr $seconds_param \/ $seconds_in_minute)
    seconds_param=$(expr $seconds_param - $minutes \* $seconds_in_minute)
    if [ "$minutes" -gt "1" ]; then
      time_string="$time_string $minutes minutes"
    elif [ "$minutes" -eq "1" ]; then
      time_string="$time_string $minutes minute"
    fi
  fi
  
  # take care of seconds
  seconds=$seconds_param
  if [ "$seconds" -gt "1" ]; then
      time_string="$time_string $seconds seconds"
  elif [ "$seconds" -eq "1" ]; then
      time_string="$time_string $seconds second"
  fi
  
  echo "$time_string" | sed "s/^ //"
}
  
# initial state
state=0

# server/device was offline when script started
ping -c 1 -W $timeout -q $address 2>&1 1>/dev/null
exit_code="$?"
if [ "$exit_code" -gt "0" ]; then
  state_in="$(date +%s)"
  state=2
  echo "Server/device [${address}] was not responding at [$(date --date="@${state_in}" -R)] when script was executed"
else
  echo "Server/device [${address}] was responding at [$(date -R)] when script was executed"
fi

while true; do
  ping -c 1 -W $timeout -q $address 2>&1 1>/dev/null
  exit_code="$?"

  # server/device was offline when script started, but it is online now
  if [ "$exit_code" -eq "0" ] && [ "$state" -eq "2" ]; then
    state_out="$(date +%s)"
    state_diff="$(expr $state_out  - $state_in)"
    state=0
    echo "Server/device [${address}] started responding at [$(date --date="@${state_in}" -R)]"
    echo "Server/device [${address}] was offline for at least [$(pretty_time $state_diff)]"
  # server/device was online, but it is offline now
  elif [ "$exit_code" -gt "0" ] && [ "$state" -eq "0" ]; then
    state_in="$(date +%s)"
    state=1
    echo "Server/device [${address}] stopped responding at [$(date --date="@${state_in}" -R)]"
  # server/device was offline, but it is online now
  elif [ "$exit_code" -eq "0" ] && [ "$state" -eq "1" ]; then
    state_out="$(date +%s)"
    state_diff="$(expr $state_out  - $state_in)"
    state=0;
    echo "Server/device [${address}] started responding at [$(date --date="@${state_out}" -R)]"
    echo "Server/device [${address}] was offline for [$(pretty_time $state_diff)]"
  fi
  sleep $sleep_time
done

Sample output when server was responding during shell script startup.

$ bash offline.sh 192.0.2.11
Server/device [192.0.2.11] was responding at [Fri, 06 Apr 2018 19:37:49 +0000] when script was executed
Server/device [192.0.2.11] stopped responding at [Fri, 06 Apr 2018 19:38:08 +0000]
Server/device [192.0.2.11] started responding at [Fri, 06 Apr 2018 19:50:22 +0000]
Server/device [192.0.2.11] was offline for [12 minutes 14 seconds] seconds
Server/device [192.0.2.11] stopped responding at [Fri, 06 Apr 2018 19:53:46 +0000]
Server/device [192.0.2.11] started responding at [Fri, 06 Apr 2018 19:55:59 +0000]
Server/device [192.0.2.11] was offline for [2 minutes 13 seconds] seconds

Sample output when server was not responding during shell script startup.

$ bash offline.sh 192.0.2.11
Server/device [192.0.2.11] was not responding at [Fri, 06 Apr 2018 19:38:32 +0000] when script was executed
Server/device [192.0.2.11] started responding at [Fri, 06 Apr 2018 19:38:32 +0000]
Server/device [192.0.2.11] was offline for at least [11 minutes 48 seconds] seconds
Server/device [192.0.2.11] stopped responding at [Fri, 06 Apr 2018 19:53:45 +0000]
Server/device [192.0.2.11] started responding at [Fri, 06 Apr 2018 19:55:58 +0000]
Server/device [192.0.2.11] was offline for [2 minutes 13 seconds] seconds