Last active
September 10, 2019 08:54
-
-
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.
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/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