diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 2205a8d48..b0fa84134 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -21,25 +21,10 @@ #import "RCTProfile.h" #import "RCTPerformanceLogger.h" #import "RCTUtils.h" - -#ifndef RCT_JSC_PROFILER -#if RCT_DEV -#define RCT_JSC_PROFILER 1 -#else -#define RCT_JSC_PROFILER 0 -#endif -#endif - -#if RCT_JSC_PROFILER -#include +#import "RCTJSCProfiler.h" static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled"; -#ifndef RCT_JSC_PROFILER_DYLIB -#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String] -#endif -#endif - @interface RCTJavaScriptContext : NSObject @property (nonatomic, strong, readonly) JSContext *context; @@ -219,51 +204,19 @@ static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObje static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) { -#if RCT_JSC_PROFILER - void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW); - if (JSCProfiler != NULL) { - void (*nativeProfilerStart)(JSContextRef, const char *) = - (__typeof__(nativeProfilerStart))dlsym(JSCProfiler, "nativeProfilerStart"); - void (*nativeProfilerEnd)(JSContextRef, const char *, const char *) = - (__typeof__(nativeProfilerEnd))dlsym(JSCProfiler, "nativeProfilerEnd"); - - if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) { - void (*nativeProfilerEnableBytecode)(void) = - (__typeof__(nativeProfilerEnableBytecode))dlsym(JSCProfiler, "nativeProfilerEnableBytecode"); - - if (nativeProfilerEnableBytecode != NULL) { - nativeProfilerEnableBytecode(); - } - - static BOOL isProfiling = NO; - - if (isProfiling) { - nativeProfilerStart(context, "profile"); - } - - [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) { - - if (shouldStart == isProfiling) { - return; - } - - isProfiling = shouldStart; - + if (RCTJSCProfilerIsSupported()) { + [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) { + if (shouldStart != RCTJSCProfilerIsProfiling(context)) { if (shouldStart) { - nativeProfilerStart(context, "profile"); + RCTJSCProfilerStart(context); } else { - NSString *outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cpu_profile.json"]; - nativeProfilerEnd(context, "profile", outputFile.UTF8String); - NSData *profileData = [NSData dataWithContentsOfFile:outputFile - options:NSDataReadingMappedIfSafe - error:NULL]; - + NSString *outputFile = RCTJSCProfilerStop(context); + NSData *profileData = [NSData dataWithContentsOfFile:outputFile options:NSDataReadingMappedIfSafe error:NULL]; RCTProfileSendResult(bridge, @"cpu-profile", profileData); } - }]]; - } + } + }]]; } -#endif } #endif diff --git a/React/Profiler/RCTJSCProfiler.h b/React/Profiler/RCTJSCProfiler.h new file mode 100644 index 000000000..5f8e23b17 --- /dev/null +++ b/React/Profiler/RCTJSCProfiler.h @@ -0,0 +1,22 @@ +/** + * 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 "RCTDefines.h" + +/** The API is not thread-safe. */ + +/** The context is not retained. */ +RCT_EXTERN void RCTJSCProfilerStart(JSContextRef ctx); +/** Returns a file path containing the profiler data. */ +RCT_EXTERN NSString *RCTJSCProfilerStop(JSContextRef ctx); + +RCT_EXTERN BOOL RCTJSCProfilerIsProfiling(JSContextRef ctx); +RCT_EXTERN BOOL RCTJSCProfilerIsSupported(void); diff --git a/React/Profiler/RCTJSCProfiler.m b/React/Profiler/RCTJSCProfiler.m new file mode 100644 index 000000000..f808c1c5f --- /dev/null +++ b/React/Profiler/RCTJSCProfiler.m @@ -0,0 +1,137 @@ +/** + * 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 "RCTJSCProfiler.h" +#import "RCTLog.h" +#import + +#ifndef RCT_JSC_PROFILER + #if RCT_DEV + #define RCT_JSC_PROFILER 1 + #else + #define RCT_JSC_PROFILER 0 + #endif +#endif + +#if RCT_JSC_PROFILER + +#include + +#ifndef RCT_JSC_PROFILER_DYLIB + #define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String] +#endif + +static const char *const JSCProfileName = "profile"; + +typedef void (*JSCProfilerStartFunctionType)(JSContextRef, const char *); +typedef void (*JSCProfilerEndFunctionType)(JSContextRef, const char *, const char *); +typedef void (*JSCProfilerEnableFunctionType)(void); + +static NSMutableDictionary *RCTJSCProfilerStateMap; + +static JSCProfilerStartFunctionType RCTNativeProfilerStart = NULL; +static JSCProfilerEndFunctionType RCTNativeProfilerEnd = NULL; + +NS_INLINE NSValue *RCTJSContextRefKey(JSContextRef ref) { + return [NSValue valueWithPointer:ref]; +} + +static void RCTJSCProfilerStateInit() +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + RCTJSCProfilerStateMap = [NSMutableDictionary new]; + + void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW); + + RCTNativeProfilerStart = (JSCProfilerStartFunctionType)dlsym(JSCProfiler, "nativeProfilerStart"); + RCTNativeProfilerEnd = (JSCProfilerEndFunctionType)dlsym(JSCProfiler, "nativeProfilerEnd"); + JSCProfilerEnableFunctionType enableBytecode = (__typeof__(enableBytecode))dlsym(JSCProfiler, "nativeProfilerEnableBytecode"); + + if (RCTNativeProfilerStart && RCTNativeProfilerEnd && enableBytecode) { + enableBytecode(); + RCTLogInfo(@"JSC profiler is available."); + } else { + RCTNativeProfilerStart = NULL; + RCTNativeProfilerEnd = NULL; + RCTLogInfo(@"JSC profiler is not supported."); + } + }); +} + +#endif + +void RCTJSCProfilerStart(JSContextRef ctx) +{ +#if RCT_JSC_PROFILER + if (ctx != NULL) { + if (RCTJSCProfilerIsSupported()) { + NSValue *key = RCTJSContextRefKey(ctx); + BOOL isProfiling = [RCTJSCProfilerStateMap[key] boolValue]; + if (!isProfiling) { + RCTLogInfo(@"Starting JSC profiler for context: %p", ctx); + RCTJSCProfilerStateMap[key] = @YES; + RCTNativeProfilerStart(ctx, JSCProfileName); + } else { + RCTLogWarn(@"Trying to start JSC profiler on a context which is already profiled."); + } + } else { + RCTLogWarn(@"Cannot start JSC profiler as it's not supported."); + } + } else { + RCTLogWarn(@"Trying to start JSC profiler for NULL context."); + } +#endif +} + +NSString *RCTJSCProfilerStop(JSContextRef ctx) +{ + NSString *outputFile = nil; +#if RCT_JSC_PROFILER + if (ctx != NULL) { + RCTJSCProfilerStateInit(); + NSValue *key = RCTJSContextRefKey(ctx); + BOOL isProfiling = [RCTJSCProfilerStateMap[key] boolValue]; + if (isProfiling) { + NSString *filename = [NSString stringWithFormat:@"cpu_profile_%ld.json", (long)CFAbsoluteTimeGetCurrent()]; + outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; + RCTNativeProfilerEnd(ctx, JSCProfileName, outputFile.UTF8String); + RCTLogInfo(@"Stopped JSC profiler for context: %p", ctx); + } else { + RCTLogWarn(@"Trying to stop JSC profiler on a context which is not being profiled."); + } + [RCTJSCProfilerStateMap removeObjectForKey:key]; + } else { + RCTLogWarn(@"Trying to stop JSC profiler for NULL context."); + } +#endif + return outputFile; +} + +BOOL RCTJSCProfilerIsProfiling(JSContextRef ctx) +{ + BOOL isProfiling = NO; +#if RCT_JSC_PROFILER + if (ctx != NULL) { + RCTJSCProfilerStateInit(); + isProfiling = [RCTJSCProfilerStateMap[RCTJSContextRefKey(ctx)] boolValue]; + } +#endif + return isProfiling; +} + +BOOL RCTJSCProfilerIsSupported(void) +{ + BOOL isSupported = NO; +#if RCT_JSC_PROFILER + RCTJSCProfilerStateInit(); + isSupported = (RCTNativeProfilerStart != NULL); +#endif + return isSupported; +}