From:	HENRY::IN%"leichter-jerry%yale.arpa%kl.sri.COM%relay.cs.net@RCA.COM" 18-APR-1987 16:26
To:	info-vax@sri-kl.ARPA
Subj:	Re: Monitor patch from DECUS to measure Ethernet activity

The following program - which provides a MONITOR-like display of Ethernet
statistics, and seems to provide a couple of values that the MONITOR ETHER
command the published patch gives you omits - appeared on INFO-VAX a while
back, at the tail end of a document on how to use the VMS Ethernet drivers.
(The document later appeared in Pageswapper; I've omitted the body of the
document here as it is quite long, and partly obsolete.)

Oh, BTW - don't bother looking for documentation on the special protocol the
program uses to pick up the counters - unless you want to look at the fiche.

							-- Jerry




              Using the DEUNA/DEQNA Ethernet Interface Under VMS

                   Ned Freed, Kevin Carosso and Dan Newman
                               MATHLIB Project
                             Harvey Mudd College
                             Claremont, CA 91711

	...

7.0  A DEUNA monitoring program

The following program monitors DEUNA performance by periodically sampling  the
internal  hardware  counters  via  a  $QIO.   Both  transmission and reception
information  is  presented   in   the   form   of   "current"   (e.g.    bytes
received/second),  "average"  (e.g.  average bytes received/second), "minimum"
and "maximum" (e.g.  minimum and maximum bytes received/second) and  "counter"
(e.g.   present value of the DEUNA's bytes received counter).  The result is a
MONITOR-like display of DEUNA activity.

XEA0 is monitored by default.  If you want to monitor a different device, just
define  a  symbol to run the program.  The device name to monitor will then be
read off the command line with a LIB$GET_FOREIGN call.

The program begins operations by clearing the terminal screen and drawing  its
display.   To exit type either control-c or control-z.  To repaint the screen,
type contrl-w.  The counters will be reset if the "seconds since  last  reset"
counter  exceeds  65530.   The  counters  can  be  reset  manually  by  typing
control-r.

The program uses the protocol type  61-02,  which  is  not  reserved  for  any
specific  purpose.   This  protocol  type  can be changed if it conflicts with
anything.  The program is written in VAX Pascal V2  and  requires  no  special
privileges.


(* Monitor DEUNA performance statistics.
   Written by Dan Newman and Ned Freed, 21-July-84,
   MATHLIB Project, Harvey Mudd College *)

[inherit ('SYS$LIBRARY:STARLET.PEN')] Program DEUNA;
Const
  NMA$C_PCLI_BFN  = %x451; NMA$C_PCLI_PTY  = %xB0E; NMA$C_CTLIN_ZER = %x000;
  NMA$C_CTLIN_DBR = %x3F2; NMA$C_CTLIN_MBL = %x3F4; NMA$C_CTLIN_RFL = %x426;
  NMA$C_CTLIN_BRC = %x3E8; NMA$C_CTLIN_MBY = %x3EA; NMA$C_CTLIN_OVR = %x428;
  NMA$C_CTLIN_LBE = %x411; NMA$C_CTLIN_DBS = %x3F3; NMA$C_CTLIN_MBS = %xA8D;
  NMA$C_CTLIN_BSM = %x3F7; NMA$C_CTLIN_BS1 = %x3F6; NMA$C_CTLIN_BID = %x3F5;
  NMA$C_CTLIN_BSN = %x3E9; NMA$C_CTLIN_MSN = %xA8E; NMA$C_CTLIN_SFL = %x424;
  NMA$C_CTLIN_CDC = %x425; NMA$C_CTLIN_UFD = %x427;
Type
  UnsignedWord = [Word] 0..65535; $Word = [Word] -32768..32767;
  CounterType = array [1..256] of unsigned;
  Counters = (BRC, DBR, MBY, MBL, RFL, OVR, LBE, BSN, DBS, MSN, MBS,
              BID, SFL, BSM, BS1, CDC, UFD);
  StartupType = Record
    PCLI_BFN       : UnsignedWord; PCLI_BFN_VALUE : unsigned;
    PCLI_PTY       : UnsignedWord; PCLI_PTY_VALUE : unsigned;
  end; (* StartupType *)
  StatType = Record
    Current, Previous           : array [Counters] of unsigned;
    Rate, Accumulated, Min, Max : array [Counters] of real;
  end; (* StatType *)
Var
  DEUNAChannel, TTYChannel : $Word; Count, Flag, Status : integer := 0;
  CounterBuffer : CounterType; StartupBuffer : StartupType; Stats : StatType;
  CounterDescriptor, IOStatus, StartupDescriptor : array [1..2] of integer;
  CurrentTime, LastTime : unsigned; Exit : boolean := false;
  DeviceName : varying [20] of char; DeviceNameLength : integer;
  First : boolean := true; Index : Counters;
  Lines : array [Counters] of integer;
  LongPointer : ^unsigned; WordPointer : ^UnsignedWord;

[Unbound, External (LIB$GET_FOREIGN)] Function $GetForeign (
  var GetStr : varying [l1] of char;
  Prompt : [Class_S] packed array [l2..u2 : integer] of char := %Immed 0;
  %Ref OutLen, Force : integer := %Immed 0) : integer; extern;

[Unbound, External (LIB$STOP)] Function $STOP (
  %Immed Error : integer) : integer; extern;

[Unbound, External (LIB$SET_BUFFER)] Function $SetBuffer (
  Buffer : [Class_S] packed array [l1..u1 : integer] of char := %Immed 0;
  %Ref OldBuffer : packed array [l2..u2 : integer] of char := %Immed 0)
  : integer; extern;

[Unbound, External (LIB$PUT_BUFFER)] Function $PutBuffer (
  %Ref OldBuffer : packed array [l1..u1 : integer] of char := %Immed 0)
  : integer; extern;

[Unbound, External (LIB$PUT_SCREEN)] Function $PutScreen (
  Text : [Class_S] packed array [l1..u1 : integer] of char;
  %Ref LineNo, ColNo, Flags : integer) : integer; extern;

[Unbound, External (LIB$ERASE_PAGE)] Function $ErasePage (
  %Ref LineNo, ColNo : integer := %Immed 0) : integer; extern;

Function Min (Real1, Real2 : real) : real;
  Begin Min := Real1; if Real2 < Real1 then Min := Real2; End;

Function Max (Real1, Real2 : real) : real;
  Begin Max := Real1; if Real2 > Real1 then Max := Real2; End; (* Max *)

Procedure IncrementPointers (Inc : integer);
  Begin (* IncrementPointers *)
    LongPointer::unsigned := LongPointer::unsigned + Inc;
    WordPointer::unsigned := WordPointer::unsigned + Inc;
  End; (* IncrementPointers *)

Procedure ReadCounterBuffer;
  var CurrentCounter : UnsignedWord; i : integer;
  Begin (* ReadCounterBuffer *)
    WordPointer := nil; LongPointer := nil;
    IncrementPointers (Iaddress (Counterbuffer));
    while LongPointer::unsigned < Iaddress (Counterbuffer) + 
                                      size (Counterbuffer) do begin
      CurrentCounter := WordPointer^;
      IncrementPointers (2);
      i := int (uand (%xFFF, CurrentCounter));
      if CurrentCounter = 0 then IncrementPointers (256)
        else if i = NMA$C_CTLIN_ZER then CurrentTime := WordPointer^
        else if i = NMA$C_CTLIN_DBR then Stats.Current[DBR] := LongPointer^
        else if i = NMA$C_CTLIN_MBL then Stats.Current[MBL] := LongPointer^
        else if i = NMA$C_CTLIN_RFL then Stats.Current[RFL] := WordPointer^
        else if i = NMA$C_CTLIN_BRC then Stats.Current[BRC] := LongPointer^
        else if i = NMA$C_CTLIN_MBY then Stats.Current[MBY] := LongPointer^
        else if i = NMA$C_CTLIN_OVR then Stats.Current[OVR] := WordPointer^
        else if i = NMA$C_CTLIN_LBE then Stats.Current[LBE] := WordPointer^
        else if i = NMA$C_CTLIN_DBS then Stats.Current[DBS] := LongPointer^
        else if i = NMA$C_CTLIN_MBS then Stats.Current[MBS] := LongPointer^
        else if i = NMA$C_CTLIN_BSM then Stats.Current[BSM] := LongPointer^
        else if i = NMA$C_CTLIN_BS1 then Stats.Current[BS1] := LongPointer^
        else if i = NMA$C_CTLIN_BID then Stats.Current[BID] := LongPointer^
        else if i = NMA$C_CTLIN_BSN then Stats.Current[BSN] := LongPointer^
        else if i = NMA$C_CTLIN_MSN then Stats.Current[MSN] := LongPointer^
        else if i = NMA$C_CTLIN_SFL then Stats.Current[SFL] := WordPointer^
        else if i = NMA$C_CTLIN_CDC then Stats.Current[CDC] := WordPointer^
        else if i = NMA$C_CTLIN_UFD then Stats.Current[UFD] := WordPointer^;
      if CurrentCounter > %xCFFF then IncrementPointers (4)
                                 else IncrementPointers (2);
    end; (* while *)
  End; (* ReadCounterBuffer *)

Procedure PutScreen (Text : packed array [l1..u1 : integer] of char;
  LineNo, ColNo, Flags : integer);
  var Status : integer;
  Begin (* PutScreen *)
    Status := $PutScreen (Text, LineNo, ColNo, Flags);
    if not odd (Status) then $STOP (Status);
  End; (* PutScreen *)

Procedure UpdateLine (Rate, Average, Min, Max : real; Current : unsigned;
  LineNo : integer);
  var String : varying [9] of char;
  Begin (* UpdateLine *)
    writev (String, Rate:7:1); PutScreen (String, LineNo, 37, 0);
    writev (String, Average:7:1); PutScreen (String, LineNo, 46, 0);
    writev (String, Min:7:1); PutScreen (String, LineNo, 55, 0);
    writev (String, Max:7:1); PutScreen (String, LineNo, 64, 0);
    writev (String, Current:9); PutScreen (String, LineNo, 72, 0);
  End; (* UpdateLine *)

Procedure Update;
  var
    Buffer : packed array [1..1000] of char; String : varying [7] of char;
    Date : packed array [1..23] of char; Index : Counters; Status : integer;
  Begin (* Update *)
    (* Set up the screen buffer *)
    Status := $SetBuffer (Buffer); if not odd (Status) then $STOP (Status);
    (* Get the date *)
    $ASCTIM (,Date); 
    for Status := 23 downto 13 do Date[Status] := Date[Status-1];
    (* Build the Buffer *)
    PutScreen (Date, 2, 29, 0);
    for Index := BRC to UFD do
      UpdateLine (Stats.Rate[Index], Stats.Accumulated[Index]/Count,
                  Stats.Min[Index], Stats.Max[Index],
                  Stats.Current[Index], Lines[Index]);
    writev (String, CurrentTime:6);
    Status := 1; While String[Status] = ' ' do Status := succ (Status);
    PutScreen (substr (String, Status, 1+length(String)-Status), 24, 35, 0);
    (* Post the buffer to the screen via one $QIO *)
    Status := $PutBuffer; if not odd (Status) then $STOP (Status);
    (* Stop blocking the AST's *)
    Status := $SETAST (1); if not odd (Status) then $STOP (Status);
    (* See if the "Resetting the Counters" flag needs to be removed *)
    if Flag = 2 then begin
      Flag := 0; PutScreen ('                      ', 1, 59, 0);
      PutScreen ('            ', 24, 35, 0);
    end; (* if Flag *)
    if Flag = 1 then Flag := succ (Flag);
  End; (* Update *)

Procedure Paint;
  var Buffer : packed array [1..900] of char; Status : integer;
  Begin (* Paint *)
    (* Set up the Screen buffer *)
    Status := $SetBuffer (Buffer); if not odd (Status) then $STOP (Status);
    (* Erase the page *)
    $ErasePage (1,1);
    (* Put up the headers *)
    PutScreen ('DEUNA Performance Statistics', 1, 26, 1);
    PutScreen ('Device', 4, 1, 0); PutScreen (DeviceName, 4, 8, 0);
    PutScreen ('Current', 4, 37, 8); PutScreen ('Average', 4, 46, 8);
    PutScreen ('Minimum', 4, 55, 8); PutScreen ('Maximum', 4, 64, 8);
    PutScreen ('Counter', 4, 74, 8); PutScreen ('Received:', 5, 1, 0);
    PutScreen ('Bytes', 6, 3, 0); PutScreen ('Packets', 7, 3, 0);
    PutScreen ('Multicast Bytes', 8, 3, 0);
    PutScreen ('Multicast Packets', 9, 3, 0);
    PutScreen ('Packets in Error', 10, 3, 0);
    PutScreen ('Lost - Internal Buffer Errors', 11, 3, 0);
    PutScreen ('Lost - Local Buffer Errors', 12, 3, 0);
    PutScreen ('Transmitted:', 13, 1, 0); PutScreen ('Bytes', 14, 3, 0);
    PutScreen ('Packets', 15, 3, 0); PutScreen ('Multicast Bytes', 16, 3, 0);
    PutScreen ('Multicast Packets', 17, 3, 0);
    PutScreen ('Packets Deferred', 18, 3, 0); 
    PutScreen ('Packets Aborted', 19, 3, 0);
    PutScreen ('Packets with Several Errors', 20, 3, 0);
    PutScreen ('Packets with 1 Error', 21, 3, 0);
    PutScreen ('Collision Check Failures', 22, 3, 0);
    PutScreen ('Unrecognized Frame Destinations', 23, 1, 0);
    PutScreen ('Seconds Since Last Counter Reset:', 24, 1, 0);
    (* Now put the info to the screen in one $QIO *)
    Status := $PutBuffer; if not odd (Status) then $STOP (Status);
  End; (* Paint *)

Procedure ResetDEUNA;
  var Status : integer;
  Begin (* ResetDEUNA *)
    Flag := 1; PutScreen ('Resetting the Counters' + chr (7), 1, 59, 3);
    Status := $QIOW (Chan := DEUNAChannel, Iosb := IOStatus,
                     Func := IO$_SENSEMODE + IO$M_CTRL + IO$M_CLR_COUNT +
                     IO$M_RD_COUNT, P2 := IAddress (CounterDescriptor));
    if not odd (Status) then $STOP (Status);
    if not odd (IOStatus[1]) then $STOP (IOStatus[1]);
    ReadCounterBuffer;
    for Index := BRC to UFD do Stats.Previous[Index] := Stats.Current[Index];
    LastTime := CurrentTime; PutScreen ('        ', 24, 34, 0);
  End; (* ResetDEUNA *)

Procedure EnableAST; forward;

Procedure AST (var Character : char);
  var Status : integer;
  Begin (* AST *)
    (* Kill any other I/O requests on this channel *)
    Status := $CANCEL (TTYChannel); if not odd (Status) then $STOP (Status);
    case IAddress (Character) of
      (* ^C and ^Z -- Exit gracefully *)
      03, 26 : begin
        (* Shut the DEUNA down *)
        RetStatus := $CANCEL (DEUNAChannel);
        if not odd (RetStatus) then $STOP (RetStatus);
        RetStatus := $QIOW (Chan := DEUNAChannel, Iosb := IOStatus,
                            Func := IO$_SETMODE + IO$M_CTRL + IO$M_SHUTDOWN);
        if not odd (RetStatus) then $STOP (RetStatus);
        RetStatus := $DASSGN (DEUNAChannel);
        if not odd (RetStatus) then $STOP (RetStatus);
        Exit := true; end;
      (* ^R -- Reset the DEUNA *)
      18 : ResetDEUNA;
      (* ^W -- Repaint the screen *)
      23 : begin Paint; Update; end;
    end; (* case *)
    (* Reset the AST *)
    EnableAST;
  End; (* AST *)

Procedure EnableAST;
  var Mask : array [1..2] of integer; Status : integer;
  Begin (* EnableAST *)
    (* Out-of-band AST Mask -- Trap ^C, ^R, ^W and ^Z*)
    Mask[1] := 0; Mask[2] := (2**3) + (2**18) + (2**23) + (2**26);
    (* Queue the out-of-band AST *)
    Status := $QIOW (Chan := TTYChannel, Func := IO$_SETMODE + IO$M_OUTBAND,
                     P1 := %Immed AST, P2 := IAddress (Mask));
    if not odd (Status) then $STOP (Status);
  End; (* EnableAST *)

Begin (* DEUNA *)
  (* These numbers represent which lines on the screen to place the data on *)
  Lines[BRC] :=  6; Lines[DBR] :=  7; Lines[MBY] :=  8; Lines[MBL] :=  9;
  Lines[RFL] := 10; Lines[OVR] := 11; Lines[LBE] := 12; Lines[BSN] := 14;
  Lines[DBS] := 15; Lines[MSN] := 16; Lines[MBS] := 17; Lines[BID] := 18;
  Lines[SFL] := 19; Lines[BSM] := 20; Lines[BS1] := 21; Lines[CDC] := 22;
  Lines[UFD] := 23;
  (* Setup the information to initialize the DEUNA *)
  with StartupBuffer do begin
    PCLI_BFN := NMA$C_PCLI_BFN; PCLI_BFN_VALUE := 1;
    PCLI_PTY := NMA$C_PCLI_PTY;
    PCLI_PTY_VALUE := %x0261; (* some unused protocol here *)
  end; (* with *)
  (* Build the descriptors for the $QIO calls *)
  CounterDescriptor[1] := size (CounterBuffer);
  CounterDescriptor[2] := IAddress (CounterBuffer);
  StartupDescriptor[1] := size (StartupBuffer);
  StartupDescriptor[2] := IAddress (StartupBuffer);
  (* Get a Channel to the terminal *)
  Status := $ASSIGN ('TT:', TTYChannel); 
  if not odd (Status) then $STOP (Status);
  (* Get a Channel to the DEUNA *)
  Status := $GetForeign (DeviceName, , DeviceNameLength);
  if not odd (Status) then $STOP (Status);
  if DeviceNameLength = 0 then DeviceName := 'XEA0';
  Status := $ASSIGN (DeviceName, DEUNAChannel);
  if not odd (Status) then $STOP (Status);
  (* Set up the ^C, ^R, ^W and ^Z traps *)
  EnableAST;
  (* Initialize the DEUNA *)
  Status := $QIOW (Chan := DEUNAChannel, Iosb := IOStatus,
                   Func := IO$_SETMODE + IO$M_STARTUP + IO$M_CTRL,
                   P2 := IAddress (StartupDescriptor));
  if not odd (Status) then $STOP (Status);
  if not odd (IOStatus[1]) then $STOP (IOStatus[1]);
  (* Start our loop *)
  Repeat
    (* Read the DEUNA counters *)
    Status := $QIOW (Chan := DEUNAChannel, Iosb := IOStatus,
                     Func := IO$_SENSEMODE + IO$M_RD_COUNT + IO$M_CTRL,
                     P2 := IAddress (CounterDescriptor));
    if not odd (Status) then $STOP (Status);
    if not odd (IOStatus[1]) then $STOP (IOStatus[1]);
    (* Dissect the returned information *)
    ReadCounterBuffer;
    (* Now generate the statisitics *)
    if CurrentTime > 65530 then ResetDEUNA else begin
      if LastTime <> CurrentTime then begin
        if First then begin
          for Index := BRC to UFD do begin
            Stats.Previous[Index] := Stats.Current[Index];
            Stats.Accumulated[Index] := 0;
            Stats.Min[Index] := maxint; Stats.Max[Index] := -1;
          end; (* for *)
        end else begin
          Count := succ (Count);
          for Index := BRC to UFD do begin
            Stats.Rate[Index] := (Stats.Current[Index] -
                       Stats.Previous[Index]) / (CurrentTime - LastTime);
            Stats.Accumulated[Index] := Stats.Accumulated[Index] +
                                        Stats.Rate[Index];
            Stats.Min[Index] := Min (Stats.Min[Index], Stats.Rate[Index]);
            Stats.Max[Index] := Max (Stats.Max[Index], Stats.Rate[Index]);
            Stats.Previous[Index] := Stats.Current[Index];
          end; (* for *)
        end; (* if First *)
        (* Display the information on the terminal *)
        if First then begin
           First := false; Paint;
        end else Update;
	if Exit then begin
	   $ErasePage (1,1); $EXIT (1);
        end; (* if Exit *)
      end; (* if LastTime <> CurrentTime *)
      LastTime := CurrentTime;
    end; (* if CurrentTime > 65530 *)
  until 1 = 2;
End. (* DEUNA *)
-------
