File: //bin/ntplogtemp
#! /usr/bin/python3 -s
# -*- coding: utf-8 -*-
"""\
usage: ntplogtemp [-h] [-l LOGFILE] [-o] [-q] [-v] [-w WAIT] [-V]
Program to log system temperatures
optional arguments:
-h, --help show this help message and exit
-l LOGFILE, --logfile LOGFILE
append log data to LOGFILE instead of stdout
-o, --once Run the output once and exit
-q, --quiet be quite
-v, --verbose be verbose
-w WAIT, --wait WAIT Set delay time in seconds, default is 60
-V, --version show program's version number and exit
See the manual page for details.
"""
from __future__ import print_function, division
import argparse
import glob
import logging
import logging.handlers
import os
import re
import subprocess
import sys
import time
class logfile_header_class(logging.handlers.TimedRotatingFileHandler):
'A class to modify the file logging handler.'
def doRollover(self):
'function to add header to new file on rotation.'
if str is bytes:
super(logfile_header_class, self).doRollover()
else:
super().doRollover()
self.stream.write('# time, sensor, value\n')
def run_binary(cmd):
"""\
Run a binary
Return output if good, None if bad
"""
try:
# sadly subprocess.check_output() is not in Python 2.6
# so use Popen()
# this throws an exception if not found
proc = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)
output = proc.communicate()[0].split("\n")
if proc.returncode:
# non-zero return code, fail
return None
except:
if args.verbose:
sys.stderr.write("Unable to run %s binary\n" % cmd[0])
# throws exception
return None
return output
class CpuTemp:
"Sensors on the CPU Core"
has_sensors = False
def __init__(self):
# check for sensors binary
ret = run_binary(["sensors", "-h"])
if ret is not None:
self.has_sensors = True
# pattern that matches the string that has the cpu temp
self._pattern = re.compile('^\s+temp\d+_input:\s+([\d\.]+).*$')
def get_data(self):
"Collects the data and return the output as an array"
if not self.has_sensors:
return None
_index = 0
_data = []
# grab the needed output
output = run_binary(["sensors", "-u"])
if output is not None:
for record in output:
match = self._pattern.match(record)
if match and match.group(1):
_now = int(time.time())
_cpu_temperature = match.group(1)
_data.append('%d LM%s %s' % (_now, _index,
_cpu_temperature))
_index += 1
else:
self.has_sensors = False
if args.verbose:
sys.stderr.write("No sensors returned temperatures. ",
"Have you run sensors-detect?\n")
return _data
class SmartCtl:
"Sensor on the Hard Drive"
_drives = []
has_smartctl = False
def __init__(self):
ret = run_binary(["smartctl", "-h"])
if ret is not None:
self.has_smartctl = True
if self.has_smartctl:
# Which drive to watch
for child in glob.glob('/dev/sd?'):
self._drives.append(child)
self._drives = sorted(self._drives)
def get_data(self):
"Collects the data and return the output as an array"
if not self.has_smartctl:
return None
data = []
for _device in self._drives[:]:
output = run_binary(["smartctl", "-A", _device])
if output is None:
# do not keep trying on failure
self._drives.remove(_device)
else:
for line in output:
if line.startswith('194 '):
now = int(time.time())
temp = line.split()[9]
data.append('%d %s %s' % (now, _device, temp))
return data
class Temper:
"""\
Reads 'temper-poll -c' for room temperature data.
Before you can use this class you must have a TEMPer USB thermometer
plugged in, and the temper-python package must be installed and configured.
See their documentation for that procedure.
"""
has_temper = False
def __init__(self):
# check for sensors binary
ret = run_binary(["temper-poll", "-h"])
if ret is not None:
self.has_temper = True
def get_data(self):
"Collects the data and return the output as an array"
if not self.has_temper:
return None
data = []
_device = 'TEMPER0'
# only one device can read the TEMPer at a time
# collisions will happen, so retry a few times
for attempt in range(0, 3):
# grab the needed output
output = run_binary(["temper-poll", "-c"])
try:
# make sure it is a temperature
temp = float(output[0])
now = int(time.time())
data.append('%d %s %s' % (now, _device, temp))
break
except (TypeError, ValueError):
# bad data, ignore it, for a bit
if args.verbose:
sys.stderr.write("TEMPer-poll failed\n")
if not data:
self.has_temper = False
return data
class ZoneTemp:
"Zone sensors"
def __init__(self):
base_dir = '/sys/class/thermal/thermal_zone?/'
self.zones = []
for child in glob.glob(base_dir):
self.zones.append(child)
def get_data(self):
"Collects the data and return the output as an array"
_zone = 0
_data = []
for zone in self.zones:
_zone_data = open(os.path.join(zone, 'temp'))
for line in _zone_data:
temp = float(line) / 1000
_now = int(time.time())
_data.append('%d ZONE%s %s' % (_now, _zone, temp))
_zone += 1
_zone_data.close()
return _data
# Work with argvars
parser = argparse.ArgumentParser(description="Temperature sensor daemon",
epilog="""See the manual page for details.""")
parser.add_argument('-l', '--logfile',
dest='logfile',
help="append log data to LOGFILE instead of stdout",
nargs=1)
parser.add_argument('-o', '--once',
dest='once',
help="Run the output once and exit",
action='store_true')
parser.add_argument('-q', '--quiet',
action="store_true",
dest='quiet',
help="be quite")
parser.add_argument('-v', '--verbose',
action="store_true",
dest='verbose',
help="be verbose")
parser.add_argument('-w', '--wait',
default=[60],
dest='wait',
help="Set delay time in seconds, default is 60",
nargs=1,
type=int)
parser.add_argument('-V', '--version',
action="version",
version="ntplogtemp ntpsec-1.2.2a")
args = parser.parse_args()
def logging_setup():
"Create logging object"
logFormat = logging.Formatter('%(message)s')
# Create logger for cpuTemp
tempLogger = logging.getLogger()
tempLogger.setLevel(logging.INFO)
# Create file handler
if args.logfile:
# log to logfile
file = logfile_header_class(
args.logfile[0],
utc=True,
when='midnight',
interval=1)
else:
# log to stdout
file = logging.StreamHandler(sys.stdout)
file.setLevel(logging.INFO)
# Create the formatter and add it to the handler
file.setFormatter(logFormat)
# Add the handler to the logger
tempLogger.addHandler(file)
return tempLogger
def logData(log, data):
"log the data"
if data is not None:
for _item in data:
log.info(_item)
def log_data():
"Write all temperature readings to one file"
# Create objects
cpu = CpuTemp()
zone = ZoneTemp()
hdd = SmartCtl()
temper = Temper()
# Create the logger instance
Logger = logging_setup()
# Create data layout
logData(Logger, ["# time, sensor, value"])
# Write data to their respective logs
while True:
logData(Logger, zone.get_data())
logData(Logger, cpu.get_data())
logData(Logger, hdd.get_data())
logData(Logger, temper.get_data())
if args.once:
sys.exit(0)
time.sleep(args.wait[0])
args = parser.parse_args()
if os.getuid():
sys.stderr.write("You must be root!\n")
sys.exit(1)
try:
log_data()
except KeyboardInterrupt:
print("") # be nice to bash