Skip to content

Commit

Permalink
[GR-11160] Json output for CPU Sampler.
Browse files Browse the repository at this point in the history
PullRequest: graal/1975
  • Loading branch information
Stefan Anzinger committed Aug 24, 2018
2 parents 0813def + fca6c00 commit 9e2a5c5
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 20 deletions.
5 changes: 4 additions & 1 deletion tools/mx.tools/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
"com.oracle.truffle.tools.profiler" : {
"subDir" : "src",
"sourceDirs" : ["src"],
"dependencies" : ["truffle:TRUFFLE_API"],
"dependencies" : [
"truffle:TRUFFLE_API",
"TruffleJSON",
],
"exports" : [
"<package-info>", # exports all packages containing package-info.java
"com.oracle.truffle.tools.profiler.impl to com.oracle.truffle.truffle_api",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.tools.profiler.test;

import java.io.ByteArrayOutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.junit.Assert;
import org.junit.Test;

import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.tools.profiler.CPUSampler;
import com.oracle.truffle.tools.profiler.ProfilerNode;
import com.oracle.truffle.tools.utils.json.JSONArray;
import com.oracle.truffle.tools.utils.json.JSONObject;

public class ProfilerCLITest {

protected Source makeSource(String s) {
return Source.newBuilder(InstrumentationTestLanguage.ID, s, "test").buildLiteral();
}

@Test
public void testSamplerJson() {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ByteArrayOutputStream err = new ByteArrayOutputStream();
Context context = Context.newBuilder().in(System.in).out(out).err(err).option("cpusampler", "true").option("cpusampler.Output", "json").build();
Source defaultSourceForSampling = makeSource("ROOT(" +
"DEFINE(foo,ROOT(SLEEP(1)))," +
"DEFINE(bar,ROOT(BLOCK(STATEMENT,LOOP(10, CALL(foo)))))," +
"DEFINE(baz,ROOT(BLOCK(STATEMENT,LOOP(10, CALL(bar)))))," +
"CALL(baz),CALL(bar)" +
")");
for (int i = 0; i < 10; i++) {
context.eval(defaultSourceForSampling);
}
CPUSampler sampler = CPUSampler.find(context.getEngine());
Map<Thread, Collection<ProfilerNode<CPUSampler.Payload>>> threadToNodesMap = sampler.getThreadToNodesMap();
final long period = sampler.getPeriod();
final long sampleCount = sampler.getSampleCount();
final boolean gatherSelfHitTimes = sampler.isGatherSelfHitTimes();
context.close();
JSONObject output = new JSONObject(out.toString());

Assert.assertEquals("Period wrong in json", period, Long.valueOf((Integer) output.get("period")).longValue());
Assert.assertEquals("Sample count not correct in json", sampleCount, Long.valueOf((Integer) output.get("sample_count")).longValue());
Assert.assertEquals("Gather self times not correct in json", gatherSelfHitTimes, output.get("gathered_hit_times"));
JSONArray profile = (JSONArray) output.get("profile");

// We are single threaded in this test
JSONObject perThreadProfile = (JSONObject) profile.get(0);
JSONArray samples = (JSONArray) perThreadProfile.get("samples");
Collection<ProfilerNode<CPUSampler.Payload>> profilerNodes = threadToNodesMap.get(Thread.currentThread());
deepCompare(samples, profilerNodes);
}

private void deepCompare(JSONArray samples, Collection<ProfilerNode<CPUSampler.Payload>> nodes) {
for (int i = 0; i < samples.length(); i++) {
JSONObject sample = (JSONObject) samples.get(i);
ProfilerNode<CPUSampler.Payload> correspondingNode = findCorrespondingNode(sample, nodes);
if (correspondingNode != null) {
CPUSampler.Payload payload = correspondingNode.getPayload();
final int selfCompiledHitCount = payload.getSelfCompiledHitCount();
Assert.assertEquals("Wrong payload", selfCompiledHitCount, sample.get("self_compiled_hit_count"));
final int selfInterpretedHitCount = payload.getSelfInterpretedHitCount();
Assert.assertEquals("Wrong payload", selfInterpretedHitCount, sample.get("self_interpreted_hit_count"));
final List<Long> selfHitTimes = payload.getSelfHitTimes();
final JSONArray selfHitTimesJson = (JSONArray) sample.get("self_hit_times");
Assert.assertEquals("Wrong payload", selfHitTimes.size(), selfHitTimesJson.length());
for (int j = 0; j < selfHitTimes.size(); j++) {
Assert.assertEquals("Wrong payload", selfHitTimes.get(j).longValue(), selfHitTimesJson.getLong(j));
}
final int compiledHitCount = payload.getCompiledHitCount();
Assert.assertEquals("Wrong payload", compiledHitCount, sample.get("compiled_hit_count"));
final int interpretedHitCount = payload.getInterpretedHitCount();
Assert.assertEquals("Wrong payload", interpretedHitCount, sample.get("interpreted_hit_count"));

deepCompare((JSONArray) sample.get("children"), correspondingNode.getChildren());
} else {
Assert.fail("No corresponding node for one in JSON.");
}
}
}

private static ProfilerNode<CPUSampler.Payload> findCorrespondingNode(JSONObject sample, Collection<ProfilerNode<CPUSampler.Payload>> nodes) {
String root = (String) sample.get("root_name");
JSONObject sourceSection = (JSONObject) sample.get("source_section");
String sourceName = (String) sourceSection.get("source_name");
int startColumn = (int) sourceSection.get("start_column");
int endColumn = (int) sourceSection.get("end_column");
int startLine = (int) sourceSection.get("start_line");
int endLine = (int) sourceSection.get("end_line");
Iterator<ProfilerNode<CPUSampler.Payload>> iterator = nodes.iterator();
while (iterator.hasNext()) {
ProfilerNode<CPUSampler.Payload> next = iterator.next();
SourceSection nextSourceSection = next.getSourceSection();
if (next.getRootName().equals(root) && nextSourceSection.getSource().getName().equals(sourceName) && nextSourceSection.getStartColumn() == startColumn &&
nextSourceSection.getEndColumn() == endColumn && nextSourceSection.getStartLine() == startLine && nextSourceSection.getEndLine() == endLine) {
return next;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ public synchronized void setFilter(SourceSectionFilter filter) {
* @param delaySamplingUntilNonInternalLangInit Enable or disable this option.
* @since 0.31
*/
public void setDelaySamplingUntilNonInternalLangInit(boolean delaySamplingUntilNonInternalLangInit) {
public synchronized void setDelaySamplingUntilNonInternalLangInit(boolean delaySamplingUntilNonInternalLangInit) {
verifyConfigAllowed();
this.delaySamplingUntilNonInternalLangInit = delaySamplingUntilNonInternalLangInit;
}
Expand Down Expand Up @@ -527,7 +527,7 @@ public boolean isGatherSelfHitTimes() {
*
* @since 0.30
*/
public void setGatherSelfHitTimes(boolean gatherSelfHitTimes) {
public synchronized void setGatherSelfHitTimes(boolean gatherSelfHitTimes) {
verifyConfigAllowed();
this.gatherSelfHitTimes = gatherSelfHitTimes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@
*/
package com.oracle.truffle.tools.profiler.impl;

import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.tools.profiler.CPUSampler;
import com.oracle.truffle.tools.profiler.ProfilerNode;
import com.oracle.truffle.tools.utils.json.JSONArray;
import com.oracle.truffle.tools.utils.json.JSONObject;
import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionType;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -36,23 +48,13 @@
import java.util.Set;
import java.util.function.Function;

import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionType;

import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.tools.profiler.CPUSampler;
import com.oracle.truffle.tools.profiler.ProfilerNode;

@Option.Group(CPUSamplerInstrument.ID)
class CPUSamplerCLI extends ProfilerCLI {

enum Output {
HISTOGRAM,
CALLTREE
CALLTREE,
JSON,
}

static final OptionType<Output> CLI_OUTPUT_TYPE = new OptionType<>("Output",
Expand All @@ -63,7 +65,7 @@ public Output apply(String s) {
try {
return Output.valueOf(s.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Output can be: histogram or calltree");
throw new IllegalArgumentException("Output can be: histogram, calltree or json");
}
}
});
Expand Down Expand Up @@ -95,7 +97,7 @@ public CPUSampler.Mode apply(String s) {

@Option(name = "StackLimit", help = "Maximum number of maximum stack elements.", category = OptionCategory.USER) static final OptionKey<Integer> STACK_LIMIT = new OptionKey<>(10000);

@Option(name = "Output", help = "Print a 'histogram' or 'calltree' as output (default:HISTOGRAM).", category = OptionCategory.USER) static final OptionKey<Output> OUTPUT = new OptionKey<>(
@Option(name = "Output", help = "Print a 'histogram', 'calltree' or 'json' as output (default:HISTOGRAM).", category = OptionCategory.USER) static final OptionKey<Output> OUTPUT = new OptionKey<>(
Output.HISTOGRAM, CLI_OUTPUT_TYPE);

@Option(name = "FilterRootName", help = "Wildcard filter for program roots. (eg. Math.*, default:*).", category = OptionCategory.USER) static final OptionKey<Object[]> FILTER_ROOT = new OptionKey<>(
Expand All @@ -111,6 +113,8 @@ public CPUSampler.Mode apply(String s) {

@Option(name = "SummariseThreads", help = "Print output as a summary of all 'per thread' profiles. (default: false)", category = OptionCategory.USER) static final OptionKey<Boolean> SUMMARISE_THREADS = new OptionKey<>(false);

@Option(name = "GatherHitTimes", help = "Save a timestamp for each taken sample (default:false).", category = OptionCategory.USER) static final OptionKey<Boolean> GATHER_HIT_TIMES = new OptionKey<>(false);

static void handleOutput(TruffleInstrument.Env env, CPUSampler sampler) {
PrintStream out = new PrintStream(env.out());
if (sampler.hasStackOverflowed()) {
Expand All @@ -129,7 +133,48 @@ static void handleOutput(TruffleInstrument.Env env, CPUSampler sampler) {
case CALLTREE:
printSamplingCallTree(out, sampler, summariseThreads);
break;
case JSON:
printSamplingJson(out, sampler);
}
}

private static void printSamplingJson(PrintStream out, CPUSampler sampler) {
JSONObject output = new JSONObject();
output.put("tool", CPUSamplerInstrument.ID);
output.put("version", CPUSamplerInstrument.VERSION);
output.put("sample_count", sampler.getSampleCount());
output.put("period", sampler.getPeriod());
output.put("gathered_hit_times", sampler.isGatherSelfHitTimes());
JSONArray profile = new JSONArray();
Map<Thread, Collection<ProfilerNode<CPUSampler.Payload>>> threadToNodesMap = sampler.getThreadToNodesMap();
for (Map.Entry<Thread, Collection<ProfilerNode<CPUSampler.Payload>>> entry : threadToNodesMap.entrySet()) {
JSONObject perThreadProfile = new JSONObject();
perThreadProfile.put("thread", entry.getKey().toString());
perThreadProfile.put("samples", getSamplesRec(entry.getValue()));
profile.put(perThreadProfile);
}
output.put("profile", profile);
out.println(output.toString());
}

private static JSONArray getSamplesRec(Collection<ProfilerNode<CPUSampler.Payload>> nodes) {
JSONArray samples = new JSONArray();
for (ProfilerNode<CPUSampler.Payload> node : nodes) {
JSONObject sample = new JSONObject();
sample.put("root_name", node.getRootName());
sample.put("source_section", sourceSectionToJSON(node.getSourceSection()));
CPUSampler.Payload payload = node.getPayload();
sample.put("hit_count", payload.getHitCount());
sample.put("interpreted_hit_count", payload.getInterpretedHitCount());
sample.put("compiled_hit_count", payload.getCompiledHitCount());
sample.put("self_hit_count", payload.getSelfHitCount());
sample.put("self_interpreted_hit_count", payload.getSelfInterpretedHitCount());
sample.put("self_compiled_hit_count", payload.getSelfCompiledHitCount());
sample.put("self_hit_times", payload.getSelfHitTimes());
sample.put("children", getSamplesRec(node.getChildren()));
samples.put(sample);
}
return samples;
}

private static Map<SourceLocation, List<ProfilerNode<CPUSampler.Payload>>> computeHistogram(Collection<ProfilerNode<CPUSampler.Payload>> profilerNodes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.tools.profiler.CPUSampler;

import static com.oracle.truffle.tools.profiler.impl.CPUSamplerCLI.GATHER_HIT_TIMES;

/**
* The {@linkplain TruffleInstrument instrument} for the CPU sampler.
*
* @since 0.30
*/
@TruffleInstrument.Registration(id = CPUSamplerInstrument.ID, name = "CPU Sampler", version = "0.2", services = {CPUSampler.class})
@TruffleInstrument.Registration(id = CPUSamplerInstrument.ID, name = "CPU Sampler", version = CPUSamplerInstrument.VERSION, services = {CPUSampler.class})
public class CPUSamplerInstrument extends TruffleInstrument {

/**
Expand All @@ -54,6 +56,7 @@ public CPUSamplerInstrument() {
* @since 0.30
*/
public static final String ID = "cpusampler";
static final String VERSION = "0.3.0";
private CPUSampler sampler;
private static ProfilerToolFactory<CPUSampler> factory;

Expand Down Expand Up @@ -108,6 +111,7 @@ protected void onCreate(Env env) {
sampler.setDelay(env.getOptions().get(CPUSamplerCLI.DELAY_PERIOD));
sampler.setStackLimit(env.getOptions().get(CPUSamplerCLI.STACK_LIMIT));
sampler.setFilter(getSourceSectionFilter(env));
sampler.setGatherSelfHitTimes(env.getOptions().get(GATHER_HIT_TIMES));
sampler.setMode(env.getOptions().get(CPUSamplerCLI.MODE));
sampler.setCollecting(true);
}
Expand Down
Loading

0 comments on commit 9e2a5c5

Please sign in to comment.