You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
This article will address a great mystery to most of us, how long do those EL calls really take? The most straight forward approach to solving that question is using Aspect Oriented Programming. With just a couple of simple pointcuts we can intercept calls to org.jboss.el.ValueExpressionImpl.getValue(..) and org.jboss.el.MethodExpressionImpl.invoke(..). After that, just wrap those methods in System.currentTimeMillis() and log it.
So lets get started. The versions I used to test with are EAP 4.3_CP02 for those non enterprise customers any 4.2.x version should work. The JDK version used is 1.5_15 and the Seam version 2.0.3.CR1 although that is inconsequential.
The first step is to enable Load Time Weaving for JBossAOP.
JAVA_OPTS="$JAVA_OPTS -javaagent:pluggable-instrumentor.jar"
Now, before we get to the main part, lets go ahead and setup the jboss-log4j.xml to correctly handle the Interceptors.
Open up JBOSS_HOME/server/<configuration>/conf/jboss-log4j.xml and add:
<appender name="EL_METRICS" class="org.jboss.logging.appender.DailyRollingFileAppender">
<errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
<param name="File" value="${jboss.server.log.dir}/el_metrics.log"/>
<param name="Append" value="false"/>
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
and
<category name="org.jboss.injbossaop.ValueExpressionMetrics">
<priority value="INFO"/>
<appender-ref ref="EL_METRICS"/>
</category>
<category name="org.jboss.injbossaop.MethodExpressionMetrics">
<priority value="INFO"/>
<appender-ref ref="EL_METRICS"/>
</category>
As you can see all of the log statements in the interceptors will be sent to JBOSS_HOME/server/<configuration>/log/el_metrics.log
It's time to deploy the .aop file to the server. Download elaopmetrics.aop which was uploaded to this directory and put that into JBOSS_HOME/server/<configuration>/deploy. The elaopmetrics.aop file is one of the ways to package and deploy JBossAOP code to the server. The structure contains the java classes and a META-INF/jboss-aop.xml that defines the pointcuts and such.
Start JBoss and deploy a Seam WAR or EAR file and access a page. Go to your filesystem and find JBOSS_HOME/server/<configuration>/log/el_metrics.log In my example I seam-gen'd a new Entity called Car and accessed car.seam. The resulting output was:
[ExpressionMetrics] 0ms ValueExpression: #{traceData}
[ExpressionMetrics] 0ms ValueExpression: #{traceData}
[ExpressionMetrics] 1ms ValueExpression: #{traceData}
[ExpressionMetrics] 0ms ValueExpression: DynamicCRUD
[ExpressionMetrics] 0ms ValueExpression: #{projectName}:
[ExpressionMetrics] 0ms ValueExpression: #{identity.loggedIn}
[ExpressionMetrics] 1ms ValueExpression: #{not identity.loggedIn}
[ExpressionMetrics] 1ms ValueExpression: #{identity.loggedIn}
[ExpressionMetrics] 0ms ValueExpression: #{not identity.loggedIn}
[ExpressionMetrics] 0ms ValueExpression: #{not identity.loggedIn}
[ExpressionMetrics] 1ms ValueExpression: #{not identity.loggedIn}
[ExpressionMetrics] 0ms ValueExpression: #{not identity.loggedIn}
[ExpressionMetrics] 1ms ValueExpression: #{not identity.loggedIn}
[ExpressionMetrics] 0ms ValueExpression: #{conversation.longRunning}
[ExpressionMetrics] 1ms ValueExpression: #{conversation.id}
[ExpressionMetrics] 1ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: #{invalid}
[ExpressionMetrics] 0ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: name #{invalid?'errors':''}
[ExpressionMetrics] 0ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: #{required}
[ExpressionMetrics] 0ms ValueExpression: #{invalid?'errors':''}
[ExpressionMetrics] 0ms ValueExpression: #{SeamExampleProjectEntityManagerFactory}
[ExpressionMetrics] 10ms ValueExpression: #{carHome.instance.name}
[ExpressionMetrics] 0ms ValueExpression: #{invalid}
[ExpressionMetrics] 6ms ValueExpression: #{!carHome.managed}
[ExpressionMetrics] 4ms ValueExpression: #{!carHome.managed}
[ExpressionMetrics] 5ms ValueExpression: #{!carHome.managed}
[ExpressionMetrics] 5ms ValueExpression: #{!carHome.managed}
[ExpressionMetrics] 4ms ValueExpression: #{!carHome.managed}
[ExpressionMetrics] 4ms ValueExpression: #{carHome.managed}
[ExpressionMetrics] 4ms ValueExpression: #{carHome.managed}
[ExpressionMetrics] 0ms ValueExpression: #{traceData}
For those interested in how this all works, here is the necessary code:
MethodExpressionMetrics.java:
public class MethodExpressionMetrics implements Interceptor {
private static Logger log = Logger.getLogger(MethodExpressionMetrics.class);
public String getName() {
return "MethodExpressionMetrics";
}
public Object invoke(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.invokeNext();
} finally {
long elapsedTime = System.currentTimeMillis() - startTime;
java.lang.reflect.Method m = ((MethodInvocation) invocation).getMethod();
Field field = invocation.getTargetObject().getClass().getDeclaredField("expr");
field.setAccessible(true);
String expr = (String)field.get(invocation.getTargetObject());
log.info("[ExpressionMetrics] " + elapsedTime + "ms MethodExpression: " + expr);
}
}
}
ValueExpressionMetrics.java:
public class ValueExpressionMetrics implements Interceptor {
private static Logger log = Logger.getLogger(ValueExpressionMetrics.class);
public String getName() {
return "ValueExpressionMetrics";
}
public Object invoke(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.invokeNext();
} finally {
long elapsedTime = System.currentTimeMillis() - startTime;
java.lang.reflect.Method m = ((MethodInvocation) invocation).getMethod();
Field field = invocation.getTargetObject().getClass().getDeclaredField("expr");
field.setAccessible(true);
String expr = (String) field.get(invocation.getTargetObject());
log.info("[ExpressionMetrics] " + elapsedTime + "ms ValueExpression: " + expr);
}
}
}
jboss-aop.xml:
<aop>
<bind pointcut="execution(public java.lang.Object org.jboss.el.ValueExpressionImpl->getValue(..))">
<interceptor class="org.jboss.injbossaop.ValueExpressionMetrics"/>
</bind>
<bind pointcut="execution(public java.lang.Object org.jboss.el.MethodExpressionImpl->invoke(..))">
<interceptor class="org.jboss.injbossaop.MethodExpressionMetrics"/>
</bind>
</aop>
I have plenty of ideas on how to evolve this basic implementation to something more user friends, but for now, those who are command-line friendly will be happy.