/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2010-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // network statistics and information dialogs #include "C4Include.h" #include "network/C4Network2Stats.h" #include "control/C4GameControl.h" #include "network/C4Network2.h" #include "object/C4GameObjects.h" #include "player/C4Player.h" #include "player/C4PlayerList.h" C4Graph::C4Graph() : szTitle(LoadResStr("IDS_NET_GRAPH")) { } C4TableGraph::C4TableGraph(int iBackLogLength, int iStartTime) : iBackLogLength(iBackLogLength), iInitialStartTime(iStartTime), iTime(iStartTime), iAveragedTime(iStartTime) { // create value buffer assert(iBackLogLength); pValues = pAveragedValues = new ValueType[iBackLogLength]; *pValues = 0; } C4TableGraph::~C4TableGraph() { // flush stuff Reset(-1); // free value buffer(s) if (pValues != pAveragedValues) delete [] pAveragedValues; delete [] pValues; } void C4TableGraph::Reset(TimeType iToTime) { // flush buffer if (!!szDumpFile) DumpToFile(szDumpFile, fWrapped); // reset stuff iInitialStartTime = iTime = iToTime; fWrapped = false; iBackLogPos = 0; *pValues = 0; } // retrieve timeframe covered by backlog C4Graph::TimeType C4TableGraph::GetStartTime() const { // wrap? -> whole buffer used if (fWrapped) return iTime - iBackLogLength; // otherwise, just buffer to the start used return iTime - iBackLogPos; } C4Graph::TimeType C4TableGraph::GetEndTime() const { // end time is current return iTime; } C4Graph::ValueType C4TableGraph::GetValue(TimeType iAtTime) const { // must be inside buffer assert(Inside(iAtTime, GetStartTime(), GetEndTime()-1)); // query it - can't be negative if inside start/end-time return pAveragedValues[(iAtTime - iInitialStartTime) % iBackLogLength] * fMultiplier; } C4Graph::ValueType C4TableGraph::GetAtValue(TimeType iAtTime) const { // must be inside buffer assert(Inside(iAtTime, GetStartTime(), GetEndTime()-1)); // query it - can't be negative if inside start/end-time return pValues[(iAtTime - iInitialStartTime) % iBackLogLength]; } void C4TableGraph::SetAvgValue(TimeType iAtTime, ValueType iValue) const { // must be inside buffer assert(Inside(iAtTime, GetStartTime(), GetEndTime()-1)); // set it - can't be negative if inside start/end-time pAveragedValues[(iAtTime - iInitialStartTime) % iBackLogLength] = iValue; } C4Graph::ValueType C4TableGraph::GetMedianValue(TimeType iStartTime, TimeType iEndTime) const { assert(iStartTime < iEndTime); // safety: Never build median if no values are recorded if (!iBackLogPos && !fWrapped) return 0; // sum up and divide in the end - let's hope this will never be called for really large values that could overflow ValueType ValueType iSum = GetValue(iStartTime), iNum=1; for (; ++iStartTime < iEndTime; ++iNum) iSum += GetValue(iStartTime); return iSum / iNum; } C4Graph::ValueType C4TableGraph::GetMinValue() const { int iPos0 = iBackLogPos ? iBackLogPos-1 : iBackLogPos; ValueType iMinVal = pAveragedValues[iPos0]; int i = iPos0; ValueType *p = pAveragedValues; while (i--) iMinVal = std::min(iMinVal, *p++); if (fWrapped) { i = iBackLogLength - iPos0; while (--i) iMinVal = std::min(iMinVal, *++p); } return iMinVal * fMultiplier; } C4Graph::ValueType C4TableGraph::GetMaxValue() const { int iPos0 = iBackLogPos ? iBackLogPos-1 : iBackLogPos; ValueType iMaxVal = pAveragedValues[iPos0]; int i = iPos0; ValueType *p = pAveragedValues; while (i--) iMaxVal = std::max(iMaxVal, *p++); if (fWrapped) { i = iBackLogLength - iPos0; while (--i) iMaxVal = std::max(iMaxVal, *++p); } return iMaxVal * fMultiplier; } void C4TableGraph::RecordValue(ValueType iValue) { // rec value pValues[iBackLogPos] = iValue; // calc time ++iTime; if (++iBackLogPos >= iBackLogLength) { // create dump before overwriting last buffer if (!!szDumpFile) DumpToFile(szDumpFile, fWrapped); // restart buffer fWrapped = true; iBackLogPos = 0; } } bool C4TableGraph::DumpToFile(const StdStrBuf &rszFilename, bool fAppend) const { assert(!!rszFilename); // nothing to write? if (!fWrapped && !iBackLogPos) return false; // try append if desired; create if unsuccessful CStdFile out; if (fAppend) if (!out.Append(rszFilename.getData())) fAppend = false; if (!fAppend) { if (!out.Create(rszFilename.getData())) return false; // print header out.WriteString("t\tv\n\r"); } // write out current timeframe int iEndTime = GetEndTime(); StdStrBuf buf; for (int iWriteTime = GetStartTime(); iWriteTime < iEndTime; ++iWriteTime) { buf.Format("%d\t%d\n\r", (int) iWriteTime, (int) GetValue(iWriteTime)); out.WriteString(buf.getData()); } return true; } void C4TableGraph::SetAverageTime(int iToTime) { // set new time; resetting valid, averaged range if (iAveragedTime == iToTime) return; assert(iToTime > 0); iAvgRange = iToTime; iAveragedTime = iInitialStartTime; } #define FORWARD_AVERAGE #define FORWARD_AVERAGE_FACTOR 4 void C4TableGraph::Update() const { // no averaging necessary? if (pAveragedValues == pValues) { if (iAvgRange == 1) return; // averaging necessary, but buffer not yet created: Create it! pAveragedValues = new ValueType[iBackLogLength]; } // up-to-date? if (iAveragedTime == iTime) return; assert(iAveragedTime < iTime); // must not have gone back! // update it int iStartTime = GetStartTime(); #ifdef FORWARD_AVERAGE int iAvgFwRange = iAvgRange/FORWARD_AVERAGE_FACTOR; #else int iAvgFwRange = 0; #endif for (int iUpdateTime = std::max(iAveragedTime-iAvgFwRange-1, iStartTime); iUpdateTime < iTime; ++iUpdateTime) { ValueType iSum=0, iSumWeight=0, iWeight; for (int iSumTime = std::max(iUpdateTime - iAvgRange, iStartTime); iSumTime < std::min(iUpdateTime + iAvgFwRange+1, iTime); ++iSumTime) { iWeight = (ValueType) iAvgRange - Abs(iUpdateTime - iSumTime) + 1; iSum += GetAtValue(iSumTime) * iWeight; iSumWeight += iWeight; } SetAvgValue(iUpdateTime, iSum / iSumWeight); } // now it's all up-to-date iAveragedTime = iTime; } // -------------------------------------------------- C4Graph::TimeType C4GraphCollection::GetStartTime() const { const_iterator i = begin(); if (i == end()) return 0; C4Graph::TimeType iTime = (*i)->GetStartTime(); while (++i != end()) iTime = std::min(iTime, (*i)->GetStartTime()); return iTime; } C4Graph::TimeType C4GraphCollection::GetEndTime() const { const_iterator i = begin(); if (i == end()) return 0; C4Graph::TimeType iTime = (*i)->GetEndTime(); while (++i != end()) iTime = std::max(iTime, (*i)->GetEndTime()); return iTime; } C4Graph::ValueType C4GraphCollection::GetMinValue() const { const_iterator i = begin(); if (i == end()) return 0; C4Graph::ValueType iVal = (*i)->GetMinValue(); while (++i != end()) iVal = std::min(iVal, (*i)->GetMinValue()); return iVal; } C4Graph::ValueType C4GraphCollection::GetMaxValue() const { const_iterator i = begin(); if (i == end()) return 0; C4Graph::ValueType iVal = (*i)->GetMaxValue(); while (++i != end()) iVal = std::max(iVal, (*i)->GetMaxValue()); return iVal; } int C4GraphCollection::GetSeriesCount() const { int iCount = 0; for (auto i : *this) iCount += i->GetSeriesCount(); return iCount; } const C4Graph *C4GraphCollection::GetSeries(int iIndex) const { for (auto i : *this) { int iCnt = i->GetSeriesCount(); if (iIndex < iCnt) return i->GetSeries(iIndex); iIndex -= iCnt; } return nullptr; } void C4GraphCollection::Update() const { // update all child graphs for (auto i : *this) i->Update(); } void C4GraphCollection::SetAverageTime(int iToTime) { if ((iCommonAvgTime = iToTime)) for (auto & i : *this) i->SetAverageTime(iToTime); } void C4GraphCollection::SetMultiplier(ValueType fToVal) { if ((fMultiplier = fToVal)) for (auto & i : *this) i->SetMultiplier(fToVal); } // -------------------------------------------------------------------------------- C4Network2Stats::C4Network2Stats() { // init callback timer Application.Add(this); SecondCounter = 0; ControlCounter = 0; // init graphs statObjCount.SetTitle(LoadResStr("IDS_MSG_OBJCOUNT")); statFPS.SetTitle(LoadResStr("IDS_MSG_FPS")); statNetI.SetTitle(LoadResStr("IDS_NET_INPUT")); statNetI.SetColorDw(0x00ff00); statNetO.SetTitle(LoadResStr("IDS_NET_OUTPUT")); statNetO.SetColorDw(0xff0000); graphNetIO.AddGraph(&statNetI); graphNetIO.AddGraph(&statNetO); statControls.SetTitle(LoadResStr("IDS_NET_CONTROL")); statControls.SetAverageTime(100); statActions.SetTitle(LoadResStr("IDS_NET_APM")); statActions.SetAverageTime(100); for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next) pPlr->CreateGraphs(); C4Network2Client *pClient = nullptr; while ((pClient = ::Network.Clients.GetNextClient(pClient))) pClient->CreateGraphs(); } C4Network2Stats::~C4Network2Stats() { for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next) pPlr->ClearGraphs(); C4Network2Client *pClient = nullptr; while ((pClient = ::Network.Clients.GetNextClient(pClient))) pClient->ClearGraphs(); Application.Remove(this); } void C4Network2Stats::ExecuteFrame() { statObjCount.RecordValue(C4Graph::ValueType(::Objects.ObjectCount())); } void C4Network2Stats::ExecuteSecond() { statFPS.RecordValue(C4Graph::ValueType(Game.FPS)); statNetI.RecordValue(C4Graph::ValueType(::Network.NetIO.getProtIRate(P_TCP) + ::Network.NetIO.getProtIRate(P_UDP))); statNetO.RecordValue(C4Graph::ValueType(::Network.NetIO.getProtORate(P_TCP) + ::Network.NetIO.getProtORate(P_UDP))); // pings for all clients C4Network2Client *pClient = nullptr; while ((pClient = ::Network.Clients.GetNextClient(pClient))) if (pClient->getStatPing()) { int iPing=0; C4Network2IOConnection *pConn = pClient->getMsgConn(); if (pConn) iPing = pConn->getLag(); pClient->getStatPing()->RecordValue(C4Graph::ValueType(iPing)); } ++SecondCounter; } void C4Network2Stats::ExecuteControlFrame() { // control rate may have updated: always convert values to actions per minute statControls.SetMultiplier((C4Graph::ValueType) 1000 / 38 / ::Control.ControlRate); statActions.SetMultiplier((C4Graph::ValueType) 1000 / 38 * 60 / ::Control.ControlRate); // register and reset control counts for all players for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next) { if (pPlr->pstatControls) { pPlr->pstatControls->RecordValue(C4Graph::ValueType(pPlr->ControlCount)); pPlr->ControlCount = 0; } if (pPlr->pstatActions) { pPlr->pstatActions->RecordValue(C4Graph::ValueType(pPlr->ActionCount)); pPlr->ActionCount = 0; } } ++ControlCounter; } C4Graph *C4Network2Stats::GetGraphByName(const StdStrBuf &rszName, bool &rfIsTemp) { // compare against default graph names rfIsTemp = false; if (SEqualNoCase(rszName.getData(), "oc")) return &statObjCount; if (SEqualNoCase(rszName.getData(), "fps")) return &statFPS; if (SEqualNoCase(rszName.getData(), "netio")) return &graphNetIO; if (SEqualNoCase(rszName.getData(), "pings")) return &statPings; if (SEqualNoCase(rszName.getData(), "control")) return &statControls; if (SEqualNoCase(rszName.getData(), "apm")) return &statActions; // no match return nullptr; } // MassGraph.SetDumpFile(StdStrBuf("C:\\test.txt"));