upt-gui: implement details view
Stefan Schuermans

Stefan Schuermans commited on 2020-05-24 10:00:54
Showing 2 changed files, with 213 additions and 23 deletions.

... ...
@@ -2,6 +2,14 @@
2 2
 <!-- Generated with glade 3.22.1 -->
3 3
 <interface>
4 4
   <requires lib="gtk+" version="3.20"/>
5
+  <object class="GtkTreeStore" id="DetailsTree">
6
+    <columns>
7
+      <!-- column-name key -->
8
+      <column type="gchararray"/>
9
+      <!-- column-name value -->
10
+      <column type="gchararray"/>
11
+    </columns>
12
+  </object>
5 13
   <object class="GtkTreeStore" id="ProcessesTree">
6 14
     <columns>
7 15
       <!-- column-name proc_id -->
... ...
@@ -42,16 +50,16 @@
42 50
                     <property name="visible">True</property>
43 51
                     <property name="can_focus">True</property>
44 52
                     <property name="model">ProcessesTree</property>
45
-                    <property name="reorderable">True</property>
46 53
                     <property name="rules_hint">True</property>
47 54
                     <property name="search_column">0</property>
48 55
                     <property name="fixed_height_mode">True</property>
49 56
                     <property name="enable_tree_lines">True</property>
57
+                    <signal name="cursor-changed" handler="onProcessesCursorChanged" swapped="no"/>
50 58
                     <child internal-child="selection">
51 59
                       <object class="GtkTreeSelection"/>
52 60
                     </child>
53 61
                     <child>
54
-                      <object class="GtkTreeViewColumn" id="Begin">
62
+                      <object class="GtkTreeViewColumn" id="ProcessesBeginCol">
55 63
                         <property name="resizable">True</property>
56 64
                         <property name="sizing">fixed</property>
57 65
                         <property name="title" translatable="yes">Begin</property>
... ...
@@ -60,7 +68,7 @@
60 68
                         <property name="sort_indicator">True</property>
61 69
                         <property name="sort_column_id">0</property>
62 70
                         <child>
63
-                          <object class="GtkCellRendererText" id="BeginText"/>
71
+                          <object class="GtkCellRendererText" id="ProcessesBeginText"/>
64 72
                           <attributes>
65 73
                             <attribute name="text">1</attribute>
66 74
                           </attributes>
... ...
@@ -68,7 +76,7 @@
68 76
                       </object>
69 77
                     </child>
70 78
                     <child>
71
-                      <object class="GtkTreeViewColumn" id="End">
79
+                      <object class="GtkTreeViewColumn" id="ProcessesEndCol">
72 80
                         <property name="resizable">True</property>
73 81
                         <property name="sizing">fixed</property>
74 82
                         <property name="title" translatable="yes">End</property>
... ...
@@ -77,7 +85,7 @@
77 85
                         <property name="sort_indicator">True</property>
78 86
                         <property name="sort_column_id">1</property>
79 87
                         <child>
80
-                          <object class="GtkCellRendererText" id="EndText"/>
88
+                          <object class="GtkCellRendererText" id="ProcessesEndText"/>
81 89
                           <attributes>
82 90
                             <attribute name="text">2</attribute>
83 91
                           </attributes>
... ...
@@ -85,7 +93,7 @@
85 93
                       </object>
86 94
                     </child>
87 95
                     <child>
88
-                      <object class="GtkTreeViewColumn" id="Command">
96
+                      <object class="GtkTreeViewColumn" id="ProcessesCommandCol">
89 97
                         <property name="resizable">True</property>
90 98
                         <property name="sizing">fixed</property>
91 99
                         <property name="title" translatable="yes">Command</property>
... ...
@@ -94,7 +102,7 @@
94 102
                         <property name="sort_indicator">True</property>
95 103
                         <property name="sort_column_id">2</property>
96 104
                         <child>
97
-                          <object class="GtkCellRendererText" id="CommandText"/>
105
+                          <object class="GtkCellRendererText" id="ProcessesCommandText"/>
98 106
                           <attributes>
