Skip to content

Commit ec0ccf5

Browse files
lukaspiatkowskiFacebook Github Bot 8
authored andcommitted
#15 Add a button in devtools to start/stop the Sampling Profiler
Differential Revision: D3555704 fbshipit-source-id: 4add16c923fcfd306892efec4630c24ae438d6dd
1 parent e762d96 commit ec0ccf5

File tree

7 files changed

+224
-0
lines changed

7 files changed

+224
-0
lines changed

Libraries/BatchedBridge/BatchedBridge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const JSTimersExecution = require('JSTimersExecution');
2727
BatchedBridge.registerCallableModule('Systrace', Systrace);
2828
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);
2929
BatchedBridge.registerCallableModule('HeapCapture', require('HeapCapture'));
30+
BatchedBridge.registerCallableModule('SamplingProfiler', require('SamplingProfiler'));
3031

3132
if (__DEV__) {
3233
BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule SamplingProfiler
10+
* @flow
11+
*/
12+
'use strict';
13+
14+
var SamplingProfiler = {
15+
poke: function (token: number): void {
16+
var error = null;
17+
var result = null;
18+
try {
19+
result = global.pokeSamplingProfiler();
20+
if (result === null) {
21+
console.log('The JSC Sampling Profiler has started');
22+
} else {
23+
console.log('The JSC Sampling Profiler has stopped');
24+
}
25+
} catch (e) {
26+
console.log(
27+
'Error occured when restarting Sampling Profiler: ' + e.toString());
28+
error = e.toString();
29+
}
30+
require('NativeModules').JSCSamplingProfiler.operationComplete(
31+
token, result, error);
32+
},
33+
};
34+
35+
module.exports = SamplingProfiler;

ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.facebook.react.bridge.ReactApplicationContext;
1919
import com.facebook.react.common.build.ReactBuildConfig;
2020
import com.facebook.react.devsupport.JSCHeapCapture;
21+
import com.facebook.react.devsupport.JSCSamplingProfiler;
2122
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
2223
import com.facebook.react.modules.core.DeviceEventManagerModule;
2324
import com.facebook.react.modules.core.ExceptionsManagerModule;
@@ -84,6 +85,7 @@ public List<NativeModule> createNativeModules(
8485
new Timing(catalystApplicationContext, mReactInstanceManager.getDevSupportManager()),
8586
new SourceCodeModule(mReactInstanceManager.getSourceUrl()),
8687
uiManagerModule,
88+
new JSCSamplingProfiler(catalystApplicationContext),
8789
new DebugComponentOwnershipModule(catalystApplicationContext)));
8890

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

108111
if (ReactBuildConfig.DEBUG) {

ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,26 @@ public void onOptionSelected() {
375375
JSCHeapUpload.captureCallback(mDevServerHelper.getHeapCaptureUploadUrl()));
376376
}
377377
});
378+
options.put(
379+
mApplicationContext.getString(R.string.catalyst_poke_sampling_profiler),
380+
new DevOptionHandler() {
381+
@Override
382+
public void onOptionSelected() {
383+
try {
384+
List<String> pokeResults = JSCSamplingProfiler.poke(60000);
385+
for (String result : pokeResults) {
386+
Toast.makeText(
387+
mCurrentContext,
388+
result == null
389+
? "Started JSC Sampling Profiler"
390+
: "Stopped JSC Sampling Profiler",
391+
Toast.LENGTH_LONG).show();
392+
}
393+
} catch (JSCSamplingProfiler.ProfilerException e) {
394+
showNewJavaError(e.getMessage(), e);
395+
}
396+
}
397+
});
378398
options.put(
379399
mApplicationContext.getString(R.string.catalyst_settings), new DevOptionHandler() {
380400
@Override
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
package com.facebook.react.devsupport;
11+
12+
import javax.annotation.Nullable;
13+
14+
import java.io.File;
15+
import java.util.HashSet;
16+
import java.util.LinkedList;
17+
import java.util.List;
18+
19+
import com.facebook.react.bridge.JavaScriptModule;
20+
import com.facebook.react.bridge.ReactApplicationContext;
21+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
22+
import com.facebook.react.bridge.ReactMethod;
23+
24+
public class JSCSamplingProfiler extends ReactContextBaseJavaModule {
25+
public interface SamplingProfiler extends JavaScriptModule {
26+
void poke(int token);
27+
}
28+
29+
public static class ProfilerException extends Exception {
30+
ProfilerException(String message) {
31+
super(message);
32+
}
33+
}
34+
35+
private @Nullable SamplingProfiler mSamplingProfiler;
36+
private boolean mOperationInProgress;
37+
private int mOperationToken;
38+
private @Nullable String mOperationError;
39+
private @Nullable String mSamplingProfilerResult;
40+
41+
private static final HashSet<JSCSamplingProfiler> sRegisteredDumpers =
42+
new HashSet<>();
43+
44+
private static synchronized void registerSamplingProfiler(
45+
JSCSamplingProfiler dumper) {
46+
if (sRegisteredDumpers.contains(dumper)) {
47+
throw new RuntimeException(
48+
"a JSCSamplingProfiler registered more than once");
49+
}
50+
sRegisteredDumpers.add(dumper);
51+
}
52+
53+
private static synchronized void unregisterSamplingProfiler(
54+
JSCSamplingProfiler dumper) {
55+
sRegisteredDumpers.remove(dumper);
56+
}
57+
58+
public static synchronized List<String> poke(long timeout)
59+
throws ProfilerException {
60+
LinkedList<String> results = new LinkedList<>();
61+
if (sRegisteredDumpers.isEmpty()) {
62+
throw new ProfilerException("No JSC registered");
63+
}
64+
65+
for (JSCSamplingProfiler dumper : sRegisteredDumpers) {
66+
dumper.pokeHelper(timeout);
67+
results.add(dumper.mSamplingProfilerResult);
68+
}
69+
return results;
70+
}
71+
72+
public JSCSamplingProfiler(ReactApplicationContext reactContext) {
73+
super(reactContext);
74+
mSamplingProfiler = null;
75+
mOperationInProgress = false;
76+
mOperationToken = 0;
77+
mOperationError = null;
78+
mSamplingProfilerResult = null;
79+
}
80+
81+
private synchronized void pokeHelper(long timeout) throws ProfilerException {
82+
if (mSamplingProfiler == null) {
83+
throw new ProfilerException("SamplingProfiler.js module not connected");
84+
}
85+
mSamplingProfiler.poke(getOperationToken());
86+
waitForOperation(timeout);
87+
}
88+
89+
private int getOperationToken() throws ProfilerException {
90+
if (mOperationInProgress) {
91+
throw new ProfilerException("Another operation already in progress.");
92+
}
93+
mOperationInProgress = true;
94+
return ++mOperationToken;
95+
}
96+
97+
private void waitForOperation(long timeout) throws ProfilerException {
98+
try {
99+
wait(timeout);
100+
} catch (InterruptedException e) {
101+
throw new ProfilerException(
102+
"Waiting for heap capture failed: " + e.getMessage());
103+
}
104+
105+
if (mOperationInProgress) {
106+
mOperationInProgress = false;
107+
throw new ProfilerException("heap capture timed out.");
108+
}
109+
110+
if (mOperationError != null) {
111+
throw new ProfilerException(mOperationError);
112+
}
113+
}
114+
115+
@ReactMethod
116+
public synchronized void operationComplete(
117+
int token, String result, String error) {
118+
if (token == mOperationToken) {
119+
mOperationInProgress = false;
120+
mSamplingProfilerResult = result;
121+
mOperationError = error;
122+
this.notify();
123+
} else {
124+
throw new RuntimeException("Completed operation is not in progress.");
125+
}
126+
}
127+
128+
@Override
129+
public String getName() {
130+
return "JSCSamplingProfiler";
131+
}
132+
133+
@Override
134+
public void initialize() {
135+
super.initialize();
136+
mSamplingProfiler =
137+
getReactApplicationContext().getJSModule(SamplingProfiler.class);
138+
registerSamplingProfiler(this);
139+
}
140+
141+
@Override
142+
public void onCatalystInstanceDestroy() {
143+
super.onCatalystInstanceDestroy();
144+
unregisterSamplingProfiler(this);
145+
mSamplingProfiler = null;
146+
}
147+
}

ReactAndroid/src/main/res/devsupport/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<string name="catalyst_heap_capture" project="catalyst" translatable="false">Capture Heap</string>
2121
<string name="catalyst_dismiss_button" project="catalyst" translatable="false">Dismiss\n(ESC)</string>
2222
<string name="catalyst_reload_button" project="catalyst" translatable="false">Reload\n(R,\u00A0R)</string>
23+
<string name="catalyst_poke_sampling_profiler" project="catalyst" translatable="false">Start/Stop Sampling Profiler</string>
2324
<string name="catalyst_copy_button" project="catalyst" translatable="false">Copy</string>
2425
<string name="catalyst_report_button" project="catalyst" translatable="false">Report</string>
2526
</resources>

ReactCommon/cxxreact/JSCSamplingProfiler.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,30 @@
44

55
#include "JSCSamplingProfiler.h"
66

7+
#include <stdio.h>
8+
#include <string.h>
79
#include <JavaScriptCore/API/JSProfilerPrivate.h>
10+
#include "JSCHelpers.h"
11+
12+
#include "Value.h"
813

914
namespace facebook {
1015
namespace react {
16+
namespace {
17+
static JSValueRef pokeSamplingProfiler(
18+
JSContextRef ctx,
19+
JSObjectRef function,
20+
JSObjectRef thisObject,
21+
size_t argumentCount,
22+
const JSValueRef arguments[],
23+
JSValueRef* exception) {
24+
return JSPokeSamplingProfiler(ctx);
25+
}
26+
}
1127

1228
void initSamplingProfilerOnMainJSCThread(JSGlobalContextRef ctx) {
1329
JSStartSamplingProfilingOnMainJSCThread(ctx);
30+
installGlobalFunction(ctx, "pokeSamplingProfiler", pokeSamplingProfiler);
1431
}
1532

1633
}

0 commit comments

Comments
 (0)