-- Copyright (C) 1983, 1985 by Xerox Corporation. All rights reserved. -- LineWatcher.mesa, HGM, 15-Nov-85 2:12:01 DIRECTORY Context USING [Create, Data, Find, Type, UniqueType], Display USING [Bitmap, Invert, replaceFlags, White], Exec USING [AddCommand, ExecProc, FreeTokenString, GetToken], Format USING [], -- Needed by Put.Number FormSW USING [ ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem, StringItem, NumberItem], FileSW USING [GetFile, IsIt, SetFile], Heap USING [systemZone], MFile USING [Acquire, Error, GetLength, Handle, Release, SetTimes], MsgSW USING [Post], MStream USING [GetFile, GetLength, Handle, IsIt, Log, SetAccess, SetLength, SetLogReadLength], Process USING [Detach, MsecToTicks, Pause, Ticks], Put USING [Char, CR, Line, Number, Text], Runtime USING [GetBcdTime], Stream USING [GetByteProcedure, GetWordProcedure, GetProcedure, Handle], String USING [AppendLongNumber, AppendNumber, AppendString, Equivalent], System USING [ AdjustGreenwichMeanTime, GreenwichMeanTime, GetGreenwichMeanTime, nullHostNumber], Time USING [Append, AppendCurrent, Unpack], Tool USING [ Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW, AddThisSW], ToolWindow USING [CreateSubwindow, DisplayProcType, nullBox, SetTinyName, TransitionProcType], Window USING [Handle, Box], Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer], EthernetDriverFriends USING [EtherStatsInfo], EthernetFormat USING [EtherStatsEntry, ethernetStatsReply, etherVersion], GateControlDefs USING [pupStatsAck, pupStatsNak, pupStatsSend], PhoneNetFriends USING [PhoneNetInfo], PhoneNetExtras USING [pupDupsFiltered, leafDupsFiltered, nsDupsFiltered], PupRouterDefs USING [GetRoutingTableEntry, maxHop, RoutingTableEntry], PupDefs USING [ AppendPupAddress, PupPackageMake, PupPackageDestroy, PupBuffer, PupAddress, EnumeratePupAddresses, PupNameTrouble, SetPupContentsWords, PupSocket, PupSocketDestroy, PupSocketMake, MsToTocks], PupTypes USING [fillInSocketID, statSoc], PupWireFormat USING [BcplToMesaLongNumber], SlaFormat USING [LineState, RoutingTableEntry, SlaStatsEntry, slaStatsReply, slaVersion], Driver; LineWatcher: MONITOR IMPORTS Context, Display, Exec, FileSW, FormSW, Heap, MFile, MsgSW, MStream, Process, Put, Runtime, String, System, Time, Tool, ToolWindow, Buffer, PupDefs, PupRouterDefs, PupWireFormat SHARES Driver = BEGIN contextType: PUBLIC Context.Type ← Context.UniqueType[]; Data: TYPE = LONG POINTER TO DataRecord; DataRecord: TYPE = RECORD [ tool, msg, form, boxes, log: Window.Handle ← NIL, running: BOOLEAN ← FALSE, pleaseStop: BOOLEAN ← FALSE, indicator: {left, right, off} ← off, seconds: CARDINAL ← 300, target: LONG STRING ← Heap.systemZone.NEW[StringBody[30]], netNumber: CARDINAL ← 0, where: PupDefs.PupAddress ← [[0], [0], PupTypes.statSoc], pool: Buffer.AccessHandle ← NIL, soc: PupDefs.PupSocket ← NIL, oldInfo: LONG POINTER TO LineInfo ← Heap.systemZone.NEW[LineInfo], newInfo: LONG POINTER TO LineInfo ← Heap.systemZone.NEW[LineInfo], new: BOOL ← FALSE, -- Old GateStats ignores new querrys cyclesPerSecond: LONG CARDINAL ← 0, thisID: CARDINAL ← 0 ]; overheadPerPacket: CARDINAL = 4; -- Flag, CRC, CRC, flag -- Arg. Can't distinguish 3MB nets from 10MB nets ethernetOneOverheadPerPacket: CARDINAL = 2; -- CRC, CRC (and 1 preamble bit) ethernetOverheadPerPacket: CARDINAL = 8 + 4 + 12; -- preamble, CRC, gap (9.6 microseconds) Start: FormSW.ProcType = BEGIN data: Data = Context.Find[contextType, sw]; oldLogFileName: LONG STRING ← FileSW.GetFile[data.log].name; newLogFileName: STRING = [100]; netNumberString: STRING = [10]; IF data.running THEN { MsgSW.Post[data.msg, "Somebody is already running..."L]; RETURN; }; String.AppendNumber[netNumberString, data.netNumber, 8]; String.AppendString[newLogFileName, data.target]; String.AppendString[newLogFileName, "-"L]; String.AppendString[newLogFileName, netNumberString]; String.AppendString[newLogFileName, ".data"L]; ToolWindow.SetTinyName[data.tool, data.target, netNumberString]; 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[data.log, newLogFileName, sh]; IF fh = NIL THEN Put.Line[data.log, -- New File: Insert header for ChartPlot (and people) " * Packets Bytes Bits/Sec Errors Date Time Sec CPU P-Sent P-Recv B-Sent B-Recv Sent Recv Dup Cong Miss CRC "L]; END; Put.CR[data.log]; Put.Text[data.log, "* Watching net "L]; O[data.log, data.netNumber]; Put.Text[data.log, " on "L]; IF ~FindPath[data] THEN RETURN; data.running ← TRUE; Process.Detach[FORK Watch[data, data.seconds]]; END; Stop: FormSW.ProcType = BEGIN data: Data = Context.Find[contextType, sw]; Off[data]; END; Off: PROCEDURE [data: Data] = BEGIN IF ~data.running THEN RETURN; data.pleaseStop ← TRUE; WHILE data.running DO Process.Pause[1]; ENDLOOP; data.pleaseStop ← FALSE; END; FindPath: PROCEDURE [data: Data] RETURNS [BOOLEAN] = BEGIN Put.Text[data.log, data.target]; Put.Char[data.log, '=]; MyGetPupAddress[ @data.where, data.target ! PupDefs.PupNameTrouble => BEGIN MsgSW.Post[data.msg, e]; Put.Line[data.log, e]; GOTO Trouble; END]; PrintPupAddress[data.log, data.where]; Put.Line[data.log, "."L]; RETURN[TRUE]; EXITS Trouble => RETURN[FALSE]; END; MyGetPupAddress: PROCEDURE [ him: LONG POINTER TO PupDefs.PupAddress, name: LONG STRING] = BEGIN SkipFlakeyNets: PROCEDURE [her: PupDefs.PupAddress] RETURNS [BOOLEAN] = BEGIN rte: PupRouterDefs.RoutingTableEntry; IF FlakeyNet[her] THEN RETURN[FALSE]; rte ← PupRouterDefs.GetRoutingTableEntry[her.net]; IF rte = NIL OR rte.context = NIL OR rte.hop > PupRouterDefs.maxHop THEN RETURN[FALSE]; him.net ← her.net; him.host ← her.host; IF her.socket # [0, 0] THEN him.socket ← her.socket; RETURN[TRUE]; END; IF PupDefs.EnumeratePupAddresses[name, SkipFlakeyNets] THEN RETURN; ERROR PupDefs.PupNameTrouble["No Route to that Host"L, noRoute]; END; FlakeyNet: PROCEDURE [him: PupDefs.PupAddress] RETURNS [BOOLEAN] = BEGIN IF him.net > 277B THEN RETURN[TRUE]; IF him.net = 106B THEN RETURN[TRUE]; -- Aylesbury/Welwyn-Link IF him.net = 141B THEN RETURN[TRUE]; -- CP10/DLOS-Link IF him.net = 202B THEN RETURN[TRUE]; -- FortXerox/CP10-Link RETURN[FALSE]; END; Watch: PROCEDURE [data: Data, seconds: CARDINAL] = BEGIN startTime, stopTime, targetTime: System.GreenwichMeanTime; actual: LONG CARDINAL; pause: Process.Ticks; MakeConnection[data]; SetupBoxes[data]; pause ← Process.MsecToTicks[500]; UNTIL data.pleaseStop DO -- Wait until even multiple of seconds targetTime ← System.GetGreenwichMeanTime[]; IF (targetTime MOD seconds) = 0 THEN EXIT; Process.Pause[pause]; ENDLOOP; startTime ← targetTime; GetInfo[data]; data.oldInfo↑ ← data.newInfo↑; Put.CR[data.log]; UNTIL data.pleaseStop DO WHILE System.GetGreenwichMeanTime[] - targetTime >= data.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, data.seconds]; ENDLOOP; UNTIL data.pleaseStop OR (System.GetGreenwichMeanTime[] - targetTime) >= data.seconds DO Process.Pause[pause]; ENDLOOP; IF data.pleaseStop THEN EXIT; GetInfo[data]; stopTime ← System.GetGreenwichMeanTime[]; actual ← stopTime - startTime; IF data.pleaseStop THEN EXIT; PrintInfo[data, actual]; data.oldInfo↑ ← data.newInfo↑; startTime ← stopTime; targetTime ← System.AdjustGreenwichMeanTime[targetTime, seconds]; ENDLOOP; SetDownBoxes[data]; KillConnection[data]; data.running ← FALSE; END; Cpu: TYPE = RECORD [cycles, cyclesPerSecond: LONG CARDINAL]; State: TYPE = {down, up, looped, unknown}; LineInfo: TYPE = RECORD [ cycles: LONG CARDINAL, packetsSent: LONG CARDINAL, packetsRecv: LONG CARDINAL, bytesSent: LONG CARDINAL, bytesRecv: LONG CARDINAL, filtered: LONG CARDINAL, congestion: LONG CARDINAL, missed: LONG CARDINAL, badCrc: LONG CARDINAL, state: State]; GetInfo: PROCEDURE [data:Data] = BEGIN b: PupDefs.PupBuffer; try: CARDINAL ← 0; data.thisID ← data.thisID + 1; DO -- until we get the answer b ← Buffer.GetBuffer[pup, data.pool, send]; b.pup.pupID ← [data.thisID, data.thisID]; b.pup.pupType ← GateControlDefs.pupStatsSend; b.pup.pupWords[0] ← data.netNumber; b.pup.pupWords[1] ← 1; PupDefs.SetPupContentsWords[b, 2]; IF ~data.new AND try > 10 THEN PupDefs.SetPupContentsWords[b, 1]; data.soc.put[b]; FlipBoxes[data]; try ← try + 1; DO b ← data.soc.get[]; IF b = NIL THEN EXIT; IF b.pup.pupType = error OR b.pup.pupType = GateControlDefs.pupStatsNak OR b.pup.pupID.b # data.thisID THEN BEGIN Buffer.ReturnBuffer[b]; LOOP; END; IF b.pup.pupType # GateControlDefs.pupStatsAck THEN ERROR; SELECT b.pup.pupWords[0] FROM SlaFormat.slaStatsReply => { SELECT b.pup.pupWords[1] FROM SlaFormat.slaVersion => CopySlaData[data, b]; SlaFormat.slaVersion+1 => CopyPhoneData[data, b]; ENDCASE => ERROR; EXIT; }; EthernetFormat.ethernetStatsReply => { SELECT b.pup.pupWords[1] FROM EthernetFormat.etherVersion => CopyOldEtherData[data, b]; EthernetFormat.etherVersion+1 => CopyNewEtherData[data, b]; ENDCASE => ERROR; EXIT; }; ENDCASE => ERROR; ENDLOOP; IF b # NIL THEN EXIT; IF data.pleaseStop THEN RETURN; LOOP; ENDLOOP; FlipBoxes[data]; Buffer.ReturnBuffer[b]; END; CopyPhoneData: PROCEDURE [data: Data, b: PupDefs.PupBuffer] = BEGIN cpu: LONG POINTER TO Cpu ← LOOPHOLE[@b.pup.pupWords[2]]; psi: LONG POINTER TO PhoneNetFriends.PhoneNetInfo ← LOOPHOLE[cpu + SIZE[Cpu]]; state: State; data.new ← TRUE; data.cyclesPerSecond ← cpu.cyclesPerSecond; SELECT psi.remoteHostNumber FROM System.nullHostNumber => state ← down; -- don't know his NS host number to check for looped ENDCASE => state ← up; data.newInfo↑ ← [ cycles: cpu.cycles, packetsSent: psi.stats[pktsSent], packetsRecv: psi.stats[pktsReceived], bytesSent: psi.stats[bytesSent], bytesRecv: psi.stats[bytesReceived], filtered: psi.stats[PhoneNetExtras.pupDupsFiltered] + psi.stats[PhoneNetExtras.leafDupsFiltered] + psi.stats[PhoneNetExtras.nsDupsFiltered], congestion: psi.stats[congestion] + psi.stats[connTooGreedy], missed: psi.stats[rcvErrorNoGet], badCrc: psi.stats[rcvErrorCRC], state: state ]; END; CopySlaData: PROCEDURE [data: Data, b: PupDefs.PupBuffer] = BEGIN lastHost: CARDINAL ← b.pup.pupWords[2]; rte: LONG POINTER TO SlaFormat.RoutingTableEntry ← LOOPHOLE[@b.pup.pupWords[3] + lastHost*SIZE[SlaFormat.RoutingTableEntry]]; sse: LONG POINTER TO SlaFormat.SlaStatsEntry ← LOOPHOLE[rte+1]; state: State; SELECT sse.state FROM loopedBack => state ← looped; up => state ← up; down => state ← down; ENDCASE => state ← unknown; data.newInfo↑ ← [ cycles: 0, packetsSent: PupWireFormat.BcplToMesaLongNumber[sse.packetsSent], packetsRecv: PupWireFormat.BcplToMesaLongNumber[sse.packetsRecv], bytesSent: PupWireFormat.BcplToMesaLongNumber[sse.bytesSent], bytesRecv: PupWireFormat.BcplToMesaLongNumber[sse.bytesRecv], filtered: 0, congestion: 0, missed: sse.syncErrors, badCrc: sse.badCrc, state: state ]; END; CopyNewEtherData: PROCEDURE [data: Data, b: PupDefs.PupBuffer] = BEGIN cpu: LONG POINTER TO Cpu ← LOOPHOLE[@b.pup.pupWords[2]]; esi: LONG POINTER TO EthernetDriverFriends.EtherStatsInfo ← LOOPHOLE[cpu + SIZE[Cpu]]; data.new ← TRUE; data.cyclesPerSecond ← cpu.cyclesPerSecond; data.newInfo↑ ← [ cycles: cpu.cycles, packetsSent: esi.packetsSent, packetsRecv: esi.packetsRecv, bytesSent: 2*esi.wordsSent, bytesRecv: 2*esi.wordsRecv, filtered: 0, congestion: 0, missed: esi.packetsMissed, badCrc: esi.badCrc + esi.crcAndBadAlignment, state: up ]; END; CopyOldEtherData: PROCEDURE [data: Data, b: PupDefs.PupBuffer] = BEGIN ese: LONG POINTER TO EthernetFormat.EtherStatsEntry ← LOOPHOLE[@b.pup.pupWords[1]]; data.newInfo↑ ← [ cycles: 0, packetsSent: PupWireFormat.BcplToMesaLongNumber[ese.packetsSent], packetsRecv: PupWireFormat.BcplToMesaLongNumber[ese.packetsRecv], bytesSent: 0, bytesRecv: 0, filtered: 0, congestion: 0, missed: PupWireFormat.BcplToMesaLongNumber[ese.inputOff], badCrc: PupWireFormat.BcplToMesaLongNumber[ese.badRecvStatus], state: up ]; END; PrintInfo: PROCEDURE [data: Data, actual: LONG CARDINAL] = BEGIN restarted: BOOL ← FALSE; temp: LineInfo ← [ cycles: data.newInfo.cycles - data.oldInfo.cycles, packetsSent: data.newInfo.packetsSent - data.oldInfo.packetsSent, packetsRecv: data.newInfo.packetsRecv - data.oldInfo.packetsRecv, bytesSent: data.newInfo.bytesSent - data.oldInfo.bytesSent, bytesRecv: data.newInfo.bytesRecv - data.oldInfo.bytesRecv, filtered: data.newInfo.filtered - data.oldInfo.filtered, congestion: data.newInfo.congestion - data.oldInfo.congestion, missed: data.newInfo.missed - data.oldInfo.missed, badCrc: data.newInfo.badCrc - data.oldInfo.badCrc, state: up ]; PrintTime[data.log]; Put.Text[data.log, " "L]; IF LOOPHOLE[temp.packetsSent, LONG INTEGER] < 0 THEN { restarted ← TRUE; temp ← data.newInfo↑; }; LD5[data.log, actual]; IF data.cyclesPerSecond = 0 THEN Put.Text[data.log, " 0"L] ELSE { used: LONG CARDINAL ← temp.cycles*100/data.cyclesPerSecond/actual; IF used > 100 THEN used ← 100; LD5[data.log, 100-used]; }; LD7[data.log, temp.packetsSent]; LD7[data.log, temp.packetsRecv]; LD9[data.log, temp.bytesSent]; LD9[data.log, temp.bytesRecv]; LD7[data.log, (temp.bytesSent + overheadPerPacket*temp.packetsSent)*8/actual]; LD7[data.log, (temp.bytesRecv + overheadPerPacket*temp.packetsRecv)*8/actual]; IF (temp.filtered + temp.congestion + temp.missed + temp.badCrc) > 0 OR restarted THEN { LD5[data.log, temp.filtered]; LD5[data.log, temp.congestion]; LD5[data.log, temp.missed]; LD5[data.log, temp.badCrc]; IF restarted THEN Put.Text[data.log, " *R"L]; }; Put.CR[data.log]; ForceOutInfo[data.log]; END; PrintTime: PROCEDURE [log: Window.Handle] = BEGIN text: STRING = [20]; Time.AppendCurrent[text]; Put.Text[log, text] END; PrintPupAddress: PROCEDURE [log: Window.Handle, a: PupDefs.PupAddress] = BEGIN text: STRING = [20]; PupDefs.AppendPupAddress[text, a]; Put.Text[log, text]; END; O: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Number[log, n, [8, FALSE, TRUE, 0]]; END; O2: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Char[log, ' ]; Put.Number[log, n, [8, FALSE, TRUE, 1]]; END; O3: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Char[log, ' ]; Put.Number[log, n, [8, FALSE, TRUE, 2]]; END; D: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Number[log, n, [10, FALSE, TRUE, 0]]; END; D2: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Char[log, ' ]; Put.Number[log, n, [10, FALSE, TRUE, 1]]; END; D3: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Char[log, ' ]; Put.Number[log, n, [10, FALSE, TRUE, 2]]; END; D4: PROCEDURE [log: Window.Handle, n: CARDINAL] = BEGIN Put.Char[log, ' ]; Put.Number[log, n, [10, FALSE, TRUE, 3]]; END; LD2: PROCEDURE [log: Window.Handle, n: LONG INTEGER] = BEGIN s: STRING = [20]; Put.Char[log, ' ]; String.AppendLongNumber[s, n, 10]; THROUGH [s.length..1) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, s]; END; LD3: PROCEDURE [log: Window.Handle, n: LONG INTEGER] = BEGIN s: STRING = [20]; Put.Char[log, ' ]; String.AppendLongNumber[s, n, 10]; THROUGH [s.length..2) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, s]; END; LD5: PROCEDURE [log: Window.Handle, n: LONG INTEGER] = BEGIN s: STRING = [20]; Put.Char[log, ' ]; String.AppendLongNumber[s, n, 10]; THROUGH [s.length..4) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, s]; END; LD7: PROCEDURE [log: Window.Handle, n: LONG INTEGER] = BEGIN s: STRING = [20]; Put.Char[log, ' ]; String.AppendLongNumber[s, n, 10]; THROUGH [s.length..6) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, s]; END; LD9: PROCEDURE [log: Window.Handle, n: LONG INTEGER] = BEGIN s: STRING = [20]; String.AppendLongNumber[s, n, 10]; Put.Char[log, ' ]; THROUGH [s.length..8) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, s]; END; MakeConnection: PROCEDURE [data: Data] = BEGIN data.pool ← Buffer.MakePool[send: 1, receive: 10]; data.soc ← PupDefs.PupSocketMake[ PupTypes.fillInSocketID, data.where, PupDefs.MsToTocks[5000]]; END; KillConnection: PROCEDURE [data: Data] = BEGIN PupDefs.PupSocketDestroy[data.soc]; Buffer.DestroyPool[data.pool]; END; indicatorBox: Window.Box = [[25, 10], [16, 16]]; DisplayBoxes: ToolWindow.DisplayProcType = BEGIN data: Data = Context.Find[contextType, window]; pattern: ARRAY [0..1] OF ARRAY [0..8) OF WORD; left: WORD = 177400B; right: WORD = 000377B; SELECT data.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 [data: Data] = BEGIN data.indicator ← left; DisplayBoxes[data.boxes]; END; FlipBoxes: PROCEDURE [data: Data] = BEGIN SELECT data.indicator FROM left => data.indicator ← right; off, right => data.indicator ← left; ENDCASE; Display.Invert[data.boxes, indicatorBox]; END; SetDownBoxes: PROCEDURE [data: Data] = BEGIN data.indicator ← off; Display.White[data.boxes, indicatorBox]; END; MakeBoxesSW: PROCEDURE [data: Data, window: Window.Handle] = BEGIN box: Window.Box ← ToolWindow.nullBox; box.dims.h ← 36; data.boxes ← ToolWindow.CreateSubwindow[ parent: window, display: DisplayBoxes, box: box]; Context.Create[type: contextType, data: data, proc: DropIt, window: data.boxes]; Tool.AddThisSW[window: window, sw: data.boxes, swType: vanilla]; END; MakeSWs: Tool.MakeSWsProc = BEGIN logFileName: STRING = [40]; data: Data ← Context.Find[contextType, window]; IF data = NIL THEN { data ← SIGNAL FindMyContext[]; Context.Create[type: contextType, data: data, proc: DropIt, window: window]; }; data.msg ← Tool.MakeMsgSW[window: window, lines: 5]; BEGIN ENABLE FindMyContext => RESUME[data]; data.form ← Tool.MakeFormSW[window: window, formProc: MakeForm]; END; Context.Create[type: contextType, data: data, proc: DropIt, window: data.form]; MakeBoxesSW[data, window]; Tool.UnusedLogName[logFileName, "LineWatcher.log$"L]; data.log ← Tool.MakeFileSW[window: window, name: logFileName, allowTypeIn: FALSE]; END; MakeForm: FormSW.ClientItemsProcType = BEGIN nParams: CARDINAL = 5; data: Data = SIGNAL FindMyContext[]; 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: @data.seconds, default: 60]; items[3] ← FormSW.NumberItem[ tag: "NetNumber"L, value: @data.netNumber, default: 7, radix: octal]; items[4] ← FormSW.StringItem[tag: "Target"L, string: @data.target]; RETURN[items, TRUE]; END; ClientTransition: ToolWindow.TransitionProcType = BEGIN data: Data ← Context.Find[contextType, window]; IF data = NIL THEN { data ← SIGNAL FindMyContext[]; Context.Create[type: contextType, data: data, proc: DropIt, window: window]; }; SELECT TRUE FROM old = inactive => BEGIN IF data.target.length = 0 THEN String.AppendString[data.target, "ME"L]; [] ← PupDefs.PupPackageMake[]; END; new = inactive => BEGIN IF data.running THEN Off[data]; PupDefs.PupPackageDestroy[]; END; ENDCASE; END; ForceOutInfo: ENTRY PROCEDURE [wh: Window.Handle] = -- Beware: This gets an SV lockup and then runs out of VM for Resident Memory BEGIN sh: Stream.Handle; IF ~FileSW.IsIt[wh] THEN RETURN; sh ← FileSW.GetFile[wh].s; IF ~MStream.IsIt[sh] THEN RETURN; Process.Pause[Process.MsecToTicks[5000]]; -- Let others play before we thrash MStream.SetLogReadLength[sh, MStream.GetLength[sh]]; MFile.SetTimes[file: MStream.GetFile[sh], create: System.GetGreenwichMeanTime[]]; END; FindMyContext: SIGNAL RETURNS [Data] = CODE; LineWatcherCommand: Exec.ExecProc = BEGIN tryIt: BOOLEAN ← FALSE; token: LONG STRING ← NIL; data: Data = Heap.systemZone.NEW[DataRecord ← []]; herald: STRING = [100]; String.AppendString[herald, "LineWatcher of "L]; Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]]; [token, ] ← h.GetToken[]; IF token # NIL THEN { data.netNumber ← 0; FOR i: CARDINAL IN [0..token.length) DO c: CHAR = token[i]; IF ~(c IN ['0..'7]) THEN EXIT; data.netNumber ← data.netNumber*10B + c-'0; ENDLOOP; token ← Exec.FreeTokenString[token]; }; [token, ] ← h.GetToken[]; IF token # NIL THEN { tryIt ← TRUE; String.AppendString[data.target, token]; token ← Exec.FreeTokenString[token]; }; BEGIN ENABLE FindMyContext => RESUME[data]; data.tool ← Tool.Create[ name: herald, cmSection: "LineWatcher"L, makeSWsProc: MakeSWs, clientTransition: ClientTransition]; END; IF Context.Find[contextType, data.tool] = NIL THEN Context.Create[type: contextType, data: data, proc: DropIt, window: data.tool]; IF tryIt THEN Start[data.form]; END; DropIt: PROCEDURE [Data, Window.Handle] = BEGIN END; Exec.AddCommand["LineWatcher.~"L, LineWatcherCommand, NIL, NIL]; END.