summaryrefslogtreecommitdiff
path: root/Userland/Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
blob: c3f198bfab6173e8e922e8f9dfb8344316900a3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2022, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include "ProcessMemoryMapWidget.h"
#include <LibCore/Timer.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/JsonArrayModel.h>
#include <LibGUI/Painter.h>
#include <LibGUI/SortingProxyModel.h>
#include <LibGUI/TableView.h>
#include <LibGfx/Palette.h>

REGISTER_WIDGET(SystemMonitor, ProcessMemoryMapWidget)

namespace SystemMonitor {

class PagemapPaintingDelegate final : public GUI::TableCellPaintingDelegate {
public:
    virtual ~PagemapPaintingDelegate() override = default;

    virtual void paint(GUI::Painter& painter, Gfx::IntRect const& a_rect, Gfx::Palette const&, const GUI::ModelIndex& index) override
    {
        auto rect = a_rect.shrunken(2, 2);
        auto pagemap = index.data(GUI::ModelRole::Custom).to_deprecated_string();

        float scale_factor = (float)pagemap.length() / (float)rect.width();

        for (int i = 0; i < rect.width(); ++i) {
            int x = rect.x() + i;
            char c = pagemap[(float)i * scale_factor];
            Color color;
            if (c == 'N') // Null (no page at all, typically an inode-backed page that hasn't been paged in.)
                color = Color::White;
            else if (c == 'Z') // Zero (globally shared zero page, typically an untouched anonymous page.)
                color = Color::from_rgb(0xc0c0ff);
            else if (c == 'P') // Physical (a resident page)
                color = Color::Black;
            else
                VERIFY_NOT_REACHED();

            painter.draw_line({ x, rect.top() }, { x, rect.bottom() }, color);
        }

        painter.draw_rect(rect, Color::Black);
    }
};

ErrorOr<NonnullRefPtr<ProcessMemoryMapWidget>> ProcessMemoryMapWidget::try_create()
{
    auto widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ProcessMemoryMapWidget()));
    TRY(widget->try_set_layout<GUI::VerticalBoxLayout>(4));
    widget->m_table_view = TRY(widget->try_add<GUI::TableView>());

    Vector<GUI::JsonArrayModel::FieldSpec> pid_vm_fields;
    TRY(pid_vm_fields.try_empend(
        "Address"_short_string, Gfx::TextAlignment::CenterLeft,
        [](auto& object) { return DeprecatedString::formatted("{:p}", object.get_u64("address"sv).value_or(0)); },
        [](auto& object) { return object.get_u64("address"sv).value_or(0); }));
    TRY(pid_vm_fields.try_empend("size", "Size"_short_string, Gfx::TextAlignment::CenterRight));
    TRY(pid_vm_fields.try_empend("amount_resident", TRY("Resident"_string), Gfx::TextAlignment::CenterRight));
    TRY(pid_vm_fields.try_empend("amount_dirty", "Dirty"_short_string, Gfx::TextAlignment::CenterRight));
    TRY(pid_vm_fields.try_empend("Access"_short_string, Gfx::TextAlignment::CenterLeft, [](auto& object) {
        StringBuilder builder;
        if (object.get_bool("readable"sv).value_or(false))
            builder.append('R');
        if (object.get_bool("writable"sv).value_or(false))
            builder.append('W');
        if (object.get_bool("executable"sv).value_or(false))
            builder.append('X');
        if (object.get_bool("shared"sv).value_or(false))
            builder.append('S');
        if (object.get_bool("syscall"sv).value_or(false))
            builder.append('C');
        if (object.get_bool("stack"sv).value_or(false))
            builder.append('T');
        return builder.to_deprecated_string();
    }));
    TRY(pid_vm_fields.try_empend(TRY("VMObject type"_string), Gfx::TextAlignment::CenterLeft, [](auto& object) {
        auto type = object.get_deprecated_string("vmobject"sv).value_or({});
        if (type.ends_with("VMObject"sv))
            type = type.substring(0, type.length() - 8);
        return type;
    }));
    TRY(pid_vm_fields.try_empend(TRY("Purgeable"_string), Gfx::TextAlignment::CenterLeft, [](auto& object) {
        if (object.get_bool("volatile"sv).value_or(false))
            return "Volatile";
        return "Non-volatile";
    }));
    TRY(pid_vm_fields.try_empend(
        TRY("Page map"_string), Gfx::TextAlignment::CenterLeft,
        [](auto&) {
            return GUI::Variant();
        },
        [](auto&) {
            return GUI::Variant(0);
        },
        [](JsonObject const& object) {
            auto pagemap = object.get_deprecated_string("pagemap"sv).value_or({});
            return pagemap;
        }));
    TRY(pid_vm_fields.try_empend("cow_pages", "# CoW"_short_string, Gfx::TextAlignment::CenterRight));
    TRY(pid_vm_fields.try_empend("name", "Name"_short_string, Gfx::TextAlignment::CenterLeft));
    widget->m_json_model = GUI::JsonArrayModel::create({}, move(pid_vm_fields));
    widget->m_table_view->set_model(TRY(GUI::SortingProxyModel::create(*widget->m_json_model)));

    widget->m_table_view->set_column_painting_delegate(7, TRY(try_make<PagemapPaintingDelegate>()));

    widget->m_table_view->set_key_column_and_sort_order(0, GUI::SortOrder::Ascending);
    widget->m_timer = TRY(widget->try_add<Core::Timer>(1000, [widget] { widget->refresh(); }));
    widget->m_timer->start();

    return widget;
}

void ProcessMemoryMapWidget::set_pid(pid_t pid)
{
    if (m_pid == pid)
        return;
    m_pid = pid;
    m_json_model->set_json_path(DeprecatedString::formatted("/proc/{}/vm", pid));
}

void ProcessMemoryMapWidget::refresh()
{
    if (m_pid != -1)
        m_json_model->update();
}

}