Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "MemoryStatsWidget.h"
9#include "GraphWidget.h"
10#include <AK/JsonObject.h>
11#include <AK/NumberFormat.h>
12#include <LibCore/DeprecatedFile.h>
13#include <LibCore/Object.h>
14#include <LibGUI/BoxLayout.h>
15#include <LibGUI/Label.h>
16#include <LibGUI/Painter.h>
17#include <LibGfx/Font/FontDatabase.h>
18#include <LibGfx/StylePainter.h>
19
20REGISTER_WIDGET(SystemMonitor, MemoryStatsWidget)
21
22namespace SystemMonitor {
23
24static MemoryStatsWidget* s_the;
25
26MemoryStatsWidget* MemoryStatsWidget::the()
27{
28 return s_the;
29}
30
31MemoryStatsWidget::MemoryStatsWidget()
32 : MemoryStatsWidget(nullptr)
33{
34}
35
36MemoryStatsWidget::MemoryStatsWidget(GraphWidget* graph)
37 : m_graph(graph)
38{
39 VERIFY(!s_the);
40 s_the = this;
41
42 REGISTER_STRING_PROPERTY("memory_graph", graph_widget_name, set_graph_widget_via_name);
43
44 set_fixed_height(110);
45
46 set_layout<GUI::VerticalBoxLayout>(GUI::Margins { 8, 0, 0 }, 3);
47
48 auto build_widgets_for_label = [this](DeprecatedString const& description) -> RefPtr<GUI::Label> {
49 auto& container = add<GUI::Widget>();
50 container.set_layout<GUI::HorizontalBoxLayout>();
51 container.set_fixed_size(275, 12);
52 auto& description_label = container.add<GUI::Label>(description);
53 description_label.set_font(Gfx::FontDatabase::default_font().bold_variant());
54 description_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
55 auto& label = container.add<GUI::Label>();
56 label.set_text_alignment(Gfx::TextAlignment::CenterRight);
57 return label;
58 };
59
60 m_physical_pages_label = build_widgets_for_label("Physical memory:");
61 m_physical_pages_committed_label = build_widgets_for_label("Committed memory:");
62 m_kmalloc_space_label = build_widgets_for_label("Kernel heap:");
63 m_kmalloc_count_label = build_widgets_for_label("Calls kmalloc:");
64 m_kfree_count_label = build_widgets_for_label("Calls kfree:");
65 m_kmalloc_difference_label = build_widgets_for_label("Difference:");
66
67 refresh();
68}
69
70void MemoryStatsWidget::set_graph_widget(GraphWidget& graph)
71{
72 m_graph = &graph;
73}
74
75void MemoryStatsWidget::set_graph_widget_via_name(DeprecatedString name)
76{
77 m_graph_widget_name = move(name);
78 if (!m_graph_widget_name.is_null()) {
79 // FIXME: We assume here that the graph widget is a sibling or descendant of a sibling. This prevents more complex hierarchies.
80 auto* maybe_graph = parent_widget()->find_descendant_of_type_named<GraphWidget>(m_graph_widget_name);
81 if (maybe_graph) {
82 m_graph = maybe_graph;
83 // Delete the stored graph name to signal that we found the widget
84 m_graph_widget_name = {};
85 } else {
86 dbgln("MemoryStatsWidget: Couldn't find graph of name '{}', retrying later.", m_graph_widget_name);
87 }
88 }
89}
90
91DeprecatedString MemoryStatsWidget::graph_widget_name()
92{
93 if (m_graph)
94 return m_graph->name();
95 return m_graph_widget_name;
96}
97
98static inline u64 page_count_to_bytes(size_t count)
99{
100 return count * 4096;
101}
102
103void MemoryStatsWidget::refresh()
104{
105 auto proc_memstat = Core::DeprecatedFile::construct("/sys/kernel/memstat");
106 if (!proc_memstat->open(Core::OpenMode::ReadOnly))
107 VERIFY_NOT_REACHED();
108
109 auto file_contents = proc_memstat->read_all();
110 auto json_result = JsonValue::from_string(file_contents).release_value_but_fixme_should_propagate_errors();
111 auto const& json = json_result.as_object();
112
113 u32 kmalloc_allocated = json.get_u32("kmalloc_allocated"sv).value_or(0);
114 u32 kmalloc_available = json.get_u32("kmalloc_available"sv).value_or(0);
115 u64 physical_allocated = json.get_u64("physical_allocated"sv).value_or(0);
116 u64 physical_available = json.get_u64("physical_available"sv).value_or(0);
117 u64 physical_committed = json.get_u64("physical_committed"sv).value_or(0);
118 u64 physical_uncommitted = json.get_u64("physical_uncommitted"sv).value_or(0);
119 u32 kmalloc_call_count = json.get_u32("kmalloc_call_count"sv).value_or(0);
120 u32 kfree_call_count = json.get_u32("kfree_call_count"sv).value_or(0);
121
122 u64 kmalloc_bytes_total = kmalloc_allocated + kmalloc_available;
123 u64 physical_pages_total = physical_allocated + physical_available;
124
125 u64 physical_pages_in_use = physical_allocated;
126 u64 total_userphysical_and_swappable_pages = physical_allocated + physical_committed + physical_uncommitted;
127
128 m_kmalloc_space_label->set_text(DeprecatedString::formatted("{}/{}", human_readable_size(kmalloc_allocated), human_readable_size(kmalloc_bytes_total)));
129 m_physical_pages_label->set_text(DeprecatedString::formatted("{}/{}", human_readable_size(page_count_to_bytes(physical_pages_in_use)), human_readable_size(page_count_to_bytes(physical_pages_total))));
130 m_physical_pages_committed_label->set_text(DeprecatedString::formatted("{}", human_readable_size(page_count_to_bytes(physical_committed))));
131 m_kmalloc_count_label->set_text(DeprecatedString::formatted("{}", kmalloc_call_count));
132 m_kfree_count_label->set_text(DeprecatedString::formatted("{}", kfree_call_count));
133 m_kmalloc_difference_label->set_text(DeprecatedString::formatted("{:+}", kmalloc_call_count - kfree_call_count));
134
135 // Because the initialization order of us and the graph is unknown, we might get a couple of updates where the graph widget lookup fails.
136 // Therefore, we can retry indefinitely. (Should not be too much of a performance hit, as we don't update that often.)
137 if (!m_graph)
138 set_graph_widget_via_name(move(m_graph_widget_name));
139
140 if (m_graph) {
141 m_graph->set_max(page_count_to_bytes(total_userphysical_and_swappable_pages) + kmalloc_bytes_total);
142 m_graph->add_value({ page_count_to_bytes(physical_committed), page_count_to_bytes(physical_allocated), kmalloc_bytes_total });
143 }
144}
145
146}