##// END OF EJS Templates
%tasks shows elapsed time in min:sec
vivainio -
Show More
@@ -1,161 +1,162 b''
1 """ Preliminary "job control" extensions for IPython
1 """ Preliminary "job control" extensions for IPython
2
2
3 requires python 2.4 (or separate 'subprocess' module
3 requires python 2.4 (or separate 'subprocess' module
4
4
5 This provides 2 features, launching background jobs and killing foreground jobs from another IPython instance.
5 This provides 2 features, launching background jobs and killing foreground jobs from another IPython instance.
6
6
7 Launching background jobs:
7 Launching background jobs:
8
8
9 Usage:
9 Usage:
10
10
11 [ipython]|2> import jobctrl
11 [ipython]|2> import jobctrl
12 [ipython]|3> &ls
12 [ipython]|3> &ls
13 <3> <jobctrl.IpyPopen object at 0x00D87FD0>
13 <3> <jobctrl.IpyPopen object at 0x00D87FD0>
14 [ipython]|4> _3.go
14 [ipython]|4> _3.go
15 -----------> _3.go()
15 -----------> _3.go()
16 ChangeLog
16 ChangeLog
17 IPython
17 IPython
18 MANIFEST.in
18 MANIFEST.in
19 README
19 README
20 README_Windows.txt
20 README_Windows.txt
21
21
22 ...
22 ...
23
23
24 Killing foreground tasks:
24 Killing foreground tasks:
25
25
26 Launch IPython instance, run a blocking command:
26 Launch IPython instance, run a blocking command:
27
27
28 [Q:/ipython]|1> import jobctrl
28 [Q:/ipython]|1> import jobctrl
29 [Q:/ipython]|2> cat
29 [Q:/ipython]|2> cat
30
30
31 Now launch a new IPython prompt and kill the process:
31 Now launch a new IPython prompt and kill the process:
32
32
33 IPython 0.8.3.svn.r2919 [on Py 2.5]
33 IPython 0.8.3.svn.r2919 [on Py 2.5]
34 [Q:/ipython]|1> import jobctrl
34 [Q:/ipython]|1> import jobctrl
35 [Q:/ipython]|2> %tasks
35 [Q:/ipython]|2> %tasks
36 6020: 'cat ' (Q:\ipython)
36 6020: 'cat ' (Q:\ipython)
37 [Q:/ipython]|3> %kill
37 [Q:/ipython]|3> %kill
38 SUCCESS: The process with PID 6020 has been terminated.
38 SUCCESS: The process with PID 6020 has been terminated.
39 [Q:/ipython]|4>
39 [Q:/ipython]|4>
40
40
41 (you don't need to specify PID for %kill if only one task is running)
41 (you don't need to specify PID for %kill if only one task is running)
42 """
42 """
43
43
44 from subprocess import Popen,PIPE
44 from subprocess import Popen,PIPE
45 import os,shlex,sys,time
45 import os,shlex,sys,time
46
46
47 from IPython import genutils
47 from IPython import genutils
48
48
49 import IPython.ipapi
49 import IPython.ipapi
50
50
51 if os.name == 'nt':
51 if os.name == 'nt':
52 def kill_process(pid):
52 def kill_process(pid):
53 os.system('taskkill /F /PID %d' % pid)
53 os.system('taskkill /F /PID %d' % pid)
54 else:
54 else:
55 def kill_process(pid):
55 def kill_process(pid):
56 os.system('kill -9 %d' % pid)
56 os.system('kill -9 %d' % pid)
57
57
58
58
59
59
60 class IpyPopen(Popen):
60 class IpyPopen(Popen):
61 def go(self):
61 def go(self):
62 print self.communicate()[0]
62 print self.communicate()[0]
63 def __repr__(self):
63 def __repr__(self):
64 return '<IPython job "%s" PID=%d>' % (self.line, self.pid)
64 return '<IPython job "%s" PID=%d>' % (self.line, self.pid)
65
65
66 def kill(self):
66 def kill(self):
67 kill_process(self.pid)
67 kill_process(self.pid)
68
68
69 def startjob(job):
69 def startjob(job):
70 p = IpyPopen(shlex.split(job), stdout=PIPE, shell = False)
70 p = IpyPopen(shlex.split(job), stdout=PIPE, shell = False)
71 p.line = job
71 p.line = job
72 return p
72 return p
73
73
74 def jobctrl_prefilter_f(self,line):
74 def jobctrl_prefilter_f(self,line):
75 if line.startswith('&'):
75 if line.startswith('&'):
76 pre,fn,rest = self.split_user_input(line[1:])
76 pre,fn,rest = self.split_user_input(line[1:])
77
77
78 line = ip.IP.expand_aliases(fn,rest)
78 line = ip.IP.expand_aliases(fn,rest)
79 return '_ip.startjob(%s)' % genutils.make_quoted_expr(line)
79 return '_ip.startjob(%s)' % genutils.make_quoted_expr(line)
80
80
81 raise IPython.ipapi.TryNext
81 raise IPython.ipapi.TryNext
82
82
83
83
84 def job_list(ip):
84 def job_list(ip):
85 keys = ip.db.keys('tasks/*')
85 keys = ip.db.keys('tasks/*')
86 ents = [ip.db[k] for k in keys]
86 ents = [ip.db[k] for k in keys]
87 return ents
87 return ents
88
88
89 def magic_tasks(self,line):
89 def magic_tasks(self,line):
90 """ Show a list of tasks.
90 """ Show a list of tasks.
91
91
92 A 'task' is a process that has been started in IPython when 'jobctrl' extension is enabled.
92 A 'task' is a process that has been started in IPython when 'jobctrl' extension is enabled.
93 Tasks can be killed with %kill.
93 Tasks can be killed with %kill.
94 """
94 """
95 ip = self.getapi()
95 ip = self.getapi()
96 ents = job_list(ip)
96 ents = job_list(ip)
97 if not ents:
97 if not ents:
98 print "No tasks running"
98 print "No tasks running"
99 for pid,cmd,cwd,t in ents:
99 for pid,cmd,cwd,t in ents:
100 print "%d: '%s' (%s)" % (pid,cmd,cwd)
100 dur = int(time.time()-t)
101 print "%d: '%s' (%s) %d:%02d" % (pid,cmd,cwd, dur / 60,dur%60)
101
102
102 def magic_kill(self,line):
103 def magic_kill(self,line):
103 """ Kill a task
104 """ Kill a task
104
105
105 Without args, either kill one task (if only one running) or show list (if many)
106 Without args, either kill one task (if only one running) or show list (if many)
106 With arg, assume it's the process id.
107 With arg, assume it's the process id.
107
108
108 %kill is typically (much) more powerful than trying to terminate a process with ctrl+C.
109 %kill is typically (much) more powerful than trying to terminate a process with ctrl+C.
109 """
110 """
110 ip = self.getapi()
111 ip = self.getapi()
111 jobs = job_list(ip)
112 jobs = job_list(ip)
112
113
113 if not line.strip():
114 if not line.strip():
114 if len(jobs) == 1:
115 if len(jobs) == 1:
115 kill_process(jobs[0][0])
116 kill_process(jobs[0][0])
116 else:
117 else:
117 magic_tasks(self,line)
118 magic_tasks(self,line)
118 return
119 return
119
120
120 try:
121 try:
121 pid = int(line)
122 pid = int(line)
122 kill_process(pid)
123 kill_process(pid)
123 except ValueError:
124 except ValueError:
124 magic_tasks(self,line)
125 magic_tasks(self,line)
125
126
126 if sys.platform == 'win32':
127 if sys.platform == 'win32':
127 shell_internal_commands = 'break chcp cls copy ctty date del erase dir md mkdir path prompt rd rmdir time type ver vol'.split()
128 shell_internal_commands = 'break chcp cls copy ctty date del erase dir md mkdir path prompt rd rmdir time type ver vol'.split()
128 else:
129 else:
129 # todo linux commands
130 # todo linux commands
130 shell_internal_commands = []
131 shell_internal_commands = []
131
132
132
133
133 def jobctrl_shellcmd(ip,cmd):
134 def jobctrl_shellcmd(ip,cmd):
134 """ os.system replacement that stores process info to db['tasks/t1234'] """
135 """ os.system replacement that stores process info to db['tasks/t1234'] """
135 cmdname = cmd.split(None,1)[0]
136 cmdname = cmd.split(None,1)[0]
136 if cmdname in shell_internal_commands:
137 if cmdname in shell_internal_commands:
137 use_shell = True
138 use_shell = True
138 else:
139 else:
139 use_shell = False
140 use_shell = False
140
141
141 p = Popen(cmd,shell = use_shell)
142 p = Popen(cmd,shell = use_shell)
142 jobentry = 'tasks/t' + str(p.pid)
143 jobentry = 'tasks/t' + str(p.pid)
143
144
144 try:
145 try:
145 ip.db[jobentry] = (p.pid,cmd,os.getcwd(),time.time())
146 ip.db[jobentry] = (p.pid,cmd,os.getcwd(),time.time())
146 p.communicate()
147 p.communicate()
147 finally:
148 finally:
148 del ip.db[jobentry]
149 del ip.db[jobentry]
149
150
150
151
151 def install():
152 def install():
152 global ip
153 global ip
153 ip = IPython.ipapi.get()
154 ip = IPython.ipapi.get()
154 # needed to make startjob visible as _ip.startjob('blah')
155 # needed to make startjob visible as _ip.startjob('blah')
155 ip.startjob = startjob
156 ip.startjob = startjob
156 ip.set_hook('input_prefilter', jobctrl_prefilter_f)
157 ip.set_hook('input_prefilter', jobctrl_prefilter_f)
157 ip.set_hook('shell_hook', jobctrl_shellcmd)
158 ip.set_hook('shell_hook', jobctrl_shellcmd)
158 ip.expose_magic('kill',magic_kill)
159 ip.expose_magic('kill',magic_kill)
159 ip.expose_magic('tasks',magic_tasks)
160 ip.expose_magic('tasks',magic_tasks)
160
161
161 install()
162 install()
General Comments 0
You need to be logged in to leave comments. Login now