add processes that occur in trace as parents
Stefan Schuermans

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]
... ...
@@ -1,3 +1,4 @@
1
+???
1 2
   __UPT_HOME__/tests/fork/forkapp
2 3
     /bin/ls
3 4
     ???
4 5