/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de>                 */
/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com)           */
/*                                                                        */
/* NXCOMP, NX protocol compression and NX extensions to this software     */
/* are copyright of the aforementioned persons and companies.             */
/*                                                                        */
/* Redistribution and use of the present software is allowed according    */
/* to terms specified in the file LICENSE.nxcomp which comes in the       */
/* source distribution.                                                   */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/* NOTE: This software has received contributions from various other      */
/* contributors, only the core maintainers and supporters are listed as   */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well.                                          */
/*                                                                        */
/**************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>

#include "Statistics.h"

#include "Control.h"

#include "Proxy.h"

#include "ClientStore.h"
#include "ServerStore.h"

//
// Length of temporary buffer
// used to format output.
//

#define FORMAT_LENGTH          1024

//
// Log level.
//

#define PANIC
#define WARNING
#undef  TEST
#undef  DEBUG

//
// Note that when presenting statistics we invert the
// correct semantics of X client and server entities.
// This is questionable, but matches the user's pers-
// pective of running remote X applications on its
// local client.
//

Statistics::Statistics(Proxy *proxy) : proxy_(proxy)
{
  transportPartial_.idleTime_             = 0;
  transportPartial_.readTime_             = 0;
  transportPartial_.writeTime_            = 0;
  transportPartial_.proxyBytesIn_         = 0;
  transportPartial_.proxyBytesOut_        = 0;
  transportPartial_.proxyFramesIn_        = 0;
  transportPartial_.proxyFramesOut_       = 0;
  transportPartial_.proxyWritesOut_       = 0;
  transportPartial_.compressedBytesIn_    = 0;
  transportPartial_.compressedBytesOut_   = 0;
  transportPartial_.decompressedBytesIn_  = 0;
  transportPartial_.decompressedBytesOut_ = 0;
  transportPartial_.framingBitsOut_       = 0;

  transportTotal_.idleTime_             = 0;
  transportTotal_.readTime_             = 0;
  transportTotal_.writeTime_            = 0;
  transportTotal_.proxyBytesIn_         = 0;
  transportTotal_.proxyBytesOut_        = 0;
  transportTotal_.proxyFramesIn_        = 0;
  transportTotal_.proxyFramesOut_       = 0;
  transportTotal_.proxyWritesOut_       = 0;
  transportTotal_.compressedBytesIn_    = 0;
  transportTotal_.compressedBytesOut_   = 0;
  transportTotal_.decompressedBytesIn_  = 0;
  transportTotal_.decompressedBytesOut_ = 0;
  transportTotal_.framingBitsOut_       = 0;

  for (int i = 0; i < STATISTICS_OPCODE_MAX; i++)
  {
    protocolPartial_.requestCached_[i]  = 0;
    protocolPartial_.requestReplied_[i] = 0;
    protocolPartial_.requestCount_[i]   = 0;
    protocolPartial_.requestBitsIn_[i]  = 0;
    protocolPartial_.requestBitsOut_[i] = 0;

    protocolPartial_.renderRequestCached_[i]  = 0;
    protocolPartial_.renderRequestCount_[i]   = 0;
    protocolPartial_.renderRequestBitsIn_[i]  = 0;
    protocolPartial_.renderRequestBitsOut_[i] = 0;

    protocolPartial_.replyCached_[i]  = 0;
    protocolPartial_.replyCount_[i]   = 0;
    protocolPartial_.replyBitsIn_[i]  = 0;
    protocolPartial_.replyBitsOut_[i] = 0;

    protocolPartial_.eventCached_[i]  = 0;
    protocolPartial_.eventCount_[i]   = 0;
    protocolPartial_.eventBitsIn_[i]  = 0;
    protocolPartial_.eventBitsOut_[i] = 0;

    protocolTotal_.requestCached_[i]  = 0;
    protocolTotal_.requestReplied_[i] = 0;
    protocolTotal_.requestCount_[i]   = 0;
    protocolTotal_.requestBitsIn_[i]  = 0;
    protocolTotal_.requestBitsOut_[i] = 0;

    protocolTotal_.renderRequestCached_[i]  = 0;
    protocolTotal_.renderRequestCount_[i]   = 0;
    protocolTotal_.renderRequestBitsIn_[i]  = 0;
    protocolTotal_.renderRequestBitsOut_[i] = 0;

    protocolTotal_.replyCached_[i]  = 0;
    protocolTotal_.replyCount_[i]   = 0;
    protocolTotal_.replyBitsIn_[i]  = 0;
    protocolTotal_.replyBitsOut_[i] = 0;

    protocolTotal_.eventCached_[i]  = 0;
    protocolTotal_.eventCount_[i]   = 0;
    protocolTotal_.eventBitsIn_[i]  = 0;
    protocolTotal_.eventBitsOut_[i] = 0;
  }

  protocolPartial_.cupsCount_    = 0;
  protocolPartial_.cupsBitsIn_   = 0;
  protocolPartial_.cupsBitsOut_  = 0;

  protocolPartial_.smbCount_     = 0;
  protocolPartial_.smbBitsIn_    = 0;
  protocolPartial_.smbBitsOut_   = 0;

  protocolPartial_.mediaCount_   = 0;
  protocolPartial_.mediaBitsIn_  = 0;
  protocolPartial_.mediaBitsOut_ = 0;

  protocolPartial_.httpCount_    = 0;
  protocolPartial_.httpBitsIn_   = 0;
  protocolPartial_.httpBitsOut_  = 0;

  protocolPartial_.fontCount_    = 0;
  protocolPartial_.fontBitsIn_   = 0;
  protocolPartial_.fontBitsOut_  = 0;

  protocolPartial_.slaveCount_   = 0;
  protocolPartial_.slaveBitsIn_  = 0;
  protocolPartial_.slaveBitsOut_ = 0;

  protocolTotal_.cupsCount_    = 0;
  protocolTotal_.cupsBitsIn_   = 0;
  protocolTotal_.cupsBitsOut_  = 0;

  protocolTotal_.smbCount_     = 0;
  protocolTotal_.smbBitsIn_    = 0;
  protocolTotal_.smbBitsOut_   = 0;

  protocolTotal_.mediaCount_   = 0;
  protocolTotal_.mediaBitsIn_  = 0;
  protocolTotal_.mediaBitsOut_ = 0;

  protocolTotal_.httpCount_    = 0;
  protocolTotal_.httpBitsIn_   = 0;
  protocolTotal_.httpBitsOut_  = 0;

  protocolTotal_.fontCount_    = 0;
  protocolTotal_.fontBitsIn_   = 0;
  protocolTotal_.fontBitsOut_  = 0;

  protocolTotal_.slaveCount_   = 0;
  protocolTotal_.slaveBitsIn_  = 0;
  protocolTotal_.slaveBitsOut_ = 0;

  packedPartial_.packedBytesIn_  = 0;
  packedPartial_.packedBytesOut_ = 0;

  packedTotal_.packedBytesIn_    = 0;
  packedTotal_.packedBytesOut_   = 0;

  splitPartial_.splitCount_           = 0;
  splitPartial_.splitAborted_         = 0;
  splitPartial_.splitAbortedBytesOut_ = 0;

  splitTotal_.splitCount_             = 0;
  splitTotal_.splitAborted_           = 0;
  splitTotal_.splitAbortedBytesOut_   = 0;

  overallPartial_.overallBytesIn_  = 0;
  overallPartial_.overallBytesOut_ = 0;

  overallTotal_.overallBytesIn_    = 0;
  overallTotal_.overallBytesOut_   = 0;

  proxyData_.protocolCount_ = 0;
  proxyData_.controlCount_  = 0;
  proxyData_.splitCount_    = 0;
  proxyData_.dataCount_     = 0;

  proxyData_.streamRatio_ = 1;

  startShortFrameTs_ = getTimestamp();
  startLongFrameTs_  = getTimestamp();
  startFrameTs_      = getTimestamp();

  bytesInShortFrame_ = 0;
  bytesInLongFrame_  = 0;

  bitrateInShortFrame_ = 0;
  bitrateInLongFrame_  = 0;

  topBitrate_ = 0;

  congestionInFrame_ = 0;
}

Statistics::~Statistics()
{
}

int Statistics::resetPartialStats()
{
  transportPartial_.idleTime_             = 0;
  transportPartial_.readTime_             = 0;
  transportPartial_.writeTime_            = 0;
  transportPartial_.proxyBytesIn_         = 0;
  transportPartial_.proxyBytesOut_        = 0;
  transportPartial_.proxyFramesIn_        = 0;
  transportPartial_.proxyFramesOut_       = 0;
  transportPartial_.proxyWritesOut_       = 0;
  transportPartial_.compressedBytesIn_    = 0;
  transportPartial_.compressedBytesOut_   = 0;
  transportPartial_.decompressedBytesIn_  = 0;
  transportPartial_.decompressedBytesOut_ = 0;
  transportPartial_.framingBitsOut_       = 0;

  for (int i = 0; i < STATISTICS_OPCODE_MAX; i++)
  {
    protocolPartial_.requestCached_[i]  = 0;
    protocolPartial_.requestReplied_[i] = 0;
    protocolPartial_.requestCount_[i]   = 0;
    protocolPartial_.requestBitsIn_[i]  = 0;
    protocolPartial_.requestBitsOut_[i] = 0;

    protocolPartial_.renderRequestCached_[i]  = 0;
    protocolPartial_.renderRequestCount_[i]   = 0;
    protocolPartial_.renderRequestBitsIn_[i]  = 0;
    protocolPartial_.renderRequestBitsOut_[i] = 0;

    protocolPartial_.replyCached_[i]  = 0;
    protocolPartial_.replyCount_[i]   = 0;
    protocolPartial_.replyBitsIn_[i]  = 0;
    protocolPartial_.replyBitsOut_[i] = 0;

    protocolPartial_.eventCached_[i]  = 0;
    protocolPartial_.eventCount_[i]   = 0;
    protocolPartial_.eventBitsIn_[i]  = 0;
    protocolPartial_.eventBitsOut_[i] = 0;
  }

  protocolPartial_.cupsCount_    = 0;
  protocolPartial_.cupsBitsIn_   = 0;
  protocolPartial_.cupsBitsOut_  = 0;

  protocolPartial_.smbCount_     = 0;
  protocolPartial_.smbBitsIn_    = 0;
  protocolPartial_.smbBitsOut_   = 0;

  protocolPartial_.mediaCount_   = 0;
  protocolPartial_.mediaBitsIn_  = 0;
  protocolPartial_.mediaBitsOut_ = 0;

  protocolPartial_.httpCount_    = 0;
  protocolPartial_.httpBitsIn_   = 0;
  protocolPartial_.httpBitsOut_  = 0;

  protocolPartial_.fontCount_    = 0;
  protocolPartial_.fontBitsIn_   = 0;
  protocolPartial_.fontBitsOut_  = 0;

  protocolPartial_.slaveCount_   = 0;
  protocolPartial_.slaveBitsIn_  = 0;
  protocolPartial_.slaveBitsOut_ = 0;

  packedPartial_.packedBytesIn_  = 0;
  packedPartial_.packedBytesOut_ = 0;

  splitPartial_.splitCount_           = 0;
  splitPartial_.splitAborted_         = 0;
  splitPartial_.splitAbortedBytesOut_ = 0;

  overallPartial_.overallBytesIn_  = 0;
  overallPartial_.overallBytesOut_ = 0;

  return 1;
}

void Statistics::addCompressedBytes(unsigned int bytesIn, unsigned int bytesOut)
{
  transportPartial_.compressedBytesIn_ += bytesIn;
  transportTotal_.compressedBytesIn_ += bytesIn;

  transportPartial_.compressedBytesOut_ += bytesOut;
  transportTotal_.compressedBytesOut_ += bytesOut;

  double ratio = bytesIn / bytesOut;

  if (ratio < 1)
  {
    ratio = 1;
  }

  #if defined(TEST) || defined(TOKEN)
  *logofs << "Statistics: TOKEN! Old ratio was "
          << proxyData_.streamRatio_ << " current is "
          << (double) ratio << " new ratio is " << (double)
             ((proxyData_.streamRatio_ * 2) + ratio) / 3 << ".\n"
          << logofs_flush;
  #endif

  proxyData_.streamRatio_ = ((proxyData_.streamRatio_ * 2) + ratio) / 3;

  #if defined(TEST) || defined(TOKEN)
  *logofs << "Statistics: TOKEN! Updated compressed bytes "
          << "with " << bytesIn << " in " << bytesOut << " out "
          << "and ratio " << (double) proxyData_.streamRatio_
          << ".\n" << logofs_flush;
  #endif
}

//
// Recalculate the current bitrate. The bytes written
// are accounted at the time the transport actually
// writes the data to the network, not at the time it
// receives the data from the upper layers. The reason
// is that data can be compressed by the stream com-
// pressor, so we can become aware of the new bitrate
// only after having flushed the ZLIB stream. This also
// means that, to have a reliable estimatation, we need
// to flush the link often.
//

void Statistics::updateBitrate(int bytes)
{
  T_timestamp thisFrameTs = getNewTimestamp();

  int diffFramesInMs = diffTimestamp(startFrameTs_, thisFrameTs);

  #ifdef DEBUG
  *logofs << "Statistics: Difference since previous timestamp is "
          << diffFramesInMs << " ms.\n" << logofs_flush;
  #endif

  if (diffFramesInMs > 0)
  {
    #ifdef DEBUG
    *logofs << "Statistics: Removing " << diffFramesInMs
            << " ms in short and long time frame.\n"
            << logofs_flush;
    #endif

    int shortBytesToRemove = (int) (((double) bytesInShortFrame_ * (double) diffFramesInMs) /
                                       (double) control -> ShortBitrateTimeFrame);

    int longBytesToRemove = (int) (((double) bytesInLongFrame_ * (double) diffFramesInMs) /
                                      (double) control -> LongBitrateTimeFrame);

    #ifdef DEBUG
    *logofs << "Statistics: Removing " << shortBytesToRemove
            << " bytes from " << bytesInShortFrame_
            << " in the short frame.\n" << logofs_flush;
    #endif

    bytesInShortFrame_ -= shortBytesToRemove;

    if (bytesInShortFrame_ < 0)
    {
      #ifdef TEST
      *logofs << "Statistics: Bytes in short frame are "
              << bytesInShortFrame_ << ". Set to 0.\n"
              << logofs_flush;
      #endif

      bytesInShortFrame_ = 0;
    }

    #ifdef DEBUG
    *logofs << "Statistics: Removing " << longBytesToRemove
            << " bytes from " << bytesInLongFrame_
            << " in the long frame.\n" << logofs_flush;
    #endif

    bytesInLongFrame_ -= longBytesToRemove;

    if (bytesInLongFrame_ < 0)
    {
      #ifdef TEST
      *logofs << "Statistics: Bytes in long frame are "
              << bytesInLongFrame_ << ". Set to 0.\n"
              << logofs_flush;
      #endif

      bytesInLongFrame_ = 0;
    }

    int diffStartInMs;

    diffStartInMs = diffTimestamp(thisFrameTs, startShortFrameTs_);

    if (diffStartInMs > control -> ShortBitrateTimeFrame)
    {
      addMsTimestamp(startShortFrameTs_, diffStartInMs);
    }

    diffStartInMs = diffTimestamp(thisFrameTs, startLongFrameTs_);

    if (diffStartInMs > control -> LongBitrateTimeFrame)
    {
      addMsTimestamp(startLongFrameTs_, diffStartInMs);
    }

    startFrameTs_ = thisFrameTs;
  }

  #ifdef DEBUG
  *logofs << "Statistics: Adding " << bytes << " bytes to "
          << bytesInShortFrame_ << " in the short frame.\n"
          << logofs_flush;
  #endif

  bytesInShortFrame_ = bytesInShortFrame_ + bytes;

  #ifdef DEBUG
  *logofs << "Statistics: Adding " << bytes << " bytes to "
          << bytesInLongFrame_ << " in the long frame.\n"
          << logofs_flush;
  #endif

  bytesInLongFrame_ = bytesInLongFrame_ + bytes;

  bitrateInShortFrame_ = (int) ((double) bytesInShortFrame_ /
                                   ((double) control -> ShortBitrateTimeFrame / 1000));

  bitrateInLongFrame_ = (int) ((double) bytesInLongFrame_ /
                                   ((double) control -> LongBitrateTimeFrame / 1000));

  if (bitrateInShortFrame_ > topBitrate_)
  {
    topBitrate_ = bitrateInShortFrame_;
  }

  #ifdef TEST
  *logofs << "Statistics: Current bitrate is short " << bitrateInShortFrame_
          << " long " << bitrateInLongFrame_ << " top " << topBitrate_
          << ".\n" << logofs_flush;
  #endif
}

void Statistics::updateCongestion(int remaining, int limit)
{
  #ifdef TEST
  *logofs << "Statistics: Updating the congestion "
          << "counters at " << strMsTimestamp()
          << ".\n" << logofs_flush;
  #endif

  double current = remaining;

  if (current < 0)
  {
    current = 0;
  }

  current = 9 * (limit - current) / limit;

  #ifdef TEST
  *logofs << "Statistics: Current congestion is "
          << current << " with " << limit << " tokens "
          << "and " << remaining << " remaining.\n"
          << logofs_flush;
  #endif

  //
  // If the current congestion counter is greater
  // than the previous, take the current value,
  // otherwise ramp down the value by calculating
  // the average of the last 8 updates.
  //

  #ifdef TEST
  *logofs << "Statistics: Old congestion was "
          << congestionInFrame_;
  #endif

  if (current >= congestionInFrame_)
  {
    congestionInFrame_ = current;
  }
  else
  {
    congestionInFrame_ = ((congestionInFrame_ * 7) + current) / 8;
  }

  #ifdef TEST
  *logofs << " new congestion is "
          << ((congestionInFrame_ * 7) + current) / 8
          << ".\n" << logofs_flush;
  #endif

  //
  // Call the function with 0 bytes flushed
  // so the agent can update its congestion
  // counter.
  //

  FlushCallback(0);
}

int Statistics::getClientCacheStats(int type, char *&buffer)
{
  if (type != PARTIAL_STATS && type != TOTAL_STATS)
  {
    #ifdef PANIC
    *logofs << "Statistics: PANIC! Cannot produce statistics "
            << "with qualifier '" << type << "'.\n"
            << logofs_flush;
    #endif

    return -1;
  }

  //
  // Print message cache data according
  // to local and remote view.
  //

  MessageStore *currentStore = NULL;
  MessageStore *anyStore     = NULL;

  char format[FORMAT_LENGTH];

  strcat(buffer, "\nNX Cache Statistics\n");
  strcat(buffer, "-------------------\n\n");

  for (int m = proxy_client; m <= proxy_server; m++)
  {
    if (m == proxy_client)
    {
      strcat(buffer, "Request\tCached\tSize at Server\t\tSize at Client\t\tCache limit\n");
      strcat(buffer, "-------\t------\t--------------\t\t--------------\t\t-----------\n");
    }
    else
    {
      strcat(buffer, "\nReply\tCached\tSize at Server\t\tSize at Client\t\tCache limit\n");
      strcat(buffer, "-----\t------\t--------------\t\t--------------\t\t-----------\n");
    }

    for (int i = 0; i < CHANNEL_STORE_OPCODE_LIMIT; i++)
    {
      if (m == proxy_client)
      {
        currentStore = proxy_ -> getClientStore() -> getRequestStore(i);
      }
      else
      {
        currentStore = proxy_ -> getServerStore() -> getReplyStore(i);
      }

      if (currentStore != NULL &&
              (currentStore -> getLocalStorageSize() ||
                   currentStore -> getRemoteStorageSize()))
      {
        anyStore = currentStore;

        sprintf(format, "#%d\t%d\t", i, currentStore -> getSize());

        strcat(buffer, format);

        sprintf(format, "%d (%.0f KB)\t\t", currentStore -> getLocalStorageSize(),
                    ((double) currentStore -> getLocalStorageSize()) / 1024);

        strcat(buffer, format);

        sprintf(format, "%d (%.0f KB)\t\t", currentStore -> getRemoteStorageSize(),
                    ((double) currentStore -> getRemoteStorageSize()) / 1024);

        strcat(buffer, format);

        sprintf(format, "%d/%.0f KB\n", currentStore -> cacheSlots,
                    ((double) control -> getUpperStorageSize() / 100 *
                          currentStore -> cacheThreshold) / 1024);

        strcat(buffer, format);
      }
    }

    if (anyStore == NULL)
    {
      strcat(buffer, "N/A\n");
    }
  }

  if (anyStore != NULL)
  {
    sprintf(format, "\ncache: %d bytes (%d KB) available at server.\n",
                control -> ClientTotalStorageSize,
                    control -> ClientTotalStorageSize / 1024);

    strcat(buffer, format);

    sprintf(format, "       %d bytes (%d KB) available at client.\n\n",
                control -> ServerTotalStorageSize,
                    control -> ServerTotalStorageSize / 1024);

    strcat(buffer, format);

    sprintf(format, "       %d bytes (%d KB) allocated at server.\n",
                anyStore -> getLocalTotalStorageSize(),
                    anyStore -> getLocalTotalStorageSize() / 1024);

    strcat(buffer, format);

    sprintf(format, "       %d bytes (%d KB) allocated at client.\n\n\n",
                anyStore -> getRemoteTotalStorageSize(),
                    anyStore -> getRemoteTotalStorageSize() / 1024);

    strcat(buffer, format);
  }
  else
  {
    strcat(buffer, "\ncache: N/A\n\n");
  }

  return 1;
}

int Statistics::getClientProtocolStats(int type, char *&buffer)
{
  if (type != PARTIAL_STATS && type != TOTAL_STATS)
  {
    #ifdef PANIC
    *logofs << "Statistics: PANIC! Cannot produce statistics "
            << "with qualifier '" << type << "'.\n"
            << logofs_flush;
    #endif

    return -1;
  }

  struct T_transportData *transportData;
  struct T_protocolData  *protocolData;
  struct T_overallData   *overallData;

  if (type == PARTIAL_STATS)
  {
    transportData = &transportPartial_;
    protocolData = &protocolPartial_;
    overallData = &overallPartial_;
  }
  else
  {
    transportData = &transportTotal_;
    protocolData = &protocolTotal_;
    overallData = &overallTotal_;
  }

  char format[FORMAT_LENGTH];

  double countRequestIn        = 0;
  double countCachedRequestIn  = 0;
  double countRepliedRequestIn = 0;

  double countRequestBitsIn  = 0;
  double countRequestBitsOut = 0;

  double countAnyIn   = 0;
  double countBitsIn  = 0;
  double countBitsOut = 0;

  //
  // Print request data.
  //

  strcat(buffer, "NX Server Side Protocol Statistics\n");
  strcat(buffer, "----------------------------------\n\n");

  //
  // Print render data.
  //

  strcat(buffer, "Render  Total\tCached\tBits In\t\tBits Out\tBits/Request\t\tRatio\n");
  strcat(buffer, "------- -----\t------\t-------\t\t--------\t------------\t\t-----\n");

  for (int i = 0; i < STATISTICS_OPCODE_MAX; i++)
  {
    if (protocolData -> renderRequestCount_[i])
    {
      sprintf(format, "#%d ", i);

      while (strlen(format) < 8)
      {
        strcat(format, " ");
      }

      strcat(buffer, format);

      if (protocolData -> renderRequestCached_[i] > 0)
      {
        sprintf(format, "%.0f\t%.0f", protocolData -> renderRequestCount_[i],
                    protocolData -> renderRequestCached_[i]);
      }
      else
      {
        sprintf(format, "%.0f\t", protocolData -> renderRequestCount_[i]);
      }

      strcat(buffer, format);

      sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                  protocolData -> renderRequestBitsIn_[i], protocolData -> renderRequestBitsIn_[i] / 8192,
                      protocolData -> renderRequestBitsOut_[i], protocolData -> renderRequestBitsOut_[i] / 8192,
                          protocolData -> renderRequestBitsIn_[i] / protocolData -> renderRequestCount_[i],
                              protocolData -> renderRequestBitsOut_[i] / protocolData -> renderRequestCount_[i]);

      strcat(buffer, format);

      if (protocolData -> renderRequestBitsOut_[i] > 0)
      {
        sprintf(format, "%5.3f:1\n", protocolData -> renderRequestBitsIn_[i] /
                                         protocolData -> renderRequestBitsOut_[i]);

        strcat(buffer, format);
      }
      else
      {
        strcat(buffer, "1:1\n");
      }
    }

    countRequestIn        += protocolData -> renderRequestCount_[i];
    countCachedRequestIn  += protocolData -> renderRequestCached_[i];

    countRequestBitsIn  += protocolData -> renderRequestBitsIn_[i];
    countRequestBitsOut += protocolData -> renderRequestBitsOut_[i];

    countAnyIn   += protocolData -> renderRequestCount_[i];
    countBitsIn  += protocolData -> renderRequestBitsIn_[i];
    countBitsOut += protocolData -> renderRequestBitsOut_[i];
  }

  if (countRequestIn > 0)
  {
    if (countCachedRequestIn > 0)
    {
      sprintf(format, "\ntotal:  %.0f\t%.0f", countRequestIn, countCachedRequestIn);
    }
    else
    {
      sprintf(format, "\ntotal:  %.0f\t", countRequestIn);
    }

    strcat(buffer, format);

    sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                countRequestBitsIn, countRequestBitsIn / 8192, countRequestBitsOut,
                    countRequestBitsOut / 8192, countRequestBitsIn / countRequestIn,
                        countRequestBitsOut / countRequestIn);

    strcat(buffer, format);

    if (countRequestBitsOut > 0)
    {
      sprintf(format, "%5.3f:1\n", countRequestBitsIn / countRequestBitsOut);
    }
    else
    {
      sprintf(format, "1:1\n");
    }

    strcat(buffer, format);
  }
  else
  {
    strcat(buffer, "N/A\n\n");
  }

  countRequestIn        = 0;
  countCachedRequestIn  = 0;

  countRequestBitsIn  = 0;
  countRequestBitsOut = 0;

  countAnyIn   = 0;
  countBitsIn  = 0;
  countBitsOut = 0;

  //
  // Print other requests' data.
  //

  strcat(buffer, "\nRequest Total\tCached\tBits In\t\tBits Out\tBits/Request\t\tRatio\n");
  strcat(buffer, "------- -----\t------\t-------\t\t--------\t------------\t\t-----\n");

  for (int i = 0; i < STATISTICS_OPCODE_MAX; i++)
  {
    if (protocolData -> requestCount_[i])
    {
      sprintf(format, "#%d ", i);

      while (strlen(format) < 5)
      {
        strcat(format, " ");
      }

      //
      // Mark NX agent-related requests, those
      // having a reply and finally those that
      // have been probably tainted by client
      // side.
      //

      if (i >= X_NXFirstOpcode && i <= X_NXLastOpcode)
      {
        strcat(format, "A");
      }

      if (i != X_NXInternalGenericReply && protocolData -> requestReplied_[i] > 0)
      {
        strcat(format, "R");
      }

      if (i == X_NoOperation && control -> TaintReplies)
      {
        strcat(format, "T");
      }

      while (strlen(format) < 8)
      {
        strcat(format, " ");
      }

      strcat(buffer, format);

      if (protocolData -> requestCached_[i] > 0)
      {
        sprintf(format, "%.0f\t%.0f", protocolData -> requestCount_[i],
                    protocolData -> requestCached_[i]);
      }
      else
      {
        sprintf(format, "%.0f\t", protocolData -> requestCount_[i]);
      }

      strcat(buffer, format);

      sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                  protocolData -> requestBitsIn_[i], protocolData -> requestBitsIn_[i] / 8192,
                      protocolData -> requestBitsOut_[i], protocolData -> requestBitsOut_[i] / 8192,
                          protocolData -> requestBitsIn_[i] / protocolData -> requestCount_[i],
                              protocolData -> requestBitsOut_[i] / protocolData -> requestCount_[i]);

      strcat(buffer, format);

      if (protocolData -> requestBitsOut_[i] > 0)
      {
        sprintf(format, "%5.3f:1\n", protocolData -> requestBitsIn_[i] /
                                         protocolData -> requestBitsOut_[i]);

        strcat(buffer, format);
      }
      else
      {
        strcat(buffer, "1:1\n");
      }
    }

    countRequestIn        += protocolData -> requestCount_[i];
    countCachedRequestIn  += protocolData -> requestCached_[i];
    countRepliedRequestIn += protocolData -> requestReplied_[i];

    countRequestBitsIn  += protocolData -> requestBitsIn_[i];
    countRequestBitsOut += protocolData -> requestBitsOut_[i];

    countAnyIn   += protocolData -> requestCount_[i];
    countBitsIn  += protocolData -> requestBitsIn_[i];
    countBitsOut += protocolData -> requestBitsOut_[i];
  }

  if (countRequestIn > 0)
  {
    if (countCachedRequestIn > 0)
    {
      sprintf(format, "\ntotal:  %.0f\t%.0f", countRequestIn, countCachedRequestIn);
    }
    else
    {
      sprintf(format, "\ntotal:  %.0f\t", countRequestIn);
    }

    strcat(buffer, format);

    sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                countRequestBitsIn, countRequestBitsIn / 8192, countRequestBitsOut,
                    countRequestBitsOut / 8192, countRequestBitsIn / countRequestIn,
                        countRequestBitsOut / countRequestIn);

    strcat(buffer, format);

    if (countRequestBitsOut > 0)
    {
      sprintf(format, "%5.3f:1\n", countRequestBitsIn / countRequestBitsOut);
    }
    else
    {
      sprintf(format, "1:1\n");
    }

    strcat(buffer, format);
  }
  else
  {
    strcat(buffer, "N/A\n\n");
  }

  //
  // Print transport data.
  //

  getTimeStats(type, buffer);

  countAnyIn   += protocolData -> cupsCount_;
  countBitsIn  += protocolData -> cupsBitsIn_;
  countBitsOut += protocolData -> cupsBitsOut_;

  countAnyIn   += protocolData -> smbCount_;
  countBitsIn  += protocolData -> smbBitsIn_;
  countBitsOut += protocolData -> smbBitsOut_;

  countAnyIn   += protocolData -> mediaCount_;
  countBitsIn  += protocolData -> mediaBitsIn_;
  countBitsOut += protocolData -> mediaBitsOut_;

  countAnyIn   += protocolData -> httpCount_;
  countBitsIn  += protocolData -> httpBitsIn_;
  countBitsOut += protocolData -> httpBitsOut_;

  countAnyIn   += protocolData -> fontCount_;
  countBitsIn  += protocolData -> fontBitsIn_;
  countBitsOut += protocolData -> fontBitsOut_;

  countAnyIn   += protocolData -> slaveCount_;
  countBitsIn  += protocolData -> slaveBitsIn_;
  countBitsOut += protocolData -> slaveBitsOut_;

  //
  // Save the overall amount of bytes
  // coming from X clients.
  //

  overallData -> overallBytesIn_ = countBitsIn / 8;

  //
  // Print performance data.
  //

  if (transportData -> readTime_ > 0)
  {
    sprintf(format, "      %.0f messages (%.0f KB) encoded per second.\n\n",
                countAnyIn / (transportData -> readTime_ / 1000),
                    (countBitsIn + transportData -> framingBitsOut_) / 8192 /
                         (transportData -> readTime_ / 1000));
  }
  else
  {
    sprintf(format, "      %.0f messages (%.0f KB) encoded per second.\n\n",
                countAnyIn, (countBitsIn + transportData ->
                    framingBitsOut_) / 8192);
  }

  strcat(buffer, format);

  strcat(buffer, "link: ");

  //
  // ZLIB compression stats.
  //

  getStreamStats(type, buffer);

  //
  // Save the overall amount of bytes
  // sent on NX proxy link.
  //

  if (transportData -> compressedBytesOut_ > 0)
  {
    overallData -> overallBytesOut_ = transportData -> compressedBytesOut_;
  }
  else
  {
    overallData -> overallBytesOut_ = countBitsOut / 8;
  }

  //
  // Print info on multiplexing overhead.
  //

  getFramingStats(type, buffer);

  //
  // Print stats about additional channels.
  //

  getServicesStats(type, buffer);

  //
  // Compression summary.
  //

  double ratio = 1;

  if (transportData -> compressedBytesOut_ / 1024 > 0)
  {
    ratio = ((countBitsIn + transportData -> framingBitsOut_) / 8192) /
                  (transportData -> compressedBytesOut_ / 1024);

  }
  else if (countBitsOut > 0)
  {
    ratio = (countBitsIn + transportData -> framingBitsOut_) /
                 countBitsOut;
  }

  sprintf(format, "      Protocol compression ratio is %5.3f:1.\n\n",
              ratio);

  strcat(buffer, format);

  getBitrateStats(type, buffer);

  getSplitStats(type, buffer);

  sprintf(format, "      %.0f total handled replies (%.0f unmatched).\n\n\n",
              countRepliedRequestIn, protocolData -> requestReplied_[X_NXInternalGenericReply]);

  strcat(buffer, format);

  return 1;
}

int Statistics::getClientOverallStats(int type, char *&buffer)
{
  if (type != PARTIAL_STATS && type != TOTAL_STATS)
  {
    #ifdef PANIC
    *logofs << "Statistics: PANIC! Cannot produce statistics "
            << "with qualifier '" << type << "'.\n"
            << logofs_flush;
    #endif

    return -1;
  }

  struct T_overallData *overallData;
  struct T_packedData  *packedData;

  if (type == PARTIAL_STATS)
  {
    overallData = &overallPartial_;
    packedData  = &packedPartial_;
  }
  else
  {
    overallData = &overallTotal_;
    packedData = &packedTotal_;
  }

  char format[FORMAT_LENGTH];

  //
  // Print header including link type,
  // followed by info on packed images.
  //

  strcat(buffer, "NX Compression Summary\n");
  strcat(buffer, "----------------------\n\n");

  char label[FORMAT_LENGTH];

  switch (control -> LinkMode)
  {
    case LINK_TYPE_NONE:
    {
      strcpy(label, "NONE");

      break;
    }
    case LINK_TYPE_MODEM:
    {
      strcpy(label, "MODEM");

      break;
    }
    case LINK_TYPE_ISDN:
    {
      strcpy(label, "ISDN");

      break;
    }
    case LINK_TYPE_ADSL:
    {
      strcpy(label, "ADSL");

      break;
    }
    case LINK_TYPE_WAN:
    {
      strcpy(label, "WAN");

      break;
    }
    case LINK_TYPE_LAN:
    {
      strcpy(label, "LAN");

      break;
    }
    default:
    {
      strcpy(label, "Unknown");

      break;
    }
  }

  sprintf(format, "link:    %s", label);

  if (control -> LocalDeltaCompression == 1)
  {
    strcat(format, " with protocol compression enabled.");
  }
  else
  {
    strcat(format, " with protocol compression disabled.");
  }

  strcat(format, "\n\n");

  strcat(buffer, format);

  if (packedData -> packedBytesIn_ > 0)
  {
    sprintf(format, "images:  %.0f bytes (%.0f KB) packed to %.0f (%.0f KB).\n\n",
                packedData -> packedBytesOut_, packedData -> packedBytesOut_ / 1024,
                    packedData -> packedBytesIn_, packedData -> packedBytesIn_ / 1024);

    strcat(buffer, format);

    sprintf(format, "         Images compression ratio is %5.3f:1.\n\n",
                packedData -> packedBytesOut_ / packedData -> packedBytesIn_);

    strcat(buffer, format);
  }

  double overallIn = overallData -> overallBytesIn_ - packedData -> packedBytesIn_ +
                         packedData -> packedBytesOut_;

  double overallOut = overallData -> overallBytesOut_;

  sprintf(format, "overall: %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
              overallIn, overallIn / 1024, overallOut, overallOut / 1024);

  strcat(buffer, format);

  if (overallData -> overallBytesOut_ > 0)
  {
    sprintf(format, "         Overall NX server compression ratio is %5.3f:1.\n\n\n",
                overallIn / overallOut);
  }
  else
  {
    sprintf(format, "         Overall NX server compression ratio is 1:1.\n\n\n");
  }

  strcat(buffer, format);

  return 1;
}

int Statistics::getServerCacheStats(int type, char *&buffer)
{
  if (type != PARTIAL_STATS && type != TOTAL_STATS)
  {
    #ifdef PANIC
    *logofs << "Statistics: PANIC! Cannot produce statistics "
            << "with qualifier '" << type << "'.\n"
            << logofs_flush;
    #endif

    return -1;
  }

  //
  // Print message cache data according
  // to local and remote view.
  //

  MessageStore *currentStore = NULL;
  MessageStore *anyStore     = NULL;

  char format[FORMAT_LENGTH];

  strcat(buffer, "\nNX Cache Statistics\n");
  strcat(buffer, "-------------------\n\n");

  for (int m = proxy_client; m <= proxy_server; m++)
  {
    if (m == proxy_client)
    {
      strcat(buffer, "Request\tCached\tSize at Server\t\tSize at Client\t\tCache limit\n");
      strcat(buffer, "-------\t------\t--------------\t\t--------------\t\t-----------\n");
    }
    else
    {
      strcat(buffer, "\nReply\tCached\tSize at Server\t\tSize at Client\t\tCache limit\n");
      strcat(buffer, "-----\t------\t--------------\t\t--------------\t\t-----------\n");
    }

    for (int i = 0; i < CHANNEL_STORE_OPCODE_LIMIT; i++)
    {
      if (m == proxy_client)
      {
        currentStore = proxy_ -> getClientStore() -> getRequestStore(i);
      }
      else
      {
        currentStore = proxy_ -> getServerStore() -> getReplyStore(i);
      }

      if (currentStore != NULL &&
              (currentStore -> getLocalStorageSize() ||
                   currentStore -> getRemoteStorageSize()))
      {
        anyStore = currentStore;

        sprintf(format, "#%d\t%d\t", i, currentStore -> getSize());

        strcat(buffer, format);

        sprintf(format, "%d (%.0f KB)\t\t", currentStore -> getRemoteStorageSize(),
                    ((double) currentStore -> getRemoteStorageSize()) / 1024);

        strcat(buffer, format);

        sprintf(format, "%d (%.0f KB)\t\t", currentStore -> getLocalStorageSize(),
                    ((double) currentStore -> getLocalStorageSize()) / 1024);

        strcat(buffer, format);

        sprintf(format, "%d/%.0f KB\n", currentStore -> cacheSlots,
                    ((double) control -> getUpperStorageSize() / 100 *
                          currentStore -> cacheThreshold) / 1024);

        strcat(buffer, format);
      }
    }

    if (anyStore == NULL)
    {
      strcat(buffer, "N/A\n");
    }
  }

  if (anyStore != NULL)
  {
    sprintf(format, "\ncache: %d bytes (%d KB) available at server.\n",
                control -> ClientTotalStorageSize,
                    control -> ClientTotalStorageSize / 1024);

    strcat(buffer, format);

    sprintf(format, "       %d bytes (%d KB) available at client.\n\n",
                control -> ServerTotalStorageSize,
                    control -> ServerTotalStorageSize / 1024);

    strcat(buffer, format);

    sprintf(format, "       %d bytes (%d KB) allocated at server.\n",
                anyStore -> getRemoteTotalStorageSize(),
                    anyStore -> getRemoteTotalStorageSize() / 1024);

    strcat(buffer, format);

    sprintf(format, "       %d bytes (%d KB) allocated at client.\n\n\n",
                anyStore -> getLocalTotalStorageSize(),
                    anyStore -> getLocalTotalStorageSize() / 1024);

    strcat(buffer, format);
  }
  else
  {
    strcat(buffer, "\ncache: N/A\n\n");
  }

  return 1;
}

int Statistics::getServerProtocolStats(int type, char *&buffer)
{
  if (type != PARTIAL_STATS && type != TOTAL_STATS)
  {
    #ifdef PANIC
    *logofs << "Statistics: PANIC! Cannot produce statistics "
            << "with qualifier '" << type << "'.\n"
            << logofs_flush;
    #endif

    return -1;
  }

  struct T_transportData *transportData;
  struct T_protocolData  *protocolData;
  struct T_overallData   *overallData;

  if (type == PARTIAL_STATS)
  {
    transportData = &transportPartial_;
    protocolData = &protocolPartial_;
    overallData = &overallPartial_;
  }
  else
  {
    transportData = &transportTotal_;
    protocolData = &protocolTotal_;
    overallData = &overallTotal_;
  }

  char format[FORMAT_LENGTH];

  double countReplyBitsIn  = 0;
  double countReplyBitsOut = 0;

  double countReplyIn        = 0;
  double countCachedReplyIn  = 0;

  double countEventBitsIn  = 0;
  double countEventBitsOut = 0;

  double countEventIn        = 0;
  double countCachedEventIn  = 0;

  double countAnyIn   = 0;
  double countBitsIn  = 0;
  double countBitsOut = 0;

  //
  // Print reply data.
  //

  strcat(buffer, "NX Client Side Protocol Statistics\n");
  strcat(buffer, "----------------------------------\n\n");

  strcat(buffer, "Reply   Total\tCached\tBits In\t\tBits Out\tBits/Reply\t\tRatio\n");
  strcat(buffer, "------- -----\t------\t-------\t\t--------\t----------\t\t-----\n");

  for (int i = 0; i < STATISTICS_OPCODE_MAX; i++)
  {
    if (protocolData -> replyCount_[i])
    {
      sprintf(format, "#%d ", i);

      while (strlen(format) < 5)
      {
        strcat(format, " ");
      }

      //
      // Mark replies originated
      // by NX agent requests.
      //

      if (i >= X_NXFirstOpcode && i <= X_NXLastOpcode)
      {
        strcat(format, "A");
      }

      //
      // Mark replies that we didn't
      // match against a request.
      //

      if (i == 1)
      {
        strcat(format, "U");
      }

      while (strlen(format) < 8)
      {
        strcat(format, " ");
      }

      strcat(buffer, format);

      if (protocolData -> replyCached_[i] > 0)
      {
        sprintf(format, "%.0f\t%.0f", protocolData -> replyCount_[i],
                    protocolData -> replyCached_[i]);
      }
      else
      {
        sprintf(format, "%.0f\t", protocolData -> replyCount_[i]);
      }

      strcat(buffer, format);

      sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                  protocolData -> replyBitsIn_[i], protocolData -> replyBitsIn_[i] / 8192,
                      protocolData -> replyBitsOut_[i], protocolData -> replyBitsOut_[i] / 8192,
                          protocolData -> replyBitsIn_[i] / protocolData -> replyCount_[i],
                              protocolData -> replyBitsOut_[i] / protocolData -> replyCount_[i]);

      strcat(buffer, format);

      if (protocolData -> replyBitsOut_[i] > 0)
      {
        sprintf(format, "%5.3f:1\n", protocolData -> replyBitsIn_[i] /
                                         protocolData -> replyBitsOut_[i]);
      }
      else
      {
        sprintf(format, "1:1\n");
      }

      strcat(buffer, format);
    }

    countReplyIn       += protocolData -> replyCount_[i];
    countCachedReplyIn += protocolData -> replyCached_[i];

    countReplyBitsIn  += protocolData -> replyBitsIn_[i];
    countReplyBitsOut += protocolData -> replyBitsOut_[i];

    countAnyIn   += protocolData -> replyCount_[i];
    countBitsIn  += protocolData -> replyBitsIn_[i];
    countBitsOut += protocolData -> replyBitsOut_[i];
  }

  if (countReplyIn > 0)
  {
    if (countCachedReplyIn > 0)
    {
      sprintf(format, "\ntotal:  %.0f\t%.0f", countReplyIn, countCachedReplyIn);
    }
    else
    {
      sprintf(format, "\ntotal:  %.0f\t", countReplyIn);
    }

    strcat(buffer, format);

    sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                countReplyBitsIn, countReplyBitsIn / 8192, countReplyBitsOut,
                    countReplyBitsOut / 8192, countReplyBitsIn / countReplyIn,
                        countReplyBitsOut / countReplyIn);

    strcat(buffer, format);

    if (countReplyBitsOut > 0)
    {
      sprintf(format, "%5.3f:1\n", countReplyBitsIn / countReplyBitsOut);
    }
    else
    {
      sprintf(format, "1:1\n");
    }

    strcat(buffer, format);
  }
  else
  {
    strcat(buffer, "N/A\n");
  }

  strcat(buffer, "\n");

  //
  // Print event and error data.
  //

  strcat(buffer, "Event   Total\tCached\tBits In\t\tBits Out\tBits/Event\t\tRatio\n");
  strcat(buffer, "------- -----\t------\t-------\t\t--------\t----------\t\t-----\n");

  for (int i = 0; i < STATISTICS_OPCODE_MAX; i++)
  {
    if (protocolData -> eventCount_[i])
    {
      sprintf(format, "#%d ", i);

      while (strlen(format) < 8)
      {
        strcat(format, " ");
      }

      strcat(buffer, format);

      if (protocolData -> eventCached_[i] > 0)
      {
        sprintf(format, "%.0f\t%.0f", protocolData -> eventCount_[i],
                    protocolData -> eventCached_[i]);
      }
      else
      {
        sprintf(format, "%.0f\t", protocolData -> eventCount_[i]);
      }

      strcat(buffer, format);

      sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                  protocolData -> eventBitsIn_[i], protocolData -> eventBitsIn_[i] / 8192,
                      protocolData -> eventBitsOut_[i], protocolData -> eventBitsOut_[i] / 8192,
                          protocolData -> eventBitsIn_[i] / protocolData -> eventCount_[i],
                              protocolData -> eventBitsOut_[i] / protocolData -> eventCount_[i]);

      strcat(buffer, format);

      if (protocolData -> eventBitsOut_[i] > 0)
      {
        sprintf(format, "%5.3f:1\n", protocolData -> eventBitsIn_[i] /
                                         protocolData -> eventBitsOut_[i]);
      }
      else
      {
        sprintf(format, "1:1\n");
      }

      strcat(buffer, format);
    }

    countEventIn       += protocolData -> eventCount_[i];
    countCachedEventIn += protocolData -> eventCached_[i];

    countEventBitsIn  += protocolData -> eventBitsIn_[i];
    countEventBitsOut += protocolData -> eventBitsOut_[i];

    countAnyIn   += protocolData -> eventCount_[i];
    countBitsIn  += protocolData -> eventBitsIn_[i];
    countBitsOut += protocolData -> eventBitsOut_[i];
  }

  if (countEventIn > 0)
  {
    if (countCachedEventIn > 0)
    {
      sprintf(format, "\ntotal:  %.0f\t%.0f", countEventIn, countCachedEventIn);
    }
    else
    {
      sprintf(format, "\ntotal:  %.0f\t", countEventIn);
    }

    strcat(buffer, format);

    sprintf(format, "\t%.0f (%.0f KB)\t%.0f (%.0f KB)\t%.0f/1 -> %.0f/1     \t",
                countEventBitsIn, countEventBitsIn / 8192, countEventBitsOut,
                    countEventBitsOut / 8192, countEventBitsIn / countEventIn,
                        countEventBitsOut / countEventIn);

    strcat(buffer, format);

    if (countEventBitsOut > 0)
    {
      sprintf(format, "%5.3f:1\n", countEventBitsIn / countEventBitsOut);
    }
    else
    {
      sprintf(format, "1:1\n");
    }

    strcat(buffer, format);
  }
  else
  {
    strcat(buffer, "N/A\n\n");
  }

  //
  // Print transport data.
  //

  getTimeStats(type, buffer);

  countAnyIn   += protocolData -> cupsCount_;
  countBitsIn  += protocolData -> cupsBitsIn_;
  countBitsOut += protocolData -> cupsBitsOut_;

  countAnyIn   += protocolData -> smbCount_;
  countBitsIn  += protocolData -> smbBitsIn_;
  countBitsOut += protocolData -> smbBitsOut_;

  countAnyIn   += protocolData -> mediaCount_;
  countBitsIn  += protocolData -> mediaBitsIn_;
  countBitsOut += protocolData -> mediaBitsOut_;

  countAnyIn   += protocolData -> httpCount_;
  countBitsIn  += protocolData -> httpBitsIn_;
  countBitsOut += protocolData -> httpBitsOut_;

  countAnyIn   += protocolData -> fontCount_;
  countBitsIn  += protocolData -> fontBitsIn_;
  countBitsOut += protocolData -> fontBitsOut_;

  countAnyIn   += protocolData -> slaveCount_;
  countBitsIn  += protocolData -> slaveBitsIn_;
  countBitsOut += protocolData -> slaveBitsOut_;

  //
  // Save the overall amount of bytes
  // coming from X clients.
  //

  overallData -> overallBytesIn_ = countBitsIn / 8;

  //
  // Print performance data.
  //

  if (transportData -> readTime_ > 0)
  {
    sprintf(format, "      %.0f messages (%.0f KB) encoded per second.\n\n",
                countAnyIn / (transportData -> readTime_ / 1000),
                    (countBitsIn + transportData -> framingBitsOut_) / 8192 /
                         (transportData -> readTime_ / 1000));
  }
  else
  {
    sprintf(format, "      %.0f messages (%.0f KB) encoded per second.\n\n",
                countAnyIn, (countBitsIn + transportData ->
                    framingBitsOut_) / 8192);
  }

  strcat(buffer, format);

  strcat(buffer, "link: ");

  //
  // ZLIB compression stats.
  //

  getStreamStats(type, buffer);

  //
  // Save the overall amount of bytes
  // sent on NX proxy link.
  //

  if (transportData -> compressedBytesOut_ > 0)
  {
    overallData -> overallBytesOut_ = transportData -> compressedBytesOut_;
  }
  else
  {
    overallData -> overallBytesOut_ = countBitsOut / 8;
  }

  //
  // Print info on multiplexing overhead.
  //

  getFramingStats(type, buffer);

  //
  // Print stats about additional channels.
  //

  getServicesStats(type, buffer);

  //
  // Compression summary.
  //

  double ratio = 1;

  if (transportData -> compressedBytesOut_ / 1024 > 0)
  {
    ratio = ((countBitsIn + transportData -> framingBitsOut_) / 8192) /
                  (transportData -> compressedBytesOut_ / 1024);

  }
  else if (countBitsOut > 0)
  {
    ratio = (countBitsIn + transportData -> framingBitsOut_) /
                 countBitsOut;
  }

  sprintf(format, "      Protocol compression ratio is %5.3f:1.\n\n",
              ratio);

  strcat(buffer, format);

  getBitrateStats(type, buffer);

  //
  // These are not included in output.
  //
  // getSplitStats(type, buffer);
  //

  strcat(buffer, "\n");

  //
  // These statistics are not included in output.
  // You can check it anyway to get the effective
  // amount of bytes produced by unpack procedure.
  //
  // getClientOverallStats(type, buffer);
  //

  return 1;
}

int Statistics::getServerOverallStats(int type, char *&buffer)
{
  return 1;
}

int Statistics::getTimeStats(int type, char *&buffer)
{
  struct T_transportData *transportData;

  if (type == PARTIAL_STATS)
  {
    transportData = &transportPartial_;
  }
  else
  {
    transportData = &transportTotal_;
  }

  char format[FORMAT_LENGTH];

  sprintf(format, "\ntime: %.0f ms idle, %.0fms (%.0f ms in read, %.0f ms in write) running.\n\n",
              transportData -> idleTime_, transportData -> readTime_,
                  transportData -> readTime_ - transportData -> writeTime_,
                      transportData -> writeTime_);

  strcat(buffer, format);

  return 1;
}

int Statistics::getStreamStats(int type, char *&buffer)
{
  struct T_transportData *transportData;

  if (type == PARTIAL_STATS)
  {
    transportData = &transportPartial_;
  }
  else
  {
    transportData = &transportTotal_;
  }

  char format[FORMAT_LENGTH];

  if (transportData -> compressedBytesOut_ > 0)
  {
    sprintf(format, "%.0f bytes (%.0f KB) compressed to %.0f (%.0f KB).\n",
                transportData -> compressedBytesIn_, transportData -> compressedBytesIn_ / 1024,
                    transportData -> compressedBytesOut_, transportData -> compressedBytesOut_ / 1024);

    strcat(buffer, format);

    sprintf(format, "      %5.3f:1 stream compression ratio.\n\n",
                transportData -> compressedBytesIn_ / transportData -> compressedBytesOut_);

    strcat(buffer, format);
  }

  if (transportData -> decompressedBytesOut_ > 0)
  {
    if (transportData -> compressedBytesOut_ > 0)
    {
      strcat(buffer, "      ");
    }

    sprintf(format, "%.0f bytes (%.0f KB) decompressed to %.0f (%.0f KB).\n",
                transportData -> decompressedBytesIn_, transportData -> decompressedBytesIn_ / 1024,
                    transportData -> decompressedBytesOut_, transportData -> decompressedBytesOut_ / 1024);

    strcat(buffer, format);

    sprintf(format, "      %5.3f:1 stream compression ratio.\n\n",
                transportData -> decompressedBytesOut_ / transportData -> decompressedBytesIn_);

    strcat(buffer, format);
  }

  if (transportData -> compressedBytesOut_ > 0 ||
          transportData -> decompressedBytesOut_ > 0)
  {
    strcat(buffer, "      ");
  }

  return 1;
}

int Statistics::getServicesStats(int type, char *&buffer)
{
  struct T_protocolData *protocolData;

  if (type == PARTIAL_STATS)
  {
    protocolData = &protocolPartial_;
  }
  else
  {
    protocolData = &protocolTotal_;
  }

  char format[FORMAT_LENGTH];

  if (protocolData -> cupsBitsOut_ > 0)
  {
    sprintf(format, "      %.0f CUPS messages, %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
                protocolData -> cupsCount_ , protocolData -> cupsBitsIn_ / 8,
                    protocolData -> cupsBitsIn_ / 8192, protocolData -> cupsBitsOut_ / 8,
                        protocolData -> cupsBitsOut_ / 8192);

    strcat(buffer, format);
  }

  if (protocolData -> smbBitsOut_ > 0)
  {
    sprintf(format, "      %.0f SMB messages, %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
                protocolData -> smbCount_ , protocolData -> smbBitsIn_ / 8,
                    protocolData -> smbBitsIn_ / 8192, protocolData -> smbBitsOut_ / 8,
                        protocolData -> smbBitsOut_ / 8192);

    strcat(buffer, format);
  }

  if (protocolData -> mediaBitsOut_ > 0)
  {
    sprintf(format, "      %.0f multimedia messages, %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
                protocolData -> mediaCount_ , protocolData -> mediaBitsIn_ / 8,
                    protocolData -> mediaBitsIn_ / 8192, protocolData -> mediaBitsOut_ / 8,
                        protocolData -> mediaBitsOut_ / 8192);

    strcat(buffer, format);
  }

  if (protocolData -> httpBitsOut_ > 0)
  {
    sprintf(format, "      %.0f HTTP messages, %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
                protocolData -> httpCount_ , protocolData -> httpBitsIn_ / 8,
                    protocolData -> httpBitsIn_ / 8192, protocolData -> httpBitsOut_ / 8,
                        protocolData -> httpBitsOut_ / 8192);

    strcat(buffer, format);
  }

  if (protocolData -> fontBitsOut_ > 0)
  {
    sprintf(format, "      %.0f font server messages, %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
                protocolData -> fontCount_ , protocolData -> fontBitsIn_ / 8,
                    protocolData -> fontBitsIn_ / 8192, protocolData -> fontBitsOut_ / 8,
                        protocolData -> fontBitsOut_ / 8192);

    strcat(buffer, format);
  }

  if (protocolData -> slaveBitsOut_ > 0)
  {
    sprintf(format, "      %.0f slave messages, %.0f bytes (%.0f KB) in, %.0f bytes (%.0f KB) out.\n\n",
                protocolData -> slaveCount_ , protocolData -> slaveBitsIn_ / 8,
                    protocolData -> slaveBitsIn_ / 8192, protocolData -> slaveBitsOut_ / 8,
                        protocolData -> slaveBitsOut_ / 8192);

    strcat(buffer, format);
  }

  return 1;
}

int Statistics::getFramingStats(int type, char *&buffer)
{
  struct T_transportData *transportData;

  if (type == PARTIAL_STATS)
  {
    transportData = &transportPartial_;
  }
  else
  {
    transportData = &transportTotal_;
  }

  char format[FORMAT_LENGTH];

  //
  // Print info on multiplexing overhead.
  //

  sprintf(format, "%.0f frames in, %.0f frames out, %.0f writes out.\n\n",
              transportData -> proxyFramesIn_, transportData -> proxyFramesOut_,
                  transportData -> proxyWritesOut_);

  strcat(buffer, format);

  sprintf(format, "      %.0f bytes (%.0f KB) used for framing and multiplexing.\n\n",
              transportData -> framingBitsOut_ / 8, transportData -> framingBitsOut_ / 8192);

  strcat(buffer, format);

  return 1;
}

int Statistics::getBitrateStats(int type, char *&buffer)
{
  struct T_transportData *transportData;
  struct T_overallData   *overallData;

  if (type == PARTIAL_STATS)
  {
    transportData = &transportPartial_;
    overallData = &overallPartial_;
  }
  else
  {
    transportData = &transportTotal_;
    overallData = &overallTotal_;
  }

  double total = 0;

  if (transportData -> idleTime_ + transportData -> readTime_ > 0)
  {
    total = overallData -> overallBytesOut_ /
                ((transportData -> idleTime_ + transportData -> readTime_) / 1000);
  }

  char format[FORMAT_LENGTH];

  sprintf(format, "      %.0f B/s average, %d B/s %ds, %d B/s %ds, %d B/s maximum.\n\n",
              total, getBitrateInShortFrame(), control -> ShortBitrateTimeFrame / 1000,
                  getBitrateInLongFrame(), control -> LongBitrateTimeFrame / 1000,
                      getTopBitrate());

  strcat(buffer, format);

  resetTopBitrate();

  return 1;
}

int Statistics::getSplitStats(int type, char *&buffer)
{
  //
  // Don't print these statistics if persistent
  // cache of images is disabled.
  //

  if (control -> ImageCacheEnableLoad == 0 &&
          control -> ImageCacheEnableSave == 0)
  {
    return 0;
  }

  struct T_splitData *splitData;

  if (type == PARTIAL_STATS)
  {
    splitData = &splitPartial_;
  }
  else
  {
    splitData = &splitTotal_;
  }

  char format[FORMAT_LENGTH];

  //
  // Print info on split messages restored from disk.
  //

  sprintf(format, "      %.0f images streamed, %.0f restored, %.0f bytes (%.0f KB) cached.\n\n",
              splitData -> splitCount_, splitData -> splitAborted_, splitData -> splitAbortedBytesOut_,
                  splitData -> splitAbortedBytesOut_ / 1024);

  strcat(buffer, format);

  return 1;
}
