[ create a new paste ] login | about

Link: http://codepad.org/90XA2mFy    [ raw code | fork ]

microlinux - Python, pasted on Aug 16:
#!/bin/env python

""" psnoop.py

License
=======
Copyright 2013 Todd Mueller <fullname@gmaildomain>

This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.

Overview
========
Pings targets and stores the results in a SQLite 3 database. If the database
does not exist, it will be created. Errors are sent to syslog.

Options
=======
-h show help
-c ping count (5)
-i ping interval (.2)
-w max worker processes (10)
-f database file (./psnoop.db)


psnoop.py -c3 -i1 -f /home/psnoop/psnoop.db

Tables
======
[targets]
<int> id
<int> active
<str> target

[runs]
<int> id
<int> timestamp
<int> status

[results]
<int> id
<int> run (runs:id)
<int> status
<int> target (targets:id)
<int> sent
<int> recvd
<int> min
<int> avg
<int> max

run status:
0 = success
1 = failure

result status:
0 = no ping loss
1 = partial ping loss
2 = total ping loss
3 = target not found
4 = unexpected error

"""

from collections import namedtuple
from itertools import imap
from math import ceil, floor
from multiprocessing import Pool
from optparse import OptionParser
from os import path
from shlex import split
import sqlite3
from subprocess import PIPE, Popen, STDOUT
from syslog import syslog
from threading import Timer
from time import time
from traceback import format_exc

""" support programs """

def strip_command_output(lines):
  if len(lines) > 0:
    for i in range(len(lines)):
      lines[i] = lines[i].strip()

    while lines and not lines[-1]:
      lines.pop()

    while lines and not lines[0]:
      lines.pop(0)

  return lines

def parse_command_result(result):
  return namedtuple('CommandResult', ['command', 'pid', 'retval', 'runtime', 'output'])._make([result[0], result[1], result[2], result[3], result[4]])

def command_worker(command):
  kill = lambda this_proc: this_proc.kill()
  output = []
  pid = None
  retval = None
  runtime = None
  start = time()

  try:
    proc = Popen(split('%s' % str(command[0])), stdout=PIPE, stderr=STDOUT, stdin=PIPE)
    timer = Timer(command[1], kill, [proc])

    timer.start()
    output = proc.communicate(command[2])[0].splitlines()
    timer.cancel()

    runtime = round(time() - start, 3)
  except OSError:
    retval = 127
    output = ['command not found']
  except Exception, e:
    retval = 257
    output = format_exc().splitlines()
  finally:
    if retval == None:
      if proc.returncode == -9:
        output = ['command timed out']

      pid = proc.pid
      retval = proc.returncode

    return [command[0], pid, retval, runtime, strip_command_output(output)]

def multi_command(commands, timeout=10, workers=10, stdins=None):
  count = len(commands)

  if workers > count:
    workers = count

  for i in range(count):
    if stdins:
      stdin = stdins[i]
    else:
      stdin = None

    commands[i] = [commands[i], timeout, stdin]

  pool = Pool(processes=workers)
  results = pool.imap(command_worker, commands)

  pool.close()
  pool.join()

  return imap(parse_command_result, results)

def parse_ping_result(result):
  avg = 0
  min = 0
  max = 0
  num_lines = len(result.output)
  recvd = 0
  sent = 0
  status = 0 # no packets lost

  if (num_lines == 5):
    info = result.output[3].split()
    sent =  int(info[0])
    recvd = int(info[3])
    stats = map(int, map(floor, (map(float, result.output[4].split()[3].split('/')))))
    min = stats[0]
    avg = stats[1]
    max = stats[2]

    if recvd < sent:
      status = 1 # some packets lost
  elif (num_lines == 4):
    status = 2 # all packets lost
  elif result.retval == 2:
    status = 3 # target did not resolve
  else:
    status = 4 # something bad happened

  return namedtuple('PingResult', ['status', 'sent', 'recvd', 'min', 'avg', 'max'])._make([status, sent, recvd, min, avg, max])

def multi_ping(targets, count, interval, workers):
  commands = []

  for target in targets:
    commands.append('/bin/ping -c%s -i%s -W1 -q %s' % (count, interval, target))

  return imap(parse_ping_result, multi_command(commands, count * interval + 2, workers))

