diff options
Diffstat (limited to 'src/memmaps.cpp')
-rw-r--r-- | src/memmaps.cpp | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/src/memmaps.cpp b/src/memmaps.cpp new file mode 100644 index 0000000..8827d9f --- /dev/null +++ b/src/memmaps.cpp @@ -0,0 +1,491 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +#include <config.h> + +#include <glibtop/procmap.h> +#include <glibtop/mountlist.h> +#include <sys/stat.h> +#include <glib/gi18n.h> + +#include <string> +#include <map> +#include <sstream> +#include <iomanip> +#include <stdexcept> + +using std::string; + + +#include "application.h" +#include "memmaps.h" +#include "proctable.h" +#include "settings-keys.h" +#include "legacy/treeview.h" +#include "util.h" + + +/* be careful with this enum, you could break the column names */ +enum +{ + MMAP_COL_FILENAME, + MMAP_COL_VMSTART, + MMAP_COL_VMEND, + MMAP_COL_VMSZ, + MMAP_COL_FLAGS, + MMAP_COL_VMOFFSET, + MMAP_COL_PRIVATE_CLEAN, + MMAP_COL_PRIVATE_DIRTY, + MMAP_COL_SHARED_CLEAN, + MMAP_COL_SHARED_DIRTY, + MMAP_COL_DEVICE, + MMAP_COL_INODE, + MMAP_COL_MAX +}; + + +namespace +{ + class OffsetFormater + { + string format; + + public: + + void set(const glibtop_map_entry &last_map) + { + this->format = (last_map.end <= G_MAXUINT32) ? "%08" G_GINT64_MODIFIER "x" : "%016" G_GINT64_MODIFIER "x"; + } + + string operator()(guint64 v) const + { + char buffer[17]; + g_snprintf(buffer, sizeof buffer, this->format.c_str(), v); + return buffer; + } + }; + + class InodeDevices + { + typedef std::map<guint16, string> Map; + Map devices; + + public: + + void update() + { + this->devices.clear(); + + glibtop_mountlist list; + glibtop_mountentry *entries = glibtop_get_mountlist(&list, 1); + + for (unsigned i = 0; i != list.number; ++i) { + struct stat buf; + + if (stat(entries[i].devname, &buf) != -1) + this->devices[buf.st_rdev] = entries[i].devname; + } + + g_free(entries); + } + + string get(guint64 dev64) + { + if (dev64 == 0) + return ""; + + guint16 dev = dev64 & 0xffff; + + if (dev != dev64) + g_warning("weird device %" G_GINT64_MODIFIER "x", dev64); + + Map::iterator it(this->devices.find(dev)); + + if (it != this->devices.end()) + return it->second; + + guint8 major, minor; + major = dev >> 8; + minor = dev; + + std::ostringstream out; + out << std::hex + << std::setfill('0') + << std::setw(2) << unsigned(major) + << ':' + << std::setw(2) << unsigned(minor); + + this->devices[dev] = out.str(); + return out.str(); + } + }; + + + class MemMapsData + { + public: + guint timer; + GsmTreeView *tree; + ProcInfo *info; + OffsetFormater format; + mutable InodeDevices devices; + + MemMapsData(GsmTreeView *a_tree) + : timer(), + tree(a_tree), + info(NULL), + format(), + devices() + { + gsm_tree_view_load_state (this->tree); + } + + ~MemMapsData() + { + gsm_tree_view_save_state (this->tree); + } + }; +} + + +struct glibtop_map_entry_cmp +{ + bool operator()(const glibtop_map_entry &a, const guint64 start) const + { + return a.start < start; + } + + bool operator()(const guint64 &start, const glibtop_map_entry &a) const + { + return start < a.start; + } + +}; + + +static void +update_row(GtkTreeModel *model, GtkTreeIter &row, const MemMapsData &mm, const glibtop_map_entry *memmaps) +{ + guint64 size; + string filename, device; + string vmstart, vmend, vmoffset; + char flags[5] = "----"; + + size = memmaps->end - memmaps->start; + + if(memmaps->perm & GLIBTOP_MAP_PERM_READ) flags [0] = 'r'; + if(memmaps->perm & GLIBTOP_MAP_PERM_WRITE) flags [1] = 'w'; + if(memmaps->perm & GLIBTOP_MAP_PERM_EXECUTE) flags [2] = 'x'; + if(memmaps->perm & GLIBTOP_MAP_PERM_SHARED) flags [3] = 's'; + if(memmaps->perm & GLIBTOP_MAP_PERM_PRIVATE) flags [3] = 'p'; + + if (memmaps->flags & (1 << GLIBTOP_MAP_ENTRY_FILENAME)) + filename = memmaps->filename; + + vmstart = mm.format(memmaps->start); + vmend = mm.format(memmaps->end); + vmoffset = mm.format(memmaps->offset); + device = mm.devices.get(memmaps->device); + + gtk_list_store_set (GTK_LIST_STORE (model), &row, + MMAP_COL_FILENAME, filename.c_str(), + MMAP_COL_VMSTART, vmstart.c_str(), + MMAP_COL_VMEND, vmend.c_str(), + MMAP_COL_VMSZ, size, + MMAP_COL_FLAGS, flags, + MMAP_COL_VMOFFSET, vmoffset.c_str(), + MMAP_COL_PRIVATE_CLEAN, memmaps->private_clean, + MMAP_COL_PRIVATE_DIRTY, memmaps->private_dirty, + MMAP_COL_SHARED_CLEAN, memmaps->shared_clean, + MMAP_COL_SHARED_DIRTY, memmaps->shared_dirty, + MMAP_COL_DEVICE, device.c_str(), + MMAP_COL_INODE, memmaps->inode, + -1); +} + + + + +static void +update_memmaps_dialog (MemMapsData *mmdata) +{ + GtkTreeModel *model; + glibtop_map_entry *memmaps; + glibtop_proc_map procmap; + + memmaps = glibtop_get_proc_map (&procmap, mmdata->info->pid); + /* process has disappeared */ + if(!memmaps or procmap.number == 0) return; + + mmdata->format.set(memmaps[procmap.number - 1]); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (mmdata->tree)); + + GtkTreeIter iter; + + typedef std::map<guint64, GtkTreeIter> IterCache; + IterCache iter_cache; + + /* + removes the old maps and + also fills a cache of start -> iter in order to speed + up add + */ + + if (gtk_tree_model_get_iter_first(model, &iter)) { + while (true) { + char *vmstart = 0; + guint64 start; + gtk_tree_model_get(model, &iter, + MMAP_COL_VMSTART, &vmstart, + -1); + + try { + std::istringstream(vmstart) >> std::hex >> start; + } catch (std::logic_error &e) { + g_warning("Could not parse %s", vmstart); + start = 0; + } + + g_free(vmstart); + + bool found = std::binary_search(memmaps, memmaps + procmap.number, + start, glibtop_map_entry_cmp()); + + if (found) { + iter_cache[start] = iter; + if (!gtk_tree_model_iter_next(model, &iter)) + break; + } else { + if (!gtk_list_store_remove(GTK_LIST_STORE(model), &iter)) + break; + } + } + } + + mmdata->devices.update(); + + /* + add the new maps + */ + + for (guint i = 0; i != procmap.number; i++) { + GtkTreeIter iter; + IterCache::iterator it(iter_cache.find(memmaps[i].start)); + + if (it != iter_cache.end()) + iter = it->second; + else + gtk_list_store_prepend(GTK_LIST_STORE(model), &iter); + + update_row(model, iter, *mmdata, &memmaps[i]); + } + + g_free (memmaps); +} + + + +static void +dialog_response (GtkDialog * dialog, gint response_id, gpointer data) +{ + MemMapsData * const mmdata = static_cast<MemMapsData*>(data); + + g_source_remove (mmdata->timer); + + delete mmdata; + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + + +static MemMapsData* +create_memmapsdata (GsmApplication *app) +{ + GsmTreeView *tree; + GtkListStore *model; + guint i; + + const gchar * const titles[] = { + N_("Filename"), + // xgettext: virtual memory start + N_("VM Start"), + // xgettext: virtual memory end + N_("VM End"), + // xgettext: virtual memory syze + N_("VM Size"), + N_("Flags"), + // xgettext: virtual memory offset + N_("VM Offset"), + // xgettext: memory that has not been modified since + // it has been allocated + N_("Private clean"), + // xgettext: memory that has been modified since it + // has been allocated + N_("Private dirty"), + // xgettext: shared memory that has not been modified + // since it has been allocated + N_("Shared clean"), + // xgettext: shared memory that has been modified + // since it has been allocated + N_("Shared dirty"), + N_("Device"), + N_("Inode") + }; + + model = gtk_list_store_new (MMAP_COL_MAX, + G_TYPE_STRING, /* MMAP_COL_FILENAME */ + G_TYPE_STRING, /* MMAP_COL_VMSTART */ + G_TYPE_STRING, /* MMAP_COL_VMEND */ + G_TYPE_UINT64, /* MMAP_COL_VMSZ */ + G_TYPE_STRING, /* MMAP_COL_FLAGS */ + G_TYPE_STRING, /* MMAP_COL_VMOFFSET */ + G_TYPE_UINT64, /* MMAP_COL_PRIVATE_CLEAN */ + G_TYPE_UINT64, /* MMAP_COL_PRIVATE_DIRTY */ + G_TYPE_UINT64, /* MMAP_COL_SHARED_CLEAN */ + G_TYPE_UINT64, /* MMAP_COL_SHARED_DIRTY */ + G_TYPE_STRING, /* MMAP_COL_DEVICE */ + G_TYPE_UINT64 /* MMAP_COL_INODE */ + ); + + auto settings = g_settings_get_child (app->settings->gobj (), GSM_SETTINGS_CHILD_MEMMAP); + + tree = gsm_tree_view_new (settings, FALSE); + gtk_tree_view_set_model (GTK_TREE_VIEW (tree), GTK_TREE_MODEL (model)); + g_object_unref (G_OBJECT (model)); + + auto font = get_monospace_system_font_name (); + + for (i = 0; i < MMAP_COL_MAX; i++) { + GtkCellRenderer *cell; + GtkTreeViewColumn *col; + PangoAttrList *attrs; + + 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); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), col); + + attrs = make_tnum_attr_list (); + g_object_set (cell, "attributes", attrs, NULL); + g_clear_pointer (&attrs, pango_attr_list_unref); + + switch (i) { + case MMAP_COL_PRIVATE_CLEAN: + case MMAP_COL_PRIVATE_DIRTY: + case MMAP_COL_SHARED_CLEAN: + case MMAP_COL_SHARED_DIRTY: + case MMAP_COL_VMSZ: + gtk_tree_view_column_set_cell_data_func(col, cell, + &procman::size_cell_data_func, + GUINT_TO_POINTER(i), + NULL); + + g_object_set(cell, "xalign", 1.0f, NULL); + break; + + default: + gtk_tree_view_column_set_attributes(col, cell, "text", i, NULL); + break; + } + + switch (i) { + case MMAP_COL_VMSTART: + case MMAP_COL_VMEND: + case MMAP_COL_FLAGS: + case MMAP_COL_VMOFFSET: + g_object_set (cell, "font", font.c_str (), NULL); + break; + } + } + + return new MemMapsData(tree); +} + + +static gboolean +memmaps_timer (gpointer data) +{ + MemMapsData * const mmdata = static_cast<MemMapsData*>(data); + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (mmdata->tree)); + g_assert(model); + + update_memmaps_dialog (mmdata); + + return TRUE; +} + + +static void +create_single_memmaps_dialog (GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + GsmApplication *app = static_cast<GsmApplication *>(data); + MemMapsData *mmdata; + GtkDialog *memmapsdialog; + GtkBox *dialog_box; + GtkLabel *label; + GtkScrolledWindow *scrolled; + ProcInfo *info; + + gtk_tree_model_get (model, iter, COL_POINTER, &info, -1); + + if (!info) + return; + + mmdata = create_memmapsdata (app); + mmdata->info = info; + + memmapsdialog = GTK_DIALOG (g_object_new (GTK_TYPE_DIALOG, + "title", _("Memory Maps"), + "use-header-bar", TRUE, + "destroy-with-parent", TRUE, NULL)); + + gtk_window_set_resizable(GTK_WINDOW(memmapsdialog), TRUE); + gtk_window_set_default_size(GTK_WINDOW(memmapsdialog), 620, 400); + gtk_container_set_border_width(GTK_CONTAINER(memmapsdialog), 5); + + dialog_box = GTK_BOX (gtk_dialog_get_content_area (memmapsdialog)); + gtk_container_set_border_width (GTK_CONTAINER (dialog_box), 5); + + label = procman_make_label_for_mmaps_or_ofiles ( + _("_Memory maps for process ā%sā (PID %u):"), + info->name.c_str(), + info->pid); + + gtk_box_pack_start (dialog_box, GTK_WIDGET (label), FALSE, TRUE, 0); + + + scrolled = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL)); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + + gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (mmdata->tree)); + gtk_label_set_mnemonic_widget (label, GTK_WIDGET (mmdata->tree)); + + gtk_box_pack_start (dialog_box, GTK_WIDGET (scrolled), TRUE, TRUE, 0); + + g_signal_connect(G_OBJECT(memmapsdialog), "response", + G_CALLBACK(dialog_response), mmdata); + + gtk_window_set_transient_for (GTK_WINDOW (memmapsdialog), GTK_WINDOW (GsmApplication::get()->main_window)); + gtk_widget_show_all (GTK_WIDGET (memmapsdialog)); + mmdata->timer = g_timeout_add_seconds (5, memmaps_timer, mmdata); + + update_memmaps_dialog (mmdata); +} + + +void +create_memmaps_dialog (GsmApplication *app) +{ + /* TODO: do we really want to open multiple dialogs ? */ + gtk_tree_selection_selected_foreach (app->selection, create_single_memmaps_dialog, + app); +} |