Skip to content

Commit

Permalink
fix(graph): handle dynamic interfaces and right convert
Browse files Browse the repository at this point in the history
  • Loading branch information
orblazer committed Mar 30, 2024
1 parent 5a64a73 commit ea00f7a
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 113 deletions.
17 changes: 8 additions & 9 deletions package/contents/ui/code/dialect.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @property {string} name The name
* @property {string} suffix The suffix
* @property {string} kiloChar The char for "kilo"
* @property {number} KiBDiff The difference with `kibibyte`
* @property {number} byteDiff The difference with `byte`
* @property {number} multiplier The multiplier amount (`1000` for metric and `1024` for binary)
*/

Expand All @@ -22,23 +22,23 @@ function getNetworkDialectInfo(dialect, i18nc = (_ = "", def = "") => def) {
name: "kilobyte",
suffix: i18nc("kilobyte suffix", "Bps"),
kiloChar: "k",
KiBDiff: 1.024,
multiplier: 1024,
byteDiff: 1,
multiplier: 1000,
};
case "kilobit":
return {
name: "kilobit",
suffix: i18nc("kilobit suffix", "bps"),
kiloChar: "k",
KiBDiff: 8,
multiplier: 1024,
byteDiff: 8,
multiplier: 1000,
};
default:
return {
name: "kibibyte",
suffix: i18nc("kibibyte suffix", "iB/s"),
kiloChar: "K",
KiBDiff: 1,
byteDiff: 1,
multiplier: 1024,
};
}
Expand All @@ -58,11 +58,10 @@ function formatByteValue(value, dialect, precision = 1) {
} else if (dialect.name === "kibibyte" && value <= dialect.multiplier) {
return "0 " + dialect.suffix.replace("i", "");
}

var sizes = ["", dialect.kiloChar, "M", "G", "T", "P", "Z", "Y"];
const sizes = ["", dialect.kiloChar, "M", "G", "T", "P", "Z", "Y"];

// Search unit conversion
var unit = Math.floor(Math.log(value) / Math.log(dialect.multiplier));
const unit = Math.floor(Math.log(value) / Math.log(dialect.multiplier));

// Bytes/Bits, no rounding
precision = precision < 0 ? 0 : precision;
Expand Down
52 changes: 52 additions & 0 deletions package/contents/ui/code/network.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// .pragma library

/**
* Command retrieve all interface speed with following format (one line per interface and rx/tx in bytes) :
* - `<ifname>,<rx>,<tx>`
*/
const NET_DATA_SOURCE =
"awk -v OFS=, 'NR > 2 { print substr($1, 1, length($1)-1), $2, $10 }' /proc/net/dev";

/**
* @typedef {Object.<string, [number, number]>} TransferData The transfer data with format `{"<ifname>": [rx, tx]}`
*/

/**
*
* @param {string} data The raw transfer data output by {@link NET_DATA_SOURCE}
* @returns {TransferData} The parsed transfer data
*/
function parseTransferData(data) {
const transferData = {};
for (const line of data.trim("\n").split("\n")) {
const [name, rx, tx] = line.split(",");
// Skip loopback interface
if (name === "lo") {
continue;
}
transferData[name] = [rx, tx];
}
return transferData;
}

/**
* Calculate speed data in kilobytes for {@link duration}
* @param {TransferData} prevTransferData The transfer data at X moment (in bytes)
* @param {TransferData} nextTransferData The transfer data at X+{@link duration} moment (in bytes)
* @param {number} duration The duration elapsed between {@link prevTransferData} and {@link nextTransferData}
* @returns {TransferData} The speed data for {@link duration}, returned in kilobytes
*/
function calcSpeedData(prevTransferData, nextTransferData, duration) {
const speedData = {};
for (const key in nextTransferData) {
if (prevTransferData && key in prevTransferData) {
const prev = prevTransferData[key];
const next = nextTransferData[key];
speedData[key] = [
((next[0] - prev[0]) * 1000) / duration,
((next[1] - prev[1]) * 1000) / duration,
];
}
}
return speedData;
}
41 changes: 0 additions & 41 deletions package/contents/ui/components/NetworkInterfaceDetector.qml

This file was deleted.

88 changes: 40 additions & 48 deletions package/contents/ui/components/graph/NetworkGraph.qml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import QtQuick
import org.kde.plasma.plasmoid
import "./base" as RMBaseGraph
import "../" as RMComponents
import "../sensors" as RMSensors
import "../../code/dialect.js" as Dialect

