Saturday, April 25, 2015

How to keep an installation running on a Raspberry Pi

Previously, I covered, how to keep an installation running. In that post, I wrote about how anything could crash. The Pi is much the same, except everything will crash at some point.

I have been working with SC3.6 (see these build instructions), but this should also be applicable to any other versions of SuperCollider. This is written for Raspbian Wheezy but should work with other versions of Raspbian. It will not work with other Pi operating systems, such as windows, without substantial modifications, especially to the Bash Script

There are a few important differences between running an installation on a laptop and on a Pi.

  • The Pi has fewer resources and crashes much more often and in more ways.
  • The SC server is started separately from sclang
  • Backing up a Pi system is just duplicating an SD card, so anything weird or dangerous you do is much less risky than it would be on your laptop

Getting the Pi Ready

Are you planning on attaching a monitor to the Pi? If so, you will want to tell to boot to the desktop. Otherwise, you want to boot to a command line. You can set these options by running sudo raspi-config. My only experience thus far is with the desktop, because I am also generating some simple graphics. Otherwise, I would definitely boot to the command line. The Pi does not have a lot of processor power and if you're not using the Desktop, you can save some space for installation to run.

Getting your code ready

Efficiency

SC 3.6 on a Pi runs exactly the same code as SC 3.6 on my laptop. Therefore, installation-ready code should run fine on a Pi. There is less memory on a Pi, which may limit the number of Buffers you can have loaded at one time. I have not experienced any issues with running out of memory. The processor, however, does climb to 100% quite easily and this can lead to audio stuttering. Your synthdefs and invocation should be as efficient as possible. If you always fire off three synths at the same time, say, each with a different frequency, you may want to combine them into one single synthdef that takes three frequency arguments. Starting off one more complicated synthdef requires fewer resources than three simpler synthdefs running at the same time.

If you are running the the desktop, you can see how much of the CPU is being used by looking at the monitor box in the upper right hand corner of the screen. If you are running headless and have connected to the Pi via ssh, you can monitor the CPU using the command 'top'. Open a second terminal window, connect to the Pi and type in top -u pi at the command line. The program will tell you the computer's load average and how much of the CPU each program is using.

Within SuperCollider, to find out how much CPU the server is using, Server.default.avgCPU returns a number between 0 and 100. I've used this information to help calculate how many steps to use for a fade in the graphics. A busier CPU means I fade in fewer steps with longer pauses between steps.

Starting the server separately

This is a constraint imposed by the build instructions for 3.6. Right now, we're just going to discuss how this changes our SC code. Fortunately, very little needs changing. Use the same code for booting the server as was covered in the previous tutorial. The error checking is still useful - even if the server is actually already booted.

As the server is actually started outside SuperCollider, the port number for the server can (and will) change. We will tell SuperCollider about the port number via command line arguments - we will tell our program about the port number when we start running it. The arguments will be available to our program in an array: thisProcess.argv. Arguments to our program start at index 0. We need to get this data before we do anything involving the server.


(
var server_port;
// ...
if ((thisProcess.argv.size > 0), {
 server_port = thisProcess.argv[0];
 Server.default = Server(\Raspberry, NetAddr("localhost", server_port.asInteger));
 s = Server.default;
})
// ... Boot or s.waitForBoot only after the above
)

We start by checking if we received any arguments. If the argv.size is 0, then we did not. This means we can still use the same code in the IDE on our regular computer.

The first argument is the very first thing in argv. If there are subsequent arguments, they will also be in argv, in the order they were given.

We create a new server object, called Raspberry, that uses the port we've been given as an argument. Note that we also convert the port to an integer, from a string.

Our new Server object will be our default server, which we also assign to the global s variable. This means everything will work as normal, even on an arbitrary port.

The Bash scripts

Previously, we used a bash script instead of an IDE. We are going to do that again, but now our script is going to do a few extra things - only if we're on a Pi.

Bash scripts always start out with #! at the top of the file. After that line, from then on, anything after a # character is a comment, like anything after // in SuperCollider.

The SuperCollider directory on Pi is either /usr/bin or us/local/bin. to find out which, type which sclang at the command line. If yours is different than the code below, you will need to change it.


#!/bin/bash

port=57110

sleep 20

