Fuite de mémoire dans mon plugin SMA_SunnyBoy - Help

Du coup le fait de redemarrer le deamon chaque jour reste une solution acceptable.
Je ne sais plus trop quoi faire de toute facon.

Ce n’est pas ça, lire ici :
https://www.php.net/manual/en/features.gc.collecting-cycles.php
Ayant eu le même souci, voici un extrait de mon code

public static function daemon() {
		gc_enable();
		log::add(__CLASS__, 'debug', '   Memory used :'.memory_get_usage());

		while(true) {
			log::add(__CLASS__, 'debug', '   Memory used :'.memory_get_usage());
			// section suspectée d'entrainer une fuite mémoire
			log::add(__CLASS__, 'debug', '   Memory used :'.memory_get_usage());
			gc_collect_cycles();
			usleep(20000);
		}
}

Le gc_collect_cycle() retarde le souci, la fuite est juste plus faible. Par contre il est indispensable pour supprimer les autres causes de variation de la consommation mémoire.
J’ai toujours une surveillance de la fuite mais qui n’est plus utile depuis que j’ai trouvé la fonction php qui produisait la fuite : socket_read(). Heureusement, il y a une alternative pour faire la même chose.

	public static function cronHourly() {
		$mem = config::byKey( 'mem',   'wifilightV2');
		if (($mem> 100000) && (date('G')>=2) && (date('G')<= 5))  {
			message::add ('wifilightV2', 'Restart Deamon memory: '.$mem.'ko');
			self::deamon_start();
		}
	}

et ajouter dans la fonction deamon :

config::save('mem', round(memory_get_usage()/1000),  'wifilightV2');	

config:: sert à la communication d’information entre les deux deamon

C’est pas joli, mais ca vous laisse du temps pour trouver la fuite.

Merci pour tes explications, moi je ne trouve pas ce qui produit cette fuite donc je vois que toi aussi tu fais un deamon_start() si besoin.
Moi je le fais chaque jour car j’ai une perte assez importante je trouve… sans cela mon Jeedom ne tient pas 1 mois.
Je vais ajouter gc_enable() et gc_collect_cycles() pour voir si ca change qqchose.

Merci,

Sebastien

même en plaçant des

memory_get_usage()

à de bons endroits ?

Comme on avait deja fait le test dansun commentaire un peu plus haut, je fait meme de faire une boucle pour lister les equipement du plugin fait consommer de la memoire:

	public static function daemon() {
		//$starttime = microtime(true);
      	log::add(__CLASS__, 'debug', "Memory_usage: ".memory_get_usage());
		foreach (self::byType(__CLASS__, true) as $eqLogic) {
          	$cmd = $eqLogic->getCmd(null, 'update');//retourne la commande 'update' si elle existe
			if (is_object($cmd)) {//si la comande existe
				//$cmd->execCmd();//alors on l'execute
			}
		}
	}

Ici je n’execute meme pas la commande ‹ update › et la memoire augmente quand meme.
Donc il y a deja fuite ici …

La dernier version de la fonction deamon est comme ceci (j’ai ajoute les ‹ elements › de nettoyage …):

	public static function daemon() {
      	gc_enable();
      	log::add(__CLASS__, 'debug', "Memory_usage: ".memory_get_usage());
		foreach (self::byType(__CLASS__, true) as $eqLogic) {
          	$cmd = $eqLogic->getCmd(null, 'update');//retourne la commande 'update' si elle existe
			if (is_object($cmd)) {//si la comande existe
				$cmd->execCmd();//alors on l'execute
			}
          	unset($cmd);
          	unset($eqLogic);
		}
      	gc_collect_cycles();
	}

Il faut partir d’une config sans fuite mémoire, normalement un deamon vide avec juste la surveillance de mémoire.
Puis ajouter progressivement jusqu’à obtenir la fuite mémoire
Il faut quand même définir fuite mémoire : explosion de son usage et saturation.
Je suis étonné que des méthodes jeedom produisent une fuite mémoire.
d’où ma proposition :partir d’une config ne provoquant pas de fuite mémoire, le problème est peut-être ailleurs.
Ton deamon est lancé périodiquement par Jeedom ?

