diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-08-08 20:43:30 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-08-08 20:43:30 +0200 |
commit | d6bce37756273948a59a7bada52643186f9090ee (patch) | |
tree | 9de6530d9c8c2059db5f51b06d687e45461f1660 /Applications | |
parent | 899366da9d8b61317226ed3003defea7139e7bc8 (diff) | |
download | serenity-d6bce37756273948a59a7bada52643186f9090ee.zip |
ProcessManager: Add a "Network" tab with live adapter and socket stats
This fetches info from /proc/netadapters and /proc/net_tcp, updating
every second. Very cool. :^)
Diffstat (limited to 'Applications')
-rw-r--r-- | Applications/ProcessManager/Makefile | 3 | ||||
-rw-r--r-- | Applications/ProcessManager/NetworkAdapterModel.cpp | 102 | ||||
-rw-r--r-- | Applications/ProcessManager/NetworkAdapterModel.h | 33 | ||||
-rw-r--r-- | Applications/ProcessManager/NetworkStatisticsWidget.cpp | 51 | ||||
-rw-r--r-- | Applications/ProcessManager/NetworkStatisticsWidget.h | 20 | ||||
-rw-r--r-- | Applications/ProcessManager/SocketModel.cpp | 120 | ||||
-rw-r--r-- | Applications/ProcessManager/SocketModel.h | 38 | ||||
-rw-r--r-- | Applications/ProcessManager/main.cpp | 4 |
8 files changed, 371 insertions, 0 deletions
diff --git a/Applications/ProcessManager/Makefile b/Applications/ProcessManager/Makefile index 56a75d48eb..f8fca1d467 100644 --- a/Applications/ProcessManager/Makefile +++ b/Applications/ProcessManager/Makefile @@ -10,6 +10,9 @@ OBJS = \ ProcessMemoryMapModel.o \ ProcessFileDescriptorMapWidget.o \ ProcessFileDescriptorMapModel.o \ + NetworkStatisticsWidget.o \ + NetworkAdapterModel.o \ + SocketModel.o \ main.o APP = ProcessManager diff --git a/Applications/ProcessManager/NetworkAdapterModel.cpp b/Applications/ProcessManager/NetworkAdapterModel.cpp new file mode 100644 index 0000000000..87cb6a926a --- /dev/null +++ b/Applications/ProcessManager/NetworkAdapterModel.cpp @@ -0,0 +1,102 @@ +#include "NetworkAdapterModel.h" +#include <AK/JsonObject.h> +#include <AK/StringBuilder.h> +#include <LibCore/CFile.h> + +void NetworkAdapterModel::update() +{ + CFile file("/proc/netadapters"); + if (!file.open(CIODevice::ReadOnly)) { + dbg() << "Unable to open " << file.filename(); + return; + } + + auto json = JsonValue::from_string(file.read_all()); + + ASSERT(json.is_array()); + m_netadapters = json.as_array(); + + did_update(); +} + +int NetworkAdapterModel::row_count(const GModelIndex&) const +{ + return m_netadapters.size(); +} + +String NetworkAdapterModel::column_name(int column) const +{ + switch (column) { + case Column::Name: + return "Name"; + case Column::ClassName: + return "Class"; + case Column::MacAddress: + return "MAC"; + case Column::IpAddress: + return "IP"; + case Column::PacketsIn: + return "Pkt In"; + case Column::PacketsOut: + return "Pkt Out"; + case Column::BytesIn: + return "Bytes In"; + case Column::BytesOut: + return "Bytes Out"; + default: + ASSERT_NOT_REACHED(); + } +} + +GModel::ColumnMetadata NetworkAdapterModel::column_metadata(int column) const +{ + switch (column) { + case Column::Name: + return { 32, TextAlignment::CenterLeft }; + case Column::ClassName: + return { 120, TextAlignment::CenterLeft }; + case Column::MacAddress: + return { 90, TextAlignment::CenterLeft }; + case Column::IpAddress: + return { 80, TextAlignment::CenterLeft }; + case Column::PacketsIn: + return { 60, TextAlignment::CenterRight }; + case Column::PacketsOut: + return { 60, TextAlignment::CenterRight }; + case Column::BytesIn: + return { 60, TextAlignment::CenterRight }; + case Column::BytesOut: + return { 60, TextAlignment::CenterRight }; + default: + ASSERT_NOT_REACHED(); + } + return {}; +} + +GVariant NetworkAdapterModel::data(const GModelIndex& index, Role role) const +{ + auto& adapter_object = m_netadapters.at(index.row()).as_object(); + if (role == GModel::Role::Display) { + switch (index.column()) { + case Column::Name: + return adapter_object.get("name").to_string(); + case Column::ClassName: + return adapter_object.get("class_name").to_string(); + case Column::MacAddress: + return adapter_object.get("mac_address").to_string(); + case Column::IpAddress: + return adapter_object.get("ipv4_address").to_string(); + case Column::PacketsIn: + return adapter_object.get("packets_in").to_u32(); + case Column::PacketsOut: + return adapter_object.get("packets_out").to_u32(); + case Column::BytesIn: + return adapter_object.get("bytes_in").to_u32(); + case Column::BytesOut: + return adapter_object.get("bytes_out").to_u32(); + default: + ASSERT_NOT_REACHED(); + } + } + return {}; +} diff --git a/Applications/ProcessManager/NetworkAdapterModel.h b/Applications/ProcessManager/NetworkAdapterModel.h new file mode 100644 index 0000000000..c72de1b28e --- /dev/null +++ b/Applications/ProcessManager/NetworkAdapterModel.h @@ -0,0 +1,33 @@ +#pragma once + +#include <AK/JsonArray.h> +#include <LibGUI/GModel.h> + +class NetworkAdapterModel final : public GModel { +public: + enum Column { + Name, + ClassName, + MacAddress, + IpAddress, + PacketsIn, + PacketsOut, + BytesIn, + BytesOut, + __Count + }; + + static NonnullRefPtr<NetworkAdapterModel> create() { return adopt(*new NetworkAdapterModel); } + virtual ~NetworkAdapterModel() override {} + + virtual int row_count(const GModelIndex& = GModelIndex()) const override; + virtual int column_count(const GModelIndex& = GModelIndex()) const override { return Column::__Count; } + virtual String column_name(int) const override; + virtual ColumnMetadata column_metadata(int) const override; + virtual GVariant data(const GModelIndex&, Role = Role::Display) const override; + virtual void update() override; + +private: + NetworkAdapterModel() {} + JsonArray m_netadapters; +}; diff --git a/Applications/ProcessManager/NetworkStatisticsWidget.cpp b/Applications/ProcessManager/NetworkStatisticsWidget.cpp new file mode 100644 index 0000000000..0a6587f248 --- /dev/null +++ b/Applications/ProcessManager/NetworkStatisticsWidget.cpp @@ -0,0 +1,51 @@ +#include "NetworkStatisticsWidget.h" +#include "NetworkAdapterModel.h" +#include "SocketModel.h" +#include <LibGUI/GBoxLayout.h> +#include <LibGUI/GGroupBox.h> +#include <LibGUI/GTableView.h> + +NetworkStatisticsWidget::NetworkStatisticsWidget(GWidget* parent) + : GWidget(parent) +{ + set_layout(make<GBoxLayout>(Orientation::Vertical)); + layout()->set_margins({ 4, 4, 4, 4 }); + set_fill_with_background_color(true); + set_background_color(Color::WarmGray); + + auto* adapters_group_box = new GGroupBox("Adapters", this); + adapters_group_box->set_layout(make<GBoxLayout>(Orientation::Vertical)); + adapters_group_box->layout()->set_margins({ 6, 16, 6, 6 }); + adapters_group_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); + adapters_group_box->set_preferred_size(0, 120); + + m_adapter_table_view = new GTableView(adapters_group_box); + m_adapter_table_view->set_model(NetworkAdapterModel::create()); + + auto* sockets_group_box = new GGroupBox("Sockets", this); + sockets_group_box->set_layout(make<GBoxLayout>(Orientation::Vertical)); + sockets_group_box->layout()->set_margins({ 6, 16, 6, 6 }); + sockets_group_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fill); + sockets_group_box->set_preferred_size(0, 0); + + m_socket_table_view = new GTableView(sockets_group_box); + m_socket_table_view->set_model(SocketModel::create()); + + m_update_timer = new CTimer( + 1000, [this] { + update_models(); + }, + this); + + update_models(); +} + +NetworkStatisticsWidget::~NetworkStatisticsWidget() +{ +} + +void NetworkStatisticsWidget::update_models() +{ + m_adapter_table_view->model()->update(); + m_socket_table_view->model()->update(); +} diff --git a/Applications/ProcessManager/NetworkStatisticsWidget.h b/Applications/ProcessManager/NetworkStatisticsWidget.h new file mode 100644 index 0000000000..502474bee1 --- /dev/null +++ b/Applications/ProcessManager/NetworkStatisticsWidget.h @@ -0,0 +1,20 @@ +#pragma once + +#include <LibCore/CTimer.h> +#include <LibGUI/GWidget.h> + +class GTableView; + +class NetworkStatisticsWidget final : public GWidget { + C_OBJECT(NetworkStatisticsWidget) +public: + explicit NetworkStatisticsWidget(GWidget* parent = nullptr); + virtual ~NetworkStatisticsWidget() override; + +private: + void update_models(); + + GTableView* m_adapter_table_view { nullptr }; + GTableView* m_socket_table_view { nullptr }; + CTimer* m_update_timer { nullptr }; +}; diff --git a/Applications/ProcessManager/SocketModel.cpp b/Applications/ProcessManager/SocketModel.cpp new file mode 100644 index 0000000000..bec130ab1b --- /dev/null +++ b/Applications/ProcessManager/SocketModel.cpp @@ -0,0 +1,120 @@ +#include "SocketModel.h" +#include <AK/JsonObject.h> +#include <AK/StringBuilder.h> +#include <LibCore/CFile.h> + +void SocketModel::update() +{ + CFile file("/proc/net_tcp"); + if (!file.open(CIODevice::ReadOnly)) { + dbg() << "Unable to open " << file.filename(); + return; + } + + auto json = JsonValue::from_string(file.read_all()); + + ASSERT(json.is_array()); + m_sockets = json.as_array(); + + did_update(); +} + +int SocketModel::row_count(const GModelIndex&) const +{ + return m_sockets.size(); +} + +String SocketModel::column_name(int column) const +{ + switch (column) { + case Column::PeerAddress: + return "Peer"; + case Column::PeerPort: + return "Port"; + case Column::LocalAddress: + return "Local"; + case Column::LocalPort: + return "Port"; + case Column::State: + return "State"; + case Column::SeqNumber: + return "Seq#"; + case Column::AckNumber: + return "Ack#"; + case Column::PacketsIn: + return "Pkt In"; + case Column::PacketsOut: + return "Pkt Out"; + case Column::BytesIn: + return "Bytes In"; + case Column::BytesOut: + return "Bytes Out"; + default: + ASSERT_NOT_REACHED(); + } +} + +GModel::ColumnMetadata SocketModel::column_metadata(int column) const +{ + switch (column) { + case Column::PeerAddress: + return { 80, TextAlignment::CenterLeft }; + case Column::PeerPort: + return { 30, TextAlignment::CenterRight }; + case Column::LocalAddress: + return { 80, TextAlignment::CenterLeft }; + case Column::LocalPort: + return { 30, TextAlignment::CenterRight }; + case Column::State: + return { 80, TextAlignment::CenterLeft }; + case Column::AckNumber: + return { 60, TextAlignment::CenterRight }; + case Column::SeqNumber: + return { 60, TextAlignment::CenterRight }; + case Column::PacketsIn: + return { 60, TextAlignment::CenterRight }; + case Column::PacketsOut: + return { 60, TextAlignment::CenterRight }; + case Column::BytesIn: + return { 60, TextAlignment::CenterRight }; + case Column::BytesOut: + return { 60, TextAlignment::CenterRight }; + default: + ASSERT_NOT_REACHED(); + } + return {}; +} + +GVariant SocketModel::data(const GModelIndex& index, Role role) const +{ + auto& socket_object = m_sockets.at(index.row()).as_object(); + if (role == GModel::Role::Display) { + switch (index.column()) { + case Column::PeerAddress: + return socket_object.get("peer_address").to_string(); + case Column::PeerPort: + return socket_object.get("peer_port").to_u32(); + case Column::LocalAddress: + return socket_object.get("local_address").to_string(); + case Column::LocalPort: + return socket_object.get("local_port").to_u32(); + case Column::State: + return socket_object.get("state").to_string(); + case Column::AckNumber: + return socket_object.get("ack_number").to_u32(); + case Column::SeqNumber: + return socket_object.get("sequence_number").to_u32(); + case Column::PacketsIn: + return socket_object.get("packets_in").to_u32(); + case Column::PacketsOut: + return socket_object.get("packets_out").to_u32(); + case Column::BytesIn: + return socket_object.get("bytes_in").to_u32(); + case Column::BytesOut: + return socket_object.get("bytes_out").to_u32(); + default: + ASSERT_NOT_REACHED(); + } + } + return {}; +} diff --git a/Applications/ProcessManager/SocketModel.h b/Applications/ProcessManager/SocketModel.h new file mode 100644 index 0000000000..81934ac443 --- /dev/null +++ b/Applications/ProcessManager/SocketModel.h @@ -0,0 +1,38 @@ +#pragma once + +#include <AK/JsonArray.h> +#include <LibGUI/GModel.h> + +class SocketModel final : public GModel { +public: + enum Column { + PeerAddress, + PeerPort, + LocalAddress, + LocalPort, + State, + AckNumber, + SeqNumber, + PacketsIn, + PacketsOut, + BytesIn, + BytesOut, + __Count + }; + + static NonnullRefPtr<SocketModel> create() { return adopt(*new SocketModel); } + + virtual ~SocketModel() override {} + + virtual int row_count(const GModelIndex& = GModelIndex()) const override; + virtual int column_count(const GModelIndex& = GModelIndex()) const override { return Column::__Count; } + virtual String column_name(int) const override; + virtual ColumnMetadata column_metadata(int) const override; + virtual GVariant data(const GModelIndex&, Role = Role::Display) const override; + virtual void update() override; + +private: + SocketModel() {} + + JsonArray m_sockets; +}; diff --git a/Applications/ProcessManager/main.cpp b/Applications/ProcessManager/main.cpp index 080683d493..7d15ff37be 100644 --- a/Applications/ProcessManager/main.cpp +++ b/Applications/ProcessManager/main.cpp @@ -4,6 +4,7 @@ #include "ProcessMemoryMapWidget.h" #include "ProcessStacksWidget.h" #include "ProcessTableView.h" +#include "NetworkStatisticsWidget.h" #include <LibCore/CTimer.h> #include <LibDraw/PNGLoader.h> #include <LibGUI/GAction.h> @@ -71,6 +72,9 @@ int main(int argc, char** argv) tabwidget->add_widget("Graphs", graphs_container); + auto* network_stats_widget = new NetworkStatisticsWidget(nullptr); + tabwidget->add_widget("Network", network_stats_widget); + process_table_container->set_layout(make<GBoxLayout>(Orientation::Vertical)); process_table_container->layout()->set_margins({ 4, 0, 4, 4 }); process_table_container->layout()->set_spacing(0); |