Thursday, July 22, 2010

Linux Server Monitoring With Munin




Introduction

Munin is a monitoring tool written in Perl started by Jimmy Olsen on late 2003, based on the excellent RRD tool by Tobi Oetiker. Even if the development has slowed down since 2005, Munin is a stable tool; it is also very widely used, thanks to its very easy setup.

It consists of munin-node, a daemon you will install on every server you want to monitor and which will gather the data, and munin, which you will install on your monitoring server and which will connect at a regular basis to every node to retrieve it. Munin will then use the data to generate the corresponding graphs and HTML pages.

Below, you will find links that will explain the different features of Munin and how to use those features. Have fun setting up you Munin monitoring server and nodes !

I hope you have learned from these post and I want to thank you for reading.

Wednesday, July 21, 2010

Munin's Performance

Munin's CPU usage


As seen before, munin is run by a cron job every five minutes. So, every five minutes, it connects to all the servers it has to monitor, fetches all the data, writes the data in hundreds of RRD files, and recreates all the HTML files and hundreds of PNG files; the more servers monitored, the more CPU munin will use.

Some other tests are also rather interesting:

/usr/share/munin$ time sudo -u munin ./munin-update

real    0m27.453s
user    0m0.152s
sys     0m0.036s


/usr/share/munin$ time sudo -u munin ./munin-limits


real    0m0.179s

user    0m0.132s
sys     0m0.016s


/usr/share/munin$ time sudo -u munin ./munin-html


real    0m0.270s


user    0m0.176s
sys     0m0.020s


/usr/share/munin$ time sudo -u munin ./munin-graph


real    0m11.376s



user    0m10.465s
sys     0m0.500s


This test (made on my desktop, one node monitored only) shows two interesting things: first, the generation of the PNGs is the heaviest part of the process (10.965 seconds of cpu usage vs 0.532 for the three other processes); second, the munin-update process takes nearly 30 seconds to complete, but barely uses the CPU - probably because it is waiting for the node to run all its plugins. That's why when munin starts, it forks, and run a process for each node, and why you should not prevent it from forking (there is an option for that - don't use it).



If now I was monitoring 10 nodes, it would take approx. 110 seconds on my desktop (if nothing else is running), every five minutes. In other words: as you add nodes to munin, it tends to become quite heavy.


Run Munin as a CGI

One of the ways to improve the performances is to change the way Munin creates the graphs; instead of recreating the graphs every five minutes, we can create them only when a user has requested them, by displaying one of the webpages. This is made possible with CGI.

So, how does it work? When installed, Munin creates a script in /usr/lib/cgi-bin/, munin-cgi-graph. When configured as CGI, Munin changes the links to the pictures in the HTML files, making them point to munin-cgi-graph:

img src="/cgi-bin/munin-cgi-graph/localdomain/localhost.localdomain/df_inode-day.png" ...

Depending on the path, munin-cgi-graph will create the appropriate graph, which will then be displayed. There is also a caching system, so that if you reload the page within five minutes, the graphs won't be regenerated again; therefore, as munin will write the files to the disk, the directory /var/www/munin must be writeable by the apache process. Making the files belong to the user munin and the group www-data, and giving the group write access, is one solution:



/var/www$ sudo chown -R munin:www-data /var/www/munin
/var/www$ sudo chmod -R g+w /var/www/munin

The performance gain is huge; but one of the drawbacks to this method is that it takes a lot more time to display a page containing several graphs, like the node view.

To configure Munin as CGI you need to add the following lines to your /etc/munin/munin.conf:


graph_strategy cgi
cgiurl /cgi-bin
cgiurl_graph /cgi-bin/munin-cgi-graph

These lines help Munin to create correct links to the graphs. Now, assuming you are using Apache, you need to edit your main apache configuration file, to allow /usr/lib/cgi-bin to run CGI scripts:

        AllowOverride None
        Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all


Finally, you need to tell Apache that your website is going to use CGI. If you have a special virtual host set up for munin, then add that line there; else add it somewhere in the main apache configuration file:

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

Munin-cgi-graph also uses the perl module Date::Manip; which you need to install. Your Munin is now running as CGI!

Move Munin's RRD databases to a TMPFS


On an install of approximatively 30 servers monitored, I have over 2000 RRD databases in /var/lib/munin. This number can vary depending on the number of services you monitor per server, but what we can remember is: every time munin runs (every 5 minutes), hundreds if not thousands of databases are written to and read from. If your disks aren't very fast, this can prove quite costly as the number of servers monitored grows.

This can be improved by moving the files contained in /var/lib/munin to a tmpfs. In my example, with 30 servers monitored and 2250 RRD files, only 115MB are used on the disk - considering the amount of RAM in servers nowadays, it may be worth saving some disk i/o at the cost of some RAM. As all the data would be lost in case of a server restart, we will back the data up every hour/day/week depending on how much data you are willing to lose.

Make a backup of the folder:


cd /var/lib
cp -ra munin/ munin-cache

Add this to your /etc/fstab:

tmpfs /var/lib/munin tmpfs rw,size=512M 0 0

Mount it:

sudo rm -rf /var/lib/munin/*
sudo mount /var/lib/munin

Copy the data back from the backup:

sudo cp -ra /var/lib/munin-cache/* /var/lib/munin

Create an hourly (or daily) cronjob that copies the files from munin to munin-cache:

ServerX $ cd /etc/cron.hourly
ServerX $ ls -l
total 4
-rwxr-xr-x 1 root root 57 2009-03-10 18:55 munin-cache
ServerX $ cat munin-cache
#!/bin/sh
cp -ra /var/lib/munin/* /var/lib/munin-cache/

And then to restore the files from the backup automatically after a reboot, add this at the end of /etc/rc.local:

cp -ra /var/lib/munin-cache/* /var/lib/munin

Frequently asked questions




Is there a munin-node for Windows?
Yes, but it is unofficial. It is maintained by TOCOMPLETE? and is written in C++. Search Google for a download link.



Expand the Power of Munin: Write Your Own Plugins!

Introduction


Munin comes with many plugins, which work out of the box after the installation. Even though, you may still want to monitor values for which no plugin has been created yet. For this chapter, we will create a plugin which monitors the CPU usage for a defined set of users; I created it to help identify which applications were using most of the CPU in a fastCGI environment, on one of our Ubuntu-eu servers.

You can create plugins in the language of your choice; most of the ones munin comes with are either in Perl or shell scripts, but you should be able to use whatever language you are familiar with. Although, as the plugin I will describe here is in sh, this chapter requires a basic knowledge of shell programming and awk.

Retrieve the Data



First, you need to define which data you want to graph, and how to retrieve it. In our example, it would be the CPU usage for a specified user, at the time the plugin is run. I noticed the output of ps on my system, with it's BSD syntax, printed out the CPU usage for every process. It is definitely not very precise, as the values are rounded up; but I believe it gives a fairly good overview of which users are using most of the CPU.


~$ ps aux | head

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root             1        0.0        0.2       2952  1856 ?                Ss    09:21        0:02    /sbin/init
root            2        0.0        0.0          0        0    ?                S<   09:21        0:00   [kthreadd]
root           3        0.0        0.0          0        0    ?                S<   09:21        0:00   [migration/0]
root          4        0.0        0.0          0        0    ?                SN   09:21       0:00   [ksoftirqd/0]
[...]


So, there are three things to do: filter out the processes that belong to a specific user, isolate the CPU column, and add all the values. To filter out processes that belong to a user, we will use the -u option of ps; to isolate the CPU column, the easiest way is to use the -o option of ps:



~$ ps -u "yann" -o "%C"

%CPU
 0.0
 0.0
 1.5
 0.3
 0.0
 0.0
 [...]

We can now add the values with a small awk script (here in a single line, to make it easy to try out):


~$ ps -u "yann" -o "%C" | awk 'BEGIN {FS=" ";CPU_USER=0}{CPU_USER+=$0}END{print CPU_USER}'
8.6

Now that looks interesting... But we don't want to monitor only a single user, but several, and the variables need to be presented under the form var_name.value:



#!/bin/sh
export USERS="root user"
for USER in $USERS ; do {


        ps -u "$USER" -o "%C" |
        awk '
        BEGIN {
                FS=" "
                CPU_USER=0
        }


        {
                CPU_USER+=$0
        }


        END { 
                print  "'$USER'.value "CPU_USER
        }'
}


done;


Save this in /usr/share/munin/plugins/cpubyuser, add a soft link to it in /etc/munin/plugins/, and try it out:


~$ sudo vi /usr/share/munin/plugins/cpubyuser
~$ sudo ln -s /usr/share/munin/plugins/cpubyuser /etc/munin/plugins/cpubyuser
~$ sudo chmod 755 /usr/share/munin/plugins/cpubyuser
~$ /etc/munin/plugins/cpubyuser

root.value 3.6
user.value 4.2

Now, we don't want the usernames to be hardcoded in the plugin, but set in one of munin's configuration files. Edit the file /etc/munin/plugin-conf.d/munin-node, and add the following lines at the end:



[cpubyuser]
env.USERS root user


... and remove the line defining $USERS in our script. Reload munin-node (sudo /etc/init.d/munin-node restart).

~$ /etc/munin/plugins/cpubyuser 
~$ munin-run cpubyuser

root.value 3


user.value 3

If you run the script directly, the variable $USERS won't be defined, and the script will exit. If you run it via munin-run, munin-node will create the environment variable for the script, so it will work.


Autoconf


As we have seen in the previous chapter, when munin-node installs, it runs every script with the autoconf parameter, to decide if it should or not activate it. Therefore, our plugin also need to handle this.




if [ "$1" = "autoconf" ] ; then
        if [ -n "$USERS" ] ; then
                echo "yes"
        else
                echo "no (\$USERS not defined - edit your /etc/munin/plugin-conf.d/!)."
        fi
        exit
fi

Add this at the top of the plugin, before the main loop. If the plugin is run with the autoconf parameter, it will check for the environment variable $USERS; if it is defined, our plugin with reply with "yes", meaning that it can be used. Else, it will reply "no". Your plugin is supposed to check in this section if everything is ok for the plugin to run correctly; like checking if apache is running if your plugin is about apache.


Config


When run with the "config" parameter, your plugin is supposed to output data for RRDTool to tell it how to graph the different values given by our plugin, and metadata like the title of the graphs, the title of the axes...



if [ "$1" = "config" ] ; then
        echo "graph_args --base 1000 -r --lower-limit 0";
        echo "graph_title CPU usage, by user";
        echo "graph_category system";
        echo "graph_info This graph shows CPU usage, for monitored users.";
        echo 'graph_vlabel %'
        echo 'graph_scale no'
        echo 'graph_period second'
        echo "graph_order $USERS"


        FIRSTUSER=1;
        for USER in $USERS; do
                echo "${USER}.label $USER"
                echo "${USER}.info CPU used by user $USER"
                echo "${USER}.type GAUGE"
                if [ $FIRSTUSER -eq 1 ] ; then
                        echo "${USER}.draw AREA"
                        export FIRSTUSER=0;
                else
                        echo "${USER}.draw STACK"
                fi
        done ;


        exit
fi


The "base" parameter on the second line states if we consider that 1M=1024K or 1M=1000K. One of the most important part of this is the loop in the end, which tells rrdtool how it should process the data we feed it with; for every variable monitored, we need to tell rrdtool what to do with it.

For example, the variable "user" should be labelled "user" (you can change it to "CPU usage for $USER", or whatever you think is better). The label is what will appear in the legend, on the graph, it should be reasonably short. For a longer description, you should use the "info" section.

For the "type" of the value, you should read man rrdcreate, there is a whole part on the different types of data sources. A "gauge" is described like this: " GAUGE: is for things like temperatures or number of people in a room or the value of a RedHat share.".

Add informations to your plugin

Well, our plugin is nearly done. Now add these two lines at the top, between the shebang and the autoconf part:


#%# family=auto
#%# capabilities=autoconf


This will tell munin that it should attempt to automatically install and configure your plugin. Now you just need to add some comments, installation instructions, license, history, author contact, etc.... and your first munin plugin is ready!

Now, this was a very simple plugin; depending on what you want to do, it can be a lot harder (look at the memory plugin!). If you want to create complex graphs, you should definitely complete your knowledge by reading rrdtool's documentation. On the other hand, if you only want to create simple plugins, reading plugins in /usr/share/munin/plugins and trying to adapt them is a very good way to get started.