// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "ios/chrome/browser/memory/memory_debugger.h" #include "base/ios/ios_util.h" #import "base/mac/scoped_nsobject.h" #import "base/memory/scoped_ptr.h" #import "ios/chrome/browser/memory/memory_metrics.h" #include "ios/chrome/browser/ui/ui_util.h" #import "ios/chrome/browser/ui/uikit_ui_util.h" namespace { // The number of bytes in a megabyte. const CGFloat kNumBytesInMB = 1024 * 1024; // The horizontal and vertical padding between subviews. const CGFloat kPadding = 10; } // namespace @implementation MemoryDebugger { // A timer to trigger refreshes. base::scoped_nsobject _refreshTimer; // A timer to trigger continuous memory warnings. base::scoped_nsobject _memoryWarningTimer; // The font to use. base::scoped_nsobject _font; // Labels for memory metrics. base::scoped_nsobject _physicalFreeMemoryLabel; base::scoped_nsobject _realMemoryUsedLabel; base::scoped_nsobject _xcodeGaugeLabel; base::scoped_nsobject _dirtyVirtualMemoryLabel; // Inputs for memory commands. base::scoped_nsobject _bloatField; base::scoped_nsobject _refreshField; base::scoped_nsobject _continuousMemoryWarningField; // A place to store the artifical memory bloat. scoped_ptr _bloat; // Distance the view was pushed up to accomodate the keyboard. CGFloat _keyboardOffset; // The current orientation of the device. BOOL _currentOrientation; } - (instancetype)init { self = [super initWithFrame:CGRectZero]; if (self) { _font.reset([[UIFont systemFontOfSize:14] retain]); self.backgroundColor = [UIColor colorWithWhite:0.8f alpha:0.9f]; self.opaque = NO; [self addSubviews]; [self adjustForOrientation:nil]; [self sizeToFit]; [self registerForNotifications]; } return self; } // NSTimers create a retain cycle so they must be invalidated before this // instance can be deallocated. - (void)invalidateTimers { [_refreshTimer invalidate]; [_memoryWarningTimer invalidate]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } #pragma mark UIView methods - (CGSize)sizeThatFits:(CGSize)size { CGFloat width = 0; CGFloat height = 0; for (UIView* subview in self.subviews) { width = MAX(width, CGRectGetMaxX(subview.frame)); height = MAX(height, CGRectGetMaxY(subview.frame)); } return CGSizeMake(width + kPadding, height + kPadding); } #pragma mark initialization helpers - (void)addSubviews { // |index| is used to calculate the vertical position of each element in // the debugger view. NSUInteger index = 0; // Display some metrics. _physicalFreeMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); [self addMetricWithName:@"Physical Free" atIndex:index++ usingLabel:_physicalFreeMemoryLabel]; _realMemoryUsedLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); [self addMetricWithName:@"Real Memory Used" atIndex:index++ usingLabel:_realMemoryUsedLabel]; _xcodeGaugeLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); [self addMetricWithName:@"Xcode Gauge" atIndex:index++ usingLabel:_xcodeGaugeLabel]; _dirtyVirtualMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); [self addMetricWithName:@"Dirty VM" atIndex:index++ usingLabel:_dirtyVirtualMemoryLabel]; // Since _performMemoryWarning is a private API it can't be compiled into // official builds. // TODO(lliabraa): Figure out how to support memory warnings (or something // like them) in official builds. #if CHROMIUM_BUILD [self addButtonWithTitle:@"Trigger Memory Warning" target:[UIApplication sharedApplication] action:@selector(_performMemoryWarning) withOrigin:[self originForSubviewAtIndex:index++]]; #endif // CHROMIUM_BUILD // Display a text input to set the amount of artificial memory bloat and a // button to reset the bloat to zero. _bloatField.reset([[UITextField alloc] initWithFrame:CGRectZero]); [self addLabelWithText:@"Set bloat (MB)" input:_bloatField inputTarget:self inputAction:@selector(updateBloat) buttonWithTitle:@"Clear" buttonTarget:self buttonAction:@selector(clearBloat) atIndex:index++]; [_bloatField setText:@"0"]; [self updateBloat]; // Since _performMemoryWarning is a private API it can't be compiled into // official builds. // TODO(lliabraa): Figure out how to support memory warnings (or something // like them) in official builds. #if CHROMIUM_BUILD // Display a text input to control the rate of continuous memory warnings. _continuousMemoryWarningField.reset( [[UITextField alloc] initWithFrame:CGRectZero]); [self addLabelWithText:@"Set memory warning interval (secs)" input:_continuousMemoryWarningField inputTarget:self inputAction:@selector(updateMemoryWarningInterval) atIndex:index++]; [_continuousMemoryWarningField setText:@"0.0"]; #endif // CHROMIUM_BUILD // Display a text input to control the refresh rate of the memory debugger. _refreshField.reset([[UITextField alloc] initWithFrame:CGRectZero]); [self addLabelWithText:@"Set refresh interval (secs)" input:_refreshField inputTarget:self inputAction:@selector(updateRefreshInterval) atIndex:index++]; [_refreshField setText:@"0.5"]; [self updateRefreshInterval]; } - (void)registerForNotifications { // On iOS 7, the screen coordinate system is not dependent on orientation so // the debugger has to handle its own rotation. if (!base::ios::IsRunningOnIOS8OrLater()) { // Register to receive orientation notifications. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(adjustForOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil]; } // Register to receive memory warning. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(lowMemoryWarningReceived:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; // Register to receive keyboard will show notification. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; // Register to receive keyboard will hide notification. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } // Adds subviews for the specified metric, the value of which will be displayed // in |label|. - (void)addMetricWithName:(NSString*)name atIndex:(NSUInteger)index usingLabel:(UILabel*)label { // The width of the view for the metric's name. const CGFloat kNameWidth = 150; // The width of the view for each metric. const CGFloat kMetricWidth = 100; CGPoint nameOrigin = [self originForSubviewAtIndex:index]; CGRect nameFrame = CGRectMake(nameOrigin.x, nameOrigin.y, kNameWidth, [_font lineHeight]); base::scoped_nsobject nameLabel( [[UILabel alloc] initWithFrame:nameFrame]); [nameLabel setText:[NSString stringWithFormat:@"%@: ", name]]; [nameLabel setFont:_font]; [self addSubview:nameLabel]; label.frame = CGRectMake(CGRectGetMaxX(nameFrame), nameFrame.origin.y, kMetricWidth, [_font lineHeight]); [label setFont:_font]; [label setTextAlignment:NSTextAlignmentRight]; [self addSubview:label]; } // Adds a subview for a button with the given title and target/action. - (void)addButtonWithTitle:(NSString*)title target:(id)target action:(SEL)action withOrigin:(CGPoint)origin { base::scoped_nsobject button( [[UIButton buttonWithType:UIButtonTypeSystem] retain]); [button setTitle:title forState:UIControlStateNormal]; [button titleLabel].font = _font; [[button titleLabel] setTextAlignment:NSTextAlignmentCenter]; [button sizeToFit]; [button setFrame:CGRectMake(origin.x, origin.y, [button frame].size.width, [_font lineHeight])]; [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside]; [self addSubview:button]; } // Adds subviews for a UI component with label and input text field. // // ------------------------- // | labelText | | // ------------------------- // // The inputTarget/inputAction will be invoked when the user finishes editing // in |input|. - (void)addLabelWithText:(NSString*)labelText input:(UITextField*)input inputTarget:(id)inputTarget inputAction:(SEL)inputAction atIndex:(NSUInteger)index { [self addLabelWithText:labelText input:input inputTarget:inputTarget inputAction:inputAction buttonWithTitle:nil buttonTarget:nil buttonAction:nil atIndex:index]; } // Adds subviews for a UI component with label, input text field and button. // // ------------------------------------- // | labelText | |