nodereality

node c0re

Entries in the Category “code”

Monitoring nginx with munin

written by Ghirai, on Jan 6, 2010 11:34:00 PM.

If you ever wanted to know how to graph nginx requests, here's one way of doing it.

You can tail the logfile(s) and calculate the requests in a give time interval, but fortunately there's a much simpler way. The first thing to do is to set things up so that we can read the statistics. Edit your nginx.conf and add the following (doesn't matter in which server{} block you add it):

location /my_nginx_stats {
  stub_status on;
  access_log off;
  allow 127.0.0.1;
  deny all;
}

If you want to gather data from a remote machine, change the allow line, or remove it alltogether (in that case remove the deny line as well).

Now if you browse to http://127.0.0.1/my_nginx_stats you will see something like this:

Active connections: 1 
server accepts handled requests
 21 21 23 
Reading: 0 Writing: 1 Waiting: 0 

With this data it's trivial to write a munin plugin:

#!/usr/local/bin/python
import sys
import urllib

LOCATION = 'http://127.0.0.1/my_nginx_stats'

if len(sys.argv) == 2 and sys.argv[1] == "autoconf":

  print "yes"

elif len(sys.argv) == 2 and sys.argv[1] == "config":

  print 'graph_title nginx connections'
  print 'graph_vlabel current connection count'
  print 'graph_category Web'

  print 'conn.label connections'
  print 'graph_args --base 1000 -l 0'

else:

  h = urllib.urlopen(LOCATION)
  t = h.read()
  h.close()

  l = t.split('\n')[0]
  conn = int(l[20:-1])-1

  print 'conn.value %s' % conn

Finally, here's a picture to go with the code:

example nginx munin graph

Abusing Fish (For Fun, Not Profit)

written by Alan, on Jan 3, 2010 9:42:00 PM.

I have been very bored and then yesterday Remy showed me this. And doing so was a very bad idea, let me explain why.

Tankedcam is an interactive fish tank. Backed by a servo board and a front-end webpage, you're able to press buttons to control what happens inside the tank. The live demo on the website contains a few interesting things.

  • Dino - Open/close the jaw of the dino. (I see it as a fish chomper)
  • Air Stone - A jet of bubbles. (Bubble blaster right?)
  • Flashlight - Makes things bright. (Blinds em -.-)

However, I cant press the buttons fast enough to chomp anything and the flashlight turns off after 10 seconds. But I can abuse the programming. The webpage sends GET requests when you press one of the buttons on the controller although a session ID and timestamp is needed. There is a very simple way to overcome this, i'll just use javascript mouse events to simulate the onclick event of the buttons.

Few minutes later I have a very simple script that rapidly 'clicks' the open and close buttons for the dino jaw. I add support for the other tank functions and Fish Hack Script was finished.

