Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Profile reports can now be written to a file

Support for writing profile reports to files, notably support for writing profiles from multiple threads to the same JSON or HTML file without syntax errors.

There's a new abstraction that handles profile printing: ProfileOutput. Together with new methods on ProfilePrinter, printHeader, printFooter and a new parameter to printProfile (first), it can orchestrate the the profile printers so that they print the right things at the right time.
  • Loading branch information...
commit fa914f67cff6659a26d621ade9bf0801015467c3 1 parent eeef5f1
Theo Hultberg authored
3  lib/ruby/shared/jruby/profiler/shutdown_hook.rb
View
@@ -3,9 +3,8 @@
trap 'INT' do
runtime = JRuby.runtime
runtime.thread_service.ruby_thread_map.each do |t, rubythread|
- java.lang.System.err.println "\n#{t} profile results:"
context = JRuby.reference(rubythread).context
- runtime.printProfileData(context.profile_data, java.lang.System.err)
+ runtime.printProfileData(context.profile_data)
end
exit
end
22 src/org/jruby/Ruby.java
View
@@ -115,6 +115,7 @@
import org.jruby.runtime.profile.ProfileData;
import org.jruby.runtime.profile.ProfilePrinter;
import org.jruby.runtime.profile.ProfiledMethod;
+import org.jruby.runtime.profile.ProfileOutput;
import org.jruby.runtime.scope.ManyVarsDynamicScope;
import org.jruby.threading.DaemonThreadFactory;
import org.jruby.util.ByteList;
@@ -2938,9 +2939,8 @@ public void tearDown(boolean systemExit) {
if (config.isProfilingEntireRun()) {
// not using logging because it's formatted
- System.err.println("\nmain thread profile results:");
ProfileData profileData = threadService.getMainThread().getContext().getProfileData();
- printProfileData(profileData, System.err);
+ printProfileData(profileData);
}
if (systemExit && status != 0) {
@@ -2953,13 +2953,25 @@ public void tearDown(boolean systemExit) {
* @param profileData
* @param out
* @see RubyInstanceConfig#getProfilingMode()
+ * @deprecated use printProfileData(ProfileData) or printProfileData(ProfileData,ProfileOutput)
*/
public void printProfileData(ProfileData profileData, PrintStream out) {
+ printProfileData(profileData, new ProfileOutput(out));
+ }
+
+ public void printProfileData(ProfileData profileData) {
+ printProfileData(profileData, config.getProfileOutput());
+ }
+
+ public void printProfileData(ProfileData profileData, ProfileOutput output) {
ProfilePrinter profilePrinter = ProfilePrinter.newPrinter(config.getProfilingMode(), profileData);
- if (profilePrinter != null) profilePrinter.printProfile(out);
- else out.println("\nno printer for profile mode: " + config.getProfilingMode() + " !");
+ if (profilePrinter != null) {
+ output.printProfile(profilePrinter);
+ } else {
+ out.println("\nno printer for profile mode: " + config.getProfilingMode() + " !");
+ }
}
-
+
// new factory methods ------------------------------------------------------------------------
public RubyArray newEmptyArray() {
10 src/org/jruby/RubyInstanceConfig.java
View
@@ -62,6 +62,7 @@
import org.jruby.runtime.backtrace.TraceType;
import org.jruby.runtime.load.LoadService;
import org.jruby.runtime.load.LoadService19;
+import org.jruby.runtime.profile.ProfileOutput;
import org.jruby.util.ClassCache;
import org.jruby.util.InputStreamMarkCursor;
import org.jruby.util.JRubyFile;
@@ -1000,6 +1001,14 @@ public ProfilingMode getProfilingMode() {
return profilingMode;
}
+ public void setProfileOutput(ProfileOutput output) {
+ this.profileOutput = output;
+ }
+
+ public ProfileOutput getProfileOutput() {
+ return profileOutput;
+ }
+
public boolean hasShebangLine() {
return hasShebangLine;
}
@@ -1208,6 +1217,7 @@ public int getProfileMaxMethods() {
private String externalEncoding = null;
private ProfilingMode profilingMode = ProfilingMode.OFF;
+ private ProfileOutput profileOutput = new ProfileOutput(System.err);
private ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
private ClassLoader loader = contextLoader == null ? RubyInstanceConfig.class.getClassLoader() : contextLoader;
2  src/org/jruby/internal/runtime/RubyRunnable.java
View
@@ -124,7 +124,7 @@ public void run() {
// dump profile, if any
if (runtime.getInstanceConfig().isProfilingEntireRun()) {
- runtime.printProfileData(context.getProfileData(), System.err);
+ runtime.printProfileData(context.getProfileData());
}
}
} catch (ThreadKill tk) {
6 src/org/jruby/runtime/profile/FlatProfilePrinter.java
View
@@ -46,8 +46,12 @@ public FlatProfilePrinter(ProfileData profileData) {
FlatProfilePrinter(ProfileData profileData, Invocation topInvocation) {
super(profileData, topInvocation);
}
+
+ public void printHeader(PrintStream out) {
+ out.printf("\n%s profile results:\n", getThreadName());
+ }
- public void printProfile(PrintStream out) {
+ public void printProfile(PrintStream out, boolean first) {
final Invocation topInvocation = getTopInvocation();
out.printf("Total time: %s\n\n", nanoString(topInvocation.getDuration()));
13 src/org/jruby/runtime/profile/GraphProfilePrinter.java
View
@@ -40,10 +40,19 @@ public GraphProfilePrinter(ProfileData profileData) {
GraphProfilePrinter(ProfileData profileData, Invocation topInvocation) {
super(profileData, topInvocation);
}
+
+ public void printHeader(PrintStream out) {
+ out.printf("\n%s profile results:\n", getThreadName());
+ }
- public void printProfile(PrintStream out) {
+ public void printProfile(PrintStream out, boolean first) {
final Invocation topInvocation = getTopInvocation();
- out.printf("\nTotal time: %s\n\n", nanoString(topInvocation.getDuration()));
+
+ if (!first) {
+ out.println();
+ }
+
+ out.printf("Total time: %s\n\n", nanoString(topInvocation.getDuration()));
out.println(" %total %self total self children calls name");
20 src/org/jruby/runtime/profile/HtmlProfilePrinter.java
View
@@ -43,12 +43,21 @@ public HtmlProfilePrinter(ProfileData profileData) {
super(profileData, topInvocation);
}
- @Override
- public void printProfile(PrintStream out) {
- final Invocation topInvocation = getTopInvocation();
+ public void printHeader(PrintStream out) {
out.println(head);
out.println("<body>");
- out.println("<h1>Profile Report</h1>");
+ }
+
+ public void printFooter(PrintStream out) {
+ out.println("</body>");
+ out.println("</html>");
+ }
+
+ @Override
+ public void printProfile(PrintStream out, boolean first) {
+ final Invocation topInvocation = getTopInvocation();
+
+ out.printf("<h1>Profile Report: %s</h1>\n", getThreadName());
out.println("<h3>Total time: " + nanoString(topInvocation.getDuration()) + "</h3>");
out.println("<table>\n" +
@@ -131,9 +140,6 @@ public int compare(Integer child1, Integer child2) {
}
}
out.println("</table>");
-
- out.println("</body>");
- out.println("</html>");
}
private void printInvocationOfChild(PrintStream out, IntHashMap<MethodData> methods, MethodData data, int childSerial, String callerName, InvocationSet invs) {
30 src/org/jruby/runtime/profile/JsonProfilePrinter.java
View
@@ -44,26 +44,32 @@ public JsonProfilePrinter(ProfileData profileData) {
super(profileData, topInvocation);
}
- public void printProfile(PrintStream out) {
+ public void printHeader(PrintStream out) {
+ out.println("{\n\thread_profiles:[");
+ }
+
+ public void printFooter(PrintStream out) {
+ out.println("\n\t]\n}");
+ }
+
+ public void printProfile(PrintStream out, boolean first) {
Invocation topInvocation = getTopInvocation();
IntHashMap<MethodData> methods = methodData(topInvocation);
- String threadName = null;
+ String threadName = getThreadName();
- if (getProfileData().getThreadContext().getThread() == null) {
- threadName = Thread.currentThread().getName();
- } else {
- threadName = getProfileData().getThreadContext().getThread().getNativeThread().getName();
+ if (!first) {
+ out.println(",");
}
- out.println("{");
- out.printf("\t\"total_time\":%s,\n", nanosToSecondsString(topInvocation.getDuration()));
- out.printf("\t\"thread_name\":\"%s\",\n", threadName);
- out.println("\t\"methods\":[");
+ out.println("\t\t{");
+ out.printf("\t\t\t\"total_time\":%s,\n", nanosToSecondsString(topInvocation.getDuration()));
+ out.printf("\t\t\t\"thread_name\":\"%s\",\n", threadName);
+ out.println("\t\t\t\"methods\":[");
Iterator<MethodData> i = methods.values().iterator();
while (i.hasNext()) {
MethodData method = i.next();
- out.print("\t\t");
+ out.print("\t\t\t\t");
out.print(methodToJson(method));
if (i.hasNext()) {
out.print(",");
@@ -71,7 +77,7 @@ public void printProfile(PrintStream out) {
out.println();
}
- out.print("\t]\n}\n");
+ out.print("\t\t\t]\n\t\t}");
}
private String methodToJson(MethodData method) {
43 src/org/jruby/runtime/profile/ProfileOutput.java
View
@@ -0,0 +1,43 @@
+package org.jruby.runtime.profile;
+
+
+import java.io.PrintStream;
+import java.io.FileOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+
+
+public class ProfileOutput {
+ private final PrintStream stream;
+
+ private boolean headerPrinted = false;
+
+
+ public ProfileOutput(PrintStream out) {
+ this.stream = out;
+ }
+
+ public ProfileOutput(File out) throws FileNotFoundException {
+ this.stream = new PrintStream(new FileOutputStream(out));
+ }
+
+ public void printProfile(ProfilePrinter printer) {
+ if (headerPrinted) {
+ printer.printProfile(stream, false);
+ } else {
+ printer.printHeader(stream);
+ printer.printProfile(stream, true);
+ headerPrinted = true;
+ footerAndCleanupOnShutdown(printer);
+ }
+ }
+
+ private void footerAndCleanupOnShutdown(final ProfilePrinter printer) {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ printer.printFooter(stream);
+ stream.close();
+ }
+ });
+ }
+}
19 src/org/jruby/runtime/profile/ProfilePrinter.java
View
@@ -47,7 +47,7 @@
* @param mode the profiling mode
* @param profileData
* @param runtime
- * @see Ruby#printProfileData(org.jruby.runtime.profile.ProfileData, java.io.PrintStream)
+ * @see Ruby#printProfileData(org.jruby.runtime.profile.ProfileData)
*/
public static ProfilePrinter newPrinter(ProfilingMode mode, ProfileData profileData) {
return newPrinter(mode, profileData, null);
@@ -93,8 +93,15 @@ public ProfileData getProfileData() {
protected Invocation getTopInvocation() {
return topInvocation;
}
+
+ public void printHeader(PrintStream out) { }
+ public void printFooter(PrintStream out) { }
- public abstract void printProfile(PrintStream out) ;
+ public void printProfile(PrintStream out) {
+ printProfile(out, false);
+ }
+
+ public abstract void printProfile(PrintStream out, boolean first) ;
public void printProfile(RubyIO out) {
printProfile(new PrintStream(out.getOutStream()));
@@ -114,6 +121,14 @@ boolean isThisProfilerInvocation(int serial) {
( name.hashCode() == stop.hashCode() && name.equals(stop) );
}
+ public String getThreadName() {
+ if (getProfileData().getThreadContext().getThread() == null) {
+ return Thread.currentThread().getName();
+ } else {
+ return getProfileData().getThreadContext().getThread().getNativeThread().getName();
+ }
+ }
+
public String methodName(int serial) {
return profileData.methodName(serial);
}
39 src/org/jruby/util/cli/ArgumentProcessor.java
View
@@ -32,12 +32,14 @@
import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.exceptions.MainExitException;
+import org.jruby.runtime.profile.ProfileOutput;
import org.jruby.util.JRubyFile;
import org.jruby.util.KCode;
import org.jruby.util.SafePropertyAccessor;
import java.io.File;
import java.io.IOException;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -399,20 +401,29 @@ private void processArgument() {
} else if (argument.equals("--fast")) {
config.setCompileMode(RubyInstanceConfig.CompileMode.FORCE);
break FOR;
- } else if (argument.equals("--profile.api")) {
- config.setProfilingMode(RubyInstanceConfig.ProfilingMode.API);
- break FOR;
- } else if (argument.equals("--profile") || argument.equals("--profile.flat")) {
- config.setProfilingMode(RubyInstanceConfig.ProfilingMode.FLAT);
- break FOR;
- } else if (argument.equals("--profile.graph")) {
- config.setProfilingMode(RubyInstanceConfig.ProfilingMode.GRAPH);
- break FOR;
- } else if (argument.equals("--profile.html")) {
- config.setProfilingMode(RubyInstanceConfig.ProfilingMode.HTML);
- break FOR;
- } else if (argument.equals("--profile.json")) {
- config.setProfilingMode(RubyInstanceConfig.ProfilingMode.JSON);
+ } else if (argument.startsWith("--profile")) {
+ int dotIndex = argument.indexOf(".");
+ if (dotIndex == -1) {
+ config.setProfilingMode(RubyInstanceConfig.ProfilingMode.FLAT);
+ } else {
+ String profilingMode = argument.substring(dotIndex + 1, argument.length());
+ try {
+ config.setProfilingMode(RubyInstanceConfig.ProfilingMode.valueOf(profilingMode.toUpperCase()));
+ } catch (IllegalArgumentException e) {
+ throw new MainExitException(1, String.format("jruby: unknown profiler mode \"%s\"", profilingMode));
+ }
+ }
+ if (config.getProfilingMode() != RubyInstanceConfig.ProfilingMode.API) {
+ characterIndex = argument.length();
+ if (argumentIndex < arguments.size() && !arguments.get(argumentIndex + 1).originalValue.startsWith("-")) {
+ String outputFile = grabValue(""); // no error message, since we already checked that the arg exists
+ try {
+ config.setProfileOutput(new ProfileOutput(new File(outputFile)));
+ } catch (FileNotFoundException e) {
+ throw new MainExitException(1, String.format("jruby: %s", e.getMessage()));
+ }
+ }
+ }
break FOR;
} else if (argument.equals("--1.9")) {
config.setCompatVersion(CompatVersion.RUBY1_9);
Please sign in to comment.
Something went wrong with that request. Please try again.