Index: main/plugin-web/scripts/mysql/jdbc_cache_mon.sql =================================================================== --- main/plugin-web/scripts/mysql/jdbc_cache_mon.sql (revision 0) +++ main/plugin-web/scripts/mysql/jdbc_cache_mon.sql (revision 0) @@ -0,0 +1,123 @@ +-- +-- Copyright 2012 Anyware Services +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +drop table if exists Server_Requests; +CREATE TABLE Server_Requests ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Unique_Id VARCHAR(31) UNIQUE KEY, + Site VARCHAR(255) NOT NULL, + Remote_Host_Name VARCHAR(255), + Request_Date DATETIME NOT NULL, + Method VARCHAR(15) NOT NULL, + Path VARCHAR(511) NOT NULL, + Query_String VARCHAR(511) NOT NULL, + Protocol VARCHAR(15), + Ori_Status_Code CHAR(3) NOT NULL, + Ret_Status_Code CHAR(3) NOT NULL, + Cache_Hit BOOLEAN NOT NULL, + Referer VARCHAR(511), + User_Agent VARCHAR(511), + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + Processed BOOLEAN NOT NULL DEFAULT 0 +) ENGINE=InnoDB; + + +drop table if exists Front_Requests; +CREATE TABLE Front_Requests ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Unique_Id VARCHAR(31), + Internal_Uuid VARCHAR(63), + Site VARCHAR(255), + Ametys_Path VARCHAR(511) NOT NULL, + Cacheable BOOLEAN NOT NULL, + Cache_Hit_1 BOOLEAN, + Cache_Hit_2 BOOLEAN, + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + Processed BOOLEAN NOT NULL DEFAULT 0 +) ENGINE=InnoDB; + +drop table if exists Back_Requests; +CREATE TABLE Back_Requests ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Internal_Uuid VARCHAR(63), + Page_Id VARCHAR(63) NOT NULL, + Page_Path VARCHAR(63) NOT NULL, + Rendering_Context VARCHAR(15) NOT NULL, + Workspace_JCR VARCHAR(15) NOT NULL, + Cacheable BOOLEAN NOT NULL, + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + Processed BOOLEAN NOT NULL DEFAULT 0 +) ENGINE=InnoDB; + +drop table if exists Back_Page_Element_Requests; +CREATE TABLE Back_Page_Element_Requests ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Internal_Uuid VARCHAR(63), + Page_Element_Id VARCHAR(63) NOT NULL, + Page_Element_Type VARCHAR(31) NOT NULL, + Page_Id VARCHAR(63) NOT NULL, + Rendering_Context VARCHAR(15) NOT NULL, + Workspace_JCR VARCHAR(15) NOT NULL, + Cacheable BOOLEAN NOT NULL, + Cache_Hit BOOLEAN, + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + Processed BOOLEAN NOT NULL DEFAULT 0 +) ENGINE=InnoDB; + +drop table if exists Cache_Front_Stats; +CREATE TABLE Cache_Front_Stats ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Server_Site VARCHAR(255), + Server_Path VARCHAR(511), + Server_Hits INT UNSIGNED NOT NULL DEFAULT 0, + Server_Cache_Hits INT UNSIGNED NOT NULL DEFAULT 0, + Front_Site VARCHAR(255), + Front_Path VARCHAR(511), + Front_Cacheable BOOLEAN DEFAULT 0, + Front_Hits INT UNSIGNED NOT NULL DEFAULT 0, + Front_Cache_Hits_1 INT UNSIGNED NOT NULL DEFAULT 0, + Front_Cache_Hits_2 INT UNSIGNED NOT NULL DEFAULT 0, + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + -- Updated_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +drop table if exists Cache_Back_Stats; +CREATE TABLE Cache_Back_Stats ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Page_Id VARCHAR(63) NOT NULL, + Page_Path VARCHAR(63) NOT NULL, + Rendering_Context VARCHAR(15) NOT NULL, + Workspace_JCR VARCHAR(15) NOT NULL, + Cacheable BOOLEAN NOT NULL, + Hits INT UNSIGNED NOT NULL, + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + -- Updated_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +drop table if exists Cache_Back_Page_Element_Stats; +CREATE TABLE Cache_Back_Page_Element_Stats ( + Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment, + Page_Element_Id VARCHAR(63) NOT NULL, + Page_Id VARCHAR(63) NOT NULL, + Rendering_Context VARCHAR(15) NOT NULL, + Workspace_JCR VARCHAR(15) NOT NULL, + Cacheable BOOLEAN NOT NULL, + Hits INT UNSIGNED NOT NULL, + Cache_Hits INT UNSIGNED NOT NULL, + Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + -- Updated_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB; + Index: main/plugin-web/i18n/messages_en.xml =================================================================== --- main/plugin-web/i18n/messages_en.xml (revision 20474) +++ main/plugin-web/i18n/messages_en.xml (working copy) @@ -290,6 +290,20 @@ You don't have an account? + Cache Monitoring + Monitoring database connection + Driver + JDBC Driver fully qualified class name + Url + JDBC URL for connecting to the database + User + User name for connecting to the database + Password + Password associated with the user name for connecting to the database + + General information @@ -326,6 +340,8 @@ Configure Site statistics Global statistics + Cache back statistics + Cache front statistics Delete Open website as a contributor New site @@ -449,7 +465,47 @@ Content count in all the sites Resource explorer Pages - + Loading statistics, please wait... + Actions + Reinitialize the statistics + Quit + Help + Statistics of the caches for the site {name} (back) + Reload + This screen displays some statistics about pages and page elements on the back-office.<br/><br/> \ + Statistics can be filtered by context (couple rendering context - workspace JCR).<br/>Thus, it is possible to select or deselect contexts and then reload the table by clicking on the 'reload' bouton.<br/><br/> \ + Statistics columns are grouped by category ('page' or 'page element').<br/>The 'cacheable' column represent the rate of page elements that are cacheable, weighted by element hits.<br/>This way, the rates 'cacheable' and 'efficiency' can be directly compared. + Pages + Page elements + Title + Hits + Cacheable + Efficiency + Hits + Cache hits + Statistics of the caches for the site {name} (front) + This screen displays statistics of the caches use on the front-office.<br/><br/> \ + It is possible to get more information by clicking on the appropriate button in the action menu.<br/>You will get the detailed statistics for Apache and the front-offiche.<br/><br/> \ + As it is for the back-office statistics, the 'cacheable' rate is weighted by the number of hits. + Display/Hide details + Main + Efficiency + Hits + Cache hits + Back + Titre + Cacheable + Total + Apache + Front + Total + Apache + Front + Total + Apache + Front + Hits + @@ -457,6 +513,7 @@ Skins management Skins management Actions + Import a Ametys skin Import a Ametys skin from a ZIP file Export to ZIP format Index: main/plugin-web/i18n/messages_fr.xml =================================================================== --- main/plugin-web/i18n/messages_fr.xml (revision 20474) +++ main/plugin-web/i18n/messages_fr.xml (working copy) @@ -290,6 +290,20 @@ Vous n'avez pas de compte ? + Monitoring Cache + Connexion à la base de données de monitoring + Pilote + Nom complet de la classe driver JDBC à charger + Url + Url JDBC de connexion au serveur de base de données + Utilisateur + Nom d'utilisateur à utiliser lors de la connexion au serveur de base de données + Mot de passe + Mot de passe associé au nom d'utilisateur à utiliser lors de la connexion au serveur de base de données + + Informations générales @@ -326,6 +340,8 @@ Configurer Statistiques du site Statistiques globales + Statistiques du cache (back) + Statistiques du cache (front) Supprimer Ouvrir dans le CMS Nouveau site @@ -449,6 +465,46 @@ Nombre de contenus dans l'ensemble des sites Explorateur de ressources Pages + Chargement des statistiques, veuillez patienter... + Actions + Réinitialiser les stats + Quitter + Aide + Statistiques des caches pour le site {name} (back) + Recharger + Ce tableau centralise les statistiques d'accès aux pages et éléments des pages sur le back-office.<br/><br/> \ + Les statistiques peuvent être filtrées par contexte (couple contexte de rendu - workspace JCR).<br/>Ainsi il est possible de sélectionner ou désélectionner des contextes puis de recharger le tableau à l'aide du bouton 'recharger'.<br/><br/> \ + Les statistiques sont regroupées selon qu'elles se rapportent aux pages ou aux éléments des pages.<br/>La colonne 'cacheable' représente le taux d'éléments des pages qui sont cacheables, pondéré par le nombre de hits par élément.<br/>En conséquence, les taux 'cacheable' et 'efficacité' peuvent être comparés directement. + Pages + Eléments des pages + Titre + Hits + Cacheable + Efficacité + Hits + Cache hits + Statistiques des caches pour le site {name} (front) + Ce tableau centralise les statistiques d'utilisation des caches côté front-office.<br/><br/> \ + Il est possible d'afficher plus d'informations en cliquant sur le bouton approprié dans le menu des actions.<br/>Vous obtiendrez le détails des statistiques pour Apache et le front-office.<br/><br/> \ + Comme pour les statistique côté back-office, le taux 'cacheable' est pondéré par le nombre de hits. + Afficher/Masquer détails + Général + Efficacité + Hits + Cache hits + Back + Titre + Cacheable + Total + Apache + Front + Total + Apache + Front + Total + Apache + Front + Hits + + + + + + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_DRIVER_DESCRIPTION + + + + com.mysql.jdbc.Driver + PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP + 10 + + + + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_URL_DESCRIPTION + + + + jdbc:mysql://servername/basename + PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP + 20 + + + + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_USER_DESCRIPTION + username + PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP + 30 + + + + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_PASSWORD_DESCRIPTION + password + PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY + PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP + 40 + + + + + + + + + + + + + + + + + + + + + + Index: main/plugin-web/pages/administrator/sites/cache-statistics-back-tree.xsl =================================================================== --- main/plugin-web/pages/administrator/sites/cache-statistics-back-tree.xsl (revision 0) +++ main/plugin-web/pages/administrator/sites/cache-statistics-back-tree.xsl (revision 0) @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + % + + + + + + + + + + + + + + + + + % + + + + + + + + + + + + + + + + + % + + + + + + + + + + + + + + + + + + % + + + + + + + + + + + + + + + + + % + + + + + + + + + + + + + + % + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + % + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + % + + + + + + + % + + + + + + + % + + + + + + + % + + + + + + + + + + + Index: main/plugin-web/pages/administrator/sites/cache-statistics-back.xsl =================================================================== --- main/plugin-web/pages/administrator/sites/cache-statistics-back.xsl (revision 0) +++ main/plugin-web/pages/administrator/sites/cache-statistics-back.xsl (revision 0) @@ -0,0 +1,78 @@ + + + + + + + + + + + + <i18n:translate> + <i18n:text i18n:key="PLUGINS_WEB_ADMINISTRATOR_CACHE_BACK_STATISTICS_TITLE"/> + <i18n:param name="name"><xsl:value-of select="site/@name"/></i18n:param> + </i18n:translate> + + + + + + + + + + + + + + + + + + + + + + + + Index: main/plugin-web/pages/administrator/sites/cache-statistics-servers-tree.xsl =================================================================== --- main/plugin-web/pages/administrator/sites/cache-statistics-servers-tree.xsl (revision 0) +++ main/plugin-web/pages/administrator/sites/cache-statistics-servers-tree.xsl (revision 0) @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N/A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N/A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: main/plugin-web/pages/administrator/sites/cache-statistics-servers.xsl =================================================================== --- main/plugin-web/pages/administrator/sites/cache-statistics-servers.xsl (revision 0) +++ main/plugin-web/pages/administrator/sites/cache-statistics-servers.xsl (revision 0) @@ -0,0 +1,81 @@ + + + + + + + + + + + + <i18n:translate> + <i18n:text i18n:key="PLUGINS_WEB_ADMINISTRATOR_CACHE_SERVERS_STATISTICS_TITLE"/> + <i18n:param name="name"><xsl:value-of select="site/@name"/></i18n:param> + </i18n:translate> + + + + + + + + + + + + + + + + + + + + + + + + Index: main/plugin-web/src/org/ametys/web/cachemonitoring/server/ApacheOnlyCacheStats.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/server/ApacheOnlyCacheStats.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/server/ApacheOnlyCacheStats.java (revision 0) @@ -0,0 +1,74 @@ +package org.ametys.web.cachemonitoring.server; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; + +import org.apache.commons.lang.BooleanUtils; + +import org.ametys.web.cachemonitoring.core.ResourceCacheStats; + +/** + * Apache cache stats objects + */ +public class ApacheOnlyCacheStats implements ResourceCacheStats +{ + private final String _site; + private final String _path; + private final boolean _cacheHit; + private final int _newHits; + + /** + * Ctor + * @param data + */ + public ApacheOnlyCacheStats(Map data) + { + _site = (String) data.get("Site"); + _path = (String) data.get("Path"); + _cacheHit = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit")); // converts null to false, avoiding NPE while unboxing. + _newHits = ((Long) data.get("increment")).intValue(); + } + + @Override + public ResourceCacheStatsType getType() + { + return ResourceCacheStatsType.APACHE_ONLY; + } + + @Override + public void configureSqlFind(PreparedStatement ps) throws SQLException + { + ps.setString(1, _site); + ps.setString(2, _path); + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _site); + ps.setString(2, _path); + ps.setInt(3, _newHits); + ps.setInt(4, getCacheHits()); + } + + @Override + public void configureSqlUpdate(PreparedStatement ps) throws SQLException + { + ps.setInt(1, _newHits); + ps.setInt(2, getCacheHits()); + ps.setString(3, _site); + ps.setString(4, _path); + } + + @Override + public int getHits() + { + return _newHits; + } + + private int getCacheHits() + { + return _cacheHit ? _newHits : 0; + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontOnlyCacheStats.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontOnlyCacheStats.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontOnlyCacheStats.java (revision 0) @@ -0,0 +1,88 @@ +package org.ametys.web.cachemonitoring.front; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; + +import org.apache.commons.lang.BooleanUtils; + +import org.ametys.web.cachemonitoring.core.ResourceCacheStats; + +/** + * Front (without apache) cache stats objects + */ +public class FrontOnlyCacheStats implements ResourceCacheStats +{ + private final String _site; + private final String _path; + private final boolean _cacheable; + private final boolean _cacheHit1; + private final boolean _cacheHit2; + private final int _newHits; + + /** + * Ctor + * @param data + */ + public FrontOnlyCacheStats(Map data) + { + _site = (String) data.get("Site"); + _path = (String) data.get("Ametys_Path"); + _cacheable = (Boolean) data.get("Cacheable"); + _cacheHit1 = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit_1")); // converts null to false, avoiding NPE while unboxing. + _cacheHit2 = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit_2")); // converts null to false, avoiding NPE while unboxing. + _newHits = ((Long) data.get("increment")).intValue(); + } + + @Override + public ResourceCacheStatsType getType() + { + return ResourceCacheStatsType.FRONT_ONLY; + } + + @Override + public void configureSqlFind(PreparedStatement ps) throws SQLException + { + ps.setString(1, _site); + ps.setString(2, _path); + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _site); + ps.setString(2, _path); + ps.setBoolean(3, _cacheable); + ps.setInt(4, _newHits); + ps.setInt(5, getCacheHits1()); + ps.setInt(6, getCacheHits2()); + } + + + @Override + public void configureSqlUpdate(PreparedStatement ps) throws SQLException + { + ps.setBoolean(1, _cacheable); + ps.setInt(2, _newHits); + ps.setInt(3, getCacheHits1()); + ps.setInt(4, getCacheHits2()); + ps.setString(5, _site); + ps.setString(6, _path); + } + + @Override + public int getHits() + { + return _newHits; + } + + private int getCacheHits1() + { + return _cacheHit1 ? _newHits : 0; + } + + private int getCacheHits2() + { + return _cacheHit2 ? _newHits : 0; + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontFromApacheCacheStats.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontFromApacheCacheStats.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontFromApacheCacheStats.java (revision 0) @@ -0,0 +1,113 @@ +package org.ametys.web.cachemonitoring.front; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; + +import org.apache.commons.lang.BooleanUtils; + +import org.ametys.web.cachemonitoring.core.ResourceCacheStats; + +/** + * Front from apache cache stats objects + */ +public class FrontFromApacheCacheStats implements ResourceCacheStats +{ + private final String _apacheSite; + private final String _apachePath; + private final boolean _apacheCacheHit; + + private final String _frontSite; + private final String _frontPath; + private final boolean _frontCacheable; + private final boolean _frontCacheHit1; + private final boolean _frontCacheHit2; + + private final int _newHits; + + /** + * Ctor + * @param data + */ + public FrontFromApacheCacheStats(Map data) + { + _apacheSite = (String) data.get("S_Site"); + _apachePath = (String) data.get("S_Path"); + _apacheCacheHit = BooleanUtils.toBoolean((Boolean) data.get("S_Cache_Hit")); // converts null to false, avoiding NPE while unboxing. + + _frontSite = (String) data.get("F_Site"); + _frontPath = (String) data.get("F_Ametys_Path"); + _frontCacheable = (Boolean) data.get("F_Cacheable"); + _frontCacheHit1 = BooleanUtils.toBoolean((Boolean) data.get("F_Cache_Hit_1")); // converts null to false, avoiding NPE while unboxing. + _frontCacheHit2 = BooleanUtils.toBoolean((Boolean) data.get("F_Cache_Hit_2")); // converts null to false, avoiding NPE while unboxing. + + _newHits = ((Long) data.get("increment")).intValue(); + } + + @Override + public ResourceCacheStatsType getType() + { + return ResourceCacheStatsType.FRONT_FROM_APACHE; + } + + @Override + public void configureSqlFind(PreparedStatement ps) throws SQLException + { + ps.setString(1, _apacheSite); + ps.setString(2, _apachePath); + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _apacheSite); + ps.setString(2, _apachePath); + ps.setInt(3, _newHits); + ps.setInt(4, getServerCacheHits()); + + ps.setString(5, _frontSite); + ps.setString(6, _frontPath); + ps.setBoolean(7, _frontCacheable); + ps.setInt(8, _newHits); + ps.setInt(9, getFrontCacheHits1()); + ps.setInt(10, getFrontCacheHits2()); + } + + @Override + public void configureSqlUpdate(PreparedStatement ps) throws SQLException + { + ps.setInt(1, _newHits); + ps.setInt(2, getServerCacheHits()); + + ps.setString(3, _frontSite); + ps.setString(4, _frontPath); + ps.setBoolean(5, _frontCacheable); + ps.setInt(6, _newHits); + ps.setInt(7, getFrontCacheHits1()); + ps.setInt(8, getFrontCacheHits2()); + + ps.setString(9, _apacheSite); + ps.setString(10, _apachePath); + } + + @Override + public int getHits() + { + return _newHits; + } + + private int getServerCacheHits() + { + return _apacheCacheHit ? _newHits : 0; + } + + private int getFrontCacheHits1() + { + return _frontCacheHit1 ? _newHits : 0; + } + + private int getFrontCacheHits2() + { + return _frontCacheHit2 ? _newHits : 0; + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceAccess.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceAccess.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceAccess.java (revision 0) @@ -0,0 +1,91 @@ +package org.ametys.web.cachemonitoring.back; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.apache.commons.lang.StringUtils; + +import org.ametys.web.cachemonitoring.back.PageElementAccess.PageElementType; +import org.ametys.web.cachemonitoring.core.ResourceAccess; +import org.ametys.web.renderingcontext.RenderingContext; + +/** + * Page resource access. Represent an access to a page on the back-office. + */ +public class PageResourceAccess implements ResourceAccess +{ + private final String _internalUuid; + private final String _pageID; + private final String _path; + private RenderingContext _renderingContext; + private String _workspaceJCR; + private boolean _cacheable; + + /** + * Ctor + * @param internalUuid + * @param pageID + * @param path + */ + public PageResourceAccess(String internalUuid, String pageID, String path) + { + _internalUuid = internalUuid; + _pageID = pageID; + _path = StringUtils.substringBefore(path, "?"); + } + + /** + * Create a page element resource access for this page resource access. + * @param pageElementID + * @param pageElementType + * @return the new PageElementAccess + */ + public PageElementAccess createPageElementAccess(String pageElementID, PageElementType pageElementType) + { + return PageElementAccess.createPageElementAccess(_internalUuid, _pageID, pageElementID, pageElementType, _renderingContext, _workspaceJCR); + } + + /** + * Set the rendering context + * @param renderingContext + */ + public void setRenderingContext(RenderingContext renderingContext) + { + _renderingContext = renderingContext; + } + + /** + * Set the jcr workspace + * @param workspaceJCR + */ + public void setWorkspaceJCR(String workspaceJCR) + { + _workspaceJCR = workspaceJCR; + } + + /** + * Set the resource as cacheable or not. + * @param cacheable + */ + public void setCacheable(boolean cacheable) + { + _cacheable = cacheable; + } + + @Override + public MonitoredResourceType getType() + { + return MonitoredResourceType.BACK_PAGE_RESOURCE; + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _internalUuid); + ps.setString(2, _pageID); + ps.setString(3, _path); + ps.setString(4, _renderingContext.toString()); + ps.setString(5, _workspaceJCR); + ps.setBoolean(6, _cacheable); + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementAccess.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementAccess.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementAccess.java (revision 0) @@ -0,0 +1,121 @@ +package org.ametys.web.cachemonitoring.back; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +import org.ametys.web.cachemonitoring.core.ResourceAccess; +import org.ametys.web.renderingcontext.RenderingContext; +import org.ametys.web.repository.page.ZoneItem.ZoneType; + +/** + * Page element resource access. Represent an access to a element of a page on the back-office. + */ +public class PageElementAccess implements ResourceAccess +{ + /** Type of a PageElementAccess. */ + public enum PageElementType + { + /** A service */ + SERVICE, + /** A content */ + CONTENT, + /** An inputdata */ + INPUTDATA; + + @Override + public String toString() + { + return super.name().toLowerCase(); + } + + /** + * Utility method the get the PageElementType corresponding to a ZoneType + * @param ziType + * @return the PageElementType + */ + public static PageElementType fromZoneItemType(ZoneType ziType) + { + switch (ziType) + { + case CONTENT: + return CONTENT; + case SERVICE: + return SERVICE; + default: + throw new IllegalArgumentException("Unknown ZoneType : " + ziType); + } + } + } + + private final String _internalUuid; + private final String _pageElementID; + private final PageElementType _pageElementType; + private final String _pageID; + private final RenderingContext _renderingContext; + private final String _workspaceJCR; + private boolean _cacheable; + private Boolean _cacheHit; + + /** + * Ctor + * @param uuid + * @param peid + * @param pet + * @param pageID + * @param rc + * @param ws + */ + protected PageElementAccess(String uuid, String peid, PageElementType pet, String pageID, RenderingContext rc, String ws) + { + _internalUuid = uuid; + _pageElementID = peid; + _pageElementType = pet; + _pageID = pageID; + _renderingContext = rc; + _workspaceJCR = ws; + } + + static PageElementAccess createPageElementAccess(String uuid, String pageID, String pageElementID, PageElementType pageElementType, RenderingContext rc, String ws) + { + return new PageElementAccess(uuid, pageElementID, pageElementType, pageID, rc, ws); + } + + /** + * Set the resource as cacheable or not. + * @param cacheable + */ + public void setCacheable(boolean cacheable) + { + _cacheable = cacheable; + } + + /** + * Set the cache hit to true/false + * @param hit + */ + public void setCacheHit(boolean hit) + { + _cacheHit = hit; + } + + @Override + public MonitoredResourceType getType() + { + return MonitoredResourceType.BACK_PAGE_ELEMENT; + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _internalUuid); + ps.setString(2, _pageElementID); + ps.setString(3, _pageElementType.toString()); + ps.setString(4, _pageID); + ps.setString(5, _renderingContext.toString()); + ps.setString(6, _workspaceJCR); + ps.setBoolean(7, _cacheable); + // Handle null cases, avoid NPE and set NULL into the db in thoses cases. + ps.setObject(8, _cacheHit, Types.BOOLEAN); + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementCacheStats.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementCacheStats.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementCacheStats.java (revision 0) @@ -0,0 +1,89 @@ +package org.ametys.web.cachemonitoring.back; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; + +import org.apache.commons.lang.BooleanUtils; + +import org.ametys.web.cachemonitoring.core.ResourceCacheStats; + +/** + * Page element cache stats objects + */ +public class PageElementCacheStats implements ResourceCacheStats +{ + private final String _pageElementId; + private final String _pageId; + private final String _renderingContext; + private final String _workspaceJCR; + private final boolean _cacheable; + private final boolean _cacheHit; + private final int _newHits; + + /** + * Ctor + * @param data + */ + public PageElementCacheStats(Map data) + { + _pageElementId = (String) data.get("Page_Element_Id"); + _pageId = (String) data.get("Page_Id"); + _renderingContext = (String) data.get("Rendering_Context"); + _workspaceJCR = (String) data.get("Workspace_JCR"); + _cacheable = (Boolean) data.get("Cacheable"); + _cacheHit = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit")); // converts null to false, avoiding NPE while unboxing. + _newHits = ((Long) data.get("increment")).intValue(); + } + + @Override + public ResourceCacheStatsType getType() + { + return ResourceCacheStatsType.BACK_PAGE_ELEMENT; + } + + @Override + public void configureSqlFind(PreparedStatement ps) throws SQLException + { + ps.setString(1, _pageElementId); + ps.setString(2, _pageId); + ps.setString(3, _renderingContext); + ps.setString(4, _workspaceJCR); + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _pageElementId); + ps.setString(2, _pageId); + ps.setString(3, _renderingContext); + ps.setString(4, _workspaceJCR); + ps.setBoolean(5, _cacheable); + ps.setInt(6, _newHits); + ps.setInt(7, getCacheHits()); + } + + @Override + public void configureSqlUpdate(PreparedStatement ps) throws SQLException + { + ps.setBoolean(1, _cacheable); + ps.setInt(2, _newHits); + ps.setInt(3, getCacheHits()); + + ps.setString(4, _pageElementId); + ps.setString(5, _pageId); + ps.setString(6, _renderingContext); + ps.setString(7, _workspaceJCR); + } + + @Override + public int getHits() + { + return _newHits; + } + + private int getCacheHits() + { + return _cacheHit ? _newHits : 0; + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceCacheStats.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceCacheStats.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceCacheStats.java (revision 0) @@ -0,0 +1,77 @@ +package org.ametys.web.cachemonitoring.back; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; + +import org.ametys.web.cachemonitoring.core.ResourceCacheStats; + +/** + * Page cache stats objects + */ +public class PageResourceCacheStats implements ResourceCacheStats +{ + private final String _pageId; + private final String _path; + private final String _renderingContext; + private final String _workspaceJCR; + private final boolean _cacheable; + private final int _newHits; + + /** + * Ctor + * @param data + */ + public PageResourceCacheStats(Map data) + { + _pageId = (String) data.get("Page_Id"); + _path = (String) data.get("Page_Path"); + _renderingContext = (String) data.get("Rendering_Context"); + _workspaceJCR = (String) data.get("Workspace_JCR"); + _cacheable = (Boolean) data.get("Cacheable"); + _newHits = ((Long) data.get("increment")).intValue(); + } + + @Override + public ResourceCacheStatsType getType() + { + return ResourceCacheStatsType.BACK_PAGE; + } + + @Override + public void configureSqlFind(PreparedStatement ps) throws SQLException + { + ps.setString(1, _pageId); + ps.setString(2, _renderingContext); + ps.setString(3, _workspaceJCR); + } + + @Override + public void configureSqlInsert(PreparedStatement ps) throws SQLException + { + ps.setString(1, _pageId); + ps.setString(2, _path); + ps.setString(3, _renderingContext); + ps.setString(4, _workspaceJCR); + ps.setBoolean(5, _cacheable); + ps.setInt(6, _newHits); + } + + @Override + public void configureSqlUpdate(PreparedStatement ps) throws SQLException + { + ps.setString(1, _path); + ps.setBoolean(2, _cacheable); + ps.setInt(3, _newHits); + + ps.setString(4, _pageId); + ps.setString(5, _renderingContext); + ps.setString(6, _workspaceJCR); + } + + @Override + public int getHits() + { + return _newHits; + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/generators/PageElementCacheStatsGenerator.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/generators/PageElementCacheStatsGenerator.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/generators/PageElementCacheStatsGenerator.java (revision 0) @@ -0,0 +1,527 @@ +package org.ametys.web.cachemonitoring.generators; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.generation.ServiceableGenerator; +import org.apache.cocoon.xml.AttributesImpl; +import org.apache.cocoon.xml.XMLUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.joda.time.Duration; +import org.joda.time.format.PeriodFormat; +import org.xml.sax.SAXException; + +import org.ametys.cms.repository.Content; +import org.ametys.plugins.repository.AmetysObjectIterable; +import org.ametys.plugins.repository.AmetysRepositoryException; +import org.ametys.plugins.repository.UnknownAmetysObjectException; +import org.ametys.runtime.datasource.ConnectionHelper; +import org.ametys.web.cachemonitoring.core.CacheMonitoringSqlStatements; +import org.ametys.web.inputdata.InputDataExtensionPoint; +import org.ametys.web.repository.page.Page; +import org.ametys.web.repository.page.Zone; +import org.ametys.web.repository.page.ZoneItem; +import org.ametys.web.repository.page.ZoneItem.ZoneType; +import org.ametys.web.repository.site.Site; +import org.ametys.web.repository.site.SiteManager; +import org.ametys.web.repository.sitemap.Sitemap; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; + +/** + * Cache stats generator for related to the back-office (pages and page + * elements) + */ +public class PageElementCacheStatsGenerator extends ServiceableGenerator +{ + /** Input Data extension point */ + protected InputDataExtensionPoint _inputDataExt; + + /** Ametys resolver */ + protected SiteManager _siteManager; + + /** Map linking page Id to page stats entries */ + protected Multimap _pageIdMap; + + /** Map of stats entries */ + protected Multimap _statsMap; + + @Override + public void service(ServiceManager sm) throws ServiceException + { + super.service(sm); + _inputDataExt = (InputDataExtensionPoint) sm.lookup(InputDataExtensionPoint.ROLE); + _siteManager = (SiteManager) sm.lookup(SiteManager.ROLE); + } + + @Override + public void recycle() + { + _statsMap = null; + _pageIdMap = null; + + super.recycle(); + } + + @Override + public void generate() throws IOException, SAXException, ProcessingException + { + String siteName = parameters.getParameter("siteName", ""); + Site site = null; + + try + { + site = _siteManager.getSite(siteName); + } + catch (UnknownAmetysObjectException e) + { + String message = "The site '" + siteName + "' does not exist."; + getLogger().error(message, e); + throw new ProcessingException(message, e); + } + + _pageIdMap = HashMultimap.create(); // does not allow duplicate key-value entries. + _statsMap = LinkedListMultimap.create(); // can contain non-distinct key-value pairs. + + _initializePECacheStats(); + + List contexts = _getContextsFilter(parameters.getParameter("contexts", null)); + + Long start = null; + if (getLogger().isDebugEnabled()) + { + start = System.currentTimeMillis(); + + getLogger().debug("Starting to SAX back-office cache statistics."); + } + + contentHandler.startDocument(); + XMLUtils.startElement(contentHandler, "stats"); + + // sax info for the given site. + _saxStats(site, contexts); + + XMLUtils.endElement(contentHandler, "stats"); + contentHandler.endDocument(); + + if (getLogger().isDebugEnabled()) + { + if (start != null) + { + long end = System.currentTimeMillis(); + String duration = PeriodFormat.getDefault().print(new Duration(end - start).toPeriod()); + getLogger().debug(String.format("The SAX process of the back-office cache statistics took %s", duration)); + } + } + } + + /** + * Initialize statistics by retrieving data from the monitoring database. + * @throws ProcessingException + */ + private void _initializePECacheStats() throws ProcessingException + { + Connection conn = null; + Statement st = null; + ResultSet rs = null; + + try + { + conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID); + + String sql = _getPECacheStatsQuery(); + st = conn.createStatement(); + rs = st.executeQuery(sql); + + _processCacheStatsResultSet(rs); + } + catch (SQLException e) + { + String msg = "SQL Error while retrieving page element cache stats"; + getLogger().error(msg); + throw new ProcessingException(msg, e); + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(st); + ConnectionHelper.cleanup(conn); + } + } + + private static String _getPECacheStatsQuery() + { + StringBuilder sb = new StringBuilder(); + + sb.append(" SELECT p.Page_Id, p.Rendering_Context, p.Workspace_JCR, p.Cacheable AS P_Cacheable, p.Hits AS P_Hits,"); + sb.append(" pe.Page_Element_Id, pe.Cacheable AS PE_Cacheable, pe.Hits AS PE_Hits, pe.Cache_Hits AS PE_Cache_Hits"); + sb.append(" FROM CACHE_BACK_STATS p"); + sb.append(" LEFT OUTER JOIN Cache_Back_Page_Element_Stats pe"); + sb.append(" ON p.Page_Id = pe.Page_Id AND p.Rendering_Context = pe.Rendering_Context AND p.Workspace_JCR = pe.Workspace_JCR"); + + return sb.toString(); + } + + private void _processCacheStatsResultSet(ResultSet rs) throws SQLException + { + ResultSetMetaData meta = rs.getMetaData(); + String[] cols = new String[meta.getColumnCount()]; + + for (int i = 0; i < cols.length; i++) + { + cols[i] = meta.getColumnLabel(i + 1); + } + + while (rs.next()) + { + Map data = new HashMap(); + for (int i = 0; i < cols.length; i++) + { + data.put(cols[i], rs.getObject(i + 1)); + } + + PageStatsEntry pageStatsEntry = new PageStatsEntry(data); + PageElementStatsEntry pageElementStatsEntry = new PageElementStatsEntry(data); + + _pageIdMap.put(pageStatsEntry._pageId, pageStatsEntry); + _statsMap.put(pageStatsEntry, pageElementStatsEntry); + } + } + + /** + * Analyse the 'contexts' parameter to filter the data to sax depending on requested contexts. + * A context is a couple as follows : 'renderingContext' - 'workspaceJcr' + * @param parameter + */ + private List _getContextsFilter(String contexts) + { + if (StringUtils.isEmpty(contexts)) + { + return null; + } + + return Arrays.asList(StringUtils.split(contexts, '#')); + } + + private void _saxStats(Site site, List contexts) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", site.getId()); + attrs.addCDATAAttribute("name", site.getName()); + XMLUtils.startElement(contentHandler, "site", attrs); + + for (Sitemap sitemap : site.getSitemaps()) + { + _saxStats(sitemap, contexts); + } + + XMLUtils.endElement(contentHandler, "site"); + } + + private void _saxStats(Sitemap sitemap, List contexts) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", sitemap.getId()); + attrs.addCDATAAttribute("name", sitemap.getName()); + XMLUtils.startElement(contentHandler, "sitemap", attrs); + + for (Page page : sitemap.getChildrenPages()) + { + _saxPageStats(page, contexts); + } + + XMLUtils.endElement(contentHandler, "sitemap"); + } + + private void _saxPageStats(Page page, List contexts) throws SAXException + { + String pageId = page.getId(); + + // SAX'ing page info + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", pageId); + attrs.addCDATAAttribute("title", page.getTitle()); + attrs.addCDATAAttribute("name", page.getName()); + attrs.addCDATAAttribute("path", page.getPathInSitemap()); + XMLUtils.startElement(contentHandler, "page", attrs); + + // SAX stats by contexts + if (_pageIdMap.containsKey(pageId)) + { + List zones = new ArrayList(); + for (Zone zone : page.getZones()) + { + zones.add(zone); + } + + _saxPageStatsContexts(page, zones, contexts); + } + + for (Page child : page.getChildrenPages()) + { + _saxPageStats(child, contexts); + } + + XMLUtils.endElement(contentHandler, "page"); + } + + private void _saxPageStatsContexts(Page page, List zones, List contexts) throws SAXException + { + Collection pageStatsEntries = _pageIdMap.get(page.getId()); + + for (PageStatsEntry pageStats : pageStatsEntries) + { + // Filter out contexts that are not in the 'contexts' list. + // null lists implies that all contexts are allowed + if (contexts == null || contexts.contains(pageStats.getContext())) + { + _saxPageStatsContext(pageStats, zones); + } + } + } + + private void _saxPageStatsContext(PageStatsEntry pageStats, List zones) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("renderingContext", pageStats._renderingContext); + attrs.addCDATAAttribute("workspaceJCR", pageStats._workspaceJCR); + attrs.addCDATAAttribute("cacheable", String.valueOf(pageStats._cacheable)); + XMLUtils.startElement(contentHandler, "context", attrs); + + XMLUtils.createElement(contentHandler, "hits", String.valueOf(pageStats._hits)); + + Collection pageElementStats = _statsMap.get(pageStats); + + // Iterate on existing zones + for (Zone zone : zones) + { + _saxZoneStats(zone, pageElementStats); + } + + // Iterate on input data + _saxInputDataStats(pageElementStats); + + XMLUtils.endElement(contentHandler, "context"); + } + + private void _saxZoneStats(Zone zone, Collection pageElementStats) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", zone.getId()); + attrs.addCDATAAttribute("name", zone.getName()); + XMLUtils.startElement(contentHandler, "zone", attrs); + + AmetysObjectIterable zoneItems = zone.getZoneItems(); + int index = 0; + for (ZoneItem zoneItem : zoneItems) + { + index++; + PageElementStatsEntry entry = _findPageElementStatsEntry(pageElementStats, zoneItem.getId()); + if (entry != null) + { + _saxZoneItemStats(zoneItem, entry, index); + } + } + + XMLUtils.endElement(contentHandler, "zone"); + } + + + private void _saxZoneItemStats(ZoneItem zoneItem, PageElementStatsEntry entry, int index) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", entry._pageElementID); + attrs.addCDATAAttribute("index", String.valueOf(index)); + attrs.addCDATAAttribute("type", zoneItem.getType().toString().toLowerCase()); + attrs.addCDATAAttribute("cacheable", String.valueOf(entry._cacheable)); + _addZoneItemSaxInfo(attrs, zoneItem); + XMLUtils.startElement(contentHandler, "zoneitem", attrs); + + XMLUtils.createElement(contentHandler, "hits", String.valueOf(entry._hits)); + XMLUtils.createElement(contentHandler, "cacheHits", String.valueOf(entry._cacheHits)); + + XMLUtils.endElement(contentHandler, "zoneitem"); + } + + private void _addZoneItemSaxInfo(AttributesImpl attrs, ZoneItem zoneItem) + { + try + { + switch (zoneItem.getType()) + { + case CONTENT: + Content content = zoneItem.getContent(); + attrs.addCDATAAttribute("contentId", content.getId()); + attrs.addCDATAAttribute("contentType", content.getType()); + break; + case SERVICE: + attrs.addCDATAAttribute("serviceId", zoneItem.getServiceId()); + break; + default: + throw new IllegalArgumentException("Illegal zone item type. Allowed values are : " + Arrays.asList(ZoneType.values())); + } + } + catch (AmetysRepositoryException e) + { + // Ignore + } + } + + private void _saxInputDataStats(Collection pageElementStats) throws SAXException + { + XMLUtils.startElement(contentHandler, "inputdata"); + + for (String id : _inputDataExt.getExtensionsIds()) + { + PageElementStatsEntry entry = _findPageElementStatsEntry(pageElementStats, id); + _saxInputDataStatsEntry(entry, id); + } + + XMLUtils.endElement(contentHandler, "inputdata"); + } + + private void _saxInputDataStatsEntry(PageElementStatsEntry entry, String inputDataId) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("id", inputDataId); + attrs.addCDATAAttribute("title", inputDataId); + attrs.addCDATAAttribute("cacheable", String.valueOf(entry._cacheable)); + XMLUtils.startElement(contentHandler, "inputdataitem", attrs); + + XMLUtils.createElement(contentHandler, "hits", String.valueOf(entry._hits)); + XMLUtils.createElement(contentHandler, "cacheHits", String.valueOf(entry._cacheHits)); + + XMLUtils.endElement(contentHandler, "inputdataitem"); + } + + private PageElementStatsEntry _findPageElementStatsEntry(Collection pageElementStats, String id) + { + for (PageElementStatsEntry entry : pageElementStats) + { + if (id.equals(entry._pageElementID)) + { + return entry; + } + } + return null; + } + + /** + * Object model representing an entry of stats for a Page + */ + @SuppressWarnings("javadoc") + protected class PageStatsEntry + { + protected final String _pageId; + protected final String _renderingContext; + protected final String _workspaceJCR; + protected final boolean _cacheable; + protected final int _hits; + + /** + * Ctor + */ + protected PageStatsEntry(Map data) + { + _pageId = (String) data.get("Page_Id"); + _renderingContext = (String) data.get("Rendering_Context"); + _workspaceJCR = (String) data.get("Workspace_JCR"); + _cacheable = (Boolean) data.get("P_Cacheable"); + _hits = __toInteger(data.get("P_Hits")); + } + + private Integer __toInteger(Object value) + { + Long lValue = (Long) value; + return lValue != null ? lValue.intValue() : null; + } + + public String getContext() + { + return _renderingContext + '-' + _workspaceJCR; + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + if (obj == this) + { + return true; + } + if (!(obj instanceof PageStatsEntry)) + { + return false; + } + + PageStatsEntry that = (PageStatsEntry) obj; + + EqualsBuilder eb = new EqualsBuilder(); + eb.append(_pageId, that._pageId); + eb.append(_renderingContext, that._renderingContext); + eb.append(_workspaceJCR, that._workspaceJCR); + return eb.isEquals(); + } + + @Override + public int hashCode() + { + HashCodeBuilder hsb = new HashCodeBuilder(); + hsb.append(_pageId).append(_renderingContext).append(_workspaceJCR); + return hsb.toHashCode(); + } + } + + /** + * Object model representing an entry of stats for a PageElement + */ + @SuppressWarnings("javadoc") + protected class PageElementStatsEntry + { + protected final String _pageElementID; + protected final String _renderingContext; + protected final String _workspaceJCR; + protected final Boolean _cacheable; + protected final Integer _hits; + protected final Integer _cacheHits; + + /** + * Ctor + */ + protected PageElementStatsEntry(Map data) + { + _pageElementID = (String) data.get("Page_Element_Id"); + _renderingContext = (String) data.get("Rendering_Context"); + _workspaceJCR = (String) data.get("Workspace_JCR"); + _cacheable = (Boolean) data.get("PE_Cacheable"); + _hits = __toInteger(data.get("PE_Hits")); + _cacheHits = __toInteger(data.get("PE_Cache_Hits")); + } + + private Integer __toInteger(Object value) + { + Long lValue = (Long) value; + return lValue != null ? lValue.intValue() : null; + } + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/generators/ServersCacheStatsGenerator.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/generators/ServersCacheStatsGenerator.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/generators/ServersCacheStatsGenerator.java (revision 0) @@ -0,0 +1,783 @@ +package org.ametys.web.cachemonitoring.generators; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; + +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.generation.ServiceableGenerator; +import org.apache.cocoon.xml.AttributesImpl; +import org.apache.cocoon.xml.XMLUtils; +import org.apache.commons.lang.StringUtils; +import org.joda.time.Duration; +import org.joda.time.format.PeriodFormat; +import org.xml.sax.SAXException; + +import org.ametys.plugins.repository.UnknownAmetysObjectException; +import org.ametys.runtime.datasource.ConnectionHelper; +import org.ametys.web.cachemonitoring.core.CacheMonitoringSqlStatements; +import org.ametys.web.repository.site.Site; +import org.ametys.web.repository.site.SiteManager; +import org.ametys.web.site.SitesGenerator; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + + +/** + * Cache stats generator grouping data collected across each server cache (http + * server/front/back) + */ +public class ServersCacheStatsGenerator extends ServiceableGenerator +{ + /** List of paths used to during the sanitize process of the server path */ + protected static final String[] _SPECIAL_PATH_PREFIXS = new String[]{"/skins/", "/plugins/", "/kernel", "/_external/"}; + + /** + * This multimap associates site names to a list of prefix. This is needed + * to sanitize the server path. + */ + protected Multimap _pathSanitizer; + + /** + * Multimaps representing the all the paths to the resources in a recursive + * way. The map key's are the site names. + */ + protected Map> _pathMaps; + + /** + * Multimap containing apache stats entries classified by site name and path + */ + protected Map> _fromApacheStats; + + /** + * Multimap containing (only) front stats entries classified by site name + * and path + */ + protected Map> _fromFrontOnlyStats; + + /** Map containing back stats entries classified by path */ + protected Map _backStats; + + + /** Ametys resolver */ + protected SiteManager _siteManager; + + @Override + public void service(ServiceManager sm) throws ServiceException + { + super.service(sm); + _siteManager = (SiteManager) sm.lookup(SiteManager.ROLE); + } + + @Override + public void generate() throws IOException, SAXException, ProcessingException + { + String siteName = parameters.getParameter("siteName", ""); + Site site = null; + + try + { + site = _siteManager.getSite(siteName); + } + catch (UnknownAmetysObjectException e) + { + String message = "The site '" + siteName + "' does not exist."; + getLogger().error(message, e); + throw new ProcessingException(message, e); + } + + // Retrieve raw info from DB. + List rawStats = _getStatsFromDb(siteName); + + _pathMaps = new HashMap>(); + _fromApacheStats = new HashMap>(); + _fromFrontOnlyStats = new HashMap>(); + _backStats = new HashMap(); + _pathSanitizer = ArrayListMultimap.create(); + + // Populate and organize stats maps. + _initializeStats(rawStats); + + Long start = null; + if (getLogger().isDebugEnabled()) + { + start = System.currentTimeMillis(); + + getLogger().debug("Starting to SAX cache statistics for Apache and the Front-office"); + } + + // Start SAX'ing + contentHandler.startDocument(); + XMLUtils.startElement(contentHandler, "stats"); + + // sax info for the given site. + _saxStatsBySite(site); + + // sax orphan (resource from front without site info). + _saxStatsOrphanEntries(); + + XMLUtils.endElement(contentHandler, "stats"); + contentHandler.endDocument(); + + if (getLogger().isDebugEnabled()) + { + if (start != null) + { + long end = System.currentTimeMillis(); + String duration = PeriodFormat.getDefault().print(new Duration(end - start).toPeriod()); + getLogger().debug(String.format("The SAX process of the Apache and Front-office cache statistics took %s", duration)); + } + } + } + + @Override + public void recycle() + { + _pathMaps = null; + _fromApacheStats = null; + _fromFrontOnlyStats = null; + _backStats = null; + _pathSanitizer = null; + + super.recycle(); + } + + /** + * Retrieves raw statistics information + * @param siteName + * @return a list of {@link StatsEntry} + * @throws ProcessingException + */ + private List _getStatsFromDb(String siteName) throws ProcessingException + { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + + try + { + conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID); + + String sql = _getStatsQuery(); + + ps = conn.prepareStatement(sql); + ps.setString(1, siteName); + ps.setString(2, siteName); + + rs = ps.executeQuery(); + + return _processCacheStatsResultSet(rs); + } + catch (SQLException e) + { + String msg = "SQL Error while retrieving cache stats"; + getLogger().error(msg); + throw new ProcessingException(msg, e); + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(ps); + ConnectionHelper.cleanup(conn); + } + } + + private static String _getStatsQuery() + { + StringBuilder sb = new StringBuilder(); + + sb.append(" SELECT F.Server_Site, F.Server_Path, F.Server_Hits, F.Server_Cache_Hits,"); + sb.append(" F.Front_Site, F.Front_Path, F.Front_Cacheable, F.Front_Hits, F.Front_Cache_Hits_1, F.Front_Cache_Hits_2,"); + sb.append(" B.Page_Id, B.Page_Path, B.Cacheable, B.Hits"); + sb.append(" FROM CACHE_FRONT_STATS F"); + sb.append(" LEFT OUTER JOIN CACHE_BACK_STATS B"); + sb.append(" ON F.Front_Path = B.Page_Path AND F.Front_Path is not NULL AND B.Rendering_Context = 'front'"); + sb.append(" WHERE F.Server_Site = ? OR F.Server_Site = '' OR F.Server_Site is NULL"); + sb.append(" OR F.Front_Site = ? OR F.Front_Site = '' OR F.Front_Site is NULL"); + + return sb.toString(); + } + + private List _processCacheStatsResultSet(ResultSet rs) throws SQLException + { + ResultSetMetaData meta = rs.getMetaData(); + String[] cols = new String[meta.getColumnCount()]; + + for (int i = 0; i < cols.length; i++) + { + cols[i] = meta.getColumnLabel(i + 1); + } + + List entries = new ArrayList(); + + while (rs.next()) + { + Map data = new HashMap(); + for (int i = 0; i < cols.length; i++) + { + data.put(cols[i], rs.getObject(i + 1)); + } + + entries.add(new RawStatsEntry(data)); + } + + return entries; + } + + /** + * Organize the different Map of stats + * @param rawStats + */ + private void _initializeStats(List rawStats) + { + // Iterate over raw entries and do the logic to populate + // Apache/FrontOnly/Back stats map. + for (RawStatsEntry rawStatsEntry : rawStats) + { + if (rawStatsEntry.hasApacheInfo()) + { + FrontFromApacheStatsEntry apacheEntry = new FrontFromApacheStatsEntry(rawStatsEntry._data); + _addToApacheStats(apacheEntry); + } + else + { + FrontFromFrontStatsEntry frontEntry = new FrontFromFrontStatsEntry(rawStatsEntry._data); + _addToFrontOnlyStats(frontEntry); + } + + if (rawStatsEntry.hasBackInfo()) + { + BackStatsEntry backEntry = new BackStatsEntry(rawStatsEntry._data); + + // remove .html in page name to avoid issues when a page as children pages. + String backPath = StringUtils.removeEnd(backEntry._pagePath, ".html"); + _backStats.put(backPath, backEntry); + } + } + } + + private void _addToApacheStats(FrontFromApacheStatsEntry apacheEntry) + { + // could be null in case of a resource. That's ok, we will handle this case later when SAX'ing. + String siteName = apacheEntry._serverSite; + + Map siteMap = _fromApacheStats.get(siteName); + if (siteMap == null) + { + siteMap = new HashMap(); + _fromApacheStats.put(siteName, siteMap); + } + + // The path must be sanitized to be of the following form: + // /site/lang/page-path or /skin/... or /plugins/... etc... + String sanitizedPath = apacheEntry.getSanitizedPath(); + + // remove .html in page name to avoid issues when a page as children pages. + sanitizedPath = StringUtils.removeEnd(sanitizedPath, ".html"); + + FrontFromApacheStatsEntry previousApacheEntry = siteMap.put(sanitizedPath, apacheEntry); + + // Must merge the info if a previous entry was already in the map (this can happens when a site as several url aliases) + if (previousApacheEntry != null) + { + apacheEntry.merge(previousApacheEntry); + } + + // Also register this new path into the path maps. + _registerPath(apacheEntry._serverSite, sanitizedPath); + } + + private void _addToFrontOnlyStats(FrontFromFrontStatsEntry frontEntry) + { + // could be null in case of a resource. That's ok, we will handle this case later when SAX'ing. + String siteName = StringUtils.defaultIfEmpty(frontEntry._frontSite, null); + + Map siteMap = _fromFrontOnlyStats.get(siteName); + if (siteMap == null) + { + siteMap = new HashMap(); + _fromFrontOnlyStats.put(siteName, siteMap); + } + + // remove .html in page name to avoid issues when a page as children pages. + String frontPath = StringUtils.removeEnd(frontEntry._frontPath, ".html"); + siteMap.put(frontPath, frontEntry); + + // Also register this new path into the path maps. + _registerPath(siteName, frontPath); + } + + private void _registerPath(String serverSite, String path) + { + Multimap pathMap = _pathMaps.get(serverSite); + if (pathMap == null) + { + pathMap = HashMultimap.create(); // does not allow duplicate key-value entries. + _pathMaps.put(serverSite, pathMap); + } + + String[] splitPath = StringUtils.split(path, '/'); + _internalRegisterPath(pathMap, StringUtils.EMPTY, new LinkedList(Arrays.asList(splitPath))); + } + + /** + * Populate the path map using recursion. + * @param pathMap Multimap representing the paths to the resources within a site. + * @param consumed the consumed part of the current path being registered. + * @param tail tail of the current path being registered. The tail will be consumed in the recursive nested calls of this internal function. + */ + private void _internalRegisterPath(Multimap pathMap, String consumed, LinkedList tail) + { + if (!tail.isEmpty()) + { + String head = tail.removeFirst(); + pathMap.put(consumed, head); + _internalRegisterPath(pathMap, consumed + '/' + head, tail); + } + } + + private void _saxStatsBySite(Site site) throws SAXException + { + String siteName = site.getName(); + + Multimap pathMap = _pathMaps.get(siteName); + if (pathMap == null) + { + return; + } + + Collection entryPoints = pathMap.get(StringUtils.EMPTY); + if (entryPoints.isEmpty()) + { + return; + } + + Map apacheStats = _fromApacheStats.get(siteName); + if (apacheStats == null) + { + apacheStats = Collections.EMPTY_MAP; + } + + Map frontOnlyStats = _fromFrontOnlyStats.get(siteName); + if (frontOnlyStats == null) + { + frontOnlyStats = Collections.EMPTY_MAP; + } + + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("name", siteName); + attrs.addCDATAAttribute("id", site.getId()); + XMLUtils.startElement(contentHandler, "site", attrs); + + for (String name : entryPoints) + { + _saxStatsEntry(name, '/' + name, pathMap, apacheStats, frontOnlyStats); + } + + XMLUtils.endElement(contentHandler, "site"); + } + + private void _saxStatsEntry(String name, String path, Multimap pathMap, Map apacheEntries, Map frontEntries) throws SAXException + { + FrontFromApacheStatsEntry apacheEntry = apacheEntries.get(path); + FrontFromFrontStatsEntry frontEntry = frontEntries.get(path); + BackStatsEntry backEntry = _backStats.get(path); + + AttributesImpl attrs = new AttributesImpl(); + + String type = "folder"; + if (frontEntry != null) + { + type = frontEntry._frontPath.endsWith(".html") ? "page" : "asset"; + } + else if (apacheEntry != null) + { + if (apacheEntry.hasFrontInfo()) + { + type = apacheEntry._frontPath.endsWith(".html") ? "page" : "asset"; + } + else + { + type = apacheEntry._serverPath.endsWith(".html") ? "page" : "asset"; + } + } + + Boolean cacheable = null; + if (apacheEntry != null) + { + // No front info means implicit cache hit on the apache cache. + cacheable = apacheEntry.hasFrontInfo() ? apacheEntry._frontCacheable : true; + } + else if (frontEntry != null) + { + cacheable = frontEntry._frontCacheable; + } + + attrs.addCDATAAttribute("type", type); + attrs.addCDATAAttribute("name", "page".equals(type) ? name + ".html" : name); + attrs.addCDATAAttribute("path", path); + __addAttrIfNotNull(attrs, "cacheable", cacheable); + XMLUtils.startElement(contentHandler, "resource", attrs); + + _saxStatsApacheEntry(apacheEntry); + _saxStatsFrontEntry(frontEntry); + _saxStatsBackEntry(backEntry); + + // Recursive call on children + for (String childName : pathMap.get(path)) + { + _saxStatsEntry(childName, path + '/' + childName, pathMap, apacheEntries, frontEntries); + } + + XMLUtils.endElement(contentHandler, "resource"); + } + + private void _saxStatsApacheEntry(FrontFromApacheStatsEntry apacheEntry) throws SAXException + { + if (apacheEntry == null) + { + return; + } + + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("type", "apache"); + __addAttrIfNotNull(attrs, "apacheSite", apacheEntry._serverSite); + __addAttrIfNotNull(attrs, "apachePath", apacheEntry._serverPath); + + // Add front info if available + if (apacheEntry.hasFrontInfo()) + { + __addAttrIfNotNull(attrs, "frontSite", apacheEntry._frontSite); + attrs.addCDATAAttribute("frontPath", apacheEntry._frontPath); + attrs.addCDATAAttribute("frontCacheable", String.valueOf(apacheEntry._frontCacheable)); + } + + XMLUtils.startElement(contentHandler, "entry", attrs); + + XMLUtils.createElement(contentHandler, "apacheHits", String.valueOf(apacheEntry._serverHits)); + XMLUtils.createElement(contentHandler, "apacheCacheHits", String.valueOf(apacheEntry._serverCacheHits)); + + // Add front info if available + if (apacheEntry.hasFrontInfo()) + { + XMLUtils.createElement(contentHandler, "frontHits", String.valueOf(apacheEntry._frontHits)); + XMLUtils.createElement(contentHandler, "frontCacheHits1", String.valueOf(apacheEntry._frontCacheHits1)); + XMLUtils.createElement(contentHandler, "frontCacheHits2", String.valueOf(apacheEntry._frontCacheHits2)); + } + + XMLUtils.endElement(contentHandler, "entry"); + } + + private void _saxStatsFrontEntry(FrontFromFrontStatsEntry frontEntry) throws SAXException + { + if (frontEntry == null) + { + return; + } + + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("type", "front"); + __addAttrIfNotNull(attrs, "site", frontEntry._frontSite); + attrs.addCDATAAttribute("path", frontEntry._frontPath); + attrs.addCDATAAttribute("cacheable", String.valueOf(frontEntry._frontCacheable)); + XMLUtils.startElement(contentHandler, "entry", attrs); + + XMLUtils.createElement(contentHandler, "hits", String.valueOf(frontEntry._frontHits)); + XMLUtils.createElement(contentHandler, "cacheHits1", String.valueOf(frontEntry._frontCacheHits1)); + XMLUtils.createElement(contentHandler, "cacheHits2", String.valueOf(frontEntry._frontCacheHits2)); + + XMLUtils.endElement(contentHandler, "entry"); + } + + private void _saxStatsBackEntry(BackStatsEntry backEntry) throws SAXException + { + if (backEntry == null) + { + return; + } + + AttributesImpl attrs = new AttributesImpl(); + attrs.addCDATAAttribute("type", "back"); + attrs.addCDATAAttribute("id", backEntry._pageId); + attrs.addCDATAAttribute("path", backEntry._pagePath); + attrs.addCDATAAttribute("cacheable", String.valueOf(backEntry._cacheable)); + XMLUtils.startElement(contentHandler, "entry", attrs); + + XMLUtils.createElement(contentHandler, "hits", String.valueOf(backEntry._hits)); + + XMLUtils.endElement(contentHandler, "entry"); + } + + private void _saxStatsOrphanEntries() throws SAXException + { + Multimap pathMap = _pathMaps.get(null); + if (pathMap == null || pathMap.isEmpty()) + { + return; + } + + Collection entryPoints = pathMap.get(StringUtils.EMPTY); + if (entryPoints.isEmpty()) + { + return; + } + + Map apacheStats = _fromApacheStats.get(null); + if (apacheStats == null) + { + apacheStats = Collections.EMPTY_MAP; + } + + Map frontOnlyStats = _fromFrontOnlyStats.get(null); + if (frontOnlyStats == null) + { + frontOnlyStats = Collections.EMPTY_MAP; + } + + XMLUtils.startElement(contentHandler, "orphans"); + + for (String name : entryPoints) + { + _saxStatsEntry(name, '/' + name, pathMap, apacheStats, frontOnlyStats); + } + + XMLUtils.endElement(contentHandler, "orphans"); + } + + private void __addAttrIfNotNull(AttributesImpl attrs, String localName, String value) + { + if (StringUtils.isNotEmpty(value)) + { + attrs.addCDATAAttribute(localName, value); + } + } + + private void __addAttrIfNotNull(AttributesImpl attrs, String localName, Boolean value) + { + if (value != null) + { + attrs.addCDATAAttribute(localName, String.valueOf(value)); + } + } + + /** + * Object model representing a raw entry of stats retrieved through the DB. + */ + @SuppressWarnings("javadoc") + protected class RawStatsEntry + { + protected final Map _data; + + /** + * Ctor + */ + protected RawStatsEntry(Map data) + { + _data = data; + } + + protected boolean hasApacheInfo() + { + return _data.get("Server_Path") != null; + } + + protected boolean hasBackInfo() + { + return _data.get("Page_Id") != null; + } + } + + /** + * Object model representing an entry of stats for a front resource, coming + * from Apache + */ + @SuppressWarnings("javadoc") + protected class FrontFromApacheStatsEntry extends FrontFromFrontStatsEntry + { + + protected final String _serverSite; + protected final String _serverPath; + protected Integer _serverHits; + protected Integer _serverCacheHits; + + /** + * Ctor + */ + protected FrontFromApacheStatsEntry(Map data) + { + super(data); + + _serverSite = (String) data.get("Server_Site"); + _serverPath = (String) data.get("Server_Path"); + _serverHits = __toInteger(data.get("Server_Hits")); + _serverCacheHits = __toInteger(data.get("Server_Cache_Hits")); + } + + protected void merge(FrontFromApacheStatsEntry that) + { + if (that == null) + { + return; + } + + _serverHits += that._serverHits; + _serverCacheHits += that._serverCacheHits; + + if (that.hasFrontInfo()) + { + _frontSite = that._frontSite; + _frontPath = that._frontPath; + _frontCacheable = that._frontCacheable; + + _frontHits = hasFrontInfo() ? _frontHits + that._frontHits : that._frontHits; + _frontCacheHits1 = hasFrontInfo() ? _frontCacheHits1 + that._frontCacheHits1 : that._frontCacheHits1; + _frontCacheHits2 = hasFrontInfo() ? _frontCacheHits2 + that._frontCacheHits2 : that._frontCacheHits2; + } + } + + protected String getSanitizedPath() + { + if (hasFrontInfo()) + { + return _frontPath; + } + + // initialize allowed prefix path list for this site, given the site url aliases. + if (!_pathSanitizer.containsKey(_serverSite)) + { + for (String url : _siteManager.getSite(_serverSite).getUrlAliases()) + { + Matcher matcher = SitesGenerator.URL_PATTERN.matcher(url); + if (matcher.matches()) + { + String path = matcher.group(5); + if (StringUtils.isNotEmpty(path)) + { + _pathSanitizer.put(_serverSite, path); + } + } + } + + if (!_pathSanitizer.containsKey(_serverSite)) + { + _pathSanitizer.put(_serverSite, null); + } + } + + // sanitize serverPath + Iterator sitePathPrefixIt = _pathSanitizer.get(_serverSite).iterator(); + boolean sanitized = false; + String sanitizedPath = _serverPath; + + while (!sanitized && sitePathPrefixIt.hasNext()) + { + String prefix = sitePathPrefixIt.next(); + + // Nothing to do in this case. + if (prefix == null) + { + sanitized = true; + } + else if (StringUtils.startsWith(_serverPath, prefix)) + { + sanitizedPath = StringUtils.removeStart(_serverPath, prefix); + sanitized = true; + } + } + + // add site name when needed + if (!StringUtils.startsWithAny(sanitizedPath, _SPECIAL_PATH_PREFIXS)) + { + sanitizedPath = '/' + _serverSite + sanitizedPath; + } + + return sanitizedPath; + } + + protected boolean hasFrontInfo() + { + return _frontPath != null; + } + } + + /** + * Object model representing an entry of stats for a front resource, coming + * from the Front (direct request to tomcat, bypassing any Apache HTTP server if any). + */ + @SuppressWarnings("javadoc") + protected class FrontFromFrontStatsEntry + { + protected String _frontSite; + protected String _frontPath; + protected Boolean _frontCacheable; + protected Integer _frontHits; + protected Integer _frontCacheHits1; + protected Integer _frontCacheHits2; + + /** + * Ctor + */ + protected FrontFromFrontStatsEntry(Map data) + { + _frontSite = (String) data.get("Front_Site"); + _frontPath = (String) data.get("Front_Path"); + _frontCacheable = (Boolean) data.get("Front_Cacheable"); + _frontHits = __toInteger(data.get("Front_Hits")); + _frontCacheHits1 = __toInteger(data.get("Front_Cache_Hits_1")); + _frontCacheHits2 = __toInteger(data.get("Front_Cache_Hits_2")); + } + + protected Integer __toInteger(Object value) + { + Long lValue = (Long) value; + return lValue != null ? lValue.intValue() : null; + } + } + + /** + * Object model representing an entry of stats for a back resource (ie. a + * page). + */ + @SuppressWarnings("javadoc") + protected class BackStatsEntry + { + protected final String _pageId; + protected final String _pagePath; + protected final boolean _cacheable; + protected final int _hits; + + /** + * Ctor + */ + protected BackStatsEntry(Map data) + { + _pageId = (String) data.get("Page_Id"); + _pagePath = (String) data.get("Page_Path"); + _cacheable = (Boolean) data.get("Cacheable"); + _hits = __toInteger(data.get("Hits")); + } + + private Integer __toInteger(Object value) + { + Long lValue = (Long) value; + return lValue != null ? lValue.intValue() : null; + } + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStats.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStats.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStats.java (revision 0) @@ -0,0 +1,61 @@ +package org.ametys.web.cachemonitoring.core; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * A ResourceCacheStats is an object that holds cache statistics coming from the + * monitoring database or to be inserted/updated in the monitoring database. + */ +public interface ResourceCacheStats +{ + /** Type of possible ResourceCacheStats */ + public enum ResourceCacheStatsType + { + /** APACHE resource with a cache hit*/ + APACHE_ONLY, + /** Front resource with a unique id (coming from apache) */ + FRONT_FROM_APACHE, + /** Front resource without a unique id (direct to tomcat) */ + FRONT_ONLY, + /*** Back page resource */ + BACK_PAGE, + /*** Back page element resource stats */ + BACK_PAGE_ELEMENT; + } + + /** + * get type + * @return the type of this ResourceCacheStats instance. + */ + public ResourceCacheStatsType getType(); + + /** + * Must configure the given PreparedStatement for a find query. + * @param ps + * @throws SQLException + */ + public void configureSqlFind(PreparedStatement ps) throws SQLException; + + /** + * Configure the {@link PreparedStatement} used when doing the SQL insert + * query. + * @param ps The {@link PreparedStatement} + * @throws SQLException + */ + public void configureSqlInsert(PreparedStatement ps) throws SQLException; + + /** + * Configure the {@link PreparedStatement} used when doing the SQL update + * query. + * @param ps The {@link PreparedStatement} + * @throws SQLException + */ + public void configureSqlUpdate(PreparedStatement ps) throws SQLException; + + /** + * Returns the number of hits + * @return int the hits + */ + public int getHits(); +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringScheduler.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringScheduler.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringScheduler.java (revision 0) @@ -0,0 +1,109 @@ +package org.ametys.web.cachemonitoring.core; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.avalon.framework.activity.Disposable; +import org.apache.avalon.framework.activity.Initializable; +import org.apache.avalon.framework.logger.LogEnabled; +import org.apache.avalon.framework.logger.Logger; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.avalon.framework.service.Serviceable; +import org.joda.time.Duration; +import org.joda.time.format.PeriodFormat; + +@SuppressWarnings("javadoc") +public class CacheMonitoringScheduler extends TimerTask implements Initializable, Serviceable, Disposable, LogEnabled +{ + /** Logger */ + protected Logger _logger; + + /** Timer. */ + protected Timer _timer; + + /** Resource Access Monitor */ + protected ResourceAccessMonitor _resourceAccessMonitor; + + /** Cache stats updater */ + protected CacheMonitoringUpdater _cacheStatsUpdater; + + @Override + public void enableLogging(Logger logger) + { + _logger = logger; + } + + @Override + public void initialize() throws Exception + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Scheduling the CacheMonitoringScheduler component to run its task each hours"); + } + + // Daemon thread + _timer = new Timer("CacheMonitoringScheduler", true); + + // The task is each hour, starting at h15m if not too late. + // It is a quarter after the execution of the task of the FrontCacheMonitoringScheduler + GregorianCalendar calendar = new GregorianCalendar(); + int minute = calendar.get(Calendar.MINUTE); + + if (minute > 10) + { + calendar.add(Calendar.HOUR_OF_DAY, 1); + } + + calendar.set(Calendar.MINUTE, 15); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + _timer.scheduleAtFixedRate(this, calendar.getTime(), 60 * 60 * 1000); + } + + @Override + public void service(ServiceManager manager) throws ServiceException + { + _cacheStatsUpdater = (CacheMonitoringUpdater) manager.lookup(CacheMonitoringUpdater.ROLE); + _resourceAccessMonitor = (ResourceAccessMonitor) manager.lookup(ResourceAccessMonitor.ROLE); + } + + @Override + public void dispose() + { + cancel(); + _timer.cancel(); + _logger = null; + } + + @Override + public void run() + { + _logger.info("Starting the cache monitoring process from the back-office."); + + // Export pending records + long start = System.currentTimeMillis(); + _resourceAccessMonitor.exportPendings(); + long endExport = System.currentTimeMillis(); + + // Then update stats. + _cacheStatsUpdater.updateStats(); + long endUpdate = System.currentTimeMillis(); + + // And purge processed raw data + _cacheStatsUpdater.purgeRawData(); + long end = System.currentTimeMillis(); + + String totalDuration = PeriodFormat.getDefault().print(new Duration(end - start).toPeriod()); + String exportDuration = PeriodFormat.getDefault().print(new Duration(endExport - start).toPeriod()); + String updateDuration = PeriodFormat.getDefault().print(new Duration(endUpdate - endExport).toPeriod()); + String purgeDuration = PeriodFormat.getDefault().print(new Duration(end - endUpdate).toPeriod()); + + _logger.info(String.format( + "The whole back-office cache monitoring process took %s [export raw data to db : %s, update cache statistics: %s, purge processed data: %s]", + totalDuration, exportDuration, updateDuration, purgeDuration)); + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStatsFactory.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStatsFactory.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStatsFactory.java (revision 0) @@ -0,0 +1,47 @@ +package org.ametys.web.cachemonitoring.core; + +import java.util.Arrays; +import java.util.Map; + +import org.ametys.web.cachemonitoring.back.PageElementCacheStats; +import org.ametys.web.cachemonitoring.back.PageResourceCacheStats; +import org.ametys.web.cachemonitoring.core.ResourceCacheStats.ResourceCacheStatsType; +import org.ametys.web.cachemonitoring.front.FrontFromApacheCacheStats; +import org.ametys.web.cachemonitoring.front.FrontOnlyCacheStats; +import org.ametys.web.cachemonitoring.server.ApacheOnlyCacheStats; + +/** + * Factory for ResourceCacheStats objects + */ +public final class ResourceCacheStatsFactory +{ + private ResourceCacheStatsFactory() + { + // ignore + } + + /** + * Create a ResourceCacheStats of the desired type. + * @param type + * @param data + * @return The instance of this new ResourceCacheStats object. + */ + public static ResourceCacheStats create(ResourceCacheStatsType type, Map data) + { + switch (type) + { + case APACHE_ONLY: + return new ApacheOnlyCacheStats(data); + case FRONT_ONLY: + return new FrontOnlyCacheStats(data); + case FRONT_FROM_APACHE: + return new FrontFromApacheCacheStats(data); + case BACK_PAGE: + return new PageResourceCacheStats(data); + case BACK_PAGE_ELEMENT: + return new PageElementCacheStats(data); + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringUpdater.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringUpdater.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringUpdater.java (revision 0) @@ -0,0 +1,423 @@ +package org.ametys.web.cachemonitoring.core; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.component.Component; +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.joda.time.Duration; +import org.joda.time.format.PeriodFormat; + +import org.ametys.runtime.datasource.ConnectionHelper; +import org.ametys.web.cachemonitoring.core.ResourceAccess.MonitoredResourceType; +import org.ametys.web.cachemonitoring.core.ResourceCacheStats.ResourceCacheStatsType; + +/** + * The cache monitoring updater. It updates the statistics in the monitoring database, given the new request entries. + */ +public class CacheMonitoringUpdater extends AbstractLogEnabled implements Component +{ + /** Avalon ROLE. */ + public static final String ROLE = CacheMonitoringUpdater.class.getName(); + + /** + * Update the cache stats tables. + */ + void updateStats() + { + _logDebug("Updating cache stats tables..."); + + long start = System.currentTimeMillis(); + int totalUpdated = 0; + + for (ResourceCacheStatsType rcst : ResourceCacheStatsType.values()) + { + int updated = 0; + updated = update(rcst); + totalUpdated += updated; + + _logDebug(String.format("%s new request log processed for resource type '%s'", updated, rcst)); + } + + String durationStr = PeriodFormat.getDefault().print(new Duration(System.currentTimeMillis() - start).toPeriod()); + _logDebug(String.format("\tTotal count of new request log that have been processed : %s", totalUpdated)); + _logDebug(String.format("Update of the cache stats tables took %s", durationStr)); + } + + private int update(ResourceCacheStatsType type) + { + Connection conn = null; + try + { + conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID); + conn.setAutoCommit(false); + + int updated = update(conn, type); + conn.commit(); + + return updated; + } + catch (Exception e) + { + _logError("Exception during update cache stats for type '" + type + "'. Performing a rollback of the transaction.", e); + + try + { + if (conn != null) + { + conn.rollback(); + } + } + catch (SQLException sqle) + { + _logError("Error : SQLException. Problem while performing the rollback.", sqle); + } + + return 0; + } + finally + { + ConnectionHelper.cleanup(conn); + } + } + + private int update(Connection conn, ResourceCacheStatsType type) throws SQLException + { + ResultSet rs = null; + + try + { + long id = getMaxIdToProcess(conn, type); + if (id <= 0) + { + // nothing to do. + return 0; + } + + // Retrieves resources to process + update cache stats + UpdateStatsInfo info = new UpdateStatsInfo(conn, type); + int hits = updateCacheStats(conn, info); + + // Mark treated resource access as processed + int processed = markResourcesAsProcessed(conn, type, id); + + _logDebug(String.format( + "It appears to be %s new hits for the ResourceCacheStatsType '%s'. Also, %s access logs have been marked has processed.", + hits, type, processed)); + + // Special case for front from apache resources + if (type == ResourceCacheStatsType.FRONT_FROM_APACHE) + { + int apacheProcessed = markApacheResourcesAsProcessed(conn); + _logDebug(String.format( + "Comparing processed and apacheProcessed for type 'FRONT_FROM_APACHE': %s-%s . These values should be equals.", + processed, apacheProcessed)); + } + + return processed; + } + finally + { + ConnectionHelper.cleanup(rs); + } + } + + private long getMaxIdToProcess(Connection conn, ResourceCacheStatsType type) throws SQLException + { + final String getMaxIdSql = CacheMonitoringSqlStatements.getMaxIdToProcessSql(type); + + Statement st = null; + ResultSet rs = null; + try + { + st = conn.createStatement(); + rs = st.executeQuery(getMaxIdSql); + + if (rs.next()) + { + return rs.getInt(1); + } + return -1; + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(st); + } + } + + private int updateCacheStats(Connection conn, UpdateStatsInfo info) throws SQLException + { + int hits = 0; + + for (ResourceCacheStats rcs : info.getNewStats()) + { + updateCacheStats(conn, rcs); + hits += rcs.getHits(); + } + + return hits; + } + + private void updateCacheStats(Connection conn, ResourceCacheStats rcs) throws SQLException + { + if (findCacheStatsEntry(conn, rcs)) + { + updateCacheStatsEntry(conn, rcs); + } + else + { + insertCacheStatsEntry(conn, rcs); + } + } + + private boolean findCacheStatsEntry(Connection conn, ResourceCacheStats rcs) throws SQLException + { + final String sql = CacheMonitoringSqlStatements.findCacheStatsEntrySql(rcs.getType()); + + PreparedStatement ps = null; + ResultSet rs = null; + try + { + ps = conn.prepareStatement(sql); + rcs.configureSqlFind(ps); + + rs = ps.executeQuery(); + + // result must be > 0 + rs.next(); + return rs.getInt(1) > 0; + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(ps); + } + } + + private void updateCacheStatsEntry(Connection conn, ResourceCacheStats rcs) throws SQLException + { + final String sql = CacheMonitoringSqlStatements.updateCacheStatsEntrySql(rcs.getType()); + + PreparedStatement ps = null; + ResultSet rs = null; + try + { + ps = conn.prepareStatement(sql); + rcs.configureSqlUpdate(ps); + + ps.executeUpdate(); + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(ps); + } + } + + private void insertCacheStatsEntry(Connection conn, ResourceCacheStats rcs) throws SQLException + { + final String sql = CacheMonitoringSqlStatements.insertCacheStatsSql(rcs.getType()); + + PreparedStatement ps = null; + ResultSet rs = null; + try + { + ps = conn.prepareStatement(sql); + rcs.configureSqlInsert(ps); + + ps.executeUpdate(); + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(ps); + } + } + + private int markResourcesAsProcessed(Connection conn, ResourceCacheStatsType type, long id) throws SQLException + { + final String markProcessedSql = CacheMonitoringSqlStatements.getMarkProcessedSql(type); + + PreparedStatement ps = null; + try + { + ps = conn.prepareStatement(markProcessedSql); + ps.setLong(1, id); + return ps.executeUpdate(); + } + finally + { + ConnectionHelper.cleanup(ps); + } + } + + private int markApacheResourcesAsProcessed(Connection conn) throws SQLException + { + final String markApacheProcessedSql = CacheMonitoringSqlStatements.getMarkApacheProcessedSql(); + + Statement st = null; + try + { + st = conn.createStatement(); + return st.executeUpdate(markApacheProcessedSql); + } + finally + { + ConnectionHelper.cleanup(st); + } + } + + private class UpdateStatsInfo + { + private final ResourceCacheStatsType _type; + private List _newCacheStats; + + public UpdateStatsInfo(Connection conn, ResourceCacheStatsType type) throws SQLException + { + _type = type; + _newCacheStats = new ArrayList(); + + updateProcess(conn); + } + + private void updateProcess(Connection conn) throws SQLException + { + Statement st = null; + ResultSet rs = null; + try + { + st = conn.createStatement(); + rs = getResourcesAccessToProcess(st); + analyzeResultSet(rs); + } + finally + { + ConnectionHelper.cleanup(rs); + ConnectionHelper.cleanup(st); + } + } + + private ResultSet getResourcesAccessToProcess(Statement st) throws SQLException + { + final String getToProcessSql = CacheMonitoringSqlStatements.getResourceAccessToProcess(_type); + return st.executeQuery(getToProcessSql); + } + + private void analyzeResultSet(ResultSet rs) throws SQLException + { + _logDebug("Start analyzing raw update info for type : " + _type); + + ResultSetMetaData meta = rs.getMetaData(); + String[] cols = new String[meta.getColumnCount()]; + + for (int i = 0; i < cols.length; i++) + { + cols[i] = meta.getColumnLabel(i + 1); + } + + while (rs.next()) + { + Map data = new HashMap(); + for (int i = 0; i < cols.length; i++) + { + data.put(cols[i], rs.getObject(i + 1)); + } + + ResourceCacheStats rcs = ResourceCacheStatsFactory.create(_type, data); + _newCacheStats.add(rcs); + } + + _logDebug("Analysis of the raw info ended well."); + } + + public List getNewStats() + { + return _newCacheStats; + } + } + + /** + * Purge the processed raw request entries. + */ + void purgeRawData() + { + _logDebug("Purging raw monitoring data..."); + + long start = System.currentTimeMillis(); + int totalPurged = 0; + + for (MonitoredResourceType mrt : MonitoredResourceType.values()) + { + totalPurged += purge(mrt); + } + + String durationStr = PeriodFormat.getDefault().print(new Duration(System.currentTimeMillis() - start).toPeriod()); + _logDebug(String.format("%s row purged from the database in %s", totalPurged, durationStr)); + } + + private int purge(MonitoredResourceType type) + { + Connection conn = null; + try + { + conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID); + return purge(conn, type); + } + catch (Exception e) + { + _logError("Exception during the purge of the raw data for type '" + type + "'.", e); + return 0; + } + finally + { + ConnectionHelper.cleanup(conn); + } + } + + private int purge(Connection conn, MonitoredResourceType type) throws SQLException + { + final String purgeSql = CacheMonitoringSqlStatements.getPurgeSql(type); + + Statement st = null; + try + { + st = conn.createStatement(); + return st.executeUpdate(purgeSql); + } + finally + { + ConnectionHelper.cleanup(st); + } + } + + /** + * Helper log debug method + * @param msg The msg to log at the debug level. + */ + protected void _logDebug(String msg) + { + if (getLogger().isDebugEnabled()) + { + getLogger().debug(msg); + } + } + + /** + * Helper log error method + * @param msg The msg to log at the error level. + * @param t the throwable + */ + protected void _logError(String msg, Throwable t) + { + getLogger().error(msg, t); + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccessMonitor.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccessMonitor.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccessMonitor.java (revision 0) @@ -0,0 +1,250 @@ +package org.ametys.web.cachemonitoring.core; + +import java.sql.BatchUpdateException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.apache.avalon.framework.activity.Initializable; +import org.apache.avalon.framework.component.Component; +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.joda.time.Duration; +import org.joda.time.format.PeriodFormat; + +import org.ametys.runtime.datasource.ConnectionHelper; +import org.ametys.web.cachemonitoring.core.ResourceAccess.MonitoredResourceType; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; + +/** + * The RessourceAccessMonitor collects the resources that have been requested, + * and export them into a database. + */ +public class ResourceAccessMonitor extends AbstractLogEnabled implements Initializable, Component +{ + /** Avalon ROLE. */ + public static final String ROLE = ResourceAccessMonitor.class.getName(); + + /** + * List of pending {@link ResourceAccess} waiting to be exported to the + * database. + */ + protected ListMultimap _pendingRecords; + + private final ExecutorService _importerPool = Executors.newCachedThreadPool(); + + @Override + public void initialize() throws Exception + { + _pendingRecords = Multimaps.synchronizedListMultimap(LinkedListMultimap.create()); + } + + void exportPendings() + { + _logDebug("Start to insert pending records."); + + long start = System.currentTimeMillis(); + int totalCount = 0; + + Map> toExport = new HashMap>(); + synchronized (_pendingRecords) + { + totalCount = _pendingRecords.size(); + for (MonitoredResourceType mrt : _pendingRecords.keySet()) + { + toExport.put(mrt, _pendingRecords.removeAll(mrt)); + } + } + + int successCount = 0; + if (!toExport.isEmpty()) + { + Connection conn = null; + try + { + conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID); + conn.setAutoCommit(false); + successCount = export(conn, toExport); + conn.commit(); + } + catch (Exception e) + { + _logError("Exception during export to DB. Performing a rollback of the transaction.", e); + + try + { + if (conn != null) + { + conn.rollback(); + } + } + catch (SQLException sqle) + { + _logError("Error : SQLException. Problem while performing the rollback.", sqle); + } + } + finally + { + ConnectionHelper.cleanup(conn); + } + } + + String durationStr = PeriodFormat.getDefault().print(new Duration(System.currentTimeMillis() - start).toPeriod()); + _logDebug(String.format("%s/%s pending records exported into db in %s", successCount, totalCount, durationStr)); + } + + private int export(Connection conn, Map> toExport) + { + try + { + int success = 0; + for (MonitoredResourceType mrt : toExport.keySet()) + { + success += exportByType(conn, mrt, toExport.get(mrt).iterator()); + } + return success; + } + catch (SQLException e) + { + e.printStackTrace(); + } + + return 0; + } + + private int exportByType(Connection conn, MonitoredResourceType mrt, Iterator rait) throws SQLException + { + PreparedStatement ps = null; + ResourceAccess ra = null; + try + { + String insertSql = CacheMonitoringSqlStatements.insertResourceAccessSql(mrt); + ps = conn.prepareStatement(insertSql, Statement.NO_GENERATED_KEYS); + + while (rait.hasNext()) + { + ra = rait.next(); + ra.configureSqlInsert(ps); + ps.addBatch(); + } + + try + { + return ps.executeBatch().length; + } + catch (BatchUpdateException e) + { + // more detailed treatment is possible through e.getUpdateCounts() + _logError("Batch exception while inserting new records into of type '" + mrt + "' to the DB", e); + } + } + finally + { + ConnectionHelper.cleanup(ps); + } + + return 0; + } + + /** + * Add a new {@link ResourceAccess} to the monitored resources. + * + * @param ra The resource access object. + * @return a {@link Future} to use if you would like to immediately block + * waiting for the add operation to finish. + */ + public Future addAccessRecord(ResourceAccess ra) + { + return _importerPool.submit(new RecordAdder(ra)); + } + + /** + * Add a new {@link ResourceAccess} to the monitored resources. + * + * @param lra The list of resource access objects. + * @return a {@link Future} to use if you would like to immediately block + * waiting for the add operation to finish. + */ + public Future addAccessRecords(List lra) + { + return _importerPool.submit(new RecordsAdder(lra)); + } + + /** + * {@link Callable} that add a {@link ResourceAccess} in the list of pending + * records. + */ + private class RecordAdder implements Callable + { + private final ResourceAccess _ra; + + public RecordAdder(ResourceAccess ra) + { + _ra = ra; + } + + @Override + public Void call() throws Exception + { + _pendingRecords.put(_ra.getType(), _ra); + return null; + } + } + + /** + * {@link Callable} that add each element a list of {@link ResourceAccess} into the + * list of pending records. + */ + private class RecordsAdder implements Callable + { + private final List _lra; + + public RecordsAdder(List lra) + { + _lra = lra; + } + + @Override + public Void call() throws Exception + { + for (ResourceAccess ra : _lra) + { + _pendingRecords.put(ra.getType(), ra); + } + return null; + } + } + + /** + * Helper log debug method + * @param msg The msg to log at the debug level. + */ + protected void _logDebug(String msg) + { + if (getLogger().isDebugEnabled()) + { + getLogger().debug(msg); + } + } + + /** + * Helper log error method + * @param msg The msg to log at the error level. + * @param t the throwable + */ + protected void _logError(String msg, Throwable t) + { + getLogger().error(msg, t); + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringSqlStatements.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringSqlStatements.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringSqlStatements.java (revision 0) @@ -0,0 +1,459 @@ +package org.ametys.web.cachemonitoring.core; + +import java.util.Arrays; + +import org.apache.commons.lang.StringUtils; + +import org.ametys.web.cachemonitoring.core.ResourceAccess.MonitoredResourceType; +import org.ametys.web.cachemonitoring.core.ResourceCacheStats.ResourceCacheStatsType; + +/** + * Gather SQL statements related to the cache monitoring process. + */ +public final class CacheMonitoringSqlStatements +{ + /** Datasource id of the cache monitoring datasource */ + public static final String MONITORING_DATASOURCE_ID = "cache.monitoring.datasource"; + + // >--------------------- ACCESS TABLES ------------------------< + + // FIXME Config + // private static final String __APACHE_RESOURCE_ACCESS_TABLE_NAME = Config.SERVER_REQUESTS_TABLE_NAME; + // private static final String __FRONT_RESOURCE_ACCESS_TABLE_NAME = Config.FRONT_REQUESTS_TABLE_NAME; + // private static final String __PAGE_RESOURCE_ACCESS_TABLE_NAME = Config.PAGE_REQUESTS_TABLE_NAME; + // private static final String __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME = Config.PAGE_ELEMENT_REQUESTS_TABLE_NAME; + + private static final String __APACHE_RESOURCE_ACCESS_TABLE_NAME = "Server_Requests"; + private static final String __FRONT_RESOURCE_ACCESS_TABLE_NAME = "Front_Requests"; + private static final String __PAGE_RESOURCE_ACCESS_TABLE_NAME = "Back_Requests"; + private static final String __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME = "Back_Page_Element_Requests"; + + + private static final String __INSERT_RESOURCE_ACCESS_SQL = "INSERT IGNORE INTO $tbl_name$ ($insert_cols$) values ($insert_values$)"; + private static final String __TBL_NAME_PATTERN = "$tbl_name$"; + private static final String __INSERT_COLS_CLAUSE_PATTERN = "$insert_cols$"; + private static final String __INSERT_VALUES_CLAUSE_PATTERN = "$insert_values$"; + private static final String __APACHE_INSERT_RESOURCE_COLS_CLAUSE = "Unique_Id, Site, Remote_Host_Name, Request_Date, Method, Path, Query_String, Protocol, Ori_Status_Code, Ret_Status_Code, Cache_Hit, Referer, User_Agent"; + private static final String __FRONT_INSERT_RESOURCE_COLS_CLAUSE = "Unique_Id, Internal_Uuid, Site, Ametys_Path, Cacheable, Cache_Hit_1, Cache_Hit_2"; + private static final String __PAGE_INSERT_RESOURCE_COLS_CLAUSE = "Internal_Uuid, Page_Id, Page_Path, Rendering_Context, Workspace_JCR, Cacheable"; + private static final String __PAGE_ELEMENT_INSERT_RESOURCE_COLS_CLAUSE = "Internal_Uuid, Page_Element_Id, Page_Element_Type, Page_Id, Rendering_Context, Workspace_JCR, Cacheable, Cache_Hit"; + + private static final String __GROUP_FIELDS_PATTERN = "$group_fields$"; + private static final String __COLS_FIELDS_PATTERN = "$cols_fields$"; + private static final String __INTERNAL_GET_RES_PATTERN = "$internal_get_res$"; + private static final String __GET_RESOURCE_TO_PROCESS = "SELECT count(*) AS increment, $cols_fields$ FROM $tbl_name$ T $internal_get_res$ GROUP BY $group_fields$ ORDER BY max(T.Id)"; + + private static final String __APACHE_PROCESS_GROUP_FIELDS = "Site, Path, Cache_Hit"; + private static final String __FRONT_PROCESS_GROUP_FIELDS = "Site, Ametys_Path, Cacheable, Cache_Hit_1, Cache_Hit_2"; + private static final String __FRONT_APACHE_PROCESS_GROUP_FIELDS = "S.Site, S.Path, S.Cache_Hit, T.Site, T.Ametys_Path, T.Cacheable, T.Cache_Hit_1, T.Cache_Hit_2"; + private static final String __PAGE_PROCESS_GROUP_FIELDS = "Page_Id, Page_Path, Rendering_Context, Workspace_JCR, Cacheable"; + private static final String __PAGE_ELEMENT_PROCESS_GROUP_FIELDS = "Page_Element_Id, Page_Id, Rendering_Context, Workspace_JCR, Cacheable, Cache_Hit"; + + private static final String __APACHE_PROCESS_COLS_FIELDS = __APACHE_PROCESS_GROUP_FIELDS; + private static final String __FRONT_PROCESS_COLS_FIELDS = __FRONT_PROCESS_GROUP_FIELDS; + private static final String __FRONT_APACHE_PROCESS_COLS_FIELDS = "S.Site AS S_Site, S.Path AS S_Path, S.Cache_Hit AS S_Cache_Hit, T.Site AS F_Site, T.Ametys_Path AS F_Ametys_Path, T.Cacheable AS F_Cacheable, T.Cache_Hit_1 AS F_Cache_Hit_1, T.Cache_Hit_2 AS F_Cache_Hit_2"; + private static final String __PAGE_PROCESS_COLS_FIELDS = __PAGE_PROCESS_GROUP_FIELDS; + private static final String __PAGE_ELEMENT_PROCESS_COLS_FIELDS = __PAGE_ELEMENT_PROCESS_GROUP_FIELDS; + + private static final String __APACHE_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false AND Cache_Hit = true AND (Ori_Status_Code = 200 OR Ori_Status_Code = 304)"; + private static final String __FRONT_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false AND Unique_Id is NULL"; + private static final String __FRONT_APACHE_INTERNAL_GET_RES_PATTERN = "INNER JOIN " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " S ON T.Unique_Id = S.Unique_Id AND T.Processed = false AND T.Unique_Id is not NULL AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304)"; + private static final String __PAGE_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false"; + private static final String __PAGE_ELEMENT_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false"; + + private static final String __GET_MAX_ID_TO_PROCESS = "SELECT max(Id) FROM $tbl_name$ WHERE PROCESSED = false"; + private static final String __GET_MAX_ID_TO_PROCESS_APACHE = "SELECT max(Id) FROM $tbl_name$ WHERE PROCESSED = false AND (Ori_Status_Code = 200 OR Ori_Status_Code = 304)"; + private static final String __GET_MAX_ID_TO_PROCESS_FRONT = "SELECT max(Id) FROM $tbl_name$ WHERE PROCESSED = false AND Unique_Id is NULL"; + private static final String __GET_MAX_ID_TO_PROCESS_FRONT_APACHE = "SELECT max(F.Id) FROM $tbl_name$ AS F INNER JOIN " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " S ON F.Unique_Id = S.Unique_Id AND F.Processed = false AND F.Unique_Id is not NULL AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304)"; + + private static final String __MARK_AS_PROCESSED = "UPDATE $tbl_name$ SET PROCESSED = true WHERE Id <= ? AND Processed = false"; + private static final String __MARK_AS_PROCESSED_APACHE = "UPDATE $tbl_name$ AS S LEFT OUTER JOIN " + __FRONT_RESOURCE_ACCESS_TABLE_NAME + " F ON S.Unique_Id = F.Unique_Id SET S.Processed = true WHERE S.Id <= ? AND F.Unique_Id is NULL AND S.Processed = false AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304)"; + private static final String __MARK_AS_PROCESSED_FRONT = "UPDATE $tbl_name$ SET PROCESSED = true WHERE Id <= ? AND Processed = false AND Unique_Id is NULL"; + private static final String __MARK_AS_PROCESSED_FRONT_APACHE = "UPDATE $tbl_name$ AS F INNER JOIN " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " S ON F.Unique_Id = S.Unique_Id AND F.Processed = false AND F.Unique_Id is not NULL AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304) SET F.Processed = true WHERE F.Id <= ?"; + private static final String __MARK_AS_PROCESSED_APACHE_AFTER_FRONT_APACHE = "UPDATE " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " AS S INNER JOIN " + __FRONT_RESOURCE_ACCESS_TABLE_NAME + " F ON S.Unique_Id = F.Unique_Id AND S.Processed = false AND F.Processed = true SET S.Processed = true"; + + private static final String __PURGE_PROCESSED_SQL = "DELETE FROM $tbl_name$ WHERE PROCESSED = true"; + + // >--------------------- CACHE STATS TABLES ------------------------< + + // FIXME Config + // private static final String __APACHE_CACHE_STATS_TABLE_NAME = Config.SERVER_CACHE_STATS_TABLE_NAME; + // private static final String __FRONT_CACHE_STATS_TABLE_NAME = Config.FRONT_CACHE_STATS_TABLE_NAME; + // private static final String __PAGE_CACHE_STATS_TABLE_NAME = Config.PAGE_CACHE_STATS_TABLE_NAME; + // private static final String __PAGE_ELEMENT_CACHE_STATS_TABLE_NAME = Config.PAGE_ELEMENT_CACHE_STATS_TABLE_NAME; + + private static final String __FRONT_CACHE_STATS_TABLE_NAME = "Cache_Front_Stats"; + private static final String __PAGE_CACHE_STATS_TABLE_NAME = "Cache_Back_Stats"; + private static final String __PAGE_ELEMENT_CACHE_STATS_TABLE_NAME = "Cache_Back_Page_Element_Stats"; + + + private static final String __INSERT_CACHE_STATS = "INSERT INTO $tbl_name$ ($insert_cols$) values ($insert_values$)"; + private static final String __APACHE_INSERT_CACHE_COLS_CLAUSE = "Server_Site, Server_Path, Server_Hits, Server_Cache_Hits"; + private static final String __FRONT_INSERT_CACHE_COLS_CLAUSE = "Front_Site, Front_Path, Front_Cacheable, Front_Hits, Front_Cache_Hits_1, Front_Cache_Hits_2"; + private static final String __FRONT_APACHE_INSERT_CACHE_COLS_CLAUSE = __APACHE_INSERT_CACHE_COLS_CLAUSE + ", " + __FRONT_INSERT_CACHE_COLS_CLAUSE; + private static final String __PAGE_INSERT_CACHE_COLS_CLAUSE = "Page_Id, Page_Path, Rendering_Context, Workspace_JCR, Cacheable, Hits"; + private static final String __PAGE_ELEMENT_INSERT_CACHE_COLS_CLAUSE = "Page_Element_Id, Page_Id, Rendering_Context, Workspace_JCR, Cacheable, Hits, Cache_Hits"; + + private static final String __FIND_CACHE_STATS = "SELECT count(1) FROM $tbl_name$ WHERE $where_clause$"; + private static final String __WHERE_CLAUSE_PATTERN = "$where_clause$"; + private static final String __APACHE_WHERE_CLAUSE = "Server_Site = ? AND Server_Path = ?"; + private static final String __FRONT_WHERE_CLAUSE = "Front_Site = ? AND Front_Path = ? AND Server_Path is NULL"; + private static final String __FRONT_APACHE_WHERE_CLAUSE = __APACHE_WHERE_CLAUSE; + private static final String __PAGE_WHERE_CLAUSE = "Page_Id = ? AND Rendering_Context = ? AND Workspace_JCR = ?"; + private static final String __PAGE_ELEMENT_WHERE_CLAUSE = "Page_Element_Id = ? AND Page_Id = ? AND Rendering_Context = ? AND Workspace_JCR = ?"; + + private static final String __UPDATE_CACHE_STATS = "UPDATE $tbl_name$ SET $set_clause$ WHERE $where_clause$"; + private static final String __SET_CLAUSE_PATTERN = "$set_clause$"; + private static final String __APACHE_SET_CLAUSE = "Server_Hits = Server_Hits + ?, Server_Cache_Hits = Server_Cache_Hits + ?"; + private static final String __FRONT_SET_CLAUSE = "Front_Cacheable = ?, Front_Hits = Front_Hits + ?, Front_Cache_Hits_1 = Front_Cache_Hits_1 + ?, Front_Cache_Hits_2 = Front_Cache_Hits_2 + ?"; + private static final String __FRONT_APACHE_SET_CLAUSE = __APACHE_SET_CLAUSE + ", Front_Site = ?, Front_Path = ?, " + __FRONT_SET_CLAUSE; + private static final String __PAGE_SET_CLAUSE = "Page_Path = ?, Cacheable = ?, Hits = Hits + ?"; + private static final String __PAGE_ELEMENT_SET_CLAUSE = "Cacheable = ?, Hits = Hits + ?, Cache_Hits = Cache_Hits + ?"; + + private CacheMonitoringSqlStatements() + { + // ignore + } + + /** + * Insert resource access + * @param type + * @return The sql query + */ + public static String insertResourceAccessSql(MonitoredResourceType type) + { + String insertCols = null; + switch (type) + { + case APACHE_RESOURCE: + insertCols = __APACHE_INSERT_RESOURCE_COLS_CLAUSE; + break; + case FRONT_RESOURCE: + insertCols = __FRONT_INSERT_RESOURCE_COLS_CLAUSE; + break; + case BACK_PAGE_RESOURCE: + insertCols = __PAGE_INSERT_RESOURCE_COLS_CLAUSE; + break; + case BACK_PAGE_ELEMENT: + insertCols = __PAGE_ELEMENT_INSERT_RESOURCE_COLS_CLAUSE; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(MonitoredResourceType.values())); + } + + String[] insertValuesArray = StringUtils.split(insertCols, ", "); + Arrays.fill(insertValuesArray, "?"); + String insertValues = StringUtils.join(insertValuesArray, ", "); + + String sql = StringUtils.replaceOnce(__INSERT_RESOURCE_ACCESS_SQL, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + sql = StringUtils.replaceOnce(sql, __INSERT_COLS_CLAUSE_PATTERN, insertCols); + sql = StringUtils.replaceOnce(sql, __INSERT_VALUES_CLAUSE_PATTERN, insertValues); + + return sql; + } + + /** + * The purge sql query + * @param type + * @return The sql query + */ + public static String getPurgeSql(MonitoredResourceType type) + { + return StringUtils.replaceOnce(__PURGE_PROCESSED_SQL, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + } + + private static String getResourceAccessTableName(MonitoredResourceType type) + { + String tblName = null; + switch (type) + { + case APACHE_RESOURCE: + tblName = __APACHE_RESOURCE_ACCESS_TABLE_NAME; + break; + case FRONT_RESOURCE: + tblName = __FRONT_RESOURCE_ACCESS_TABLE_NAME; + break; + case BACK_PAGE_RESOURCE: + tblName = __PAGE_RESOURCE_ACCESS_TABLE_NAME; + break; + case BACK_PAGE_ELEMENT: + tblName = __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(MonitoredResourceType.values())); + } + + return tblName; + } + + /** + * Get resource access to be processed + * @param type + * @return The sql query + */ + public static String getResourceAccessToProcess(ResourceCacheStatsType type) + { + String colsFields = null; + String groupFields = null; + String internalClause = null; + switch (type) + { + case APACHE_ONLY: + colsFields = __APACHE_PROCESS_COLS_FIELDS; + groupFields = __APACHE_PROCESS_GROUP_FIELDS; + internalClause = __APACHE_INTERNAL_GET_RES_PATTERN; + break; + case FRONT_ONLY: + colsFields = __FRONT_PROCESS_COLS_FIELDS; + groupFields = __FRONT_PROCESS_GROUP_FIELDS; + internalClause = __FRONT_INTERNAL_GET_RES_PATTERN; + break; + case FRONT_FROM_APACHE: + colsFields = __FRONT_APACHE_PROCESS_COLS_FIELDS; + groupFields = __FRONT_APACHE_PROCESS_GROUP_FIELDS; + internalClause = __FRONT_APACHE_INTERNAL_GET_RES_PATTERN; + break; + case BACK_PAGE: + colsFields = __PAGE_PROCESS_COLS_FIELDS; + groupFields = __PAGE_PROCESS_GROUP_FIELDS; + internalClause = __PAGE_INTERNAL_GET_RES_PATTERN; + break; + case BACK_PAGE_ELEMENT: + colsFields = __PAGE_ELEMENT_PROCESS_COLS_FIELDS; + groupFields = __PAGE_ELEMENT_PROCESS_GROUP_FIELDS; + internalClause = __PAGE_ELEMENT_INTERNAL_GET_RES_PATTERN; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + + String sql = StringUtils.replaceOnce(__GET_RESOURCE_TO_PROCESS, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + sql = StringUtils.replaceOnce(sql, __COLS_FIELDS_PATTERN, colsFields); + sql = StringUtils.replaceOnce(sql, __GROUP_FIELDS_PATTERN, groupFields); + sql = StringUtils.replaceOnce(sql, __INTERNAL_GET_RES_PATTERN, internalClause); + + return sql; + } + + /** + * Get the max id of the of the resources to process + * @param type + * @return The sql query + */ + public static String getMaxIdToProcessSql(ResourceCacheStatsType type) + { + switch (type) + { + case BACK_PAGE: + case BACK_PAGE_ELEMENT: + return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + case APACHE_ONLY: + return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + case FRONT_ONLY: + return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS_FRONT, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + case FRONT_FROM_APACHE: + return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS_FRONT_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + } + + /** + * The mark as processed sql query + * @param type + * @return The sql query + */ + public static String getMarkProcessedSql(ResourceCacheStatsType type) + { + switch (type) + { + case BACK_PAGE: + case BACK_PAGE_ELEMENT: + return StringUtils.replaceOnce(__MARK_AS_PROCESSED, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + case APACHE_ONLY: + return StringUtils.replaceOnce(__MARK_AS_PROCESSED_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + case FRONT_ONLY: + return StringUtils.replaceOnce(__MARK_AS_PROCESSED_FRONT, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + case FRONT_FROM_APACHE: + return StringUtils.replaceOnce(__MARK_AS_PROCESSED_FRONT_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type)); + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + } + + /** + * The mark as processed sql query. + * This is a special case for the type FRONT_FROM_APACHE. + * After having marked as processed the front resources, the corresponding apache resource must be marked too. + * @return The sql query + */ + public static String getMarkApacheProcessedSql() + { + return __MARK_AS_PROCESSED_APACHE_AFTER_FRONT_APACHE; + } + + private static String getResourceAccessTableName(ResourceCacheStatsType type) + { + String tblName = null; + switch (type) + { + case APACHE_ONLY: + tblName = __APACHE_RESOURCE_ACCESS_TABLE_NAME; + break; + case FRONT_FROM_APACHE: + case FRONT_ONLY: + tblName = __FRONT_RESOURCE_ACCESS_TABLE_NAME; + break; + case BACK_PAGE: + tblName = __PAGE_RESOURCE_ACCESS_TABLE_NAME; + break; + case BACK_PAGE_ELEMENT: + tblName = __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + + return tblName; + } + + /** + * The find sql cache stats entry query + * @param type + * @return The sql query + */ + public static String findCacheStatsEntrySql(ResourceCacheStatsType type) + { + String whereClause = null; + switch (type) + { + case APACHE_ONLY: + whereClause = __APACHE_WHERE_CLAUSE; + break; + case FRONT_ONLY: + whereClause = __FRONT_WHERE_CLAUSE; + break; + case FRONT_FROM_APACHE: + whereClause = __FRONT_APACHE_WHERE_CLAUSE; + break; + case BACK_PAGE: + whereClause = __PAGE_WHERE_CLAUSE; + break; + case BACK_PAGE_ELEMENT: + whereClause = __PAGE_ELEMENT_WHERE_CLAUSE; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + + String sql = StringUtils.replaceOnce(__FIND_CACHE_STATS, __TBL_NAME_PATTERN, getCacheStatsTableName(type)); + sql = StringUtils.replaceOnce(sql, __WHERE_CLAUSE_PATTERN, whereClause); + + return sql; + } + + /** + * The insert sql cache stats entry query + * @param type + * @return The sql query + */ + public static String insertCacheStatsSql(ResourceCacheStatsType type) + { + String insertCols = null; + switch (type) + { + case APACHE_ONLY: + insertCols = __APACHE_INSERT_CACHE_COLS_CLAUSE; + break; + case FRONT_ONLY: + insertCols = __FRONT_INSERT_CACHE_COLS_CLAUSE; + break; + case FRONT_FROM_APACHE: + insertCols = __FRONT_APACHE_INSERT_CACHE_COLS_CLAUSE; + break; + case BACK_PAGE: + insertCols = __PAGE_INSERT_CACHE_COLS_CLAUSE; + break; + case BACK_PAGE_ELEMENT: + insertCols = __PAGE_ELEMENT_INSERT_CACHE_COLS_CLAUSE; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + + String[] insertValuesArray = StringUtils.split(insertCols, ", "); + Arrays.fill(insertValuesArray, "?"); + String insertValues = StringUtils.join(insertValuesArray, ", "); + + String sql = StringUtils.replaceOnce(__INSERT_CACHE_STATS, __TBL_NAME_PATTERN, getCacheStatsTableName(type)); + sql = StringUtils.replaceOnce(sql, __INSERT_COLS_CLAUSE_PATTERN, insertCols); + sql = StringUtils.replaceOnce(sql, __INSERT_VALUES_CLAUSE_PATTERN, insertValues); + + return sql; + } + + /** + * The update sql cache stats entry query + * @param type + * @return The sql query + */ + public static String updateCacheStatsEntrySql(ResourceCacheStatsType type) + { + String setClause = null; + String whereClause = null; + switch (type) + { + case APACHE_ONLY: + setClause = __APACHE_SET_CLAUSE; + whereClause = __APACHE_WHERE_CLAUSE; + break; + case FRONT_ONLY: + setClause = __FRONT_SET_CLAUSE; + whereClause = __FRONT_WHERE_CLAUSE; + break; + case FRONT_FROM_APACHE: + setClause = __FRONT_APACHE_SET_CLAUSE; + whereClause = __FRONT_APACHE_WHERE_CLAUSE; + break; + case BACK_PAGE: + setClause = __PAGE_SET_CLAUSE; + whereClause = __PAGE_WHERE_CLAUSE; + break; + case BACK_PAGE_ELEMENT: + setClause = __PAGE_ELEMENT_SET_CLAUSE; + whereClause = __PAGE_ELEMENT_WHERE_CLAUSE; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + + String sql = StringUtils.replaceOnce(__UPDATE_CACHE_STATS, __TBL_NAME_PATTERN, getCacheStatsTableName(type)); + sql = StringUtils.replaceOnce(sql, __SET_CLAUSE_PATTERN, setClause); + sql = StringUtils.replaceOnce(sql, __WHERE_CLAUSE_PATTERN, whereClause); + + return sql; + } + + private static String getCacheStatsTableName(ResourceCacheStatsType type) + { + String tblName = null; + switch (type) + { + case APACHE_ONLY: + case FRONT_ONLY: + case FRONT_FROM_APACHE: + tblName = __FRONT_CACHE_STATS_TABLE_NAME; + break; + case BACK_PAGE: + tblName = __PAGE_CACHE_STATS_TABLE_NAME; + break; + case BACK_PAGE_ELEMENT: + tblName = __PAGE_ELEMENT_CACHE_STATS_TABLE_NAME; + break; + default: + throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values())); + } + + return tblName; + } +} Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccess.java =================================================================== --- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccess.java (revision 0) +++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccess.java (revision 0) @@ -0,0 +1,42 @@ +package org.ametys.web.cachemonitoring.core; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Monitored resources. Each access to theses resources is monitored in order to + * be able to analyze cache efficiency and to calculate meaningful statistics + */ +public interface ResourceAccess +{ + /** Type of monitored resources */ + public enum MonitoredResourceType + { + /** APACHE resource */ + APACHE_RESOURCE, + /** Front resource */ + FRONT_RESOURCE, + /*** Back page resource */ + BACK_PAGE_RESOURCE, + /*** Back page element resource */ + BACK_PAGE_ELEMENT; + } + + /** + * get type + * + * @return the type of this {@link ResourceAccess} instance. + */ + public MonitoredResourceType getType(); + + /** + * Configure the {@link PreparedStatement} used when doing the SQL insert + * query. This method must be consistent with the {@link #configureSqlInsert(PreparedStatement)} + * method. + * + * @param ps The {@link PreparedStatement} + * @throws SQLException + */ + public void configureSqlInsert(PreparedStatement ps) throws SQLException; +} + Index: main/plugin-web/src/org/ametys/web/repository/site/SiteGenerator.java =================================================================== --- main/plugin-web/src/org/ametys/web/repository/site/SiteGenerator.java (revision 20474) +++ main/plugin-web/src/org/ametys/web/repository/site/SiteGenerator.java (working copy) @@ -74,6 +74,7 @@ attr.addAttribute("", "id", "id", "CDATA", site.getId()); attr.addAttribute("", "name", "name", "CDATA", site.getName()); attr.addAttribute("", "title", "title", "CDATA", StringUtils.defaultString(site.getTitle())); + attr.addAttribute("", "path", "path", "CDATA", site.getSitePath()); XMLUtils.startElement(contentHandler, "site", attr); _saxSite (site); XMLUtils.endElement(contentHandler, "site"); @@ -84,7 +85,7 @@ private void _saxSite (Site site) throws SAXException { CompositeMetadata metaHolder = site.getMetadataHolder(); - + XMLUtils.startElement(contentHandler, "metadata"); String[] metadataNames = metaHolder.getMetadataNames(); for (int i = 0; i < metadataNames.length; i++) @@ -100,5 +101,5 @@ XMLUtils.endElement(contentHandler, "metadata"); } - + } Index: main/plugin-web/sitemap-back.xmap =================================================================== --- main/plugin-web/sitemap-back.xmap (revision 20474) +++ main/plugin-web/sitemap-back.xmap (working copy) @@ -28,6 +28,8 @@ + + @@ -300,6 +302,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: main/workspace-web/src/org/ametys/web/repository/IsPageCacheableAction.java =================================================================== --- main/workspace-web/src/org/ametys/web/repository/IsPageCacheableAction.java (revision 20474) +++ main/workspace-web/src/org/ametys/web/repository/IsPageCacheableAction.java (working copy) @@ -32,14 +32,15 @@ import org.apache.cocoon.environment.SourceResolver; import org.ametys.plugins.repository.AmetysObjectIterable; +import org.ametys.web.cachemonitoring.back.PageResourceAccess; import org.ametys.web.inputdata.InputData; import org.ametys.web.inputdata.InputDataExtensionPoint; import org.ametys.web.pageaccess.PageAccessInfo; import org.ametys.web.pageaccess.PageAccessManager; import org.ametys.web.repository.page.Page; +import org.ametys.web.repository.page.Page.PageType; import org.ametys.web.repository.page.Zone; import org.ametys.web.repository.page.ZoneItem; -import org.ametys.web.repository.page.Page.PageType; import org.ametys.web.repository.page.ZoneItem.ZoneType; import org.ametys.web.service.Service; import org.ametys.web.service.ServiceExtensionPoint; @@ -66,15 +67,21 @@ public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception { Request request = ObjectModelHelper.getRequest(objectModel); - Page page = (Page) request.getAttribute(Page.class.getName()); - if (_isCacheable(page)) + boolean isCacheable = _isCacheable(page); + if (isCacheable) { Response response = ObjectModelHelper.getResponse(objectModel); response.addHeader("X-Ametys-Cacheable", "true"); } - + + // Page access monitoring. + String uuid = request.getHeader("X-Ametys-FO-UUID"); + PageResourceAccess pageAccess = new PageResourceAccess(uuid, page.getId(), request.getRequestURI()); + pageAccess.setCacheable(isCacheable); + request.setAttribute("PAGE_ACCESS", pageAccess); + return EMPTY_MAP; } Index: main/workspace-web/src/org/ametys/web/repository/PageGenerator.java =================================================================== --- main/workspace-web/src/org/ametys/web/repository/PageGenerator.java (revision 20474) +++ main/workspace-web/src/org/ametys/web/repository/PageGenerator.java (working copy) @@ -52,6 +52,10 @@ import org.ametys.runtime.authentication.AuthorizationRequiredException; import org.ametys.runtime.util.IgnoreRootHandler; import org.ametys.web.cache.pageelement.PageElementCache; +import org.ametys.web.cachemonitoring.back.PageElementAccess; +import org.ametys.web.cachemonitoring.back.PageElementAccess.PageElementType; +import org.ametys.web.cachemonitoring.back.PageResourceAccess; +import org.ametys.web.cachemonitoring.core.ResourceAccessMonitor; import org.ametys.web.renderingcontext.RenderingContext; import org.ametys.web.renderingcontext.RenderingContextHandler; import org.ametys.web.repository.content.SharedContent; @@ -105,11 +109,17 @@ /** The service assignment handler. */ private ServicesAssignmentHandler _serviceAssignmentHandler; + /** The resource access monitoring component */ + private ResourceAccessMonitor _resourceAccessMonitor; + + /** The monitored resource access */ + private PageResourceAccess _pageAccess; + private int _zoneItemsInCache; private int _zoneItemsSaxed; private int _zonesSaxed; - + @Override public void service(ServiceManager serviceManager) throws ServiceException { @@ -124,6 +134,7 @@ _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); _cTypeAssignmentHandler = (ContentTypesAssignmentHandler) serviceManager.lookup(ContentTypesAssignmentHandler.ROLE); _serviceAssignmentHandler = (ServicesAssignmentHandler) serviceManager.lookup(ServicesAssignmentHandler.ROLE); + _resourceAccessMonitor = (ResourceAccessMonitor) serviceManager.lookup(ResourceAccessMonitor.ROLE); } @Override @@ -131,12 +142,12 @@ { long t0 = System.currentTimeMillis(); + Request request = ObjectModelHelper.getRequest(objectModel); + _zoneItemsInCache = 0; _zoneItemsSaxed = 0; _zonesSaxed = 0; - - Request request = ObjectModelHelper.getRequest(objectModel); - + Page page = (Page) request.getAttribute(Page.class.getName()); String title = page.getTitle(); @@ -149,13 +160,19 @@ throw new IllegalStateException("Cannot invoke the PageGenerator on a Page without a PageContent"); } + // Monitor the access to this page. + _pageAccess = (PageResourceAccess) request.getAttribute("PAGE_ACCESS"); + _pageAccess.setRenderingContext(renderingContext); + _pageAccess.setWorkspaceJCR(workspace); + _resourceAccessMonitor.addAccessRecord(_pageAccess); + contentHandler.startDocument(); AttributesImpl attrs = new AttributesImpl(); attrs.addCDATAAttribute("title", title); attrs.addCDATAAttribute("long-title", page.getLongTitle()); attrs.addCDATAAttribute("id", page.getId()); XMLUtils.startElement(contentHandler, "page", attrs); - + try { XMLUtils.startElement(contentHandler, "metadata"); @@ -187,16 +204,17 @@ } catch (AmetysRepositoryException e) { + _pageAccess = null; throw new ProcessingException("Unable to SAX page metadata", e); } - - AttributesImpl pcattrs = new AttributesImpl(); + + AttributesImpl pcattrs = new AttributesImpl(); pcattrs.addCDATAAttribute("modifiable", Boolean.toString(page instanceof ModifiablePage)); pcattrs.addCDATAAttribute("moveable", Boolean.toString(page instanceof MoveablePage)); XMLUtils.startElement(contentHandler, "pageContents", pcattrs); long t1 = System.currentTimeMillis(); - + try { // Iterate on existing zones @@ -204,7 +222,7 @@ { String zoneName = zone.getName(); AmetysObjectIterable zoneItems = zone.getZoneItems(); - + _saxZone(page, zoneName, zoneItems, workspace, siteName, renderingContext); } @@ -223,6 +241,7 @@ } catch (AmetysRepositoryException ex) { + _pageAccess = null; throw new ProcessingException("Unable to get Content", ex); } @@ -239,8 +258,10 @@ XMLUtils.endElement(contentHandler, "pageContents"); XMLUtils.endElement(contentHandler, "page"); contentHandler.endDocument(); + + _pageAccess = null; } - + /** * Sax a zone * @param page The page @@ -259,7 +280,7 @@ AttributesImpl zoneAttrs = new AttributesImpl(); zoneAttrs.addCDATAAttribute("name", zoneName); - + if (localZoneItems == null || !localZoneItems.hasNext()) { // zone is empty => try to inherit @@ -271,7 +292,7 @@ localZoneItems = parentPageZone.getZoneItems(); } } - + Request request = ObjectModelHelper.getRequest(objectModel); request.setAttribute(Zone.class.getName(), zoneName); @@ -281,10 +302,10 @@ _saxAvailableServices(page, zoneName); _saxZoneItems(page, localZoneItems, workspace, site, renderingContext); - + XMLUtils.endElement(contentHandler, "zone"); request.setAttribute(Zone.class.getName(), null); - + } /** @@ -331,7 +352,7 @@ /** * Sax zone items - * @param page + * @param page * @param zoneItems The zone items to sax * @throws SAXException * @throws MalformedURLException @@ -355,18 +376,23 @@ String id = zoneItem.getId(); ZoneType type = zoneItem.getType(); - + request.setAttribute(ZoneItem.class.getName(), zoneItem); AttributesImpl zoneItemAttrs = new AttributesImpl(); zoneItemAttrs.addCDATAAttribute("id", id); + PageElementAccess zoneAccess = _pageAccess.createPageElementAccess(id, PageElementType.fromZoneItemType(type)); + // try to get content from cache SaxBuffer cachedContent = _zoneItemCache.getPageElement(workspace, site, _getType(zoneItem), id, page.getId(), renderingContext); - + if (cachedContent != null) { _zoneItemsInCache++; + zoneAccess.setCacheable(true); + zoneAccess.setCacheHit(true); + cachedContent.toSAX(contentHandler); } else @@ -381,14 +407,17 @@ { String serviceId = zoneItem.getServiceId(); Service service = _serviceExtPt.getExtension(serviceId); - + if (service != null) - { + { isCacheable = service.isCacheable(zoneItem); } // if service is null, an exception will be thrown later } + zoneAccess.setCacheable(isCacheable); + zoneAccess.setCacheHit(false); + SaxBuffer buffer = null; ContentHandler handler; // the actual ContentHandler, either the real one, or a buffer if (isCacheable) @@ -402,10 +431,10 @@ } XMLUtils.startElement(handler, "zoneItem", zoneItemAttrs); - + XMLUtils.startElement(handler, "information"); XMLUtils.createElement(handler, "type", type.toString()); - + Source src = null; Exception ex = null; if (type == ZoneType.CONTENT) @@ -414,7 +443,7 @@ { Content content = zoneItem.getContent(); String metadataSetName = StringUtils.defaultString(zoneItem.getMetadataSetName(), "main"); - + XMLUtils.createElement(handler, "contentId", content.getId()); XMLUtils.createElement(handler, "contentName", content.getName()); XMLUtils.createElement(handler, "metadataSetName", metadataSetName); @@ -462,7 +491,7 @@ { String serviceId = zoneItem.getServiceId(); Service service = _serviceExtPt.getExtension(serviceId); - + if (service == null) { ex = new ProcessingException("Unable to get service for name '" + serviceId + "'"); @@ -472,20 +501,20 @@ AttributesImpl serviceAttrs = new AttributesImpl(); serviceAttrs.addCDATAAttribute("id", service.getId()); XMLUtils.startElement(handler, "type-information", serviceAttrs); - + service.getLabel().toSAX(handler, "label"); service.getDescription().toSAX(handler, "description"); XMLUtils.createElement(handler, "smallIcon", service.getSmallIcon()); XMLUtils.createElement(handler, "mediumIcon", service.getMediumIcon()); - + XMLUtils.endElement(handler, "type-information"); - + src = resolver.resolveURI(service.getURL(), null, _getParameters(service, zoneItem)); } } - + XMLUtils.endElement(handler, "information"); - + if (src == null) { getLogger().error("Unable to display zone item", ex); @@ -515,7 +544,7 @@ resolver.release(src); } } - + XMLUtils.endElement(handler, "zoneItem"); // finally store the buffered data in the cache and SAX it to the pipeline @@ -526,6 +555,9 @@ } } + // Monitor the access to this zone item. + _resourceAccessMonitor.addAccessRecord(zoneAccess); + // Empty content request attributes request.setAttribute(Content.class.getName(), null); // Empty zone item request attribute @@ -619,7 +651,7 @@ getLogger().error("The page '" + childPage.getId() + "' cannot inherit a zone '" + zoneName + "' of template '" + templateName + "' in skin '" + skinId + "' as asked for page '" + page.getId() + "' in site '" + site.getName() + "'", e); return null; } - + // This zone is not defined for the template if (zoneDef == null) { @@ -632,7 +664,7 @@ { return null; } - + // Get the parent page (that is a container page) Page parentPage = page; do @@ -656,8 +688,8 @@ // No inheritance for this template return null; } - - // Finally we will inherit from the parentPage and the zone inheritanceSrc + + // Finally we will inherit from the parentPage and the zone inheritanceSrc return _inherit(childPage, parentPage, inheritanceSrc); } @@ -689,7 +721,7 @@ { CompositeMetadata serviceParams = zoneItem.getServiceParameters(); Map params = new HashMap(); - + for (ServiceParameterOrRepeater serviceParameter : service.getParameters().values()) { if (serviceParameter instanceof ServiceParameter) @@ -716,7 +748,7 @@ return params; } - + /** * Get the values of a repeater in a service instance. * @param repeater the repeater definition. Index: main/workspace-web/src/org/ametys/web/inputdata/InputDataGenerator.java =================================================================== --- main/workspace-web/src/org/ametys/web/inputdata/InputDataGenerator.java (revision 20474) +++ main/workspace-web/src/org/ametys/web/inputdata/InputDataGenerator.java (working copy) @@ -28,6 +28,10 @@ import org.ametys.plugins.repository.provider.WorkspaceSelector; import org.ametys.web.cache.pageelement.PageElementCache; +import org.ametys.web.cachemonitoring.back.PageElementAccess; +import org.ametys.web.cachemonitoring.back.PageElementAccess.PageElementType; +import org.ametys.web.cachemonitoring.back.PageResourceAccess; +import org.ametys.web.cachemonitoring.core.ResourceAccessMonitor; import org.ametys.web.renderingcontext.RenderingContext; import org.ametys.web.renderingcontext.RenderingContextHandler; import org.ametys.web.repository.page.Page; @@ -45,6 +49,9 @@ private RenderingContextHandler _renderingconContextHandler; private SiteManager _sitesManager; + /** The resource access monitoring component */ + private ResourceAccessMonitor _resourceAccessMonitor; + @Override public void service(ServiceManager sManager) throws ServiceException { @@ -54,8 +61,9 @@ _renderingconContextHandler = (RenderingContextHandler) sManager.lookup(RenderingContextHandler.ROLE); _workspaceSelector = (WorkspaceSelector) sManager.lookup(WorkspaceSelector.ROLE); _sitesManager = (SiteManager) sManager.lookup(SiteManager.ROLE); + _resourceAccessMonitor = (ResourceAccessMonitor) sManager.lookup(ResourceAccessMonitor.ROLE); } - + @Override public void generate() throws SAXException, ProcessingException { @@ -66,7 +74,7 @@ String siteName; Site site; String pageId = null; - + Page page = (Page) request.getAttribute(Page.class.getName()); if (page != null) { @@ -83,6 +91,9 @@ String workspace = _workspaceSelector.getWorkspace(); RenderingContext renderingContext = _renderingconContextHandler.getRenderingContext(); + // Access monitoring + PageResourceAccess pageAccess = (PageResourceAccess) request.getAttribute("PAGE_ACCESS"); + contentHandler.startDocument(); XMLUtils.startElement(contentHandler, "inputData"); @@ -90,17 +101,25 @@ { InputData inputData = _inputDataExtensionPoint.getExtension(id); + PageElementAccess inputDataAccess = pageAccess.createPageElementAccess(id, PageElementType.INPUTDATA); + // lookup cached content SaxBuffer cachedContent = _inputDataCache.getPageElement(workspace, siteName, id, pageId, renderingContext); if (cachedContent != null) { + inputDataAccess.setCacheable(true); + inputDataAccess.setCacheHit(true); + cachedContent.toSAX(contentHandler); } else { boolean isCacheable = inputData.isCacheable(site, page); + inputDataAccess.setCacheable(isCacheable); + inputDataAccess.setCacheHit(false); + SaxBuffer buffer = null; ContentHandler handler; // the actual ContentHandler, either the real one, or a buffer if (isCacheable) @@ -122,6 +141,9 @@ buffer.toSAX(contentHandler); } } + + // Monitor the access to this zone item. + _resourceAccessMonitor.addAccessRecord(inputDataAccess); } XMLUtils.endElement(contentHandler, "inputData");