Serenity Operating System
at master 300 lines 12 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2020, Linus Groh <linusg@serenityos.org> 4 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/CircularQueue.h> 10#include <AK/JsonObject.h> 11#include <LibCore/ArgsParser.h> 12#include <LibCore/System.h> 13#include <LibGUI/Application.h> 14#include <LibGUI/Frame.h> 15#include <LibGUI/Painter.h> 16#include <LibGUI/Process.h> 17#include <LibGUI/Window.h> 18#include <LibGfx/Palette.h> 19#include <LibMain/Main.h> 20#include <stdio.h> 21 22enum class GraphType { 23 CPU, 24 Memory, 25 Network, 26}; 27 28class GraphWidget final : public GUI::Frame { 29 C_OBJECT(GraphWidget); 30 31public: 32 static constexpr size_t history_size = 24; 33 34private: 35 GraphWidget(GraphType graph_type, Optional<Gfx::Color> graph_color, Optional<Gfx::Color> graph_error_color) 36 : m_graph_type(graph_type) 37 { 38 set_frame_thickness(1); 39 m_graph_color = graph_color.value_or(palette().menu_selection()); 40 m_graph_error_color = graph_error_color.value_or(Color::Red); 41 start_timer(1000); 42 } 43 44 virtual void timer_event(Core::TimerEvent&) override 45 { 46 switch (m_graph_type) { 47 case GraphType::CPU: { 48 u64 total, idle; 49 if (get_cpu_usage(total, idle)) { 50 auto total_diff = total - m_last_total; 51 m_last_total = total; 52 auto idle_diff = idle - m_last_idle; 53 m_last_idle = idle; 54 float cpu = total_diff > 0 ? (float)(total_diff - idle_diff) / (float)total_diff : 0; 55 m_history.enqueue(cpu); 56 m_tooltip = DeprecatedString::formatted("CPU usage: {:.1}%", 100 * cpu); 57 } else { 58 m_history.enqueue(-1); 59 m_tooltip = "Unable to determine CPU usage"sv; 60 } 61 break; 62 } 63 case GraphType::Memory: { 64 u64 allocated, available; 65 if (get_memory_usage(allocated, available)) { 66 double total_memory = allocated + available; 67 double memory = (double)allocated / total_memory; 68 m_history.enqueue(memory); 69 m_tooltip = DeprecatedString::formatted("Memory: {} MiB of {:.1} MiB in use", allocated / MiB, total_memory / MiB); 70 } else { 71 m_history.enqueue(-1); 72 m_tooltip = "Unable to determine memory usage"sv; 73 } 74 break; 75 } 76 case GraphType::Network: { 77 u64 tx, rx, link_speed; 78 if (get_network_usage(tx, rx, link_speed)) { 79 u64 recent_tx = tx - m_last_total; 80 m_last_total = tx; 81 if (recent_tx > m_current_scale) { 82 u64 m_old_scale = m_current_scale; 83 // Scale in multiples of 1000 kB/s 84 m_current_scale = (recent_tx / scale_unit) * scale_unit; 85 rescale_history(m_old_scale, m_current_scale); 86 } else { 87 // Figure out if we can scale back down. 88 float max = static_cast<float>(recent_tx) / static_cast<float>(m_current_scale); 89 for (auto const value : m_history) { 90 if (value > max) 91 max = value; 92 } 93 if (max < 0.5f && m_current_scale > scale_unit) { 94 u64 m_old_scale = m_current_scale; 95 m_current_scale = ::max((static_cast<u64>(max * m_current_scale) / scale_unit) * scale_unit, scale_unit); 96 rescale_history(m_old_scale, m_current_scale); 97 } 98 } 99 m_history.enqueue(static_cast<float>(recent_tx) / static_cast<float>(m_current_scale)); 100 m_tooltip = DeprecatedString::formatted("Network: TX {} / RX {} ({:.1} kbit/s)", tx, rx, static_cast<double>(recent_tx) * 8.0 / 1000.0); 101 } else { 102 m_history.enqueue(-1); 103 m_tooltip = "Unable to determine network usage"sv; 104 } 105 break; 106 } 107 default: 108 VERIFY_NOT_REACHED(); 109 } 110 set_tooltip(m_tooltip); 111 update(); 112 } 113 114 virtual void paint_event(GUI::PaintEvent& event) override 115 { 116 GUI::Frame::paint_event(event); 117 GUI::Painter painter(*this); 118 painter.add_clip_rect(event.rect()); 119 painter.add_clip_rect(frame_inner_rect()); 120 painter.fill_rect(event.rect(), Color::Black); 121 int i = m_history.capacity() - m_history.size(); 122 auto rect = frame_inner_rect(); 123 for (auto value : m_history) { 124 if (value >= 0) { 125 painter.draw_line( 126 { rect.x() + i, rect.bottom() }, 127 { rect.x() + i, rect.top() + (int)(roundf(rect.height() - (value * rect.height()))) }, 128 m_graph_color); 129 } else { 130 painter.draw_line( 131 { rect.x() + i, rect.top() }, 132 { rect.x() + i, rect.bottom() }, 133 m_graph_error_color); 134 } 135 ++i; 136 } 137 } 138 139 virtual void mousedown_event(GUI::MouseEvent& event) override 140 { 141 if (event.button() != GUI::MouseButton::Primary) 142 return; 143 GUI::Process::spawn_or_show_error(window(), "/bin/SystemMonitor"sv, Array { "-t", m_graph_type == GraphType::Network ? "network" : "graphs" }); 144 } 145 146 ErrorOr<JsonValue> get_data_as_json(OwnPtr<Core::File>& file, StringView filename) 147 { 148 if (file) { 149 // Seeking to the beginning causes a data refresh! 150 TRY(file->seek(0, SeekMode::SetPosition)); 151 } else { 152 file = TRY(Core::File::open(filename, Core::File::OpenMode::Read)); 153 } 154 155 auto file_contents = TRY(file->read_until_eof()); 156 return TRY(JsonValue::from_string(file_contents)); 157 } 158 159 bool get_cpu_usage(u64& total, u64& idle) 160 { 161 total = 0; 162 idle = 0; 163 164 auto json = get_data_as_json(m_proc_stat, "/sys/kernel/stats"sv); 165 if (json.is_error()) 166 return false; 167 168 auto const& obj = json.value().as_object(); 169 total = obj.get_u64("total_time"sv).value_or(0); 170 idle = obj.get_u64("idle_time"sv).value_or(0); 171 return true; 172 } 173 174 bool get_memory_usage(u64& allocated, u64& available) 175 { 176 auto json = get_data_as_json(m_proc_mem, "/sys/kernel/memstat"sv); 177 if (json.is_error()) 178 return false; 179 180 auto const& obj = json.value().as_object(); 181 unsigned kmalloc_allocated = obj.get_u32("kmalloc_allocated"sv).value_or(0); 182 unsigned kmalloc_available = obj.get_u32("kmalloc_available"sv).value_or(0); 183 auto physical_allocated = obj.get_u64("physical_allocated"sv).value_or(0); 184 auto physical_committed = obj.get_u64("physical_committed"sv).value_or(0); 185 auto physical_uncommitted = obj.get_u64("physical_uncommitted"sv).value_or(0); 186 unsigned kmalloc_bytes_total = kmalloc_allocated + kmalloc_available; 187 unsigned kmalloc_pages_total = (kmalloc_bytes_total + PAGE_SIZE - 1) / PAGE_SIZE; 188 u64 total_userphysical_and_swappable_pages = kmalloc_pages_total + physical_allocated + physical_committed + physical_uncommitted; 189 allocated = kmalloc_allocated + ((physical_allocated + physical_committed) * PAGE_SIZE); 190 available = (total_userphysical_and_swappable_pages * PAGE_SIZE) - allocated; 191 return true; 192 } 193 194 bool get_network_usage(u64& tx, u64& rx, u64& link_speed) 195 { 196 tx = rx = link_speed = 0; 197 198 auto json = get_data_as_json(m_proc_net, "/sys/kernel/net/adapters"sv); 199 if (json.is_error()) 200 return false; 201 202 auto const& array = json.value().as_array(); 203 for (auto const& adapter_value : array.values()) { 204 auto const& adapter_obj = adapter_value.as_object(); 205 if (!adapter_obj.has_string("ipv4_address"sv) || !adapter_obj.get_bool("link_up"sv).value()) 206 continue; 207 208 tx += adapter_obj.get_u64("bytes_in"sv).value_or(0); 209 rx += adapter_obj.get_u64("bytes_out"sv).value_or(0); 210 // Link speed data is given in megabits, but we want all return values to be in bytes. 211 link_speed += adapter_obj.get_u64("link_speed"sv).value_or(0) * 8'000'000; 212 } 213 link_speed /= 8; 214 return tx != 0; 215 } 216 217 void rescale_history(u64 old_scale, u64 new_scale) 218 { 219 float factor = static_cast<float>(old_scale) / static_cast<float>(new_scale); 220 for (auto& value : m_history) 221 value *= factor; 222 } 223 224 GraphType m_graph_type; 225 Gfx::Color m_graph_color; 226 Gfx::Color m_graph_error_color; 227 CircularQueue<float, history_size> m_history; 228 u64 m_last_idle { 0 }; 229 u64 m_last_total { 0 }; 230 static constexpr u64 const scale_unit = 8000; 231 u64 m_current_scale { scale_unit }; 232 DeprecatedString m_tooltip; 233 OwnPtr<Core::File> m_proc_stat; 234 OwnPtr<Core::File> m_proc_mem; 235 OwnPtr<Core::File> m_proc_net; 236}; 237 238ErrorOr<int> serenity_main(Main::Arguments arguments) 239{ 240 TRY(Core::System::pledge("stdio recvfd sendfd proc exec rpath unix")); 241 242 auto app = TRY(GUI::Application::try_create(arguments)); 243 244 TRY(Core::System::pledge("stdio recvfd sendfd proc exec rpath")); 245 246 StringView cpu {}; 247 StringView memory {}; 248 StringView network {}; 249 Core::ArgsParser args_parser; 250 args_parser.add_option(cpu, "Create CPU graph", "cpu", 'C', "cpu"); 251 args_parser.add_option(memory, "Create memory graph", "memory", 'M', "memory"); 252 args_parser.add_option(network, "Create network graph", "network", 'N', "network"); 253 args_parser.parse(arguments); 254 255 if (cpu.is_empty() && memory.is_empty() && network.is_empty()) { 256 printf("At least one of --cpu, --memory, or --network must be used"); 257 return 1; 258 } 259 260 Vector<NonnullRefPtr<GUI::Window>> applet_windows; 261 262 auto create_applet = [&](GraphType graph_type, StringView spec) -> ErrorOr<void> { 263 auto parts = spec.split_view(','); 264 265 dbgln("Create applet: {} with spec '{}'", (int)graph_type, spec); 266 267 if (parts.size() != 2) 268 return Error::from_string_literal("ResourceGraph: Applet spec is not composed of exactly 2 comma-separated parts"); 269 270 auto name = parts[0]; 271 auto graph_color = Gfx::Color::from_string(parts[1]); 272 273 auto window = GUI::Window::construct(); 274 window->set_title(name); 275 window->set_window_type(GUI::WindowType::Applet); 276 window->resize(GraphWidget::history_size + 2, 15); 277 278 auto graph_widget = TRY(window->set_main_widget<GraphWidget>(graph_type, graph_color, Optional<Gfx::Color> {})); 279 window->show(); 280 applet_windows.append(move(window)); 281 282 return {}; 283 }; 284 285 if (!cpu.is_empty()) 286 TRY(create_applet(GraphType::CPU, cpu)); 287 if (!memory.is_empty()) 288 TRY(create_applet(GraphType::Memory, memory)); 289 if (!network.is_empty()) 290 TRY(create_applet(GraphType::Network, network)); 291 292 TRY(Core::System::unveil("/res", "r")); 293 TRY(Core::System::unveil("/sys/kernel/stats", "r")); 294 TRY(Core::System::unveil("/sys/kernel/memstat", "r")); 295 TRY(Core::System::unveil("/sys/kernel/net/adapters", "r")); 296 TRY(Core::System::unveil("/bin/SystemMonitor", "x")); 297 TRY(Core::System::unveil(nullptr, nullptr)); 298 299 return app->exec(); 300}