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] |