diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 05f425374b..360172efde 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -57,7 +57,8 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _linkedData(NULL), _isAlive(true), _clockSkewUsec(0), - _mutex() + _mutex(), + _clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples { } @@ -133,6 +134,11 @@ float Node::getAverageKilobitsPerSecond() { } } +void Node::updateClockSkewUsec(int clockSkewSample) { + _clockSkewMovingPercentile.updatePercentile((float)clockSkewSample); + _clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile(); +} + QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index f52cda0d0d..85fe2e4458 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -23,6 +23,7 @@ #include "HifiSockAddr.h" #include "NodeData.h" #include "SimpleMovingAverage.h" +#include "MovingPercentile.h" typedef quint8 NodeType_t; @@ -94,7 +95,7 @@ public: void setPingMs(int pingMs) { _pingMs = pingMs; } int getClockSkewUsec() const { return _clockSkewUsec; } - void setClockSkewUsec(int clockSkew) { _clockSkewUsec = clockSkew; } + void updateClockSkewUsec(int clockSkewSample); QMutex& getMutex() { return _mutex; } friend QDataStream& operator<<(QDataStream& out, const Node& node); @@ -120,6 +121,7 @@ private: int _pingMs; int _clockSkewUsec; QMutex _mutex; + MovingPercentile _clockSkewMovingPercentile; }; QDebug operator<<(QDebug debug, const Node &message); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index ebd33ef132..9a298ce26c 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -96,8 +96,8 @@ void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& int clockSkew = othersReplyTime - othersExprectedReply; sendingNode->setPingMs(pingTime / 1000); - sendingNode->setClockSkewUsec(clockSkew); - + sendingNode->updateClockSkewUsec(clockSkew); + const bool wantDebug = false; if (wantDebug) { diff --git a/libraries/shared/src/MovingPercentile.cpp b/libraries/shared/src/MovingPercentile.cpp new file mode 100644 index 0000000000..ec007b5c22 --- /dev/null +++ b/libraries/shared/src/MovingPercentile.cpp @@ -0,0 +1,61 @@ +// +// MovingPercentile.cpp +// libraries/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MovingPercentile.h" + +MovingPercentile::MovingPercentile(int numSamples, float percentile) + : _numSamples(numSamples), + _percentile(percentile), + _samplesSorted(), + _sampleIds(), + _newSampleId(0), + _indexOfPercentile(0), + _valueAtPercentile(0.0f) +{ +} + +void MovingPercentile::updatePercentile(float sample) { + + // insert the new sample into _samplesSorted + int newSampleIndex; + if (_samplesSorted.size() < _numSamples) { + // if not all samples have been filled yet, simply append it + newSampleIndex = _samplesSorted.size(); + _samplesSorted.append(sample); + _sampleIds.append(_newSampleId); + + // update _indexOfPercentile + float index = _percentile * (float)(_samplesSorted.size() - 1); + _indexOfPercentile = (int)(index + 0.5f); // round to int + } else { + // find index of sample with id = _newSampleId and replace it with new sample + newSampleIndex = _sampleIds.indexOf(_newSampleId); + _samplesSorted[newSampleIndex] = sample; + } + + // increment _newSampleId. cycles from 0 thru N-1 + _newSampleId = (_newSampleId == _numSamples - 1) ? 0 : _newSampleId + 1; + + // swap new sample with neighbors in _samplesSorted until it's in sorted order + // try swapping up first, then down. element will only be swapped one direction. + while (newSampleIndex < _samplesSorted.size() - 1 && sample > _samplesSorted[newSampleIndex + 1]) { + _samplesSorted.swap(newSampleIndex, newSampleIndex + 1); + _sampleIds.swap(newSampleIndex, newSampleIndex + 1); + newSampleIndex++; + } + while (newSampleIndex > 0 && sample < _samplesSorted[newSampleIndex - 1]) { + _samplesSorted.swap(newSampleIndex, newSampleIndex - 1); + _sampleIds.swap(newSampleIndex, newSampleIndex - 1); + newSampleIndex--; + } + + // find new value at percentile + _valueAtPercentile = _samplesSorted[_indexOfPercentile]; +} diff --git a/libraries/shared/src/MovingPercentile.h b/libraries/shared/src/MovingPercentile.h new file mode 100644 index 0000000000..284ed9d890 --- /dev/null +++ b/libraries/shared/src/MovingPercentile.h @@ -0,0 +1,36 @@ +// +// MovingPercentile.h +// libraries/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// +// 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_MovingPercentile_h +#define hifi_MovingPercentile_h + +#include + +class MovingPercentile { + +public: + MovingPercentile(int numSamples, float percentile = 0.5f); + + void updatePercentile(float sample); + float getValueAtPercentile() const { return _valueAtPercentile; } + +private: + const int _numSamples; + const float _percentile; + + QList _samplesSorted; + QList _sampleIds; // incrementally assigned, is cyclic + int _newSampleId; + + int _indexOfPercentile; + float _valueAtPercentile; +}; + +#endif diff --git a/tests/shared/CMakeLists.txt b/tests/shared/CMakeLists.txt new file mode 100644 index 0000000000..b9513e3f26 --- /dev/null +++ b/tests/shared/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(TARGET_NAME shared-tests) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +#find_package(Qt5Network REQUIRED) +#find_package(Qt5Script REQUIRED) +#find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiProject.cmake) +setup_hifi_project(${TARGET_NAME} TRUE) + +include(${MACRO_DIR}/AutoMTC.cmake) +auto_mtc(${TARGET_NAME} ${ROOT_DIR}) + +#qt5_use_modules(${TARGET_NAME} Network Script Widgets) + +#include glm +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + +# link in the shared libraries +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +IF (WIN32) + #target_link_libraries(${TARGET_NAME} Winmm Ws2_32) +ENDIF(WIN32) + diff --git a/tests/shared/src/MovingPercentileTests.cpp b/tests/shared/src/MovingPercentileTests.cpp new file mode 100644 index 0000000000..26870717ca --- /dev/null +++ b/tests/shared/src/MovingPercentileTests.cpp @@ -0,0 +1,169 @@ +// +// MovingPercentileTests.cpp +// tests/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// Copyright 2014 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 "MovingPercentileTests.h" + +#include "SharedUtil.h" +#include "MovingPercentile.h" + +#include + +float MovingPercentileTests::random() { + return rand() / (float)RAND_MAX; +} + +void MovingPercentileTests::runAllTests() { + + QVector valuesForN; + + valuesForN.append(1); + valuesForN.append(2); + valuesForN.append(3); + valuesForN.append(4); + valuesForN.append(5); + valuesForN.append(10); + valuesForN.append(100); + + + QQueue lastNSamples; + + for (int i=0; i N) { + lastNSamples.pop_front(); + } + + movingMin.updatePercentile(sample); + + float experimentMin = movingMin.getValueAtPercentile(); + + float actualMin = lastNSamples[0]; + for (int j = 0; j < lastNSamples.size(); j++) { + if (lastNSamples.at(j) < actualMin) { + actualMin = lastNSamples.at(j); + } + } + + if (experimentMin != actualMin) { + qDebug() << "\t\t FAIL at sample" << s; + fail = true; + break; + } + } + if (!fail) { + qDebug() << "\t\t PASS"; + } + } + + + { + bool fail = false; + + qDebug() << "\t testing running max..."; + + lastNSamples.clear(); + MovingPercentile movingMax(N, 1.0f); + + for (int s = 0; s < 10000; s++) { + + float sample = random(); + + lastNSamples.push_back(sample); + if (lastNSamples.size() > N) { + lastNSamples.pop_front(); + } + + movingMax.updatePercentile(sample); + + float experimentMax = movingMax.getValueAtPercentile(); + + float actualMax = lastNSamples[0]; + for (int j = 0; j < lastNSamples.size(); j++) { + if (lastNSamples.at(j) > actualMax) { + actualMax = lastNSamples.at(j); + } + } + + if (experimentMax != actualMax) { + qDebug() << "\t\t FAIL at sample" << s; + fail = true; + break; + } + } + if (!fail) { + qDebug() << "\t\t PASS"; + } + } + + + { + bool fail = false; + + qDebug() << "\t testing running median..."; + + lastNSamples.clear(); + MovingPercentile movingMedian(N, 0.5f); + + for (int s = 0; s < 10000; s++) { + + float sample = random(); + + lastNSamples.push_back(sample); + if (lastNSamples.size() > N) { + lastNSamples.pop_front(); + } + + movingMedian.updatePercentile(sample); + + float experimentMedian = movingMedian.getValueAtPercentile(); + + int samplesLessThan = 0; + int samplesMoreThan = 0; + + for (int j=0; j experimentMedian) { + samplesMoreThan++; + } + } + + + if (!(samplesLessThan <= N/2 && samplesMoreThan <= N-1/2)) { + qDebug() << "\t\t FAIL at sample" << s; + fail = true; + break; + } + } + if (!fail) { + qDebug() << "\t\t PASS"; + } + } + } +} + diff --git a/tests/shared/src/MovingPercentileTests.h b/tests/shared/src/MovingPercentileTests.h new file mode 100644 index 0000000000..34460880fb --- /dev/null +++ b/tests/shared/src/MovingPercentileTests.h @@ -0,0 +1,22 @@ +// +// MovingPercentileTests.h +// tests/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// Copyright 2014 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_MovingPercentileTests_h +#define hifi_MovingPercentileTests_h + +namespace MovingPercentileTests { + + float random(); + + void runAllTests(); +} + +#endif // hifi_MovingPercentileTests_h diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp new file mode 100644 index 0000000000..3ae1b7b34d --- /dev/null +++ b/tests/shared/src/main.cpp @@ -0,0 +1,16 @@ +// +// main.cpp +// tests/physics/src +// +// Copyright 2014 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 "MovingPercentileTests.h" + +int main(int argc, char** argv) { + MovingPercentileTests::runAllTests(); + return 0; +}