Add support for JSC's sampling profiler on iOS for RN
Reviewed By: javache Differential Revision: D4107919 fbshipit-source-id: ecfe2cacdb78b857e461f7006b29e4d1fe1a1862
This commit is contained in:
parent
c2a55baf80
commit
9fc6204efc
|
@ -31,6 +31,7 @@
|
|||
#import "RCTSourceCode.h"
|
||||
#import "RCTJSCWrapper.h"
|
||||
#import "RCTJSCErrorHandling.h"
|
||||
#import "JSCSamplingProfiler.h"
|
||||
|
||||
NSString *const RCTJSCThreadName = @"com.facebook.react.JavaScript";
|
||||
NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
|
||||
|
@ -417,6 +418,36 @@ static NSThread *newJavaScriptThread(void)
|
|||
[weakBridge.flowIDMapLock unlock];
|
||||
}
|
||||
};
|
||||
|
||||
// Add toggles for JSC's sampling profiler, if the profiler is enabled
|
||||
if (self->_jscWrapper->JSSamplingProfilerEnabled()) {
|
||||
// Mark this thread as the main JS thread before starting profiling.
|
||||
self->_jscWrapper->JSStartSamplingProfilingOnMainJSCThread(context.JSGlobalContextRef);
|
||||
|
||||
// Allow to toggle the sampling profiler through RN's dev menu
|
||||
__weak JSContext *weakContext = self->_context.context;
|
||||
[self->_bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:@"Start / Stop JS Sampling Profiler" handler:^{
|
||||
// JSPokeSamplingProfiler() toggles the profiling process
|
||||
JSValueRef jsResult = self->_jscWrapper->JSPokeSamplingProfiler(weakContext.JSGlobalContextRef);
|
||||
|
||||
if (!self->_jscWrapper->JSValueIsNull(weakContext.JSGlobalContextRef, jsResult)) {
|
||||
NSString *results = [[self->_jscWrapper->JSValue valueWithJSValueRef:jsResult inContext:weakContext] toObject];
|
||||
JSCSamplingProfiler *profilerModule = [self->_bridge moduleForClass:[JSCSamplingProfiler class]];
|
||||
[profilerModule operationCompletedWithResults:results];
|
||||
}
|
||||
}]];
|
||||
|
||||
// Allow for the profiler to be poked from JS code as well
|
||||
// (see SamplingProfiler.js for an example of how it could be used with the JSCSamplingProfiler module).
|
||||
context[@"pokeSamplingProfiler"] = ^(NSDictionary *){
|
||||
RCTJSCExecutor *strongSelf = weakSelf;
|
||||
if (!strongSelf.isValid) {
|
||||
return @{};
|
||||
}
|
||||
JSValueRef result = strongSelf->_jscWrapper->JSPokeSamplingProfiler(weakContext.JSGlobalContextRef);
|
||||
return (NSDictionary *)[[strongSelf->_jscWrapper->JSValue valueWithJSValueRef:result inContext:weakContext] toObject];
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RCT_DEV
|
||||
|
|
|
@ -28,13 +28,15 @@ typedef JSStringRef (*JSPropertyNameArrayGetNameAtIndexFuncType)(JSPropertyNameA
|
|||
typedef void (*JSPropertyNameArrayReleaseFuncType)(JSPropertyNameArrayRef);
|
||||
typedef JSValueRef (*JSValueMakeFromJSONStringFuncType)(JSContextRef, JSStringRef);
|
||||
typedef JSValueRef (*JSObjectCallAsFunctionFuncType)(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef *, JSValueRef *);
|
||||
typedef JSValueRef (*JSValueMakeNullFuncType)(JSContextRef);
|
||||
typedef JSValueRef (*JSValueRefWithJSContextRefFuncType)(JSContextRef);
|
||||
typedef JSStringRef (*JSValueCreateJSONStringFuncType)(JSContextRef, JSValueRef, unsigned, JSValueRef *);
|
||||
typedef bool (*JSValueIsUndefinedFuncType)(JSContextRef, JSValueRef);
|
||||
typedef bool (*JSValueIsNullFuncType)(JSContextRef, JSValueRef);
|
||||
typedef JSObjectRef (*JSValueToObjectFuncType)(JSContextRef, JSValueRef, JSValueRef *);
|
||||
typedef JSValueRef (*JSEvaluateScriptFuncType)(JSContextRef, JSStringRef, JSObjectRef, JSStringRef, int, JSValueRef *);
|
||||
typedef JSValueRef (*JSEvaluateBytecodeBundleFuncType)(JSContextRef, JSObjectRef, int, JSStringRef, JSValueRef *);
|
||||
typedef bool (*JSSamplingProfilerEnabledFuncType)();
|
||||
typedef void (*JSStartSamplingProfilingOnMainJSCThreadFuncType)(JSGlobalContextRef);
|
||||
|
||||
/**
|
||||
* JSNoBytecodeFileFormatVersion
|
||||
|
@ -60,7 +62,7 @@ typedef struct RCTJSCWrapper {
|
|||
JSPropertyNameArrayReleaseFuncType JSPropertyNameArrayRelease;
|
||||
JSValueMakeFromJSONStringFuncType JSValueMakeFromJSONString;
|
||||
JSObjectCallAsFunctionFuncType JSObjectCallAsFunction;
|
||||
JSValueMakeNullFuncType JSValueMakeNull;
|
||||
JSValueRefWithJSContextRefFuncType JSValueMakeNull;
|
||||
JSValueCreateJSONStringFuncType JSValueCreateJSONString;
|
||||
JSValueIsUndefinedFuncType JSValueIsUndefined;
|
||||
JSValueIsNullFuncType JSValueIsNull;
|
||||
|
@ -68,6 +70,9 @@ typedef struct RCTJSCWrapper {
|
|||
JSEvaluateScriptFuncType JSEvaluateScript;
|
||||
JSEvaluateBytecodeBundleFuncType JSEvaluateBytecodeBundle;
|
||||
voidWithNoParamsFuncType configureJSCForIOS;
|
||||
JSSamplingProfilerEnabledFuncType JSSamplingProfilerEnabled;
|
||||
JSValueRefWithJSContextRefFuncType JSPokeSamplingProfiler;
|
||||
JSStartSamplingProfilingOnMainJSCThreadFuncType JSStartSamplingProfilingOnMainJSCThread;
|
||||
const int32_t JSBytecodeFileFormatVersion;
|
||||
Class JSContext;
|
||||
Class JSValue;
|
||||
|
|
|
@ -24,12 +24,15 @@ assert(false);\
|
|||
}
|
||||
|
||||
UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSEvaluateBytecodeBundle)
|
||||
UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSPokeSamplingProfiler)
|
||||
UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSStartSamplingProfilingOnMainJSCThread)
|
||||
|
||||
#undef UNIMPLEMENTED_SYSTEM_JSC_FUNCTION
|
||||
|
||||
// A no-op function, to replace void functions that do no exist in the system JSC
|
||||
// with a function that does nothing.
|
||||
static void noOpSystemJSCFunc(void *args...){ }
|
||||
static bool alwaysFalseSystemJSCFunc(void *args...){ return false; }
|
||||
|
||||
void __attribute__((visibility("hidden"),weak)) RCTCustomJSCInit(__unused void *handle) {
|
||||
return;
|
||||
|
@ -85,6 +88,9 @@ static RCTJSCWrapper *RCTSetUpSystemLibraryPointers()
|
|||
.JSBytecodeFileFormatVersion = JSNoBytecodeFileFormatVersion,
|
||||
.JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)UnimplementedJSEvaluateBytecodeBundle,
|
||||
.configureJSCForIOS = (voidWithNoParamsFuncType)noOpSystemJSCFunc,
|
||||
.JSSamplingProfilerEnabled = (JSSamplingProfilerEnabledFuncType)alwaysFalseSystemJSCFunc,
|
||||
.JSPokeSamplingProfiler = (JSValueRefWithJSContextRefFuncType)UnimplementedJSPokeSamplingProfiler,
|
||||
.JSStartSamplingProfilingOnMainJSCThread = (JSStartSamplingProfilingOnMainJSCThreadFuncType)UnimplementedJSStartSamplingProfilingOnMainJSCThread,
|
||||
.JSContext = [JSContext class],
|
||||
.JSValue = [JSValue class],
|
||||
};
|
||||
|
@ -114,7 +120,7 @@ static RCTJSCWrapper *RCTSetUpCustomLibraryPointers()
|
|||
.JSPropertyNameArrayRelease = (JSPropertyNameArrayReleaseFuncType)dlsym(libraryHandle, "JSPropertyNameArrayRelease"),
|
||||
.JSValueMakeFromJSONString = (JSValueMakeFromJSONStringFuncType)dlsym(libraryHandle, "JSValueMakeFromJSONString"),
|
||||
.JSObjectCallAsFunction = (JSObjectCallAsFunctionFuncType)dlsym(libraryHandle, "JSObjectCallAsFunction"),
|
||||
.JSValueMakeNull = (JSValueMakeNullFuncType)dlsym(libraryHandle, "JSValueMakeNull"),
|
||||
.JSValueMakeNull = (JSValueRefWithJSContextRefFuncType)dlsym(libraryHandle, "JSValueMakeNull"),
|
||||
.JSValueCreateJSONString = (JSValueCreateJSONStringFuncType)dlsym(libraryHandle, "JSValueCreateJSONString"),
|
||||
.JSValueIsUndefined = (JSValueIsUndefinedFuncType)dlsym(libraryHandle, "JSValueIsUndefined"),
|
||||
.JSValueIsNull = (JSValueIsNullFuncType)dlsym(libraryHandle, "JSValueIsNull"),
|
||||
|
@ -122,6 +128,9 @@ static RCTJSCWrapper *RCTSetUpCustomLibraryPointers()
|
|||
.JSEvaluateScript = (JSEvaluateScriptFuncType)dlsym(libraryHandle, "JSEvaluateScript"),
|
||||
.JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)dlsym(libraryHandle, "JSEvaluateBytecodeBundle"),
|
||||
.configureJSCForIOS = (voidWithNoParamsFuncType)dlsym(libraryHandle, "configureJSCForIOS"),
|
||||
.JSSamplingProfilerEnabled = (JSSamplingProfilerEnabledFuncType)dlsym(libraryHandle, "JSSamplingProfilerEnabled"),
|
||||
.JSPokeSamplingProfiler = (JSValueRefWithJSContextRefFuncType)dlsym(libraryHandle, "JSPokeSamplingProfiler"),
|
||||
.JSStartSamplingProfilingOnMainJSCThread = (JSStartSamplingProfilingOnMainJSCThreadFuncType)dlsym(libraryHandle, "JSStartSamplingProfilingOnMainJSCThread"),
|
||||
.JSBytecodeFileFormatVersion = *(const int32_t *)dlsym(libraryHandle, "JSBytecodeFileFormatVersion"),
|
||||
.JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext"),
|
||||
.JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue"),
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface JSCSamplingProfiler : NSObject <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Receives a JSON string containing the result of a JSC CPU Profiling run,
|
||||
* and sends them to the packager to be symbolicated and saved to disk.
|
||||
* It is safe to call this method from any thread.
|
||||
*/
|
||||
- (void)operationCompletedWithResults:(NSString *)results;
|
||||
|
||||
@end
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "JSCSamplingProfiler.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation JSCSamplingProfiler
|
||||
|
||||
@synthesize methodQueue = _methodQueue;
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE(JSCSamplingProfiler);
|
||||
|
||||
#ifdef RCT_PROFILE
|
||||
RCT_EXPORT_METHOD(operationComplete:(int)token result:(id)profileData error:(id)error)
|
||||
{
|
||||
if (error) {
|
||||
RCTLogError(@"JSC Sampling profiler ended with error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a POST request with all of the datas
|
||||
NSURL *url = [NSURL URLWithString:@"/jsc-profile" relativeToURL:self.bridge.bundleURL];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
|
||||
timeoutInterval:60];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
[request setHTTPBody:[profileData dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
// Send the request
|
||||
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:nil];
|
||||
|
||||
if (connection) {
|
||||
RCTLogInfo(@"JSC CPU Profile data sent successfully.");
|
||||
} else {
|
||||
RCTLogWarn(@"JSC CPU Profile data failed to send.")
|
||||
}
|
||||
}
|
||||
|
||||
- (void)operationCompletedWithResults:(NSString *)results
|
||||
{
|
||||
// Send the results to the packager, using the module's queue.
|
||||
dispatch_async(self.methodQueue, ^{
|
||||
[self operationComplete:0 result:results error:nil];
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
Loading…
Reference in New Issue