RCTProfile.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTProfile.h"
  8. #import <dlfcn.h>
  9. #import <mach/mach.h>
  10. #import <objc/message.h>
  11. #import <objc/runtime.h>
  12. #import <stdatomic.h>
  13. #import <UIKit/UIKit.h>
  14. #import "RCTAssert.h"
  15. #import "RCTBridge+Private.h"
  16. #import "RCTBridge.h"
  17. #import "RCTComponentData.h"
  18. #import "RCTDefines.h"
  19. #import "RCTLog.h"
  20. #import "RCTModuleData.h"
  21. #import "RCTUIManager.h"
  22. #import "RCTUIManagerUtils.h"
  23. #import "RCTUtils.h"
  24. NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
  25. NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling";
  26. const uint64_t RCTProfileTagAlways = 1L << 0;
  27. #if RCT_PROFILE
  28. #pragma mark - Constants
  29. static NSString *const kProfileTraceEvents = @"traceEvents";
  30. static NSString *const kProfileSamples = @"samples";
  31. static NSString *const kProfilePrefix = @"rct_profile_";
  32. #pragma mark - Variables
  33. static atomic_bool RCTProfileProfiling = ATOMIC_VAR_INIT(NO);
  34. static NSDictionary *RCTProfileInfo;
  35. static NSMutableDictionary *RCTProfileOngoingEvents;
  36. static NSTimeInterval RCTProfileStartTime;
  37. static NSUInteger RCTProfileEventID = 0;
  38. static CADisplayLink *RCTProfileDisplayLink;
  39. static __weak RCTBridge *_RCTProfilingBridge;
  40. static UIWindow *RCTProfileControlsWindow;
  41. #pragma mark - Macros
  42. #define RCTProfileAddEvent(type, props...) \
  43. [RCTProfileInfo[type] addObject:@{@"pid" : @([[NSProcessInfo processInfo] processIdentifier]), props}];
  44. #define CHECK(...) \
  45. if (!RCTProfileIsProfiling()) { \
  46. return __VA_ARGS__; \
  47. }
  48. #pragma mark - systrace glue code
  49. static RCTProfileCallbacks *callbacks;
  50. static char *systrace_buffer;
  51. static systrace_arg_t *newSystraceArgsFromDictionary(NSDictionary<NSString *, NSString *> *args)
  52. {
  53. if (args.count == 0) {
  54. return NULL;
  55. }
  56. systrace_arg_t *systrace_args = malloc(sizeof(systrace_arg_t) * args.count);
  57. if (systrace_args) {
  58. __block size_t i = 0;
  59. [args enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) {
  60. systrace_args[i].key = [key UTF8String];
  61. systrace_args[i].key_len = [key length];
  62. systrace_args[i].value = [value UTF8String];
  63. systrace_args[i].value_len = [value length];
  64. i++;
  65. }];
  66. }
  67. return systrace_args;
  68. }
  69. void RCTProfileRegisterCallbacks(RCTProfileCallbacks *cb)
  70. {
  71. callbacks = cb;
  72. }
  73. #pragma mark - Private Helpers
  74. static RCTBridge *RCTProfilingBridge(void)
  75. {
  76. return _RCTProfilingBridge ?: [RCTBridge currentBridge];
  77. }
  78. static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp)
  79. {
  80. return @((timestamp - RCTProfileStartTime) * 1e6);
  81. }
  82. static NSString *RCTProfileMemory(vm_size_t memory)
  83. {
  84. double mem = ((double)memory) / 1024 / 1024;
  85. return [NSString stringWithFormat:@"%.2lfmb", mem];
  86. }
  87. static NSDictionary *RCTProfileGetMemoryUsage(void)
  88. {
  89. struct task_basic_info info;
  90. mach_msg_type_number_t size = sizeof(info);
  91. kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
  92. if (kerr == KERN_SUCCESS) {
  93. return @{
  94. @"suspend_count" : @(info.suspend_count),
  95. @"virtual_size" : RCTProfileMemory(info.virtual_size),
  96. @"resident_size" : RCTProfileMemory(info.resident_size),
  97. };
  98. } else {
  99. return @{};
  100. }
  101. }
  102. #pragma mark - Module hooks
  103. static const char *RCTProfileProxyClassName(Class class)
  104. {
  105. return [kProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String;
  106. }
  107. static dispatch_group_t RCTProfileGetUnhookGroup(void)
  108. {
  109. static dispatch_group_t unhookGroup;
  110. static dispatch_once_t onceToken;
  111. dispatch_once(&onceToken, ^{
  112. unhookGroup = dispatch_group_create();
  113. });
  114. return unhookGroup;
  115. }
  116. // Used by RCTProfileTrampoline assembly file to call libc`malloc
  117. RCT_EXTERN void *RCTProfileMalloc(size_t size);
  118. void *RCTProfileMalloc(size_t size)
  119. {
  120. return malloc(size);
  121. }
  122. // Used by RCTProfileTrampoline assembly file to call libc`free
  123. RCT_EXTERN void RCTProfileFree(void *buf);
  124. void RCTProfileFree(void *buf)
  125. {
  126. free(buf);
  127. }
  128. RCT_EXTERN IMP RCTProfileGetImplementation(id obj, SEL cmd);
  129. IMP RCTProfileGetImplementation(id obj, SEL cmd)
  130. {
  131. return class_getMethodImplementation([obj class], cmd);
  132. }
  133. /**
  134. * For the profiling we have to execute some code before and after every
  135. * function being profiled, the only way of doing that with pure Objective-C is
  136. * by using `-forwardInvocation:`, which is slow and could skew the profile
  137. * results.
  138. *
  139. * The alternative in assembly is much simpler, we just need to store all the
  140. * state at the beginning of the function, start the profiler, restore all the
  141. * state, call the actual function we want to profile and stop the profiler.
  142. *
  143. * The implementation can be found in RCTProfileTrampoline-<arch>.s where arch
  144. * is one of: i386, x86_64, arm, arm64.
  145. */
  146. #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__arm64__)
  147. RCT_EXTERN void RCTProfileTrampoline(void);
  148. #else
  149. static void *RCTProfileTrampoline = NULL;
  150. #endif
  151. RCT_EXTERN void RCTProfileTrampolineStart(id, SEL);
  152. void RCTProfileTrampolineStart(id self, SEL cmd)
  153. {
  154. /**
  155. * This call might be during dealloc, so we shouldn't retain the object in the
  156. * block.
  157. */
  158. Class klass = [self class];
  159. RCT_PROFILE_BEGIN_EVENT(
  160. RCTProfileTagAlways, ([NSString stringWithFormat:@"-[%s %s]", class_getName(klass), sel_getName(cmd)]), nil);
  161. }
  162. RCT_EXTERN void RCTProfileTrampolineEnd(void);
  163. void RCTProfileTrampolineEnd(void)
  164. {
  165. RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call,modules,auto");
  166. }
  167. static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *, NSNumber *);
  168. static UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag, NSNumber *rootTag)
  169. {
  170. UIView *view = originalCreateView(self, _cmd, tag, rootTag);
  171. RCTProfileHookInstance(view);
  172. return view;
  173. }
  174. static void RCTProfileHookUIManager(RCTUIManager *uiManager)
  175. {
  176. dispatch_async(dispatch_get_main_queue(), ^{
  177. for (id view in [uiManager valueForKey:@"viewRegistry"]) {
  178. RCTProfileHookInstance([uiManager viewForReactTag:view]);
  179. }
  180. Method createView = class_getInstanceMethod([RCTComponentData class], @selector(createViewWithTag:rootTag:));
  181. if (method_getImplementation(createView) != (IMP)RCTProfileCreateView) {
  182. originalCreateView = (typeof(originalCreateView))method_getImplementation(createView);
  183. method_setImplementation(createView, (IMP)RCTProfileCreateView);
  184. }
  185. });
  186. }
  187. void RCTProfileHookInstance(id instance)
  188. {
  189. Class moduleClass = object_getClass(instance);
  190. /**
  191. * We swizzle the instance -class method to return the original class, but
  192. * object_getClass will return the actual class.
  193. *
  194. * If they are different, it means that the object is returning the original
  195. * class, but it's actual class is the proxy subclass we created.
  196. */
  197. if ([instance class] != moduleClass) {
  198. return;
  199. }
  200. Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
  201. if (!proxyClass) {
  202. proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass));
  203. if (proxyClass) {
  204. object_setClass(instance, proxyClass);
  205. }
  206. return;
  207. }
  208. unsigned int methodCount;
  209. Method *methods = class_copyMethodList(moduleClass, &methodCount);
  210. for (NSUInteger i = 0; i < methodCount; i++) {
  211. Method method = methods[i];
  212. SEL selector = method_getName(method);
  213. /**
  214. * Bail out on struct returns (except arm64) - we don't use it enough
  215. * to justify writing a stret version
  216. */
  217. #ifdef __arm64__
  218. BOOL returnsStruct = NO;
  219. #else
  220. const char *typeEncoding = method_getTypeEncoding(method);
  221. // bail out on structs and unions (since they might contain structs)
  222. BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '(';
  223. #endif
  224. /**
  225. * Avoid hooking into NSObject methods, methods generated by React Native
  226. * and special methods that start `.` (e.g. .cxx_destruct)
  227. */
  228. if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] ||
  229. sel_getName(selector)[0] == '.' || returnsStruct) {
  230. continue;
  231. }
  232. const char *types = method_getTypeEncoding(method);
  233. class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types);
  234. }
  235. free(methods);
  236. class_replaceMethod(
  237. object_getClass(proxyClass),
  238. @selector(initialize),
  239. imp_implementationWithBlock(^{
  240. }),
  241. "v@:");
  242. for (Class cls in @[ proxyClass, object_getClass(proxyClass) ]) {
  243. Method oldImp = class_getInstanceMethod(cls, @selector(class));
  244. class_replaceMethod(
  245. cls,
  246. @selector(class),
  247. imp_implementationWithBlock(^{
  248. return moduleClass;
  249. }),
  250. method_getTypeEncoding(oldImp));
  251. }
  252. objc_registerClassPair(proxyClass);
  253. object_setClass(instance, proxyClass);
  254. if (moduleClass == [RCTUIManager class]) {
  255. RCTProfileHookUIManager((RCTUIManager *)instance);
  256. }
  257. }
  258. void RCTProfileHookModules(RCTBridge *bridge)
  259. {
  260. _RCTProfilingBridge = bridge;
  261. #pragma clang diagnostic push
  262. #pragma clang diagnostic ignored "-Wtautological-pointer-compare"
  263. if (RCTProfileTrampoline == NULL) {
  264. return;
  265. }
  266. #pragma clang diagnostic pop
  267. RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"RCTProfileHookModules", nil);
  268. for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) {
  269. // Only hook modules with an instance, to prevent initializing everything
  270. if ([moduleData hasInstance]) {
  271. [bridge
  272. dispatchBlock:^{
  273. RCTProfileHookInstance(moduleData.instance);
  274. }
  275. queue:moduleData.methodQueue];
  276. }
  277. }
  278. RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  279. }
  280. static void RCTProfileUnhookInstance(id instance)
  281. {
  282. if ([instance class] != object_getClass(instance)) {
  283. object_setClass(instance, [instance class]);
  284. }
  285. }
  286. void RCTProfileUnhookModules(RCTBridge *bridge)
  287. {
  288. _RCTProfilingBridge = nil;
  289. dispatch_group_enter(RCTProfileGetUnhookGroup());
  290. NSDictionary *moduleDataByID = [bridge valueForKey:@"moduleDataByID"];
  291. for (RCTModuleData *moduleData in moduleDataByID) {
  292. if ([moduleData hasInstance]) {
  293. RCTProfileUnhookInstance(moduleData.instance);
  294. }
  295. }
  296. if ([bridge moduleIsInitialized:[RCTUIManager class]]) {
  297. dispatch_async(dispatch_get_main_queue(), ^{
  298. for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) {
  299. RCTProfileUnhookInstance([bridge.uiManager viewForReactTag:view]);
  300. }
  301. dispatch_group_leave(RCTProfileGetUnhookGroup());
  302. });
  303. }
  304. }
  305. #pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target
  306. @interface RCTProfile : NSObject
  307. @end
  308. @implementation RCTProfile
  309. + (void)vsync:(CADisplayLink *)displayLink
  310. {
  311. RCTProfileImmediateEvent(RCTProfileTagAlways, @"VSYNC", displayLink.timestamp, 'g');
  312. }
  313. + (void)reload
  314. {
  315. [RCTProfilingBridge() reloadWithReason:@"Profiling controls"];
  316. }
  317. + (void)toggle:(UIButton *)target
  318. {
  319. BOOL isProfiling = RCTProfileIsProfiling();
  320. // Start and Stop are switched here, since we're going to toggle isProfiling
  321. [target setTitle:isProfiling ? @"Start" : @"Stop" forState:UIControlStateNormal];
  322. if (isProfiling) {
  323. RCTProfileEnd(RCTProfilingBridge(), ^(NSString *result) {
  324. NSString *outFile = [NSTemporaryDirectory() stringByAppendingString:@"tmp_trace.json"];
  325. [result writeToFile:outFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
  326. #if !TARGET_OS_TV
  327. UIActivityViewController *activityViewController =
  328. [[UIActivityViewController alloc] initWithActivityItems:@[ [NSURL fileURLWithPath:outFile] ]
  329. applicationActivities:nil];
  330. activityViewController.completionWithItemsHandler =
  331. ^(__unused UIActivityType activityType,
  332. __unused BOOL completed,
  333. __unused NSArray *items,
  334. __unused NSError *error) {
  335. RCTProfileControlsWindow.hidden = NO;
  336. };
  337. RCTProfileControlsWindow.hidden = YES;
  338. dispatch_async(dispatch_get_main_queue(), ^{
  339. [[[[RCTSharedApplication() delegate] window] rootViewController] presentViewController:activityViewController
  340. animated:YES
  341. completion:nil];
  342. });
  343. #endif
  344. });
  345. } else {
  346. RCTProfileInit(RCTProfilingBridge());
  347. }
  348. }
  349. + (void)drag:(UIPanGestureRecognizer *)gestureRecognizer
  350. {
  351. CGPoint translation = [gestureRecognizer translationInView:RCTProfileControlsWindow];
  352. RCTProfileControlsWindow.center =
  353. CGPointMake(RCTProfileControlsWindow.center.x + translation.x, RCTProfileControlsWindow.center.y + translation.y);
  354. [gestureRecognizer setTranslation:CGPointMake(0, 0) inView:RCTProfileControlsWindow];
  355. }
  356. @end
  357. #pragma mark - Public Functions
  358. dispatch_queue_t RCTProfileGetQueue(void)
  359. {
  360. static dispatch_queue_t queue;
  361. static dispatch_once_t onceToken;
  362. dispatch_once(&onceToken, ^{
  363. queue = dispatch_queue_create("com.facebook.react.Profiler", DISPATCH_QUEUE_SERIAL);
  364. });
  365. return queue;
  366. }
  367. BOOL RCTProfileIsProfiling(void)
  368. {
  369. return atomic_load(&RCTProfileProfiling);
  370. }
  371. void RCTProfileInit(RCTBridge *bridge)
  372. {
  373. // TODO: enable assert JS thread from any file (and assert here)
  374. BOOL wasProfiling = atomic_fetch_or(&RCTProfileProfiling, 1);
  375. if (wasProfiling) {
  376. return;
  377. }
  378. if (callbacks != NULL) {
  379. systrace_buffer = callbacks->start();
  380. } else {
  381. NSTimeInterval time = CACurrentMediaTime();
  382. dispatch_async(RCTProfileGetQueue(), ^{
  383. RCTProfileStartTime = time;
  384. RCTProfileOngoingEvents = [NSMutableDictionary new];
  385. RCTProfileInfo = @{
  386. kProfileTraceEvents : [NSMutableArray new],
  387. kProfileSamples : [NSMutableArray new],
  388. };
  389. });
  390. }
  391. // Set up thread ordering
  392. dispatch_async(RCTProfileGetQueue(), ^{
  393. NSArray *orderedThreads =
  394. @[ @"JS async", @"RCTPerformanceLogger", @"com.facebook.react.JavaScript", @(RCTUIManagerQueueName), @"main" ];
  395. [orderedThreads enumerateObjectsUsingBlock:^(NSString *thread, NSUInteger idx, __unused BOOL *stop) {
  396. RCTProfileAddEvent(kProfileTraceEvents,
  397. @"ph"
  398. : @"M", // metadata event
  399. @"name"
  400. : @"thread_sort_index", @"tid"
  401. : thread, @"args"
  402. :
  403. @{@"sort_index" : @(-1000 + (NSInteger)idx)});
  404. }];
  405. });
  406. RCTProfileHookModules(bridge);
  407. RCTProfileDisplayLink = [CADisplayLink displayLinkWithTarget:[RCTProfile class] selector:@selector(vsync:)];
  408. [RCTProfileDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  409. [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling object:bridge];
  410. }
  411. void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *))
  412. {
  413. // assert JavaScript thread here again
  414. BOOL wasProfiling = atomic_fetch_and(&RCTProfileProfiling, 0);
  415. if (!wasProfiling) {
  416. return;
  417. }
  418. [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling object:bridge];
  419. [RCTProfileDisplayLink invalidate];
  420. RCTProfileDisplayLink = nil;
  421. RCTProfileUnhookModules(bridge);
  422. if (callbacks != NULL) {
  423. if (systrace_buffer) {
  424. callbacks->stop();
  425. callback(@(systrace_buffer));
  426. }
  427. } else {
  428. dispatch_async(RCTProfileGetQueue(), ^{
  429. NSString *log = RCTJSONStringify(RCTProfileInfo, NULL);
  430. RCTProfileEventID = 0;
  431. RCTProfileInfo = nil;
  432. RCTProfileOngoingEvents = nil;
  433. callback(log);
  434. });
  435. }
  436. }
  437. static NSMutableArray<NSArray *> *RCTProfileGetThreadEvents(NSThread *thread)
  438. {
  439. static NSString *const RCTProfileThreadEventsKey = @"RCTProfileThreadEventsKey";
  440. NSMutableArray<NSArray *> *threadEvents = thread.threadDictionary[RCTProfileThreadEventsKey];
  441. if (!threadEvents) {
  442. threadEvents = [NSMutableArray new];
  443. thread.threadDictionary[RCTProfileThreadEventsKey] = threadEvents;
  444. }
  445. return threadEvents;
  446. }
  447. void _RCTProfileBeginEvent(
  448. NSThread *calleeThread,
  449. NSTimeInterval time,
  450. uint64_t tag,
  451. NSString *name,
  452. NSDictionary<NSString *, NSString *> *args)
  453. {
  454. CHECK();
  455. if (callbacks != NULL) {
  456. systrace_arg_t *systraceArgs = newSystraceArgsFromDictionary(args);
  457. callbacks->begin_section(tag, name.UTF8String, args.count, systraceArgs);
  458. free(systraceArgs);
  459. return;
  460. }
  461. dispatch_async(RCTProfileGetQueue(), ^{
  462. NSMutableArray *events = RCTProfileGetThreadEvents(calleeThread);
  463. [events addObject:@[
  464. RCTProfileTimestamp(time),
  465. name,
  466. RCTNullIfNil(args),
  467. ]];
  468. });
  469. }
  470. void _RCTProfileEndEvent(
  471. NSThread *calleeThread,
  472. NSString *threadName,
  473. NSTimeInterval time,
  474. uint64_t tag,
  475. NSString *category)
  476. {
  477. CHECK();
  478. if (callbacks != NULL) {
  479. callbacks->end_section(tag, 0, nil);
  480. return;
  481. }
  482. dispatch_async(RCTProfileGetQueue(), ^{
  483. NSMutableArray<NSArray *> *events = RCTProfileGetThreadEvents(calleeThread);
  484. NSArray *event = events.lastObject;
  485. [events removeLastObject];
  486. if (!event) {
  487. return;
  488. }
  489. NSNumber *start = event[0];
  490. RCTProfileAddEvent(kProfileTraceEvents, @"tid"
  491. : threadName, @"name"
  492. : event[1], @"cat"
  493. : category, @"ph"
  494. : @"X", @"ts"
  495. : start, @"dur"
  496. : @(RCTProfileTimestamp(time).doubleValue - start.doubleValue), @"args"
  497. : event[2], );
  498. });
  499. }
  500. NSUInteger RCTProfileBeginAsyncEvent(uint64_t tag, NSString *name, NSDictionary<NSString *, NSString *> *args)
  501. {
  502. CHECK(0);
  503. static NSUInteger eventID = 0;
  504. NSTimeInterval time = CACurrentMediaTime();
  505. NSUInteger currentEventID = ++eventID;
  506. if (callbacks != NULL) {
  507. systrace_arg_t *systraceArgs = newSystraceArgsFromDictionary(args);
  508. callbacks->begin_async_section(tag, name.UTF8String, (int)(currentEventID % INT_MAX), args.count, systraceArgs);
  509. free(systraceArgs);
  510. } else {
  511. dispatch_async(RCTProfileGetQueue(), ^{
  512. RCTProfileOngoingEvents[@(currentEventID)] = @[
  513. RCTProfileTimestamp(time),
  514. name,
  515. RCTNullIfNil(args),
  516. ];
  517. });
  518. }
  519. return currentEventID;
  520. }
  521. void RCTProfileEndAsyncEvent(uint64_t tag, NSString *category, NSUInteger cookie, NSString *name, NSString *threadName)
  522. {
  523. CHECK();
  524. if (callbacks != NULL) {
  525. callbacks->end_async_section(tag, name.UTF8String, (int)(cookie % INT_MAX), 0, nil);
  526. return;
  527. }
  528. NSTimeInterval time = CACurrentMediaTime();
  529. dispatch_async(RCTProfileGetQueue(), ^{
  530. NSArray *event = RCTProfileOngoingEvents[@(cookie)];
  531. if (event) {
  532. NSNumber *endTimestamp = RCTProfileTimestamp(time);
  533. RCTProfileAddEvent(kProfileTraceEvents, @"tid"
  534. : threadName, @"name"
  535. : event[1], @"cat"
  536. : category, @"ph"
  537. : @"X", @"ts"
  538. : event[0], @"dur"
  539. : @(endTimestamp.doubleValue - [event[0] doubleValue]), @"args"
  540. : event[2], );
  541. [RCTProfileOngoingEvents removeObjectForKey:@(cookie)];
  542. }
  543. });
  544. }
  545. void RCTProfileImmediateEvent(uint64_t tag, NSString *name, NSTimeInterval time, char scope)
  546. {
  547. CHECK();
  548. if (callbacks != NULL) {
  549. callbacks->instant_section(tag, name.UTF8String, scope);
  550. return;
  551. }
  552. NSString *threadName = RCTCurrentThreadName();
  553. dispatch_async(RCTProfileGetQueue(), ^{
  554. RCTProfileAddEvent(kProfileTraceEvents, @"tid"
  555. : threadName, @"name"
  556. : name, @"ts"
  557. : RCTProfileTimestamp(time), @"scope"
  558. : @(scope), @"ph"
  559. : @"i", @"args"
  560. : RCTProfileGetMemoryUsage(), );
  561. });
  562. }
  563. NSUInteger _RCTProfileBeginFlowEvent(void)
  564. {
  565. static NSUInteger flowID = 0;
  566. CHECK(0);
  567. NSUInteger cookie = ++flowID;
  568. if (callbacks != NULL) {
  569. callbacks->begin_async_flow(1, "flow", (int)cookie);
  570. return cookie;
  571. }
  572. NSTimeInterval time = CACurrentMediaTime();
  573. NSString *threadName = RCTCurrentThreadName();
  574. dispatch_async(RCTProfileGetQueue(), ^{
  575. RCTProfileAddEvent(kProfileTraceEvents, @"tid"
  576. : threadName, @"name"
  577. : @"flow", @"id"
  578. : @(cookie), @"cat"
  579. : @"flow", @"ph"
  580. : @"s", @"ts"
  581. : RCTProfileTimestamp(time), );
  582. });
  583. return cookie;
  584. }
  585. void _RCTProfileEndFlowEvent(NSUInteger cookie)
  586. {
  587. CHECK();
  588. if (callbacks != NULL) {
  589. callbacks->end_async_flow(1, "flow", (int)cookie);
  590. return;
  591. }
  592. NSTimeInterval time = CACurrentMediaTime();
  593. NSString *threadName = RCTCurrentThreadName();
  594. dispatch_async(RCTProfileGetQueue(), ^{
  595. RCTProfileAddEvent(kProfileTraceEvents, @"tid"
  596. : threadName, @"name"
  597. : @"flow", @"id"
  598. : @(cookie), @"cat"
  599. : @"flow", @"ph"
  600. : @"f", @"ts"
  601. : RCTProfileTimestamp(time), );
  602. });
  603. }
  604. void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data)
  605. {
  606. if (![bridge.bundleURL.scheme hasPrefix:@"http"]) {
  607. RCTLogWarn(
  608. @"Cannot upload profile information because you're not connected to the packager. The profiling data is still saved in the app container.");
  609. return;
  610. }
  611. NSURL *URL = [NSURL URLWithString:[@"/" stringByAppendingString:route] relativeToURL:bridge.bundleURL];
  612. NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
  613. URLRequest.HTTPMethod = @"POST";
  614. [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
  615. NSURLSessionTask *task = [[NSURLSession sharedSession]
  616. uploadTaskWithRequest:URLRequest
  617. fromData:data
  618. completionHandler:^(NSData *responseData, __unused NSURLResponse *response, NSError *error) {
  619. if (error) {
  620. RCTLogError(@"%@", error.localizedDescription);
  621. } else {
  622. NSString *message = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
  623. if (message.length) {
  624. #if !TARGET_OS_TV
  625. dispatch_async(dispatch_get_main_queue(), ^{
  626. UIAlertController *alertController =
  627. [UIAlertController alertControllerWithTitle:@"Profile"
  628. message:message
  629. preferredStyle:UIAlertControllerStyleAlert];
  630. [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
  631. style:UIAlertActionStyleCancel
  632. handler:nil]];
  633. [RCTPresentedViewController() presentViewController:alertController animated:YES completion:nil];
  634. });
  635. #endif
  636. }
  637. }
  638. }];
  639. [task resume];
  640. }
  641. void RCTProfileShowControls(void)
  642. {
  643. static const CGFloat height = 30;
  644. static const CGFloat width = 60;
  645. UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(20, 80, width * 2, height)];
  646. window.windowLevel = UIWindowLevelAlert + 1000;
  647. window.hidden = NO;
  648. window.backgroundColor = [UIColor lightGrayColor];
  649. window.layer.borderColor = [UIColor grayColor].CGColor;
  650. window.layer.borderWidth = 1;
  651. window.alpha = 0.8;
  652. UIButton *startOrStop = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, width, height)];
  653. [startOrStop setTitle:RCTProfileIsProfiling() ? @"Stop" : @"Start" forState:UIControlStateNormal];
  654. [startOrStop addTarget:[RCTProfile class] action:@selector(toggle:) forControlEvents:UIControlEventTouchUpInside];
  655. startOrStop.titleLabel.font = [UIFont systemFontOfSize:12];
  656. UIButton *reload = [[UIButton alloc] initWithFrame:CGRectMake(width, 0, width, height)];
  657. [reload setTitle:@"Reload" forState:UIControlStateNormal];
  658. [reload addTarget:[RCTProfile class] action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
  659. reload.titleLabel.font = [UIFont systemFontOfSize:12];
  660. [window addSubview:startOrStop];
  661. [window addSubview:reload];
  662. UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:[RCTProfile class]
  663. action:@selector(drag:)];
  664. [window addGestureRecognizer:gestureRecognizer];
  665. RCTProfileControlsWindow = window;
  666. }
  667. void RCTProfileHideControls(void)
  668. {
  669. RCTProfileControlsWindow.hidden = YES;
  670. RCTProfileControlsWindow = nil;
  671. }
  672. #endif