99 107
                             <attribute name="text">3</attribute>
100 108
                           </attributes>
... ...
@@ -111,7 +119,66 @@
111 119
               </packing>
112 120
             </child>
113 121
             <child>
114
-              <placeholder/>
122
+              <object class="GtkScrolledWindow" id="DetailsScroll">
123
+                <property name="width_request">256</property>
124
+                <property name="height_request">256</property>
125
+                <property name="visible">True</property>
126
+                <property name="can_focus">True</property>
127
+                <property name="shadow_type">in</property>
128
+                <child>
129
+                  <object class="GtkTreeView" id="DetailsView">
130
+                    <property name="visible">True</property>
131
+                    <property name="can_focus">True</property>
132
+                    <property name="model">DetailsTree</property>
133
+                    <property name="rules_hint">True</property>
134
+                    <property name="search_column">0</property>
135
+                    <property name="fixed_height_mode">True</property>
136
+                    <property name="enable_tree_lines">True</property>
137
+                    <child internal-child="selection">
138
+                      <object class="GtkTreeSelection"/>
139
+                    </child>
140
+                    <child>
141
+                      <object class="GtkTreeViewColumn" id="DetailsKeyCol">
142
+                        <property name="resizable">True</property>
143
+                        <property name="sizing">fixed</property>
144
+                        <property name="title" translatable="yes">Key</property>
145
+                        <property name="clickable">True</property>
146
+                        <property name="reorderable">True</property>
147
+                        <property name="sort_indicator">True</property>
148
+                        <property name="sort_column_id">0</property>
149
+                        <child>
150
+                          <object class="GtkCellRendererText" id="DetailsKeyText"/>
151
+                          <attributes>
152
+                            <attribute name="text">0</attribute>
153
+                          </attributes>
154
+                        </child>
155
+                      </object>
156
+                    </child>
157
+                    <child>
158
+                      <object class="GtkTreeViewColumn" id="DetailsValueCol">
159
+                        <property name="resizable">True</property>
160
+                        <property name="sizing">fixed</property>
161
+                        <property name="title" translatable="yes">Value</property>
162
+                        <property name="clickable">True</property>
163
+                        <property name="reorderable">True</property>
164
+                        <property name="sort_indicator">True</property>
165
+                        <property name="sort_column_id">1</property>
166
+                        <child>
167
+                          <object class="GtkCellRendererText" id="DetailsValueText"/>
168
+                          <attributes>
169
+                            <attribute name="text">1</attribute>
170
+                          </attributes>
171
+                        </child>
172
+                      </object>
173
+                    </child>
174
+                  </object>
175
+                </child>
176
+              </object>
177
+              <packing>
178
+                <property name="expand">True</property>
179
+                <property name="fill">True</property>
180
+                <property name="position">1</property>
181
+              </packing>
115 182
             </child>
116 183
           </object>
117 184
           <packing>
... ...
@@ -1,16 +1,11 @@
1 1
 #! /usr/bin/env python3
2 2
 
3
-import cairo
4
-import datetime
5 3
 import gi
6 4
 import os
7 5
 import shlex
8 6
 import sys
9 7
 import time
10
-gi.require_version('Gdk', '3.0')
11 8
 gi.require_version('Gtk', '3.0')
12
-from gi.repository import Gdk
13
-from gi.repository import GLib
14 9
 from gi.repository import Gtk
15 10
 
16 11
 import uproctrace.processes
... ...
@@ -21,15 +16,77 @@ def cmdline2str(cmdline: list) -> str:
21 16
     Convert command line to string.
