OpenClonk
C4Network2Stats.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2010-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // network statistics and information dialogs
17 
18 #include "C4Include.h"
20 
21 #include "control/C4GameControl.h"
22 #include "network/C4Network2.h"
23 #include "object/C4GameObjects.h"
24 #include "player/C4Player.h"
25 #include "player/C4PlayerList.h"
26 
28  : szTitle(LoadResStr("IDS_NET_GRAPH"))
29 {
30 }
31 
32 C4TableGraph::C4TableGraph(int iBackLogLength, int iStartTime)
33  : iBackLogLength(iBackLogLength), iInitialStartTime(iStartTime), iTime(iStartTime), iAveragedTime(iStartTime)
34 {
35  // create value buffer
36  assert(iBackLogLength);
37  pValues = pAveragedValues = new ValueType[iBackLogLength];
38  *pValues = 0;
39 }
40 
42 {
43  // flush stuff
44  Reset(-1);
45  // free value buffer(s)
46  if (pValues != pAveragedValues) delete [] pAveragedValues;
47  delete [] pValues;
48 }
49 
51 {
52  // flush buffer
53  if (!!szDumpFile) DumpToFile(szDumpFile, fWrapped);
54  // reset stuff
55  iInitialStartTime = iTime = iToTime;
56  fWrapped = false;
57  iBackLogPos = 0;
58  *pValues = 0;
59 }
60 
61 
62 // retrieve timeframe covered by backlog
64 {
65  // wrap? -> whole buffer used
66  if (fWrapped) return iTime - iBackLogLength;
67  // otherwise, just buffer to the start used
68  return iTime - iBackLogPos;
69 }
70 
72 {
73  // end time is current
74  return iTime;
75 }
76 
78 {
79  // must be inside buffer
80  assert(Inside(iAtTime, GetStartTime(), GetEndTime()-1));
81  // query it - can't be negative if inside start/end-time
82  return pAveragedValues[(iAtTime - iInitialStartTime) % iBackLogLength] * fMultiplier;
83 }
84 
86 {
87  // must be inside buffer
88  assert(Inside(iAtTime, GetStartTime(), GetEndTime()-1));
89  // query it - can't be negative if inside start/end-time
90  return pValues[(iAtTime - iInitialStartTime) % iBackLogLength];
91 }
92 
93 void C4TableGraph::SetAvgValue(TimeType iAtTime, ValueType iValue) const
94 {
95  // must be inside buffer
96  assert(Inside(iAtTime, GetStartTime(), GetEndTime()-1));
97  // set it - can't be negative if inside start/end-time
98  pAveragedValues[(iAtTime - iInitialStartTime) % iBackLogLength] = iValue;
99 }
100 
102 {
103  assert(iStartTime < iEndTime);
104  // safety: Never build median if no values are recorded
105  if (!iBackLogPos && !fWrapped) return 0;
106  // sum up and divide in the end - let's hope this will never be called for really large values that could overflow ValueType
107  ValueType iSum = GetValue(iStartTime), iNum=1;
108  for (; ++iStartTime < iEndTime; ++iNum) iSum += GetValue(iStartTime);
109  return iSum / iNum;
110 }
111 
113 {
114  int iPos0 = iBackLogPos ? iBackLogPos-1 : iBackLogPos;
115  ValueType iMinVal = pAveragedValues[iPos0];
116  int i = iPos0; ValueType *p = pAveragedValues;
117  while (i--) iMinVal = std::min(iMinVal, *p++);
118  if (fWrapped)
119  {
120  i = iBackLogLength - iPos0;
121  while (--i) iMinVal = std::min(iMinVal, *++p);
122  }
123  return iMinVal * fMultiplier;
124 }
125 
127 {
128  int iPos0 = iBackLogPos ? iBackLogPos-1 : iBackLogPos;
129  ValueType iMaxVal = pAveragedValues[iPos0];
130  int i = iPos0; ValueType *p = pAveragedValues;
131  while (i--) iMaxVal = std::max(iMaxVal, *p++);
132  if (fWrapped)
133  {
134  i = iBackLogLength - iPos0;
135  while (--i) iMaxVal = std::max(iMaxVal, *++p);
136  }
137  return iMaxVal * fMultiplier;
138 }
139 
141 {
142  // rec value
143  pValues[iBackLogPos] = iValue;
144  // calc time
145  ++iTime;
146  if (++iBackLogPos >= iBackLogLength)
147  {
148  // create dump before overwriting last buffer
149  if (!!szDumpFile) DumpToFile(szDumpFile, fWrapped);
150  // restart buffer
151  fWrapped = true;
152  iBackLogPos = 0;
153  }
154 }
155 
156 bool C4TableGraph::DumpToFile(const StdStrBuf &rszFilename, bool fAppend) const
157 {
158  assert(!!rszFilename);
159  // nothing to write?
160  if (!fWrapped && !iBackLogPos) return false;
161  // try append if desired; create if unsuccessful
162  CStdFile out;
163  if (fAppend) if (!out.Append(rszFilename.getData())) fAppend = false;
164  if (!fAppend)
165  {
166  if (!out.Create(rszFilename.getData())) return false;
167  // print header
168  out.WriteString("t\tv\n\r");
169  }
170  // write out current timeframe
171  int iEndTime = GetEndTime();
172  StdStrBuf buf;
173  for (int iWriteTime = GetStartTime(); iWriteTime < iEndTime; ++iWriteTime)
174  {
175  buf.Format("%d\t%d\n\r", (int) iWriteTime, (int) GetValue(iWriteTime));
176  out.WriteString(buf.getData());
177  }
178  return true;
179 }
180 
182 {
183  // set new time; resetting valid, averaged range
184  if (iAveragedTime == iToTime) return;
185  assert(iToTime > 0);
186  iAvgRange = iToTime;
187  iAveragedTime = iInitialStartTime;
188 }
189 
190 #define FORWARD_AVERAGE
191 #define FORWARD_AVERAGE_FACTOR 4
192 
194 {
195  // no averaging necessary?
196  if (pAveragedValues == pValues)
197  {
198  if (iAvgRange == 1) return;
199  // averaging necessary, but buffer not yet created: Create it!
200  pAveragedValues = new ValueType[iBackLogLength];
201  }
202  // up-to-date?
203  if (iAveragedTime == iTime) return;
204  assert(iAveragedTime < iTime); // must not have gone back!
205  // update it
206  int iStartTime = GetStartTime();
207 #ifdef FORWARD_AVERAGE
208  int iAvgFwRange = iAvgRange/FORWARD_AVERAGE_FACTOR;
209 #else
210  int iAvgFwRange = 0;
211 #endif
212  for (int iUpdateTime = std::max(iAveragedTime-iAvgFwRange-1, iStartTime); iUpdateTime < iTime; ++iUpdateTime)
213  {
214  ValueType iSum=0, iSumWeight=0, iWeight;
215  for (int iSumTime = std::max(iUpdateTime - iAvgRange, iStartTime); iSumTime < std::min(iUpdateTime + iAvgFwRange+1, iTime); ++iSumTime)
216  {
217  iWeight = (ValueType) iAvgRange - Abs(iUpdateTime - iSumTime) + 1;
218  iSum += GetAtValue(iSumTime) * iWeight;
219  iSumWeight += iWeight;
220  }
221  SetAvgValue(iUpdateTime, iSum / iSumWeight);
222  }
223  // now it's all up-to-date
224  iAveragedTime = iTime;
225 }
226 
227 // --------------------------------------------------
228 
230 {
231  const_iterator i = begin(); if (i == end()) return 0;
232  C4Graph::TimeType iTime = (*i)->GetStartTime();
233  while (++i != end()) iTime = std::min(iTime, (*i)->GetStartTime());
234  return iTime;
235 }
236 
238 {
239  const_iterator i = begin(); if (i == end()) return 0;
240  C4Graph::TimeType iTime = (*i)->GetEndTime();
241  while (++i != end()) iTime = std::max(iTime, (*i)->GetEndTime());
242  return iTime;
243 }
244 
246 {
247  const_iterator i = begin(); if (i == end()) return 0;
248  C4Graph::ValueType iVal = (*i)->GetMinValue();
249  while (++i != end()) iVal = std::min(iVal, (*i)->GetMinValue());
250  return iVal;
251 }
252 
254 {
255  const_iterator i = begin(); if (i == end()) return 0;
256  C4Graph::ValueType iVal = (*i)->GetMaxValue();
257  while (++i != end()) iVal = std::max(iVal, (*i)->GetMaxValue());
258  return iVal;
259 }
260 
262 {
263  int iCount = 0;
264  for (auto i : *this) iCount += i->GetSeriesCount();
265  return iCount;
266 }
267 
268 const C4Graph *C4GraphCollection::GetSeries(int iIndex) const
269 {
270  for (auto i : *this)
271  {
272  int iCnt = i->GetSeriesCount();
273  if (iIndex < iCnt) return i->GetSeries(iIndex);
274  iIndex -= iCnt;
275  }
276  return nullptr;
277 }
278 
280 {
281  // update all child graphs
282  for (auto i : *this) i->Update();
283 }
284 
286 {
287  if ((iCommonAvgTime = iToTime))
288  for (auto & i : *this) i->SetAverageTime(iToTime);
289 }
290 
292 {
293  if ((fMultiplier = fToVal))
294  for (auto & i : *this) i->SetMultiplier(fToVal);
295 }
296 
297 
298 // --------------------------------------------------------------------------------
299 
301 {
302  // init callback timer
303  Application.Add(this);
304  SecondCounter = 0;
305  ControlCounter = 0;
306  // init graphs
307  statObjCount.SetTitle(LoadResStr("IDS_MSG_OBJCOUNT"));
308  statFPS.SetTitle(LoadResStr("IDS_MSG_FPS"));
309  statNetI.SetTitle(LoadResStr("IDS_NET_INPUT"));
310  statNetI.SetColorDw(0x00ff00);
311  statNetO.SetTitle(LoadResStr("IDS_NET_OUTPUT"));
312  statNetO.SetColorDw(0xff0000);
313  graphNetIO.AddGraph(&statNetI); graphNetIO.AddGraph(&statNetO);
314  statControls.SetTitle(LoadResStr("IDS_NET_CONTROL"));
316  statActions.SetTitle(LoadResStr("IDS_NET_APM"));
318  for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next) pPlr->CreateGraphs();
319  C4Network2Client *pClient = nullptr;
320  while ((pClient = ::Network.Clients.GetNextClient(pClient))) pClient->CreateGraphs();
321 }
322 
324 {
325  for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next) pPlr->ClearGraphs();
326  C4Network2Client *pClient = nullptr;
327  while ((pClient = ::Network.Clients.GetNextClient(pClient))) pClient->ClearGraphs();
328  Application.Remove(this);
329 }
330 
332 {
334 }
335 
337 {
341  // pings for all clients
342  C4Network2Client *pClient = nullptr;
343  while ((pClient = ::Network.Clients.GetNextClient(pClient))) if (pClient->getStatPing())
344  {
345  int iPing=0;
346  C4Network2IOConnection *pConn = pClient->getMsgConn();
347  if (pConn) iPing = pConn->getLag();
348  pClient->getStatPing()->RecordValue(C4Graph::ValueType(iPing));
349  }
350  ++SecondCounter;
351 }
352 
354 {
355  // control rate may have updated: always convert values to actions per minute
358  // register and reset control counts for all players
359  for (C4Player *pPlr = ::Players.First; pPlr; pPlr = pPlr->Next)
360  {
361  if (pPlr->pstatControls)
362  {
363  pPlr->pstatControls->RecordValue(C4Graph::ValueType(pPlr->ControlCount));
364  pPlr->ControlCount = 0;
365  }
366  if (pPlr->pstatActions)
367  {
368  pPlr->pstatActions->RecordValue(C4Graph::ValueType(pPlr->ActionCount));
369  pPlr->ActionCount = 0;
370  }
371  }
372  ++ControlCounter;
373 }
374 
375 C4Graph *C4Network2Stats::GetGraphByName(const StdStrBuf &rszName, bool &rfIsTemp)
376 {
377  // compare against default graph names
378  rfIsTemp = false;
379  if (SEqualNoCase(rszName.getData(), "oc")) return &statObjCount;
380  if (SEqualNoCase(rszName.getData(), "fps")) return &statFPS;
381  if (SEqualNoCase(rszName.getData(), "netio")) return &graphNetIO;
382  if (SEqualNoCase(rszName.getData(), "pings")) return &statPings;
383  if (SEqualNoCase(rszName.getData(), "control")) return &statControls;
384  if (SEqualNoCase(rszName.getData(), "apm")) return &statActions;
385  // no match
386  return nullptr;
387 }
388 
389 // MassGraph.SetDumpFile(StdStrBuf("C:\\test.txt"));
C4GameControl Control
C4Game Game
Definition: C4Globals.cpp:52
C4Application Application
Definition: C4Globals.cpp:44
C4GameObjects Objects
Definition: C4Globals.cpp:48
C4Network2 Network
Definition: C4Globals.cpp:53
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
@ P_UDP
Definition: C4Network2IO.h:31
@ P_TCP
Definition: C4Network2IO.h:31
#define FORWARD_AVERAGE_FACTOR
C4PlayerList Players
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
T Abs(T val)
Definition: Standard.h:42
bool Inside(T ival, U lbound, V rbound)
Definition: Standard.h:43
int iCnt
Definition: TstC4NetIO.cpp:32
int32_t ControlRate
Definition: C4GameControl.h:88
int32_t FPS
Definition: C4Game.h:111
void Update() const override
C4Graph::TimeType GetStartTime() const override
void SetAverageTime(int iToTime) override
int GetSeriesCount() const override
C4Graph::ValueType GetMaxValue() const override
C4Graph::TimeType GetEndTime() const override
void AddGraph(C4Graph *pAdd)
C4Graph::ValueType GetMinValue() const override
const C4Graph * GetSeries(int iIndex) const override
void SetMultiplier(ValueType fToVal) override
void SetTitle(const char *szNewTitle)
void SetColorDw(DWORD dwClr)
float ValueType
class C4TableGraph * getStatPing() const
C4Network2IOConnection * getMsgConn() const
C4Network2Client * GetNextClient(C4Network2Client *pClient)
C4Network2IO NetIO
Definition: C4Network2.h:110
C4Network2ClientList Clients
Definition: C4Network2.h:116
int getProtORate(C4Network2IOProtocol eProt) const
Definition: C4Network2IO.h:154
int getProtIRate(C4Network2IOProtocol eProt) const
Definition: C4Network2IO.h:153
~C4Network2Stats() override
C4GraphCollection statPings
C4GraphCollection statControls
C4GraphCollection statActions
C4Graph * GetGraphByName(const StdStrBuf &rszName, bool &rfIsTemp)
int ObjectCount(C4ID id=C4ID::None) const
C4Player * Next
Definition: C4Player.h:142
C4Player * First
Definition: C4PlayerList.h:31
void SetAvgValue(TimeType iAtTime, ValueType iValue) const
ValueType GetAtValue(TimeType iAtTime) const
void Reset(TimeType iToTime)
void RecordValue(ValueType iValue)
ValueType GetMedianValue(TimeType iStartTime, TimeType iEndTime) const override
C4TableGraph(int iBackLogLength=DefaultBlockLength, TimeType iStartTime=0)
virtual bool DumpToFile(const StdStrBuf &rszFilename, bool fAppend) const
ValueType GetMaxValue() const override
TimeType GetStartTime() const override
TimeType GetEndTime() const override
ValueType GetValue(TimeType iAtTime) const override
~C4TableGraph() override
void SetAverageTime(int iToTime) override
void Update() const override
ValueType GetMinValue() const override
bool Create(const char *szFileName, bool fCompressed=false, bool fExecutable=false, bool fMemory=false)
Definition: CStdFile.cpp:49
bool WriteString(const char *szStr)
Definition: CStdFile.cpp:264
bool Append(const char *szFilename, bool text=false)
Definition: CStdFile.cpp:132
void Remove(StdSchedulerProc *pProc)
void Add(StdSchedulerProc *pProc)
const char * getData() const
Definition: StdBuf.h:442
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174