Skip to content

Commit a5083fd

Browse files
jchalouzakkak
authored andcommitted
Use all active contexts for CPUSampler#takeSample. Don't store hard references to contexts on the engine.
(cherry picked from commit 6c20487) 6c20487 appears to be a squash commit of the changes in oracle/graal#8759 which include the following commits: * e3dd860 * e77be20 * 85303f9 * 4f5de74 * dd810f0 * ef4d967 * 5d86317 * 8efd0f3 * 7e0bb41 * cccfa5e * c818b9e
1 parent 69f685d commit a5083fd

File tree

13 files changed

+418
-146
lines changed

13 files changed

+418
-146
lines changed

tools/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This changelog summarizes major changes between Truffle Tools versions.
44

5+
## Version 23.1.4
6+
* GR-53413: `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.
7+
* GR-53413: `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.
8+
59
## Version 23.0.0
610
* GR-41407: Added new option `--dap.SourcePath` to allow to resolve sources with relative paths.
711

tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorProfiler.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
import java.util.Map;
3636
import java.util.concurrent.TimeUnit;
3737

38+
import org.graalvm.shadowed.org.json.JSONArray;
39+
import org.graalvm.shadowed.org.json.JSONObject;
40+
3841
import com.oracle.truffle.api.InstrumentInfo;
39-
import com.oracle.truffle.api.TruffleContext;
4042
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
4143
import com.oracle.truffle.api.instrumentation.StandardTags;
4244
import com.oracle.truffle.api.source.Source;
@@ -62,8 +64,6 @@
6264
import com.oracle.truffle.tools.profiler.ProfilerNode;
6365
import com.oracle.truffle.tools.profiler.impl.CPUSamplerInstrument;
6466
import com.oracle.truffle.tools.profiler.impl.CPUTracerInstrument;
65-
import org.graalvm.shadowed.org.json.JSONArray;
66-
import org.graalvm.shadowed.org.json.JSONObject;
6767

