upload JSC heap capture to bundle server

Reviewed By: bestander

Differential Revision: D3642116

fbshipit-source-id: 9626078bb0f087f55d8270c8e0b082c74bd2df9d
This commit is contained in:
Charles Dick 2016-08-02 08:05:50 -07:00 коммит произвёл Facebook Github Bot
Родитель 73f3713cd0
Коммит aba87550cc
10 изменённых файлов: 187 добавлений и 72 удалений

Просмотреть файл

@ -12,7 +12,7 @@
'use strict';
var HeapCapture = {
captureHeap: function (token: number, path: string) {
captureHeap: function (path: string) {
var error = null;
try {
global.nativeCaptureHeap(path);
@ -21,7 +21,7 @@ var HeapCapture = {
console.log('HeapCapture.captureHeap error: ' + e.toString());
error = e.toString();
}
require('NativeModules').JSCHeapCapture.operationComplete(token, error);
require('NativeModules').JSCHeapCapture.captureComplete(path, error);
},
};

Просмотреть файл

@ -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.common.build.ReactBuildConfig;
import com.facebook.react.devsupport.JSCHeapCapture;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
@ -73,7 +74,7 @@ import com.facebook.systrace.Systrace;
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
return Arrays.<NativeModule>asList(
List<NativeModule> nativeModulesList = new ArrayList<>(Arrays.<NativeModule>asList(
new AnimationsDebugModule(
catalystApplicationContext,
mReactInstanceManager.getDevSupportManager().getDevSettings()),
@ -83,13 +84,18 @@ import com.facebook.systrace.Systrace;
new Timing(catalystApplicationContext, mReactInstanceManager.getDevSupportManager()),
new SourceCodeModule(mReactInstanceManager.getSourceUrl()),
uiManagerModule,
new JSCHeapCapture(catalystApplicationContext),
new DebugComponentOwnershipModule(catalystApplicationContext));
new DebugComponentOwnershipModule(catalystApplicationContext)));
if (ReactBuildConfig.DEBUG) {
nativeModulesList.add(new JSCHeapCapture(catalystApplicationContext));
}
return nativeModulesList;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Arrays.asList(
List<Class<? extends JavaScriptModule>> jsModules = new ArrayList<>(Arrays.asList(
DeviceEventManagerModule.RCTDeviceEventEmitter.class,
JSTimersExecution.class,
RCTEventEmitter.class,
@ -97,8 +103,13 @@ import com.facebook.systrace.Systrace;
AppRegistry.class,
com.facebook.react.bridge.Systrace.class,
HMRClient.class,
JSCHeapCapture.HeapCapture.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class));
if (ReactBuildConfig.DEBUG) {
jsModules.add(JSCHeapCapture.HeapCapture.class);
}
return jsModules;
}
@Override

Просмотреть файл

@ -62,6 +62,7 @@ public class DevServerHelper {
private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client";
private static final String PACKAGER_CONNECTION_URL_FORMAT = "ws://%s/message?role=shell";
private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status";
private static final String HEAP_CAPTURE_UPLOAD_URL_FORMAT = "http://%s/jscheapcaptureupload";
private static final String PACKAGER_OK_STATUS = "packager-status:running";
@ -130,6 +131,10 @@ public class DevServerHelper {
return String.format(Locale.US, PACKAGER_CONNECTION_URL_FORMAT, getDebugServerHost());
}
public String getHeapCaptureUploadUrl() {
return String.format(Locale.US, HEAP_CAPTURE_UPLOAD_URL_FORMAT, getDebugServerHost());
}
/**
* @return the host to use when connecting to the bundle server from the host itself.
*/
@ -383,7 +388,7 @@ public class DevServerHelper {
}
private String createLaunchJSDevtoolsCommandUrl() {
return String.format(LAUNCH_JS_DEVTOOLS_COMMAND_URL_FORMAT, getDebugServerHost());
return String.format(Locale.US, LAUNCH_JS_DEVTOOLS_COMMAND_URL_FORMAT, getDebugServerHost());
}
public void launchJSDevtools() {

Просмотреть файл

@ -39,6 +39,7 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
String getSourceUrl();
String getJSBundleURLForRemoteDebugging();
String getDownloadedJSBundleFile();
String getHeapCaptureUploadUrl();
boolean hasUpToDateJSBundleInCache();
void reloadSettings();
void handleReloadJS();

Просмотреть файл

@ -370,18 +370,9 @@ public class DevSupportManagerImpl implements DevSupportManager {
new DevOptionHandler() {
@Override
public void onOptionSelected() {
try {
String heapDumpPath = mApplicationContext.getCacheDir().getPath();
List<String> captureFiles = JSCHeapCapture.captureHeap(heapDumpPath, 60000);
for (String captureFile : captureFiles) {
Toast.makeText(
mCurrentContext,
"Heap captured to " + captureFile,
Toast.LENGTH_LONG).show();
}
} catch (JSCHeapCapture.CaptureException e) {
showNewJavaError(e.getMessage(), e);
}
JSCHeapCapture.captureHeap(
mApplicationContext.getCacheDir().getPath(),
JSCHeapUpload.captureCallback(mDevServerHelper.getHeapCaptureUploadUrl()));
}
});
options.put(
@ -486,6 +477,11 @@ public class DevSupportManagerImpl implements DevSupportManager {
return mJSBundleTempFile.getAbsolutePath();
}
@Override
public String getHeapCaptureUploadUrl() {
return mDevServerHelper.getHeapCaptureUploadUrl();
}
/**
* @return {@code true} if {@link com.facebook.react.ReactInstanceManager} should use downloaded JS bundle file
* instead of using JS file from assets. This may happen when app has not been updated since

Просмотреть файл

@ -104,6 +104,11 @@ public class DisabledDevSupportManager implements DevSupportManager {
return null;
}
@Override
public String getHeapCaptureUploadUrl() {
return null;
}
@Override
public boolean hasUpToDateJSBundleInCache() {
return false;

Просмотреть файл

@ -23,20 +23,29 @@ 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);
void captureHeap(String path);
}
public static class CaptureException extends Exception {
CaptureException(String message) {
super(message);
}
CaptureException(String message, Throwable cause) {
super(message, cause);
}
}
public interface CaptureCallback {
void onComplete(List<File> captures, List<CaptureException> failures);
}
private interface PerCaptureCallback {
void onSuccess(File capture);
void onFailure(CaptureException cause);
}
private @Nullable HeapCapture mHeapCapture;
private boolean mOperationInProgress;
private int mOperationToken;
private @Nullable String mOperationError;
private @Nullable PerCaptureCallback mCaptureInProgress;
private static final HashSet<JSCHeapCapture> sRegisteredDumpers = new HashSet<>();
@ -52,11 +61,14 @@ public class JSCHeapCapture extends ReactContextBaseJavaModule {
sRegisteredDumpers.remove(dumper);
}
public static synchronized List<String> captureHeap(String path, long timeout)
throws CaptureException {
LinkedList<String> captureFiles = new LinkedList<>();
public static synchronized void captureHeap(String path, final CaptureCallback callback) {
final LinkedList<File> captureFiles = new LinkedList<>();
final LinkedList<CaptureException> captureFailures = new LinkedList<>();
if (sRegisteredDumpers.isEmpty()) {
throw new CaptureException("No JSC registered");
captureFailures.add(new CaptureException("No JSC registered"));
callback.onComplete(captureFiles, captureFailures);
return;
}
int disambiguate = 0;
@ -66,64 +78,57 @@ public class JSCHeapCapture extends ReactContextBaseJavaModule {
f = new File(path + "/capture" + Integer.toString(disambiguate) + ".json");
}
final int numRegisteredDumpers = sRegisteredDumpers.size();
disambiguate = 0;
for (JSCHeapCapture dumper : sRegisteredDumpers) {
String file = path + "/capture" + Integer.toString(disambiguate) + ".json";
dumper.captureHeapHelper(file, timeout);
captureFiles.add(file);
File file = new File(path + "/capture" + Integer.toString(disambiguate) + ".json");
dumper.captureHeapHelper(file, new PerCaptureCallback() {
@Override
public void onSuccess(File capture) {
captureFiles.add(capture);
if (captureFiles.size() + captureFailures.size() == numRegisteredDumpers) {
callback.onComplete(captureFiles, captureFailures);
}
}
@Override
public void onFailure(CaptureException cause) {
captureFailures.add(cause);
if (captureFiles.size() + captureFailures.size() == numRegisteredDumpers) {
callback.onComplete(captureFiles, captureFailures);
}
}
});
}
return captureFiles;
}
public JSCHeapCapture(ReactApplicationContext reactContext) {
super(reactContext);
mHeapCapture = null;
mOperationInProgress = false;
mOperationToken = 0;
mOperationError = null;
mCaptureInProgress = null;
}
private synchronized void captureHeapHelper(String path, long timeout) throws CaptureException {
private synchronized void captureHeapHelper(File file, PerCaptureCallback callback) {
if (mHeapCapture == null) {
throw new CaptureException("HeapCapture.js module not connected");
callback.onFailure(new CaptureException("HeapCapture.js module not connected"));
return;
}
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);
if (mCaptureInProgress != null) {
callback.onFailure(new CaptureException("Heap capture already in progress"));
return;
}
mCaptureInProgress = callback;
mHeapCapture.captureHeap(file.getPath());
}
@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.");
public synchronized void captureComplete(String path, String error) {
if (mCaptureInProgress != null) {
if (error == null) {
mCaptureInProgress.onSuccess(new File(path));
} else {
mCaptureInProgress.onFailure(new CaptureException(error));
}
mCaptureInProgress = null;
}
}

Просмотреть файл

@ -0,0 +1,64 @@
/**
* Copyright (c) 2016-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 android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* Created by cwdick on 7/22/16.
*/
public class JSCHeapUpload {
public static JSCHeapCapture.CaptureCallback captureCallback(final String uploadUrl) {
return new JSCHeapCapture.CaptureCallback() {
@Override
public void onComplete(
List<File> captures,
List<JSCHeapCapture.CaptureException> failures) {
for (JSCHeapCapture.CaptureException e : failures) {
Log.e("JSCHeapCapture", e.getMessage());
}
OkHttpClient httpClient = new OkHttpClient.Builder().build();
for (File path : captures) {
RequestBody body = RequestBody.create(MediaType.parse("application/json"), path);
Request request = new Request.Builder()
.url(uploadUrl)
.method("POST", body)
.build();
Call call = httpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("JSCHeapCapture", "Upload of heap capture failed: " + e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
Log.e("JSCHeapCapture", "Upload of heap capture failed with code: " + Integer.toString(response.code()));
}
}
});
}
}
};
}
}

Просмотреть файл

@ -0,0 +1,26 @@
/**
* Copyright (c) 2016-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.
*/
'use strict';
const exec = require('child_process').exec;
const fs = require('fs');
const path = require('path');
module.exports = function(req, res, next) {
if (req.url !== '/jscheapcaptureupload') {
next();
return;
}
console.log('Receiving heap capture...');
var captureName = '/tmp/capture_' + Date.now() + '.json';
fs.writeFileSync(captureName, req.rawBody);
console.log('Capture written to ' + captureName);
res.end();
};

Просмотреть файл

@ -22,6 +22,7 @@ const ReactPackager = require('../../packager/react-packager');
const statusPageMiddleware = require('./middleware/statusPageMiddleware.js');
const indexPageMiddleware = require('./middleware/indexPage');
const systraceProfileMiddleware = require('./middleware/systraceProfileMiddleware.js');
const heapCaptureMiddleware = require('./middleware/heapCaptureMiddleware.js');
const webSocketProxy = require('./util/webSocketProxy.js');
function runServer(args, config, readyCallback) {
@ -37,6 +38,7 @@ function runServer(args, config, readyCallback) {
.use(copyToClipBoardMiddleware)
.use(statusPageMiddleware)
.use(systraceProfileMiddleware)
.use(heapCaptureMiddleware)
.use(cpuProfilerMiddleware)
.use(indexPageMiddleware)
.use(packagerServer.processRequest.bind(packagerServer));