From 7f9d0849bd952155461eeada9e5a0fbeb5719bdf Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 27 Nov 2013 13:37:47 -0800 Subject: [PATCH] added more client side voxel stats including lost packets, out of order packets, and average flight time --- interface/src/Application.cpp | 18 ++++ interface/src/Application.h | 2 + interface/src/VoxelPacketProcessor.cpp | 6 ++ interface/src/ui/VoxelStatsDialog.cpp | 113 ++++++++++++++++------- interface/src/ui/VoxelStatsDialog.h | 5 +- libraries/voxels/src/VoxelSceneStats.cpp | 56 +++++++++++ libraries/voxels/src/VoxelSceneStats.h | 21 ++++- 7 files changed, 186 insertions(+), 35 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3fc5cfb0a5..ec3beb7458 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4318,6 +4318,24 @@ void Application::nodeKilled(Node* node) { } } +void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, + sockaddr senderAddress, bool wasStatsPacket) { + + // Attempt to identify the sender from it's address. + Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress); + if (voxelServer) { + QUuid nodeUUID = voxelServer->getUUID(); + + // now that we know the node ID, let's add these stats to the stats for that node... + _voxelSceneStatsLock.lockForWrite(); + if (_voxelServerSceneStats.find(nodeUUID) != _voxelServerSceneStats.end()) { + VoxelSceneStats& stats = _voxelServerSceneStats[nodeUUID]; + stats.trackIncomingVoxelPacket(messageData, messageLength, wasStatsPacket); + } + _voxelSceneStatsLock.unlock(); + } +} + int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress) { // But, also identify the sender, and keep track of the contained jurisdiction root for this server diff --git a/interface/src/Application.h b/interface/src/Application.h index 4695a1bf80..e2d0820ab9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -465,6 +465,8 @@ private: PieMenu _pieMenu; int parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress); + void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, + sockaddr senderAddress, bool wasStatsPacket); NodeToJurisdictionMap _voxelServerJurisdictions; NodeToVoxelSceneStats _voxelServerSceneStats; diff --git a/interface/src/VoxelPacketProcessor.cpp b/interface/src/VoxelPacketProcessor.cpp index 5917e2fb05..011fb7f9ed 100644 --- a/interface/src/VoxelPacketProcessor.cpp +++ b/interface/src/VoxelPacketProcessor.cpp @@ -25,6 +25,8 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char* ssize_t messageLength = packetLength; Application* app = Application::getInstance(); + bool wasStatsPacket = false; + // check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that if (app->_wantToKillLocalVoxels) { @@ -38,6 +40,7 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char* if (packetData[0] == PACKET_TYPE_VOXEL_STATS) { int statsMessageLength = app->parseVoxelStats(packetData, messageLength, senderAddress); + wasStatsPacket = true; if (messageLength > statsMessageLength) { packetData += statsMessageLength; messageLength -= statsMessageLength; @@ -50,6 +53,9 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char* } // fall through to piggyback message if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { + + app->trackIncomingVoxelPacket(packetData, messageLength, senderAddress, wasStatsPacket); + Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress); if (voxelServer && socketMatch(voxelServer->getActiveSocket(), &senderAddress)) { if (packetData[0] == PACKET_TYPE_ENVIRONMENT_DATA) { diff --git a/interface/src/ui/VoxelStatsDialog.cpp b/interface/src/ui/VoxelStatsDialog.cpp index d8b4df62b0..9def2a2326 100644 --- a/interface/src/ui/VoxelStatsDialog.cpp +++ b/interface/src/ui/VoxelStatsDialog.cpp @@ -27,7 +27,7 @@ VoxelStatsDialog::VoxelStatsDialog(QWidget* parent, NodeToVoxelSceneStats* model for (int i = 0; i < MAX_VOXEL_SERVERS; i++) { _voxelServerLables[i] = 0; - _extraServerDetails[i] = false; + _extraServerDetails[i] = LESS; } for (int i = 0; i < MAX_STATS; i++) { @@ -67,16 +67,20 @@ void VoxelStatsDialog::moreless(const QString& link) { QString serverNumberString = linkDetails[SERVER_NUMBER_ITEM]; QString command = linkDetails[COMMAND_ITEM]; int serverNumber = serverNumberString.toInt(); + if (command == "more") { - _extraServerDetails[serverNumber-1] = true; + _extraServerDetails[serverNumber-1] = MORE; + } else if (command == "most") { + _extraServerDetails[serverNumber-1] = MOST; } else { - _extraServerDetails[serverNumber-1] = false; + _extraServerDetails[serverNumber-1] = LESS; } } int VoxelStatsDialog::AddStatItem(const char* caption, unsigned colorRGBA) { char strBuf[64]; + const int STATS_LABEL_WIDTH = 600; _statCount++; // increment our current stat count @@ -98,6 +102,7 @@ int VoxelStatsDialog::AddStatItem(const char* caption, unsigned colorRGBA) { label->setPalette(palette); snprintf(strBuf, sizeof(strBuf), " %s:", caption); _form->addRow(strBuf, label); + label->setFixedWidth(STATS_LABEL_WIDTH); return _statCount; } @@ -213,7 +218,6 @@ void VoxelStatsDialog::paintEvent(QPaintEvent* event) { showAllVoxelServers(); this->QDialog::paintEvent(event); - //this->setFixedSize(this->width(), this->height()); } void VoxelStatsDialog::showAllVoxelServers() { @@ -239,6 +243,8 @@ void VoxelStatsDialog::showAllVoxelServers() { } std::stringstream serverDetails(""); + std::stringstream extraDetails(""); + std::stringstream linkDetails(""); if (nodeList->getNodeActiveSocketOrPing(&(*node))) { serverDetails << "active "; @@ -247,7 +253,6 @@ void VoxelStatsDialog::showAllVoxelServers() { } QUuid nodeUUID = node->getUUID(); - serverDetails << " " << nodeUUID.toString().toLocal8Bit().constData() << " "; NodeToJurisdictionMap& voxelServerJurisdictions = Application::getInstance()->getVoxelServerJurisdictions(); @@ -280,44 +285,86 @@ void VoxelStatsDialog::showAllVoxelServers() { } // jurisdiction // now lookup stats details for this server... - Application::getInstance()->lockVoxelSceneStats(); - NodeToVoxelSceneStats* sceneStats = Application::getInstance()->getVoxelSceneStats(); - if (sceneStats->find(nodeUUID) != sceneStats->end()) { - VoxelSceneStats& stats = sceneStats->at(nodeUUID); + if (_extraServerDetails[serverNumber-1] != LESS) { + Application::getInstance()->lockVoxelSceneStats(); + NodeToVoxelSceneStats* sceneStats = Application::getInstance()->getVoxelSceneStats(); + if (sceneStats->find(nodeUUID) != sceneStats->end()) { + VoxelSceneStats& stats = sceneStats->at(nodeUUID); - QString totalString = locale.toString((uint)stats.getTotalVoxels()); - QString internalString = locale.toString((uint)stats.getTotalInternal()); - QString leavesString = locale.toString((uint)stats.getTotalLeaves()); + switch (_extraServerDetails[serverNumber-1]) { + case MOST: { + extraDetails << "
" ; + + const unsigned long USECS_PER_MSEC = 1000; + float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; + float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; - serverDetails << "
" << "Voxels: " << - totalString.toLocal8Bit().constData() << " total " << - internalString.toLocal8Bit().constData() << " internal " << - leavesString.toLocal8Bit().constData() << " leaves "; + QString lastFullEncodeString = locale.toString(lastFullEncode); + QString lastFullSendString = locale.toString(lastFullSend); + + extraDetails << "
" << "Last Full Scene... " << + "Encode Time: " << lastFullEncodeString.toLocal8Bit().constData() << " ms " << + "Send Time: " << lastFullSendString.toLocal8Bit().constData() << " ms "; - const unsigned long USECS_PER_MSEC = 1000; - float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; - float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; + for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) { + VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i); + VoxelSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); + extraDetails << "
" << itemInfo.caption << " " << stats.getItemValue(item); + } + } // fall through... since MOST has all of MORE + case MORE: { + QString totalString = locale.toString((uint)stats.getTotalVoxels()); + QString internalString = locale.toString((uint)stats.getTotalInternal()); + QString leavesString = locale.toString((uint)stats.getTotalLeaves()); - QString lastFullEncodeString = locale.toString(lastFullEncode); - QString lastFullSendString = locale.toString(lastFullSend); + serverDetails << "
" << "Node UUID: " << + nodeUUID.toString().toLocal8Bit().constData() << " "; - serverDetails << "
" << "Last Full Scene... " << - "Encode Time: " << lastFullEncodeString.toLocal8Bit().constData() << " ms " << - "Send Time: " << lastFullSendString.toLocal8Bit().constData() << " ms "; + serverDetails << "
" << "Voxels: " << + totalString.toLocal8Bit().constData() << " total " << + internalString.toLocal8Bit().constData() << " internal " << + leavesString.toLocal8Bit().constData() << " leaves "; + + QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets()); + QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes()); + QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes()); + QString incomingOutOfOrderString = locale.toString((uint)stats.getIncomingOutOfOrder()); + QString incomingLikelyLostString = locale.toString((uint)stats.getIncomingLikelyLost()); + QString incomingFlightTimeString = locale.toString(stats.getIncomingFlightTimeAverage()); + + serverDetails << "
" << "Incoming Packets: " << + incomingPacketsString.toLocal8Bit().constData() << + " Out of Order: " << incomingOutOfOrderString.toLocal8Bit().constData() << + " Likely Lost: " << incomingLikelyLostString.toLocal8Bit().constData(); + + serverDetails << "
" << + " Average Flight Time: " << incomingFlightTimeString.toLocal8Bit().constData() << " msecs"; + serverDetails << "
" << "Incoming" << + " Bytes: " << incomingBytesString.toLocal8Bit().constData() << + " Wasted Bytes: " << incomingWastedBytesString.toLocal8Bit().constData(); + + serverDetails << extraDetails.str(); + if (_extraServerDetails[serverNumber-1] == MORE) { + linkDetails << " " << " [most...]"; + linkDetails << " " << " [less...]"; + } else { + linkDetails << " " << " [less...]"; + linkDetails << " " << " [least...]"; + } - if (_extraServerDetails[serverNumber-1]) { - for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) { - VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i); - VoxelSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); - serverDetails << "
" << itemInfo.caption << " " << stats.getItemValue(item); + } break; + case LESS: { + // nothing + } break; } - serverDetails << " " << " [less...]"; - } else { - serverDetails << " " << " [more...]"; } + Application::getInstance()->unlockVoxelSceneStats(); + } else { + linkDetails << " " << " [more...]"; + linkDetails << " " << " [most...]"; } - Application::getInstance()->unlockVoxelSceneStats(); + serverDetails << linkDetails.str(); _labels[_voxelServerLables[serverCount - 1]]->setText(serverDetails.str().c_str()); } // is VOXEL_SERVER } // Node Loop diff --git a/interface/src/ui/VoxelStatsDialog.h b/interface/src/ui/VoxelStatsDialog.h index 7e00d4c37b..eb95a87a7e 100644 --- a/interface/src/ui/VoxelStatsDialog.h +++ b/interface/src/ui/VoxelStatsDialog.h @@ -45,6 +45,9 @@ protected: void showAllVoxelServers(); private: + + typedef enum { LESS, MORE, MOST } details; + QFormLayout* _form; QLabel* _labels[MAX_STATS]; NodeToVoxelSceneStats* _model; @@ -57,7 +60,7 @@ private: int _voxelsRendered; int _voxelServerLables[MAX_VOXEL_SERVERS]; int _voxelServerLabelsCount; - bool _extraServerDetails[MAX_VOXEL_SERVERS]; + details _extraServerDetails[MAX_VOXEL_SERVERS]; }; #endif /* defined(__interface__VoxelStatsDialog__) */ diff --git a/libraries/voxels/src/VoxelSceneStats.cpp b/libraries/voxels/src/VoxelSceneStats.cpp index 25d6380bb3..ebda4448b6 100644 --- a/libraries/voxels/src/VoxelSceneStats.cpp +++ b/libraries/voxels/src/VoxelSceneStats.cpp @@ -13,6 +13,7 @@ #include #include +#include "VoxelPacketData.h" #include "VoxelNode.h" #include "VoxelSceneStats.h" @@ -21,6 +22,7 @@ const int samples = 100; VoxelSceneStats::VoxelSceneStats() : _elapsedAverage(samples), _bitsPerVoxelAverage(samples), + _incomingFlightTimeAverage(samples), _jurisdictionRoot(NULL) { reset(); @@ -28,6 +30,13 @@ VoxelSceneStats::VoxelSceneStats() : _isStarted = false; _lastFullTotalEncodeTime = 0; _lastFullElapsed = 0; + _incomingPacket = 0; + _incomingBytes = 0; + _incomingWastedBytes = 0; + _incomingLastSequence = 0; + _incomingOutOfOrder = 0; + _incomingLikelyLost = 0; + } // copy constructor @@ -121,6 +130,13 @@ void VoxelSceneStats::copyFromOther(const VoxelSceneStats& other) { _jurisdictionEndNodes.push_back(endNodeCodeCopy); } } + + _incomingPacket = other._incomingPacket; + _incomingBytes = other._incomingBytes; + _incomingWastedBytes = other._incomingWastedBytes; + _incomingLastSequence = other._incomingLastSequence; + _incomingOutOfOrder = other._incomingOutOfOrder; + _incomingLikelyLost = other._incomingLikelyLost; } @@ -782,4 +798,44 @@ const char* VoxelSceneStats::getItemValue(Item item) { return _itemValueBuffer; } +void VoxelSceneStats::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket) { + _incomingPacket++; + _incomingBytes += messageLength; + if (!wasStatsPacket) { + _incomingWastedBytes += (MAX_PACKET_SIZE - messageLength); + } + + int numBytesPacketHeader = numBytesForPacketHeader(messageData); + unsigned char* dataAt = messageData + numBytesPacketHeader; + + //VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt)); + dataAt += sizeof(VOXEL_PACKET_FLAGS); + VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(VOXEL_PACKET_SEQUENCE); + + VOXEL_PACKET_SENT_TIME sentAt = (*(VOXEL_PACKET_SENT_TIME*)dataAt); + dataAt += sizeof(VOXEL_PACKET_SENT_TIME); + + //bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); + //bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); + + VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); + int flightTime = arrivedAt - sentAt; + const int USECS_PER_MSEC = 1000; + float flightTimeMsecs = flightTime / USECS_PER_MSEC; + _incomingFlightTimeAverage.updateAverage(flightTimeMsecs); + + + // detect out of order packets + if (sequence < _incomingLastSequence) { + _incomingOutOfOrder++; + } + + // detect likely lost packets + if (sequence > (_incomingLastSequence+1)) { + _incomingLikelyLost++; + } + + _incomingLastSequence = sequence; +} diff --git a/libraries/voxels/src/VoxelSceneStats.h b/libraries/voxels/src/VoxelSceneStats.h index e6ec63a6d0..526ccffadb 100644 --- a/libraries/voxels/src/VoxelSceneStats.h +++ b/libraries/voxels/src/VoxelSceneStats.h @@ -151,7 +151,17 @@ public: unsigned long getLastFullTotalEncodeTime() const { return _lastFullTotalEncodeTime; } unsigned long getLastFullElapsedTime() const { return _lastFullElapsed; } - + + // Used in client implementations to track individual voxel packets + void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket); + + unsigned int getIncomingPackets() const { return _incomingPacket; } + unsigned long getIncomingBytes() const { return _incomingBytes; } + unsigned long getIncomingWastedBytes() const { return _incomingWastedBytes; } + unsigned int getIncomingOutOfOrder() const { return _incomingOutOfOrder; } + unsigned int getIncomingLikelyLost() const { return _incomingLikelyLost; } + float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); } + private: void copyFromOther(const VoxelSceneStats& other); @@ -236,6 +246,15 @@ private: unsigned long _bytes; unsigned int _passes; + // incoming packets stats + unsigned int _incomingPacket; + unsigned long _incomingBytes; + unsigned long _incomingWastedBytes; + unsigned int _incomingLastSequence; + unsigned int _incomingOutOfOrder; + unsigned int _incomingLikelyLost; + SimpleMovingAverage _incomingFlightTimeAverage; + // features related items bool _isMoving; bool _isFullScene;