summaryrefslogtreecommitdiffstats
path: root/tests/perf/encperf.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'tests/perf/encperf.cxx')
-rw-r--r--tests/perf/encperf.cxx506
1 files changed, 506 insertions, 0 deletions
diff --git a/tests/perf/encperf.cxx b/tests/perf/encperf.cxx
new file mode 100644
index 00000000..e461197e
--- /dev/null
+++ b/tests/perf/encperf.cxx
@@ -0,0 +1,506 @@
+/* Copyright 2015 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * Copyright (C) 2015 D. R. Commander. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*
+ * This program reads files produced by TightVNC's/TurboVNC's
+ * fbs-dump, which in turn takes files from rfbproxy. It is
+ * basically a dump of the RFB protocol from the server side after
+ * the ServerInit message. Mostly this consists of FramebufferUpdate
+ * message using the HexTile encoding. Screen size and pixel format
+ * are not encoded in the file and must be specified by the user.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/time.h>
+
+#include <rdr/Exception.h>
+#include <rdr/OutStream.h>
+#include <rdr/FileInStream.h>
+
+#include <rfb/PixelFormat.h>
+
+#include <rfb/CConnection.h>
+#include <rfb/CMsgReader.h>
+#include <rfb/UpdateTracker.h>
+
+#include <rfb/EncodeManager.h>
+#include <rfb/SConnection.h>
+#include <rfb/SMsgWriter.h>
+
+#include "util.h"
+
+static rfb::IntParameter width("width", "Frame buffer width", 0);
+static rfb::IntParameter height("height", "Frame buffer height", 0);
+static rfb::IntParameter count("count", "Number of benchmark iterations", 9);
+
+static rfb::StringParameter format("format", "Pixel format (e.g. bgr888)", "");
+
+static rfb::BoolParameter translate("translate",
+ "Translate 8-bit and 16-bit datasets into 24-bit",
+ true);
+
+// The frame buffer (and output) is always this format
+static const rfb::PixelFormat fbPF(32, 24, false, true, 255, 255, 255, 0, 8, 16);
+
+// Encodings to use
+static const rdr::S32 encodings[] = {
+ rfb::encodingTight, rfb::encodingCopyRect, rfb::encodingRRE,
+ rfb::encodingHextile, rfb::encodingZRLE, rfb::pseudoEncodingLastRect,
+ rfb::pseudoEncodingQualityLevel0 + 8,
+ rfb::pseudoEncodingCompressLevel0 + 2};
+
+class DummyOutStream : public rdr::OutStream {
+public:
+ DummyOutStream();
+
+ virtual int length();
+ virtual void flush();
+
+private:
+ virtual int overrun(int itemSize, int nItems);
+
+ int offset;
+ rdr::U8 buf[131072];
+};
+
+class CConn : public rfb::CConnection {
+public:
+ CConn(const char *filename);
+ ~CConn();
+
+ void getStats(double& ratio, unsigned long long& bytes,
+ unsigned long long& rawEquivalent);
+
+ virtual void initDone();
+ virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*);
+ virtual void framebufferUpdateStart();
+ virtual void framebufferUpdateEnd();
+ virtual void dataRect(const rfb::Rect&, int);
+ virtual void setColourMapEntries(int, int, rdr::U16*);
+ virtual void bell();
+ virtual void serverCutText(const char*);
+
+public:
+ double decodeTime;
+ double encodeTime;
+
+protected:
+ rdr::FileInStream *in;
+ rfb::SimpleUpdateTracker updates;
+ class SConn *sc;
+};
+
+class Manager : public rfb::EncodeManager {
+public:
+ Manager(class rfb::SConnection *conn);
+
+ void getStats(double&, unsigned long long&, unsigned long long&);
+};
+
+class SConn : public rfb::SConnection {
+public:
+ SConn();
+ ~SConn();
+
+ void writeUpdate(const rfb::UpdateInfo& ui, const rfb::PixelBuffer* pb);
+
+ void getStats(double&, unsigned long long&, unsigned long long&);
+
+ virtual void setAccessRights(AccessRights ar);
+
+ virtual void setDesktopSize(int fb_width, int fb_height,
+ const rfb::ScreenSet& layout);
+
+protected:
+ DummyOutStream *out;
+ Manager *manager;
+};
+
+DummyOutStream::DummyOutStream()
+{
+ offset = 0;
+ ptr = buf;
+ end = buf + sizeof(buf);
+}
+
+int DummyOutStream::length()
+{
+ flush();
+ return offset;
+}
+
+void DummyOutStream::flush()
+{
+ offset += ptr - buf;
+ ptr = buf;
+}
+
+int DummyOutStream::overrun(int itemSize, int nItems)
+{
+ flush();
+ if (itemSize * nItems > end - ptr)
+ nItems = (end - ptr) / itemSize;
+ return nItems;
+}
+
+CConn::CConn(const char *filename)
+{
+ decodeTime = 0.0;
+ encodeTime = 0.0;
+
+ in = new rdr::FileInStream(filename);
+ setStreams(in, NULL);
+
+ // Need to skip the initial handshake and ServerInit
+ setState(RFBSTATE_NORMAL);
+ // That also means that the reader and writer weren't setup
+ setReader(new rfb::CMsgReader(this, in));
+ // Nor the frame buffer size and format
+ rfb::PixelFormat pf;
+ pf.parse(format);
+ setPixelFormat(pf);
+ setDesktopSize(width, height);
+
+ sc = new SConn();
+ sc->client.setPF((bool)translate ? fbPF : pf);
+ sc->setEncodings(sizeof(encodings) / sizeof(*encodings), encodings);
+}
+
+CConn::~CConn()
+{
+ delete sc;
+ delete in;
+}
+
+void CConn::getStats(double& ratio, unsigned long long& bytes,
+ unsigned long long& rawEquivalent)
+{
+ sc->getStats(ratio, bytes, rawEquivalent);
+}
+
+void CConn::initDone()
+{
+ rfb::ModifiablePixelBuffer *pb;
+
+ pb = new rfb::ManagedPixelBuffer((bool)translate ? fbPF : server.pf(),
+ server.width(), server.height());
+ setFramebuffer(pb);
+}
+
+void CConn::setCursor(int, int, const rfb::Point&, const rdr::U8*)
+{
+}
+
+void CConn::framebufferUpdateStart()
+{
+ CConnection::framebufferUpdateStart();
+
+ updates.clear();
+ startCpuCounter();
+}
+
+void CConn::framebufferUpdateEnd()
+{
+ rfb::UpdateInfo ui;
+ rfb::PixelBuffer* pb = getFramebuffer();
+ rfb::Region clip(pb->getRect());
+
+ CConnection::framebufferUpdateEnd();
+
+ endCpuCounter();
+
+ decodeTime += getCpuCounter();
+
+ updates.getUpdateInfo(&ui, clip);
+
+ startCpuCounter();
+ sc->writeUpdate(ui, pb);
+ endCpuCounter();
+
+ encodeTime += getCpuCounter();
+}
+
+void CConn::dataRect(const rfb::Rect &r, int encoding)
+{
+ CConnection::dataRect(r, encoding);
+
+ if (encoding != rfb::encodingCopyRect) // FIXME
+ updates.add_changed(rfb::Region(r));
+}
+
+void CConn::setColourMapEntries(int, int, rdr::U16*)
+{
+}
+
+void CConn::bell()
+{
+}
+
+void CConn::serverCutText(const char*)
+{
+}
+
+Manager::Manager(class rfb::SConnection *conn) :
+ EncodeManager(conn)
+{
+}
+
+void Manager::getStats(double& ratio, unsigned long long& encodedBytes,
+ unsigned long long& rawEquivalent)
+{
+ StatsVector::iterator iter;
+ unsigned long long bytes, equivalent;
+
+ bytes = equivalent = 0;
+ for (iter = stats.begin(); iter != stats.end(); ++iter) {
+ StatsVector::value_type::iterator iter2;
+ for (iter2 = iter->begin(); iter2 != iter->end(); ++iter2) {
+ bytes += iter2->bytes;
+ equivalent += iter2->equivalent;
+ }
+ }
+
+ ratio = (double)equivalent / bytes;
+ encodedBytes = bytes;
+ rawEquivalent = equivalent;
+}
+
+SConn::SConn()
+{
+ out = new DummyOutStream;
+ setStreams(NULL, out);
+
+ setWriter(new rfb::SMsgWriter(&client, out));
+
+ manager = new Manager(this);
+}
+
+SConn::~SConn()
+{
+ delete manager;
+ delete out;
+}
+
+void SConn::writeUpdate(const rfb::UpdateInfo& ui, const rfb::PixelBuffer* pb)
+{
+ manager->writeUpdate(ui, pb, NULL);
+}
+
+void SConn::getStats(double& ratio, unsigned long long& bytes,
+ unsigned long long& rawEquivalent)
+{
+ manager->getStats(ratio, bytes, rawEquivalent);
+}
+
+void SConn::setAccessRights(AccessRights ar)
+{
+}
+
+void SConn::setDesktopSize(int fb_width, int fb_height,
+ const rfb::ScreenSet& layout)
+{
+}
+
+struct stats
+{
+ double decodeTime;
+ double encodeTime;
+ double realTime;
+
+ double ratio;
+ unsigned long long bytes;
+ unsigned long long rawEquivalent;
+};
+
+static struct stats runTest(const char *fn)
+{
+ CConn *cc;
+ struct stats s;
+ struct timeval start, stop;
+
+ gettimeofday(&start, NULL);
+
+ try {
+ cc = new CConn(fn);
+ } catch (rdr::Exception& e) {
+ fprintf(stderr, "Failed to open rfb file: %s\n", e.str());
+ exit(1);
+ }
+
+ try {
+ while (true)
+ cc->processMsg();
+ } catch (rdr::EndOfStream& e) {
+ } catch (rdr::Exception& e) {
+ fprintf(stderr, "Failed to run rfb file: %s\n", e.str());
+ exit(1);
+ }
+
+ gettimeofday(&stop, NULL);
+
+ s.decodeTime = cc->decodeTime;
+ s.encodeTime = cc->encodeTime;
+ s.realTime = (double)stop.tv_sec - start.tv_sec;
+ s.realTime += ((double)stop.tv_usec - start.tv_usec)/1000000.0;
+ cc->getStats(s.ratio, s.bytes, s.rawEquivalent);
+
+ delete cc;
+
+ return s;
+}
+
+static void sort(double *array, int count)
+{
+ bool sorted;
+ int i;
+ do {
+ sorted = true;
+ for (i = 1; i < count; i++) {
+ if (array[i-1] > array[i]) {
+ double d;
+ d = array[i];
+ array[i] = array[i - 1];
+ array[i - 1] = d;
+ sorted = false;
+ }
+ }
+ } while (!sorted);
+}
+
+static void usage(const char *argv0)
+{
+ fprintf(stderr, "Syntax: %s [options] <rfb file>\n", argv0);
+ fprintf(stderr, "Options:\n");
+ rfb::Configuration::listParams(79, 14);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ const char *fn;
+
+ fn = NULL;
+ for (i = 1; i < argc; i++) {
+ if (rfb::Configuration::setParam(argv[i]))
+ continue;
+
+ if (argv[i][0] == '-') {
+ if (i + 1 < argc) {
+ if (rfb::Configuration::setParam(&argv[i][1], argv[i + 1])) {
+ i++;
+ continue;
+ }
+ }
+ usage(argv[0]);
+ }
+
+ if (fn != NULL)
+ usage(argv[0]);
+
+ fn = argv[i];
+ }
+
+ int runCount = count;
+ struct stats *runs = new struct stats[runCount];
+ double *values = new double[runCount];
+ double *dev = new double[runCount];
+ double median, meddev;
+
+ if (fn == NULL) {
+ fprintf(stderr, "No file specified!\n\n");
+ usage(argv[0]);
+ }
+
+ if (strcmp(format, "") == 0) {
+ fprintf(stderr, "Pixel format not specified!\n\n");
+ usage(argv[0]);
+ }
+
+ if (width == 0 || height == 0) {
+ fprintf(stderr, "Frame buffer size not specified!\n\n");
+ usage(argv[0]);
+ }
+
+ // Warmup
+ runTest(fn);
+
+ // Multiple runs to get a good average
+ for (i = 0; i < runCount; i++)
+ runs[i] = runTest(fn);
+
+ // Calculate median and median deviation for CPU usage decoding
+ for (i = 0;i < runCount;i++)
+ values[i] = runs[i].decodeTime;
+
+ sort(values, runCount);
+ median = values[runCount/2];
+
+ for (i = 0;i < runCount;i++)
+ dev[i] = fabs((values[i] - median) / median) * 100;
+
+ sort(dev, runCount);
+ meddev = dev[runCount/2];
+
+ printf("CPU time (decoding): %g s (+/- %g %%)\n", median, meddev);
+
+ // And for CPU usage encoding
+ for (i = 0;i < runCount;i++)
+ values[i] = runs[i].encodeTime;
+
+ sort(values, runCount);
+ median = values[runCount/2];
+
+ for (i = 0;i < runCount;i++)
+ dev[i] = fabs((values[i] - median) / median) * 100;
+
+ sort(dev, runCount);
+ meddev = dev[runCount/2];
+
+ printf("CPU time (encoding): %g s (+/- %g %%)\n", median, meddev);
+
+ // And for CPU core usage encoding
+ for (i = 0;i < runCount;i++)
+ values[i] = (runs[i].decodeTime + runs[i].encodeTime) / runs[i].realTime;
+
+ sort(values, runCount);
+ median = values[runCount/2];
+
+ for (i = 0;i < runCount;i++)
+ dev[i] = fabs((values[i] - median) / median) * 100;
+
+ sort(dev, runCount);
+ meddev = dev[runCount/2];
+
+ printf("Core usage (total): %g (+/- %g %%)\n", median, meddev);
+
+#ifdef WIN32
+ printf("Encoded bytes: %I64d\n", runs[0].bytes);
+ printf("Raw equivalent bytes: %I64d\n", runs[0].rawEquivalent);
+#else
+ printf("Encoded bytes: %lld\n", runs[0].bytes);
+ printf("Raw equivalent bytes: %lld\n", runs[0].rawEquivalent);
+#endif
+ printf("Ratio: %g\n", runs[0].ratio);
+
+ return 0;
+}