Thursday, December 7, 2006

Monitor your Linux computer with machine-generated music

Use open source FluidSynth and Perl to create simple tonal compositions representing the status of various parameters in your Linux computer

Use Perl and FluidSynth to create a real-time musical composition of your system status. Learn how to integrate various system monitoring data into a harmony-producing, MIDI-controlled audio synthesis. Explore audible information methods and configurations to help you monitor and manage your computing environment.

There are many visually informative monitoring programs for assessing the health of your computer environment. Everything from simple text displays to real-time charts and 3-D colored graphs are available to help you diagnose issues with your personal, server, or network computing devices. chordStats adds a new channel of interface to your system monitoring setup -- information passed through tone, timbre, and harmony.

Around 1998, Peep! The Network Auralizer was developed to provide an "ambient" audible environment to assist administrators in sensing a baseline and a perturbed state of their networks. Using sound effects played back according to certain events, administrators could immerse themselves in the normal sounds of their network (water rushing for general load, bird calls for network events, for example), and have an instant sense when something is amiss by the change in ambient sounds.

chordStats produces a similar sound environment with the addition of tones, instrument sound characteristics, and harmony creation based on system load. In this article, we create a simple Perl script to send note events to FluidSynth, force the various system events to be interpreted as a part of a harmonious interval, and discuss future options for enhancing your musical monitoring environment.

Requirements

Hardware

This article was developed, in part, on an Intel® Pentium® 4 with 256 megabytes of RAM. Since FluidSynth MIDI software synthesis can be quite resource-intensive, don't try it on anything less than a Pentium III with 256 MB of RAM. You also need a sound card. For the purposes of this article, we assume the user does not have a hardware MIDI wave-table synthesizer in the sound card, and, therefore, a software synthesizer was used. If you have a hardware synthesizer in your sound card or want to use chordStats with external hardware, to help you get started.

Software

A newer version (2.4 or later) of Linux® is recommended, along with Perl and FluidSynth. See Resources for the FluidSynth application. Also recommend are selected SoundFonts of your choice. An included SoundFont focusing on hammered instruments (tubular bells, piano, xylophone) is included with the Downloads. There are many sources of free SoundFonts on the Internet.

We use the vmstat program for simple system status monitoring. Standard on many Linux distributions, vmstat provides three forms of CPU load, generalized disk blocks in and out, and the capability for monitoring many other system functions.


Example setup and configuration

FluidSynth setup and checkout

After installing FluidSynth and downloading the example SoundFont, start the FluidSynth program with the command fluidsynth Hammered_Instruments.sf2. You will see output similar to the following:

Listing 1. FluidSynth output

lash_open_socket: could not look up host 'localhost': \
Servname not supported for ai_socktype
lash_open_socket: could not connect to host 'localhost', service '14541'
lash_comm_connect_to_server: could not create server connection
lash_init: could not connect to server 'localhost' - disabling LASH
lash_init: LASH_START_SERVER unset, not attempting to start server automatically
fluidsynth: warning: Failed to pin the sample data to RAM; swapping is possible.
ALSA lib timer_hw.c:269:(snd_timer_hw_open) \
extended read is not supported (SNDRV_TIMER_IOCTL_TREAD)
fluidsynth: warning: Requested a period size of 64, got 940 instead
fluidsynth: ALSA driver: Using format s16, rw, interleaved
FluidSynth version 1.0.7
Copyright (C) 2000-2006 Peter Hanappe and others.
Distributed under the LGPL license.
SoundFont(R) is a registered trademark of E-mu Systems, Inc.

Type 'help' for information on commands and 'help help' for help topics.


Don't worry about any of these warning messages. If you see the > symbol, FluidSynth is ready to start generating sounds. Try using noteon 5 77 100 to create a note in channel 5 with velocity 77 at volume level 100. If you can hear an electronic piano tone, you are ready to move on to the next step. Type quit or use Ctrl+C to exit FluidSynth. If the sound is unavailable or if you receive an error message, make sure your sound server has been started and that your mixer settings are correct.

When sending multiple note events to FluidSynth, you may notice some choppiness in the sound -- almost like a skipping audio CD. Increase the number of audio buffers and the size of each buffer to ameliorate this problem with the command fluidsynth Hammered_Instruments.sf2 -c10000 -z10000. This will start FluidSynth with 10,000 audio buffers each of size 10,000 -- plenty of space to use to reduce the choppiness.


The chordStats.pl program

General strategy

The selection of tempo, timbre, chords, and note velocity blends art and science in a way that is far beyond the scope of this article. With simplicity of development and information delivery in mind, the primary characteristics of this program are a 1-Hz refresh and octave-based note scales. The vmstat program provides a simple interface to the basic system data, as well as creating a 1-Hz "heartbeat" with which to base the tempo upon.

Listing 2. Main program parameters

#!/usr/bin/perl -w
# chordStats.pl - create music based on system status
use strict;