while true
  do
    #are we on a raspberry pi?
    if [ -f /etc/rpi-issue ]
      then
        # we need these two lines in order to make sound
        export SC_JACK_DEFAULT_INPUTS="system"
        export SC_JACK_DEFAULT_OUTPUTS="system"

        # get rid of anything already running
        killall scsynth
        sleep 2
        killall jackd

        # wait for things to settle down
        sleep 20

        # start jackd
        ( jackd -T -p 32 -d alsa -d hw:0,0 -r 44100 -p 1024 -n3  -s || sudo shutdown -r now ) &

        #wait for jack to settle down
        sleep 10

        # make sure we increment the port every loop
        port=$(( $port + 1))

        # start the server
        /usr/local/bin/scsynth -u $port & # check supercollider's directory!
        server=$!
        sleep 1
        
        # is the server running?
        if kill -0 $server  2> /dev/null
            then
                #all good
                echo "started"
            else
                # try again
                        
                sleep 5
                port=$(( $port + 1 ))                        
                /usr/local/bin/scsynth -u $port & # check supercollider's directory!
                server=$!
                        
                sleep 1

                # is the server running?
                if kill -0 $server  2> /dev/null
                    then
                        #all good
                        echo "started"
                    else
                        sudo shutdown -r now
                fi  
        fi

        sleep 5

        # is the server still running?
        if kill -0 $server  2> /dev/null
            then
                #all good
                echo "still going"
            else
                sudo shutdown -r now
        fi

        /usr/local/sclang installation.scd $port # check supercollider's directory!
    
    else

        /path/to/sclang installation.scd #SuperCollider on your normal computer

    fi


    sleep 1

    killall scsynth

    sleep 1

done


This script is a lot more complex than the last one! This is because more things can go wrong, alas.

We start by initialising a variable and then sleeping for 20 seconds before doing anything. The sleep is there at the start because we are going to start the script whenever the Pi boots. The 20 seconds of waiting around gives time for services to start, like the internet connection, if we have one. Also, it gives us time to open a terminal or a connection and type killall installation.sh if we don't want the installation to actually run.

Then we test if we're on a Pi, by looking to see if a Raspbian-specific file exists. If we are on a Pi, we set two variables that SC3.6 needs set in order to make sound.

Then we kill any servers or versions of jack left running, possibly from previous times through the loop. Then we sleep for 20 more seconds. The Pi is not a fast computer and this gives the system time to settle down after killing jackd, which is the audio server (like CoreAudio) used on Raspbian.

Then we try to start jack up again and run it in the background. If it does not start, we reboot the computer. The default user on a Pi is called 'pi'. By default, that user can sudo without entering a password. We are taking advantage of that to make the system reboot. On a normal computer, it would be risky to leave an installation running that could get superuser privileges so easily. However, the Pi's system is just an SD card, which hopefully we have a spare copy of. If something goes horribly wrong, we can just pop in a new card.

We will configure the Pi so the script runs on startup. This means if the computer reboots, the installation will restart.

Then we wait 10 seconds for jack to settle down.

We then increment the port number. In my testing, many crashes are caused by the server crashing. When it crashes, it may not let go of the port it was using. By incrementing every time, we are less likely to have trouble starting the server.

Then we start the server, wait 1 second and check if it's running. If it's not running, we wait 5 seconds, increment the port and try again. If the second attempt fails, we reboot.

Then we wait five more seconds and check AGAIN if the server is running. This may seem like overkill, but, unfortunately, the system can get into a state where a server will start, but then crash about 2 seconds later. By waiting five seconds, we can make sure the server will actually stay started. If not, we reboot.

Then, finally, we start running the installation program!

Below that, we have an else and the invocation we would use on our other computer. Obviously, this example is slightly silly, as it would make more sense in this case just to have two different scripts. However, if you have helper apps or have to move files around before starting, etc, it can make sense to use the same script for two different systems.

Using helper scripts

If you have a helper script working in the background, it's going to take up some of the processor power of the Pi. However, if you don't mind it running more slowly, you can tell it to use less power. There is a unix command called nice, which you can use when starting your helper script. You give it a number saying how nice a process should be, ranging from -20 to 19. A high number is a very nice process - it becomes more and more sharing (and runs more and more slowly). A negative number is less and less nice. For our installation, we will tell helper scripts to be more nice, but we'll leave sclang and scsynth running normally. (You might want to experiment with making them less nice. I have avoided this out of worry it would interfere with my debugging process.)