6868
public final class InspectorProfiler extends ProfilerDomain {
6969

@@ -127,12 +127,12 @@ public void start() {
127127
@Override
128128
public Params stop() {
129129
long time = System.currentTimeMillis();
130-
Map<TruffleContext, CPUSamplerData> data;
130+
List<CPUSamplerData> data;
131131
long period;
132132
synchronized (sampler) {
133133
sampler.setCollecting(false);
134134
sampler.setGatherSelfHitTimes(oldGatherSelfHitTimes);
135-
data = sampler.getData();
135+
data = sampler.getDataList();
136136
sampler.clearData();
137137
period = sampler.getPeriod();
138138
}
@@ -141,18 +141,18 @@ public Params stop() {
141141
return profile;
142142
}
143143

144-
private static Collection<ProfilerNode<CPUSampler.Payload>> getRootNodes(Map<TruffleContext, CPUSamplerData> data) {
144+
private static Collection<ProfilerNode<CPUSampler.Payload>> getRootNodes(List<CPUSamplerData> data) {
145145
Collection<ProfilerNode<CPUSampler.Payload>> retVal = new ArrayList<>();
146-
for (CPUSamplerData samplerData : data.values()) {
146+
for (CPUSamplerData samplerData : data) {
147147
for (Collection<ProfilerNode<CPUSampler.Payload>> profilerNodes : samplerData.getThreadData().values()) {
148148
retVal.addAll(profilerNodes);
149149
}
150150
}
151151
return retVal;
152152
}
153153

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

158158
@Override
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.truffle.tools.profiler.test;
26+
27+
import java.io.ByteArrayOutputStream;
28+
import java.io.IOException;
29+
import java.lang.ref.WeakReference;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.concurrent.ConcurrentHashMap;
34+
import java.util.concurrent.CountDownLatch;
35+
import java.util.concurrent.ExecutionException;
36+
import java.util.concurrent.ExecutorService;
37+
import java.util.concurrent.Executors;
38+
import java.util.concurrent.Future;
39+
import java.util.concurrent.atomic.AtomicBoolean;
40+
import java.util.regex.Matcher;
41+
import java.util.regex.Pattern;
42+
43+
import org.graalvm.polyglot.Context;
44+
import org.graalvm.polyglot.Engine;
45+
import org.graalvm.polyglot.Source;
46+
import org.graalvm.polyglot.Value;
47+
import org.graalvm.polyglot.proxy.ProxyExecutable;
48+
import org.junit.Assert;
49+
import org.junit.Test;
50+
51+
import com.oracle.truffle.api.test.GCUtils;
52+
import com.oracle.truffle.tools.profiler.CPUSampler;
53+
import com.oracle.truffle.tools.profiler.StackTraceEntry;
54+
55+
public class CPUSamplerMultiContextTest {
56+
public static final String FIB = """
57+
function fib(n) {
58+
if (n < 3) {
59+
return 1;
60+
} else {
61+
return fib(n - 1) + fib(n - 2);
62+
}
63+
}
64+
function main() {
65+
return fib;
66+
}
67+
""";
68+
69+
public static final String FIB_15_PLUS = """
70+
function fib15plus(n, remainder) {
71+
if (n < 15) {
72+
return remainder(n);
73+
} else {
74+
return fib15plus(n - 1, remainder) + fib15plus(n - 2, remainder);
75+
}
76+
}
77+
function main() {
78+
return fib15plus;
79+
}
80+
""";
81+
82+
@Test
83+
public void testSamplerDoesNotKeepContexts() throws IOException {
84+
ByteArrayOutputStream out = new ByteArrayOutputStream();
85+
try (Engine engine = Engine.newBuilder().out(out).option("cpusampler", "histogram").build()) {
86+
List<WeakReference<Context>> contextReferences = new ArrayList<>();
87+
for (int i = 0; i < 27; i++) {
88+
try (Context context = Context.newBuilder().engine(engine).build()) {
89+
contextReferences.add(new WeakReference<>(context));
90+
Source src = Source.newBuilder("sl", FIB, "fib.sl").build();
91+
Value fib = context.eval(src);
92+
fib.execute(29);
93+
}
94+
}
95+
GCUtils.assertGc("CPUSampler prevented collecting contexts", contextReferences);
96+
}
97+
Pattern pattern = Pattern.compile("Sampling Histogram. Recorded (\\d+) samples");
98+
Matcher matcher = pattern.matcher(out.toString());
99+
int histogramCount = 0;
100+
while (matcher.find()) {
101+
histogramCount++;
102+
Assert.assertTrue("Histogram no. " + histogramCount + " didn't contain any samples.", Integer.parseInt(matcher.group(1)) > 0);
103+
}
104+
Assert.assertEquals(27, histogramCount);
105+
}
106+
107+
static class RootCounter {
108+
int fibCount;
109+
int fib15plusCount;
110+
}
111+
112+
@Test
113+
public void testMultiThreadedAndMultiContextPerThread() throws InterruptedException, ExecutionException, IOException {
114+
try (Engine engine = Engine.create(); ExecutorService executorService = Executors.newFixedThreadPool(10)) {
115+
AtomicBoolean runFlag = new AtomicBoolean(true);
116+
CPUSampler sampler = CPUSampler.find(engine);
117+
int nThreads = 5;
118+
int nSamples = 5;
119+
Map<Thread, RootCounter> threads = new ConcurrentHashMap<>();
120+
List<Future<?>> futures = new ArrayList<>();
121+
CountDownLatch fibLatch = new CountDownLatch(nThreads);
122+
Source src1 = Source.newBuilder("sl", FIB_15_PLUS, "fib15plus.sl").build();
123+
Source src2 = Source.newBuilder("sl", FIB, "fib.sl").build();
124+
for (int i = 0; i < nThreads; i++) {
125+
futures.add(executorService.submit(() -> {
126+
threads.putIfAbsent(Thread.currentThread(), new RootCounter());
127+
AtomicBoolean countedDown = new AtomicBoolean();
128+
while (runFlag.get()) {
129+
try (Context context1 = Context.newBuilder().engine(engine).build(); Context context2 = Context.newBuilder().engine(engine).build()) {
130+
Value fib15plus = context1.eval(src1);
131+
Value fib = context2.eval(src2);
132+
ProxyExecutable proxyExecutable = (n) -> {
133+
if (countedDown.compareAndSet(false, true)) {
134+
fibLatch.countDown();
135+
}
136+
return fib.execute((Object[]) n);
137+
};
138+
Assert.assertEquals(514229, fib15plus.execute(29, proxyExecutable).asInt());
139+
}
140+
}
141+
}));
142+
}
143+
fibLatch.await();
144+
for (int i = 0; i < nSamples; i++) {
145+
Map<Thread, List<StackTraceEntry>> sample = sampler.takeSample();
146+
for (Map.Entry<Thread, List<StackTraceEntry>> sampleEntry : sample.entrySet()) {
147+
RootCounter rootCounter = threads.get(sampleEntry.getKey());
148+
for (StackTraceEntry stackTraceEntry : sampleEntry.getValue()) {
149+
if ("fib".equals(stackTraceEntry.getRootName())) {
150+
rootCounter.fibCount++;
151+
}
152+
if ("fib15plus".equals(stackTraceEntry.getRootName())) {
153+
rootCounter.fib15plusCount++;
154+
}
155+
}
156+
}
157+
}
158+
runFlag.set(false);
159+
for (Future<?> future : futures) {
160+
future.get();
161+
}
162+
for (Map.Entry<Thread, RootCounter> threadEntry : threads.entrySet()) {
163+
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,
164+
threadEntry.getValue().fibCount > 1);
165+
Assert.assertTrue(nSamples + " samples should contain at least 10 occurrences of the fib15plus root, but one thread contained only " + threadEntry.getValue().fib15plusCount,
166+
threadEntry.getValue().fib15plusCount > 10);
167+
}
168+
}
169+
}
170+
}

tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerTest.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected void initializeContext(LanguageContext c) throws Exception {
8787
context.initialize(ProxyLanguage.ID);
8888
sampler.setCollecting(false);
8989

90-
Map<TruffleContext, CPUSamplerData> data = sampler.getData();
90+
List<CPUSamplerData> data = sampler.getDataList();
9191
assertEquals(1, data.size());
9292

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

116-
Map<TruffleContext, CPUSamplerData> data = sampler.getData();
116+
List<CPUSamplerData> data = sampler.getDataList();
117117
assertEquals(1, data.size());
118118

119119
assertEquals(0, searchInitializeContext(data).size());
120120
}
121121

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

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

148148
sampler.setCollecting(true);
149-
Map<TruffleContext, CPUSamplerData> before = sampler.getData();
150-
Assert.assertEquals(0, before.values().iterator().next().getSamples());
149+
List<CPUSamplerData> before = sampler.getDataList();
150+
Assert.assertEquals(0, before.iterator().next().getSamples());
151151
Assert.assertTrue(sampler.isCollecting());
152152
Assert.assertFalse(sampler.hasData());
153153

154154
for (int i = 0; i < executionCount; i++) {
155155
eval(defaultSourceForSampling);
156156
}
157157

158-
Map<TruffleContext, CPUSamplerData> after = sampler.getData();
159-
Assert.assertNotEquals(0, after.values().iterator().next().getSamples());
158+
List<CPUSamplerData> after = sampler.getDataList();
159+
Assert.assertNotEquals(0, after.iterator().next().getSamples());
160160
Assert.assertTrue(sampler.isCollecting());
161161
Assert.assertTrue(sampler.hasData());
162162

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

168168
sampler.clearData();
169-
Map<TruffleContext, CPUSamplerData> cleared = sampler.getData();
169+
List<CPUSamplerData> cleared = sampler.getDataList();
170170
Assert.assertFalse(sampler.isCollecting());
171-
Assert.assertEquals(0, cleared.values().iterator().next().getSamples());
171+
Assert.assertEquals(0, cleared.iterator().next().getSamples());
172172

173173
Assert.assertFalse(sampler.hasData());
174174
}
@@ -221,9 +221,9 @@ public void testCorrectRootStructure() {
221221
}
222222

223223
private Collection<ProfilerNode<Payload>> getProfilerNodes() {
224-
Map<TruffleContext, CPUSamplerData> data = sampler.getData();
224+
List<CPUSamplerData> data = sampler.getDataList();
225225
Assert.assertEquals(1, data.size());
226-
Map<Thread, Collection<ProfilerNode<Payload>>> threadData = data.values().iterator().next().getThreadData();
226+
Map<Thread, Collection<ProfilerNode<Payload>>> threadData = data.iterator().next().getThreadData();
227227
Assert.assertEquals(1, threadData.size());
228228
Collection<ProfilerNode<Payload>> children = threadData.values().iterator().next();
229229
return children;
@@ -334,6 +334,7 @@ public void testClosedConfig() {
334334
}
335335

336336
@Test
337+
@SuppressWarnings("deprecation")
337338
public void testTiers() {
338339
Assume.assumeFalse(Truffle.getRuntime().getClass().toString().contains("Default"));
339340
Context.Builder builder = Context.newBuilder().option("engine.FirstTierCompilationThreshold", Integer.toString(FIRST_TIER_THRESHOLD)).option("engine.LastTierCompilationThreshold",
@@ -345,6 +346,7 @@ public void testTiers() {
345346
for (int i = 0; i < 3 * FIRST_TIER_THRESHOLD; i++) {
346347
c.eval(defaultSourceForSampling);
347348
}
349+
// Intentionally kept one usage of the deprecated API
348350
data = cpuSampler.getData();
349351
}
350352
CPUSamplerData samplerData = data.values().iterator().next();

tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/ProfilerCLITest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@
3131
import java.util.List;
3232
import java.util.Map;
3333

34-
import com.oracle.truffle.api.Truffle;
35-
import com.oracle.truffle.api.TruffleContext;
36-
import com.oracle.truffle.tools.profiler.CPUSamplerData;
3734
import org.graalvm.polyglot.Context;
3835
import org.graalvm.polyglot.Source;
3936
import org.graalvm.shadowed.org.json.JSONArray;
@@ -43,9 +40,11 @@
4340
import org.junit.Ignore;
4441
import org.junit.Test;
4542

43+
import com.oracle.truffle.api.Truffle;
4644
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage;
4745
import com.oracle.truffle.api.source.SourceSection;
4846
import com.oracle.truffle.tools.profiler.CPUSampler;
47+
import com.oracle.truffle.tools.profiler.CPUSamplerData;
4948
import com.oracle.truffle.tools.profiler.ProfilerNode;
5049

5150
public class ProfilerCLITest {
@@ -364,8 +363,8 @@ public void testSamplerJson() {
364363
final long sampleCount;
365364
final boolean gatherSelfHitTimes;
366365
synchronized (sampler) {
367-
Map<TruffleContext, CPUSamplerData> data = sampler.getData();
368-
CPUSamplerData samplerData = data.values().iterator().next();
366+
List<CPUSamplerData> data = sampler.getDataList();
367+
CPUSamplerData samplerData = data.iterator().next();
369368
threadToNodesMap = samplerData.getThreadData();
370369
period = sampler.getPeriod();
371370
sampleCount = samplerData.getSamples();

0 commit comments

Comments
 (0)