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;
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';
{ "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);
}
};
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),
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.
{
CConnection::framebufferUpdateEnd();
+ frameCount++;
+
Fl::remove_timeout(handleUpdateTimeout, this);
desktop->updateWindow();
CConnection::dataRect(r, encoding);
sock->inStream().stopTiming();
+
+ pixelCount += r.area();
}
void CConn::setCursor(int width, int height, const Point& hotspot,
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;
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);
}
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);
delete overlay;
delete offscreen;
+ delete statsGraph;
+
// FLTK automatically deletes all child widgets, so we shouldn't touch
// them ourselves here
}
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;
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);
+}