Last active
February 28, 2024 01:46
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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