Vaadin, let's hack the Profiler
Vaadin comes with a builtin Profiler which is only available during debug mode, which might not be available or reasonable. So, let us see if we can use it without the debug mode enabled.
It has a profiler?
Yes, there is one right builtin on the client side. You can activate it easily enough by adding the following to the widgetset:
-
<set-property name="vaadin.profiler" value="true" />
But to see the results, you also have to switch the application into debug mode by changing the web.xml to the following:
-
<context-param>
-
<description>Vaadin production mode</description>
-
<param-name>productionMode</param-name>
-
<param-value>false</param-value>
-
</context-param>
This allows you to enter the debug mode of an application, though, that requires to restart the application (or in the worst case, a redeploy). One upside of testing new Widgetsets can be that one can apply them to any running application without modifications, because that is purely client-side. So changing the configuration of the application might not be possible or desirable.
There are some forum posts out there which talk about that it is enough to enable the profiler and see the the output of it being logged to the debug console of the browser, but that is not the case anymore.
Let's have a deeper look
The Profiler can be found in the class com.vaain.vaadin.client.Profiler and is comletely client-side. It will store all gathered information until the function logTimings() is called, which then will hand the gathered information to a consumer which can do with it whatever it wants. Now comes the interesting part, there is no public default implementation for the consumer provided. If you want to log what was profiled to the debug console you'll have to write your own. Even if there were, the function setProfilerResultConsumer(ProfilerResultConsumer) is commented with a warning that it might change in future versions without notice and should not be used - interesting. Also interesting is the fact that it can only be set once. Once set, it can not be changed.
Hm, looks a little bare bone. Of course there is an "internal" class that is utilizing it to send its output to the debug window, but we can't use any of that code, unfortunately. So let's see what we can do with it.
Send everything to the debug console
The easiest thing is to simply send all the output to the debug console of the browser, we can do this easily enough:
-
import java.util.LinkedHashMap;
-
import java.util.List;
-
-
import com.vaadin.client.Profiler.Node;
-
import com.vaadin.client.Profiler.ProfilerResultConsumer;
-
-
/**
-
* A simple {@link ProfilerResultConsumer} which is outputting everything to the
-
* debug console of the browser.
-
*
-
* @author Robert Zenz
-
*/
-
public class DebugConsoleProfilerResultConsumer implements ProfilerResultConsumer
-
{
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Initialization
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* Creates a new instance of {@link DebugConsoleProfilerResultConsumer}.
-
*/
-
public DebugConsoleProfilerResultConsumer()
-
{
-
super();
-
}
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Interface implementation
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* {@inheritDoc}
-
*/
-
@Override
-
public void addProfilerData(Node pRootNode, List pTotals)
-
{
-
debug(pRootNode);
-
-
for (Node node : pTotals)
-
{
-
debug(node);
-
}
-
}
-
-
/**
-
* {@inheritDoc}
-
*/
-
@Override
-
public void addBootstrapData(LinkedHashMap pTimings)
-
{
-
// TODO We'll ingore this for now.
-
}
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// User-defined methods
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* Sends the given {@link Node} to the debug console if it is
-
* {@link #isValid(Node) valid}.
-
*
-
* @param pNode the {@link Node} to log.
-
* @see #isValid(Node)
-
*/
-
private void debug(Node pNode)
-
{
-
if (isValid(pNode))
-
{
-
debug(pNode.getStringRepresentation(""));
-
}
-
}
-
-
/**
-
* Tests if the given {@link Node} is valid, meaning not {@code null}, has a
-
* {@link Node#getName() name} and was {@link Node#getCount() invoked at
-
* all}.
-
*
-
* @param pNode the {@link Node} to test}.
-
* @return {@code true} if the given {@link Node} is considered valid.
-
*/
-
private boolean isValid(Node pNode)
-
{
-
return pNode != null && pNode.getName() != null && pNode.getCount() > 0;
-
}
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Native methods
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* Logs the given message to the debug console.
-
*
-
* @param pMessage the message to log.
-
*/
-
private native void debug(String pMessage)
-
/*-{
-
console.debug(pMessage);
-
}-*/;
-
-
} // DebugConsoleProfilerResultConsumer
And we can attach it rather easily, too:
-
Profiler.setProfilerResultConsumer(new DebugConsoleProfilerResultConsumer());
Make sure to do this only once, otherwise an Exception will be thrown, stating that it can only be done once. Also the application should not be in debug mode, otherwise the Vaadin consumer will be attached. Now that we've attached a consumer we can recompile the Widgetset and actually try it, and low and behold, we see output in the debug window of the browser. Quite a lot, actually, seems like the Profiler is used often, which is good.
Filter the results
Skimming through the results is tedious, luckily most browsers come with the possibility to filter the results, but that possibility is quite limited. If we are interested in multiple results, the easiest way will be to filter them on the server side, obviously we can do that just as easily:
-
import java.util.HashSet;
-
import java.util.LinkedHashMap;
-
import java.util.List;
-
import java.util.Set;
-
-
import com.vaadin.client.Profiler.Node;
-
import com.vaadin.client.Profiler.ProfilerResultConsumer;
-
-
/**
-
* A simple {@link ProfilerResultConsumer} which is outputting everything to the
-
* debug console of the browser.
-
*
-
* @author Robert Zenz
-
*/
-
public class DebugConsoleProfilerResultConsumer implements ProfilerResultConsumer
-
{
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Class members
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/** The names of nodes we want to output. */
-
private Set wantedNames = new HashSet();
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Initialization
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* Creates a new instance of {@link DebugConsoleProfilerResultConsumer}.
-
*
-
* @param pWantedNames the names of the profiler data which should be
-
* displayed.
-
*/
-
public DebugConsoleProfilerResultConsumer(String... pWantedNames)
-
{
-
super();
-
-
if (pWantedNames != null && pWantedNames.length > 0)
-
{
-
for (String wantedName : pWantedNames)
-
{
-
wantedNames.add(wantedName);
-
}
-
}
-
}
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Interface implementation
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* {@inheritDoc}
-
*/
-
@Override
-
public void addProfilerData(Node pRootNode, List pTotals)
-
{
-
debug(pRootNode);
-
-
for (Node node : pTotals)
-
{
-
debug(node);
-
}
-
}
-
-
/**
-
* {@inheritDoc}
-
*/
-
@Override
-
public void addBootstrapData(LinkedHashMap pTimings)
-
{
-
// TODO We'll ingore this for now.
-
}
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// User-defined methods
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* Sends the given {@link Node} to the debug console if it is
-
* {@link #isValid(Node) valid}.
-
*
-
* @param pNode the {@link Node} to log.
-
* @see #isValid(Node)
-
*/
-
private void debug(Node pNode)
-
{
-
if (isValid(pNode))
-
{
-
debug(pNode.getStringRepresentation(""));
-
}
-
}
-
-
/**
-
* Tests if the given {@link Node} is valid, meaning not {@code null}, has a
-
* {@link Node#getName() name} and was {@link Node#getCount() invoked at
-
* all}.
-
*
-
* @param pNode the {@link Node} to test}.
-
* @return {@code true} if the given {@link Node} is considered valid.
-
*/
-
private boolean isValid(Node pNode)
-
{
-
return pNode != null
-
&& pNode.getName() != null
-
&& pNode.getCount() > 0
-
&& (wantedNames.isEmpty() || wantedNames.contains(pNode.getName()));
-
}
-
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
// Native methods
-
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
/**
-
* Logs the given message to the debug console.
-
*
-
* @param pMessage the message to log.
-
*/
-
private native void debug(String pMessage)
-
/*-{
-
console.debug(pMessage);
-
}-*/;
-
-
} // DebugConsoleProfilerResultConsumer
Now we can simply pass the list of "interesting" event names to the consumer and see only these.
JavaScript hacking
But there is more, instead of setting a consumer we can attach ourselves to the JavaScript function and instead process each profiled section individually. So in the debug console we can simply run something like this:
-
window.__gwtStatsEvent = function(event)
-
{
-
console.debug(event);
-
};
This will output every single profiler event into the console. Now, if we want to process these events, we must first understand there is always a "begin" and an "end" event, which respectively are marking the begin and end of an event.
We can now listen for a certain event and simply output how long it took, like this:
-
window.__profiler = {};
-
-
window.__gwtStatsEvent = function(event)
-
{
-
if (event.subSystem === "Layout pass")
-
{
-
if (event.type === "begin")
-
{
-
window.__profiler[event.subSystem] = event.millis;
-
}
-
else
-
{
-
console.log(
-
event.subSystem
-
+ ": "
-
+ (event.millis - window.__profiler[event.subSystem]).toFixed(0)
-
+ "ms");
-
}
-
}
-
};
Or, to go over the top, we could create a generic listener which informs us about everything:
-
window.__wantedEvents = [
-
"Layout pass",
-
"Layout measure connectors",
-
"layout PostLayoutListener"
-
];
-
-
window.__profiler = {};
-
-
window.__gwtStatsEvent = function(event)
-
{
-
if (window.__wantedEvents.indexOf(event.subSystem) >= 0)
-
{
-
if (typeof window.__profiler[event.subSystem] === "undefined")
-
{
-
window.__profiler[event.subSystem] = {
-
averageRuntime : 0,
-
count : 0,
-
lastBegin : 0,
-
lastEnd : 0,
-
lastRuntime: 0,
-
lastRuntimes : new Array(),
-
minRuntime : 999999999,
-
maxRuntime : -999999999,
-
totalRuntime : 0
-
}
-
}
-
-
var info = window.__profiler[event.subSystem];
-
-
if (event.type === "begin")
-
{
-
info.count = info.count + 1;
-
info.lastBegin = event.millis;
-
}
-
else
-
{
-
info.lastEnd = event.millis;
-
-
info.lastRuntime = info.lastEnd - info.lastBegin;
-
info.lastRuntimes.push(info.lastRuntime);
-
info.minRuntime = Math.min(info.lastRuntime, info.minRuntime);
-
info.maxRuntime = Math.max(info.lastRuntime, info.maxRuntime);
-
info.totalRuntime = info.totalRuntime + info.lastRuntime;
-
-
info.averageRuntime = 0;
-
for (var index = 0; index < info.lastRuntimes.length; index++)
-
{
-
info.averageRuntime = info.averageRuntime + info.lastRuntimes[index];
-
}
-
info.averageRuntime = info.averageRuntime / info.lastRuntimes.length;
-
-
console.log(
-
event.subSystem
-
+ ": "
-
+ info.count.toFixed(0)
-
+ " times at "
-
+ info.averageRuntime.toFixed(3)
-
+ " totaling "
-
+ info.totalRuntime.toFixed(0)
-
+ "ms with current at "
-
+ info.lastRuntime.toFixed(0)
-
+ "ms ("
-
+ info.minRuntime.toFixed(0)
-
+ "ms/"
-
+ info.maxRuntime.toFixed(0)
-
+ "ms)");
-
}
-
}
-
};
And we now have a neat little system in place which displays everything we'd like to know, and it is easily extendable and modifiable, too.
Conclusion
As we see, we have quite a few ways to get the information from the profiler even without the application running in debug mode, some might not be as obvious as others, though. The interesting part is that many things are easily accessible on the JavaScript side of Vaadin, directly from the debug console of the browser, one only has to look for it.