""" main program """

if __name__ == '__main__':
  db = None
  index = 0
  targets = []

  parser = OptionParser()

  parser.add_option('-c', type='int', action='store', dest='count', default=5, metavar='COUNT', help='ping count (5)')
  parser.add_option('-i', type='float', action='store', dest='interval', default=.2, metavar='INTERVAL', help='ping interval (.2)')
  parser.add_option('-f', action='store', dest='file', default='./psnoop.db', metavar='FILE', help='db file (./psnoop.db)')
  parser.add_option('-w', type='int', action='store', dest='workers', default=10, metavar='WORKERS', help='max worker processes (10)')

  options = parser.parse_args()[0]

  """ create db if file not found """
  if not path.exists(options.file):
    try:
      db = sqlite3.connect(options.file)
      cursor = db.cursor()

      cursor.execute('CREATE TABLE results (id INTEGER PRIMARY KEY, run NUMERIC, status NUMERIC, target NUMERIC, sent NUMERIC, recvd NUMERIC, min NUMERIC, avg NUMERIC, max NUMERIC)');
      cursor.execute('CREATE TABLE runs (id INTEGER PRIMARY KEY, timestamp NUMERIC, status NUMERIC)');
      cursor.execute('CREATE TABLE targets (id INTEGER PRIMARY KEY, active NUMERIC, address TEXT)');
      cursor.execute('CREATE INDEX results_run ON results(run ASC)');
      cursor.execute('CREATE INDEX results_status ON results(status ASC)');
      cursor.execute('CREATE INDEX results_target ON results(target ASC)');
      cursor.execute('CREATE INDEX runs_status ON runs(status ASC)');
      cursor.execute('CREATE INDEX runs_timestamp ON runs(timestamp ASC)');
      cursor.execute('CREATE INDEX targets_active ON targets(active ASC)');
      cursor.execute('CREATE UNIQUE INDEX targets_address ON targets(address ASC)');

      db.commit()
    except sqlite3.Error, e:
      syslog('psnoop: db creation failed, %s' % e)
      exit(1)

  """ connect to db if file found """
  if db == None:
    try:
      db = sqlite3.connect(options.file)
      cursor = db.cursor()
    except sqlite3.Error, e:
      syslog('psnoop: db connect failed, %s' % e)
      exit(2)

  """ select active targets """
  try:
    cursor.execute('SELECT * from `targets` WHERE `active` = 1')
    rows = cursor.fetchall()

    if len(rows) < 1:
      raise RuntimeError('no active targets found')

    for target in rows:
      targets.append(target[2])

    results = multi_ping(targets, options.count, options.interval, options.workers)
  except sqlite3.Error, e:
    syslog('pnsoop: target select failed, %s' %e)
    exit(3)
  except RuntimeError, e:
    syslog('psnoop: %s' % e)
    exit(4)
  except Exception, e:
    syslog('psnoop: unexpected error, %s' % e)
    exit(5)

  """ insert new run into db """
  try:
    cursor.execute('INSERT INTO `runs` VALUES(NULL, %d, 1)' % int(time()))
    db.commit()
  except sqlite3.Error, e:
    syslog('psnoop: run insert failed, %s' % e)
    exit(6)

  run_id = cursor.lastrowid

  """ insert ping results into db """
  try:
    for result in results:
      cursor.execute('INSERT INTO `results` VALUES(NULL, %d, %d, %d, %d, %d, %d, %d, %d)' % (run_id, result.status, rows[index][0], result.sent, result.recvd, result.min, result.avg, result.max))
      index = index + 1

    if len(rows) != index:
      raise RuntimeError('number of results != number of targets')

    db.commit()
  except sqlite3.Error, e:
    syslog('psnoop: result insert failed, %s' % e)
    exit(7)
  except RuntimeError, e:
    syslog('psnoop: %s' %e)
    exit(8)

  """ success, set status of run to 0 """
  try:
    cursor.execute('UPDATE `runs` SET `status` = 0 WHERE `id` = %s' % run_id)
    db.commit()
  except sqlite3.Error, e:
    syslog('psnoop: run status update failed, %s' % e)
    exit(9)

  exit(0)


Create a new paste based on this one


Comments: