a040909b |
#!/usr/bin/python3 -u
#
# Module: rrdbase.py
#
# Description: This module acts as an interface between the agent module
# the rrdtool command line app. Interface functions provide for updating
# the rrdtool database and for creating charts. This module acts as a
# library module that can be imported into and called from other
# Python programs.
#
# Copyright 2021 Jeff Owrey
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public Licensef
# along with this program. If not, see http://www.gnu.org/license.
#
# Revision History
# * v30 17 Oct 2021 by J L Owrey; first release
#
#2345678901234567890123456789012345678901234567890123456789012345678901234567890
import subprocess
import time
class rrdbase:
def __init__(self, rrdFile, chartsDirectory, chartWidth, \
chartHeight, verboseMode, debugMode):
"""Initialize instance variables that remain constant throughout
the life of this object instance. These items are set by the
calling module.
Parameters:
rrdFile - the path to the rrdtool database file
chartsDirectory - the path to the folder to contain charts
chartWidth - the width of charts in pixels
chartHeight - the height of charts in pixels
verboseMode - verbose output
debugMode - full debug output
Returns: nothing
"""
self.rrdFile = rrdFile
self.chartsDirectory = chartsDirectory
self.chartWidth = chartWidth
self.chartHeight = chartHeight
self.verboseMode = verboseMode
self.debugMode = debugMode
## end def
def getTimeStamp():
"""Sets the error message time stamp to the local system time.
Parameters: none
Returns: string containing the time stamp
"""
return time.strftime('%m/%d/%Y %H:%M:%S', time.localtime())
## end def
def getEpochSeconds(sTime):
"""Converts the time stamp supplied in the weather data string
to seconds since 1/1/1970 00:00:00.
Parameters:
sTime - the time stamp to be converted must be formatted
as %m/%d/%Y %H:%M:%S
Returns: epoch seconds
"""
try:
t_sTime = time.strptime(sTime, '%m/%d/%Y %H:%M:%S')
except Exception as exError:
print('%s getEpochSeconds: %s' % \
(rrdbase.getTimeStamp(), exError))
return None
tSeconds = int(time.mktime(t_sTime))
return tSeconds
## end def
def updateDatabase(self, *tData):
"""Updates the rrdtool round robin database with data supplied in
the weather data string.
Parameters:
tData - a tuple object containing the data items to be written
to the rrdtool database
Returns: True if successful, False otherwise
"""
# Get the time stamp supplied with the data. This must always be
# the first element of the tuple argument passed to this function.
tData = list(tData)
date = tData.pop(0)
# Convert the time stamp to unix epoch seconds.
try:
time = rrdbase.getEpochSeconds(date)
# Trap any data conversion errors.
except Exception as exError:
print('%s updateDatabase error: %s' % \
(rrdbase.getTimeStamp(), exError))
return False
# Create the rrdtool command for updating the rrdtool database. Add a
# '%s' format specifier for each data item remaining in tData.
# Note that this is the list remaining after the
# first item (the date) has been removed by the above code.
strFmt = 'rrdtool update %s %s' + ':%s' * len(tData)
strCmd = strFmt % ((self.rrdFile, time,) + tuple(tData))
if self.debugMode:
print('%s' % strCmd) # DEBUG
# Run the formatted command as a subprocess.
try:
subprocess.check_output(strCmd, stderr=subprocess.STDOUT, \
shell=True)
except subprocess.CalledProcessError as exError:
print('%s rrdtool update failed: %s' % \
(rrdbase.getTimeStamp(), exError.output.decode('utf-8')))
return False
if self.verboseMode and not self.debugMode:
print('database update successful')
return True
## end def
def createWeaGraph(self, fileName, dataItem, gLabel, gTitle, gStart,
lower, upper, addTrend, autoScale):
"""Uses rrdtool to create a graph of specified weather data item.
Graphs are for display in html documents.
Parameters:
fileName - name of graph file
dataItem - the weather data item to be graphed
gLabel - string containing a graph label for the data item
gTitle - string containing a title for the graph
gStart - time from now when graph starts
lower - lower bound for graph ordinate
upper - upper bound for graph ordinate
addTrend - 0, show only graph data
1, show only a trend line
2, show a trend line and the graph data
autoScale - if True, then use vertical axis auto scaling
(lower and upper parameters are ignored), otherwise use
lower and upper parameters to set vertical axis scale
Returns: True if successful, False otherwise
"""
gPath = self.chartsDirectory + fileName + '.png'
# Format the rrdtool graph command.
# Set chart start time, height, and width.
strCmd = 'rrdtool graph %s -a PNG -s %s -e \'now\' -w %s -h %s ' \
% (gPath, gStart, self.chartWidth, self.chartHeight)
# Set the range and scaling of the chart y-axis.
if lower < upper:
strCmd += '-l %s -u %s -r ' % (lower, upper)
elif autoScale:
strCmd += '-A '
strCmd += '-Y '
# Set the chart ordinate label and chart title.
strCmd += '-v %s -t %s ' % (gLabel, gTitle)
# Show the data, or a moving average trend line, or both.
strCmd += 'DEF:dSeries=%s:%s:AVERAGE ' % (self.rrdFile, dataItem)
if addTrend == 0:
strCmd += 'LINE1:dSeries#0400ff '
elif addTrend == 1:
strCmd += 'CDEF:smoothed=dSeries,86400,TREND LINE2:smoothed#006600 '
elif addTrend == 2:
strCmd += 'LINE1:dSeries#0400ff '
strCmd += 'CDEF:smoothed=dSeries,86400,TREND LINE2:smoothed#006600 '
# if wind plot show color coded wind direction
if dataItem == 'windspeedmph':
strCmd += 'DEF:wDir=%s:winddir:AVERAGE ' % (_RRD_FILE)
strCmd += 'VDEF:wMax=dSeries,MAXIMUM '
strCmd += 'CDEF:wMaxScaled=dSeries,0,*,wMax,+,-0.15,* '
strCmd += 'CDEF:ndir=wDir,337.5,GE,wDir,22.5,LE,+,wMaxScaled,0,IF '
strCmd += 'CDEF:nedir=wDir,22.5,GT,wDir,67.5,LT,*,wMaxScaled,0,IF '
strCmd += 'CDEF:edir=wDir,67.5,GE,wDir,112.5,LE,*,wMaxScaled,0,IF '
strCmd += 'CDEF:sedir=wDir,112.5,GT,wDir,157.5,LT,*,wMaxScaled,0,IF '
strCmd += 'CDEF:sdir=wDir,157.5,GE,wDir,202.5,LE,*,wMaxScaled,0,IF '
strCmd += 'CDEF:swdir=wDir,202.5,GT,wDir,247.5,LT,*,wMaxScaled,0,IF '
strCmd += 'CDEF:wdir=wDir,247.5,GE,wDir,292.5,LE,*,wMaxScaled,0,IF '
strCmd += 'CDEF:nwdir=wDir,292.5,GT,wDir,337.5,LT,*,wMaxScaled,0,IF '
strCmd += 'AREA:ndir#0000FF:N ' # Blue
strCmd += 'AREA:nedir#1E90FF:NE ' # DodgerBlue
strCmd += 'AREA:edir#00FFFF:E ' # Cyan
strCmd += 'AREA:sedir#00FF00:SE ' # Lime
strCmd += 'AREA:sdir#FFFF00:S ' # Yellow
strCmd += 'AREA:swdir#FF8C00:SW ' # DarkOrange
strCmd += 'AREA:wdir#FF0000:W ' # Red
strCmd += 'AREA:nwdir#FF00FF:NW ' # Magenta
##end if
if self.debugMode:
print('%s' % strCmd) # DEBUG
# Run the formatted rrdtool command as a subprocess.
try:
result = subprocess.check_output(strCmd, \
stderr=subprocess.STDOUT, \
shell=True)
except subprocess.CalledProcessError as exError:
print('rrdtool graph failed: %s' % (exError.output.decode('utf-8')))
return False
if self.verboseMode:
print('rrdtool graph: %s' % result.decode('utf-8')) #, end='')
return True
## end def
def createAutoGraph(self, fileName, dataItem, gLabel, gTitle, gStart,
lower, upper, addTrend, autoScale):
"""Uses rrdtool to create a graph of specified radmon data item.
Parameters:
fileName - name of file containing the graph
dataItem - data item to be graphed
gLabel - string containing a graph label for the data item
gTitle - string containing a title for the graph
gStart - beginning time of the graphed data
lower - lower bound for graph ordinate #NOT USED
upper - upper bound for graph ordinate #NOT USED
addTrend - 0, show only graph data
1, show only a trend line
2, show a trend line and the graph data
autoScale - if True, then use vertical axis auto scaling
(lower and upper parameters are ignored), otherwise use
lower and upper parameters to set vertical axis scale
Returns: True if successful, False otherwise
"""
gPath = self.chartsDirectory + fileName + ".png"
trendWindow = { 'end-1day': 7200,
'end-4weeks': 172800,
'end-12months': 604800 }
# Format the rrdtool graph command.
# Set chart start time, height, and width.
strCmd = "rrdtool graph %s -a PNG -s %s -e now -w %s -h %s " \
% (gPath, gStart, self.chartWidth, self.chartHeight)
# Set the range and scaling of the chart y-axis.
if lower < upper:
strCmd += "-l %s -u %s -r " % (lower, upper)
elif autoScale:
strCmd += "-A "
strCmd += "-Y "
# Set the chart ordinate label and chart title.
strCmd += "-v %s -t %s " % (gLabel, gTitle)
# Show the data, or a moving average trend line over
# the data, or both.
strCmd += "DEF:dSeries=%s:%s:LAST " % (self.rrdFile, dataItem)
if addTrend == 0:
strCmd += "LINE1:dSeries#0400ff "
elif addTrend == 1:
strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \
% trendWindow[gStart]
elif addTrend == 2:
strCmd += "LINE1:dSeries#0400ff "
strCmd += "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 " \
% trendWindow[gStart]
if self.debugMode:
print("%s" % strCmd) # DEBUG
# Run the formatted rrdtool command as a subprocess.
try:
result = subprocess.check_output(strCmd, \
stderr=subprocess.STDOUT, \
shell=True)
except subprocess.CalledProcessError as exError:
print("rrdtool graph failed: %s" % (exError.output.decode('utf-8')))
return False
if self.verboseMode:
print("rrdtool graph: %s" % result.decode('utf-8')) #, end='')
return True
##end def
## end class
|