From 82bfe30b9fa5110627d50252b686ac83a1304313 Mon Sep 17 00:00:00 2001 From: Andre Ramnitz Date: Mon, 12 Aug 2024 18:12:13 +0200 Subject: [PATCH] bin: add sensors-plotter script --- local/bin/sensors-plotter | 210 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100755 local/bin/sensors-plotter diff --git a/local/bin/sensors-plotter b/local/bin/sensors-plotter new file mode 100755 index 00000000..895e59fb --- /dev/null +++ b/local/bin/sensors-plotter @@ -0,0 +1,210 @@ +#!/bin/ksh +# upstream url: https://github.com/ezonakiusagi/sensors-plotter + +## +## USAGE definition +## +# usage: sensors-plotter [options] +# -c, --chip=chip select sensor chip +# -s, --sensor=sensor select specific sensor within selected chip +# -w, --width=seconds the number of seconds of the width of plot +# -t, --title=title set the title of the plot +# -S, --stats turn on statistics at bottom of screen +# -C, --continue=file continue from previous data stream file +USAGE=$'[-?\n@(#)$Id: 0.1\n]' +USAGE+="[-author?Art of Server ]" +USAGE+="[-copyright?Copyright (C) 2019 The Art of Server.]" +USAGE+="[-license?GNU General Public License v3.]" +USAGE+="[+NAME?sensors-plotter --- plot graphs of sensors data stream in terminal.]" +USAGE+="[+DESCRIPTION?sensor-plotter is a small script to plot graphs in a terminal " +USAGE+="using a stream of data points from the sensors command.]" +USAGE+="[c:chip]:[sensor chip?Select the sensor chip to probe (required).]" +USAGE+="[s:sensor]:[sensor?Select the sensor to probe (required).]" +USAGE+="[w:width]:[width?The number of seconds of the width of plot (default=120).]" +USAGE+="[t:title]:[title?The title of the plot.]" +USAGE+="[S:stats?turn on statistics at bottom of screen.]" +USAGE+="[C:continue]:[file?continue from previous data stream file (default is to start new data stream).]" + +## +## configuration settings +## +# option flags +OPT_CHIP=0 +OPT_SENSOR=0 +OPT_WIDTH=0 +OPT_TITLE=0 +OPT_STATS=0 +OPT_CONT=0 +# variables +CHIP= +SENSOR= +WIDTH=120 +TITLE= +DATALOG=./sensors-data.log +typeset -f STAT_max= +typeset -f STAT_min= +typeset -f STAT_avg= +typeset -f STAT_last= +GNUPLOT_INPUT= +# external dependencies +typeset -A UTILS +UTILS[rm]=$(which rm 2>/dev/null) +UTILS[grep]=$(which grep 2>/dev/null) +UTILS[awk]=$(which awk 2>/dev/null) +UTILS[tail]=$(which tail 2>/dev/null) +UTILS[head]=$(which head 2>/dev/null) +UTILS[sort]=$(which sort 2>/dev/null) +UTILS[wc]=$(which wc 2>/dev/null) +UTILS[mktemp]=$(which mktemp 2>/dev/null) +UTILS[sensors]=$(which sensors 2>/dev/null) +UTILS[gnuplot]=$(which gnuplot 2>/dev/null) + +## +## functions +## + +#------------------------------------------------------------------------------ +# description: check availability of external utilities +# inputs: (global) UTILS[*] +# outputs: error messages to stdout +# exit: 0=success, 1=failed +#------------------------------------------------------------------------------ +function check_utils +{ + typeset key= + typeset -i err=0 + + for key in ${!UTILS[*]} + do + # if empty, means not in PATH + if [[ -z ${UTILS[$key]} ]] ; then + (( err++ )) + print -u2 "Error: could not find $key in your PATH." + print -u2 "Hint: maybe need to install ${key}?" + continue + fi + + # check file exists and executable + if [[ ! -f ${UTILS[$key]} ]] ; then + (( err++ )) + print -u2 "Error: could not find file ${UTILS[$key]}." + fi + if [[ ! -x ${UTILS[$key]} ]] ; then + (( err++ )) + print -u2 "Error: file ${UTILS[$key]} is not executable." + fi + done + (( err == 0 )) && return 0 || return 1 +} + +#------------------------------------------------------------------------------ +# description: collect sensor data and stream to $_datalog +# inputs: $1=chip, $2=sensor, $datalog +# outputs: stream sensor data to $datalog +# exit: none +#------------------------------------------------------------------------------ +function collect_sensor_data +{ + typeset _chip=$1 + typeset _sensor=$2 + typeset _datalog=$3 + typeset _reading= + + while true + do + _reading=$(${UTILS[sensors]} -u $_chip \ + | ${UTILS[awk]} -v sensor=$_sensor '$1==sensor":" {print $2}') + print $_reading >> $_datalog + sleep 1 + done +} + +## +## main +## + +SELF=$0 +PID=$$ + +# check external dependencies +check_utils || exit 1 + +# process command line args +while getopts "$USAGE" option +do + case $option in + c|chip) OPT_CHIP=1 && CHIP=$OPTARG ;; + s|sensor) OPT_SENSOR=1 && SENSOR=$OPTARG ;; + w|width) OPT_WIDTH=1 && WIDTH=$OPTARG ;; + t|title) OPT_TITLE=1 && TITLE=$OPTARG ;; + S|stats) OPT_STATS=1 ;; + C|continue) OPT_CONT=1 && DATALOG=$OPTARG ;; + esac +done + +if (( OPT_CONT == 0 )) ; then + DATALOG=$(${UTILS[mktemp]} ./sensor-data.XXXXX) +fi +# check DATALOG is writable +if [[ ! -w $DATALOG ]] ; then + print -u2 "Error: cannot write to $DATALOG" + exit 1 +fi + +# fork collect_sensor_data() in background +collect_sensor_data $CHIP $SENSOR $DATALOG & +COLLECTOR_PID=$! +trap "kill $COLLECTOR_PID && print \"\nsensor data=$DATALOG\"" EXIT + +# wait for $DATALOG to have data +while (( $(${UTILS[wc]} -l <$DATALOG) == 0 )) +do + sleep 1 +done + +while true +do + # get size of terminal + read rows cols < <(stty size) + # if OPT_STATS=1, reduce rows by 3 for space to print max, min, avg, last. + if (( OPT_STATS == 1 )) ; then + (( rows -= 3 )) + fi + GNUPLOT_INPUT="set terminal dumb size $cols, $rows;" + + # if $STAT_max is not empty, base yrange max on 120% of STAT_max + if [[ -n $STAT_max ]] ; then + (( yrange_max = STAT_max * 1.20 )) + GNUPLOT_INPUT+="set yrange [0:$yrange_max];" + else + GNUPLOT_INPUT+="set yrange [0:*];" + fi + + # if TITLE is not empty, set it + if [[ -n $TITLE ]] ; then + GNUPLOT_INPUT+="set title \"$TITLE\";" + fi + + # wrap up final bits of GNUPLOT_INPUT + GNUPLOT_INPUT+="set nokey; plot '-' with lines" + + ${UTILS[tail]} -$WIDTH $DATALOG \ + | ${UTILS[gnuplot]} -e "$GNUPLOT_INPUT" + + # if OPT_STATS=1, print stats at bottom + if (( OPT_STATS == 1 )) ; then + # get last value + STAT_last=$(${UTILS[tail]} -1 $DATALOG) + # sort, and get max/min/avg + SORTED_DATA=$(${UTILS[mktemp]} /var/tmp/sensors-plotter.XXXXX) + ${UTILS[sort]} -n $DATALOG > $SORTED_DATA + STAT_min=$(${UTILS[head]} -1 $SORTED_DATA) + STAT_max=$(${UTILS[tail]} -1 $SORTED_DATA) + STAT_avg=$(${UTILS[awk]} '{ total += $1; count++ } END { print total/count }' $SORTED_DATA) + printf "\tlast = %f, max = %f, min = %f, avg = %f\n" $STAT_last $STAT_max $STAT_min $STAT_avg + ${UTILS[rm]} -f $SORTED_DATA + fi + + sleep 1 +done + -- 2.51.2