From 0ad5167caf30d1bddf334c43bdbfd2762916dbf1 Mon Sep 17 00:00:00 2001 From: Marouane El Hallaoui Date: Tue, 9 Apr 2024 11:35:03 +0000 Subject: [PATCH 001/130] new dev cycle GraalVM 24.0.2 --- compiler/mx.compiler/suite.py | 4 ++-- espresso/mx.espresso/suite.py | 4 ++-- regex/mx.regex/suite.py | 4 ++-- sdk/mx.sdk/suite.py | 4 ++-- substratevm/mx.substratevm/suite.py | 4 ++-- tools/mx.tools/suite.py | 4 ++-- truffle/external_repos/simplelanguage/pom.xml | 2 +- truffle/external_repos/simpletool/pom.xml | 2 +- truffle/mx.truffle/suite.py | 4 ++-- vm/mx.vm/suite.py | 10 +++++----- wasm/mx.wasm/suite.py | 2 +- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 0731335748f9..85ba32476442 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -4,8 +4,8 @@ "sourceinprojectwhitelist" : [], "groupId" : "org.graalvm.compiler", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "url" : "http://www.graalvm.org/", "developer" : { "name" : "GraalVM Development", diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py index a0f1531885f7..d82e52c817e8 100644 --- a/espresso/mx.espresso/suite.py +++ b/espresso/mx.espresso/suite.py @@ -24,8 +24,8 @@ suite = { "mxversion": "6.44.0", "name": "espresso", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "groupId" : "org.graalvm.espresso", "url" : "https://www.graalvm.org/reference-manual/java-on-truffle/", "developer" : { diff --git a/regex/mx.regex/suite.py b/regex/mx.regex/suite.py index dc87f492ad90..613baa8982d4 100644 --- a/regex/mx.regex/suite.py +++ b/regex/mx.regex/suite.py @@ -43,8 +43,8 @@ "name" : "regex", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "groupId" : "org.graalvm.regex", "url" : "http://www.graalvm.org/", "developer" : { diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index 10b54766c296..1d5463ae8dff 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -41,8 +41,8 @@ suite = { "mxversion": "6.53.2", "name" : "sdk", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "sourceinprojectwhitelist" : [], "url" : "https://github.com/oracle/graal", "groupId" : "org.graalvm.sdk", diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 4101b50a7e52..ddd5ff17cd2c 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -2,8 +2,8 @@ suite = { "mxversion": "6.27.1", "name": "substratevm", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "url" : "https://github.com/oracle/graal/tree/master/substratevm", "groupId" : "org.graalvm.nativeimage", diff --git a/tools/mx.tools/suite.py b/tools/mx.tools/suite.py index a2b1749e94aa..242f15f4876c 100644 --- a/tools/mx.tools/suite.py +++ b/tools/mx.tools/suite.py @@ -26,8 +26,8 @@ "defaultLicense" : "GPLv2-CPE", "groupId" : "org.graalvm.tools", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "url" : "http://openjdk.java.net/projects/graal", "developer" : { "name" : "GraalVM Development", diff --git a/truffle/external_repos/simplelanguage/pom.xml b/truffle/external_repos/simplelanguage/pom.xml index 1797a1e618b3..87ac9e84d65f 100644 --- a/truffle/external_repos/simplelanguage/pom.xml +++ b/truffle/external_repos/simplelanguage/pom.xml @@ -47,7 +47,7 @@ UTF-8 jdt_apt - 24.0.1-dev + 24.0.2-dev 17 17 org.graalvm.sl.launcher/com.oracle.truffle.sl.launcher.SLMain diff --git a/truffle/external_repos/simpletool/pom.xml b/truffle/external_repos/simpletool/pom.xml index f4be91c70b3e..810da2cf5087 100644 --- a/truffle/external_repos/simpletool/pom.xml +++ b/truffle/external_repos/simpletool/pom.xml @@ -48,7 +48,7 @@ ${graalvm.version} simpletool - 24.0.1-dev + 24.0.2-dev UTF-8 17 17 diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index ce1c9b8f3fe6..6fda9d39a084 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -41,8 +41,8 @@ suite = { "mxversion": "7.0.3", "name" : "truffle", - "version" : "24.0.1", - "release" : True, + "version" : "24.0.2", + "release" : False, "groupId" : "org.graalvm.truffle", "sourceinprojectwhitelist" : [], "url" : "http://openjdk.java.net/projects/graal", diff --git a/vm/mx.vm/suite.py b/vm/mx.vm/suite.py index 71e0317c3ccc..e632500f11e4 100644 --- a/vm/mx.vm/suite.py +++ b/vm/mx.vm/suite.py @@ -1,8 +1,8 @@ suite = { "name": "vm", - "version" : "24.0.1", + "version" : "24.0.2", "mxversion": "6.41.0", - "release" : True, + "release" : False, "groupId" : "org.graalvm", "url" : "http://www.graalvm.org/", @@ -33,7 +33,7 @@ "name": "graal-nodejs", "subdir": True, "dynamic": True, - "version": "d1a4fb72a45e37bdfd4b018ee4d59a88c4b23dc7", + "version": "2e378122eb7b008619337cd94ecc94a938db2ae2", "urls" : [ {"url" : "https://github.com/graalvm/graaljs.git", "kind" : "git"}, ] @@ -42,7 +42,7 @@ "name": "graal-js", "subdir": True, "dynamic": True, - "version": "d1a4fb72a45e37bdfd4b018ee4d59a88c4b23dc7", + "version": "2e378122eb7b008619337cd94ecc94a938db2ae2", "urls": [ {"url": "https://github.com/graalvm/graaljs.git", "kind" : "git"}, ] @@ -65,7 +65,7 @@ }, { "name": "graalpython", - "version": "0b2a38ff709eb9e40ca7e18ba259ff925aae1cd2", + "version": "aa8373463e1eebb8addb0bfa2443570406286044", "dynamic": True, "urls": [ {"url": "https://github.com/graalvm/graalpython.git", "kind": "git"}, diff --git a/wasm/mx.wasm/suite.py b/wasm/mx.wasm/suite.py index f6995454eda4..abcb5e8593b4 100644 --- a/wasm/mx.wasm/suite.py +++ b/wasm/mx.wasm/suite.py @@ -42,7 +42,7 @@ "mxversion": "6.41.0", "name" : "wasm", "groupId" : "org.graalvm.wasm", - "version" : "24.0.1", + "version" : "24.0.2", "versionConflictResolution" : "latest", "url" : "http://graalvm.org/", "developer" : { From 9eee0215e0922a550771a8b807e103e171270f72 Mon Sep 17 00:00:00 2001 From: Jakub Chaloupka Date: Thu, 4 Apr 2024 17:39:54 +0200 Subject: [PATCH 002/130] Use all active contexts for CPUSampler#takeSample. Don't store hard references to contexts on the engine. --- tools/CHANGELOG.md | 4 + .../chromeinspector/InspectorProfiler.java | 18 +- .../test/CPUSamplerMultiContextTest.java | 170 ++++++++++++++++++ .../tools/profiler/test/CPUSamplerTest.java | 26 +-- .../tools/profiler/test/ProfilerCLITest.java | 9 +- .../snapshot.sigtest | 14 +- .../truffle/tools/profiler/CPUSampler.java | 111 +++++++++--- .../tools/profiler/CPUSamplerData.java | 29 ++- .../tools/profiler/SafepointStackSampler.java | 56 ++++-- .../tools/profiler/impl/CPUSamplerCLI.java | 21 ++- .../tools/profiler/impl/SVGSamplerOutput.java | 102 +++++------ .../profiler/impl/resources/flamegraph.js | 2 +- .../profiler/impl/resources/histogram.js | 2 +- 13 files changed, 418 insertions(+), 146 deletions(-) create mode 100644 tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerMultiContextTest.java diff --git a/tools/CHANGELOG.md b/tools/CHANGELOG.md index 91edef4f01f1..9ee2285ec4c4 100644 --- a/tools/CHANGELOG.md +++ b/tools/CHANGELOG.md @@ -2,6 +2,10 @@ This changelog summarizes major changes between Truffle Tools versions. +## Version 24.0.2 +* 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. +* 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. + ## Version 23.0.0 * GR-41407: Added new option `--dap.SourcePath` to allow to resolve sources with relative paths. diff --git a/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorProfiler.java b/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorProfiler.java index 041c9908317f..c52fbc11e3f3 100644 --- a/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorProfiler.java +++ b/tools/src/com.oracle.truffle.tools.chromeinspector/src/com/oracle/truffle/tools/chromeinspector/InspectorProfiler.java @@ -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; @@ -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 { @@ -127,12 +127,12 @@ public void start() { @Override public Params stop() { long time = System.currentTimeMillis(); - Map data; + List data; long period; synchronized (sampler) { sampler.setCollecting(false); sampler.setGatherSelfHitTimes(oldGatherSelfHitTimes); - data = sampler.getData(); + data = sampler.getDataList(); sampler.clearData(); period = sampler.getPeriod(); } @@ -141,9 +141,9 @@ public Params stop() { return profile; } - private static Collection> getRootNodes(Map data) { + private static Collection> getRootNodes(List data) { Collection> retVal = new ArrayList<>(); - for (CPUSamplerData samplerData : data.values()) { + for (CPUSamplerData samplerData : data) { for (Collection> profilerNodes : samplerData.getThreadData().values()) { retVal.addAll(profilerNodes); } @@ -151,8 +151,8 @@ private static Collection> getRootNodes(Map data) { - return data.values().stream().map(CPUSamplerData::getSamples).reduce(0L, Long::sum); + private static long getSampleCount(List data) { + return data.stream().map(CPUSamplerData::getSamples).reduce(0L, Long::sum); } @Override diff --git a/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerMultiContextTest.java b/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerMultiContextTest.java new file mode 100644 index 000000000000..e5a10e3a041b --- /dev/null +++ b/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerMultiContextTest.java @@ -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> 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 threads = new ConcurrentHashMap<>(); + List> 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> sample = sampler.takeSample(); + for (Map.Entry> 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 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); + } + } + } +} diff --git a/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerTest.java b/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerTest.java index 91f843d1977b..0edd6eb9e80c 100644 --- a/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerTest.java +++ b/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/CPUSamplerTest.java @@ -87,7 +87,7 @@ protected void initializeContext(LanguageContext c) throws Exception { context.initialize(ProxyLanguage.ID); sampler.setCollecting(false); - Map data = sampler.getData(); + List data = sampler.getDataList(); assertEquals(1, data.size()); assertEquals(0, searchInitializeContext(data).size()); @@ -113,15 +113,15 @@ protected void initializeContext(LanguageContext c) throws Exception { context.initialize(ProxyLanguage.ID); sampler.setCollecting(false); - Map data = sampler.getData(); + List data = sampler.getDataList(); assertEquals(1, data.size()); assertEquals(0, searchInitializeContext(data).size()); } - private static List> searchInitializeContext(Map data) { + private static List> searchInitializeContext(List data) { List> found = new ArrayList<>(); - for (CPUSamplerData d : data.values()) { + for (CPUSamplerData d : data) { Map>> threadData = d.getThreadData(); assertEquals(threadData.toString(), 1, threadData.size()); @@ -146,8 +146,8 @@ private static void searchNodes(List> results, public void testCollectingAndHasData() { sampler.setCollecting(true); - Map before = sampler.getData(); - Assert.assertEquals(0, before.values().iterator().next().getSamples()); + List before = sampler.getDataList(); + Assert.assertEquals(0, before.iterator().next().getSamples()); Assert.assertTrue(sampler.isCollecting()); Assert.assertFalse(sampler.hasData()); @@ -155,8 +155,8 @@ public void testCollectingAndHasData() { eval(defaultSourceForSampling); } - Map after = sampler.getData(); - Assert.assertNotEquals(0, after.values().iterator().next().getSamples()); + List after = sampler.getDataList(); + Assert.assertNotEquals(0, after.iterator().next().getSamples()); Assert.assertTrue(sampler.isCollecting()); Assert.assertTrue(sampler.hasData()); @@ -166,9 +166,9 @@ public void testCollectingAndHasData() { Assert.assertTrue(sampler.hasData()); sampler.clearData(); - Map cleared = sampler.getData(); + List 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()); } @@ -221,9 +221,9 @@ public void testCorrectRootStructure() { } private Collection> getProfilerNodes() { - Map data = sampler.getData(); + List data = sampler.getDataList(); Assert.assertEquals(1, data.size()); - Map>> threadData = data.values().iterator().next().getThreadData(); + Map>> threadData = data.iterator().next().getThreadData(); Assert.assertEquals(1, threadData.size()); Collection> children = threadData.values().iterator().next(); return children; @@ -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", @@ -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(); diff --git a/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/ProfilerCLITest.java b/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/ProfilerCLITest.java index 673e82d7ceb5..e1201e4312c7 100644 --- a/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/ProfilerCLITest.java +++ b/tools/src/com.oracle.truffle.tools.profiler.test/src/com/oracle/truffle/tools/profiler/test/ProfilerCLITest.java @@ -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; @@ -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 { @@ -364,8 +363,8 @@ public void testSamplerJson() { final long sampleCount; final boolean gatherSelfHitTimes; synchronized (sampler) { - Map data = sampler.getData(); - CPUSamplerData samplerData = data.values().iterator().next(); + List data = sampler.getDataList(); + CPUSamplerData samplerData = data.iterator().next(); threadToNodesMap = samplerData.getThreadData(); period = sampler.getPeriod(); sampleCount = samplerData.getSamples(); diff --git a/tools/src/com.oracle.truffle.tools.profiler/snapshot.sigtest b/tools/src/com.oracle.truffle.tools.profiler/snapshot.sigtest index 3654b5f0afd5..9dfbca20d9ab 100644 --- a/tools/src/com.oracle.truffle.tools.profiler/snapshot.sigtest +++ b/tools/src/com.oracle.truffle.tools.profiler/snapshot.sigtest @@ -10,7 +10,9 @@ meth public boolean isCollecting() meth public boolean isGatherSelfHitTimes() meth public com.oracle.truffle.api.instrumentation.SourceSectionFilter getFilter() meth public int getStackLimit() +meth public java.util.List getDataList() meth public java.util.Map getData() + anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="") meth public java.util.Map> takeSample() meth public java.util.Map> takeSample(long,java.util.concurrent.TimeUnit) meth public long getPeriod() @@ -25,8 +27,8 @@ meth public void setPeriod(long) meth public void setSampleContextInitialization(boolean) meth public void setStackLimit(int) supr java.lang.Object -hfds COPY_PAYLOAD,DEFAULT_FILTER,activeContexts,closed,collecting,delay,env,filter,gatherSelfHitTimes,period,processingThread,processingThreadRunnable,resultsToProcess,safepointStackSampler,sampleContextInitialization,samplerTask,samplerThread,stackLimit -hcls MutableSamplerData,ResultProcessingRunnable,SamplingResult,SamplingTimerTask +hfds COPY_PAYLOAD,DEFAULT_FILTER,activeContexts,closed,collecting,delay,env,filter,gatherSelfHitTimes,nextContextIndex,period,processingExecutionService,processingThreadFuture,processingThreadRunnable,resultsToProcess,safepointStackSampler,sampleContextInitialization,samplerData,samplerExecutionService,samplerFuture,stackLimit +hcls MutableSamplerData,ResultProcessingRunnable,SamplingResult,SamplingTask CLSS public final static com.oracle.truffle.tools.profiler.CPUSampler$Payload outer com.oracle.truffle.tools.profiler.CPUSampler @@ -41,6 +43,8 @@ hfds selfHitTimes,selfTierCount,tierCount CLSS public final com.oracle.truffle.tools.profiler.CPUSamplerData meth public com.oracle.truffle.api.TruffleContext getContext() + anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="") +meth public int getContextIndex() meth public java.util.LongSummaryStatistics getSampleBias() meth public java.util.LongSummaryStatistics getSampleDuration() meth public java.util.Map>> getThreadData() @@ -48,7 +52,7 @@ meth public long getSampleInterval() meth public long getSamples() meth public long missedSamples() supr java.lang.Object -hfds biasStatistics,context,durationStatistics,intervalMs,missedSamples,samplesTaken,threadData +hfds biasStatistics,contextIndex,durationStatistics,intervalMs,missedSamples,samplesTaken,threadData CLSS public final com.oracle.truffle.tools.profiler.CPUTracer innr public final static Payload @@ -87,7 +91,7 @@ meth public void clearData() meth public void close() meth public void setCollecting(boolean) supr java.lang.Object -hfds CLEAN_INTERVAL,INTEROP,RECURSIVE,activeBinding,closed,collecting,env,initializedLanguages,newReferences,processedReferences,referenceQueue,referenceThread,summaryData +hfds CLEAN_INTERVAL,INTEROP,RECURSIVE,activeBinding,closed,collecting,env,initializedLanguages,newReferences,processedReferences,referenceExecutorService,referenceFuture,referenceQueue,summaryData hcls Listener,ObjectWeakReference CLSS public final com.oracle.truffle.tools.profiler.HeapSummary @@ -177,7 +181,7 @@ CLSS public java.lang.Object cons public init() meth protected java.lang.Object clone() throws java.lang.CloneNotSupportedException meth protected void finalize() throws java.lang.Throwable - anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="9") + anno 0 java.lang.Deprecated(boolean forRemoval=true, java.lang.String since="9") meth public boolean equals(java.lang.Object) meth public final java.lang.Class getClass() meth public final void notify() diff --git a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSampler.java b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSampler.java index cc6e2cbcad4e..6dcd0bbfaf65 100644 --- a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSampler.java +++ b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSampler.java @@ -30,11 +30,13 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.ListIterator; import java.util.Locale; import java.util.LongSummaryStatistics; import java.util.Map; -import java.util.Map.Entry; +import java.util.WeakHashMap; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -99,7 +101,10 @@ public CPUSampler create(Env env) { } private final Env env; - private final Map activeContexts = Collections.synchronizedMap(new HashMap<>()); + + private int nextContextIndex; + private final Map activeContexts = new WeakHashMap<>(); + private final List samplerData = new ArrayList<>(); private volatile boolean closed; private volatile boolean collecting; private long period = 10; @@ -133,7 +138,9 @@ public CPUSampler create(Env env) { @Override public void onContextCreated(TruffleContext context) { synchronized (CPUSampler.this) { - activeContexts.put(context, new MutableSamplerData()); + int contextIndex = nextContextIndex++; + activeContexts.put(context, contextIndex); + samplerData.add(new MutableSamplerData(contextIndex)); } } @@ -326,28 +333,59 @@ public boolean hasStackOverflowed() { * {@link #setCollecting(boolean)}. The collected data can be cleared from the cpusampler using * {@link #clearData()} * - * @return a map from {@link TruffleContext} to {@link CPUSamplerData}. + * @return a map from {@link TruffleContext} to {@link CPUSamplerData}. The contexts that were + * already collected are not in the returned map even though data was collected for + * them. All collected data can be obtained using {@link CPUSampler#getDataList()}. * @since 21.3.0 + * + * @deprecated in 24.0.2. Contexts are no longer stored permanently. Use {@link #getDataList()} + * to get all sampler data. */ + @Deprecated public synchronized Map getData() { - if (activeContexts.isEmpty()) { + List dataList = getDataList(); + if (dataList.isEmpty()) { return Collections.emptyMap(); } Map contextToData = new HashMap<>(); - for (Entry contextEntry : this.activeContexts.entrySet()) { + for (Map.Entry contextEntry : activeContexts.entrySet()) { + contextToData.put(contextEntry.getKey(), dataList.get(contextEntry.getValue())); + } + return Collections.unmodifiableMap(contextToData); + } + + /** + * Get per-context profiling data. Context objects are not stored, the profiling data is an + * unmodifiable list of {@link CPUSamplerData}. It is collected based on the configuration of + * the {@link CPUSampler} (e.g. {@link #setFilter(SourceSectionFilter)}, + * {@link #setPeriod(long)}, etc.) and collecting can be controlled by the + * {@link #setCollecting(boolean)}. The collected data can be cleared from the cpusampler using + * {@link #clearData()} + * + * @return a list of {@link CPUSamplerData} where each element corresponds to one context. + * @since 24.0.2 + */ + public synchronized List getDataList() { + if (samplerData.isEmpty()) { + return Collections.emptyList(); + } + Map availableContexts = new HashMap<>(); + for (Map.Entry contextEntry : activeContexts.entrySet()) { + availableContexts.put(contextEntry.getValue(), contextEntry.getKey()); + } + List dataList = new ArrayList<>(); + for (MutableSamplerData mutableSamplerData : this.samplerData) { Map>> threads = new HashMap<>(); - final MutableSamplerData mutableSamplerData = contextEntry.getValue(); for (Map.Entry> threadEntry : mutableSamplerData.threadData.entrySet()) { ProfilerNode copy = new ProfilerNode<>(); copy.deepCopyChildrenFrom(threadEntry.getValue(), COPY_PAYLOAD); threads.put(threadEntry.getKey(), copy.getChildren()); } - TruffleContext context = contextEntry.getKey(); - contextToData.put(context, - new CPUSamplerData(context, threads, mutableSamplerData.biasStatistic, mutableSamplerData.durationStatistic, mutableSamplerData.samplesTaken.get(), period, - mutableSamplerData.missedSamples.get())); + dataList.add(new CPUSamplerData(mutableSamplerData.index, availableContexts.get(mutableSamplerData.index), threads, mutableSamplerData.biasStatistic, mutableSamplerData.durationStatistic, + mutableSamplerData.samplesTaken.get(), period, + mutableSamplerData.missedSamples.get())); } - return Collections.unmodifiableMap(contextToData); + return Collections.unmodifiableList(dataList); } /** @@ -356,8 +394,8 @@ public synchronized Map getData() { * @since 0.30 */ public synchronized void clearData() { - for (TruffleContext context : activeContexts.keySet()) { - activeContexts.put(context, new MutableSamplerData()); + for (ListIterator dataIterator = samplerData.listIterator(); dataIterator.hasNext();) { + dataIterator.set(new MutableSamplerData(dataIterator.next().index)); } } @@ -366,7 +404,7 @@ public synchronized void clearData() { * @since 0.30 */ public synchronized boolean hasData() { - for (MutableSamplerData mutableSamplerData : activeContexts.values()) { + for (MutableSamplerData mutableSamplerData : samplerData) { if (mutableSamplerData.samplesTaken.get() > 0) { return true; } @@ -464,16 +502,25 @@ public Map> takeSample(long timeout, TimeUnit time if (activeContexts.isEmpty()) { return Collections.emptyMap(); } - TruffleContext context = activeContexts.keySet().iterator().next(); - if (context.isActive()) { - throw new IllegalArgumentException("Cannot sample a context that is currently active on the current thread."); + Map contexts = new LinkedHashMap<>(); + for (TruffleContext context : activeContexts.keySet()) { + if (context.isActive()) { + throw new IllegalArgumentException("Cannot sample a context that is currently active on the current thread."); + } + if (!context.isClosed()) { + contexts.put(context, samplerData.get(activeContexts.get(context))); + } } - Map> stacks = new HashMap<>(); - List sample = safepointStackSampler.sample(env, context, activeContexts.get(context), !sampleContextInitialization, timeout, timeoutUnit); - for (StackSample stackSample : sample) { - stacks.put(stackSample.thread, stackSample.stack); + if (!contexts.isEmpty()) { + Map> stacks = new HashMap<>(); + List sample = safepointStackSampler.sample(env, contexts, !sampleContextInitialization, timeout, timeoutUnit); + for (StackSample stackSample : sample) { + stacks.put(stackSample.thread, stackSample.stack); + } + return Collections.unmodifiableMap(stacks); + } else { + return Collections.emptyMap(); } - return Collections.unmodifiableMap(stacks); } } @@ -534,7 +581,7 @@ private void enterChangeConfig() { } private synchronized TruffleContext[] contexts() { - return activeContexts.keySet().toArray(new TruffleContext[activeContexts.size()]); + return activeContexts.keySet().toArray(TruffleContext[]::new); } /** @@ -654,7 +701,7 @@ public void run() { if (!collecting) { return; } - final MutableSamplerData mutableSamplerData = activeContexts.get(result.context); + final MutableSamplerData mutableSamplerData = samplerData.get(activeContexts.get(result.context)); for (StackSample sample : result.samples) { mutableSamplerData.biasStatistic.accept(sample.biasNs); mutableSamplerData.durationStatistic.accept(sample.durationNs); @@ -750,7 +797,12 @@ public void run() { if (context.isClosed()) { continue; } - List samples = safepointStackSampler.sample(env, context, activeContexts.get(context), !sampleContextInitialization, period, TimeUnit.MILLISECONDS); + MutableSamplerData data; + synchronized (CPUSampler.this) { + data = samplerData.get(activeContexts.get(context)); + } + List samples = safepointStackSampler.sample(env, Collections.singletonMap(context, data), !sampleContextInitialization, period, + TimeUnit.MILLISECONDS); resultsToProcess.add(new SamplingResult(samples, context, taskStartTime)); } } @@ -758,11 +810,16 @@ public void run() { } static class MutableSamplerData { + final int index; final Map> threadData = new HashMap<>(); final AtomicLong samplesTaken = new AtomicLong(0); final LongSummaryStatistics biasStatistic = new LongSummaryStatistics(); // nanoseconds final LongSummaryStatistics durationStatistic = new LongSummaryStatistics(); // nanoseconds final AtomicLong missedSamples = new AtomicLong(0); + + MutableSamplerData(int index) { + this.index = index; + } } private static String format(String format, Object... args) { @@ -786,7 +843,7 @@ public void example() { sampler.close(); // Read information about the roots of the tree per thread. for (Collection> nodes - : sampler.getData().values().iterator().next().threadData.values()) { + : sampler.getDataList().iterator().next().threadData.values()) { for (ProfilerNode node : nodes) { final String rootName = node.getRootName(); final int selfHitCount = node.getPayload().getSelfHitCount(); diff --git a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSamplerData.java b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSamplerData.java index 410016601929..73b8a47525b6 100644 --- a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSamplerData.java +++ b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/CPUSamplerData.java @@ -24,6 +24,7 @@ */ package com.oracle.truffle.tools.profiler; +import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Collections; import java.util.LongSummaryStatistics; @@ -35,12 +36,13 @@ /** * Execution profile of a particular context. * - * @see CPUSampler#getData() + * @see CPUSampler#getDataList() * @since 21.3.0 */ public final class CPUSamplerData { - final TruffleContext context; + final int contextIndex; + final WeakReference contextRef; final Map>> threadData; final LongSummaryStatistics biasStatistics; // nanoseconds final LongSummaryStatistics durationStatistics; // nanoseconds @@ -48,10 +50,11 @@ public final class CPUSamplerData { final long intervalMs; final long missedSamples; - CPUSamplerData(TruffleContext context, Map>> threadData, LongSummaryStatistics biasStatistics, LongSummaryStatistics durationStatistics, + CPUSamplerData(int contextIndex, TruffleContext context, Map>> threadData, LongSummaryStatistics biasStatistics, LongSummaryStatistics durationStatistics, long samplesTaken, long intervalMs, long missedSamples) { - this.context = context; + this.contextIndex = contextIndex; + this.contextRef = new WeakReference<>(context); this.threadData = threadData; this.biasStatistics = biasStatistics; this.durationStatistics = durationStatistics; @@ -61,11 +64,25 @@ public final class CPUSamplerData { } /** - * @return The context this data applies to. + * @return The index of the context this data applies to. It is the index of this data in the + * {@link CPUSampler#getDataList() data list}. The index is zero based and corresponds + * to the order of context creations on the engine. + * @since 24.0.2 + */ + public int getContextIndex() { + return contextIndex; + } + + /** + * @return The context this data applies to or null if the context was already collected. * @since 21.3.0 + * @deprecated in 24.0.2. Contexts are no longer stored permanently. This method will return + * null if the context was already collected. Use {@link #getContextIndex()} to + * differentiate sampler data for different contexts. */ + @Deprecated public TruffleContext getContext() { - return context; + return contextRef.get(); } /** diff --git a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/SafepointStackSampler.java b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/SafepointStackSampler.java index f3690124eb5c..05b0d96fc7b8 100644 --- a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/SafepointStackSampler.java +++ b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/SafepointStackSampler.java @@ -26,9 +26,10 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -79,10 +80,8 @@ private StackVisitor fetchStackVisitor() { return visitor; } - List sample(Env env, TruffleContext context, CPUSampler.MutableSamplerData mutableSamplerData, boolean useSyntheticFrames, long timeout, TimeUnit timeoutUnit) { - if (context.isClosed()) { - return Collections.emptyList(); - } + List sample(Env env, Map contexts, boolean useSyntheticFrames, long timeout, TimeUnit timeoutUnit) { + long startNanos = System.nanoTime(); SampleAction action = cachedAction.getAndSet(null); if (action == null) { long index = sampleIndex.getAndIncrement(); @@ -96,21 +95,37 @@ List sample(Env env, TruffleContext context, CPUSampler.MutableSamp action.useSyntheticFrames = useSyntheticFrames; long submitTime = System.nanoTime(); - Future future; - try { - future = env.submitThreadLocal(context, null, action); - } catch (IllegalStateException e) { - // context may be closed while submitting - return Collections.emptyList(); + Map> futures = new LinkedHashMap<>(); + for (TruffleContext context : contexts.keySet()) { + if (!context.isClosed()) { + try { + futures.put(context, env.submitThreadLocal(context, null, action)); + } catch (IllegalStateException e) { + // context may be closed while submitting + } + } } - try { - future.get(timeout, timeoutUnit); - } catch (InterruptedException | ExecutionException e) { - env.getLogger(getClass()).log(Level.SEVERE, "Sampling failed", e); - return null; - } catch (TimeoutException e) { - future.cancel(false); - mutableSamplerData.missedSamples.incrementAndGet(); + + boolean incompleteSample = false; + for (Map.Entry> futureEntry : futures.entrySet()) { + TruffleContext context = futureEntry.getKey(); + Future future = futureEntry.getValue(); + long timeElapsed = System.nanoTime() - startNanos; + long timeoutNanos = timeoutUnit.toNanos(timeout); + if (!incompleteSample && timeElapsed < timeoutNanos) { + try { + futureEntry.getValue().get(timeout, timeoutUnit); + } catch (InterruptedException | ExecutionException e) { + env.getLogger(getClass()).log(Level.SEVERE, "Sampling error", e); + incompleteSample = true; + } catch (TimeoutException e) { + future.cancel(false); + contexts.get(context).missedSamples.incrementAndGet(); + incompleteSample = true; + } + } else { + future.cancel(false); + } } // we compute the time to find out how accurate this sample is. List perThreadSamples = new ArrayList<>(); @@ -259,6 +274,9 @@ protected SampleAction(long index) { @Override protected void perform(Access access) { + if (completed.containsKey(access.getThread())) { + return; + } if (useSyntheticFrames) { SyntheticFrame syntheticFrame = syntheticFrameThreadLocal.get(); if (syntheticFrame != null) { diff --git a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/CPUSamplerCLI.java b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/CPUSamplerCLI.java index db5498b80b75..d94efba4e11d 100644 --- a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/CPUSamplerCLI.java +++ b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/CPUSamplerCLI.java @@ -47,7 +47,6 @@ import org.graalvm.options.OptionValues; import com.oracle.truffle.api.Option; -import com.oracle.truffle.api.TruffleContext; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.tools.profiler.CPUSampler; import com.oracle.truffle.tools.profiler.CPUSamplerData; @@ -223,7 +222,7 @@ public int[] apply(String s) { static void handleOutput(TruffleInstrument.Env env, CPUSampler sampler) { PrintStream out = chooseOutputStream(env); - Map data = sampler.getData(); + List data = sampler.getDataList(); OptionValues options = env.getOptions(); switch (chooseOutput(options)) { case HISTOGRAM: @@ -278,15 +277,15 @@ private static Output chooseOutput(OptionValues options) { return OUTPUT.getDefaultValue(); } - private static void printSamplingCallTree(PrintStream out, OptionValues options, Map data) { - for (Map.Entry entry : data.entrySet()) { - new SamplingCallTree(entry.getValue(), options).print(out); + private static void printSamplingCallTree(PrintStream out, OptionValues options, List data) { + for (CPUSamplerData entry : data) { + new SamplingCallTree(entry, options).print(out); } } - private static void printSamplingHistogram(PrintStream out, OptionValues options, Map data) { - for (Map.Entry entry : data.entrySet()) { - new SamplingHistogram(entry.getValue(), options).print(out); + private static void printSamplingHistogram(PrintStream out, OptionValues options, List data) { + for (CPUSamplerData entry : data) { + new SamplingHistogram(entry, options).print(out); } } @@ -309,7 +308,7 @@ private static void printWarnings(CPUSampler sampler, PrintStream out) { } private static boolean sampleDurationTooLong(CPUSampler sampler) { - for (CPUSamplerData value : sampler.getData().values()) { + for (CPUSamplerData value : sampler.getDataList()) { if (value.getSampleDuration().getAverage() > MAX_OVERHEAD_WARNING_THRESHOLD * sampler.getPeriod() * MILLIS_TO_NANOS) { return true; } @@ -321,13 +320,13 @@ private static void printDiv(PrintStream out) { out.println("-------------------------------------------------------------------------------- "); } - private static void printSamplingJson(PrintStream out, OptionValues options, Map data) { + private static void printSamplingJson(PrintStream out, OptionValues options, List data) { boolean gatheredHitTimes = options.get(GATHER_HIT_TIMES); JSONObject output = new JSONObject(); output.put("tool", CPUSamplerInstrument.ID); output.put("version", CPUSamplerInstrument.VERSION); JSONArray contexts = new JSONArray(); - for (CPUSamplerData samplerData : data.values()) { + for (CPUSamplerData samplerData : data) { contexts.put(perContextData(samplerData, gatheredHitTimes)); } output.put("contexts", contexts); diff --git a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/SVGSamplerOutput.java b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/SVGSamplerOutput.java index 662eb666f597..e7a3d23c96ee 100644 --- a/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/SVGSamplerOutput.java +++ b/tools/src/com.oracle.truffle.tools.profiler/src/com/oracle/truffle/tools/profiler/impl/SVGSamplerOutput.java @@ -24,31 +24,32 @@ */ package com.oracle.truffle.tools.profiler.impl; -import com.oracle.truffle.api.source.Source; -import com.oracle.truffle.api.source.SourceSection; -import com.oracle.truffle.api.TruffleContext; -import com.oracle.truffle.tools.profiler.CPUSampler; -import com.oracle.truffle.tools.profiler.CPUSamplerData; -import com.oracle.truffle.tools.profiler.ProfilerNode; -import org.graalvm.shadowed.org.json.JSONArray; -import org.graalvm.shadowed.org.json.JSONObject; - -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.ArrayDeque; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Scanner; +import org.graalvm.shadowed.org.json.JSONArray; +import org.graalvm.shadowed.org.json.JSONObject; + +import com.oracle.truffle.api.source.Source; +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; + final class SVGSamplerOutput { - public static void printSamplingFlameGraph(PrintStream out, Map data) { + public static void printSamplingFlameGraph(PrintStream out, List data) { GraphOwner graph = new GraphOwner(new StringBuilder(), data); graph.addComponent(new SVGFlameGraph(graph)); @@ -68,7 +69,7 @@ public static void printSamplingFlameGraph(PrintStream out, Map\n"); output.append("\n"); - output.append(String.format( + output.append(String.format(Locale.ROOT, "\n", width, height)); } @@ -78,7 +79,7 @@ public void include(String data) { } public static String allocateColor(int r, int g, int b) { - return String.format("rgb(%d, %d, %d)", r, g, b); + return String.format(Locale.ROOT, "rgb(%d, %d, %d)", r, g, b); } public static String startGroup(Map attributes) { @@ -86,7 +87,7 @@ public static String startGroup(Map attributes) { result.append(" attributes) { } if (attributes.containsKey("title")) { - result.append(String.format("%s", attributes.get("title"))); + result.append(String.format(Locale.ROOT, "%s", attributes.get("title"))); } if (attributes.containsKey("href")) { - result.append(String.format(" attributes) { } else { target = "_top"; } - result.append(String.format(" target=%s", target)); + result.append(String.format(Locale.ROOT, " target=%s", target)); } return result.toString(); } @@ -127,7 +128,7 @@ public static String startSubDrawing(Map attributes) { StringBuilder result = new StringBuilder(); result.append(" e : attributes.entrySet()) { - result.append(String.format("%s=\"%s\"", e.getKey(), e.getValue())); + result.append(String.format(Locale.ROOT, "%s=\"%s\"", e.getKey(), e.getValue())); result.append(" "); } result.append(">\n"); @@ -143,16 +144,17 @@ public static String endSubDrawing() { public static String fillRectangle(double x1, double y1, double w, double h, String fill, String extras, Map attributes) { StringBuilder result = new StringBuilder(); - result.append(String.format(" entry : attributes.entrySet()) { - result.append(String.format(" %s=\"%s\"", entry.getKey(), entry.getValue())); + result.append(String.format(Locale.ROOT, " %s=\"%s\"", entry.getKey(), entry.getValue())); } result.append("/>\n"); return result.toString(); } public static String ttfString(String color, String font, double size, double x, double y, String text, String loc, String extras) { - return String.format("%s\n", loc == null ? "left" : loc, x, y, size, font, color, + return String.format(Locale.ROOT, "%s\n", loc == null ? "left" : loc, x, y, size, font, + color, extras == null ? "" : extras, escape(text)); } @@ -212,7 +214,7 @@ private enum GraphColorMap { private static class GraphOwner implements SVGComponent { private final SVGSamplerOutput svg; - private final Map data; + private final List data; private ArrayList components; private Random random = new Random(); private Map languageColors; @@ -230,7 +232,7 @@ private static class GraphOwner implements SVGComponent { public final JSONArray sampleJsonKeys = new JSONArray(); public final JSONArray sampleData = new JSONArray(); - GraphOwner(StringBuilder output, Map data) { + GraphOwner(StringBuilder output, List data) { svg = new SVGSamplerOutput(output); this.data = data; components = new ArrayList<>(); @@ -267,8 +269,8 @@ public String css() { StringBuilder css = new StringBuilder(); css.append("\n"); css.append(" \n"); - css.append(String.format(" \n", background1())); - css.append(String.format(" \n", background2())); + css.append(String.format(Locale.ROOT, " \n", background1())); + css.append(String.format(Locale.ROOT, " \n", background2())); css.append(" \n"); css.append("\n"); css.append("