Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(382)

Side by Side Diff: tools/android/meminfo.py

Issue 1228393009: Upstream meminfo.py (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 # 'top'-like memory polling for Chrome on Android
7
8 import argparse
9 import commands
nyquist 2015/07/17 21:42:11 Nit: Is this import unused?
David Trainor- moved to gerrit 2015/07/21 18:19:57 Done.
10 import copy
nyquist 2015/07/17 21:42:10 Nit: Is this import unused?
David Trainor- moved to gerrit 2015/07/21 18:19:57 Done.
11 import curses
12 import os
13 import re
14 import sys
15 import time
16
17 from operator import sub
18
19 sys.path.append(os.path.join(os.path.dirname(__file__),
20 os.pardir,
21 os.pardir,
22 'build',
23 'android'))
24 from pylib import android_commands
25 from pylib.device import adb_wrapper
26 from pylib.device import device_errors
27
28 class Validator(object):
29 """A helper class with validation methods for argparse."""
30
31 @staticmethod
32 def ValidatePath(path):
33 """An argparse validation method to make sure a file path is writable."""
34 if os.path.exists(path):
35 return path
36 elif os.access(os.path.dirname(path), os.W_OK):
37 return path
38 raise argparse.ArgumentTypeError("%s is an invalid file path" % path)
39
40 @staticmethod
41 def ValidatePdfPath(path):
42 """An argparse validation method to make sure a pdf file path is writable.
43 Validates a file path to make sure it is writable and also appends '.pdf' if
44 necessary."""
45 if os.path.splitext(path)[-1].lower() != 'pdf':
46 path = path + '.pdf'
47 return Validator.ValidatePath(path)
48
49 @staticmethod
50 def ValidatePositiveNumber(val):
nyquist 2015/07/17 21:42:11 This method allows 0, which is not positive. How a
David Trainor- moved to gerrit 2015/07/21 18:19:57 Done.
51 """An argparse validation method to make sure a number is positive."""
52 ival = int(val)
53 if ival < 0:
54 raise argparse.ArgumentTypeError("%s is not a positive integer" % val)
55 return ival
56
57 class Timer(object):
58 """A helper class to track timestamps based on when this program was
59 started"""
60 starting_time = time.time()
61
62 @staticmethod
63 def GetTimestamp():
64 """A helper method to return the time (in seconds) since this program was
65 started."""
66 return time.time() - Timer.starting_time
67
68 class DeviceHelper(object):
69 """A helper class with various generic device interaction methods."""
70
71 @staticmethod
72 def GetDeviceModel(adb):
73 """Returns the model of the device with the |adb| connection."""
74 return adb.Shell(' '.join(['getprop', 'ro.product.model'])).strip()
75
76 @staticmethod
77 def GetDeviceToTrack(preset=None):
78 """Returns a device serial to connect to. If |preset| is specified it will
79 return |preset| if it is connected and |None| otherwise. If |preset| is not
80 specified it will return the first connected device."""
81 devices = android_commands.GetAttachedDevices()
82 if not devices:
83 return None
84
85 if preset:
86 return preset if preset in devices else None
87
88 return devices[0]
89
90 @staticmethod
91 def GetPidsToTrack(adb, default_pid=None, process_filter=None):
92 """Returns a list of pids based on the input arguments. If |default_pid| is
93 specified it will return that pid if it exists. If |process_filter| is
94 specified it will return the pids of processes with that string in the name.
95 If both are specified it will intersect the two."""
96 pids = []
97 try:
98 cmd = ['ps']
99 if default_pid:
100 cmd.extend(['|', 'grep', '-F', str(default_pid)])
101 if process_filter:
102 cmd.extend(['|', 'grep', '-F', process_filter])
103 pid_str = adb.Shell(' '.join(cmd))
104 for line in pid_str.splitlines():
105 data = re.split('\s+', line.strip())
106 pid = data[1]
107 name = data[-1]
108
109 # Confirm that the pid and name match. Using a regular grep isn't
110 # reliable when doing it on the whole 'ps' input line.
111 if (not default_pid or pid == str(default_pid)) and (not process_filter
nyquist 2015/07/17 21:42:10 this if-block looks a bit confusing since only the
David Trainor- moved to gerrit 2015/07/21 18:19:56 Done.
112 or name.find(process_filter) != -1):
113 pids.append((pid, name))
114 except device_errors.AdbShellCommandFailedError:
115 pass
116 return pids
117
118 class MemoryHelper(object):
119 """A helper class to query basic memory usage of a process."""
120
121 @staticmethod
122 def QueryMemory(adb, pid):
123 """Queries the device for memory information about the process with a pid of
124 |pid|. It will query Native, Dalvik, and Pss memory of the process. It
125 returns a list of values: [ Native, Pss, Dalvik]. If the process is not
nyquist 2015/07/17 21:42:11 Nit: Missing space after Dalvid, or extra space be
David Trainor- moved to gerrit 2015/07/21 18:19:57 Done.
126 found it will return [ 0, 0, 0 ]."""
127 results = [0, 0, 0]
128
129 memstr = adb.Shell(' '.join(['dumpsys', 'meminfo', pid]))
130 for line in memstr.splitlines():
131 match = re.split('\s+', line.strip())
132 result_idx = None
133 data_idx = None
nyquist 2015/07/17 21:42:10 query_idx?
David Trainor- moved to gerrit 2015/07/21 18:19:56 gah good catch!
134 if match[0] == 'Native':
nyquist 2015/07/17 21:42:10 Would this match the app summary as well? ... App
David Trainor- moved to gerrit 2015/07/21 18:19:56 Since the query_idx is -2, wouldn't it pull the "h
135 result_idx = 0
136 query_idx = -2
137 elif match[0] == 'Dalvik' and match[1] == 'Heap':
138 result_idx = 2
139 query_idx = -2
140 elif match[0] == 'TOTAL':
141 result_idx = 1
142 query_idx = 1
143
144 if result_idx is not None and query_idx is not None:
145 results[result_idx] = round(float(match[query_idx]) / 1000.0, 2)
146 return results
147
148 class GraphicsHelper(object):
149 """A helper class to query basic graphics memory usage of a process."""
150
151 # TODO(dtrainor): Find a generic way to query/fall back for other devices.
nyquist 2015/07/17 21:42:11 Nit: Is this indent using spaces instead of tabs?
David Trainor- moved to gerrit 2015/07/21 18:19:56 I don't think so. At least not in my editor! Wil
152 # Is showmap consistently reliable?
153 __NV_MAP_MODELS = ['Xoom']
nyquist 2015/07/17 21:42:11 Do we really need to support Xoom?
David Trainor- moved to gerrit 2015/07/21 18:19:57 No, but I'm not sure if other drivers use NV_MAP f
154 __NV_MAP_FILE_LOCATIONS = ['/d/nvmap/generic-0/clients',
155 '/d/nvmap/iovmm/clients']
156
157 __SHOWMAP_MODELS = ['Nexus S',
158 'Nexus S 4G',
159 'Galaxy Nexus',
160 'Nexus 4',
161 'Nexus 5',
162 'Nexus 7']
163 __SHOWMAP_KEY_MATCHES = ['/dev/pvrsrvkm',
164 '/dev/kgsl-3d0']
165
166 @staticmethod
167 def __QueryShowmap(adb, pid):
168 """Attempts to query graphics memory via the 'showmap' command. It will
169 look for |self.__SHOWMAP_KEY_MATCHES| entries to try to find one that
170 represents the graphics memory usage. Will return this as a single entry
171 array of [ Graphics ]. If not found, will return [ 0 ]."""
172 try:
173 memstr = adb.Shell(' '.join(['showmap', '-t', pid]))
174 for line in memstr.splitlines():
175 match = re.split('[ ]+', line.strip())
176 if match[-1] in GraphicsHelper.__SHOWMAP_KEY_MATCHES:
177 return [ round(float(match[2]) / 1000.0, 2) ]
178 except device_errors.AdbShellCommandFailedError:
179 pass
180 return [ 0 ]
181
182 @staticmethod
183 def __NvMapPath(adb):
184 """Attempts to find a valid NV Map file on the device. It will look for a
185 file in |self.__NV_MAP_FILE_LOCATIONS| and see if one exists. If so, it
186 will return it."""
187 for nv_file in GraphicsHelper.__NV_MAP_FILE_LOCATIONS:
188 exists = adb.shell(' '.join(['ls', nv_file]))
189 if exists == nv_file.split('/')[-1]:
190 return nv_file
191 return None
192
193 @staticmethod
194 def __QueryNvMap(adb, pid):
195 """Attempts to query graphics memory via the NV file map method. It will
196 find a possible NV Map file from |self.__NvMapPath| and try to parse the
197 graphics memory from it. Will return this as a single entry array of
198 [ Graphics ]. If not found, will return [ 0 ]."""
199 nv_file = GraphicsHelper.__NvMapPath(adb)
200 if nv_file:
201 memstr = adb.Shell(' '.join(['cat', nv_file]))
202 for line in memstr.splitlines():
203 match = re.split(' +', line.strip())
204 if match[2] == pid:
205 return [ round(float(match[3]) / 1000000.0, 2) ]
206 return [ 0 ]
207
208 @staticmethod
209 def QueryVideoMemory(adb, pid):
210 """Queries the device for graphics memory information about the process with
211 a pid of |pid|. Not all devices are currently supported. If possible, this
212 will return a single entry array of [ Graphics ]. Otherwise it will return
213 [ 0 ].
214
215 Please see |self.__NV_MAP_MODELS| and |self.__SHOWMAP_MODELS|
216 to see if the device is supported. For new devices, see if they can be
217 supported by existing methods and add their entry appropriately. Also,
218 please add any new way of querying graphics memory as they become
219 available."""
220 model = DeviceHelper.GetDeviceModel(adb)
221 if model in GraphicsHelper.__NV_MAP_MODELS:
222 return GraphisHelper.__QueryNvMap(adb, pid)
nyquist 2015/07/17 21:42:10 GraphicsHelper
David Trainor- moved to gerrit 2015/07/21 18:19:57 Guess you can tell I didn't test zoom since the re
223 elif model in GraphicsHelper.__SHOWMAP_MODELS:
224 return GraphicsHelper.__QueryShowmap(adb, pid)
225 return [ 0 ]
226
227 class MemorySnapshot(object):
228 """A class holding a snapshot of memory for various pids that are being
229 tracked.
230
231 Attributes:
232 pids: A list of tuples (pid, process name) that should be tracked.
233 memory: A map of entries of pid => memory consumption array. Right now
234 the indices are [ Native, Pss, Dalvik, Graphics ].
235 timestamp: The amount of time (in seconds) between when this program started
236 and this snapshot was taken.
237 """
238
239 def __init__(self, adb, pids):
240 """Creates an instances of a MemorySnapshot with an |adb| device connection
241 and a list of (pid, process name) tuples."""
242 super(MemorySnapshot, self).__init__()
243
244 self.pids = pids
245 self.memory = {}
246 self.timestamp = Timer.GetTimestamp()
247
248 for (pid, name) in pids:
249 self.memory[pid] = self.__QueryMemoryForPid(adb, pid)
250
251 @staticmethod
252 def __QueryMemoryForPid(adb, pid):
253 """Queries the |adb| device for memory information about |pid|. This will
254 return a list of memory values that map to [ Native, Pss, Dalvik,
255 Graphics ]."""
256 results = MemoryHelper.QueryMemory(adb, pid)
257 results.extend(GraphicsHelper.QueryVideoMemory(adb, pid))
258 return results
259
260 def __GetProcessNames(self):
261 """Returns a list of all of the process names tracked by this snapshot."""
262 return [tuple[1] for tuple in self.pids]
263
264 def HasResults(self):
265 """Whether or not this snapshot was tracking any processes."""
266 return self.pids
267
268 def GetPidAndNames(self):
269 """Returns a list of (pid, process name) tuples that are being tracked in
270 this snapshot."""
271 return self.pids
272
273 def GetNameForPid(self, search_pid):
274 """Returns the process name of a tracked |search_pid|. This only works if
275 |search_pid| is tracked by this snapshot."""
276 for (pid, name) in self.pids:
277 if pid == search_pid:
278 return name
279 return None
280
281 def GetResults(self, pid):
282 """Returns a list of entries about the memory usage of the process specified
283 by |pid|. This will be of the format [ Native, Pss, Dalvik, Graphics ]."""
284 if pid in self.memory:
285 return self.memory[pid]
286 return None
287
288 def GetLongestNameLength(self):
289 """Returns the length of the longest process name tracked by this
290 snapshot."""
291 return len(max(self.__GetProcessNames(), key=len))
292
293 def GetTimestamp(self):
294 """Returns the time since program start that this snapshot was taken."""
295 return self.timestamp
296
297 class OutputBeautifier(object):
298 """A helper class to beautify the memory output to various destinations.
299
300 Attributes:
301 can_color: Whether or not the output should include ASCII color codes to
302 make it look nicer. Default is |True|. This is disabled when
303 writing to a file or a graph.
304 overwrite: Whether or not the output should overwrite the previous output.
305 Default is |True|. This is disabled when writing to a file or a
306 graph.
307 """
308
309 __MEMORY_COLUMN_TITLES = ['Native',
310 'Pss',
311 'Dalvik',
312 'Graphics']
313
314 __TERMINAL_COLORS = {'ENDC': 0,
315 'BOLD': 1,
316 'GREY30': 90,
317 'RED': 91,
318 'DARK_YELLOW': 33,
319 'GREEN': 92}
320
321 def __init__(self, can_color=True, overwrite=True):
322 """Creates an instance of an OutputBeautifier."""
323 super(OutputBeautifier, self).__init__()
324 self.can_color = can_color
325 self.overwrite = overwrite
326
327 self.lines_printed = 0
328 self.printed_header = False
329
330 @staticmethod
331 def __TermCode(num):
332 """Escapes a terminal code. See |self.__TERMINAL_COLORS| for a list of some
333 terminal codes that are used by this program."""
334 return '\033[%sm'%num
nyquist 2015/07/17 21:42:11 Nit: Add spaces around the last % to separate form
David Trainor- moved to gerrit 2015/07/21 18:19:56 Done.
335
336 def __ColorString(self, string, color):
337 """Colors |string| based on |color|. |color| must be in
338 |self.__TERMINAL_COLORS|. Returns the colored string or the original
339 string if |self.can_color| is |False| or the |color| is invalid."""
340 if not self.can_color or not color or not self.__TERMINAL_COLORS[color]:
341 return string
342
343 return '%s%s%s' % (self.__TermCode(self.__TERMINAL_COLORS[color]),
344 string,
345 self.__TermCode(self.__TERMINAL_COLORS['ENDC']))
346
347 @staticmethod
348 def __PadString(string, length, left_align):
349 """Pads |string| to at least |length| with spaces. Depending on
350 |left_align| the padding will appear at either the left or the right of the
351 original string."""
352 return (('%' if left_align else '%-') + str(length) + 's') % string
353
354 def __PadAndColor(self, string, length, left_align, color):
355 """A helper method to both pad and color the string. See
356 |self.__ColorString| and |self.__PadString|."""
357 return self.__ColorString(
358 self.__PadString(string, length, left_align), color)
359
360 @staticmethod
361 def __GetDiffColor(delta):
362 """Returns a color based on |delta|. Used to color the deltas between
363 different snapshots."""
364 if not delta or delta == 0.0:
365 return 'GREY30'
366 elif delta < 0:
367 return 'GREEN'
368 elif delta > 0:
369 return 'RED'
370
371 def __OutputLine(self, line):
372 """Writes a line to the screen. This also tracks how many times this method
373 was called so that the screen can be cleared properly if |self.overwrite| is
374 |True|."""
375 sys.stdout.write(line + '\n')
376 if self.overwrite:
377 self.lines_printed += 1
378
379 def __ClearScreen(self):
380 """Clears the screen based on the number of times |self.__OutputLine| was
381 called."""
382 if self.lines_printed == 0 or not self.overwrite:
383 return
384
385 key_term_up = curses.tparm(curses.tigetstr('cuu1'))
386 key_term_clear_eol = curses.tparm(curses.tigetstr('el'))
387 key_term_go_to_bol = curses.tparm(curses.tigetstr('cr'))
388
389 sys.stdout.write(key_term_go_to_bol)
390 sys.stdout.write(key_term_clear_eol)
391
392 for i in range(self.lines_printed):
393 sys.stdout.write(key_term_up)
394 sys.stdout.write(key_term_clear_eol)
395 self.lines_printed = 0
396
397 def __PrintBasicStatsHeader(self):
398 """Returns a common header for the memory usage stats."""
399 titles = ''
400 for title in self.__MEMORY_COLUMN_TITLES:
401 titles += self.__PadString(title, 8, True) + ' '
402 titles += self.__PadString('', 8, True)
403 return self.__ColorString(titles, 'BOLD')
404
405 def __PrintLabeledStatsHeader(self, snapshot):
406 """Returns a header for the memory usage stats that includes sections for
407 the pid and the process name. The available room given to the process name
408 is based on the length of the longest process name tracked by |snapshot|.
409 This header also puts the timestamp of the snapshot on the right."""
410 if not snapshot or not snapshot.HasResults():
411 return
412
413 name_length = max(8, snapshot.GetLongestNameLength())
414
415 titles = self.__PadString('Pid', 8, True) + ' '
416 titles += self.__PadString('Name', name_length, False) + ' '
417 titles += self.__PrintBasicStatsHeader()
418 titles += '(' + str(round(snapshot.GetTimestamp(), 2)) + 's)'
419 titles = self.__ColorString(titles, 'BOLD')
420 return titles
421
422 def __PrintTimestampedBasicStatsHeader(self):
423 """Returns a header for the memory usage stats that includes a the
424 timestamp of the snapshot."""
425 titles = self.__PadString('Timestamp', 8, False) + ' '
426 titles = self.__ColorString(titles, 'BOLD')
427 titles += self.__PrintBasicStatsHeader()
428 return titles
429
430 def __PrintBasicSnapshotStats(self, pid, snapshot, prev_snapshot):
431 """Returns a string that contains the basic snapshot memory statistics.
432 This string should line up with the header returned by
433 |self.__PrintBasicStatsHeader|."""
434 if not snapshot or not snapshot.HasResults():
435 return
436
437 results = snapshot.GetResults(pid)
438 if not results:
439 return
440
441 old_results = prev_snapshot.GetResults(pid) if prev_snapshot else None
442
443 # Build Delta List
444 deltas = [ 0, 0, 0, 0 ]
445 if old_results:
446 deltas = map(sub, results, old_results)
447 assert len(deltas) == len(results)
448 for idx, delta in enumerate(deltas):
449 delta = round(delta, 2)
nyquist 2015/07/17 21:42:10 Does this really edit the values in |deltas|? You
David Trainor- moved to gerrit 2015/07/21 18:19:56 Turns out this code can be removed because I run b
450
451 output = ''
452 for idx, mem in enumerate(results):
453 output += self.__PadString(mem, 8, True) + ' '
454 output += self.__PadAndColor('(' + str(round(deltas[idx], 2)) + ')',
455 8, False, self.__GetDiffColor(deltas[idx]))
nyquist 2015/07/17 21:42:11 Nit: Should this be indented one more step?
David Trainor- moved to gerrit 2015/07/21 18:19:57 Done.
456
457 return output
458
459 def __PrintLabeledSnapshotStats(self, pid, snapshot, prev_snapshot):
460 """Returns a string that contains memory usage stats along with the pid and
461 process name. This string should line up with the header returned by
462 |self.__PrintLabeledStatsHeader|."""
463 if not snapshot or not snapshot.HasResults():
464 return
465
466 name_length = max(8, snapshot.GetLongestNameLength())
467 name = snapshot.GetNameForPid(pid)
468
469 output = self.__PadAndColor(pid, 8, True, 'DARK_YELLOW') + ' '
470 output += self.__PadAndColor(name, name_length, False, None) + ' '
471 output += self.__PrintBasicSnapshotStats(pid, snapshot, prev_snapshot)
472 return output
473
474 def __PrintTimestampedBasicSnapshotStats(self, pid, snapshot, prev_snapshot):
475 """Returns a string that contains memory usage stats along with the
476 timestamp of the snapshot. This string should line up with the header
477 returned by |self.__PrintTimestampedBasicStatsHeader|."""
478 if not snapshot or not snapshot.HasResults():
479 return
480
481 timestamp_length = max(8, len("Timestamp"))
482 timestamp = round(snapshot.GetTimestamp(), 2)
483
484 output = self.__PadString(str(timestamp), timestamp_length, True) + ' '
485 output += self.__PrintBasicSnapshotStats(pid, snapshot, prev_snapshot)
486 return output
487
488 def PrettyPrint(self, snapshot, prev_snapshot):
489 """Prints |snapshot| to the console. This will show memory deltas between
490 |snapshot| and |prev_snapshot|. This will also either color or overwrite
491 the previous entries based on |self.can_color| and |self.overwrite|."""
492 self.__ClearScreen()
493
494 if not snapshot or not snapshot.HasResults():
495 self.__OutputLine("No results...")
496 return
497
498 self.__OutputLine(self.__PrintLabeledStatsHeader(snapshot))
499
500 for (pid, name) in snapshot.GetPidAndNames():
501 self.__OutputLine(self.__PrintLabeledSnapshotStats(pid,
502 snapshot,
503 prev_snapshot))
504
505 def PrettyFile(self, file_path, snapshots, diff_against_start):
506 """Writes |snapshots| (a list of MemorySnapshots) to |file_path|.
507 |diff_against_start| determines whether or not the snapshot deltas are
508 between the first entry and all entries or each previous entry. This output
509 will not follow |self.can_color| or |self.overwrite|."""
510 if not file_path or not snapshots:
511 return
512
513 pids = set()
nyquist 2015/07/17 21:42:11 This is done both here and in PrettyGraph. Extract
David Trainor- moved to gerrit 2015/07/21 18:19:56 Done.
514 # Find all unique pids
515 for snapshot in snapshots:
516 for (pid, name) in snapshot.GetPidAndNames():
517 pids.add((pid, name))
518
519 # Disable special output formatting for file writing.
520 can_color = self.can_color
521 self.can_color = False
522
523 with open(file_path, 'w') as out:
524 for (pid, name) in pids:
525 out.write(name + ' (' + str(pid) + '):\n')
526 out.write(self.__PrintTimestampedBasicStatsHeader())
527 out.write('\n')
528
529 prev_snapshot = None
530 for snapshot in snapshots:
531 if not snapshot.GetResults(pid):
532 continue
533 out.write(self.__PrintTimestampedBasicSnapshotStats(pid,
534 snapshot,
535 prev_snapshot))
536 out.write('\n')
537 if not prev_snapshot or not diff_against_start:
538 prev_snapshot = snapshot
539 out.write('\n\n')
540
541 # Restore special output formatting.
542 self.can_color = can_color
543
544 def PrettyGraph(self, file_path, snapshots):
545 """Creates a pdf graph of |snapshots| (a list of MemorySnapshots) at
546 |file_path|."""
547 # Import these here so the rest of the functionality doesn't rely on
548 # matplotlib
549 from matplotlib import pyplot
550 from matplotlib.backends.backend_pdf import PdfPages
551
552 if not file_path or not snapshots:
553 return
554
555 # Find all unique (pid, name) pairs
556 pids = set()
557 for snapshot in snapshots:
558 for (pid, name) in snapshot.GetPidAndNames():
559 pids.add((pid, name))
560
561 pp = PdfPages(file_path)
562 for (pid, name) in pids:
563 figure = pyplot.figure()
564 ax = figure.add_subplot(1, 1, 1)
565 ax.set_xlabel('Time (s)')
566 ax.set_ylabel('MB')
567 ax.set_title(name + ' (' + pid + ')')
568
569 mem_list = [[] for x in range(len(self.__MEMORY_COLUMN_TITLES))]
570 timestamps = []
571
572 for snapshot in snapshots:
573 results = snapshot.GetResults(pid)
574 if not results:
575 continue
576
577 timestamps.append(round(snapshot.GetTimestamp(), 2))
578
579 assert len(results) == len(self.__MEMORY_COLUMN_TITLES)
580 for idx, result in enumerate(results):
581 mem_list[idx].append(result)
582
583 colors = []
584 for data in mem_list:
585 colors.append(ax.plot(timestamps, data)[0])
586 for i in xrange(len(timestamps)):
587 ax.annotate(data[i], xy=(timestamps[i], data[i]))
588 figure.legend(colors, self.__MEMORY_COLUMN_TITLES)
589 pp.savefig()
590 pp.close()
591
592 def main(argv):
593 parser = argparse.ArgumentParser()
594 parser.add_argument('--process',
595 dest='procname',
596 help="A (sub)string to match against process names.")
597 parser.add_argument('-p',
598 '--pid',
599 dest='pid',
600 type=Validator.ValidatePositiveNumber,
601 help='Which pid to scan for.')
602 parser.add_argument('-d',
603 '--device',
604 dest='device',
605 help='Device serial to scan.')
606 parser.add_argument('-t',
607 '--timelimit',
608 dest='limit',
nyquist 2015/07/17 21:42:10 Nit: I personally find it easier to read later in
David Trainor- moved to gerrit 2015/07/21 18:19:56 Done.
609 type=Validator.ValidatePositiveNumber,
610 help='How long to track memory in seconds.')
611 parser.add_argument('-f',
612 '--frequency',
613 dest='frequency',
614 default=0,
615 type=Validator.ValidatePositiveNumber,
616 help='How often to poll in seconds.')
617 parser.add_argument('-s',
618 '--diff-against-start',
619 dest='diff_against_start',
620 action='store_true',
621 help='Whether or not to always compare against the'
622 ' original memory values for deltas.')
623 parser.add_argument('-b',
624 '--boring-output',
625 dest='dull_output',
626 action='store_true',
627 help='Whether or not to dull down the output.')
628 parser.add_argument('-n',
629 '--no-overwrite',
630 dest='no_overwrite',
631 action='store_true',
632 help='Keeps printing the results in a list instead of'
633 ' overwriting the previous values.')
634 parser.add_argument('-g',
635 '--graph-file',
636 dest='graph_file',
637 type=Validator.ValidatePdfPath,
638 help='Pdf file to save graph of memory stats to.')
nyquist 2015/07/17 21:42:11 Nit: PDF
David Trainor- moved to gerrit 2015/07/21 18:19:57 Done.
639 parser.add_argument('-o',
640 '--text-file',
641 dest='text_file',
642 type=Validator.ValidatePath,
643 help='File to save memory tracking stats to.')
644
645 args = parser.parse_args()
646
647 # Add a basic filter to make sure we search for something.
648 if not args.procname and not args.pid:
649 args.procname = 'chrome'
650
651 curses.setupterm()
652
653 printer = OutputBeautifier(not args.dull_output, not args.no_overwrite)
654
655 sys.stdout.write("Running... Hold CTRL-C to stop (or specify timeout).\n")
656 try:
657 last_time = time.time()
658
659 adb = None
660 old_snapshot = None
661 snapshots = []
662 while not args.limit or Timer.GetTimestamp() < float(args.limit):
663 # Check if we need to track another device
664 device = DeviceHelper.GetDeviceToTrack(args.device)
665 if not device:
666 adb = None
667 elif not adb or device != str(adb):
nyquist 2015/07/17 21:42:10 I had to look up AdbWrapper.__str__. That's cute!
David Trainor- moved to gerrit 2015/07/21 18:19:56 Yeah I thought so too when I saw that! :)
668 adb = adb_wrapper.AdbWrapper(device)
669 old_snapshot = None
nyquist 2015/07/17 21:42:11 Would we want to clear snapshots?
David Trainor- moved to gerrit 2015/07/21 18:19:57 O_o good point!
670 try:
671 adb.Root()
672 except device_errors.AdbCommandFailedError:
673 sys.stderr.write('Unable to run adb as root.\n')
674 sys.exit(1)
675
676 # Grab a snapshot if we have a device
677 snapshot = None
678 if adb:
679 pids = DeviceHelper.GetPidsToTrack(adb, args.pid, args.procname)
680 snapshot = MemorySnapshot(adb, pids) if pids else None
681
682 if snapshot and snapshot.HasResults():
683 snapshots.append(snapshot)
684
685 printer.PrettyPrint(snapshot, old_snapshot)
686
687 # Transfer state for the next iteration and sleep
688 delay = max(1, args.frequency)
689 if snapshot:
690 delay = max(0, args.frequency - (time.time() - last_time))
691 time.sleep(delay)
692
693 last_time = time.time()
694 if not old_snapshot or not args.diff_against_start:
695 old_snapshot = snapshot
696 except KeyboardInterrupt:
697 pass
698
699 if args.graph_file:
700 printer.PrettyGraph(args.graph_file, snapshots)
701
702 if args.text_file:
703 printer.PrettyFile(args.text_file, snapshots, args.diff_against_start)
704
705 if __name__ == '__main__':
706 sys.exit(main(sys.argv))
707
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698