* @license GPL v3 * @license CECILL v2 * @copyright 1999-2014 Tela Botanica */ class SurveillanceMysql extends Script { private $bdd = null; private $nb_proc_action = 100; private $delai_action = 3600; private $chemin_log_critique = null; private $url_log_critique = null; private $nom_fichier_log = 'mysql_critique.tsv'; private $destinataire_mail_alerte = null; public function executer() { $this->destinataire_mail_alerte = Config::get('destinataire_mail_alerte_mysql'); $this->nb_proc_action = Config::get('nb_proc_mysql_critique'); $this->delai_action = Config::get('delai_proc_mysql_critique'); $this->bdd = new Bdd(); $this->surveillerNbProcessus(); } protected function surveillerNbProcessus() { // Un calcul rapide pour vérifier si on est au dessus du seuil if($this->nbProcessusEstInquietant()) { // Si oui on les charge tous (ordonné par temps décroissant) $processus = $this->chargerListeProcessus(); // Si le premier et donc plus vieux existe depuis trop longtemps if($this->tempsProcessusEstCritique($processus)) { // On le supprime et on avertit les admin avec les détails $this->supprimerProcessusLePlusCritique($processus); $this->envoyerMailSuppression($processus); } else { // Sinon envoi d'une simple alerte $this->envoyerMailAlerte($processus); } } } protected function nbProcessusEstInquietant() { // "INFO IS NOT NULL" permet de ne pas prendre en compte les requetes qui dorment et concernent // la connexion aux tables federated et "DB != 'information_schema'" permet 'ignorer cette requete-ci $commentaire = " -- Comptage des processus par le script de surveillance mysql ".__FILE__." ".__LINE__; $requete = "SELECT COUNT(*) as nb FROM INFORMATION_SCHEMA.PROCESSLIST ". "WHERE INFO IS NOT NULL AND DB != 'information_schema' "; $nb_procs = $this->bdd->recuperer($requete); return $nb_procs['nb'] >= $this->nb_proc_action; } protected function tempsProcessusEstCritique($processus) { // Etant ordonné par temps décroissant, le premier processus est le plus vieux return $processus[0]['TIME'] >= $this->delai_action; } protected function chargerListeProcessus() { $commentaire = " -- Recherche des processus potentiellement long par le script de surveillance mysql ".__FILE__." ".__LINE__; $requete = "SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST ". "WHERE INFO IS NOT NULL AND DB != 'information_schema' ". "ORDER BY TIME DESC"; return $this->bdd->recupererTous($requete); } protected function supprimerProcessusLePlusCritique($processus) { $commentaire = " -- Suppression d'un processus trop long par le script de surveillance mysql ".__FILE__." ".__LINE__; $requete = "KILL ".$this->bdd->proteger($processus[0]['ID']).$commentaire; return $this->bdd->executer($requete); } protected function genererFichierLog($processus) { $tsv = ""; // Si on entre dans cette fonction, $processus contient au moins un élément normalement // Ceci sert à afficher les titres de colonnes $header = array_keys($processus[0]); $tsv .= implode("\t", $header)."\n"; foreach ($processus as $proc) { $tsv .= implode("\t", $proc)."\n"; } return $tsv; } protected function envoyerMailSuppression($processus) { $contenu_supprime = ""; foreach($processus[0] as $cle => $valeur) { $contenu_supprime .= $cle.' : '.$valeur."\n"; } $sujet = "[dev-log] Surveillance des processus mysql : Un processus mysql a été supprimé "; $corps = "Le processus mysql suivant a été supprimé car son temps d'éxécution a dépassé les ".$this->delai_action." secondes : \n"; $corps .= $contenu_supprime." \n\n"; $corps .= "Les processus en éxécution à ce moment là sont référencés dans le fichier en pièce jointe \n"; return $this->envoyerMailAvecPieceJointe($this->destinataire_mail_alerte, $sujet, $corps, $this->genererFichierLog($processus)); } protected function envoyerMailAlerte($processus) { $sujet = "[dev-log] Surveillance des processus mysql : Attention surcharge, il y a ".count($processus)." processus en cours "; $corps = "Le nombre critique de ".$this->nb_proc_action." processus a été dépassé \n"; $corps .= "Les processus en éxécution sont référencés dans le fichier en pièce jointe \n"; return $this->envoyerMailAvecPieceJointe($this->destinataire_mail_alerte, $sujet, $corps, $this->genererFichierLog($processus)); } private function envoyerMailAvecPieceJointe($destinaire, $sujet, $message, $piece_jointe) { $limite = "_----------=_parties_".md5(uniqid (rand())); $limite_partie_message = "_----------=_parties_".md5(uniqid (rand() + 1)); // Définition d'un mail avec différents type de contenu $entetes = "X-Sender: \n". "X-Mailer: PHP-OUTILS\n". "Date: ".date('r')."\n". "From: root@agathis.tela-botanica.net\n". 'MIME-Version: 1.0' . "\n"; // Définition d'un type de contenu mixed (mail + piece jointe) $entetes .= "Content-Type: multipart/mixed; boundary=\"$limite\";\n\n"; // Première sous partie : contenu du mail $contenu = "\n". "--$limite\n". // Définition d'un type de contenu alternatif "Content-Type: multipart/alternative; boundary=\"$limite_partie_message\";\n". "\n". "--$limite_partie_message\n". "Content-Type: text/plain; charset=\"UTF-8\";\n". "Content-Transfer-Encoding: 8bit;\n". "\n". "$message\n". "--$limite_partie_message--\n". "--$limite\n"; // Seconde sous partie : pièce jointe if ($piece_jointe != null) { $attachment = chunk_split(base64_encode($piece_jointe)); $contenu .= "Content-Type: text/tab-separated-values; name=\"$this->nom_fichier_log\"\n". "Content-Transfer-Encoding: base64\n". "Content-Disposition: attachment; filename=\"$this->nom_fichier_log\"\n". "X-Attachment-Id: ".md5($attachment)."\n\n". "$attachment\n". "--$limite--\n"; } return mail($this->destinataire_mail_alerte, $sujet, $contenu, $entetes); } }