Automatic Inventory

Now I have four machines.  Keeping them in sync is the challenge.  Worse yet, knowing whether they are in sync or out of sync is a challenge.

So the first step is to make a tool to inventory each machine.  In order to use the inventory utility in a scalable way, I want to design it to produce machine-readable results so that I can easily incorporate them into whatever I need.

What I want is a representation that is both friendly to humans and to computers.  This suggests a self-describing text representation like XML or JSON.  After a little thought I picked JSON.

What sorts of things do I want to know about the machine?  Well, let’s start with the hardware and the operating system software plus things like the quantity of RAM and other system resources.  Some of that information is available from uname and other is availble from the sysinfo(2) function.

To get the information from the sysinfo(2) function I had to do several things:

  • Install sysinfo on each machine
    • sudo apt-get install sysinfo
  • Write a little program to call sysinfo(2) and report out the results
    • getSysinfo.c

Of course this program, getSysinfo.c is a quick-and-dirty – the error handling is almost nonexistent and I ought to have generalized the mechanism to work from a data structure that includes the name of the flag and the attribute name and doesn’t have the clumsy sequence of if statements.

/*
 * getSysinfo.c
 *
 * $Id: getSysinfo.c,v 1.4 2014/08/31 17:29:43 marc Exp $
 *
 * Started 2014-08-31 by Marc Donner
 *
 * Using the sysinfo(2) call to report on system information
 *
 */

#include <stdio.h> /* for printf */
#include <stdlib.h> /* for exit */
#include <unistd.h> /* for getopt */
#include <sys/sysinfo.h> /* for sysinfo */

int main(int argc, char **argv) {

   /* Call the sysinfo(2) system call with a pointer to a structure */
   /* and then display the results */
   struct sysinfo toDisplay;
   int rc;

   if ( rc = sysinfo(&toDisplay) ) {
      printf("  rc: %dn", rc);
      exit(rc);
   }

   int c;
   int opt_a = 0;
   int opt_b = 0;
   int opt_f = 0;
   int opt_g = 0;
   int opt_h = 0;
   int opt_m = 0;
   int opt_r = 0;
   int opt_s = 0;
   int opt_u = 0;
   int opt_w = 0;
   int opt_help = 0;
   int opt_none = 1;

   while ( (c = getopt(argc, argv, "abfghmrsuw?")) != -1) {
      opt_none = 0;
      switch (c) {
         case 'a':
            opt_a = 1;
            break;
         case 'b':
            opt_b = 1;
            break;
         case 'f':
            opt_f = 1;
            break;
         case 'g':
            opt_g = 1;
            break;
         case 'h':
            opt_h = 1;
            break;
         case 'm':
            opt_m = 1;
            break;
         case 'r':
            opt_r = 1;
            break;
         case 's':
            opt_s = 1;
            break;
         case 'u':
            opt_u = 1;
            break;
         case 'w':
            opt_w = 1;
            break;
         case '?':
            opt_help = 1;
            break;
      }
   }

   if ( opt_none || opt_help ) {
      showHelp();
      return 100;
   } else {
      if ( opt_u || opt_a ) { printf("  "uptime": %lun", toDisplay.uptime); }
      if ( opt_r || opt_a ) { printf("  "totalram": %lun", toDisplay.totalram); }
      if ( opt_f || opt_a ) { printf("  "freeram": %lun", toDisplay.freeram); }
      if ( opt_b || opt_a ) { printf("  "bufferram": %lun", toDisplay.bufferram); }
      if ( opt_s || opt_a ) { printf("  "sharedram": %lun", toDisplay.sharedram); }
      if ( opt_w || opt_a ) { printf("  "totalswap": %lun", toDisplay.totalswap); }
      if ( opt_g || opt_a ) { printf("  "freeswap": %lun", toDisplay.freeswap); }
      if ( opt_h || opt_a ) { printf("  "totalhigh": %lun", toDisplay.totalhigh); }
      if ( opt_m || opt_a ) { printf("  "mem_unit": %dn", toDisplay.mem_unit); }
      return 0;
   }
}

int showHelp() {
   printf( "Syntax: getSysinfo [options]n" );
   printf( "nDisplay results from the sysinfo(2) result structurenn" );
   printf( "Options:n" );
   printf( " -b : bufferramn" );
   printf( " -f : freeramn" );
   printf( " -g : freeswapn" );
   printf( " -h : totalhighn" );
   printf( " -m : mem_unitn" );
   printf( " -r : totalramn" );
   printf( " -s : sharedramn" );
   printf( " -u : uptimen" );
   printf( " -w : totalswapnn" );
   printf( "getSysinfo also accepts arbitrary combinations of permitted options." );
   return 100;
}

And with this in place, the python program sysinfo.py required to pull together various other bits and pieces becomes possible:

#
# sysinfo
#
# report a JSON object describing the current system
#
# $Id: sysinfo.py,v 1.8 2014/08/31 21:04:30 marc Exp $
#

from subprocess import call
from subprocess import check_output
import time

# First we get the uname information
#
# kernel_name : -s
# nodename : -n
# kernel_release : -r
# kernel_version : -v
# machine : -m
# processor : -p
# hardware_platform : -i
# operating_system : -o
#

operating_system = check_output( ["uname", "-o"] ).rstrip()
kernel_name = check_output( ["uname", "-s"] ).rstrip()
kernel_release = check_output( ["uname", "-r"] ).rstrip()
kernel_version = check_output( ["uname", "-v"] ).rstrip()
nodename = check_output( ["uname", "-n"] ).rstrip()
machine = check_output( ["uname", "-m"] ).rstrip()
processor = check_output( ["uname", "-p"] ).rstrip()
hardware_platform = check_output( ["uname", "-i"] ).rstrip()

# now we get the boot time using who -b
boot_time = check_output( ["who", "-b"]).rstrip().lstrip()

# now we get information from our handy-dandy getSysinfo program
GETSYSINFO = "/home/marc/projects/s/sysinfo/getSysinfo"
getsysinfo_uptime = check_output( [GETSYSINFO, "-u"] ).rstrip().lstrip()
getsysinfo_totalram = check_output( [GETSYSINFO, "-r"] ).rstrip().lstrip()
getsysinfo_freeram = check_output( [GETSYSINFO, "-f"] ).rstrip().lstrip()
getsysinfo_bufferrram = check_output( [GETSYSINFO, "-b"] ).rstrip().lstrip()
getsysinfo_sharedram = check_output( [GETSYSINFO, "-s"] ).rstrip().lstrip()
getsysinfo_totalswap = check_output( [GETSYSINFO, "-w"] ).rstrip().lstrip()
getsysinfo_freeswap = check_output( [GETSYSINFO, "-g"] ).rstrip().lstrip()
getsysinfo_totalhigh = check_output( [GETSYSINFO, "-h"] ).rstrip().lstrip()
getsysinfo_mem_unit = check_output( [GETSYSINFO, "-m"] ).rstrip().lstrip()

