diff options
author | droger <droger@chromium.org> | 2015-04-08 04:59:45 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-08 12:00:19 +0000 |
commit | a7b5e669bd7b9cf14a5b409d39d6673ea512f2f2 (patch) | |
tree | d0be93b2b82c48c4ab37826fe7ec62cef3c5eab6 /ios | |
parent | 303b4f842223724c710b89cd342e9382e5c2b7a5 (diff) | |
download | chromium_src-a7b5e669bd7b9cf14a5b409d39d6673ea512f2f2.zip chromium_src-a7b5e669bd7b9cf14a5b409d39d6673ea512f2f2.tar.gz chromium_src-a7b5e669bd7b9cf14a5b409d39d6673ea512f2f2.tar.bz2 |
[iOS] Upstream //ios/chrome/browser/memory
Review URL: https://codereview.chromium.org/1057933002
Cr-Commit-Position: refs/heads/master@{#324216}
Diffstat (limited to 'ios')
-rw-r--r-- | ios/chrome/browser/memory/OWNERS | 1 | ||||
-rw-r--r-- | ios/chrome/browser/memory/memory_debugger.h | 26 | ||||
-rw-r--r-- | ios/chrome/browser/memory/memory_debugger.mm | 600 | ||||
-rw-r--r-- | ios/chrome/browser/memory/memory_debugger_manager.h | 26 | ||||
-rw-r--r-- | ios/chrome/browser/memory/memory_debugger_manager.mm | 72 | ||||
-rw-r--r-- | ios/chrome/browser/memory/memory_metrics.cc | 89 | ||||
-rw-r--r-- | ios/chrome/browser/memory/memory_metrics.h | 29 | ||||
-rw-r--r-- | ios/chrome/browser/pref_names.cc | 4 | ||||
-rw-r--r-- | ios/chrome/browser/pref_names.h | 1 | ||||
-rw-r--r-- | ios/chrome/ios_chrome.gyp | 6 |
10 files changed, 854 insertions, 0 deletions
diff --git a/ios/chrome/browser/memory/OWNERS b/ios/chrome/browser/memory/OWNERS new file mode 100644 index 0000000..f723afb --- /dev/null +++ b/ios/chrome/browser/memory/OWNERS @@ -0,0 +1 @@ +lliabraa@chromium.org diff --git a/ios/chrome/browser/memory/memory_debugger.h b/ios/chrome/browser/memory/memory_debugger.h new file mode 100644 index 0000000..a3c9c07 --- /dev/null +++ b/ios/chrome/browser/memory/memory_debugger.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef IOS_CHROME_BROWSER_MEMORY_MEMORY_DEBUGGER_H_ +#define IOS_CHROME_BROWSER_MEMORY_MEMORY_DEBUGGER_H_ + +#import <UIKit/UIKit.h> + +// A view that contains memory information (e.g. amount of free memory) and +// tools (e.g. trigger memory warning) to help investigate memory issues and +// performance. +// +// The debugger ensures that it remains visible by continuously calling +// bringSubviewToFront on it's parent so it should be added as a subview of the +// the application's window in order to stay visible all the times. +// +// The debugger owns some timers that must be invalidated before it can be +// deallocated so the owner must call |invalidateTimers| before a MemoryDebugger +// instance can be deallocated. +@interface MemoryDebugger : UIView<UITextFieldDelegate> +// Must be called before the object can be deallocated! +- (void)invalidateTimers; +@end + +#endif // IOS_CHROME_BROWSER_MEMORY_MEMORY_DEBUGGER_H_ diff --git a/ios/chrome/browser/memory/memory_debugger.mm b/ios/chrome/browser/memory/memory_debugger.mm new file mode 100644 index 0000000..22bf881 --- /dev/null +++ b/ios/chrome/browser/memory/memory_debugger.mm @@ -0,0 +1,600 @@ +// 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<NSTimer> _refreshTimer; + + // A timer to trigger continuous memory warnings. + base::scoped_nsobject<NSTimer> _memoryWarningTimer; + + // The font to use. + base::scoped_nsobject<UIFont> _font; + + // Labels for memory metrics. + base::scoped_nsobject<UILabel> _physicalFreeMemoryLabel; + base::scoped_nsobject<UILabel> _realMemoryUsedLabel; + base::scoped_nsobject<UILabel> _xcodeGaugeLabel; + base::scoped_nsobject<UILabel> _dirtyVirtualMemoryLabel; + + // Inputs for memory commands. + base::scoped_nsobject<UITextField> _bloatField; + base::scoped_nsobject<UITextField> _refreshField; + base::scoped_nsobject<UITextField> _continuousMemoryWarningField; + + // A place to store the artifical memory bloat. + scoped_ptr<uint8> _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<UILabel> 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<UIButton> 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 | <input> | +// ------------------------- +// +// 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 | <input> | <button> | +// ------------------------------------- +// +// 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 + buttonWithTitle:(NSString*)buttonTitle + buttonTarget:(id)buttonTarget + buttonAction:(SEL)buttonAction + atIndex:(NSUInteger)index { + base::scoped_nsobject<UILabel> label( + [[UILabel alloc] initWithFrame:CGRectZero]); + if (labelText) { + [label setText:[NSString stringWithFormat:@"%@: ", labelText]]; + } + [label setFont:_font]; + [label sizeToFit]; + CGPoint labelOrigin = [self originForSubviewAtIndex:index]; + [label setFrame:CGRectOffset([label frame], labelOrigin.x, labelOrigin.y)]; + [self addSubview:label]; + if (input) { + // The width of the views for each input text field. + const CGFloat kInputWidth = 50; + input.frame = + CGRectMake(CGRectGetMaxX([label frame]) + kPadding, + [label frame].origin.y, kInputWidth, [_font lineHeight]); + input.font = _font; + input.backgroundColor = [UIColor whiteColor]; + input.delegate = self; + input.keyboardType = UIKeyboardTypeNumbersAndPunctuation; + input.adjustsFontSizeToFitWidth = YES; + input.textAlignment = NSTextAlignmentRight; + [input addTarget:inputTarget + action:inputAction + forControlEvents:UIControlEventEditingDidEnd]; + + [self addSubview:input]; + } + + if (buttonTitle) { + const CGFloat kButtonXOffset = + input ? CGRectGetMaxX(input.frame) : CGRectGetMaxX([label frame]); + CGPoint origin = + CGPointMake(kButtonXOffset + kPadding, [label frame].origin.y); + [self addButtonWithTitle:buttonTitle + target:buttonTarget + action:buttonAction + withOrigin:origin]; + } +} + +// Returns the CGPoint of the origin of the subview at |index|. +- (CGPoint)originForSubviewAtIndex:(NSUInteger)index { + return CGPointMake(kPadding, + (index + 1) * kPadding + index * [_font lineHeight]); +} + +#pragma mark Refresh callback + +// Updates content and ensures the view is visible. +- (void)refresh:(NSTimer*)timer { + [self.superview bringSubviewToFront:self]; + [self updateMemoryInfo]; +} + +#pragma mark Memory inspection + +// Updates the memory metrics shown. +- (void)updateMemoryInfo { + CGFloat value = memory_util::GetFreePhysicalBytes() / kNumBytesInMB; + [_physicalFreeMemoryLabel + setText:[NSString stringWithFormat:@"%.2f MB", value]]; + value = memory_util::GetRealMemoryUsedInBytes() / kNumBytesInMB; + [_realMemoryUsedLabel setText:[NSString stringWithFormat:@"%.2f MB", value]]; + value = memory_util::GetInternalVMBytes() / kNumBytesInMB; + [_xcodeGaugeLabel setText:[NSString stringWithFormat:@"%.2f MB", value]]; + value = memory_util::GetDirtyVMBytes() / kNumBytesInMB; + [_dirtyVirtualMemoryLabel + setText:[NSString stringWithFormat:@"%.2f MB", value]]; +} + +#pragma mark Memory Warning notification callback + +// Flashes the debugger to indicate memory warning. +- (void)lowMemoryWarningReceived:(NSNotification*)notification { + UIColor* originalColor = self.backgroundColor; + self.backgroundColor = + [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.9]; + [UIView animateWithDuration:1.0 + delay:0.0 + options:UIViewAnimationOptionAllowUserInteraction + animations:^{ + self.backgroundColor = originalColor; + } + completion:nil]; +} + +#pragma mark Rotation notification callback + +- (void)didMoveToSuperview { + UIView* superview = [self superview]; + if (superview) + [self setCenter:[superview center]]; +} + +- (void)adjustForOrientation:(NSNotification*)notification { + if (base::ios::IsRunningOnIOS8OrLater()) { + return; + } + UIInterfaceOrientation orientation = + [[UIApplication sharedApplication] statusBarOrientation]; + if (orientation == _currentOrientation) { + return; + } + _currentOrientation = orientation; + CGFloat angle; + switch (orientation) { + case UIInterfaceOrientationPortrait: + angle = 0; + break; + case UIInterfaceOrientationPortraitUpsideDown: + angle = M_PI; + break; + case UIInterfaceOrientationLandscapeLeft: + angle = -M_PI_2; + break; + case UIInterfaceOrientationLandscapeRight: + angle = M_PI_2; + break; + case UIInterfaceOrientationUnknown: + default: + angle = 0; + } + + // Since the debugger view is in screen coordinates and handles its own + // rotation via the |transform| property, the view's position after rotation + // can be unexpected and partially off-screen. Centering the view before + // rotating it ensures that the view remains within the bounds of the screen. + if (self.superview) { + self.center = self.superview.center; + } + self.transform = CGAffineTransformMakeRotation(angle); +} + +#pragma mark Keyboard notification callbacks + +// Ensures the debugger is visible by shifting it up as the keyboard animates +// in. +- (void)keyboardWillShow:(NSNotification*)notification { + NSDictionary* userInfo = [notification userInfo]; + NSValue* keyboardFrameValue = + [userInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; + CGFloat keyboardHeight = CurrentKeyboardHeight(keyboardFrameValue); + + // Get the coord of the bottom of the debugger's frame. This is orientation + // dependent on iOS 7 because the debugger is in screen coords. + CGFloat bottomOfFrame = CGRectGetMaxY(self.frame); + if (!base::ios::IsRunningOnIOS8OrLater() && IsLandscape()) + bottomOfFrame = CGRectGetMaxX(self.frame); + + // Shift the debugger up by the "height" of the keyboard, but since the + // keyboard rect is in screen coords, use the orientation to find the height. + CGFloat distanceFromBottom = CurrentScreenHeight() - bottomOfFrame; + _keyboardOffset = -1 * fmax(0.0f, keyboardHeight - distanceFromBottom); + [self animateForKeyboardNotification:notification + withOffset:CGPointMake(0, _keyboardOffset)]; +} + +// Shifts the debugger back down when the keyboard is hidden. +- (void)keyboardWillHide:(NSNotification*)notification { + [self animateForKeyboardNotification:notification + withOffset:CGPointMake(0, -_keyboardOffset)]; +} + +- (void)animateForKeyboardNotification:(NSNotification*)notification + withOffset:(CGPoint)offset { + // Account for orientation. + offset = CGPointApplyAffineTransform(offset, self.transform); + // Normally this would use an animation block, but there is no API to + // convert the UIKeyboardAnimationCurveUserInfoKey's value from a + // UIViewAnimationCurve to a UIViewAnimationOption. Awesome! + NSDictionary* userInfo = [notification userInfo]; + [UIView beginAnimations:nil context:nullptr]; + [UIView setAnimationDuration: + [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; + NSInteger animationCurveKeyValue = + [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + UIViewAnimationCurve animationCurve = + (UIViewAnimationCurve)animationCurveKeyValue; + [UIView setAnimationCurve:animationCurve]; + [UIView setAnimationBeginsFromCurrentState:YES]; + self.frame = CGRectOffset(self.frame, offset.x, offset.y); + [UIView commitAnimations]; +} + +#pragma mark Artificial memory bloat methods + +- (void)updateBloat { + double bloatSizeMB; + NSScanner* scanner = [NSScanner scannerWithString:[_bloatField text]]; + if (![scanner scanDouble:&bloatSizeMB] || bloatSizeMB < 0.0) { + bloatSizeMB = 0; + NSString* errorMessage = + [NSString stringWithFormat:@"Invalid value \"%@\" for bloat size.\n" + @"Must be a positive number.\n" + @"Resetting to %.1f MB", + [_bloatField text], bloatSizeMB]; + [self alert:errorMessage]; + [_bloatField setText:[NSString stringWithFormat:@"%.1f", bloatSizeMB]]; + } + const CGFloat kBloatSizeBytes = ceil(bloatSizeMB * kNumBytesInMB); + const uint64 kNumberOfBytes = static_cast<uint64>(kBloatSizeBytes); + _bloat.reset(kNumberOfBytes ? new uint8[kNumberOfBytes] : nullptr); + if (_bloat) { + memset(_bloat.get(), -1, kNumberOfBytes); // Occupy memory. + } else { + if (kNumberOfBytes) { + [self alert:@"Could not allocate memory."]; + } + } +} + +- (void)clearBloat { + [_bloatField setText:@"0"]; + [_bloatField resignFirstResponder]; + [self updateBloat]; +} + +#pragma mark Refresh interval methods + +- (void)updateRefreshInterval { + double refreshTimerValue; + NSScanner* scanner = [NSScanner scannerWithString:[_refreshField text]]; + if (![scanner scanDouble:&refreshTimerValue] || refreshTimerValue < 0.0) { + refreshTimerValue = 0.5; + NSString* errorMessage = [NSString + stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n" + @"Must be a positive number.\n" @"Resetting to %.1f", + [_refreshField text], refreshTimerValue]; + [self alert:errorMessage]; + [_refreshField + setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]]; + return; + } + [_refreshTimer invalidate]; + _refreshTimer.reset( + [[NSTimer scheduledTimerWithTimeInterval:refreshTimerValue + target:self + selector:@selector(refresh:) + userInfo:nil + repeats:YES] retain]); +} + +#pragma mark Memory warning interval methods + +// 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 +- (void)updateMemoryWarningInterval { + [_memoryWarningTimer invalidate]; + double timerValue; + NSString* text = [_continuousMemoryWarningField text]; + NSScanner* scanner = [NSScanner scannerWithString:text]; + BOOL valueFound = [scanner scanDouble:&timerValue]; + // If the text field is empty or contains 0, return early to turn off + // continuous memory warnings. + if (![text length] || timerValue == 0.0) { + return; + } + // If no value could be parsed or a non-positive value was found, throw up an + // error message and return early to turn off continuous memory warnings. + if (!valueFound || timerValue <= 0.0) { + NSString* errorMessage = [NSString + stringWithFormat:@"Invalid value \"%@\" for memory warning interval.\n" + @"Must be a positive number.\n" + @"Turning off continuous memory warnings", + text]; + [self alert:errorMessage]; + [_continuousMemoryWarningField setText:@""]; + return; + } + // If a valid value was found have the timer start triggering continuous + // memory warnings. + _memoryWarningTimer.reset( + [[NSTimer scheduledTimerWithTimeInterval:timerValue + target:[UIApplication sharedApplication] + selector:@selector(_performMemoryWarning) + userInfo:nil + repeats:YES] retain]); +} +#endif // CHROMIUM_BUILD + +#pragma mark UITextViewDelegate methods + +// Dismisses the keyboard if the user hits return. +- (BOOL)textFieldShouldReturn:(UITextField*)textField { + [textField resignFirstResponder]; + return YES; +} + +#pragma mark UIResponder methods + +// Allows the debugger to be dragged around the screen. +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { + UITouch* touch = [touches anyObject]; + CGPoint start = [touch previousLocationInView:self]; + CGPoint end = [touch locationInView:self]; + CGPoint offset = CGPointMake(end.x - start.x, end.y - start.y); + offset = CGPointApplyAffineTransform(offset, self.transform); + self.frame = CGRectOffset(self.frame, offset.x, offset.y); +} + +#pragma mark Error handling + +// Shows an alert with the given |errorMessage|. +- (void)alert:(NSString*)errorMessage { + UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Error" + message:errorMessage + delegate:self + cancelButtonTitle:@"OK" + otherButtonTitles:nil, nil]; + [alert show]; +} + +@end diff --git a/ios/chrome/browser/memory/memory_debugger_manager.h b/ios/chrome/browser/memory/memory_debugger_manager.h new file mode 100644 index 0000000..4cd7941 --- /dev/null +++ b/ios/chrome/browser/memory/memory_debugger_manager.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef IOS_CHROME_BROWSER_MEMORY_MEMORY_DEBUGGER_MANAGER_H_ +#define IOS_CHROME_BROWSER_MEMORY_MEMORY_DEBUGGER_MANAGER_H_ + +#import <Foundation/Foundation.h> + +class PrefRegistrySimple; +class PrefService; +@class UIView; + +// A class to manage the life cycle of a MemoryDebugger instance. +// +// A MemoryDebugger's existence is controlled by a pref in local state, so the +// MemoryDebuggerManager listens for changes to that pref and instantiates or +// frees the debugger as appropriate. +@interface MemoryDebuggerManager : NSObject +// Designated initializer. +- (instancetype)initWithView:(UIView*)view prefs:(PrefService*)prefs; +// Registers local state preferences. ++ (void)registerLocalState:(PrefRegistrySimple*)registry; +@end + +#endif // IOS_CHROME_BROWSER_MEMORY_MEMORY_DEBUGGER_MANAGER_H_ diff --git a/ios/chrome/browser/memory/memory_debugger_manager.mm b/ios/chrome/browser/memory/memory_debugger_manager.mm new file mode 100644 index 0000000..e9e9acb --- /dev/null +++ b/ios/chrome/browser/memory/memory_debugger_manager.mm @@ -0,0 +1,72 @@ +// 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_manager.h" + +#include "base/ios/weak_nsobject.h" +#import "base/mac/bind_objc_block.h" +#include "base/mac/scoped_nsobject.h" +#include "base/prefs/pref_member.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/pref_service.h" +#import "ios/chrome/browser/memory/memory_debugger.h" +#import "ios/chrome/browser/pref_names.h" + +@implementation MemoryDebuggerManager { + UIView* debuggerParentView_; // weak + base::scoped_nsobject<MemoryDebugger> memoryDebugger_; + BooleanPrefMember showMemoryDebugger_; +} + +- (instancetype)initWithView:(UIView*)debuggerParentView + prefs:(PrefService*)prefs { + if (self = [super init]) { + debuggerParentView_ = debuggerParentView; + + // Set up the callback for when the pref to show/hide the debugger changes. + base::WeakNSObject<MemoryDebuggerManager> weakSelf(self); + base::Closure callback = base::BindBlock(^{ + base::scoped_nsobject<MemoryDebuggerManager> strongSelf( + [weakSelf retain]); + if (strongSelf) { + [self onShowMemoryDebuggingToolsChange]; + } + }); + showMemoryDebugger_.Init(prefs::kShowMemoryDebuggingTools, prefs, callback); + // Invoke the pref change callback once to show the debugger on start up, + // if necessary. + [self onShowMemoryDebuggingToolsChange]; + } + return self; +} + +- (void)dealloc { + [self tearDownDebugger]; + [super dealloc]; +} + +#pragma mark - Pref-handling methods + +// Registers local state prefs. ++ (void)registerLocalState:(PrefRegistrySimple*)registry { + registry->RegisterBooleanPref(prefs::kShowMemoryDebuggingTools, false); +} + +// Shows or hides the debugger when the pref changes. +- (void)onShowMemoryDebuggingToolsChange { + if (showMemoryDebugger_.GetValue()) { + memoryDebugger_.reset([[MemoryDebugger alloc] init]); + [debuggerParentView_ addSubview:memoryDebugger_]; + } else { + [self tearDownDebugger]; + } +} + +// Tears down the debugger so it can be deallocated. +- (void)tearDownDebugger { + [memoryDebugger_ invalidateTimers]; + [memoryDebugger_ removeFromSuperview]; + memoryDebugger_.reset(); +} +@end diff --git a/ios/chrome/browser/memory/memory_metrics.cc b/ios/chrome/browser/memory/memory_metrics.cc new file mode 100644 index 0000000..360c533 --- /dev/null +++ b/ios/chrome/browser/memory/memory_metrics.cc @@ -0,0 +1,89 @@ +// 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. + +#include "ios/chrome/browser/memory/memory_metrics.h" + +#include <mach/mach.h> + +#include "base/logging.h" +#include "base/process/process_handle.h" +#include "base/process/process_metrics.h" + +#ifdef ARCH_CPU_64_BITS +#define cr_vm_region vm_region_64 +#else +#define cr_vm_region vm_region +#endif + +namespace { +// The number of pages returned by host_statistics and vm_region are a count +// of pages of 4096 bytes even when running on arm64 but the constants that +// are exposed (vm_page_size, VM_PAGE_SIZE, host_page_size) are all equals to +// 16384 bytes. So we define our own constant here to convert from page count +// to bytes. +const uint64_t kVMPageSize = 4096; +} + +namespace memory_util { + +uint64 GetFreePhysicalBytes() { + vm_statistics_data_t vmstat; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + kern_return_t result = + host_statistics(mach_host_self(), HOST_VM_INFO, + reinterpret_cast<host_info_t>(&vmstat), &count); + if (result != KERN_SUCCESS) { + LOG(ERROR) << "Calling host_statistics failed."; + return 0; + } + return vmstat.free_count * kVMPageSize; +} + +uint64 GetRealMemoryUsedInBytes() { + base::ProcessHandle process_handle = base::GetCurrentProcessHandle(); + scoped_ptr<base::ProcessMetrics> process_metrics( + base::ProcessMetrics::CreateProcessMetrics(process_handle)); + return static_cast<uint64>(process_metrics->GetWorkingSetSize()); +} + +uint64 GetDirtyVMBytes() { + // Iterate over all VM regions and sum their dirty pages. + unsigned int total_dirty_pages = 0; + vm_size_t vm_size = 0; + kern_return_t result; + for (vm_address_t address = MACH_VM_MIN_ADDRESS;; address += vm_size) { + vm_region_extended_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_EXTENDED_INFO_COUNT; + mach_port_t object_name; + result = cr_vm_region( + mach_task_self(), &address, &vm_size, VM_REGION_EXTENDED_INFO, + reinterpret_cast<vm_region_info_t>(&info), &info_count, &object_name); + if (result == KERN_INVALID_ADDRESS) { + // The end of the address space has been reached. + break; + } else if (result != KERN_SUCCESS) { + LOG(ERROR) << "Calling vm_region failed with code: " << result; + break; + } else { + total_dirty_pages += info.pages_dirtied; + } + } + return total_dirty_pages * kVMPageSize; +} + +uint64 GetInternalVMBytes() { + task_vm_info_data_t task_vm_info; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + kern_return_t result = + task_info(mach_task_self(), TASK_VM_INFO, + reinterpret_cast<task_info_t>(&task_vm_info), &count); + if (result != KERN_SUCCESS) { + LOG(ERROR) << "Calling task_info failed."; + return 0; + } + + return static_cast<uint64>(task_vm_info.internal); +} + +} // namespace memory_util diff --git a/ios/chrome/browser/memory/memory_metrics.h b/ios/chrome/browser/memory/memory_metrics.h new file mode 100644 index 0000000..7903848 --- /dev/null +++ b/ios/chrome/browser/memory/memory_metrics.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef IOS_CHROME_BROWSER_MEMORY_MEMORY_METRICS_H_ +#define IOS_CHROME_BROWSER_MEMORY_MEMORY_METRICS_H_ + +#include "base/basictypes.h" + +namespace memory_util { +// "Physical Free" memory metric. This corresponds to the "Physical Memory Free" +// value reported by the Memory Monitor in Instruments. +uint64 GetFreePhysicalBytes(); + +// "Real Memory Used" memory metric. This corresponds to the "Real Memory" value +// reported for the app by the Memory Monitor in Instruments. +uint64 GetRealMemoryUsedInBytes(); + +// "Xcode Gauge" memory metric. This corresponds to the "Memory" value reported +// for the app by the Debug Navigator in Xcode. Only supported in iOS 7 and +// later. +uint64 GetInternalVMBytes(); + +// "Dirty VM" memory metric. This corresponds to the "Dirty Size" value reported +// for the app by the VM Tracker in Instruments. +uint64 GetDirtyVMBytes(); +} // namespace memory_util + +#endif // IOS_CHROME_BROWSER_MEMORY_MEMORY_METRICS_H_ diff --git a/ios/chrome/browser/pref_names.cc b/ios/chrome/browser/pref_names.cc index 529a093..4ef5ece 100644 --- a/ios/chrome/browser/pref_names.cc +++ b/ios/chrome/browser/pref_names.cc @@ -33,4 +33,8 @@ const char kIosBookmarkPromoAlreadySeen[] = "ios.bookmark.promo_already_seen"; // The preferred SSO user for wallet payments. const char kPaymentsPreferredUserId[] = "ios.payments.preferred_user_id"; +// True if the memory debugging tools should be visible. +extern const char kShowMemoryDebuggingTools[] = + "ios.memory.show_debugging_tools"; + } // namespace prefs diff --git a/ios/chrome/browser/pref_names.h b/ios/chrome/browser/pref_names.h index cda81cd..95ede81 100644 --- a/ios/chrome/browser/pref_names.h +++ b/ios/chrome/browser/pref_names.h @@ -20,6 +20,7 @@ namespace prefs { extern const char kIosBookmarkFolderDefault[]; extern const char kIosBookmarkPromoAlreadySeen[]; extern const char kPaymentsPreferredUserId[]; +extern const char kShowMemoryDebuggingTools[]; } // namespace prefs diff --git a/ios/chrome/ios_chrome.gyp b/ios/chrome/ios_chrome.gyp index 6a221ec..0c376a4 100644 --- a/ios/chrome/ios_chrome.gyp +++ b/ios/chrome/ios_chrome.gyp @@ -105,6 +105,12 @@ 'browser/infobars/infobar_manager_impl.h', 'browser/infobars/infobar_utils.h', 'browser/infobars/infobar_utils.mm', + 'browser/memory/memory_debugger.h', + 'browser/memory/memory_debugger.mm', + 'browser/memory/memory_debugger_manager.h', + 'browser/memory/memory_debugger_manager.mm', + 'browser/memory/memory_metrics.cc', + 'browser/memory/memory_metrics.h', 'browser/net/chrome_cookie_store_ios_client.h', 'browser/net/chrome_cookie_store_ios_client.mm', 'browser/net/image_fetcher.h', |