diff --git a/com.oracle.truffle.r.agents/src/com/oracle/truffle/r/agents/objsize/ObjSizeAgent.java b/com.oracle.truffle.r.agents/src/com/oracle/truffle/r/agents/objsize/ObjSizeAgent.java
new file mode 100644
index 0000000000000000000000000000000000000000..073d6bf9a9587b005d09f1d185aa4ff9f4cb4c65
--- /dev/null
+++ b/com.oracle.truffle.r.agents/src/com/oracle/truffle/r/agents/objsize/ObjSizeAgent.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2016, 2016, 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.agents.objsize;
+
+import java.lang.instrument.Instrumentation;
+
+public class ObjSizeAgent {
+    private static Instrumentation instrumentation;
+
+    public static void agentmain(@SuppressWarnings("unused") String agentArgs, Instrumentation inst) {
+        instrumentation = inst;
+    }
+
+    public static long objectSize(Object obj) {
+        return instrumentation.getObjectSize(obj);
+    }
+}
diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguage.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguage.java
index dc9ba058337ef8f48d23465a6b0f6085c07ffc49..362e8af2e20c8ed5f4c31f5487ccab7ef277860b 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguage.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/TruffleRLanguage.java
@@ -41,7 +41,6 @@ import com.oracle.truffle.r.nodes.instrumentation.RSyntaxTags;
 import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RAccuracyInfo;
 import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.RPerfStats;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.RVersionInfo;
 import com.oracle.truffle.r.runtime.TempPathName;