Non, le deamon est lancé une seule fois depuis la configuration du plugin.
Si le code de la fonction deamon() est vide alors il n’y a pas de fuite de mémoire… mais dès que je fais le for each des équipements du plugin alors il y a déjà petite fuite.
J’avais bien en tête de faire comme tu le proposais, démarrer avec 0 fuite puis voir quand / ce qui fait démarrer la fuite.

Même sans onduleur ça fuit:

Dans daemon, pourquoi

  • aller chercher dans l’eqLogic la commande ‹ update ›
  • exécuter la commande qui va chercher son eqLogic
  • qui va lancer eqLogic->getSmaData

Et ne pas juste appeler $eqLogic = $eqlogic->getSmaData(); dans daemon

Ton daemon, au vu de ton code, est lancé périodiquement dans la config du plugin. Sinon il devrait avoir une boucle infinie comme mon exemple.
Exemple d’un daemon dans le plugin teleinfo, c’est le setSchedule() qui le lance périodiquement.

    $cron = cron::byClassAndFunction('teleinfo', 'calculateOtherStats');
    if (!is_object($cron)) {
        $cron = new cron();
        $cron->setClass('teleinfo');
        $cron->setFunction('calculateOtherStats');
        $cron->setEnable(1);
        $cron->setDeamon(0);
        $cron->setSchedule('10 00 * * *'); // c'est ici que le daemon est lancé périodiquement via un cron
        $cron->save();
    }

et au vu du log qui vient d’être posté comment fais tu pour lancer ton daemon toutes les 30s ?
Car la période min c’est 1 minute :

$cron->setSchedule('* * * * *');

Avec ce daemon modifié, la mem est stable:

	public static function daemon() {
      $starttime = microtime(true);
      foreach (self::byType(__CLASS__, true) as $eqLogic) {
        $mem0 = memory_get_usage();
        log::add('SMA_SunnyBoy','debug',"Memory usage: $mem0");
        $eqLogic->getSmaData();
        $mem1 = memory_get_usage();
        log::add('SMA_SunnyBoy','debug',"    Conso getSmaData Usage: " .($mem1-$mem0));
      }
      $endtime = microtime(true);
      if ($endtime - $starttime < config::byKey('pollInterval', __CLASS__, 60, true)) {
        usleep(floor((config::byKey('pollInterval', __CLASS__) + $starttime - $endtime) * 1000000));
      }
	}

image

On a mis le doigt sur le souci.
C’est le lancement du daemon qui pose souci, pas son contenu
il faudrait que @Sattaz poste son code de démarrage du daemon et explique comment il fait pour obtenir une période de 30s.

Là je n’ai modifié que le lancement de la fonction $eqLogic->getSmaData() directement dans le daemon.
sans passer par la recherche et l’execution de la cmd update de l’eqLogic

Pour le code de démarrage du daemon, j’ai installé le plugin.

	public static function deamon_start() {
		self::deamon_stop();
		$deamon_info = self::deamon_info();
		if ($deamon_info['launchable'] != 'ok') {
			throw new Exception(__('Veuillez vérifier la configuration', __FILE__));
		}
		$cron = cron::byClassAndFunction(__CLASS__, 'daemon');
		if (!is_object($cron)) {
			$cron = new cron();
			$cron->setClass(__CLASS__);
			$cron->setFunction('daemon');
			$cron->setEnable(1);
			$cron->setDeamon(1);
			$cron->setTimeout(1440);
			$cron->setSchedule('* * * * *');
			$cron->save();
		}
		$cron->run();
	}

Je n’ai pas encore creusé pour voir comment ça se lance toutes les 30 sec
Comme vous, je verrai bien un while(true) dans daemon pour en faire un vrai daemon sans compter sur le cron de Jeedom.

Edit:
Dernière fonction avec une boucle infinie pour faire le daemon.

  public static function daemon() {
    $pollInterval = config::byKey('pollInterval', __CLASS__, 60, true);
    while(true) {
      $starttime = microtime(true);
      foreach (self::byType(__CLASS__, true) as $eqLogic) {
        log::add('SMA_SunnyBoy','debug',__FUNCTION__ ." Memory usage: " .memory_get_usage());
        $eqLogic->getSmaData();
      }
      $endtime = microtime(true);
      if ($endtime - $starttime < $pollInterval) {
        usleep(floor(($pollInterval + $starttime - $endtime) * 1000000));
      }
    }
  }

Mémoire stable:

Je laisse à @Sattaz la main pour la suite avec les onduleurs.

1 « J'aime »

@bernardfr.caron , avant dans le deamon() j’avais un USleep qui attendait en fonction de la valeur de ‹ pause › specifiée dans la configuration du plugin (par defaut 30s, mais changeable a ce que tu veux en fait … moi je met 10s chez moi).
Puis @Mips m’a donné une autre technique prévu par Jeedom, ici:

$cron->setDeamonSleepTime(config::byKey(‹ pollInterval ›, CLASS, 60));

	public static function deamon_start() {
		self::deamon_stop();
		$deamon_info = self::deamon_info();
		if ($deamon_info['launchable'] != 'ok') {
			throw new Exception(__('Veuillez vérifier la configuration', __FILE__));
		}
		$cron = cron::byClassAndFunction(__CLASS__, 'daemon');
		if (!is_object($cron)) {
			$cron = new cron();
			$cron->setClass(__CLASS__);
			$cron->setFunction('daemon');
			$cron->setEnable(1);
			$cron->setDeamon(1);
          	$cron->setDeamonSleepTime(config::byKey('pollInterval', __CLASS__, 60));
			$cron->setTimeout(1440);
			$cron->setSchedule('* * * * *');
			$cron->save();
		}
		$cron->run();
	}

@jpty , merci tout plein, je mets ça en place ce soir et reviens vers vous …

au debut j’avais comme dis aussi ce code de check entre endtime et starttime mais le parametre ‹ setDeamonSleepTime › permet de faire la même chose … du coup pas besoin de boucle ‹ while › dans le deamon() …

Je vais essayer les 2 methodes …

C’est cool d’avoir votre soutient, merci à tous !

En résumé, dans daemon(), il faut remplacer:

            $cmd = $eqLogic->getCmd(null, 'update');//retourne la commande 'update' si elle existe
			if (is_object($cmd)) {//si la comande existe
				$cmd->execCmd();//alors on l'execute
			}

par

$eqLogic->getSmaData();

Pourquoi avoir fait si compliqué ? :thinking:

Et virer les unset() mis sans avoir vérifié qu’il y avait des fuites de mémoire. :wink:

1 « J'aime »

C’est dans ma tête, toutjours trop compliqué :slight_smile:

1 « J'aime »

Bon j’ai appliqué le changement et en ayant 4 appareils SMA, j’ai encore des fuites entre chaque cycle:

[2024-04-04 16:08:35][DEBUG] : Memory_usage: 1046680
[2024-04-04 16:08:50][DEBUG] : Memory_usage: 1936656
[2024-04-04 16:09:05][DEBUG] : Memory_usage: 2305800
[2024-04-04 16:09:21][DEBUG] : Memory_usage: 2643320
[2024-04-04 16:09:35][DEBUG] : Memory_usage: 2916288

Pour info j’ai enlevé tous les unset() …

Pourriez-vous SVP montrer sur Github, ce que vous avez fait?
Dans une branche dev ou votre beta mise à jour.

https://github.com/Sattaz/Jeedom_SMA_Sunny_Boy/blob/beta/core/class/SMA_SunnyBoy.class.php