From 1aa82e144f61aef1ef1754a26eb8f59cad358cd1 Mon Sep 17 00:00:00 2001
From: gloas <gloas@afi-sa.fr>
Date: Thu, 21 Oct 2021 10:48:32 +0200
Subject: [PATCH] hotline #142649 add memcached status

---
 VERSIONS_HOTLINE/142649                       |   3 +-
 .../admin/controllers/SystemeController.php   |  14 +-
 .../scripts/systeme/memcached-status.phtml    |   2 +
 .../ZendAfi/View/Helper/Admin/ContentNav.php  |   1 +
 .../View/Helper/Admin/MemcachedStatus.php     | 205 ++++++++++++++++++
 library/startup.php                           |  15 +-
 public/admin/skins/bokeh72/config.json        |   1 +
 public/admin/skins/bokeh74/config.json        |   2 +-
 public/admin/skins/noel/config.json           |   1 +
 public/admin/skins/retro/config.json          |   1 +
 .../controllers/AdminIndexControllerTest.php  |   6 +
 .../controllers/SystemeControllerTest.php     | 150 +++++++++++++
 tests/library/Class/CatalogueTest.php         |   2 +-
 .../ZendAfi/View/Helper/Accueil/SitoTest.php  |   2 +-
 14 files changed, 397 insertions(+), 8 deletions(-)
 create mode 100644 application/modules/admin/views/scripts/systeme/memcached-status.phtml
 create mode 100644 library/ZendAfi/View/Helper/Admin/MemcachedStatus.php

diff --git a/VERSIONS_HOTLINE/142649 b/VERSIONS_HOTLINE/142649
index 444d82c9316..93beac64fa3 100644
--- a/VERSIONS_HOTLINE/142649
+++ b/VERSIONS_HOTLINE/142649
@@ -1,2 +1,3 @@
  - ticket #142649 : Magasin de thèmes : Amélioration des performances du rendu liste à interactions.
-   	  	    	       	      	Amélioration des performances de rendu des exemplaires de périodiques. Lorsqu'une notice de périodiques a plus de 20 exemplaires, elle utilise le rendu de liste à interactions.
\ No newline at end of file
+   	  	    	       	      	Amélioration des performances de rendu des exemplaires de périodiques. Lorsqu'une notice de périodiques a plus de 20 exemplaires, elle utilise le rendu de liste à interactions.
+                    Administration : Ajout d'un écran d'état de l'utilisation de Memcached.
diff --git a/application/modules/admin/controllers/SystemeController.php b/application/modules/admin/controllers/SystemeController.php
index 5db2c4b5c87..67a63dc7bd8 100644
--- a/application/modules/admin/controllers/SystemeController.php
+++ b/application/modules/admin/controllers/SystemeController.php
@@ -19,8 +19,12 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class Admin_SystemeController extends Zend_Controller_Action {
-  use Trait_Translator, Trait_StaticFileSystem;
+class Admin_SystemeController extends ZendAfi_Controller_Action {
+
+  use
+    Trait_StaticFileSystem,
+    Trait_TimeSource;
+
 
   public function webservicesAction() {
     $this->view->titre = $this->_('Test des Web Services');
@@ -285,5 +289,9 @@ class Admin_SystemeController extends Zend_Controller_Action {
   public function statusAction() {
     $this->view->titre = $this->_('Etat du système');
   }
+
+
+  public function memcachedStatusAction() {
+    $this->view->titre = $this->_('État de la configuration et de l\'utilisation de Memcached à %s', static::getCurrentDateTime());
+  }
 }
-?>
diff --git a/application/modules/admin/views/scripts/systeme/memcached-status.phtml b/application/modules/admin/views/scripts/systeme/memcached-status.phtml
new file mode 100644
index 00000000000..8d8b6e5a878
--- /dev/null
+++ b/application/modules/admin/views/scripts/systeme/memcached-status.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->Admin_MemcachedStatus();
diff --git a/library/ZendAfi/View/Helper/Admin/ContentNav.php b/library/ZendAfi/View/Helper/Admin/ContentNav.php
index 6a978af8b96..c9f1ba92415 100644
--- a/library/ZendAfi/View/Helper/Admin/ContentNav.php
+++ b/library/ZendAfi/View/Helper/Admin/ContentNav.php
@@ -158,6 +158,7 @@ class ZendAfi_View_Helper_Admin_ContentNav extends ZendAfi_View_Helper_BaseHelpe
 
                     ['sendmail_tests', $this->_('Test envoi mails'), '/admin/systeme/mailtest', [], $is_super_admin],
                     ['php', $this->_('Informations système'), '/admin/systeme/phpinfo', [], $is_super_admin],
+                    ['memcached', $this->_('Informations Memcached'), '/admin/systeme/memcached-status', [], $is_super_admin],
                     ['image_cache', $this->_('Cache des images'), '/admin/systeme/cacheimages', [], $is_admin],
 
                     ['migrate_comments', $this->_('Import avis opac2'), '/admin/systeme/importavisopac2',
diff --git a/library/ZendAfi/View/Helper/Admin/MemcachedStatus.php b/library/ZendAfi/View/Helper/Admin/MemcachedStatus.php
new file mode 100644
index 00000000000..aa3adbc4503
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Admin/MemcachedStatus.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_Admin_MemcachedStatus extends ZendAfi_View_Helper_BaseHelper {
+
+
+  protected static
+    $_port,
+    $_host,
+    $_memcached;
+
+
+  public function Admin_MemcachedStatus() {
+    $this->_tag('h1', $this->view->titre);
+
+    if ( ! $cache = Storm_Cache::getDefaultZendCache() )
+      return $this->_tagWarning($this->_('Memcached n\'est pas utilisé.'));
+
+    if ( $cache instanceof Storm_Cache_Null)
+      return $this->_tagWarning($this->_('Memcached n\'est pas utilisé.'));
+
+    if ( ! $backend = $cache->getBackend())
+      return $this->_tagWarning($this->_('Memcached n\'est pas utilisé.'));
+
+    if ( (! $backend instanceof Zend_Cache_Backend_Memcached) && (! isset(static::$_memcached)))
+      return $this->_tagWarning($this->_('Memcached n\'est pas utilisé.'));
+
+    $memcached = $this->getMemcached();
+    $memcached->addServer($this->getHost(), $this->getPort());
+
+    $html = [$this->_renderStats($memcached),
+             $this->_renderSessions($memcached),
+             $this->_renderLocalStats($memcached)];
+
+    return $this->_tag('div', implode($html));
+  }
+
+
+  protected function _renderStats($memcached) {
+    $stats = $memcached->getStats();
+    return
+      $this->_div(['class' => 'memcached_status_stats'],
+                  $this->_tag('h2',
+                              $this->_('Statistiques globales du server Memcached'))
+
+                  . $this->_tag('pre', json_encode($stats, JSON_PRETTY_PRINT)));
+  }
+
+
+  protected function _renderSessions($memcached) {
+    $keys = $memcached->getAllKeys();
+    $session_keys =
+      array_filter($keys,
+                   function($key)
+                   {
+                     return 0 === strpos($key, 'memc.sess.key');
+                   });
+    $count = count($session_keys);
+
+    $locked_sessions =
+      array_filter($session_keys,
+                   function($key)
+                   {
+                     return false !== strpos($key, '.lock');
+                   });
+
+    $count_locked = count($locked_sessions);
+
+    return
+      $this->_div(['class' => 'memcached_status_sessions'],
+                  $this->_tag('h2',
+                              $this->_('Toutes les sessions sur le serveur Memcached'))
+                  . $this->_tagNotice($this->_('Nombre de sessions : %s', $count))
+                  . $this->_tagNotice($this->_('Nombre de sessions bloquée (locked) : %s', $count_locked)));
+  }
+
+
+  protected function _renderLocalStats($memcached) {
+    $seed = Bokeh_Engine::getInstance()->getCacheSeed();
+    $keys = $memcached->getAllKeys();
+
+    $local_keys =
+      array_filter($keys,
+                   function($key) use ($seed)
+                   {
+                     return false !== strpos($key, $seed);
+                   });
+
+    $local_keys = array_values($local_keys);
+
+    $html = [];
+    $total = 0;
+
+    foreach ($local_keys  as $key){
+      $cache_content = $memcached->get($key);
+
+      if ( ! $cache_content) {
+        $html [] = $this->_tagError($this->_('Memcached::get(%s) a retourné le message suivant : %s',
+                                             $key,
+                                             $memcached->getResultMessage()));
+        continue;
+      }
+
+      $cache_content = array_shift($cache_content);
+
+      $size = mb_strlen($cache_content);
+
+      $cache_content = substr($cache_content, 0, 100);
+
+      $html [] =
+        $this->_tagNotice($this->_('Informations sur la cle %s : ',
+                                   $key))
+
+        . $this->_tagNotice($this->_('Taille de la valeur : %s ko',
+                                     number_format(($size / 1024), 2, ',', ' ')))
+
+        . $this->_tag('pre',
+                      $this->_('Les 100 premiers caractères de la valeur: %s',
+                               htmlspecialchars($cache_content)));
+
+      $total += $size;
+    }
+
+    return
+      $this->_div(['class' => 'memcached_status_local'],
+                  $this->_tag('h2',
+                              $this->_('Données de ce site dans le Memcached'))
+
+                  . $this->_tagNotice($this->_('Clé du site : %s',
+                                               Bokeh_Engine::getInstance()->getCacheSeed()))
+
+                  . $this->_tagNotice($this->_('Clé du site pour le memcached : %s',
+                                               (new Storm_Cache)->getRealSeed()))
+
+                  . $this->_tagNotice($this->_('Taille totale des valeurs en cache: %s ko',
+                                               (number_format(($total / 1024), 2, ',', ' '))))
+
+                  . $this->_tag('h3',
+                                $this->_('Liste des clés pour ce site'))
+
+                  . $this->_tag('pre',
+                                json_encode($local_keys, JSON_PRETTY_PRINT))
+
+                  . $this->_tag('h3',
+                                $this->_('Liste des valeurs pour ce site'))
+
+                  . implode($html)
+      );
+  }
+
+
+  public function getMemcached() {
+    return static::$_memcached
+      ? static::$_memcached
+      : new \Memcached;
+  }
+
+
+  public function getHost() {
+    return static::$_host
+      ? static::$_host
+      : MEMCACHED_HOST;
+  }
+
+
+  public function getPort() {
+    return static::$_port
+      ? static::$_port
+      : MEMCACHED_PORT;
+  }
+
+
+  public static function setMemcached($memcached) {
+    static::$_memcached = $memcached;
+  }
+
+
+  public static function setHost($host) {
+    static::$_host = $host;
+  }
+
+
+  public static function setPort($port) {
+    static::$_port = $port;
+  }
+}
diff --git a/library/startup.php b/library/startup.php
index 1a3e32e7d35..d28bce1e658 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -42,6 +42,7 @@ class Bokeh_Engine {
 
 
   protected
+    $_cache_seed,
     $_warm_up_cache = false,
     $_config,
     $_front_controller;
@@ -223,7 +224,19 @@ class Bokeh_Engine {
                                  $backendOptions);
 
     Storm_Cache::setDefaultZendCache($cache);
-    Storm_Cache::setSeed($this->_config->sgbd->config->dbname.md5(BASE_URL));
+    $this->setCacheSeed(md5($this->_config->sgbd->config->dbname . BASE_URL));
+    Storm_Cache::setSeed($this->getCacheSeed());
+    return $this;
+  }
+
+
+  public function getCacheSeed() {
+    return $this->_cache_seed;;
+  }
+
+
+  public function setCacheSeed($seed) {
+    $this->_cache_seed = $seed;
     return $this;
   }
 
diff --git a/public/admin/skins/bokeh72/config.json b/public/admin/skins/bokeh72/config.json
index 985cad96e82..ae5627df50a 100644
--- a/public/admin/skins/bokeh72/config.json
+++ b/public/admin/skins/bokeh72/config.json
@@ -58,6 +58,7 @@
     "thesaurus_init": "../../images/picto/generation_16.png",
     "thesaurus_edit": "../../images/picto/generation_16.png",
     "testmyopac": "icons/menu/link_24.png",
+    "memcached": "icons/menu/collections_24.png",
 
     "home": "../../images/picto/icon_home.gif",
     "back_to_front": "../../images/picto/profils_16.png",
diff --git a/public/admin/skins/bokeh74/config.json b/public/admin/skins/bokeh74/config.json
index 46017cdd264..10611bb2cf1 100644
--- a/public/admin/skins/bokeh74/config.json
+++ b/public/admin/skins/bokeh74/config.json
@@ -61,7 +61,7 @@
 
     "pnb": "../../images/picto/pnb_16.png",
     "php": "../../images/picto/php.png",
-
+    "memcached": "icons/menu/collections_24.png",
 
     "home": "icons/menu/home_24.png",
     "back_to_front": "icons/menu/site_24.png",
diff --git a/public/admin/skins/noel/config.json b/public/admin/skins/noel/config.json
index a4bd1988adc..7c29e99825b 100644
--- a/public/admin/skins/noel/config.json
+++ b/public/admin/skins/noel/config.json
@@ -61,6 +61,7 @@
 
     "pnb": "../../images/picto/pnb_16.png",
     "php": "../../images/picto/php.png",
+    "memcached": "icons/menu/collections_24.png",
 
     "home": "icons/menu/home_24.png",
     "back_to_front": "icons/menu/site_24.png",
diff --git a/public/admin/skins/retro/config.json b/public/admin/skins/retro/config.json
index 3fbb30d1640..6829adb6e2d 100644
--- a/public/admin/skins/retro/config.json
+++ b/public/admin/skins/retro/config.json
@@ -61,6 +61,7 @@
 
     "pnb": "../../images/picto/pnb_16.png",
     "php": "../../images/picto/php.png",
+    "memcached": "icons/menu/collections_24.png",
 
     "home": "icons/menu/home_24.png",
     "back_to_front": "icons/menu/site_24.png",
diff --git a/tests/application/modules/admin/controllers/AdminIndexControllerTest.php b/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
index 5f31a81f99d..0461d5a690d 100644
--- a/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
+++ b/tests/application/modules/admin/controllers/AdminIndexControllerTest.php
@@ -88,6 +88,12 @@ class AdminIndexControllerIndexActionTest extends AdminIndexControllerTestCase {
   public function menuGaucheShouldContainsTestMyOpac() {
     $this->assertXPathContentContains('//li//a', 'Test de mon OPAC');
   }
+
+
+  /** @test */
+  public function menuGaucheShouldContainsInformationsMemcached() {
+    $this->assertXPathContentContains('//li//a[contains(@href, "/admin/systeme/memcached-status")]', 'Informations Memcached');
+  }
 }
 
 
diff --git a/tests/application/modules/admin/controllers/SystemeControllerTest.php b/tests/application/modules/admin/controllers/SystemeControllerTest.php
index e85a732560e..4450cf9be0a 100644
--- a/tests/application/modules/admin/controllers/SystemeControllerTest.php
+++ b/tests/application/modules/admin/controllers/SystemeControllerTest.php
@@ -500,3 +500,153 @@ class Admin_SystemControllerStatusWithErrorDeterminingPublicIpTest extends Admin
     $this->assertXPathContentContains('//dt[text()="IP publique"]/following-sibling::dd', 'Unable to connect');
   }
 }
+
+
+
+
+class Admin_SystemControllerNoMemchaedStatusTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/systeme/memcached-status');
+  }
+
+
+  /** @test */
+  public function shouldDisplayTitleMemcachedStatus() {
+    $this->assertXPathContentContains('//h1', 'État de la configuration et de l\'utilisation de Memcached');
+  }
+
+
+  /** @test */
+  public function shouldDisplayMemcachedIsNotEnabled() {
+    $this->assertXPathContentContains('//p', 'Memcached n\'est pas utilisé.');
+  }
+}
+
+
+
+
+class Admin_SystemControllerMemchaedStatusTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $memcached = $this
+      ->mock()
+
+      ->whenCalled('addServer')->with('localhost', 11211)
+      ->answers(true)
+
+      ->whenCalled('getStats')
+      ->answers([[]])
+
+      ->whenCalled('getAllKeys')
+      ->answers(['memc.sess.key1',
+                 'memc.sess.key2',
+                 '12345789237OU8787897',
+                 '123457123982987P89É7',
+                 '1234578UIEUIESRT3242'])
+
+      ->whenCalled('get')->with('12345789237OU8787897')
+      ->answers(['A1924 A211 A8 M1860 Lfre G3 T1 B19 S1 Y19 V19 B20 E33 Y20 V20 B44 Y44 V44 B23 Y23 V23 S2 HNRNR000',
+                 787893724789234])
+
+      ->whenCalled('get')->with('123457123982987P89É7')
+      ->answers(['a:13:{i:0;a:2:{i:0;i:6;i:1;s:45:"A8 M18435 Lfre G3 T1 B1 S1 E5 Y1 V1 HNRNR0002";}i',
+                 1231312312313])
+
+      ->whenCalled('get')->with('1234578UIEUIESRT3242')
+      ->answers(false)
+
+      ->whenCalled('getResultMessage')
+      ->answers('NOT STORED')
+
+      ->beStrict();
+
+    Bokeh_Engine::getInstance()->setCacheSeed('12345');
+    Storm_Cache::setSeed('12345');
+
+    ZendAfi_View_Helper_Admin_MemcachedStatus::setHost('localhost');
+    ZendAfi_View_Helper_Admin_MemcachedStatus::setPort(11211);
+    ZendAfi_View_Helper_Admin_MemcachedStatus::setMemcached($memcached);
+
+    $factory = $this
+      ->mock()
+
+      ->whenCalled('getBackend')
+      ->answers($memcached)
+
+      ->whenCalled('load')->with('12345')
+      ->answers('12345')
+
+      ->beStrict();
+
+    Storm_Cache::setDefaultZendCache($factory);
+
+    $this->dispatch('/admin/systeme/memcached-status');
+  }
+
+
+  public function tearDown() {
+    Storm_Cache::setDefaultZendCache(null);
+  }
+
+
+  /** @test */
+  public function shouldDisplayTitleMemcachedStatus() {
+    $this->assertXPathContentContains('//h1', 'État de la configuration et de l\'utilisation de Memcached');
+  }
+
+
+  /** @test */
+  public function shouldNotDisplayMemcachedIsNotEnabled() {
+    $this->assertNotXPathContentContains('//p', 'Memcached n\'est pas utilisé.');
+  }
+
+
+  /** @test */
+  public function shouldDisplayNumberOfSessions2() {
+    $this->assertXPathContentContains('//p', 'Nombre de sessions : 2');
+  }
+
+
+  /** @test */
+  public function shouldDisplayOthers() {
+    $this->assertXPathContentContains('//pre', '"12345789237OU8787897"');
+  }
+
+
+  /** @test */
+  public function shouldDisplaySeed() {
+    $this->assertXPathContentContains('//p', 'Clé du site : 12345');
+  }
+
+
+  /** @test */
+  public function shouldDisplayRealSeed() {
+    $this->assertXPathContentContains('//p', 'Clé du site pour le memcached : 12345');
+  }
+
+
+  /** @test */
+  public function shouldDisplayTotalSize() {
+    $this->assertXPathContentContains('//p', 'Taille totale des valeurs en cache: 0,17 ko');
+  }
+
+
+  /** @test */
+  public function shouldDisplayValueSize() {
+    $this->assertXPathContentContains('//p', 'Taille de la valeur : 0,09 ko');
+  }
+
+
+  /** @test */
+  public function memcachedGetShouldReturnAnError() {
+    $this->assertXPathContentContains('//p[@class="error"]', 'Memcached::get(1234578UIEUIESRT3242) a retourné le message suivant : NOT STORED');
+  }
+}
\ No newline at end of file
diff --git a/tests/library/Class/CatalogueTest.php b/tests/library/Class/CatalogueTest.php
index fb320bcefbf..f633104bbef 100644
--- a/tests/library/Class/CatalogueTest.php
+++ b/tests/library/Class/CatalogueTest.php
@@ -544,7 +544,7 @@ class CatalogueTestOAISpec extends ModelTestCase {
 
 
 class CatalogueGetNoticesByPreferencesNotRandomTest extends ModelTestCase {
-  protected $_cache_key = '25d43b0fa219225c79624883f5f7fd99';
+  protected $_cache_key = 'fixed_seed_ddc31b220940c90625375ed2f381bc8c';
 
   public function setUp() {
     parent::setUp();
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
index c240c9aa8c0..d7ccd8bc7e5 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/SitoTest.php
@@ -254,7 +254,7 @@ class SitoViewHelperCachedTest extends SitoViewHelperTestCase {
 
   /** @test */
   public function cacheShouldBeUse() {
-    $value = (new Storm_Cache())->getCache()->load('2834982d0d27025a7755ea93669e9537');
+    $value = (new Storm_Cache())->getCache()->load('real_seed_6421da151f69b65d9601ce3c3eb18656');
     $this->assertNotEquals(false, $value);
   }
 }
-- 
GitLab