Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT(server, client): Add rolling average for packet statistics #6683

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions auxiliary_files/mumble-server.ini
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,12 @@ allowping=true
;
; allowRecording=true

; The time frame in seconds the server will keep rolling packet stats for each client.
; The default is 5 minutes = 300 seconds. Minimum window is 10 seconds. Set to 0 to
; disable rolling packet stats. This option has been introduced with 1.6.0
;
; rollingStatsWindow=300

; The amount of allowed listener proxies in a single channel. It defaults to -1
; meaning that there is no limit. Set to 0 to disable Channel Listeners altogether.
; This option has been introduced with 1.4.0.
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ set(SHARED_SOURCES

"crypto/CryptographicHash.cpp"
"crypto/CryptographicRandom.cpp"
"crypto/CryptState.cpp"
"crypto/CryptStateOCB2.cpp"

"${3RDPARTY_DIR}/arc4random/arc4random_uniform.cpp"
Expand Down
12 changes: 12 additions & 0 deletions src/Mumble.proto
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,15 @@ message UserStats {
optional uint32 resync = 4;
}

message RollingStats {
// Rolling packet statistics time window as defined on the server.
optional uint32 time_window = 1;
// Rolling packet statistics for packets received from the client.
optional Stats from_client = 2;
// Rolling packet statistics for packets sent by the server.
optional Stats from_server = 3;
}

// User whose stats these are.
optional uint32 session = 1;
// True if the message contains only mutable stats (packets, ping).
Expand Down Expand Up @@ -553,6 +562,9 @@ message UserStats {
// True if the user has a strong certificate.
optional bool strong_certificate = 18 [default = false];
optional bool opus = 19 [default = false];

// Rolling packet statistics
optional RollingStats rolling_stats = 20;
}

// Used by the client to request binary data from the server. By default large
Expand Down
53 changes: 53 additions & 0 deletions src/crypto/CryptState.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "CryptState.h"

void CryptState::updateRollingStats() {
if (!m_rollingStatsEnabled) {
return;
}

std::chrono::time_point< std::chrono::steady_clock > now = std::chrono::steady_clock::now();

// Update no more than every few seconds
if ((now - m_rollingLastSampleTime) < m_rollingScanInterval) {
return;
}

m_rollingLastSampleTime = now;

PacketStatsSnapshot snapshotLocal;
snapshotLocal.stats = m_statsLocal;
snapshotLocal.timestamp = now;
m_statsLocalReference.push(snapshotLocal);

PacketStatsSnapshot snapshotRemote;
snapshotRemote.stats = m_statsRemote;
snapshotRemote.timestamp = now;
m_statsRemoteReference.push(snapshotRemote);

while (!m_statsLocalReference.empty() && (now - m_statsLocalReference.front().timestamp) > m_rollingWindow) {
m_statsLocalReference.pop();
}

while (!m_statsRemoteReference.empty() && (now - m_statsRemoteReference.front().timestamp) > m_rollingWindow) {
m_statsRemoteReference.pop();
}

if (!m_statsLocalReference.empty()) {
m_statsLocalRolling.good = m_statsLocal.good - m_statsLocalReference.front().stats.good;
m_statsLocalRolling.late = m_statsLocal.late - m_statsLocalReference.front().stats.late;
m_statsLocalRolling.lost = m_statsLocal.lost - m_statsLocalReference.front().stats.lost;
m_statsLocalRolling.resync = m_statsLocal.resync - m_statsLocalReference.front().stats.resync;
}

if (!m_statsRemoteReference.empty()) {
m_statsRemoteRolling.good = m_statsRemote.good - m_statsRemoteReference.front().stats.good;
m_statsRemoteRolling.late = m_statsRemote.late - m_statsRemoteReference.front().stats.late;
m_statsRemoteRolling.lost = m_statsRemote.lost - m_statsRemoteReference.front().stats.lost;
m_statsRemoteRolling.resync = m_statsRemote.resync - m_statsRemoteReference.front().stats.resync;
}
}
41 changes: 32 additions & 9 deletions src/crypto/CryptState.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,44 @@
#define MUMBLE_CRYPTSTATE_H_

#include "Timer.h"
#include <chrono>
#include <queue>
#include <string>

struct PacketStats {
unsigned int good = 0;
unsigned int late = 0;
unsigned int lost = 0;
unsigned int resync = 0;
};

struct PacketStatsSnapshot {
PacketStats stats;
std::chrono::time_point< std::chrono::steady_clock > timestamp;
};

class CryptState {
private:
Q_DISABLE_COPY(CryptState)

const std::chrono::seconds m_rollingScanInterval = std::chrono::seconds(5);
std::chrono::time_point< std::chrono::steady_clock > m_rollingLastSampleTime = {};

std::queue< PacketStatsSnapshot > m_statsLocalReference;
std::queue< PacketStatsSnapshot > m_statsRemoteReference;
Krzmbrzl marked this conversation as resolved.
Show resolved Hide resolved

protected:
void updateRollingStats();

public:
unsigned int uiGood = 0;
unsigned int uiLate = 0;
unsigned int uiLost = 0;
unsigned int uiResync = 0;

unsigned int uiRemoteGood = 0;
unsigned int uiRemoteLate = 0;
unsigned int uiRemoteLost = 0;
unsigned int uiRemoteResync = 0;
PacketStats m_statsLocal = {};
PacketStats m_statsRemote = {};
PacketStats m_statsLocalRolling = {};
PacketStats m_statsRemoteRolling = {};

bool m_rollingStatsEnabled = false;
/// This is the packet statistics sliding time window size in seconds
std::chrono::duration< unsigned int, std::ratio< 1 > > m_rollingWindow = std::chrono::minutes(5);

Timer tLastGood;
Timer tLastRequest;
Expand Down
19 changes: 10 additions & 9 deletions src/crypto/CryptStateOCB2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,20 +206,21 @@ bool CryptStateOCB2::decrypt(const unsigned char *source, unsigned char *dst, un
if (restore)
memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE);

uiGood++;
// uiLate += late, but we have to make sure we don't cause wrap-arounds on the unsigned lhs
m_statsLocal.good++;
// m_statsLocal.late += late, but we have to make sure we don't cause wrap-arounds on the unsigned lhs
if (late > 0) {
uiLate += static_cast< unsigned int >(late);
} else if (static_cast< int >(uiLate) > std::abs(late)) {
uiLate -= static_cast< unsigned int >(std::abs(late));
m_statsLocal.late += static_cast< unsigned int >(late);
} else if (static_cast< int >(m_statsLocal.late) > std::abs(late)) {
m_statsLocal.late -= static_cast< unsigned int >(std::abs(late));
}
// uiLost += lost, but we have to make sure we don't cause wrap-arounds on the unsigned lhs
// m_statsLocal.lost += lost, but we have to make sure we don't cause wrap-arounds on the unsigned lhs
if (lost > 0) {
uiLost += static_cast< unsigned int >(lost);
} else if (static_cast< int >(uiLost) > std::abs(lost)) {
uiLost -= static_cast< unsigned int >(std::abs(lost));
m_statsLocal.lost += static_cast< unsigned int >(lost);
} else if (static_cast< int >(m_statsLocal.lost) > std::abs(lost)) {
m_statsLocal.lost -= static_cast< unsigned int >(std::abs(lost));
}

updateRollingStats();
tLastGood.restart();
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/mumble/Messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@ void MainWindow::msgCryptSetup(const MumbleProto::CryptSetup &msg) {
} else if (msg.has_server_nonce()) {
const std::string &server_nonce = msg.server_nonce();
if (server_nonce.size() == AES_BLOCK_SIZE) {
c->csCrypt->uiResync++;
c->csCrypt->m_statsLocal.resync++;
if (!c->csCrypt->setDecryptIV(server_nonce)) {
qWarning("Messages: Cipher resync failed: Invalid nonce from the server!");
}
Expand Down
27 changes: 14 additions & 13 deletions src/mumble/ServerHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,10 +591,10 @@ void ServerHandler::sendPingInternal() {
MumbleProto::Ping mpp;

mpp.set_timestamp(t);
mpp.set_good(connection->csCrypt->uiGood);
mpp.set_late(connection->csCrypt->uiLate);
mpp.set_lost(connection->csCrypt->uiLost);
mpp.set_resync(connection->csCrypt->uiResync);
mpp.set_good(connection->csCrypt->m_statsLocal.good);
mpp.set_late(connection->csCrypt->m_statsLocal.late);
mpp.set_lost(connection->csCrypt->m_statsLocal.lost);
mpp.set_resync(connection->csCrypt->m_statsLocal.resync);


if (boost::accumulators::count(accUDP)) {
Expand Down Expand Up @@ -639,20 +639,20 @@ void ServerHandler::message(Mumble::Protocol::TCPMessageType type, const QByteAr
// connection is still OK.
iInFlightTCPPings = 0;

connection->csCrypt->uiRemoteGood = msg.good();
connection->csCrypt->uiRemoteLate = msg.late();
connection->csCrypt->uiRemoteLost = msg.lost();
connection->csCrypt->uiRemoteResync = msg.resync();
connection->csCrypt->m_statsRemote.good = msg.good();
connection->csCrypt->m_statsRemote.late = msg.late();
connection->csCrypt->m_statsRemote.lost = msg.lost();
connection->csCrypt->m_statsRemote.resync = msg.resync();
accTCP(static_cast< double >(tTimestamp.elapsed() - msg.timestamp()) / 1000.0);

if (((connection->csCrypt->uiRemoteGood == 0) || (connection->csCrypt->uiGood == 0)) && bUdp
&& (tTimestamp.elapsed() > 20000000ULL)) {
if (((connection->csCrypt->m_statsRemote.good == 0) || (connection->csCrypt->m_statsLocal.good == 0))
&& bUdp && (tTimestamp.elapsed() > 20000000ULL)) {
bUdp = false;
if (!NetworkConfig::TcpModeEnabled()) {
if ((connection->csCrypt->uiRemoteGood == 0) && (connection->csCrypt->uiGood == 0))
if ((connection->csCrypt->m_statsRemote.good == 0) && (connection->csCrypt->m_statsLocal.good == 0))
Global::get().mw->msgBox(
tr("UDP packets cannot be sent to or received from the server. Switching to TCP mode."));
else if (connection->csCrypt->uiRemoteGood == 0)
else if (connection->csCrypt->m_statsRemote.good == 0)
Global::get().mw->msgBox(
tr("UDP packets cannot be sent to the server. Switching to TCP mode."));
else
Expand All @@ -661,7 +661,8 @@ void ServerHandler::message(Mumble::Protocol::TCPMessageType type, const QByteAr

database->setUdp(qbaDigest, false);
}
} else if (!bUdp && (connection->csCrypt->uiRemoteGood > 3) && (connection->csCrypt->uiGood > 3)) {
} else if (!bUdp && (connection->csCrypt->m_statsRemote.good > 3)
&& (connection->csCrypt->m_statsLocal.good > 3)) {
bUdp = true;
if (!NetworkConfig::TcpModeEnabled()) {
Global::get().mw->msgBox(
Expand Down
16 changes: 8 additions & 8 deletions src/mumble/ServerInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,14 @@ void ServerInformation::populateUDPStatistics(const Connection &connection) {
constexpr int LOST_ROW = 2;
constexpr int RESYNC_ROW = 3;

QTableWidgetItem *toGoodItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteGood));
QTableWidgetItem *fromGoodItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiGood));
QTableWidgetItem *toLateItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteLate));
QTableWidgetItem *fromLateItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiLate));
QTableWidgetItem *toLostItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteLost));
QTableWidgetItem *fromLostItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiLost));
QTableWidgetItem *toResyncItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteResync));
QTableWidgetItem *fromResyncItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiResync));
QTableWidgetItem *toGoodItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsRemote.good));
QTableWidgetItem *fromGoodItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsLocal.good));
QTableWidgetItem *toLateItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsRemote.late));
QTableWidgetItem *fromLateItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsLocal.late));
QTableWidgetItem *toLostItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsRemote.lost));
QTableWidgetItem *fromLostItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsLocal.lost));
QTableWidgetItem *toResyncItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsRemote.resync));
QTableWidgetItem *fromResyncItem = new QTableWidgetItem(QString::number(connection.csCrypt->m_statsLocal.resync));

