diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 1b800901f..7f6b8860c 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -29,9 +29,9 @@ var BridgeProfiling = { } }, - profileEnd(profileName?: string) { + profileEnd() { if (GLOBAL.__BridgeProfilingIsProfiling) { - console.profileEnd(profileName); + console.profileEnd(); } }, @@ -41,14 +41,14 @@ var BridgeProfiling = { ReactPerf.measure = function (objName, fnName, func) { func = originalMeasure.call(ReactPerf, objName, fnName, func); return function (component) { - BridgeProfiling.profile(); - var ret = func.apply(this, arguments); if (GLOBAL.__BridgeProfilingIsProfiling) { var name = this._instance && this._instance.constructor && (this._instance.constructor.displayName || this._instance.constructor.name); - BridgeProfiling.profileEnd(`${objName}.${fnName}(${name})`); + BridgeProfiling.profile(`${objName}.${fnName}(${name})`); } + var ret = func.apply(this, arguments); + BridgeProfiling.profileEnd(); return ret; }; }; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index c8387242c..d05625720 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -167,11 +167,11 @@ id RCTGetLatestExecutor(void) - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad { RCTPerformanceLoggerStart(RCTPLScriptDownload); - RCTProfileBeginEvent(); + int cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil); RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) { RCTPerformanceLoggerEnd(RCTPLScriptDownload); - RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]); + RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil); if (error) { NSArray *stack = [error userInfo][@"stack"]; @@ -517,7 +517,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } } -- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete +- (void)enqueueApplicationScript:(NSString *)script + url:(NSURL *)url + onComplete:(RCTJavaScriptCompleteBlock)onComplete { RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil"); @@ -531,20 +533,21 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL return; } - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"FetchApplicationScriptCallbacks", nil); [_javaScriptExecutor executeJSCall:@"BatchedBridge" method:@"flushedQueue" arguments:@[] - callback:^(id json, NSError *error) { - RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{ - @"json": RCTNullIfNil(json), - @"error": RCTNullIfNil(error), - }); + callback:^(id json, NSError *error) + { + RCTProfileEndEvent(0, @"js_call,init", @{ + @"json": RCTNullIfNil(json), + @"error": RCTNullIfNil(error), + }); - [self _handleBuffer:json]; + [self _handleBuffer:json]; - onComplete(error); - }]; + onComplete(error); + }]; }]; } @@ -565,7 +568,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL __weak RCTBatchedBridge *weakSelf = self; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"enqueue_call", nil); RCTBatchedBridge *strongSelf = weakSelf; if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) { @@ -588,11 +591,13 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [strongSelf->_scheduledCalls addObject:call]; } - RCTProfileEndEvent(@"enqueue_call", @"objc_call", call); + RCTProfileEndEvent(0, @"objc_call", call); }]; } -- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args +- (void)_actuallyInvokeAndProcessModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args { RCTAssertJSThread(); @@ -678,7 +683,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [moduleData dispatchBlock:^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, RCTCurrentThreadName(), nil); NSOrderedSet *calls = [buckets objectForKey:moduleData]; @autoreleasepool { @@ -691,7 +696,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } } - RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) }); + RCTProfileEndEvent(0, @"objc_call,dispatch_async", @{ + @"calls": @(calls.count), + }); }]; } @@ -719,7 +726,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL return NO; } - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"Invoke callback", nil); RCTModuleData *moduleData = _moduleDataByID[moduleID]; if (RCT_DEBUG && !moduleData) { @@ -743,7 +750,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } } - RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{ + RCTProfileEndEvent(0, @"objc_call", @{ @"module": NSStringFromClass(method.moduleClass), @"method": method.JSMethodName, @"selector": NSStringFromSelector(method.selector), @@ -756,7 +763,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { RCTAssertJSThread(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"DispatchFrameUpdate", nil); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; for (RCTModuleData *moduleData in _frameUpdateObservers) { @@ -767,9 +774,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [moduleData dispatchBlock:^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, name, nil); [observer didUpdateFrame:frameUpdate]; - RCTProfileEndEvent(name, @"objc_call,fps", nil); + RCTProfileEndEvent(0, @"objc_call,fps", nil); }]; } } @@ -777,7 +784,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; RCT_IF_DEV( - RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); + RCTProfileImmediateEvent(0, @"JS Thread Tick", 'g'); for (NSDictionary *call in calls) { _RCTProfileEndFlowEvent(call[@"call_id"]); @@ -792,7 +799,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL arguments:@[[calls valueForKey:@"js_args"]]]; } - RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); + RCTProfileEndEvent(0, @"objc_call", nil); dispatch_async(dispatch_get_main_queue(), ^{ [self.perfStats.jsGraph onTick:displayLink.timestamp]; @@ -803,7 +810,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL { RCTAssertMainThread(); - RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); + RCTProfileImmediateEvent(0, @"VSYNC", 'g'); _modulesByName == nil ?: [self.perfStats.uiGraph onTick:displayLink.timestamp]; } diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 66cf40bf4..794f4a910 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -59,36 +59,42 @@ RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *); /** * Collects the initial event information for the event and returns a reference ID */ -RCT_EXTERN NSNumber *_RCTProfileBeginEvent(void); +RCT_EXTERN void RCTProfileBeginEvent(uint64_t tag, + NSString *name, + NSDictionary *args); /** * The ID returned by BeginEvent should then be passed into EndEvent, with the * rest of the event information. Just at this point the event will actually be * registered */ -RCT_EXTERN void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id); +RCT_EXTERN void RCTProfileEndEvent(uint64_t tag, + NSString *category, + NSDictionary *args); /** - * This pair of macros implicitly handle the event ID when beginning and ending - * an event, for both simplicity and performance reasons, this method is preferred - * - * NOTE: The EndEvent call has to be either, in the same scope of BeginEvent, - * or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible - * for EndEvent, in this case you may want to use the actual C functions. + * Collects the initial event information for the event and returns a reference ID */ -#define RCTProfileBeginEvent() \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSNumber *__rct_profile_id = _RCTProfileBeginEvent(); \ -_Pragma("clang diagnostic pop") - -#define RCTProfileEndEvent(name, category, args...) \ -_RCTProfileEndEvent(__rct_profile_id, name, category, args) +RCT_EXTERN int RCTProfileBeginAsyncEvent(uint64_t tag, + NSString *name, + NSDictionary *args); +/** + * The ID returned by BeginEvent should then be passed into EndEvent, with the + * rest of the event information. Just at this point the event will actually be + * registered + */ +RCT_EXTERN void RCTProfileEndAsyncEvent(uint64_t tag, + NSString *category, + int cookie, + NSString *name, + NSDictionary *args); /** * An event that doesn't have a duration (i.e. Notification, VSync, etc) */ -RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *); +RCT_EXTERN void RCTProfileImmediateEvent(uint64_t tag, + NSString *name, + char scope); /** * Helper to profile the duration of the execution of a block. This method uses @@ -96,11 +102,11 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString * * * NOTE: The block can't expect any argument */ -#define RCTProfileBlock(block, category, arguments) \ +#define RCTProfileBlock(block, tag, category, arguments) \ ^{ \ - RCTProfileBeginEvent(); \ + RCTProfileBeginEvent(tag, @(__PRETTY_FUNCTION__), nil); \ block(); \ - RCTProfileEndEvent([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)], category, arguments); \ + RCTProfileEndEvent(tag, category, arguments); \ } /** @@ -125,12 +131,12 @@ RCT_EXTERN void RCTProfileUnhookModules(RCTBridge *); #define RCTProfileInit(...) #define RCTProfileEnd(...) @"" -#define _RCTProfileBeginEvent(...) @0 #define RCTProfileBeginEvent(...) - -#define _RCTProfileEndEvent(...) #define RCTProfileEndEvent(...) +#define RCTProfileBeginAsyncEvent(...) 0 +#define RCTProfileEndAsyncEvent(...) + #define RCTProfileImmediateEvent(...) #define RCTProfileBlock(block, ...) block diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 2de44b206..85d5a799a 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -26,12 +26,6 @@ NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; #if RCT_DEV -#pragma mark - Prototypes - -NSNumber *RCTProfileTimestamp(NSTimeInterval); -NSString *RCTProfileMemory(vm_size_t); -NSDictionary *RCTProfileGetMemoryUsage(void); - #pragma mark - Constants NSString const *RCTProfileTraceEvents = @"traceEvents"; @@ -67,18 +61,18 @@ __VA_ARGS__ \ #pragma mark - Private Helpers -NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) +static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) { return @((timestamp - RCTProfileStartTime) * 1e6); } -NSString *RCTProfileMemory(vm_size_t memory) +static NSString *RCTProfileMemory(vm_size_t memory) { double mem = ((double)memory) / 1024 / 1024; return [NSString stringWithFormat:@"%.2lfmb", mem]; } -NSDictionary *RCTProfileGetMemoryUsage(void) +static NSDictionary *RCTProfileGetMemoryUsage(void) { struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); @@ -97,6 +91,22 @@ NSDictionary *RCTProfileGetMemoryUsage(void) } } +static NSDictionary *RCTProfileMergeArgs(NSDictionary *args0, NSDictionary *args1) +{ + args0 = RCTNilIfNull(args0); + args1 = RCTNilIfNull(args1); + + if (!args0 && args1) { + args0 = args1; + } else if (args0 && args1) { + NSMutableDictionary *d = [args0 mutableCopy]; + [d addEntriesFromDictionary:args1]; + args0 = [d copy]; + } + + return RCTNullIfNil(args0); +} + #pragma mark - Module hooks static const char *RCTProfileProxyClassName(Class); @@ -120,9 +130,9 @@ static void RCTProfileForwardInvocation(NSObject *self, __unused SEL cmd, NSInvo if ([object_getClass(self) instancesRespondToSelector:newSel]) { invocation.selector = newSel; - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, name, nil); [invocation invoke]; - RCTProfileEndEvent(name, @"objc_call,modules,auto", nil); + RCTProfileEndEvent(0, @"objc_call,modules,auto", nil); } else if ([self respondsToSelector:invocation.selector]) { [invocation invoke]; } else { @@ -248,45 +258,116 @@ NSString *RCTProfileEnd(RCTBridge *bridge) return log; } -NSNumber *_RCTProfileBeginEvent(void) +static NSMutableArray *RCTProfileGetThreadEvents(void) { - CHECK(@0); - RCTProfileLock( - NSNumber *eventID = @(++RCTProfileEventID); - RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); - ); - return eventID; + static NSString *const RCTProfileThreadEventsKey = @"RCTProfileThreadEventsKey"; + NSMutableArray *threadEvents = [NSThread currentThread].threadDictionary[RCTProfileThreadEventsKey]; + if (!threadEvents) { + threadEvents = [[NSMutableArray alloc] init]; + [NSThread currentThread].threadDictionary[RCTProfileThreadEventsKey] = threadEvents; + } + return threadEvents; } -void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args) +void RCTProfileBeginEvent(uint64_t tag, NSString *name, NSDictionary *args) { CHECK(); + NSMutableArray *events = RCTProfileGetThreadEvents(); + [events addObject:@[ + RCTProfileTimestamp(CACurrentMediaTime()), + @(tag), + name, + RCTNullIfNil(args), + ]]; +} + +void RCTProfileEndEvent( + __unused uint64_t tag, + NSString *category, + NSDictionary *args +) { + CHECK(); + + NSMutableArray *events = RCTProfileGetThreadEvents(); + NSArray *event = [events lastObject]; + [events removeLastObject]; + + if (!event) { + return; + } + + NSNumber *start = event[0]; + RCTProfileLock( - NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; - if (startTimestamp) { + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": event[2], + @"cat": category, + @"ph": @"X", + @"ts": start, + @"dur": @(RCTProfileTimestamp(CACurrentMediaTime()).doubleValue - start.doubleValue), + @"args": RCTProfileMergeArgs(event[3], args), + ); + ); +} + +int RCTProfileBeginAsyncEvent( + __unused uint64_t tag, + NSString *name, + NSDictionary *args +) { + CHECK(0); + + static int eventID = 0; + + RCTProfileLock( + RCTProfileOngoingEvents[@(eventID)] = @[ + RCTProfileTimestamp(CACurrentMediaTime()), + name, + RCTNullIfNil(args), + ]; + ); + + return eventID++; +} + +void RCTProfileEndAsyncEvent( + __unused uint64_t tag, + NSString *category, + int cookie, + __unused NSString *name, + NSDictionary *args +) { + CHECK(); + RCTProfileLock( + NSArray *event = RCTProfileOngoingEvents[@(cookie)]; + if (event) { NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"cat": categories, + @"name": event[1], + @"cat": category, @"ph": @"X", - @"ts": startTimestamp, - @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), - @"args": args ?: @[], + @"ts": event[0], + @"dur": @(endTimestamp.doubleValue - [event[0] doubleValue]), + @"args": RCTProfileMergeArgs(event[2], args), ); - [RCTProfileOngoingEvents removeObjectForKey:eventID]; + [RCTProfileOngoingEvents removeObjectForKey:@(cookie)]; } ); } -void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope) -{ +void RCTProfileImmediateEvent( + __unused uint64_t tag, + NSString *name, + char scope +) { CHECK(); + RCTProfileLock( RCTProfileAddEvent(RCTProfileTraceEvents, @"name": name, - @"ts": RCTProfileTimestamp(timestamp), - @"scope": scope, + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + @"scope": @(scope), @"ph": @"i", @"args": RCTProfileGetMemoryUsage(), ); diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 9eb147dbf..95f22fe92 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -136,18 +136,10 @@ static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __u #if RCT_DEV -static NSMutableArray *profiles; - static JSValueRef RCTConsoleProfile(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], __unused JSValueRef *exception) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - profiles = [NSMutableArray new]; - }); - static int profileCounter = 1; NSString *profileName; - NSNumber *profileID = _RCTProfileBeginEvent(); if (argumentCount > 0) { profileName = RCTJSValueToNSString(context, arguments[0]); @@ -160,25 +152,14 @@ static JSValueRef RCTConsoleProfile(JSContextRef context, __unused JSObjectRef o profileInfo = @[RCTJSValueToNSString(context, arguments[1])]; } - [profiles addObjectsFromArray:@[profileName, profileID, profileInfo]]; + RCTProfileBeginEvent(0, profileName, profileInfo); return JSValueMakeUndefined(context); } static JSValueRef RCTConsoleProfileEnd(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception) { - NSString *profileInfo = [profiles lastObject]; - [profiles removeLastObject]; - NSNumber *profileID = [profiles lastObject]; - [profiles removeLastObject]; - NSString *profileName = [profiles lastObject]; - [profiles removeLastObject]; - - if (argumentCount > 0 && !JSValueIsUndefined(context, arguments[0])) { - profileName = RCTJSValueToNSString(context, arguments[0]); - } - - _RCTProfileEndEvent(profileID, profileName, @"console", profileInfo); + RCTProfileEndEvent(0, @"console", nil); return JSValueMakeUndefined(context); } @@ -461,7 +442,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } onComplete(objcValue, nil); - }), @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; + }), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; } - (void)executeApplicationScript:(NSString *)script @@ -493,7 +474,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } onComplete(error); } - }), @"js_call", (@{ @"url": sourceURL.absoluteString }))]; + }), 0, @"js_call", (@{ @"url": sourceURL.absoluteString }))]; } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block @@ -555,7 +536,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) if (onComplete) { onComplete(nil); } - }), @"js_call,json_call", (@{@"objectName": objectName}))]; + }), 0, @"js_call,json_call", (@{@"objectName": objectName}))]; } RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 7f72a8074..811d661d6 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -825,7 +825,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po - (void)batchDidComplete { - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"[RCTUIManager batchDidComplete]", nil); // Gather blocks to be executed now that all view hierarchy manipulations have // been completed (note that these may still take place before layout has finished) @@ -857,7 +857,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po _nextLayoutAnimation = nil; } - RCTProfileEndEvent(@"[RCTUIManager batchDidComplete]", @"uimanager", @{ + RCTProfileEndEvent(0, @"uimanager", @{ @"view_count": @([_viewRegistry count]), }); [self flushUIBlocks]; @@ -877,7 +877,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po RCTProfileBeginFlowEvent(); dispatch_async(dispatch_get_main_queue(), ^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"UIManager flushUIBlocks", nil); @try { for (dispatch_block_t block in previousPendingUIBlocks) { block(); @@ -886,7 +886,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po @catch (NSException *exception) { RCTLogError(@"Exception thrown while executing UI block: %@", exception); } - RCTProfileEndEvent(@"UIManager flushUIBlocks", @"objc_call", @{ + RCTProfileEndEvent(0, @"objc_call", @{ @"count": @(previousPendingUIBlocks.count), }); });