A Real-Time Data Plotting Program

How to program using the Qt windowing system in X.
Inside Qt's Event Handling

In order to understand how the X socket, other sockets and timers are prioritized, we have to look into the Qt source. Troll makes the Qt source freely available (see Resources for the URL). The code we want is in /src/kernel/qapplication_x11.cpp, under the distribution tree. Note that while Qt source is freely redistributable, Troll's license prohibits modification, unlike the GPL.

The function QApplication::processNextEvent, which is called by the main event loop, services the X socket, QSocketNotifier sockets and QTimer timers. QApplication::processNextEvent first checks for an X event to process. If none is found, it enters the select system call.

After select returns, QApplication::processNextEvent dispatches events to all QSocketNotifier objects whose file descriptors are ready. It then dispatches events to all QTimers whose timeouts have been reached. The event loop for Qt 1.44 can be summarized as follows (fd stands for file descriptor):

while (1)
{
   if (X event pending) {
      dispatch X event;
      continue;
   }
      timeout = minimum of all QTimer times to
         next event;
      select (X fd, all QSocketNotifier fd's,
         timeout);
      dispatch events to all QSocketNotifier's with
        active sockets;
      dispatch events to all QTimer's with expired
         times;
}

Note that X events get highest priority, in the sense that as long as there are more X events, the loop will ignore the QSocketNotifier's and QTimer's events. However, when an X event is not available, it is possible that Qt will execute every QSocketNotifier and QTimer event before returning to X events. This means we must consider the sum of registered QSocketNotifier and QTimer event processing times as the worst-case user interface latency.

Data Structures

From here on, I will refer to the rtp code in some detail. You may want to download the code from the URL given earlier and print it out with line numbers.

All of rtp's non-automatic data structures are embedded within two primary C++ classes. PlotWindow is derived from Qt's QWidget and provides all user interface callbacks, as well as the STDIN callback. These classes are laid out in rtp.h. The important data members of PlotWindow are:

  • deque<DoubPt> _points: the entire set of raw (x,y) data points received from stdin. deque is a C++ Standard Template Library class that (among other things) gives the illusion of contiguous memory layout (array indices, fake pointers that you can increment to move through the deque) to a dynamically sized block-linked data structure. Points are kept in the order in which they are received.

  • QPixmap *_buffer: the pixmap that is copied into the plot window whenever the window is painted.

  • RtpMapping _map: holds the viewport boundaries, scale factors and offsets currently in effect for mapping received data points into the plot window.

  • QRect *_rubberBox: if non-NULL, defines the “rubber” box that the user is bounding with the mouse to define a new viewport. Once the user releases the mouse, the box will be deleted from the screen and the viewport will change.

The other important rtp class is RtpRender. Its important data members are:

  • QTimer _timer: activated to call RtpRender::slotWorkAwhile while a rendering is in progress. Inactive when rendering is not in progress.

  • unsigned int _pti: marks the position in points as successive calls to RtpRender::slotWorkAwhile progress through the data.

  • QPixMap *_privateBuff: *_buf. _privateBuff will be created for a private rendering (explained below). _buf is the pixmap actually drawn into by RtpRender::slotWorkAwhile. It will equal either _privateBuff for a private rendering or the main repaint pixmap for an on-line rendering.

How rtp Processes Data Points

Data points to plot come in on STDIN. As part of its initial setup, rtp sets the STDIN file descriptor mode to non-blocking so that any read of STDIN will not block the program. This allows us to read stdin in relatively large chunks, increasing efficiency. rtp then creates a QSocketNotifier for STDIN, registering PlotWindow::slotStdinAwake as the callback through the signal/slot mechanism. Here is the code, from line 454 of rtp.c:

fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
QSocketNotifier sn(STDIN_FILENO,
  QSocketNotifier::Read);
QObject::connect(&sn, SIGNAL(activated(int)),
  plotter, SLOT(slotStdinAwake()));

When slotStdinAwake (rtp.c, line 100) is called, we know there is at least one character of data on STDIN (because select returned with STDIN marked as ready, and all POSIX I/O is character-based). However, simply reading one character and then returning is very inefficient. For optimal efficiency, we want to read and process as many characters as possible.

However, the time spent in slotStdinAwake contributes to the user interface latency, because no X events can be processed until slotStdinAwake exits. If we processed as many STDIN characters as were available and STDIN was receiving points at a faster rate than they could be processed, we could risk locking out the UI (user interface) completely. So we have a classic tradeoff between efficiency and latency. The current version of rtp attempts to read and process 1024 characters of data per slotStdinAwake call. However, because the read call doesn't block, we may not actually process this many characters.

slotStdinAwake is uglified by the fact that it does its own buffering and doesn't use the STDIO library. I couldn't tell from the GNU libc information whether STDIO would work after O_NONBLOCK had been set on the descriptor. Rather than find out, I took the lazy approach and wrote my own buffering code.

Listing 1

When rtp parses a new x,y data point, it will map it into the current pixmap if it is within the current viewport's range. If the point is out of range and the plot mode is auto-scale or auto-tracking, the entire plot must be redrawn at a new scaling and offset. The code in Listing 1 (rtp.c, line 255) handles these cases.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

bad link

Anonymous's picture
White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState