Rapport quotidien de votre serveur et plus…

Bonjour visiteur du jour.

Au moyen d’une subtile recherche Google, vous venez vous perdre ici et tant pis pour vous 🙂

Au menu du jour, comment se faire un rapport en pdf sur l’état de santé de son serveur et le recevoir sur sa boite mail.

Regarder tous les jours, les beaux graphiques réalisés pour nous par domoticz ça va bien un moment. Recevoir des alertes par sms en cas de problème c’est du déjà vu.  Je recherchais le moyen de recevoir par mail un rapport en pdf sur l’état de santé (à un instant T) de mon serveur domoticz ainsi qu’un certain nombre d’informations utiles pour moi.

Les utilisateurs domoticz trouveront ici l’information sur la date et l’heure de la mise en route du service ainsi que son pid et la conso mémoire.

Je laisse libre à chacun de juger l’utilité de ce type de rapport. L’idée de ce partage est de permettre d’explorer de nouvelles pistes.

Le rapport est un document pdf, tramis par mail via des tâches planifiées (cron).

Rapport

Et c’est ainsi que depuis lundi, je reçois tous les matins ce petit rapport sur ma boite mail.

Dans la première partie, on retrouve des infos sur la mémoire, cpu, disque etc. Dans la seconde, je place des informations pratiques. D’ailleurs, si j’oublie de commander des granulés ça va être ma fête 🙂

ça marche comment ?

La partie système est mise en œuvre grâce au module psutil de python dont l’emploi le plus connu est le logiciel glances de Nicolargo.

Un script python lance des requêtes json au serveur domoticz pour la partie granulés, edf, batterie des modules z-wave.

Enfin, le pdf est créé grâce à la librairie fpdf

Le code du programme rapport.py

#!/usr/bin/env python
# -*- coding:Utf-8 -*-
#
# Ce programme python prépare, met en forme et envoi par mail un document pdf reprenant des informations du serveur domoticz
# Utilise les modules psutil, request fpdf
# Version 1 de février 2015 par José
#
# Basé sur le travail de Christophe CARON  http://www.caron.ws/ pour la partie supervision psutils https://github.com/giampaolo/psutil
# Utilise le module de recherche de Patrice du site easydomoticz.fr
# Le code de la partie mail vient du site http://astuces.absolacom.com/developpement/envoyer-un-mail-avec-pieces-jointes-en-python/
# Merci à eux.
# Les paramètres liés à domoticz se trouvent sur un fichier (config.py) situé dans le même répertoire
#
##########
#
# Il faut préalablement installer les modules suivants:
# installer python-pip
# par sudo apt-get install python-pip
#
# installer request
# sudo pip install requests
#
# installer psutils
# sudo pip install psutil
#
# installer fpdf
# sudo apt-get install pypdf
#
###########

from fpdf import FPDF
import data
import datetime
import sys
import config
import time
import requests

# Récupération et formatage de la date pour donner le nom du fichier
datejour = datetime.datetime.now()
datejour = datejour.strftime('%d-%m-%Y %H-%M-%S')
title=u"Rapport d'état du serveur Domoticz"

# Classe PDF de pypdf
class PDF(FPDF):
        def header(self):
                #Arial bold 15
                self.set_font('Arial','B',15)
                #Calculate width of title and position
                w=self.get_string_width(title)+6
                self.set_x((210-w)/2)
                #Colors of frame, background and text
                self.set_draw_color(0,0,204)
                self.set_fill_color(255,153,102)
                self.set_text_color(0,0,0)
                #Thickness of frame (1 mm)
                self.set_line_width(1)
                #Title avec logo
                self.image('img/logo.jpg',10,6,30);
                self.cell(w,9,title,1,1,'C',1)
                #Line break
                self.ln(10)

        def footer(self):
                #Position at 1.5 cm from bottom
                self.set_y(-15)
                #Arial italic 8
                self.set_font('Arial','I',8)
                #Text color in gray
                self.set_text_color(128)
                #Page number
                self.cell(0,10,'Rapport du '+ datejour + ' - P'+str(self.page_no()),0,0,'C')

        def chapter_title(self,num,label):
                #Arial 11
                self.set_font('Arial','',11)
                #Background color
                self.set_fill_color(255,255,255)
                #Title avec image du rapsberry pi
                self.cell(0,5,label,0,1,'L',1)
                self.image('img/pi.jpg',110,42,65);
                #Line break
                self.ln(2)

        def chapter_body(self,name):
                #Arial 11
                self.set_font('Arial','',11)
                #Output justified text
                self.multi_cell(0,5,name)
                #Line break
                self.ln()
                #Mention in italics
                self.set_font('','I')

        def print_chapter(self,num,title,name):
                self.chapter_title(num,title)
                self.chapter_body(name)

        def chapitre_general_titre(self,num,texte):
                #Arial 11
                self.set_font('Arial','',11)
                #Background color
                self.set_fill_color(255,255,255)
                # Placement suivant l'image
                if num == 20:
                        self.image('img/edf.jpg',10,135,20)
                        self.set_y(136)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                if num == 21:
                        self.image('img/eo2.jpg',10,152,15)
                        self.set_y(153)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                if num == 22:
                        self.image('img/cuve.jpg',10,187,20)
                        self.set_y(188)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                if num == 23:
                        self.image('img/oregon.jpg',10,220,20)
                        self.set_y(220)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                #Line break
                self.ln(2)
                self.set_x(36)

        def chapitre_general(self,texte):
                #Arial 11
                self.set_font('Arial','',11)
                #Background color
                self.set_fill_color(255,255,255)
                #Abcisse
                self.set_x(36)
                #Texte à mettre dans le pdf
                self.cell(0,5,texte,0,1,'L',1)
                #Line break
                self.ln(2)
                self.set_x(36)

# Module de recherche de Patrice
def recherche(idx, recherche, decal, nb_car):

        # URL Domoticz a interroger pour recuperer les infos est type=devices&rid=XXX

        json_url='/json.htm?type=devices&rid='
        requete='http://'+config.domoticz_ip+':'+config.domoticz_port+json_url+str(idx)

        r=requests.get (requete)
        ch= r.text
        lgterme=len(str(recherche))
        trouve_a= ch.find(str(recherche))
        debut=trouve_a+lgterme

        resultat=ch[debut+int(decal):debut+int(decal)+int(nb_car)]

        return resultat

# Lance les requêtes au moyen du programme collecte

# Récupération EDF
# recherche(idx,recherche,decal,nb_car)
edf=recherche('180', 'Data', '5', '4')

# Récupération du nombre de granulés en stock
# Calcul du nombre de sac par rapport à la variable du fichier config
granules=recherche('164', 'Counter', '5', '2')
humidite=recherche('171', 'Humidity', '4', '2')
stock=int(config.qte_granules_total) - int(granules)
poids=int(granules) * int(config.poids_sac_granules)

# Récupération du volume et de la hauteur de la cuve
volume=recherche('177', 'Counter', '5', '4')
hauteur=recherche('176', 'Data', '5', '3')
temp=recherche('101', 'Data', '5', '3')

# Récupération de l'état des batteries
batsdb=recherche('169', 'BatteryLevel', '4', '3')

# Création du PDF
pdf=PDF()
pdf.set_title(title)
pdf.set_author(u'José')
pdf.add_page()

# Mise en forme des données système
pdf.print_chapter(1,u'Information mémoire (Disponible ' + data.mpourcent + u" %)","Total = " + data.mtotal + u" - Utilisée = " + data.mutilise + " - Libre = " + data.mlibre)
pdf.print_chapter(2,u'Information mémoire swap (Utilisé ' + data.mspourcent + u" %)","Total = " + data.mstotal + u" - Utilisée = " + data.msutilise + " - Libre = " + data.mslibre)
pdf.print_chapter(3,u'Information CPU',"Charge = " + data.usagecpu + u" % - Température = "+str(data.cpu_temperature)+u" °C")
pdf.print_chapter(4,u'Espace disque carte SD (utilisée à '+ data.pourcent + "%)","Total = " + data.total + u" - Utilisé = " + data.utilise + " - Libre = " + data.libre)
pdf.print_chapter(5,u'Le module Domoticz (pid ' + str(data.pid) + ') fonctionne depuis le ' + str(data.boot) + ' - Status : ' + data.status,u'Ce soft utilise '+ str(data.mem) + u' de mémoire.')
pdf.print_chapter(6,u'Temps de fonctionnement du serveur : '+data.demarrage,'')

# Trace une ligne pour séparer les données système du reste
pdf.dashed_line(10,130,200,130,2,2)

# Mise en forme des données générales
# EDF
pdf.chapitre_general_titre(20,u"La valeur de la consommation EDF de la journée d'hier s'élève à " + edf + u" euros (hors abo).")

# Granulés
pdf.chapitre_general_titre(21,u"Il reste " + str(stock) + u" sacs de granulés en stock sur " + str(config.qte_granules_total) + '.')
pdf.chapitre_general(u"Représente une consommation de "+ str(granules) + u" sacs à ce jour, soit " + str(poids) + " kg." )
pdf.chapitre_general(u"Le niveau d'humidité ambiante du stockage est de " + humidite + u" %.")

# Ajoute un message d'alerte pour lancer la commande
if int(stock) < 20:
        # Place la couleur du texte en rouge
        pdf.set_text_color(204,0,0)
        pdf.chapitre_general(u"Attention le stock est trop bas. Il faut lancer une commande !")
        # Remet la couleur en noir
        pdf.set_text_color(0,0,0)

# Cuve
pdf.chapitre_general_titre(22,u'Le volume de la cuve est de '+ volume + " m3.")
pdf.chapitre_general(u"La hauteur d'eau est de " + hauteur + " cm.")
pdf.chapitre_general(u"La température de l'eau est de "+ temp + u"°C.")

# Batteries des sondes Oregon
pdf.chapitre_general_titre(23,u'Etat batterie sonde salle de bains: '+ batsdb + " %.")
pdf.chapitre_general(u"Etat batterie sonde cabane de jardin: "+ batsdb + " %.")
pdf.chapitre_general(u"Etat batterie sonde congélateur: "+ batsdb + " %.")

# Lancement de la création du rapport
pdf.output('Rapport.pdf','F')
rapport.py

Le programme d’envoi de mail

#!/usr/bin/env python
# -*- coding:Utf-8 -*-
#
# Récupération du code de la page
# http://astuces.absolacom.com/developpement/envoyer-un-mail-avec-pieces-jointes-en-python/
# Merci à son auteur
#
########

import smtplib
import os
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders

# Variables
files = ['Rapport.pdf'] # Pièces jointes avec leur chemin
destinataires = ['destinataire1@free.fr', 'destinataire2@free.fr'] # Mettre des [] Adresses mail séparées par des virgules
expediteur = 'expediteur@free.fr'
titre = u"Rapport journalier Domoticz"
message = u"Bonjour a tous, \nVoici votre rapport journalier du systeme domoticz. \nBonne journee."
smtp = 'smtp.free.fr'

def send_mail(send_from, send_to, subject, text, files=[], server='localhost'):

        assert type(send_to)==list
        assert type(files)==list
        msg = MIMEMultipart()
        msg['From'] = send_from
        msg['To'] = COMMASPACE.join(send_to)
        msg['Date'] = formatdate(localtime=True)
        msg['Subject'] = subject
        msg.attach( MIMEText(text) )

        for f in files:
                part = MIMEBase('application', "octet-stream")
        #print "Transmission du mail avec la pièce jointe : " + f
        part.set_payload( open(f,"rb").read())
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))

        msg.attach(part)

        smtp = smtplib.SMTP(server)
        smtp.sendmail(send_from, send_to, msg.as_string())
        smtp.close()

# Module d'envoi de mail
send_mail(expediteur, destinataires, titre, message, files, smtp)
mail.py

Le programme de supervision data.py qui utilise la librairie psutil:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
#########
#
# Utilisation du travail de Christophe CARON
# http://www.caron.ws/
#
# Récupération des données pour le module rapport de domoticz
#
# Jose - Février 2015 -  Ajout de la partie domoticz
#
#########

import psutil
import os
import sys
import string
import datetime
from subprocess import PIPE, Popen
from datetime import timedelta

#convertion des unites en Ko,Go,To
def bytes2human(n):
    # http://code.activestate.com/recipes/578019
    # >>> bytes2human(10000)
    # '9.8K'
    # >>> bytes2human(100001221)
    # '95.4M'
    symbols = (' Ko', ' Mo', ' Go', ' To', ' Po', 'Eo', 'Zo', 'Yo')
    prefix = {}
    for i, s in enumerate(symbols):
        prefix[s] = 1 << (i+1)*10
    for s in reversed(symbols):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return '%.1f%s' % (value, s)
    return "%sB" % n

# Lecture de la temperature
def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    return float(output[output.index('=') + 1:output.rindex("'")])

# Détermine la date et l'heure du dernier reboot de domoticz
def domo():
        for p in psutil.process_iter():
                try:
                        pi = p.as_dict(attrs=['pid', 'name', 'create_time', 'memory_info', 'status'])
                except:
                        pass
                else:
                        if pi['name'] == 'domoticz':
                                pid = pi['pid']
                                #print 'Pid correspondant: ', pi['pid']
                                boot = datetime.datetime.fromtimestamp(pi['create_time']).strftime("%d-%m-%Y %H:%M:%S")
                                #print 'Heure de la mise en service domoticz', boot
                                mem = pi['memory_info'][0]+pi['memory_info'][1]
                                #print 'Mémoire :', mem
                                status = pi['status']
                                #print status

        return boot, pid, mem, status

# Uptime
with open('/proc/uptime', 'r') as f:
        uptime_seconds = round(float(f.readline().split()[0]),0)
        demarrage = str(timedelta(seconds = uptime_seconds))
        demarrage = demarrage.replace('days','Jours',1)
        demarrage = demarrage.replace(',',' et',1)
        demarrage = demarrage.replace('day','Jour',1)
        demarrage = demarrage.replace(':','h ',1)
        demarrage = demarrage.replace(':','mn ',1)
        demarrage = demarrage+ 's '

# Attribution des valeurs aux variables
# SD card
valeur = psutil.disk_usage('/')
part = psutil.disk_partitions(all=False)
infopart = (part[0])[2]        # extration de l'extension
total = bytes2human(valeur.total)
utilise = bytes2human(valeur.used)
libre = bytes2human(valeur.free)
pourcent = str(valeur.percent)

# CPU
usagecpu = str(psutil.cpu_percent(interval=1))
p = psutil.Process(os.getpid())

# Heure mise a jour
dateheure = datetime.datetime.now()
dateheure = dateheure.strftime('%d/%m/%Y')
#dateheure = datetime.datetime.fromtimestamp(p.create_time).strftime("%d-%m-%Y %H:%M")

# Temperature
cpu_temperature = get_cpu_temperature()
p = psutil.Process(os.getpid())

# Memoire Vive RAM
valeur = psutil.virtual_memory()
mtotal = bytes2human(valeur.total)
mdispo = bytes2human(valeur.available)
mutilise = bytes2human(valeur.used)
mlibre = bytes2human(valeur.free)
mpourcent = str(valeur.percent)

# Memoire SWAP
valeur = psutil.swap_memory()
mstotal = bytes2human(valeur.total)
msutilise = bytes2human(valeur.used)
mslibre = bytes2human(valeur.free)
mspourcent = str(valeur.percent)

# Date et heure de boot domoticz et pid système
boot, pid, mem, status = domo()

# Mise en forme de la mémoire
mem =  bytes2human(mem)

Maintenant que vous avez les sources, je détaille rapidement le contenu.

Les programmes data.py et config.py sont chargés comme modules du programme rapport.py. Ce dernier récupère toutes les données à mettre dans le rapport et procède à sa création grâce à la classe fpdf. Une tâche planifiée réalise l’opération puis transmet le rapport par mail (gare au spam, le blacklistage n’est pas loin).

Les images sont stockées dans un répertoire « img » afin que ça ne devienne pas le bazar.

Allez, je file commander mes granulés.

Si je suis allé trop vite, laissez un message. Je repasse de temps en temps.

 Sources et remerciements:

La partie supervision : Christophe Caron – http://www.caron.ws

La partie mail : Manuberro – http://astuces.absolacom.com/developpement/envoyer-un-mail-avec-pieces-jointes-en-python/

La requête domoticz : Patrice – http://easydomoticz.com

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *