Skip to content

Instantly share code, notes, and snippets.

@Andersson007
Last active September 10, 2019 08:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Andersson007/31d6a8741089ae34c4651902e1785614 to your computer and use it in GitHub Desktop.
Save Andersson007/31d6a8741089ae34c4651902e1785614 to your computer and use it in GitHub Desktop.
The script collects, analyzes and sends a mail notification about filesystem space situation.
#!/usr/bin/python3
# space_stat.py - collects, analyses and sends
# a mail notification about filesystem space situation
#
# Author: Andrey Klychkov aaklychkov
# Version: 1.0
# Date: 2018-07-26
# Licence: Copyleft free software
# Requirements: Python3+
#
# Use it by a cron daily job: ./space_stat.py
# If something wrong happens, just clear or fix
# a corresponding stat file.
#
# Configuration:
# 1. Define desired file systems in the FILE_SYSTEMS dict
# 2. Set up your mail server account parameters in
# the 'Mail params' section
#
# Notification output example:
# ----------------------------
# 2018.04.05 10:34 test-server.lan: Disk space usage statistics
#
# **/mnt/ssd_db**, space usage stat:
# total size: 2.33 TB
# available: 1.18 TB
# one day growth: 93.57 GB
# ------------
# ESTIMATE:
# ------------
# free space will end in 12 day(s) -> 2018.04.17
# ------------
# 7 days average growth: 93.13 GB
# 7 days total growth: 651.93 GB
# 30 days average growth: 93.13 GB
# 30 days total growth: 838.19 GB
import datetime
import math
import os
import smtplib
import socket
import subprocess
import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
########################
# PARAMETERS BLOCK #
########################
# Main params:
# {'/path/to/filesystem': '/path/to/stat/file'}
FILE_SYSTEMS = {'/mnt/ssd_db': '/var/lib/pgsql/andreyk/stat_space_ssd.db',
'/mnt/hdd_db': '/var/lib/pgsql/andreyk/stat_space_hdd.db'}
# Common params:
HOSTNAME = socket.gethostname()
NOW = datetime.datetime.now()
DATE = NOW.strftime('%Y%m%d')
TIME = NOW.strftime('%Y.%m.%d %H:%M')
# Mail params:
SEND_MAIL = 1
MAIL_SBJ = '%s: Disk space usage statistics' % HOSTNAME
SENDER = 'report.account@example.com'
RECIPIENT = ['mymail@mydomain.com']
SMTP_SRV = 'smtp.gmail.com'
SMTP_PORT = 587
SMTP_PASS = 'pass_of_report.account@_here'
#####################
# FUCTION BLOCK #
#####################
def send_mail(sbj, ms):
if SEND_MAIL:
msg = MIMEMultipart()
msg['Subject'] = (sbj)
msg['From'] = 'root@%s' % HOSTNAME
msg['To'] = RECIPIENT[0]
body = MIMEText(ms, 'plain')
msg.attach(body)
smtpconnect = smtplib.SMTP(SMTP_SRV, SMTP_PORT)
smtpconnect.starttls()
smtpconnect.login(SENDER, SMTP_PASS)
smtpconnect.sendmail(SENDER, RECIPIENT, msg.as_string())
smtpconnect.quit()
else:
pass
def get_pretty_size(size_bytes):
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return "%s %s" % (s, size_name[i])
def db_write(db_file, entry):
db = open(db_file, 'a')
try:
db.write(entry)
finally:
db.close()
def db_read(db_file):
# If there is an abnormal number of rows
# in a stat file or it has an abnormal size,
# exit with an error code
# to prevent out of memory:
ROWS_LIMIT = 100000
# Max stat file size in bytes:
FILE_MAX_SIZE = 104857600
db_file_size = os.path.getsize(db_file)
if db_file_size > FILE_MAX_SIZE:
msg = "Error: size of %s is abnormal: %s" % (
db_file, get_pretty_size(db_file_size))
print(msg)
send_mail(MAIL_SBJ, msg)
sys.exit(1)
db = open(db_file, 'r')
r_count = 0
for r in db:
r_count += 1
if r_count > ROWS_LIMIT:
msg = "Error: file %s contets too many rows: %s" % (
db_file, r_count)
print(msg)
send_mail(MAIL_SBJ, msg)
sys.exit(1)
db.seek(0)
# Make an array of rows in memory:
try:
stat = db.readlines()
return stat
finally:
db.close()
if __name__ == '__main__':
report = ['%s %s\n\n' % (TIME, MAIL_SBJ)]
for fs in FILE_SYSTEMS:
# Get stat about filesystem size:
statfs = os.statvfs(fs)
total_fs_size = statfs.f_frsize * statfs.f_blocks
free_fs_size = statfs.f_frsize * statfs.f_bfree
db_file = FILE_SYSTEMS.get(fs)
f_content = db_read(db_file)
# If a stat file is empty, nothing to compare,
# write the first string and exit:
if not f_content:
entry = '%s,%s,%s\n' % (DATE, free_fs_size, 0)
db_write(db_file, entry)
continue
prev_free_fs_size = int(f_content[-1].split(',')[1])
if free_fs_size > prev_free_fs_size:
growth = -(free_fs_size - prev_free_fs_size)
else:
growth = (free_fs_size - prev_free_fs_size) * -1
entry = '%s,%s,%s\n' % (DATE, free_fs_size, growth)
# Write current stat to a stat file:
db_write(db_file, entry)
# Get stat from a file::
stat = db_read(db_file)
# Count total amount of growth for last 7 days:
amount = 0
for line in stat[-7:]:
day_growth = int(line.split(',')[2])
if day_growth > 0:
amount += day_growth
# Calculate average 7 days growth:
if len(stat) < 7:
average_7growth = amount // len(stat)
else:
average_7growth = amount // 7
# Calculate estimated end space time:
if average_7growth:
estim_ttl = int(free_fs_size / average_7growth)
print(free_fs_size)
print(average_7growth)
print(estim_ttl)
if estim_ttl > 0 and estim_ttl < 3650:
end_date = (NOW + datetime.timedelta(
days=estim_ttl)).strftime('%Y.%m.%d')
else:
estim_ttl = '--'
end_date = 'never'
else:
estim_ttl = '--'
end_date = 'never'
tot_growth7 = get_pretty_size(amount)
av_growth7 = get_pretty_size(average_7growth)
# Calculate average 30 days growth:
if len(stat) > 7:
amount = 0
for line in stat[-30:]:
day_growth = int(line.split(',')[2])
if day_growth > 0:
amount += day_growth
if len(stat) < 30:
average_30growth = amount / len(stat)
else:
average_30growth = amount / 30
tot_growth30 = get_pretty_size(amount)
av_growth30 = get_pretty_size(average_30growth)
else:
tot_growth30 = 'no data'
av_growth30 = 'no data'
# Make a part of the mail report for a specific fs:
total_size = get_pretty_size(total_fs_size)
available = get_pretty_size(free_fs_size)
if growth > 0:
pretty_growth = get_pretty_size(growth)
else:
pretty_growth = '0 B'
sep = ('-' * 12) + '\n'
msg = ['**%s**, space usage stat:\n' % fs,
'total size: %s\n' % total_size,
'available: %s\n' % available,
'one day growth: %s\n' % pretty_growth,
sep,
'ESTIMATE:\n',
sep,
'free space will end in %s day(s) -> %s\n' % (
estim_ttl, end_date),
sep,
'7 days average growth: %s\n' % av_growth7,
'7 days total growth: %s\n' % tot_growth7,
'30 days average growth: %s\n' % av_growth30,
'30 days total growth: %s\n\n' % tot_growth30]
for i in msg:
report.append(i)
for i in report:
print(i, end='')
# Make a body of the mail report:
report_msg = ''.join(report)
# Send the mail report:
send_mail(MAIL_SBJ, report_msg)
sys.exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment