diff --git a/React/Executors/RCTJSCExecutor.mm b/React/Executors/RCTJSCExecutor.mm index f01669999..94b3bc586 100644 --- a/React/Executors/RCTJSCExecutor.mm +++ b/React/Executors/RCTJSCExecutor.mm @@ -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 diff --git a/React/Executors/RCTJSCWrapper.h b/React/Executors/RCTJSCWrapper.h index 4af13fe7e..769d696d1 100644 --- a/React/Executors/RCTJSCWrapper.h +++ b/React/Executors/RCTJSCWrapper.h @@ -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; diff --git a/React/Executors/RCTJSCWrapper.mm b/React/Executors/RCTJSCWrapper.mm index d642557b8..d5118db7e 100644 --- a/React/Executors/RCTJSCWrapper.mm +++ b/React/Executors/RCTJSCWrapper.mm @@ -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"), diff --git a/React/Modules/JSCSamplingProfiler.h b/React/Modules/JSCSamplingProfiler.h new file mode 100644 index 000000000..4e727a670 --- /dev/null +++ b/React/Modules/JSCSamplingProfiler.h @@ -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 + +#import "RCTBridgeModule.h" + +@interface JSCSamplingProfiler : NSObject + +/** + * 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 diff --git a/React/Modules/JSCSamplingProfiler.m b/React/Modules/JSCSamplingProfiler.m new file mode 100644 index 000000000..f89921b52 --- /dev/null +++ b/React/Modules/JSCSamplingProfiler.m @@ -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