• Today's ARexx

    From Neil Williams@1:2320/100 to All on Sun Sep 2 15:39:02 2001
    1.71.5.204-B20160823

    Today's ARexx is something for someone who runs a network of machines (with an Amiga, of course) and wants to check if all the machines are running okay during the day. This might be especially useful if you're not in the same location as the machines.

    What this does is ping a list of network names, one at a time, and checks if there is a response. If there is, it logs the ping time, or if not just logs the machine as "Down". It continues doing this until broken with Ctrl-C. When broke, or every hour, it also prints a summary to the log which is in the format of the standard Ping summary.

    Sadly, being ARexx, the ping times are badly out for local machines (ARexx latency seems to be around 20ms - fine for the Internet, when a machine might be 500ms away, but on a LAN you'd often expect <1ms). This isn't a problem for us, as this script only wants to see if the machine is up and running.

    Here's an example log file, set to loop every second or so - normally, you'd only want this to loop every five minutes or half hour unless you're controlling "mission critical" machines (see configurable bits of the script).

    --------------------------------------------------------------------

    20010902.15:54:48 | nuke mono burn rhic bauen

    20010902.15:54:49 | 26.2 28.77 26.36 26.54 Down 20010902.15:54:51 | 21.15 23.66 22.17 22.33 Down 20010902.15:54:54 | 19.49 21.94 21.05 21.16 Down 20010902.15:54:56 | 18.78 21.02 20.4 20.53 Down 20010902.15:54:58 | 18.26 20.86 19.6 19.71 Down

    Hourly network response statistics 02/09/01 15:55:00 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    nuke - 5 packets transmitted, 5 packets received, 0% packet loss. round-trip min/avg/max = 16.1/18.26/26.21 ms
    mono - 5 packets transmitted, 5 packets received, 0% packet loss. round-trip min/avg/max = 18.26/20.86/28.78 ms
    burn - 5 packets transmitted, 5 packets received, 0% packet loss. round-trip min/avg/max = 16.41/19.6/26.37 ms
    rhic - 5 packets transmitted, 5 packets received, 0% packet loss. round-trip min/avg/max = 16.43/19.71/26.55 ms
    bauen - 5 packets transmitted, 0 packets received, 100% packet loss.

    --------------------------------------------------------------------

    The top part is the constantly (per configured interval) updated bit, the bottom part is output every hour or when you Ctrl-C it.

    In this example, there are four machines running and one down (in this case, it's switched off and not even phyically connected to the network).

    'nuke' is the localhost, so you can see what the ping latency is like.

    Something I plan to do later is a script to graph this data and pull out more useful information (e.g "host burn was down between 1am and 9am").

    The script doesn't hold the log file open, so you can easily set up something to do this on a weekly cron job:

    rename logfile to logfile.date
    archive logfile.date with lha/lzx
    net/email logfile.date to your personal mailbox
    delete logfile.date


    External libraries: this uses a few, available on AmiNet or your local BBS:
    * RXsocket
    * RexxTricks
    * RMH (RexxMustHave) -- this appears to require rxwiz.library installed too.


    The program requires an external file which holds the names or IP addresses of each machine you want to monitor. Very simple, one machine to a line in a file named LANstatus.config:

    --[ LANstatus.config ]------------------------------
    nuke
    mono
    burn
    rhic
    bauen
    ----------------------------------------------------

    Here's the script itself. As before, you'll need to go over every line to configure it for yourself (but this is AMIGA_PROG, and this is a programming exercise, so go do it!) - although there is a configurable bit. Particularly, "waitloop" should be set to a value you're comfortable with. I recommend between 5 and 30 minutes, but it's set to 1 minute here so you can get immediate results.

    --[ LANstatus.rexx ]--------------------------------
    /* LANstatus.rexx
    *
    * Pings a group of machines, and reports in
    * a log if those machines were up.
    * Neil Williams neil@zeusdev.co.uk
    * 2/9/2001.
    *
    * Parts based on ping.rexx from rxsocket
    * examples.
    */

    parse arg args

    options cache
    options failat 99
    options results

    signal on break_c

    if ~show("L","rexxsupport.library") then
    if ~addlib("rexxsupport.library",0,-30) then do
    say "no rexxsupport.library"
    exit
    end

    if ~show("L","rxsocket.library") then
    if ~addlib("rxsocket.library",0,-30) then do
    say "no rxsocket.library"
    exit
    end

    if ~show("L","rmh.library") then
    if ~addlib("rmh.library",0,-30) then do
    say "no rmh.library"
    exit
    end

    if( ~show( 'l', "rexxtricks.library" ) ) then
    if( ~addlib( "rexxtricks.library", 0, -30, 0 ) ) then do
    say "Could not open rexxtricks.library"
    exit
    end

    /* nb. for an AmiNet release, implement args - RMH has an excellent
    readargs() function:

    if ~RMH_ReadArgs("HOST/A,SIZE/K/N,NUM/K/N,QUICK/S") then do
    call PrintFault(IoErr(),prg)
    exit
    end*/

    /*
    */
    dataSize = 56 /* Size of ping packet */
    wait = 1 /* Seconds to wait for response from pings. Set to one
    * unless one of your machines is on the other side of
    * the planet! */
    waitloop = 1*60 /* Seconds to wait between each complete check of all
    * machines (x*60 for values of x minutes)
    */

    logfile = 'LOGS:LANstatus.log'

    /* Read a list of machines to check into stem LAN
    */
    if ~rxtr_readfile( 'LANstatus.config', 'LAN' ) then do
    say 'Error on config file LANstatus.config'
    exit
    end

    /** create a RAW ICMP socket **/
    sock=socket("INET","RAW","ICMP")
    if sock<0 then call err("no socket")

    /** socket must NOT blocking **/
    call IOCtlSocket(sock,"FIONBIO",1)

    /** we need an ID to recognize our icmp **/
    ourID=right(x2c(pragma("ID")),2)

    /** create a timer **/
    t=CreateTimer()
    timSig=TimerSignal(t)


    /* Initalise and write log header
    */
    line = date('S') ||'.'|| time() || ' |'

    do i = 1 to LAN.0
    LAN.i.min=1E10
    LAN.i.max=0
    LAN.i.avg=0
    LAN.i.nrec=0
    LAN.i.ntrans=0

    LAN.i.UP=0

    line = line || ' ' || left( LAN.i, 10 )
    end

    /* Wow, what lazy coding.. */
    if ~exists( logfile ) then
    address command 'echo >LOGS:LANstatus.log'
    if open('MyCon',logfile, 'a') then do
    writeln( 'MyCon', line )
    writeln( 'MyCon', '' )
    call close( 'MyCon' )
    end

    /* Main Loop.
    * Networking code is mostly from rxsocket ping from here
    */

    running = 1
    brunning = 1

    do while brunning
    do i = 1 to LAN.0
    LAN.i.UP = 0

    /** resolve host name **/
    sin.addrAddr=resolve( LAN.i )
    if sin.addrAddr ~== "-1" then do

    call StartTimer(t,wait)
    call sendPacket(dataSize)
    sel.read.0=sock

    res=WaitSelect("SEL",,,timSig)
    if res~=0 then call readPacket

    /* Timeout */
    if and(sel.signals,timSig)=0 then
    LAN.i.UP = 1

    end

    end

    /* Do log entry
    */

    line = date('S') ||'.'|| time() || ' |'
    do i = 1 to LAN.0
    if LAN.i.UP then
    line = line || ' ' || left( (((LAN.i.avg/LAN.i.nrec)*100)%1)/100, 10 )
    else
    line = line || ' ' || left( 'Down', 10 )
    end

    if ~exists( logfile ) then
    address command 'echo >LOGS:LANstatus.log'
    if open('MyCon',logfile, 'a') then do
    writeln( 'MyCon', line )
    call close( 'MyCon' )
    end

    /* Delay for specified period, and check if it's time for hourly stats
    */
    markhour = time( 'h' )
    call delay( waitloop*50 )

    Stats:

    if markhour ~= time( 'h' ) | running = 0 then do

    if ~exists( logfile ) then
    address command 'echo >LOGS:LANstatus.log'
    if open('MyCon',logfile, 'a') then do
    writeln( 'MyCon', '' )
    writeln( 'MyCon', 'Hourly network response statistics '|| date('e') ||
    ' ' || time() )
    writeln( 'MyCon', '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' )

    do i = 1 to LAN.0
    if LAN.i.ntrans==0 then loss=100
    else loss=(LAN.i.ntrans-LAN.i.nrec)/LAN.i.ntrans*100

    line = left( LAN.i, 15 ) ||'- '|| LAN.i.ntrans 'packets transmitted,' LAN.i.nrec 'packets received,' loss'% packet loss.'
    if LAN.i.nrec>0 then
    line = line || ' round-trip min/avg/max =' LAN.i.min'/'|| (((LAN.i.avg/LAN.i.nrec)*100)%1)/100 || '/'LAN.i.max 'ms'

    writeln( 'MyCon', line )
    end
    writeln( 'MyCon', '' )

    call close( 'MyCon' )
    end

    if running then
    line = date('S') ||'.'|| time() || ' |'

    do i = 1 to LAN.0
    LAN.i.min=1E10
    LAN.i.max=0
    LAN.i.avg=0
    LAN.i.nrec=0
    LAN.i.ntrans=0

    if running then
    line = line || ' ' || left( LAN.i, 10 )
    end

    if running then do
    if ~exists( logfile ) then
    address command 'echo >LOGS:LANstatus.log'
    if open('MyCon',logfile, 'a') then do
    writeln( 'MyCon', line )
    call close( 'MyCon' )
    end
    end

    if ~running then
    brunning = 0
    end /* hour/break stats */

    if ~running then
    signal ExitProgram

    end /* loop while running */

    ExitProgram:

    exit
    /* end of main loop */

    err:
    parse arg msg
    say prg":" msg
    exit

    pad: PROCEDURE
    parse arg string,len
    return copies(d2c(0),len-length(string)) || string

    d2m: PROCEDURE
    parse arg val,len
    return pad(d2c(val),len)

    /* NOT procedure, so we can pick up variables from the main loop */
    sendPacket:
    parse arg dataSize
    /** here we create an ICMP echo packet **/
    pktf = ourID || d2m( LAN.i.ntrans, 2 )
    call GetSysTime("TV")
    pktf=pktf || d2m(tv.secs,4) || d2m(tv.micro,4) || copies("0"x,dataSize-8)
    temp="0800"x || "0000"x || pktf
    pkt="0800"x || d2c(InetCksum(temp)) || pktf

    res=SendTo(sock,pkt,0,"SIN")
    if res~=length(pkt) then do
    e=errno()

    if IsLibOn("TTCP") then es=e
    else es=ErrorString(e)

    LAN.i.UP = 0
    end

    LAN.i.ntrans=LAN.i.ntrans+1
    return

    /* NOT procedure, so we can pick up variables from the main loop */
    readPacket:
    /** here we read an ICMP packet and check if it is an ICMP
    echo-replay for us **/
    call GetSysTime("NOW")

    res=RecvFrom(sock,"BUFF",256)
    if res<0 then do
    LAN.i.UP = 0
    end

    parse var buff vhl +1 tos +1 len +2 id +2 off +2 ttl +1 rest
    hl=c2d(bitand(vhl,'F'x))*8
    buff=c2x(buff)
    parse var buff +hl type +2 code +2 cksum +4 id +4 seq +4 s +8 m +8 data
    if c2d(len)~=dataSize+8 | type~='00' | code~='00' | id~=c2x(ourID)
    then return
    old.secs=x2d(s)
    old.micro=x2d(m)

    call SubTime("NOW","OLD")

    time=now.secs*1000000+now.micro
    time=time%1000"."time//1000
    time=((time*100)%1)/100

    LAN.i.nrec=LAN.i.nrec+1
    if time>LAN.i.max then LAN.i.max=time
    if time<LAN.i.min then LAN.i.min=time
    LAN.i.avg=LAN.i.avg+time
    return

    /** statistics **/
    BREAK_C:
    if t~="T" then call StopTimer(t)
    signal off BREAK_C

    call close( 'MyCon' )

    running = 0
    signal Stats


    --------------------------------------------------

    Have fun!


    --
    Neil Williams, neil@zeusdev.co.uk - ICQ UINs: 18223711 & 116110052
    FidoNet 2:442/107.0 - Part time BBS: telnet:tkgbbs.darktech.org
    --- Zeus BBS 1.5
    # Origin: Killing^Ground . 22:00 - 08:00 . +44 [0]1978 751393 (2:442/107.0)
    * Origin: LiveWire BBS - Synchronet - LiveWireBBS.com (1:2320/100)