Skip to content

Commit

Permalink
[GR-52742] Various CPUSampler fixes
Browse files Browse the repository at this point in the history
PullRequest: graal/17442
  • Loading branch information
jchalou committed Apr 12, 2024
2 parents 97ad1b7 + c818b9e commit 0808c62
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 146 deletions.
4 changes: 4 additions & 0 deletions tools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This changelog summarizes major changes between Truffle Tools versions.

## Version 24.1.0
* GR-52742: `CPUSampler` no longer guarantees to keep all contexts on the engine alive. As a result `CPUSampler#getData()` is deprecated and may not return data for all contexts on the engine. The contexts that were already collected by GC won't be in the returned map. `CPUSamplerData#getContext()` is also deprecated and returns null if the context was already collected.
* GR-52742: `CPUSamplerData#getDataList()` was introduced and returns all data collected by the sampler as a list of `CPUSamplerData`. For each context on the engine, including the ones that were already collected, there is a corresponding element in the list. `CPUSamplerData#getContextIndex()` returns the index of the data in the list.

## Version 23.0.0
* GR-41407: Added new option `--dap.SourcePath` to allow to resolve sources with relative paths.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.graalvm.shadowed.org.json.JSONArray;
import org.graalvm.shadowed.org.json.JSONObject;

import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.source.Source;
Expand All @@ -62,8 +64,6 @@
import com.oracle.truffle.tools.profiler.ProfilerNode;
import com.oracle.truffle.tools.profiler.impl.CPUSamplerInstrument;
import com.oracle.truffle.tools.profiler.impl.CPUTracerInstrument;
import org.graalvm.shadowed.org.json.JSONArray;
import org.graalvm.shadowed.org.json.JSONObject;

