-- 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.