diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js
index 5b582be40..70ebfe3be 100644
--- a/Libraries/BatchedBridge/BatchedBridge.js
+++ b/Libraries/BatchedBridge/BatchedBridge.js
@@ -24,6 +24,7 @@ const JSTimersExecution = require('JSTimersExecution');
BatchedBridge.registerCallableModule('Systrace', Systrace);
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);
+BatchedBridge.registerCallableModule('HeapCapture', require('HeapCapture'));
if (__DEV__) {
BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
diff --git a/Libraries/Utilities/HeapCapture.js b/Libraries/Utilities/HeapCapture.js
new file mode 100644
index 000000000..264a84e42
--- /dev/null
+++ b/Libraries/Utilities/HeapCapture.js
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule HeapCapture
+ * @flow
+ */
+'use strict';
+
+var HeapCapture = {
+ captureHeap: function (token: number, path: string) {
+ var error = null;
+ try {
+ global.nativeCaptureHeap(path);
+ console.log('HeapCapture.captureHeap succeeded: ' + path);
+ } catch (e) {
+ console.log('HeapCapture.captureHeap error: ' + e.toString());
+ error = e.toString();
+ }
+ require('NativeModules').JSCHeapCapture.operationComplete(token, error);
+ },
+};
+
+module.exports = HeapCapture;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
index d7b34e270..9b975bf55 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
@@ -16,6 +16,7 @@ import java.util.List;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.devsupport.JSCHeapCapture;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ExceptionsManagerModule;
@@ -84,6 +85,7 @@ import com.facebook.systrace.Systrace;
mReactInstanceManager.getSourceUrl(),
mReactInstanceManager.getDevSupportManager().getSourceMapUrl()),
uiManagerModule,
+ new JSCHeapCapture(catalystApplicationContext),
new DebugComponentOwnershipModule(catalystApplicationContext));
}
@@ -97,6 +99,7 @@ import com.facebook.systrace.Systrace;
AppRegistry.class,
com.facebook.react.bridge.Systrace.class,
HMRClient.class,
+ JSCHeapCapture.HeapCapture.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
index 087eb6686..84bef62fb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
@@ -304,6 +304,23 @@ public class DevSupportManagerImpl implements DevSupportManager {
mReactInstanceCommandsHandler.toggleElementInspector();
}
});
+ options.put(
+ mApplicationContext.getString(R.string.catalyst_heap_capture),
+ new DevOptionHandler() {
+ @Override
+ public void onOptionSelected() {
+ try {
+ String heapDumpPath = mApplicationContext.getCacheDir() + "/heapdump.json";
+ JSCHeapCapture.captureHeap(heapDumpPath, 60000);
+ Toast.makeText(
+ mCurrentContext,
+ "Heap captured to " + heapDumpPath,
+ Toast.LENGTH_LONG).show();
+ } catch (JSCHeapCapture.CaptureException e) {
+ showNewJavaError(e.getMessage(), e);
+ }
+ }
+ });
options.put(
mDevSettings.isFpsDebugEnabled()
? mApplicationContext.getString(R.string.catalyst_perf_monitor_off)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapCapture.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapCapture.java
new file mode 100644
index 000000000..b2bdc5e3d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapCapture.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.devsupport;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+
+public class JSCHeapCapture extends ReactContextBaseJavaModule {
+ public interface HeapCapture extends JavaScriptModule {
+ void captureHeap(int token, String path);
+ void setAllocationTracking(int token, boolean enabled);
+ }
+
+ public static class CaptureException extends Exception {
+ CaptureException(String message) {
+ super(message);
+ }
+ }
+
+ private @Nullable HeapCapture mHeapCapture;
+ private boolean mOperationInProgress;
+ private int mOperationToken;
+ private @Nullable String mOperationError;
+
+ private static @Nullable JSCHeapCapture sJSCHeapCapture = null;
+
+ private static synchronized void registerHeapCapture(JSCHeapCapture dumper) {
+ if (sJSCHeapCapture != null) {
+ throw new RuntimeException(
+ "JSCHeapCapture already registered. Are you running more than one JSC?");
+ }
+ sJSCHeapCapture = dumper;
+ }
+
+ private static synchronized void unregisterHeapCapture(JSCHeapCapture dumper) {
+ if (sJSCHeapCapture != dumper) {
+ throw new RuntimeException("Can't unregister JSCHeapCapture that is not registered.");
+ }
+ sJSCHeapCapture = null;
+ }
+
+ public static synchronized void captureHeap(String path, long timeout) throws CaptureException {
+ if (sJSCHeapCapture == null) {
+ throw new CaptureException("No JSC registered.");
+ }
+ sJSCHeapCapture.captureHeapHelper(path, timeout);
+ }
+
+ public JSCHeapCapture(ReactApplicationContext reactContext) {
+ super(reactContext);
+ mHeapCapture = null;
+ mOperationInProgress = false;
+ mOperationToken = 0;
+ mOperationError = null;
+ }
+
+ private synchronized void captureHeapHelper(String path, long timeout) throws CaptureException {
+ if (mHeapCapture == null) {
+ throw new CaptureException("HeapCapture.js module not connected");
+ }
+ mHeapCapture.captureHeap(getOperationToken(), path);
+ waitForOperation(timeout);
+ }
+
+ private int getOperationToken() throws CaptureException {
+ if (mOperationInProgress) {
+ throw new CaptureException("Another operation already in progress.");
+ }
+ mOperationInProgress = true;
+ return ++mOperationToken;
+ }
+
+ private void waitForOperation(long timeout) throws CaptureException {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ throw new CaptureException("Waiting for heap capture failed: " + e.getMessage());
+ }
+
+ if (mOperationInProgress) {
+ mOperationInProgress = false;
+ throw new CaptureException("heap capture timed out.");
+ }
+
+ if (mOperationError != null) {
+ throw new CaptureException(mOperationError);
+ }
+ }
+
+ @ReactMethod
+ public synchronized void operationComplete(int token, String error) {
+ if (token == mOperationToken) {
+ mOperationInProgress = false;
+ mOperationError = error;
+ this.notify();
+ } else {
+ throw new RuntimeException("Completed operation is not in progress.");
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "JSCHeapCapture";
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ mHeapCapture = getReactApplicationContext().getJSModule(HeapCapture.class);
+ registerHeapCapture(this);
+ }
+
+ @Override
+ public void onCatalystInstanceDestroy() {
+ super.onCatalystInstanceDestroy();
+ unregisterHeapCapture(this);
+ mHeapCapture = null;
+ }
+}
diff --git a/ReactAndroid/src/main/res/devsupport/values/strings.xml b/ReactAndroid/src/main/res/devsupport/values/strings.xml
index a685b5ab8..e1f914dee 100644
--- a/ReactAndroid/src/main/res/devsupport/values/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values/strings.xml
@@ -19,4 +19,5 @@
Toggle Inspector
Start Profile
Stop Profile
+ Capture Heap