-- File: LineWatcher.mesa - last edit:
-- AOF 4-Feb-88 10:36:47
-- HGM 15-Nov-85 2:12:01
-- Copyright (C) 1983, 1985, 1988 by Xerox Corporation. All rights reserved.
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],
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],
EthernetDriverFriends USING [EtherStatsInfo],
EthernetFormat USING [EtherStatsEntry, ethernetStatsReply, etherVersion],
GateControlDefs USING [pupStatsAck, pupStatsNak, pupStatsSend],
PupRouterDefs USING [GetRoutingTableEntry, maxHop, RoutingTableEntry],
PupDefs USING [
AppendPupAddress, PupPackageMake, PupPackageDestroy, PupBuffer,
PupAddress, EnumeratePupAddresses,
PupNameTrouble, SetPupContentsWords, PupSocket, PupSocketDestroy,
PupSocketMake, MsToTocks, Body,
AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
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,
PupDefs, PupRouterDefs, PupWireFormat =
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: PupDefs.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;
body: PupDefs.Body;
try: CARDINAL ← 0;
data.thisID ← data.thisID + 1;
DO -- until we get the answer
b ← PupDefs.GetBuffer[data.pool, send];
body ← b.pup;
body.pupID ← [data.thisID, data.thisID];
body.pupType ← GateControlDefs.pupStatsSend;
body.pupWords[0] ← data.netNumber;
body.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;
body ← b.pup;
IF body.pupType = error OR body.pupType = GateControlDefs.pupStatsNak
OR body.pupID.b # data.thisID THEN BEGIN PupDefs.ReturnBuffer[b]; LOOP; END;
IF body.pupType # GateControlDefs.pupStatsAck THEN ERROR;
SELECT body.pupWords[0] FROM
SlaFormat.slaStatsReply => {
SELECT body.pupWords[1] FROM
SlaFormat.slaVersion => CopySlaData[data, b];
SlaFormat.slaVersion+1 => CopyPhoneData[data, b];
ENDCASE => ERROR;
EXIT; };
EthernetFormat.ethernetStatsReply => {
SELECT body.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];
PupDefs.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: 0
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 ← PupDefs.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];
PupDefs.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.