One thing I have noticed if I lower the timer the whole servo starts to play up, maybe it cant draw enough power. :(

Anyway, have fun the script is here (UPDATE: Right click save, nodereality referer has been blocked). If you catch a fish in the chomper I want a picture, there is also some snails that come out at night they move slower. I had fun throwing one of the top off the chopper.

munin plugin for gstat

written by Ghirai, on Dec 29, 2009 1:02:00 AM.

gstat(8) is an utility that shows GEOM providers (and consumers) statistics.

It would be useful to have a munin plugin that plots IO activity if you have a storage box or a similar setup where there's a lot of disk action going on.

The code is for Python 2.6; if you're running 3.x you need to make slight adjustments (like print). Usage instructions are in the comments.

#!/usr/bin/env python
'''
Munin plugin to monitor I/O transactions of geom(4) devices
on FreeBSD as reported by gstat(8).

To get the list of devices you might want to monitor,
run gstat.
If your device contains '/', substitute it with '_';
example: mirror/myarray -> mirror_myarray

Install it as any other munin plugin (if you want to monitor
device foo, symlink the plugin in your plugins directory
as gstat_foo.py).

The plugin need to be run as root.
Add these 2 lines to your plugins.conf:
[gstat_*]
user root

You need to restart munin_node for the changes to take effect.


Written by ghirai-at-ghirai-d0t-com
'''

import sys
from subprocess import Popen,PIPE
from string import split


device = sys.argv[0][(sys.argv[0].find('_')+1):]
if device == None: sys.exit(1)

device = device.replace('_','/')

if len(sys.argv) == 2 and sys.argv[1] == "autoconf":

  print "yes"

elif len(sys.argv) == 2 and sys.argv[1] == "config":

  print 'graph_title I/O transactions for %s' % device
  print 'graph_vlabel Transfers in kBps'
  print 'graph_category Disk'

  print 'r.label read'
  print 'w.label write'

  print 'graph_args --base 1000 -l 0'

else:

  r = 0
  w = 0

  res = Popen(['gstat','-b'],stdout=PIPE).stdout.read()
  res = res.split('\n')

  for ln in res:

    lns = split(ln)
    lns.reverse()

    try:
      if lns[0] == device:
        r = lns[6]
        w = lns[3]
        break
    except IndexError:
      break

  print 'r.value %s' % r
  print 'w.value %s' % w

munin plugin for djbdns' DNS cache

written by Ghirai, on Sep 17, 2009 10:47:00 AM.

If you run a DNS cache with djbdns, you might want to see pretty graphs of the number of requests your cache received (queries/second).

Here's a very simple munin plugin that does just that:

#!/usr/local/bin/python
import sys

logfile = '/etc/dnscache/log/main/current'

if len(sys.argv) == 2 and sys.argv[1] == "autoconf":

  print "yes"

elif len(sys.argv) == 2 and sys.argv[1] == "config":

  print 'graph_title djbdns dnscache queries'
  print 'graph_vlabel Queries/s'
  print 'graph_category DNS'
  print 'queries.label Queries/s'
  print 'queries.type COUNTER'
  print 'graph_args --base 1000 -l 0'

else:

  f = open(logfile)

  for line in f:

    txt = line.split(' ')
    try:
      if txt[1] == 'query':
        query_count = txt[2]
    except IndexError: continue

  f.close()

  print 'queries.value %s' % query_count

Adjust the "logfile" on line 4 to fit your installation.

Python 3.x users will probably need to make slight adjustments to the code.

Install it like any other munin plugin, then restart munin_node. It doesn't require any additional configuration.

EDIT: Here's how it looks: queries/s graph

Basic RRDTool usage with Python

written by Ghirai, on Aug 9, 2009 2:48:00 AM.

If you're reading this, you probably know about RRDTool. It's a collection of applications that allow you to store just about any sort of time series data, perform various calculations on it, and most importantly, print pretty graphs based on that data.

In order to test the code in this article, you will need to install/compile RRDTool with Python bindings. You can get more info on that, as well as binary packages here.

For the purpose of this exercise, we will be storing and plotting temperature-related information, but you should get an idea on how to process other kind of information as well.

Assume we have two values - temperature and dew point, and we want to plot both on the same graph. We measure these values from some sensors (or from your local weather station's website) every 300 seconds.

The code to create the RRD file is:

rrdtool.create('test.rrd' ,
        '--step','300',
        'DS:temp:GAUGE:600:-50:50' ,
        'DS:dewpoint:GAUGE:600:-50:50',
        'RRA:AVERAGE:0.5:1:576',   #day
        'RRA:AVERAGE:0.5:6:672',   #week
        'RRA:AVERAGE:0.5:24:732',  #month
        'RRA:AVERAGE:0.5:144:1460' #year)

The parameters are identical to what is described in the official rrdcreate documentation. You can use the same values if you want to invoke the command line application, as opposed to doing it programatically.

The first one is obviously the filename (you can have any name/extension).

The second one is the interval at which we will be measuring our data. If you don't explicitly set this, it will default to 300 seconds. Following are the two data sources (DS) we will be using, their names (temp and dewpoint), GAUGE is to be used for temperature (there are other types as well, which are described in more detail in the documentation).

600 is the heartbeat, which specifies how many seconds need to pass before two consecutive updates, before the value is assumed to be unknown. Twice the measure interval seems reasonable in this case.

Finally, -50:50 is the interval between which the measured values are expected to be found.

The next thing to do is to define the round robin archives (RRA).

AVERAGE is our CF (consolidation function), which does exactly what the name says. Again, there are others, which you can learn about in the documentation. 0.5 is ratio of unknown measurements (PDPs, or primary data point; we get a new one every 300 seconds in this case) to the total number of measurements in the specified interval.

For the "day" RRA, 1 means the number of PDPs that build a consolidated datapoint (CDP), and 576 means how many of these get stored in the RRA before being overwritten. This is actually easier than it sounds - 1 PDP means we just store the data directly, every 300 seconds (5 minutes). We store 576 of these CDPs (576*300 seconds=2 days) in the "day" RRA, which means we will be able to plot data for the last 2 days with a 5 minutes resolution. The exact same applies for the week, month and year RRAs: for "week" we have 2 weeks worth of data, with averages of every half hour (6 means 6*300 seconds, which is 30 minutes; we store 672 of these, which means 672*30 minutes = 14 days), for "month" there will be 2 months worth of data, containing 2 hours averages, and finally "year" stores 2 years worth of data, containing 12 hours averages.

In order to have some data to test things out (and not having to wait say one month to see if the code works), we'll just generate some random values.

Note that RRDTool will refuse to insert data that is timestamped (seconds since epoch) before the creation of the database.

Let's add some data:

for i in xrange(nr_samples):
    now = random.randint(12 , 17)
    fake_time = fake_time + 300
    rrdtool.update(rrd_file , '%d:%d:%d' % (fake_time,now,now - \
    10 + random.randint(1,8)))

fake_time gets the current time.

Then we loop nr_samples time, produce a random temperature between 12 and 17C, while making sure to increment the time offset by 300 seconds.

The actual update operation is simple; the parameters are the database filename, following by the "current" time (which we fake here by incrementing fake_time after each update; in a real situation you would just want to get the current time here) and the measured values, how many there may be.

Our fake dew point, which is the second value, is adjusted by a random delta from the temperature, to make the graph a little more realistic.

Finally, it's time to generate the picture, along with the legend:

rrdtool.graph(pic_file ,
  '--start' , str(fake_time - 60*60*24*2) ,
  '--end' , str(fake_time) ,
  #'--slope-mode',
  '--vertical-label' , 'Degrees Celsius' ,
  '--imgformat' , 'PNG' ,
  '--title' , 'Temperature Data (by DAY)' ,
  '-w 500', '-h 200',
  'DEF:temp=%s:temp:AVERAGE' % rrd_file,
  'DEF:dewpoint=%s:dewpoint:AVERAGE' % rrd_file,

  'AREA:temp#ccedff:Temperature',
  'LINE1:temp#aaaaaa',

  'GPRINT:temp:LAST:CUR\: %2.1lf',
  'GPRINT:temp:AVERAGE:AVG\: %2.1lf',
  'GPRINT:temp:MIN:MIN\: %2.1lf',
  'GPRINT:temp:MAX:MAX\: %2.1lf\\n',

  'LINE1:dewpoint#007000:Dewpoint',
  'GPRINT:dewpoint:LAST:   CUR\: %2.1lf',
  'GPRINT:dewpoint:AVERAGE:AVG\: %2.1lf',
  'GPRINT:dewpoint:MIN:MIN\:%2.1lf',
  'GPRINT:dewpoint:MAX:MAX\:%2.1lf\\n')

Graph generation is pretty easy, you just have to pay attention to the start and end points. In a real world situation, you could use --start -2day, or calculate the UNIX time for 2 days ago yourself.

The rest of the parameters, as well as many others are described in more detail in the rrdgraph documentation.

The picture should look like this.

Complete source file for this demo is rrdtool_example.py.

While we're on the subject of temperature, here is some code to calculate the dewpoint, if you have the temperature (in degrees Celsius) and the relative humidity:

def dewpoint(temp,hum):

  f = 17.271 * temp / float(237.7 + temp) + log(H / float(100))
  return 237.7 * f / (17.271 - f)

If you're using Python 3 and up, you don't need the float(100), you can just use 100, and the result will still be a float.

Finally, here is another function that calculates the heat index, along with helper functions to covert around between Fahrenheit and Celsius:

def c2f(t):
  return 9/float(5) * t + 32


def f2c(t):
  return 5/float(9)*(t-32)


def heatindex(temp,hum):

# temperature needs to be in Fahrenheit
# return value is degrees Fahrenheit as well

  if temp < 80: return 0

  return -42.379 + (2.04901523 * temp) + (10.14333127 * hum) - \
    (0.22475541 * temp * hum) - (6.83783 * pow(10,-3) * \
    pow(temp,2)) - (5.481717 * pow(10,-2) * pow(hum,2)) + \
    (1.22874 * pow(10,-3) * pow(temp,2) * hum) + (8.5282 * \
    pow(10,-4) * temp * pow(hum,2)) - (1.99 * pow(10,-6) * \
    pow(temp,2) * pow(hum,2))
The calculations above are from Wikipedia and US NOAA, and i also tested them against known values, so they should be good.