openclonk/src/network/C4Network2Stats.cpp

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"));