]> source.dussan.org Git - tigervnc.git/commitdiff
Display performance statistics in viewer
authorPierre Ossman <ossman@cendio.se>
Fri, 24 Feb 2017 11:33:09 +0000 (12:33 +0100)
committerPierre Ossman <ossman@cendio.se>
Fri, 24 Feb 2017 11:34:27 +0000 (12:34 +0100)
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.

common/rfb/util.cxx
common/rfb/util.h
vncviewer/CConn.cxx
vncviewer/CConn.h
vncviewer/DesktopWindow.cxx
vncviewer/DesktopWindow.h

index aec45f6136da2a85a8bcea68e4240f88a71e9f48..22e00ffc37dd12e0b6457ef546ab412e8aa382f2 100644 (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);
   }
 };
index 9ad177207f79160cb6ba4d290cfd4989b691fd92..e9114c3d79c04795d672b349ff667ab6ac6ed27c 100644 (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
index a692732c2fc6a40a03fd002702296ae859de72ff..addc30dfbf431f15d55de446477c043920e14d1c 100644 (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,
index d6dd4a755086ec4c658309d3cb274d4b334e7404..93cc278f8a6ebc0a2758072dec51fe64f0a538ab 100644 (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;
 
index 8fdd59b7efb130109d1e84eca0b797dc20149818..1f0f55f2b6fd0c9b3ac9102a44320bbf61506b8b 100644 (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);
+}
index 11f3dc2059cfe1a9f4d9aa6d872d2208f881e018..4224699c497fafdfd30e6d7c47786c6ad1b7c831 100644 (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