add more fields from getrusage to proc_end
Stefan Schuermans

Stefan Schuermans commited on 2020-08-16 11:04:25
Showing 6 changed files, with 298 additions and 35 deletions.

... ...
@@ -46,6 +46,18 @@ int uptev_proc_end(void **data, size_t *size) {
46 46
     proc_end.sys_time = &sys_time;
47 47
     proc_end.has_max_rss_kb = 1;
48 48
     proc_end.max_rss_kb = usage.ru_maxrss;
49
+    proc_end.has_min_flt = 1;
50
+    proc_end.min_flt = usage.ru_minflt;
51
+    proc_end.has_maj_flt = 1;
52
+    proc_end.maj_flt = usage.ru_majflt;
53
+    proc_end.has_in_block = 1;
54
+    proc_end.in_block = usage.ru_inblock;
55
+    proc_end.has_ou_block = 1;
56
+    proc_end.ou_block = usage.ru_oublock;
57
+    proc_end.has_n_v_csw = 1;
58
+    proc_end.n_v_csw = usage.ru_nvcsw;
59
+    proc_end.has_n_iv_csw = 1;
60
+    proc_end.n_iv_csw = usage.ru_nivcsw;
49 61
   }
50 62
 
51 63
   struct _Uproctrace__Event event = UPROCTRACE__EVENT__INIT;
... ...
@@ -34,6 +34,18 @@
34 34
       <column type="gint"/>
35 35
       <!-- column-name max_rss_kb_text -->
36 36
       <column type="gchararray"/>
37
+      <!-- column-name page_faults -->
38
+      <column type="gint"/>
39
+      <!-- column-name page_faults_text -->
40
+      <column type="gchararray"/>
41
+      <!-- column-name file_sys_ops -->
42
+      <column type="gint"/>
43
+      <!-- column-name file_sys_ops_text -->
44
+      <column type="gchararray"/>
45
+      <!-- column-name ctx_sw -->
46
+      <column type="gint"/>
47
+      <!-- column-name ctx_sw_text -->
48
+      <column type="gchararray"/>
37 49
     </columns>
38 50
   </object>
39 51
   <object class="GtkWindow" id="Application">
... ...
@@ -157,6 +169,57 @@
157 169
                         </child>
158 170
                       </object>
159 171
                     </child>
172
+                    <child>
173
+                      <object class="GtkTreeViewColumn" id="ProcessesPageFaultsCol">
174
+                        <property name="resizable">True</property>
175
+                        <property name="sizing">fixed</property>
176
+                        <property name="title" translatable="yes">Page Faults</property>
177
+                        <property name="clickable">True</property>
178
+                        <property name="reorderable">True</property>
179
+                        <property name="sort_indicator">True</property>
180
+                        <property name="sort_column_id">10</property>
181
+                        <child>
182
+                          <object class="GtkCellRendererText" id="ProcessesPageFaultsText"/>
183
+                          <attributes>
184
+                            <attribute name="text">11</attribute>
185
+                          </attributes>
186
+                        </child>
187
+                      </object>
188
+                    </child>
189
+                    <child>
190
+                      <object class="GtkTreeViewColumn" id="ProcessesFileSysOpsCol">
191
+                        <property name="resizable">True</property>
192
+                        <property name="sizing">fixed</property>
193
+                        <property name="title" translatable="yes">File System Operations</property>
194
+                        <property name="clickable">True</property>
195
+                        <property name="reorderable">True</property>
196
+                        <property name="sort_indicator">True</property>
197
+                        <property name="sort_column_id">12</property>
198
+                        <child>
199
+                          <object class="GtkCellRendererText" id="ProcessesFileSysOpsText"/>
200
+                          <attributes>
201
+                            <attribute name="text">13</attribute>
202
+                          </attributes>
203
+                        </child>
204
+                      </object>
205
+                    </child>
206
+                    <child>
207
+                      <object class="GtkTreeViewColumn" id="ProcessesCtxSwCol">
208
+                        <property name="resizable">True</property>
209
+                        <property name="sizing">fixed</property>
210
+                        <property name="title" translatable="yes">Context Switches</property>
211
+                        <property name="clickable">True</property>
212
+                        <property name="reorderable">True</property>
213
+                        <property name="sort_indicator">True</property>
214
+                        <property name="sort_column_id">14</property>
215
+                        <child>
216
+                          <object class="GtkCellRendererText" id="ProcessesCtxSwText"/>
217
+                          <attributes>
218
+                            <attribute name="text">15</attribute>
219
+                          </attributes>
220
+                        </child>
221
+                      </object>
222
+                    </child>
160 223
                   </object>
161 224
                 </child>
162 225
               </object>
... ...
@@ -5,6 +5,7 @@
5 5
 Graphical user interface of UProcTrace.
6 6
 """
7 7
 
8
+import functools
8 9
 import shlex
9 10
 import time
10 11
 
... ...
@@ -19,6 +20,15 @@ gi.require_version('Gtk', '3.0')
19 20
 from gi.repository import Gdk, Gtk
20 21
 
21 22
 
23
+def add_none(val_a: int, val_b: int) -> int:
24
+    """
25
+    Integer addition with support for None.
26
+    """
27
+    if val_a is None or val_b is None:
28
+        return None
29
+    return val_a + val_b
30
+
31
+
22 32
 def cmdline2str(cmdline: list) -> str:
23 33
     """
24 34
     Convert command line to string.
... ...
@@ -67,6 +77,15 @@ def duration2str(duration: float) -> str:
67 77
     return txt
68 78
 
69 79
 
80
+def int2str(val: int) -> str:
81
+    """
82
+    Convert integer to string, support None.
83
+    """
84
+    if val is None:
85
+        return '???'
86
+    return f'{val:d}'
87
+
88
+
70 89
 def kb2str(size_kb: int) -> str:
71 90
     """
72 91
     Convert size in KiB to string.
... ...
@@ -119,6 +138,12 @@ class UptGui:
119 138
     PROC_CPU_TIME_TEXT = 7
120 139
     PROC_MAX_RSS_KB = 8
121 140
     PROC_MAX_RSS_KB_TEXT = 9
141
+    PROC_PAGE_FAULTS = 10
142
+    PROC_PAGE_FAULTS_TEXT = 11
143
+    PROC_FILE_SYS_OPS = 12
144
+    PROC_FILE_SYS_OPS_TEXT = 13
145
+    PROC_CTX_SW = 14
146
+    PROC_CTX_SW_TEXT = 15
122 147
 
123 148
     def __init__(self, proto_filename):
124 149
         """
... ...
@@ -140,6 +165,54 @@ class UptGui:
140 165
         # open trace file
141 166
         self.openTrace(proto_filename)
142 167
 
168
+    def fillProcessesEntry(self, proc_iter,
169
+                           proc: uproctrace.processes.Process):
170
+        """
171
+        Fill attributes of processes tree entry.
172
+        proc_iter: process tree entry
173
+        proc: process object
174
+        """
175
+        self.wid_processes_tree.set_value(proc_iter, self.PROC_PROC_ID,
176
+                                          proc.proc_id)
177
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_BEGIN_TIMESTAMP,
178
+                                    self.PROC_BEGIN_TIMESTAMP_TEXT,
179
+                                    timestamp2str, proc.begin_timestamp)
180
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_END_TIMESTAMP,
181
+                                    self.PROC_END_TIMESTAMP_TEXT,
182
+                                    timestamp2str, proc.end_timestamp)
183
+        self.wid_processes_tree.set_value(proc_iter, self.PROC_CMDLINE,
184
+                                          cmdline2str(proc.cmdline))
185
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_CPU_TIME,
186
+                                    self.PROC_CPU_TIME_TEXT, duration2str,
187
+                                    proc.cpu_time)
188
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_MAX_RSS_KB,
189
+                                    self.PROC_MAX_RSS_KB_TEXT, kb2str,
190
+                                    proc.max_rss_kb)
191
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_PAGE_FAULTS,
192
+                                    self.PROC_PAGE_FAULTS_TEXT, int2str,
193
+                                    add_none(proc.min_flt, proc.maj_flt))
194
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_FILE_SYS_OPS,
195
+                                    self.PROC_FILE_SYS_OPS_TEXT, int2str,
196
+                                    add_none(proc.in_block, proc.ou_block))
197
+        self.fillProcessesEntryAttr(proc_iter, self.PROC_CTX_SW,
198
+                                    self.PROC_CTX_SW_TEXT, int2str,
199
+                                    add_none(proc.n_v_csw, proc.n_iv_csw))
200
+
201
+    def fillProcessesEntryAttr(self, proc_iter, col: int, text_col: int,
202
+                               val2str_func, val):
203
+        """
204
+        Fill attribute of processes tree entry.
205
+        proc_iter: process tree entry
206
+        col: value column number
207
+        text_col: text column number
208
+        val2str_func: function to transform value to string
209
+        val: value
210
+        """
211
+        # pylint: disable=R0913
212
+        self.wid_processes_tree.set_value(proc_iter, col, val)
213
+        self.wid_processes_tree.set_value(proc_iter, text_col,
214
+                                          val2str_func(val))
215
+
143 216
     def onDestroy(self, _widget):
144 217
         """
145 218
         Window will be destroyed.
... ...
@@ -219,32 +292,7 @@ class UptGui:
219 292
             proc = procs[0]
220 293
             del procs[0]
221 294
             proc_iter = self.wid_processes_tree.append(parent_iter)
222
-            self.wid_processes_tree.set_value(proc_iter, self.PROC_PROC_ID,
223
-                                              proc.proc_id)
224
-            self.wid_processes_tree.set_value(proc_iter,
225
-                                              self.PROC_BEGIN_TIMESTAMP,
226
-                                              proc.begin_timestamp)
227
-            self.wid_processes_tree.set_value(
228
-                proc_iter, self.PROC_BEGIN_TIMESTAMP_TEXT,
229
-                timestamp2str(proc.begin_timestamp))
230
-            self.wid_processes_tree.set_value(proc_iter,
231
-                                              self.PROC_END_TIMESTAMP,
232
-                                              proc.end_timestamp)
233
-            self.wid_processes_tree.set_value(
234
-                proc_iter, self.PROC_END_TIMESTAMP_TEXT,
235
-                timestamp2str(proc.end_timestamp))
236
-            self.wid_processes_tree.set_value(proc_iter, self.PROC_CMDLINE,
237
-                                              cmdline2str(proc.cmdline))
238
-            self.wid_processes_tree.set_value(proc_iter, self.PROC_CPU_TIME,
239
-                                              proc.cpu_time)
240
-            self.wid_processes_tree.set_value(proc_iter,
241
-                                              self.PROC_CPU_TIME_TEXT,
242
-                                              duration2str(proc.cpu_time))
243
-            self.wid_processes_tree.set_value(proc_iter, self.PROC_MAX_RSS_KB,
244
-                                              proc.max_rss_kb)
245
-            self.wid_processes_tree.set_value(proc_iter,
246
-                                              self.PROC_MAX_RSS_KB_TEXT,
247
-                                              kb2str(proc.max_rss_kb))
295
+            self.fillProcessesEntry(proc_iter, proc)
248 296
             to_be_output.append((proc.children, proc_iter))
249 297
         # show all processes
250 298
         self.wid_processes_view.expand_all()
... ...
@@ -317,15 +365,36 @@ class UptGui:
317 365
                 add(f'{key} {i:d}', value, list_iter)
318 366
             return list_iter
319 367
 
368
+        def add_sum(key: str, sub_keys: list, values: list, parent_iter=None):
369
+            """
370
+            Add a sum of multiple values to a process and include individual
371
+            values as subtree.
372
+            Add to specified parent (if parent_iter is specified).
373
+            Return iterator to added top-level of detail subtree.
374
+            """
375
+            sum_val = functools.reduce(add_none, values, 0)
376
+            sum_iter = add(key, int2str(sum_val), parent_iter)
377
+            for sub_key, val in zip(sub_keys, values):
378
+                add(sub_key, int2str(val), sum_iter)
379
+            self.wid_details_view.expand_row(
380
+                self.wid_details_tree.get_path(sum_iter), True)
381
+            return sum_iter
382
+
320 383
         add('begin time', timestamp2str(proc.begin_timestamp))
321 384
         cmdline_iter = add_list('command line', proc.cmdline)
322 385
         self.wid_details_view.expand_row(
323 386
             self.wid_details_tree.get_path(cmdline_iter), True)
387
+        add_sum('context switches', ['involuntary', 'voluntary'],
388
+                [proc.n_iv_csw, proc.n_v_csw])
324 389
         add('CPU time', duration2str(proc.cpu_time))
325 390
         add('end time', timestamp2str(proc.end_timestamp))
326 391
         add_list('environment', sorted(proc.environ))
327 392
         add('executable', proc.exe)
393
+        add_sum('file system operations', ['input', 'output'],
394
+                [proc.in_block, proc.ou_block])
328 395
         add('max. resident memory', kb2str(proc.max_rss_kb))
396
+        add_sum('page faults', ['major', 'minor'],
397
+                [proc.maj_flt, proc.min_flt])
329 398
         add('pid', str(proc.pid))
330 399
         add('ppid', str(proc.ppid))
331 400
         add('system CPU time', duration2str(proc.sys_time))
... ...
@@ -164,6 +163,9 @@ class ProcEnd(ProcBeginOrEnd):
164 163
     """
