Here is a simple custom solution that I just made and tested. It provides some statistics on runtime and runtime. Of course, all information is available only from server processing.
Example result:
Date ID request URL Phase Execution time 2013-05-16 21:10:29.781 34 http://localhost:8080/web/page.jspx RESTORE_VIEW 1 15 2013-05-16 21:10:29.796 34 http://localhost:8080/web/page.jspx RENDER_RESPONSE 6 4438 2013-05-16 21:10:39.437 35 http://localhost:8080/web/page.jspx RESTORE_VIEW 1 16 2013-05-16 21:10:39.453 35 http://localhost:8080/web/page.jspx RENDER_RESPONSE 6 3937
The implementation is quite complicated. First you have fine-tuning PhaseListener inside faces-config.xml
<lifecycle> <phase-listener>com.spectotechnologies.jsf.phaselisteners.PhaseProcessesAnalyserListener</phase-listener> </lifecycle>
Here is a helper class that stores information about each processing:
package com.spectotechnologies.website.common.helper; import java.util.Date; import javax.faces.event.PhaseId; public class PhaseProcess { private int m_nIDRequest; private String m_sURL; private Date m_oStart; private Date m_oEnd; private PhaseId m_oPhase; public void setIdRequest(int p_nIDRequest) { m_nIDRequest = p_nIDRequest; } public int getIdRequest() { return m_nIDRequest; } public void setUrl(String p_sURL) { m_sURL = p_sURL; } public String getUrl() { return m_sURL; } public void setStart(Date p_oStart) { m_oStart = p_oStart; } public Date getStart() { return m_oStart; } public void setEnd(Date p_oEnd) { m_oEnd = p_oEnd; } public Date getEnd() { return m_oEnd; } public void setPhase(PhaseId p_oPhase) { m_oPhase = p_oPhase; } public PhaseId getPhase() { return m_oPhase; } public long getExecutionTime() { long lExecutionTime = -1; if(getEnd() != null) { lExecutionTime = getEnd().getTime() - getStart().getTime(); } return lExecutionTime; } }
Here is a class with business logic , note that at the moment the statistics will only grow and will never be deleted, it can be a good update to save only the last hour, for example:
package com.spectotechnologies.website.common.helper; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.faces.context.FacesContext; import javax.faces.event.PhaseId; public class PhaseProcesses { private List<PhaseProcess> m_lItems = new ArrayList(); private int m_nNextIDRequest = 0; public static PhaseProcesses getInstance() { FacesContext oFaces = FacesContext.getCurrentInstance(); PhaseProcesses oPhaseProcesses = (PhaseProcesses)oFaces.getExternalContext().getSessionMap().get("sessionPhaseProcesses"); if(oPhaseProcesses == null) { oPhaseProcesses = new PhaseProcesses(); FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("sessionPhaseProcesses",oPhaseProcesses); } return oPhaseProcesses; } public void set(int p_nIDRequest, String p_sURL, PhaseId p_oPhase, int p_nType) { PhaseProcess oPhaseProcess;
Here is the famous PhaseListener where information is tracked:
package com.spectotechnologies.jsf.phaselisteners; import com.spectotechnologies.website.common.helper.PhaseProcesses; import java.net.URLEncoder; import java.util.Enumeration; import javax.faces.context.FacesContext; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; import javax.servlet.http.HttpServletRequest; public class PhaseProcessesAnalyserListener implements PhaseListener { @Override public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } @Override public void beforePhase(PhaseEvent p_oEvent) { PhaseProcesses.getInstance().set(getIDRequest(),getURL(),p_oEvent.getPhaseId(),0); } @Override public void afterPhase(PhaseEvent p_oEvent) { PhaseProcesses.getInstance().set(getIDRequest(),getURL(),p_oEvent.getPhaseId(),1); } private Integer getIDRequest() { Integer iIDRequest = (Integer)FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("idrequest"); if(iIDRequest == null) { iIDRequest = PhaseProcesses.getInstance().getNextIDRequest(); FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("idrequest",iIDRequest); } return iIDRequest; } private String getURL() { Enumeration<String> lParameters; String sParameter; StringBuilder sbURL = new StringBuilder(); Object oRequest = FacesContext.getCurrentInstance().getExternalContext().getRequest(); try { if(oRequest instanceof HttpServletRequest) { sbURL.append(((HttpServletRequest)oRequest).getRequestURL().toString()); lParameters = ((HttpServletRequest)oRequest).getParameterNames(); if(lParameters.hasMoreElements()) { if(!sbURL.toString().contains("?")) { sbURL.append("?"); } else { sbURL.append("&"); } } while(lParameters.hasMoreElements()) { sParameter = lParameters.nextElement(); sbURL.append(sParameter); sbURL.append("="); sbURL.append(URLEncoder.encode(((HttpServletRequest)oRequest).getParameter(sParameter),"UTF-8")); if(lParameters.hasMoreElements()) { sbURL.append("&"); } } } } catch(Exception e) {
Finally, here is a simple page that I created to display statistics. A good improvement would be to add averages to process the pages, as well as the processing time at a time.
Bean code:
package com.spectotechnologies.website.common.beans; import com.spectotechnologies.website.common.helper.PhaseProcess; import com.spectotechnologies.website.common.helper.PhaseProcesses; import java.util.List; import javax.faces.bean.ManagedBean; import javax.faces.bean.RequestScoped; @ManagedBean @RequestScoped public class PagesStatisticsActions { public List<PhaseProcess> getList() { return PhaseProcesses.getInstance().getList(); } }
View code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <f:view contentType="application/xhtml+xml"> <h:head> <meta http-equiv="Content-Type" content="application/xhtml+xml;charset=UTF-8" /> </h:head> <h:body> <h:dataTable value="#{pagesStatisticsActions.list}" var="item"> <h:column> <f:facet name="header"> Date </f:facet> <h:outputText value="#{item.start}"> <f:convertDateTime timeZone="America/Montreal" pattern="yyyy-MM-dd HH:mm:ss.SSS" /> </h:outputText> </h:column> <h:column> <f:facet name="header"> ID request </f:facet> #{item.idRequest} </h:column> <h:column> <f:facet name="header"> URL </f:facet> #{item.url} </h:column> <h:column> <f:facet name="header"> Phase </f:facet> #{item.phase} </h:column> <h:column> <f:facet name="header"> Execution time (ms) </f:facet> #{item.executionTime} </h:column> </h:dataTable> </h:body> </f:view> </html>
UPDATE 1:
- URL parameters added
- 250 log limit added