RMBaseGraph.TwoSensorsGraph {
id: root
objectName: "NetworkGraph"
sensorsModel.enabled: false // Disable base sensort due to use custom one
_update: networkSpeed.execute

// Settings
property var ignoredInterfaces: []
Expand All @@ -16,7 +18,7 @@ RMBaseGraph.TwoSensorsGraph {
property int downloadIndex: sensorsType[0] ? 1 : 0
property int uploadIndex: sensorsType[0] ? 0 : 1

// Labels
// Labels config
textContainer {
hints: {
const receiving = i18nc("Graph label", "Receiving");
Expand All @@ -25,73 +27,63 @@ RMBaseGraph.TwoSensorsGraph {
}
}

// Initialized sensors
RMComponents.NetworkInterfaceDetector {
onReady: {
if (typeof count === "undefined") {
return;
// Charts config
realUplimits: [uplimits[0] * dialect.multiplier, uplimits[1] * dialect.multiplier]

// Custom sensor
RMSensors.NetworkSpeed {
id: networkSpeed

function cummulateSpeeds() {
const data = Object.entries(value ?? {});
if (data.length === 0) {
return [undefined, undefined];
}
const sensors = [];
for (let i = 0; i < count; i++) {
const name = getInterfaceName(i);
if (typeof name === 'undefined') {

// Cumulate speeds
let download = 0, upload = 0;
for (const [ifname, speed] of data) {
if (root.ignoredInterfaces.indexOf(ifname) !== -1) {
continue;
}
if (root.ignoredInterfaces.indexOf(name) === -1) {
sensors.push("network/" + name + "/download", "network/" + name + "/upload");
}
download += speed[0];
upload += speed[1];
}
root.sensorsModel.sensors = sensors;
return [download, upload];
}
}

realUplimits: [uplimits[0] * dialect.multiplier, uplimits[1] * dialect.multiplier]

// Override methods, for cummulate sensors and support custom dialect
_update: () => {
// Cummulate sensors by group
let data;
let downloadValue = 0, uploadValue = 0;
for (let i = 0; i < sensorsModel.sensors.length; i++) {
data = sensorsModel.getValue(i);
if (typeof data === "undefined") {
continue;
} else if (data.sensorId.indexOf('/download') !== -1) {
downloadValue += data.value;
} else {
uploadValue += data.value;
onValueChanged: {
let [downloadValue, uploadValue] = cummulateSpeeds();
if (typeof downloadValue === "undefined") {
// Skip first run
return;
}
}

// Apply selected dialect
downloadValue *= dialect.KiBDiff;
uploadValue *= dialect.KiBDiff;
// Apply selected dialect
downloadValue *= dialect.byteDiff;
uploadValue *= dialect.byteDiff;

// Insert datas
_insertChartData(downloadIndex, downloadValue);
_insertChartData(uploadIndex, uploadValue);
// Insert datas
_insertChartData(downloadIndex, downloadValue);
_insertChartData(uploadIndex, uploadValue);

// Update labels
if (textContainer.enabled && textContainer.valueVisible) {
_updateData(downloadIndex, downloadValue);
_updateData(uploadIndex, uploadValue);
// Update labels
if (textContainer.enabled && textContainer.valueVisible) {
_updateData(downloadIndex, downloadValue);
_updateData(uploadIndex, uploadValue);
}
}
}

function _updateData(index, value) {
// Cancel update if first data is not here
if (!sensorsModel.hasIndex(0, 0)) {
return;
}

// Retrieve label need to update
const label = textContainer.getLabel(index);
if (typeof label === "undefined" || !label.enabled) {
return;
}

// Show value on label
label.text = Functions.formatByteValue(value, dialect);
label.text = Dialect.formatByteValue(value, dialect);
label.visible = true;
}
}
47 changes: 47 additions & 0 deletions package/contents/ui/components/sensors/NetworkSpeed.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import QtQuick
import org.kde.plasma.plasma5support as Plasma5Support
import "../../code/network.js" as NetworkUtils

/**
* SRCs:
* - https://invent.kde.org/plasma/plasma5support/-/blob/master/src/declarativeimports/datasource.h
* - https://invent.kde.org/plasma/plasma-workspace/-/blob/master/dataengines/executable/executable.h
* - https://github.com/dfaust/plasma-applet-netspeed-widget
*/
Plasma5Support.DataSource {
id: root
engine: 'executable'

// Format: {"interface":[tx,rx]} (in kilobytes)
property var value: {
}

// Cache for calculate
property real _previousTs: 0
property var _transferData: {
} // Format: {"interface":[tx,rx]} (in bytes)

// Retrieve data
onNewData: (sourceName, data) => {
// run just once (reconnected when update)
connectedSources.length = 0;
if (data['exit code'] > 0) {
print(data.stderr);
} else {
const now = Date.now();
const nextTransferData = NetworkUtils.parseTransferData(data.stdout);
// Skip calculate if is first run
if (root._previousTs !== 0) {
const duration = now - root._previousTs;
value = NetworkUtils.calcSpeedData(root._transferData, nextTransferData, duration);
// root.valueChanged();
}
root._transferData = nextTransferData;
root._previousTs = now;
}
}

function execute() {
root.connectSource(NetworkUtils.NET_DATA_SOURCE);
}
}
Loading

1 comment on commit ea00f7a

@dfaust
Copy link

@dfaust dfaust commented on ea00f7a Mar 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.