[ReactNative] Add profiling hooks to bridge modules at runtime
Summary: @public Add `RCT_DEV`-only profiling hooks to every method in every bridge modules to add a little bit more detail to the timeline profile. Test Plan: {F22522834}
This commit is contained in:
parent
f744c7c444
commit
7009f0a47b
|
@ -1313,7 +1313,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
|
||||||
|
|
||||||
#pragma mark - Payload Generation
|
#pragma mark - Payload Generation
|
||||||
|
|
||||||
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
|
- (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module
|
||||||
|
{
|
||||||
|
[self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID
|
||||||
{
|
{
|
||||||
RCTAssertJSThread();
|
RCTAssertJSThread();
|
||||||
|
|
||||||
|
@ -1458,7 +1463,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
|
||||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||||
[self dispatchBlock:^{
|
[self dispatchBlock:^{
|
||||||
[module batchDidComplete];
|
[module batchDidComplete];
|
||||||
} forModule:moduleID];
|
} forModuleID:moduleID];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -1526,7 +1531,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
|
||||||
@"selector": NSStringFromSelector(method.selector),
|
@"selector": NSStringFromSelector(method.selector),
|
||||||
@"args": RCTJSONStringify(params ?: [NSNull null], NULL),
|
@"args": RCTJSONStringify(params ?: [NSNull null], NULL),
|
||||||
});
|
});
|
||||||
} forModule:@(moduleID)];
|
} forModuleID:@(moduleID)];
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -1546,7 +1551,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
|
||||||
RCTProfileBeginEvent();
|
RCTProfileBeginEvent();
|
||||||
[observer didUpdateFrame:frameUpdate];
|
[observer didUpdateFrame:frameUpdate];
|
||||||
RCTProfileEndEvent(name, @"objc_call,fps", nil);
|
RCTProfileEndEvent(name, @"objc_call,fps", nil);
|
||||||
} forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]];
|
} forModule:(id<RCTBridgeModule>)observer];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1591,35 +1596,39 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
|
||||||
|
|
||||||
- (void)startProfiling
|
- (void)startProfiling
|
||||||
{
|
{
|
||||||
RCTAssertMainThread();
|
RCTAssertMainThread();
|
||||||
|
|
||||||
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
|
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
|
||||||
RCTLogError(@"To run the profiler you must be running from the dev server");
|
RCTLogError(@"To run the profiler you must be running from the dev server");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RCTProfileInit();
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||||
|
RCTProfileInit(self);
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stopProfiling
|
- (void)stopProfiling
|
||||||
{
|
{
|
||||||
RCTAssertMainThread();
|
RCTAssertMainThread();
|
||||||
|
|
||||||
NSString *log = RCTProfileEnd();
|
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||||
NSURL *bundleURL = _parentBridge.bundleURL;
|
NSString *log = RCTProfileEnd(self);
|
||||||
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
|
NSURL *bundleURL = _parentBridge.bundleURL;
|
||||||
NSURL *URL = [NSURL URLWithString:URLString];
|
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
|
||||||
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
NSURL *URL = [NSURL URLWithString:URLString];
|
||||||
URLRequest.HTTPMethod = @"POST";
|
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||||
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
URLRequest.HTTPMethod = @"POST";
|
||||||
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
|
[URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||||
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
|
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest
|
||||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
fromData:[log dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
if (error) {
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
RCTLogError(@"%@", error.localizedDescription);
|
if (error) {
|
||||||
}
|
RCTLogError(@"%@", error.localizedDescription);
|
||||||
}];
|
}
|
||||||
[task resume];
|
}];
|
||||||
|
[task resume];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -25,6 +25,8 @@ NSString *const RCTProfileDidEndProfiling;
|
||||||
|
|
||||||
#if RCT_DEV
|
#if RCT_DEV
|
||||||
|
|
||||||
|
@class RCTBridge;
|
||||||
|
|
||||||
#define RCTProfileBeginFlowEvent() \
|
#define RCTProfileBeginFlowEvent() \
|
||||||
_Pragma("clang diagnostic push") \
|
_Pragma("clang diagnostic push") \
|
||||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||||
|
@ -45,14 +47,14 @@ RCT_EXTERN BOOL RCTProfileIsProfiling(void);
|
||||||
/**
|
/**
|
||||||
* Start collecting profiling information
|
* Start collecting profiling information
|
||||||
*/
|
*/
|
||||||
RCT_EXTERN void RCTProfileInit(void);
|
RCT_EXTERN void RCTProfileInit(RCTBridge *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop profiling and return a JSON string of the collected data - The data
|
* Stop profiling and return a JSON string of the collected data - The data
|
||||||
* returned is compliant with google's trace event format - the format used
|
* returned is compliant with google's trace event format - the format used
|
||||||
* as input to trace-viewer
|
* as input to trace-viewer
|
||||||
*/
|
*/
|
||||||
RCT_EXTERN NSString *RCTProfileEnd(void);
|
RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects the initial event information for the event and returns a reference ID
|
* Collects the initial event information for the event and returns a reference ID
|
||||||
|
|
|
@ -10,10 +10,13 @@
|
||||||
#import "RCTProfile.h"
|
#import "RCTProfile.h"
|
||||||
|
|
||||||
#import <mach/mach.h>
|
#import <mach/mach.h>
|
||||||
|
#import <objc/message.h>
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#import "RCTAssert.h"
|
#import "RCTAssert.h"
|
||||||
|
#import "RCTBridge.h"
|
||||||
#import "RCTDefines.h"
|
#import "RCTDefines.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
|
@ -32,6 +35,7 @@ NSDictionary *RCTProfileGetMemoryUsage(void);
|
||||||
|
|
||||||
NSString const *RCTProfileTraceEvents = @"traceEvents";
|
NSString const *RCTProfileTraceEvents = @"traceEvents";
|
||||||
NSString const *RCTProfileSamples = @"samples";
|
NSString const *RCTProfileSamples = @"samples";
|
||||||
|
NSString *const RCTProfilePrefix = @"rct_profile_";
|
||||||
|
|
||||||
#pragma mark - Variables
|
#pragma mark - Variables
|
||||||
|
|
||||||
|
@ -92,6 +96,111 @@ NSDictionary *RCTProfileGetMemoryUsage(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Module hooks
|
||||||
|
|
||||||
|
@interface RCTBridge (Private)
|
||||||
|
|
||||||
|
- (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
static const char *RCTProfileProxyClassName(Class);
|
||||||
|
static const char *RCTProfileProxyClassName(Class class)
|
||||||
|
{
|
||||||
|
return [RCTProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SEL RCTProfileProxySelector(SEL);
|
||||||
|
static SEL RCTProfileProxySelector(SEL selector)
|
||||||
|
{
|
||||||
|
NSString *selectorName = NSStringFromSelector(selector);
|
||||||
|
return NSSelectorFromString([RCTProfilePrefix stringByAppendingString:selectorName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RCTProfileForwardInvocation(NSObject *, SEL, NSInvocation *);
|
||||||
|
static void RCTProfileForwardInvocation(NSObject *self, SEL cmd, NSInvocation *invocation)
|
||||||
|
{
|
||||||
|
NSString *name = [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(invocation.selector)];
|
||||||
|
SEL newSel = RCTProfileProxySelector(invocation.selector);
|
||||||
|
|
||||||
|
if ([object_getClass(self) instancesRespondToSelector:newSel]) {
|
||||||
|
invocation.selector = newSel;
|
||||||
|
RCTProfileBeginEvent();
|
||||||
|
[invocation invoke];
|
||||||
|
RCTProfileEndEvent(name, @"objc_call,modules,auto", nil);
|
||||||
|
} else {
|
||||||
|
// Use original selector to don't change error message
|
||||||
|
[self doesNotRecognizeSelector:invocation.selector];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IMP RCTProfileMsgForward(NSObject *, SEL);
|
||||||
|
static IMP RCTProfileMsgForward(NSObject *self, SEL selector)
|
||||||
|
{
|
||||||
|
IMP imp = _objc_msgForward;
|
||||||
|
#if !defined(__arm64__)
|
||||||
|
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
|
||||||
|
if (signature.methodReturnType[0] == _C_STRUCT_B && signature.methodReturnLength > 8) {
|
||||||
|
imp = _objc_msgForward_stret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return imp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RCTProfileHookModules(RCTBridge *);
|
||||||
|
static void RCTProfileHookModules(RCTBridge *bridge)
|
||||||
|
{
|
||||||
|
[bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id<RCTBridgeModule> module, BOOL *stop) {
|
||||||
|
[bridge dispatchBlock:^{
|
||||||
|
Class moduleClass = object_getClass(module);
|
||||||
|
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
|
||||||
|
|
||||||
|
unsigned int methodCount;
|
||||||
|
Method *methods = class_copyMethodList(moduleClass, &methodCount);
|
||||||
|
for (NSUInteger i = 0; i < methodCount; i++) {
|
||||||
|
Method method = methods[i];
|
||||||
|
SEL selector = method_getName(method);
|
||||||
|
if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IMP originalIMP = method_getImplementation(method);
|
||||||
|
const char *returnType = method_getTypeEncoding(method);
|
||||||
|
class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType);
|
||||||
|
class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType);
|
||||||
|
}
|
||||||
|
free(methods);
|
||||||
|
|
||||||
|
for (Class cls in @[proxyClass, object_getClass(proxyClass)]) {
|
||||||
|
Method oldImp = class_getInstanceMethod(cls, @selector(class));
|
||||||
|
class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp));
|
||||||
|
}
|
||||||
|
|
||||||
|
IMP originalFwd = class_replaceMethod(moduleClass, @selector(forwardInvocation:), (IMP)RCTProfileForwardInvocation, "v@:@");
|
||||||
|
if (originalFwd != NULL) {
|
||||||
|
class_addMethod(proxyClass, RCTProfileProxySelector(@selector(forwardInvocation:)), originalFwd, "v@:@");
|
||||||
|
}
|
||||||
|
|
||||||
|
objc_registerClassPair(proxyClass);
|
||||||
|
object_setClass(module, proxyClass);
|
||||||
|
} forModule:module];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
void RCTProfileUnhookModules(RCTBridge *);
|
||||||
|
void RCTProfileUnhookModules(RCTBridge *bridge)
|
||||||
|
{
|
||||||
|
[bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id<RCTBridgeModule> module, BOOL *stop) {
|
||||||
|
[bridge dispatchBlock:^{
|
||||||
|
Class proxyClass = object_getClass(module);
|
||||||
|
if (module.class != proxyClass) {
|
||||||
|
object_setClass(module, module.class);
|
||||||
|
objc_disposeClassPair(proxyClass);
|
||||||
|
}
|
||||||
|
} forModule:module];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Public Functions
|
#pragma mark - Public Functions
|
||||||
|
|
||||||
BOOL RCTProfileIsProfiling(void)
|
BOOL RCTProfileIsProfiling(void)
|
||||||
|
@ -102,8 +211,10 @@ BOOL RCTProfileIsProfiling(void)
|
||||||
return profiling;
|
return profiling;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RCTProfileInit(void)
|
void RCTProfileInit(RCTBridge *bridge)
|
||||||
{
|
{
|
||||||
|
RCTProfileHookModules(bridge);
|
||||||
|
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
_RCTProfileLock = [[NSLock alloc] init];
|
_RCTProfileLock = [[NSLock alloc] init];
|
||||||
|
@ -121,7 +232,7 @@ void RCTProfileInit(void)
|
||||||
object:nil];
|
object:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *RCTProfileEnd(void)
|
NSString *RCTProfileEnd(RCTBridge *bridge)
|
||||||
{
|
{
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling
|
||||||
object:nil];
|
object:nil];
|
||||||
|
@ -132,6 +243,9 @@ NSString *RCTProfileEnd(void)
|
||||||
RCTProfileInfo = nil;
|
RCTProfileInfo = nil;
|
||||||
RCTProfileOngoingEvents = nil;
|
RCTProfileOngoingEvents = nil;
|
||||||
);
|
);
|
||||||
|
|
||||||
|
RCTProfileUnhookModules(bridge);
|
||||||
|
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue