1 /* Procman tree view and process updating
2 * Copyright (C) 2001 Kevin Vandersloot
3 * Copyright (C) 2012-2021 MATE Developers
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20
21
22 #include <config.h>
23
24 #include <cairo-gobject.h>
25 #include <string.h>
26 #include <math.h>
27 #include <glib/gi18n.h>
28 #include <glib/gprintf.h>
29 #include <glibtop.h>
30 #include <glibtop/loadavg.h>
31 #include <glibtop/proclist.h>
32 #include <glibtop/procio.h>
33 #include <glibtop/procstate.h>
34 #include <glibtop/procmem.h>
35 #include <glibtop/procmap.h>
36 #include <glibtop/proctime.h>
37 #include <glibtop/procuid.h>
38 #include <glibtop/procargs.h>
39 #include <glibtop/prockernel.h>
40 #include <glibtop/mem.h>
41 #include <glibtop/swap.h>
42 #include <sys/stat.h>
43 #include <pwd.h>
44 #include <time.h>
45
46 #include <set>
47 #include <list>
48
49 #ifdef HAVE_SYSTEMD
50 #include <systemd/sd-login.h>
51 #endif
52
53 #include "procman.h"
54 #include "selection.h"
55 #include "proctable.h"
56 #include "callbacks.h"
57 #include "prettytable.h"
58 #include "util.h"
59 #include "interface.h"
60 #include "selinux.h"
61 #include "cgroups.h"
62
63 #ifdef GDK_WINDOWING_X11
64 #include <gdk/gdkx.h>
65 #endif
66
67 ProcInfo::UserMap ProcInfo::users;
68 ProcInfo::List ProcInfo::all;
69 std::map<pid_t, guint64> ProcInfo::cpu_times;
70
71
find(pid_t pid)72 ProcInfo* ProcInfo::find(pid_t pid)
73 {
74 Iterator it(ProcInfo::all.find(pid));
75 return (it == ProcInfo::all.end() ? NULL : it->second);
76 }
77
78 void
get_last_selected(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)79 get_last_selected (GtkTreeModel *model, GtkTreePath *path,
80 GtkTreeIter *iter, gpointer data)
81 {
82 ProcInfo **info = static_cast<ProcInfo**>(data);
83
84 gtk_tree_model_get (model, iter, COL_POINTER, info, -1);
85 }
86
87 static void
cb_columns_changed(GtkTreeView * treeview,gpointer user_data)88 cb_columns_changed(GtkTreeView *treeview, gpointer user_data)
89 {
90 ProcData * const procdata = static_cast<ProcData*>(user_data);
91
92 procman_save_tree_state(procdata->settings,
93 GTK_WIDGET(treeview),
94 "proctree");
95 }
96
97 static void
cb_sort_changed(GtkTreeSortable * model,gpointer user_data)98 cb_sort_changed (GtkTreeSortable *model, gpointer user_data)
99 {
100 ProcData * const procdata = static_cast<ProcData*>(user_data);
101
102 procman_save_tree_state (procdata->settings,
103 GTK_WIDGET (procdata->tree),
104 "proctree");
105 }
106
107
108 static GtkTreeViewColumn*
my_gtk_tree_view_get_column_with_sort_column_id(GtkTreeView * treeview,int id)109 my_gtk_tree_view_get_column_with_sort_column_id(GtkTreeView *treeview, int id)
110 {
111 GList *columns, *it;
112 GtkTreeViewColumn *col = NULL;
113
114 columns = gtk_tree_view_get_columns(treeview);
115
116 for(it = columns; it; it = it->next)
117 {
118 if(gtk_tree_view_column_get_sort_column_id(static_cast<GtkTreeViewColumn*>(it->data)) == id)
119 {
120 col = static_cast<GtkTreeViewColumn*>(it->data);
121 break;
122 }
123 }
124
125 g_list_free(columns);
126
127 return col;
128 }
129
130
131 void
proctable_set_columns_order(GtkTreeView * treeview,GSList * order)132 proctable_set_columns_order(GtkTreeView *treeview, GSList *order)
133 {
134 GtkTreeViewColumn* last = NULL;
135 GSList *it;
136
137 for(it = order; it; it = it->next)
138 {
139 int id;
140 GtkTreeViewColumn *cur;
141
142 id = GPOINTER_TO_INT(it->data);
143
144 cur = my_gtk_tree_view_get_column_with_sort_column_id(treeview, id);
145
146 if(cur && cur != last)
147 {
148 gtk_tree_view_move_column_after(treeview, cur, last);
149 last = cur;
150 }
151 }
152 }
153
154
155 GSList*
proctable_get_columns_order(GtkTreeView * treeview)156 proctable_get_columns_order(GtkTreeView *treeview)
157 {
158 GList *columns, *col;
159 GSList *order = NULL;
160
161 columns = gtk_tree_view_get_columns(treeview);
162
163 for(col = columns; col; col = col->next)
164 {
165 int id;
166
167 id = gtk_tree_view_column_get_sort_column_id(static_cast<GtkTreeViewColumn*>(col->data));
168 order = g_slist_prepend(order, GINT_TO_POINTER(id));
169 }
170
171 g_list_free(columns);
172
173 order = g_slist_reverse(order);
174
175 return order;
176 }
177
178 static guint timeout_id = 0;
179 static GtkTreeViewColumn *current_column;
180
181 static gboolean
save_column_width(gpointer data)182 save_column_width (gpointer data)
183 {
184 gint width;
185 gchar *key;
186 int id;
187 GSettings *settings;
188
189 settings = g_settings_get_child (G_SETTINGS (data), "proctree");
190 id = gtk_tree_view_column_get_sort_column_id (current_column);
191 width = gtk_tree_view_column_get_width (current_column);
192
193 key = g_strdup_printf ("col-%d-width", id);
194 g_settings_set_int(settings, key, width);
195 g_free (key);
196
197 if (timeout_id) {
198 g_source_remove (timeout_id);
199 timeout_id = 0;
200 }
201
202 return FALSE;
203 }
204
205 static void
cb_proctable_column_resized(GtkWidget * widget,GParamSpec * pspec,gpointer data)206 cb_proctable_column_resized(GtkWidget *widget, GParamSpec *pspec, gpointer data)
207 {
208 current_column = GTK_TREE_VIEW_COLUMN(widget);
209
210 if (timeout_id)
211 g_source_remove (timeout_id);
212
213 timeout_id = g_timeout_add (250, save_column_width, data);
214 }
215
216 static gboolean
search_equal_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer search_data)217 search_equal_func(GtkTreeModel *model,
218 gint column,
219 const gchar *key,
220 GtkTreeIter *iter,
221 gpointer search_data)
222 {
223 char* name;
224 char* user;
225 gboolean found;
226
227 gtk_tree_model_get(model, iter,
228 COL_NAME, &name,
229 COL_USER, &user,
230 -1);
231
232 found = !((name && strcasestr(name, key))
233 || (user && strcasestr(user, key)));
234
235 g_free(name);
236 g_free(user);
237
238 return found;
239 }
240
241
242
243 GtkWidget *
proctable_new(ProcData * const procdata)244 proctable_new (ProcData * const procdata)
245 {
246 GtkWidget *proctree;
247 GtkWidget *scrolled;
248 GtkTreeStore *model;
249 GtkTreeSelection *selection;
250 GtkTreeViewColumn *column;
251 GtkCellRenderer *cell_renderer;
252
253 const gchar *titles[] = {
254 N_("Process Name"),
255 N_("User"),
256 N_("Status"),
257 N_("Virtual Memory"),
258 N_("Resident Memory"),
259 N_("Writable Memory"),
260 N_("Shared Memory"),
261 N_("X Server Memory"),
262 /* xgettext:no-c-format */ N_("% CPU"),
263 N_("CPU Time"),
264 N_("Started"),
265 N_("Nice"),
266 N_("ID"),
267 N_("Security Context"),
268 N_("Command Line"),
269 N_("Memory"),
270 /* xgettext: combined noun, the function the process is waiting in, see wchan ps(1) */
271 N_("Waiting Channel"),
272 N_("Control Group"),
273 N_("Unit"),
274 N_("Session"),
275 /* TRANSLATORS: Seat = i.e. the physical seat the session of the process belongs to, only
276 for multi-seat environments. See http://en.wikipedia.org/wiki/Multiseat_configuration */
277 N_("Seat"),
278 N_("Owner"),
279 N_("Disk Read Total"),
280 N_("Disk Write Total"),
281 N_("Disk Read"),
282 N_("Disk Write"),
283 N_("Priority"),
284 NULL,
285 "POINTER"
286 };
287
288 gint i;
289
290 scrolled = gtk_scrolled_window_new (NULL, NULL);
291 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
292 GTK_POLICY_AUTOMATIC,
293 GTK_POLICY_AUTOMATIC);
294
295 model = gtk_tree_store_new (NUM_COLUMNS,
296 G_TYPE_STRING, /* Process Name */
297 G_TYPE_STRING, /* User */
298 G_TYPE_UINT, /* Status */
299 G_TYPE_ULONG, /* VM Size */
300 G_TYPE_ULONG, /* Resident Memory */
301 G_TYPE_ULONG, /* Writable Memory */
302 G_TYPE_ULONG, /* Shared Memory */
303 G_TYPE_ULONG, /* X Server Memory */
304 G_TYPE_UINT, /* % CPU */
305 G_TYPE_UINT64, /* CPU time */
306 G_TYPE_ULONG, /* Started */
307 G_TYPE_INT, /* Nice */
308 G_TYPE_UINT, /* ID */
309 G_TYPE_STRING, /* Security Context */
310 G_TYPE_STRING, /* Arguments */
311 G_TYPE_ULONG, /* Memory */
312 G_TYPE_STRING, /* wchan */
313 G_TYPE_STRING, /* Cgroup */
314 G_TYPE_STRING, /* Unit */
315 G_TYPE_STRING, /* Session */
316 G_TYPE_STRING, /* Seat */
317 G_TYPE_STRING, /* Owner */
318 G_TYPE_UINT64, /* Disk read total */
319 G_TYPE_UINT64, /* Disk write total*/
320 G_TYPE_UINT64, /* Disk read */
321 G_TYPE_UINT64, /* Disk write */
322 G_TYPE_STRING, /* Priority */
323 CAIRO_GOBJECT_TYPE_SURFACE, /* Icon */
324 G_TYPE_POINTER, /* ProcInfo */
325 G_TYPE_STRING /* Sexy tooltip */
326 );
327
328 proctree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
329 gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (proctree), COL_TOOLTIP);
330 g_object_set(G_OBJECT(proctree),
331 "show-expanders", procdata->config.show_tree,
332 NULL);
333 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (proctree),
334 search_equal_func,
335 NULL,
336 NULL);
337 g_object_unref (G_OBJECT (model));
338
339 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree));
340 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
341
342 column = gtk_tree_view_column_new ();
343
344 cell_renderer = gtk_cell_renderer_pixbuf_new ();
345 gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
346 gtk_tree_view_column_set_attributes (column, cell_renderer,
347 "surface", COL_SURFACE,
348 NULL);
349
350 cell_renderer = gtk_cell_renderer_text_new ();
351 gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
352 gtk_tree_view_column_set_attributes (column, cell_renderer,
353 "text", COL_NAME,
354 NULL);
355 gtk_tree_view_column_set_title (column, _(titles[0]));
356 gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
357 gtk_tree_view_column_set_resizable (column, TRUE);
358 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
359 gtk_tree_view_column_set_min_width (column, 20);
360 gtk_tree_view_column_set_reorderable (column, TRUE);
361 g_signal_connect(G_OBJECT(column), "notify::fixed-width", G_CALLBACK(cb_proctable_column_resized), procdata->settings);
362 gtk_tree_view_append_column (GTK_TREE_VIEW (proctree), column);
363 gtk_tree_view_set_expander_column (GTK_TREE_VIEW (proctree), column);
364
365
366 for (i = COL_USER; i <= COL_PRIORITY; i++) {
367
368 GtkCellRenderer *cell;
369 GtkTreeViewColumn *col;
370
371 #ifndef HAVE_WNCK
372 if (i == COL_MEMXSERVER) {
373 continue;
374 }
375 #endif
376
377 cell = gtk_cell_renderer_text_new();
378 col = gtk_tree_view_column_new();
379 gtk_tree_view_column_pack_start(col, cell, TRUE);
380 gtk_tree_view_column_set_title(col, _(titles[i]));
381 gtk_tree_view_column_set_sort_column_id(col, i);
382 gtk_tree_view_column_set_resizable(col, TRUE);
383 gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
384 g_signal_connect(G_OBJECT(col), "notify::fixed-width", G_CALLBACK(cb_proctable_column_resized), procdata->settings);
385 gtk_tree_view_column_set_reorderable(col, TRUE);
386 gtk_tree_view_append_column(GTK_TREE_VIEW(proctree), col);
387
388 // type
389 switch (i) {
390 #ifdef HAVE_WNCK
391 case COL_MEMXSERVER:
392 gtk_tree_view_column_set_cell_data_func(col, cell,
393 &procman::memory_size_cell_data_func,
394 GUINT_TO_POINTER(i),
395 NULL);
396 break;
397 #endif
398 case COL_VMSIZE:
399 case COL_MEMRES:
400 case COL_MEMSHARED:
401 case COL_MEM:
402 case COL_MEMWRITABLE:
403 gtk_tree_view_column_set_cell_data_func(col, cell,
404 &procman::memory_size_na_cell_data_func,
405 GUINT_TO_POINTER(i),
406 NULL);
407 break;
408
409 case COL_CPU_TIME:
410 gtk_tree_view_column_set_cell_data_func(col, cell,
411 &procman::duration_cell_data_func,
412 GUINT_TO_POINTER(i),
413 NULL);
414 break;
415
416 case COL_START_TIME:
417 gtk_tree_view_column_set_cell_data_func(col, cell,
418 &procman::time_cell_data_func,
419 GUINT_TO_POINTER(i),
420 NULL);
421 break;
422
423 case COL_STATUS:
424 gtk_tree_view_column_set_cell_data_func(col, cell,
425 &procman::status_cell_data_func,
426 GUINT_TO_POINTER(i),
427 NULL);
428 break;
429
430 case COL_DISK_READ_TOTAL:
431 case COL_DISK_WRITE_TOTAL:
432 gtk_tree_view_column_set_cell_data_func(col, cell,
433 &procman::storage_size_na_cell_data_func,
434 GUINT_TO_POINTER(i),
435 NULL);
436 break;
437
438 case COL_DISK_READ_CURRENT:
439 case COL_DISK_WRITE_CURRENT:
440 gtk_tree_view_column_set_cell_data_func(col, cell,
441 &procman::io_rate_cell_data_func,
442 GUINT_TO_POINTER(i),
443 NULL);
444 break;
445
446 case COL_PRIORITY:
447 gtk_tree_view_column_set_cell_data_func(col, cell,
448 &procman::priority_cell_data_func,
449 GUINT_TO_POINTER(COL_NICE),
450 NULL);
451 break;
452
453 default:
454 gtk_tree_view_column_set_attributes(col, cell, "text", i, NULL);
455 }
456
457 // sorting
458 switch (i) {
459 #ifdef HAVE_WNCK
460 case COL_MEMXSERVER:
461 #endif
462 case COL_VMSIZE:
463 case COL_MEMRES:
464 case COL_MEMSHARED:
465 case COL_MEM:
466 case COL_MEMWRITABLE:
467 case COL_CPU:
468 case COL_CPU_TIME:
469 case COL_DISK_READ_TOTAL:
470 case COL_DISK_WRITE_TOTAL:
471 case COL_DISK_READ_CURRENT:
472 case COL_DISK_WRITE_CURRENT:
473 case COL_START_TIME:
474 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), i,
475 procman::number_compare_func, GUINT_TO_POINTER(i),
476 NULL);
477 break;
478 case COL_PRIORITY:
479 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), i,
480 procman::priority_compare_func,
481 GUINT_TO_POINTER(COL_NICE), NULL);
482 break;
483 default:
484 break;
485 }
486
487 // xalign
488 switch(i)
489 {
490 case COL_VMSIZE:
491 case COL_MEMRES:
492 case COL_MEMWRITABLE:
493 case COL_MEMSHARED:
494 #ifdef HAVE_WNCK
495 case COL_MEMXSERVER:
496 #endif
497 case COL_CPU:
498 case COL_NICE:
499 case COL_PID:
500 case COL_DISK_READ_TOTAL:
501 case COL_DISK_WRITE_TOTAL:
502 case COL_DISK_READ_CURRENT:
503 case COL_DISK_WRITE_CURRENT:
504 case COL_CPU_TIME:
505 case COL_MEM:
506 g_object_set(G_OBJECT(cell), "xalign", 1.0f, NULL);
507 break;
508 }
509
510 // sizing
511 switch (i) {
512 case COL_ARGS:
513 gtk_tree_view_column_set_min_width(col, 150);
514 break;
515 default:
516 gtk_tree_view_column_set_min_width(col, 20);
517 break;
518 }
519 }
520
521 gtk_container_add (GTK_CONTAINER (scrolled), proctree);
522
523 procdata->tree = proctree;
524
525 procman_get_tree_state (procdata->settings, proctree, "proctree");
526
527 /* Override column settings by hiding this column if it's meaningless: */
528 if (!can_show_security_context_column ()) {
529 column = my_gtk_tree_view_get_column_with_sort_column_id (GTK_TREE_VIEW (proctree), COL_SECURITYCONTEXT);
530 gtk_tree_view_column_set_visible (column, FALSE);
531 }
532
533 if (!cgroups_enabled()) {
534 column = my_gtk_tree_view_get_column_with_sort_column_id(GTK_TREE_VIEW(proctree), COL_CGROUP);
535 gtk_tree_view_column_set_visible(column, FALSE);
536 }
537
538 #ifdef HAVE_SYSTEMD
539 if (!LOGIND_RUNNING())
540 #endif
541 {
542 for (i = COL_UNIT; i <= COL_OWNER; i++) {
543 column = my_gtk_tree_view_get_column_with_sort_column_id(GTK_TREE_VIEW(proctree), i);
544 gtk_tree_view_column_set_visible(column, FALSE);
545 }
546 }
547
548 g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree))),
549 "changed",
550 G_CALLBACK (cb_row_selected), procdata);
551 g_signal_connect (G_OBJECT (proctree), "popup_menu",
552 G_CALLBACK (cb_tree_popup_menu), procdata);
553 g_signal_connect (G_OBJECT (proctree), "button_press_event",
554 G_CALLBACK (cb_tree_button_pressed), procdata);
555
556 g_signal_connect (G_OBJECT(proctree), "columns-changed",
557 G_CALLBACK(cb_columns_changed), procdata);
558
559 g_signal_connect (G_OBJECT (model), "sort-column-changed",
560 G_CALLBACK (cb_sort_changed), procdata);
561
562 return scrolled;
563 }
564
565
~ProcInfo()566 ProcInfo::~ProcInfo()
567 {
568 g_free(this->name);
569 g_free(this->tooltip);
570 g_free(this->arguments);
571 g_free(this->security_context);
572 g_free(this->cgroup_name);
573 g_free(this->unit);
574 g_free(this->session);
575 g_free(this->seat);
576 cairo_surface_destroy(this->surface);
577 }
578
579
580 static void
get_process_name(ProcInfo * info,const gchar * cmd,const GStrv args)581 get_process_name (ProcInfo *info,
582 const gchar *cmd, const GStrv args)
583 {
584 if (args) {
585 // look for /usr/bin/very_long_name
586 // and also /usr/bin/interpreter /usr/.../very_long_name
587 // which may have use prctl to alter 'cmd' name
588 for (int i = 0; i != 2 && args[i]; ++i) {
589 char* basename;
590 basename = g_path_get_basename(args[i]);
591
592 if (g_str_has_prefix(basename, cmd)) {
593 info->name = basename;
594 return;
595 }
596
597 g_free(basename);
598 }
599 }
600
601 info->name = g_strdup (cmd);
602 }
603
604 std::string
lookup_user(guint uid)605 ProcInfo::lookup_user(guint uid)
606 {
607 typedef std::pair<ProcInfo::UserMap::iterator, bool> Pair;
608 ProcInfo::UserMap::value_type hint(uid, "");
609 Pair p(ProcInfo::users.insert(hint));
610
611 // procman_debug("User lookup for uid %u: %s", uid, (p.second ? "MISS" : "HIT"));
612
613 if (p.second) {
614 struct passwd* pwd;
615 pwd = getpwuid(uid);
616
617 if (pwd && pwd->pw_name)
618 p.first->second = pwd->pw_name;
619 else {
620 char username[16];
621 g_sprintf(username, "%u", uid);
622 p.first->second = username;
623 }
624 }
625
626 return p.first->second;
627 }
628
629 void
set_user(guint uid)630 ProcInfo::set_user(guint uid)
631 {
632 if (G_LIKELY(this->uid == uid))
633 return;
634
635 this->uid = uid;
636 this->user = lookup_user(uid);
637 }
638
get_process_memory_writable(ProcInfo * info)639 static void get_process_memory_writable(ProcInfo *info)
640 {
641 glibtop_proc_map buf;
642 glibtop_map_entry *maps;
643
644 maps = glibtop_get_proc_map(&buf, info->pid);
645
646 gulong memwritable = 0;
647 const unsigned number = buf.number;
648
649 for (unsigned i = 0; i < number; ++i) {
650 #ifdef __linux__
651 memwritable += maps[i].private_dirty;
652 #else
653 if (maps[i].perm & GLIBTOP_MAP_PERM_WRITE)
654 memwritable += maps[i].size;
655 #endif
656 }
657
658 info->memwritable = memwritable;
659
660 g_free(maps);
661 }
662
663 static void
get_process_memory_info(ProcInfo * info)664 get_process_memory_info(ProcInfo *info)
665 {
666 glibtop_proc_mem procmem;
667
668 #ifdef HAVE_WNCK
669 info->memxserver = 0;
670 #ifdef GDK_WINDOWING_X11
671 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
672 WnckResourceUsage xresources;
673
674 wnck_pid_read_resource_usage (gdk_screen_get_display (gdk_screen_get_default ()),
675 info->pid,
676 &xresources);
677
678 info->memxserver = xresources.total_bytes_estimate;
679 }
680 #endif // GDK_WINDOWING_X11
681 #endif // HAVE_WNCK
682
683 glibtop_get_proc_mem(&procmem, info->pid);
684
685 info->vmsize = procmem.vsize;
686 info->memres = procmem.resident;
687 info->memshared = procmem.share;
688
689 get_process_memory_writable(info);
690
691 // fake the smart memory column if writable is not available
692 info->mem = info->memwritable ? info->memwritable : info->memres;
693 #ifdef HAVE_WNCK
694 info->mem += info->memxserver;
695 #endif
696 }
697
698
699 static void
update_info_mutable_cols(ProcInfo * info)700 update_info_mutable_cols(ProcInfo *info)
701 {
702 ProcData * const procdata = ProcData::get_instance();
703 GtkTreeModel *model;
704 model = gtk_tree_view_get_model(GTK_TREE_VIEW(procdata->tree));
705
706 using procman::tree_store_update;
707
708 tree_store_update(model, &info->node, COL_STATUS, info->status);
709 tree_store_update(model, &info->node, COL_USER, info->user.c_str());
710 tree_store_update(model, &info->node, COL_VMSIZE, info->vmsize);
711 tree_store_update(model, &info->node, COL_MEMRES, info->memres);
712 tree_store_update(model, &info->node, COL_MEMWRITABLE, info->memwritable);
713 tree_store_update(model, &info->node, COL_MEMSHARED, info->memshared);
714 #ifdef HAVE_WNCK
715 tree_store_update(model, &info->node, COL_MEMXSERVER, info->memxserver);
716 #endif
717 tree_store_update(model, &info->node, COL_CPU, info->pcpu);
718 tree_store_update(model, &info->node, COL_CPU_TIME, info->cpu_time);
719 tree_store_update(model, &info->node, COL_DISK_READ_TOTAL, info->disk_read_bytes_total);
720 tree_store_update(model, &info->node, COL_DISK_WRITE_TOTAL, info->disk_write_bytes_total);
721 tree_store_update(model, &info->node, COL_DISK_READ_CURRENT, info->disk_read_bytes_current);
722 tree_store_update(model, &info->node, COL_DISK_WRITE_CURRENT, info->disk_write_bytes_current);
723 tree_store_update(model, &info->node, COL_START_TIME, info->start_time);
724 tree_store_update(model, &info->node, COL_NICE, info->nice);
725 tree_store_update(model, &info->node, COL_MEM, info->mem);
726 tree_store_update(model, &info->node, COL_WCHAN, info->wchan);
727 tree_store_update(model, &info->node, COL_CGROUP, info->cgroup_name);
728 tree_store_update(model, &info->node, COL_UNIT, info->unit);
729 tree_store_update(model, &info->node, COL_SESSION, info->session);
730 tree_store_update(model, &info->node, COL_SEAT, info->seat);
731 tree_store_update(model, &info->node, COL_OWNER, info->owner.c_str());
732 }
733
734
735
736 static void
insert_info_to_tree(ProcInfo * info,ProcData * procdata,bool forced=false)737 insert_info_to_tree (ProcInfo *info, ProcData *procdata, bool forced = false)
738 {
739 GtkTreeModel *model;
740
741 model = gtk_tree_view_get_model (GTK_TREE_VIEW (procdata->tree));
742
743 if (procdata->config.show_tree) {
744
745 ProcInfo *parent = 0;
746
747 if (not forced)
748 parent = ProcInfo::find(info->ppid);
749
750 if (parent) {
751 GtkTreePath *parent_node = gtk_tree_model_get_path(model, &parent->node);
752 gtk_tree_store_insert(GTK_TREE_STORE(model), &info->node, &parent->node, 0);
753
754 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(procdata->tree), parent_node)
755 #ifdef __linux__
756 // on linuxes we don't want to expand kthreadd by default (always has pid 2)
757 && (parent->pid != 2)
758 #endif
759 )
760 gtk_tree_view_expand_row(GTK_TREE_VIEW(procdata->tree), parent_node, FALSE);
761 gtk_tree_path_free(parent_node);
762 } else
763 gtk_tree_store_insert(GTK_TREE_STORE(model), &info->node, NULL, 0);
764 }
765 else
766 gtk_tree_store_insert (GTK_TREE_STORE (model), &info->node, NULL, 0);
767
768 gtk_tree_store_set (GTK_TREE_STORE (model), &info->node,
769 COL_POINTER, info,
770 COL_NAME, info->name,
771 COL_ARGS, info->arguments,
772 COL_TOOLTIP, info->tooltip,
773 COL_PID, info->pid,
774 COL_SECURITYCONTEXT, info->security_context,
775 -1);
776
777 procdata->pretty_table.set_icon(*info);
778
779 procman_debug("inserted %d%s", info->pid, (forced ? " (forced)" : ""));
780 }
781
782
783 /* Removing a node with children - make sure the children are queued
784 ** to be readded.
785 */
786 template<typename List>
787 static void
remove_info_from_tree(ProcData * procdata,GtkTreeModel * model,ProcInfo * current,List & orphans,unsigned lvl=0)788 remove_info_from_tree (ProcData *procdata, GtkTreeModel *model,
789 ProcInfo *current, List &orphans, unsigned lvl = 0)
790 {
791 GtkTreeIter child_node;
792
793 if (std::find(orphans.begin(), orphans.end(), current) != orphans.end()) {
794 procman_debug("[%u] %d already removed from tree", lvl, int(current->pid));
795 return;
796 }
797
798 procman_debug("[%u] pid %d, %d children", lvl, int(current->pid),
799 gtk_tree_model_iter_n_children(model, ¤t->node));
800
801 // it is not possible to iterate&erase over a treeview so instead we
802 // just pop one child after another and recursively remove it and
803 // its children
804
805 while (gtk_tree_model_iter_children(model, &child_node, ¤t->node)) {
806 ProcInfo *child = 0;
807 gtk_tree_model_get(model, &child_node, COL_POINTER, &child, -1);
808 remove_info_from_tree(procdata, model, child, orphans, lvl + 1);
809 }
810
811 g_assert(not gtk_tree_model_iter_has_child(model, ¤t->node));
812
813 if (procdata->selected_process == current)
814 procdata->selected_process = NULL;
815
816 orphans.push_back(current);
817 gtk_tree_store_remove(GTK_TREE_STORE(model), ¤t->node);
818 procman::poison(current->node, 0x69);
819 }
820
821 static void
get_process_systemd_info(ProcInfo * info)822 get_process_systemd_info(ProcInfo *info)
823 {
824 #ifdef HAVE_SYSTEMD
825 uid_t uid;
826
827 if (!LOGIND_RUNNING())
828 return;
829
830 free(info->unit);
831 info->unit = NULL;
832 sd_pid_get_unit(info->pid, &info->unit);
833
834 free(info->session);
835 info->session = NULL;
836 sd_pid_get_session(info->pid, &info->session);
837
838 free(info->seat);
839 info->seat = NULL;
840
841 if (info->session != NULL)
842 sd_session_get_seat(info->session, &info->seat);
843
844 if (sd_pid_get_owner_uid(info->pid, &uid) >= 0)
845 info->owner = info->lookup_user(uid);
846 else
847 info->owner = "";
848 #endif
849 }
850
851 static void
update_info(ProcData * procdata,ProcInfo * info)852 update_info (ProcData *procdata, ProcInfo *info)
853 {
854 glibtop_proc_state procstate;
855 glibtop_proc_uid procuid;
856 glibtop_proc_time proctime;
857 glibtop_proc_kernel prockernel;
858 glibtop_proc_io procio;
859
860 glibtop_get_proc_kernel(&prockernel, info->pid);
861 g_strlcpy(info->wchan, prockernel.wchan, sizeof info->wchan);
862
863 glibtop_get_proc_state (&procstate, info->pid);
864 info->status = procstate.state;
865
866 glibtop_get_proc_uid (&procuid, info->pid);
867 glibtop_get_proc_time (&proctime, info->pid);
868 glibtop_get_proc_io (&procio, info->pid);
869
870 get_process_memory_info(info);
871
872 info->set_user(procstate.uid);
873
874 // if the cpu time has increased reset the status to running
875 // regardless of kernel state (https://bugzilla.gnome.org/606579)
876 guint64 difference = proctime.rtime - info->cpu_time;
877 if (difference > 0)
878 info->status = GLIBTOP_PROCESS_RUNNING;
879 info->pcpu = difference * 100 / procdata->cpu_total_time;
880 info->pcpu = MIN(info->pcpu, 100);
881
882 if (not procdata->config.solaris_mode)
883 info->pcpu *= procdata->config.num_cpus;
884
885 ProcInfo::cpu_times[info->pid] = info->cpu_time = proctime.rtime;
886 info->nice = procuid.nice;
887
888 gdouble update_interval_seconds = procdata->config.update_interval / 1000.0;
889 difference = procio.disk_wbytes - info->disk_write_bytes_total;
890 info->disk_write_bytes_current = difference > update_interval_seconds ? difference/update_interval_seconds : 0ULL;
891 difference = procio.disk_rbytes - info->disk_read_bytes_total;
892 info->disk_read_bytes_current = difference > update_interval_seconds ? difference/update_interval_seconds : 0ULL;
893
894 info->disk_write_bytes_total = procio.disk_wbytes;
895 info->disk_read_bytes_total = procio.disk_rbytes;
896
897 // set the ppid only if one can exist
898 // i.e. pid=0 can never have a parent
899 if (info->pid > 0) {
900 info->ppid = procuid.ppid;
901 }
902
903 g_assert(info->pid != info->ppid);
904 g_assert(info->ppid != -1 || info->pid == 0);
905
906 /* get cgroup data */
907 get_process_cgroup_info(info);
908
909 get_process_systemd_info(info);
910 }
911
912
ProcInfo(pid_t pid)913 ProcInfo::ProcInfo(pid_t pid)
914 : node(),
915 surface(),
916 tooltip(NULL),
917 name(NULL),
918 arguments(NULL),
919 security_context(NULL),
920 pid(pid),
921 ppid(-1),
922 uid(-1)
923 {
924 ProcInfo * const info = this;
925 glibtop_proc_state procstate;
926 glibtop_proc_time proctime;
927 glibtop_proc_args procargs;
928 gchar** arguments;
929
930 glibtop_get_proc_state (&procstate, pid);
931 glibtop_get_proc_time (&proctime, pid);
932 arguments = glibtop_get_proc_argv (&procargs, pid, 0);
933
934 /* FIXME : wrong. name and arguments may change with exec* */
935 get_process_name (info, procstate.cmd, static_cast<const GStrv>(arguments));
936
937 std::string tooltip = make_string(g_strjoinv(" ", arguments));
938 if (tooltip.empty())
939 tooltip = procstate.cmd;
940
941 info->tooltip = g_markup_escape_text(tooltip.c_str(), -1);
942
943 info->arguments = g_strescape(tooltip.c_str(), "\\\"");
944 g_strfreev(arguments);
945
946 guint64 cpu_time = proctime.rtime;
947 std::map<pid_t, guint64>::iterator it(ProcInfo::cpu_times.find(pid));
948 if (it != ProcInfo::cpu_times.end())
949 {
950 if (proctime.rtime >= it->second)
951 cpu_time = it->second;
952 }
953 info->cpu_time = cpu_time;
954 info->start_time = proctime.start_time;
955
956 get_process_selinux_context (info);
957 info->cgroup_name = NULL;
958 get_process_cgroup_info(info);
959
960 info->unit = info->session = info->seat = NULL;
961 get_process_systemd_info(info);
962 }
963
964 static void
refresh_list(ProcData * procdata,const pid_t * pid_list,const guint n)965 refresh_list (ProcData *procdata, const pid_t* pid_list, const guint n)
966 {
967 typedef std::list<ProcInfo*> ProcList;
968 ProcList addition;
969
970 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (procdata->tree));
971 guint i;
972
973 // Add or update processes in the process list
974 for(i = 0; i < n; ++i) {
975 ProcInfo *info = ProcInfo::find(pid_list[i]);
976
977 if (!info) {
978 info = new ProcInfo(pid_list[i]);
979 ProcInfo::all[info->pid] = info;
980 addition.push_back(info);
981 }
982
983 update_info (procdata, info);
984 }
985
986
987 // Remove dead processes from the process list and from the
988 // tree. children are queued to be readded at the right place
989 // in the tree.
990
991 const std::set<pid_t> pids(pid_list, pid_list + n);
992
993 ProcInfo::Iterator it(ProcInfo::begin());
994
995 while (it != ProcInfo::end()) {
996 ProcInfo * const info = it->second;
997 ProcInfo::Iterator next(it);
998 ++next;
999
1000 if (pids.find(info->pid) == pids.end()) {
1001 procman_debug("ripping %d", info->pid);
1002 remove_info_from_tree(procdata, model, info, addition);
1003 addition.remove(info);
1004 ProcInfo::all.erase(it);
1005 delete info;
1006 }
1007
1008 it = next;
1009 }
1010
1011 // INVARIANT
1012 // pid_list == ProcInfo::all + addition
1013
1014
1015 if (procdata->config.show_tree) {
1016
1017 // insert process in the tree. walk through the addition list
1018 // (new process + process that have a new parent). This loop
1019 // handles the dependencies because we cannot insert a process
1020 // until its parent is in the tree.
1021
1022 std::set<pid_t> in_tree(pids);
1023
1024 for (ProcList::iterator it(addition.begin()); it != addition.end(); ++it)
1025 in_tree.erase((*it)->pid);
1026
1027
1028 while (not addition.empty()) {
1029 procman_debug("looking for %d parents", int(addition.size()));
1030 ProcList::iterator it(addition.begin());
1031
1032 while (it != addition.end()) {
1033 procman_debug("looking for %d's parent with ppid %d",
1034 int((*it)->pid), int((*it)->ppid));
1035
1036
1037 // inserts the process in the treeview if :
1038 // - it has no parent (ppid = -1),
1039 // ie it is for example the [kernel] on FreeBSD
1040 // - it is init
1041 // - its parent is already in tree
1042 // - its parent is unreachable
1043 //
1044 // rounds == 2 means that addition contains processes with
1045 // unreachable parents
1046 //
1047 // FIXME: this is broken if the unreachable parent becomes active
1048 // i.e. it gets active or changes ower
1049 // so we just clear the tree on __each__ update
1050 // see proctable_update_list (ProcData * const procdata)
1051
1052
1053 if ((*it)->ppid <= 0 or in_tree.find((*it)->ppid) != in_tree.end()) {
1054 insert_info_to_tree(*it, procdata);
1055 in_tree.insert((*it)->pid);
1056 it = addition.erase(it);
1057 continue;
1058 }
1059
1060 ProcInfo *parent = ProcInfo::find((*it)->ppid);
1061 // if the parent is unreachable
1062 if (not parent) {
1063 // or std::find(addition.begin(), addition.end(), parent) == addition.end()) {
1064 insert_info_to_tree(*it, procdata, true);
1065 in_tree.insert((*it)->pid);
1066 it = addition.erase(it);
1067 continue;
1068 }
1069
1070 ++it;
1071 }
1072 }
1073 }
1074 else {
1075 // don't care of the tree
1076 for (ProcList::iterator it(addition.begin()); it != addition.end(); ++it)
1077 insert_info_to_tree(*it, procdata);
1078 }
1079
1080
1081 for (ProcInfo::Iterator it(ProcInfo::begin()); it != ProcInfo::end(); ++it)
1082 update_info_mutable_cols(it->second);
1083 }
1084
1085
1086 static void
proctable_update_list(ProcData * const procdata)1087 proctable_update_list (ProcData * const procdata)
1088 {
1089 pid_t* pid_list;
1090 glibtop_proclist proclist;
1091 glibtop_cpu cpu;
1092 gint which, arg;
1093 procman::SelectionMemento selection;
1094
1095 switch (procdata->config.whose_process) {
1096 case ALL_PROCESSES:
1097 which = GLIBTOP_KERN_PROC_ALL;
1098 arg = 0;
1099 break;
1100
1101 case ACTIVE_PROCESSES:
1102 which = GLIBTOP_KERN_PROC_ALL | GLIBTOP_EXCLUDE_IDLE;
1103 arg = 0;
1104 if (procdata->config.show_tree)
1105 {
1106 selection.save(procdata->tree);
1107 }
1108 break;
1109
1110 default:
1111 which = GLIBTOP_KERN_PROC_UID;
1112 arg = getuid ();
1113 if (procdata->config.show_tree)
1114 {
1115 selection.save(procdata->tree);
1116 }
1117 break;
1118 }
1119
1120 pid_list = glibtop_get_proclist (&proclist, which, arg);
1121
1122 /* FIXME: total cpu time elapsed should be calculated on an individual basis here
1123 ** should probably have a total_time_last gint in the ProcInfo structure */
1124 glibtop_get_cpu (&cpu);
1125 procdata->cpu_total_time = MAX(cpu.total - procdata->cpu_total_time_last, 1);
1126 procdata->cpu_total_time_last = cpu.total;
1127
1128 // FIXME: not sure if glibtop always returns a sorted list of pid
1129 // but it is important otherwise refresh_list won't find the parent
1130 std::sort(pid_list, pid_list + proclist.number);
1131
1132 refresh_list (procdata, pid_list, proclist.number);
1133
1134 selection.restore(procdata->tree);
1135
1136 g_free (pid_list);
1137
1138 /* proclist.number == g_list_length(procdata->info) == g_hash_table_size(procdata->pids) */
1139 }
1140
1141
1142 void
proctable_update(ProcData * const procdata)1143 proctable_update (ProcData * const procdata)
1144 {
1145 char* string;
1146
1147 string = make_loadavg_string();
1148 gtk_label_set_text (GTK_LABEL(procdata->loadavg), string);
1149 g_free (string);
1150
1151 proctable_update_list (procdata);
1152 }
1153
1154
1155 void
proctable_free_table(ProcData * const procdata)1156 proctable_free_table (ProcData * const procdata)
1157 {
1158 for (ProcInfo::Iterator it(ProcInfo::begin()); it != ProcInfo::end(); ++it)
1159 delete it->second;
1160
1161 ProcInfo::all.clear();
1162 }
1163
1164
1165 void
proctable_clear_tree(ProcData * const procdata)1166 proctable_clear_tree (ProcData * const procdata)
1167 {
1168 GtkTreeModel *model;
1169
1170 model = gtk_tree_view_get_model (GTK_TREE_VIEW (procdata->tree));
1171
1172 gtk_tree_store_clear (GTK_TREE_STORE (model));
1173
1174 proctable_free_table (procdata);
1175
1176 update_sensitivity(procdata);
1177 }
1178
1179
1180 char*
make_loadavg_string(void)1181 make_loadavg_string(void)
1182 {
1183 glibtop_loadavg buf;
1184
1185 glibtop_get_loadavg(&buf);
1186
1187 return g_strdup_printf(
1188 _("Load averages for the last 1, 5, 15 minutes: "
1189 "%0.2f, %0.2f, %0.2f"),
1190 buf.loadavg[0],
1191 buf.loadavg[1],
1192 buf.loadavg[2]);
1193 }
1194
1195
1196
1197 void
set_icon(Glib::RefPtr<Gdk::Pixbuf> icon)1198 ProcInfo::set_icon(Glib::RefPtr<Gdk::Pixbuf> icon)
1199 {
1200 this->surface = gdk_cairo_surface_create_from_pixbuf (icon->gobj(), 0, NULL);
1201
1202 GtkTreeModel *model;
1203 model = gtk_tree_view_get_model(GTK_TREE_VIEW(ProcData::get_instance()->tree));
1204 gtk_tree_store_set(GTK_TREE_STORE(model), &this->node,
1205 COL_SURFACE, this->surface,
1206 -1);
1207 }
1208