diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 11d94271c23..15ba6caecac 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -20,7 +20,6 @@ #import "RCTModuleMap.h" #import "RCTBridgeMethod.h" #import "RCTPerformanceLogger.h" -#import "RCTPerfStats.h" #import "RCTProfile.h" #import "RCTRedBox.h" #import "RCTSourceCode.h" @@ -68,7 +67,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); NSMutableArray *_pendingCalls; NSMutableArray *_moduleDataByID; RCTModuleMap *_modulesByName; - CADisplayLink *_mainDisplayLink; CADisplayLink *_jsDisplayLink; NSMutableSet *_frameUpdateObservers; } @@ -94,11 +92,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); _frameUpdateObservers = [NSMutableSet new]; _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; - if (RCT_DEV) { - _mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)]; - [_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - } - [RCTBridge setCurrentBridge:self]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification @@ -508,9 +501,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR [RCTBridge setCurrentBridge:nil]; } - [_mainDisplayLink invalidate]; - _mainDisplayLink = nil; - // Invalidate modules dispatch_group_t group = dispatch_group_create(); for (RCTModuleData *moduleData in _moduleDataByID) { @@ -868,21 +858,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR RCTProfileImmediateEvent(0, @"JS Thread Tick", 'g'); RCTProfileEndEvent(0, @"objc_call", nil); - - RCT_IF_DEV( - dispatch_async(dispatch_get_main_queue(), ^{ - [self.perfStats.jsGraph onTick:displayLink.timestamp]; - }); - ) -} - -- (void)_mainThreadUpdate:(CADisplayLink *)displayLink -{ - RCTAssertMainThread(); - - RCTProfileImmediateEvent(0, @"VSYNC", 'g'); - - _modulesByName == nil ?: [self.perfStats.uiGraph onTick:displayLink.timestamp]; } - (void)startProfiling diff --git a/React/Base/RCTFPSGraph.m b/React/Base/RCTFPSGraph.m deleted file mode 100644 index 1cd33432fdd..00000000000 --- a/React/Base/RCTFPSGraph.m +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTFPSGraph.h" - -#import "RCTAssert.h" -#import "RCTDefines.h" - -#if RCT_DEV - -@implementation RCTFPSGraph -{ - CAShapeLayer *_graph; - NSString *_name; - NSTimeInterval _prevTime; - RCTFPSGraphPosition _position; - UILabel *_label; - - float *_frames; - int _frameCount; - int _maxFPS; - int _minFPS; - int _length; - int _margin; - int _height; -} - -- (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color -{ - if ((self = [super initWithFrame:frame])) { - _margin = 2; - _prevTime = -1; - _maxFPS = 0; - _minFPS = 60; - _length = (frame.size.width - 2 * _margin) / 2; - _height = frame.size.height - 2 * _margin; - _frames = malloc(sizeof(float) * _length); - memset(_frames, 0, sizeof(float) * _length); - - _name = name ?: @"FPS"; - _position = position ?: RCTFPSGraphPositionLeft; - - color = color ?: [UIColor greenColor]; - _graph = [self createGraph:color]; - _label = [self createLabel:color]; - - [self addSubview:_label]; - [self.layer addSublayer:_graph]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) -RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - -- (void)dealloc -{ - free(_frames); -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; -} - -- (CAShapeLayer *)createGraph:(UIColor *)color -{ - CGFloat left = _position & RCTFPSGraphPositionLeft ? 0 : _length; - CAShapeLayer *graph = [CAShapeLayer new]; - graph.frame = CGRectMake(left, 0, 2 * _margin + _length, self.frame.size.height); - graph.backgroundColor = [color colorWithAlphaComponent:0.2].CGColor; - graph.fillColor = color.CGColor; - return graph; -} - -- (UILabel *)createLabel:(UIColor *)color -{ - CGFloat left = _position & RCTFPSGraphPositionLeft ? 2 * _margin + _length : 0; - UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(left, 0, _length, self.frame.size.height)]; - label.textColor = color; - label.font = [UIFont systemFontOfSize:9]; - label.minimumScaleFactor = .5; - label.adjustsFontSizeToFitWidth = YES; - label.numberOfLines = 3; - label.lineBreakMode = NSLineBreakByWordWrapping; - label.textAlignment = NSTextAlignmentCenter; - return label; -} - -- (void)onTick:(NSTimeInterval)timestamp -{ - _frameCount++; - if (_prevTime == -1) { - _prevTime = timestamp; - } else if (timestamp - _prevTime > 1) { - float fps = round(_frameCount / (timestamp - _prevTime)); - _minFPS = MIN(_minFPS, fps); - _maxFPS = MAX(_maxFPS, fps); - - _label.text = [NSString stringWithFormat:@"%@\n%d FPS\n(%d - %d)", _name, (int)fps, _minFPS, _maxFPS]; - - float scale = 60.0 / _height; - for (int i = 0; i < _length - 1; i++) { - _frames[i] = _frames[i + 1]; - } - _frames[_length - 1] = fps / scale; - - CGMutablePathRef path = CGPathCreateMutable(); - if (_position & RCTFPSGraphPositionLeft) { - CGPathMoveToPoint(path, NULL, _margin, _margin + _height); - for (int i = 0; i < _length; i++) { - CGPathAddLineToPoint(path, NULL, _margin + i, _margin + _height - _frames[i]); - } - CGPathAddLineToPoint(path, NULL, _margin + _length - 1, _margin + _height); - } else { - CGPathMoveToPoint(path, NULL, _margin + _length - 1, _margin + _height); - for (int i = 0; i < _length; i++) { - CGPathAddLineToPoint(path, NULL, _margin + _length - i - 1, _margin + _height - _frames[i]); - } - CGPathAddLineToPoint(path, NULL, _margin, _margin + _height); - } - _graph.path = path; - CGPathRelease(path); - - _prevTime = timestamp; - _frameCount = 0; - } -} - -@end - -#endif diff --git a/React/Base/RCTPerfStats.h b/React/Base/RCTPerfStats.h deleted file mode 100644 index 18c13cad684..00000000000 --- a/React/Base/RCTPerfStats.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTBridge.h" -#import "RCTFPSGraph.h" - -@interface RCTPerfStats : NSObject - -@property (nonatomic, strong) RCTFPSGraph *jsGraph; -@property (nonatomic, strong) RCTFPSGraph *uiGraph; - -- (void)show; -- (void)hide; - -@end - -@interface RCTBridge (RCTPerfStats) - -@property (nonatomic, strong, readonly) RCTPerfStats *perfStats; - -@end diff --git a/React/Base/RCTPerfStats.m b/React/Base/RCTPerfStats.m deleted file mode 100644 index 462aa0f9ea8..00000000000 --- a/React/Base/RCTPerfStats.m +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTPerfStats.h" - -#import "RCTDefines.h" -#import "RCTUtils.h" - -#if RCT_DEV - -@interface RCTPerfStats() - -@end - -@implementation RCTPerfStats -{ - UIView *_container; -} - -RCT_EXPORT_MODULE() - -- (void)dealloc -{ - [self hide]; -} - -- (UIView *)container -{ - if (!_container) { - _container = [UIView new]; - _container.backgroundColor = [UIColor colorWithRed:0 green:0 blue:34/255.0 alpha:1]; - _container.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; - } - return _container; -} - -- (RCTFPSGraph *)jsGraph -{ - if (!_jsGraph && _container) { - UIColor *jsColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:1]; - _jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34) - graphPosition:RCTFPSGraphPositionRight - name:@"[ JS ]" - color:jsColor]; - _jsGraph.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - } - return _jsGraph; -} - -- (RCTFPSGraph *)uiGraph -{ - if (!_uiGraph && _container) { - UIColor *uiColor = [UIColor colorWithRed:0 green:1 blue:1 alpha:1]; - _uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34) - graphPosition:RCTFPSGraphPositionLeft - name:@"[ UI ]" - color:uiColor]; - } - return _uiGraph; -} - -- (void)show -{ - if (RCTRunningInAppExtension()) { - return; - } - - UIView *targetView = RCTSharedApplication().delegate.window.rootViewController.view; - - targetView.frame = (CGRect){ - targetView.frame.origin, - { - targetView.frame.size.width, - targetView.frame.size.height - 38, - } - }; - - self.container.frame = (CGRect){{0, targetView.frame.size.height}, {targetView.frame.size.width, 38}}; - self.jsGraph.frame = (CGRect){ - { - targetView.frame.size.width - self.uiGraph.frame.size.width - self.uiGraph.frame.origin.x, - self.uiGraph.frame.origin.x, - }, - self.uiGraph.frame.size, - }; - - [self.container addSubview:self.jsGraph]; - [self.container addSubview:self.uiGraph]; - [targetView addSubview:self.container]; -} - -- (void)hide -{ - UIView *targetView = _container.superview; - - targetView.frame = (CGRect){ - targetView.frame.origin, - { - targetView.frame.size.width, - targetView.frame.size.height + _container.frame.size.height - } - }; - - [_container removeFromSuperview]; -} - -- (dispatch_queue_t)methodQueue -{ - return dispatch_get_main_queue(); -} - -@end - -@implementation RCTBridge (RCTPerfStats) - -- (RCTPerfStats *)perfStats -{ - return self.modules[RCTBridgeModuleNameForClass([RCTPerfStats class])]; -} - -@end - -#else - -@implementation RCTPerfStats - -- (void)show {} -- (void)hide {} - -@end - -@implementation RCTBridge (RCTPerfStats) - -- (RCTPerfStats *)perfStats -{ - return nil; -} - -@end - -#endif diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index db24baf0acf..bc2ca9f8ebb 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -15,7 +15,6 @@ #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" -#import "RCTPerfStats.h" #import "RCTProfile.h" #import "RCTRootView.h" #import "RCTSourceCode.h" @@ -182,22 +181,6 @@ RCT_EXPORT_MODULE() __weak RCTDevMenu *weakSelf = self; - [_extraMenuItems addObject:[RCTDevMenuItem toggleItemWithKey:@"showFPS" - title:@"Show FPS Monitor" - selectedTitle:@"Hide FPS Monitor" - handler:^(BOOL showFPS) - { - RCTDevMenu *strongSelf = weakSelf; - if (strongSelf) { - strongSelf->_showFPS = showFPS; - if (showFPS) { - [strongSelf.bridge.perfStats show]; - } else { - [strongSelf.bridge.perfStats hide]; - } - } - }]]; - [_extraMenuItems addObject:[RCTDevMenuItem toggleItemWithKey:@"showInspector" title:@"Show Inspector" selectedTitle:@"Hide Inspector" diff --git a/React/Base/RCTFPSGraph.h b/React/Profiler/RCTFPSGraph.h similarity index 55% rename from React/Base/RCTFPSGraph.h rename to React/Profiler/RCTFPSGraph.h index 0c0e2664a84..ecec7aa5b66 100644 --- a/React/Base/RCTFPSGraph.h +++ b/React/Profiler/RCTFPSGraph.h @@ -9,15 +9,21 @@ #import -typedef NS_ENUM(NSUInteger, RCTFPSGraphPosition) { - RCTFPSGraphPositionLeft = 1, - RCTFPSGraphPositionRight = 2 -}; +#import "RCTDefines.h" + +#if RCT_DEV @interface RCTFPSGraph : UIView -- (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color NS_DESIGNATED_INITIALIZER; +@property (nonatomic, assign, readonly) NSUInteger FPS; +@property (nonatomic, assign, readonly) NSUInteger maxFPS; +@property (nonatomic, assign, readonly) NSUInteger minFPS; + +- (instancetype)initWithFrame:(CGRect)frame + color:(UIColor *)color NS_DESIGNATED_INITIALIZER; - (void)onTick:(NSTimeInterval)timestamp; @end + +#endif diff --git a/React/Profiler/RCTFPSGraph.m b/React/Profiler/RCTFPSGraph.m new file mode 100644 index 00000000000..44a381bfd30 --- /dev/null +++ b/React/Profiler/RCTFPSGraph.m @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTFPSGraph.h" + +#import "RCTAssert.h" + +#if RCT_DEV + +@interface RCTFPSGraph() + +@property (nonatomic, strong, readonly) CAShapeLayer *graph; +@property (nonatomic, strong, readonly) UILabel *label; + +@end + +@implementation RCTFPSGraph +{ + CAShapeLayer *_graph; + UILabel *_label; + + CGFloat *_frames; + UIColor *_color; + + NSTimeInterval _prevTime; + NSUInteger _frameCount; + NSUInteger _FPS; + NSUInteger _maxFPS; + NSUInteger _minFPS; + NSUInteger _length; + NSUInteger _height; +} + +- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color +{ + if ((self = [super initWithFrame:frame])) { + _frameCount = -1; + _prevTime = -1; + _maxFPS = 0; + _minFPS = 60; + _length = (NSUInteger)floor(frame.size.width); + _height = (NSUInteger)floor(frame.size.height); + _frames = calloc(sizeof(CGFloat), _length); + _color = color; + + [self.layer addSublayer:self.graph]; + [self addSubview:self.label]; + } + return self; +} + +- (void)dealloc +{ + free(_frames); +} + +RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) +RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) + +- (CAShapeLayer *)graph +{ + if (!_graph) { + _graph = [CAShapeLayer new]; + _graph.frame = self.bounds; + _graph.backgroundColor = [_color colorWithAlphaComponent:0.2].CGColor; + _graph.fillColor = _color.CGColor; + } + + return _graph; +} + +- (UILabel *)label +{ + if (!_label) { + _label = [[UILabel alloc] initWithFrame:self.bounds]; + _label.font = [UIFont boldSystemFontOfSize:13]; + _label.textAlignment = NSTextAlignmentCenter; + } + + return _label; +} + +- (void)onTick:(NSTimeInterval)timestamp +{ + _frameCount++; + if (_prevTime == -1) { + _prevTime = timestamp; + } else if (timestamp - _prevTime >= 1) { + _FPS = round(_frameCount / (timestamp - _prevTime)); + _minFPS = MIN(_minFPS, _FPS); + _maxFPS = MAX(_maxFPS, _FPS); + + _label.text = [NSString stringWithFormat:@"%lu", (unsigned long)_FPS]; + + CGFloat scale = 60.0 / _height; + for (NSUInteger i = 0; i < _length - 1; i++) { + _frames[i] = _frames[i + 1]; + } + _frames[_length - 1] = _FPS / scale; + + CGMutablePathRef path = CGPathCreateMutable(); + CGPathMoveToPoint(path, NULL, 0, _height); + for (NSUInteger i = 0; i < _length; i++) { + CGPathAddLineToPoint(path, NULL, i, _height - _frames[i]); + } + CGPathAddLineToPoint(path, NULL, _length - 1, _height); + + _graph.path = path; + CGPathRelease(path); + + _prevTime = timestamp; + _frameCount = 0; + } +} + +@end + +#endif diff --git a/React/Profiler/RCTPerfMonitor.m b/React/Profiler/RCTPerfMonitor.m new file mode 100644 index 00000000000..cebc89ac38b --- /dev/null +++ b/React/Profiler/RCTPerfMonitor.m @@ -0,0 +1,450 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTDefines.h" + +#if RCT_DEV + +#import + +#import + +#import "RCTBridge.h" +#import "RCTDevMenu.h" +#import "RCTFPSGraph.h" +#import "RCTInvalidating.h" +#import "RCTJavaScriptExecutor.h" +#import "RCTRootView.h" +#import "RCTSparseArray.h" +#import "RCTUIManager.h" + +static NSString *const RCTPerfMonitorKey = @"RCTPerfMonitorKey"; + +typedef BOOL (*RCTJSCSetOptionType)(const char *); + +static BOOL RCTJSCSetOption(const char *option) +{ + static RCTJSCSetOptionType setOption; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + setOption = dlsym(RTLD_DEFAULT, "_ZN3JSC7Options9setOptionEPKc"); + setOption("logGC=1"); + }); + + if (setOption) { + return setOption(option); + } else { + return NO; + } +} + +static vm_size_t RCTGetResidentMemorySize(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 0; + } + + return info.resident_size; +} + +@class RCTDevMenuItem; + +@interface RCTPerfMonitor : NSObject + +@property (nonatomic, strong, readonly) RCTDevMenuItem *devMenuItem; +@property (nonatomic, strong, readonly) UIPanGestureRecognizer *gestureRecognizer; +@property (nonatomic, strong, readonly) UIView *container; +@property (nonatomic, strong, readonly) UILabel *memory; +@property (nonatomic, strong, readonly) UILabel *heap; +@property (nonatomic, strong, readonly) UILabel *views; +@property (nonatomic, strong, readonly) RCTFPSGraph *jsGraph; +@property (nonatomic, strong, readonly) RCTFPSGraph *uiGraph; +@property (nonatomic, strong, readonly) UILabel *jsGraphLabel; +@property (nonatomic, strong, readonly) UILabel *uiGraphLabel; + +@end + +@implementation RCTPerfMonitor { + RCTDevMenuItem *_devMenuItem; + UIPanGestureRecognizer *_gestureRecognizer; + UIView *_container; + UILabel *_memory; + UILabel *_heap; + UILabel *_views; + UILabel *_uiGraphLabel; + UILabel *_jsGraphLabel; + + RCTFPSGraph *_uiGraph; + RCTFPSGraph *_jsGraph; + + CADisplayLink *_uiDisplayLink; + CADisplayLink *_jsDisplayLink; + + NSUInteger _heapSize; + + dispatch_queue_t _queue; + dispatch_io_t _io; + int _stderr; + int _pipe[2]; + NSString *_remaining; +} + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + +- (void)invalidate +{ + [self hide]; +} + +- (RCTDevMenuItem *)devMenuItem +{ + if (!_devMenuItem) { + __weak __typeof__(self) weakSelf = self; + _devMenuItem = + [RCTDevMenuItem toggleItemWithKey:RCTPerfMonitorKey + title:@"Show Monitor" + selectedTitle:@"Hide Monitor" + handler: + ^(BOOL selected) { + if (selected) { + [weakSelf show]; + } else { + [weakSelf hide]; + } + }]; + } + + return _devMenuItem; +} + +- (UIPanGestureRecognizer *)gestureRecognizer +{ + if (!_gestureRecognizer) { + _gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self + action:@selector(gesture:)]; + } + + return _gestureRecognizer; +} + +- (UIView *)container +{ + if (!_container) { + _container = [[UIView alloc] initWithFrame:CGRectMake(10, 25, 180, 50)]; + _container.backgroundColor = UIColor.whiteColor; + _container.layer.borderWidth = 2; + _container.layer.borderColor = [UIColor grayColor].CGColor; + [_container addGestureRecognizer:self.gestureRecognizer]; + } + + return _container; +} + +- (UILabel *)memory +{ + if (!_memory) { + _memory = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 44, 50)]; + _memory.font = [UIFont systemFontOfSize:12]; + _memory.numberOfLines = 3; + _memory.textAlignment = NSTextAlignmentCenter; + } + + return _memory; +} + +- (UILabel *)heap +{ + if (!_heap) { + _heap = [[UILabel alloc] initWithFrame:CGRectMake(44, 0, 44, 50)]; + _heap.font = [UIFont systemFontOfSize:12]; + _heap.numberOfLines = 3; + _heap.textAlignment = NSTextAlignmentCenter; + } + + return _heap; +} + +- (UILabel *)views +{ + if (!_views) { + _views = [[UILabel alloc] initWithFrame:CGRectMake(88, 0, 44, 50)]; + _views.font = [UIFont systemFontOfSize:12]; + _views.numberOfLines = 3; + _views.textAlignment = NSTextAlignmentCenter; + } + + return _views; +} + +- (RCTFPSGraph *)uiGraph +{ + if (!_uiGraph) { + _uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(134, 14, 40, 30) + color:[UIColor lightGrayColor]]; + } + return _uiGraph; +} + +- (RCTFPSGraph *)jsGraph +{ + if (!_jsGraph) { + _jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(178, 14, 40, 30) + color:[UIColor lightGrayColor]]; + } + return _jsGraph; +} + +- (UILabel *)uiGraphLabel +{ + if (!_uiGraphLabel) { + _uiGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(134, 3, 40, 10)]; + _uiGraphLabel.font = [UIFont systemFontOfSize:11]; + _uiGraphLabel.textAlignment = NSTextAlignmentCenter; + _uiGraphLabel.text = @"UI"; + } + + return _uiGraphLabel; +} + +- (UILabel *)jsGraphLabel +{ + if (!_jsGraphLabel) { + _jsGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(178, 3, 38, 10)]; + _jsGraphLabel.font = [UIFont systemFontOfSize:11]; + _jsGraphLabel.textAlignment = NSTextAlignmentCenter; + _jsGraphLabel.text = @"JS"; + } + + return _jsGraphLabel; +} + +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + + [_bridge.devMenu addItem:self.devMenuItem]; +} + +- (void)show +{ + if (_container) { + return; + } + + [self.container addSubview:self.memory]; + [self.container addSubview:self.heap]; + [self.container addSubview:self.views]; + [self.container addSubview:self.uiGraph]; + [self.container addSubview:self.uiGraphLabel]; + + [self redirectLogs]; + + RCTJSCSetOption("logGC=1"); + + [self updateStats]; + + UIWindow *window = [UIApplication sharedApplication].delegate.window; + [window addSubview:self.container]; + + + _uiDisplayLink = [CADisplayLink displayLinkWithTarget:self + selector:@selector(threadUpdate:)]; + [_uiDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] + forMode:NSRunLoopCommonModes]; + + id executor = [_bridge valueForKey:@"javaScriptExecutor"]; + if ([executor isKindOfClass:NSClassFromString(@"RCTContextExecutor")]) { + self.container.frame = (CGRect) { + self.container.frame.origin, { + self.container.frame.size.width + 44, + self.container.frame.size.height + } + }; + [self.container addSubview:self.jsGraph]; + [self.container addSubview:self.jsGraphLabel]; + [executor executeBlockOnJavaScriptQueue:^{ + _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self + selector:@selector(threadUpdate:)]; + [_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] + forMode:NSRunLoopCommonModes]; + }]; + } +} + +- (void)hide +{ + if (!_container) { + return; + } + + [self.container removeFromSuperview]; + _container = nil; + _jsGraph = nil; + _uiGraph = nil; + + RCTJSCSetOption("logGC=0"); + + [self stopLogs]; + + [_uiDisplayLink invalidate]; + [_jsDisplayLink invalidate]; + + _uiDisplayLink = nil; + _jsDisplayLink = nil; +} + +- (void)redirectLogs +{ + _stderr = dup(STDERR_FILENO); + + if (pipe(_pipe) != 0) { + return; + } + + dup2(_pipe[1], STDERR_FILENO); + close(_pipe[1]); + + __weak __typeof__(self) weakSelf = self; + _queue = dispatch_queue_create("com.facebook.react.RCTPerfMonitor", DISPATCH_QUEUE_SERIAL); + _io = dispatch_io_create( + DISPATCH_IO_STREAM, + _pipe[0], + _queue, + ^(__unused int error) {}); + + dispatch_io_set_low_water(_io, 20); + + dispatch_io_read( + _io, + 0, + SIZE_MAX, + _queue, + ^(__unused bool done, dispatch_data_t data, __unused int error) { + if (!data) { + return; + } + + dispatch_data_apply( + data, + ^bool( + __unused dispatch_data_t region, + __unused size_t offset, + const void *buffer, + size_t size + ) { + write(_stderr, buffer, size); + + NSString *log = [[NSString alloc] initWithBytes:buffer + length:size + encoding:NSUTF8StringEncoding]; + [weakSelf parse:log]; + return true; + }); + }); +} + +- (void)stopLogs +{ + dup2(_stderr, STDERR_FILENO); + dispatch_io_close(_io, 0); +} + +- (void)parse:(NSString *)log +{ + static NSRegularExpression *GCRegex; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *pattern = @"\\[GC: (Eden|Full)Collection, (?:Skipped copying|Did copy), ([\\d\\.]+) (\\wb), ([\\d.]+) (\\ws)\\]"; + GCRegex = [NSRegularExpression regularExpressionWithPattern:pattern + options:0 + error:nil]; + }); + + if (_remaining) { + log = [_remaining stringByAppendingString:log]; + _remaining = nil; + } + + NSArray *lines = [log componentsSeparatedByString:@"\n"]; + if (lines.count == 1) { // no newlines + _remaining = log; + return; + } + + for (NSString *line in lines) { + NSTextCheckingResult *match = [GCRegex firstMatchInString:line options:0 range:NSMakeRange(0, line.length)]; + if (match) { + NSString *heapSizeStr = [line substringWithRange:[match rangeAtIndex:2]]; + _heapSize = [heapSizeStr integerValue]; + } + } +} + +- (void)updateStats +{ + RCTSparseArray *views = [_bridge.uiManager valueForKey:@"viewRegistry"]; + NSUInteger viewCount = views.count; + NSUInteger visibleViewCount = 0; + for (UIView *view in views.allObjects) { + if (view.window || view.superview.window) { + visibleViewCount++; + } + } + + double mem = (double)RCTGetResidentMemorySize() / 1024 / 1024; + self.memory.text =[NSString stringWithFormat:@"RAM\n%.2lf\nMB", mem]; + self.heap.text = [NSString stringWithFormat:@"JSC\n%.2lf\nMB", (double)_heapSize / 1024]; + self.views.text = [NSString stringWithFormat:@"Views\n%lu\n%lu", (unsigned long)visibleViewCount, (unsigned long)viewCount]; + + __weak __typeof__(self) weakSelf = self; + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), + ^{ + __strong __typeof__(weakSelf) strongSelf = weakSelf; + if (strongSelf && strongSelf->_container.superview) { + [strongSelf updateStats]; + } + }); +} + +- (void)gesture:(UIPanGestureRecognizer *)gestureRecognizer +{ + CGPoint translation = [gestureRecognizer translationInView:self.container.superview]; + self.container.center = CGPointMake( + self.container.center.x + translation.x, + self.container.center.y + translation.y + ); + [gestureRecognizer setTranslation:CGPointMake(0, 0) + inView:self.container.superview]; +} + +- (void)threadUpdate:(CADisplayLink *)displayLink +{ + RCTFPSGraph *graph = displayLink == _jsDisplayLink ? _jsGraph : _uiGraph; + [graph onTick:displayLink.timestamp]; +} + +@end + +#endif diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 5239d97392c..de2ef090f37 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -49,7 +49,6 @@ 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; }; 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; }; 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F17A841B8493E5007D4C75 /* RCTRedBox.m */; }; - 1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */; }; 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; }; 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 142014171B32094000CC17BA /* RCTPerformanceLogger.m */; }; 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; @@ -59,7 +58,6 @@ 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */; settings = {ASSET_TAGS = (); }; }; 1450FF891BCFF28A00208362 /* RCTProfileTrampoline-x86.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */; settings = {ASSET_TAGS = (); }; }; 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */; settings = {ASSET_TAGS = (); }; }; - 146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; }; 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; }; 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; }; 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; }; @@ -67,6 +65,8 @@ 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; }; 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; }; 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; }; + 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */; settings = {ASSET_TAGS = (); }; }; + 14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; settings = {ASSET_TAGS = (); }; }; 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; }; 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; @@ -196,8 +196,6 @@ 13EF7F441BC69646003F47DD /* RCTImageComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageComponent.h; sourceTree = ""; }; 13F17A831B8493E5007D4C75 /* RCTRedBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRedBox.h; sourceTree = ""; }; 13F17A841B8493E5007D4C75 /* RCTRedBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRedBox.m; sourceTree = ""; }; - 1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPerfStats.h; sourceTree = ""; }; - 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfStats.m; sourceTree = ""; }; 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = ""; }; 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = ""; }; 142014171B32094000CC17BA /* RCTPerformanceLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerformanceLogger.m; sourceTree = ""; }; @@ -213,8 +211,6 @@ 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm64.S"; sourceTree = ""; }; 1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86.S"; sourceTree = ""; }; 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86_64.S"; sourceTree = ""; }; - 146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = ""; }; - 146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = ""; }; 1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeDelegate.h; sourceTree = ""; }; 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = ""; }; 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = ""; }; @@ -228,6 +224,9 @@ 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitchManager.m; sourceTree = ""; }; 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSliderManager.h; sourceTree = ""; }; 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSliderManager.m; sourceTree = ""; }; + 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfMonitor.m; sourceTree = ""; }; + 14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = ""; }; + 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = ""; }; 58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = ""; }; 58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = ""; }; 58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = ""; }; @@ -433,6 +432,9 @@ 1450FF7F1BCFF28A00208362 /* Profiler */ = { isa = PBXGroup; children = ( + 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */, + 14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */, + 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */, 1450FF801BCFF28A00208362 /* RCTProfile.h */, 1450FF811BCFF28A00208362 /* RCTProfile.m */, 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */, @@ -489,8 +491,6 @@ 13AF1F851AE6E777005F5298 /* RCTDefines.h */, 83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */, 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, - 146459241B06C49500B389AA /* RCTFPSGraph.h */, - 146459251B06C49500B389AA /* RCTFPSGraph.m */, 1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */, 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */, 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, @@ -511,8 +511,6 @@ 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */, 142014181B32094000CC17BA /* RCTPerformanceLogger.h */, 142014171B32094000CC17BA /* RCTPerformanceLogger.m */, - 1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */, - 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */, 830A229C1A66C68A008503DA /* RCTRootView.h */, 830A229D1A66C68A008503DA /* RCTRootView.m */, 83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */, @@ -635,15 +633,16 @@ 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */, + 14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */, 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */, 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */, 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */, + 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */, 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */, 13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */, - 146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */, 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */, 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */, 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */, @@ -688,7 +687,6 @@ 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, 1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */, - 1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, 83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */, 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,