1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
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 |
|
29 | NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
|
30 | NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling";
|
31 |
|
32 | const uint64_t RCTProfileTagAlways = 1L << 0;
|
33 |
|
34 | #if RCT_PROFILE
|
35 |
|
36 | #pragma mark - Constants
|
37 |
|
38 | static NSString *const kProfileTraceEvents = @"traceEvents";
|
39 | static NSString *const kProfileSamples = @"samples";
|
40 | static NSString *const kProfilePrefix = @"rct_profile_";
|
41 |
|
42 | #pragma mark - Variables
|
43 |
|
44 | static atomic_bool RCTProfileProfiling = ATOMIC_VAR_INIT(NO);
|
45 |
|
46 | static NSDictionary *RCTProfileInfo;
|
47 | static NSMutableDictionary *RCTProfileOngoingEvents;
|
48 | static NSTimeInterval RCTProfileStartTime;
|
49 | static NSUInteger RCTProfileEventID = 0;
|
50 | static CADisplayLink *RCTProfileDisplayLink;
|
51 | static __weak RCTBridge *_RCTProfilingBridge;
|
52 | static 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(...) \
|
63 | if (!RCTProfileIsProfiling()) { \
|
64 | return __VA_ARGS__; \
|
65 | }
|
66 |
|
67 | #pragma mark - systrace glue code
|
68 |
|
69 | static RCTProfileCallbacks *callbacks;
|
70 | static char *systrace_buffer;
|
71 |
|
72 | static 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 |
|
92 | void RCTProfileRegisterCallbacks(RCTProfileCallbacks *cb)
|
93 | {
|
94 | callbacks = cb;
|
95 | }
|
96 |
|
97 | #pragma mark - Private Helpers
|
98 |
|
99 | static RCTBridge *RCTProfilingBridge(void)
|
100 | {
|
101 | return _RCTProfilingBridge ?: [RCTBridge currentBridge];
|
102 | }
|
103 |
|
104 | static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp)
|
105 | {
|
106 | return @((timestamp - RCTProfileStartTime) * 1e6);
|
107 | }
|
108 |
|
109 | static NSString *RCTProfileMemory(vm_size_t memory)
|
110 | {
|
111 | double mem = ((double)memory) / 1024 / 1024;
|
112 | return [NSString stringWithFormat:@"%.2lfmb", mem];
|
113 | }
|
114 |
|
115 | static 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 |
|
136 | static const char *RCTProfileProxyClassName(Class class)
|
137 | {
|
138 | return [kProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String;
|
139 | }
|
140 |
|
141 | static 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
|
153 | RCT_EXTERN void *RCTProfileMalloc(size_t size);
|
154 | void *RCTProfileMalloc(size_t size)
|
155 | {
|
156 | return malloc(size);
|
157 | }
|
158 |
|
159 | // Used by RCTProfileTrampoline assembly file to call libc`free
|
160 | RCT_EXTERN void RCTProfileFree(void *buf);
|
161 | void RCTProfileFree(void *buf)
|
162 | {
|
163 | free(buf);
|
164 | }
|
165 |
|
166 | RCT_EXTERN IMP RCTProfileGetImplementation(id obj, SEL cmd);
|
167 | IMP RCTProfileGetImplementation(id obj, SEL cmd)
|
168 | {
|
169 | return class_getMethodImplementation([obj class], cmd);
|
170 | }
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
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 |
|
195 | RCT_EXTERN void RCTProfileTrampolineStart(id, SEL);
|
196 | void RCTProfileTrampolineStart(id self, SEL cmd)
|
197 | {
|
198 | |
199 |
|
200 |
|
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 |
|
206 | RCT_EXTERN void RCTProfileTrampolineEnd(void);
|
207 | void RCTProfileTrampolineEnd(void)
|
208 | {
|
209 | RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call,modules,auto");
|
210 | }
|
211 |
|
212 | static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *, NSNumber *);
|
213 | static 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 |
|
220 | static 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 |
|
236 | void RCTProfileHookInstance(id instance)
|
237 | {
|
238 | Class moduleClass = object_getClass(instance);
|
239 |
|
240 | |
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
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 |
|
269 |
|
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 |
|
281 |
|
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 |
|
307 | void 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 |
|
330 | static void RCTProfileUnhookInstance(id instance)
|
331 | {
|
332 | if ([instance class] != object_getClass(instance)) {
|
333 | object_setClass(instance, [instance class]);
|
334 | }
|
335 | }
|
336 |
|
337 | void 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 |
|
430 | dispatch_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 |
|
440 | BOOL RCTProfileIsProfiling(void)
|
441 | {
|
442 | return atomic_load(&RCTProfileProfiling);
|
443 | }
|
444 |
|
445 | void 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 |
|
492 | void 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 |
|
525 | static 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 |
|
537 | void _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 |
|
563 | void _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 |
|
599 | NSUInteger 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
|
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 |
|
628 | void 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
|
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 |
|
664 | void 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 |
|
691 | NSUInteger _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 |
|
721 | void _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 |
|
745 | void 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 |
|
790 | void 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 |
|
824 | void RCTProfileHideControls(void)
|
825 | {
|
826 | RCTProfileControlsWindow.hidden = YES;
|
827 | RCTProfileControlsWindow = nil;
|
828 | }
|
829 |
|
830 | #endif
|