diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprof.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprof.java index 8ef4e9fb592f66cc0f89ae4b40c45ee331b81000..f6b5274db4285effd5d473bcfb84dbddca83d8ab 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprof.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprof.java @@ -137,6 +137,9 @@ public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFa @TruffleBoundary public void reportAllocation(RTypedValue data) { RprofState profState = RprofState.get(); + if (profState.memoryQuad == null) { + return; + } long size = RObjectSize.getObjectSize(data, Rprofmem.myIgnoreObjectHandler); if (data instanceof RAbstractVector) { if (size >= Rprofmem.LARGE_VECTOR) { diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java index dd4bcc5fb5864254da2fa32440e890f4cbf33d1b..a1be8ea5fe3b4abb8eacd139d58327e7367c394e 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java @@ -128,6 +128,14 @@ import com.oracle.truffle.r.nodes.builtin.fastr.FastrDqrls; import com.oracle.truffle.r.nodes.builtin.fastr.FastrDqrlsNodeGen; import com.oracle.truffle.r.nodes.unary.UnaryArithmeticBuiltinNode; import com.oracle.truffle.r.nodes.unary.UnaryArithmeticSpecial; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmem; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemNodeGen; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemShow; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemShowNodeGen; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemSnapshot; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemSnapshotNodeGen; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemSource; +import com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmemSourceNodeGen; import com.oracle.truffle.r.nodes.unary.UnaryNotNode; import com.oracle.truffle.r.nodes.unary.UnaryNotNodeGen; import com.oracle.truffle.r.runtime.RVisibility; @@ -434,6 +442,10 @@ public class BasePackage extends RBuiltinPackage { add(FastRTrace.Untrace.class, FastRTraceFactory.UntraceNodeGen::create); add(FastRTree.class, FastRTreeNodeGen::create); add(FastRTreeStats.class, FastRTreeStatsNodeGen::create); + add(FastRprofmem.class, FastRprofmemNodeGen::create); + add(FastRprofmemShow.class, FastRprofmemShowNodeGen::create); + add(FastRprofmemSource.class, FastRprofmemSourceNodeGen::create); + add(FastRprofmemSnapshot.class, FastRprofmemSnapshotNodeGen::create); add(FileFunctions.BaseName.class, FileFunctionsFactory.BaseNameNodeGen::create); add(FileFunctions.DirCreate.class, FileFunctionsFactory.DirCreateNodeGen::create); add(FileFunctions.DirExists.class, FileFunctionsFactory.DirExistsNodeGen::create); diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IsNA.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IsNA.java index 166728e6642771dc131f3944539d2638e4ad2bc0..05cbcbc30395a817ec2ee19a832c87018dced4ef 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IsNA.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/IsNA.java @@ -207,7 +207,7 @@ public abstract class IsNA extends RBuiltinNode.Arg1 { // fallback @Fallback protected byte isNA(Object value) { - warning(RError.Message.IS_NA_TO_NON_VECTOR, ((RTypedValue) value).getRType().getName()); + warning(RError.Message.IS_NA_TO_NON_VECTOR, value instanceof RTypedValue ? ((RTypedValue) value).getRType().getName() : value); return RRuntime.LOGICAL_FALSE; } diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java index 9cfc3ee40d25f18841cf2d5571c26f30d21f4cdc..dfd0f56b4b0b838e32dd325082c79f321c61e51c 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java @@ -204,7 +204,7 @@ public class FastRInterop { @Specialization(guards = "!isRMissing(value)") @TruffleBoundary - protected Object exportSymbol(String name, RTypedValue value) { + protected Object exportSymbol(String name, TruffleObject value) { if (name == null) { throw error(RError.Message.INVALID_ARGUMENT, "name"); } diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmem.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmem.java new file mode 100644 index 0000000000000000000000000000000000000000..5b5b1c975a8c5ee6fe522829cb7ebdc7be7f0ba3 --- /dev/null +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmem.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016, 2017, 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. + * + * 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.r.nodes.builtin.fastr.memprof; + +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.eq; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.singleElement; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean; +import static com.oracle.truffle.r.runtime.RVisibility.OFF; +import static com.oracle.truffle.r.runtime.builtins.RBehavior.IO; +import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerInstrument; +import com.oracle.truffle.api.vm.PolyglotEngine; +import com.oracle.truffle.api.vm.PolyglotRuntime.Instrument; +import com.oracle.truffle.r.nodes.builtin.RBuiltinNode; +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.builtins.RBuiltin; +import com.oracle.truffle.r.runtime.context.RContext; +import com.oracle.truffle.r.runtime.data.RNull; + +@RBuiltin(name = ".fastr.profmem", visibility = OFF, kind = PRIMITIVE, parameterNames = {"on"}, behavior = IO) +public abstract class FastRprofmem extends RBuiltinNode.Arg1 { + + public static final String STACKS_VIEW = "stacks"; + public static final String HOTSPOTS_VIEW = "hotspots"; + + static { + Casts casts = new Casts(FastRprofmem.class); + casts.arg("on").asLogicalVector().mustBe(singleElement()).findFirst().map(toBoolean()); + } + + static void castViewArg(Casts casts) { + casts.arg("view").asStringVector().mustBe(singleElement()).findFirst().mustBe(eq("stacks").or(eq("hotspots"))); + } + + static void castSnapshotArg(Casts casts) { + casts.arg("snapshot").mustBe(TruffleObject.class); + } + + @Specialization + @TruffleBoundary + public Object doProfMem(boolean on) { + PolyglotEngine vm = RContext.getInstance().getVM(); + if (vm != null) { + Instrument profilerInstr = vm.getRuntime().getInstruments().get(MemAllocProfilerInstrument.ID); + if (profilerInstr != null && profilerInstr.isEnabled() != on) { + profilerInstr.setEnabled(on); + } + } else { + throw error(RError.Message.GENERIC, "No context VM found"); + } + return RNull.instance; + } + + static MemAllocProfilerPrinter getProfilerPrinter() { + PolyglotEngine vm = RContext.getInstance().getVM(); + MemAllocProfilerPrinter profPrinter = null; + if (vm != null) { + Instrument profilerInstr = vm.getRuntime().getInstruments().get(MemAllocProfilerInstrument.ID); + if (profilerInstr != null && profilerInstr.isEnabled()) { + profPrinter = profilerInstr.lookup(MemAllocProfilerPrinter.class); + } + } + + if (profPrinter == null) { + profPrinter = new MemAllocProfilerPrinter(System.out); + } + + return profPrinter; + } + +} diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemShow.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemShow.java new file mode 100644 index 0000000000000000000000000000000000000000..b91d1b1de25cb5a3653aea0248d688f8cc54784f --- /dev/null +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemShow.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016, 2017, 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. + * + * 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.r.nodes.builtin.fastr.memprof; + +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.singleElement; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean; +import static com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmem.castSnapshotArg; +import static com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmem.castViewArg; +import static com.oracle.truffle.r.runtime.RVisibility.OFF; +import static com.oracle.truffle.r.runtime.builtins.RBehavior.IO; +import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.r.nodes.builtin.RBuiltinNode; +import com.oracle.truffle.r.runtime.RRuntime; +import com.oracle.truffle.r.runtime.builtins.RBuiltin; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerPaths; + +@RBuiltin(name = ".fastr.profmem.show", visibility = OFF, kind = PRIMITIVE, parameterNames = {"levels", "desc", "id", "printParents", "view", "snapshot"}, behavior = IO) +public abstract class FastRprofmemShow extends RBuiltinNode.Arg6 { + + static { + Casts casts = new Casts(FastRprofmemShow.class); + casts.arg("levels").asIntegerVector().mustBe(singleElement()).findFirst().replaceNA(Integer.MAX_VALUE); + casts.arg("desc").asLogicalVector().mustBe(singleElement()).findFirst().map(toBoolean()); + casts.arg("id").returnIf(nullValue()).asIntegerVector().mustBe(singleElement()).findFirst().replaceNA(Integer.MAX_VALUE); + casts.arg("printParents").asLogicalVector().mustBe(singleElement()).findFirst().map(toBoolean()); + castViewArg(casts); + castSnapshotArg(casts); + } + + @Override + public Object[] getDefaultParameterValues() { + return new Object[]{RRuntime.INT_NA, RRuntime.LOGICAL_TRUE, RNull.instance, RRuntime.LOGICAL_FALSE, FastRprofmem.STACKS_VIEW, RNull.instance}; + } + + @Specialization + public Object doProfMem(int levels, boolean desc, @SuppressWarnings("unused") RNull n, boolean printParents, String view, TruffleObject snapshot) { + return show(levels, desc, null, printParents, view, snapshot); + } + + @Specialization + public Object doProfMem(int levels, boolean desc, int entryId, boolean printParents, String view, TruffleObject snapshot) { + return show(levels, desc, entryId, printParents, view, snapshot); + } + + @TruffleBoundary + private static Object show(int levels, boolean desc, Integer entryId, boolean printParents, String view, TruffleObject snapshotTO) { + MemAllocProfilerPaths snapshot = MemAllocProfilerPaths.fromTruffleObject(snapshotTO); + return show(levels, desc, entryId, printParents, view, snapshot); + } + + private static Object show(int levels, boolean desc, Integer entryId, boolean printParents, String view, MemAllocProfilerPaths snapshot) { + MemAllocProfilerPaths usedSnapshot = snapshot; + if (FastRprofmem.HOTSPOTS_VIEW.equals(view)) { + usedSnapshot = usedSnapshot.toHotSpots(); + } + FastRprofmem.getProfilerPrinter().show(usedSnapshot, entryId, levels, desc, printParents); + return RNull.instance; + } +} diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemSnapshot.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemSnapshot.java new file mode 100644 index 0000000000000000000000000000000000000000..9c04dc658bc114f5bd21ddb4001aa2aa4a5323d4 --- /dev/null +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemSnapshot.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 2017, 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. + * + * 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.r.nodes.builtin.fastr.memprof; + +import static com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmem.castViewArg; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.singleElement; +import static com.oracle.truffle.r.runtime.RVisibility.OFF; +import static com.oracle.truffle.r.runtime.builtins.RBehavior.IO; +import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.r.nodes.builtin.RBuiltinNode; +import com.oracle.truffle.r.runtime.builtins.RBuiltin; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerPaths; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerStacks; + +@RBuiltin(name = ".fastr.profmem.snapshot", visibility = OFF, kind = PRIMITIVE, parameterNames = {"name", "view"}, behavior = IO) +public abstract class FastRprofmemSnapshot extends RBuiltinNode.Arg2 { + + static { + Casts casts = new Casts(FastRprofmemSnapshot.class); + casts.arg("name").asStringVector().mustBe(singleElement()).findFirst(); + castViewArg(casts); + } + + @Override + public Object[] getDefaultParameterValues() { + return new Object[]{RNull.instance, FastRprofmem.STACKS_VIEW}; + } + + @Specialization + @TruffleBoundary + public TruffleObject makeSnapshot(String name, String view) { + MemAllocProfilerPaths snapshot = MemAllocProfilerStacks.getInstance().getStackPaths().getOrMakeSnapshot(name); + + if (FastRprofmem.HOTSPOTS_VIEW.equals(view)) { + snapshot = snapshot.toHotSpots(); + } + + return snapshot.toTruffleObject(); + } + +} diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemSource.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemSource.java new file mode 100644 index 0000000000000000000000000000000000000000..5339bbaac4633efe60166d41b95cb4b33f8b61a3 --- /dev/null +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/FastRprofmemSource.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016, 2017, 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. + * + * 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.r.nodes.builtin.fastr.memprof; + +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.singleElement; +import static com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmem.castSnapshotArg; +import static com.oracle.truffle.r.nodes.builtin.fastr.memprof.FastRprofmem.castViewArg; +import static com.oracle.truffle.r.runtime.RVisibility.OFF; +import static com.oracle.truffle.r.runtime.builtins.RBehavior.IO; +import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.r.nodes.builtin.RBuiltinNode; +import com.oracle.truffle.r.runtime.RRuntime; +import com.oracle.truffle.r.runtime.builtins.RBuiltin; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerPaths; + +@RBuiltin(name = ".fastr.profmem.source", visibility = OFF, kind = PRIMITIVE, parameterNames = {"id", "view", "snapshot"}, behavior = IO) +public abstract class FastRprofmemSource extends RBuiltinNode.Arg3 { + + static { + Casts casts = new Casts(FastRprofmemSource.class); + casts.arg("id").asIntegerVector().mustBe(singleElement()).findFirst().replaceNA(Integer.MAX_VALUE); + castViewArg(casts); + castSnapshotArg(casts); + } + + @Override + public Object[] getDefaultParameterValues() { + return new Object[]{RRuntime.INT_NA, FastRprofmem.STACKS_VIEW, RNull.instance}; + } + + @Specialization + @TruffleBoundary + public Object showSource(int entryId, String view, TruffleObject snapshotTO) { + MemAllocProfilerPaths paths = MemAllocProfilerPaths.fromTruffleObject(snapshotTO); + return showSource(entryId, view, paths); + + } + + private static Object showSource(int entryId, String view, MemAllocProfilerPaths snap) { + MemAllocProfilerPaths snapshot = snap; + if (FastRprofmem.HOTSPOTS_VIEW.equals(view)) { + snapshot = snapshot.toHotSpots(); + } + + FastRprofmem.getProfilerPrinter().source(snapshot, entryId); + + return RNull.instance; + } + +} diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/MemAllocProfilerPrinter.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/MemAllocProfilerPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..c3cda91fda8596729ce54f97f3c9b1ab2cf4a595 --- /dev/null +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/memprof/MemAllocProfilerPrinter.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017, 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. + * + * 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.r.nodes.builtin.fastr.memprof; + +import java.io.PrintStream; +import java.util.Iterator; + +import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerPaths; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerStacks; + +/** + * A utility class that can be used for developing simple command line profiling tools. It works on + * top of the {@link MemAllocProfilerStacks memory allocations model}. + * + * <pre> + * MemAllocProfilerPrinter memAllocPrinter = new MemAllocProfilerPrinter(System.out); + * // Print the whole hierarchy of allocations + * memAllocPrinter.show(null, entryId.MAX_VALUE, true, true); + * // Print the source section associated with the allocation entry 103 + * memAllocPrinter.source(103); + * </pre> + */ +public final class MemAllocProfilerPrinter { + + private final PrintStream out; + + /** + * @param out the output stream used to print the output + */ + public MemAllocProfilerPrinter(PrintStream out) { + this.out = out; + } + + /** + * Show the allocations hierarchy. + * + * @param paths the allocation paths + * @param entryId the id of the top allocation entry. If <code>null</code> the absolute root + * entry is used. + * @param levels the maximum number of levels of the hierarchy to print + * @param desc if true allocation entries will be sorted in the descending order below their + * parent entries + * @param printParents instructs to print the parent entries of the top entry. It makes sense + * only if a sub-hierarchy is printed. + */ + public void show(MemAllocProfilerPaths paths, Integer entryId, int levels, boolean desc, boolean printParents) { + MemAllocProfilerPaths.Entry rootEntry = null; + if (entryId != null) { + rootEntry = paths.getEntry(entryId); + } + + paths.traverse(rootEntry, stack -> { + if (stack.peek().getStats().getCount() == 0 && stack.size() != levels) { + return; + } + + StringBuilder sb = new StringBuilder(); + Iterator<MemAllocProfilerPaths.Entry> iter = stack.descendingIterator(); + while (iter.hasNext()) { + MemAllocProfilerPaths.Entry entry = iter.next(); + if (entry.getId() == 0) { + continue; + } + sb.append('/').append(entry.getName()).append("[id=").append(entry.getId()).append(']'); + } + MemAllocProfilerPaths.Entry entry = stack.peek(); + sb.append(" { size: ").append(entry.getAllocatedAggr()).append(", count: ").append(entry.getCountAggr()).append(" }"); + out.println(sb.toString()); + }, new MemAllocProfilerStacks.AlocatedAggrComparator(desc), levels, printParents); + } + + /** + * Print the source section associated with the given allocation entry id. + * + * @param entryId the entry id + */ + public void source(MemAllocProfilerPaths paths, int entryId) { + MemAllocProfilerPaths.Entry entry = paths.getEntry(entryId); + if (entry == null) { + out.format("No entry found for id %s\n", entryId); + } else { + SourceSection sel = entry.getSourceSection(); + StringBuilder sb = new StringBuilder(); + sb.append("{ size: ").append(entry.getAllocatedAggr()).append(", localSize: ").append(entry.getStats().getAllocated()).append(", count: ").append(entry.getCountAggr()).append( + ", localCount: ").append(entry.getStats().getCount()).append(" }"); + out.println(sb); + + if (sel == null) { + out.println("No source available"); + } else { + out.format("<<< %s at %s:%s\n", entry.getName(), sel.getStartLine(), sel.getStartColumn()); + out.println(sel.getCode()); + out.format(">>> %s at %s:%s\n", entry.getName(), sel.getEndLine(), sel.getEndColumn()); + } + } + } +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java index 7a74c11d3dfb83499df7e0451068102d70283917..b8f37f4b22ff8ed51c3e44747111c401049df864 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java @@ -22,6 +22,8 @@ */ package com.oracle.truffle.r.runtime.context; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.Closeable; import java.lang.ref.WeakReference; import java.nio.file.Path; @@ -36,6 +38,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage.Env; +import com.oracle.truffle.api.instrumentation.AllocationReporter; import com.oracle.truffle.api.instrumentation.Instrumenter; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.vm.PolyglotEngine; @@ -62,6 +65,7 @@ import com.oracle.truffle.r.runtime.conn.StdConnections; import com.oracle.truffle.r.runtime.data.RFunction; import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.RTruffleObject; +import com.oracle.truffle.r.runtime.data.RTypedValue; import com.oracle.truffle.r.runtime.env.REnvironment; import com.oracle.truffle.r.runtime.ffi.DLL; import com.oracle.truffle.r.runtime.ffi.RFFIFactory; @@ -69,6 +73,7 @@ import com.oracle.truffle.r.runtime.instrument.InstrumentationState; import com.oracle.truffle.r.runtime.nodes.RCodeBuilder; import com.oracle.truffle.r.runtime.nodes.RSyntaxNode; import com.oracle.truffle.r.runtime.rng.RRNG; +import com.oracle.truffle.r.runtime.data.RDataFactory; /** * Encapsulates the runtime state ("context") of an R session. All access to that state from the @@ -327,6 +332,8 @@ public final class RContext implements RTruffleObject { public final WeakHashMap<Source, REnvironment> sourceRefEnvironments = new WeakHashMap<>(); public final WeakHashMap<Path, REnvironment> srcfileEnvironments = new WeakHashMap<>(); + private final AllocationReporter allocationReporter; + private ContextState[] contextStates() { return new ContextState[]{stateREnvVars, stateRProfile, stateTempPath, stateROptions, stateREnvironment, stateRErrorHandling, stateRConnection, stateStdConnections, stateRNG, stateRFFI, stateRSerialize, @@ -381,6 +388,9 @@ public final class RContext implements RTruffleObject { this.stateDLL = DLL.ContextStateImpl.newContextState(); this.engine = RContext.getRRuntimeASTAccess().createEngine(this); state.add(State.CONSTRUCTED); + + this.allocationReporter = env.lookup(AllocationReporter.class); + this.allocationReporter.addPropertyChangeListener(ALLOCATION_ACTIVATION_LISTENER); } /** @@ -522,6 +532,8 @@ public final class RContext implements RTruffleObject { threadLocalContext.set(info.getParent()); } state = EnumSet.of(State.DESTROYED); + + this.allocationReporter.removePropertyChangeListener(ALLOCATION_ACTIVATION_LISTENER); } } @@ -785,4 +797,16 @@ public final class RContext implements RTruffleObject { public static Class<? extends TruffleRLanguage> getTruffleRLanguage() { return getRRuntimeASTAccess().getTruffleRLanguage(); } + + public AllocationReporter getAllocationReporter() { + return this.allocationReporter; + } + + private static final PropertyChangeListener ALLOCATION_ACTIVATION_LISTENER = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + RDataFactory.setTracingState(event.getNewValue() == Boolean.TRUE); + } + }; + } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java index 2192e8f0598c9b472336e793a2cefe92a5593ef8..c3c0ff363ab66b62382450ff2396d1f30faa6170 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java @@ -33,6 +33,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.instrumentation.AllocationReporter; import com.oracle.truffle.api.utilities.CyclicAssumption; import com.oracle.truffle.r.runtime.FastROptions; import com.oracle.truffle.r.runtime.RCaller; @@ -40,9 +41,11 @@ import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.Utils; import com.oracle.truffle.r.runtime.builtins.RBuiltinDescriptor; +import com.oracle.truffle.r.runtime.context.RContext; import com.oracle.truffle.r.runtime.data.RPromise.Closure; import com.oracle.truffle.r.runtime.data.RPromise.EagerFeedback; import com.oracle.truffle.r.runtime.data.RPromise.PromiseState; +import com.oracle.truffle.r.runtime.data.model.RAbstractVector; import com.oracle.truffle.r.runtime.env.REnvironment; import com.oracle.truffle.r.runtime.ffi.DLL.SymbolHandle; import com.oracle.truffle.r.runtime.gnur.SEXPTYPE; @@ -157,6 +160,7 @@ public final class RDataFactory { } public static RComplexVector createComplexVector(double[] data, boolean complete, int[] dims, RStringVector names) { + return traceDataCreated(new RComplexVector(data, complete, dims, names)); } @@ -219,6 +223,7 @@ public final class RDataFactory { } public static RLogicalVector createLogicalVector(byte[] data, boolean complete, int[] dims, RStringVector names) { + return traceDataCreated(new RLogicalVector(data, complete, dims, names)); } @@ -228,25 +233,30 @@ public final class RDataFactory { public static RIntSequence createAscendingRange(int start, int end) { assert start <= end; + return traceDataCreated(new RIntSequence(start, 1, end - start + 1)); } public static RIntSequence createDescendingRange(int start, int end) { assert start > end; + return traceDataCreated(new RIntSequence(start, -1, start - end + 1)); } public static RIntSequence createIntSequence(int start, int stride, int length) { + return traceDataCreated(new RIntSequence(start, stride, length)); } public static RDoubleSequence createAscendingRange(double start, double end) { assert start <= end; + return traceDataCreated(new RDoubleSequence(start, 1, (int) ((end - start) + 1))); } public static RDoubleSequence createDescendingRange(double start, double end) { assert start > end; + return traceDataCreated(new RDoubleSequence(start, -1, (int) ((start - end) + 1))); } @@ -388,6 +398,7 @@ public final class RDataFactory { } public static RList createList(Object[] data, int[] newDimensions, RStringVector names) { + return traceDataCreated(new RList(data, newDimensions, names)); } @@ -462,11 +473,19 @@ public final class RDataFactory { } public static Object createLangPairList(int size) { - return size == 0 ? RNull.instance : traceDataCreated(RPairList.create(size, SEXPTYPE.LANGSXP)); + if (size == 0) { + return RNull.instance; + } else { + return traceDataCreated(RPairList.create(size, SEXPTYPE.LANGSXP)); + } } public static Object createPairList(int size) { - return size == 0 ? RNull.instance : traceDataCreated(RPairList.create(size)); + if (size == 0) { + return RNull.instance; + } else { + return traceDataCreated(RPairList.create(size)); + } } public static RPairList createPairList() { @@ -474,6 +493,7 @@ public final class RDataFactory { } public static RPairList createPairList(Object car) { + return traceDataCreated(new RPairList(car, RNull.instance, RNull.instance, null)); } @@ -546,25 +566,54 @@ public final class RDataFactory { */ private static Deque<Listener> listeners = new ConcurrentLinkedDeque<>(); - @CompilationFinal private static boolean enabled; + + @CompilationFinal private static byte enabled = RRuntime.LOGICAL_NA; + private static final CyclicAssumption noAllocationTracingAssumption = new CyclicAssumption("data allocation"); public static void setTracingState(boolean newState) { - if (enabled != newState) { + byte newStateLogical = RRuntime.asLogical(newState); + if (enabled != newStateLogical) { noAllocationTracingAssumption.invalidate(); - enabled = newState; + enabled = newStateLogical; } } private static <T> T traceDataCreated(T data) { - if (enabled) { + if (RRuntime.isNA(enabled)) { + RContext ctx = RContext.getThreadLocalInstance(); + if (ctx != null) { + setTracingState(ctx.getAllocationReporter().isActive()); + } + } + + if (enabled == RRuntime.LOGICAL_TRUE) { + + if (noAllocationTracingAssumption.getAssumption().isValid()) { + reportAllocation(data); + } + for (Listener listener : listeners) { listener.reportAllocation((RTypedValue) data); } } + return data; } + @TruffleBoundary + private static void reportAllocation(Object data) { + RContext ctx = RContext.getThreadLocalInstance(); + assert ctx != null; + AllocationReporter allocationReporter = ctx.getAllocationReporter(); + + allocationReporter.onEnter(null, 0, AllocationReporter.SIZE_UNKNOWN); + + long size = data instanceof RTypedValue ? getSize((RTypedValue) data) : AllocationReporter.SIZE_UNKNOWN; + allocationReporter.onReturnValue(data, 0, size); + + } + public interface Listener { /** * Invoked when an instance of an {@link RTypedValue} is created. Note that the initial @@ -590,4 +639,28 @@ public final class RDataFactory { Arrays.fill(data, RNull.instance); return data; } + + private static long getSize(RTypedValue data) { + long multiplier = 8; + switch (data.getRType()) { + case Complex: + multiplier = 16; + break; + case Integer: + multiplier = 4; + break; + case Logical: + case Raw: + multiplier = 1; + break; + } + if (data instanceof RSequence) { + return 32 + 2 * multiplier; + } else if (data instanceof RAbstractVector) { + return 32 + ((RAbstractVector) data).getLength() * multiplier; + } else { + // take a default value for non-vector objects + return 64; + } + } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerInstrument.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerInstrument.java new file mode 100644 index 0000000000000000000000000000000000000000..11763843642b44594edc33901aa1f8a942dc89b4 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerInstrument.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017, 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. + * + * 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.r.runtime.instrument.memprof; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.AllocationEvent; +import com.oracle.truffle.api.instrumentation.AllocationEventFilter; +import com.oracle.truffle.api.instrumentation.AllocationListener; +import com.oracle.truffle.api.instrumentation.EventBinding; +import com.oracle.truffle.api.instrumentation.EventContext; +import com.oracle.truffle.api.instrumentation.ExecutionEventNode; +import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; +import com.oracle.truffle.api.instrumentation.Instrumenter; +import com.oracle.truffle.api.instrumentation.SourceSectionFilter; +import com.oracle.truffle.api.instrumentation.StandardTags; +import com.oracle.truffle.api.instrumentation.TruffleInstrument; +import com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration; +import com.oracle.truffle.api.source.SourceSection; + +@Registration(name = "MemAllocProfiler", id = MemAllocProfilerInstrument.ID) +public class MemAllocProfilerInstrument extends TruffleInstrument { + + public static final String ID = "mem-alloc-profiler"; + + private EventBinding<MemAllocEventFactory> allocationEventBinding; + + @Override + protected void onCreate(TruffleInstrument.Env env) { + env.registerService(this); + + Instrumenter instrumenter = env.getInstrumenter(); + MemAllocEventFactory eventFactory = new MemAllocEventFactory(env); + SourceSectionFilter.Builder builder = SourceSectionFilter.newBuilder(); + SourceSectionFilter filter = builder.tagIs(StandardTags.StatementTag.class).build(); + instrumenter.attachFactory(filter, eventFactory); + allocationEventBinding = instrumenter.attachAllocationListener(AllocationEventFilter.newBuilder().build(), eventFactory); + + env.registerService(eventFactory.memAllocStacks); + } + + @Override + protected void onDispose(Env env) { + allocationEventBinding.dispose(); + } + + public static class MemAllocEventFactory implements ExecutionEventNodeFactory, AllocationListener { + + protected final Env env; + protected final MemAllocProfilerStacks memAllocStacks = MemAllocProfilerStacks.getInstance(); + + protected MemAllocEventFactory(final Env env) { + this.env = env; + } + + @Override + @TruffleBoundary + public void onEnter(AllocationEvent event) { + } + + @Override + @TruffleBoundary + public void onReturnValue(AllocationEvent event) { + memAllocStacks.reportAllocation(event.getNewSize() - event.getOldSize()); + } + + public MemAllocProfilerStacks getStacks() { + return memAllocStacks; + } + + public void dispose() { + memAllocStacks.clear(); + } + + @Override + public ExecutionEventNode create(final EventContext ec) { + return new ExecutionEventNode() { + + @Override + protected void onEnter(VirtualFrame frame) { + pushEntry(); + } + + @Override + public void onReturnValue(VirtualFrame vFrame, Object result) { + popEntry(); + } + + @Override + protected void onReturnExceptional(VirtualFrame frame, Throwable exception) { + popEntry(); + } + + @TruffleBoundary + private void pushEntry() { + SourceSection src = ec.getInstrumentedSourceSection(); + memAllocStacks.push(ec.getInstrumentedNode().getRootNode().getName(), src); + } + + @TruffleBoundary + private void popEntry() { + memAllocStacks.pop(); + } + + }; + } + } +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerPaths.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerPaths.java new file mode 100644 index 0000000000000000000000000000000000000000..75ca4b5c90c3a2d68c36d9fe8310e4962f18dcb5 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerPaths.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2017, 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. + * + * 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.r.runtime.instrument.memprof; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.java.JavaInterop; +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; + +public final class MemAllocProfilerPaths { + private final AtomicLong version = new AtomicLong(); + private final AtomicInteger idGen = new AtomicInteger(); + private final Map<Integer, Entry> entryMap = new ConcurrentHashMap<>(); + private volatile Entry root = new Entry(this, null, "", null); + + private MemAllocProfilerPaths hotSpotsView; + private long hotSpotsViewVersion; + + /** + * Clear the model. + */ + public synchronized void clear() { + idGen.set(0); + entryMap.clear(); + root = new Entry(this, null, "", null); + } + + /** + * + * @param id the id of the allocation entry + * @return the allocation entry + */ + public Entry getEntry(int id) { + return entryMap.get(id); + } + + /** + * @return the root entry. + */ + public Entry getRootEntry() { + return root; + } + + public TruffleObject toTruffleObject() { + return JavaInterop.asTruffleObject(this); + } + + public static MemAllocProfilerPaths fromTruffleObject(TruffleObject to) { + return JavaInterop.asJavaObject(MemAllocProfilerPaths.class, to); + } + + /** + * Store the snapshot of the current paths hierarchy. + * + * @param name the snapshot name + * @return the snapshot + */ + public MemAllocProfilerPaths getOrMakeSnapshot(String name) { + return clonePaths(); + } + + public synchronized MemAllocProfilerPaths toHotSpots() { + long curVer = version.get(); + if (curVer == hotSpotsViewVersion && hotSpotsView != null) { + return hotSpotsView; + } + hotSpotsViewVersion = curVer; + hotSpotsView = invert().groupBySrcSection(); + return hotSpotsView; + } + + @SuppressWarnings("unused") + synchronized MemAllocProfilerPaths clonePaths() { + MemAllocProfilerPaths clonedPaths = new MemAllocProfilerPaths(); + new Entry(clonedPaths, null, root); + return clonedPaths; + } + + MemAllocProfilerPaths invert() { + List<Entry> inverted = new ArrayList<>(); + MemAllocProfilerPaths targetPaths = new MemAllocProfilerPaths(); + for (Entry entry : entryMap.values()) { + inverted.add(entry.invert(targetPaths)); + } + return targetPaths; + } + + MemAllocProfilerPaths groupBySrcSection() { + MemAllocProfilerPaths targetPaths = new MemAllocProfilerPaths(); + + List<Entry> tops = new ArrayList<>(); + for (Entry synthSubRoot : root.children.values()) { + tops.addAll(synthSubRoot.children.values()); + } + + groupBySrcSection(targetPaths, targetPaths.root, tops); + return targetPaths; + } + + private void groupBySrcSection(MemAllocProfilerPaths targetPaths, Entry newParent, Collection<Entry> nonGroupedEntries) { + Map<SourceSection, List<Entry>> entriesBySection = new HashMap<>(); + + for (Entry nonGroupedEntry : nonGroupedEntries) { + List<Entry> group = entriesBySection.get(nonGroupedEntry.sourceSection); + if (group == null) { + group = new ArrayList<>(); + entriesBySection.put(nonGroupedEntry.sourceSection, group); + } + group.add(nonGroupedEntry); + } + + for (Map.Entry<SourceSection, List<Entry>> mapEntry : entriesBySection.entrySet()) { + List<Entry> nonGroupedChildren = new ArrayList<>(); + Entry reducedEntry = null; + for (Entry entryForSameSect : mapEntry.getValue()) { + if (reducedEntry == null) { + reducedEntry = new Entry(targetPaths, newParent, entryForSameSect.name, entryForSameSect.sourceSection); + } + reducedEntry.stats.add(entryForSameSect.stats); + nonGroupedChildren.addAll(entryForSameSect.children.values()); + } + assert reducedEntry != null; + groupBySrcSection(targetPaths, reducedEntry, nonGroupedChildren); + } + } + + /** + * Traverse the allocations hierarchy. This method is usually used by profiling tools for + * displaying the allocations. + * + * @param startEntry the start entry from which the traversal begins. It can be null to denote + * the absolute root. + * @param consumer the consumer receiving the current stack (i.e. the path of entries to the + * root entry) + * @param childrenEntriesComparator the comparator used to sort children + * @param levels the maximum number of hierarchy levels to traverse + * @param prependParentEntries include the parent entries of the root entry + */ + public void traverse(Entry startEntry, Consumer<Deque<Entry>> consumer, Comparator<Entry> childrenEntriesComparator, int levels, boolean prependParentEntries) { + Entry se = startEntry; + if (se == null) { + se = root; + } + traverseEntry(se, consumer, childrenEntriesComparator, levels, prependParentEntries); + } + + private void traverseEntry(Entry rootEntry, Consumer<Deque<Entry>> consumer, Comparator<Entry> childrenEntriesComparator, int levels, boolean prependParentEntries) { + ArrayDeque<Entry> stack = new ArrayDeque<>(); + if (prependParentEntries) { + Entry e = rootEntry; + while (e != null) { + stack.addLast(e); + e = e.parent; + } + } else { + stack.push(rootEntry); + } + traverseStack(stack, consumer, childrenEntriesComparator, levels); + } + + private void traverseStack(Deque<Entry> stack, Consumer<Deque<Entry>> consumer, Comparator<Entry> childrenEntriesComparator, int levels) { + if (levels <= 0) { + return; + } + + consumer.accept(stack); + Collection<Entry> children = stack.peek().children.values(); + Collection<Entry> sortedChildren; + + if (childrenEntriesComparator != null) { + sortedChildren = new TreeSet<>(childrenEntriesComparator); + sortedChildren.addAll(children); + } else { + sortedChildren = children; + } + + for (Entry childEntry : sortedChildren) { + stack.push(childEntry); + traverseStack(stack, consumer, childrenEntriesComparator, levels - 1); + stack.pop(); + } + } + + public static final class Entry { + // private static final SourceSection UNAVAILABLE_SECTION = + // Source.newBuilder("").name("unavailable").mimeType("").build().createUnavailableSection(); + + final MemAllocProfilerPaths paths; + final int id; + final String name; + final SourceSection sourceSection; + final Entry parent; + final Map<SourceSection, Entry> children = new ConcurrentHashMap<>(); + final Stats stats = new Stats() { + + @Override + public void set(long allocated, long count) { + synchronized (paths) { + super.set(allocated, count); + paths.version.incrementAndGet(); + } + } + + }; + + Entry(MemAllocProfilerPaths paths, Entry parent, String name, SourceSection sourceSection) { + this.paths = paths; + synchronized (paths) { + this.id = paths.idGen.getAndIncrement(); + this.parent = parent; + this.name = name; + this.sourceSection = sourceSection == null ? Source.newBuilder("").name(name).mimeType("").build().createUnavailableSection() : sourceSection; + this.paths.entryMap.put(id, this); + if (parent != null) { + parent.children.put(this.sourceSection, this); + } + } + } + + @SuppressWarnings("unused") + Entry(MemAllocProfilerPaths paths, Entry parent, Entry original) { + this.paths = paths; + this.parent = parent; + assert parent == null || parent.paths == paths; + this.id = original.id; + this.name = original.name; + this.sourceSection = original.sourceSection; + this.paths.entryMap.put(id, this); + this.stats.add(original.stats); + + if (parent != null) { + parent.children.put(sourceSection, this); + } else { + paths.root = this; + } + + for (Entry origChild : original.children.values()) { + new Entry(paths, this, origChild); + } + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public SourceSection getSourceSection() { + return sourceSection; + } + + public Entry getParent() { + return parent; + } + + public Map<SourceSection, Entry> getChildren() { + return children; + } + + public synchronized Entry[] getChildrenAsArray() { + return children.values().toArray(new Entry[children.size()]); + } + + public Stats getStats() { + return stats; + } + + public long getAllocatedAggr() { + long a = stats.allocated; + for (Entry childEntry : children.values()) { + a += childEntry.getAllocatedAggr(); + } + return a; + } + + public long getCountAggr() { + long a = stats.count; + for (Entry childEntry : children.values()) { + a += childEntry.getCountAggr(); + } + return a; + } + + public long getAllocated() { + return stats.allocated; + } + + public long getCount() { + return stats.count; + } + + Entry invert(MemAllocProfilerPaths targetPaths) { + return invert(targetPaths, new Entry(targetPaths, targetPaths.root, "", null), stats); + } + + private Entry invert(MemAllocProfilerPaths targetPaths, Entry newParent, Stats rootStat) { + if (this == paths.root) { + return targetPaths.root; + } + Entry ie = new Entry(targetPaths, newParent, name, sourceSection); + if (parent.parent != null) { + Entry invertedParent = parent.invert(targetPaths, ie, rootStat); + ie.children.put(parent.sourceSection, invertedParent); + } else { + // Move the stats from the inverted node to its lowest child (which corresponds to + // the root in the original hierarchy). This way the stats of a parent entry in the + // hot-spot paths will be the aggregation of the children stats, i.e. the same fact + // as in the stacks paths. + ie.stats.add(rootStat); + } + return ie; + } + + Entry merge(MemAllocProfilerPaths targetPaths, Entry other) { + Entry merged = new Entry(targetPaths, this.parent, this.name, this.sourceSection); + merged.children.putAll(this.children); + merged.children.putAll(other.children); + merged.stats.add(this.stats); + merged.stats.add(other.stats); + return merged; + } + + @Override + public String toString() { + return String.format("{name: %s, alloc: %s, count: %s}", name, stats.allocated, stats.count); + } + } + + /** + * The summary allocation statistic. + */ + public static class Stats { + private volatile long allocated; + private volatile long count; + + /** + * @return the allocated memory in bytes + */ + public final long getAllocated() { + return this.allocated; + } + + /** + * @return the number of allocations + */ + public final long getCount() { + return count; + } + + /** + * Set the allocated memory in bytes and allocations count. + */ + public void set(long allocated, long count) { + this.allocated = allocated; + this.count = count; + } + + final void clear() { + set(0, 0); + } + + final void add(long alloc, long cnt) { + set(this.allocated + alloc, this.count + cnt); + } + + final void add(Stats other) { + add(other.allocated, other.count); + } + + } + +} diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerStacks.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerStacks.java new file mode 100644 index 0000000000000000000000000000000000000000..2a4ee643bf26c8511908cfd87a0870dba7e03700 --- /dev/null +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/memprof/MemAllocProfilerStacks.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2017, 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. + * + * 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.r.runtime.instrument.memprof; + +import java.util.Comparator; +import java.util.Deque; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerPaths.Entry; +import com.oracle.truffle.r.runtime.instrument.memprof.MemAllocProfilerPaths.Stats; +import com.oracle.truffle.api.source.SourceSection; + +/** + * This class represents the hierarchical model of memory allocations. It is designed as a + * singleton, since it is assumed that there is at most one profiling session at any moment. + */ +public final class MemAllocProfilerStacks { + + private static final MemAllocProfilerStacks instance = new MemAllocProfilerStacks(); + + final MemAllocProfilerPaths stackPaths = new MemAllocProfilerPaths(); + final ConcurrentHashMap<Thread, Deque<Entry>> stacks = new ConcurrentHashMap<>(); + final MemAllocProfilerPaths.Stats globalStats = new MemAllocProfilerPaths.Stats(); + + private MemAllocProfilerStacks() { + + } + + /** + * @return the single instance of the model + */ + public static MemAllocProfilerStacks getInstance() { + return instance; + } + + /** + * Clear the model. + */ + public synchronized void clear() { + stackPaths.clear(); + globalStats.clear(); + stacks.clear(); + } + + /** + * + * @return the stack paths + */ + public MemAllocProfilerPaths getStackPaths() { + return stackPaths; + } + + /** + * + * @param id the id of the allocation entry + * @return the allocation entry + */ + public Entry getEntry(int id) { + return stackPaths.getEntry(id); + } + + /** + * @return the global (overall) allocations statistic + */ + public Stats getGlobalStats() { + return globalStats; + } + + void push(String name, SourceSection section) { + Deque<Entry> stack = getStackForThread(); + Entry parentEntry = stack.peek(); + Entry entry = parentEntry.children.get(section); + if (entry == null) { + entry = new Entry(stackPaths, parentEntry, name, section); + } + stack.push(entry); + } + + private Deque<Entry> getStackForThread() { + return stacks.computeIfAbsent(Thread.currentThread(), (t) -> { + ConcurrentLinkedDeque<Entry> stk = new ConcurrentLinkedDeque<>(); + Entry stackRoot = new Entry(stackPaths, stackPaths.getRootEntry(), "<" + Thread.currentThread().getName() + ">", null); + stk.add(stackRoot); + return stk; + }); + } + + void pop() { + Deque<Entry> stack = stacks.get(Thread.currentThread()); + if (stack != null) { + // The stacks map might get cleared during the preceding execution of the instrumented + // node or its child node. Typically it happens if there is a function in a guest + // language turning off the profiler. + stack.pop(); + } + } + + void reportAllocation(long size) { + Deque<Entry> stack = stacks.get(Thread.currentThread()); + if (stack != null) { + Entry entry = stack.peek(); + globalStats.add(size, 1); + entry.stats.add(size, 1); + } + } + + public static final class AlocatedAggrComparator implements Comparator<Entry> { + + private final boolean desc; + + public AlocatedAggrComparator(boolean desc) { + this.desc = desc; + } + + @Override + public int compare(Entry e1, Entry e2) { + Long a1 = e1.getAllocatedAggr(); + Long a2 = e2.getAllocatedAggr(); + return desc ? a2.compareTo(a1) : a1.compareTo(a2); + } + } +} diff --git a/documentation/allocprof.md b/documentation/allocprof.md new file mode 100644 index 0000000000000000000000000000000000000000..3358da8b7721e521e009fbb2aa4a2127d47e951d --- /dev/null +++ b/documentation/allocprof.md @@ -0,0 +1,83 @@ +# FastR Allocation Profiler - Getting Started + +The basic usage is illustrated by the following examples: + +* Activating the profiling instrument +``` +.fastr.profmem(TRUE) +``` + +* Actual profiling +``` +example(glm) +``` +* Creating a snapshot +``` +s1 <- .fastr.profmem.snapshot("s1") +``` +* Displaying the result +``` +.fastr.profmem.show(snapshot=s1) +``` +producing: +``` +/<main>[id=0] { size: 35155856, count: 140515 } +/<main>[id=0]/example[id=2788] { size: 17197400, count: 87422 } +/<main>[id=0]/example[id=2788]/source[id=2987] { size: 16747568, count: 82327 } +/<main>[id=0]/example[id=2788]/source[id=2987]/source[id=3107] { size: 14999312, count: 76365 } +/<main>[id=0]/example[id=2788]/source[id=2987]/source[id=3107]/source[id=3108] { size: 10815880, count: 56889 } +/<main>[id=0]/example[id=2788]/source[id=2987]/source[id=3107]/source[id=3108]/eval[id=3109] { size: 10784176, count: 56823 } +/<main>[id=0]/example[id=2788]/source[id=2987]/source[id=3107]/source[id=3108]/eval[id=3109]/<no source>[id=8056] { size: 4317704, count: 684 } +/<main>[id=0]/example[id=2788]/source[id=2987]/source[id=3107]/source[id=3108]/eval[id=3109]/<no source>[id=8056]/<no source>[id=8058] { size: 4317704, count: 684 } +``` + +* Displaying the result, 3 top levels only +``` +.fastr.profmem.show(3, snapshot=s1) +``` +producing: +``` +/<main>[id=0] { size: 35140384, count: 140294 } +/<main>[id=0]/example[id=2788] { size: 17197400, count: 87422 } +/<main>[id=0]/example[id=2788]/source[id=2987] { size: 16747568, count: 82327 } +/<main>[id=0]/example[id=2788]/source[id=2803] { size: 292256, count: 3114 } +/<main>[id=0]/example[id=2788]/source[id=2969] { size: 5456, count: 47 } +/<main>[id=0]/example[id=2788]/source[id=2978] { size: 3024, count: 26 } +/<main>[id=0]/example[id=2788]/source[id=2789] { size: 824, count: 15 } +/<main>[id=0]/example[id=2788]/source[id=2796] { size: 112, count: 2 } +/<main>[id=0]/example[id=2788]/source[id=2973] { size: 88, count: 2 } +/<main>[id=0]/example[id=2788]/source[id=2986] { size: 56, count: 1 } +/<main>[id=0]/example[id=2788]/source[id=2795] { size: 32, count: 1 } +/<main>[id=0]/example[id=2788]/source[id=2977] { size: 0, count: 0 } +``` +* Showing the source associated with a stack entry +``` +# stack entry id = 2630 +.fastr.profmem.source(2630, snapshot=s1) +``` +producing: +``` +<<< at 53:9 +txt <- unlist(x) +>>> at 53:24 +``` +* Showing the hot-spots +``` +# single-level hospot view +.fastr.profmem.show(2, snapshot=s1, view = "hotspots") +# unlimited hostspot view +.fastr.profmem.show(snapshot=s1, view = "hotspots") +# show the source of the entry 2630 from the hostspot view on the snapshot s1 +.fastr.profmem.source(2630, view="hotspots", snapshot=s1) +``` +* Deactivating the profilinig instrument +``` +.fastr.profmem(FALSE) +``` + +The `.fastr.profmem.show` builtin offers several arguments for controlling the output: + +* `levels`: determines the maximum number of levels displayed +* `desc`: specifies the sorting order, which is TRUE by default, ie. descending +* `id`: displays only the sub tree of the specified stack entry +* `printParents`: forces printing the parent stack entries. It is meaningful in connection with a nun-NULL id parameter value only. \ No newline at end of file