get rid of some bashisms / clean up. add ping metric. add --metrics cli option

This commit is contained in:
Patrick Stadler 2015-03-15 14:55:36 +01:00
parent 470ed0a828
commit d018d6ea12
12 changed files with 166 additions and 48 deletions

View File

@ -1,3 +1,52 @@
# metrics.sh
metrics.sh is a metrics collection and fowarding daemon implemented in portable shell scripts. A simple interface based on hooks allows for writing custom metrics collectors.
## Usage
```
Usage: ./metrics.sh [-d] [-h] [-v] [-r reporter] [-i interval]
Options:
-r, --reporter <reporter> use specified reporter (default: stdout)
-i, --interval <seconds> collect metrics every n seconds (default: 2)
-v, --verbose enable verbose mode
-d, --docs show documentation
-h, --help show this text
```
## Installation
```bash
$ git clone git@github.com:pstadler/metrics.sh.git
```
TODO: /etc/init.d
### Requirements
metrics.sh has been tested on Ubuntu and Mac OS X but is supposed to run on most *NIX-line operating systems. Some of the provided metrics require [procfs](http://en.wikipedia.org/wiki/Procfs) to be available (check with `[ -d /proc ] && echo available || echo not available`).
## Metrics
Metric | Description
------------- | -------------
cpu | CPU usage in %
memory | Memory usage in %
swap | Swap usage in %
network_io | Network I/O in kB/s
disk_io | Disk I/O in MB/s
disk_usage | Disk usage in %
heartbeat | System heartbeat
ping | Check if remote host is reachable
TODO: how to write custom metrics
## Reporters
TODO: how to write custom reporters
## TODO ## TODO
- README - README

View File

@ -1,9 +1,13 @@
#!/bin/sh
# load utils # load utils
for util in ./lib/utils/*.sh; do source $util; done for util in ./lib/utils/*.sh; do
. $util
done
# init # init
__METRICS=() __AVAILABLE_METRICS=
__REPORTERS=() __AVAILABLE_REPORTERS=
main_load () { main_load () {
# load reporter # load reporter
@ -12,14 +16,14 @@ main_load () {
local reporter=${filename%.*} local reporter=${filename%.*}
# source reporter and copy functions # source reporter and copy functions
source $file . $file
copy_function init __r_${reporter}_init copy_function init __r_${reporter}_init
copy_function report __r_${reporter}_report copy_function report __r_${reporter}_report
copy_function terminate __r_${reporter}_terminate copy_function terminate __r_${reporter}_terminate
copy_function docs __r_${reporter}_docs copy_function docs __r_${reporter}_docs
unset -f init report terminate docs unset -f init report terminate docs
__REPORTERS+=($reporter) __AVAILABLE_REPORTERS=$(trim "$__AVAILABLE_REPORTERS $reporter")
done done
# load metrics # load metrics
@ -28,7 +32,7 @@ main_load () {
local metric=${filename%.*} local metric=${filename%.*}
# soruce metric and copy functions # soruce metric and copy functions
source $file . $file
copy_function init __m_${metric}_init copy_function init __m_${metric}_init
copy_function collect __m_${metric}_collect copy_function collect __m_${metric}_collect
copy_function terminate __m_${metric}_terminate copy_function terminate __m_${metric}_terminate
@ -36,11 +40,16 @@ main_load () {
unset -f init collect terminate docs unset -f init collect terminate docs
# register metric # register metric
__METRICS+=($metric) __AVAILABLE_METRICS=$(trim "$__AVAILABLE_METRICS $metric")
done done
} }
main_init () { main_init () {
# handle args
__METRICS=$(echo $1 | sed 's/,/ /g')
__REPORTER=$2
# create temp dir
TEMP_DIR=$(make_temp_dir) TEMP_DIR=$(make_temp_dir)
# register trap # register trap
@ -49,13 +58,27 @@ main_init () {
trap - SIGTERM && kill -- -$$ SIGINT SIGTERM EXIT trap - SIGTERM && kill -- -$$ SIGINT SIGTERM EXIT
' SIGINT SIGTERM EXIT ' SIGINT SIGTERM EXIT
# check if reporter exists
if ! in_array $__REPORTER "$__AVAILABLE_REPORTERS"; then
echo "Error: reporter '$__REPORTER' is not available"
exit 1
fi
# check if metrics exist
for metric in $__METRICS; do
if ! in_array $metric "$__AVAILABLE_METRICS"; then
echo "Error: metric '$metric' is not available"
exit 1
fi
done
# init reporter # init reporter
if is_function __r_${REPORTER}_init; then if is_function __r_${__REPORTER}_init; then
__r_${REPORTER}_init __r_${__REPORTER}_init
fi fi
# init metrics # init metrics
for metric in ${__METRICS[@]}; do for metric in $__METRICS; do
if ! is_function __m_${metric}_init; then if ! is_function __m_${metric}_init; then
continue continue
fi fi
@ -76,32 +99,33 @@ main_collect () {
_r_result="$2" _r_result="$2"
fi fi
if is_number $_r_result; then if is_number $_r_result; then
__r_${REPORTER}_report $_r_label $_r_result __r_${__REPORTER}_report $_r_label $_r_result
fi fi
} }
# collect metrics # collect metrics
for metric in ${__METRICS[@]}; do for metric in $__METRICS; do
if ! is_function __m_${metric}_collect; then if ! is_function __m_${metric}_collect; then
continue continue
fi fi
fork () { fork () {
while true; do
__m_${metric}_collect __m_${metric}_collect
sleep $INTERVAL sleep $INTERVAL
fork done
} }
fork & fork &
unset -f fork unset -f fork
done done
# run forever # run forever
tail -f /dev/null # `sleep infinity` is not portable sleep 2147483647 # `sleep infinity` is not portable
} }
main_terminate () { main_terminate () {
# terminate metrics # terminate metrics
for metric in ${__METRICS[@]}; do for metric in $__METRICS; do
if ! is_function __m_${metric}_terminate; then if ! is_function __m_${metric}_terminate; then
continue continue
fi fi
@ -109,8 +133,8 @@ main_terminate () {
done done
# terminate reporter # terminate reporter
if is_function __r_${REPORTER}_terminate; then if is_function __r_${__REPORTER}_terminate; then
__r_${REPORTER}_terminate __r_${__REPORTER}_terminate
fi fi
# delete temporary directory # delete temporary directory
@ -121,7 +145,7 @@ main_terminate () {
main_docs () { main_docs () {
echo "# Metrics" echo "# Metrics"
for metric in ${__METRICS[@]}; do for metric in $__AVAILABLE_METRICS; do
if ! is_function __m_${metric}_docs; then if ! is_function __m_${metric}_docs; then
continue continue
fi fi
@ -133,7 +157,7 @@ main_docs () {
echo echo
echo "# REPORTERS" echo "# REPORTERS"
for reporter in ${__REPORTERS[@]}; do for reporter in $__AVAILABLE_REPORTERS; do
if ! is_function __r_${reporter}_docs; then if ! is_function __r_${reporter}_docs; then
continue continue
fi fi

View File

@ -1,11 +1,11 @@
#!/bin/sh #!/bin/sh
is_function () { is_function () {
[ "`type -t $1`" == 'function' ] declare -f -F $1 > /dev/null; return $?
} }
# http://stackoverflow.com/a/1369211/183097 # http://stackoverflow.com/a/1369211/183097
copy_function () { copy_function () {
declare -F $1 > /dev/null || return 1 is_function $1 || return 1
eval "$(echo "${2}()"; declare -f ${1} | tail -n +2)" eval "$(echo "${2}()"; declare -f ${1} | tail -n +2)"
} }

View File

@ -1,7 +1,19 @@
#!/bin/sh
is_number () { is_number () {
[ ! -z "$1" ] && printf '%f' "$1" &>/dev/null [ ! -z "$1" ] && printf '%f' "$1" > /dev/null 2>&1
} }
iso_date () { iso_date () {
date -u +"%Y-%m-%dT%H:%M:%SZ" date -u +"%Y-%m-%dT%H:%M:%SZ"
} }
in_array () {
local item=$1
local arr=$2
[[ " $2 " = *" $1 "* ]]
}
trim () {
echo $1 | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'
}

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
declare -r OS_TYPE=$(case "$OSTYPE" in readonly OS_TYPE=$(case "$OSTYPE" in
(solaris*) echo solaris;; (solaris*) echo solaris;;
(darwin*) echo osx;; (darwin*) echo osx;;
(linux*) echo linux;; (linux*) echo linux;;
@ -8,11 +8,11 @@ declare -r OS_TYPE=$(case "$OSTYPE" in
(*) echo unknown;; (*) echo unknown;;
esac) esac)
is_solaris () { [ $OS_TYPE == 'solaris' ]; } is_solaris () { [ $OS_TYPE = 'solaris' ]; }
is_osx () { [ $OS_TYPE == 'osx' ]; } is_osx () { [ $OS_TYPE = 'osx' ]; }
is_linux () { [ $OS_TYPE == 'solaris' ]; } is_linux () { [ $OS_TYPE = 'solaris' ]; }
is_bsd () { [ $OS_TYPE == 'bsd']; } is_bsd () { [ $OS_TYPE = 'bsd']; }
is_unknown () { [ $OS_TYPE == 'unknown' ]; } is_unknown () { [ $OS_TYPE = 'unknown' ]; }
# http://unix.stackexchange.com/a/84980/50905 # http://unix.stackexchange.com/a/84980/50905
if is_osx; then if is_osx; then

View File

@ -1,3 +1,5 @@
#!/bin/sh
VERBOSE_MODE=false VERBOSE_MODE=false
verbose_on () { verbose_on () {

View File

@ -3,6 +3,7 @@
# config # config
INTERVAL=2 INTERVAL=2
REPORTER=stdout REPORTER=stdout
METRICS=cpu,disk_io,disk_usage,heartbeat,memory,network_io,swap
# env # env
LC_ALL=en_US.UTF-8 LC_ALL=en_US.UTF-8
@ -15,7 +16,7 @@ opt_docs=false
opt_verbose=false opt_verbose=false
usage () { usage () {
echo " Usage: $0 [-d] [-h] [-v] [-r reporter] [-i interval]" echo " Usage: $0 [-d] [-h] [-v] [-m metrics] [-r reporter] [-i interval]"
} }
help () { help () {
@ -24,6 +25,7 @@ help () {
echo echo
echo " Options: " echo " Options: "
echo echo
echo " -m, --metrics <metric1,...> use specified metrics"
echo " -r, --reporter <reporter> use specified reporter (default: stdout)" echo " -r, --reporter <reporter> use specified reporter (default: stdout)"
echo " -i, --interval <seconds> collect metrics every n seconds (default: 2)" echo " -i, --interval <seconds> collect metrics every n seconds (default: 2)"
echo " -v, --verbose enable verbose mode" echo " -v, --verbose enable verbose mode"
@ -34,6 +36,11 @@ help () {
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case $1 in case $1 in
-m|--metrics)
shift
METRICS=$1
;;
-r|--reporter) -r|--reporter)
shift shift
REPORTER=$1 REPORTER=$1
@ -66,9 +73,8 @@ while [ $# -gt 0 ]; do
shift shift
done done
# run # run
source ./lib/main.sh . ./lib/main.sh
if [ $opt_verbose = "true" ]; then if [ $opt_verbose = "true" ]; then
verbose_on verbose_on
@ -77,16 +83,17 @@ fi
verbose "OS detected: $OS_TYPE" verbose "OS detected: $OS_TYPE"
main_load main_load
verbose "Metrics loaded: ${__METRICS[@]}" verbose "Available metrics: $__AVAILABLE_METRICS"
verbose "Reporters loaded: ${REPORTER}" verbose "Available reporters: $__AVAILABLE_REPORTERS"
if [ "$opt_docs" = true ]; then if [ "$opt_docs" = true ]; then
main_docs main_docs
exit exit
fi fi
main_init main_init $METRICS $REPORTER
verbose "Metrics initialized" verbose "Using metrics: $__METRICS"
verbose "Using reporter: $__REPORTER"
verbose "Collecting metrics every $INTERVAL second(s)" verbose "Collecting metrics every $INTERVAL second(s)"
main_collect main_collect

9
metrics/heartbeat.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/sh
collect () {
report 1
}
docs () {
echo "Send a simple heartbeat in form of an integer '1'."
}

View File

@ -8,7 +8,7 @@ init () {
NETWORK_IO_INTERFACE="eth0" NETWORK_IO_INTERFACE="eth0"
fi fi
fi fi
readonly __network_io_divisor=$[$INTERVAL * 1024] readonly __network_io_divisor=$(($INTERVAL * 1024))
} }
if is_osx; then if is_osx; then

View File

@ -1,9 +1,23 @@
#!/bin/sh #!/bin/sh
init () {
if [ -z $PING_REMOTE_HOST ]; then
echo "Error: ping metric requires \$PING_REMOTE_HOST to be specified"
exit 1
fi
}
collect () { collect () {
ping -c 1 $PING_REMOTE_HOST > /dev/null 2>&1
if [ $? -eq 0 ]; then
report 1 report 1
else
report 0
fi
} }
docs () { docs () {
echo "Send a simple ping in form of an integer '1'." echo "Check if remote host is reachable by sending a single ping."
echo "Reports '1' if ping was successful, '0' if not."
echo "\$PING_REMOTE_HOST="
} }

View File

@ -12,6 +12,7 @@ init () {
if [ "$INFLUXDB_SEND_HOSTNAME" = true ]; then if [ "$INFLUXDB_SEND_HOSTNAME" = true ]; then
__influxdb_columns="[\"value\",\"host\"]" __influxdb_columns="[\"value\",\"host\"]"
__influxdb_hostname=$(hostname)
else else
__influxdb_columns="[\"value\"]" __influxdb_columns="[\"value\"]"
fi fi
@ -22,7 +23,7 @@ report () {
local value=$2 local value=$2
local points local points
if [ "$INFLUXDB_SEND_HOSTNAME" = true ]; then if [ "$INFLUXDB_SEND_HOSTNAME" = true ]; then
points="[$value,\"$HOSTNAME\"]" points="[$value,\"$__influxdb_hostname\"]"
else else
points="[$value]" points="[$value]"
fi fi

View File

@ -12,7 +12,7 @@ init() {
fi fi
if [ -z $KEEN_IO_EVENT_COLLECTION ]; then if [ -z $KEEN_IO_EVENT_COLLECTION ]; then
KEEN_IO_EVENT_COLLECTION=$HOSTNAME KEEN_IO_EVENT_COLLECTION=$(hostname)
fi fi
__keen_io_api_url="https://api.keen.io/3.0" __keen_io_api_url="https://api.keen.io/3.0"
@ -33,5 +33,5 @@ docs () {
echo "Send data to Keen IO (https://keen.io)." echo "Send data to Keen IO (https://keen.io)."
echo "\$KEEN_IO_WRITE_KEY=<write_key>" echo "\$KEEN_IO_WRITE_KEY=<write_key>"
echo "\$KEEN_IO_PROJECT_ID=<project_id>" echo "\$KEEN_IO_PROJECT_ID=<project_id>"
echo "\$KEEN_IO_EVENT_COLLECTION=$HOSTNAME" echo "\$KEEN_IO_EVENT_COLLECTION=$(hostname)"
} }