Stefan Schuermans commited on 2021-03-09 20:24:11
Showing 3 changed files, with 77 additions and 31 deletions.
... | ... |
@@ -131,6 +131,15 @@ def kb2str(size_kb: int) -> str: |
131 | 131 |
return txt |
132 | 132 |
|
133 | 133 |
|
134 |
+def str2str(str_or_none: str) -> str: |
|
135 |
+ """ |
|
136 |
+ Convert string (or None) to string. |
|
137 |
+ """ |
|
138 |
+ if str_or_none is None: |
|
139 |
+ return '???' |
|
140 |
+ return str_or_none |
|
141 |
+ |
|
142 |
+ |
|
134 | 143 |
def timestamp2str(timestamp: float) -> str: |
135 | 144 |
""" |
136 | 145 |
Convert a timestamp to a human-reable time string." |
... | ... |
@@ -406,6 +415,7 @@ class UptGui: |
406 | 415 |
""" |
407 | 416 |
Show details of process. |
408 | 417 |
""" |
418 |
+ # pylint: disable=R0914 |
|
409 | 419 |
# forget old details |
410 | 420 |
self.wid_details_tree.clear() |
411 | 421 |
# leave if invalid proc_id |
... | ... |
@@ -443,6 +453,13 @@ class UptGui: |
443 | 453 |
add(f'{key} {i:d}', value, list_iter) |
444 | 454 |
return list_iter |
445 | 455 |
|
456 |
+ def add_list_sorted(key: str, values: list, parent_iter=None): |
|
457 |
+ """ |
|
458 |
+ Wrapper for add_list(...) that sorts the list (if any) before. |
|
459 |
+ """ |
|
460 |
+ values_sorted = sorted(values) if values is not None else None |
|
461 |
+ return add_list(key, values_sorted, parent_iter) |
|
462 |
+ |
|
446 | 463 |
def add_sum(key: str, sub_keys: list, values: list, parent_iter=None): |
447 | 464 |
""" |
448 | 465 |
Add a sum of multiple values to a process and include individual |
... | ... |
@@ -466,19 +483,18 @@ class UptGui: |
466 | 483 |
[proc.n_iv_csw, proc.n_v_csw]) |
467 | 484 |
add('CPU time', duration2str(proc.cpu_time)) |
468 | 485 |
add('end time', timestamp2str(proc.end_timestamp)) |
469 |
- add_list('environment', |
|
470 |
- sorted(proc.environ) if proc.environ is not None else None) |
|
471 |
- add('executable', proc.exe) |
|
486 |
+ add_list_sorted('environment', proc.environ) |
|
487 |
+ add('executable', str2str(proc.exe)) |
|
472 | 488 |
add_sum('file system operations', ['input', 'output'], |
473 | 489 |
[proc.in_block, proc.ou_block]) |
474 | 490 |
add('max. resident memory', kb2str(proc.max_rss_kb)) |
475 | 491 |
add_sum('page faults', ['major', 'minor'], |
476 | 492 |
[proc.maj_flt, proc.min_flt]) |
477 |
- add('pid', str(proc.pid)) |
|
478 |
- add('ppid', str(proc.ppid)) |
|
493 |
+ add('pid', int2str(proc.pid)) |
|
494 |
+ add('ppid', int2str(proc.ppid)) |
|
479 | 495 |
add('system CPU time', duration2str(proc.sys_time)) |
480 | 496 |
add('user CPU time', duration2str(proc.user_time)) |
481 |
- add('working directory', proc.cwd) |
|
497 |
+ add('working directory', str2str(proc.cwd)) |
|
482 | 498 |
# add parent |
483 | 499 |
parent_proc = proc.parent |
484 | 500 |
if parent_proc is None: |
... | ... |
@@ -5,6 +5,7 @@ |
5 | 5 |
Processes in a trace file. |
6 | 6 |
""" |
7 | 7 |
|
8 |
+import collections |
|
8 | 9 |
import uproctrace.parse |
9 | 10 |
|
10 | 11 |
|
... | ... |
@@ -23,7 +24,7 @@ class Process(): |
23 | 24 |
self._begin = None |
24 | 25 |
self._end = None |
25 | 26 |
self._parent = None |
26 |
- self._children = list() |
|
27 |
+ self._children = collections.OrderedDict() # proc_id -> Process |
|
27 | 28 |
|
28 | 29 |
@property |
29 | 30 |
def begin_timestamp(self) -> list: |
... | ... |
@@ -39,7 +40,7 @@ class Process(): |
39 | 40 |
""" |
40 | 41 |
List of child processes. |
41 | 42 |
""" |
42 |
- return self._children.copy() |
|
43 |
+ return list(self._children.values()) |
|
43 | 44 |
|
44 | 45 |
@property |
45 | 46 |
def cmdline(self) -> list: |
... | ... |
@@ -214,7 +215,14 @@ class Process(): |
214 | 215 |
""" |
215 | 216 |
Add a child process. |
216 | 217 |
""" |
217 |
- self._children.append(child) |
|
218 |
+ self._children[child.proc_id] = child |
|
219 |
+ |
|
220 |
+ def removeChild(self, child_proc_id: int): |
|
221 |
+ """ |
|
222 |
+ Remove a child process. |
|
223 |
+ """ |
|
224 |
+ if child_proc_id in self._children: |
|
225 |
+ del self._children[child_proc_id] |
|
218 | 226 |
|
219 | 227 |
def setBegin(self, proc_begin: uproctrace.parse.ProcBegin): |
220 | 228 |
""" |
... | ... |
@@ -247,18 +255,48 @@ class Processes(uproctrace.parse.Visitor): |
247 | 255 |
self._timeline = dict() # time -> list(parse.BaseEvent) |
248 | 256 |
self._all_processes = dict() # proc_id -> process |
249 | 257 |
self._current_processes = dict() # pid -> process (while pid alive) |
250 |
- self._toplevel_processes = list() # list of processes without parent |
|
258 |
+ # ordered dictionary of toplevel processes: proc_id -> Process |
|
259 |
+ self._toplevel_processes = collections.OrderedDict() |
|
260 |
+ # parse trace |
|
251 | 261 |
self._readTrace(proto_file) |
252 | 262 |
|
253 |
- def _newProcess(self, pid: int): |
|
263 |
+ def _getProcess(self, pid: int) -> Process: |
|
254 | 264 |
""" |
255 |
- Create new process, set its PID, store it and return it. |
|
265 |
+ Get process with passed PID. |
|
266 |
+ Create it if it does not exist. |
|
267 |
+ Return process. |
|
268 |
+ """ |
|
269 |
+ if pid in self._current_processes: |
|
270 |
+ return self._current_processes[pid] |
|
271 |
+ return self._newProcess(pid) |
|
272 |
+ |
|
273 |
+ def _newProcess(self, pid: int) -> Process: |
|
274 |
+ """ |
|
275 |
+ Create new process and set its PID. |
|
276 |
+ Store it in all processes and current processes. |
|
277 |
+ Make it a toplevel process initially (may be changed by caller). |
|
278 |
+ Return new process. |
|
256 | 279 |
""" |
257 | 280 |
proc_id = len(self._all_processes) |
258 | 281 |
proc = Process(proc_id, pid) |
259 | 282 |
self._all_processes[proc_id] = proc |
283 |
+ self._current_processes[pid] = proc |
|
284 |
+ self._toplevel_processes[proc_id] = proc |
|
260 | 285 |
return proc |
261 | 286 |
|
287 |
+ def _parentChild(self, parent: Process, child: Process): |
|
288 |
+ """ |
|
289 |
+ Establish parent/child relationship of two processes. |
|
290 |
+ """ |
|
291 |
+ # terminate old relationship (if any) |
|
292 |
+ if child.parent is not None: |
|
293 |
+ child.parent.removeChild(child.proc_id) |
|
294 |
+ if child.proc_id in self._toplevel_processes: |
|
295 |
+ del self._toplevel_processes[child.proc_id] |
|
296 |
+ # establish new relationship |
|
297 |
+ parent.addChild(child) |
|
298 |
+ child.setParent(parent) |
|
299 |
+ |
|
262 | 300 |
def _readTrace(self, proto_file): |
263 | 301 |
""" |
264 | 302 |
Read events from trace file (proto_file) and add them. |
... | ... |
@@ -274,11 +312,11 @@ class Processes(uproctrace.parse.Visitor): |
274 | 312 |
self._timeline.setdefault(event.timestamp, list()).append(event) |
275 | 313 |
|
276 | 314 |
@property |
277 |
- def toplevel(self): |
|
315 |
+ def toplevel(self) -> list: |
|
278 | 316 |
""" |
279 | 317 |
List of toplevel processes. |
280 | 318 |
""" |
281 |
- return self._toplevel_processes.copy() |
|
319 |
+ return list(self._toplevel_processes.values()) |
|
282 | 320 |
|
283 | 321 |
def getAllProcesses(self) -> dict: |
284 | 322 |
""" |
... | ... |
@@ -286,7 +324,7 @@ class Processes(uproctrace.parse.Visitor): |
286 | 324 |
""" |
287 | 325 |
return self._all_processes.copy() |
288 | 326 |
|
289 |
- def getProcess(self, proc_id: int): |
|
327 |
+ def getProcess(self, proc_id: int) -> Process: |
|
290 | 328 |
""" |
291 | 329 |
Return process with proc_id, or None if not found. |
292 | 330 |
""" |
... | ... |
@@ -299,33 +337,24 @@ class Processes(uproctrace.parse.Visitor): |
299 | 337 |
self._visitBaseEvent(proc_begin) |
300 | 338 |
# new process |
301 | 339 |
proc = self._newProcess(proc_begin.pid) |
302 |
- # add process to dict of current processes |
|
303 |
- self._current_processes[proc_begin.pid] = proc |
|
304 | 340 |
# set begin event of process and process of begin event |
305 | 341 |
proc.setBegin(proc_begin) |
306 | 342 |
proc_begin.setProcess(proc) |
307 |
- # connect to parent |
|
308 |
- if proc_begin.ppid in self._current_processes: |
|
309 |
- parent = self._current_processes[proc_begin.ppid] |
|
310 |
- proc.setParent(parent) |
|
311 |
- parent.addChild(proc) |
|
312 |
- else: |
|
313 |
- self._toplevel_processes.append(proc) |
|
343 |
+ # add process to parent if available |
|
344 |
+ if proc_begin.ppid is not None: |
|
345 |
+ parent = self._getProcess(proc_begin.ppid) |
|
346 |
+ self._parentChild(parent, proc) |
|
314 | 347 |
|
315 | 348 |
def visitProcEnd(self, proc_end: uproctrace.parse.ProcEnd): |
316 | 349 |
""" |
317 | 350 |
Process a process end event. |
318 | 351 |
""" |
319 | 352 |
self._visitBaseEvent(proc_end) |
320 |
- # get process (or create it if it is not known) |
|
321 |
- if proc_end.pid in self._current_processes: |
|
322 |
- proc = self._current_processes[proc_end.pid] |
|
323 |
- else: |
|
324 |
- proc = self._newProcess(proc_end.pid) |
|
325 |
- self._toplevel_processes.append(proc) # unknown parent -> toplevel |
|
353 |
+ # get process |
|
354 |
+ proc = self._getProcess(proc_end.pid) |
|
326 | 355 |
# set end event of process and process of end event |
327 | 356 |
proc.setEnd(proc_end) |
328 | 357 |
proc_end.setProcess(proc) |
329 | 358 |
# remove process from dict of current processes (it ended) |
330 |
- if proc_end.pid in self._current_processes: |
|
359 |
+ # - it is guaranteed to be in it, because it came from _getProcess() |
|
331 | 360 |
del self._current_processes[proc_end.pid] |