public final class InspectorProfiler extends ProfilerDomain {

Expand Down Expand Up @@ -127,12 +127,12 @@ public void start() {
@Override
public Params stop() {
long time = System.currentTimeMillis();
Map<TruffleContext, CPUSamplerData> data;
List<CPUSamplerData> data;
long period;
synchronized (sampler) {
sampler.setCollecting(false);
sampler.setGatherSelfHitTimes(oldGatherSelfHitTimes);
data = sampler.getData();
data = sampler.getDataList();
sampler.clearData();
period = sampler.getPeriod();
}
Expand All @@ -141,18 +141,18 @@ public Params stop() {
return profile;
}

private static Collection<ProfilerNode<CPUSampler.Payload>> getRootNodes(Map<TruffleContext, CPUSamplerData> data) {
private static Collection<ProfilerNode<CPUSampler.Payload>> getRootNodes(List<CPUSamplerData> data) {
Collection<ProfilerNode<CPUSampler.Payload>> retVal = new ArrayList<>();
for (CPUSamplerData samplerData : data.values()) {
for (CPUSamplerData samplerData : data) {
for (Collection<ProfilerNode<CPUSampler.Payload>> profilerNodes : samplerData.getThreadData().values()) {
retVal.addAll(profilerNodes);
}
}
return retVal;
}

private static long getSampleCount(Map<TruffleContext, CPUSamplerData> data) {
return data.values().stream().map(CPUSamplerData::getSamples).reduce(0L, Long::sum);
private static long getSampleCount(List<CPUSamplerData> data) {
return data.stream().map(CPUSamplerData::getSamples).reduce(0L, Long::sum);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright (c) 2024, 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.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyExecutable;
import org.junit.Assert;
import org.junit.Test;

import com.oracle.truffle.api.test.GCUtils;
import com.oracle.truffle.tools.profiler.CPUSampler;
import com.oracle.truffle.tools.profiler.StackTraceEntry;

public class CPUSamplerMultiContextTest {
public static final String FIB = """
function fib(n) {
if (n < 3) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
function main() {
return fib;
}
""";

public static final String FIB_15_PLUS = """
function fib15plus(n, remainder) {
if (n < 15) {
return remainder(n);
} else {
return fib15plus(n - 1, remainder) + fib15plus(n - 2, remainder);
}
}
function main() {
return fib15plus;
}
""";

@Test
public void testSamplerDoesNotKeepContexts() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (Engine engine = Engine.newBuilder().out(out).option("cpusampler", "histogram").build()) {
List<WeakReference<Context>> contextReferences = new ArrayList<>();
for (int i = 0; i < 27; i++) {
try (Context context = Context.newBuilder().engine(engine).build()) {
contextReferences.add(new WeakReference<>(context));
Source src = Source.newBuilder("sl", FIB, "fib.sl").build();
Value fib = context.eval(src);
fib.execute(29);
}
}
GCUtils.assertGc("CPUSampler prevented collecting contexts", contextReferences);
}
Pattern pattern = Pattern.compile("Sampling Histogram. Recorded (\\d+) samples");
Matcher matcher = pattern.matcher(out.toString());
int histogramCount = 0;
while (matcher.find()) {
histogramCount++;
Assert.assertTrue("Histogram no. " + histogramCount + " didn't contain any samples.", Integer.parseInt(matcher.group(1)) > 0);
}
Assert.assertEquals(27, histogramCount);
}

static class RootCounter {
int fibCount;
int fib15plusCount;
}

@Test
public void testMultiThreadedAndMultiContextPerThread() throws InterruptedException, ExecutionException, IOException {
try (Engine engine = Engine.create(); ExecutorService executorService = Executors.newFixedThreadPool(10)) {
AtomicBoolean runFlag = new AtomicBoolean(true);
CPUSampler sampler = CPUSampler.find(engine);
int nThreads = 5;
int nSamples = 5;
Map<Thread, RootCounter> threads = new ConcurrentHashMap<>();
List<Future<?>> futures = new ArrayList<>();
CountDownLatch fibLatch = new CountDownLatch(nThreads);
Source src1 = Source.newBuilder("sl", FIB_15_PLUS, "fib15plus.sl").build();
Source src2 = Source.newBuilder("sl", FIB, "fib.sl").build();
for (int i = 0; i < nThreads; i++) {
futures.add(executorService.submit(() -> {
threads.putIfAbsent(Thread.currentThread(), new RootCounter());
AtomicBoolean countedDown = new AtomicBoolean();
while (runFlag.get()) {
try (Context context1 = Context.newBuilder().engine(engine).build(); Context context2 = Context.newBuilder().engine(engine).build()) {
Value fib15plus = context1.eval(src1);
Value fib = context2.eval(src2);
ProxyExecutable proxyExecutable = (n) -> {
if (countedDown.compareAndSet(false, true)) {
fibLatch.countDown();
}
return fib.execute((Object[]) n);
};
Assert.assertEquals(514229, fib15plus.execute(29, proxyExecutable).asInt());
}
}
}));
}
fibLatch.await();
for (int i = 0; i < nSamples; i++) {
Map<Thread, List<StackTraceEntry>> sample = sampler.takeSample();
for (Map.Entry<Thread, List<StackTraceEntry>> sampleEntry : sample.entrySet()) {
RootCounter rootCounter = threads.get(sampleEntry.getKey());
for (StackTraceEntry stackTraceEntry : sampleEntry.getValue()) {
if ("fib".equals(stackTraceEntry.getRootName())) {
rootCounter.fibCount++;
}
if ("fib15plus".equals(stackTraceEntry.getRootName())) {
rootCounter.fib15plusCount++;
}
}
}
}
runFlag.set(false);
for (Future<?> future : futures) {
future.get();
}
for (Map.Entry<Thread, RootCounter> threadEntry : threads.entrySet()) {
Assert.assertTrue(nSamples + " samples should contain at least 1 occurrence of the fib root for each thread, but one thread contained only " + threadEntry.getValue().fibCount,
threadEntry.getValue().fibCount > 1);
Assert.assertTrue(nSamples + " samples should contain at least 10 occurrences of the fib15plus root, but one thread contained only " + threadEntry.getValue().fib15plusCount,
threadEntry.getValue().fib15plusCount > 10);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ protected void initializeContext(LanguageContext c) throws Exception {
context.initialize(ProxyLanguage.ID);
sampler.setCollecting(false);

Map<TruffleContext, CPUSamplerData> data = sampler.getData();
List<CPUSamplerData> data = sampler.getDataList();
assertEquals(1, data.size());

assertEquals(0, searchInitializeContext(data).size());
Expand All @@ -113,15 +113,15 @@ protected void initializeContext(LanguageContext c) throws Exception {
context.initialize(ProxyLanguage.ID);
sampler.setCollecting(false);

Map<TruffleContext, CPUSamplerData> data = sampler.getData();
List<CPUSamplerData> data = sampler.getDataList();
assertEquals(1, data.size());

assertEquals(0, searchInitializeContext(data).size());
}

private static List<ProfilerNode<Payload>> searchInitializeContext(Map<TruffleContext, CPUSamplerData> data) {
private static List<ProfilerNode<Payload>> searchInitializeContext(List<CPUSamplerData> data) {
List<ProfilerNode<Payload>> found = new ArrayList<>();
for (CPUSamplerData d : data.values()) {
for (CPUSamplerData d : data) {
Map<Thread, Collection<ProfilerNode<Payload>>> threadData = d.getThreadData();
assertEquals(threadData.toString(), 1, threadData.size());

Expand All @@ -146,17 +146,17 @@ private static void searchNodes(List<ProfilerNode<CPUSampler.Payload>> results,
public void testCollectingAndHasData() {

sampler.setCollecting(true);
Map<TruffleContext, CPUSamplerData> before = sampler.getData();
Assert.assertEquals(0, before.values().iterator().next().getSamples());
List<CPUSamplerData> before = sampler.getDataList();
Assert.assertEquals(0, before.iterator().next().getSamples());
Assert.assertTrue(sampler.isCollecting());
Assert.assertFalse(sampler.hasData());

for (int i = 0; i < executionCount; i++) {
eval(defaultSourceForSampling);
}

Map<TruffleContext, CPUSamplerData> after = sampler.getData();
Assert.assertNotEquals(0, after.values().iterator().next().getSamples());
List<CPUSamplerData> after = sampler.getDataList();
Assert.assertNotEquals(0, after.iterator().next().getSamples());
Assert.assertTrue(sampler.isCollecting());
Assert.assertTrue(sampler.hasData());

Expand All @@ -166,9 +166,9 @@ public void testCollectingAndHasData() {
Assert.assertTrue(sampler.hasData());

sampler.clearData();
Map<TruffleContext, CPUSamplerData> cleared = sampler.getData();
List<CPUSamplerData> cleared = sampler.getDataList();
Assert.assertFalse(sampler.isCollecting());
Assert.assertEquals(0, cleared.values().iterator().next().getSamples());
Assert.assertEquals(0, cleared.iterator().next().getSamples());

Assert.assertFalse(sampler.hasData());
}
Expand Down Expand Up @@ -221,9 +221,9 @@ public void testCorrectRootStructure() {
}

private Collection<ProfilerNode<Payload>> getProfilerNodes() {
Map<TruffleContext, CPUSamplerData> data = sampler.getData();
List<CPUSamplerData> data = sampler.getDataList();
Assert.assertEquals(1, data.size());
Map<Thread, Collection<ProfilerNode<Payload>>> threadData = data.values().iterator().next().getThreadData();
Map<Thread, Collection<ProfilerNode<Payload>>> threadData = data.iterator().next().getThreadData();
Assert.assertEquals(1, threadData.size());
Collection<ProfilerNode<Payload>> children = threadData.values().iterator().next();
return children;
Expand Down Expand Up @@ -334,6 +334,7 @@ public void testClosedConfig() {
}

@Test
@SuppressWarnings("deprecation")
public void testTiers() {
Assume.assumeFalse(Truffle.getRuntime().getClass().toString().contains("Default"));
Context.Builder builder = Context.newBuilder().option("engine.FirstTierCompilationThreshold", Integer.toString(FIRST_TIER_THRESHOLD)).option("engine.LastTierCompilationThreshold",
Expand All @@ -345,6 +346,7 @@ public void testTiers() {
for (int i = 0; i < 3 * FIRST_TIER_THRESHOLD; i++) {
c.eval(defaultSourceForSampling);
}
// Intentionally kept one usage of the deprecated API
data = cpuSampler.getData();
}
CPUSamplerData samplerData = data.values().iterator().next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@
import java.util.List;
import java.util.Map;

import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.tools.profiler.CPUSamplerData;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.shadowed.org.json.JSONArray;
Expand All @@ -43,9 +40,11 @@
import org.junit.Ignore;
import org.junit.Test;

import com.oracle.truffle.api.Truffle;
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.CPUSamplerData;
import com.oracle.truffle.tools.profiler.ProfilerNode;

public class ProfilerCLITest {
Expand Down Expand Up @@ -364,8 +363,8 @@ public void testSamplerJson() {
final long sampleCount;
final boolean gatherSelfHitTimes;
synchronized (sampler) {
Map<TruffleContext, CPUSamplerData> data = sampler.getData();
CPUSamplerData samplerData = data.values().iterator().next();
List<CPUSamplerData> data = sampler.getDataList();
CPUSamplerData samplerData = data.iterator().next();
threadToNodesMap = samplerData.getThreadData();
period = sampler.getPeriod();
sampleCount = samplerData.getSamples();
Expand Down

0 comments on commit 0808c62

Please sign in to comment.