From b518c807d13c3cc5cfe61261b3c53c6dab5cea14 Mon Sep 17 00:00:00 2001 From: Patrick Stadler Date: Sat, 21 Mar 2015 21:19:08 +0100 Subject: [PATCH] add defaults() fn. add --print-config. config loader now fails on error --- README.md | 2 +- lib/main.sh | 133 +++++++++++++++++++++++++++++++----------- lib/utils/config.sh | 9 ++- lib/utils/helpers.sh | 6 +- lib/utils/loader.sh | 10 ++-- metrics.sh | 62 +++++++++++++------- metrics/disk_io.sh | 42 +++++++------ metrics/disk_usage.sh | 4 +- metrics/memory.sh | 2 +- metrics/network_io.sh | 39 +++++++------ metrics/ping.sh | 4 +- reporters/file.sh | 4 +- reporters/influxdb.sh | 16 ++--- reporters/keen_io.sh | 18 +++--- reporters/stathat.sh | 4 +- reporters/stdout.sh | 2 +- 16 files changed, 232 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index 29fd6d3..a7e8707 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,6 @@ TODO: how to write custom reporters - README - config file docs -- config file auto-generation - load custom/contrib metrics and reporters +- enable -m / -r - allow multiple reporters? \ No newline at end of file diff --git a/lib/main.sh b/lib/main.sh index 2b5de44..ceae8ea 100644 --- a/lib/main.sh +++ b/lib/main.sh @@ -9,7 +9,22 @@ done __AVAILABLE_METRICS= __AVAILABLE_REPORTERS= +main_defaults () { + if [ -z $INTERVAL ]; then + INTERVAL=2 + fi + if [ -z $METRICS ]; then + METRICS=cpu,disk_io,disk_usage,heartbeat,memory,network_io,swap + fi + if [ -z $REPORTER ]; then + REPORTER=stdout + fi +} + main_load () { + # set defaults + main_defaults + # load reporter for file in ./reporters/*.sh; do local filename=$(basename $file) @@ -54,11 +69,14 @@ main_init () { done # init reporter + if is_function __r_${__REPORTER}_defaults; then + __r_${__REPORTER}_defaults + fi if is_function __r_${__REPORTER}_config; then __r_${__REPORTER}_config fi - if is_function __r_${__REPORTER}_init; then - __r_${__REPORTER}_init + if is_function __r_${__REPORTER}_start; then + __r_${__REPORTER}_start fi } @@ -77,6 +95,35 @@ main_collect () { ( local metric_name=$(get_name $metric) local metric_alias=$(get_alias $metric) + + # init metric + load_metric_with_prefix __m_${metric_alias}_ ./metrics/${metric_name}.sh + + if is_function __m_${metric_alias}_defaults; then + __m_${metric_alias}_defaults + fi + + if is_function __m_${metric_alias}_config; then + __m_${metric_alias}_config + fi + + if is_function __m_${metric_alias}_start; then + __m_${metric_alias}_start + fi + + if ! is_function __m_${metric_alias}_collect; then + continue + fi + + # collect metrics + trap " + verbose \"Stopping metric '${metric_alias}'\" + if is_function __m_${metric_alias}_stop; then + __m_${metric_alias}_stop + fi + exit 0 + " 13 + # used by metrics to return results report () { local _r_label _r_result @@ -92,30 +139,6 @@ main_collect () { fi } - # init metric - if is_function __m_${metric_alias}_config; then - __m_${metric_alias}_config - fi - - load_metric_with_prefix __m_${metric_alias}_ ./metrics/${metric_name}.sh - - if is_function __m_${metric_alias}_init; then - __m_${metric_alias}_init - fi - - if ! is_function __m_${metric_alias}_collect; then - continue - fi - - # collect metrics - trap " - if is_function __m_${metric_alias}_terminate; then - verbose 'Stopping metric ${metric_alias}' - __m_${metric_alias}_terminate - fi - exit 0 - " 13 - while true; do __m_${metric_alias}_collect sleep $INTERVAL @@ -132,13 +155,13 @@ main_collect () { } main_terminate () { - # terminate reporter - if is_function __r_${__REPORTER}_terminate; then - verbose "Stopping reporter ${__REPORTER}" - __r_${__REPORTER}_terminate + # stop reporter + verbose "Stopping reporter '${__REPORTER}'" + if is_function __r_${__REPORTER}_stop; then + __r_${__REPORTER}_stop fi - verbose -n "Cleaning up..." + verbose "Cleaning up..." # delete temporary directory if [ -d $TEMP_DIR ]; then rmdir $TEMP_DIR @@ -146,8 +169,8 @@ main_terminate () { verbose "done" } -main_docs () { - echo "# Metrics" +main_print_docs () { + echo "# METRICS" for metric in $__AVAILABLE_METRICS; do load_metric_with_prefix __m_${metric}_ ./metrics/${metric}.sh @@ -156,6 +179,10 @@ main_docs () { continue fi + if is_function __m_${metric}_defaults; then + __m_${metric}_defaults + fi + echo echo "[$metric]" __m_${metric}_docs @@ -168,8 +195,48 @@ main_docs () { continue fi + if is_function __r_${reporter}_defaults; then + __r_${reporter}_defaults + fi + echo echo "[$reporter]" __r_${reporter}_docs done +} + +main_print_config () { + echo "[metrics.sh]" + echo ";INTERVAL=$INTERVAL" + echo ";METRICS=$METRICS" + echo ";REPORTER=$REPORTER" + + for metric in $__AVAILABLE_METRICS; do + load_metric_with_prefix __m_${metric}_ ./metrics/${metric}.sh + + if is_function __m_${metric}_defaults; then + __m_${metric}_defaults + fi + + echo + echo ";[metric $metric]" + if ! is_function __m_${metric}_docs; then + continue + fi + print_prefixed ";" "$(__m_${metric}_docs)" + done + + for reporter in $__AVAILABLE_REPORTERS; do + if ! is_function __r_${reporter}_docs; then + continue + fi + + if is_function __r_${reporter}_defaults; then + __r_${reporter}_defaults + fi + + echo + echo ";[reporter $reporter]" + print_prefixed ";" "$(__r_${reporter}_docs)" + done } \ No newline at end of file diff --git a/lib/utils/config.sh b/lib/utils/config.sh index c08c252..c37e273 100644 --- a/lib/utils/config.sh +++ b/lib/utils/config.sh @@ -48,8 +48,13 @@ parse_config () { return fi - #echo "${fn_name}_config () { ${_body}; }" - eval "${fn_name}_config () { ${_body}; }" + if ! eval "$_body" > /dev/null 2>&1; then + echo "Error parsing config section: $_name: $_body" + exit 1 + fi + + #echo "${fn_name}_config () { $_body; }" + eval "${fn_name}_config () { $_body; }" } for line in $(cat $1); do diff --git a/lib/utils/helpers.sh b/lib/utils/helpers.sh index 0daf636..eb0431c 100644 --- a/lib/utils/helpers.sh +++ b/lib/utils/helpers.sh @@ -9,8 +9,6 @@ iso_date () { } in_array () { - local item=$1 - local arr=$2 echo " $2 " | grep -q " $1 " } @@ -23,4 +21,8 @@ unique_id () { LC_ALL=C echo __u_$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 10) LC_ALL=$RESTORE_LC_ALL +} + +print_prefixed () { + printf "$2" | sed -e "s/^/$1/g" } \ No newline at end of file diff --git a/lib/utils/loader.sh b/lib/utils/loader.sh index afb190c..0264803 100644 --- a/lib/utils/loader.sh +++ b/lib/utils/loader.sh @@ -6,9 +6,10 @@ load_reporter_with_prefix () { local content content=$(sed \ - -e 's/^[[:space:]]*\(init[ ]*()[ ]*{\)/'"$prefix"'\1/' \ + -e 's/^[[:space:]]*\(defaults[ ]*()[ ]*{\)/'"$prefix"'\1/' \ + -e 's/^[[:space:]]*\(start[ ]*()[ ]*{\)/'"$prefix"'\1/' \ -e 's/^[[:space:]]*\(report[ ]*()[ ]*{\)/'"$prefix"'\1/' \ - -e 's/^[[:space:]]*\(terminate[ ]*()[ ]*{\)/'"$prefix"'\1/' \ + -e 's/^[[:space:]]*\(stop[ ]*()[ ]*{\)/'"$prefix"'\1/' \ -e 's/^[[:space:]]*\(docs[ ]*()[ ]*{\)/'"$prefix"'\1/' $file) eval "$content" @@ -21,9 +22,10 @@ load_metric_with_prefix () { # dash will error if this variable is defined as `local` content=$(sed \ - -e 's/^[[:space:]]*\(init[ ]*()[ ]*{\)/'"$prefix"'\1/' \ + -e 's/^[[:space:]]*\(defaults[ ]*()[ ]*{\)/'"$prefix"'\1/' \ + -e 's/^[[:space:]]*\(start[ ]*()[ ]*{\)/'"$prefix"'\1/' \ -e 's/^[[:space:]]*\(collect[ ]*()[ ]*{\)/'"$prefix"'\1/' \ - -e 's/^[[:space:]]*\(terminate[ ]*()[ ]*{\)/'"$prefix"'\1/' \ + -e 's/^[[:space:]]*\(stop[ ]*()[ ]*{\)/'"$prefix"'\1/' \ -e 's/^[[:space:]]*\(docs[ ]*()[ ]*{\)/'"$prefix"'\1/' $file) eval "$content" diff --git a/metrics.sh b/metrics.sh index e1489f5..0fc3006 100755 --- a/metrics.sh +++ b/metrics.sh @@ -1,18 +1,12 @@ #!/bin/sh -# config -INTERVAL=2 -REPORTER=stdout -METRICS=cpu,disk_io,disk_usage,heartbeat,memory,network_io,swap -CONFIG_FILE= - # env LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 usage () { - echo " Usage: $0 [-d] [-h] [-v] [-c] [-m] [-r] [-i]" + echo " Usage: $0 [-d] [-h] [-v] [-c] [-m] [-r] [-i] [-C]" } help () { @@ -27,34 +21,40 @@ help () { echo " -i, --interval collect metrics every n seconds (default: 2)" echo " -v, --verbose enable verbose mode" echo " -d, --docs show documentation" + echo " -C, --print-config print output to be used in a config file" echo " -h, --help show this text" echo } # handle opts +opt_config_file= +opt_metrics= +opt_reporter= +opt_interval= opt_docs=false opt_verbose=false +opt_print_config=false while [ $# -gt 0 ]; do case $1 in -c|--config) shift - CONFIG_FILE=$1 + opt_config_file=$1 ;; -m|--metrics) shift - METRICS=$1 + opt_metrics=$1 ;; -r|--reporter) shift - REPORTER=$1 + opt_reporter=$1 ;; -i|--interval) shift - INTERVAL=$1 + opt_interval=$1 ;; -v|-verbose) @@ -65,6 +65,10 @@ while [ $# -gt 0 ]; do opt_docs=true ;; + -C|--print-config) + opt_print_config=true + ;; + -h|--help) help exit @@ -85,31 +89,32 @@ done if [ $opt_verbose = true ]; then verbose_on verbose "Started in verbose mode" + verbose "PID: $$" + verbose "OS detected: $OS_TYPE" fi -verbose "PID: $$" -verbose "OS detected: $OS_TYPE" main_load verbose "Available metrics: $__AVAILABLE_METRICS" verbose "Available reporters: $__AVAILABLE_REPORTERS" if [ $opt_docs = true ]; then - main_docs + main_print_docs exit fi -if [ -n "$CONFIG_FILE" ]; then - verbose "Loading configuration file: $CONFIG_FILE" +if [ $opt_print_config = true ]; then + main_print_config + exit +fi - parse_config $CONFIG_FILE +if [ -n "$opt_config_file" ]; then + verbose "Loading configuration file: $opt_config_file" + + parse_config $opt_config_file if [ $? -ne 0 ]; then exit 1 fi - if is_function main_config; then - main_config - fi - configured_reporters=$(get_configured_reporters) if [ -n "$configured_reporters" ]; then REPORTER=$configured_reporters @@ -121,6 +126,21 @@ if [ -n "$CONFIG_FILE" ]; then fi fi +# --reporter always wins +if [ -n "$opt_reporter" ]; then + REPORTER=$opt_reporter +fi + +# --metrics always wins +if [ -n "$opt_metrics" ]; then + METRICS=$opt_metrics +fi + +# --interval always wins +if [ -n "$opt_interval" ]; then + INTERVAL=$opt_interval +fi + main_init "$METRICS" "$REPORTER" verbose "Using metrics: $__METRICS" verbose "Using reporter: $__REPORTER" diff --git a/metrics/disk_io.sh b/metrics/disk_io.sh index 555e089..1e37977 100644 --- a/metrics/disk_io.sh +++ b/metrics/disk_io.sh @@ -1,6 +1,6 @@ #!/bin/sh -init () { +defaults () { if [ -z $DISK_IO_MOUNTPOINT ]; then if is_osx; then DISK_IO_MOUNTPOINT="disk0" @@ -8,30 +8,34 @@ init () { DISK_IO_MOUNTPOINT="/dev/vda" fi fi - readonly __disk_io_fifo=$TEMP_DIR/$(unique_id) - mkfifo $__disk_io_fifo - __disk_io_bgproc & } -if is_osx; then - __disk_io_bgproc () { - iostat -K -d -w $INTERVAL $DISK_IO_MOUNTPOINT | while read line; do - echo $line | awk '{ print $3 }' > $__disk_io_fifo - done - } -else - __disk_io_bgproc () { - iostat -y -m -d $INTERVAL $DISK_IO_MOUNTPOINT | while read line; do - echo $line | awk '/[0-9.]/{ print $3 }' > $__disk_io_fifo - done - } -fi +start () { + readonly __disk_io_fifo=$TEMP_DIR/$(unique_id) + mkfifo $__disk_io_fifo + + if is_osx; then + __disk_io_bgproc () { + iostat -K -d -w $INTERVAL $DISK_IO_MOUNTPOINT | while read line; do + echo $line | awk '{ print $3 }' > $__disk_io_fifo + done + } + else + __disk_io_bgproc () { + iostat -y -m -d $INTERVAL $DISK_IO_MOUNTPOINT | while read line; do + echo $line | awk '/[0-9.]/{ print $3 }' > $__disk_io_fifo + done + } + fi + + __disk_io_bgproc & +} collect () { report $(cat $__disk_io_fifo) } -terminate () { +stop () { if [ ! -z $__disk_io_fifo ] && [ -p $__disk_io_fifo ]; then rm $__disk_io_fifo fi @@ -39,5 +43,5 @@ terminate () { docs () { echo "Disk I/O in MB/s." - echo "\$DISK_IO_MOUNTPOINT=" + echo "DISK_IO_MOUNTPOINT=$DISK_IO_MOUNTPOINT" } \ No newline at end of file diff --git a/metrics/disk_usage.sh b/metrics/disk_usage.sh index 99d9190..fb48a48 100644 --- a/metrics/disk_usage.sh +++ b/metrics/disk_usage.sh @@ -1,6 +1,6 @@ #!/bin/sh -init () { +defaults () { if [ -z $DISK_USAGE_MOUNTPOINT ]; then if is_osx; then DISK_USAGE_MOUNTPOINT="/dev/disk1" @@ -17,5 +17,5 @@ collect () { docs () { echo "Disk usage percentage for a file system at a given mount point." - echo "\$DISK_USAGE_MOUNTPOINT=" + echo "DISK_USAGE_MOUNTPOINT=$DISK_USAGE_MOUNTPOINT" } \ No newline at end of file diff --git a/metrics/memory.sh b/metrics/memory.sh index 17274b8..cb63146 100644 --- a/metrics/memory.sh +++ b/metrics/memory.sh @@ -1,7 +1,7 @@ #!/bin/sh if is_osx; then - init () { + start () { readonly __memory_os_memsize=$(sysctl -n hw.memsize) } diff --git a/metrics/network_io.sh b/metrics/network_io.sh index 30aa0e5..3dc876c 100644 --- a/metrics/network_io.sh +++ b/metrics/network_io.sh @@ -1,6 +1,6 @@ #!/bin/sh -init () { +defaults () { if [ -z $NETWORK_IO_INTERFACE ]; then if is_osx; then NETWORK_IO_INTERFACE="en0" @@ -8,32 +8,35 @@ init () { NETWORK_IO_INTERFACE="eth0" fi fi - readonly __network_io_divisor=$(($INTERVAL * 1024)) } -if is_osx; then - __network_io_collect () { - netstat -b -I $NETWORK_IO_INTERFACE | awk '{ print $7" "$10 }' | tail -n 1 - } -else - __network_io_collect () { - cat /proc/net/dev | awk -v iface_regex="$NETWORK_IO_INTERFACE:" \ - '$0 ~ iface_regex { print $2" "$10 }' - } -fi +start () { + readonly __network_io_divisor=$(($INTERVAL * 1024)) -__network_io_calc_kBps() { - echo $1 $2 | awk -v divisor=$__network_io_divisor \ + if is_osx; then + get_netstat () { + netstat -b -I $NETWORK_IO_INTERFACE | awk '{ print $7" "$10 }' | tail -n 1 + } + else + get_netstat () { + cat /proc/net/dev | awk -v iface_regex="$NETWORK_IO_INTERFACE:" \ + '$0 ~ iface_regex { print $2" "$10 }' + } + fi + + calc_kBps() { + echo $1 $2 | awk -v divisor=$__network_io_divisor \ '{ printf "%.2f", ($1 - $2) / divisor }' + } } collect () { local sample - sample=$(__network_io_collect) + sample=$(get_netstat) if [ ! -z "$__network_io_sample" ]; then - report "in" $(__network_io_calc_kBps $(echo $sample | awk '{print $1}') \ + report "in" $(calc_kBps $(echo $sample | awk '{print $1}') \ $(echo $__network_io_sample | awk '{print $1}')) - report "out" $(__network_io_calc_kBps $(echo $sample | awk '{print $2}') \ + report "out" $(calc_kBps $(echo $sample | awk '{print $2}') \ $(echo $__network_io_sample | awk '{print $2}')) fi __network_io_sample="$sample" @@ -41,5 +44,5 @@ collect () { docs () { echo "Network traffic in kB/s." - echo "\$NETWORK_IO_INTERFACE=" + echo "NETWORK_IO_INTERFACE=$NETWORK_IO_INTERFACE" } \ No newline at end of file diff --git a/metrics/ping.sh b/metrics/ping.sh index 637f65b..941539e 100644 --- a/metrics/ping.sh +++ b/metrics/ping.sh @@ -1,6 +1,6 @@ #!/bin/sh -init () { +start () { if [ -z $PING_REMOTE_HOST ]; then echo "Error: ping metric requires \$PING_REMOTE_HOST to be specified" exit 1 @@ -19,5 +19,5 @@ collect () { docs () { 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=" + echo "PING_REMOTE_HOST=$PING_REMOTE_HOST" } \ No newline at end of file diff --git a/reporters/file.sh b/reporters/file.sh index 111a2cd..42301f5 100644 --- a/reporters/file.sh +++ b/reporters/file.sh @@ -1,6 +1,6 @@ #!/bin/sh -init () { +start () { if [ -z $FILE_LOCATION ]; then echo "Error: file reporter requires \$FILE_LOCATION to be specified" exit 1 @@ -20,5 +20,5 @@ report () { docs () { echo "Write to a file or named pipe." - echo "\$FILE_LOCATION=" + echo "FILE_LOCATION=$FILE_LOCATION" } \ No newline at end of file diff --git a/reporters/influxdb.sh b/reporters/influxdb.sh index b63fe39..180d92b 100644 --- a/reporters/influxdb.sh +++ b/reporters/influxdb.sh @@ -1,15 +1,17 @@ #!/bin/sh -init () { +defaults () { + if [ -z $INFLUXDB_SEND_HOSTNAME ]; then + INFLUXDB_SEND_HOSTNAME=true + fi +} + +start () { if [ -z $INFLUXDB_API_ENDPOINT ]; then echo "Error: influxdb requires \$INFLUXDB_API_ENDPOINT to be specified" exit 1 fi - if [ -z $INFLUXDB_SEND_HOSTNAME ]; then - INFLUXDB_SEND_HOSTNAME=true - fi - if [ "$INFLUXDB_SEND_HOSTNAME" = true ]; then __influxdb_columns="[\"value\",\"host\"]" __influxdb_hostname=$(hostname) @@ -34,6 +36,6 @@ report () { docs () { echo "Send data to InfluxDB." - echo "\$INFLUXDB_API_ENDPOINT=" - echo "\$INFLUXDB_SEND_HOSTNAME=true" + echo "INFLUXDB_API_ENDPOINT=$INFLUXDB_API_ENDPOINT" + echo "INFLUXDB_SEND_HOSTNAME=$INFLUXDB_SEND_HOSTNAME" } \ No newline at end of file diff --git a/reporters/keen_io.sh b/reporters/keen_io.sh index 17ff0e7..618b824 100644 --- a/reporters/keen_io.sh +++ b/reporters/keen_io.sh @@ -1,6 +1,12 @@ #!/bin/sh -init() { +defaults () { + if [ -z $KEEN_IO_EVENT_COLLECTION ]; then + KEEN_IO_EVENT_COLLECTION=$(hostname) + fi +} + +start() { if [ -z $KEEN_IO_PROJECT_ID ]; then echo "Error: keen_io requires \$KEEN_IO_PROJECT_ID to be specified" exit 1 @@ -11,10 +17,6 @@ init() { exit 1 fi - if [ -z $KEEN_IO_EVENT_COLLECTION ]; then - KEEN_IO_EVENT_COLLECTION=$(hostname) - fi - __keen_io_api_url="https://api.keen.io/3.0" __keen_io_api_url="$__keen_io_api_url/projects/$KEEN_IO_PROJECT_ID" __keen_io_api_url="$__keen_io_api_url/events/$KEEN_IO_EVENT_COLLECTION" @@ -31,7 +33,7 @@ report () { docs () { echo "Send data to Keen IO (https://keen.io)." - echo "\$KEEN_IO_WRITE_KEY=" - echo "\$KEEN_IO_PROJECT_ID=" - echo "\$KEEN_IO_EVENT_COLLECTION=$(hostname)" + echo "KEEN_IO_WRITE_KEY=$KEEN_IO_WRITE_KEY" + echo "KEEN_IO_PROJECT_ID=$KEEN_IO_PROJECT_ID" + echo "KEEN_IO_EVENT_COLLECTION=$KEEN_IO_EVENT_COLLECTION" } \ No newline at end of file diff --git a/reporters/stathat.sh b/reporters/stathat.sh index cf467a8..faf4a31 100644 --- a/reporters/stathat.sh +++ b/reporters/stathat.sh @@ -1,6 +1,6 @@ #!/bin/sh -init () { +start () { if [ -z $STATHAT_API_KEY ]; then echo "Error: stathat requires \$STATHAT_API_KEY to be specified" exit 1 @@ -17,5 +17,5 @@ report () { docs () { echo "Send data to StatHat (https://www.stathat.com)." - echo "\$STATHAT_API_KEY=" + echo "STATHAT_API_KEY=$STATHAT_API_KEY" } \ No newline at end of file diff --git a/reporters/stdout.sh b/reporters/stdout.sh index c47913c..59f7e25 100644 --- a/reporters/stdout.sh +++ b/reporters/stdout.sh @@ -8,5 +8,5 @@ report () { } docs () { - echo "Print to standard output (e.g. the TTY you're running the script in)" + echo "Print to standard output, e.g. the TTY you're running the script in." } \ No newline at end of file