summaryrefslogtreecommitdiff
path: root/Applications
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-08-08 20:43:30 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-08-08 20:43:30 +0200
commitd6bce37756273948a59a7bada52643186f9090ee (patch)
tree9de6530d9c8c2059db5f51b06d687e45461f1660 /Applications
parent899366da9d8b61317226ed3003defea7139e7bc8 (diff)
downloadserenity-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/Makefile3
-rw-r--r--Applications/ProcessManager/NetworkAdapterModel.cpp102
-rw-r--r--Applications/ProcessManager/NetworkAdapterModel.h33
-rw-r--r--Applications/ProcessManager/NetworkStatisticsWidget.cpp51
-rw-r--r--Applications/ProcessManager/NetworkStatisticsWidget.h20
-rw-r--r--Applications/ProcessManager/SocketModel.cpp120
-rw-r--r--Applications/ProcessManager/SocketModel.h38
-rw-r--r--Applications/ProcessManager/main.cpp4
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);