Upstream tarball 20080902
[amule.git] / src / Statistics.cpp
blob40b5e2ac6f32509f74633a6624b6b502e4a8b3e8
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 // Copyright (c) 2005-2008 Dévai Tamás ( gonosztopi@amule.org )
7 //
8 // Any parts of this program derived from the xMule, lMule or eMule project,
9 // or contributed by third-party developers are copyrighted by their
10 // respective authors.
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 2 of the License, or
15 // (at your option) any later version.
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 #include "Statistics.h" // Interface declarations
29 #include <protocol/ed2k/ClientSoftware.h>
31 #include <ec/cpp/ECTag.h> // Needed for CECTag
33 #ifndef EC_REMOTE
34 #ifndef AMULE_DAEMON
35 #include <common/Format.h> // Needed for CFormat
36 #endif
37 #include "DataToText.h" // Needed for GetSoftName()
38 #include "Preferences.h" // Needed for thePrefs
39 #include "amule.h" // Needed for theApp
40 #include "ListenSocket.h" // (tree, GetAverageConnections)
41 #include "ServerList.h" // Needed for CServerList (tree)
42 #include <cmath> // Needed for std::floor
43 #include "updownclient.h" // Needed for CUpDownClient
44 #include "DownloadQueue.h" // Needed for theApp->downloadqueue
45 #else
46 #include "Preferences.h"
47 #include <ec/cpp/RemoteConnect.h> // Needed for CRemoteConnect
48 #endif
50 #include <wx/intl.h>
52 #ifdef __BSD__
53 // glibc -> bsd libc
54 #define round rint
55 #else
56 #define round(x) floor(x+0.5)
57 #endif /* __BSD__ */
60 #ifndef EC_REMOTE
62 /*----- CPreciseRateCounter -----*/
64 void CPreciseRateCounter::CalculateRate(uint64_t now)
66 wxMutexLocker lock(m_mutex);
68 m_total += m_tmp_sum;
69 m_byte_history.push_back(m_tmp_sum);
70 m_tick_history.push_back(now);
71 m_tmp_sum = 0;
73 uint64_t timespan = now - m_tick_history.front();
75 // Checking maximal timespan, but make sure not to remove
76 // the extra node in m_tick_history.
77 while (timespan > m_timespan && m_byte_history.size() > 0) {
78 m_total -= m_byte_history.front();
79 m_byte_history.pop_front();
80 m_tick_history.pop_front();
81 timespan = now - m_tick_history.front();
84 // Count rate/average
85 if (m_count_average) {
86 if (m_byte_history.size() > 0) {
87 m_rate = m_total / (double)m_byte_history.size();
89 } else {
90 if (timespan > 0) {
91 m_rate = m_total / (timespan / 1000.0);
95 if (m_rate > m_max_rate) {
96 m_max_rate = m_rate;
101 /*----- CStatTreeItemRateCounter -----*/
103 #ifndef AMULE_DAEMON
104 wxString CStatTreeItemRateCounter::GetDisplayString() const
106 return CFormat(wxGetTranslation(m_label)) % CastItoSpeed(m_show_maxrate ? (uint32)m_max_rate : (uint32)m_rate);
108 #endif
110 void CStatTreeItemRateCounter::AddECValues(CECTag* tag) const
112 CECTag value(EC_TAG_STAT_NODE_VALUE, m_show_maxrate ? (uint32)m_max_rate : (uint32)m_rate);
113 value.AddTag(CECTag(EC_TAG_STAT_VALUE_TYPE, (uint8)EC_VALUE_SPEED));
114 tag->AddTag(value);
118 /*----- CStatTreeItemPeakConnections -----*/
120 #ifndef AMULE_DAEMON
121 wxString CStatTreeItemPeakConnections::GetDisplayString() const
123 return wxString::Format(wxGetTranslation(m_label), theStats::GetPeakConnections());
125 #endif
127 void CStatTreeItemPeakConnections::AddECValues(CECTag* tag) const
129 tag->AddTag(CECTag(EC_TAG_STAT_NODE_VALUE, (uint64)theStats::GetPeakConnections()));
133 /*----- CStatistics -----*/
135 // Static variables
137 // Rate counters
138 CPreciseRateCounter* CStatistics::s_upOverheadRate;
139 CPreciseRateCounter* CStatistics::s_downOverheadRate;
140 CStatTreeItemRateCounter* CStatistics::s_uploadrate;
141 CStatTreeItemRateCounter* CStatistics::s_downloadrate;
143 #else /* EC_REMOTE */
145 uint64 CStatistics::s_start_time;
146 uint64 CStatistics::s_statData[sdTotalItems];
148 #endif /* !EC_REMOTE / EC_REMOTE */
150 // Tree root
151 CStatTreeItemBase* CStatistics::s_statTree;
153 #ifndef EC_REMOTE
154 // Uptime
155 CStatTreeItemTimer* CStatistics::s_uptime;
157 // Upload
158 CStatTreeItemUlDlCounter* CStatistics::s_sessionUpload;
159 CStatTreeItemPacketTotals* CStatistics::s_totalUpOverhead;
160 CStatTreeItemPackets* CStatistics::s_fileReqUpOverhead;
161 CStatTreeItemPackets* CStatistics::s_sourceXchgUpOverhead;
162 CStatTreeItemPackets* CStatistics::s_serverUpOverhead;
163 CStatTreeItemPackets* CStatistics::s_kadUpOverhead;
164 CStatTreeItemCounter* CStatistics::s_cryptUpOverhead;
165 CStatTreeItemNativeCounter* CStatistics::s_activeUploads;
166 CStatTreeItemNativeCounter* CStatistics::s_waitingUploads;
167 CStatTreeItemCounter* CStatistics::s_totalSuccUploads;
168 CStatTreeItemCounter* CStatistics::s_totalFailedUploads;
169 CStatTreeItemCounter* CStatistics::s_totalUploadTime;
171 // Download
172 CStatTreeItemUlDlCounter* CStatistics::s_sessionDownload;
173 CStatTreeItemPacketTotals* CStatistics::s_totalDownOverhead;
174 CStatTreeItemPackets* CStatistics::s_fileReqDownOverhead;
175 CStatTreeItemPackets* CStatistics::s_sourceXchgDownOverhead;
176 CStatTreeItemPackets* CStatistics::s_serverDownOverhead;
177 CStatTreeItemPackets* CStatistics::s_kadDownOverhead;
178 CStatTreeItemCounter* CStatistics::s_cryptDownOverhead;
179 CStatTreeItemNativeCounter* CStatistics::s_foundSources;
180 CStatTreeItemNativeCounter* CStatistics::s_activeDownloads;
181 float CStatistics::s_downloadRateAdjust = 1.0;
183 // Connection
184 CStatTreeItemReconnects* CStatistics::s_reconnects;
185 CStatTreeItemTimer* CStatistics::s_sinceFirstTransfer;
186 CStatTreeItemTimer* CStatistics::s_sinceConnected;
187 CStatTreeItemCounterMax* CStatistics::s_activeConnections;
188 CStatTreeItemMaxConnLimitReached* CStatistics::s_limitReached;
189 CStatTreeItemSimple* CStatistics::s_avgConnections;
191 // Clients
192 CStatTreeItemHiddenCounter* CStatistics::s_clients;
193 CStatTreeItemCounter* CStatistics::s_unknown;
194 //CStatTreeItem CStatistics::s_lowID;
195 //CStatTreeItem CStatistics::s_secIdentOnOff;
196 #ifdef __DEBUG__
197 CStatTreeItemNativeCounter* CStatistics::s_hasSocket;
198 #endif
199 CStatTreeItemNativeCounter* CStatistics::s_filtered;
200 CStatTreeItemNativeCounter* CStatistics::s_banned;
202 // Servers
203 CStatTreeItemSimple* CStatistics::s_workingServers;
204 CStatTreeItemSimple* CStatistics::s_failedServers;
205 CStatTreeItemNativeCounter* CStatistics::s_totalServers;
206 CStatTreeItemNativeCounter* CStatistics::s_deletedServers;
207 CStatTreeItemNativeCounter* CStatistics::s_filteredServers;
208 CStatTreeItemSimple* CStatistics::s_usersOnWorking;
209 CStatTreeItemSimple* CStatistics::s_filesOnWorking;
210 CStatTreeItemSimple* CStatistics::s_totalUsers;
211 CStatTreeItemSimple* CStatistics::s_totalFiles;
212 CStatTreeItemSimple* CStatistics::s_serverOccupation;
214 // Shared files
215 CStatTreeItemCounter* CStatistics::s_numberOfShared;
216 CStatTreeItemCounter* CStatistics::s_sizeOfShare;
218 // Kad
219 uint64 CStatistics::s_kadNodesTotal;
220 uint16 CStatistics::s_kadNodesCur;
223 CStatistics::CStatistics()
224 : m_graphRunningAvgDown(thePrefs::GetStatsAverageMinutes() * 60 * 1000, true),
225 m_graphRunningAvgUp(thePrefs::GetStatsAverageMinutes() * 60 * 1000, true),
226 m_graphRunningAvgKad(thePrefs::GetStatsAverageMinutes() * 60 * 1000, true)
228 uint64 start_time = GetTickCount64();
230 // Init graphs
232 average_minutes = thePrefs::GetStatsAverageMinutes();
234 HR hr = {0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0};
235 hrInit = hr;
236 nHistRanges = 7; // =ceil(log(max_update_delay)/log(2))
237 nPointsPerRange = GetPointsPerRange();
238 bitsHistClockMask = (1 << (nHistRanges-1)) - 1;
239 aposRecycle = new listPOS[nHistRanges];
240 listPOS *ppos = aposRecycle+nHistRanges-1;
241 for (int i=nHistRanges; i>0; --i) { // permanently allocated history list
242 listHR.push_back(hr);
243 *ppos-- = --listHR.end();
244 for (int j=nPointsPerRange; j>1; --j)
245 listHR.push_back(hr);
248 // Init rate counters outside the tree
250 s_upOverheadRate = new CPreciseRateCounter(5000);
251 s_downOverheadRate = new CPreciseRateCounter(5000);
253 // Init Tree
255 InitStatsTree();
256 s_uptime->SetStartTime(start_time);
260 CStatistics::~CStatistics()
262 // clearing listHR frees the memory occupied by the nodes
263 listHR.clear();
264 delete [] aposRecycle;
266 delete s_statTree;
268 // delete items not in the tree
269 delete s_totalUploadTime;
271 // delete rate counters outside the tree
272 delete s_upOverheadRate;
273 delete s_downOverheadRate;
277 void CStatistics::CalculateRates()
279 uint64_t now = GetTickCount64();
280 s_downOverheadRate->CalculateRate(now);
281 s_upOverheadRate->CalculateRate(now);
282 s_downloadrate->CalculateRate(now);
283 s_uploadrate->CalculateRate(now);
284 // calculate rate adjustment, so that the sum of all file download rates
285 // matches the total transfer rate
286 float dlspeedfiles = theApp->downloadqueue->GetDownloadingFileRate();
287 float dlspeedall = s_downloadrate->GetRate();
288 s_downloadRateAdjust = (dlspeedfiles < 1.0 || dlspeedall < 1.0) ? 1.0 : dlspeedall/dlspeedfiles;
292 /* ------------------------------- GRAPHS ---------------------------- */
295 History List
297 The basic idea here is that we want to keep as much history as we can without paying
298 a high price in terms of memory space. Because we keep the history for display purposes,
299 we can take advantage of the fact that when the period shown in the graphs is long
300 then each pixel represents a long period. So as the most recent history we keep one
301 window full of points at a resolution of 1 second, the next window full at 2 seconds,
302 the next at 4 seconds and so on, up to the maximum desired. This way there is always
303 at least one sample point per pixel for any update delay set by the user, and the
304 memory required grows with the *log* of the total time period covered.
305 The history is kept in a doubly-linked list, with the most recent snapshot at the tail.
306 The number of nodes in the list is fixed, and there are no calls to RemoveHead() and
307 AddTail() which would add overhead and contribute to memory fragmentation. Instead,
308 every second when a new point gets recorded, one of the existing nodes is recycled;
309 it is disjoined from its present place, put at the tail of the list, and then gets
310 filled with new data. [Emilio Sandoz]
311 This unfortunately does not work with stl classes, as none of them supports moving
312 a node to another place, so we have to erase and re-add nodes.
315 void CStatistics::RecordHistory()
316 { // First we query and compute some values, then we store them in the history list
318 // A few comments about the use of double and float in computations:
319 // Even on a hi-res screen our graphs will have 10 bits of resolution at most,
320 // so the 24 bits resolution of a float on 32 bit Intel processors is more than
321 // enough for all displayed values. Rate computations however, and especially
322 // running average computations, use differences (delta bytes/ delta time), and
323 // for long uptimes the difference between two timestamps can lose too much
324 // accuracy because the large mantissa causes less significant bits to be dropped
325 // (same for the difference between two cumulative byte counts). [We don't store
326 // these values as integers because they will be used in floating point calculations,
327 // and we want to perform the conversion only once). Therefore timestamps and
328 // Kbyte counts are stored in the history as doubles, while computed values use
329 // float (to save space and execution time).
332 Store values; first determine the node to be recycled (using the bits in iClock)
334 oldest records ----------------- listHR ------------------ youngest records
336 O-[Range 2^n sec]-O- ... -O-[Range 4 sec]-O-[Range 2 sec]-O-[Range 1 sec]-O
337 | | | | > every 2 secs -^
338 | | ... | >--------------- every 4 secs -^
339 | | >------------------------ recycle every 8 secs -^
340 | | ...
341 | >-the node at this position is recycled every 2^n secs -^
342 >-------------------(ditto for the oldest node at the head of the list) --^
344 aposRecycle[nHistRanges-1] ... aposRecycle[0] Tail
346 listPOS *ppos;
347 static int iClock;
348 int iClockPrev = iClock++;
349 int bits = (iClockPrev^iClock) & iClock; // identify the highest changed bit
350 if (bits <= bitsHistClockMask) {
351 ppos = aposRecycle;
352 while ((bits /= 2) != 0) // count to the highest bit that was just toggled to 1
353 ++ppos;
354 // recycle one node and jump over the next to move it to the next higher range
355 listHR.push_back(**ppos);
356 *ppos = ++listHR.erase(*ppos);
357 } else {
358 ppos = aposRecycle+nHistRanges-1;
359 // recycle the node at the head; there is no higher range to move nodes into
360 listHR.push_back(**ppos);
361 *ppos = listHR.erase(*ppos);
364 // now save the latest data point in this node
365 listPOS phr = --listHR.end();
366 phr->kBytesSent = GetSessionSentBytes() / 1024.0;
367 phr->kBytesReceived = GetSessionReceivedBytes() / 1024.0;
368 phr->kBpsUpCur = GetUploadRate() / 1024.0;
369 phr->kBpsDownCur = GetDownloadRate() / 1024.0;
370 phr->cntUploads = GetActiveUploadsCount();
371 phr->cntConnections = GetActiveConnections();
372 phr->cntDownloads = GetDownloadingSources();
373 phr->sTimestamp = GetUptimeMillis() / 1000.0;
375 s_kadNodesTotal += s_kadNodesCur;
376 phr->kadNodesTotal = s_kadNodesTotal;
377 phr->kadNodesCur = s_kadNodesCur;
381 unsigned CStatistics::GetHistory( // Assemble arrays of sample points for a graph
382 unsigned cntPoints, // number of sample points to assemble
383 double sStep, // time difference between sample points
384 double sFinal, // latest allowed timestamp
385 const std::vector<float *> &ppf,// an array of pointers to arrays of floats for the result
386 StatsGraphType which_graph) // the graph which will receive the points
388 if (sStep==0.0 || cntPoints==0) {
389 return(0);
392 float *pf1 = ppf[0];
393 float *pf2 = ppf[1];
394 float *pf3 = ppf[2];
395 unsigned cntFilled = 0;
396 listRPOS pos = listHR.rbegin();
398 // start of list should be an integer multiple of the sampling period for samples
399 // to be consistent when the graphs are resized horizontally
400 double sTarget;
401 if (sFinal >= 0.0) {
402 sTarget = sFinal;
403 } else {
404 sTarget = sStep==1.0 ?
405 pos->sTimestamp :
406 std::floor(pos->sTimestamp/sStep) * sStep;
409 HR **ahr = NULL, **pphr = NULL;
410 bool bRateGraph = (which_graph != GRAPH_CONN); // rate graph or connections graph?
411 if (bRateGraph) {
412 ahr = new HR* [cntPoints];
413 pphr = ahr;
416 while (pos != listHR.rend()) {
417 if (pos->sTimestamp > sTarget) {
418 ++pos;
419 continue;
421 if (bRateGraph) { // assemble an array of pointers for ComputeAverages
422 *pphr++ = &(*pos);
423 } else { // or build the arrays if possible
424 *pf1++ = (float)pos->cntUploads;
425 *pf2++ = (float)pos->cntConnections;
426 *pf3++ = (float)pos->cntDownloads;
428 if (++cntFilled == cntPoints) { // enough points
429 break;
431 if (pos->sTimestamp == 0.0) { // reached beginning of uptime
432 break;
434 if ((sTarget -= sStep) <= 0.0) { // don't overshoot the beginning
435 if (bRateGraph) {
436 *pphr++ = &hrInit;
437 } else {
438 *pf1++ = *pf2++ = *pf3++ = 0.0;
440 ++cntFilled;
441 break;
445 if (bRateGraph) {
446 if (cntFilled > 0) {
447 ComputeAverages(pphr, pos, cntFilled, sStep, ppf, which_graph);
449 delete[] ahr;
452 return cntFilled;
456 unsigned CStatistics::GetHistoryForWeb( // Assemble arrays of sample points for the webserver
457 unsigned cntPoints, // maximum number of sample points to assemble
458 double sStep, // time difference between sample points
459 double *sStart, // earliest allowed timestamp
460 uint32 **graphData) // a pointer to a pointer that will point to the graph data array
462 if (*sStart < 0.0) {
463 *sStart = 0.0;
465 if (sStep==0.0 || cntPoints==0)
466 return(0);
467 unsigned cntFilled = 0;
468 listRPOS pos = listHR.rbegin();
469 double LastTimeStamp = pos->sTimestamp;
470 double sTarget = LastTimeStamp;
472 HR **pphr = new HR *[cntPoints];
474 while (pos != listHR.rend()) {
475 if (pos->sTimestamp > sTarget) {
476 ++pos; // find next history record
477 continue;
479 pphr[cntFilled] = &(*pos);
480 if (++cntFilled == cntPoints) // enough points
481 break;
482 if (pos->sTimestamp <= *sStart) // reached beginning of requested time
483 break;
484 if ((sTarget -= sStep) <= 0.0) { // don't overshoot the beginning
485 pphr[cntFilled++] = NULL;
486 break;
490 if (cntFilled) {
491 *graphData = new uint32 [4 * cntFilled];
492 if (*graphData) {
493 for (unsigned int i = 0; i < cntFilled; i++) {
494 HR *phr = pphr[cntFilled - i - 1];
495 if (phr) {
496 (*graphData)[4 * i ] = ENDIAN_HTONL((uint32)(phr->kBpsDownCur * 1024.0));
497 (*graphData)[4 * i + 1] = ENDIAN_HTONL((uint32)(phr->kBpsUpCur * 1024.0));
498 (*graphData)[4 * i + 2] = ENDIAN_HTONL((uint32)phr->cntConnections);
499 (*graphData)[4 * i + 3] = ENDIAN_HTONL((uint32)phr->kadNodesCur);
500 } else {
501 (*graphData)[4 * i] = (*graphData)[4 * i + 1] = 0;
502 (*graphData)[4 * i + 2] = (*graphData)[4 * i + 3] = 0;
506 } else {
507 *graphData = NULL;
510 delete [] pphr;
512 *sStart = LastTimeStamp;
514 return cntFilled;
518 void CStatistics::ComputeAverages(
519 HR **pphr, // pointer to (end of) array of assembled history records
520 listRPOS pos, // position in history list from which to backtrack
521 unsigned cntFilled, // number of points in the sample data
522 double sStep, // time difference between two samples
523 const std::vector<float *> &ppf,// an array of pointers to arrays of floats with sample data
524 StatsGraphType which_graph) // the graph which will receive the points
526 double sTarget, kValueRun;
527 uint64 avgTime = average_minutes * 60;
528 unsigned nBtPoints = (unsigned)(avgTime / sStep);
530 CPreciseRateCounter* runningAvg = NULL;
531 switch (which_graph) {
532 case GRAPH_DOWN: runningAvg = &m_graphRunningAvgDown; break;
533 case GRAPH_UP: runningAvg = &m_graphRunningAvgUp; break;
534 case GRAPH_KAD: runningAvg = &m_graphRunningAvgKad; break;
535 default:
536 wxCHECK_RET(false, wxT("ComputeAverages called with unsupported graph type."));
539 runningAvg->m_timespan = avgTime * 1000;
540 runningAvg->m_tick_history.clear();
541 runningAvg->m_byte_history.clear();
542 runningAvg->m_total = 0;
543 runningAvg->m_tmp_sum = 0;
545 if (pos == listHR.rend()) {
546 sTarget = 0.0;
547 } else {
548 sTarget = std::max(0.0, pos->sTimestamp - sStep);
551 while (nBtPoints--) {
552 while (pos != listHR.rend() && pos->sTimestamp > sTarget) ++pos; // find next history record
553 if (pos != listHR.rend()) {
554 runningAvg->m_tick_history.push_front((uint64)(pos->sTimestamp * 1000.0));
556 uint32 value = 0;
557 switch (which_graph) {
558 case GRAPH_DOWN:
559 value = (uint32)(pos->kBpsDownCur * 1024.0);
560 break;
561 case GRAPH_UP:
562 value = (uint32)(pos->kBpsUpCur * 1024.0);
563 break;
564 case GRAPH_KAD:
565 value = (uint32)(pos->kadNodesCur * 1024.0);
566 break;
567 default:
568 wxCHECK_RET(false, wxT("ComputeAverages called with unsupported graph type."));
571 runningAvg->m_byte_history.push_front(value);
572 runningAvg->m_total += value;
573 } else {
574 break;
576 if ((sTarget -= sStep) < 0.0) {
577 break;
581 // now compute averages in returned arrays, starting with the earliest values
582 float *pf1 = ppf[0] + cntFilled - 1; // holds session avg
583 float *pf2 = ppf[1] + cntFilled - 1; // holds running avg
584 float *pf3 = ppf[2] + cntFilled - 1; // holds current rate
586 for (int cnt=cntFilled; cnt>0; cnt--, pf1--, pf2--, pf3--) {
587 HR *phr = *(--pphr);
588 if (which_graph == GRAPH_DOWN) {
589 kValueRun = phr->kBytesReceived;
590 *pf3 = phr->kBpsDownCur;
591 } else if (which_graph == GRAPH_UP) {
592 kValueRun = phr->kBytesSent;
593 *pf3 = phr->kBpsUpCur;
594 } else {
595 kValueRun = phr->kadNodesTotal;
596 *pf3 = phr->kadNodesCur;
599 *pf1 = kValueRun / phr->sTimestamp;
600 (*runningAvg) += (uint32)(*pf3 * 1024.0);
601 runningAvg->CalculateRate((uint64)(phr->sTimestamp * 1000.0));
602 *pf2 = (float)(runningAvg->GetRate() / 1024.0);
607 GraphUpdateInfo CStatistics::GetPointsForUpdate()
609 GraphUpdateInfo update;
610 listPOS phr = --listHR.end();
611 update.timestamp = (double) phr->sTimestamp;
613 m_graphRunningAvgDown += (uint32)(phr->kBpsDownCur * 1024.0);
614 m_graphRunningAvgUp += (uint32)(phr->kBpsUpCur * 1024.0);
615 // Note: kadNodesCur is multiplied by 1024 since the value is done
616 // in other places, so we simply follow suit here to avoid trouble.
617 m_graphRunningAvgKad += (uint32)(phr->kadNodesCur * 1024.0);
618 m_graphRunningAvgDown.CalculateRate((uint64)(phr->sTimestamp * 1000.0));
619 m_graphRunningAvgUp.CalculateRate((uint64)(phr->sTimestamp * 1000.0));
620 m_graphRunningAvgKad.CalculateRate((uint64)(phr->sTimestamp * 1000.0));
622 update.downloads[0] = phr->kBytesReceived / phr->sTimestamp;
623 update.downloads[1] = m_graphRunningAvgDown.GetRate() / 1024.0;
624 update.downloads[2] = phr->kBpsDownCur;
626 update.uploads[0] = phr->kBytesSent / phr->sTimestamp;
627 update.uploads[1] = m_graphRunningAvgUp.GetRate() / 1024.0;
628 update.uploads[2] = phr->kBpsUpCur;
630 update.connections[0] = (float)phr->cntUploads;
631 update.connections[1] = (float)phr->cntConnections;
632 update.connections[2] = (float)phr->cntDownloads;
634 update.kadnodes[0] = phr->kadNodesTotal / phr->sTimestamp;
635 update.kadnodes[1] = m_graphRunningAvgKad.GetRate() / 1024.0;
636 update.kadnodes[2] = phr->kadNodesCur;
638 return update;
642 /* ------------------------------- TREE ---------------------------- */
644 void CStatistics::InitStatsTree()
646 s_statTree = new CStatTreeItemBase(wxTRANSLATE("Statistics"));
648 CStatTreeItemBase* tmpRoot1;
649 CStatTreeItemBase* tmpRoot2;
651 s_uptime = (CStatTreeItemTimer*)s_statTree->AddChild(new CStatTreeItemTimer(wxTRANSLATE("Uptime: %s")));
653 tmpRoot1 = s_statTree->AddChild(new CStatTreeItemBase(wxTRANSLATE("Transfer"), stSortChildren));
655 tmpRoot2 = tmpRoot1->AddChild(new CStatTreeItemBase(wxTRANSLATE("Uploads")), 2);
656 s_sessionUpload = (CStatTreeItemUlDlCounter*)tmpRoot2->AddChild(new CStatTreeItemUlDlCounter(wxTRANSLATE("Uploaded Data (Session (Total)): %s"), thePrefs::GetTotalUploaded, stSortChildren | stSortByValue));
657 // Children will be added on-the-fly
658 s_totalUpOverhead = (CStatTreeItemPacketTotals*)tmpRoot2->AddChild(new CStatTreeItemPacketTotals(wxTRANSLATE("Total Overhead (Packets): %s")));
659 s_fileReqUpOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("File Request Overhead (Packets): %s")));
660 s_totalUpOverhead->AddPacketCounter(s_fileReqUpOverhead);
661 s_sourceXchgUpOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("Source Exchange Overhead (Packets): %s")));
662 s_totalUpOverhead->AddPacketCounter(s_sourceXchgUpOverhead);
663 s_serverUpOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("Server Overhead (Packets): %s")));
664 s_totalUpOverhead->AddPacketCounter(s_serverUpOverhead);
665 s_kadUpOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("Kad Overhead (Packets): %s")));
666 s_totalUpOverhead->AddPacketCounter(s_kadUpOverhead);
667 s_cryptUpOverhead = (CStatTreeItemCounter*)tmpRoot2->AddChild(new CStatTreeItemCounter(wxTRANSLATE("Crypt overhead (UDP): %s")));
668 s_cryptUpOverhead->SetDisplayMode(dmBytes);
669 s_activeUploads = (CStatTreeItemNativeCounter*)tmpRoot2->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Active Uploads: %s")));
670 s_waitingUploads = (CStatTreeItemNativeCounter*)tmpRoot2->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Waiting Uploads: %s")));
671 s_totalSuccUploads = (CStatTreeItemCounter*)tmpRoot2->AddChild(new CStatTreeItemCounter(wxTRANSLATE("Total successful upload sessions: %s")));
672 s_totalFailedUploads = (CStatTreeItemCounter*)tmpRoot2->AddChild(new CStatTreeItemCounter(wxTRANSLATE("Total failed upload sessions: %s")));
673 s_totalUploadTime = new CStatTreeItemCounter(wxEmptyString);
674 tmpRoot2->AddChild(new CStatTreeItemAverage(wxTRANSLATE("Average upload time: %s"), s_totalUploadTime, s_totalSuccUploads, dmTime));
676 tmpRoot2 = tmpRoot1->AddChild(new CStatTreeItemBase(wxTRANSLATE("Downloads")), 1);
677 s_sessionDownload = (CStatTreeItemUlDlCounter*)tmpRoot2->AddChild(new CStatTreeItemUlDlCounter(wxTRANSLATE("Downloaded Data (Session (Total)): %s"), thePrefs::GetTotalDownloaded, stSortChildren | stSortByValue));
678 // Children will be added on-the-fly
679 s_totalDownOverhead = (CStatTreeItemPacketTotals*)tmpRoot2->AddChild(new CStatTreeItemPacketTotals(wxTRANSLATE("Total Overhead (Packets): %s")));
680 s_fileReqDownOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("File Request Overhead (Packets): %s")));
681 s_totalDownOverhead->AddPacketCounter(s_fileReqDownOverhead);
682 s_sourceXchgDownOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("Source Exchange Overhead (Packets): %s")));
683 s_totalDownOverhead->AddPacketCounter(s_sourceXchgDownOverhead);
684 s_serverDownOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("Server Overhead (Packets): %s")));
685 s_totalDownOverhead->AddPacketCounter(s_serverDownOverhead);
686 s_kadDownOverhead = (CStatTreeItemPackets*)tmpRoot2->AddChild(new CStatTreeItemPackets(wxTRANSLATE("Kad Overhead (Packets): %s")));
687 s_totalDownOverhead->AddPacketCounter(s_kadDownOverhead);
688 s_cryptDownOverhead = (CStatTreeItemCounter*)tmpRoot2->AddChild(new CStatTreeItemCounter(wxTRANSLATE("Crypt overhead (UDP): %s")));
689 s_cryptDownOverhead->SetDisplayMode(dmBytes);
690 s_foundSources = (CStatTreeItemNativeCounter*)tmpRoot2->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Found Sources: %s"), stSortChildren | stSortByValue));
691 s_activeDownloads = (CStatTreeItemNativeCounter*)tmpRoot2->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Active Downloads (chunks): %s")));
693 tmpRoot1->AddChild(new CStatTreeItemRatio(wxTRANSLATE("Session UL:DL Ratio (Total): %s"), s_sessionUpload, s_sessionDownload), 3);
695 tmpRoot1 = s_statTree->AddChild(new CStatTreeItemBase(wxTRANSLATE("Connection")));
696 tmpRoot1->AddChild(new CStatTreeItemAverageSpeed(wxTRANSLATE("Average download rate (Session): %s"), s_sessionDownload, s_uptime));
697 tmpRoot1->AddChild(new CStatTreeItemAverageSpeed(wxTRANSLATE("Average upload rate (Session): %s"), s_sessionUpload, s_uptime));
698 s_downloadrate = (CStatTreeItemRateCounter*)tmpRoot1->AddChild(new CStatTreeItemRateCounter(wxTRANSLATE("Max download rate (Session): %s"), true, 30000));
699 s_uploadrate = (CStatTreeItemRateCounter*)tmpRoot1->AddChild(new CStatTreeItemRateCounter(wxTRANSLATE("Max upload rate (Session): %s"), true, 30000));
700 s_reconnects = (CStatTreeItemReconnects*)tmpRoot1->AddChild(new CStatTreeItemReconnects(wxTRANSLATE("Reconnects: %i")));
701 s_sinceFirstTransfer = (CStatTreeItemTimer*)tmpRoot1->AddChild(new CStatTreeItemTimer(wxTRANSLATE("Time Since First Transfer: %s"), stHideIfZero));
702 s_sinceConnected = (CStatTreeItemTimer*)tmpRoot1->AddChild(new CStatTreeItemTimer(wxTRANSLATE("Connected To Server Since: %s")));
703 s_activeConnections = (CStatTreeItemCounterMax*)tmpRoot1->AddChild(new CStatTreeItemCounterMax(wxTRANSLATE("Active Connections (estimate): %i")));
704 s_limitReached = (CStatTreeItemMaxConnLimitReached*)tmpRoot1->AddChild(new CStatTreeItemMaxConnLimitReached(wxTRANSLATE("Max Connection Limit Reached: %s")));
705 s_avgConnections = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Average Connections (estimate): %g")));
706 s_avgConnections->SetValue(0.0);
707 tmpRoot1->AddChild(new CStatTreeItemPeakConnections(wxTRANSLATE("Peak Connections (estimate): %i")));
709 s_clients = (CStatTreeItemHiddenCounter*)s_statTree->AddChild(new CStatTreeItemHiddenCounter(wxTRANSLATE("Clients"), stSortChildren | stSortByValue));
710 s_unknown = (CStatTreeItemCounter*)s_clients->AddChild(new CStatTreeItemCounter(wxString(wxTRANSLATE("Unknown")) + wxT(": %s")), 6);
711 //s_lowID = (CStatTreeItem*)s_clients->AddChild(new CStatTreeItem(wxTRANSLATE("LowID: %u (%.2f%% Total %.2f%% Known)")), 5);
712 //s_secIdentOnOff = (CStatTreeItem*)s_clients->AddChild(new CStatTreeItem(wxTRANSLATE("SecIdent On/Off: %u (%.2f%%) : %u (%.2f%%)")), 4);
713 #ifdef __DEBUG__
714 s_hasSocket = (CStatTreeItemNativeCounter*)s_clients->AddChild(new CStatTreeItemNativeCounter(wxT("HasSocket: %s")), 3);
715 #endif
716 s_filtered = (CStatTreeItemNativeCounter*)s_clients->AddChild(new CStatTreeItemNativeCounter(wxString(wxTRANSLATE("Filtered")) + wxT(": %s")), 2);
717 s_banned = (CStatTreeItemNativeCounter*)s_clients->AddChild(new CStatTreeItemNativeCounter(wxString(wxTRANSLATE("Banned")) + wxT(": %s")), 1);
718 s_clients->AddChild(new CStatTreeItemTotalClients(wxTRANSLATE("Total: %i Known: %i"), s_clients, s_unknown), 0x80000000);
720 // TODO: Use counters?
721 tmpRoot1 = s_statTree->AddChild(new CStatTreeItemBase(wxTRANSLATE("Servers")));
722 s_workingServers = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Working Servers: %i")));
723 s_failedServers = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Failed Servers: %i")));
724 s_totalServers = (CStatTreeItemNativeCounter*)tmpRoot1->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Total: %s")));
725 s_deletedServers = (CStatTreeItemNativeCounter*)tmpRoot1->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Deleted Servers: %s")));
726 s_filteredServers = (CStatTreeItemNativeCounter*)tmpRoot1->AddChild(new CStatTreeItemNativeCounter(wxTRANSLATE("Filtered Servers: %s")));
727 s_usersOnWorking = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Users on Working Servers: %llu")));
728 s_filesOnWorking = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Files on Working Servers: %llu")));
729 s_totalUsers = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Total Users: %llu")));
730 s_totalFiles = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Total Files: %llu")));
731 s_serverOccupation = (CStatTreeItemSimple*)tmpRoot1->AddChild(new CStatTreeItemSimple(wxTRANSLATE("Server Occupation: %.2f%%")));
732 s_serverOccupation->SetValue(0.0);
734 tmpRoot1 = s_statTree->AddChild(new CStatTreeItemBase(wxTRANSLATE("Shared Files")));
735 s_numberOfShared = (CStatTreeItemCounter*)tmpRoot1->AddChild(new CStatTreeItemCounter(wxTRANSLATE("Number of Shared Files: %s")));
736 s_sizeOfShare = (CStatTreeItemCounter*)tmpRoot1->AddChild(new CStatTreeItemCounter(wxTRANSLATE("Total size of Shared Files: %s")));
737 s_sizeOfShare->SetDisplayMode(dmBytes);
738 tmpRoot1->AddChild(new CStatTreeItemAverage(wxTRANSLATE("Average file size: %s"), s_sizeOfShare, s_numberOfShared, dmBytes));
742 void CStatistics::UpdateStatsTree()
744 // get sort orders right
745 s_sessionUpload->ReSortChildren();
746 s_sessionDownload->ReSortChildren();
747 s_clients->ReSortChildren();
748 s_foundSources->ReSortChildren();
749 // TODO: sort OS_Info subtrees.
751 s_avgConnections->SetValue(theApp->listensocket->GetAverageConnections());
753 // get serverstats
754 // TODO: make these realtime, too
755 uint32 servfail;
756 uint32 servuser;
757 uint32 servfile;
758 uint32 servtuser;
759 uint32 servtfile;
760 float servocc;
761 theApp->serverlist->GetStatus(servfail, servuser, servfile, servtuser, servtfile, servocc);
762 s_workingServers->SetValue((uint64)((*s_totalServers)-servfail));
763 s_failedServers->SetValue((uint64)servfail);
764 s_usersOnWorking->SetValue((uint64)servuser);
765 s_filesOnWorking->SetValue((uint64)servfile);
766 s_totalUsers->SetValue((uint64)servtuser);
767 s_totalFiles->SetValue((uint64)servtfile);
768 s_serverOccupation->SetValue(servocc);
772 void CStatistics::AddSourceOrigin(unsigned origin)
774 CStatTreeItemNativeCounter* counter = (CStatTreeItemNativeCounter*)s_foundSources->GetChildById(0x0100 + origin);
775 if (counter) {
776 ++(*counter);
777 } else {
778 counter = new CStatTreeItemNativeCounter(OriginToText(origin) + wxT(": %s"), stHideIfZero | stShowPercent);
779 ++(*counter);
780 s_foundSources->AddChild(counter, 0x0100 + origin);
784 void CStatistics::RemoveSourceOrigin(unsigned origin)
786 CStatTreeItemNativeCounter* counter = (CStatTreeItemNativeCounter*)s_foundSources->GetChildById(0x0100 + origin);
787 wxASSERT(counter);
788 --(*counter);
791 uint32 GetSoftID(uint8 SoftType)
793 // prevent appearing multiple tree entries with the same name
794 // this should be kept in sync with GetSoftName().
795 switch (SoftType) {
796 case SO_OLDEMULE:
797 return 0x0100 + SO_EMULE;
798 case SO_NEW_SHAREAZA:
799 case SO_NEW2_SHAREAZA:
800 return 0x0100 + SO_SHAREAZA;
801 case SO_NEW2_MLDONKEY:
802 return 0x0100 + SO_NEW_MLDONKEY;
803 default:
804 return 0x0100 + SoftType;
808 void CStatistics::AddDownloadFromSoft(uint8 SoftType, uint32 bytes)
810 AddReceivedBytes(bytes);
812 uint32 id = GetSoftID(SoftType);
814 if (s_sessionDownload->HasChildWithId(id)) {
815 (*((CStatTreeItemCounter*)s_sessionDownload->GetChildById(id))) += bytes;
816 } else {
817 CStatTreeItemCounter* tmp = new CStatTreeItemCounter(GetSoftName(SoftType) + wxT(": %s"));
818 tmp->SetDisplayMode(dmBytes);
819 (*tmp) += bytes;
820 s_sessionDownload->AddChild(tmp, id);
824 void CStatistics::AddUploadToSoft(uint8 SoftType, uint32 bytes)
826 uint32 id = GetSoftID(SoftType);
828 if (s_sessionUpload->HasChildWithId(id)) {
829 (*((CStatTreeItemCounter*)s_sessionUpload->GetChildById(id))) += bytes;
830 } else {
831 CStatTreeItemCounter* tmp = new CStatTreeItemCounter(GetSoftName(SoftType) + wxT(": %s"));
832 tmp->SetDisplayMode(dmBytes);
833 (*tmp) += bytes;
834 s_sessionUpload->AddChild(tmp, id);
838 inline bool SupportsOSInfo(unsigned clientSoft)
840 return (clientSoft == SO_AMULE) || (clientSoft == SO_HYDRANODE) || (clientSoft == SO_NEW2_MLDONKEY);
843 // Do some random black magic to strings to get a relatively unique number for them.
844 uint32 GetIdFromString(const wxString& str)
846 uint32 id = 0;
847 for (unsigned i = 0; i < str.Length(); ++i) {
848 unsigned old_id = id;
849 id += (uint32)str.GetChar(i);
850 id <<= 2;
851 id ^= old_id;
852 id -= old_id;
854 return ((id >> 1) + id | 0x00000100) & 0x7fffffff;
857 void CStatistics::AddKnownClient(CUpDownClient *pClient)
859 ++(*s_clients);
861 uint32 clientSoft = pClient->GetClientSoft();
862 uint32 id = GetSoftID(clientSoft);
864 CStatTreeItemCounter *client;
866 if (s_clients->HasChildWithId(id)) {
867 client = (CStatTreeItemCounter*)s_clients->GetChildById(id);
868 ++(*client);
869 } else {
870 uint32 flags = stSortChildren | stShowPercent | stHideIfZero;
871 if (!SupportsOSInfo(clientSoft)) {
872 flags |= stCapChildren;
874 client = new CStatTreeItemCounter(GetSoftName(clientSoft) + wxT(": %s"), flags);
875 ++(*client);
876 s_clients->AddChild(client, id);
877 if (SupportsOSInfo(clientSoft)) {
878 client->AddChild(new CStatTreeItemBase(wxTRANSLATE("Version"), stSortChildren | stCapChildren), 2);
879 client->AddChild(new CStatTreeItemBase(wxTRANSLATE("Operating System"), stSortChildren | stSortByValue), 1);
883 CStatTreeItemBase *versionRoot = SupportsOSInfo(clientSoft) ? client->GetChildById(2) : client;
884 uint32 clientVersion = pClient->GetVersion();
886 if (versionRoot->HasChildWithId(clientVersion)) {
887 CStatTreeItemCounter *version = (CStatTreeItemCounter*)versionRoot->GetChildById(clientVersion);
888 ++(*version);
889 } else {
890 const wxString& versionStr = pClient->GetVersionString();
891 CStatTreeItemCounter *version = new CStatTreeItemCounter((versionStr.IsEmpty() ? wxString(wxTRANSLATE("Unknown")) : versionStr) + wxT(": %s"), stShowPercent | stHideIfZero);
892 ++(*version);
893 versionRoot->AddChild(version, clientVersion, SupportsOSInfo(clientSoft));
896 if (SupportsOSInfo(clientSoft)) {
897 const wxString& OSInfo = pClient->GetClientOSInfo();
898 uint32 OS_ID = OSInfo.IsEmpty() ? 0 : GetIdFromString(OSInfo);
899 CStatTreeItemBase* OSRoot = client->GetChildById(1);
900 CStatTreeItemCounter* OSNode = (CStatTreeItemCounter*)OSRoot->GetChildById(OS_ID);
901 if (OSNode) {
902 ++(*OSNode);
903 } else {
904 OSNode = new CStatTreeItemCounter((OS_ID ? OSInfo : wxString(wxTRANSLATE("Not Received"))) + wxT(": %s"), stShowPercent | stHideIfZero);
905 ++(*OSNode);
906 OSRoot->AddChild(OSNode, OS_ID, true);
911 void CStatistics::RemoveKnownClient(uint32 clientSoft, uint32 clientVersion, const wxString& OSInfo)
913 --(*s_clients);
915 uint32 id = GetSoftID(clientSoft);
917 CStatTreeItemCounter *client = (CStatTreeItemCounter*)s_clients->GetChildById(id);
918 wxASSERT(client);
919 --(*client);
921 CStatTreeItemBase *versionRoot = SupportsOSInfo(clientSoft) ? client->GetChildById(2) : client;
923 CStatTreeItemCounter *version = (CStatTreeItemCounter*)versionRoot->GetChildById(clientVersion);
924 wxASSERT(version);
925 --(*version);
927 if (SupportsOSInfo(clientSoft)) {
928 uint32 OS_ID = OSInfo.IsEmpty() ? 0 : GetIdFromString(OSInfo);
929 CStatTreeItemCounter* OSNode = (CStatTreeItemCounter*)client->GetChildById(1)->GetChildById(OS_ID);
930 wxASSERT(OSNode);
931 --(*OSNode);
935 #else /* EC_REMOTE (CLIENT_GUI) */
937 CStatistics::CStatistics(CRemoteConnect &conn)
939 m_conn(conn)
941 s_start_time = GetTickCount64();
943 // Init Tree
944 s_statTree = new CStatTreeItemBase(_("Statistics"), 0);
946 // Clear stat data container
947 for (int i = 0; i < sdTotalItems; ++i) {
948 s_statData[i] = 0;
953 CStatistics::~CStatistics()
955 delete s_statTree;
959 void CStatistics::UpdateStats(const CECPacket* stats)
961 s_statData[sdUpload] = stats->GetTagByNameSafe(EC_TAG_STATS_UL_SPEED)->GetInt();
962 s_statData[sdUpOverhead] = stats->GetTagByNameSafe(EC_TAG_STATS_UP_OVERHEAD)->GetInt();
963 s_statData[sdDownload] = stats->GetTagByNameSafe(EC_TAG_STATS_DL_SPEED)->GetInt();
964 s_statData[sdDownOverhead] = stats->GetTagByNameSafe(EC_TAG_STATS_DOWN_OVERHEAD)->GetInt();
965 s_statData[sdWaitingClients] = stats->GetTagByNameSafe(EC_TAG_STATS_UL_QUEUE_LEN)->GetInt();
966 s_statData[sdBannedClients] = stats->GetTagByNameSafe(EC_TAG_STATS_BANNED_COUNT)->GetInt();
967 s_statData[sdED2KUsers] = stats->GetTagByNameSafe(EC_TAG_STATS_ED2K_USERS)->GetInt();
968 s_statData[sdKadUsers] = stats->GetTagByNameSafe(EC_TAG_STATS_KAD_USERS)->GetInt();
969 s_statData[sdED2KFiles] = stats->GetTagByNameSafe(EC_TAG_STATS_ED2K_FILES)->GetInt();
970 s_statData[sdKadFiles] = stats->GetTagByNameSafe(EC_TAG_STATS_KAD_FILES)->GetInt();
974 void CStatistics::UpdateStatsTree()
976 CECPacket request(EC_OP_GET_STATSTREE);
977 if (thePrefs::GetMaxClientVersions() != 0) {
978 request.AddTag(CECTag(EC_TAG_STATTREE_CAPPING, (uint8)thePrefs::GetMaxClientVersions()));
980 const CECPacket* reply = m_conn.SendRecvPacket(&request);
981 if (reply) {
982 const CECTag* treeRoot = reply->GetTagByName(EC_TAG_STATTREE_NODE);
983 if (treeRoot) {
984 delete s_statTree;
985 s_statTree = new CStatTreeItemBase(treeRoot);
988 delete reply;
991 #endif /* !EC_REMOTE */
993 // File_checked_for_headers