Browse Source

Display performance statistics in viewer

Adds an optional graph to the viewer to display current frame rate,
pixel rate and network bandwidth. Makes it easier to debug and test
performance related issues.
tags/v1.7.90
Pierre Ossman 7 years ago
parent
commit
921f6c86ba
6 changed files with 209 additions and 10 deletions
  1. 8
    6
      common/rfb/util.cxx
  2. 2
    2
      common/rfb/util.h
  3. 20
    1
      vncviewer/CConn.cxx
  4. 7
    0
      vncviewer/CConn.h
  5. 154
    1
      vncviewer/DesktopWindow.cxx
  6. 18
    0
      vncviewer/DesktopWindow.h

+ 8
- 6
common/rfb/util.cxx View File

@@ -139,7 +139,7 @@ namespace rfb {
static size_t doPrefix(long long value, const char *unit,
char *buffer, size_t maxlen,
unsigned divisor, const char **prefixes,
size_t prefixCount) {
size_t prefixCount, int precision) {
double newValue;
size_t prefix, len;

@@ -152,7 +152,7 @@ namespace rfb {
prefix++;
}

len = snprintf(buffer, maxlen, "%g %s%s", newValue,
len = snprintf(buffer, maxlen, "%.*g %s%s", precision, newValue,
(prefix == 0) ? "" : prefixes[prefix-1], unit);
buffer[maxlen-1] = '\0';

@@ -165,14 +165,16 @@ namespace rfb {
{ "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };

size_t siPrefix(long long value, const char *unit,
char *buffer, size_t maxlen) {
char *buffer, size_t maxlen, int precision) {
return doPrefix(value, unit, buffer, maxlen, 1000, siPrefixes,
sizeof(siPrefixes)/sizeof(*siPrefixes));
sizeof(siPrefixes)/sizeof(*siPrefixes),
precision);
}

size_t iecPrefix(long long value, const char *unit,
char *buffer, size_t maxlen) {
char *buffer, size_t maxlen, int precision) {
return doPrefix(value, unit, buffer, maxlen, 1024, iecPrefixes,
sizeof(iecPrefixes)/sizeof(*iecPrefixes));
sizeof(iecPrefixes)/sizeof(*iecPrefixes),
precision);
}
};

+ 2
- 2
common/rfb/util.h View File

@@ -99,9 +99,9 @@ namespace rfb {
unsigned msSince(const struct timeval *then);

size_t siPrefix(long long value, const char *unit,
char *buffer, size_t maxlen);
char *buffer, size_t maxlen, int precision=6);
size_t iecPrefix(long long value, const char *unit,
char *buffer, size_t maxlen);
char *buffer, size_t maxlen, int precision=6);
}

// Some platforms (e.g. Windows) include max() and min() macros in their

+ 20
- 1
vncviewer/CConn.cxx View File

@@ -73,7 +73,7 @@ static const PixelFormat mediumColourPF(8, 8, false, true,

CConn::CConn(const char* vncServerName, network::Socket* socket=NULL)
: serverHost(0), serverPort(0), desktop(NULL),
pendingPFChange(false),
frameCount(0), pixelCount(0), pendingPFChange(false),
currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
formatChange(false), encodingChange(false),
firstUpdate(true), pendingUpdate(false), continuousUpdates(false),
@@ -223,6 +223,21 @@ const char *CConn::connectionInfo()
return infoText;
}

unsigned CConn::getFrameCount()
{
return frameCount;
}

unsigned CConn::getPixelCount()
{
return pixelCount;
}

unsigned CConn::getPosition()
{
return sock->inStream().pos();
}

// The RFB core is not properly asynchronous, so it calls this callback
// whenever it needs to block to wait for more data. Since FLTK is
// monitoring the socket, we just make sure FLTK gets to run.
@@ -365,6 +380,8 @@ void CConn::framebufferUpdateEnd()
{
CConnection::framebufferUpdateEnd();

frameCount++;

Fl::remove_timeout(handleUpdateTimeout, this);
desktop->updateWindow();

@@ -441,6 +458,8 @@ void CConn::dataRect(const Rect& r, int encoding)
CConnection::dataRect(r, encoding);

sock->inStream().stopTiming();

pixelCount += r.area();
}