22 17
     """
23 18
     if cmdline is None:
24
-        return "???"
19
+        return '???'
25 20
     return ' '.join([shlex.quote(s) for s in cmdline])
26 21
 
22
+
23
+def duration2str(duration: float) -> str:
24
+    """
25
+    Convert duration to string.
26
+    """
27
+    if duration is None:
28
+        return '???'
29
+    # split into day, hours, minutes, seconds
30
+    s = int(duration)
31
+    m = s // 60
32
+    s = s % 60
33
+    h = m // 60
34
+    m = m % 60
35
+    d = h // 24
36
+    h = h % 24
37
+    # split into ms, us, ns
38
+    ns = int((duration - s) * 1e9)
39
+    us = ns // 1000
40
+    ns = ns % 1000
41
+    ms = us // 1000
42
+    us = us % 1000
43
+    # assemble text
44
+    txt = ''
45
+    if d > 0:
46
+        txt += f'{d:d} d '
47
+    if h > 0 or txt:
48
+        txt += f'{h:d} h '
49
+    if m > 0 or txt:
50
+        txt += f'{m:d} m '
51
+    if s > 0 or txt:
52
+        txt += f'{s:d} s '
53
+    if ms > 0 or txt:
54
+        txt += f'{ms:d} ms '
55
+    if us > 0 or txt:
56
+        txt += f'{us:d} us '
57
+    txt += f'{ns:d} ns '
58
+    txt += f'({duration:f} s)'
59
+    return txt
60
+
61
+
62
+def kb2str(kb: int) -> str:
63
+    """
64
+    Convert size in KiB to string.
65
+    """
66
+    if kb is None:
67
+        return '???'
68
+    # split into GiB, MiB, KiB
69
+    mib = kb // 1024
70
+    kib = kb % 1024
71
+    gib = mib // 1024
72
+    mib = mib % 1024
73
+    # assemble text
74
+    txt = ''
75
+    if gib > 0:
76
+        txt += f'{gib:d} GiB '
77
+    if mib > 0 or txt:
78
+        txt += f'{mib:d} MiB '
79
+    txt += f'{kib:d} KiB '
80
+    txt += f'({kb:d} KiB)'
81
+    return txt
82
+
83
+
27 84
 def timestamp2str(timestamp: float) -> str:
28 85
     """
29 86
     Convert a timestamp to a human-reable time string."
30 87
     """
31 88
     if timestamp is None:
32
-        return "???"
89
+        return '???'
33 90
     sec = int(timestamp)
34 91
     nsec = int((timestamp - sec) * 1e9)
35 92
     time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(sec))
... ...
@@ -37,6 +94,14 @@ def timestamp2str(timestamp: float) -> str:
37 94
 
38 95
 
39 96
 class UptGui:
97
+
98
+    DETAIL_KEY = 0
99
+    DETAIL_VALUE = 1
100
+    PROC_PROC_ID = 0
101
+    PROC_BEGIN = 1
102
+    PROC_END = 2
103
+    PROC_COMMAND = 3
104
+
40 105
     def __init__(self, proto_filename):
41 106
         """
42 107
         Construct the GUI.
... ...
@@ -44,9 +109,14 @@ class UptGui:
44 109
         self.builder = Gtk.Builder()
45 110
         script_dir = os.path.dirname(os.path.abspath(__file__))
46 111
         self.builder.add_from_file(os.path.join(script_dir, 'upt-gui.glade'))
112
+        self.widDetailsTree = self.builder.get_object('DetailsTree')
113
+        self.widDetailsView = self.builder.get_object('DetailsView')
47 114
         self.widProcessesTree = self.builder.get_object('ProcessesTree')
48 115
         self.widProcessesView = self.builder.get_object('ProcessesView')
49
-        handlers = {'onDestroy': self.onDestroy}
116
+        handlers = {
117
+            'onDestroy': self.onDestroy,
118
+            'onProcessesCursorChanged': self.onProcessesCursorChanged
119
+        }
50 120
         self.builder.connect_signals(handlers)
51 121
         # open trace file
52 122
         self.openTrace(proto_filename)
