diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml new file mode 100644 index 0000000000..21f63dd070 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -0,0 +1,312 @@ +// +// TabletEntityStatistics.qml +// +// Created by Vlad Stelmahovsky on 3/11/17 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../styles-uit" +import "../../controls-uit" as HifiControls +import "../../windows" + +Rectangle { + id: root + objectName: "EntityStatistics" + + property var eventBridge; + signal sendToScript(var message); + property bool isHMD: false + + color: hifi.colors.baseGray + + property int colorScheme: hifi.colorSchemes.dark + + HifiConstants { id: hifi } + + Component.onCompleted: { + OctreeStats.startUpdates() + } + + Component.onDestruction: { + OctreeStats.stopUpdates() + } + + Flickable { + id: scrollView + width: parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.bottomMargin: hifi.dimensions.tabletMenuHeader + contentWidth: column.implicitWidth + contentHeight: column.implicitHeight + + Column { + id: column + anchors.margins: 10 + anchors.left: parent.left + anchors.right: parent.right + y: hifi.dimensions.tabletMenuHeader //-bgNavBar + spacing: 20 + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Elements on Servers:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: elementsOnServerLabel + size: 20 + text: OctreeStats.serverElements + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Local Elements:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: localElementsLabel + size: 20 + text: OctreeStats.localElements + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Elements Memory:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: elementsMemoryLabel + size: 20 + text: OctreeStats.localElementsMemory + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Sending Mode:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: sendingModeLabel + size: 20 + text: OctreeStats.sendingMode + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Incoming Entity Packets:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: incomingEntityPacketsLabel + size: 20 + text: OctreeStats.processedPackets + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Processed Packets Elements:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: processedPacketsElementsLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Processed Packets Entities:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: processedPacketsEntitiesLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Processed Packets Timing:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: processedPacketsTimingLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Outbound Entity Packets:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: outboundEntityPacketsLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Entity Update Time:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: entityUpdateTimeLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + HifiControls.Label { + size: 20 + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Entity Updates:") + colorScheme: root.colorScheme + } + HifiControls.Label { + id: entityUpdatesLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + Repeater { + model: OctreeStats.serversNum + + delegate: Column { + id: serverColumn + width: scrollView.width - 10 + x: 5 + spacing: 5 + + state: "less" + + HifiControls.Label { + size: 20 + width: parent.width + text: qsTr("Entity Server ") + (index+1) + ":" + colorScheme: root.colorScheme + } + HifiControls.Label { + id: entityServer1Label + size: 20 + width: parent.width + colorScheme: root.colorScheme + } + Row { + id: buttonsRow + width: parent.width + height: 30 + spacing: 10 + + HifiControls.Button { + id: moreButton + color: hifi.buttons.blue + colorScheme: root.colorScheme + width: parent.width / 2 - 10 + height: 30 + onClicked: { + if (serverColumn.state === "less") { + serverColumn.state = "more" + } else if (serverColumn.state === "more") { + serverColumn.state = "most" + } else { + serverColumn.state = "less" + } + } + } + HifiControls.Button { + id: mostButton + color: hifi.buttons.blue + colorScheme: root.colorScheme + height: 30 + width: parent.width / 2 - 10 + onClicked: { + if (serverColumn.state === "less") { + serverColumn.state = "most" + } else if (serverColumn.state === "more") { + serverColumn.state = "less" + } else { + serverColumn.state = "more" + } + } + + } + } + states: [ + State { + name: "less" + PropertyChanges { target: moreButton; text: qsTr("more..."); } + PropertyChanges { target: mostButton; text: qsTr("most..."); } + }, + State { + name: "more" + PropertyChanges { target: moreButton; text: qsTr("most..."); } + PropertyChanges { target: mostButton; text: qsTr("less..."); } + }, + State { + name: "most" + PropertyChanges { target: moreButton; text: qsTr("less..."); } + PropertyChanges { target: mostButton; text: qsTr("least..."); } + } + ] + } //servers column + } //repeater + } //column + } //flickable +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dcd47979f1..5faf2b8ff0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -173,6 +173,7 @@ #include "ui/overlays/Overlays.h" #include "Util.h" #include "InterfaceParentFinder.h" +#include "ui/OctreeStatsProvider.h" #include "FrameTimingsScriptingInterface.h" #include @@ -523,6 +524,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); return previousSessionCrashed; } @@ -1803,6 +1805,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); ResourceManager::cleanup(); @@ -6363,6 +6366,18 @@ void Application::loadScriptURLDialog() const { } } + +void Application::loadEntityStatisticsDialog() { + auto tabletScriptingInterface = DependencyManager::get(); + auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !isHMDMode())) { + auto dialogsManager = DependencyManager::get(); + dialogsManager->octreeStatsDetails(); + } else { + tablet->pushOntoStack("../../hifi/dialogs/TabletEntityStatistics.qml"); + } + +} void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(nullptr, getLogger()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 9fab4aef81..e4715ac41f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -403,6 +403,7 @@ public slots: void addAssetToWorldMessageClose(); Q_INVOKABLE void toggleMuteAudio(); + void loadEntityStatisticsDialog(); private slots: void showDesktop(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 241f908190..15fc46d6cb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -544,8 +544,10 @@ Menu::Menu() { // Developer > Entities >>> MenuWrapper* entitiesOptionsMenu = developerMenu->addMenu("Entities"); + addActionToQMenuAndActionHash(entitiesOptionsMenu, MenuOption::OctreeStats, 0, - dialogsManager.data(), SLOT(octreeStatsDetails())); + qApp, SLOT(loadEntityStatisticsDialog())); + addCheckableActionToQMenuAndActionHash(entitiesOptionsMenu, MenuOption::ShowRealtimeEntityStats); // Developer > Network >>> diff --git a/interface/src/ui/OctreeStatsProvider.cpp b/interface/src/ui/OctreeStatsProvider.cpp new file mode 100644 index 0000000000..9c6fdcea8f --- /dev/null +++ b/interface/src/ui/OctreeStatsProvider.cpp @@ -0,0 +1,561 @@ +// +// OctreeStatsProvider.cpp +// interface/src/ui +// +// Created by Vlad Stelmahovsky on 3/12/17. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include + +#include "Application.h" + +#include "../octree/OctreePacketProcessor.h" +#include "ui/OctreeStatsProvider.h" + +OctreeStatsProvider::OctreeStatsProvider(QObject* parent, NodeToOctreeSceneStats* model) : + QObject(parent), + _model(model) + //, _averageUpdatesPerSecond(SAMPLES_PER_SECOND) +{ + + _statCount = 0; +// _octreeServerLabelsCount = 0; + +// for (int i = 0; i < MAX_VOXEL_SERVERS; i++) { +// _octreeServerLables[i] = 0; +// } + + // Setup stat items +// _serverElements = AddStatItem("Elements on Servers"); + _localElements = AddStatItem("Local Elements"); + _localElementsMemory = AddStatItem("Elements Memory"); + _sendingMode = AddStatItem("Sending Mode"); + + _processedPackets = AddStatItem("Incoming Entity Packets"); + _processedPacketsElements = AddStatItem("Processed Packets Elements"); + _processedPacketsEntities = AddStatItem("Processed Packets Entities"); + _processedPacketsTiming = AddStatItem("Processed Packets Timing"); + + _outboundEditPackets = AddStatItem("Outbound Entity Packets"); + + _entityUpdateTime = AddStatItem("Entity Update Time"); + _entityUpdates = AddStatItem("Entity Updates"); + + //schedule updates + connect(&_updateTimer, &QTimer::timeout, this, &OctreeStatsProvider::updateOctreeStatsData); + _updateTimer.setInterval(100); + //timer will be rescheduled on each new timeout + _updateTimer.setSingleShot(true); + +} + +void OctreeStatsProvider::moreless(const QString& link) { + QStringList linkDetails = link.split("-"); + const int COMMAND_ITEM = 0; + const int SERVER_NUMBER_ITEM = 1; + QString serverNumberString = linkDetails[SERVER_NUMBER_ITEM]; + QString command = linkDetails[COMMAND_ITEM]; + int serverNumber = serverNumberString.toInt(); + +// if (command == "more") { +// _extraServerDetails[serverNumber-1] = MORE; +// } else if (command == "most") { +// _extraServerDetails[serverNumber-1] = MOST; +// } else { +// _extraServerDetails[serverNumber-1] = LESS; +// } +} + +/* + * Start updates statistics +*/ +void OctreeStatsProvider::startUpdates() { + _updateTimer.start(); +} + +/* + * Stop updates statistics +*/ +void OctreeStatsProvider::stopUpdates() { + _updateTimer.stop(); +} + + +int OctreeStatsProvider::AddStatItem(const char* caption, unsigned colorRGBA) { + const int STATS_LABEL_WIDTH = 600; + + _statCount++; // increment our current stat count + + if (colorRGBA == 0) { + static unsigned rotatingColors[] = { GREENISH, YELLOWISH, GREYISH }; + colorRGBA = rotatingColors[_statCount % (sizeof(rotatingColors)/sizeof(rotatingColors[0]))]; + } + //QLabel* label = _labels[_statCount] = new QLabel(); + + // Set foreground color to 62.5% brightness of the meter (otherwise will be hard to read on the bright background) + //QPalette palette = label->palette(); + + // This goofiness came from the bandwidth meter code, it basically stores a color in an unsigned and extracts it + unsigned rgb = colorRGBA >> 8; + const unsigned colorpart1 = 0xfefefeu; + const unsigned colorpart2 = 0xf8f8f8; + rgb = ((rgb & colorpart1) >> 1) + ((rgb & colorpart2) >> 3); +// palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb)); +// label->setPalette(palette); +// _form->addRow(QString(" %1:").arg(caption), label); +// label->setFixedWidth(STATS_LABEL_WIDTH); + + return _statCount; +} + +OctreeStatsProvider::~OctreeStatsProvider() { + _updateTimer.stop(); +} + +int OctreeStatsProvider::serversNum() const { + return m_serversNum; +} + +void OctreeStatsProvider::updateOctreeStatsData() { + + // Processed Entities Related stats + auto entities = qApp->getEntities(); + auto entitiesTree = entities->getTree(); + + // Do this ever paint event... even if we don't update + auto totalTrackedEdits = entitiesTree->getTotalTrackedEdits(); + + + // Only refresh our stats every once in a while, unless asked for realtime + //if no realtime, then update once per second. Otherwise consider 60FPS update, ie 16ms interval + int updateinterval = Menu::getInstance()->isOptionChecked(MenuOption::ShowRealtimeEntityStats) ? 16 : 1000; + _updateTimer.start(updateinterval); + + const int FLOATING_POINT_PRECISION = 3; + + // Update labels + + //QLabel* label; + QLocale locale(QLocale::English); + std::stringstream statsValue; +// statsValue.precision(4); + + // Octree Elements Memory Usage + //label = _labels[_localElementsMemory]; +// statsValue.str(""); +// statsValue << "Elements RAM: " << OctreeElement::getTotalMemoryUsage() / 1000000.0f << "MB "; + //label->setText(statsValue.str().c_str()); + m_localElementsMemory = QString("Elements RAM: %1MB").arg(OctreeElement::getTotalMemoryUsage() / 1000000.0f, 5, 'f', 4); + emit localElementsMemoryChanged(m_localElementsMemory); + // Local Elements + //label = _labels[_localElements]; +// unsigned long localTotal = OctreeElement::getNodeCount(); +// unsigned long localInternal = OctreeElement::getInternalNodeCount(); +// unsigned long localLeaves = OctreeElement::getLeafNodeCount(); +// QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); +// QString localInternalString = locale.toString((uint)localInternal); +// QString localLeavesString = locale.toString((uint)localLeaves); + +// statsValue.str(""); +// statsValue << +// "Total: " << qPrintable(localTotalString) << " / " << +// "Internal: " << qPrintable(localInternalString) << " / " << +// "Leaves: " << qPrintable(localLeavesString) << ""; + + m_localElements = QString("Total: %1 / Internal: %2 / Leaves: %3"). + arg(OctreeElement::getNodeCount()). + arg(OctreeElement::getInternalNodeCount()). + arg(OctreeElement::getLeafNodeCount()); + emit localElementsChanged(m_localElements); + + //label->setText(statsValue.str().c_str()); + + // iterate all the current octree stats, and list their sending modes, total their octree elements, etc... + std::stringstream sendingMode(""); + + int serverCount = 0; + int movingServerCount = 0; + unsigned long totalNodes = 0; + unsigned long totalInternal = 0; + unsigned long totalLeaves = 0; + + NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); + sceneStats->withReadLock([&] { + for (NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) { + //const QUuid& uuid = i->first; + OctreeSceneStats& stats = i->second; + serverCount++; + + // calculate server node totals + totalNodes += stats.getTotalElements(); + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + + // Sending mode + if (serverCount > 1) { + sendingMode << ","; + } + if (stats.isMoving()) { + sendingMode << "M"; + movingServerCount++; + } else { + sendingMode << "S"; + } + if (stats.isFullScene()) { + sendingMode << "F"; + } else { + sendingMode << "p"; + } + } + }); + sendingMode << " - " << serverCount << " servers"; + if (movingServerCount > 0) { + sendingMode << " "; + } else { + sendingMode << " "; + } + +// label = _labels[_sendingMode]; +// label->setText(sendingMode.str().c_str()); + + // Server Elements + m_serverElements = QString("Total: %1/ Internal: %2/ Leaves: %3"). + arg(totalNodes).arg(totalInternal).arg(totalLeaves); + emit serverElementsChanged(m_serverElements); +// QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); +// QString serversInternalString = locale.toString((uint)totalInternal); +// QString serversLeavesString = locale.toString((uint)totalLeaves); +//// label = _labels[_serverElements]; +// statsValue.str(""); +// statsValue << +// "Total: " << qPrintable(serversTotalString) << " / " << +// "Internal: " << qPrintable(serversInternalString) << " / " << +// "Leaves: " << qPrintable(serversLeavesString) << ""; +//// label->setText(statsValue.str().c_str()); + + // Processed Packets Elements + auto averageElementsPerPacket = entities->getAverageElementsPerPacket(); + auto averageEntitiesPerPacket = entities->getAverageEntitiesPerPacket(); + + auto averageElementsPerSecond = entities->getAverageElementsPerSecond(); + auto averageEntitiesPerSecond = entities->getAverageEntitiesPerSecond(); + + auto averageWaitLockPerPacket = entities->getAverageWaitLockPerPacket(); + auto averageUncompressPerPacket = entities->getAverageUncompressPerPacket(); + auto averageReadBitstreamPerPacket = entities->getAverageReadBitstreamPerPacket(); + + QString averageElementsPerPacketString = locale.toString(averageElementsPerPacket, 'f', FLOATING_POINT_PRECISION); + QString averageEntitiesPerPacketString = locale.toString(averageEntitiesPerPacket, 'f', FLOATING_POINT_PRECISION); + + QString averageElementsPerSecondString = locale.toString(averageElementsPerSecond, 'f', FLOATING_POINT_PRECISION); + QString averageEntitiesPerSecondString = locale.toString(averageEntitiesPerSecond, 'f', FLOATING_POINT_PRECISION); + + QString averageWaitLockPerPacketString = locale.toString(averageWaitLockPerPacket); + QString averageUncompressPerPacketString = locale.toString(averageUncompressPerPacket); + QString averageReadBitstreamPerPacketString = locale.toString(averageReadBitstreamPerPacket); + +// label = _labels[_processedPackets]; + const OctreePacketProcessor& entitiesPacketProcessor = qApp->getOctreePacketProcessor(); + + auto incomingPacketsDepth = entitiesPacketProcessor.packetsToProcessCount(); + auto incomingPPS = entitiesPacketProcessor.getIncomingPPS(); + auto processedPPS = entitiesPacketProcessor.getProcessedPPS(); + auto treeProcessedPPS = entities->getAveragePacketsPerSecond(); + + QString incomingPPSString = locale.toString(incomingPPS, 'f', FLOATING_POINT_PRECISION); + QString processedPPSString = locale.toString(processedPPS, 'f', FLOATING_POINT_PRECISION); + QString treeProcessedPPSString = locale.toString(treeProcessedPPS, 'f', FLOATING_POINT_PRECISION); + + statsValue.str(""); + statsValue << + "Queue Size: " << incomingPacketsDepth << " Packets / " << + "Network IN: " << qPrintable(incomingPPSString) << " PPS / " << + "Queue OUT: " << qPrintable(processedPPSString) << " PPS / " << + "Tree IN: " << qPrintable(treeProcessedPPSString) << " PPS"; + +// label->setText(statsValue.str().c_str()); + +// label = _labels[_processedPacketsElements]; + statsValue.str(""); + statsValue << + "" << qPrintable(averageElementsPerPacketString) << " per packet / " << + "" << qPrintable(averageElementsPerSecondString) << " per second"; + +// label->setText(statsValue.str().c_str()); + +// label = _labels[_processedPacketsEntities]; + statsValue.str(""); + statsValue << + "" << qPrintable(averageEntitiesPerPacketString) << " per packet / " << + "" << qPrintable(averageEntitiesPerSecondString) << " per second"; + +// label->setText(statsValue.str().c_str()); + +// label = _labels[_processedPacketsTiming]; + statsValue.str(""); + statsValue << + "Lock Wait: " << qPrintable(averageWaitLockPerPacketString) << " (usecs) / " << + "Uncompress: " << qPrintable(averageUncompressPerPacketString) << " (usecs) / " << + "Process: " << qPrintable(averageReadBitstreamPerPacketString) << " (usecs)"; + + //label->setText(statsValue.str().c_str()); + + auto entitiesEditPacketSender = qApp->getEntityEditPacketSender(); + + auto outboundPacketsDepth = entitiesEditPacketSender->packetsToSendCount(); + auto outboundQueuedPPS = entitiesEditPacketSender->getLifetimePPSQueued(); + auto outboundSentPPS = entitiesEditPacketSender->getLifetimePPS(); + + QString outboundQueuedPPSString = locale.toString(outboundQueuedPPS, 'f', FLOATING_POINT_PRECISION); + QString outboundSentPPSString = locale.toString(outboundSentPPS, 'f', FLOATING_POINT_PRECISION); + + //label = _labels[_outboundEditPackets]; + statsValue.str(""); + statsValue << + "Queue Size: " << outboundPacketsDepth << " packets / " << + "Queued IN: " << qPrintable(outboundQueuedPPSString) << " PPS / " << + "Sent OUT: " << qPrintable(outboundSentPPSString) << " PPS"; + + //label->setText(statsValue.str().c_str()); + + + // Entity Edits update time + //label = _labels[_entityUpdateTime]; + auto averageEditDelta = entitiesTree->getAverageEditDeltas(); + auto maxEditDelta = entitiesTree->getMaxEditDelta(); + + QString averageEditDeltaString = locale.toString((uint)averageEditDelta); + QString maxEditDeltaString = locale.toString((uint)maxEditDelta); + + statsValue.str(""); + statsValue << + "Average: " << qPrintable(averageEditDeltaString) << " (usecs) / " << + "Max: " << qPrintable(maxEditDeltaString) << " (usecs)"; + + //label->setText(statsValue.str().c_str()); + + // Entity Edits + //label = _labels[_entityUpdates]; + auto bytesPerEdit = entitiesTree->getAverageEditBytes(); + + auto updatesPerSecond = _averageUpdatesPerSecond.getAverage(); + if (updatesPerSecond < 1) { + updatesPerSecond = 0; // we don't really care about small updates per second so suppress those + } + + QString totalTrackedEditsString = locale.toString((uint)totalTrackedEdits); + QString updatesPerSecondString = locale.toString(updatesPerSecond, 'f', FLOATING_POINT_PRECISION); + QString bytesPerEditString = locale.toString(bytesPerEdit); + + statsValue.str(""); + statsValue << + "" << qPrintable(updatesPerSecondString) << " updates per second / " << + "" << qPrintable(totalTrackedEditsString) << " total updates / " << + "Average Size: " << qPrintable(bytesPerEditString) << " bytes "; + + //label->setText(statsValue.str().c_str()); + + updateOctreeServers(); +} + +void OctreeStatsProvider::updateOctreeServers() { + int serverCount = 0; + + showOctreeServersOfType(serverCount, NodeType::EntityServer, "Entity", + qApp->getEntityServerJurisdictions()); + //qDebug() << "vladest: octree servers:" << serverCount; + if (m_serversNum != serverCount) { + m_serversNum = serverCount; + emit serversNumChanged(m_serversNum); + } +} + +void OctreeStatsProvider::showOctreeServersOfType(int& serverCount, NodeType_t serverType, const char* serverTypeName, + NodeToJurisdictionMap& serverJurisdictions) { + + QLocale locale(QLocale::English); + + auto nodeList = DependencyManager::get(); + nodeList->eachNode([&](const SharedNodePointer& node){ + + // only send to the NodeTypes that are NodeType_t_VOXEL_SERVER + if (node->getType() == serverType) { + serverCount++; + +// if (serverCount > _octreeServerLabelsCount) { +// QString label = QString("%1 Server %2").arg(serverTypeName).arg(serverCount); +// //int thisServerRow = _octreeServerLables[serverCount-1] = AddStatItem(label.toUtf8().constData()); +//// _labels[thisServerRow]->setTextFormat(Qt::RichText); +//// _labels[thisServerRow]->setTextInteractionFlags(Qt::TextBrowserInteraction); +//// connect(_labels[thisServerRow], SIGNAL(linkActivated(const QString&)), this, SLOT(moreless(const QString&))); +// _octreeServerLabelsCount++; +// } + + std::stringstream serverDetails(""); + std::stringstream extraDetails(""); + std::stringstream linkDetails(""); + + if (node->getActiveSocket()) { + serverDetails << "active "; + } else { + serverDetails << "inactive "; + } + + QUuid nodeUUID = node->getUUID(); + + // lookup our nodeUUID in the jurisdiction map, if it's missing then we're + // missing at least one jurisdiction + serverJurisdictions.withReadLock([&] { + if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) { + serverDetails << " unknown jurisdiction "; + return; + } + const JurisdictionMap& map = serverJurisdictions[nodeUUID]; + + auto rootCode = map.getRootOctalCode(); + + if (rootCode) { + QString rootCodeHex = octalCodeToHexString(rootCode.get()); + + VoxelPositionSize rootDetails; + voxelDetailsForCode(rootCode.get(), rootDetails); + AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); + serverDetails << " jurisdiction: " + << qPrintable(rootCodeHex) + << " [" + << rootDetails.x << ", " + << rootDetails.y << ", " + << rootDetails.z << ": " + << rootDetails.s << "] "; + } else { + serverDetails << " jurisdiction has no rootCode"; + } // root code + }); + + // now lookup stats details for this server... + if (/*_extraServerDetails[serverCount-1] != LESS*/true) { + NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); + sceneStats->withReadLock([&] { + if (sceneStats->find(nodeUUID) != sceneStats->end()) { + OctreeSceneStats& stats = sceneStats->at(nodeUUID); +/* + switch (_extraServerDetails[serverCount - 1]) { + case MOST: { + extraDetails << "
"; + + float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; + float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; + float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; + float lastFullPackets = stats.getLastFullTotalPackets(); + float lastFullPPS = lastFullPackets; + if (lastFullSendInSeconds > 0) { + lastFullPPS = lastFullPackets / lastFullSendInSeconds; + } + + QString lastFullEncodeString = locale.toString(lastFullEncode); + QString lastFullSendString = locale.toString(lastFullSend); + QString lastFullPacketsString = locale.toString(lastFullPackets); + QString lastFullBytesString = locale.toString((uint)stats.getLastFullTotalBytes()); + QString lastFullPPSString = locale.toString(lastFullPPS); + + extraDetails << "
" << "Last Full Scene... " << + "Encode: " << qPrintable(lastFullEncodeString) << " ms " << + "Send: " << qPrintable(lastFullSendString) << " ms " << + "Packets: " << qPrintable(lastFullPacketsString) << " " << + "Bytes: " << qPrintable(lastFullBytesString) << " " << + "Rate: " << qPrintable(lastFullPPSString) << " PPS"; + + for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { + OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); + OctreeSceneStats::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.getTotalElements()); + QString internalString = locale.toString((uint)stats.getTotalInternal()); + QString leavesString = locale.toString((uint)stats.getTotalLeaves()); + + serverDetails << "
" << "Node UUID: " << qPrintable(nodeUUID.toString()) << " "; + + serverDetails << "
" << "Elements: " << + qPrintable(totalString) << " total " << + qPrintable(internalString) << " internal " << + qPrintable(leavesString) << " leaves "; + + QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets()); + QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes()); + QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes()); + const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); + QString incomingOutOfOrderString = locale.toString((uint)seqStats.getOutOfOrder()); + QString incomingLateString = locale.toString((uint)seqStats.getLate()); + QString incomingUnreasonableString = locale.toString((uint)seqStats.getUnreasonable()); + QString incomingEarlyString = locale.toString((uint)seqStats.getEarly()); + QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost()); + QString incomingRecovered = locale.toString((uint)seqStats.getRecovered()); + + qint64 clockSkewInUsecs = node->getClockSkewUsec(); + QString formattedClockSkewString = formatUsecTime(clockSkewInUsecs); + qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; + QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage()); + QString incomingPingTimeString = locale.toString(node->getPingMs()); + QString incomingClockSkewString = locale.toString(clockSkewInMS); + + serverDetails << "
" << "Incoming Packets: " << qPrintable(incomingPacketsString) << + "/ Lost: " << qPrintable(incomingLikelyLostString) << + "/ Recovered: " << qPrintable(incomingRecovered); + + serverDetails << "
" << " Out of Order: " << qPrintable(incomingOutOfOrderString) << + "/ Early: " << qPrintable(incomingEarlyString) << + "/ Late: " << qPrintable(incomingLateString) << + "/ Unreasonable: " << qPrintable(incomingUnreasonableString); + + serverDetails << "
" << + " Average Flight Time: " << qPrintable(incomingFlightTimeString) << " msecs"; + + serverDetails << "
" << + " Average Ping Time: " << qPrintable(incomingPingTimeString) << " msecs"; + + serverDetails << "
" << + " Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs" << + " [" << qPrintable(formattedClockSkewString) << "]"; + + + serverDetails << "
" << "Incoming" << + " Bytes: " << qPrintable(incomingBytesString) << + " Wasted Bytes: " << qPrintable(incomingWastedBytesString); + + serverDetails << extraDetails.str(); + if (_extraServerDetails[serverCount - 1] == MORE) { + linkDetails << " " << " [most...]"; + linkDetails << " " << " [less...]"; + } else { + linkDetails << " " << " [less...]"; + linkDetails << " " << " [least...]"; + } + + } break; + case LESS: { + // nothing + } break; + }*/ + } + }); + } else { + linkDetails << " " << " [more...]"; + linkDetails << " " << " [most...]"; + } + serverDetails << linkDetails.str(); + //_labels[_octreeServerLables[serverCount - 1]]->setText(serverDetails.str().c_str()); + } // is VOXEL_SERVER + }); +} + + diff --git a/interface/src/ui/OctreeStatsProvider.h b/interface/src/ui/OctreeStatsProvider.h new file mode 100644 index 0000000000..c2fc46218d --- /dev/null +++ b/interface/src/ui/OctreeStatsProvider.h @@ -0,0 +1,128 @@ +// +// OctreeStatsProvider.h +// interface/src/ui +// +// Created by Vlad Stelmahovsky on 3/12/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OctreeStatsProvider_h +#define hifi_OctreeStatsProvider_h + +#include +#include + +#include "DependencyManager.h" + +#define MAX_STATS 100 +#define MAX_VOXEL_SERVERS 50 +#define DEFAULT_COLOR 0 + +class OctreeStatsProvider : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + + Q_PROPERTY(int serversNum READ serversNum NOTIFY serversNumChanged) + Q_PROPERTY(QString serverElements READ serverElements NOTIFY serverElementsChanged) + Q_PROPERTY(QString localElements READ localElements NOTIFY localElementsChanged) + Q_PROPERTY(QString localElementsMemory READ localElementsMemory NOTIFY localElementsMemoryChanged) + Q_PROPERTY(QString sendingMode READ sendingMode NOTIFY sendingModeChanged) + //Incoming Entity Packets + Q_PROPERTY(QString processedPackets READ processedPackets NOTIFY processedPacketsChanged) + +// int _entityUpdateTime; +// int _entityUpdates; +// int _processedPacketsElements; +// int _processedPacketsEntities; +// int _processedPacketsTiming; +// int _outboundEditPackets; + +public: + // Sets up the UI + OctreeStatsProvider(QObject* parent, NodeToOctreeSceneStats* model); + ~OctreeStatsProvider(); + + int serversNum() const; + + QString serverElements() const { + return m_serverElements; + } + + QString localElements() const { + return m_localElements; + } + + QString localElementsMemory() const { + return m_localElementsMemory; + } + + QString sendingMode() const { + return m_sendingMode; + } + + QString processedPackets() const { + return m_processedPackets; + } + +signals: + + void serversNumChanged(int serversNum); + void serverElementsChanged(QString serverElements); + void localElementsChanged(QString localElements); + void sendingModeChanged(QString sendingMode); + void processedPacketsChanged(QString processedPackets); + void localElementsMemoryChanged(QString localElementsMemory); + +public slots: + void moreless(const QString& link); + void startUpdates(); + void stopUpdates(); + +private slots: + void updateOctreeStatsData(); +protected: + + int AddStatItem(const char* caption, unsigned colorRGBA = DEFAULT_COLOR); + void updateOctreeServers(); + void showOctreeServersOfType(int& serverNumber, NodeType_t serverType, + const char* serverTypeName, NodeToJurisdictionMap& serverJurisdictions); + +private: + NodeToOctreeSceneStats* _model; + int _statCount; + + int _sendingMode; + int _serverElements; + int _localElements; + int _localElementsMemory; + + int _entityUpdateTime; + int _entityUpdates; + int _processedPackets; + int _processedPacketsElements; + int _processedPacketsEntities; + int _processedPacketsTiming; + int _outboundEditPackets; + + const int SAMPLES_PER_SECOND = 10; + SimpleMovingAverage _averageUpdatesPerSecond; + quint64 _lastWindowAt = usecTimestampNow(); + quint64 _lastKnownTrackedEdits = 0; + + quint64 _lastRefresh = 0; + +// int _octreeServerLables[MAX_VOXEL_SERVERS]; +// int _octreeServerLabelsCount; + QTimer _updateTimer; + int m_serversNum {0}; + QString m_serverElements; + QString m_localElements; + QString m_localElementsMemory; + QString m_sendingMode; + QString m_processedPackets; +}; + +#endif // hifi_OctreeStatsProvider_h diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 32fe26a697..2aa6725aa2 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -43,6 +43,7 @@ #include "FileDialogHelper.h" #include "avatar/AvatarManager.h" #include "AudioClient.h" +#include "ui/OctreeStatsProvider.h" static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; @@ -182,6 +183,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("Tablet", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("pathToFonts", "../../"); tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());