Skip to content

Instantly share code, notes, and snippets.

@pklaus
Last active February 28, 2024 01:46
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pklaus/cfd9719cfd4073cc3d73 to your computer and use it in GitHub Desktop.
Save pklaus/cfd9719cfd4073cc3d73 to your computer and use it in GitHub Desktop.
S.M.A.R.T. to DB – A Python tool to store your HDD's SMART values in an SQLite database.
#!/usr/bin/env python
# -*- encoding: UTF8 -*-
""" read out S.M.A.R.T. values out of the database and plot them using matplotlib
<http://matplotlib.sourceforge.net/examples/pylab_examples/anscombe.html>
"""
from pylab import *
from os import geteuid
import sys
import pdb
import smartToDB
def main(*args):
if geteuid() != 0:
print("You must be root to run this script.")
sys.exit(1)
if len(args)<2:
print("usage: "+args[0]+" t / s")
sys.exit(1)
# meaning of the positions in the returned list:
dev_pos=1
date_pos=2
temperature_pos=3
seek_err_pos=4
#
date, temperature, seek_err, style = dict(), dict(), dict(), dict()
datasets = smartToDB.get_all_datasets()
styles, i = ['bo','go','ro'], 0 # plot styles
for dataset in datasets:
name = dataset[dev_pos]
try:
date[name]
except:
date[name], temperature[name], seek_err[name] = [], [], []
style[name] = styles[i%len(styles)]
i = i+1
date[name].append(dataset[date_pos])
temperature[name].append(dataset[temperature_pos])
seek_err[name].append(dataset[seek_err_pos])
for name in date.keys():
if args[1]=="t":
# <http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.plot_date>
plot_date(date[name], temperature[name], fmt=style[name], tz=None, xdate=True, ydate=False, label=name)
title("Temperature of the HDDs over time")
elif args[1]=="s":
plot_date(date[name], seek_err[name], fmt=style[name], tz=None, xdate=True, ydate=False, label=name)
title("Seek errors of the HDDs over time")
else:
print("no proper action defined!")
sys.exit(1)
legend()
grid()
show()
if __name__ == "__main__":
main(*sys.argv)
#!/usr/bin/env python
# -*- encoding: UTF8 -*-
""" store S.M.A.R.T. values in a database
I should consider to record all SMART parameters.
This python script has to be run as root! You may set it up as a cron job (to be run every hour) using crontab:
sudo crontab -e
0 * * * * /home/pklaus/b/smartToDB.py
"""
import sqlite3
from os import remove, listdir, WEXITSTATUS, path, makedirs, geteuid
from commands import getstatusoutput
from re import search
from shlex import shlex
import sys
from datetime import datetime
import pdb
__author__ = "Philipp Klaus"
__copyright__ = "Copyright 2015 Philipp Klaus"
__credits__ = ""
__license__ = "GPL"
__version__ = "2.0"
__maintainer__= ""
__email__ = "philipp.klaus AT gmail.com"
__status__ = "Development" # Prototype Production
DATABASE_FILE = path.expanduser('~root/.smartToDB/smartToDB.sqlite')
def main(*args):
if geteuid() != 0:
print("You must be root to run this script.")
sys.exit(1)
connect_db()
# now query S.M.A.R.T. status and insert new datasets to the DB:
for drive in [dev for dev in listdir("/dev/") if search("^[hs]d.$", dev) != None]: # can also done with filter()... see commit 26c34437bbe4663547fed08202b08d8ca97ed783
#insert_smart(drive)
drive = '/dev/'+drive
# absolute path for smartctl (for compatibility reasons with cron). See "It works from the command line but not in crontab" on http://www.unix.com/answers-frequently-asked-questions/13527-cron-crontab.html
(status, txt) = getstatusoutput('/usr/sbin/smartctl -a -d ata '+drive)
# print WEXITSTATUS(status) #exit status of command
if WEXITSTATUS(status) == 2:
print "Please run "+args[0]+" script as root."
sys.exit(1)
temperature, seek_error_rate = 0, 0
for line in txt.split("\n"):
status_splitter = shlex(line, posix=True) # we need shlex here instead of .split(" ") as the string is separated by MULTIPLE spaces.
status_splitter.whitespace_split = True
if search("^194 ", line):
temperature = int(list(status_splitter)[9])
if search("^ 7 ", line):
seek_error_rate = int(list(status_splitter)[9])
insert_smart((drive,datetime.now(),temperature,seek_error_rate))
def create_initial_database(db):
curs = db.cursor()
# Create
curs.execute('CREATE TABLE smart (id INTEGER PRIMARY KEY, drive_address TEXT, time TIMESTAMP , temperature INTEGER, seek_error_rate INTEGER)')
db.commit()
return db
def connect_db():
try:
open(DATABASE_FILE)
db = sqlite3.connect(DATABASE_FILE, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
except:
if not path.exists(path.split(DATABASE_FILE)[0]):
makedirs(path.split(DATABASE_FILE)[0])
db = sqlite3.connect(DATABASE_FILE, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
create_initial_database(db)
print "created inital database."
# "detect_...": to use the default adapters for datetime.date and datetime.datetime see http://docs.python.org/library/sqlite3.html#default-adapters-and-converters
return db
def insert_smart(drive):
conn = connect_db()
curs = conn.cursor()
# structure of table ta: id INTEGER PRIMARY KEY, drive_address TEXT, time TIMESTAMP , temperature REAL, seek_errors INTEGER
curs.execute('INSERT INTO smart VALUES (NULL,?,?,?,?)',drive)
conn.commit()
def delete_db():
try:
remove(DATABASE_FILE)
except:
print "error while trying to delete sqlite database " + DATABASE_FILE
def get_all_datasets():
curs=connect_db().cursor()
curs.execute('SELECT * from smart')
return curs
def print_all_datasets():
for ds in get_all_datasets():
print ds
if __name__ == "__main__":
main(*sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment