88import math
99import logging
1010import itertools
11+ from ast import literal_eval
1112
1213from collections import defaultdict
1314from argparse import ArgumentParser , ArgumentError , REMAINDER , RawTextHelpFormatter
1415
1516import importlib
1617import memory_profiler as mp
1718
18- ALL_ACTIONS = ("run" , "rm" , "clean" , "list" , "plot" )
19+ ALL_ACTIONS = ("run" , "rm" , "clean" , "list" , "plot" , "attach" )
1920help_msg = """
2021Available commands:
2122
2223 run run a given command or python file
24+ attach alias for 'run --attach': attach to an existing process by pid or name
2325 rm remove a given file generated by mprof
2426 clean clean the current directory from files created by mprof
2527 list display existing profiles, with indices
@@ -176,6 +178,16 @@ def get_cmd_line(args):
176178 args = [s if blanks .isdisjoint (s ) else "'" + s + "'" for s in args ]
177179 return ' ' .join (args )
178180
181+ def find_first_process (name ):
182+ for i in mp .psutil .process_iter ():
183+ if name in i .name ():
184+ return i
185+ return None
186+
187+ def attach_action ():
188+ argv = sys .argv
189+ sys .argv = argv [:1 ] + ['--attach' ] + argv [1 :]
190+ run_action ()
179191
180192def run_action ():
181193 import time , subprocess
@@ -192,6 +204,10 @@ def run_action():
192204 parser .add_argument ("--multiprocess" , "-M" , dest = "multiprocess" , action = "store_true" ,
193205 help = """Monitors forked processes creating individual plots for each child (disables --python features)""" )
194206 parser .add_argument ("--exit-code" , "-E" , dest = "exit_code" , action = "store_true" , help = """Propagate the exit code""" )
207+ attach_arg = parser .add_argument ("--attach" , "-a" , dest = "attach_existing" , action = "store_true" ,
208+ help = "Attach to an existing process, by process name or by pid" )
209+ parser .add_argument ("--timeout" , "-t" , dest = "timeout" , action = "store" , type = int ,
210+ help = "timeout in seconds for the profiling, default new process has no timeout, attach existing is 1 hour" )
195211 parser .add_argument ("--output" , "-o" , dest = "filename" ,
196212 default = "mprofile_%s.dat" % time .strftime ("%Y%m%d%H%M%S" , time .localtime ()),
197213 help = """File to store results in, defaults to 'mprofile_<YYYYMMDDhhmmss>.dat' in the current directory,
@@ -214,34 +230,53 @@ def run_action():
214230
215231 mprofile_output = args .filename
216232
217- # .. TODO: more than one script as argument ? ..
218233 program = args .program
219- if program [0 ].endswith ('.py' ) and not args .nopython :
220- if args .multiprocess :
221- # in multiprocessing mode you want to spawn a separate
222- # python process
234+ if args .attach_existing :
235+ print ('attaching to existing process, using hint: {}' .format (program [0 ]))
236+ if program [0 ].isdigit ():
237+ p = literal_eval (program [0 ])
238+ cmd_line = get_cmd_line (program )
239+ else :
240+ proc = find_first_process (program [0 ])
241+ if proc is None :
242+ raise ArgumentError (attach_arg , '\n When attaching, program should be process name or pid.\n Failed to find a process using hint: {}' .format (program [0 ]))
243+
244+ p = proc .pid
245+ try :
246+ cmd_line = proc .cmdline ()
247+ except :
248+ cmd_line = get_cmd_line (program )
249+ if args .timeout is None :
250+ args .timeout = 3600
251+ else :
252+ print ('running new process' )
253+ # .. TODO: more than one script as argument ? ..
254+ if program [0 ].endswith ('.py' ) and not args .nopython :
255+ if args .multiprocess :
256+ # in multiprocessing mode you want to spawn a separate
257+ # python process
258+ if not program [0 ].startswith ("python" ):
259+ program .insert (0 , sys .executable )
260+ args .python = False
261+ else :
262+ args .python = True
263+ if args .python :
264+ print ("running as a Python program..." )
223265 if not program [0 ].startswith ("python" ):
224266 program .insert (0 , sys .executable )
225- args .python = False
267+ cmd_line = get_cmd_line (program )
268+ extra_args = ["-m" , "memory_profiler" , "--timestamp" , "-o" , mprofile_output ]
269+ if args .include_children :
270+ extra_args .append ("--include-children" )
271+ program [1 :1 ] = extra_args
272+ p = subprocess .Popen (program )
226273 else :
227- args .python = True
228- if args .python :
229- print ("running as a Python program..." )
230- if not program [0 ].startswith ("python" ):
231- program .insert (0 , sys .executable )
232- cmd_line = get_cmd_line (program )
233- extra_args = ["-m" , "memory_profiler" , "--timestamp" , "-o" , mprofile_output ]
234- if args .include_children :
235- extra_args .append ("--include-children" )
236- program [1 :1 ] = extra_args
237- p = subprocess .Popen (program )
238- else :
239- cmd_line = get_cmd_line (program )
240- p = subprocess .Popen (program )
274+ cmd_line = get_cmd_line (program )
275+ p = subprocess .Popen (program )
241276
242277 with open (mprofile_output , "a" ) as f :
243278 f .write ("CMDLINE {0}\n " .format (cmd_line ))
244- mp .memory_usage (proc = p , interval = args .interval , timestamps = True ,
279+ mp .memory_usage (proc = p , interval = args .interval , timeout = args . timeout , timestamps = True ,
245280 include_children = args .include_children ,
246281 multiprocess = args .multiprocess , stream = f )
247282
@@ -837,6 +872,7 @@ def main():
837872 "clean" : clean_action ,
838873 "list" : list_action ,
839874 "run" : run_action ,
875+ "attach" : attach_action ,
840876 "plot" : plot_action }
841877 actions [get_action ()]()
842878
0 commit comments