You would alter your script to add:


nice -n 10 /path/to/helper/script.sh &
helperpid=$!
sleep 1
/usr/local/sclang installation.scd $port # check supercollider's directory!
sleep 1
killall scsynth
kill $helperpid

Changing the order

What if you want to start the helper script after sclang? We still would want to base the loop on when sclang crashes. Fortunately, we can tell a loop to wait for something to stop (read: crash).


/usr/local/sclang installation.scd $port &# check supercollider's directory!
scpid=$!
sleep 1 # if you want to pause, otherwise, skip this line
nice -n 10 /path/to/helper/script.sh &
helperpid=$!

wait $scpid
sleep 1
killall scsynth
kill $helperpid

Writing Files

To write or not to write? We might want to write files for a few reasons. Perhaps we have a helper script that doesn't understand OSC, or the data we want to pass is particularly large, or a buffer, for example. Or, because on the Pi a server crash means an sclang crash, we may find that our program crashes much more often, so we want to leave notes to ourself, telling the program where it should start up again.

On the other hand, SD cards are not as robust as a normal disk drive. They have a limited number of writes before they die. (Always make a copy of your SD card!) The raspbian operating system does a lot of file writes already. Some Pi users have instructions on how to vastly reduce the number of writes. If you are concerned about the longevity of your SD card, the questions to ask yourself are about budget ('Can I afford the more expensive SD cards that will last longer?' 'How many spares can I keep on hand?') and the installation longevity ('Is this going to run for days or for weeks / months / years? Can I just give a small stack of duplicate SD cards to the organisation running it and will they be able to manage recognising the installation is borked and putting in a new card?'). For what it's worth, I have been using one SD card for more than two weeks of testing with no effort to reduce file writes and doing my own file writing. I have made an image of my SD card and plan to bring a couple of duplicates with me when I set up the installation.

Crash Recovery

Because everything on the installation eventually will crash, you need to keep in mind that a program writing a file will sometimes crash mid-write. Therefore, the program reading the files needs to check if the file exists before opening it, and then, once open, check if it has been corrupted. Note that since the system will sometimes reboot, the writer may have been just partway through writing the file when the system went down.

When my installation is running, I keep my files in /tmp. This is a special directory on Raspbian (and other unix systems, including Mac OS X) that holds files that are meant to be temporary. When the system reboots, /tmp is completely erased. Therefore, when my script starts, I copy files I will need to read and modify to /tmp, so that I know I'm starting with a clean slate after a reboot.

Autostart

I have only done autostarting with a system that boots to a desktop. If you do not, you will need to look into another way of starting. I would suggest picking one that is not tied to logging in. You should be able to open as many or as few ssh connections as you would like, without accidentally firing off many copies of your script.

For Pis that boot to a desktop, you will need to create a file called installation.desktop. Put in it:


[Desktop Entry]
Name=Installation
Exec=/home/pi/installation/installation.sh
Type=application

Change the name and the exec line to match your actual file names and paths. Save that file to ~/.config/autostart/. This is a hidden directory, so if you can't find in your gui, save the file in your home directory and then open a terminal and type mv installation.desktop ~/.config/autostart/ . When your computer boots, it will now automatically start your installation when the desktop opens.

Stopping the installation

If you just want to stop everything, shut down the computer! shudo shutdown -h now But if you want to stop the installation and keep the computer going, this is also possible! You might want to do this because you are updating files or testing. Remember that SD cards are pretty cheap, so if you just need a Raspbian system with SuperCollider on it, you should probably keep that on a separate SD card.

When you log in, the first thing you will want to kill is the bash script that is running everything. Then, you will want to kill sclang, the server and jackd and your helper apps. You can type:


killall installation.sh
killall sclang
killall scsynth
killall jackd

