cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / ios / chrome / browser / memory / memory_debugger.mm
blob22bf88181d6da8b8b9fe6f3cfa2475bb19adcb51
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/chrome/browser/memory/memory_debugger.h"
7 #include "base/ios/ios_util.h"
8 #import "base/mac/scoped_nsobject.h"
9 #import "base/memory/scoped_ptr.h"
10 #import "ios/chrome/browser/memory/memory_metrics.h"
11 #include "ios/chrome/browser/ui/ui_util.h"
12 #import "ios/chrome/browser/ui/uikit_ui_util.h"
14 namespace {
15 // The number of bytes in a megabyte.
16 const CGFloat kNumBytesInMB = 1024 * 1024;
17 // The horizontal and vertical padding between subviews.
18 const CGFloat kPadding = 10;
19 }  // namespace
21 @implementation MemoryDebugger {
22   // A timer to trigger refreshes.
23   base::scoped_nsobject<NSTimer> _refreshTimer;
25   // A timer to trigger continuous memory warnings.
26   base::scoped_nsobject<NSTimer> _memoryWarningTimer;
28   // The font to use.
29   base::scoped_nsobject<UIFont> _font;
31   // Labels for memory metrics.
32   base::scoped_nsobject<UILabel> _physicalFreeMemoryLabel;
33   base::scoped_nsobject<UILabel> _realMemoryUsedLabel;
34   base::scoped_nsobject<UILabel> _xcodeGaugeLabel;
35   base::scoped_nsobject<UILabel> _dirtyVirtualMemoryLabel;
37   // Inputs for memory commands.
38   base::scoped_nsobject<UITextField> _bloatField;
39   base::scoped_nsobject<UITextField> _refreshField;
40   base::scoped_nsobject<UITextField> _continuousMemoryWarningField;
42   // A place to store the artifical memory bloat.
43   scoped_ptr<uint8> _bloat;
45   // Distance the view was pushed up to accomodate the keyboard.
46   CGFloat _keyboardOffset;
48   // The current orientation of the device.
49   BOOL _currentOrientation;
52 - (instancetype)init {
53   self = [super initWithFrame:CGRectZero];
54   if (self) {
55     _font.reset([[UIFont systemFontOfSize:14] retain]);
56     self.backgroundColor = [UIColor colorWithWhite:0.8f alpha:0.9f];
57     self.opaque = NO;
59     [self addSubviews];
60     [self adjustForOrientation:nil];
61     [self sizeToFit];
62     [self registerForNotifications];
63   }
64   return self;
67 // NSTimers create a retain cycle so they must be invalidated before this
68 // instance can be deallocated.
69 - (void)invalidateTimers {
70   [_refreshTimer invalidate];
71   [_memoryWarningTimer invalidate];
74 - (void)dealloc {
75   [[NSNotificationCenter defaultCenter] removeObserver:self];
76   [super dealloc];
79 #pragma mark UIView methods
81 - (CGSize)sizeThatFits:(CGSize)size {
82   CGFloat width = 0;
83   CGFloat height = 0;
84   for (UIView* subview in self.subviews) {
85     width = MAX(width, CGRectGetMaxX(subview.frame));
86     height = MAX(height, CGRectGetMaxY(subview.frame));
87   }
88   return CGSizeMake(width + kPadding, height + kPadding);
91 #pragma mark initialization helpers
93 - (void)addSubviews {
94   // |index| is used to calculate the vertical position of each element in
95   // the debugger view.
96   NSUInteger index = 0;
98   // Display some metrics.
99   _physicalFreeMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]);
100   [self addMetricWithName:@"Physical Free"
101                   atIndex:index++
102                usingLabel:_physicalFreeMemoryLabel];
103   _realMemoryUsedLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]);
104   [self addMetricWithName:@"Real Memory Used"
105                   atIndex:index++
106                usingLabel:_realMemoryUsedLabel];
107   _xcodeGaugeLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]);
108   [self addMetricWithName:@"Xcode Gauge"
109                   atIndex:index++
110                usingLabel:_xcodeGaugeLabel];
111   _dirtyVirtualMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]);
112   [self addMetricWithName:@"Dirty VM"
113                   atIndex:index++
114                usingLabel:_dirtyVirtualMemoryLabel];
116 // Since _performMemoryWarning is a private API it can't be compiled into
117 // official builds.
118 // TODO(lliabraa): Figure out how to support memory warnings (or something
119 // like them) in official builds.
120 #if CHROMIUM_BUILD
121   [self addButtonWithTitle:@"Trigger Memory Warning"
122                     target:[UIApplication sharedApplication]
123                     action:@selector(_performMemoryWarning)
124                 withOrigin:[self originForSubviewAtIndex:index++]];
125 #endif  // CHROMIUM_BUILD
127   // Display a text input to set the amount of artificial memory bloat and a
128   // button to reset the bloat to zero.
129   _bloatField.reset([[UITextField alloc] initWithFrame:CGRectZero]);
130   [self addLabelWithText:@"Set bloat (MB)"
131                    input:_bloatField
132              inputTarget:self
133              inputAction:@selector(updateBloat)
134          buttonWithTitle:@"Clear"
135             buttonTarget:self
136             buttonAction:@selector(clearBloat)
137                  atIndex:index++];
138   [_bloatField setText:@"0"];
139   [self updateBloat];
141 // Since _performMemoryWarning is a private API it can't be compiled into
142 // official builds.
143 // TODO(lliabraa): Figure out how to support memory warnings (or something
144 // like them) in official builds.
145 #if CHROMIUM_BUILD
146   // Display a text input to control the rate of continuous memory warnings.
147   _continuousMemoryWarningField.reset(
148       [[UITextField alloc] initWithFrame:CGRectZero]);
149   [self addLabelWithText:@"Set memory warning interval (secs)"
150                    input:_continuousMemoryWarningField
151              inputTarget:self
152              inputAction:@selector(updateMemoryWarningInterval)
153                  atIndex:index++];
154   [_continuousMemoryWarningField setText:@"0.0"];
155 #endif  // CHROMIUM_BUILD
157   // Display a text input to control the refresh rate of the memory debugger.
158   _refreshField.reset([[UITextField alloc] initWithFrame:CGRectZero]);
159   [self addLabelWithText:@"Set refresh interval (secs)"
160                    input:_refreshField
161              inputTarget:self
162              inputAction:@selector(updateRefreshInterval)
163                  atIndex:index++];
164   [_refreshField setText:@"0.5"];
165   [self updateRefreshInterval];
168 - (void)registerForNotifications {
169   // On iOS 7, the screen coordinate system is not dependent on orientation so
170   // the debugger has to handle its own rotation.
171   if (!base::ios::IsRunningOnIOS8OrLater()) {
172     // Register to receive orientation notifications.
173     [[NSNotificationCenter defaultCenter]
174         addObserver:self
175            selector:@selector(adjustForOrientation:)
176                name:UIDeviceOrientationDidChangeNotification
177              object:nil];
178   }
180   // Register to receive memory warning.
181   [[NSNotificationCenter defaultCenter]
182       addObserver:self
183          selector:@selector(lowMemoryWarningReceived:)
184              name:UIApplicationDidReceiveMemoryWarningNotification
185            object:nil];
187   // Register to receive keyboard will show notification.
188   [[NSNotificationCenter defaultCenter]
189       addObserver:self
190          selector:@selector(keyboardWillShow:)
191              name:UIKeyboardWillShowNotification
192            object:nil];
194   // Register to receive keyboard will hide notification.
195   [[NSNotificationCenter defaultCenter]
196       addObserver:self
197          selector:@selector(keyboardWillHide:)
198              name:UIKeyboardWillHideNotification
199            object:nil];
202 // Adds subviews for the specified metric, the value of which will be displayed
203 // in |label|.
204 - (void)addMetricWithName:(NSString*)name
205                   atIndex:(NSUInteger)index
206                usingLabel:(UILabel*)label {
207   // The width of the view for the metric's name.
208   const CGFloat kNameWidth = 150;
209   // The width of the view for each metric.
210   const CGFloat kMetricWidth = 100;
211   CGPoint nameOrigin = [self originForSubviewAtIndex:index];
212   CGRect nameFrame =
213       CGRectMake(nameOrigin.x, nameOrigin.y, kNameWidth, [_font lineHeight]);
214   base::scoped_nsobject<UILabel> nameLabel(
215       [[UILabel alloc] initWithFrame:nameFrame]);
216   [nameLabel setText:[NSString stringWithFormat:@"%@: ", name]];
217   [nameLabel setFont:_font];
218   [self addSubview:nameLabel];
219   label.frame = CGRectMake(CGRectGetMaxX(nameFrame), nameFrame.origin.y,
220                            kMetricWidth, [_font lineHeight]);
221   [label setFont:_font];
222   [label setTextAlignment:NSTextAlignmentRight];
223   [self addSubview:label];
226 // Adds a subview for a button with the given title and target/action.
227 - (void)addButtonWithTitle:(NSString*)title
228                     target:(id)target
229                     action:(SEL)action
230                 withOrigin:(CGPoint)origin {
231   base::scoped_nsobject<UIButton> button(
232       [[UIButton buttonWithType:UIButtonTypeSystem] retain]);
233   [button setTitle:title forState:UIControlStateNormal];
234   [button titleLabel].font = _font;
235   [[button titleLabel] setTextAlignment:NSTextAlignmentCenter];
236   [button sizeToFit];
237   [button setFrame:CGRectMake(origin.x, origin.y, [button frame].size.width,
238                               [_font lineHeight])];
239   [button addTarget:target
240                 action:action
241       forControlEvents:UIControlEventTouchUpInside];
242   [self addSubview:button];
245 // Adds subviews for a UI component with label and input text field.
247 // -------------------------
248 // | labelText  | <input>  |
249 // -------------------------
251 // The inputTarget/inputAction will be invoked when the user finishes editing
252 // in |input|.
253 - (void)addLabelWithText:(NSString*)labelText
254                    input:(UITextField*)input
255              inputTarget:(id)inputTarget
256              inputAction:(SEL)inputAction
257                  atIndex:(NSUInteger)index {
258   [self addLabelWithText:labelText
259                    input:input
260              inputTarget:inputTarget
261              inputAction:inputAction
262          buttonWithTitle:nil
263             buttonTarget:nil
264             buttonAction:nil
265                  atIndex:index];
268 // Adds subviews for a UI component with label, input text field and button.
270 // -------------------------------------
271 // | labelText  | <input>  |  <button> |
272 // -------------------------------------
274 // The inputTarget/inputAction will be invoked when the user finishes editing
275 // in |input|.
276 - (void)addLabelWithText:(NSString*)labelText
277                    input:(UITextField*)input
278              inputTarget:(id)inputTarget
279              inputAction:(SEL)inputAction
280          buttonWithTitle:(NSString*)buttonTitle
281             buttonTarget:(id)buttonTarget
282             buttonAction:(SEL)buttonAction
283                  atIndex:(NSUInteger)index {
284   base::scoped_nsobject<UILabel> label(
285       [[UILabel alloc] initWithFrame:CGRectZero]);
286   if (labelText) {
287     [label setText:[NSString stringWithFormat:@"%@: ", labelText]];
288   }
289   [label setFont:_font];
290   [label sizeToFit];
291   CGPoint labelOrigin = [self originForSubviewAtIndex:index];
292   [label setFrame:CGRectOffset([label frame], labelOrigin.x, labelOrigin.y)];
293   [self addSubview:label];
294   if (input) {
295     // The width of the views for each input text field.
296     const CGFloat kInputWidth = 50;
297     input.frame =
298         CGRectMake(CGRectGetMaxX([label frame]) + kPadding,
299                    [label frame].origin.y, kInputWidth, [_font lineHeight]);
300     input.font = _font;
301     input.backgroundColor = [UIColor whiteColor];
302     input.delegate = self;
303     input.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
304     input.adjustsFontSizeToFitWidth = YES;
305     input.textAlignment = NSTextAlignmentRight;
306     [input addTarget:inputTarget
307                   action:inputAction
308         forControlEvents:UIControlEventEditingDidEnd];
310     [self addSubview:input];
311   }
313   if (buttonTitle) {
314     const CGFloat kButtonXOffset =
315         input ? CGRectGetMaxX(input.frame) : CGRectGetMaxX([label frame]);
316     CGPoint origin =
317         CGPointMake(kButtonXOffset + kPadding, [label frame].origin.y);
318     [self addButtonWithTitle:buttonTitle
319                       target:buttonTarget
320                       action:buttonAction
321                   withOrigin:origin];
322   }
325 // Returns the CGPoint of the origin of the subview at |index|.
326 - (CGPoint)originForSubviewAtIndex:(NSUInteger)index {
327   return CGPointMake(kPadding,
328                      (index + 1) * kPadding + index * [_font lineHeight]);
331 #pragma mark Refresh callback
333 // Updates content and ensures the view is visible.
334 - (void)refresh:(NSTimer*)timer {
335   [self.superview bringSubviewToFront:self];
336   [self updateMemoryInfo];
339 #pragma mark Memory inspection
341 // Updates the memory metrics shown.
342 - (void)updateMemoryInfo {
343   CGFloat value = memory_util::GetFreePhysicalBytes() / kNumBytesInMB;
344   [_physicalFreeMemoryLabel
345       setText:[NSString stringWithFormat:@"%.2f MB", value]];
346   value = memory_util::GetRealMemoryUsedInBytes() / kNumBytesInMB;
347   [_realMemoryUsedLabel setText:[NSString stringWithFormat:@"%.2f MB", value]];
348   value = memory_util::GetInternalVMBytes() / kNumBytesInMB;
349   [_xcodeGaugeLabel setText:[NSString stringWithFormat:@"%.2f MB", value]];
350   value = memory_util::GetDirtyVMBytes() / kNumBytesInMB;
351   [_dirtyVirtualMemoryLabel
352       setText:[NSString stringWithFormat:@"%.2f MB", value]];
355 #pragma mark Memory Warning notification callback
357 // Flashes the debugger to indicate memory warning.
358 - (void)lowMemoryWarningReceived:(NSNotification*)notification {
359   UIColor* originalColor = self.backgroundColor;
360   self.backgroundColor =
361       [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.9];
362   [UIView animateWithDuration:1.0
363                         delay:0.0
364                       options:UIViewAnimationOptionAllowUserInteraction
365                    animations:^{
366                      self.backgroundColor = originalColor;
367                    }
368                    completion:nil];
371 #pragma mark Rotation notification callback
373 - (void)didMoveToSuperview {
374   UIView* superview = [self superview];
375   if (superview)
376     [self setCenter:[superview center]];
379 - (void)adjustForOrientation:(NSNotification*)notification {
380   if (base::ios::IsRunningOnIOS8OrLater()) {
381     return;
382   }
383   UIInterfaceOrientation orientation =
384       [[UIApplication sharedApplication] statusBarOrientation];
385   if (orientation == _currentOrientation) {
386     return;
387   }
388   _currentOrientation = orientation;
389   CGFloat angle;
390   switch (orientation) {
391     case UIInterfaceOrientationPortrait:
392       angle = 0;
393       break;
394     case UIInterfaceOrientationPortraitUpsideDown:
395       angle = M_PI;
396       break;
397     case UIInterfaceOrientationLandscapeLeft:
398       angle = -M_PI_2;
399       break;
400     case UIInterfaceOrientationLandscapeRight:
401       angle = M_PI_2;
402       break;
403     case UIInterfaceOrientationUnknown:
404     default:
405       angle = 0;
406   }
408   // Since the debugger view is in screen coordinates and handles its own
409   // rotation via the |transform| property, the view's position after rotation
410   // can be unexpected and partially off-screen. Centering the view before
411   // rotating it ensures that the view remains within the bounds of the screen.
412   if (self.superview) {
413     self.center = self.superview.center;
414   }
415   self.transform = CGAffineTransformMakeRotation(angle);
418 #pragma mark Keyboard notification callbacks
420 // Ensures the debugger is visible by shifting it up as the keyboard animates
421 // in.
422 - (void)keyboardWillShow:(NSNotification*)notification {
423   NSDictionary* userInfo = [notification userInfo];
424   NSValue* keyboardFrameValue =
425       [userInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
426   CGFloat keyboardHeight = CurrentKeyboardHeight(keyboardFrameValue);
428   // Get the coord of the bottom of the debugger's frame. This is orientation
429   // dependent on iOS 7 because the debugger is in screen coords.
430   CGFloat bottomOfFrame = CGRectGetMaxY(self.frame);
431   if (!base::ios::IsRunningOnIOS8OrLater() && IsLandscape())
432     bottomOfFrame = CGRectGetMaxX(self.frame);
434   // Shift the debugger up by the "height" of the keyboard, but since the
435   // keyboard rect is in screen coords, use the orientation to find the height.
436   CGFloat distanceFromBottom = CurrentScreenHeight() - bottomOfFrame;
437   _keyboardOffset = -1 * fmax(0.0f, keyboardHeight - distanceFromBottom);
438   [self animateForKeyboardNotification:notification
439                             withOffset:CGPointMake(0, _keyboardOffset)];
442 // Shifts the debugger back down when the keyboard is hidden.
443 - (void)keyboardWillHide:(NSNotification*)notification {
444   [self animateForKeyboardNotification:notification
445                             withOffset:CGPointMake(0, -_keyboardOffset)];
448 - (void)animateForKeyboardNotification:(NSNotification*)notification
449                             withOffset:(CGPoint)offset {
450   // Account for orientation.
451   offset = CGPointApplyAffineTransform(offset, self.transform);
452   // Normally this would use an animation block, but there is no API to
453   // convert the UIKeyboardAnimationCurveUserInfoKey's value from a
454   // UIViewAnimationCurve to a UIViewAnimationOption. Awesome!
455   NSDictionary* userInfo = [notification userInfo];
456   [UIView beginAnimations:nil context:nullptr];
457   [UIView setAnimationDuration:
458               [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
459   NSInteger animationCurveKeyValue =
460       [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
461   UIViewAnimationCurve animationCurve =
462       (UIViewAnimationCurve)animationCurveKeyValue;
463   [UIView setAnimationCurve:animationCurve];
464   [UIView setAnimationBeginsFromCurrentState:YES];
465   self.frame = CGRectOffset(self.frame, offset.x, offset.y);
466   [UIView commitAnimations];
469 #pragma mark Artificial memory bloat methods
471 - (void)updateBloat {
472   double bloatSizeMB;
473   NSScanner* scanner = [NSScanner scannerWithString:[_bloatField text]];
474   if (![scanner scanDouble:&bloatSizeMB] || bloatSizeMB < 0.0) {
475     bloatSizeMB = 0;
476     NSString* errorMessage =
477         [NSString stringWithFormat:@"Invalid value \"%@\" for bloat size.\n"
478                                    @"Must be a positive number.\n"
479                                    @"Resetting to %.1f MB",
480                                    [_bloatField text], bloatSizeMB];
481     [self alert:errorMessage];
482     [_bloatField setText:[NSString stringWithFormat:@"%.1f", bloatSizeMB]];
483   }
484   const CGFloat kBloatSizeBytes = ceil(bloatSizeMB * kNumBytesInMB);
485   const uint64 kNumberOfBytes = static_cast<uint64>(kBloatSizeBytes);
486   _bloat.reset(kNumberOfBytes ? new uint8[kNumberOfBytes] : nullptr);
487   if (_bloat) {
488     memset(_bloat.get(), -1, kNumberOfBytes);  // Occupy memory.
489   } else {
490     if (kNumberOfBytes) {
491       [self alert:@"Could not allocate memory."];
492     }
493   }
496 - (void)clearBloat {
497   [_bloatField setText:@"0"];
498   [_bloatField resignFirstResponder];
499   [self updateBloat];
502 #pragma mark Refresh interval methods
504 - (void)updateRefreshInterval {
505   double refreshTimerValue;
506   NSScanner* scanner = [NSScanner scannerWithString:[_refreshField text]];
507   if (![scanner scanDouble:&refreshTimerValue] || refreshTimerValue < 0.0) {
508     refreshTimerValue = 0.5;
509     NSString* errorMessage = [NSString
510         stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n"
511                          @"Must be a positive number.\n" @"Resetting to %.1f",
512                          [_refreshField text], refreshTimerValue];
513     [self alert:errorMessage];
514     [_refreshField
515         setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]];
516     return;
517   }
518   [_refreshTimer invalidate];
519   _refreshTimer.reset(
520       [[NSTimer scheduledTimerWithTimeInterval:refreshTimerValue
521                                         target:self
522                                       selector:@selector(refresh:)
523                                       userInfo:nil
524                                        repeats:YES] retain]);
527 #pragma mark Memory warning interval methods
529 // Since _performMemoryWarning is a private API it can't be compiled into
530 // official builds.
531 // TODO(lliabraa): Figure out how to support memory warnings (or something
532 // like them) in official builds.
533 #if CHROMIUM_BUILD
534 - (void)updateMemoryWarningInterval {
535   [_memoryWarningTimer invalidate];
536   double timerValue;
537   NSString* text = [_continuousMemoryWarningField text];
538   NSScanner* scanner = [NSScanner scannerWithString:text];
539   BOOL valueFound = [scanner scanDouble:&timerValue];
540   // If the text field is empty or contains 0, return early to turn off
541   // continuous memory warnings.
542   if (![text length] || timerValue == 0.0) {
543     return;
544   }
545   // If no value could be parsed or a non-positive value was found, throw up an
546   // error message and return early to turn off continuous memory warnings.
547   if (!valueFound || timerValue <= 0.0) {
548     NSString* errorMessage = [NSString
549         stringWithFormat:@"Invalid value \"%@\" for memory warning interval.\n"
550                          @"Must be a positive number.\n"
551                          @"Turning off continuous memory warnings",
552                          text];
553     [self alert:errorMessage];
554     [_continuousMemoryWarningField setText:@""];
555     return;
556   }
557   // If a valid value was found have the timer start triggering continuous
558   // memory warnings.
559   _memoryWarningTimer.reset(
560       [[NSTimer scheduledTimerWithTimeInterval:timerValue
561                                         target:[UIApplication sharedApplication]
562                                       selector:@selector(_performMemoryWarning)
563                                       userInfo:nil
564                                        repeats:YES] retain]);
566 #endif  // CHROMIUM_BUILD
568 #pragma mark UITextViewDelegate methods
570 // Dismisses the keyboard if the user hits return.
571 - (BOOL)textFieldShouldReturn:(UITextField*)textField {
572   [textField resignFirstResponder];
573   return YES;
576 #pragma mark UIResponder methods
578 // Allows the debugger to be dragged around the screen.
579 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
580   UITouch* touch = [touches anyObject];
581   CGPoint start = [touch previousLocationInView:self];
582   CGPoint end = [touch locationInView:self];
583   CGPoint offset = CGPointMake(end.x - start.x, end.y - start.y);
584   offset = CGPointApplyAffineTransform(offset, self.transform);
585   self.frame = CGRectOffset(self.frame, offset.x, offset.y);
588 #pragma mark Error handling
590 // Shows an alert with the given |errorMessage|.
591 - (void)alert:(NSString*)errorMessage {
592   UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Error"
593                                                   message:errorMessage
594                                                  delegate:self
595                                         cancelButtonTitle:@"OK"
596                                         otherButtonTitles:nil, nil];
597   [alert show];
600 @end