my $vmStatCmd = "vmstat 1"; # run vmstat every second
my $totalPackets = 0; # total of packets received and transmitted
my $lineCount = 0; # count number of vmstat output lines

my %fields = ();
my $count = 0;
# the field headers in the vmstat output, useful for referring to them by name
for( split " ", "r b swpd free buff cache si so bi bo in cs us sy id wa" ){
$fields{$_} = $count;
$count++;
}

# buffering output must be turned off because fluidsynth does not appear to
# accept buffered input from stdin
$|=1;


At the beginning of the script, we choose vmstat 1 as the command to be executed and read from every second. After setting up variables for the total number of packets to be recorded between reads and recording the number of lines read from the vmstat program, we move on to header definitions. The field headers bi (blocks in), bo (blocks out), and us (user CPU usage), are read from the vmstat program every second. The fields hash will allow referring to these data fields by name later in the program. Note the $|=1 line. You will notice some difficult-to-diagnose behavior if you remove this line; suffering from buffering, indeed!

Listing 3. Main program begin

# open the vmstat program to read from
open( INPIPE, "$vmStatCmd |" ) || die "can't read from vmstat";

# wait while the fluidsynth program opens
sleep(3);

while(my $statLine = ){

# ignore the header display, and the fieldname display lines
if( $statLine !~ /\-\-\-\-/ && $statLine !~ /swpd/ ){

# the first line of vmstat data is a recent average, ignore
if( $lineCount > 2 ){


The next section of code creates a pipe from the vmstat command, where the program will read data once per second. After waiting a few seconds for the FluidSynth program to activate, the processing of vmstat output can begin. The first three lines of the output will be ignored because they contain delimiters, header information, and recent averages, respectively.

Listing 4. Main program note processing

# reset wavetable synthesis
if( $totalTime % 10 == 0 ){ print "reset\n" }
$totalTime ++;

my $note = "";
my @currLine = split " ", $statLine;

# user cpu usage
$note = $currLine[ $fields{us} ];
sendNote( $note, 14, 12, 96 );

# conglomerate disk i/o fields to one stat
$note = $currLine[ $fields{bi} ] + $currLine[ $fields{bo} ];
if( $note > 1000 ){ $note = 1000; }
$note = $note/10;
sendNote( $note, 8, 12, 96 );

# network throughput on eth0
$note = getNetworkStats();
sendNote( $note, 5, 12, 84 );

}#if not first 3 lines to ignore

}#if not a header line

$lineCount++;

}#while reading the pipe

close(INPIPE);


If 10 seconds of time have passed, send a reset event to FluidSynth. This will clear any residual notes that are still processing, even if they have faded to an inaudible volume level. After variable initialization, the note corresponding to the us (user CPU usage) is activated with the sendNote command. Since the us field is always of a value 0-100, no additional processing is required. We simply use the sendNote subroutine to send the note velocity on channel 14 with a minimum velocity of 12 and a maximum of 96.

Following the initial note event the bi (blocks in) and bo (blocks out) data fields from vmstat will be conglomerated into one field between 0 and 1,000. Note that these values are well suited the test machine's standard IDE disk setup. If you have a high -- or low -- throughput disk subsystem, you may need to modify the maximum value to more accurately represent the available bandwidth. As soon as the total disk usage has been scaled between 0 and 1,000, it is divided by 10 to acquire a 0-100 value. This value is then sent as the note velocity on a channel 8 with the usual minimum and maximum.

The getNetworkStats section is slightly more complex, but as you can see, the data value is returned in the 0-100 range and sent off as another noteon event. Notice how the maximum value is 84 in this last note event. This is due to the SoundFont only creating audible notes on channel 5 from 0 to 84 in velocity. This may prove problematic to those musically inclined enough to detect a change in harmony at maximum utilization.

After we wrap up our looping brackets and timing code, it's on to the subroutines.

Listing 5. sendNote subroutine

sub sendNote {

my( $noteVal, $noteChan, $min, $max ) = @_;

if( $noteVal < $min ){

$noteVal = $min;

}else{

# divide it into twelve parts
$noteVal = sprintf( "%0.0f", $noteVal/12);

# reduce the note to 12 at the very least;
$noteVal = ($noteVal * 12);

if( $noteVal > $max ) { $noteVal = $max }

}#if note is > minimum

print "noteon $noteChan $noteVal 100\n";

}#sendNote


The sendNote subroutine takes a note velocity 0-100 in value and transforms that to the nearest 12-note step from the base note. In this case, our base note is 12, and all notes will use that as their 0 state. This provides a nice low frequency "throbbing" when the system is at low load personifying the sedentary state satisfyingly. All notes are printed at a volume level of 100 for simplicity's sake. Modifying the volume level according to other system factors could be an intuitive way to add information without adding notes or harmonial changes.

Listing 6. getNetworkStats subroutine

