4

The question is - how do you measure the time of page load, which technics you use, which recomendations can you give, and what positive and negative experience do you have.

The trouble is that even light pages in jsf can take up to 10 seconds to load. These pages don't require any evaluations, resources on rendering, etc. The obvious answer - it is in the queue on rendering... Ok, but may be something else?

Seems, it is needed to measure time starting from the request, including rendering, evaluations, data transfer time, client-side rendering time, etc.

Do you have any well working ways, tool chains, tools for a full lifetime trace of time of the page in JSF?

Glassfish-3, JSF-2 is used. Machine with 64 CPUs.

4

2 回答 2

6

这是我刚刚制作和测试的一个简单的自定义解决方案。它提供了一些关于执行和渲染时间的统计数据。当然,所有信息仅来自服务器端处理。

结果示例:

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

实施非常简单。首先你必须在里面配置一个PhaseListenerfaces-config.xml

<lifecycle>
    <phase-listener>com.spectotechnologies.jsf.phaselisteners.PhaseProcessesAnalyserListener</phase-listener>
</lifecycle>

这是保存每个处理信息的辅助类

package com.spectotechnologies.website.common.helper;

import java.util.Date;
import javax.faces.event.PhaseId;

/**
 *
 * @author Alexandre Lavoie
 */
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;
    }
}

这是具有业务逻辑的类,请注意,目前统计信息只会增长并且永远不会被删除,这可能是一个很好的更新,只保留最后一个小时,例如:

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;

/**
 *
 * @author Alexandre Lavoie
 */
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;

        // Phase start
        switch(p_nType)
        {
            case 0:
                // start
                oPhaseProcess = new PhaseProcess();

                oPhaseProcess.setIdRequest(p_nIDRequest);
                oPhaseProcess.setUrl(p_sURL);
                oPhaseProcess.setPhase(p_oPhase);
                oPhaseProcess.setStart(new Date());

                if(m_lItems.size() > 250)
            {
                m_lItems.remove(0);
            }

                m_lItems.add(oPhaseProcess);
                break;
            case 1:
                // end
                for(int nPhase = m_lItems.size() - 1;nPhase >= 0;nPhase--)
                {
                    if(m_lItems.get(nPhase).getIdRequest() == p_nIDRequest && m_lItems.get(nPhase).getPhase() == p_oPhase)
                    {
                        m_lItems.get(nPhase).setEnd(new Date());
                        break;
                    }
                }
                break;
        }
    }

    public List<PhaseProcess> getList()
    {
        return m_lItems;
    }

    public Integer getNextIDRequest()
    {
        return m_nNextIDRequest++;
    }
}

这是著名的PhaseListener,其中跟踪信息:

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;

/**
 *
 * @author Alexandre Lavoie
 */
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)
        {
            // Do nothing
        }

        return sbURL.toString();
    }
}

最后,这是我创建的用于显示统计信息的简单页面。一个好的改进可能是增加页面处理的平均值,也增加了每个 idrequest 的处理时间。

豆代码:

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;

/**
 *
 * @author Alexandre Lavoie
 */
@ManagedBean
@RequestScoped
public class PagesStatisticsActions
{
    public List<PhaseProcess> getList()
    {
        return PhaseProcesses.getInstance().getList();
    }
}

查看代码:

<!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>

更新 1:

  • 在 URL 中添加参数
  • 添加了 250 条日志的限制
于 2013-05-17T01:22:36.840 回答
1

以下解决方案基于Alexandre Lavoie发布的解决方案,但适用于 ADF Faces:

PhaseProcessingTimeAnalyserListener

import java.io.Serializable;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.Map;
 
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
 
import oracle.adf.controller.v2.lifecycle.Lifecycle;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.controller.v2.lifecycle.PagePhaseListener;
import oracle.adf.share.ADFContext;
 
import com.mhis.posm.web.lifecycle.helper.PhaseProcessor;
 
/**
 * Class PhaseProcessingTimeAnalyserListener calculates the execution time of each Phase of ADF
 * @author TapasB
 */
public class PhaseProcessingTimeAnalyserListener implements PagePhaseListener, Serializable {
 
 private static final long serialVersionUID = 6814928970314659328L;
 
 /**
  * Method beforePhase notifies the listener before the execution of a specific phase of the ADF Page Lifecycle.
  * @author TapasB
  * @param phaseEvent
  * @see oracle.adf.controller.v2.lifecycle.PagePhaseListener#beforePhase(oracle.adf.controller.v2.lifecycle.PagePhaseEvent)
  */
 @Override
 public void beforePhase(PagePhaseEvent phaseEvent) {
  int phaseId = phaseEvent.getPhaseId();
  String phaseName = Lifecycle.getPhaseName(phaseId);
  PhaseProcessor.getInstance().process(getRequestId(), getURL(), phaseId, phaseName, PhaseProcessor.PhaseType.BEGIN);
 }
 
 /**
  * Method afterPhase notifies the listener after the execution of a specific phase of the ADF Page Lifecycle.
  * @author TapasB
  * @param phaseEvent
  * @see oracle.adf.controller.v2.lifecycle.PagePhaseListener#afterPhase(oracle.adf.controller.v2.lifecycle.PagePhaseEvent)
  */
 @Override
 public void afterPhase(PagePhaseEvent phaseEvent) {
  int phaseId = phaseEvent.getPhaseId();
  String phaseName = Lifecycle.getPhaseName(phaseId);
  PhaseProcessor.getInstance().process(getRequestId(), getURL(), phaseId, phaseName, PhaseProcessor.PhaseType.END);
 }
 
 /**
  * Method getRequestId generates and returns an unique ID value for each Request
  * @author TapasB
  * @return requestId
  */
 private Integer getRequestId() {
  @SuppressWarnings("unchecked")
  Map<String, Object> requestScope = ADFContext.getCurrent().getRequestScope();
  Integer requestId = (Integer) requestScope.get("requestId");
 
  if (requestId == null) {
   requestId = PhaseProcessor.getInstance().getNextRequestId();
   requestScope.put("requestId", requestId);
  }
 
  return requestId;
 }
 
 /**
  * Method getURL returns the URL in which the application is requested
  * @author TapasB
  * @return a String URL
  */
 private String getURL() {
  Enumeration<String> parameterNames = null;
  String parameterName = null;
  StringBuilder urlBuilder = new StringBuilder();
  Object request = FacesContext.getCurrentInstance().getExternalContext().getRequest();
 
  try {
   if (request instanceof HttpServletRequest) {
    HttpServletRequest servletRequest = (HttpServletRequest) request;
    urlBuilder.append(servletRequest.getRequestURL().toString());
    parameterNames = servletRequest.getParameterNames();
 
    if (parameterNames.hasMoreElements()) {
     if (!urlBuilder.toString().contains("?")) {
      urlBuilder.append("?");
     } else {
      urlBuilder.append("&");
     }
    }
 
    while (parameterNames.hasMoreElements()) {
     parameterName = parameterNames.nextElement();
 
     urlBuilder.append(parameterName);
     urlBuilder.append("=");
     urlBuilder.append(URLEncoder.encode(servletRequest.getParameter(parameterName), "UTF-8"));
 
     if (parameterNames.hasMoreElements()) {
      urlBuilder.append("&");
     }
    }
   }
  } catch (Exception ex) {
  }
 
  return urlBuilder.toString();
 }
}

相位处理器

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
 
import oracle.adf.share.ADFContext;
 
import com.edifixio.osrd.generic.log.Log;
 
/**
 * Class PhaseProcessor processes the Phase execution time
 * @author TapasB
 */
public class PhaseProcessor implements Serializable {
 
 private static final long serialVersionUID = 6658181867505616109L;
 
 private List<Phase> phases = new ArrayList<Phase>();
 private AtomicInteger nextRequestId = new AtomicInteger(1);
 
