Backport JS profiler from Helma NG. Use rhino.profile = true to activate.
This commit is contained in:
parent
410226aa36
commit
0dc5aed374
3 changed files with 158 additions and 1 deletions
|
@ -95,6 +95,7 @@ public final class RhinoCore implements ScopeProvider {
|
||||||
// debugger/tracer flags
|
// debugger/tracer flags
|
||||||
boolean hasDebugger = false;
|
boolean hasDebugger = false;
|
||||||
boolean hasTracer = false;
|
boolean hasTracer = false;
|
||||||
|
boolean hasProfiler = false;
|
||||||
private boolean isInitialized = false;
|
private boolean isInitialized = false;
|
||||||
|
|
||||||
// dynamic portion of the type check sleep that grows
|
// dynamic portion of the type check sleep that grows
|
||||||
|
@ -120,9 +121,10 @@ public final class RhinoCore implements ScopeProvider {
|
||||||
|
|
||||||
hasDebugger = "true".equalsIgnoreCase(app.getProperty("rhino.debug"));
|
hasDebugger = "true".equalsIgnoreCase(app.getProperty("rhino.debug"));
|
||||||
hasTracer = "true".equalsIgnoreCase(app.getProperty("rhino.trace"));
|
hasTracer = "true".equalsIgnoreCase(app.getProperty("rhino.trace"));
|
||||||
|
hasProfiler = "true".equalsIgnoreCase(app.getProperty("rhino.profile"));
|
||||||
|
|
||||||
// Set default optimization level according to whether debugger is on
|
// Set default optimization level according to whether debugger is on
|
||||||
if (hasDebugger || hasTracer) {
|
if (hasDebugger || hasTracer || hasProfiler) {
|
||||||
optLevel = -1;
|
optLevel = -1;
|
||||||
} else {
|
} else {
|
||||||
String opt = app.getProperty("rhino.optlevel");
|
String opt = app.getProperty("rhino.optlevel");
|
||||||
|
|
|
@ -28,6 +28,7 @@ import helma.objectmodel.db.Relation;
|
||||||
import helma.objectmodel.db.Node;
|
import helma.objectmodel.db.Node;
|
||||||
import helma.scripting.*;
|
import helma.scripting.*;
|
||||||
import helma.scripting.rhino.debug.Tracer;
|
import helma.scripting.rhino.debug.Tracer;
|
||||||
|
import helma.scripting.rhino.debug.Profiler;
|
||||||
import helma.util.StringUtils;
|
import helma.util.StringUtils;
|
||||||
import org.mozilla.javascript.*;
|
import org.mozilla.javascript.*;
|
||||||
import org.mozilla.javascript.serialize.ScriptableOutputStream;
|
import org.mozilla.javascript.serialize.ScriptableOutputStream;
|
||||||
|
@ -163,6 +164,8 @@ public class RhinoEngine implements ScriptingEngine {
|
||||||
|
|
||||||
if (core.hasTracer) {
|
if (core.hasTracer) {
|
||||||
context.setDebugger(new Tracer(getResponse()), null);
|
context.setDebugger(new Tracer(getResponse()), null);
|
||||||
|
} else if (core.hasProfiler) {
|
||||||
|
context.setDebugger(new Profiler(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the engine with the current thread
|
// register the engine with the current thread
|
||||||
|
@ -209,6 +212,16 @@ public class RhinoEngine implements ScriptingEngine {
|
||||||
* execution context has terminated.
|
* execution context has terminated.
|
||||||
*/
|
*/
|
||||||
public synchronized void exitContext() {
|
public synchronized void exitContext() {
|
||||||
|
if (core.hasProfiler) {
|
||||||
|
try {
|
||||||
|
Profiler profiler = (Profiler) Context.getCurrentContext().getDebugger();
|
||||||
|
String result = profiler.getResult();
|
||||||
|
getResponse().debug("<pre>" + result + "</pre>");
|
||||||
|
System.out.println(result);
|
||||||
|
} catch (Exception x) {
|
||||||
|
app.logError("Error in profiler: " + x, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
// unregister the engine threadlocal
|
// unregister the engine threadlocal
|
||||||
engines.set(null);
|
engines.set(null);
|
||||||
Context.exit();
|
Context.exit();
|
||||||
|
|
142
src/helma/scripting/rhino/debug/Profiler.java
Normal file
142
src/helma/scripting/rhino/debug/Profiler.java
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package helma.scripting.rhino.debug;
|
||||||
|
|
||||||
|
import org.mozilla.javascript.debug.Debugger;
|
||||||
|
import org.mozilla.javascript.debug.DebuggableScript;
|
||||||
|
import org.mozilla.javascript.debug.DebugFrame;
|
||||||
|
import org.mozilla.javascript.Context;
|
||||||
|
import org.mozilla.javascript.Scriptable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class Profiler implements Debugger {
|
||||||
|
|
||||||
|
HashMap frames = new HashMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a profiler that writes to this response object
|
||||||
|
*/
|
||||||
|
public Profiler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementws handleCompilationDone in interface org.mozilla.javascript.debug.Debugger
|
||||||
|
*/
|
||||||
|
public void handleCompilationDone(Context cx, DebuggableScript script, String source) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements getFrame() in interface org.mozilla.javascript.debug.Debugger
|
||||||
|
*/
|
||||||
|
public DebugFrame getFrame(Context cx, DebuggableScript script) {
|
||||||
|
if (script.isFunction()) {
|
||||||
|
String name = getFunctionName(script);
|
||||||
|
ProfilerFrame frame = (ProfilerFrame) frames.get(name);
|
||||||
|
if (frame == null) {
|
||||||
|
frame = new ProfilerFrame(name);
|
||||||
|
frames.put(name, frame);
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string representation for the given script
|
||||||
|
* @param script a function or script
|
||||||
|
* @return the file and/or function name of the script
|
||||||
|
*/
|
||||||
|
static String getFunctionName(DebuggableScript script) {
|
||||||
|
if (script.isFunction()) {
|
||||||
|
if (script.getFunctionName() != null) {
|
||||||
|
return script.getSourceName() + ": " + script.getFunctionName();
|
||||||
|
} else {
|
||||||
|
return script.getSourceName() + ": #" + script.getLineNumbers()[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return script.getSourceName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResult() {
|
||||||
|
ProfilerFrame[] f = (ProfilerFrame[]) frames.values().toArray(new ProfilerFrame[0]);
|
||||||
|
Arrays.sort(f, new Comparator() {
|
||||||
|
public int compare(Object o1, Object o2) {
|
||||||
|
return ((ProfilerFrame)o2).runtime - ((ProfilerFrame)o1).runtime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
StringBuffer buffer = new StringBuffer(" total average calls path\n");
|
||||||
|
buffer.append("==================================================================\n");
|
||||||
|
for (int i = 0; i < Math.min(100, f.length); i++) {
|
||||||
|
buffer.append(f[i].renderLine(0));
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfilerFrame implements DebugFrame {
|
||||||
|
|
||||||
|
Stack timer = new Stack();
|
||||||
|
int runtime, invocations, lineNumber;
|
||||||
|
String name;
|
||||||
|
|
||||||
|
ProfilerFrame(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when execution is ready to start bytecode interpretation
|
||||||
|
* for entered a particular function or script.
|
||||||
|
*/
|
||||||
|
public void onEnter(Context cx, Scriptable activation,
|
||||||
|
Scriptable thisObj, Object[] args) {
|
||||||
|
|
||||||
|
long time = System.nanoTime();
|
||||||
|
timer.push(new Long(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when thrown exception is handled by the function or script.
|
||||||
|
*/
|
||||||
|
public void onExceptionThrown(Context cx, Throwable ex) {
|
||||||
|
invocations ++;
|
||||||
|
Long time = (Long) timer.pop();
|
||||||
|
runtime += System.nanoTime() - time.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the function or script for this frame is about to return.
|
||||||
|
*/
|
||||||
|
public void onExit(Context cx, boolean byThrow, Object resultOrException) {
|
||||||
|
invocations ++;
|
||||||
|
Long time = (Long) timer.pop();
|
||||||
|
runtime += System.nanoTime() - time.longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the function or script executes a 'debugger' statement.
|
||||||
|
*
|
||||||
|
* @param cx current Context for this thread
|
||||||
|
*/
|
||||||
|
public void onDebuggerStatement(Context cx) {
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when executed code reaches new line in the source.
|
||||||
|
*/
|
||||||
|
public void onLineChange(Context cx, int lineNumber) {
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String renderLine(int prefixLength) {
|
||||||
|
long millis = Math.round(this.runtime / 1000000);
|
||||||
|
Formatter formatter = new java.util.Formatter();
|
||||||
|
Object[] args = new Object[] {
|
||||||
|
Integer.valueOf((int) millis),
|
||||||
|
Integer.valueOf(Math.round(millis / invocations)),
|
||||||
|
Integer.valueOf(invocations),
|
||||||
|
name.substring(prefixLength)
|
||||||
|
};
|
||||||
|
formatter.format("%1$7d ms %2$5d ms %3$6d %4$s%n", args);
|
||||||
|
return formatter.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue