This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
828 lines
24 KiB
828 lines
24 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
|
|
#import "RCTProfile.h" |
|
|
|
#import <dlfcn.h> |
|
#import <mach/mach.h> |
|
#import <objc/message.h> |
|
#import <objc/runtime.h> |
|
#import <stdatomic.h> |
|
|
|
#import <UIKit/UIKit.h> |
|
|
|
#import "RCTAssert.h" |
|
#import "RCTBridge+Private.h" |
|
#import "RCTBridge.h" |
|
#import "RCTComponentData.h" |
|
#import "RCTDefines.h" |
|
#import "RCTLog.h" |
|
#import "RCTModuleData.h" |
|
#import "RCTUIManager.h" |
|
#import "RCTUIManagerUtils.h" |
|
#import "RCTUtils.h" |
|
|
|
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling"; |
|
NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; |
|
|
|
const uint64_t RCTProfileTagAlways = 1L << 0; |
|
|
|
#if RCT_PROFILE |
|
|
|
#pragma mark - Constants |
|
|
|
static NSString *const kProfileTraceEvents = @"traceEvents"; |
|
static NSString *const kProfileSamples = @"samples"; |
|
static NSString *const kProfilePrefix = @"rct_profile_"; |
|
|
|
#pragma mark - Variables |
|
|
|
static atomic_bool RCTProfileProfiling = ATOMIC_VAR_INIT(NO); |
|
|
|
static NSDictionary *RCTProfileInfo; |
|
static NSMutableDictionary *RCTProfileOngoingEvents; |
|
static NSTimeInterval RCTProfileStartTime; |
|
static NSUInteger RCTProfileEventID = 0; |
|
static CADisplayLink *RCTProfileDisplayLink; |
|
static __weak RCTBridge *_RCTProfilingBridge; |
|
static UIWindow *RCTProfileControlsWindow; |
|
|
|
#pragma mark - Macros |
|
|
|
#define RCTProfileAddEvent(type, props...) \ |
|
[RCTProfileInfo[type] addObject:@{ \ |
|
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \ |
|
props \ |
|
}]; |
|
|
|
#define CHECK(...) \ |
|
if (!RCTProfileIsProfiling()) { \ |
|
return __VA_ARGS__; \ |
|
} |
|
|
|
#pragma mark - systrace glue code |
|
|
|
static RCTProfileCallbacks *callbacks; |
|
static char *systrace_buffer; |
|
|
|
static systrace_arg_t *newSystraceArgsFromDictionary(NSDictionary<NSString *, NSString *> *args) |
|
{ |
|
if (args.count == 0) { |
|
return NULL; |
|
} |
|
|
|
systrace_arg_t *systrace_args = malloc(sizeof(systrace_arg_t) * args.count); |
|
__block size_t i = 0; |
|
[args enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) { |
|
systrace_args[i].key = [key UTF8String]; |
|
systrace_args[i].key_len = [key length]; |
|
systrace_args[i].value = [value UTF8String]; |
|
systrace_args[i].value_len = [value length]; |
|
i++; |
|
}]; |
|
return systrace_args; |
|
} |
|
|
|
void RCTProfileRegisterCallbacks(RCTProfileCallbacks *cb) |
|
{ |
|
callbacks = cb; |
|
} |
|
|
|
#pragma mark - Private Helpers |
|
|
|
static RCTBridge *RCTProfilingBridge(void) |
|
{ |
|
return _RCTProfilingBridge ?: [RCTBridge currentBridge]; |
|
} |
|
|
|
static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) |
|
{ |
|
return @((timestamp - RCTProfileStartTime) * 1e6); |
|
} |
|
|
|
static NSString *RCTProfileMemory(vm_size_t memory) |
|
{ |
|
double mem = ((double)memory) / 1024 / 1024; |
|
return [NSString stringWithFormat:@"%.2lfmb", mem]; |
|
} |
|
|
|
static NSDictionary *RCTProfileGetMemoryUsage(void) |
|
{ |
|
struct task_basic_info info; |
|
mach_msg_type_number_t size = sizeof(info); |
|
kern_return_t kerr = task_info(mach_task_self(), |
|
TASK_BASIC_INFO, |
|
(task_info_t)&info, |
|
&size); |
|
if ( kerr == KERN_SUCCESS ) { |
|
return @{ |
|
@"suspend_count": @(info.suspend_count), |
|
@"virtual_size": RCTProfileMemory(info.virtual_size), |
|
@"resident_size": RCTProfileMemory(info.resident_size), |
|
}; |
|
} else { |
|
return @{}; |
|
} |
|
} |
|
|
|
#pragma mark - Module hooks |
|
|
|
static const char *RCTProfileProxyClassName(Class class) |
|
{ |
|
return [kProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String; |
|
} |
|
|
|
static dispatch_group_t RCTProfileGetUnhookGroup(void) |
|
{ |
|
static dispatch_group_t unhookGroup; |
|
static dispatch_once_t onceToken; |
|
dispatch_once(&onceToken, ^{ |
|
unhookGroup = dispatch_group_create(); |
|
}); |
|
|
|
return unhookGroup; |
|
} |
|
|
|
// Used by RCTProfileTrampoline assembly file to call libc`malloc |
|
RCT_EXTERN void *RCTProfileMalloc(size_t size); |
|
void *RCTProfileMalloc(size_t size) |
|
{ |
|
return malloc(size); |
|
} |
|
|
|
// Used by RCTProfileTrampoline assembly file to call libc`free |
|
RCT_EXTERN void RCTProfileFree(void *buf); |
|
void RCTProfileFree(void *buf) |
|
{ |
|
free(buf); |
|
} |
|
|
|
RCT_EXTERN IMP RCTProfileGetImplementation(id obj, SEL cmd); |
|
IMP RCTProfileGetImplementation(id obj, SEL cmd) |
|
{ |
|
return class_getMethodImplementation([obj class], cmd); |
|
} |
|
|
|
/** |
|
* For the profiling we have to execute some code before and after every |
|
* function being profiled, the only way of doing that with pure Objective-C is |
|
* by using `-forwardInvocation:`, which is slow and could skew the profile |
|
* results. |
|
* |
|
* The alternative in assembly is much simpler, we just need to store all the |
|
* state at the beginning of the function, start the profiler, restore all the |
|
* state, call the actual function we want to profile and stop the profiler. |
|
* |
|
* The implementation can be found in RCTProfileTrampoline-<arch>.s where arch |
|
* is one of: i386, x86_64, arm, arm64. |
|
*/ |
|
#if defined(__i386__) || \ |
|
defined(__x86_64__) || \ |
|
defined(__arm__) || \ |
|
defined(__arm64__) |
|
|
|
RCT_EXTERN void RCTProfileTrampoline(void); |
|
#else |
|
static void *RCTProfileTrampoline = NULL; |
|
#endif |
|
|
|
RCT_EXTERN void RCTProfileTrampolineStart(id, SEL); |
|
void RCTProfileTrampolineStart(id self, SEL cmd) |
|
{ |
|
/** |
|
* This call might be during dealloc, so we shouldn't retain the object in the |
|
* block. |
|
*/ |
|
Class klass = [self class]; |
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, ([NSString stringWithFormat:@"-[%s %s]", class_getName(klass), sel_getName(cmd)]), nil); |
|
} |
|
|
|
RCT_EXTERN void RCTProfileTrampolineEnd(void); |
|
void RCTProfileTrampolineEnd(void) |
|
{ |
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call,modules,auto"); |
|
} |
|
|
|
static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *); |
|
static UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag) |
|
{ |
|
UIView *view = originalCreateView(self, _cmd, tag); |
|
RCTProfileHookInstance(view); |
|
return view; |
|
} |
|
|
|
static void RCTProfileHookUIManager(RCTUIManager *uiManager) |
|
{ |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
for (id view in [uiManager valueForKey:@"viewRegistry"]) { |
|
RCTProfileHookInstance([uiManager viewForReactTag:view]); |
|
} |
|
|
|
Method createView = class_getInstanceMethod([RCTComponentData class], @selector(createViewWithTag:)); |
|
|
|
if (method_getImplementation(createView) != (IMP)RCTProfileCreateView) { |
|
originalCreateView = (typeof(originalCreateView))method_getImplementation(createView); |
|
method_setImplementation(createView, (IMP)RCTProfileCreateView); |
|
} |
|
}); |
|
} |
|
|
|
void RCTProfileHookInstance(id instance) |
|
{ |
|
Class moduleClass = object_getClass(instance); |
|
|
|
/** |
|
* We swizzle the instance -class method to return the original class, but |
|
* object_getClass will return the actual class. |
|
* |
|
* If they are different, it means that the object is returning the original |
|
* class, but it's actual class is the proxy subclass we created. |
|
*/ |
|
if ([instance class] != moduleClass) { |
|
return; |
|
} |
|
|
|
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); |
|
|
|
if (!proxyClass) { |
|
proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); |
|
if (proxyClass) { |
|
object_setClass(instance, proxyClass); |
|
} |
|
return; |
|
} |
|
|
|
unsigned int methodCount; |
|
Method *methods = class_copyMethodList(moduleClass, &methodCount); |
|
for (NSUInteger i = 0; i < methodCount; i++) { |
|
Method method = methods[i]; |
|
SEL selector = method_getName(method); |
|
|
|
/** |
|
* Bail out on struct returns (except arm64) - we don't use it enough |
|
* to justify writing a stret version |
|
*/ |
|
#ifdef __arm64__ |
|
BOOL returnsStruct = NO; |
|
#else |
|
const char *typeEncoding = method_getTypeEncoding(method); |
|
// bail out on structs and unions (since they might contain structs) |
|
BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; |
|
#endif |
|
|
|
/** |
|
* Avoid hooking into NSObject methods, methods generated by React Native |
|
* and special methods that start `.` (e.g. .cxx_destruct) |
|
*/ |
|
if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || sel_getName(selector)[0] == '.' || returnsStruct) { |
|
continue; |
|
} |
|
|
|
const char *types = method_getTypeEncoding(method); |
|
class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types); |
|
} |
|
free(methods); |
|
|
|
class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:"); |
|
|
|
for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { |
|
Method oldImp = class_getInstanceMethod(cls, @selector(class)); |
|
class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); |
|
} |
|
|
|
objc_registerClassPair(proxyClass); |
|
object_setClass(instance, proxyClass); |
|
|
|
if (moduleClass == [RCTUIManager class]) { |
|
RCTProfileHookUIManager((RCTUIManager *)instance); |
|
} |
|
} |
|
|
|
void RCTProfileHookModules(RCTBridge *bridge) |
|
{ |
|
_RCTProfilingBridge = bridge; |
|
|
|
#pragma clang diagnostic push |
|
#pragma clang diagnostic ignored "-Wtautological-pointer-compare" |
|
if (RCTProfileTrampoline == NULL) { |
|
return; |
|
} |
|
#pragma clang diagnostic pop |
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"RCTProfileHookModules", nil); |
|
for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { |
|
// Only hook modules with an instance, to prevent initializing everything |
|
if ([moduleData hasInstance]) { |
|
[bridge dispatchBlock:^{ |
|
RCTProfileHookInstance(moduleData.instance); |
|
} queue:moduleData.methodQueue]; |
|
} |
|
} |
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); |
|
} |
|
|
|
static void RCTProfileUnhookInstance(id instance) |
|
{ |
|
if ([instance class] != object_getClass(instance)) { |
|
object_setClass(instance, [instance class]); |
|
} |
|
} |
|
|
|
void RCTProfileUnhookModules(RCTBridge *bridge) |
|
{ |
|
_RCTProfilingBridge = nil; |
|
|
|
dispatch_group_enter(RCTProfileGetUnhookGroup()); |
|
|
|
NSDictionary *moduleDataByID = [bridge valueForKey:@"moduleDataByID"]; |
|
for (RCTModuleData *moduleData in moduleDataByID) { |
|
if ([moduleData hasInstance]) { |
|
RCTProfileUnhookInstance(moduleData.instance); |
|
} |
|
} |
|
|
|
if ([bridge moduleIsInitialized:[RCTUIManager class]]) { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) { |
|
RCTProfileUnhookInstance(view); |
|
} |
|
|
|
dispatch_group_leave(RCTProfileGetUnhookGroup()); |
|
}); |
|
} |
|
} |
|
|
|
#pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target |
|
|
|
@interface RCTProfile : NSObject |
|
@end |
|
|
|
@implementation RCTProfile |
|
|
|
+ (void)vsync:(CADisplayLink *)displayLink |
|
{ |
|
RCTProfileImmediateEvent(RCTProfileTagAlways, @"VSYNC", displayLink.timestamp, 'g'); |
|
} |
|
|
|
+ (void)reload |
|
{ |
|
[RCTProfilingBridge() reload]; |
|
} |
|
|
|
+ (void)toggle:(UIButton *)target |
|
{ |
|
BOOL isProfiling = RCTProfileIsProfiling(); |
|
|
|
// Start and Stop are switched here, since we're going to toggle isProfiling |
|
[target setTitle:isProfiling ? @"Start" : @"Stop" |
|
forState:UIControlStateNormal]; |
|
|
|
if (isProfiling) { |
|
RCTProfileEnd(RCTProfilingBridge(), ^(NSString *result) { |
|
NSString *outFile = [NSTemporaryDirectory() stringByAppendingString:@"tmp_trace.json"]; |
|
[result writeToFile:outFile |
|
atomically:YES |
|
encoding:NSUTF8StringEncoding |
|
error:nil]; |
|
#if !TARGET_OS_TV |
|
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[[NSURL fileURLWithPath:outFile]] |
|
applicationActivities:nil]; |
|
activityViewController.completionWithItemsHandler = ^(__unused UIActivityType activityType, |
|
__unused BOOL completed, |
|
__unused NSArray *items, |
|
__unused NSError *error) { |
|
RCTProfileControlsWindow.hidden = NO; |
|
}; |
|
RCTProfileControlsWindow.hidden = YES; |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[[[[RCTSharedApplication() delegate] window] rootViewController] presentViewController:activityViewController |
|
animated:YES |
|
completion:nil]; |
|
}); |
|
#endif |
|
}); |
|
} else { |
|
RCTProfileInit(RCTProfilingBridge()); |
|
} |
|
} |
|
|
|
+ (void)drag:(UIPanGestureRecognizer *)gestureRecognizer |
|
{ |
|
CGPoint translation = [gestureRecognizer translationInView:RCTProfileControlsWindow]; |
|
RCTProfileControlsWindow.center = CGPointMake( |
|
RCTProfileControlsWindow.center.x + translation.x, |
|
RCTProfileControlsWindow.center.y + translation.y |
|
); |
|
[gestureRecognizer setTranslation:CGPointMake(0, 0) |
|
inView:RCTProfileControlsWindow]; |
|
} |
|
|
|
@end |
|
|
|
#pragma mark - Public Functions |
|
|
|
dispatch_queue_t RCTProfileGetQueue(void) |
|
{ |
|
static dispatch_queue_t queue; |
|
static dispatch_once_t onceToken; |
|
dispatch_once(&onceToken, ^{ |
|
queue = dispatch_queue_create("com.facebook.react.Profiler", DISPATCH_QUEUE_SERIAL); |
|
}); |
|
return queue; |
|
} |
|
|
|
BOOL RCTProfileIsProfiling(void) |
|
{ |
|
return atomic_load(&RCTProfileProfiling); |
|
} |
|
|
|
void RCTProfileInit(RCTBridge *bridge) |
|
{ |
|
// TODO: enable assert JS thread from any file (and assert here) |
|
BOOL wasProfiling = atomic_fetch_or(&RCTProfileProfiling, 1); |
|
if (wasProfiling) { |
|
return; |
|
} |
|
|
|
if (callbacks != NULL) { |
|
systrace_buffer = callbacks->start(); |
|
} else { |
|
NSTimeInterval time = CACurrentMediaTime(); |
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
RCTProfileStartTime = time; |
|
RCTProfileOngoingEvents = [NSMutableDictionary new]; |
|
RCTProfileInfo = @{ |
|
kProfileTraceEvents: [NSMutableArray new], |
|
kProfileSamples: [NSMutableArray new], |
|
}; |
|
}); |
|
} |
|
|
|
// Set up thread ordering |
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
NSArray *orderedThreads = @[@"JS async", @"RCTPerformanceLogger", @"com.facebook.react.JavaScript", |
|
@(RCTUIManagerQueueName), @"main"]; |
|
[orderedThreads enumerateObjectsUsingBlock:^(NSString *thread, NSUInteger idx, __unused BOOL *stop) { |
|
RCTProfileAddEvent(kProfileTraceEvents, |
|
@"ph": @"M", // metadata event |
|
@"name": @"thread_sort_index", |
|
@"tid": thread, |
|
@"args": @{ @"sort_index": @(-1000 + (NSInteger)idx) } |
|
); |
|
}]; |
|
}); |
|
|
|
RCTProfileHookModules(bridge); |
|
|
|
RCTProfileDisplayLink = [CADisplayLink displayLinkWithTarget:[RCTProfile class] |
|
selector:@selector(vsync:)]; |
|
[RCTProfileDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] |
|
forMode:NSRunLoopCommonModes]; |
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling |
|
object:bridge]; |
|
} |
|
|
|
void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *)) |
|
{ |
|
// assert JavaScript thread here again |
|
BOOL wasProfiling = atomic_fetch_and(&RCTProfileProfiling, 0); |
|
if (!wasProfiling) { |
|
return; |
|
} |
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling |
|
object:bridge]; |
|
|
|
[RCTProfileDisplayLink invalidate]; |
|
RCTProfileDisplayLink = nil; |
|
|
|
RCTProfileUnhookModules(bridge); |
|
|
|
if (callbacks != NULL) { |
|
if (systrace_buffer) { |
|
callbacks->stop(); |
|
callback(@(systrace_buffer)); |
|
} |
|
} else { |
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); |
|
RCTProfileEventID = 0; |
|
RCTProfileInfo = nil; |
|
RCTProfileOngoingEvents = nil; |
|
|
|
callback(log); |
|
}); |
|
} |
|
} |
|
|
|
static NSMutableArray<NSArray *> *RCTProfileGetThreadEvents(NSThread *thread) |
|
{ |
|
static NSString *const RCTProfileThreadEventsKey = @"RCTProfileThreadEventsKey"; |
|
NSMutableArray<NSArray *> *threadEvents = |
|
thread.threadDictionary[RCTProfileThreadEventsKey]; |
|
if (!threadEvents) { |
|
threadEvents = [NSMutableArray new]; |
|
thread.threadDictionary[RCTProfileThreadEventsKey] = threadEvents; |
|
} |
|
return threadEvents; |
|
} |
|
|
|
void _RCTProfileBeginEvent( |
|
NSThread *calleeThread, |
|
NSTimeInterval time, |
|
uint64_t tag, |
|
NSString *name, |
|
NSDictionary<NSString *, NSString *> *args |
|
) { |
|
CHECK(); |
|
|
|
if (callbacks != NULL) { |
|
systrace_arg_t *systraceArgs = newSystraceArgsFromDictionary(args); |
|
callbacks->begin_section(tag, name.UTF8String, args.count, systraceArgs); |
|
free(systraceArgs); |
|
return; |
|
} |
|
|
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
NSMutableArray *events = RCTProfileGetThreadEvents(calleeThread); |
|
[events addObject:@[ |
|
RCTProfileTimestamp(time), |
|
name, |
|
RCTNullIfNil(args), |
|
]]; |
|
}); |
|
} |
|
|
|
void _RCTProfileEndEvent( |
|
NSThread *calleeThread, |
|
NSString *threadName, |
|
NSTimeInterval time, |
|
uint64_t tag, |
|
NSString *category |
|
) { |
|
CHECK(); |
|
|
|
if (callbacks != NULL) { |
|
callbacks->end_section(tag, 0, nil); |
|
return; |
|
} |
|
|
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
NSMutableArray<NSArray *> *events = RCTProfileGetThreadEvents(calleeThread); |
|
NSArray *event = events.lastObject; |
|
[events removeLastObject]; |
|
|
|
if (!event) { |
|
return; |
|
} |
|
|
|
NSNumber *start = event[0]; |
|
RCTProfileAddEvent(kProfileTraceEvents, |
|
@"tid": threadName, |
|
@"name": event[1], |
|
@"cat": category, |
|
@"ph": @"X", |
|
@"ts": start, |
|
@"dur": @(RCTProfileTimestamp(time).doubleValue - start.doubleValue), |
|
@"args": event[2], |
|
); |
|
}); |
|
} |
|
|
|
NSUInteger RCTProfileBeginAsyncEvent( |
|
uint64_t tag, |
|
NSString *name, |
|
NSDictionary<NSString *, NSString *> *args |
|
) { |
|
CHECK(0); |
|
|
|
static NSUInteger eventID = 0; |
|
|
|
NSTimeInterval time = CACurrentMediaTime(); |
|
NSUInteger currentEventID = ++eventID; |
|
|
|
if (callbacks != NULL) { |
|
systrace_arg_t *systraceArgs = newSystraceArgsFromDictionary(args); |
|
callbacks->begin_async_section(tag, name.UTF8String, (int)(currentEventID % INT_MAX), args.count, systraceArgs); |
|
free(systraceArgs); |
|
} else { |
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
RCTProfileOngoingEvents[@(currentEventID)] = @[ |
|
RCTProfileTimestamp(time), |
|
name, |
|
RCTNullIfNil(args), |
|
]; |
|
}); |
|
} |
|
|
|
return currentEventID; |
|
} |
|
|
|
void RCTProfileEndAsyncEvent( |
|
uint64_t tag, |
|
NSString *category, |
|
NSUInteger cookie, |
|
NSString *name, |
|
NSString *threadName |
|
) { |
|
CHECK(); |
|
|
|
if (callbacks != NULL) { |
|
callbacks->end_async_section(tag, name.UTF8String, (int)(cookie % INT_MAX), 0, nil); |
|
return; |
|
} |
|
|
|
NSTimeInterval time = CACurrentMediaTime(); |
|
|
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
NSArray *event = RCTProfileOngoingEvents[@(cookie)]; |
|
|
|
if (event) { |
|
NSNumber *endTimestamp = RCTProfileTimestamp(time); |
|
|
|
RCTProfileAddEvent(kProfileTraceEvents, |
|
@"tid": threadName, |
|
@"name": event[1], |
|
@"cat": category, |
|
@"ph": @"X", |
|
@"ts": event[0], |
|
@"dur": @(endTimestamp.doubleValue - [event[0] doubleValue]), |
|
@"args": event[2], |
|
); |
|
[RCTProfileOngoingEvents removeObjectForKey:@(cookie)]; |
|
} |
|
}); |
|
} |
|
|
|
void RCTProfileImmediateEvent( |
|
uint64_t tag, |
|
NSString *name, |
|
NSTimeInterval time, |
|
char scope |
|
) { |
|
CHECK(); |
|
|
|
if (callbacks != NULL) { |
|
callbacks->instant_section(tag, name.UTF8String, scope); |
|
return; |
|
} |
|
|
|
NSString *threadName = RCTCurrentThreadName(); |
|
|
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
RCTProfileAddEvent(kProfileTraceEvents, |
|
@"tid": threadName, |
|
@"name": name, |
|
@"ts": RCTProfileTimestamp(time), |
|
@"scope": @(scope), |
|
@"ph": @"i", |
|
@"args": RCTProfileGetMemoryUsage(), |
|
); |
|
}); |
|
} |
|
|
|
NSUInteger _RCTProfileBeginFlowEvent(void) |
|
{ |
|
static NSUInteger flowID = 0; |
|
|
|
CHECK(0); |
|
|
|
NSUInteger cookie = ++flowID; |
|
if (callbacks != NULL) { |
|
callbacks->begin_async_flow(1, "flow", (int)cookie); |
|
return cookie; |
|
} |
|
|
|
NSTimeInterval time = CACurrentMediaTime(); |
|
NSString *threadName = RCTCurrentThreadName(); |
|
|
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
RCTProfileAddEvent(kProfileTraceEvents, |
|
@"tid": threadName, |
|
@"name": @"flow", |
|
@"id": @(cookie), |
|
@"cat": @"flow", |
|
@"ph": @"s", |
|
@"ts": RCTProfileTimestamp(time), |
|
); |
|
|
|
}); |
|
|
|
return cookie; |
|
} |
|
|
|
void _RCTProfileEndFlowEvent(NSUInteger cookie) |
|
{ |
|
CHECK(); |
|
|
|
if (callbacks != NULL) { |
|
callbacks->end_async_flow(1, "flow", (int)cookie); |
|
return; |
|
} |
|
|
|
NSTimeInterval time = CACurrentMediaTime(); |
|
NSString *threadName = RCTCurrentThreadName(); |
|
|
|
dispatch_async(RCTProfileGetQueue(), ^{ |
|
RCTProfileAddEvent(kProfileTraceEvents, |
|
@"tid": threadName, |
|
@"name": @"flow", |
|
@"id": @(cookie), |
|
@"cat": @"flow", |
|
@"ph": @"f", |
|
@"ts": RCTProfileTimestamp(time), |
|
); |
|
}); |
|
} |
|
|
|
void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data) |
|
{ |
|
if (![bridge.bundleURL.scheme hasPrefix:@"http"]) { |
|
RCTLogWarn(@"Cannot upload profile information because you're not connected to the packager. The profiling data is still saved in the app container."); |
|
return; |
|
} |
|
|
|
NSURL *URL = [NSURL URLWithString:[@"/" stringByAppendingString:route] relativeToURL:bridge.bundleURL]; |
|
|
|
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; |
|
URLRequest.HTTPMethod = @"POST"; |
|
[URLRequest setValue:@"application/json" |
|
forHTTPHeaderField:@"Content-Type"]; |
|
|
|
NSURLSessionTask *task = |
|
[[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest |
|
fromData:data |
|
completionHandler: |
|
^(NSData *responseData, __unused NSURLResponse *response, NSError *error) { |
|
if (error) { |
|
RCTLogError(@"%@", error.localizedDescription); |
|
} else { |
|
NSString *message = [[NSString alloc] initWithData:responseData |
|
encoding:NSUTF8StringEncoding]; |
|
|
|
if (message.length) { |
|
#if !TARGET_OS_TV |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
UIAlertController *alertController = [UIAlertController |
|
alertControllerWithTitle:@"Profile" |
|
message:message |
|
preferredStyle:UIAlertControllerStyleAlert]; |
|
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" |
|
style:UIAlertActionStyleCancel |
|
handler:nil]]; |
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:nil]; |
|
}); |
|
#endif |
|
} |
|
} |
|
}]; |
|
|
|
[task resume]; |
|
} |
|
|
|
void RCTProfileShowControls(void) |
|
{ |
|
static const CGFloat height = 30; |
|
static const CGFloat width = 60; |
|
|
|
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(20, 80, width * 2, height)]; |
|
window.windowLevel = UIWindowLevelAlert + 1000; |
|
window.hidden = NO; |
|
window.backgroundColor = [UIColor lightGrayColor]; |
|
window.layer.borderColor = [UIColor grayColor].CGColor; |
|
window.layer.borderWidth = 1; |
|
window.alpha = 0.8; |
|
|
|
UIButton *startOrStop = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, width, height)]; |
|
[startOrStop setTitle:RCTProfileIsProfiling() ? @"Stop" : @"Start" |
|
forState:UIControlStateNormal]; |
|
[startOrStop addTarget:[RCTProfile class] action:@selector(toggle:) forControlEvents:UIControlEventTouchUpInside]; |
|
startOrStop.titleLabel.font = [UIFont systemFontOfSize:12]; |
|
|
|
UIButton *reload = [[UIButton alloc] initWithFrame:CGRectMake(width, 0, width, height)]; |
|
[reload setTitle:@"Reload" forState:UIControlStateNormal]; |
|
[reload addTarget:[RCTProfile class] action:@selector(reload) forControlEvents:UIControlEventTouchUpInside]; |
|
reload.titleLabel.font = [UIFont systemFontOfSize:12]; |
|
|
|
[window addSubview:startOrStop]; |
|
[window addSubview:reload]; |
|
|
|
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:[RCTProfile class] |
|
action:@selector(drag:)]; |
|
[window addGestureRecognizer:gestureRecognizer]; |
|
|
|
RCTProfileControlsWindow = window; |
|
} |
|
|
|
void RCTProfileHideControls(void) |
|
{ |
|
RCTProfileControlsWindow.hidden = YES; |
|
RCTProfileControlsWindow = nil; |
|
} |
|
|
|
#endif
|
|
|