upload JSC heap capture to bundle server
Reviewed By: bestander Differential Revision: D3642116 fbshipit-source-id: 9626078bb0f087f55d8270c8e0b082c74bd2df9d
This commit is contained in:
Родитель
73f3713cd0
Коммит
aba87550cc
|
@ -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));
|
||||
|
|
Загрузка…
Ссылка в новой задаче