A Real-Time Data Plotting Program

In order to provide an acceptable user interface, rtp must
quickly respond to GUI events (i.e., mouse events, etc.) at all
times. This requirement would be easily met if all program activity
were directed by GUI events. For example, an interactive drawing
program is entirely GUI-driven, so its only responsibility is to
execute relatively short sequences of code in response to GUI
events.

rtp's architecture is
complicated by two additional requirements, beyond the snappy GUI
response. It must quickly update when new data points are available
on STDIN. This feature is what differentiates rtp from other plot
programs, such as gnuplot. It must also deal with the fact that
rendering the data set often takes more time than is acceptable for
a GUI delay. This precludes the use of a simple function call to
render the whole plot.

Multiplexing Tasks

Fundamentally, there are three “tasks” that rtp must
multiplex, listed below from highest to lowest priority:

Respond quickly to GUI events. These events come as
data from the X server on a socket.

Read data points from STDIN as they become
available.

Render the data set into the plot window when it
needs updating.

The Qt library provides mechanisms that support this
processing structure. The first mechanism is the QSocketNotifier
class. When we create a QSocketNotifier object, we pass it the file
descriptor of interest. (The fancy name QSocketNotifier made me
think the class was all tied in with network sockets, when in
reality it can work with most any file descriptor.) In the case of
rtp, this is the STDIN file descriptor
(STDIN_FILENO). We then connect
QSocketNotifier's activated signal to a
particular slot that deals with new data.

The second mechanism is the QTimer class. This class is
provided to support regularly scheduled background processing, as
well as single-shot timed events. The Qt documentation tells us
that by setting up a QTimer object with zero timeout, we can cause
a function to be called whenever there are no events to process.
Again, the mechanisms for connecting the QTimer to the actual
function that does the background processing are signals and
slots.

Figure 2. rtp Control Flow and Data Processing

Components

Figure 2 illustrates rtp's control flow and data processing
scheme. The Qt event loop is the control center for the
application. It calls functions in the rtp application as events
occur. Each arrow in the diagram represents a call into a function
or library. Note that only the functions which have names starting
with PlotWindow or
RtpRender are actual rtp code. The
rtp functions consist of X event callbacks (such as
PlotWindow::paintEvent and
friends), the QSocketNotifier callback
(PlotWindow::slotStdinAwake) and
the QTimer callback
(RtpRender::slotWorkAwhile).

XLib is the lowest-level C library provided as an interface
to an X server across a byte-stream socket. It manages both the
input side of the socket, which provides events, and the output
side which sends requests to the server. (Note that Figure 2 shows
only the input side.) XLib also provides certain performance
optimizations, such as filtering redundant events and delaying
requests in an internal queue in order to group requests in large
data blocks for efficient socket usage. For details, see Adrian
Nye's classic XLib book in Resources.

The POSIX select system call
is commonly used by applications that service more than one file
descriptor (socket) in a single thread.
select is used by applications,
such as rtp, that must respond to data on any of several file
descriptors and do not wish to waste CPU time by polling.
Additionally, Qt uses select's timeout function to start
QTimer-scheduled functions.

The select call in the Qt library is the only place (to my
knowledge) where the rtp process can block. For newcomers to
systems programming, I should explain what “blocking” means. A
multitasking operating system such as Linux must be able to
multiplex the execution of a large number of programs on a smaller
number of processors. By trapping interrupts, Linux switches the
processor(s) among running programs in a certain order. This makes
it impossible for a single program to lock the system by entering
an infinite loop.

Because Linux has pre-emptive multitasking, rtp could enter
an infinite loop waiting for either an X event or a data point on
STDIN without locking the system. However, the CPU time spent in
this loop would unnecessarily degrade the performance of the rest
of the executing programs. Therefore, most calls into the kernel
for I/O will “block” the executing program until the I/O is
complete. The program will be removed from the set of programs run
by the CPU. Once the I/O is done, the program is marked as
“runnable” and will re-enter the kernel's run queue to be
switched in and out of the CPU along with other runnable
programs.

select is Qt's way of
saying, “I have nothing to do until an X event is available, an
I/O event occurs on one of the QSocketNotifier objects, or a
timeout from one of the QTimer objects
expires.” From Qt's point of view, select waits for one of these
events to occur, then returns.

The Qt documentation clearly describes how to use
QSocketNotifier and QTimer to hook into select. However, it does
not fully describe the priority levels of X events vs. other socket
events vs. timer events. In writing a program such as rtp, it is
important to understand these details, because the program's
performance greatly depends on them.