print "{"
print "  "report_date": "" + time.strftime("%Y-%m-%d %H:%M:%S") + "","
print "  "operating_system": " + """ + operating_system + "","
print "  "kernel_name": " + """ + kernel_name + "","
print "  "kernel_release": " + """ + kernel_release + "","
print "  "kernel_version": " + """ + kernel_version + "","
print "  "nodename": " + """ + nodename + "","
print "  "machine": " + """ + machine + "","
print "  "processor": " + """ + processor + "","
print "  "hardware_platform": " + """ + hardware_platform + "","
print "  "boot_time": " + """ + boot_time + "","
print "  " + getsysinfo_uptime + ","
print "  " + getsysinfo_totalram + ","
print "  " + getsysinfo_freeram + ","
print "  " + getsysinfo_sharedram + ","
print "  " + getsysinfo_totalswap + ","
print "  " + getsysinfo_totalhigh + ","
print "  " + getsysinfo_freeswap + ","
print "  " + getsysinfo_mem_unit
print "}"

which in turn enables the Makefile:

#
# Makefile for sysinfo
#
# $Id: Makefile,v 1.9 2014/08/31 21:27:35 marc Exp $
#

FORCE := force

HOST := $(shell hostname)
HOSTS := flapjack waffle pancake frenchtoast
SSH_FILES := $(HOSTS:%=.%_ssh)
PUSH_HOSTS := $(filter-out ${HOST}, ${HOSTS})
PUSH_FILES := $(PUSH_HOSTS:%=.%_push)

help: ${FORCE}
	cat Makefile

FILES := Makefile sysinfo.py sysinfo.bash getSysinfo.c

checkin: ${FILES}
	ci -l ${FILES}

install: ~/bin/sysinfo

~/bin/sysinfo: ./sysinfo.bash
	cp $< $@
	chmod +x $@

getSysinfo: getSysinfo.c
	cc $ $*.sysinfo
	touch $@

test: ${FORCE}
	time python sysinfo.py

force:

Notice the little trick with the Makefile variables HOST, HOSTS, SSH_FILES, PUSH_HOSTS, and PUSH_FILES that lets one host push to the others for distributing the code but lets it call on all of the hosts when gathering data.

With all of this machinery in place and distributed to all of the UNIX machines in my little network, I was now able to type ‘make ssh’ and get the resulting output:

marc@flapjack:~/projects/s/sysinfo$ more *.sysinfo
::::::::::::::
flapjack.sysinfo
::::::::::::::
{
  "report_date": "2014-09-01 10:37:30",
  "operating_system": "GNU/Linux",
  "kernel_name": "Linux",
  "kernel_release": "3.2.0-52-generic",
  "kernel_version": "#78-Ubuntu SMP Fri Jul 26 16:21:44 UTC 2013",
  "nodename": "flapjack",
  "machine": "x86_64",
  "processor": "x86_64",
  "hardware_platform": "x86_64",
  "boot_time": "system boot  2014-08-07 22:01",
  "uptime": 2118958,
  "totalram": 2089889792,
  "freeram": 145928192,
  "sharedram": 0,
  "totalswap": 2134896640,
  "totalhigh": 0,
  "freeswap": 2062192640,
  "mem_unit": 1
}
::::::::::::::
frenchtoast.sysinfo
::::::::::::::
{
  "report_date": "2014-09-01 10:37:31",
  "operating_system": "GNU/Linux",
  "kernel_name": "Linux",
  "kernel_release": "3.13.0-32-generic",
  "kernel_version": "#57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014",
  "nodename": "frenchtoast",
  "machine": "x86_64",
  "processor": "x86_64",
  "hardware_platform": "x86_64",
  "boot_time": "system boot  2014-07-19 14:58",
  "uptime": 3785970,
  "totalram": 16753840128,
  "freeram": 14150377472,
  "sharedram": 0,
  "totalswap": 17103319040,
  "totalhigh": 0,
  "freeswap": 17103319040,
  "mem_unit": 1
}
::::::::::::::
pancake.sysinfo
::::::::::::::
{
  "report_date": "2014-09-01 10:37:31",
  "operating_system": "GNU/Linux",
  "kernel_name": "Linux",
  "kernel_release": "3.13.0-35-generic",
  "kernel_version": "#62-Ubuntu SMP Fri Aug 15 01:58:42 UTC 2014",
  "nodename": "pancake",
  "machine": "x86_64",
  "processor": "x86_64",
  "hardware_platform": "x86_64",
  "boot_time": "system boot  2014-08-31 09:06",
  "uptime": 91840,
  "totalram": 16753819648,
  "freeram": 15609884672,
  "sharedram": 0,
  "totalswap": 17104367616,
  "totalhigh": 0,
  "freeswap": 17104367616,
  "mem_unit": 1
}
::::::::::::::
waffle.sysinfo
::::::::::::::
{
  "report_date": "2014-09-01 10:37:30",
  "operating_system": "GNU/Linux",
  "kernel_name": "Linux",
  "kernel_release": "3.13.0-35-generic",
  "kernel_version": "#62-Ubuntu SMP Fri Aug 15 01:58:42 UTC 2014",
  "nodename": "waffle",
  "machine": "x86_64",
  "processor": "x86_64",
  "hardware_platform": "x86_64",
  "boot_time": "system boot  2014-08-31 09:07",
  "uptime": 91784,
  "totalram": 16752275456,
  "freeram": 15594139648,
  "sharedram": 0,
  "totalswap": 17104367616,
  "totalhigh": 0,
  "freeswap": 17104367616,
  "mem_unit": 1
}

So now I have the beginning of a structured inventory of all of my machines, and an easy way to scale it up.

Leave a Reply

Your email address will not be published. Required fields are marked *