@@ -69,7 +68,6 @@ public final class TruffleRLanguage extends TruffleLanguage<RContext> {
     private static void initialize() {
         try {
             Load_RFFIFactory.initialize(true);
-            RPerfStats.initialize();
             Locale.setDefault(Locale.ROOT);
             RAccuracyInfo.initialize();
             RVersionInfo.initialize();
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 db38227316cc29e6eec6064ae342363e887c6018..7deff193de910b2d8bd3cd91b6f23fa2a49d0515 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
@@ -22,9 +22,9 @@
  */
 package com.oracle.truffle.r.library.utils;
 
-import java.io.FileWriter;
+import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.function.Function;
@@ -57,8 +57,7 @@ import com.oracle.truffle.r.runtime.data.RTypedValue;
 import com.oracle.truffle.r.runtime.data.MemoryCopyTracer;
 import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
-import com.oracle.truffle.r.runtime.instrument.InstrumentationState.RprofState;
-import com.oracle.truffle.r.runtime.instrument.InstrumentationState.RprofState.MemoryQuad;
+import com.oracle.truffle.r.runtime.instrument.InstrumentationState;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
@@ -79,36 +78,31 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
  */
 public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFactory.Listener, MemoryCopyTracer.Listener {
 
-    private RprofState profState;
-
     @SuppressWarnings("unused")
     @Specialization
     public Object doRprof(RAbstractStringVector filenameVec, byte appendL, double intervalD, byte memProfilingL,
                     byte gcProfilingL, byte lineProfilingL, int numFiles, int bufSize) {
-        if (!RContext.getInstance().isInitial()) {
-            throw RError.error(this, RError.Message.GENERIC, "profiling not supported in created contexts");
-        }
-        profState = RContext.getInstance().stateInstrumentation.getRprof();
+        RprofState profState = RprofState.get();
         String filename = filenameVec.getDataAt(0);
         if (filename.length() == 0) {
             // disable
             endProfiling();
         } else {
             // enable after ending any previous session
-            if (profState.out() != null) {
+            if (profState != null && profState.out() != null) {
                 endProfiling();
             }
             boolean append = RRuntime.fromLogical(appendL);
             boolean memProfiling = RRuntime.fromLogical(memProfilingL);
             boolean gcProfiling = RRuntime.fromLogical(gcProfilingL);
             try {
-                PrintWriter out = new PrintWriter(new FileWriter(filename, append));
+                PrintStream out = new PrintStream(new FileOutputStream(filename, append));
                 if (gcProfiling) {
                     RError.warning(this, RError.Message.GENERIC, "Rprof: gc profiling not supported");
                 }
                 if (memProfiling) {
                     RDataFactory.addListener(this);
-                    RDataFactory.setAllocationTracing(true);
+                    RDataFactory.setTracingState(true);
                     MemoryCopyTracer.addListener(this);
                     MemoryCopyTracer.setTracingState(true);
                 }
@@ -129,15 +123,16 @@ public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFa
     @Override
     @TruffleBoundary
     public void reportAllocation(RTypedValue data) {
+        RprofState profState = RprofState.get();
         long size = RObjectSize.getObjectSize(data, Rprofmem.myIgnoreObjectHandler);
         if (data instanceof RAbstractVector) {
             if (size >= Rprofmem.LARGE_VECTOR) {
-                profState.memoryQuad().largeV += size;
+                profState.memoryQuad.largeV += size;
             } else {
-                profState.memoryQuad().smallV += size;
+                profState.memoryQuad.smallV += size;
             }
         } else {
-            profState.memoryQuad().nodes += size;
+            profState.memoryQuad.nodes += size;
         }
 
     }
@@ -145,65 +140,15 @@ public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFa
     @Override
     @TruffleBoundary
     public void reportCopying(RAbstractVector source, RAbstractVector dest) {
-        profState.memoryQuad().copied += RObjectSize.getObjectSize(source, Rprofmem.myIgnoreObjectHandler);
+        RprofState profState = RprofState.get();
+        profState.memoryQuad.copied += RObjectSize.getObjectSize(source, Rprofmem.myIgnoreObjectHandler);
     }
 
-    private void endProfiling() {
-        ProfileThread profileThread = (ProfileThread) profState.profileThread();
-        profileThread.running = false;
-        HashMap<String, Integer> fileMap = null;
-        PrintWriter out = profState.out();
-        StatementListener statementListener = (StatementListener) profState.statementListener();
-        if (profState.memoryProfiling()) {
-            out.print("memory profiling: ");
+    private static void endProfiling() {
+        RprofState profState = RprofState.get();
+        if (profState.out() != null) {
+            profState.cleanup(0);
         }
-        if (profState.lineProfiling()) {
-            out.print("line profiling: ");
-        }
-        out.printf("sample.interval=%d\n", profState.intervalInMillis() * 1000);
-        if (profState.lineProfiling()) {
-            // scan stacks to find files
-            fileMap = new HashMap<>();
-            int fileIndex = 0;
-            for (ArrayList<RSyntaxNode> intervalStack : statementListener.intervalStacks) {
-                for (RSyntaxNode node : intervalStack) {
-                    String path = getPath(node);
-                    if (path != null && fileMap.get(path) == null) {
-                        fileMap.put(path, ++fileIndex);
-                        out.printf("#File %d: %s\n", fileIndex, path);
-                    }
-                }
-            }
-        }
-        int index = 0;
-        for (ArrayList<RSyntaxNode> intervalStack : statementListener.intervalStacks) {
-            if (profState.memoryProfiling()) {
-                MemoryQuad mq = statementListener.intervalMemory.get(index);
-                out.printf(":%d:%d:%d:%d:", mq.largeV, mq.smallV, mq.nodes, mq.copied);
-            }
-            for (RSyntaxNode node : intervalStack) {
-                RootNode rootNode = node.asRNode().getRootNode();
-                if (rootNode instanceof FunctionDefinitionNode) {
-                    String name = rootNode.getName();
-                    if (profState.lineProfiling()) {
-                        Integer fileIndex = fileMap.get(getPath(node));
-                        if (fileIndex != null) {
-                            out.printf("%d#%d ", fileIndex, node.getSourceSection().getStartLine());
-                        }
-                    }
-                    out.printf("\"%s\" ", name);
-                }
-            }
-            out.println();
-            index++;
-        }
-        out.close();
-        profState.setOut(null);
-        if (profState.memoryProfiling()) {
-            RDataFactory.setAllocationTracing(false);
-            MemoryCopyTracer.setTracingState(false);
-        }
-
     }
 
     private static String getPath(RSyntaxNode node) {
@@ -242,7 +187,7 @@ public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFa
      */
     private final class StatementListener implements ExecutionEventListener {
         private ArrayList<ArrayList<RSyntaxNode>> intervalStacks = new ArrayList<>();
-        private ArrayList<MemoryQuad> intervalMemory = new ArrayList<>();
+        private ArrayList<RprofState.MemoryQuad> intervalMemory = new ArrayList<>();
         private volatile boolean newInterval;
 
         private StatementListener() {
@@ -264,8 +209,9 @@ public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFa
                 stack.add((RSyntaxNode) context.getInstrumentedNode());
                 collectStack(stack);
                 intervalStacks.add(stack);
-                if (profState.memoryProfiling()) {
-                    intervalMemory.add(profState.memoryQuad().copyAndClear());
+                RprofState profState = RprofState.get();
+                if (profState.memoryProfiling) {
+                    intervalMemory.add(profState.memoryQuad.copyAndClear());
                 }
 
                 newInterval = false;
@@ -301,4 +247,115 @@ public abstract class Rprof extends RExternalBuiltinNode.Arg8 implements RDataFa
         }
 
     }
+
+    /**
+     * State used by {@code Rprof}.
+     *
+     */
+    private static final class RprofState extends InstrumentationState.RprofState {
+        private ProfileThread profileThread;
+        private StatementListener statementListener;
+        private long intervalInMillis;
+        private boolean lineProfiling;
+        private boolean memoryProfiling;
+        private MemoryQuad memoryQuad;
+
+        public static final class MemoryQuad {
+            public long smallV;
+            public long largeV;
+            public long nodes;
+            public long copied;
+
+            public MemoryQuad copyAndClear() {
+                MemoryQuad result = new MemoryQuad();
+                result.copied = copied;
+                result.largeV = largeV;
+                result.smallV = smallV;
+                result.nodes = nodes;
+                copied = 0;
+                largeV = 0;
+                smallV = 0;
+                nodes = 0;
+                return result;
+            }
+        }
+
+        private static RprofState get() {
+            RprofState state = (RprofState) RContext.getInstance().stateInstrumentation.getRprofState("prof");
+            if (state == null) {
+                state = new RprofState();
+                RContext.getInstance().stateInstrumentation.setRprofState("prof", state);
+            }
+            return state;
+        }
+
+        public void initialize(PrintStream outA, ProfileThread profileThreadA, StatementListener statementListenerA, long intervalInMillisA,
+                        boolean lineProfilingA, boolean memoryProfilingA) {
+            setOut(outA);
+            this.profileThread = profileThreadA;
+            this.statementListener = statementListenerA;
+            this.intervalInMillis = intervalInMillisA;
+            this.lineProfiling = lineProfilingA;
+            this.memoryProfiling = memoryProfilingA;
+            this.memoryQuad = memoryProfilingA ? new MemoryQuad() : null;
+        }
+
+        @Override
+        public void cleanup(int status) {
+            profileThread.running = false;
+            HashMap<String, Integer> fileMap = null;
+            PrintStream out = this.out();
+            if (this.memoryProfiling) {
+                out.print("memory profiling: ");
+            }
+            if (this.lineProfiling) {
+                out.print("line profiling: ");
+            }
+            out.printf("sample.interval=%d\n", this.intervalInMillis * 1000);
+            if (this.lineProfiling) {
+                // scan stacks to find files
+                fileMap = new HashMap<>();
+                int fileIndex = 0;
+                for (ArrayList<RSyntaxNode> intervalStack : statementListener.intervalStacks) {
+                    for (RSyntaxNode node : intervalStack) {
+                        String path = getPath(node);
+                        if (path != null && fileMap.get(path) == null) {
+                            fileMap.put(path, ++fileIndex);
+                            out.printf("#File %d: %s\n", fileIndex, path);
+                        }
+                    }
+                }
+            }
+            int index = 0;
+            for (ArrayList<RSyntaxNode> intervalStack : statementListener.intervalStacks) {
+                if (this.memoryProfiling) {
+                    RprofState.MemoryQuad mq = statementListener.intervalMemory.get(index);
+                    out.printf(":%d:%d:%d:%d:", mq.largeV, mq.smallV, mq.nodes, mq.copied);
+                }
+                for (RSyntaxNode node : intervalStack) {
+                    RootNode rootNode = node.asRNode().getRootNode();
+                    if (rootNode instanceof FunctionDefinitionNode) {
+                        String name = rootNode.getName();
+                        if (this.lineProfiling) {
+                            Integer fileIndex = fileMap.get(getPath(node));
+                            if (fileIndex != null) {
+                                out.printf("%d#%d ", fileIndex, node.getSourceSection().getStartLine());
+                            }
+                        }
+                        out.printf("\"%s\" ", name);
+                    }
+                }
+                out.println();
+                index++;
+            }
+            out.close();
+            this.setOut(null);
+            if (this.memoryProfiling) {
+                RDataFactory.setTracingState(false);
+                MemoryCopyTracer.setTracingState(false);
+            }
+
+        }
+
+    }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprofmem.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprofmem.java
index 5fa5c672e50bcf65325c39085cb018110c0dc822..b88be4b9d9563568d5a0994910a3ed8b595e13c4 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprofmem.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/Rprofmem.java
@@ -22,10 +22,9 @@
  */
 package com.oracle.truffle.r.library.utils;
 
-import java.io.FileWriter;
+import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
-
+import java.io.PrintStream;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.Frame;
@@ -44,34 +43,29 @@ import com.oracle.truffle.r.runtime.data.RFunction;
 import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
-import com.oracle.truffle.r.runtime.instrument.InstrumentationState.RprofmemState;
+import com.oracle.truffle.r.runtime.instrument.InstrumentationState.RprofState;
 
 public abstract class Rprofmem extends RExternalBuiltinNode.Arg3 implements RDataFactory.Listener {
 
-    private RprofmemState profmemState;
-
     @Specialization
     @TruffleBoundary
     public Object doRprofmem(RAbstractStringVector filenameVec, byte appendL, RAbstractDoubleVector thresholdVec) {
-        if (!RContext.getInstance().isInitial()) {
-            throw RError.error(this, RError.Message.GENERIC, "profiling not supported in created contexts");
-        }
         String filename = filenameVec.getDataAt(0);
         if (filename.length() == 0) {
             // disable
             endProfiling();
         } else {
             // enable after ending any previous session
-            profmemState = RContext.getInstance().stateInstrumentation.getRprofmem();
-            if (profmemState.out() != null) {
+            RprofmemState profmemState = RprofmemState.get();
+            if (profmemState != null && profmemState.out() != null) {
                 endProfiling();
             }
             boolean append = RRuntime.fromLogical(appendL);
             try {
-                PrintWriter out = new PrintWriter(new FileWriter(filename, append));
+                PrintStream out = new PrintStream(new FileOutputStream(filename, append));
                 profmemState.initialize(out, thresholdVec.getDataAt(0));
                 RDataFactory.addListener(this);
-                RDataFactory.setAllocationTracing(true);
+                RDataFactory.setTracingState(true);
             } catch (IOException ex) {
                 throw RError.error(this, RError.Message.GENERIC, String.format("Rprofmem: cannot open profile file '%s'", filename));
             }
@@ -79,12 +73,10 @@ public abstract class Rprofmem extends RExternalBuiltinNode.Arg3 implements RDat
         return RNull.instance;
     }
 
-    private void endProfiling() {
-        if (profmemState != null) {
-            RDataFactory.setAllocationTracing(false);
-            profmemState.out().flush();
-            profmemState.out().close();
-            profmemState.setOut(null);
+    private static void endProfiling() {
+        RprofmemState profmemState = RprofmemState.get();
+        if (profmemState.out() != null) {
+            profmemState.cleanup(0);
         }
     }
 
@@ -119,6 +111,7 @@ public abstract class Rprofmem extends RExternalBuiltinNode.Arg3 implements RDat
     public void reportAllocation(RTypedValue data) {
         // We could do some in memory buffering
         // TODO write out full stack
+        RprofmemState profmemState = RprofmemState.get();
         Frame frame = Utils.getActualCurrentFrame();
         if (frame == null) {
             // not an R evaluation, some internal use
@@ -132,18 +125,46 @@ public abstract class Rprofmem extends RExternalBuiltinNode.Arg3 implements RDat
 
         long size = RObjectSize.getObjectSize(data, myIgnoreObjectHandler);
         if (data instanceof RAbstractVector && size >= LARGE_VECTOR) {
-            profmemState.out().printf("%d: %s\n", size, name);
+            RAbstractVector absv = (RAbstractVector) data;
+            if (size > profmemState.threshold) {
+                profmemState.out().printf("%d: %s\n", size, name);
+            }
         } else {
-            int pageCount = profmemState.pageCount();
+            int pageCount = profmemState.pageCount;
             long pcs = pageCount + size;
             if (pcs > PAGE_SIZE) {
                 profmemState.out().printf("new page: %s\n", name);
-                profmemState.setPageCount((int) (pcs - PAGE_SIZE));
+                profmemState.pageCount = (int) (pcs - PAGE_SIZE);
             } else {
-                profmemState.setPageCount((int) pcs);
+                profmemState.pageCount = (int) pcs;
+            }
+        }
+
+    }
+
+    private static final class RprofmemState extends RprofState {
+        private double threshold;
+        private int pageCount;
+
+        private static RprofmemState get() {
+            RprofmemState state = (RprofmemState) RContext.getInstance().stateInstrumentation.getRprofState("mem");
+            if (state == null) {
+                state = new RprofmemState();
+                RContext.getInstance().stateInstrumentation.setRprofState("mem", state);
             }
+            return state;
         }
 
+        public void initialize(PrintStream outA, double thresholdA) {
+            setOut(outA);
+            this.threshold = thresholdA;
+        }
+
+        @Override
+        public void cleanup(int status) {
+            RDataFactory.setTracingState(false);
+            closeAndResetOut();
+        }
     }
 
 }
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 8170e1d1b19f680b8335398e890fe242f5ef98cb..842c52397077ded5530639fd9aa1201a406e55a3 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
@@ -48,8 +48,6 @@ import com.oracle.truffle.r.nodes.builtin.fastr.FastRContext;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRContextFactory;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRDebug;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRDebugNodeGen;
-import com.oracle.truffle.r.nodes.builtin.fastr.FastRFunctionProfiler;
-import com.oracle.truffle.r.nodes.builtin.fastr.FastRFunctionProfilerFactory;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRIdentity;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRIdentityNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInspect;
@@ -60,6 +58,10 @@ import com.oracle.truffle.r.nodes.builtin.fastr.FastRRefCountInfo;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRRefCountInfoNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRStackTrace;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRStackTraceNodeGen;
+import com.oracle.truffle.r.nodes.builtin.fastr.FastRStats.FastRProfAttr;
+import com.oracle.truffle.r.nodes.builtin.fastr.FastRStats.FastRProfFuncounts;
+import com.oracle.truffle.r.nodes.builtin.fastr.FastRStats.FastRProfTypecounts;
+import com.oracle.truffle.r.nodes.builtin.fastr.FastRStatsFactory;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRSyntaxTree;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRSyntaxTreeNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRThrowIt;
@@ -289,10 +291,6 @@ public class BasePackage extends RBuiltinPackage {
         add(FastRContext.Join.class, FastRContextFactory.JoinNodeGen::create);
         add(FastrDqrls.class, FastrDqrlsNodeGen::create);
         add(FastRDebug.class, FastRDebugNodeGen::create);
-        add(FastRFunctionProfiler.Create.class, FastRFunctionProfilerFactory.CreateNodeGen::create);
-        add(FastRFunctionProfiler.Get.class, FastRFunctionProfilerFactory.GetNodeGen::create);
-        add(FastRFunctionProfiler.Reset.class, FastRFunctionProfilerFactory.ResetNodeGen::create);
-        add(FastRFunctionProfiler.Clear.class, FastRFunctionProfilerFactory.ClearNodeGen::create);
         add(FastRIdentity.class, FastRIdentityNodeGen::create);
         add(FastRInspect.class, FastRInspectNodeGen::create);
         add(FastRInterop.Eval.class, FastRInteropFactory.EvalNodeGen::create);
@@ -300,6 +298,9 @@ public class BasePackage extends RBuiltinPackage {
         add(FastRInterop.Import.class, FastRInteropFactory.ImportNodeGen::create);
         add(FastRRefCountInfo.class, FastRRefCountInfoNodeGen::create);
         add(FastRStackTrace.class, FastRStackTraceNodeGen::create);
+        add(FastRProfAttr.class, FastRStatsFactory.FastRProfAttrNodeGen::create);
+        add(FastRProfTypecounts.class, FastRStatsFactory.FastRProfTypecountsNodeGen::create);
+        add(FastRProfFuncounts.class, FastRStatsFactory.FastRProfFuncountsNodeGen::create);
         add(FastRSyntaxTree.class, FastRSyntaxTreeNodeGen::create);
         add(FastRThrowIt.class, FastRThrowItNodeGen::create);
         add(FastRTrace.Trace.class, FastRTraceFactory.TraceNodeGen::create);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRContext.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRContext.java
index ae7d75ffc1eff9aaa109ff114b61611e4fff620b..d4e2fe9c14e54bcbd422b3c076807c755cc37efe 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRContext.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRContext.java
@@ -348,4 +348,5 @@ public class FastRContext {
             }
         }
     }
+
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRFunctionProfiler.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRFunctionProfiler.java
deleted file mode 100644
index 580a10472010aacda10f4bc56192bca9b2f80c1a..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRFunctionProfiler.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (c) 2014, 2016, 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;
-
-import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.doubleValue;
-import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.instanceOf;
-import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.integerValue;
-import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue;
-import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue;
-import static com.oracle.truffle.r.runtime.RVisibility.OFF;
-import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
-import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
-
-import java.util.ArrayList;
-
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.dsl.Specialization;
-import com.oracle.truffle.r.nodes.builtin.CastBuilder;
-import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
-import com.oracle.truffle.r.nodes.instrumentation.RFunctionProfiler;
-import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.builtins.RBuiltin;
-import com.oracle.truffle.r.runtime.data.RDataFactory;
-import com.oracle.truffle.r.runtime.data.RDoubleVector;
-import com.oracle.truffle.r.runtime.data.RFunction;
-import com.oracle.truffle.r.runtime.data.RList;
-import com.oracle.truffle.r.runtime.data.RMissing;
-import com.oracle.truffle.r.runtime.data.RNull;
-import com.oracle.truffle.r.runtime.data.RStringVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
-import com.oracle.truffle.tools.Profiler;
-
-public class FastRFunctionProfiler {
-
-    @RBuiltin(name = ".fastr.profiler.create", visibility = OFF, kind = PRIMITIVE, parameterNames = {"func", "mode"}, behavior = COMPLEX)
-    public abstract static class Create extends RBuiltinNode {
-        private static final int COUNTING = 1;
-        private static final int TIMING = 2;
-
-        @Override
-        public Object[] getDefaultParameterValues() {
-            return new Object[]{RMissing.instance, "counting"};
-        }
-
-        @Override
-        protected void createCasts(CastBuilder casts) {
-            casts.arg("func").mustBe(instanceOf(RFunction.class).or(missingValue()));
-
-            casts.arg("mode").mustBe(stringValue()).asStringVector();
-        }
-
-        private int checkMode(RAbstractStringVector modeVec) {
-            int result = 0;
-            for (int i = 0; i < modeVec.getLength(); i++) {
-                String mode = modeVec.getDataAt(i);
-                switch (mode) {
-                    case "counting":
-                        result |= COUNTING;
-                        break;
-                    case "timing":
-                        result |= TIMING;
-                        break;
-                    default:
-                        throw RError.error(this, RError.Message.GENERIC, "invalid 'mode', one of 'count, timning' expected");
-                }
-            }
-            return result;
-        }
-
-        @Specialization
-        @TruffleBoundary
-        protected RNull createFunctionProfiler(RFunction function, RAbstractStringVector modeVec) {
-            int mode = checkMode(modeVec);
-            if (!function.isBuiltin()) {
-                RFunctionProfiler.installTimer(function, (mode & COUNTING) != 0, (mode & TIMING) != 0);
-            } else {
-                throw RError.error(this, RError.Message.GENERIC, "cannot profile builtin functions");
-            }
-            return RNull.instance;
-        }
-
-        @Specialization
-        @TruffleBoundary
-        protected RNull createFunctionProfiler(@SuppressWarnings("unused") RMissing value, RAbstractStringVector modeVec) {
-            int mode = checkMode(modeVec);
-            RFunctionProfiler.installTimer(null, (mode & COUNTING) != 0, (mode & TIMING) != 0);
-            return RNull.instance;
-        }
-
-    }
-
-    @RBuiltin(name = ".fastr.profiler.get", kind = PRIMITIVE, parameterNames = {"func", "threshold", "scale"}, behavior = COMPLEX)
-    public abstract static class Get extends RBuiltinNode {
-
-        private static final RStringVector COLNAMES = RDataFactory.createStringVector(new String[]{"Invocations", "TotalTime", "SelfTime"}, RDataFactory.COMPLETE_VECTOR);
-        private static final RStringVector ROWNAMES = RDataFactory.createStringVector(new String[]{"Combined", "Interpreted", "Compiled"}, RDataFactory.COMPLETE_VECTOR);
-        private static final int NCOLS = 3;
-        private static final int NROWS = 3;
-
-        @Override
-        protected void createCasts(CastBuilder casts) {
-            casts.arg("func").mustBe(instanceOf(RFunction.class).or(missingValue()));
-
-            casts.arg("threshold").mustBe(integerValue().or(doubleValue())).asDoubleVector();
-
-            casts.arg("scale").mustBe(stringValue()).asStringVector();
-        }
-
-        @Override
-        public Object[] getDefaultParameterValues() {
-            return new Object[]{RMissing.instance, 0.0, "nanos"};
-        }
-
-        private void checkScale(String s) throws RError {
-            if (!(s.equals("nanos") || s.equals("millis") || s.equals("micros") || s.equals("secs"))) {
-                throw RError.error(this, RError.Message.GENERIC, "invalid scale: one of 'nanos, micros, millis, secs' expected");
-            }
-        }
-
-        @Specialization
-        @TruffleBoundary
-        protected Object get(@SuppressWarnings("unused") RMissing value, RAbstractDoubleVector thresholdVec, RAbstractStringVector scaleVec) {
-            String scale = scaleVec.getDataAt(0);
-            checkScale(scale);
-            double threshold = thresholdVec.getDataAt(0);
-            Profiler.Counter[] counters = RFunctionProfiler.getCounters();
-            if (counters == null) {
-                throw RError.error(this, RError.Message.GENERIC, "profiling not enabled");
-            }
-            ArrayList<RDoubleVector> dataList = new ArrayList<>();
-            ArrayList<String> nameList = new ArrayList<>();
-            for (int i = 0; i < counters.length; i++) {
-                Profiler.Counter counter = counters[i];
-                if (threshold > 0.0) {
-                    long time = counter.getTotalTime(Profiler.Counter.TimeKind.INTERPRETED_AND_COMPILED);
-                    if (time <= threshold) {
-                        continue;
-                    }
-                }
-                dataList.add(getFunctionMatrix(counter, scale));
-                nameList.add(counter.getName());
-            }
-            Object[] data = new Object[dataList.size()];
-            String[] names = new String[nameList.size()];
-            return RDataFactory.createList(dataList.toArray(data), RDataFactory.createStringVector(nameList.toArray(names), RDataFactory.COMPLETE_VECTOR));
-        }
-
-        @Specialization
-        @TruffleBoundary
-        protected Object get(RFunction function, @SuppressWarnings("unused") RAbstractDoubleVector threshold, RAbstractStringVector scaleVec) {
-            String scale = scaleVec.getDataAt(0);
-            checkScale(scale);
-            if (!function.isBuiltin()) {
-                Profiler.Counter counter = RFunctionProfiler.getCounter(function);
-                if (counter == null) {
-                    throw RError.error(this, RError.Message.GENERIC, "profiling not enabled");
-                } else {
-                    return getFunctionMatrix(counter, scale);
-                }
-            } else {
-                throw RError.error(this, RError.Message.GENERIC, "cannot profile builtin functions");
-            }
-        }
-
-        private static RDoubleVector getFunctionMatrix(Profiler.Counter counter, String scale) {
-            double[] data = new double[NROWS * NCOLS];
-            boolean isTiming = RFunctionProfiler.isTiming();
-            boolean complete = isTiming ? RDataFactory.COMPLETE_VECTOR : RDataFactory.INCOMPLETE_VECTOR;
-            for (int r = 0; r < NROWS; r++) {
-                Profiler.Counter.TimeKind timeKind = Profiler.Counter.TimeKind.values()[r];
-                for (int c = 0; c < NCOLS; c++) {
-                    int index = c * NROWS + r;
-                    double value = 0.0;
-                    switch (c) {
-                        case 0:
-                            value = counter.getInvocations(timeKind);
-                            break;
-                        case 1:
-                            value = isTiming ? counter.getTotalTime(timeKind) : RRuntime.DOUBLE_NA;
-                            break;
-                        case 2:
-                            value = isTiming ? counter.getSelfTime(timeKind) : RRuntime.DOUBLE_NA;
-                    }
-                    data[index] = c == 0 ? value : scaledTime(scale, value);
-                }
-            }
-            RDoubleVector result = RDataFactory.createDoubleVector(data, complete, new int[]{3, 3});
-            Object[] dimNamesData = new Object[2];
-            dimNamesData[0] = ROWNAMES;
-            dimNamesData[1] = COLNAMES;
-            RList dimNames = RDataFactory.createList(dimNamesData);
-            result.setDimNames(dimNames);
-            return result;
-        }
-
-        private static double scaledTime(String scale, double time) {
-            if (RRuntime.isNA(time)) {
-                return time;
-            }
-            switch (scale) {
-                case "nanos":
-                    return time;
-                case "micros":
-                    return time / 1000.0;
-                case "millis":
-                    return time / 1000000.0;
-                case "secs":
-                    return time / 1000000000.0;
-                default:
-                    throw RInternalError.shouldNotReachHere();
-            }
-        }
-    }
-
-    @RBuiltin(name = ".fastr.profiler.reset", visibility = OFF, kind = PRIMITIVE, parameterNames = {}, behavior = COMPLEX)
-    public abstract static class Reset extends RBuiltinNode {
-        @Specialization
-        @TruffleBoundary
-        protected Object reset() {
-            RFunctionProfiler.reset();
-            return RNull.instance;
-        }
-    }
-
-    @RBuiltin(name = ".fastr.profiler.clear", visibility = OFF, kind = PRIMITIVE, parameterNames = {}, behavior = COMPLEX)
-    public abstract static class Clear extends RBuiltinNode {
-        @Specialization
-        @TruffleBoundary
-        protected Object clear() {
-            RFunctionProfiler.clear();
-            return RNull.instance;
-        }
-    }
-}
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRStats.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRStats.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab13f89bcefa882aa7611db5bbafd560915745fa
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRStats.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (c) 2016, 2016, 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;
+
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.*;
+import static com.oracle.truffle.r.runtime.RVisibility.OFF;
+import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
+import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.source.Source;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.builtin.CastBuilder;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.RSource;
+import com.oracle.truffle.r.runtime.builtins.RBuiltin;
+import com.oracle.truffle.r.runtime.context.RContext;
+import com.oracle.truffle.r.runtime.data.RAttributes;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
+import com.oracle.truffle.r.runtime.data.RAttributes.AttributeTracer.Change;
+import com.oracle.truffle.r.runtime.instrument.InstrumentationState.RprofState;
+import com.oracle.truffle.tools.Profiler;
+import com.oracle.truffle.tools.Profiler.Counter.TimeKind;
+
+public class FastRStats {
+
+    private abstract static class CastHelper extends RBuiltinNode {
+        protected void filename(CastBuilder casts) {
+            casts.arg("filename").mustBe(stringValue().or(nullValue())).asStringVector();
+        }
+
+        protected void append(CastBuilder casts) {
+            casts.arg("append").asLogicalVector().findFirst(RRuntime.LOGICAL_FALSE).notNA().map(toBoolean());
+        }
+    }
+
+    @RBuiltin(name = ".fastr.prof.attr", visibility = OFF, kind = PRIMITIVE, parameterNames = {"filename", "append"}, behavior = COMPLEX)
+    public abstract static class FastRProfAttr extends CastHelper implements RAttributes.AttributeTracer.Listener {
+        @Override
+        public Object[] getDefaultParameterValues() {
+            return new Object[]{"Rprofattr.out", RRuntime.LOGICAL_FALSE};
+        }
+
+        @Override
+        protected void createCasts(CastBuilder casts) {
+            filename(casts);
+            append(casts);
+        }
+
+        @SuppressWarnings("unused")
+        @Specialization
+        @TruffleBoundary
+        protected RNull profAttr(RNull filenameVec, boolean append) {
+            endProfiling();
+            return RNull.instance;
+        }
+
+        @Specialization
+        @TruffleBoundary
+        protected RNull profAttr(RAbstractStringVector filenameVec, boolean append) {
+            if (filenameVec.getLength() == 0) {
+                // disable
+                endProfiling();
+            } else {
+                // enable after ending any previous session
+                State state = State.get();
+                try {
+                    PrintStream out = new PrintStream(new FileOutputStream(filenameVec.getDataAt(0), append));
+                    state.setOut(out);
+                    RAttributes.AttributeTracer.addListener(this);
+                    RAttributes.AttributeTracer.setTracingState(true);
+                } catch (IOException ex) {
+                    throw RError.error(this, RError.Message.GENERIC, String.format("Rprofmem: cannot open profile file '%s'", filenameVec.getDataAt(0)));
+                }
+
+            }
+            return RNull.instance;
+        }
+
+        protected void endProfiling() {
+            State state = State.get();
+            if (state.out() != null) {
+                state.cleanup(0);
+            }
+        }
+
+        @Override
+        public void reportAttributeChange(Change change, RAttributes attrs, Object value) {
+            State rprofattrState = State.get();
+            rprofattrState.out().printf("%s,%d,", change.name(), System.identityHashCode(attrs));
+            switch (change) {
+                case CREATE:
+                    rprofattrState.out().print("NA");
+                    break;
+                case GROW:
+                    rprofattrState.out().printf("%d", (int) value);
+                    break;
+                default:
+                    rprofattrState.out().printf("%s", (String) value);
+                    break;
+            }
+            rprofattrState.out().println();
+        }
+
+        private static class State extends RprofState {
+            private static State get() {
+                State state = (State) RContext.getInstance().stateInstrumentation.getRprofState("attr");
+                if (state == null) {
+                    state = new State();
+                    RContext.getInstance().stateInstrumentation.setRprofState("attr", state);
+                }
+                return state;
+            }
+
+            @Override
+            public void cleanup(int status) {
+                RAttributes.AttributeTracer.setTracingState(false);
+                closeAndResetOut();
+            }
+
+        }
+
+    }
+
+    @RBuiltin(name = ".fastr.stats.typecounts", visibility = OFF, kind = PRIMITIVE, parameterNames = {"filename", "append"}, behavior = COMPLEX)
+    public abstract static class FastRProfTypecounts extends CastHelper implements RDataFactory.Listener {
+        @Override
+        public Object[] getDefaultParameterValues() {
+            return new Object[]{"Rproftypecounts.out", RRuntime.LOGICAL_FALSE};
+        }
+
+        @Override
+        protected void createCasts(CastBuilder casts) {
+            filename(casts);
+            append(casts);
+        }
+
+        @SuppressWarnings("unused")
+        @Specialization
+        @TruffleBoundary
+        protected RNull profTypecounts(RNull filenameVec, boolean append) {
+            endProfiling();
+            return RNull.instance;
+        }
+
+        @Specialization
+        @TruffleBoundary
+        protected RNull profTypecounts(RAbstractStringVector filenameVec, boolean append) {
+            if (filenameVec.getLength() == 0) {
+                // disable
+                endProfiling();
+            } else {
+                // enable after ending any previous session
+                State state = State.get();
+                try {
+                    PrintStream out = new PrintStream(new FileOutputStream(filenameVec.getDataAt(0), append));
+                    state.setOut(out);
+                    RDataFactory.addListener(this);
+                    RDataFactory.setTracingState(true);
+                } catch (IOException ex) {
+                    throw RError.error(this, RError.Message.GENERIC, String.format("Rprofmem: cannot open profile file '%s'", filenameVec.getDataAt(0)));
+                }
+
+            }
+            return RNull.instance;
+        }
+
+        protected void endProfiling() {
+            State state = State.get();
+            if (state.out() != null) {
+                RDataFactory.setTracingState(false);
+                state.cleanup(0);
+            }
+        }
+
+        @Override
+        public void reportAllocation(RTypedValue data) {
+            Class<? extends RTypedValue> klass = data.getClass();
+            boolean isVector = (data instanceof RAbstractVector);
+            State state = State.get();
+            Map<Class<? extends RTypedValue>, SortedMap<Integer, State.Counter>> typecountsMap = state.getTypecountsMap();
+            SortedMap<Integer, State.Counter> countsMap = typecountsMap.get(klass);
+            if (countsMap == null) {
+                countsMap = new TreeMap<>();
+                typecountsMap.put(klass, countsMap);
+            }
+            int length;
+            if (isVector) {
+                RAbstractVector vector = (RAbstractVector) data;
+                length = vector.getLength();
+            } else {
+                length = 1;
+            }
+            State.Counter count = countsMap.get(length);
+            if (count == null) {
+                count = new State.Counter();
+                countsMap.put(length, count);
+            }
+            count.incCount();
+        }
+
+        private static class State extends RprofState {
+            public static class Counter {
+                private int count;
+
+                public void incCount() {
+                    count++;
+                }
+
+                public int getCount() {
+                    return count;
+                }
+
+                @Override
+                public String toString() {
+                    return Integer.toString(count);
+                }
+
+            }
+
+            private Map<Class<? extends RTypedValue>, SortedMap<Integer, Counter>> typecountsMap;
+
+            private static State get() {
+                State state = (State) RContext.getInstance().stateInstrumentation.getRprofState("typecounts");
+                if (state == null) {
+                    state = new State();
+                    RContext.getInstance().stateInstrumentation.setRprofState("typecounts", state);
+                }
+                return state;
+            }
+
+            private Map<Class<? extends RTypedValue>, SortedMap<Integer, Counter>> getTypecountsMap() {
+                if (typecountsMap == null) {
+                    typecountsMap = new HashMap<>();
+                }
+                return typecountsMap;
+            }
+
+            @Override
+            public void cleanup(int status) {
+                for (Map.Entry<Class<? extends RTypedValue>, SortedMap<Integer, Counter>> entry : getTypecountsMap().entrySet()) {
+                    SortedMap<Integer, Counter> countsMap = entry.getValue();
+                    int totalCount = 0;
+                    for (Counter counter : countsMap.values()) {
+                        totalCount += counter.getCount();
+                    }
+                    out().printf("%s,%d", entry.getKey().getSimpleName(), totalCount);
+                    for (Map.Entry<Integer, Counter> countsEntry : countsMap.entrySet()) {
+                        out().printf(",%d:%d", countsEntry.getKey(), countsEntry.getValue().getCount());
+                    }
+                    out().println();
+                }
+                closeAndResetOut();
+            }
+        }
+
+    }
+
+    @RBuiltin(name = ".fastr.stats.funcounts", visibility = OFF, kind = PRIMITIVE, parameterNames = {"filename", "append", "timing", "threshold", "histograms"}, behavior = COMPLEX)
+    public abstract static class FastRProfFuncounts extends CastHelper {
+        @Override
+        public Object[] getDefaultParameterValues() {
+            return new Object[]{"Rproffuncounts.out", RRuntime.LOGICAL_FALSE, RRuntime.LOGICAL_FALSE, 0, RRuntime.LOGICAL_FALSE};
+        }
+
+        @Override
+        protected void createCasts(CastBuilder casts) {
+            filename(casts);
+            append(casts);
+            casts.arg("timing").asLogicalVector().findFirst().notNA().map(toBoolean());
+            casts.arg("threshold").asIntegerVector().findFirst().notNA();
+            casts.arg("histograms").asLogicalVector().findFirst().notNA().map(toBoolean());
+        }
+
+        @SuppressWarnings("unused")
+        @Specialization
+        @TruffleBoundary
+        protected RNull profFuncounts(RNull filenameVec, boolean append, boolean timing, int threshold, boolean histograms) {
+            endProfiling();
+            return RNull.instance;
+        }
+
+        @Specialization
+        @TruffleBoundary
+        protected RNull profFuncounts(RAbstractStringVector filenameVec, boolean append, boolean timing, int threshold, boolean histograms) {
+            if (filenameVec.getLength() == 0) {
+                // disable
+                endProfiling();
+            } else {
+                // enable after ending any previous session
+                State state = State.get();
+                try {
+                    PrintStream out = new PrintStream(new FileOutputStream(filenameVec.getDataAt(0), append));
+                    state.initialize(out, threshold, timing, histograms);
+                    Profiler profiler = RContext.getInstance().stateInstrumentation.getProfiler();
+                    profiler.setCollecting(true);
+                    profiler.setTiming(timing);
+                } catch (IOException ex) {
+                    throw RError.error(this, RError.Message.GENERIC, String.format("Rprofmem: cannot open profile file '%s'", filenameVec.getDataAt(0)));
+                }
+
+            }
+            return RNull.instance;
+        }
+
+        protected void endProfiling() {
+            State state = State.get();
+            if (state.out() != null) {
+                state.cleanup(0);
+            }
+        }
+
+        private static class State extends RprofState {
+            private int threshold;
+            private boolean timing;
+            private boolean histograms;
+
+            private static State get() {
+                State state = (State) RContext.getInstance().stateInstrumentation.getRprofState("funcounts");
+                if (state == null) {
+                    state = new State();
+                    RContext.getInstance().stateInstrumentation.setRprofState("funcounts", state);
+                }
+                return state;
+            }
+
+            private void initialize(PrintStream outA, int thresholdA, boolean timingA, boolean histogramsA) {
+                this.setOut(outA);
+                this.threshold = thresholdA;
+                this.timing = timingA;
+                this.histograms = histogramsA;
+            }
+
+            @Override
+            public void cleanup(int status) {
+                Profiler profiler = RContext.getInstance().getInstrumentationState().getProfiler();
+                if (histograms) {
+                    profiler.printHistograms(out());
+                    return;
+                }
+                /*
+                 * Report the statement counts/timing information at the end of the run. The report
+                 * is per function. If timing is on, output is sorted by time, otherwise by
+                 * invocation count. When timing, functions that consumed less time than requested
+                 * threshold (default 0) are not included in the report.
+                 */
+                Map<SourceSection, Profiler.Counter> counters = profiler.getCounters();
+                long totalTime = 0;
+                SortableCounter[] sortedCounters = new SortableCounter[counters.size()];
+                int i = 0;
+                for (Profiler.Counter counter : counters.values()) {
+                    sortedCounters[i++] = new SortableCounter(counter, timing);
+                    if (timing) {
+                        totalTime += counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
+                    }
+                }
+                Arrays.sort(sortedCounters);
+                for (SortableCounter scounter : sortedCounters) {
+                    long time = scounter.counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
+                    long invocations = scounter.counter.getInvocations(TimeKind.INTERPRETED_AND_COMPILED);
+                    boolean include = timing ? time > 0 && time > threshold : invocations > 0;
+                    if (include) {
+                        SourceSection ss = scounter.counter.getSourceSection();
+                        Source source = ss.getSource();
+                        out().println("==========");
+                        out().printf("calls %d", invocations);
+                        if (timing) {
+                            double thisPercent = percent(time, totalTime);
+                            out().printf(", time %d ms (%.2f%%)", time, thisPercent);
+                        }
+                        out().printf(": %s, %s%n", scounter.counter.getName(), RSource.getOrigin(source));
+                    }
+                }
+                profiler.clearData();
+                closeAndResetOut();
+            }
+
+            private static double percent(long a, long b) {
+                return ((double) a * 100) / b;
+            }
+
+        }
+
+        private static final class SortableCounter implements Comparable<SortableCounter> {
+            private boolean timing;
+            private Profiler.Counter counter;
+
+            private SortableCounter(Profiler.Counter counter, boolean timing) {
+                this.counter = counter;
+                this.timing = timing;
+            }
+
+            @Override
+            public int compareTo(SortableCounter other) {
+                if (timing) {
+                    long myTime = counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
+                    long otherTime = other.counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
+                    return myTime < otherTime ? 1 : (myTime > otherTime ? -1 : 0);
+                } else {
+                    long myInvocations = counter.getInvocations(TimeKind.INTERPRETED_AND_COMPILED);
+                    long otherInvocations = other.counter.getInvocations(TimeKind.INTERPRETED_AND_COMPILED);
+                    return myInvocations < otherInvocations ? 1 : (myInvocations > otherInvocations ? -1 : 0);
+                }
+            }
+
+        }
+
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RFunctionProfiler.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RFunctionProfiler.java
deleted file mode 100644
index d3158a4c3e13a058b87ed179d903eaec0db6c873..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RFunctionProfiler.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (c) 2014, 2016, 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.instrumentation;
-
-import java.util.Arrays;
-import java.util.Map;
-
-import com.oracle.truffle.api.source.Source;
-import com.oracle.truffle.api.source.SourceSection;
-import com.oracle.truffle.api.vm.PolyglotEngine;
-import com.oracle.truffle.r.runtime.RPerfStats;
-import com.oracle.truffle.r.runtime.RSource;
-import com.oracle.truffle.r.runtime.Utils;
-import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.RFunction;
-import com.oracle.truffle.tools.Profiler;
-import com.oracle.truffle.tools.Profiler.Counter;
-import com.oracle.truffle.tools.Profiler.Counter.TimeKind;
-
-/**
- * Interface to the Truffle {@link Profiler}.
- */
-public class RFunctionProfiler {
-    static {
-        RPerfStats.register(new PerfHandler());
-    }
-
-    static boolean enabled() {
-        return RPerfStats.enabled(PerfHandler.NAME);
-    }
-
-    /**
-     * This is called on startup to support {@link RPerfStats}.
-     */
-    static void installTimers(RContext context) {
-        if (enabled()) {
-            enableTiming(context, true, true);
-        }
-    }
-
-    private static Profiler getProfiler(RContext context) {
-        PolyglotEngine vm = context.getVM();
-        Profiler profiler = Profiler.find(vm);
-        return profiler;
-    }
-
-    private static Profiler getProfiler() {
-        PolyglotEngine vm = RContext.getInstance().getVM();
-        Profiler profiler = Profiler.find(vm);
-        return profiler;
-    }
-
-    private static void enableTiming(RContext context, @SuppressWarnings("unused") boolean counting, boolean timing) {
-        Profiler profiler = getProfiler(context);
-        context.getInstrumentationState().setProfiler(profiler);
-        profiler.setTiming(timing);
-        profiler.setCollecting(true);
-    }
-
-    /**
-     * (Interactively) installs a timer for a specific function. Currently the {@link Profiler} does
-     * not support profiling limited to specific functions so this effectively enables everything.
-     * If {@code func} is {@code null} profile all functions. In principle profiling can be
-     * restricted to entry counting and timing but currently counting is always on.
-     *
-     */
-    public static void installTimer(@SuppressWarnings("unused") RFunction func, boolean counting, boolean timing) {
-        enableTiming(RContext.getInstance(), counting, timing);
-    }
-
-    public static Counter getCounter(RFunction func) {
-        Profiler profiler = getProfiler();
-        if (profiler.isCollecting()) {
-            String funcName = func.getTarget().getRootNode().getName();
-            Map<SourceSection, Counter> counters = profiler.getCounters();
-            for (Counter counter : counters.values()) {
-                if (counter.getName().equals(funcName)) {
-                    return counter;
-                }
-            }
-        }
-        return null;
-    }
-
-    public static void reset() {
-        Profiler profiler = getProfiler();
-        profiler.clearData();
-        profiler.setCollecting(false);
-    }
-
-    public static void clear() {
-        Profiler profiler = getProfiler();
-        profiler.clearData();
-    }
-
-    public static Counter[] getCounters() {
-        Profiler profiler = getProfiler();
-        if (profiler.isCollecting()) {
-            Map<SourceSection, Counter> counters = profiler.getCounters();
-            Counter[] result = new Counter[counters.size()];
-            counters.values().toArray(result);
-            return result;
-        } else {
-            return null;
-        }
-    }
-
-    public static boolean isTiming() {
-        Profiler profiler = getProfiler();
-        return profiler.isTiming();
-    }
-
-    private static class PerfHandler implements RPerfStats.Handler {
-        static final String NAME = "timing";
-        @SuppressWarnings("unused") private boolean stmts;
-        private int threshold;
-
-        @Override
-        public void initialize(String optionText) {
-            if (optionText.length() > 0) {
-                String[] subOptions = optionText.split(":");
-                for (String subOption : subOptions) {
-                    if (subOption.equals("stmts")) {
-                        Utils.warn("statement timing is not implemented");
-                        stmts = true;
-                    } else if (subOption.startsWith("threshold")) {
-                        threshold = Integer.parseInt(subOption.substring(subOption.indexOf('=') + 1)) * 1000;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public String getName() {
-            return NAME;
-        }
-
-        private static class SortableCounter implements Comparable<SortableCounter> {
-            private Counter counter;
-
-            SortableCounter(Counter counter) {
-                this.counter = counter;
-            }
-
-            @Override
-            public int compareTo(SortableCounter other) {
-                long myTime = counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
-                long otherTime = other.counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
-                return myTime < otherTime ? 1 : (myTime > otherTime ? -1 : 0);
-            }
-
-        }
-
-        /**
-         * Report the statement timing information at the end of the run. The report is per function
-         * Functions that consumed less time than requested threshold (default 0) are not included
-         * in the report. The report is sorted by cumulative time.
-         */
-        @Override
-        public void report() {
-            Profiler profiler = RContext.getInstance().getInstrumentationState().getProfiler();
-            // profiler.printHistograms(RPerfStats.out());
-            Map<SourceSection, Counter> counters = profiler.getCounters();
-            long totalTime = 0;
-            SortableCounter[] sortedCounters = new SortableCounter[counters.size()];
-            int i = 0;
-            for (Counter counter : counters.values()) {
-                totalTime += counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
-                sortedCounters[i++] = new SortableCounter(counter);
-            }
-            Arrays.sort(sortedCounters);
-            for (SortableCounter scounter : sortedCounters) {
-                long time = scounter.counter.getSelfTime(TimeKind.INTERPRETED_AND_COMPILED);
-                if (time > 0 && time > threshold) {
-                    SourceSection ss = scounter.counter.getSourceSection();
-                    Source source = ss.getSource();
-                    RPerfStats.out().println("==========");
-                    double thisPercent = percent(time, totalTime);
-                    RPerfStats.out().printf("%d ms (%.2f%%): %s, %s%n", time, thisPercent, scounter.counter.getName(), RSource.getOrigin(source));
-                }
-            }
-            System.console();
-        }
-
-        private static double percent(long a, long b) {
-            return ((double) a * 100) / b;
-        }
-
-    }
-}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java
index 25bf947a8304aebda46b083aa8fe12e93397d044..476d02bada8e22c5d41723fd882cdb0bbc2266ac 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/instrumentation/RInstrumentation.java
@@ -96,14 +96,11 @@ public class RInstrumentation {
      * Activate the instrumentation system for {@code context}. Currently this simply checks for the
      * global (command-line) options for tracing and timing. They are applied to every context.
      */
-    public static void activate(RContext context) {
+    public static void activate(@SuppressWarnings("unused") RContext context) {
         String rdebugValue = FastROptions.Rdebug.getStringValue();
         if (rdebugValue != null) {
             debugFunctionNames = rdebugValue.split(",");
         }
-        if (RFunctionProfiler.enabled()) {
-            RFunctionProfiler.installTimers(context);
-        }
         // Check for function tracing
         RContext.getRRuntimeASTAccess().traceAllFunctions();
     }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FastROptions.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FastROptions.java
index fb9aadcd68888f2d52dfbf0cbf6ddc9437b65fbd..0ab5761308e3f0f1be099fefdebc6dabab802672 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FastROptions.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/FastROptions.java
@@ -43,8 +43,6 @@ public enum FastROptions {
     TraceCalls("Trace all R function calls", false),
     TraceCallsToFile("TraceCalls output is sent to 'fastr_tracecalls.log'", false),
     TraceNativeCalls("Trace all native function calls (performed via .Call, .External, etc.)", false),
-    PerfStats("PerfStats=p1,p2,...; Collect performance stats identified by p1, etc.", null, true),
-    PerfStatsFile("PerfStatsFile=file; Send performance stats to 'file', default stdout", null, true),
     Rdebug("Rdebug=f1,f2.,,,; list of R function to call debug on (implies +Instrument)", null, true),
     PerformanceWarnings("Print FastR performance warning", false),
     LoadBase("Load base package", true),
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java
index 4df846f7beb1b36cfa4407146931e273abd2fd59..5069b1fd910103e0225d7f9f780375cce6cf36a3 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RCleanUp.java
@@ -11,13 +11,22 @@
  */
 package com.oracle.truffle.r.runtime;
 
+import java.util.ArrayList;
+
 import com.oracle.truffle.r.runtime.RStartParams.SA_TYPE;
 import com.oracle.truffle.r.runtime.context.ConsoleHandler;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.ffi.RFFIFactory;
+import com.oracle.truffle.r.runtime.instrument.InstrumentationState;
 
 public class RCleanUp {
 
+    private static ArrayList<InstrumentationState.CleanupHandler> cleanupHandlers = new ArrayList<>();
+
+    public static void registerCleanupHandler(InstrumentationState.CleanupHandler cleanupHandler) {
+        cleanupHandlers.add(cleanupHandler);
+    }
+
     public static void cleanUp(SA_TYPE saveType, int status, boolean runLast) {
         if (RInterfaceCallbacks.R_CleanUp.isOverridden()) {
             RFFIFactory.getRFFI().getREmbedRFFI().cleanUp(saveType.ordinal(), status, runLast ? 1 : 0);
@@ -87,6 +96,14 @@ public class RCleanUp {
             case SUICIDE:
             default:
 
+        }
+        for (InstrumentationState.CleanupHandler cleanupHandler : cleanupHandlers) {
+            try {
+                cleanupHandler.cleanup(status);
+            } catch (Throwable t) {
+                RInternalError.reportError(t);
+            }
+
         }
         // TODO run exit finalizers (FFI)
         // TODO clean tmpdir
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RPerfStats.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RPerfStats.java
deleted file mode 100644
index e655114e7aeec6d48bcc99bda719ed83909fb540..0000000000000000000000000000000000000000
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RPerfStats.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (c) 2014, 2016, 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;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-
-/**
- * Manage the creation/activation of handlers or performance analysis. Enabled by the
- * {@code FastROptions.Option.PerfStats} option.
- *
- * The handlers are all registered statically from the class wanting to participate. The handlers
- * are enabled selectively at runtime based on the command line option. An enabled handler gets a
- * call to its {@link Handler#initialize(String)} method so that it can enable its perf-mode
- * behavior.
- */
-public class RPerfStats {
-
-    public interface Handler {
-        /**
-         * Called on startup if enabled to initialize any necessary state.
-         *
-         * @param optionText any text after the handler name on the command line
-         */
-        void initialize(String optionText);
-
-        String getName();
-
-        void report();
-    }
-
-    public static class Histogram {
-        private final long[] hist;
-        private int maxSize = -1;
-
-        public Histogram(int buckets) {
-            hist = new long[buckets + 1];
-        }
-
-        public void inc(int size) {
-            if (size > maxSize) {
-                maxSize = size;
-            }
-            hist[effectiveBucket(size)]++;
-        }
-
-        public void dec(int size) {
-            hist[effectiveBucket(size)]--;
-        }
-
-        public int numBuckets() {
-            return hist.length - 1;
-        }
-
-        public long getMaxSize() {
-            return maxSize;
-        }
-
-        public int effectiveBucket(int size) {
-            if (size > hist.length - 1) {
-                return hist.length - 1;
-            } else {
-                return size;
-            }
-        }
-
-        public long getTotalCount() {
-            long totalCount = 0;
-            for (int i = 0; i < hist.length; i++) {
-                totalCount += hist[i];
-            }
-            return totalCount;
-        }
-
-        public void report() {
-            long maxCount = -1;
-            for (int i = 0; i < hist.length; i++) {
-                if (hist[i] > maxCount) {
-                    maxCount = hist[i];
-                }
-            }
-            int fieldWidth = Long.toString(maxCount).length() + 1;
-            if (fieldWidth < 10) {
-                fieldWidth = 10;
-            }
-            String fieldWidthString = Integer.toString(fieldWidth);
-            String sFormat = "%-" + fieldWidthString + "s";
-            String dFormat = "%-" + fieldWidthString + "d";
-            out().printf(sFormat, "Size");
-            for (int i = 0; i < hist.length - 1; i++) {
-                out().printf(dFormat, i);
-            }
-            out().printf(sFormat, "> " + (hist.length - 1));
-            out().println();
-            out().printf(sFormat, "Count");
-            for (int i = 0; i < hist.length - 1; i++) {
-                out().printf(dFormat, hist[i]);
-            }
-            out().printf(dFormat, hist[hist.length - 1]);
-            out().println();
-        }
-    }
-
-    private static final ArrayList<Handler> handlers = new ArrayList<>();
-    private static boolean initialized;
-    private static PrintStream out = System.out;
-
-    public static PrintStream out() {
-        return out;
-    }
-
-    /**
-     * Register a {@link Handler}. This should be done in a {@code static} block so that, in an AOT
-     * VM, all handlers are included in the image. N.B. Owing to dynamic class loading in a standard
-     * VM, this may be called after {@link RPerfStats#initialize}, so we may have to invoke
-     * {@code handler.initialize} from here.
-     */
-    public static void register(Handler handler) {
-        handlers.add(handler);
-        if (initialized) {
-            String optionText = getOptionText(handler.getName());
-            if (optionText != null) {
-                handler.initialize(optionText);
-            }
-        }
-    }
-
-    /**
-     * Called by the engine startup sequence to initialize all registered handlers.
-     */
-    public static void initialize() {
-        for (Handler handler : handlers) {
-            String optionText = getOptionText(handler.getName());
-            if (optionText != null) {
-                handler.initialize(optionText);
-            }
-        }
-        initialized = true;
-    }
-
-    private static String getOptionText(String name) {
-        String optionValue = getPerfStatsOption(name);
-        if (optionValue != null) {
-            if (optionValue.length() > 0) {
-                optionValue = optionValue.substring(name.length());
-            }
-        }
-        return optionValue;
-    }
-
-    private static String getPerfStatsOption(String name) {
-        return FastROptions.matchesElement(name, FastROptions.PerfStats.getStringValue());
-    }
-
-    public static boolean enabled(String name) {
-        return getPerfStatsOption(name) != null;
-    }
-
-    private static boolean reporting;
-
-    /**
-     * Called just before FastR exits.
-     */
-    public static void report() {
-        if (reporting) {
-            // some crash in a reporter caused a recursive entry
-            return;
-        }
-        reporting = true;
-        String file = FastROptions.PerfStatsFile.getStringValue();
-        if (file != null) {
-            try {
-                out = new PrintStream(new FileOutputStream(file));
-            } catch (IOException ex) {
-                System.err.print("PerfStats: can't open " + file + " for output, using stdout");
-            }
-        }
-        boolean headerOutput = false;
-        for (Handler handler : handlers) {
-            if (enabled(handler.getName())) {
-                if (!headerOutput) {
-                    out().println("RPerfStats Reports");
-                    headerOutput = true;
-                }
-                handler.report();
-            }
-        }
-    }
-}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/Utils.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/Utils.java
index 486c4b98687d8b2f54e026a3d432d2ecdb9cd7c0..35ed26d1d5d52539a19cbc667e585d10b4a8b45a 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/Utils.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/Utils.java
@@ -173,11 +173,8 @@ public final class Utils {
      * This the real, final, non-overrideable, exit of the entire R system. TODO well, modulo how
      * quit() is interpreted when R is started implicitly from a Polyglot shell that is running
      * other languages.
-     *
-     * @param status
      */
     public static void systemExit(int status) {
-        RPerfStats.report();
         System.exit(status);
     }
 
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RAttributes.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RAttributes.java
index 69956311d4b3aa3156ec465a70e3fa9fceb015b0..328b2cc713a467155b4e3e5fd46f2e2022d9e50f 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RAttributes.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RAttributes.java
@@ -23,15 +23,16 @@
 package com.oracle.truffle.r.runtime.data;
 
 import java.util.Arrays;
+import java.util.Deque;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedDeque;
 
 import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.CompilerDirectives.ValueType;
-import com.oracle.truffle.api.profiles.ConditionProfile;
-import com.oracle.truffle.r.runtime.RPerfStats;
+import com.oracle.truffle.api.utilities.CyclicAssumption;
 
 /**
  * Provides the generic mechanism for associating attributes with a R object. It does no special
@@ -71,8 +72,6 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
         }
     }
 
-    private static final ConditionProfile statsProfile = ConditionProfile.createBinaryProfile();
-
     public static RAttributes create() {
         return new RAttributes();
     }
@@ -98,8 +97,8 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
      */
 
     RAttributes() {
-        if (statsProfile.profile(stats != null)) {
-            stats.init();
+        if (AttributeTracer.enabled) {
+            AttributeTracer.reportAttributeChange(AttributeTracer.Change.CREATE, this, null);
         }
     }
 
@@ -130,6 +129,7 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
     public void put(String name, Object value) {
         assert isInterned(name);
         int pos = find(name);
+        int spos = pos;
         if (pos == -1) {
             ensureFreeSpace();
             pos = size++;
@@ -139,8 +139,8 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
         // assert value == null || !(value instanceof RShareable) || !((RShareable)
         // value).isTemporary();
         values[pos] = value;
-        if (statsProfile.profile(stats != null)) {
-            stats.update(this);
+        if (AttributeTracer.enabled) {
+            AttributeTracer.reportAttributeChange(spos == -1 ? AttributeTracer.Change.ADD : AttributeTracer.Change.UPDATE, this, name);
         }
     }
 
@@ -149,6 +149,9 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
             names = Arrays.copyOf(names, (size + 1) * 2);
             values = Arrays.copyOf(values, (size + 1) * 2);
             assert names.length == values.length;
+            if (AttributeTracer.enabled) {
+                AttributeTracer.reportAttributeChange(AttributeTracer.Change.GROW, this, names.length);
+            }
         }
     }
 
@@ -202,6 +205,9 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
             }
             names[size] = null;
             values[size] = null;
+            if (AttributeTracer.enabled) {
+                AttributeTracer.reportAttributeChange(AttributeTracer.Change.REMOVE, this, name);
+            }
         }
     }
 
@@ -267,55 +273,64 @@ public final class RAttributes implements Iterable<RAttributes.RAttribute> {
         throw new NoSuchElementException();
     }
 
-    // Performance analysis
-
-    @CompilationFinal private static PerfHandler stats;
-
-    static {
-        RPerfStats.register(new PerfHandler());
+    public void setSize(int size) {
+        this.size = size;
     }
 
-    /**
-     * Collects data on the maximum size of the attribute set. So only interested in adding not
-     * removing attributes.
-     */
-    private static class PerfHandler implements RPerfStats.Handler {
-        private static final RPerfStats.Histogram hist = new RPerfStats.Histogram(5);
+    public static final class AttributeTracer {
+        private static Deque<Listener> listeners = new ConcurrentLinkedDeque<>();
+        @CompilationFinal private static boolean enabled;
 
-        @TruffleBoundary
-        void init() {
-            hist.inc(0);
+        private static final CyclicAssumption noAttributeTracingAssumption = new CyclicAssumption("data copying");
+
+        private AttributeTracer() {
+            // only static methods
         }
 
-        @TruffleBoundary
-        void update(RAttributes attr) {
-            // incremented size by 1
-            int s = attr.size();
-            int effectivePrevSize = hist.effectiveBucket(s - 1);
-            int effectiveSizeNow = hist.effectiveBucket(s);
-            hist.dec(effectivePrevSize);
-            hist.inc(effectiveSizeNow);
+        /**
+         * Adds a listener of attribute events.
+         */
+        public static void addListener(Listener listener) {
+            listeners.addLast(listener);
         }
 
-        @Override
-        public void initialize(String optionText) {
-            stats = this;
+        /**
+         * After calling this method attribute related events will be reported to the listener. This
+         * invalidates global assumption and should be used with caution.
+         */
+        public static void setTracingState(boolean newState) {
+            if (enabled != newState) {
+                noAttributeTracingAssumption.invalidate();
+                enabled = newState;
+            }
         }
 
-        @Override
-        public String getName() {
-            return "attributes";
+        public enum Change {
+            CREATE,
+            ADD,
+            UPDATE,
+            REMOVE,
+            GROW
+        }
 
+        public static void reportAttributeChange(Change change, RAttributes attrs, Object value) {
+            if (enabled) {
+                for (Listener listener : listeners) {
+                    listener.reportAttributeChange(change, attrs, value);
+                }
+            }
         }
 
-        @Override
-        public void report() {
-            RPerfStats.out().printf("RAttributes: %d, max size %d%n", hist.getTotalCount(), hist.getMaxSize());
-            hist.report();
+        public interface Listener {
+            /**
+             * Reports attribute events to the listener. If there are no traced objects, this should
+             * turn into no-op. {@code valuer} depends on the value of {@code change}. For
+             * {@code CREATE} it is {@code null}, for {@code ADD,UPDATE, REMOVE} it is the
+             * {@code String} name of the attribute and for {@code GROW} it is the (new) size.
+             */
+            void reportAttributeChange(Change change, RAttributes attrs, Object value);
         }
-    }
 
-    public void setSize(int size) {
-        this.size = size;
     }
+
 }
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 dad4afd45a6e0e52823f4e946f459dd9a5514e1e..b9c726e6fcc73b1770b32b45eb6808b099a63c3f 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
@@ -24,27 +24,22 @@ package com.oracle.truffle.r.runtime.data;
 
 import java.util.Arrays;
 import java.util.Deque;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import com.oracle.truffle.api.Assumption;
 import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.RootCallTarget;
 import com.oracle.truffle.api.frame.MaterializedFrame;
 import com.oracle.truffle.api.utilities.CyclicAssumption;
 import com.oracle.truffle.r.runtime.FastROptions;
 import com.oracle.truffle.r.runtime.RCaller;
 import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.RPerfStats;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.builtins.RBuiltinDescriptor;
 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.gnur.SEXPTYPE;
 import com.oracle.truffle.r.runtime.nodes.RNode;
@@ -505,7 +500,7 @@ public final class RDataFactory {
     @CompilationFinal private static boolean enabled;
     private static final CyclicAssumption noAllocationTracingAssumption = new CyclicAssumption("data allocation");
 
-    public static void setAllocationTracing(boolean newState) {
+    public static void setTracingState(boolean newState) {
         if (enabled != newState) {
             noAllocationTracingAssumption.invalidate();
             enabled = newState;
@@ -541,64 +536,4 @@ public final class RDataFactory {
         listeners.addLast(listener);
     }
 
-    /*
-     * (Legacy) support for R:PerfStats option. This does produce more information than Rprofmem
-     * regarding the types and length of the objects being allocated, but it does not record where
-     * in R the allocation took place.
-     */
-    static {
-        RPerfStats.register(new PerfHandler());
-    }
-
-    private static class PerfHandler implements RPerfStats.Handler, Listener {
-        private static Map<Class<?>, RPerfStats.Histogram> histMap;
-
-        @Override
-        public void initialize(String optionData) {
-            histMap = new HashMap<>();
-            addListener(this);
-            setAllocationTracing(true);
-        }
-
-        @Override
-        public String getName() {
-            return "datafactory";
-        }
-
-        @Override
-        @TruffleBoundary
-        public void reportAllocation(RTypedValue data) {
-            Class<?> klass = data.getClass();
-            boolean isBounded = data instanceof RAbstractVector;
-            RPerfStats.Histogram hist = histMap.get(klass);
-            if (hist == null) {
-                hist = new RPerfStats.Histogram(isBounded ? 10 : 1);
-                histMap.put(klass, hist);
-            }
-            int length = isBounded ? ((RAbstractVector) data).getLength() : 0;
-            hist.inc(length);
-        }
-
-        @Override
-        public void report() {
-            RPerfStats.out().println("Scalar types");
-            for (Map.Entry<Class<?>, RPerfStats.Histogram> entry : histMap.entrySet()) {
-                RPerfStats.Histogram hist = entry.getValue();
-                if (hist.numBuckets() == 1) {
-                    RPerfStats.out().printf("%s: %d%n", entry.getKey().getSimpleName(), hist.getTotalCount());
-                }
-            }
-            RPerfStats.out().println();
-            RPerfStats.out().println("Vector types");
-            for (Map.Entry<Class<?>, RPerfStats.Histogram> entry : histMap.entrySet()) {
-                RPerfStats.Histogram hist = entry.getValue();
-                if (hist.numBuckets() > 1) {
-                    RPerfStats.out().printf("%s: %d, max size %d%n", entry.getKey().getSimpleName(), hist.getTotalCount(), hist.getMaxSize());
-                    entry.getValue().report();
-                }
-            }
-            RPerfStats.out().println();
-        }
-
-    }
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
index a75b54fdb4bd80a721e5dfb173f611368db7cb62..0115ab8555136eec2d0fc9d786b4a4aa9726b49a 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
@@ -26,12 +26,9 @@ import java.util.function.Function;
 
 import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
-import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.RPerfStats;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.RType;
 import com.oracle.truffle.r.runtime.SuppressFBWarnings;
@@ -553,7 +550,6 @@ public abstract class RVector extends RSharingAttributeStorage implements RShare
     public final RVector copy() {
         RVector result = internalCopyAndReport();
         setAttributes(result);
-        incCopyCount();
         result.setTypedValueInfo(getTypedValueInfo());
         return result;
     }
@@ -842,45 +838,6 @@ public abstract class RVector extends RSharingAttributeStorage implements RShare
         }
     }
 
-    private static final ConditionProfile statsProfile = ConditionProfile.createBinaryProfile();
-
-    @CompilationFinal private static PerfHandler stats;
-
-    private static void incCopyCount() {
-        if (statsProfile.profile(stats != null)) {
-            stats.record(null);
-        }
-    }
-
-    static {
-        RPerfStats.register(new PerfHandler());
-    }
-
-    private static class PerfHandler implements RPerfStats.Handler {
-
-        private static int count;
-
-        void record(@SuppressWarnings("unused") Object data) {
-            count++;
-        }
-
-        @Override
-        public void initialize(String optionData) {
-            stats = this;
-            count = 0;
-        }
-
-        @Override
-        public String getName() {
-            return "vectorcopies";
-        }
-
-        @Override
-        public void report() {
-            RPerfStats.out().printf("NUMBER OF VECTOR COPIES: %d\n", count);
-        }
-    }
-
     private static final int MAX_TOSTRING_LENGTH = 100;
 
     protected String toString(Function<Integer, String> element) {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java
index 0b601ce31970ad98c9347a687fc8de366dbcef6c..786cd82929516d3c34d38258ac1554978a7e6416 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java
@@ -22,21 +22,22 @@
  */
 package com.oracle.truffle.r.runtime.instrument;
 
-import java.io.PrintWriter;
+import java.io.PrintStream;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.WeakHashMap;
-
+import java.util.concurrent.ConcurrentHashMap;
 import com.oracle.truffle.api.instrumentation.EventBinding;
 import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
 import com.oracle.truffle.api.instrumentation.Instrumenter;
 import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.api.vm.PolyglotEngine;
+import com.oracle.truffle.r.runtime.RCleanUp;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.tools.Profiler;
 
 /**
- * The tracingState is a global variable in R, so we store it (and the associated listener objects)
- * in the {@link RContext}. We also store related {@code debug} state, as that is also context
- * specific.
+ * Collects together all the context-specific state related to profiling, instrumentation.
  *
  */
 public final class InstrumentationState implements RContext.ContextState {
@@ -63,17 +64,9 @@ public final class InstrumentationState implements RContext.ContextState {
      */
     private Profiler profiler;
 
-    /**
-     * The {@link RprofState} state, if any, associated with this {@link RContext}.
-     */
-    private final RprofState rprofState;
-
-    /**
-     * The {@link RprofmemState} state, if any, associated with this {@link RContext}.
-     */
-    private final RprofmemState rprofmemState;
+    private TracememContext tracememContext;
 
-    private final TracememContext tracememContext;
+    Map<String, RprofState> rprofStates = new ConcurrentHashMap<>(7);
 
     /**
      * State used by the {@code tracemem} built-in.
@@ -92,7 +85,7 @@ public final class InstrumentationState implements RContext.ContextState {
     /**
      * The {@link BrowserState} state, if any, associated with this {@link RContext}.
      */
-    private final BrowserState browserState;
+    private BrowserState browserState;
 
     /**
      * Whether debugging is globally disabled in this {@link RContext}. Used to (temporarily)
@@ -101,107 +94,28 @@ public final class InstrumentationState implements RContext.ContextState {
      */
     private boolean debugGloballyDisabled;
 
-    private abstract static class RprofAdapter {
-        protected PrintWriter out;
+    public abstract static class RprofState implements CleanupHandler {
+        private PrintStream out;
+
+        protected RprofState() {
+            RCleanUp.registerCleanupHandler(this);
+        }
 
         /**
          * Return current output or {@code null} if not profiling.
          */
-        public PrintWriter out() {
+        public PrintStream out() {
             return out;
         }
 
-        public void setOut(PrintWriter out) {
+        public void setOut(PrintStream out) {
             this.out = out;
         }
-    }
-
-    /**
-     * State used by {@code Rprof}.
-     *
-     */
-    public static final class RprofState extends RprofAdapter {
-        private Thread profileThread;
-        private ExecutionEventListener statementListener;
-        private long intervalInMillis;
-        private boolean lineProfiling;
-        private MemoryQuad memoryQuad;
-
-        public static final class MemoryQuad {
-            public long smallV;
-            public long largeV;
-            public long nodes;
-            public long copied;
-
-            public MemoryQuad copyAndClear() {
-                MemoryQuad result = new MemoryQuad();
-                result.copied = copied;
-                result.largeV = largeV;
-                result.smallV = smallV;
-                result.nodes = nodes;
-                copied = 0;
-                largeV = 0;
-                smallV = 0;
-                nodes = 0;
-                return result;
-            }
-        }
-
-        public void initialize(PrintWriter outA, Thread profileThreadA, ExecutionEventListener statementListenerA, long intervalInMillisA,
-                        boolean lineProfilingA, boolean memoryProfilingA) {
-            this.out = outA;
-            this.profileThread = profileThreadA;
-            this.statementListener = statementListenerA;
-            this.intervalInMillis = intervalInMillisA;
-            this.lineProfiling = lineProfilingA;
-            this.memoryQuad = memoryProfilingA ? new MemoryQuad() : null;
-        }
-
-        public boolean lineProfiling() {
-            return lineProfiling;
-        }
-
-        public boolean memoryProfiling() {
-            return memoryQuad != null;
-        }
-
-        public MemoryQuad memoryQuad() {
-            return memoryQuad;
-        }
-
-        public long intervalInMillis() {
-            return intervalInMillis;
-        }
-
-        public ExecutionEventListener statementListener() {
-            return statementListener;
-        }
-
-        public Thread profileThread() {
-            return profileThread;
-        }
-
-    }
-
-    public static final class RprofmemState extends RprofAdapter {
-        private double threshold;
-        private int pageCount;
-
-        public void initialize(PrintWriter outA, double thresholdA) {
-            this.out = outA;
-            this.threshold = thresholdA;
-        }
-
-        public double threshold() {
-            return threshold;
-        }
 
-        public int pageCount() {
-            return pageCount;
-        }
-
-        public void setPageCount(int pageCount) {
-            this.pageCount = pageCount;
+        public void closeAndResetOut() {
+            out.flush();
+            out.close();
+            out = null;
         }
     }
 
@@ -226,12 +140,17 @@ public final class InstrumentationState implements RContext.ContextState {
         }
     }
 
+    /**
+     * An interface that can be used to register an action to be taken when the system shuts down as
+     * part of the {@code R_Cleanup}.
+     *
+     */
+    public interface CleanupHandler {
+        void cleanup(int status);
+    }
+
     private InstrumentationState(Instrumenter instrumenter) {
         this.instrumenter = instrumenter;
-        this.rprofState = new RprofState();
-        this.rprofmemState = new RprofmemState();
-        this.tracememContext = new TracememContext();
-        this.browserState = new BrowserState();
     }
 
     public void putTraceBinding(SourceSection ss, EventBinding<?> binding) {
@@ -267,11 +186,11 @@ public final class InstrumentationState implements RContext.ContextState {
         return tracingState;
     }
 
-    public void setProfiler(Profiler profiler) {
-        this.profiler = profiler;
-    }
-
     public Profiler getProfiler() {
+        if (profiler == null) {
+            PolyglotEngine vm = RContext.getInstance().getVM();
+            profiler = Profiler.find(vm);
+        }
         return profiler;
     }
 
@@ -279,19 +198,26 @@ public final class InstrumentationState implements RContext.ContextState {
         return instrumenter;
     }
 
-    public RprofState getRprof() {
-        return rprofState;
+    public RprofState getRprofState(String name) {
+        RprofState state = rprofStates.get(name);
+        return state;
     }
 
-    public RprofmemState getRprofmem() {
-        return rprofmemState;
+    public void setRprofState(String name, RprofState state) {
+        rprofStates.put(name, state);
     }
 
     public TracememContext getTracemem() {
+        if (tracememContext == null) {
+            tracememContext = new TracememContext();
+        }
         return tracememContext;
     }
 
     public BrowserState getBrowserState() {
+        if (browserState == null) {
+            browserState = new BrowserState();
+        }
         return browserState;
     }
 
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java
index 7b40db76e668e950a4a887f015dafb093c931042..189d8ab31ee6be1b3561b5f67e0565bb191f590f 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java
@@ -38,7 +38,6 @@ import org.junit.runner.Description;
 import org.junit.runner.Result;
 
 import com.oracle.truffle.r.runtime.RInternalError;
-import com.oracle.truffle.r.runtime.RPerfStats;
 import com.oracle.truffle.r.runtime.ResourceHandlerFactory;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.context.ContextInfo;
@@ -203,7 +202,6 @@ public class TestBase {
                 if (diffsOutputFile != null) {
                     TestOutputManager.writeDiffsTestOutputFile(diffsOutputFile, expectedOutputManager, fastROutputManager);
                 }
-                RPerfStats.report();
             } catch (IOException ex) {
                 throw new RuntimeException(ex);
             }