... ...
@@ -57,17 +127,65 @@ class UptGui:
57 127
         """
58 128
         Gtk.main_quit()
59 129
 
130
+    def onProcessesCursorChanged(self, widget):
131
+        """
132
+        Cursor changed in processes tree view.
133
+        """
134
+        # get proc_id of selected process
135
+        proc_id = None
136
+        proc_sel = self.widProcessesView.get_selection()
137
+        if proc_sel is not None:
138
+            proc_iter = proc_sel.get_selected()[1]
139
+            if proc_iter is not None:
140
+                proc_id = self.widProcessesTree.get_value(
141
+                    proc_iter, self.PROC_PROC_ID)
142
+        # forget old details
143
+        self.widDetailsTree.clear()
144
+        # leave if no process selected
145
+        if proc_id is None:
146
+            return
147
+        # get process, leave if not found
148
+        proc = self.processes.getProcess(proc_id)
149
+        if proc is None:
150
+            return
151
+        # add details of new process
152
+        def add(key: str, value: str, parent_iter = None):
153
+            detail_iter = self.widDetailsTree.append(parent_iter)
154
+            self.widDetailsTree.set_value(detail_iter, self.DETAIL_KEY, key)
155
+            self.widDetailsTree.set_value(detail_iter, self.DETAIL_VALUE,
156
+                                          value)
157
+            return detail_iter
158
+
159
+        def add_list(key: str, values: list, parent_iter = None):
160
+            if values is None:
161
+                return add(key, '???', parent_iter)
162
+            list_iter = add(key, f'{len(values):d} entries', parent_iter)
163
+            for i, value in enumerate(values):
164
+                add(f'{key} {i:d}', value, list_iter)
165
+
166
+        add('begin time', timestamp2str(proc.begin_timestamp))
167
+        add_list('command line', proc.cmdline)
168
+        add('CPU time', duration2str(proc.cpu_time))
169
+        add('end time', timestamp2str(proc.end_timestamp))
170
+        add_list('environment', sorted(proc.environ))
171
+        add('executable', proc.exe)
172
+        add('max. resident memory', kb2str(proc.max_rss_kb))
173
+        add('system CPU time', duration2str(proc.sys_time))
174
+        add('user CPU time', duration2str(proc.user_time))
175
+        add('working directory', proc.cwd)
176
+        # TODO
177
+
60 178
     def openTrace(self, proto_filename: str):
61 179
         """
62 180
         Open a trace file.
63 181
         """
64
-        # forget old data
182
+        # forget old processes
65 183
         self.widProcessesTree.clear()
66 184
         # lead new data
67 185
         with open(proto_filename, 'rb') as proto_file:
68
-            self._processes = uproctrace.processes.Processes(proto_file)
186
+            self.processes = uproctrace.processes.Processes(proto_file)
69 187
         # add processes to processes tree store
70
-        to_be_output = [(self._processes.toplevel, None)]
188
+        to_be_output = [(self.processes.toplevel, None)]
71 189
         while to_be_output:
72 190
             procs, parent_iter = to_be_output[-1]
73 191
             if not procs:
... ...
@@ -76,10 +194,15 @@ class UptGui:
76 194
             proc = procs[0]
77 195
             del procs[0]
78 196
             proc_iter = self.widProcessesTree.append(parent_iter)
79
-            self.widProcessesTree.set_value(proc_iter, 0, proc.proc_id)
80
-            self.widProcessesTree.set_value(proc_iter, 1, timestamp2str(proc.begin_timestamp))
81
-            self.widProcessesTree.set_value(proc_iter, 2, timestamp2str(proc.end_timestamp))
82
-            self.widProcessesTree.set_value(proc_iter, 3, cmdline2str(proc.cmdline))
197
+            self.widProcessesTree.set_value(proc_iter, self.PROC_PROC_ID,
198
+                                            proc.proc_id)
199
+            self.widProcessesTree.set_value(
200
+                proc_iter, self.PROC_BEGIN,
201
+                timestamp2str(proc.begin_timestamp))
202
+            self.widProcessesTree.set_value(proc_iter, self.PROC_END,
203
+                                            timestamp2str(proc.end_timestamp))
204
+            self.widProcessesTree.set_value(proc_iter, self.PROC_COMMAND,
205
+                                            cmdline2str(proc.cmdline))
83 206
             to_be_output.append((proc.children, proc_iter))
84 207
         # show all processes
85 208
         self.widProcessesView.expand_all()
86 209