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:
Dan Caspi 2016-11-15 09:28:10 -08:00 committed by Facebook Github Bot
parent c2a55baf80
commit 9fc6204efc
5 changed files with 129 additions and 3 deletions

View File

@ -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

View File

@ -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;

View File

@ -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"),

View File

@ -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

View File

@ -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