Skip to content

Commit

Permalink
facebook#15 Add a button in devtools to start/stop the Sampling Profiler
Browse files Browse the repository at this point in the history
Differential Revision: D3555704

fbshipit-source-id: 4add16c923fcfd306892efec4630c24ae438d6dd
  • Loading branch information
lukaspiatkowski authored and Morgan Pretty committed Aug 24, 2016
1 parent 470d486 commit 6ed6a7b
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 0 deletions.
1 change: 1 addition & 0 deletions Libraries/BatchedBridge/BatchedBridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const JSTimersExecution = require('JSTimersExecution');
BatchedBridge.registerCallableModule('Systrace', Systrace);
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);
BatchedBridge.registerCallableModule('HeapCapture', require('HeapCapture'));
BatchedBridge.registerCallableModule('SamplingProfiler', require('SamplingProfiler'));

if (__DEV__) {
BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
Expand Down
35 changes: 35 additions & 0 deletions Libraries/Utilities/SamplingProfiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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 SamplingProfiler
* @flow
*/
'use strict';

var SamplingProfiler = {
poke: function (token: number): void {
var error = null;
var result = null;
try {
result = global.pokeSamplingProfiler();
if (result === null) {
console.log('The JSC Sampling Profiler has started');
} else {
console.log('The JSC Sampling Profiler has stopped');
}
} catch (e) {
console.log(
'Error occured when restarting Sampling Profiler: ' + e.toString());
error = e.toString();
}
require('NativeModules').JSCSamplingProfiler.operationComplete(
token, result, error);
},
};

module.exports = SamplingProfiler;
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.devsupport.JSCHeapCapture;
import com.facebook.react.devsupport.JSCSamplingProfiler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ExceptionsManagerModule;
Expand Down Expand Up @@ -84,6 +85,7 @@ public List<NativeModule> createNativeModules(
new Timing(catalystApplicationContext, mReactInstanceManager.getDevSupportManager()),
new SourceCodeModule(mReactInstanceManager.getSourceUrl()),
uiManagerModule,
new JSCSamplingProfiler(catalystApplicationContext),
new DebugComponentOwnershipModule(catalystApplicationContext)));

if (ReactBuildConfig.DEBUG) {
Expand All @@ -103,6 +105,7 @@ public List<Class<? extends JavaScriptModule>> createJSModules() {
AppRegistry.class,
com.facebook.react.bridge.Systrace.class,
HMRClient.class,
JSCSamplingProfiler.SamplingProfiler.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class));

if (ReactBuildConfig.DEBUG) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,26 @@ public void onOptionSelected() {
JSCHeapUpload.captureCallback(mDevServerHelper.getHeapCaptureUploadUrl()));
}
});
options.put(
mApplicationContext.getString(R.string.catalyst_poke_sampling_profiler),
new DevOptionHandler() {
@Override
public void onOptionSelected() {
try {
List<String> pokeResults = JSCSamplingProfiler.poke(60000);
for (String result : pokeResults) {
Toast.makeText(
mCurrentContext,
result == null
? "Started JSC Sampling Profiler"
: "Stopped JSC Sampling Profiler",
Toast.LENGTH_LONG).show();
}
} catch (JSCSamplingProfiler.ProfilerException e) {
showNewJavaError(e.getMessage(), e);
}
}
});
options.put(
mApplicationContext.getString(R.string.catalyst_settings), new DevOptionHandler() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* 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 java.io.File;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

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 JSCSamplingProfiler extends ReactContextBaseJavaModule {
public interface SamplingProfiler extends JavaScriptModule {
void poke(int token);
}

public static class ProfilerException extends Exception {
ProfilerException(String message) {
super(message);
}
}

private @Nullable SamplingProfiler mSamplingProfiler;
private boolean mOperationInProgress;
private int mOperationToken;
private @Nullable String mOperationError;
private @Nullable String mSamplingProfilerResult;

private static final HashSet<JSCSamplingProfiler> sRegisteredDumpers =
new HashSet<>();

private static synchronized void registerSamplingProfiler(
JSCSamplingProfiler dumper) {
if (sRegisteredDumpers.contains(dumper)) {
throw new RuntimeException(
"a JSCSamplingProfiler registered more than once");
}
sRegisteredDumpers.add(dumper);
}

private static synchronized void unregisterSamplingProfiler(
JSCSamplingProfiler dumper) {
sRegisteredDumpers.remove(dumper);
}

public static synchronized List<String> poke(long timeout)
throws ProfilerException {
LinkedList<String> results = new LinkedList<>();
if (sRegisteredDumpers.isEmpty()) {
throw new ProfilerException("No JSC registered");
}

for (JSCSamplingProfiler dumper : sRegisteredDumpers) {
dumper.pokeHelper(timeout);
results.add(dumper.mSamplingProfilerResult);
}
return results;
}

public JSCSamplingProfiler(ReactApplicationContext reactContext) {
super(reactContext);
mSamplingProfiler = null;
mOperationInProgress = false;
mOperationToken = 0;
mOperationError = null;
mSamplingProfilerResult = null;
}

private synchronized void pokeHelper(long timeout) throws ProfilerException {
if (mSamplingProfiler == null) {
throw new ProfilerException("SamplingProfiler.js module not connected");
}
mSamplingProfiler.poke(getOperationToken());
waitForOperation(timeout);
}

private int getOperationToken() throws ProfilerException {
if (mOperationInProgress) {
throw new ProfilerException("Another operation already in progress.");
}
mOperationInProgress = true;
return ++mOperationToken;
}

private void waitForOperation(long timeout) throws ProfilerException {
try {
wait(timeout);
} catch (InterruptedException e) {
throw new ProfilerException(
"Waiting for heap capture failed: " + e.getMessage());
}

if (mOperationInProgress) {
mOperationInProgress = false;
throw new ProfilerException("heap capture timed out.");
}

if (mOperationError != null) {
throw new ProfilerException(mOperationError);
}
}

@ReactMethod
public synchronized void operationComplete(
int token, String result, String error) {
if (token == mOperationToken) {
mOperationInProgress = false;
mSamplingProfilerResult = result;
mOperationError = error;
this.notify();
} else {
throw new RuntimeException("Completed operation is not in progress.");
}
}

@Override
public String getName() {
return "JSCSamplingProfiler";
}

@Override
public void initialize() {
super.initialize();
mSamplingProfiler =
getReactApplicationContext().getJSModule(SamplingProfiler.class);
registerSamplingProfiler(this);
}

@Override
public void onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy();
unregisterSamplingProfiler(this);
mSamplingProfiler = null;
}
}
1 change: 1 addition & 0 deletions ReactAndroid/src/main/res/devsupport/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<string name="catalyst_heap_capture" project="catalyst" translatable="false">Capture Heap</string>
<string name="catalyst_dismiss_button" project="catalyst" translatable="false">Dismiss\n(ESC)</string>
<string name="catalyst_reload_button" project="catalyst" translatable="false">Reload\n(R,\u00A0R)</string>
<string name="catalyst_poke_sampling_profiler" project="catalyst" translatable="false">Start/Stop Sampling Profiler</string>
<string name="catalyst_copy_button" project="catalyst" translatable="false">Copy</string>
<string name="catalyst_report_button" project="catalyst" translatable="false">Report</string>
</resources>
17 changes: 17 additions & 0 deletions ReactCommon/cxxreact/JSCSamplingProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@

#include "JSCSamplingProfiler.h"

#include <stdio.h>
#include <string.h>
#include <JavaScriptCore/API/JSProfilerPrivate.h>
#include "JSCHelpers.h"

#include "Value.h"

namespace facebook {
namespace react {
namespace {
static JSValueRef pokeSamplingProfiler(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return JSPokeSamplingProfiler(ctx);
}
}

void initSamplingProfilerOnMainJSCThread(JSGlobalContextRef ctx) {
JSStartSamplingProfilingOnMainJSCThread(ctx);
installGlobalFunction(ctx, "pokeSamplingProfiler", pokeSamplingProfiler);
}

}
Expand Down

0 comments on commit 6ed6a7b

Please sign in to comment.