fix: 为所有接口采集速度历史而非仅活动接口
原 calculateSpeed 仅给活动接口追加采样点,切换到非活动接口时趋势图 为空需等待数分钟。新增 m_lastRxBytesByIface/m_lastTxBytesByIface 跟踪所有接口字节基线,遍历全部接口采集速度历史。 切换接口时趋势图立即有数据,新增接口(如USB网卡)也自动开始采集。
This commit is contained in:
@@ -35,8 +35,9 @@
|
|||||||
~~可传入不存在的接口名,会被持久化到配置文件,当前会话期间速度恒为 0。~~
|
~~可传入不存在的接口名,会被持久化到配置文件,当前会话期间速度恒为 0。~~
|
||||||
已在方法入口增加 `m_interfaceList.contains(interface)` 校验,无效接口名直接忽略。
|
已在方法入口增加 `m_interfaceList.contains(interface)` 校验,无效接口名直接忽略。
|
||||||
|
|
||||||
**7. 速度历史仅记录活动接口**
|
**7. ~~速度历史仅记录活动接口~~ ✅ 已修复**
|
||||||
`m_speedHistory` 每秒只给 `m_activeInterface` 追加采样点。非活动接口永不采集,切换到新接口时趋势图为空,需等待数分钟才有数据。若要"切回仍能看到历史",应后台为所有接口持续采样。
|
~~`m_speedHistory` 每秒只给 `m_activeInterface` 追加采样点,非活动接口永不采集,切换到新接口时趋势图为空。~~
|
||||||
|
已新增 `m_lastRxBytesByIface` / `m_lastTxBytesByIface` 跟踪所有接口的字节基线,`calculateSpeed()` 遍历所有接口采集速度历史。切换接口时趋势图立即有数据。
|
||||||
|
|
||||||
**8. 窗口居中未考虑多显示器**
|
**8. 窗口居中未考虑多显示器**
|
||||||
`AboutWindow`、`SettingsWindow`、`TrafficChartWindow` 用 `Screen.width/height` 居中,frameless 窗口在多屏环境下可能出现在非预期屏幕。
|
`AboutWindow`、`SettingsWindow`、`TrafficChartWindow` 用 `Screen.width/height` 居中,frameless 窗口在多屏环境下可能出现在非预期屏幕。
|
||||||
|
|||||||
@@ -303,12 +303,14 @@ void NetworkMonitorApplet::readNetworkStats()
|
|||||||
m_interfaceList = newInterfaces.keys();
|
m_interfaceList = newInterfaces.keys();
|
||||||
m_interfaces = newInterfaces;
|
m_interfaces = newInterfaces;
|
||||||
|
|
||||||
// 清理已消失接口的历史缓冲,避免内存泄漏
|
// 清理已消失接口的历史缓冲和基线数据,避免内存泄漏
|
||||||
// 设计原因:USB 网卡、VPN 隧道等接口可能随时消失,其历史数据不再需要
|
// 设计原因:USB 网卡、VPN 隧道等接口可能随时消失,其历史数据和基线不再需要
|
||||||
const QStringList oldIfaces = m_speedHistory.keys();
|
const QStringList oldIfaces = m_speedHistory.keys();
|
||||||
for (const QString &name : oldIfaces) {
|
for (const QString &name : oldIfaces) {
|
||||||
if (!newInterfaces.contains(name)) {
|
if (!newInterfaces.contains(name)) {
|
||||||
m_speedHistory.remove(name);
|
m_speedHistory.remove(name);
|
||||||
|
m_lastRxBytesByIface.remove(name);
|
||||||
|
m_lastTxBytesByIface.remove(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,61 +363,69 @@ void NetworkMonitorApplet::readNetworkStats()
|
|||||||
|
|
||||||
void NetworkMonitorApplet::calculateSpeed()
|
void NetworkMonitorApplet::calculateSpeed()
|
||||||
{
|
{
|
||||||
qint64 currentRxBytes = getActiveRxBytes();
|
|
||||||
qint64 currentTxBytes = getActiveTxBytes();
|
|
||||||
// 当前采样时间戳(毫秒),用于按真实流逝时间计算速度
|
// 当前采样时间戳(毫秒),用于按真实流逝时间计算速度
|
||||||
const qint64 nowMs = QDateTime::currentMSecsSinceEpoch();
|
const qint64 nowMs = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
const qint64 nowSec = QDateTime::currentSecsSinceEpoch();
|
||||||
if (m_firstUpdate) {
|
|
||||||
m_firstUpdate = false;
|
|
||||||
m_lastRxBytes = currentRxBytes;
|
|
||||||
m_lastTxBytes = currentTxBytes;
|
|
||||||
m_lastTimestampMs = nowMs;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按真实流逝时间计算速度(字节/秒)
|
|
||||||
// 设计原因:原实现假设定时间隔严格 1 秒,直接取字节差作为速度;
|
|
||||||
// 系统负载高时 QTimer 可能延迟触发,实际间隔偏离 1 秒导致速度失真。
|
|
||||||
// 改为除以真实间隔后,无论定时器抖动如何速度都准确。
|
|
||||||
const double elapsedSec = (nowMs - m_lastTimestampMs) / 1000.0;
|
const double elapsedSec = (nowMs - m_lastTimestampMs) / 1000.0;
|
||||||
|
m_lastTimestampMs = nowMs;
|
||||||
|
|
||||||
// 仅当间隔合法时计算速度并累加会话流量,避免除零;间隔为 0 时保持上次值
|
// ---- 活动接口速度计算(用于任务栏显示和总量累加)----
|
||||||
if (elapsedSec > 0) {
|
if (m_firstUpdate) {
|
||||||
|
// 首次更新或接口切换后:仅记录基线,不计算速度
|
||||||
|
m_firstUpdate = false;
|
||||||
|
m_lastRxBytes = getActiveRxBytes();
|
||||||
|
m_lastTxBytes = getActiveTxBytes();
|
||||||
|
} else if (elapsedSec > 0 && !m_activeInterface.isEmpty()) {
|
||||||
|
// 按真实流逝时间计算速度(字节/秒),避免定时器抖动失真
|
||||||
|
const qint64 currentRxBytes = getActiveRxBytes();
|
||||||
|
const qint64 currentTxBytes = getActiveTxBytes();
|
||||||
// 计数器回绕/接口重置时差值可能为负,钳制为 0 避免显示负速度
|
// 计数器回绕/接口重置时差值可能为负,钳制为 0 避免显示负速度
|
||||||
// 触发场景:网卡重启、/proc/net/dev 计数器溢出、USB 网卡拔出重插
|
|
||||||
const qint64 rxDelta = currentRxBytes - m_lastRxBytes;
|
const qint64 rxDelta = currentRxBytes - m_lastRxBytes;
|
||||||
const qint64 txDelta = currentTxBytes - m_lastTxBytes;
|
const qint64 txDelta = currentTxBytes - m_lastTxBytes;
|
||||||
const qint64 rxDeltaClamped = rxDelta > 0 ? rxDelta : 0;
|
const qint64 rxDeltaClamped = rxDelta > 0 ? rxDelta : 0;
|
||||||
const qint64 txDeltaClamped = txDelta > 0 ? txDelta : 0;
|
const qint64 txDeltaClamped = txDelta > 0 ? txDelta : 0;
|
||||||
m_downloadSpeed = rxDeltaClamped / elapsedSec;
|
m_downloadSpeed = rxDeltaClamped / elapsedSec;
|
||||||
m_uploadSpeed = txDeltaClamped / elapsedSec;
|
m_uploadSpeed = txDeltaClamped / elapsedSec;
|
||||||
// 累加会话总量:原实现直接取计数器值,切换网卡/网卡重启时总量跳变;
|
// 累加会话总量:总量 = 本次会话期间所有活动接口的流量总和
|
||||||
// 改为累加增量后,总量 = 本次会话期间所有活动接口的流量总和,不再跳变
|
|
||||||
m_totalDownload += rxDeltaClamped;
|
m_totalDownload += rxDeltaClamped;
|
||||||
m_totalUpload += txDeltaClamped;
|
m_totalUpload += txDeltaClamped;
|
||||||
}
|
|
||||||
|
|
||||||
m_lastRxBytes = currentRxBytes;
|
m_lastRxBytes = currentRxBytes;
|
||||||
m_lastTxBytes = currentTxBytes;
|
m_lastTxBytes = currentTxBytes;
|
||||||
m_lastTimestampMs = nowMs;
|
}
|
||||||
|
|
||||||
|
// ---- 为所有接口采集速度历史(非仅活动接口)----
|
||||||
|
// 设计原因:原仅采集活动接口,切换到非活动接口时趋势图为空需等待数分钟。
|
||||||
|
// 改为遍历所有接口计算各自速度并追加历史,切换接口时趋势图立即有数据。
|
||||||
|
if (elapsedSec > 0) {
|
||||||
|
for (const QString &name : m_interfaceList) {
|
||||||
|
if (!m_interfaces.contains(name)) continue;
|
||||||
|
const NetworkInterface &iface = m_interfaces[name];
|
||||||
|
|
||||||
|
// 接口尚未初始化基线(首次采集或运行中新增的接口),记录基线跳过本次
|
||||||
|
if (!m_lastRxBytesByIface.contains(name)) {
|
||||||
|
m_lastRxBytesByIface[name] = iface.rxBytes;
|
||||||
|
m_lastTxBytesByIface[name] = iface.txBytes;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qint64 iRxDelta = iface.rxBytes - m_lastRxBytesByIface[name];
|
||||||
|
const qint64 iTxDelta = iface.txBytes - m_lastTxBytesByIface[name];
|
||||||
|
|
||||||
// 追加采样点到当前活动接口的环形缓冲
|
|
||||||
// 设计原因:applet 启动即持续采集,用户随时打开趋势图都能看到完整 5 分钟数据
|
|
||||||
if (!m_activeInterface.isEmpty()) {
|
|
||||||
SpeedSample sample;
|
SpeedSample sample;
|
||||||
sample.timestamp = QDateTime::currentSecsSinceEpoch();
|
sample.timestamp = nowSec;
|
||||||
sample.downloadSpeed = m_downloadSpeed;
|
sample.downloadSpeed = (iRxDelta > 0 ? iRxDelta : 0) / elapsedSec;
|
||||||
sample.uploadSpeed = m_uploadSpeed;
|
sample.uploadSpeed = (iTxDelta > 0 ? iTxDelta : 0) / elapsedSec;
|
||||||
m_speedHistory[m_activeInterface].append(sample);
|
m_speedHistory[name].append(sample);
|
||||||
|
|
||||||
// 滑动窗口裁剪:超过 300 点时丢弃最旧的
|
// 滑动窗口裁剪:超过 300 点时丢弃最旧的
|
||||||
// removeFirst 是 O(n),但 300 点每秒一次开销可忽略,无需复杂环形索引
|
QVector<SpeedSample> &samples = m_speedHistory[name];
|
||||||
QVector<SpeedSample> &samples = m_speedHistory[m_activeInterface];
|
|
||||||
while (samples.size() > MAX_HISTORY_SAMPLES) {
|
while (samples.size() > MAX_HISTORY_SAMPLES) {
|
||||||
samples.removeFirst();
|
samples.removeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_lastRxBytesByIface[name] = iface.rxBytes;
|
||||||
|
m_lastTxBytesByIface[name] = iface.txBytes;
|
||||||
|
}
|
||||||
emit speedHistoryChanged();
|
emit speedHistoryChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ private:
|
|||||||
// 当前瞬时速度(字节/秒),用 double 存储以保留按真实间隔除算的小数精度
|
// 当前瞬时速度(字节/秒),用 double 存储以保留按真实间隔除算的小数精度
|
||||||
double m_downloadSpeed;
|
double m_downloadSpeed;
|
||||||
double m_uploadSpeed;
|
double m_uploadSpeed;
|
||||||
|
// 每个接口的上次收发字节数,用于为所有接口(非仅活动接口)计算速度并采集历史
|
||||||
|
// 设计原因:原仅跟踪活动接口,切换到非活动接口时趋势图为空需等待数分钟才有数据
|
||||||
|
QHash<QString, qint64> m_lastRxBytesByIface;
|
||||||
|
QHash<QString, qint64> m_lastTxBytesByIface;
|
||||||
|
|
||||||
// 总量
|
// 总量
|
||||||
qint64 m_totalDownload;
|
qint64 m_totalDownload;
|
||||||
|
|||||||
Reference in New Issue
Block a user