mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 11:27:35 +00:00
Applications: Move to Userland/Applications/
This commit is contained in:
parent
aa939c4b4b
commit
dc28c07fa5
287 changed files with 1 additions and 1 deletions
16
Userland/Applications/SystemMonitor/CMakeLists.txt
Normal file
16
Userland/Applications/SystemMonitor/CMakeLists.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
set(SOURCES
|
||||
DevicesModel.cpp
|
||||
GraphWidget.cpp
|
||||
InterruptsWidget.cpp
|
||||
main.cpp
|
||||
MemoryStatsWidget.cpp
|
||||
NetworkStatisticsWidget.cpp
|
||||
ProcessFileDescriptorMapWidget.cpp
|
||||
ProcessMemoryMapWidget.cpp
|
||||
ProcessModel.cpp
|
||||
ProcessUnveiledPathsWidget.cpp
|
||||
ThreadStackWidget.cpp
|
||||
)
|
||||
|
||||
serenity_app(SystemMonitor ICON app-system-monitor)
|
||||
target_link_libraries(SystemMonitor LibGUI LibPCIDB)
|
198
Userland/Applications/SystemMonitor/DevicesModel.cpp
Normal file
198
Userland/Applications/SystemMonitor/DevicesModel.cpp
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "DevicesModel.h"
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <LibCore/DirIterator.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
NonnullRefPtr<DevicesModel> DevicesModel::create()
|
||||
{
|
||||
return adopt(*new DevicesModel);
|
||||
}
|
||||
|
||||
DevicesModel::DevicesModel()
|
||||
{
|
||||
}
|
||||
|
||||
DevicesModel::~DevicesModel()
|
||||
{
|
||||
}
|
||||
|
||||
int DevicesModel::row_count(const GUI::ModelIndex&) const
|
||||
{
|
||||
return m_devices.size();
|
||||
}
|
||||
|
||||
int DevicesModel::column_count(const GUI::ModelIndex&) const
|
||||
{
|
||||
return Column::__Count;
|
||||
}
|
||||
|
||||
String DevicesModel::column_name(int column) const
|
||||
{
|
||||
switch (column) {
|
||||
case Column::Device:
|
||||
return "Device";
|
||||
case Column::Major:
|
||||
return "Major";
|
||||
case Column::Minor:
|
||||
return "Minor";
|
||||
case Column::ClassName:
|
||||
return "Class";
|
||||
case Column::Type:
|
||||
return "Type";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Variant DevicesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
ASSERT(is_valid(index));
|
||||
|
||||
if (role == GUI::ModelRole::TextAlignment) {
|
||||
switch (index.column()) {
|
||||
case Column::Device:
|
||||
return Gfx::TextAlignment::CenterLeft;
|
||||
case Column::Major:
|
||||
return Gfx::TextAlignment::CenterRight;
|
||||
case Column::Minor:
|
||||
return Gfx::TextAlignment::CenterRight;
|
||||
case Column::ClassName:
|
||||
return Gfx::TextAlignment::CenterLeft;
|
||||
case Column::Type:
|
||||
return Gfx::TextAlignment::CenterLeft;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == GUI::ModelRole::Sort) {
|
||||
const DeviceInfo& device = m_devices[index.row()];
|
||||
switch (index.column()) {
|
||||
case Column::Device:
|
||||
return device.path;
|
||||
case Column::Major:
|
||||
return device.major;
|
||||
case Column::Minor:
|
||||
return device.minor;
|
||||
case Column::ClassName:
|
||||
return device.class_name;
|
||||
case Column::Type:
|
||||
return device.type;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
const DeviceInfo& device = m_devices[index.row()];
|
||||
switch (index.column()) {
|
||||
case Column::Device:
|
||||
return device.path;
|
||||
case Column::Major:
|
||||
return device.major;
|
||||
case Column::Minor:
|
||||
return device.minor;
|
||||
case Column::ClassName:
|
||||
return device.class_name;
|
||||
case Column::Type:
|
||||
switch (device.type) {
|
||||
case DeviceInfo::Type::Block:
|
||||
return "Block";
|
||||
case DeviceInfo::Type::Character:
|
||||
return "Character";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void DevicesModel::update()
|
||||
{
|
||||
auto proc_devices = Core::File::construct("/proc/devices");
|
||||
if (!proc_devices->open(Core::IODevice::OpenMode::ReadOnly))
|
||||
ASSERT_NOT_REACHED();
|
||||
|
||||
auto json = JsonValue::from_string(proc_devices->read_all());
|
||||
ASSERT(json.has_value());
|
||||
|
||||
m_devices.clear();
|
||||
json.value().as_array().for_each([this](auto& value) {
|
||||
JsonObject device = value.as_object();
|
||||
DeviceInfo device_info;
|
||||
|
||||
device_info.major = device.get("major").to_uint();
|
||||
device_info.minor = device.get("minor").to_uint();
|
||||
device_info.class_name = device.get("class_name").to_string();
|
||||
|
||||
String type_str = device.get("type").to_string();
|
||||
if (type_str == "block")
|
||||
device_info.type = DeviceInfo::Type::Block;
|
||||
else if (type_str == "character")
|
||||
device_info.type = DeviceInfo::Type::Character;
|
||||
else
|
||||
ASSERT_NOT_REACHED();
|
||||
|
||||
m_devices.append(move(device_info));
|
||||
});
|
||||
|
||||
auto fill_in_paths_from_dir = [this](const String& dir) {
|
||||
Core::DirIterator dir_iter { dir, Core::DirIterator::Flags::SkipDots };
|
||||
while (dir_iter.has_next()) {
|
||||
auto name = dir_iter.next_path();
|
||||
auto path = String::format("%s/%s", dir.characters(), name.characters());
|
||||
struct stat statbuf;
|
||||
if (lstat(path.characters(), &statbuf) != 0) {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
if (!S_ISBLK(statbuf.st_mode) && !S_ISCHR(statbuf.st_mode))
|
||||
continue;
|
||||
unsigned _major = major(statbuf.st_rdev);
|
||||
unsigned _minor = minor(statbuf.st_rdev);
|
||||
|
||||
auto it = m_devices.find_if([_major, _minor](const auto& device_info) {
|
||||
return device_info.major == _major && device_info.minor == _minor;
|
||||
});
|
||||
if (it != m_devices.end()) {
|
||||
(*it).path = move(path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fill_in_paths_from_dir("/dev");
|
||||
fill_in_paths_from_dir("/dev/pts");
|
||||
|
||||
did_update();
|
||||
}
|
69
Userland/Applications/SystemMonitor/DevicesModel.h
Normal file
69
Userland/Applications/SystemMonitor/DevicesModel.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/Model.h>
|
||||
|
||||
class DevicesModel final : public GUI::Model {
|
||||
public:
|
||||
enum Column {
|
||||
Device = 0,
|
||||
Major,
|
||||
Minor,
|
||||
ClassName,
|
||||
Type,
|
||||
__Count
|
||||
};
|
||||
|
||||
virtual ~DevicesModel() override;
|
||||
static NonnullRefPtr<DevicesModel> create();
|
||||
|
||||
virtual int row_count(const GUI::ModelIndex&) const override;
|
||||
virtual int column_count(const GUI::ModelIndex&) const override;
|
||||
virtual String column_name(int column) const override;
|
||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||
virtual void update() override;
|
||||
|
||||
private:
|
||||
DevicesModel();
|
||||
|
||||
struct DeviceInfo {
|
||||
String path;
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
String class_name;
|
||||
enum Type {
|
||||
Block,
|
||||
Character
|
||||
};
|
||||
Type type;
|
||||
};
|
||||
|
||||
Vector<DeviceInfo> m_devices;
|
||||
};
|
163
Userland/Applications/SystemMonitor/GraphWidget.cpp
Normal file
163
Userland/Applications/SystemMonitor/GraphWidget.cpp
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "GraphWidget.h"
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/Path.h>
|
||||
|
||||
GraphWidget::GraphWidget()
|
||||
{
|
||||
}
|
||||
|
||||
GraphWidget::~GraphWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void GraphWidget::add_value(Vector<int, 1>&& value)
|
||||
{
|
||||
m_values.enqueue(move(value));
|
||||
update();
|
||||
}
|
||||
|
||||
void GraphWidget::paint_event(GUI::PaintEvent& event)
|
||||
{
|
||||
GUI::Frame::paint_event(event);
|
||||
GUI::Painter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
painter.add_clip_rect(frame_inner_rect());
|
||||
painter.fill_rect(event.rect(), m_background_color);
|
||||
|
||||
auto inner_rect = frame_inner_rect();
|
||||
float scale = (float)inner_rect.height() / (float)m_max;
|
||||
|
||||
if (!m_values.is_empty()) {
|
||||
// Draw one set of values at a time
|
||||
for (size_t k = 0; k < m_value_format.size(); k++) {
|
||||
const auto& format = m_value_format[k];
|
||||
if (format.line_color == Color::Transparent && format.background_color == Color::Transparent)
|
||||
continue;
|
||||
m_calculated_points.clear_with_capacity();
|
||||
for (size_t i = 0; i < m_values.size(); i++) {
|
||||
int x = inner_rect.right() - (i * 2) + 1;
|
||||
if (x < 0)
|
||||
break;
|
||||
const auto& current_values = m_values.at(m_values.size() - i - 1);
|
||||
if (current_values.size() <= k) {
|
||||
// Don't have a data point
|
||||
m_calculated_points.append({ -1, -1 });
|
||||
continue;
|
||||
}
|
||||
float value = current_values[k];
|
||||
if (m_stack_values) {
|
||||
for (size_t l = k + 1; l < current_values.size(); l++)
|
||||
value += current_values[l];
|
||||
}
|
||||
float scaled_value = value * scale;
|
||||
Gfx::IntPoint current_point { x, inner_rect.bottom() - (int)scaled_value };
|
||||
m_calculated_points.append(current_point);
|
||||
}
|
||||
ASSERT(m_calculated_points.size() <= m_values.size());
|
||||
if (format.background_color != Color::Transparent) {
|
||||
// Fill the background for the area we have values for
|
||||
Gfx::Path path;
|
||||
size_t points_in_path = 0;
|
||||
bool started_path = false;
|
||||
const Gfx::IntPoint* current_point = nullptr;
|
||||
const Gfx::IntPoint* first_point = nullptr;
|
||||
auto check_fill_area = [&]() {
|
||||
if (!started_path)
|
||||
return;
|
||||
if (points_in_path > 1) {
|
||||
ASSERT(current_point);
|
||||
ASSERT(first_point);
|
||||
path.line_to({ current_point->x() - 1, inner_rect.bottom() + 1 });
|
||||
path.line_to({ first_point->x() + 1, inner_rect.bottom() + 1 });
|
||||
path.close();
|
||||
painter.fill_path(path, format.background_color, Gfx::Painter::WindingRule::EvenOdd);
|
||||
} else if (points_in_path == 1 && current_point) {
|
||||
// Can't fill any area, we only have one data point.
|
||||
// Just draw a vertical line as a "fill"...
|
||||
painter.draw_line(*current_point, { current_point->x(), inner_rect.bottom() }, format.background_color);
|
||||
}
|
||||
path = {};
|
||||
points_in_path = 0;
|
||||
first_point = nullptr;
|
||||
started_path = false;
|
||||
};
|
||||
for (size_t i = 0; i < m_calculated_points.size(); i++) {
|
||||
current_point = &m_calculated_points[i];
|
||||
if (current_point->x() < 0) {
|
||||
check_fill_area();
|
||||
continue;
|
||||
}
|
||||
if (!started_path) {
|
||||
path.move_to({ current_point->x() + 1, current_point->y() });
|
||||
points_in_path = 1;
|
||||
first_point = current_point;
|
||||
started_path = true;
|
||||
} else {
|
||||
path.line_to({ current_point->x(), current_point->y() });
|
||||
points_in_path++;
|
||||
}
|
||||
}
|
||||
check_fill_area();
|
||||
}
|
||||
if (format.line_color != Color::Transparent) {
|
||||
// Draw the line for the data points we have
|
||||
const Gfx::IntPoint* previous_point = nullptr;
|
||||
for (size_t i = 0; i < m_calculated_points.size(); i++) {
|
||||
const auto& current_point = m_calculated_points[i];
|
||||
if (current_point.x() < 0) {
|
||||
previous_point = nullptr;
|
||||
continue;
|
||||
}
|
||||
if (previous_point)
|
||||
painter.draw_line(*previous_point, current_point, format.line_color);
|
||||
previous_point = ¤t_point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_values.is_empty() && !m_value_format.is_empty()) {
|
||||
const auto& current_values = m_values.last();
|
||||
int y = 0;
|
||||
for (size_t i = 0; i < min(m_value_format.size(), current_values.size()); i++) {
|
||||
const auto& format = m_value_format[i];
|
||||
if (!format.text_formatter)
|
||||
continue;
|
||||
auto constrain_rect = inner_rect.shrunken(8, 8);
|
||||
auto text_rect = constrain_rect.translated(0, y).intersected(constrain_rect);
|
||||
text_rect.set_height(font().glyph_height());
|
||||
auto text = format.text_formatter(current_values[i]);
|
||||
if (format.text_shadow_color != Color::Transparent)
|
||||
painter.draw_text(text_rect.translated(1, 1), text.characters(), Gfx::TextAlignment::CenterRight, format.text_shadow_color);
|
||||
painter.draw_text(text_rect, text.characters(), Gfx::TextAlignment::CenterRight, format.line_color);
|
||||
y += text_rect.height() + 4;
|
||||
}
|
||||
}
|
||||
}
|
70
Userland/Applications/SystemMonitor/GraphWidget.h
Normal file
70
Userland/Applications/SystemMonitor/GraphWidget.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/CircularQueue.h>
|
||||
#include <LibGUI/Frame.h>
|
||||
|
||||
class GraphWidget final : public GUI::Frame {
|
||||
C_OBJECT(GraphWidget)
|
||||
public:
|
||||
virtual ~GraphWidget() override;
|
||||
|
||||
void set_max(int max) { m_max = max; }
|
||||
int max() const { return m_max; }
|
||||
|
||||
void add_value(Vector<int, 1>&&);
|
||||
|
||||
void set_background_color(Color color) { m_background_color = color; }
|
||||
|
||||
struct ValueFormat {
|
||||
Color line_color { Color::Transparent };
|
||||
Color background_color { Color::Transparent };
|
||||
Color text_shadow_color { Color::Transparent };
|
||||
Function<String(int)> text_formatter;
|
||||
};
|
||||
void set_value_format(size_t index, ValueFormat&& format)
|
||||
{
|
||||
if (m_value_format.size() <= index)
|
||||
m_value_format.resize(index + 1);
|
||||
m_value_format[index] = move(format);
|
||||
}
|
||||
void set_stack_values(bool stack_values) { m_stack_values = stack_values; }
|
||||
|
||||
private:
|
||||
explicit GraphWidget();
|
||||
|
||||
virtual void paint_event(GUI::PaintEvent&) override;
|
||||
|
||||
int m_max { 100 };
|
||||
Vector<ValueFormat, 1> m_value_format;
|
||||
CircularQueue<Vector<int, 1>, 4000> m_values;
|
||||
Color m_background_color { Color::Black };
|
||||
bool m_stack_values { false };
|
||||
|
||||
Vector<Gfx::IntPoint, 1> m_calculated_points;
|
||||
};
|
68
Userland/Applications/SystemMonitor/InterruptsWidget.cpp
Normal file
68
Userland/Applications/SystemMonitor/InterruptsWidget.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "InterruptsWidget.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/GroupBox.h>
|
||||
#include <LibGUI/JsonArrayModel.h>
|
||||
#include <LibGUI/SortingProxyModel.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
|
||||
InterruptsWidget::InterruptsWidget()
|
||||
{
|
||||
on_first_show = [this](auto&) {
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> interrupts_field;
|
||||
interrupts_field.empend("interrupt_line", "Line", Gfx::TextAlignment::CenterRight);
|
||||
interrupts_field.empend("purpose", "Purpose", Gfx::TextAlignment::CenterLeft);
|
||||
interrupts_field.empend("controller", "Controller", Gfx::TextAlignment::CenterLeft);
|
||||
interrupts_field.empend("cpu_handler", "CPU Handler", Gfx::TextAlignment::CenterRight);
|
||||
interrupts_field.empend("device_sharing", "# Devices Sharing", Gfx::TextAlignment::CenterRight);
|
||||
interrupts_field.empend("call_count", "Call Count", Gfx::TextAlignment::CenterRight);
|
||||
|
||||
m_interrupt_table_view = add<GUI::TableView>();
|
||||
m_interrupt_model = GUI::JsonArrayModel::create("/proc/interrupts", move(interrupts_field));
|
||||
m_interrupt_table_view->set_model(GUI::SortingProxyModel::create(*m_interrupt_model));
|
||||
|
||||
m_update_timer = add<Core::Timer>(
|
||||
1000, [this] {
|
||||
update_model();
|
||||
});
|
||||
|
||||
update_model();
|
||||
};
|
||||
}
|
||||
|
||||
InterruptsWidget::~InterruptsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void InterruptsWidget::update_model()
|
||||
{
|
||||
m_interrupt_table_view->model()->update();
|
||||
}
|
44
Userland/Applications/SystemMonitor/InterruptsWidget.h
Normal file
44
Userland/Applications/SystemMonitor/InterruptsWidget.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGUI/LazyWidget.h>
|
||||
|
||||
class InterruptsWidget final : public GUI::LazyWidget {
|
||||
C_OBJECT(InterruptsWidget)
|
||||
public:
|
||||
virtual ~InterruptsWidget() override;
|
||||
|
||||
private:
|
||||
InterruptsWidget();
|
||||
void update_model();
|
||||
|
||||
RefPtr<GUI::TableView> m_interrupt_table_view;
|
||||
RefPtr<GUI::JsonArrayModel> m_interrupt_model;
|
||||
RefPtr<Core::Timer> m_update_timer;
|
||||
};
|
138
Userland/Applications/SystemMonitor/MemoryStatsWidget.cpp
Normal file
138
Userland/Applications/SystemMonitor/MemoryStatsWidget.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "MemoryStatsWidget.h"
|
||||
#include "GraphWidget.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/FontDatabase.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static MemoryStatsWidget* s_the;
|
||||
|
||||
MemoryStatsWidget* MemoryStatsWidget::the()
|
||||
{
|
||||
return s_the;
|
||||
}
|
||||
|
||||
MemoryStatsWidget::MemoryStatsWidget(GraphWidget& graph)
|
||||
: m_graph(graph)
|
||||
{
|
||||
ASSERT(!s_the);
|
||||
s_the = this;
|
||||
|
||||
set_fixed_height(110);
|
||||
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 0, 8, 0, 0 });
|
||||
layout()->set_spacing(3);
|
||||
|
||||
auto build_widgets_for_label = [this](const String& description) -> RefPtr<GUI::Label> {
|
||||
auto& container = add<GUI::Widget>();
|
||||
container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
container.set_fixed_size(275, 12);
|
||||
auto& description_label = container.add<GUI::Label>(description);
|
||||
description_label.set_font(Gfx::FontDatabase::default_bold_font());
|
||||
description_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
auto& label = container.add<GUI::Label>();
|
||||
label.set_text_alignment(Gfx::TextAlignment::CenterRight);
|
||||
return label;
|
||||
};
|
||||
|
||||
m_user_physical_pages_label = build_widgets_for_label("Physical memory:");
|
||||
m_user_physical_pages_committed_label = build_widgets_for_label("Committed memory:");
|
||||
m_supervisor_physical_pages_label = build_widgets_for_label("Supervisor physical:");
|
||||
m_kmalloc_space_label = build_widgets_for_label("Kernel heap:");
|
||||
m_kmalloc_count_label = build_widgets_for_label("Calls kmalloc:");
|
||||
m_kfree_count_label = build_widgets_for_label("Calls kfree:");
|
||||
m_kmalloc_difference_label = build_widgets_for_label("Difference:");
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
MemoryStatsWidget::~MemoryStatsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
static inline size_t page_count_to_kb(size_t kb)
|
||||
{
|
||||
return (kb * 4096) / 1024;
|
||||
}
|
||||
|
||||
static inline size_t bytes_to_kb(size_t bytes)
|
||||
{
|
||||
return bytes / 1024;
|
||||
}
|
||||
|
||||
void MemoryStatsWidget::refresh()
|
||||
{
|
||||
auto proc_memstat = Core::File::construct("/proc/memstat");
|
||||
if (!proc_memstat->open(Core::IODevice::OpenMode::ReadOnly))
|
||||
ASSERT_NOT_REACHED();
|
||||
|
||||
auto file_contents = proc_memstat->read_all();
|
||||
auto json_result = JsonValue::from_string(file_contents);
|
||||
ASSERT(json_result.has_value());
|
||||
auto json = json_result.value().as_object();
|
||||
|
||||
[[maybe_unused]] unsigned kmalloc_eternal_allocated = json.get("kmalloc_eternal_allocated").to_u32();
|
||||
unsigned kmalloc_allocated = json.get("kmalloc_allocated").to_u32();
|
||||
unsigned kmalloc_available = json.get("kmalloc_available").to_u32();
|
||||
unsigned user_physical_allocated = json.get("user_physical_allocated").to_u32();
|
||||
unsigned user_physical_available = json.get("user_physical_available").to_u32();
|
||||
unsigned user_physical_committed = json.get("user_physical_committed").to_u32();
|
||||
unsigned user_physical_uncommitted = json.get("user_physical_uncommitted").to_u32();
|
||||
unsigned super_physical_alloc = json.get("super_physical_allocated").to_u32();
|
||||
unsigned super_physical_free = json.get("super_physical_available").to_u32();
|
||||
unsigned kmalloc_call_count = json.get("kmalloc_call_count").to_u32();
|
||||
unsigned kfree_call_count = json.get("kfree_call_count").to_u32();
|
||||
|
||||
size_t kmalloc_bytes_total = kmalloc_allocated + kmalloc_available;
|
||||
size_t user_physical_pages_total = user_physical_allocated + user_physical_available;
|
||||
size_t supervisor_pages_total = super_physical_alloc + super_physical_free;
|
||||
|
||||
size_t physical_pages_total = user_physical_pages_total + supervisor_pages_total;
|
||||
size_t physical_pages_in_use = user_physical_allocated + super_physical_alloc;
|
||||
size_t total_userphysical_and_swappable_pages = user_physical_allocated + user_physical_committed + user_physical_uncommitted;
|
||||
|
||||
m_kmalloc_space_label->set_text(String::formatted("{}K/{}K", bytes_to_kb(kmalloc_allocated), bytes_to_kb(kmalloc_bytes_total)));
|
||||
m_user_physical_pages_label->set_text(String::formatted("{}K/{}K", page_count_to_kb(physical_pages_in_use), page_count_to_kb(physical_pages_total)));
|
||||
m_user_physical_pages_committed_label->set_text(String::formatted("{}K", page_count_to_kb(user_physical_committed)));
|
||||
m_supervisor_physical_pages_label->set_text(String::formatted("{}K/{}K", page_count_to_kb(super_physical_alloc), page_count_to_kb(supervisor_pages_total)));
|
||||
m_kmalloc_count_label->set_text(String::formatted("{}", kmalloc_call_count));
|
||||
m_kfree_count_label->set_text(String::formatted("{}", kfree_call_count));
|
||||
m_kmalloc_difference_label->set_text(String::formatted("{:+}", kmalloc_call_count - kfree_call_count));
|
||||
|
||||
m_graph.set_max(page_count_to_kb(total_userphysical_and_swappable_pages) + bytes_to_kb(kmalloc_bytes_total));
|
||||
m_graph.add_value({ (int)page_count_to_kb(user_physical_committed), (int)page_count_to_kb(user_physical_allocated), (int)bytes_to_kb(kmalloc_bytes_total) });
|
||||
}
|
53
Userland/Applications/SystemMonitor/MemoryStatsWidget.h
Normal file
53
Userland/Applications/SystemMonitor/MemoryStatsWidget.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
class GraphWidget;
|
||||
|
||||
class MemoryStatsWidget final : public GUI::Widget {
|
||||
C_OBJECT(MemoryStatsWidget)
|
||||
public:
|
||||
static MemoryStatsWidget* the();
|
||||
|
||||
virtual ~MemoryStatsWidget() override;
|
||||
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
MemoryStatsWidget(GraphWidget& graph);
|
||||
|
||||
GraphWidget& m_graph;
|
||||
RefPtr<GUI::Label> m_user_physical_pages_label;
|
||||
RefPtr<GUI::Label> m_user_physical_pages_committed_label;
|
||||
RefPtr<GUI::Label> m_supervisor_physical_pages_label;
|
||||
RefPtr<GUI::Label> m_kmalloc_space_label;
|
||||
RefPtr<GUI::Label> m_kmalloc_count_label;
|
||||
RefPtr<GUI::Label> m_kfree_count_label;
|
||||
RefPtr<GUI::Label> m_kmalloc_difference_label;
|
||||
};
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "NetworkStatisticsWidget.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/GroupBox.h>
|
||||
#include <LibGUI/JsonArrayModel.h>
|
||||
#include <LibGUI/SortingProxyModel.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
|
||||
NetworkStatisticsWidget::NetworkStatisticsWidget()
|
||||
{
|
||||
on_first_show = [this](auto&) {
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
set_fill_with_background_color(true);
|
||||
|
||||
auto& adapters_group_box = add<GUI::GroupBox>("Adapters");
|
||||
adapters_group_box.set_layout<GUI::VerticalBoxLayout>();
|
||||
adapters_group_box.layout()->set_margins({ 6, 16, 6, 6 });
|
||||
adapters_group_box.set_fixed_height(120);
|
||||
|
||||
m_adapter_table_view = adapters_group_box.add<GUI::TableView>();
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> net_adapters_fields;
|
||||
net_adapters_fields.empend("name", "Name", Gfx::TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("class_name", "Class", Gfx::TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("mac_address", "MAC", Gfx::TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("ipv4_address", "IPv4", Gfx::TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("packets_in", "Pkt In", Gfx::TextAlignment::CenterRight);
|
||||
net_adapters_fields.empend("packets_out", "Pkt Out", Gfx::TextAlignment::CenterRight);
|
||||
net_adapters_fields.empend("bytes_in", "Bytes In", Gfx::TextAlignment::CenterRight);
|
||||
net_adapters_fields.empend("bytes_out", "Bytes Out", Gfx::TextAlignment::CenterRight);
|
||||
m_adapter_model = GUI::JsonArrayModel::create("/proc/net/adapters", move(net_adapters_fields));
|
||||
m_adapter_table_view->set_model(GUI::SortingProxyModel::create(*m_adapter_model));
|
||||
|
||||
auto& sockets_group_box = add<GUI::GroupBox>("Sockets");
|
||||
sockets_group_box.set_layout<GUI::VerticalBoxLayout>();
|
||||
sockets_group_box.layout()->set_margins({ 6, 16, 6, 6 });
|
||||
|
||||
m_socket_table_view = sockets_group_box.add<GUI::TableView>();
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> net_tcp_fields;
|
||||
net_tcp_fields.empend("peer_address", "Peer", Gfx::TextAlignment::CenterLeft);
|
||||
net_tcp_fields.empend("peer_port", "Port", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("local_address", "Local", Gfx::TextAlignment::CenterLeft);
|
||||
net_tcp_fields.empend("local_port", "Port", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("state", "State", Gfx::TextAlignment::CenterLeft);
|
||||
net_tcp_fields.empend("ack_number", "Ack#", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("sequence_number", "Seq#", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("packets_in", "Pkt In", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("packets_out", "Pkt Out", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("bytes_in", "Bytes In", Gfx::TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("bytes_out", "Bytes Out", Gfx::TextAlignment::CenterRight);
|
||||
m_socket_model = GUI::JsonArrayModel::create("/proc/net/tcp", move(net_tcp_fields));
|
||||
m_socket_table_view->set_model(GUI::SortingProxyModel::create(*m_socket_model));
|
||||
|
||||
m_update_timer = add<Core::Timer>(
|
||||
1000, [this] {
|
||||
update_models();
|
||||
});
|
||||
|
||||
update_models();
|
||||
};
|
||||
}
|
||||
|
||||
NetworkStatisticsWidget::~NetworkStatisticsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkStatisticsWidget::update_models()
|
||||
{
|
||||
m_adapter_table_view->model()->update();
|
||||
m_socket_table_view->model()->update();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGUI/LazyWidget.h>
|
||||
|
||||
class NetworkStatisticsWidget final : public GUI::LazyWidget {
|
||||
C_OBJECT(NetworkStatisticsWidget)
|
||||
public:
|
||||
virtual ~NetworkStatisticsWidget() override;
|
||||
|
||||
private:
|
||||
NetworkStatisticsWidget();
|
||||
void update_models();
|
||||
|
||||
RefPtr<GUI::TableView> m_adapter_table_view;
|
||||
RefPtr<GUI::TableView> m_socket_table_view;
|
||||
RefPtr<GUI::JsonArrayModel> m_adapter_model;
|
||||
RefPtr<GUI::JsonArrayModel> m_socket_model;
|
||||
RefPtr<Core::Timer> m_update_timer;
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ProcessFileDescriptorMapWidget.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/JsonArrayModel.h>
|
||||
#include <LibGUI/SortingProxyModel.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
|
||||
ProcessFileDescriptorMapWidget::ProcessFileDescriptorMapWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_table_view = add<GUI::TableView>();
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> pid_fds_fields;
|
||||
pid_fds_fields.empend("fd", "FD", Gfx::TextAlignment::CenterRight);
|
||||
pid_fds_fields.empend("class", "Class", Gfx::TextAlignment::CenterLeft);
|
||||
pid_fds_fields.empend("offset", "Offset", Gfx::TextAlignment::CenterRight);
|
||||
pid_fds_fields.empend("absolute_path", "Path", Gfx::TextAlignment::CenterLeft);
|
||||
pid_fds_fields.empend("Access", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
return object.get("seekable").to_bool() ? "Seekable" : "Sequential";
|
||||
});
|
||||
pid_fds_fields.empend("Blocking", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
return object.get("blocking").to_bool() ? "Blocking" : "Nonblocking";
|
||||
});
|
||||
pid_fds_fields.empend("On exec", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
return object.get("cloexec").to_bool() ? "Close" : "Keep";
|
||||
});
|
||||
pid_fds_fields.empend("Can read", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
return object.get("can_read").to_bool() ? "Yes" : "No";
|
||||
});
|
||||
pid_fds_fields.empend("Can write", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
return object.get("can_write").to_bool() ? "Yes" : "No";
|
||||
});
|
||||
|
||||
m_model = GUI::JsonArrayModel::create({}, move(pid_fds_fields));
|
||||
m_table_view->set_model(GUI::SortingProxyModel::create(*m_model));
|
||||
}
|
||||
|
||||
ProcessFileDescriptorMapWidget::~ProcessFileDescriptorMapWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessFileDescriptorMapWidget::set_pid(pid_t pid)
|
||||
{
|
||||
if (m_pid == pid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
m_model->set_json_path(String::formatted("/proc/{}/fds", m_pid));
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
class ProcessFileDescriptorMapWidget final : public GUI::Widget {
|
||||
C_OBJECT(ProcessFileDescriptorMapWidget);
|
||||
|
||||
public:
|
||||
virtual ~ProcessFileDescriptorMapWidget() override;
|
||||
|
||||
void set_pid(pid_t);
|
||||
|
||||
private:
|
||||
ProcessFileDescriptorMapWidget();
|
||||
|
||||
RefPtr<GUI::TableView> m_table_view;
|
||||
RefPtr<GUI::JsonArrayModel> m_model;
|
||||
pid_t m_pid { -1 };
|
||||
};
|
141
Userland/Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
Normal file
141
Userland/Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#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>
|
||||
|
||||
class PagemapPaintingDelegate final : public GUI::TableCellPaintingDelegate {
|
||||
public:
|
||||
virtual ~PagemapPaintingDelegate() override { }
|
||||
|
||||
virtual void paint(GUI::Painter& painter, const Gfx::IntRect& a_rect, const Gfx::Palette&, const GUI::ModelIndex& index) override
|
||||
{
|
||||
auto rect = a_rect.shrunken(2, 2);
|
||||
auto pagemap = index.data(GUI::ModelRole::Custom).to_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
|
||||
ASSERT_NOT_REACHED();
|
||||
|
||||
painter.draw_line({ x, rect.top() }, { x, rect.bottom() }, color);
|
||||
}
|
||||
|
||||
painter.draw_rect(rect, Color::Black);
|
||||
}
|
||||
};
|
||||
|
||||
ProcessMemoryMapWidget::ProcessMemoryMapWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_table_view = add<GUI::TableView>();
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> pid_vm_fields;
|
||||
pid_vm_fields.empend(
|
||||
"Address", Gfx::TextAlignment::CenterLeft,
|
||||
[](auto& object) { return String::formatted("{:#x}", object.get("address").to_u32()); },
|
||||
[](auto& object) { return object.get("address").to_u32(); });
|
||||
pid_vm_fields.empend("size", "Size", Gfx::TextAlignment::CenterRight);
|
||||
pid_vm_fields.empend("amount_resident", "Resident", Gfx::TextAlignment::CenterRight);
|
||||
pid_vm_fields.empend("amount_dirty", "Dirty", Gfx::TextAlignment::CenterRight);
|
||||
pid_vm_fields.empend("Access", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
StringBuilder builder;
|
||||
if (!object.get("user_accessible").to_bool())
|
||||
builder.append('K');
|
||||
if (object.get("readable").to_bool())
|
||||
builder.append('R');
|
||||
if (object.get("writable").to_bool())
|
||||
builder.append('W');
|
||||
if (object.get("executable").to_bool())
|
||||
builder.append('X');
|
||||
if (object.get("shared").to_bool())
|
||||
builder.append('S');
|
||||
if (object.get("stack").to_bool())
|
||||
builder.append('T');
|
||||
return builder.to_string();
|
||||
});
|
||||
pid_vm_fields.empend("vmobject", "VMObject type", Gfx::TextAlignment::CenterLeft);
|
||||
pid_vm_fields.empend("Purgeable", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
if (object.get("volatile").to_bool())
|
||||
return "Volatile";
|
||||
return "Non-volatile";
|
||||
});
|
||||
pid_vm_fields.empend(
|
||||
"Page map", Gfx::TextAlignment::CenterLeft,
|
||||
[](auto&) {
|
||||
return GUI::Variant();
|
||||
},
|
||||
[](auto&) {
|
||||
return GUI::Variant(0);
|
||||
},
|
||||
[](const JsonObject& object) {
|
||||
auto pagemap = object.get("pagemap").as_string_or({});
|
||||
return pagemap;
|
||||
});
|
||||
pid_vm_fields.empend("cow_pages", "# CoW", Gfx::TextAlignment::CenterRight);
|
||||
pid_vm_fields.empend("name", "Name", Gfx::TextAlignment::CenterLeft);
|
||||
m_json_model = GUI::JsonArrayModel::create({}, move(pid_vm_fields));
|
||||
m_table_view->set_model(GUI::SortingProxyModel::create(*m_json_model));
|
||||
|
||||
m_table_view->set_column_painting_delegate(7, make<PagemapPaintingDelegate>());
|
||||
|
||||
m_table_view->set_key_column_and_sort_order(0, GUI::SortOrder::Ascending);
|
||||
m_timer = add<Core::Timer>(1000, [this] { refresh(); });
|
||||
}
|
||||
|
||||
ProcessMemoryMapWidget::~ProcessMemoryMapWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessMemoryMapWidget::set_pid(pid_t pid)
|
||||
{
|
||||
if (m_pid == pid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
m_json_model->set_json_path(String::formatted("/proc/{}/vm", pid));
|
||||
}
|
||||
|
||||
void ProcessMemoryMapWidget::refresh()
|
||||
{
|
||||
if (m_pid != -1)
|
||||
m_json_model->update();
|
||||
}
|
46
Userland/Applications/SystemMonitor/ProcessMemoryMapWidget.h
Normal file
46
Userland/Applications/SystemMonitor/ProcessMemoryMapWidget.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
class ProcessMemoryMapWidget final : public GUI::Widget {
|
||||
C_OBJECT(ProcessMemoryMapWidget);
|
||||
|
||||
public:
|
||||
virtual ~ProcessMemoryMapWidget() override;
|
||||
|
||||
void set_pid(pid_t);
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
ProcessMemoryMapWidget();
|
||||
RefPtr<GUI::TableView> m_table_view;
|
||||
RefPtr<GUI::JsonArrayModel> m_json_model;
|
||||
pid_t m_pid { -1 };
|
||||
RefPtr<Core::Timer> m_timer;
|
||||
};
|
454
Userland/Applications/SystemMonitor/ProcessModel.cpp
Normal file
454
Userland/Applications/SystemMonitor/ProcessModel.cpp
Normal file
|
@ -0,0 +1,454 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ProcessModel.h"
|
||||
#include "GraphWidget.h"
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/SharedBuffer.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/ProcessStatisticsReader.h>
|
||||
#include <LibGUI/FileIconProvider.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static ProcessModel* s_the;
|
||||
|
||||
ProcessModel& ProcessModel::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
ProcessModel::ProcessModel()
|
||||
{
|
||||
ASSERT(!s_the);
|
||||
s_the = this;
|
||||
m_generic_process_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/gear.png");
|
||||
m_high_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/highpriority.png");
|
||||
m_low_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/lowpriority.png");
|
||||
m_normal_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/normalpriority.png");
|
||||
|
||||
auto file = Core::File::construct("/proc/cpuinfo");
|
||||
if (file->open(Core::IODevice::ReadOnly)) {
|
||||
auto json = JsonValue::from_string({ file->read_all() });
|
||||
auto cpuinfo_array = json.value().as_array();
|
||||
cpuinfo_array.for_each([&](auto& value) {
|
||||
auto& cpu_object = value.as_object();
|
||||
auto cpu_id = cpu_object.get("processor").as_u32();
|
||||
m_cpus.append(make<CpuInfo>(cpu_id));
|
||||
});
|
||||
}
|
||||
|
||||
if (m_cpus.is_empty())
|
||||
m_cpus.append(make<CpuInfo>(0));
|
||||
}
|
||||
|
||||
ProcessModel::~ProcessModel()
|
||||
{
|
||||
}
|
||||
|
||||
int ProcessModel::row_count(const GUI::ModelIndex&) const
|
||||
{
|
||||
return m_pids.size();
|
||||
}
|
||||
|
||||
int ProcessModel::column_count(const GUI::ModelIndex&) const
|
||||
{
|
||||
return Column::__Count;
|
||||
}
|
||||
|
||||
String ProcessModel::column_name(int column) const
|
||||
{
|
||||
switch (column) {
|
||||
case Column::Icon:
|
||||
return "";
|
||||
case Column::PID:
|
||||
return "PID";
|
||||
case Column::TID:
|
||||
return "TID";
|
||||
case Column::PPID:
|
||||
return "PPID";
|
||||
case Column::PGID:
|
||||
return "PGID";
|
||||
case Column::SID:
|
||||
return "SID";
|
||||
case Column::State:
|
||||
return "State";
|
||||
case Column::User:
|
||||
return "User";
|
||||
case Column::Priority:
|
||||
return "Pr";
|
||||
case Column::EffectivePriority:
|
||||
return "EPr";
|
||||
case Column::Virtual:
|
||||
return "Virtual";
|
||||
case Column::Physical:
|
||||
return "Physical";
|
||||
case Column::DirtyPrivate:
|
||||
return "DirtyP";
|
||||
case Column::CleanInode:
|
||||
return "CleanI";
|
||||
case Column::PurgeableVolatile:
|
||||
return "Purg:V";
|
||||
case Column::PurgeableNonvolatile:
|
||||
return "Purg:N";
|
||||
case Column::CPU:
|
||||
return "CPU";
|
||||
case Column::Processor:
|
||||
return "Processor";
|
||||
case Column::Name:
|
||||
return "Name";
|
||||
case Column::Syscalls:
|
||||
return "Syscalls";
|
||||
case Column::InodeFaults:
|
||||
return "F:Inode";
|
||||
case Column::ZeroFaults:
|
||||
return "F:Zero";
|
||||
case Column::CowFaults:
|
||||
return "F:CoW";
|
||||
case Column::IPv4SocketReadBytes:
|
||||
return "IPv4 In";
|
||||
case Column::IPv4SocketWriteBytes:
|
||||
return "IPv4 Out";
|
||||
case Column::UnixSocketReadBytes:
|
||||
return "Unix In";
|
||||
case Column::UnixSocketWriteBytes:
|
||||
return "Unix Out";
|
||||
case Column::FileReadBytes:
|
||||
return "File In";
|
||||
case Column::FileWriteBytes:
|
||||
return "File Out";
|
||||
case Column::Pledge:
|
||||
return "Pledge";
|
||||
case Column::Veil:
|
||||
return "Veil";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static String pretty_byte_size(size_t size)
|
||||
{
|
||||
return String::formatted("{}K", size / 1024);
|
||||
}
|
||||
|
||||
GUI::Variant ProcessModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
ASSERT(is_valid(index));
|
||||
|
||||
if (role == GUI::ModelRole::TextAlignment) {
|
||||
switch (index.column()) {
|
||||
case Column::Icon:
|
||||
case Column::Name:
|
||||
case Column::State:
|
||||
case Column::User:
|
||||
case Column::Pledge:
|
||||
case Column::Veil:
|
||||
return Gfx::TextAlignment::CenterLeft;
|
||||
case Column::PID:
|
||||
case Column::TID:
|
||||
case Column::PPID:
|
||||
case Column::PGID:
|
||||
case Column::SID:
|
||||
case Column::Priority:
|
||||
case Column::EffectivePriority:
|
||||
case Column::Virtual:
|
||||
case Column::Physical:
|
||||
case Column::DirtyPrivate:
|
||||
case Column::CleanInode:
|
||||
case Column::PurgeableVolatile:
|
||||
case Column::PurgeableNonvolatile:
|
||||
case Column::CPU:
|
||||
case Column::Processor:
|
||||
case Column::Syscalls:
|
||||
case Column::InodeFaults:
|
||||
case Column::ZeroFaults:
|
||||
case Column::CowFaults:
|
||||
case Column::FileReadBytes:
|
||||
case Column::FileWriteBytes:
|
||||
case Column::UnixSocketReadBytes:
|
||||
case Column::UnixSocketWriteBytes:
|
||||
case Column::IPv4SocketReadBytes:
|
||||
case Column::IPv4SocketWriteBytes:
|
||||
return Gfx::TextAlignment::CenterRight;
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
auto it = m_threads.find(m_pids[index.row()]);
|
||||
auto& thread = *(*it).value;
|
||||
|
||||
if (role == GUI::ModelRole::Sort) {
|
||||
switch (index.column()) {
|
||||
case Column::Icon:
|
||||
return 0;
|
||||
case Column::PID:
|
||||
return thread.current_state.pid;
|
||||
case Column::TID:
|
||||
return thread.current_state.tid;
|
||||
case Column::PPID:
|
||||
return thread.current_state.ppid;
|
||||
case Column::PGID:
|
||||
return thread.current_state.pgid;
|
||||
case Column::SID:
|
||||
return thread.current_state.sid;
|
||||
case Column::State:
|
||||
return thread.current_state.state;
|
||||
case Column::User:
|
||||
return thread.current_state.user;
|
||||
case Column::Priority:
|
||||
return thread.current_state.priority;
|
||||
case Column::EffectivePriority:
|
||||
return thread.current_state.effective_priority;
|
||||
case Column::Virtual:
|
||||
return (int)thread.current_state.amount_virtual;
|
||||
case Column::Physical:
|
||||
return (int)thread.current_state.amount_resident;
|
||||
case Column::DirtyPrivate:
|
||||
return (int)thread.current_state.amount_dirty_private;
|
||||
case Column::CleanInode:
|
||||
return (int)thread.current_state.amount_clean_inode;
|
||||
case Column::PurgeableVolatile:
|
||||
return (int)thread.current_state.amount_purgeable_volatile;
|
||||
case Column::PurgeableNonvolatile:
|
||||
return (int)thread.current_state.amount_purgeable_nonvolatile;
|
||||
case Column::CPU:
|
||||
return thread.current_state.cpu_percent;
|
||||
case Column::Processor:
|
||||
return thread.current_state.cpu;
|
||||
case Column::Name:
|
||||
return thread.current_state.name;
|
||||
case Column::Syscalls:
|
||||
return thread.current_state.syscall_count;
|
||||
case Column::InodeFaults:
|
||||
return thread.current_state.inode_faults;
|
||||
case Column::ZeroFaults:
|
||||
return thread.current_state.zero_faults;
|
||||
case Column::CowFaults:
|
||||
return thread.current_state.cow_faults;
|
||||
case Column::IPv4SocketReadBytes:
|
||||
return thread.current_state.ipv4_socket_read_bytes;
|
||||
case Column::IPv4SocketWriteBytes:
|
||||
return thread.current_state.ipv4_socket_write_bytes;
|
||||
case Column::UnixSocketReadBytes:
|
||||
return thread.current_state.unix_socket_read_bytes;
|
||||
case Column::UnixSocketWriteBytes:
|
||||
return thread.current_state.unix_socket_write_bytes;
|
||||
case Column::FileReadBytes:
|
||||
return thread.current_state.file_read_bytes;
|
||||
case Column::FileWriteBytes:
|
||||
return thread.current_state.file_write_bytes;
|
||||
case Column::Pledge:
|
||||
return thread.current_state.pledge;
|
||||
case Column::Veil:
|
||||
return thread.current_state.veil;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
switch (index.column()) {
|
||||
case Column::Icon: {
|
||||
auto icon = GUI::FileIconProvider::icon_for_executable(thread.current_state.executable);
|
||||
if (auto* bitmap = icon.bitmap_for_size(16))
|
||||
return *bitmap;
|
||||
return *m_generic_process_icon;
|
||||
}
|
||||
case Column::PID:
|
||||
return thread.current_state.pid;
|
||||
case Column::TID:
|
||||
return thread.current_state.tid;
|
||||
case Column::PPID:
|
||||
return thread.current_state.ppid;
|
||||
case Column::PGID:
|
||||
return thread.current_state.pgid;
|
||||
case Column::SID:
|
||||
return thread.current_state.sid;
|
||||
case Column::State:
|
||||
return thread.current_state.state;
|
||||
case Column::User:
|
||||
return thread.current_state.user;
|
||||
case Column::Priority:
|
||||
return thread.current_state.priority;
|
||||
case Column::EffectivePriority:
|
||||
return thread.current_state.effective_priority;
|
||||
case Column::Virtual:
|
||||
return pretty_byte_size(thread.current_state.amount_virtual);
|
||||
case Column::Physical:
|
||||
return pretty_byte_size(thread.current_state.amount_resident);
|
||||
case Column::DirtyPrivate:
|
||||
return pretty_byte_size(thread.current_state.amount_dirty_private);
|
||||
case Column::CleanInode:
|
||||
return pretty_byte_size(thread.current_state.amount_clean_inode);
|
||||
case Column::PurgeableVolatile:
|
||||
return pretty_byte_size(thread.current_state.amount_purgeable_volatile);
|
||||
case Column::PurgeableNonvolatile:
|
||||
return pretty_byte_size(thread.current_state.amount_purgeable_nonvolatile);
|
||||
case Column::CPU:
|
||||
return thread.current_state.cpu_percent;
|
||||
case Column::Processor:
|
||||
return thread.current_state.cpu;
|
||||
case Column::Name:
|
||||
return thread.current_state.name;
|
||||
case Column::Syscalls:
|
||||
return thread.current_state.syscall_count;
|
||||
case Column::InodeFaults:
|
||||
return thread.current_state.inode_faults;
|
||||
case Column::ZeroFaults:
|
||||
return thread.current_state.zero_faults;
|
||||
case Column::CowFaults:
|
||||
return thread.current_state.cow_faults;
|
||||
case Column::IPv4SocketReadBytes:
|
||||
return thread.current_state.ipv4_socket_read_bytes;
|
||||
case Column::IPv4SocketWriteBytes:
|
||||
return thread.current_state.ipv4_socket_write_bytes;
|
||||
case Column::UnixSocketReadBytes:
|
||||
return thread.current_state.unix_socket_read_bytes;
|
||||
case Column::UnixSocketWriteBytes:
|
||||
return thread.current_state.unix_socket_write_bytes;
|
||||
case Column::FileReadBytes:
|
||||
return thread.current_state.file_read_bytes;
|
||||
case Column::FileWriteBytes:
|
||||
return thread.current_state.file_write_bytes;
|
||||
case Column::Pledge:
|
||||
return thread.current_state.pledge;
|
||||
case Column::Veil:
|
||||
return thread.current_state.veil;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProcessModel::update()
|
||||
{
|
||||
auto previous_pid_count = m_pids.size();
|
||||
auto all_processes = Core::ProcessStatisticsReader::get_all(m_proc_all);
|
||||
|
||||
u64 last_sum_ticks_scheduled = 0, last_sum_ticks_scheduled_kernel = 0;
|
||||
for (auto& it : m_threads) {
|
||||
auto& current_state = it.value->current_state;
|
||||
last_sum_ticks_scheduled += current_state.ticks_user + current_state.ticks_kernel;
|
||||
last_sum_ticks_scheduled_kernel += current_state.ticks_kernel;
|
||||
}
|
||||
|
||||
HashTable<PidAndTid> live_pids;
|
||||
u64 sum_ticks_scheduled = 0, sum_ticks_scheduled_kernel = 0;
|
||||
if (all_processes.has_value()) {
|
||||
for (auto& it : all_processes.value()) {
|
||||
for (auto& thread : it.value.threads) {
|
||||
ThreadState state;
|
||||
state.pid = it.value.pid;
|
||||
state.user = it.value.username;
|
||||
state.pledge = it.value.pledge;
|
||||
state.veil = it.value.veil;
|
||||
state.syscall_count = thread.syscall_count;
|
||||
state.inode_faults = thread.inode_faults;
|
||||
state.zero_faults = thread.zero_faults;
|
||||
state.cow_faults = thread.cow_faults;
|
||||
state.unix_socket_read_bytes = thread.unix_socket_read_bytes;
|
||||
state.unix_socket_write_bytes = thread.unix_socket_write_bytes;
|
||||
state.ipv4_socket_read_bytes = thread.ipv4_socket_read_bytes;
|
||||
state.ipv4_socket_write_bytes = thread.ipv4_socket_write_bytes;
|
||||
state.file_read_bytes = thread.file_read_bytes;
|
||||
state.file_write_bytes = thread.file_write_bytes;
|
||||
state.amount_virtual = it.value.amount_virtual;
|
||||
state.amount_resident = it.value.amount_resident;
|
||||
state.amount_dirty_private = it.value.amount_dirty_private;
|
||||
state.amount_clean_inode = it.value.amount_clean_inode;
|
||||
state.amount_purgeable_volatile = it.value.amount_purgeable_volatile;
|
||||
state.amount_purgeable_nonvolatile = it.value.amount_purgeable_nonvolatile;
|
||||
|
||||
state.name = thread.name;
|
||||
state.executable = it.value.executable;
|
||||
|
||||
state.ppid = it.value.ppid;
|
||||
state.tid = thread.tid;
|
||||
state.pgid = it.value.pgid;
|
||||
state.sid = it.value.sid;
|
||||
state.times_scheduled = thread.times_scheduled;
|
||||
state.ticks_user = thread.ticks_user;
|
||||
state.ticks_kernel = thread.ticks_kernel;
|
||||
state.cpu = thread.cpu;
|
||||
state.cpu_percent = 0;
|
||||
state.priority = thread.priority;
|
||||
state.effective_priority = thread.effective_priority;
|
||||
state.state = thread.state;
|
||||
sum_ticks_scheduled += thread.ticks_user + thread.ticks_kernel;
|
||||
sum_ticks_scheduled_kernel += thread.ticks_kernel;
|
||||
{
|
||||
auto pit = m_threads.find({ it.value.pid, thread.tid });
|
||||
if (pit == m_threads.end())
|
||||
m_threads.set({ it.value.pid, thread.tid }, make<Thread>());
|
||||
}
|
||||
auto pit = m_threads.find({ it.value.pid, thread.tid });
|
||||
ASSERT(pit != m_threads.end());
|
||||
(*pit).value->previous_state = (*pit).value->current_state;
|
||||
(*pit).value->current_state = state;
|
||||
|
||||
live_pids.set({ it.value.pid, thread.tid });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_pids.clear();
|
||||
for (auto& c : m_cpus) {
|
||||
c.total_cpu_percent = 0.0;
|
||||
c.total_cpu_percent_kernel = 0.0;
|
||||
}
|
||||
Vector<PidAndTid, 16> pids_to_remove;
|
||||
for (auto& it : m_threads) {
|
||||
if (!live_pids.contains(it.key)) {
|
||||
pids_to_remove.append(it.key);
|
||||
continue;
|
||||
}
|
||||
auto& process = *it.value;
|
||||
u32 ticks_scheduled_diff = (process.current_state.ticks_user + process.current_state.ticks_kernel)
|
||||
- (process.previous_state.ticks_user + process.previous_state.ticks_kernel);
|
||||
u32 ticks_scheduled_diff_kernel = process.current_state.ticks_kernel - process.previous_state.ticks_kernel;
|
||||
process.current_state.cpu_percent = ((float)ticks_scheduled_diff * 100) / (float)(sum_ticks_scheduled - last_sum_ticks_scheduled);
|
||||
process.current_state.cpu_percent_kernel = ((float)ticks_scheduled_diff_kernel * 100) / (float)(sum_ticks_scheduled - last_sum_ticks_scheduled);
|
||||
if (it.key.pid != 0) {
|
||||
auto& cpu_info = m_cpus[process.current_state.cpu];
|
||||
cpu_info.total_cpu_percent += process.current_state.cpu_percent;
|
||||
cpu_info.total_cpu_percent_kernel += process.current_state.cpu_percent_kernel;
|
||||
m_pids.append(it.key);
|
||||
}
|
||||
}
|
||||
for (auto pid : pids_to_remove)
|
||||
m_threads.remove(pid);
|
||||
|
||||
if (on_cpu_info_change)
|
||||
on_cpu_info_change(m_cpus);
|
||||
|
||||
// FIXME: This is a rather hackish way of invalidating indexes.
|
||||
// It would be good if GUI::Model had a way to orchestrate removal/insertion while preserving indexes.
|
||||
did_update(previous_pid_count == m_pids.size() ? GUI::Model::UpdateFlag::DontInvalidateIndexes : GUI::Model::UpdateFlag::InvalidateAllIndexes);
|
||||
}
|
172
Userland/Applications/SystemMonitor/ProcessModel.h
Normal file
172
Userland/Applications/SystemMonitor/ProcessModel.h
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullOwnPtrVector.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class GraphWidget;
|
||||
|
||||
struct PidAndTid {
|
||||
bool operator==(const PidAndTid& other) const
|
||||
{
|
||||
return pid == other.pid && tid == other.tid;
|
||||
}
|
||||
pid_t pid;
|
||||
int tid;
|
||||
};
|
||||
|
||||
class ProcessModel final : public GUI::Model {
|
||||
public:
|
||||
enum Column {
|
||||
Icon = 0,
|
||||
Name,
|
||||
CPU,
|
||||
Processor,
|
||||
State,
|
||||
Priority,
|
||||
EffectivePriority,
|
||||
User,
|
||||
PID,
|
||||
TID,
|
||||
PPID,
|
||||
PGID,
|
||||
SID,
|
||||
Virtual,
|
||||
Physical,
|
||||
DirtyPrivate,
|
||||
CleanInode,
|
||||
PurgeableVolatile,
|
||||
PurgeableNonvolatile,
|
||||
Veil,
|
||||
Pledge,
|
||||
Syscalls,
|
||||
InodeFaults,
|
||||
ZeroFaults,
|
||||
CowFaults,
|
||||
FileReadBytes,
|
||||
FileWriteBytes,
|
||||
UnixSocketReadBytes,
|
||||
UnixSocketWriteBytes,
|
||||
IPv4SocketReadBytes,
|
||||
IPv4SocketWriteBytes,
|
||||
__Count
|
||||
};
|
||||
|
||||
static ProcessModel& the();
|
||||
|
||||
static NonnullRefPtr<ProcessModel> create() { return adopt(*new ProcessModel); }
|
||||
virtual ~ProcessModel() override;
|
||||
|
||||
virtual int row_count(const GUI::ModelIndex&) const override;
|
||||
virtual int column_count(const GUI::ModelIndex&) const override;
|
||||
virtual String column_name(int column) const override;
|
||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||
virtual void update() override;
|
||||
|
||||
struct CpuInfo {
|
||||
u32 id;
|
||||
float total_cpu_percent { 0.0 };
|
||||
float total_cpu_percent_kernel { 0.0 };
|
||||
|
||||
CpuInfo(u32 id)
|
||||
: id(id)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
Function<void(const NonnullOwnPtrVector<CpuInfo>&)> on_cpu_info_change;
|
||||
|
||||
const NonnullOwnPtrVector<CpuInfo>& cpus() const { return m_cpus; }
|
||||
|
||||
private:
|
||||
ProcessModel();
|
||||
|
||||
struct ThreadState {
|
||||
pid_t tid;
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
pid_t pgid;
|
||||
pid_t sid;
|
||||
unsigned times_scheduled;
|
||||
unsigned ticks_user;
|
||||
unsigned ticks_kernel;
|
||||
String executable;
|
||||
String name;
|
||||
String state;
|
||||
String user;
|
||||
String pledge;
|
||||
String veil;
|
||||
u32 cpu;
|
||||
u32 priority;
|
||||
u32 effective_priority;
|
||||
size_t amount_virtual;
|
||||
size_t amount_resident;
|
||||
size_t amount_dirty_private;
|
||||
size_t amount_clean_inode;
|
||||
size_t amount_purgeable_volatile;
|
||||
size_t amount_purgeable_nonvolatile;
|
||||
unsigned syscall_count;
|
||||
unsigned inode_faults;
|
||||
unsigned zero_faults;
|
||||
unsigned cow_faults;
|
||||
unsigned unix_socket_read_bytes;
|
||||
unsigned unix_socket_write_bytes;
|
||||
unsigned ipv4_socket_read_bytes;
|
||||
unsigned ipv4_socket_write_bytes;
|
||||
unsigned file_read_bytes;
|
||||
unsigned file_write_bytes;
|
||||
float cpu_percent;
|
||||
float cpu_percent_kernel;
|
||||
};
|
||||
|
||||
struct Thread {
|
||||
ThreadState current_state;
|
||||
ThreadState previous_state;
|
||||
};
|
||||
|
||||
HashMap<uid_t, String> m_usernames;
|
||||
HashMap<PidAndTid, NonnullOwnPtr<Thread>> m_threads;
|
||||
NonnullOwnPtrVector<CpuInfo> m_cpus;
|
||||
Vector<PidAndTid> m_pids;
|
||||
RefPtr<Gfx::Bitmap> m_generic_process_icon;
|
||||
RefPtr<Gfx::Bitmap> m_high_priority_icon;
|
||||
RefPtr<Gfx::Bitmap> m_low_priority_icon;
|
||||
RefPtr<Gfx::Bitmap> m_normal_priority_icon;
|
||||
RefPtr<Core::File> m_proc_all;
|
||||
};
|
||||
|
||||
namespace AK {
|
||||
template<>
|
||||
struct Traits<PidAndTid> : public GenericTraits<PidAndTid> {
|
||||
static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); }
|
||||
};
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ProcessUnveiledPathsWidget.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/JsonArrayModel.h>
|
||||
#include <LibGUI/SortingProxyModel.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
|
||||
ProcessUnveiledPathsWidget::ProcessUnveiledPathsWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_table_view = add<GUI::TableView>();
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> pid_unveil_fields;
|
||||
pid_unveil_fields.empend("path", "Path", Gfx::TextAlignment::CenterLeft);
|
||||
pid_unveil_fields.empend("permissions", "Permissions", Gfx::TextAlignment::CenterLeft);
|
||||
|
||||
m_model = GUI::JsonArrayModel::create({}, move(pid_unveil_fields));
|
||||
m_table_view->set_model(GUI::SortingProxyModel::create(*m_model));
|
||||
}
|
||||
|
||||
ProcessUnveiledPathsWidget::~ProcessUnveiledPathsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessUnveiledPathsWidget::set_pid(pid_t pid)
|
||||
{
|
||||
if (m_pid == pid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
m_model->set_json_path(String::formatted("/proc/{}/unveil", m_pid));
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
class ProcessUnveiledPathsWidget final : public GUI::Widget {
|
||||
C_OBJECT(ProcessUnveiledPathsWidget);
|
||||
|
||||
public:
|
||||
virtual ~ProcessUnveiledPathsWidget() override;
|
||||
|
||||
void set_pid(pid_t);
|
||||
|
||||
private:
|
||||
ProcessUnveiledPathsWidget();
|
||||
|
||||
RefPtr<GUI::TableView> m_table_view;
|
||||
RefPtr<GUI::JsonArrayModel> m_model;
|
||||
pid_t m_pid { -1 };
|
||||
};
|
68
Userland/Applications/SystemMonitor/ThreadStackWidget.cpp
Normal file
68
Userland/Applications/SystemMonitor/ThreadStackWidget.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ThreadStackWidget.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
|
||||
ThreadStackWidget::ThreadStackWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_stack_editor = add<GUI::TextEditor>();
|
||||
m_stack_editor->set_mode(GUI::TextEditor::ReadOnly);
|
||||
|
||||
m_timer = add<Core::Timer>(1000, [this] { refresh(); });
|
||||
}
|
||||
|
||||
ThreadStackWidget::~ThreadStackWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ThreadStackWidget::set_ids(pid_t pid, pid_t tid)
|
||||
{
|
||||
if (m_pid == pid && m_tid == tid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
m_tid = tid;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void ThreadStackWidget::refresh()
|
||||
{
|
||||
auto file = Core::File::construct(String::formatted("/proc/{}/stacks/{}", m_pid, m_tid));
|
||||
if (!file->open(Core::IODevice::ReadOnly)) {
|
||||
m_stack_editor->set_text(String::formatted("Unable to open {}", file->filename()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto new_text = file->read_all();
|
||||
if (m_stack_editor->text() != new_text) {
|
||||
m_stack_editor->set_text(new_text);
|
||||
}
|
||||
}
|
47
Userland/Applications/SystemMonitor/ThreadStackWidget.h
Normal file
47
Userland/Applications/SystemMonitor/ThreadStackWidget.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/TextEditor.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
class ThreadStackWidget final : public GUI::Widget {
|
||||
C_OBJECT(ThreadStackWidget)
|
||||
public:
|
||||
virtual ~ThreadStackWidget() override;
|
||||
|
||||
void set_ids(pid_t pid, pid_t tid);
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
ThreadStackWidget();
|
||||
|
||||
pid_t m_pid { -1 };
|
||||
pid_t m_tid { -1 };
|
||||
RefPtr<GUI::TextEditor> m_stack_editor;
|
||||
RefPtr<Core::Timer> m_timer;
|
||||
};
|
670
Userland/Applications/SystemMonitor/main.cpp
Normal file
670
Userland/Applications/SystemMonitor/main.cpp
Normal file
|
@ -0,0 +1,670 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "DevicesModel.h"
|
||||
#include "GraphWidget.h"
|
||||
#include "InterruptsWidget.h"
|
||||
#include "MemoryStatsWidget.h"
|
||||
#include "NetworkStatisticsWidget.h"
|
||||
#include "ProcessFileDescriptorMapWidget.h"
|
||||
#include "ProcessMemoryMapWidget.h"
|
||||
#include "ProcessModel.h"
|
||||
#include "ProcessUnveiledPathsWidget.h"
|
||||
#include "ThreadStackWidget.h"
|
||||
#include <AK/NumberFormat.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/GroupBox.h>
|
||||
#include <LibGUI/Icon.h>
|
||||
#include <LibGUI/JsonArrayModel.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/LazyWidget.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/MenuBar.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/SortingProxyModel.h>
|
||||
#include <LibGUI/Splitter.h>
|
||||
#include <LibGUI/TabWidget.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
#include <LibGUI/ToolBar.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibPCIDB/Database.h>
|
||||
#include <serenity.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static NonnullRefPtr<GUI::Widget> build_file_systems_tab();
|
||||
static NonnullRefPtr<GUI::Widget> build_pci_devices_tab();
|
||||
static NonnullRefPtr<GUI::Widget> build_devices_tab();
|
||||
static NonnullRefPtr<GUI::Widget> build_graphs_tab();
|
||||
static NonnullRefPtr<GUI::Widget> build_processors_tab();
|
||||
|
||||
class UnavailableProcessWidget final : public GUI::Frame {
|
||||
C_OBJECT(UnavailableProcessWidget)
|
||||
public:
|
||||
virtual ~UnavailableProcessWidget() override { }
|
||||
|
||||
const String& text() const { return m_text; }
|
||||
void set_text(String text) { m_text = move(text); }
|
||||
|
||||
private:
|
||||
UnavailableProcessWidget(String text)
|
||||
: m_text(move(text))
|
||||
{
|
||||
}
|
||||
|
||||
virtual void paint_event(GUI::PaintEvent& event) override
|
||||
{
|
||||
Frame::paint_event(event);
|
||||
if (text().is_empty())
|
||||
return;
|
||||
GUI::Painter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
painter.draw_text(frame_inner_rect(), text(), Gfx::TextAlignment::Center, palette().window_text(), Gfx::TextElision::Right);
|
||||
}
|
||||
|
||||
String m_text;
|
||||
};
|
||||
|
||||
static bool can_access_pid(pid_t pid)
|
||||
{
|
||||
auto path = String::formatted("/proc/{}", pid);
|
||||
return access(path.characters(), X_OK) == 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (pledge("stdio proc shared_buffer accept rpath exec unix cpath fattr", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto app = GUI::Application::construct(argc, argv);
|
||||
|
||||
if (pledge("stdio proc shared_buffer accept rpath exec", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/etc/passwd", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/res", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/proc", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/dev", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/bin", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/bin/Profiler", "rx") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/bin/Inspector", "rx") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unveil(nullptr, nullptr);
|
||||
|
||||
const char* args_tab = "processes";
|
||||
Core::ArgsParser parser;
|
||||
parser.add_option(args_tab, "Tab, one of 'processes', 'graphs', 'fs', 'pci', 'devices', 'network', 'processors' or 'interrupts'", "open-tab", 't', "tab");
|
||||
parser.parse(argc, argv);
|
||||
StringView args_tab_view = args_tab;
|
||||
|
||||
auto app_icon = GUI::Icon::default_icon("app-system-monitor");
|
||||
|
||||
auto window = GUI::Window::construct();
|
||||
window->set_title("System Monitor");
|
||||
window->resize(680, 400);
|
||||
|
||||
auto& keeper = window->set_main_widget<GUI::Widget>();
|
||||
keeper.set_layout<GUI::VerticalBoxLayout>();
|
||||
keeper.set_fill_with_background_color(true);
|
||||
keeper.layout()->set_margins({ 2, 2, 2, 2 });
|
||||
|
||||
auto& tabwidget = keeper.add<GUI::TabWidget>();
|
||||
|
||||
auto process_container_splitter = GUI::VerticalSplitter::construct();
|
||||
tabwidget.add_widget("Processes", process_container_splitter);
|
||||
process_container_splitter->layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto& process_table_container = process_container_splitter->add<GUI::Widget>();
|
||||
|
||||
auto graphs_widget = build_graphs_tab();
|
||||
tabwidget.add_widget("Graphs", graphs_widget);
|
||||
|
||||
auto file_systems_widget = build_file_systems_tab();
|
||||
tabwidget.add_widget("File systems", file_systems_widget);
|
||||
|
||||
auto pci_devices_widget = build_pci_devices_tab();
|
||||
tabwidget.add_widget("PCI devices", pci_devices_widget);
|
||||
|
||||
auto devices_widget = build_devices_tab();
|
||||
tabwidget.add_widget("Devices", devices_widget);
|
||||
|
||||
auto network_stats_widget = NetworkStatisticsWidget::construct();
|
||||
tabwidget.add_widget("Network", network_stats_widget);
|
||||
|
||||
auto processors_widget = build_processors_tab();
|
||||
tabwidget.add_widget("Processors", processors_widget);
|
||||
|
||||
auto interrupts_widget = InterruptsWidget::construct();
|
||||
tabwidget.add_widget("Interrupts", interrupts_widget);
|
||||
|
||||
process_table_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
process_table_container.layout()->set_spacing(0);
|
||||
|
||||
auto& process_table_view = process_table_container.add<GUI::TableView>();
|
||||
process_table_view.set_column_headers_visible(true);
|
||||
process_table_view.set_model(GUI::SortingProxyModel::create(ProcessModel::create()));
|
||||
process_table_view.set_key_column_and_sort_order(ProcessModel::Column::CPU, GUI::SortOrder::Descending);
|
||||
process_table_view.model()->update();
|
||||
|
||||
auto& refresh_timer = window->add<Core::Timer>(
|
||||
3000, [&] {
|
||||
process_table_view.model()->update();
|
||||
if (auto* memory_stats_widget = MemoryStatsWidget::the())
|
||||
memory_stats_widget->refresh();
|
||||
});
|
||||
|
||||
auto selected_id = [&](ProcessModel::Column column) -> pid_t {
|
||||
if (process_table_view.selection().is_empty())
|
||||
return -1;
|
||||
auto pid_index = process_table_view.model()->index(process_table_view.selection().first().row(), column);
|
||||
return pid_index.data().to_i32();
|
||||
};
|
||||
|
||||
auto kill_action = GUI::Action::create("Kill process", { Mod_Ctrl, Key_K }, Gfx::Bitmap::load_from_file("/res/icons/16x16/kill.png"), [&](const GUI::Action&) {
|
||||
pid_t pid = selected_id(ProcessModel::Column::PID);
|
||||
if (pid != -1)
|
||||
kill(pid, SIGKILL);
|
||||
});
|
||||
|
||||
auto stop_action = GUI::Action::create("Stop process", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/stop-hand.png"), [&](const GUI::Action&) {
|
||||
pid_t pid = selected_id(ProcessModel::Column::PID);
|
||||
if (pid != -1)
|
||||
kill(pid, SIGSTOP);
|
||||
});
|
||||
|
||||
auto continue_action = GUI::Action::create("Continue process", { Mod_Ctrl, Key_C }, Gfx::Bitmap::load_from_file("/res/icons/16x16/continue.png"), [&](const GUI::Action&) {
|
||||
pid_t pid = selected_id(ProcessModel::Column::PID);
|
||||
if (pid != -1)
|
||||
kill(pid, SIGCONT);
|
||||
});
|
||||
|
||||
auto profile_action = GUI::Action::create("Profile process", { Mod_Ctrl, Key_P },
|
||||
Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"), [&](auto&) {
|
||||
pid_t pid = selected_id(ProcessModel::Column::PID);
|
||||
if (pid != -1) {
|
||||
auto pid_string = String::format("%d", pid);
|
||||
pid_t child;
|
||||
const char* argv[] = { "/bin/Profiler", "--pid", pid_string.characters(), nullptr };
|
||||
if ((errno = posix_spawn(&child, "/bin/Profiler", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
||||
perror("posix_spawn");
|
||||
} else {
|
||||
if (disown(child) < 0)
|
||||
perror("disown");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto inspect_action = GUI::Action::create("Inspect process", { Mod_Ctrl, Key_I },
|
||||
Gfx::Bitmap::load_from_file("/res/icons/16x16/app-inspector.png"), [&](auto&) {
|
||||
pid_t pid = selected_id(ProcessModel::Column::PID);
|
||||
if (pid != -1) {
|
||||
auto pid_string = String::format("%d", pid);
|
||||
pid_t child;
|
||||
const char* argv[] = { "/bin/Inspector", pid_string.characters(), nullptr };
|
||||
if ((errno = posix_spawn(&child, "/bin/Inspector", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
||||
perror("posix_spawn");
|
||||
} else {
|
||||
if (disown(child) < 0)
|
||||
perror("disown");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto menubar = GUI::MenuBar::construct();
|
||||
auto& app_menu = menubar->add_menu("System Monitor");
|
||||
app_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
|
||||
GUI::Application::the()->quit();
|
||||
return;
|
||||
}));
|
||||
|
||||
auto& process_menu = menubar->add_menu("Process");
|
||||
process_menu.add_action(kill_action);
|
||||
process_menu.add_action(stop_action);
|
||||
process_menu.add_action(continue_action);
|
||||
process_menu.add_separator();
|
||||
process_menu.add_action(profile_action);
|
||||
process_menu.add_action(inspect_action);
|
||||
|
||||
auto process_context_menu = GUI::Menu::construct();
|
||||
process_context_menu->add_action(kill_action);
|
||||
process_context_menu->add_action(stop_action);
|
||||
process_context_menu->add_action(continue_action);
|
||||
process_context_menu->add_separator();
|
||||
process_context_menu->add_action(profile_action);
|
||||
process_context_menu->add_action(inspect_action);
|
||||
process_table_view.on_context_menu_request = [&]([[maybe_unused]] const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) {
|
||||
process_context_menu->popup(event.screen_position());
|
||||
};
|
||||
|
||||
auto& frequency_menu = menubar->add_menu("Frequency");
|
||||
GUI::ActionGroup frequency_action_group;
|
||||
frequency_action_group.set_exclusive(true);
|
||||
|
||||
auto make_frequency_action = [&](auto& title, int interval, bool checked = false) {
|
||||
auto action = GUI::Action::create_checkable(title, [&refresh_timer, interval](auto&) {
|
||||
refresh_timer.restart(interval);
|
||||
});
|
||||
action->set_checked(checked);
|
||||
frequency_action_group.add_action(*action);
|
||||
frequency_menu.add_action(*action);
|
||||
};
|
||||
|
||||
make_frequency_action("1 sec", 1000);
|
||||
make_frequency_action("3 sec", 3000, true);
|
||||
make_frequency_action("5 sec", 5000);
|
||||
|
||||
auto& help_menu = menubar->add_menu("Help");
|
||||
help_menu.add_action(GUI::CommonActions::make_about_action("System Monitor", app_icon, window));
|
||||
|
||||
app->set_menubar(move(menubar));
|
||||
|
||||
auto& process_tab_unused_widget = process_container_splitter->add<UnavailableProcessWidget>("No process selected");
|
||||
process_tab_unused_widget.set_visible(true);
|
||||
|
||||
auto& process_tab_widget = process_container_splitter->add<GUI::TabWidget>();
|
||||
process_tab_widget.set_tab_position(GUI::TabWidget::TabPosition::Bottom);
|
||||
process_tab_widget.set_visible(false);
|
||||
|
||||
auto& memory_map_widget = process_tab_widget.add_tab<ProcessMemoryMapWidget>("Memory map");
|
||||
auto& open_files_widget = process_tab_widget.add_tab<ProcessFileDescriptorMapWidget>("Open files");
|
||||
auto& unveiled_paths_widget = process_tab_widget.add_tab<ProcessUnveiledPathsWidget>("Unveiled paths");
|
||||
auto& stack_widget = process_tab_widget.add_tab<ThreadStackWidget>("Stack");
|
||||
|
||||
process_table_view.on_selection = [&](auto&) {
|
||||
auto pid = selected_id(ProcessModel::Column::PID);
|
||||
auto tid = selected_id(ProcessModel::Column::TID);
|
||||
if (!can_access_pid(pid)) {
|
||||
process_tab_widget.set_visible(false);
|
||||
process_tab_unused_widget.set_text("Process cannot be accessed");
|
||||
process_tab_unused_widget.set_visible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
process_tab_widget.set_visible(true);
|
||||
process_tab_unused_widget.set_visible(false);
|
||||
open_files_widget.set_pid(pid);
|
||||
stack_widget.set_ids(pid, tid);
|
||||
memory_map_widget.set_pid(pid);
|
||||
unveiled_paths_widget.set_pid(pid);
|
||||
};
|
||||
|
||||
window->show();
|
||||
|
||||
window->set_icon(app_icon.bitmap_for_size(16));
|
||||
|
||||
if (args_tab_view == "processes")
|
||||
tabwidget.set_active_widget(process_container_splitter);
|
||||
else if (args_tab_view == "graphs")
|
||||
tabwidget.set_active_widget(graphs_widget);
|
||||
else if (args_tab_view == "fs")
|
||||
tabwidget.set_active_widget(file_systems_widget);
|
||||
else if (args_tab_view == "pci")
|
||||
tabwidget.set_active_widget(pci_devices_widget);
|
||||
else if (args_tab_view == "devices")
|
||||
tabwidget.set_active_widget(devices_widget);
|
||||
else if (args_tab_view == "network")
|
||||
tabwidget.set_active_widget(network_stats_widget);
|
||||
else if (args_tab_view == "processors")
|
||||
tabwidget.set_active_widget(processors_widget);
|
||||
else if (args_tab_view == "interrupts")
|
||||
tabwidget.set_active_widget(interrupts_widget);
|
||||
|
||||
return app->exec();
|
||||
}
|
||||
|
||||
class ProgressBarPaintingDelegate final : public GUI::TableCellPaintingDelegate {
|
||||
public:
|
||||
virtual ~ProgressBarPaintingDelegate() override { }
|
||||
|
||||
virtual void paint(GUI::Painter& painter, const Gfx::IntRect& a_rect, const Palette& palette, const GUI::ModelIndex& index) override
|
||||
{
|
||||
auto rect = a_rect.shrunken(2, 2);
|
||||
auto percentage = index.data(GUI::ModelRole::Custom).to_i32();
|
||||
|
||||
auto data = index.data();
|
||||
String text;
|
||||
if (data.is_string())
|
||||
text = data.as_string();
|
||||
Gfx::StylePainter::paint_progress_bar(painter, rect, palette, 0, 100, percentage, text);
|
||||
painter.draw_rect(rect, Color::Black);
|
||||
}
|
||||
};
|
||||
|
||||
NonnullRefPtr<GUI::Widget> build_file_systems_tab()
|
||||
{
|
||||
auto fs_widget = GUI::LazyWidget::construct();
|
||||
|
||||
fs_widget->on_first_show = [](GUI::LazyWidget& self) {
|
||||
self.set_layout<GUI::VerticalBoxLayout>();
|
||||
self.layout()->set_margins({ 4, 4, 4, 4 });
|
||||
auto& fs_table_view = self.add<GUI::TableView>();
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> df_fields;
|
||||
df_fields.empend("mount_point", "Mount point", Gfx::TextAlignment::CenterLeft);
|
||||
df_fields.empend("class_name", "Class", Gfx::TextAlignment::CenterLeft);
|
||||
df_fields.empend("source", "Source", Gfx::TextAlignment::CenterLeft);
|
||||
df_fields.empend(
|
||||
"Size", Gfx::TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
StringBuilder size_builder;
|
||||
size_builder.append(" ");
|
||||
size_builder.append(human_readable_size(object.get("total_block_count").to_u32() * object.get("block_size").to_u32()));
|
||||
size_builder.append(" ");
|
||||
return size_builder.to_string();
|
||||
},
|
||||
[](const JsonObject& object) {
|
||||
return object.get("total_block_count").to_u32() * object.get("block_size").to_u32();
|
||||
},
|
||||
[](const JsonObject& object) {
|
||||
auto total_blocks = object.get("total_block_count").to_u32();
|
||||
if (total_blocks == 0)
|
||||
return 0;
|
||||
auto free_blocks = object.get("free_block_count").to_u32();
|
||||
auto used_blocks = total_blocks - free_blocks;
|
||||
int percentage = (int)((float)used_blocks / (float)total_blocks * 100.0f);
|
||||
return percentage;
|
||||
});
|
||||
df_fields.empend(
|
||||
"Used", Gfx::TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
auto total_blocks = object.get("total_block_count").to_u32();
|
||||
auto free_blocks = object.get("free_block_count").to_u32();
|
||||
auto used_blocks = total_blocks - free_blocks;
|
||||
return human_readable_size(used_blocks * object.get("block_size").to_u32()); },
|
||||
[](const JsonObject& object) {
|
||||
auto total_blocks = object.get("total_block_count").to_u32();
|
||||
auto free_blocks = object.get("free_block_count").to_u32();
|
||||
auto used_blocks = total_blocks - free_blocks;
|
||||
return used_blocks * object.get("block_size").to_u32();
|
||||
});
|
||||
df_fields.empend(
|
||||
"Available", Gfx::TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
return human_readable_size(object.get("free_block_count").to_u32() * object.get("block_size").to_u32());
|
||||
},
|
||||
[](const JsonObject& object) {
|
||||
return object.get("free_block_count").to_u32() * object.get("block_size").to_u32();
|
||||
});
|
||||
df_fields.empend("Access", Gfx::TextAlignment::CenterLeft, [](const JsonObject& object) {
|
||||
bool readonly = object.get("readonly").to_bool();
|
||||
int mount_flags = object.get("mount_flags").to_int();
|
||||
return readonly || (mount_flags & MS_RDONLY) ? "Read-only" : "Read/Write";
|
||||
});
|
||||
df_fields.empend("Mount flags", Gfx::TextAlignment::CenterLeft, [](const JsonObject& object) {
|
||||
int mount_flags = object.get("mount_flags").to_int();
|
||||
StringBuilder builder;
|
||||
bool first = true;
|
||||
auto check = [&](int flag, const char* name) {
|
||||
if (!(mount_flags & flag))
|
||||
return;
|
||||
if (!first)
|
||||
builder.append(',');
|
||||
builder.append(name);
|
||||
first = false;
|
||||
};
|
||||
check(MS_NODEV, "nodev");
|
||||
check(MS_NOEXEC, "noexec");
|
||||
check(MS_NOSUID, "nosuid");
|
||||
check(MS_BIND, "bind");
|
||||
check(MS_RDONLY, "ro");
|
||||
if (builder.string_view().is_empty())
|
||||
return String("defaults");
|
||||
return builder.to_string();
|
||||
});
|
||||
df_fields.empend("free_block_count", "Free blocks", Gfx::TextAlignment::CenterRight);
|
||||
df_fields.empend("total_block_count", "Total blocks", Gfx::TextAlignment::CenterRight);
|
||||
df_fields.empend("free_inode_count", "Free inodes", Gfx::TextAlignment::CenterRight);
|
||||
df_fields.empend("total_inode_count", "Total inodes", Gfx::TextAlignment::CenterRight);
|
||||
df_fields.empend("block_size", "Block size", Gfx::TextAlignment::CenterRight);
|
||||
fs_table_view.set_model(GUI::SortingProxyModel::create(GUI::JsonArrayModel::create("/proc/df", move(df_fields))));
|
||||
|
||||
fs_table_view.set_column_painting_delegate(3, make<ProgressBarPaintingDelegate>());
|
||||
|
||||
fs_table_view.model()->update();
|
||||
};
|
||||
return fs_widget;
|
||||
}
|
||||
|
||||
NonnullRefPtr<GUI::Widget> build_pci_devices_tab()
|
||||
{
|
||||
auto pci_widget = GUI::LazyWidget::construct();
|
||||
|
||||
pci_widget->on_first_show = [](GUI::LazyWidget& self) {
|
||||
self.set_layout<GUI::VerticalBoxLayout>();
|
||||
self.layout()->set_margins({ 4, 4, 4, 4 });
|
||||
auto& pci_table_view = self.add<GUI::TableView>();
|
||||
|
||||
auto db = PCIDB::Database::open();
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> pci_fields;
|
||||
pci_fields.empend(
|
||||
"Address", Gfx::TextAlignment::CenterLeft,
|
||||
[](const JsonObject& object) {
|
||||
auto seg = object.get("seg").to_u32();
|
||||
auto bus = object.get("bus").to_u32();
|
||||
auto slot = object.get("slot").to_u32();
|
||||
auto function = object.get("function").to_u32();
|
||||
return String::formatted("{:04x}:{:02x}:{:02x}.{}", seg, bus, slot, function);
|
||||
});
|
||||
pci_fields.empend(
|
||||
"Class", Gfx::TextAlignment::CenterLeft,
|
||||
[db](const JsonObject& object) {
|
||||
auto class_id = object.get("class").to_u32();
|
||||
String class_name = db->get_class(class_id);
|
||||
return class_name == "" ? String::formatted("{:04x}", class_id) : class_name;
|
||||
});
|
||||
pci_fields.empend(
|
||||
"Vendor", Gfx::TextAlignment::CenterLeft,
|
||||
[db](const JsonObject& object) {
|
||||
auto vendor_id = object.get("vendor_id").to_u32();
|
||||
String vendor_name = db->get_vendor(vendor_id);
|
||||
return vendor_name == "" ? String::formatted("{:02x}", vendor_id) : vendor_name;
|
||||
});
|
||||
pci_fields.empend(
|
||||
"Device", Gfx::TextAlignment::CenterLeft,
|
||||
[db](const JsonObject& object) {
|
||||
auto vendor_id = object.get("vendor_id").to_u32();
|
||||
auto device_id = object.get("device_id").to_u32();
|
||||
String device_name = db->get_device(vendor_id, device_id);
|
||||
return device_name == "" ? String::formatted("{:02x}", device_id) : device_name;
|
||||
});
|
||||
pci_fields.empend(
|
||||
"Revision", Gfx::TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
auto revision_id = object.get("revision_id").to_u32();
|
||||
return String::formatted("{:02x}", revision_id);
|
||||
});
|
||||
|
||||
pci_table_view.set_model(GUI::SortingProxyModel::create(GUI::JsonArrayModel::create("/proc/pci", move(pci_fields))));
|
||||
pci_table_view.model()->update();
|
||||
};
|
||||
|
||||
return pci_widget;
|
||||
}
|
||||
|
||||
NonnullRefPtr<GUI::Widget> build_devices_tab()
|
||||
{
|
||||
auto devices_widget = GUI::LazyWidget::construct();
|
||||
|
||||
devices_widget->on_first_show = [](GUI::LazyWidget& self) {
|
||||
self.set_layout<GUI::VerticalBoxLayout>();
|
||||
self.layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto& devices_table_view = self.add<GUI::TableView>();
|
||||
devices_table_view.set_model(GUI::SortingProxyModel::create(DevicesModel::create()));
|
||||
devices_table_view.model()->update();
|
||||
};
|
||||
|
||||
return devices_widget;
|
||||
}
|
||||
|
||||
NonnullRefPtr<GUI::Widget> build_graphs_tab()
|
||||
{
|
||||
auto graphs_container = GUI::LazyWidget::construct();
|
||||
|
||||
graphs_container->on_first_show = [](GUI::LazyWidget& self) {
|
||||
self.set_fill_with_background_color(true);
|
||||
self.set_background_role(ColorRole::Button);
|
||||
self.set_layout<GUI::VerticalBoxLayout>();
|
||||
self.layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto& cpu_graph_group_box = self.add<GUI::GroupBox>("CPU usage");
|
||||
cpu_graph_group_box.set_layout<GUI::HorizontalBoxLayout>();
|
||||
cpu_graph_group_box.layout()->set_margins({ 6, 16, 6, 6 });
|
||||
cpu_graph_group_box.set_fixed_height(120);
|
||||
Vector<GraphWidget*> cpu_graphs;
|
||||
for (size_t i = 0; i < ProcessModel::the().cpus().size(); i++) {
|
||||
auto& cpu_graph = cpu_graph_group_box.add<GraphWidget>();
|
||||
cpu_graph.set_max(100);
|
||||
cpu_graph.set_background_color(Color::White);
|
||||
cpu_graph.set_value_format(0, {
|
||||
.line_color = Color::Blue,
|
||||
.background_color = Color::from_rgb(0xaaaaff),
|
||||
.text_formatter = [](int value) {
|
||||
return String::formatted("Total: {}%", value);
|
||||
},
|
||||
});
|
||||
cpu_graph.set_value_format(1, {
|
||||
.line_color = Color::Red,
|
||||
.background_color = Color::from_rgb(0xffaaaa),
|
||||
.text_formatter = [](int value) {
|
||||
return String::formatted("Kernel: {}%", value);
|
||||
},
|
||||
});
|
||||
cpu_graphs.append(&cpu_graph);
|
||||
}
|
||||
ProcessModel::the().on_cpu_info_change = [cpu_graphs](const NonnullOwnPtrVector<ProcessModel::CpuInfo>& cpus) {
|
||||
for (size_t i = 0; i < cpus.size(); i++)
|
||||
cpu_graphs[i]->add_value({ (int)cpus[i].total_cpu_percent, (int)cpus[i].total_cpu_percent_kernel });
|
||||
};
|
||||
|
||||
auto& memory_graph_group_box = self.add<GUI::GroupBox>("Memory usage");
|
||||
memory_graph_group_box.set_layout<GUI::VerticalBoxLayout>();
|
||||
memory_graph_group_box.layout()->set_margins({ 6, 16, 6, 6 });
|
||||
memory_graph_group_box.set_fixed_height(120);
|
||||
auto& memory_graph = memory_graph_group_box.add<GraphWidget>();
|
||||
memory_graph.set_background_color(Color::White);
|
||||
memory_graph.set_stack_values(true);
|
||||
memory_graph.set_value_format(0, {
|
||||
.line_color = Color::from_rgb(0x619910),
|
||||
.background_color = Color::from_rgb(0xbbffbb),
|
||||
.text_formatter = [&memory_graph](int value) {
|
||||
return String::formatted("Committed: {} KiB", value);
|
||||
},
|
||||
});
|
||||
memory_graph.set_value_format(1, {
|
||||
.line_color = Color::Blue,
|
||||
.background_color = Color::from_rgb(0xaaaaff),
|
||||
.text_formatter = [&memory_graph](int value) {
|
||||
return String::formatted("Allocated: {} KiB", value);
|
||||
},
|
||||
});
|
||||
memory_graph.set_value_format(2, {
|
||||
.line_color = Color::Red,
|
||||
.background_color = Color::from_rgb(0xffaaaa),
|
||||
.text_formatter = [&memory_graph](int value) {
|
||||
return String::formatted("Kernel heap: {} KiB", value);
|
||||
},
|
||||
});
|
||||
|
||||
self.add<MemoryStatsWidget>(memory_graph);
|
||||
};
|
||||
return graphs_container;
|
||||
}
|
||||
|
||||
NonnullRefPtr<GUI::Widget> build_processors_tab()
|
||||
{
|
||||
auto processors_widget = GUI::LazyWidget::construct();
|
||||
|
||||
processors_widget->on_first_show = [](GUI::LazyWidget& self) {
|
||||
self.set_layout<GUI::VerticalBoxLayout>();
|
||||
self.layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
Vector<GUI::JsonArrayModel::FieldSpec> processors_field;
|
||||
processors_field.empend("processor", "Processor", Gfx::TextAlignment::CenterRight);
|
||||
processors_field.empend("cpuid", "CPUID", Gfx::TextAlignment::CenterLeft);
|
||||
processors_field.empend("brandstr", "Brand", Gfx::TextAlignment::CenterLeft);
|
||||
processors_field.empend("Features", Gfx::TextAlignment::CenterLeft, [](auto& object) {
|
||||
StringBuilder builder;
|
||||
auto features = object.get("features").as_array();
|
||||
for (auto& feature : features.values()) {
|
||||
builder.append(feature.to_string());
|
||||
builder.append(' ');
|
||||
}
|
||||
return GUI::Variant(builder.to_string());
|
||||
});
|
||||
processors_field.empend("family", "Family", Gfx::TextAlignment::CenterRight);
|
||||
processors_field.empend("model", "Model", Gfx::TextAlignment::CenterRight);
|
||||
processors_field.empend("stepping", "Stepping", Gfx::TextAlignment::CenterRight);
|
||||
processors_field.empend("type", "Type", Gfx::TextAlignment::CenterRight);
|
||||
|
||||
auto& processors_table_view = self.add<GUI::TableView>();
|
||||
processors_table_view.set_model(GUI::JsonArrayModel::create("/proc/cpuinfo", move(processors_field)));
|
||||
processors_table_view.model()->update();
|
||||
};
|
||||
|
||||
return processors_widget;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue