/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Procman tree view and process updating * Copyright (C) 2001 Kevin Vandersloot * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_WNCK #define WNCK_I_KNOW_THIS_IS_UNSTABLE #include #endif #include "application.h" #include "proctable.h" #include "prettytable.h" #include "util.h" #include "interface.h" #include "selinux.h" #include "settings-keys.h" #include "cgroups.h" #include "legacy/treeview.h" #include "systemd.h" #ifdef GDK_WINDOWING_X11 #include #endif ProcInfo* ProcList::find(pid_t pid) { auto it = data.find(pid); return (it == data.end() ? nullptr : &it->second); } static void cb_save_tree_state(gpointer, gpointer data) { GsmApplication * const app = static_cast(data); gsm_tree_view_save_state (app->tree); } static void cb_proctree_destroying (GtkTreeView *self, gpointer data) { g_signal_handlers_disconnect_by_func (self, (gpointer) cb_save_tree_state, data); g_signal_handlers_disconnect_by_func (gtk_tree_view_get_model (self), (gpointer) cb_save_tree_state, data); } static gboolean cb_tree_button_pressed (GtkWidget *widget, GdkEventButton *event, gpointer data) { GsmApplication *app = (GsmApplication *) data; GtkTreePath *path; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); if (!gdk_event_triggers_context_menu ((GdkEvent *) event)) return FALSE; if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (app->tree), event->x, event->y, &path, NULL, NULL, NULL)) return FALSE; if (!gtk_tree_selection_path_is_selected (selection, path)) { if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) gtk_tree_selection_unselect_all (selection); gtk_tree_selection_select_path (selection, path); } gtk_tree_path_free (path); gtk_menu_popup_at_pointer (GTK_MENU (app->popup_menu), NULL); return TRUE; } static gboolean cb_tree_popup_menu (GtkWidget *widget, gpointer data) { GsmApplication *app = (GsmApplication *) data; gtk_menu_popup_at_pointer (GTK_MENU (app->popup_menu), NULL); return TRUE; } void get_last_selected (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { ProcInfo **info = (ProcInfo**) data; gtk_tree_model_get (model, iter, COL_POINTER, info, -1); } static void cb_row_selected (GtkTreeSelection *selection, gpointer data) { GsmApplication *app = (GsmApplication *) data; ProcInfo *selected_process = NULL; gint selected_count = gtk_tree_selection_count_selected_rows (selection); app->selection = selection; gchar *button_text = ngettext("_End Process", "_End Processes", selected_count); gtk_button_set_label (GTK_BUTTON(app->end_process_button), button_text); /* get the most recent selected process and determine if there are ** no selected processes */ gtk_tree_selection_selected_foreach (selection, get_last_selected, &selected_process); if (selected_process) { GVariant *priority; gint nice = selected_process->nice; if (nice < -7) priority = g_variant_new_int32 (-20); else if (nice < -2) priority = g_variant_new_int32 (-5); else if (nice < 3) priority = g_variant_new_int32 (0); else if (nice < 7) priority = g_variant_new_int32 (5); else priority = g_variant_new_int32 (19); GAction *action = g_action_map_lookup_action (G_ACTION_MAP (app->main_window), "priority"); g_action_change_state (action, priority); } update_sensitivity(app); } static gint cb_timeout (gpointer data) { GsmApplication *app = (GsmApplication *) data; guint new_interval; proctable_update (app); if (app->smooth_refresh->get(new_interval)) { app->timeout = g_timeout_add(new_interval, cb_timeout, app); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static void cb_refresh_icons (GtkIconTheme *theme, gpointer data) { GsmApplication *app = (GsmApplication *) data; for (auto& v : app->processes) { app->pretty_table->set_icon(v.second); } proctable_update (app); } static gboolean iter_matches_search_key (GtkTreeModel *model, GtkTreeIter *iter, const gchar *search_text) { char *name; char *user; pid_t pid; char *pids; char *args; gboolean found; char *search_pattern; char **keys; Glib::RefPtr regex; gtk_tree_model_get (model, iter, COL_NAME, &name, COL_USER, &user, COL_PID, &pid, COL_ARGS, &args, -1); pids = g_strdup_printf ("%d", pid); keys = g_strsplit_set(search_text, " |", -1); search_pattern = g_strjoinv ("|", keys); try { regex = Glib::Regex::create(search_pattern, Glib::REGEX_CASELESS); } catch (const Glib::Error& ex) { regex = Glib::Regex::create(Glib::Regex::escape_string(search_pattern), Glib::REGEX_CASELESS); } found = (name && regex->match(name)) || (user && regex->match(user)) || (pids && regex->match(pids)) || (args && regex->match(args)); g_strfreev (keys); g_free (search_pattern); g_free (name); g_free (user); g_free (args); g_free (pids); return found; } static gboolean process_visibility_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GsmApplication * const app = static_cast(data); const gchar * search_text = app->search_entry == NULL ? "" : gtk_entry_get_text (GTK_ENTRY (app->search_entry)); GtkTreePath *tree_path = gtk_tree_model_get_path (model, iter); if (strcmp (search_text, "") == 0) { gtk_tree_path_free (tree_path); return TRUE; } // in case we are in dependencies view, we show (and expand) rows not matching the text, but having a matching child gboolean match = false; if (app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)) { GtkTreeIter child; if (gtk_tree_model_iter_children (model, &child, iter)) { gboolean child_match = FALSE; do { child_match = process_visibility_func (model, &child, data); } while (gtk_tree_model_iter_next (model, &child) && !child_match); match = child_match; } match |= iter_matches_search_key (model, iter, search_text); if (match && (strlen (search_text) > 0)) { gtk_tree_view_expand_to_path (GTK_TREE_VIEW (app->tree), tree_path); } } else { match = iter_matches_search_key (model, iter, search_text); } gtk_tree_path_free (tree_path); return match; } static void proctable_clear_tree (GsmApplication * const app) { GtkTreeModel *model; model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER ( gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT ( gtk_tree_view_get_model (GTK_TREE_VIEW(app->tree)))))); gtk_tree_store_clear (GTK_TREE_STORE (model)); proctable_free_table (app); update_sensitivity(app); } static void cb_show_dependencies_changed(Gio::Settings& settings, Glib::ustring key, GsmApplication* app) { if (app->timeout) { gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (app->tree), settings.get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)); proctable_clear_tree (app); proctable_update (app); } } static void cb_show_whose_processes_changed(Gio::Settings& settings, Glib::ustring key, GsmApplication* app) { if (app->timeout) { proctable_clear_tree (app); proctable_update (app); } } GsmTreeView * proctable_new (GsmApplication * const app) { GsmTreeView *proctree; GtkTreeStore *model; GtkTreeModelFilter *model_filter; GtkTreeModelSort *model_sort; GtkTreeSelection *selection; GtkTreeViewColumn *column; GtkCellRenderer *cell_renderer; const gchar *titles[] = { N_("Process Name"), N_("User"), N_("Status"), N_("Virtual Memory"), N_("Resident Memory"), N_("Writable Memory"), N_("Shared Memory"), N_("X Server Memory"), /* xgettext:no-c-format */ N_("% CPU"), N_("CPU Time"), N_("Started"), N_("Nice"), N_("ID"), N_("Security Context"), N_("Command Line"), N_("Memory"), /* xgettext: combined noun, the function the process is waiting in, see wchan ps(1) */ N_("Waiting Channel"), N_("Control Group"), N_("Unit"), N_("Session"), /* TRANSLATORS: Seat = i.e. the physical seat the session of the process belongs to, only for multi-seat environments. See http://en.wikipedia.org/wiki/Multiseat_configuration */ N_("Seat"), N_("Owner"), N_("Disk read total"), N_("Disk write total"), N_("Disk read"), N_("Disk write"), N_("Priority"), NULL, "POINTER" }; gint i; auto settings = g_settings_get_child (app->settings->gobj (), GSM_SETTINGS_CHILD_PROCESSES); model = gtk_tree_store_new (NUM_COLUMNS, G_TYPE_STRING, /* Process Name */ G_TYPE_STRING, /* User */ G_TYPE_UINT, /* Status */ G_TYPE_ULONG, /* VM Size */ G_TYPE_ULONG, /* Resident Memory */ G_TYPE_ULONG, /* Writable Memory */ G_TYPE_ULONG, /* Shared Memory */ G_TYPE_ULONG, /* X Server Memory */ G_TYPE_DOUBLE, /* % CPU */ G_TYPE_UINT64, /* CPU time */ G_TYPE_ULONG, /* Started */ G_TYPE_INT, /* Nice */ G_TYPE_UINT, /* ID */ G_TYPE_STRING, /* Security Context */ G_TYPE_STRING, /* Arguments */ G_TYPE_ULONG, /* Memory */ G_TYPE_STRING, /* wchan */ G_TYPE_STRING, /* Cgroup */ G_TYPE_STRING, /* Unit */ G_TYPE_STRING, /* Session */ G_TYPE_STRING, /* Seat */ G_TYPE_STRING, /* Owner */ G_TYPE_UINT64, /* Disk read total */ G_TYPE_UINT64, /* Disk write total*/ G_TYPE_UINT64, /* Disk read */ G_TYPE_UINT64, /* Disk write */ G_TYPE_STRING, /* Priority */ GDK_TYPE_PIXBUF, /* Icon */ G_TYPE_POINTER, /* ProcInfo */ G_TYPE_STRING /* Sexy tooltip */ ); model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (model), NULL)); gtk_tree_model_filter_set_visible_func(model_filter, process_visibility_func, app, NULL); model_sort = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (model_filter))); proctree = gsm_tree_view_new (settings, TRUE); gtk_tree_view_set_model (GTK_TREE_VIEW (proctree), GTK_TREE_MODEL (model_sort)); gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (proctree), COL_TOOLTIP); gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (proctree), app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (proctree), FALSE); g_object_unref (G_OBJECT (model)); column = gtk_tree_view_column_new (); cell_renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (column, cell_renderer, FALSE); gtk_tree_view_column_set_attributes (column, cell_renderer, "pixbuf", COL_PIXBUF, NULL); cell_renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, cell_renderer, FALSE); gtk_tree_view_column_set_attributes (column, cell_renderer, "text", COL_NAME, NULL); gtk_tree_view_column_set_title (column, _(titles[0])); gtk_tree_view_column_set_sort_column_id (column, COL_NAME); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_min_width (column, 1); gtk_tree_view_column_set_reorderable(column, TRUE); gsm_tree_view_append_and_bind_column (proctree, column); gtk_tree_view_set_expander_column (GTK_TREE_VIEW (proctree), column); gtk_tree_view_column_set_expand (column, TRUE); for (i = COL_USER; i <= COL_PRIORITY; i++) { GtkTreeViewColumn *col; GtkCellRenderer *cell; PangoAttrList *attrs = NULL; #ifndef HAVE_WNCK if (i == COL_MEMXSERVER) continue; #endif if (i == COL_MEMWRITABLE) continue; cell = gtk_cell_renderer_text_new(); col = gtk_tree_view_column_new(); gtk_tree_view_column_pack_start(col, cell, TRUE); gtk_tree_view_column_set_title(col, _(titles[i])); gtk_tree_view_column_set_resizable(col, TRUE); gtk_tree_view_column_set_sort_column_id(col, i); gtk_tree_view_column_set_reorderable(col, TRUE); gsm_tree_view_append_and_bind_column (proctree, col); // type switch (i) { #ifdef HAVE_WNCK case COL_MEMXSERVER: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::size_cell_data_func, GUINT_TO_POINTER(i), NULL); break; #endif case COL_VMSIZE: case COL_MEMRES: case COL_MEMSHARED: case COL_MEM: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::size_na_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_CPU: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::percentage_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_CPU_TIME: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::duration_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_START_TIME: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::time_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_STATUS: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::status_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_DISK_READ_TOTAL: case COL_DISK_WRITE_TOTAL: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::size_na_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_DISK_READ_CURRENT: case COL_DISK_WRITE_CURRENT: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::io_rate_cell_data_func, GUINT_TO_POINTER(i), NULL); break; case COL_PRIORITY: gtk_tree_view_column_set_cell_data_func(col, cell, &procman::priority_cell_data_func, GUINT_TO_POINTER(COL_NICE), NULL); break; default: gtk_tree_view_column_set_attributes(col, cell, "text", i, NULL); break; } // Tabular Numbers switch (i) { #ifdef HAVE_WNCK case COL_MEMXSERVER: #endif case COL_PID: case COL_VMSIZE: case COL_MEMRES: case COL_MEMSHARED: case COL_MEM: case COL_CPU: case COL_CPU_TIME: case COL_DISK_READ_TOTAL: case COL_DISK_WRITE_TOTAL: case COL_DISK_READ_CURRENT: case COL_DISK_WRITE_CURRENT: case COL_START_TIME: case COL_NICE: case COL_WCHAN: attrs = make_tnum_attr_list (); g_object_set (cell, "attributes", attrs, NULL); g_clear_pointer (&attrs, pango_attr_list_unref); break; default: break; } // sorting switch (i) { #ifdef HAVE_WNCK case COL_MEMXSERVER: #endif case COL_VMSIZE: case COL_MEMRES: case COL_MEMSHARED: case COL_MEM: case COL_CPU: case COL_CPU_TIME: case COL_DISK_READ_TOTAL: case COL_DISK_WRITE_TOTAL: case COL_DISK_READ_CURRENT: case COL_DISK_WRITE_CURRENT: case COL_START_TIME: gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model_sort), i, procman::number_compare_func, GUINT_TO_POINTER (i), NULL); break; case COL_PRIORITY: gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model_sort), i, procman::priority_compare_func, GUINT_TO_POINTER (COL_NICE), NULL); break; default: break; } // xalign switch(i) { case COL_VMSIZE: case COL_MEMRES: case COL_MEMSHARED: #ifdef HAVE_WNCK case COL_MEMXSERVER: #endif case COL_CPU: case COL_NICE: case COL_PID: case COL_DISK_READ_TOTAL: case COL_DISK_WRITE_TOTAL: case COL_DISK_READ_CURRENT: case COL_DISK_WRITE_CURRENT: case COL_CPU_TIME: case COL_MEM: g_object_set(G_OBJECT(cell), "xalign", 1.0f, NULL); break; } gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED); // sizing switch (i) { case COL_ARGS: gtk_tree_view_column_set_min_width(col, 150); break; default: gtk_tree_view_column_set_min_width(column, 20); break; } if (i == COL_ARGS) { gtk_tree_view_column_set_expand(col, TRUE); } else { gtk_tree_view_column_set_expand(col, FALSE); } } app->tree = proctree; app->top_of_tree = NULL; app->last_vscroll_max = 0; app->last_vscroll_value = 0; if (!cgroups_enabled ()) gsm_tree_view_add_excluded_column (proctree, COL_CGROUP); if (!procman::systemd_logind_running()) { gsm_tree_view_add_excluded_column (proctree, COL_UNIT); gsm_tree_view_add_excluded_column (proctree, COL_SESSION); gsm_tree_view_add_excluded_column (proctree, COL_SEAT); gsm_tree_view_add_excluded_column (proctree, COL_OWNER); } if (!can_show_security_context_column ()) gsm_tree_view_add_excluded_column (proctree, COL_SECURITYCONTEXT); gsm_tree_view_load_state (proctree); GtkIconTheme* theme = gtk_icon_theme_get_default(); g_signal_connect(G_OBJECT (theme), "changed", G_CALLBACK (cb_refresh_icons), app); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree)); app->selection = selection; gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (cb_row_selected), app); g_signal_connect (G_OBJECT (proctree), "popup_menu", G_CALLBACK (cb_tree_popup_menu), app); g_signal_connect (G_OBJECT (proctree), "button_press_event", G_CALLBACK (cb_tree_button_pressed), app); g_signal_connect (G_OBJECT (proctree), "destroy", G_CALLBACK (cb_proctree_destroying), app); g_signal_connect (G_OBJECT (proctree), "columns-changed", G_CALLBACK (cb_save_tree_state), app); g_signal_connect (G_OBJECT (model_sort), "sort-column-changed", G_CALLBACK (cb_save_tree_state), app); app->settings->signal_changed(GSM_SETTING_SHOW_DEPENDENCIES).connect([app](const Glib::ustring& key) { cb_show_dependencies_changed(*app->settings.operator->(), key, app); }); app->settings->signal_changed(GSM_SETTING_SHOW_WHOSE_PROCESSES).connect([app](const Glib::ustring& key) { cb_show_whose_processes_changed(*app->settings.operator->(), key, app); }); gtk_widget_show (GTK_WIDGET (proctree)); return proctree; } static void get_process_name (ProcInfo *info, const gchar *cmd, const GStrv args) { if (args) { // look for /usr/bin/very_long_name // and also /usr/bin/interpreter /usr/.../very_long_name // which may have use prctl to alter 'cmd' name for (int i = 0; i != 2 && args[i]; ++i) { char* basename; basename = g_path_get_basename(args[i]); if (g_str_has_prefix(basename, cmd)) { info->name = make_string(basename); return; } g_free(basename); } } info->name = cmd; } std::string ProcInfo::lookup_user(guint uid) { static std::map users; auto p = users.insert({uid, ""}); // procman_debug("User lookup for uid %u: %s", uid, (p.second ? "MISS" : "HIT")); if (p.second) { struct passwd* pwd; pwd = getpwuid(uid); if (pwd && pwd->pw_name) p.first->second = pwd->pw_name; else { char username[16]; g_sprintf(username, "%u", uid); p.first->second = username; } } return p.first->second; } void ProcInfo::set_user(guint uid) { if (G_LIKELY(this->uid == uid)) return; this->uid = uid; this->user = lookup_user(uid); } void get_process_memory_writable (ProcInfo *info) { glibtop_proc_map buf; glibtop_map_entry *maps; maps = glibtop_get_proc_map(&buf, info->pid); const bool use_private_dirty = buf.flags & (1 << GLIBTOP_MAP_ENTRY_PRIVATE_DIRTY); gulong memwritable = 0; const unsigned number = buf.number; for (unsigned i = 0; i < number; ++i) { if (use_private_dirty) { // clang++ 3.4 is not smart enough to move this invariant out of the loop // but who cares ? memwritable += maps[i].private_dirty; } else if (maps[i].perm & GLIBTOP_MAP_PERM_WRITE) { memwritable += maps[i].size; } } info->memwritable = memwritable; g_free(maps); } static void get_process_memory_info(ProcInfo *info) { glibtop_proc_mem procmem; #ifdef HAVE_WNCK info->memxserver = 0; #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) { WnckResourceUsage xresources; wnck_pid_read_resource_usage (gdk_display_get_default (), info->pid, &xresources); info->memxserver = xresources.total_bytes_estimate; } #endif #endif glibtop_get_proc_mem(&procmem, info->pid); info->vmsize = procmem.vsize; info->memres = procmem.resident; info->memshared = procmem.share; info->mem = info->memres - info->memshared; #ifdef HAVE_WNCK info->mem += info->memxserver; #endif } static void update_info_mutable_cols(ProcInfo *info) { GtkTreeModel *model; model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER ( gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT( gtk_tree_view_get_model (GTK_TREE_VIEW(GsmApplication::get()->tree)))))); using procman::tree_store_update; tree_store_update(model, &info->node, COL_STATUS, info->status); tree_store_update(model, &info->node, COL_USER, info->user.c_str()); tree_store_update(model, &info->node, COL_VMSIZE, info->vmsize); tree_store_update(model, &info->node, COL_MEMRES, info->memres); tree_store_update(model, &info->node, COL_MEMSHARED, info->memshared); #ifdef HAVE_WNCK tree_store_update(model, &info->node, COL_MEMXSERVER, info->memxserver); #endif tree_store_update(model, &info->node, COL_CPU, info->pcpu); tree_store_update(model, &info->node, COL_CPU_TIME, info->cpu_time); tree_store_update(model, &info->node, COL_DISK_READ_TOTAL, info->disk_read_bytes_total); tree_store_update(model, &info->node, COL_DISK_WRITE_TOTAL, info->disk_write_bytes_total); tree_store_update(model, &info->node, COL_DISK_READ_CURRENT, info->disk_read_bytes_current); tree_store_update(model, &info->node, COL_DISK_WRITE_CURRENT, info->disk_write_bytes_current); tree_store_update(model, &info->node, COL_START_TIME, info->start_time); tree_store_update(model, &info->node, COL_NICE, info->nice); tree_store_update(model, &info->node, COL_MEM, info->mem); tree_store_update(model, &info->node, COL_WCHAN, info->wchan.c_str()); tree_store_update(model, &info->node, COL_CGROUP, info->cgroup_name.c_str()); tree_store_update(model, &info->node, COL_UNIT, info->unit.c_str()); tree_store_update(model, &info->node, COL_SESSION, info->session.c_str()); tree_store_update(model, &info->node, COL_SEAT, info->seat.c_str()); tree_store_update(model, &info->node, COL_OWNER, info->owner.c_str()); } static void insert_info_to_tree (ProcInfo *info, GsmApplication *app, bool forced = false) { GtkTreeModel *model; GtkTreeModel *filtered; GtkTreeModel *sorted; sorted = gtk_tree_view_get_model (GTK_TREE_VIEW(app->tree)); filtered = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(sorted)); model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filtered)); if (app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)) { ProcInfo *parent = 0; if (not forced) parent = app->processes.find(info->ppid); if (parent) { GtkTreePath *parent_node = gtk_tree_model_get_path(model, &parent->node); gtk_tree_store_insert(GTK_TREE_STORE(model), &info->node, &parent->node, 0); GtkTreePath *filtered_parent = gtk_tree_model_filter_convert_child_path_to_path (GTK_TREE_MODEL_FILTER (filtered), parent_node); if (filtered_parent != NULL) { GtkTreePath *sorted_parent = gtk_tree_model_sort_convert_child_path_to_path (GTK_TREE_MODEL_SORT (sorted), filtered_parent); if (sorted_parent != NULL) { if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(app->tree), sorted_parent) #ifdef __linux__ // on linuxes we don't want to expand kthreadd by default (always has pid 2) && (parent->pid != 2) #endif ) gtk_tree_view_expand_row(GTK_TREE_VIEW(app->tree), sorted_parent, FALSE); gtk_tree_path_free (sorted_parent); } gtk_tree_path_free (filtered_parent); } gtk_tree_path_free (parent_node); } else gtk_tree_store_insert(GTK_TREE_STORE(model), &info->node, NULL, 0); } else gtk_tree_store_insert (GTK_TREE_STORE (model), &info->node, NULL, 0); gtk_tree_store_set (GTK_TREE_STORE (model), &info->node, COL_POINTER, info, COL_NAME, info->name.c_str(), COL_ARGS, info->arguments.c_str(), COL_TOOLTIP, info->tooltip.c_str(), COL_PID, info->pid, COL_SECURITYCONTEXT, info->security_context.c_str(), -1); app->pretty_table->set_icon(*info); procman_debug("inserted %d%s", info->pid, (forced ? " (forced)" : "")); } /* Removing a node with children - make sure the children are queued ** to be readded. */ template static void remove_info_from_tree (GsmApplication *app, GtkTreeModel *model, ProcInfo& current, List &orphans, unsigned lvl = 0) { GtkTreeIter child_node; if (std::find(orphans.begin(), orphans.end(), ¤t) != orphans.end()) { procman_debug("[%u] %d already removed from tree", lvl, int(current.pid)); return; } procman_debug("[%u] pid %d, %d children", lvl, int(current.pid), gtk_tree_model_iter_n_children(model, ¤t.node)); // it is not possible to iterate&erase over a treeview so instead we // just pop one child after another and recursively remove it and // its children while (gtk_tree_model_iter_children(model, &child_node, ¤t.node)) { ProcInfo *child = 0; gtk_tree_model_get(model, &child_node, COL_POINTER, &child, -1); remove_info_from_tree(app, model, *child, orphans, lvl + 1); } g_assert(not gtk_tree_model_iter_has_child(model, ¤t.node)); orphans.push_back(¤t); gtk_tree_store_remove(GTK_TREE_STORE(model), ¤t.node); procman::poison(current.node, 0x69); } static std::string get_proc_kernel_wchan(glibtop_proc_kernel& obj) { char buf[40] = {0}; g_strlcpy(buf, obj.wchan, sizeof(buf)); buf[sizeof(buf)-1] = '\0'; return buf; } static void update_info (GsmApplication *app, ProcInfo *info) { glibtop_proc_state procstate; glibtop_proc_uid procuid; glibtop_proc_time proctime; glibtop_proc_kernel prockernel; glibtop_proc_io procio; gdouble update_interval_seconds = app->config.update_interval / 1000; glibtop_get_proc_kernel(&prockernel, info->pid); info->wchan = get_proc_kernel_wchan(prockernel); glibtop_get_proc_state (&procstate, info->pid); info->status = procstate.state; glibtop_get_proc_uid (&procuid, info->pid); glibtop_get_proc_time (&proctime, info->pid); glibtop_get_proc_io (&procio, info->pid); get_process_memory_info(info); info->set_user(procstate.uid); // if the cpu time has increased reset the status to running // regardless of kernel state (#606579) guint64 difference = proctime.rtime - info->cpu_time; if (difference > 0) info->status = GLIBTOP_PROCESS_RUNNING; guint cpu_scale = 100; if (not app->config.solaris_mode) cpu_scale *= app->config.num_cpus; info->pcpu = (gdouble)difference * cpu_scale / app->cpu_total_time; info->pcpu = MIN(info->pcpu, cpu_scale); app->processes.cpu_times[info->pid] = info->cpu_time = proctime.rtime; info->nice = procuid.nice; info->disk_write_bytes_current = (procio.disk_wbytes - info->disk_write_bytes_total)/update_interval_seconds; info->disk_read_bytes_current = (procio.disk_rbytes - info->disk_read_bytes_total)/update_interval_seconds; info->disk_write_bytes_total = procio.disk_wbytes; info->disk_read_bytes_total = procio.disk_rbytes; // set the ppid only if one can exist // i.e. pid=0 can never have a parent if (info->pid > 0) { info->ppid = procuid.ppid; } g_assert(info->pid != info->ppid); g_assert(info->ppid != -1 || info->pid == 0); /* get cgroup data */ get_process_cgroup_info(*info); procman::get_process_systemd_info(info); } ProcInfo::ProcInfo(pid_t pid) : node(), pixbuf(), pid(pid), ppid(-1), uid(-1) { ProcInfo * const info = this; glibtop_proc_state procstate; glibtop_proc_time proctime; glibtop_proc_args procargs; gchar** arguments; glibtop_get_proc_state (&procstate, pid); glibtop_get_proc_time (&proctime, pid); arguments = glibtop_get_proc_argv (&procargs, pid, 0); /* FIXME : wrong. name and arguments may change with exec* */ get_process_name (info, procstate.cmd, static_cast(arguments)); std::string tooltip = make_string(g_strjoinv(" ", arguments)); if (tooltip.empty()) tooltip = procstate.cmd; info->tooltip = make_string(g_markup_escape_text(tooltip.c_str(), -1)); info->arguments = make_string(g_strescape(tooltip.c_str(), "\\\"")); g_strfreev(arguments); guint64 cpu_time = proctime.rtime; auto app = GsmApplication::get(); auto it = app->processes.cpu_times.find(pid); if (it != app->processes.cpu_times.end()) { if (proctime.rtime >= it->second) cpu_time = it->second; } info->cpu_time = cpu_time; info->start_time = proctime.start_time; get_process_selinux_context (info); get_process_cgroup_info(*info); get_process_systemd_info(info); } static void refresh_list (GsmApplication *app, const pid_t* pid_list, const guint n) { typedef std::list ProcList; ProcList addition; GtkTreeModel *model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER ( gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT ( gtk_tree_view_get_model (GTK_TREE_VIEW(app->tree)))))); guint i; // Add or update processes in the process list for(i = 0; i < n; ++i) { ProcInfo *info = app->processes.find(pid_list[i]); if (!info) { info = app->processes.add(pid_list[i]); addition.push_back(info); } update_info (app, info); } // Remove dead processes from the process list and from the // tree. children are queued to be readded at the right place // in the tree. const std::set pids(pid_list, pid_list + n); auto it = std::begin(app->processes); while (it != std::end(app->processes)) { auto& info = it->second; if (pids.find(info.pid) == pids.end()) { procman_debug("ripping %d", info.pid); remove_info_from_tree(app, model, info, addition); addition.remove(&info); it = app->processes.erase(it); } else { ++it; } } // INVARIANT // pid_list == ProcInfo::all + addition if (app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)) { // insert process in the tree. walk through the addition list // (new process + process that have a new parent). This loop // handles the dependencies because we cannot insert a process // until its parent is in the tree. std::set in_tree(pids); for (ProcList::iterator it(addition.begin()); it != addition.end(); ++it) in_tree.erase((*it)->pid); while (not addition.empty()) { procman_debug("looking for %d parents", int(addition.size())); ProcList::iterator it(addition.begin()); while (it != addition.end()) { procman_debug("looking for %d's parent with ppid %d", int((*it)->pid), int((*it)->ppid)); // inserts the process in the treeview if : // - it has no parent (ppid = -1), // ie it is for example the [kernel] on FreeBSD // - it is init // - its parent is already in tree // - its parent is unreachable // // rounds == 2 means that addition contains processes with // unreachable parents // // FIXME: this is broken if the unreachable parent becomes active // i.e. it gets active or changes ower // so we just clear the tree on __each__ update // see proctable_update (ProcData * const procdata) if ((*it)->ppid <= 0 or in_tree.find((*it)->ppid) != in_tree.end()) { insert_info_to_tree(*it, app); in_tree.insert((*it)->pid); it = addition.erase(it); continue; } ProcInfo *parent = app->processes.find((*it)->ppid); // if the parent is unreachable if (not parent) { // or std::find(addition.begin(), addition.end(), parent) == addition.end()) { insert_info_to_tree(*it, app, true); in_tree.insert((*it)->pid); it = addition.erase(it); continue; } ++it; } } } else { // don't care of the tree for (auto& v : addition) insert_info_to_tree(v, app); } for (auto& v : app->processes) update_info_mutable_cols(&v.second); } void proctable_update (GsmApplication *app) { pid_t* pid_list; glibtop_proclist proclist; glibtop_cpu cpu; int which = 0; int arg = 0; auto whose_processes = app->settings->get_string(GSM_SETTING_SHOW_WHOSE_PROCESSES); if (whose_processes == "all") { which = GLIBTOP_KERN_PROC_ALL; arg = 0; } else if (whose_processes == "active") { which = GLIBTOP_KERN_PROC_ALL | GLIBTOP_EXCLUDE_IDLE; arg = 0; } else if (whose_processes == "user") { which = GLIBTOP_KERN_PROC_UID; arg = getuid (); } pid_list = glibtop_get_proclist (&proclist, which, arg); /* FIXME: total cpu time elapsed should be calculated on an individual basis here ** should probably have a total_time_last gint in the ProcInfo structure */ glibtop_get_cpu (&cpu); app->cpu_total_time = MAX(cpu.total - app->cpu_total_time_last, 1); app->cpu_total_time_last = cpu.total; // FIXME: not sure if glibtop always returns a sorted list of pid // but it is important otherwise refresh_list won't find the parent std::sort(pid_list, pid_list + proclist.number); refresh_list (app, pid_list, proclist.number); // juggling with tree scroll position to fix https://bugzilla.gnome.org/show_bug.cgi?id=92724 GtkTreePath* current_top; if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(app->tree), 0,0, ¤t_top, NULL, NULL, NULL)) { GtkAdjustment *vadjustment = GTK_ADJUSTMENT (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (app->tree))); gdouble current_max = gtk_adjustment_get_upper(vadjustment); gdouble current_value = gtk_adjustment_get_value(vadjustment); if (app->top_of_tree) { // if the visible cell from the top of the tree is still the same, as last time if (gtk_tree_path_compare (app->top_of_tree, current_top) == 0) { //but something from the scroll parameters has changed compared to the last values if (app->last_vscroll_value == 0 && current_value != 0) { // the tree was scrolled to top, and something has been added above the current top row gtk_tree_view_scroll_to_point(GTK_TREE_VIEW(app->tree), -1, 0); } else if (current_max > app->last_vscroll_max && app->last_vscroll_max == app->last_vscroll_value) { // the tree was scrolled to bottom, something has been added below the current bottom row gtk_tree_view_scroll_to_point(GTK_TREE_VIEW(app->tree), -1, current_max); } } gtk_tree_path_free(app->top_of_tree); } app->top_of_tree = current_top; app->last_vscroll_value = current_value; app->last_vscroll_max = current_max; } g_free (pid_list); /* proclist.number == g_list_length(procdata->info) == g_hash_table_size(procdata->pids) */ } void proctable_free_table (GsmApplication * const app) { app->processes.clear(); } void ProcInfo::set_icon(Glib::RefPtr icon) { this->pixbuf = icon; GtkTreeModel *model; model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER ( gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT( gtk_tree_view_get_model (GTK_TREE_VIEW(GsmApplication::get()->tree)))))); gtk_tree_store_set(GTK_TREE_STORE(model), &this->node, COL_PIXBUF, (this->pixbuf ? this->pixbuf->gobj() : NULL), -1); } void proctable_freeze (GsmApplication *app) { if (app->timeout) { g_source_remove (app->timeout); app->timeout = 0; } } void proctable_thaw (GsmApplication *app) { if (app->timeout) return; app->timeout = g_timeout_add (app->config.update_interval, cb_timeout, app); } void proctable_reset_timeout (GsmApplication *app) { proctable_freeze (app); proctable_thaw (app); }