void CConn::setCursor(int width, int height, const Point& hotspot,

+ 7
- 0
vncviewer/CConn.h View File

@@ -40,6 +40,10 @@ public:

const char *connectionInfo();

unsigned getFrameCount();
unsigned getPixelCount();
unsigned getPosition();

// FdInStreamBlockCallback methods
void blockCallback();

@@ -89,6 +93,9 @@ private:

DesktopWindow *desktop;

unsigned frameCount;
unsigned pixelCount;

rfb::PixelFormat serverPF;
rfb::PixelFormat fullColourPF;


+ 154
- 1
vncviewer/DesktopWindow.cxx View File

@@ -64,7 +64,9 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
CConn* cc_)
: Fl_Window(w, h), cc(cc_), offscreen(NULL), overlay(NULL),
firstUpdate(true),
delayedFullscreen(false), delayedDesktopSize(false)
delayedFullscreen(false), delayedDesktopSize(false),
statsLastFrame(0), statsLastPixels(0), statsLastPosition(0),
statsGraph(NULL)
{
Fl_Group* group;

@@ -174,6 +176,12 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name,
fullscreen_on();
}

// Throughput graph for debugging
if (vlog.getLevel() >= LogWriter::LEVEL_DEBUG) {
memset(&stats, 0, sizeof(stats));
Fl::add_timeout(0, handleStatsTimeout, this);
}

// Show hint about menu key
Fl::add_timeout(0.5, menuOverlay, this);
}
@@ -187,6 +195,7 @@ DesktopWindow::~DesktopWindow()
Fl::remove_timeout(handleResizeTimeout, this);
Fl::remove_timeout(handleFullscreenTimeout, this);
Fl::remove_timeout(handleEdgeScroll, this);
Fl::remove_timeout(handleStatsTimeout, this);
Fl::remove_timeout(menuOverlay, this);
Fl::remove_timeout(updateOverlay, this);

@@ -195,6 +204,8 @@ DesktopWindow::~DesktopWindow()
delete overlay;
delete offscreen;

delete statsGraph;

// FLTK automatically deletes all child widgets, so we shouldn't touch
// them ourselves here
}
@@ -325,6 +336,23 @@ void DesktopWindow::draw()
update_child(*viewport);
}

// Debug graph (if active)
if (statsGraph) {
int ox, oy, ow, oh;

ox = X = w() - statsGraph->width() - 30;
oy = Y = h() - statsGraph->height() - 30;
ow = statsGraph->width();
oh = statsGraph->height();

fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh);

if (offscreen)
statsGraph->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, 204);
else
statsGraph->blend(ox - X, oy - Y, ox, oy, ow, oh, 204);
}

// Overlay (if active)
if (overlay) {
int ox, oy, ow, oh;
@@ -1161,3 +1189,128 @@ void DesktopWindow::handleEdgeScroll(void *data)

Fl::repeat_timeout(0.1, handleEdgeScroll, data);
}