165 164
     Process end event.
166 165
     """
166
+
167
+    # pylint: disable=R0902
168
+
167 169
     def __init__(self, pb2_ev: pb2.event):
168 170
         """
169 171
         Initialize process end event from PB2 event.
... ...
@@ -179,6 +181,12 @@ class ProcEnd(ProcBeginOrEnd):
179 181
             p_e.sys_time) if p_e.HasField('sys_time') else None
180 182
         self._max_rss_kb = p_e.max_rss_kb if p_e.HasField(
181 183
             'max_rss_kb') else None
184
+        self._min_flt = p_e.min_flt if p_e.HasField('min_flt') else None
185
+        self._maj_flt = p_e.maj_flt if p_e.HasField('maj_flt') else None
186
+        self._in_block = p_e.in_block if p_e.HasField('in_block') else None
187
+        self._ou_block = p_e.ou_block if p_e.HasField('ou_block') else None
188
+        self._n_v_csw = p_e.n_v_csw if p_e.HasField('n_v_csw') else None
189
+        self._n_iv_csw = p_e.n_iv_csw if p_e.HasField('n_iv_csw') else None
182 190
 
183 191
     @property
184 192
     def pid(self) -> int:
... ...
@@ -215,6 +223,48 @@ class ProcEnd(ProcBeginOrEnd):
215 223
         """
216 224
         return self._max_rss_kb
217 225
 
226
+    @property
227
+    def min_flt(self) -> int:
228
+        """
229
+        Minor page fault count (i.e. no I/O).
230
+        """
231
+        return self._min_flt
232
+
233
+    @property
234
+    def maj_flt(self) -> int:
235
+        """
236
+        Major page fault count (i.e. I/O needed).
237
+        """
238
+        return self._maj_flt
239
+
240
+    @property
241
+    def in_block(self) -> int:
242
+        """
243
+        Number of input operations on file system.
244
+        """
245
+        return self._in_block
246
+
247
+    @property
248
+    def ou_block(self) -> int:
249
+        """
250
+        Number of output operations on file system.
251
+        """
252
+        return self._ou_block
253
+
254
+    @property
255
+    def n_v_csw(self) -> int:
256
+        """
257
+        Number of voluntary context switches.
258
+        """
259
+        return self._n_v_csw
260
+
261
+    @property
262
+    def n_iv_csw(self) -> int:
263
+        """
264
+        Number of involuntary context switches.
265
+        """
266
+        return self._n_iv_csw
267
+
218 268
 
219 269
 class Visitor(abc.ABC):
220 270
     """
... ...
@@ -13,6 +12,8 @@ class Process():
13 12
     """
14 13
     A process parsed from a trace.
15 14
     """
15
+
16
+    # pylint: disable=R0904
16 17
     def __init__(self, proc_id: int, pid: int):
17 18
         """
18 19
         Initialize process.
... ...
@@ -94,6 +95,24 @@ class Process():
94 95
             return None
95 96
         return self._begin.exe
96 97
 
98
+    @property
99
+    def in_block(self) -> int:
100
+        """
101
+        Number of input operations on file system.
102
+        """
103
+        if self._end is None:
104
+            return None
105
+        return self._end.in_block
106
+
107
+    @property
108
+    def maj_flt(self) -> int:
109
+        """
110
+        Major page fault count (i.e. I/O needed).
111
+        """
112
+        if self._end is None:
113
+            return None
114
+        return self._end.maj_flt
115
+
97 116
     @property
98 117
     def max_rss_kb(self) -> int:
99 118
         """
... ...
@@ -103,6 +122,42 @@ class Process():
103 122
             return None
104 123
         return self._end.max_rss_kb
105 124
 
125
+    @property
126
+    def min_flt(self) -> int:
127
+        """
128
+        Minor page fault count (i.e. no I/O).
129
+        """
130
+        if self._end is None:
131
+            return None
132
+        return self._end.min_flt
133
+
134
+    @property
135
+    def n_iv_csw(self) -> int:
136
+        """
137
+        Number of involuntary context switches.
138
+        """
139
+        if self._end is None:
140
+            return None
141
+        return self._end.n_iv_csw
142
+
143
+    @property
144
+    def n_v_csw(self) -> int:
145
+        """
146
+        Number of voluntary context switches.
147
+        """
148
+        if self._end is None:
149
+            return None
150
+        return self._end.n_v_csw
151
+
152
+    @property
153
+    def ou_block(self) -> int:
154
+        """
155
+        Number of output operations on file system.
156
+        """
157
+        if self._end is None:
158
+            return None
159
+        return self._end.ou_block
160
+
106 161
     @property
107 162
     def parent(self):
108 163
         """
... ...
@@ -13,19 +13,31 @@ message stringlist {
13 13
 
14 14
 message proc_begin {
15 15
   required int32 pid = 1;
16
-  optional int32 ppid = 2;
17
-  optional string exe = 3;
18
-  optional string cwd = 4;
19
-  optional stringlist cmdline = 5;
20
-  optional stringlist environ = 6;
16
+  optional int32 ppid = 2; ///< pid of parent process
17
+  optional string exe = 3; ///< path to executable
18
+  optional string cwd = 4; ///< working directory
19
+  optional stringlist cmdline = 5; ///< command line
20
+  optional stringlist environ = 6; ///< environment variables
21 21
 }
22 22
 
23 23
 message proc_end {
24 24
   required int32 pid = 1;
25
-  optional timespec cpu_time = 2;
26
-  optional timespec user_time = 3;
27
-  optional timespec sys_time = 4;
28
-  optional int64 max_rss_kb = 5;
25
+  /// fields from clock_gettime
26
+  //@{
27
+  optional timespec cpu_time = 2; ///< CPU time usage
28
+  //@}
29
+  /// fields from getrusage
30
+  //@{
31
+  optional timespec user_time = 3; ///< CPU time spent in user space
32
+  optional timespec sys_time = 4; ///< CPU spent in kernel space
33
+  optional int64 max_rss_kb = 5; ///< maximum resisent set size in KiB
34
+  optional int64 min_flt = 6; ///< minor page fault count (i.e. no I/O)
35
+  optional int64 maj_flt = 7; ///< major page fault count (i.e. I/O needed)
36
+  optional int64 in_block = 8; ///< number of input operations on file system
37
+  optional int64 ou_block = 9; ///< number of output operations on file system
38
+  optional int64 n_v_csw = 10; ///< number of voluntary context switches
39
+  optional int64 n_iv_csw = 11; ///< number of involuntary context switches
40
+  //@}
29 41
 }
30 42
 
31 43
 message event {
32 44