It has been a while since I last worked on Aesalon proper.
commit58bca812424b53c476fe4a8a191a34cc0860cad7
authorethereal <ethereal.visage@gmail.com>
Thu, 4 Nov 2010 01:29:31 +0000 (3 19:29 -0600)
committerethereal <ethereal.visage@gmail.com>
Thu, 4 Nov 2010 01:29:31 +0000 (3 19:29 -0600)
treeb9dddf10ad2df3b69f079d8e394b1338a6e83d16
parent2ef8d0c6c6c5b2a8c7a2fb6d625ed2505571eead
It has been a while since I last worked on Aesalon proper.

I have, however, been doing some relatively serious thinking about it.
Basically, I've come to a conclusion: The current system of data collection,
transfer, and visualization is good . . .

. . . but it's not good enough.

Having only a single block of shared memory makes things easier, yes, but it
also impedes flexibility to a great degree. If each module loaded has its own,
unique block of shared memory -- except perhaps in situations where several
modules may share a line of communication -- then many problems, once
difficult, fade away into the mists of software design.

What problems, you ask? Well, consider the issue of monitoring the forked
children of a process. Rather than instituting a special system, the child's
modules are simply treated normally. Only a single monitor instance is
required, naught but a filing system (sorting by PID) is required for the
visualizer. I had another example when I originally concieved this design, but
I cannot seem to recall it now.

How will this be accomplished, though, some might ask. After thinking mightily
upon the subject (well, for about a second . . . whatever), the idea of using
the System V IPC functions appeared. They seem to be a good idea, primarily
because of their flexibility and other such reasons. The default maximum queue
length is 16384 bytes; and if one assumes the length of a "new module" message
is approximately 64 bytes (~30 bytes for module name, ~30 bytes for SHM path),
then one can place about 256 new module requests on the queue before blocking
behaviour begins. It is not unrealistic, however, to say that 256 modules is
not very many -- consider the case of a program that continually forks itself
to handle small jobs. If one has sixteen modules loaded, then the process must
fork itself only sixteen times before the queue becomes full.

A better idea is to use a UNIX pipe. UNIX pipe writes of <= PIPE_BUF bytes are
considered atomic (PIPE_BUF is specified to be at least 512 bytes by
POSIX.1-2001, more than enough for our purposes), and since it is a file
handle, it will also be shared throughout fork() and execve() calls, allowing
child processes to also make use of the pipe and thereby allow child modules
to open new shared memory blocks. Also, the pipe's buffer is typically
approximately 64KB (depending on the UNIX platform involved), allowing for
about four times the messages to be queued.

The major downside to this is the additional memory overhead; if each module
has its own shared memory block, there is clearly more memory used. It is not,
however, an unreasonable amount of memory -- assuming the sizes are chosen
intelligently. (this assumption breaks down under the users who allocate 256MB-
buffers . . .) Most modules (the thread module, for example) will only require
a minimally-sized buffer, say 16KB. The same will go for the network module
(this will change on, say, wget or firefox). In *general*, most modules will
only use a few kilobytes of buffer space, perhaps a megabyte for for the
higher-bandwidth modules, such as the thread-specific CPU-usage module on
processes with twenty threads. Proof: if the thread-specific CPU-usage module
is set to update a ten thousand times per second on a process with sixty
threads, and if the size of each packet is a mere 64 bytes (much larger than
the actual), then the calculations are simple: 10,000*60*64 = 38,400,000 bytes
per second, or about 38 MB/sec. It is not unreasonable to assume that the
monitor, on a relatively fast machine, can easily clear the shared memory
block of 1MB thirty-eight times per second. This becomes even more reasonable
if dynamic load-balancing between threads is implemented . . . which leads to
the next topic.

This system of multiple shared memory blocks works very well from the module's
point of view. However, it breaks down quite quickly for the monitor.
Currently, the convience of a single semaphore is used to control access to
the shared memory block. This semaphore system breaks down when the proposed
multiple-shared-memory-block setup is introduced. Therefore, it is necessary
for a thread to be created per module. This seems like it is far too resource-
intensive, however; it is not. Even with 30,000-odd threads, only
approximately 100MB of memory is required, approximately three kilobytes per
thread (not counting kernel overhead, of course). Therefore, as long as the
module count is kept reasonable (e.g. under a few thousand), the overhead is
relatively minimal. If the cost becomes too large, then a dynamic balancing
system can be implemented. (Find an average bandwidth per thread, then swap
different blocks between threads as their bandwidth changes. Not terribly
difficult . . . just annoying.)

This is by far the longest commit message for Aesalon that I've written yet,
and before my fingers fall off (and to give my aching wrist a break), I think
I will stop here. I've given myself something to consider further now, at
least.