Skip to content

Commit 9fc6204

Browse files
dcaspiFacebook Github Bot
authored andcommitted
Add support for JSC's sampling profiler on iOS for RN
Reviewed By: javache Differential Revision: D4107919 fbshipit-source-id: ecfe2cacdb78b857e461f7006b29e4d1fe1a1862
1 parent c2a55ba commit 9fc6204

File tree

5 files changed

+129
-3
lines changed

5 files changed

+129
-3
lines changed

React/Executors/RCTJSCExecutor.mm

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#import "RCTSourceCode.h"
3232
#import "RCTJSCWrapper.h"
3333
#import "RCTJSCErrorHandling.h"
34+
#import "JSCSamplingProfiler.h"
3435

3536
NSString *const RCTJSCThreadName = @"com.facebook.react.JavaScript";
3637
NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
@@ -417,6 +418,36 @@ - (void)setUp
417418
[weakBridge.flowIDMapLock unlock];
418419
}
419420
};
421+
422+
// Add toggles for JSC's sampling profiler, if the profiler is enabled
423+
if (self->_jscWrapper->JSSamplingProfilerEnabled()) {
424+
// Mark this thread as the main JS thread before starting profiling.
425+
self->_jscWrapper->JSStartSamplingProfilingOnMainJSCThread(context.JSGlobalContextRef);
426+
427+
// Allow to toggle the sampling profiler through RN's dev menu
428+
__weak JSContext *weakContext = self->_context.context;
429+
[self->_bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:@"Start / Stop JS Sampling Profiler" handler:^{
430+
// JSPokeSamplingProfiler() toggles the profiling process
431+
JSValueRef jsResult = self->_jscWrapper->JSPokeSamplingProfiler(weakContext.JSGlobalContextRef);
432+
433+
if (!self->_jscWrapper->JSValueIsNull(weakContext.JSGlobalContextRef, jsResult)) {
434+
NSString *results = [[self->_jscWrapper->JSValue valueWithJSValueRef:jsResult inContext:weakContext] toObject];
435+
JSCSamplingProfiler *profilerModule = [self->_bridge moduleForClass:[JSCSamplingProfiler class]];
436+
[profilerModule operationCompletedWithResults:results];
437+
}
438+
}]];
439+
440+
// Allow for the profiler to be poked from JS code as well
441+
// (see SamplingProfiler.js for an example of how it could be used with the JSCSamplingProfiler module).
442+
context[@"pokeSamplingProfiler"] = ^(NSDictionary *){
443+
RCTJSCExecutor *strongSelf = weakSelf;
444+
if (!strongSelf.isValid) {
445+
return @{};
446+
}
447+
JSValueRef result = strongSelf->_jscWrapper->JSPokeSamplingProfiler(weakContext.JSGlobalContextRef);
448+
return (NSDictionary *)[[strongSelf->_jscWrapper->JSValue valueWithJSValueRef:result inContext:weakContext] toObject];
449+
};
450+
}
420451
#endif
421452

422453
#if RCT_DEV

