| /* 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; |
| } |