Permalink
Browse files

Added a JSON profile printer and --profile.json to enable it

  • Loading branch information...
1 parent c4a1182 commit 645a1031df9f853955a0eba59d0de57fb0182031 @iconara committed Aug 22, 2012
@@ -7,6 +7,7 @@ module Profiler
java_import org.jruby.runtime.profile.FlatProfilePrinter
java_import org.jruby.runtime.profile.GraphProfilePrinter
java_import org.jruby.runtime.profile.HtmlProfilePrinter
+ java_import org.jruby.runtime.profile.JsonProfilePrinter
def self.profile(&block)
start
@@ -0,0 +1,71 @@
+require 'spec/profiler/profiler_spec_helpers'
+
+describe JRuby::Profiler, "::JsonProfilePrinter" do
+ include JRuby::Profiler::SpecHelpers
+
+ context 'when printing an empty profile' do
+ before do
+ @profile_data = JRuby::Profiler.profile {}
+ end
+
+ it 'contains only the top invocation' do
+ json_output['methods'].should have(1).items
+ json_output['methods'].first['name'].should == '(top)'
+ end
+
+ it 'contains the total duration' do
+ json_output['total_time'].should == 0.0
+ end
+ end
+
+ context 'when printing a profile' do
+ before do
+ obj = ProfilerTest.new
+ @profile_data = JRuby::Profiler.profile do
+ obj.wait(0.01)
+ obj.test_instance_method
+ end
+ end
+
+ it 'outputs the total duration' do
+ json_output['total_time'].should > 0.0
+ end
+
+ context 'outputs method data which' do
+ let :method_invocation do
+ json_output['methods'].find { |m| m['name'] == 'ProfilerTest#test_instance_method' }
+ end
+
+ let :top_invocation do
+ json_output['methods'].find { |m| m['name'] == '(top)' }
+ end
+
+ it 'contains the number of calls, total, self and child time' do
+ method_invocation.should include('total_calls' => 1, 'total_time' => anything, 'self_time' => anything, 'child_time' => anything)
+ end
+
+ it 'contains data on the calls from parents, including calls, total, self and child time' do
+ method_invocation['parents'].should have(1).item
+ call_data = method_invocation['parents'][top_invocation['id']]
+ call_data.should include('calls' => 1, 'total_time' => anything, 'self_time' => anything, 'child_time' => anything)
+ end
+
+ it 'contains data on the calls to children' do
+ method1_invocation = json_output['methods'].find { |m| m['name'] == 'ProfilerTest#wait' }
+ method2_invocation = json_output['methods'].find { |m| m['name'] == 'ProfilerTest#test_instance_method' }
+ call1_data = top_invocation['children'][method1_invocation['id']]
+ call1_data.should include('calls' => 1, 'total_time' => anything, 'self_time' => anything, 'child_time' => anything)
+ call2_data = top_invocation['children'][method2_invocation['id']]
+ call2_data.should include('calls' => 1, 'total_time' => anything, 'self_time' => anything, 'child_time' => anything)
+ end
+ end
+ end
+end
+
+
+
+
+
+
+
+
@@ -1,5 +1,5 @@
-
require 'jruby/profiler'
+require 'json'
module JRuby::Profiler::SpecHelpers
@@ -30,6 +30,11 @@ def graph_output
data_output JRuby::Profiler::GraphProfilePrinter
end
+ def json_output
+ raw_output = data_output(JRuby::Profiler::JsonProfilePrinter)
+ JSON.parse(raw_output)
+ end
+
def flat_output
data_output JRuby::Profiler::FlatProfilePrinter
end
@@ -1244,7 +1244,7 @@ public LoadService create(Ruby runtime) {
}
public enum ProfilingMode {
- OFF, API, FLAT, GRAPH, HTML
+ OFF, API, FLAT, GRAPH, HTML, JSON
}
public enum CompileMode {
@@ -0,0 +1,152 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Common Public
+ * License Version 1.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the CPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the CPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.jruby.runtime.profile;
+
+
+import java.io.PrintStream;
+import java.util.Iterator;
+
+import org.jruby.util.collections.IntHashMap;
+
+
+public class JsonProfilePrinter extends ProfilePrinter {
+
+ public JsonProfilePrinter(ProfileData profileData) {
+ super(profileData);
+ }
+
+ JsonProfilePrinter(ProfileData profileData, Invocation topInvocation) {
+ super(profileData, topInvocation);
+ }
+
+ public void printProfile(PrintStream out) {
+ Invocation topInvocation = getTopInvocation();
+ IntHashMap<MethodData> methods = methodData(topInvocation);
+
+ out.printf("{\n\t\"total_time\":%d,\n\t\"methods\":[\n", topInvocation.getDuration());
+
+ Iterator<MethodData> i = methods.values().iterator();
+ while (i.hasNext()) {
+ MethodData method = i.next();
+ out.print("\t\t");
+ out.print(methodToJson(method));
+ if (i.hasNext()) {
+ out.print(",");
+ }
+ out.println();
+ }
+
+ out.print("\n\t]\n}\n");
+ }
+
+ private String methodToJson(MethodData method) {
+ return toJsonObject(
+ "id", quote(method.serialNumber),
+ "name", quote(methodName(method.serialNumber)),
+ "total_calls", String.valueOf(method.totalCalls()),
+ "total_time", String.valueOf(method.totalTime()),
+ "self_time", String.valueOf(method.selfTime()),
+ "child_time", String.valueOf(method.childTime()),
+ "parents", parentCallsToJson(method),
+ "children", childCallsToJson(method)
+ );
+ }
+
+ private String parentCallsToJson(MethodData method) {
+ if (method.serialNumber == 0) {
+ return toJsonObject(new String[] { });
+ } else {
+ int[] parentSerials = method.parents();
+ String[] parentCalls = new String[parentSerials.length * 2];
+ for (int i = 0; i < parentSerials.length; i++) {
+ parentCalls[i * 2] = String.valueOf(parentSerials[i]);
+ parentCalls[i * 2 + 1] = callToJson(
+ method.invocationsFromParent(parentSerials[i]).totalCalls(),
+ method.rootInvocationsFromParent(parentSerials[i])
+ );
+ }
+ return toJsonObject(parentCalls);
+ }
+ }
+
+ private String childCallsToJson(MethodData method) {
+ int[] childSerials = method.children();
+ String[] childCalls = new String[childSerials.length * 2];
+ for (int i = 0; i < childSerials.length; i++) {
+ childCalls[i * 2] = String.valueOf(childSerials[i]);
+ childCalls[i * 2 + 1] = callToJson(
+ method.invocationsOfChild(childSerials[i]).totalCalls(),
+ method.rootInvocationsOfChild(childSerials[i])
+ );
+ }
+ return toJsonObject(childCalls);
+ }
+
+ private String callToJson(int calls, InvocationSet invocations) {
+ return toJsonObject(
+ "calls", String.valueOf(calls),
+ "total_time", String.valueOf(invocations.totalTime()),
+ "self_time", String.valueOf(invocations.selfTime()),
+ "child_time", String.valueOf(invocations.childTime())
+ );
+ }
+
+ private String quote(String str) {
+ return String.format("\"%s\"", str);
+ }
+
+ private String quote(long num) {
+ return String.format("\"%d\"", num);
+ }
+
+ private String toJsonArray(String... values) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("[");
+ for (String v : values) {
+ buffer.append(v);
+ if (v != values[values.length - 1]) {
+ buffer.append(",");
+ }
+ }
+ buffer.append("]");
+ return buffer.toString();
+ }
+
+ private String toJsonObject(String... keysAndValues) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("{");
+ for (int i = 0; i < keysAndValues.length; i += 2) {
+ buffer.append(quote(keysAndValues[i]));
+ buffer.append(":");
+ buffer.append(keysAndValues[i + 1]);
+ if (i < keysAndValues.length - 3) {
+ buffer.append(",");
+ }
+ }
+ buffer.append("}");
+ return buffer.toString();
+ }
+}
@@ -65,6 +65,9 @@ else if (mode == ProfilingMode.GRAPH) {
else if (mode == ProfilingMode.HTML) {
printer = new HtmlProfilePrinter(profileData, topInvocation);
}
+ else if (mode == ProfilingMode.JSON) {
+ printer = new JsonProfilePrinter(profileData, topInvocation);
+ }
else {
printer = null;
}
@@ -411,6 +411,9 @@ private void processArgument() {
} else if (argument.equals("--profile.html")) {
config.setProfilingMode(RubyInstanceConfig.ProfilingMode.HTML);
break FOR;
+ } else if (argument.equals("--profile.json")) {
+ config.setProfilingMode(RubyInstanceConfig.ProfilingMode.JSON);
+ break FOR;
} else if (argument.equals("--1.9")) {
config.setCompatVersion(CompatVersion.RUBY1_9);
break FOR;

0 comments on commit 645a103

Please sign in to comment.