├── README.md └── memusg /README.md: -------------------------------------------------------------------------------- 1 | A 'time'-like utility for Unix that measures peak memory usage. 2 | 3 | Works in both interactive and non-interactive environments. 4 | 5 | Usage: 6 | 7 | ```bash 8 | usage: memusg [-h] [-o OUTPUT_TO_FILE] [-t] [-p POLL_INTERVAL] 9 | [-w WRITE_INTERVAL] [-H] [-s] 10 | ... 11 | 12 | positional arguments: 13 | args 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -o OUTPUT_TO_FILE, --output-to-file OUTPUT_TO_FILE 18 | Output usage to file 19 | -t, --time Output run time of subprocess as well 20 | -p POLL_INTERVAL, --poll-interval POLL_INTERVAL 21 | Polling interval in seconds to check for current 22 | memory usage 23 | -w WRITE_INTERVAL, --write-interval WRITE_INTERVAL 24 | Interval in seconds to write current peak usage to 25 | stdout while process is running (Disable using 0) 26 | -H, --no-humanize No humanization of the value of time (second) and 27 | memory (KB) usage 28 | -s, --shell execute through shell, useful for multiple cmmands and 29 | commands with pipes 30 | ``` 31 | 32 | ```bash 33 | export PATH=$path_to_memusg:$PATH 34 | memusg my_command 35 | ``` 36 | 37 | Example: 38 | 39 | ```bash 40 | $ memusg -t sleep 2 41 | 42 | elapsed time: 2.039s 43 | peak rss: 664.0 KB 44 | 45 | $ memusg -t -s "echo 1 | cat" 46 | 1 47 | 48 | elapsed time: 0.110s 49 | peak rss: 0B 50 | 51 | ``` 52 | -------------------------------------------------------------------------------- /memusg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # https://github.com/drewkett/memusg 4 | 5 | import sys 6 | import os 7 | import signal 8 | from time import sleep, time 9 | from subprocess import * 10 | from argparse import ArgumentParser, REMAINDER 11 | import math 12 | 13 | parser = ArgumentParser() 14 | parser.add_argument('-o', 15 | '--output-to-file', 16 | help="Output usage to file", 17 | default="") 18 | parser.add_argument('-t', 19 | '--time', 20 | help="Output run time of subprocess as well", 21 | default=False, 22 | action='store_true') 23 | parser.add_argument( 24 | '-p', 25 | '--poll-interval', 26 | help="Polling interval in seconds to check for current memory usage", 27 | type=float, 28 | default=0.1) 29 | parser.add_argument( 30 | '-w', 31 | '--write-interval', 32 | help="Interval in seconds to write current peak usage to stdout while process is running (Disable using 0)", 33 | type=float, 34 | default=0) 35 | parser.add_argument( 36 | '-H', 37 | '--no-humanize', 38 | help="No humanization of the value of time (second) and memory (KB) usage", 39 | default=False, 40 | action='store_true') 41 | parser.add_argument('-s', 42 | '--shell', 43 | help="execute through shell, useful for multiple cmmands and commands with pipes", 44 | default=False, 45 | action='store_true') 46 | parser.add_argument('args', nargs=REMAINDER) 47 | args = parser.parse_args() 48 | 49 | 50 | def get_rssize(sid): 51 | """Get total resident set memory of processes in session id""" 52 | proc = Popen(['ps', '-o', 'rssize=', '--sid', str(sid)], stdout=PIPE) 53 | stdout, _ = proc.communicate() 54 | return sum(int(line.strip()) for line in stdout.split() if line.strip()) 55 | 56 | 57 | def human_time(t): 58 | if t < 60: 59 | return "{:.3f}s".format(t) 60 | elif t < 60 * 60: 61 | minutes = (t % 3600) // 60 62 | seconds = (t % 60) 63 | return "{:.0f}m:{:02.0f}s".format(minutes, seconds) 64 | elif t < 24 * 60 * 60: 65 | hours = (t % (24 * 60 * 60)) // 3600 66 | minutes = (t % 3600) // 60 67 | seconds = (t % 60) 68 | return "{:.0f}h:{:02.0f}m:{:02.0f}s".format(hours, minutes, seconds) 69 | else: 70 | days = t // (24 * 60 * 60) 71 | hours = (t % (24 * 60 * 60)) // 3600 72 | minutes = (t % 3600) // 60 73 | seconds = (t % 60) 74 | return "{}days {:.0f}h:{:02.0f}m:{:02.0f}s".format(days, hours, minutes, seconds) 75 | 76 | 77 | def human_size(size): 78 | """Convert integer representing bytes to human readable string""" 79 | size_name = ("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") 80 | if size: 81 | i = int(math.floor(math.log(size, 1000))) 82 | p = math.pow(1000, i) 83 | s = round(size / p, 2) 84 | return '%s %s' % (s, size_name[i]) 85 | else: 86 | return '0B' 87 | 88 | 89 | if args.output_to_file: 90 | stdout = open(args.output_to_file, "w") 91 | else: 92 | stdout = sys.stderr 93 | 94 | if args.time: 95 | start_time = time() 96 | 97 | if sys.version_info >= (3, 0): 98 | p = Popen(tuple(args.args), start_new_session=True, shell=args.shell) 99 | else: 100 | p = Popen(tuple(args.args), preexec_fn=os.setsid, shell=args.shell) 101 | 102 | # Catch interrupt signal and send to subprocesses 103 | signal.signal(signal.SIGINT, lambda signum, frame: os.killpg(p.pid, signum)) 104 | 105 | if args.write_interval: 106 | n_interval = int(args.write_interval / args.poll_interval) 107 | last_peak = 0 108 | 109 | peak = 0 110 | n = 0 111 | while p.poll() is None: 112 | peak = max(peak, get_rssize(os.getsid(p.pid))) 113 | n += 1 114 | # Check every {write_interval} seconds if current peak memory usage is 115 | # greater than previously output peak memory usage 116 | if args.write_interval and (n % n_interval) == 0: 117 | if peak > last_peak: 118 | sys.stdout.write("peak rss: {}\n".format( 119 | human_size(peak) if not args.no_humanize else peak)) 120 | last_peak = peak 121 | sleep(args.poll_interval) 122 | 123 | stdout.write("\n") 124 | if args.time: 125 | elapsed_time = time() - start_time 126 | stdout.write("elapsed time: {}\n".format(human_time( 127 | elapsed_time) if not args.no_humanize else "{:.3f}".format(elapsed_time))) 128 | stdout.write("peak rss: {}\n".format( 129 | human_size(peak) if not args.no_humanize else peak)) 130 | stdout.write("\n") 131 | 132 | # Return subprocess return code except when returncode is negative, which 133 | # indicates the subprocess was killed by a signal. Don't know how to get 134 | # returncode in that case 135 | if p.returncode < 0: 136 | sys.exit(255) 137 | else: 138 | sys.exit(p.returncode) 139 | --------------------------------------------------------------------------------