connection_udp_statisticsTable->setItem(GOOD_ROW, TO_SERVER_COL, toGoodItem);
connection_udp_statisticsTable->setItem(GOOD_ROW, FROM_SERVER_COL, fromGoodItem);
Expand Down
70 changes: 62 additions & 8 deletions src/mumble/UserInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,12 @@ void UserInformation::update(const MumbleProto::UserStats &msg) {
qlTCPVar->setText(QString::number(msg.tcp_ping_var() > 0.0f ? sqrtf(msg.tcp_ping_var()) : 0.0f, 'f', 2));
qlUDPVar->setText(QString::number(msg.udp_ping_var() > 0.0f ? sqrtf(msg.udp_ping_var()) : 0.0f, 'f', 2));

if (msg.has_from_client() && msg.has_from_server()) {
qgbUDP->setVisible(true);
bool hasTotalStats = msg.has_from_client() && msg.has_from_server();
bool hasRollingStats = msg.has_rolling_stats();

qgbUDP->setVisible(hasTotalStats || hasRollingStats);

if (hasTotalStats) {
const MumbleProto::UserStats_Stats &from = msg.from_client();
qlFromGood->setText(QString::number(from.good()));
qlFromLate->setText(QString::number(from.late()));
Expand All @@ -179,17 +183,67 @@ void UserInformation::update(const MumbleProto::UserStats &msg) {

quint32 allFromPackets = from.good() + from.late() + from.lost();
qlFromLatePercent->setText(
QString::number(allFromPackets > 0 ? from.late() * 100.0 / allFromPackets : 0., 'f', 2));
QString::number(allFromPackets > 0 ? from.late() * 100.0 / allFromPackets : 0., 'f', 1));
qlFromLostPercent->setText(
QString::number(allFromPackets > 0 ? from.lost() * 100.0 / allFromPackets : 0., 'f', 2));
QString::number(allFromPackets > 0 ? from.lost() * 100.0 / allFromPackets : 0., 'f', 1));

quint32 allToPackets = to.good() + to.late() + to.lost();
qlToLatePercent->setText(QString::number(allToPackets > 0 ? to.late() * 100.0 / allToPackets : 0., 'f', 2));
qlToLostPercent->setText(QString::number(allToPackets > 0 ? to.lost() * 100.0 / allToPackets : 0., 'f', 2));
} else {
qgbUDP->setVisible(false);
qlToLatePercent->setText(QString::number(allToPackets > 0 ? to.late() * 100.0 / allToPackets : 0., 'f', 1));
qlToLostPercent->setText(QString::number(allToPackets > 0 ? to.lost() * 100.0 / allToPackets : 0., 'f', 1));
}

if (hasRollingStats) {
const MumbleProto::UserStats_RollingStats &rolling = msg.rolling_stats();

const MumbleProto::UserStats_Stats &from = rolling.from_client();
qlFromGoodRolling->setText(QString::number(from.good()));
qlFromLateRolling->setText(QString::number(from.late()));
qlFromLostRolling->setText(QString::number(from.lost()));
qlFromResyncRolling->setText(QString::number(from.resync()));

const MumbleProto::UserStats_Stats &to = rolling.from_server();
qlToGoodRolling->setText(QString::number(to.good()));
qlToLateRolling->setText(QString::number(to.late()));
qlToLostRolling->setText(QString::number(to.lost()));
qlToResyncRolling->setText(QString::number(to.resync()));

quint32 allFromPackets = from.good() + from.late() + from.lost();
qlFromLatePercentRolling->setText(
QString::number(allFromPackets > 0 ? from.late() * 100.0 / allFromPackets : 0., 'f', 1));
qlFromLostPercentRolling->setText(
QString::number(allFromPackets > 0 ? from.lost() * 100.0 / allFromPackets : 0., 'f', 1));

quint32 allToPackets = to.good() + to.late() + to.lost();
qlToLatePercentRolling->setText(
QString::number(allToPackets > 0 ? to.late() * 100.0 / allToPackets : 0., 'f', 1));
qlToLostPercentRolling->setText(
QString::number(allToPackets > 0 ? to.lost() * 100.0 / allToPackets : 0., 'f', 1));

uint32_t rollingSeconds = rolling.time_window();
QString rollingText = tr("Last %1 %2:");
if (rollingSeconds < 120) {
qliRolling->setText(rollingText.arg(QString::number(rollingSeconds)).arg(tr("seconds")));
} else {
qliRolling->setText(rollingText.arg(QString::number(rollingSeconds / 60)).arg(tr("minutes")));
}
}

qlFromGoodRolling->setVisible(hasRollingStats);
qlFromLateRolling->setVisible(hasRollingStats);
qlFromLostRolling->setVisible(hasRollingStats);
qlFromResyncRolling->setVisible(hasRollingStats);
qlToGoodRolling->setVisible(hasRollingStats);
qlToLateRolling->setVisible(hasRollingStats);
qlToLostRolling->setVisible(hasRollingStats);
qlToResyncRolling->setVisible(hasRollingStats);
qlFromLatePercentRolling->setVisible(hasRollingStats);
qlFromLostPercentRolling->setVisible(hasRollingStats);
qlToLatePercentRolling->setVisible(hasRollingStats);
qlToLostPercentRolling->setVisible(hasRollingStats);
qliRolling->setVisible(hasRollingStats);
qliRollingFrom->setVisible(hasRollingStats);
qliRollingTo->setVisible(hasRollingStats);

if (msg.has_onlinesecs()) {
if (msg.has_idlesecs())
qlTime->setText(
Expand Down
Loading
Loading