React/Executors/RCTJSCWrapper.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ typedef JSStringRef (*JSPropertyNameArrayGetNameAtIndexFuncType)(JSPropertyNameA
2828
typedef void (*JSPropertyNameArrayReleaseFuncType)(JSPropertyNameArrayRef);
2929
typedef JSValueRef (*JSValueMakeFromJSONStringFuncType)(JSContextRef, JSStringRef);
3030
typedef JSValueRef (*JSObjectCallAsFunctionFuncType)(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef *, JSValueRef *);
31-
typedef JSValueRef (*JSValueMakeNullFuncType)(JSContextRef);
31+
typedef JSValueRef (*JSValueRefWithJSContextRefFuncType)(JSContextRef);
3232
typedef JSStringRef (*JSValueCreateJSONStringFuncType)(JSContextRef, JSValueRef, unsigned, JSValueRef *);
3333
typedef bool (*JSValueIsUndefinedFuncType)(JSContextRef, JSValueRef);
3434
typedef bool (*JSValueIsNullFuncType)(JSContextRef, JSValueRef);
3535
typedef JSObjectRef (*JSValueToObjectFuncType)(JSContextRef, JSValueRef, JSValueRef *);
3636
typedef JSValueRef (*JSEvaluateScriptFuncType)(JSContextRef, JSStringRef, JSObjectRef, JSStringRef, int, JSValueRef *);
3737
typedef JSValueRef (*JSEvaluateBytecodeBundleFuncType)(JSContextRef, JSObjectRef, int, JSStringRef, JSValueRef *);
38+
typedef bool (*JSSamplingProfilerEnabledFuncType)();
39+
typedef void (*JSStartSamplingProfilingOnMainJSCThreadFuncType)(JSGlobalContextRef);
3840

3941
/**
4042
* JSNoBytecodeFileFormatVersion
@@ -60,14 +62,17 @@ typedef struct RCTJSCWrapper {
6062
JSPropertyNameArrayReleaseFuncType JSPropertyNameArrayRelease;
6163
JSValueMakeFromJSONStringFuncType JSValueMakeFromJSONString;
6264
JSObjectCallAsFunctionFuncType JSObjectCallAsFunction;
63-
JSValueMakeNullFuncType JSValueMakeNull;
65+
JSValueRefWithJSContextRefFuncType JSValueMakeNull;
6466
JSValueCreateJSONStringFuncType JSValueCreateJSONString;
6567
JSValueIsUndefinedFuncType JSValueIsUndefined;
6668
JSValueIsNullFuncType JSValueIsNull;
6769
JSValueToObjectFuncType JSValueToObject;
6870
JSEvaluateScriptFuncType JSEvaluateScript;
6971
JSEvaluateBytecodeBundleFuncType JSEvaluateBytecodeBundle;
7072
voidWithNoParamsFuncType configureJSCForIOS;
73+
JSSamplingProfilerEnabledFuncType JSSamplingProfilerEnabled;
74+
JSValueRefWithJSContextRefFuncType JSPokeSamplingProfiler;
75+
JSStartSamplingProfilingOnMainJSCThreadFuncType JSStartSamplingProfilingOnMainJSCThread;
7176
const int32_t JSBytecodeFileFormatVersion;
7277
Class JSContext;
7378
Class JSValue;

React/Executors/RCTJSCWrapper.mm

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424
}
2525

2626
UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSEvaluateBytecodeBundle)
27+
UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSPokeSamplingProfiler)
28+
UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSStartSamplingProfilingOnMainJSCThread)
2729

2830
#undef UNIMPLEMENTED_SYSTEM_JSC_FUNCTION
2931

3032
// A no-op function, to replace void functions that do no exist in the system JSC
3133
// with a function that does nothing.
3234
static void noOpSystemJSCFunc(void *args...){ }
35+
static bool alwaysFalseSystemJSCFunc(void *args...){ return false; }
3336

3437
void __attribute__((visibility("hidden"),weak)) RCTCustomJSCInit(__unused void *handle) {
3538
return;
@@ -85,6 +88,9 @@ void __attribute__((visibility("hidden"),weak)) RCTCustomJSCInit(__unused void *
8588
.JSBytecodeFileFormatVersion = JSNoBytecodeFileFormatVersion,
8689
.JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)UnimplementedJSEvaluateBytecodeBundle,
8790
.configureJSCForIOS = (voidWithNoParamsFuncType)noOpSystemJSCFunc,
91+
.JSSamplingProfilerEnabled = (JSSamplingProfilerEnabledFuncType)alwaysFalseSystemJSCFunc,
92+
.JSPokeSamplingProfiler = (JSValueRefWithJSContextRefFuncType)UnimplementedJSPokeSamplingProfiler,
93+
.JSStartSamplingProfilingOnMainJSCThread = (JSStartSamplingProfilingOnMainJSCThreadFuncType)UnimplementedJSStartSamplingProfilingOnMainJSCThread,
8894
.JSContext = [JSContext class],
8995
.JSValue = [JSValue class],
9096
};
@@ -114,14 +120,17 @@ void __attribute__((visibility("hidden"),weak)) RCTCustomJSCInit(__unused void *
114120
.JSPropertyNameArrayRelease = (JSPropertyNameArrayReleaseFuncType)dlsym(libraryHandle, "JSPropertyNameArrayRelease"),
115121
.JSValueMakeFromJSONString = (JSValueMakeFromJSONStringFuncType)dlsym(libraryHandle, "JSValueMakeFromJSONString"),
116122
.JSObjectCallAsFunction = (JSObjectCallAsFunctionFuncType)dlsym(libraryHandle, "JSObjectCallAsFunction"),
117-
.JSValueMakeNull = (JSValueMakeNullFuncType)dlsym(libraryHandle, "JSValueMakeNull"),
123+
.JSValueMakeNull = (JSValueRefWithJSContextRefFuncType)dlsym(libraryHandle, "JSValueMakeNull"),
118124
.JSValueCreateJSONString = (JSValueCreateJSONStringFuncType)dlsym(libraryHandle, "JSValueCreateJSONString"),
119125
.JSValueIsUndefined = (JSValueIsUndefinedFuncType)dlsym(libraryHandle, "JSValueIsUndefined"),
120126
.JSValueIsNull = (JSValueIsNullFuncType)dlsym(libraryHandle, "JSValueIsNull"),
121127
.JSValueToObject = (JSValueToObjectFuncType)dlsym(libraryHandle, "JSValueToObject"),
122128
.JSEvaluateScript = (JSEvaluateScriptFuncType)dlsym(libraryHandle, "JSEvaluateScript"),
123129
.JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)dlsym(libraryHandle, "JSEvaluateBytecodeBundle"),
124130
.configureJSCForIOS = (voidWithNoParamsFuncType)dlsym(libraryHandle, "configureJSCForIOS"),
131+
.JSSamplingProfilerEnabled = (JSSamplingProfilerEnabledFuncType)dlsym(libraryHandle, "JSSamplingProfilerEnabled"),
132+
.JSPokeSamplingProfiler = (JSValueRefWithJSContextRefFuncType)dlsym(libraryHandle, "JSPokeSamplingProfiler"),
133+
.JSStartSamplingProfilingOnMainJSCThread = (JSStartSamplingProfilingOnMainJSCThreadFuncType)dlsym(libraryHandle, "JSStartSamplingProfilingOnMainJSCThread"),
125134
.JSBytecodeFileFormatVersion = *(const int32_t *)dlsym(libraryHandle, "JSBytecodeFileFormatVersion"),
126135
.JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext"),
127136
.JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue"),
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
#import <Foundation/Foundation.h>
11+
12+
#import "RCTBridgeModule.h"
13+
14+
@interface JSCSamplingProfiler : NSObject <RCTBridgeModule>
15+
16+
/**
17+
* Receives a JSON string containing the result of a JSC CPU Profiling run,
18+
* and sends them to the packager to be symbolicated and saved to disk.
19+
* It is safe to call this method from any thread.
20+
*/
21+
- (void)operationCompletedWithResults:(NSString *)results;
22+
23+
@end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
#import "JSCSamplingProfiler.h"
11+
12+
#import "RCTBridge.h"
13+
#import "RCTLog.h"
14+
15+
@implementation JSCSamplingProfiler
16+
17+
@synthesize methodQueue = _methodQueue;
18+
@synthesize bridge = _bridge;
19+
20+
RCT_EXPORT_MODULE(JSCSamplingProfiler);
21+
22+
#ifdef RCT_PROFILE
23+
RCT_EXPORT_METHOD(operationComplete:(int)token result:(id)profileData error:(id)error)
24+
{
25+
if (error) {
26+
RCTLogError(@"JSC Sampling profiler ended with error: %@", error);
27+
return;
28+
}
29+
30+
// Create a POST request with all of the datas
31+
NSURL *url = [NSURL URLWithString:@"/jsc-profile" relativeToURL:self.bridge.bundleURL];
32+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
33+
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
34+
timeoutInterval:60];
35+
[request setHTTPMethod:@"POST"];
36+
[request setHTTPBody:[profileData dataUsingEncoding:NSUTF8StringEncoding]];
37+
38+
// Send the request
39+
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:nil];
40+
41+
if (connection) {
42+
RCTLogInfo(@"JSC CPU Profile data sent successfully.");
43+
} else {
44+
RCTLogWarn(@"JSC CPU Profile data failed to send.")
45+
}
46+
}
47+
48+
- (void)operationCompletedWithResults:(NSString *)results
49+
{
50+
// Send the results to the packager, using the module's queue.
51+
dispatch_async(self.methodQueue, ^{
52+
[self operationComplete:0 result:results error:nil];
53+
});
54+
}
55+
56+
#endif
57+
58+
@end

0 commit comments

Comments
 (0)