To get a list of everything running, use the ps command: ps -u pi . It will print out a list of everything running that belongs to the user pi (that's us!). The number on the left of the listing is the PID (Processed ID) and the name on the left is the name of the program. If you see your helper script has a PID of 27142, you can kill it directly: kill 27142. If you notice that your programs are resisting your effort to kill them, you can force them to die: kill -9 27142, where the number is what you see when you run ps.

Debugging

In our bash script above, we've got a lot of reboots. This is how the script will look when we deploy it, but not how it should be early in the testing process. We instead want to run the script ourselves, from the command line, so we can see what is being printed out in the way of error codes or from printlns in SuperCollider. Therefore, when first trying this, we would replace all of our 'sudo shutdown -r now' lines with 'exit'. We may need to reboot anyway when the program stops, but this will give a chance to try to read what went wrong. This can also tell us that, say, the server never wants to start, which may indicate that we have not slept for long enough after starting jack.

To launch a script form the command line, cd to the directory that its in and type: ./installation.sh . The ./ at the beginning of the command just says that the script is local to the directory that we're in.

Once the script and program are fairly stable, though, we will want to just leave the installation running, to see if it gets stuck in a state where it stops without restarting or rebooting. When we're ready to do that, we will put the shutdown commands back in and that's when we have the script autostart.

When you've autostarted your program, it runs silently. How can you tell what's going on? It may be helpful to just know what's running at any given time. You can do that via the desktop by running the Task Manager. Or you can to do it from the command line by running top -u pi. This will keep us current on what is running on the system. Is your helper app still going? Is scsynth dying every 10 seconds, causing an endless loop of crashes? Is everything starting when it should and staying alive long enough? This also tells you what is using up the most CPU resources. Does your helper app climb up to 100% before the server crashes?

If you are getting weird crashes that you didn't get when developing the code on your own computer, then one possible problem is the pi version of your startup script. Make a copy of the script and change it so that it always runs as if you are on a Pi. Try it on your own computer (changing the paths as appropriate). Maybe the logic got slightly screwed up.

Maybe your helper script is too nice and the output isn't ready when sclang needs it? Because the Pi has such low power, your program will need to be able to deal with late data. Changing nice values may not be enough to solve this problem. You will need to make sure your SuperCollider program can cope with data arriving late.

Or maybe your helper script isn't nice enough. If it's always at the top in top, it might not be leaving enough space for your server to run. If the server can't get enough CPU, the audio will sound weird and fragmented and the server will crash. This leads to a lot of waiting for everything to restart itself! If you are tending to be using a high percentage of the CPU, scsynth needs to be getting its share of that.

Does your program start out ok, but then seem to always need to reboot in order to recover from a crash? Try making it sleep longer at the end of the loop. Sleeping for several extra seconds in between restarting jack, ssynth and sclang may seem like a long wait between running things, but it's a shorter wait than a reboot.

the aesthetic of glitch

You may want to create a long, lush drone that has a relatively simple synthdef, which just starts and runs without getting messages from sclang. That's not going to crash very often. You will have tons of uptime. Give it a try and see if the Pi will work for you.

Or you might want to start a ton of short, complex synths that rely on doing strange calculations, where the possibility of failure is inherent in the process. (Welcome to my world.) That will definitely crash. There will be long silences while the installation sorts itself out. Therefore, this needs to be a part of your aesthetic that you deal with using some sort of intentionality. You might want to consider having sudden stops and pauses happen when your piece is not crashed, so that a crash just seems like a longer pause. If your piece is using the desktop to do any kind of graphics, set a background image that works within the context of someone experiencing your piece. When the system reboots, people will see that background image, so it should be approached as a part of the installation.

Your audience needs to be prepared for pauses. Foreshadowing in the output is an important part of this, but my last bit of advice is to put something in the program notes, so a person approaching the installation knows it sometimes takes short breaks and may be willing to wait a few minutes if they happen to walk up as it is crashing in a way that will require a reboot.

Conclusion

This document has mostly spoken about the disadvantages of the Pi, sometimes at great length. However, if you are reading this while considering running an installation on the Pi, do not despair. The advantages of the Pi may still be greater than the disadvantages.

  • It is still small and cheap, thus making it ideal for installations (and for shipping in the post).
  • It has a certain coolness factor, which may make venues more interested in your work.
  • An SD card is a stable platform. Once you get an installation working, if you want to run it again a year from now, you don't need to worry overly about system updates, etc. It should just still work.
  • You can do risky things. Again, if you do something really weird that breaks your system, you can just re-image the sd card. Be bold and audacious.

1 comment:

Seph Reed said...

Frigging awesome! Thank you so much!