UNPKG

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