[ReactNative] Add fps monitor

Summary:
@public

Add basic JS and UI thread FPS monitor

Test Plan: Launch the UIExplorer, open the Dev Menu with cmd+D, and select `Show FPS Monitor`
This commit is contained in:
Tadeu Zagallo 2015-05-20 18:24:34 -07:00
parent 1d5d01c3c9
commit 08844e3ddc
8 changed files with 366 additions and 8 deletions

View File

@ -22,6 +22,7 @@
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
@ -930,6 +931,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
_queuesByID = [[RCTSparseArray alloc] init];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
if (RCT_DEV) {
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
/**
* Initialize executor to allow enqueueing calls
*/
@ -1560,6 +1566,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
}
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
[self.perfStats.jsGraph tick:displayLink.timestamp];
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
@ -1567,6 +1575,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
[self.perfStats.uiGraph tick:displayLink.timestamp];
}
- (void)startProfiling
@ -1578,10 +1588,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
return;
}
[_mainDisplayLink invalidate];
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
RCTProfileInit();
}
@ -1589,8 +1595,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
{
RCTAssertMainThread();
[_mainDisplayLink invalidate];
NSString *log = RCTProfileEnd();
NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];

View File

@ -35,6 +35,11 @@
*/
@property (nonatomic, assign) BOOL liveReloadEnabled;
/**
* Shows the FPS monitor for the JS and Main threads
*/
@property (nonatomic, assign) BOOL showFPS;
/**
* Manually show the dev menu (can be called from JS).
*/

View File

@ -13,6 +13,7 @@
#import "RCTDefines.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
#import "RCTProfile.h"
#import "RCTRootView.h"
#import "RCTSourceCode.h"
@ -145,6 +146,7 @@ RCT_EXPORT_MODULE()
self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
self.showFPS = [_settings[@"showFPS"] ?: @NO boolValue];
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
}
@ -230,13 +232,14 @@ RCT_EXPORT_METHOD(show)
NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome";
NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari";
NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, nil];
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
if (_liveReloadURL) {
@ -293,10 +296,14 @@ RCT_EXPORT_METHOD(reload)
break;
}
case 3: {
self.liveReloadEnabled = !_liveReloadEnabled;
self.showFPS = !_showFPS;
break;
}
case 4: {
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 5: {
self.profilingEnabled = !_profilingEnabled;
break;
}
@ -368,6 +375,21 @@ RCT_EXPORT_METHOD(reload)
}
}
- (void)setShowFPS:(BOOL)showFPS
{
if (_showFPS != showFPS) {
_showFPS = showFPS;
if (showFPS) {
[_bridge.perfStats show];
} else {
[_bridge.perfStats hide];
}
[self updateSetting:@"showFPS" value:@(showFPS)];
}
}
- (void)checkForUpdates
{
if (!_jsLoaded || !_liveReloadEnabled || !_liveReloadURL) {

23
React/Base/RCTFPSGraph.h Normal file
View File

@ -0,0 +1,23 @@
/**
* 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 <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, RCTFPSGraphPosition) {
RCTFPSGraphPositionLeft = 1,
RCTFPSGraphPositionRight = 2
};
@interface RCTFPSGraph : UIView
- (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color NS_DESIGNATED_INITIALIZER;
- (void)tick:(NSTimeInterval)timestamp;
@end

132
React/Base/RCTFPSGraph.m Normal file
View File

@ -0,0 +1,132 @@
/**
* 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 "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;
_position = position;
_graph = [self createGraph:color];
_label = [self createLabel:color];
[self addSubview:_label];
[self.layer addSublayer:_graph];
}
return self;
}
- (void)dealloc
{
free(_frames);
}
- (void)layoutSubviews
{
[super layoutSubviews];
}
- (CAShapeLayer *)createGraph:(UIColor *)color
{
CGFloat left = _position & RCTFPSGraphPositionLeft ? 0 : _length;
CAShapeLayer *graph = [[CAShapeLayer alloc] init];
graph.frame = CGRectMake(left, 0, 2 * _margin + _length, self.frame.size.height);
graph.backgroundColor = [[color colorWithAlphaComponent:.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)tick:(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

27
React/Base/RCTPerfStats.h Normal file
View File

@ -0,0 +1,27 @@
/**
* 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

133
React/Base/RCTPerfStats.m Normal file
View File

@ -0,0 +1,133 @@
/**
* 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"
#if RCT_DEV
@interface RCTPerfStats() <RCTBridgeModule>
@end
@implementation RCTPerfStats
{
UIView *_container;
}
RCT_EXPORT_MODULE()
- (void)dealloc
{
[self hide];
}
- (UIView *)container
{
if (!_container) {
_container = [[UIView alloc] init];
_container.backgroundColor = [UIColor colorWithRed:0 green:0 blue:34/255.0 alpha:1];
_container.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
}
return _container;
}
- (RCTFPSGraph *)jsGraph
{
if (!_jsGraph) {
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) {
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
{
UIView *targetView = [[[[[UIApplication sharedApplication] 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
#else
@implementation RCTPerfStats
- (void)show {}
- (void)hide {}
@end
#endif
@implementation RCTBridge (RCTPerfStats)
- (RCTPerfStats *)perfStats
{
return self.modules[RCTBridgeModuleNameForClass([RCTPerfStats class])];
}
@end

View File

@ -44,9 +44,11 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; };
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */; };
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; };
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; };
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 */; };
@ -163,6 +165,8 @@
13E067501A70F44B002CDEE1 /* RCTView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTView.m; sourceTree = "<group>"; };
13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = "<group>"; };
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPerfStats.h; sourceTree = "<group>"; };
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfStats.m; sourceTree = "<group>"; };
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = "<group>"; };
@ -170,6 +174,8 @@
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; };
146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = "<group>"; };
146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = "<group>"; };
14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = "<group>"; };
14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = "<group>"; };
14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; };
@ -415,6 +421,10 @@
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
14F4D3891AE1B7E40049C042 /* RCTProfile.h */,
14F4D38A1AE1B7E40049C042 /* RCTProfile.m */,
146459241B06C49500B389AA /* RCTFPSGraph.h */,
146459251B06C49500B389AA /* RCTFPSGraph.m */,
1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */,
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */,
);
path = Base;
sourceTree = "<group>";
@ -514,6 +524,7 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m 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 */,
@ -549,6 +560,7 @@
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */,
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */,