From 24b2990467bfd4a7464083c288d6cf8c77344987 Mon Sep 17 00:00:00 2001 From: Charles Dick Date: Fri, 29 Apr 2016 07:34:51 -0700 Subject: [PATCH] Capture heap from the dev menu Reviewed By: foghina Differential Revision: D3229856 fb-gh-sync-id: c6321cfb309c93572a51bba625d63246a75f0254 fbshipit-source-id: c6321cfb309c93572a51bba625d63246a75f0254 --- Libraries/BatchedBridge/BatchedBridge.js | 1 + Libraries/Utilities/HeapCapture.js | 28 ++++ .../facebook/react/CoreModulesPackage.java | 3 + .../devsupport/DevSupportManagerImpl.java | 17 +++ .../react/devsupport/JSCHeapCapture.java | 130 ++++++++++++++++++ .../main/res/devsupport/values/strings.xml | 1 + 6 files changed, 180 insertions(+) create mode 100644 Libraries/Utilities/HeapCapture.js create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapCapture.java 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