Skip to content

Commit 484dfec

Browse files
committed
Add example of how to use psutil for processes
1 parent fd6353d commit 484dfec

File tree

3 files changed

+200
-2
lines changed

3 files changed

+200
-2
lines changed

source-code/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ to create it. There is some material not covered in the presentation as well.
3737
and the file system.
3838
1. `paramiko`: a few examples of using the Paramiko library for SSH
3939
to remote hosts.
40-
1. `Sched`: scheduled execution of funcitons in Python.
41-
1. `Subprocess`: illustrates executing a shell command from a Python script
40+
1. `processes`: examples of how to work with processes.
41+
1. `sched`: scheduled execution of funcitons in Python.
42+
1. `subprocess`: illustrates executing a shell command from a Python script
4243
using the `subprocess` module.
4344
1. `xml-generator`: code to generate a random XML documents.

source-code/processes/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Processes
2+
3+
Some illustrations of using psutil processes.
4+
5+
## What is it?
6+
7+
1. `monitor.py`: script to monitor processes.

source-code/processes/monitor.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python
2+
3+
from argparse import ArgumentParser
4+
import os
5+
from pathlib import Path
6+
import platform
7+
import psutil
8+
import pwd
9+
import shlex
10+
import sys
11+
import time
12+
13+
14+
class Metric:
15+
16+
def __init__(self, name, action, is_active=True):
17+
self._name = name
18+
self._action = action
19+
self._is_active = is_active
20+
21+
@property
22+
def name(self):
23+
return name
24+
25+
def measure(self, process):
26+
return str(self._action(process))
27+
28+
@property
29+
def is_active(self):
30+
return self._is_active
31+
32+
@is_active.setter
33+
def is_active(self, value):
34+
self._is_active = value
35+
36+
def get_username():
37+
'''Find the user name of the current process'''
38+
return pwd.getpwuid( os.getuid() ).pw_name
39+
40+
41+
def find_ancestor(pid=None, username=None):
42+
'''Find the anceestor of the process with ID pid thatis owned by the user with
43+
the given user name'''
44+
if pid is not None and not psutil.pid_exists(pid):
45+
raise ValueError(f'PID {pid} does not exist')
46+
if username is None:
47+
username = get_username()
48+
process = psutil.Process(pid)
49+
parents = process.parents()
50+
for parent in reversed(parents):
51+
if parent.username() == username:
52+
return parent
53+
54+
55+
def get_cmdline(process):
56+
return f'"{" ".join(shlex.quote(s) for s in process.cmdline())}"'
57+
58+
59+
def get_affinity(process):
60+
affinities = process.cpu_affinity()
61+
return ';'.join(str(affinity) for affinity in affinities)
62+
63+
64+
def get_read_open_files(process):
65+
open_files = list()
66+
try:
67+
for file in process.open_files():
68+
try:
69+
if 'r' == file.mode:
70+
open_files.append(file.path)
71+
except:
72+
pass
73+
except:
74+
pass
75+
return ';'.join(open_files)
76+
77+
78+
def get_write_open_files(process):
79+
open_files = list()
80+
try:
81+
for file in process.open_files():
82+
try:
83+
if 'r' != file.mode:
84+
open_files.append(f'{file.path}:{Path(file.path).stat().st_size}')
85+
except:
86+
pass
87+
except:
88+
pass
89+
return ';'.join(open_files)
90+
91+
92+
def define_actions(inactive=None):
93+
metrics = dict()
94+
metrics['time'] = Metric('time', lambda x: time.time())
95+
metrics['node'] = Metric('node', lambda x: platform.node())
96+
metrics['pid'] = Metric('pid', lambda x: x.pid)
97+
metrics['ppid'] = Metric('ppid', lambda x: x.ppid())
98+
metrics['cmd'] = Metric('cmd', lambda x: x.exe())
99+
metrics['cmdline'] = Metric('cmdline', lambda x: get_cmdline(x))
100+
metrics['cpu_percent'] = Metric('cpu_percent',
101+
lambda x: f'{x.cpu_percent(interval=None):.2f}')
102+
metrics['cpu_user'] = Metric('cpu_user', lambda x: x.cpu_times().user)
103+
metrics['cpu_sys'] = Metric('cpu_sys', lambda x: x.cpu_times().system)
104+
metrics['num_threads'] = Metric('num_threads', lambda x: x.num_threads())
105+
metrics['mem_perpent'] = Metric('mem_perpent', lambda x: f'{x.cpu_percent():.2f}')
106+
metrics['mem'] = Metric('mem', lambda x: x.memory_full_info().uss)
107+
metrics['affinity'] = Metric('affinity', lambda x: get_affinity(x))
108+
metrics['read_files'] = Metric('read_files', lambda x: get_read_open_files(x))
109+
metrics['write_files'] = Metric('write_files', lambda x: get_write_open_files(x))
110+
if inactive:
111+
for metric_name in inactive:
112+
metrics[metric_name].is_active = False
113+
return metrics
114+
115+
116+
def status_header(metrics):
117+
return ','.join(metric_name for metric_name, metric in metrics.items()
118+
if metric.is_active)
119+
120+
121+
def process_status(process, metrics):
122+
'''Show properties of the specified process'''
123+
status = list()
124+
with process.oneshot():
125+
for metric in metrics.values():
126+
if metric.is_active:
127+
status.append(metric.measure(process))
128+
return ','.join(status)
129+
130+
131+
def get_filename_suffix(file_id):
132+
filename = ''
133+
if 'PBS_JOBID' in os.environ:
134+
filename += f'_{os.environ["PBS_JOBID"]}'
135+
if 'PBS_ARRAYID' in os.environ:
136+
filename += f'_{os.environ["PBS_ARRAYJOBID"]}'
137+
if file_id:
138+
filename += f'_{file_id}'
139+
return f'{filename}.txt'
140+
141+
142+
def get_filename(file_id):
143+
return platform.node() + suffix + get_filename_suffix(file_id)
144+
145+
146+
def main():
147+
arg_parser = ArgumentParser(description='monitor processes')
148+
arg_parser.add_argument('--pid', type=int, help='parent process ID ot monitor')
149+
arg_parser.add_argument('--user', help='user of the processes to monitor')
150+
arg_parser.add_argument('--delta', type=float, default=60.0,
151+
help='number of seconds between measurements')
152+
arg_parser.add_argument('--affinity', action='store_true',
153+
help='monitor process affinity')
154+
arg_parser.add_argument('--files', action='store_true', help='monitor poen files')
155+
output_group = arg_parser.add_mutually_exclusive_group()
156+
output_group.add_argument('--output-file', help='name of file to store informatoin')
157+
output_group.add_argument('--stdout', action='store_true',
158+
help='output to standard output')
159+
output_group.add_argument('--file_id', help='identification string for output filename')
160+
options = arg_parser.parse_args()
161+
ancestor = find_ancestor(options.pid, options.user)
162+
inactive = []
163+
if not options.affinity:
164+
inactive.append('affinity')
165+
if not options.files:
166+
inactive.append('read_files')
167+
inactive.append('write_files')
168+
metrics = define_actions(inactive)
169+
if options.stdout:
170+
file = sys.stdout
171+
elif options.output_file:
172+
file = open(options.output_file, 'w')
173+
else:
174+
file = open(get_filename(options.file_id), 'w')
175+
try:
176+
with file:
177+
print(status_header(metrics), file=file)
178+
while True:
179+
process_info = [process_status(ancestor, metrics)]
180+
for process in ancestor.children(recursive=True):
181+
process_info.append(process_status(process, metrics))
182+
print('\n'.join(process_info), file=file)
183+
time.sleep(options.delta)
184+
except KeyboardInterrupt:
185+
pass
186+
187+
188+
if __name__ == '__main__':
189+
status = main()
190+
sys.exit(status)

0 commit comments

Comments
 (0)