-- Copyright (C) 1983, 1985  by Xerox Corporation. All rights reserved. 
-- LineWatcher.mesa, HGM, 12-Mar-85 23:32:50

DIRECTORY
  Display USING [Bitmap, Invert, replaceFlags, White],
  Format USING [],  -- Needed by Put.Number
  FormSW USING [
    ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem,
    StringItem, NumberItem],
  FileSW USING [GetFile, IsIt, SetFile],
  MFile USING [Acquire, Error, GetLength, Handle, Release],
  MsgSW USING [Post],
  MStream USING [GetLength, Handle, IsIt, Log, SetAccess, SetLength, SetLogReadLength],
  Process USING [Detach, MsecToTicks, Pause, SetTimeout],
  Put USING [Char, CR, Line, Number, Text],
  Runtime USING [GetBcdTime],
  Stream USING [GetByteProcedure, GetWordProcedure, GetProcedure, Handle],
  String USING [AppendLongNumber, AppendNumber, AppendString, Equivalent],
  System USING [GreenwichMeanTime, GetGreenwichMeanTime, AdjustGreenwichMeanTime],
  Time USING [Append, AppendCurrent, Unpack],
  Tool USING [
    Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW,
    AddThisSW],
  ToolWindow USING [CreateSubwindow, DisplayProcType, nullBox, TransitionProcType],
  Window USING [Handle, Box],

  Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  GateControlDefs USING [pupStatsAck, pupStatsNak, pupStatsSend],
  SlaFormat,
  Sla,
  PupWireFormat USING [BcplToMesaLongNumber],
  PupDefs USING [
    AppendPupAddress, PupPackageMake, PupPackageDestroy, PupBuffer,
    PupAddress, GetPupAddress,
    PupNameTrouble, SetPupContentsWords, PupSocket, PupSocketDestroy,
    PupSocketMake, MsToTocks],
  PupTypes USING [fillInSocketID, statSoc],
  Driver;

LineWatcher: MONITOR
  IMPORTS
    Display, FileSW, FormSW, MFile, MsgSW, MStream, Process, Put,
    Runtime, String, System, Time, Tool, ToolWindow,
    Buffer, PupDefs, PupWireFormat
  SHARES Driver =
  BEGIN OPEN PupDefs;

  msg, form, boxes, log: Window.Handle;

  running: BOOLEAN ← FALSE;
  pleaseStop: BOOLEAN ← FALSE;
  indicator: {left, right, off} ← off;
  seconds: CARDINAL ← 60;
  target: LONG STRING ← [30];
  netNumber: CARDINAL ← 7;
  where: PupAddress ← [[0], [0], PupTypes.statSoc];

  activeHosts: Sla.SlaHost ← LAST[Sla.SlaHost];
  activeLines: Sla.Line ← LAST[Sla.Line];

  pool: Buffer.AccessHandle;
  soc: PupSocket;
  
  overheadPerPacket: CARDINAL = 4; -- Flag, CRC, CRC, flag


  Start: FormSW.ProcType =
    BEGIN
    oldLogFileName: LONG STRING ← FileSW.GetFile[log].name;
    newLogFileName: STRING = [100];
    IF running THEN { MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; };
    String.AppendString[newLogFileName, target];
    String.AppendString[newLogFileName, "-"L];
    String.AppendNumber[newLogFileName, netNumber, 8];
    String.AppendString[newLogFileName, ".data"L];
    IF ~String.Equivalent[oldLogFileName, newLogFileName] THEN
      BEGIN -- All this to get append mode. Yetch.
      fh: MFile.Handle ← NIL;
      bytes: LONG CARDINAL ← 0;
      sh: MStream.Handle;
      fh ← MFile.Acquire[newLogFileName, anchor, [] ! MFile.Error => CONTINUE];
      IF fh # NIL THEN {
        bytes ← MFile.GetLength[fh];
        MFile.Release[fh]; };
      sh ← MStream.Log[newLogFileName, []];
      IF bytes # 0 THEN {
        getByte: Stream.GetByteProcedure = sh.getByte;
        getWord: Stream.GetWordProcedure = sh.getWord;
        get: Stream.GetProcedure = sh.get;
        MStream.SetAccess[sh, writeOnly];
        MStream.SetLength[sh, bytes];
        MStream.SetAccess[sh, log];
        sh.getByte ← getByte;
        sh.getWord ← getWord;
        sh.get ← get; };
      FileSW.SetFile[log, newLogFileName, sh];
      IF fh = NIL THEN Put.Line[log, -- New File: Insert header for ChartPlot (and people)
"
*                         Packets             Bytes      Bits/Sec     Errors
Date       Time     P-Sent P-Recv   B-Sent   B-Recv   Sent   Recv  CRC Lost  Dev
"L];
      END;
    Put.CR[log];
    Put.Text[log, "*  Watching net "L];
    O[netNumber];
    Put.Text[log, " on "L];
    IF ~FindPath[] THEN RETURN;
    running ← TRUE;
    Process.Detach[FORK Watch[seconds]];
    END;

  Stop: FormSW.ProcType = BEGIN Off[]; END;

  Off: PROCEDURE =
    BEGIN
    IF ~running THEN RETURN;
    pleaseStop ← TRUE;
    WHILE running DO Process.Pause[1]; ENDLOOP;
    pleaseStop ← FALSE;
    END;

  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    Put.Text[log, target];
    Put.Char[log, '=];
    GetPupAddress[
      @where, target !
      PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; Put.Line[log, e]; GOTO Trouble; END];
    PrintPupAddress[where];
    Put.Line[log, "."L];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;


  Watch: ENTRY PROCEDURE [seconds: CARDINAL] =
    BEGIN
    startTime, stopTime, targetTime: System.GreenwichMeanTime;
    actual: LONG CARDINAL;
    pause: CONDITION;
    MakeConnection[];
    SetupBoxes[];
    Process.SetTimeout[@pause, Process.MsecToTicks[500]];
    UNTIL pleaseStop DO -- Wait until even multiple of seconds
      targetTime ← System.GetGreenwichMeanTime[];
      IF (targetTime MOD seconds) = 0 THEN EXIT;
      WAIT pause;
      ENDLOOP;
    startTime ← targetTime;
    GetInfo[];
    oldLineInfo ← newLineInfo;
    Put.CR[log];
    UNTIL pleaseStop DO
      WHILE System.GetGreenwichMeanTime[] - targetTime >= seconds DO
        IF targetTime > System.GetGreenwichMeanTime[] THEN EXIT;  -- Clock set backwards ==> hang
        -- Oops, it took us more than a cycle to process everything.
        -- Maybe we were sitting in the debugger.
        targetTime ← System.AdjustGreenwichMeanTime[targetTime, seconds];
        ENDLOOP;
      UNTIL pleaseStop OR (System.GetGreenwichMeanTime[] - targetTime) >= seconds DO
        WAIT pause; ENDLOOP;
      IF pleaseStop THEN EXIT;
      GetInfo[];
      stopTime ← System.GetGreenwichMeanTime[];
      actual ← stopTime - startTime;
      IF pleaseStop THEN EXIT;
      PrintInfo[actual];
      oldLineInfo ← newLineInfo;
      startTime ← stopTime;
      targetTime ← System.AdjustGreenwichMeanTime[targetTime, seconds];
      ENDLOOP;
    SetDownBoxes[];
    KillConnection[];
    running ← FALSE;
    END;

  StatsEntry: TYPE = RECORD [
    packetsSent: LONG CARDINAL,
    packetsRecv: LONG CARDINAL,
    bytesSent: LONG CARDINAL,
    bytesRecv: LONG CARDINAL,
    syncErrors: CARDINAL,
    badCrc: CARDINAL,
    controlError: CARDINAL,
    state: SlaFormat.LineState];


  oldLineInfo, newLineInfo: ARRAY Sla.Line OF StatsEntry;
  newRoutingTable: ARRAY Sla.SlaHost OF SlaFormat.RoutingTableEntry;
  oldDest: ARRAY Sla.Line OF CARDINAL;

  thisID: CARDINAL ← 0;
  GetInfo: PROCEDURE =
    BEGIN
    b: PupBuffer;
    thisID ← thisID + 1;
    DO  -- until we get the answer
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID ← [thisID, thisID];
      b.pup.pupType ← GateControlDefs.pupStatsSend;
      b.pup.pupWords[0] ← netNumber;
      SetPupContentsWords[b, 1];
      soc.put[b];
      FlipBoxes[];
      DO
        b ← soc.get[];
        IF b = NIL THEN EXIT;
        IF b.pup.pupType = error OR b.pup.pupType = GateControlDefs.pupStatsNak
          OR b.pup.pupID.b # thisID THEN BEGIN Buffer.ReturnBuffer[b]; LOOP; END;
        IF b.pup.pupType # GateControlDefs.pupStatsAck THEN ERROR;
        IF b.pup.pupWords[0] # SlaFormat.slaStatsReply THEN ERROR;
        IF b.pup.pupWords[1] # SlaFormat.slaVersion THEN ERROR;
        EXIT;
	ENDLOOP;
      IF b # NIL THEN EXIT;
      IF pleaseStop THEN RETURN;
      LOOP;
      ENDLOOP;
    FlipBoxes[];
    CopyData[b];
    Buffer.ReturnBuffer[b];
    END;

  CopyData: PROCEDURE [b: PupBuffer] =
    BEGIN
    rte: LONG POINTER TO SlaFormat.RoutingTableEntry;
    sse: LONG POINTER TO SlaFormat.SlaStatsEntry;
    lib: LONG POINTER TO StatsEntry;
    p: LONG POINTER TO CARDINAL;
    lastHost: Sla.SlaHost ← b.pup.pupWords[2];
    activeHosts ← MIN[lastHost, LAST[Sla.SlaHost]];
    rte ← LOOPHOLE[@b.pup.pupWords[3]];
    FOR host: Sla.SlaHost IN (0..activeHosts] DO
      newRoutingTable[host] ← rte↑;
      rte ← rte + SIZE[SlaFormat.RoutingTableEntry];
      ENDLOOP;
    rte ← LOOPHOLE[@b.pup.pupWords[3]];  -- Get in sync again in case we don't have room
    FOR host: Sla.SlaHost IN (0..lastHost] DO
      rte ← rte + SIZE[SlaFormat.RoutingTableEntry]; ENDLOOP;
    p ← LOOPHOLE[rte];
    activeLines ← MIN[(p↑ + 1), LAST[Sla.Line]];
    sse ← LOOPHOLE[p + 1];
    FOR line: Sla.Line IN [0..activeLines) DO
      lib ← @newLineInfo[line];
      lib.packetsSent ← PupWireFormat.BcplToMesaLongNumber[sse.packetsSent];
      lib.packetsRecv ← PupWireFormat.BcplToMesaLongNumber[sse.packetsRecv];
      lib.bytesSent ← PupWireFormat.BcplToMesaLongNumber[sse.bytesSent];
      lib.bytesRecv ← PupWireFormat.BcplToMesaLongNumber[sse.bytesRecv];
      lib.syncErrors ← sse.syncErrors;
      lib.badCrc ← sse.badCrc;
      lib.controlError ← sse.controlError;
      lib.state ← sse.state;
      sse ← sse + SIZE[SlaFormat.SlaStatsEntry];
      ENDLOOP;
    END;


  PrintInfo: PROCEDURE [actual: LONG CARDINAL] =
    BEGIN
    FOR line: Sla.Line IN [0..activeLines) DO
      lib: StatsEntry;
      temp: LONG INTEGER;
      PrintTime[];
      Put.Text[log, " "];
      lib.packetsSent ←
        newLineInfo[line].packetsSent - oldLineInfo[line].packetsSent;
      lib.packetsRecv ←
        newLineInfo[line].packetsRecv - oldLineInfo[line].packetsRecv;
      lib.bytesSent ← newLineInfo[line].bytesSent - oldLineInfo[line].bytesSent;
      lib.bytesRecv ← newLineInfo[line].bytesRecv - oldLineInfo[line].bytesRecv;
      lib.badCrc ← newLineInfo[line].badCrc - oldLineInfo[line].badCrc;
      lib.syncErrors ←
        newLineInfo[line].syncErrors - oldLineInfo[line].syncErrors;
      lib.controlError ←
        newLineInfo[line].controlError - oldLineInfo[line].controlError;
      IF LOOPHOLE[lib.packetsSent, LONG INTEGER] < 0 THEN lib.packetsSent ← 0;
      IF LOOPHOLE[lib.packetsRecv, LONG INTEGER] < 0 THEN lib.packetsRecv ← 0;
      IF LOOPHOLE[lib.bytesSent, LONG INTEGER] < 0 THEN lib.bytesSent ← 0;
      IF LOOPHOLE[lib.bytesRecv, LONG INTEGER] < 0 THEN lib.bytesRecv ← 0;
      IF LOOPHOLE[lib.badCrc, INTEGER] < 0 THEN lib.badCrc ← 0;
      IF LOOPHOLE[lib.syncErrors, INTEGER] < 0 THEN lib.syncErrors ← 0;
      IF LOOPHOLE[lib.controlError, INTEGER] < 0 THEN lib.controlError ← 0;
      LD7[lib.packetsSent];
      LD7[lib.packetsRecv];
      LD9[lib.bytesSent];
      LD9[lib.bytesRecv];
      temp ← lib.bytesSent + overheadPerPacket*lib.packetsSent;
      LD7[temp*8/actual];
      temp ← lib.bytesRecv + overheadPerPacket*lib.packetsRecv;
      LD7[temp*8/actual];
      IF TRUE OR (lib.badCrc # 0) OR (lib.syncErrors # 0) OR (lib.controlError # 0) THEN
        BEGIN LD5[lib.badCrc]; LD5[lib.syncErrors]; LD5[lib.controlError]; END;
      Put.CR[log];
      ENDLOOP;
    ForceOutInfo[log];
    END;

  CheckLineState: PROCEDURE =
    BEGIN
    changed: BOOLEAN;
    FOR line: Sla.Line IN [0..activeLines) DO
      changed ← FALSE;
      -- find out who this line is connected to
      FOR host: Sla.SlaHost IN Sla.SlaHost DO
        rte: POINTER TO SlaFormat.RoutingTableEntry;
        rte ← @newRoutingTable[host];
        IF rte.line = line AND rte.hops = 1 THEN
          BEGIN
          IF oldDest[line] # host THEN changed ← TRUE;
          oldDest[line] ← host;
          EXIT;
          END;
        ENDLOOP;
      IF oldLineInfo[line].state # newLineInfo[line].state THEN changed ← TRUE;
      IF ~changed THEN LOOP;
      Put.Text[log, "* "];
      PrintTime[];
      Put.Text[log, "  Line "];
      O[line];
      Put.Text[log, " is "];
      SELECT newLineInfo[line].state FROM
        up => BEGIN Put.Text[log, "up to host "]; O[oldDest[line]]; END;
        down => Put.Text[log, "down"L];
        loopedBack => Put.Text[log, "looped back"L];
        ENDCASE => Put.Text[log, "??"L];
      Put.Char[log, '.];
      Put.CR[log];
      ENDLOOP;
    END;

  PrintTime: PROCEDURE =
    BEGIN
    text: STRING = [20];
    Time.AppendCurrent[text];
    Put.Text[log, text]
     END;

  PrintPupAddress: PROCEDURE [a: PupAddress] =
    BEGIN
    text: STRING = [20];
    AppendPupAddress[text, a];
    Put.Text[log, text];
    END;

  O: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Number[log, n, [8, FALSE, TRUE, 0]];
    END;

  O2: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Char[log, ' ];
    Put.Number[log, n, [8, FALSE, TRUE, 1]];
    END;

  O3: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Char[log, ' ];
    Put.Number[log, n, [8, FALSE, TRUE, 2]];
    END;

  D: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Number[log, n, [10, FALSE, TRUE, 0]];
    END;

  D2: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Char[log, ' ];
    Put.Number[log, n, [10, FALSE, TRUE, 1]];
    END;

  D3: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Char[log, ' ];
    Put.Number[log, n, [10, FALSE, TRUE, 2]];
    END;

  D4: PROCEDURE [n: CARDINAL] =
    BEGIN
    Put.Char[log, ' ];
    Put.Number[log, n, [10, FALSE, TRUE, 3]];
    END;

  LD2: PROCEDURE [num: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    Put.Char[log, ' ];
    String.AppendLongNumber[s, num, 10];
    THROUGH [s.length..1) DO Put.Char[log, ' ]; ENDLOOP;
    Put.Text[log, s];
    END;

  LD3: PROCEDURE [num: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    Put.Char[log, ' ];
    String.AppendLongNumber[s, num, 10];
    THROUGH [s.length..2) DO Put.Char[log, ' ]; ENDLOOP;
    Put.Text[log, s];
    END;

  LD5: PROCEDURE [num: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    Put.Char[log, ' ];
    String.AppendLongNumber[s, num, 10];
    THROUGH [s.length..4) DO Put.Char[log, ' ]; ENDLOOP;
    Put.Text[log, s];
    END;

  LD7: PROCEDURE [num: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    Put.Char[log, ' ];
    String.AppendLongNumber[s, num, 10];
    THROUGH [s.length..6) DO Put.Char[log, ' ]; ENDLOOP;
    Put.Text[log, s];
    END;

  LD9: PROCEDURE [num: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    String.AppendLongNumber[s, num, 10];
    Put.Char[log, ' ];
    THROUGH [s.length..8) DO Put.Char[log, ' ]; ENDLOOP;
    Put.Text[log, s];
    END;

  MakeConnection: PROCEDURE =
    BEGIN
    pool ← Buffer.MakePool[send: 1, receive: 10];
    soc ← PupSocketMake[PupTypes.fillInSocketID, where, MsToTocks[1000]];
    END;

  KillConnection: PROCEDURE =
    BEGIN
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  indicatorBox: Window.Box = [[25, 10], [16, 16]];
  DisplayBoxes: ToolWindow.DisplayProcType =
    BEGIN
    pattern: ARRAY [0..1] OF ARRAY [0..8) OF WORD;
    left: WORD = 177400B;
    right: WORD = 000377B;
    SELECT indicator FROM
      left => pattern ← [ALL[left], ALL[right]];
      right => pattern ← [ALL[right], ALL[left]];
      off => pattern ← [ALL[0], ALL[0]];
      ENDCASE;
    Display.Bitmap[window, indicatorBox, [@pattern, 0, 0], 16, Display.replaceFlags]
    END;

  SetupBoxes: PROCEDURE = BEGIN indicator ← left; DisplayBoxes[boxes]; END;

  FlipBoxes: PROCEDURE =
    BEGIN
    SELECT indicator FROM
      left => indicator ← right;
      off, right => indicator ← left;
      ENDCASE;
    Display.Invert[boxes, indicatorBox];
    END;

  SetDownBoxes: PROCEDURE =
    BEGIN indicator ← off; Display.White[boxes, indicatorBox]; END;

  MakeBoxesSW: PROCEDURE [window: Window.Handle] =
    BEGIN
    box: Window.Box ← ToolWindow.nullBox;
    box.dims.h ← 36;
    boxes ← ToolWindow.CreateSubwindow[parent: window, display: DisplayBoxes, box: box];
    Tool.AddThisSW[window: window, sw: boxes, swType: vanilla];
    END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    logFileName: STRING = [40];
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    MakeBoxesSW[window];
    Tool.UnusedLogName[logFileName, "LineWatcher.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName, allowTypeIn: FALSE];
    END;

  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 5;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[0] ← FormSW.CommandItem[
      tag: "Stop"L, proc: Stop, place: FormSW.newLine];
    items[1] ← FormSW.CommandItem[tag: "Start"L, proc: Start];
    items[2] ← FormSW.NumberItem[tag: "Seconds"L, value: @seconds, default: 60];
    items[3] ← FormSW.NumberItem[
      tag: "NetNumber"L, value: @netNumber, default: 7, radix: octal];
    items[4] ← FormSW.StringItem[tag: "Target"L, string: @target];
    RETURN[items, TRUE];
    END;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
        BEGIN
	IF target.length = 0 THEN String.AppendString[target, "ME"L];
	PupDefs.PupPackageMake[];
	END;
      new = inactive =>
        BEGIN IF running THEN Off[]; PupDefs.PupPackageDestroy[]; END;
      ENDCASE;
    END;

  ForceOutInfo: PROCEDURE [wh: Window.Handle] =
    BEGIN
    sh: Stream.Handle;
    IF ~FileSW.IsIt[wh] THEN RETURN;
    sh ← FileSW.GetFile[wh].s;
    IF ~MStream.IsIt[sh] THEN RETURN;
    MStream.SetLogReadLength[sh, MStream.GetLength[sh]];
    END;

  Init: PROCEDURE =
    BEGIN
    herald: STRING = [100];
    String.AppendString[herald, "LineWatcher of  "L];
    Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    END;

  Init[];

  END.