 /**
  * Constructor PhaseProcessor is private
  * @author TapasB
  */
 private PhaseProcessor() {
 
 }
 
 /**
  * Class PhaseType
  * @author TapasB
  */
 public static enum PhaseType {
  BEGIN,
  END;
 }
 
 /**
  * Method getInstance returns a Session Instance of this class 
  * @author TapasB
  * @return an PhaseProcessor instance
  */
 public static PhaseProcessor getInstance() {
  @SuppressWarnings("unchecked")
  Map<String, Object> sessionScope = ADFContext.getCurrent().getSessionScope();
  PhaseProcessor phaseProcessor = (PhaseProcessor) sessionScope.get("phases");
 
  if (phaseProcessor == null) {
   phaseProcessor = new PhaseProcessor();
   sessionScope.put("phases", phaseProcessor);
  }
 
  return phaseProcessor;
 }
 
 /**
  * Method process processes the {@link Phase} 
  * @author TapasB
  * @param requestId - the unique ID for each request
  * @param url - the url in which the application is requested
  * @param phaseId - ADF's Phase ID
  * @param phaseName - ADF's Phase Name
  * @param phaseType - BEGIN or END
  */
 public void process(int requestId, String url, int phaseId, String phaseName, PhaseType phaseType) {
  Phase phase;
 
  switch (phaseType) {
   case BEGIN:
    phase = new Phase();
 
    phase.setRequestId(requestId);
    phase.setUrl(url);
    phase.setPhaseId(phaseId);
    phase.setPhaseName(phaseName);
    phase.setStartTime(new Date());
 
    phases.add(phase);
 
    Log.info(this, "The Phase: " + phase.getPhaseName(phaseId) + " begins. Requested URL: " + phase.getUrl());
    break;
 
   case END:
    ListIterator<Phase> phaseIterator = phases.listIterator(phases.size());
 
    while (phaseIterator.hasPrevious()) {
     phase = phaseIterator.previous();
 
     if (phase.getRequestId() == requestId && phase.getPhaseId() == phaseId && phase.getPhaseName().equals(phaseName)) {
      phase.setEndTime(new Date());
      Log.info(this, "The Phase: " + phase.getPhaseName(phaseId) + " ends with execution time: '" + (phase.getEndTime().getTime() - phase.getStartTime().getTime()) + "' millisecondes. Requested URL: " + phase.getUrl());
      phaseIterator.remove();
      break;
     }
 
    }
 
    break;
  }
 }
 
 /**
  * Method getNextRequestId returns and increment the unique ID 
  * @author TapasB
  * @return the ID
  */
 public Integer getNextRequestId() {
  return nextRequestId.getAndIncrement();
 }
}

阶段

import java.io.Serializable;
import java.util.Date;
 
import oracle.adf.controller.faces.lifecycle.JSFLifecycle;
 
/**
 * Class Phase represent a phase
 * @author TapasB
 */
public class Phase implements Serializable {
 
 private static final long serialVersionUID = -461595462579265128L;
 
 private int requestId;
 private String url;
 private Date startTime;
 private Date endTime;
 private int phaseId;
 private String phaseName;
 
 /**
  * Constructor Phase is default
  * @author TapasB
  */
 public Phase() {
 
 }
 
 /**
  * Method getRequestId returns requestId
  * @author TapasB
  * @return requestId
  */
 public int getRequestId() {
  return requestId;
 }
 
 /**
  * Method setRequestId sets the requestId
  * @author TapasB
  * @param requestId the requestId to set
  */
 public void setRequestId(int requestId) {
  this.requestId = requestId;
 }
 
 /**
  * Method getUrl returns url
  * @author TapasB
  * @return url
  */
 public String getUrl() {
  return url;
 }
 
 /**
  * Method setUrl sets the url
  * @author TapasB
  * @param url the url to set
  */
 public void setUrl(String url) {
  this.url = url;
 }
 
