Permalink
Browse files

[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`
  • Loading branch information...
tadeuzagallo committed May 21, 2015
1 parent 1d5d01c commit 08844e3ddc40d435ac96cf98575e633249235222
View
@@ -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 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge
_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,13 +1566,17 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink
}
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
[self.perfStats.jsGraph tick:displayLink.timestamp];
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
[self.perfStats.uiGraph tick:displayLink.timestamp];
}
- (void)startProfiling
@@ -1578,19 +1588,13 @@ - (void)startProfiling
return;
}
[_mainDisplayLink invalidate];
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
RCTProfileInit();
}
- (void)stopProfiling
{
RCTAssertMainThread();
[_mainDisplayLink invalidate];
NSString *log = RCTProfileEnd();
NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
View
@@ -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
@@ -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 @@ - (void)updateSettings
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 @@ - (void)toggle
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 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
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 @@ - (void)setExecutorClass:(Class)executorClass
}
}
- (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) {
View
@@ -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
View
@@ -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
View
@@ -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
Oops, something went wrong.

0 comments on commit 08844e3

Please sign in to comment.