sub getNetworkStats {

my $networkCmd = "/sbin/ifconfig eth0 | grep 'RX bytes'";
$networkCmd = `$networkCmd`;

my $rxBytes = 0;
my $txBytes = 0;

chomp($networkCmd);

for( $networkCmd ){
$rxBytes = substr($_, 19);
$rxBytes = substr($rxBytes,0,index($rxBytes," "));
$txBytes = substr($_, 52);
$txBytes = substr($txBytes,0,index($txBytes," "));
my $bothBytes = $rxBytes + $txBytes;

if( $totalPackets == 0 ){
$totalPackets = $bothBytes;
}else{
# find the difference between measurements, set maximum difference to
# 1Mbit, which works well for `saturated' on a 100Mbit/sec network
# reduce the value by a factor of 10000, which spreads the usage
# nicely over 1-100
my $diffRX = $bothBytes - $totalPackets;
if( $diffRX > 1000000 ){ $diffRX = 1000000 }
$diffRX = ($diffRX / 10000);

$totalPackets = $bothBytes;
return( $diffRX );
}# if not first packet check

}# packet count check

}#getNetworkStats


The code is a simple if obtuse method of approximating the load on your Ethernet card. The output from the /sbin/ifconfig/eth0 command lists a total of all packets received and transmitted. On the test machines, 100Mbit/sec network, anything more than 1 million transmitted or received packets is considered full saturation. This value is then scaled to a 0-100 note velocity and played as an electronic piano note.

Running chordStats

The chordStats program will read the vmstat program from standard in, and write the note events -- unbuffered -- to FluidSynth's stdin. Start the chordStats program with the command perl chordStats.pl |fluidsynth Hammered_Instruments.sf2 -c10000 -z10000 >/dev/null. Remember that the -c and -z parameters are for enlarged sound-buffering options and may need to be tuned further for your particular system.

Now that you have the chordStats program running, try starting Mozilla. Note first the increase in CPU usage and disk I/O activity represented by the tubular bells and celesta. If you load a large page in Mozilla or download a file with wget, you will hear the electronic piano take a more strident tone as you saturate your network link.

Modifying chordStats

In addition to basic system load, chordStats can be easily modified to represent different aspects of your computing environment. Recall that vmstat 1 generates a list of values every second. This is used to provide note values, as well as creating a basic tempo dictation. One simple way to modify chordStats is to replace the vmstat command with an event generator that refreshes faster. For example, the fastUpdate.pl program included with the code distribution archive will scan the free memory, current processes, and Ethernet interrupts every two-tenths of a second. To use the fast-update version, modify the chordStats.pl program with the following changes.

Change the command from:

my $vmStatCmd = "vmstat 1";


to:

my $vmStatCmd = "./fastUpdate.pl 0.2 "


And change the header description area from:

for( split " ", "r b swpd free buff cache si so bi bo in cs us sy id wa" ){


to:

for( split " ", "memFree ethInt proc" ){


In the main program loop, there is no need for the code that ignores the header lines and the first two lines of input. You can remove those conditionals if you like. The output from the fastUpdate.pl script is already formatted in a 0-100 scale, so change the sendNote events from:

# user cpu usage
$note = $currLine[ $fields{us} ];
sendNote( $note, 14, 12, 96 );

# conglomerate disk i/o fields to one stat
$note = $currLine[ $fields{bi} ] + $currLine[ $fields{bo} ];
if( $note > 1000 ){ $note = 1000; }
$note = $note/10;
sendNote( $note, 8, 12, 96 );

# network throughput on eth0
$note = getNetworkStats();
sendNote( $note, 5, 12, 84 );


to:

$note = $currLine[ $fields{memFree} ];
sendNote( $note, 14, 48, 96 );

$note = $currLine[ $fields{ethInt} ];
sendNote( $note, 8, 48, 96 );

$note = $currLine[ $fields{proc} ];
sendNote( $note, 5, 48, 96 );


Notice how the minimum values have been increased to 48 for this revision. This is to compensate for the peculiarities of the selected SoundFont (Electronic_Sounds.sf2). Run this updated version of chordStats with the command perl chordStats.pl |fluidsynth Electronic_Sounds.sf2 -c10000 -z10000 >/dev/null, and you will have a nice 1960s sci-fi/B-movie soundtrack emanating from the speakers.


Conclusion

With this example, you can modify chordStats to play music based on any parameters of your choosing. Modify fastUpdate to read from the strace or gprof output, collate your build-process log files and monitor errors, or combine performance statistics from multiple machines and network devices into the note stream.

For additional interest, try modifying the volume parameters based on additional data. For example, if the number of data packets is staying constant, but the number of interrupts is increasing, increase the volume of the network note to emphasize attention on that aspect. Try moving in thirds instead of whole octaves, or insert a rest when a large change in values is detected to emphasize the succeeding phrase.