void DesktopWindow::handleStatsTimeout(void *data)
{
DesktopWindow *self = (DesktopWindow*)data;

const size_t statsCount = sizeof(stats)/sizeof(stats[0]);

unsigned frame, pixels, pos;
unsigned elapsed;

const unsigned statsWidth = 200;
const unsigned statsHeight = 100;
const unsigned graphWidth = statsWidth - 10;
const unsigned graphHeight = statsHeight - 25;

Fl_Image_Surface *surface;
Fl_RGB_Image *image;

unsigned maxFPS, maxPPS, maxBPS;
size_t i;

char buffer[256];

frame = self->cc->getFrameCount();
pixels = self->cc->getPixelCount();
pos = self->cc->getPosition();
elapsed = msSince(&self->statsLastTime);
if (elapsed < 1)
elapsed = 1;

memmove(&self->stats[0], &self->stats[1], sizeof(stats[0])*(statsCount-1));

self->stats[statsCount-1].fps = (frame - self->statsLastFrame) * 1000 / elapsed;
self->stats[statsCount-1].pps = (pixels - self->statsLastPixels) * 1000 / elapsed;
self->stats[statsCount-1].bps = (pos - self->statsLastPosition) * 1000 / elapsed;

gettimeofday(&self->statsLastTime, NULL);
self->statsLastFrame = frame;
self->statsLastPixels = pixels;
self->statsLastPosition = pos;

#if !defined(WIN32) && !defined(__APPLE__)
// FLTK < 1.3.5 crashes if fl_gc is unset
if (!fl_gc)
fl_gc = XDefaultGC(fl_display, 0);
#endif

surface = new Fl_Image_Surface(statsWidth, statsHeight);
surface->set_current();

fl_rectf(0, 0, statsWidth, statsHeight, FL_BLACK);

fl_rect(5, 5, graphWidth, graphHeight, FL_WHITE);

maxFPS = maxPPS = maxBPS = 0;
for (i = 0;i < statsCount;i++) {
if (self->stats[i].fps > maxFPS)
maxFPS = self->stats[i].fps;
if (self->stats[i].pps > maxPPS)
maxPPS = self->stats[i].pps;
if (self->stats[i].bps > maxBPS)
maxBPS = self->stats[i].bps;
}

if (maxFPS != 0) {
fl_color(FL_GREEN);
for (i = 0;i < statsCount-1;i++) {
fl_line(5 + i * graphWidth / statsCount,
5 + graphHeight - graphHeight * self->stats[i].fps / maxFPS,
5 + (i+1) * graphWidth / statsCount,
5 + graphHeight - graphHeight * self->stats[i+1].fps / maxFPS);
}
}

if (maxPPS != 0) {
fl_color(FL_YELLOW);
for (i = 0;i < statsCount-1;i++) {
fl_line(5 + i * graphWidth / statsCount,
5 + graphHeight - graphHeight * self->stats[i].pps / maxPPS,
5 + (i+1) * graphWidth / statsCount,
5 + graphHeight - graphHeight * self->stats[i+1].pps / maxPPS);
}
}

if (maxBPS != 0) {
fl_color(FL_RED);
for (i = 0;i < statsCount-1;i++) {
fl_line(5 + i * graphWidth / statsCount,
5 + graphHeight - graphHeight * self->stats[i].bps / maxBPS,
5 + (i+1) * graphWidth / statsCount,
5 + graphHeight - graphHeight * self->stats[i+1].bps / maxBPS);
}
}

fl_font(FL_HELVETICA, 10);

fl_color(FL_GREEN);
snprintf(buffer, sizeof(buffer), "%u fps", self->stats[statsCount-1].fps);
fl_draw(buffer, 5, statsHeight - 5);

fl_color(FL_YELLOW);
siPrefix(self->stats[statsCount-1].pps * 8, "pix/s",
buffer, sizeof(buffer), 3);
fl_draw(buffer, 5 + (statsWidth-10)/3, statsHeight - 5);

fl_color(FL_RED);
iecPrefix(self->stats[statsCount-1].bps * 8, "Bps",
buffer, sizeof(buffer), 3);
fl_draw(buffer, 5 + (statsWidth-10)*2/3, statsHeight - 5);

image = surface->image();
delete surface;

Fl_Display_Device::display_device()->set_current();

delete self->statsGraph;
self->statsGraph = new Surface(image);
delete image;

self->damage(FL_DAMAGE_CHILD, self->w() - statsWidth - 30,
self->h() - statsHeight - 30,
statsWidth, statsHeight);

Fl::repeat_timeout(0.5, handleStatsTimeout, data);
}

+ 18
- 0
vncviewer/DesktopWindow.h View File

@@ -22,6 +22,8 @@

#include <map>

#include <sys/time.h>

#include <rfb/Rect.h>
#include <rfb/Pixel.h>

@@ -103,6 +105,8 @@ private:
static void handleScroll(Fl_Widget *wnd, void *data);
static void handleEdgeScroll(void *data);

static void handleStatsTimeout(void *data);

private:
CConn* cc;
Fl_Scrollbar *hscroll, *vscroll;
@@ -115,6 +119,20 @@ private:
bool firstUpdate;
bool delayedFullscreen;
bool delayedDesktopSize;

struct statsEntry {
unsigned fps;
unsigned pps;
unsigned bps;
};
struct statsEntry stats[100];

struct timeval statsLastTime;
unsigned statsLastFrame;
unsigned statsLastPixels;
unsigned statsLastPosition;

Surface *statsGraph;
};

#endif

Loading…
Cancel
Save