--- /dev/null
+#!/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 <the.art.of.server@gmail.com>]"
+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
+