forked from Mirrors/openclonk
390 lines
11 KiB
C++
390 lines
11 KiB
C++
/*
|
|
* 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"));
|