 /**
  * Method getStartTime returns startTime
  * @author TapasB
  * @return startTime
  */
 public Date getStartTime() {
  return startTime;
 }
 
 /**
  * Method setStartTime sets the startTime
  * @author TapasB
  * @param startTime the startTime to set
  */
 public void setStartTime(Date startTime) {
  this.startTime = startTime;
 }
 
 /**
  * Method getEndTime returns endTime
  * @author TapasB
  * @return endTime
  */
 public Date getEndTime() {
  return endTime;
 }
 
 /**
  * Method setEndTime sets the endTime
  * @author TapasB
  * @param endTime the endTime to set
  */
 public void setEndTime(Date endTime) {
  this.endTime = endTime;
 }
 
 /**
  * Method getPhaseId returns phaseId
  * @author TapasB
  * @return phaseId
  */
 public int getPhaseId() {
  return phaseId;
 }
 
 /**
  * Method setPhaseId sets the phaseId
  * @author TapasB
  * @param phaseId the phaseId to set
  */
 public void setPhaseId(int phaseId) {
  this.phaseId = phaseId;
 }
 
 /**
  * Method getPhaseName returns phaseName
  * @author TapasB
  * @return phaseName
  */
 public String getPhaseName() {
  return phaseName;
 }
 
 /**
  * Method setPhaseName sets the phaseName
  * @author TapasB
  * @param phaseName the phaseName to set
  */
 public void setPhaseName(String phaseName) {
  this.phaseName = phaseName;
 }
 
 /**
  * Method getPhaseName returns the name of the Phase
  * @author TapasB
  * @param phaseId
  * @return the phase name
  */
 public String getPhaseName(int phaseId) {
  if (phaseId == JSFLifecycle.INIT_CONTEXT_ID) {
   return "INIT_CONTEXT";
  } else if (phaseId == JSFLifecycle.PREPARE_MODEL_ID) {
   return "PREPARE_MODEL";
  } else if (phaseId == JSFLifecycle.APPLY_INPUT_VALUES_ID) {
   return "APPLY_INPUT_VALUES";
  } else if (phaseId == JSFLifecycle.VALIDATE_INPUT_VALUES_ID) {
   return "VALIDATE_INPUT_VALUES";
  } else if (phaseId == JSFLifecycle.PROCESS_UPDATE_MODEL_ID) {
   return "PROCESS_UPDATE_MODEL";
  } else if (phaseId == JSFLifecycle.VALIDATE_MODEL_UPDATES_ID) {
   return "VALIDATE_MODEL_UPDATES";
  } else if (phaseId == JSFLifecycle.PROCESS_COMPONENT_EVENTS_ID) {
   return "PROCESS_COMPONENT_EVENTS";
  } else if (phaseId == JSFLifecycle.METADATA_COMMIT_ID) {
   return "METADATA_COMMIT";
  } else if (phaseId == JSFLifecycle.PREPARE_RENDER_ID) {
   return "PREPARE_RENDER";
  } else if (phaseId == JSFLifecycle.JSF_RESTORE_VIEW_ID) {
   return "RESTORE_VIEW";
  } else if (phaseId == JSFLifecycle.JSF_APPLY_REQUEST_VALUES_ID) {
   return "JSF_APPLY_REQUEST_VALUES";
  } else if (phaseId == JSFLifecycle.JSF_PROCESS_VALIDATIONS_ID) {
   return "JSF_PROCESS_VALIDATIONS";
  } else if (phaseId == JSFLifecycle.JSF_UPDATE_MODEL_VALUES_ID) {
   return "JSF_UPDATE_MODEL_VALUES";
  } else if (phaseId == JSFLifecycle.JSF_INVOKE_APPLICATION_ID) {
   return "JSF_INVOKE_APPLICATION";
  } else {
   return "JSF_RENDER_RESPONSE";
  }
 }
}

更多信息请看这里

于 2014-01-01T15:33:56.040 回答