-- File: EchoUserTool.mesa, Last Edit: HGM January 30, 1981 2:58 PM
-- Please don't forget to update the herald....
DIRECTORY
Inline USING [BITNOT, BITAND],
Process USING [SetPriority],
Storage USING [Node, String, FreeNodeNil, FreeString],
String USING [AppendString, AppendChar, AppendNumber],
Time USING [AppendCurrent, Current],
Event USING [Item, Reason, AddNotifier],
FormSW USING [
ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, Display,
FindItem, CommandItem, BooleanItem, StringItem, NumberItem],
MsgSW USING [Post],
Put USING [Char, CR, Text, Line, LongDecimal, LongNumber],
Tool USING [
Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW,
AddThisSW],
ToolWindow USING [TransitionProcType, DisplayProcType, CreateSubwindow],
Window USING [Handle, Box, DisplayData, DisplayInvert, DisplayWhite],
StatsDefs USING [StatPrintCurrent, StatReady, StatSince],
PupDefs USING [
PupPackageMake, PupPackageDestroy, GetFreePupBuffer, ReturnFreePupBuffer,
PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, SecondsToTocks,
SetPupContentsWords, GetPupContentsBytes, DataWordsPerPupBuffer,
AppendPupAddress, GetPupAddress, PupNameTrouble],
PupTypes USING [PupAddress, fillInSocketID, echoSoc, maxDataWordsPerGatewayPup];
EchoUserTool: PROGRAM
IMPORTS
Inline, Process, Storage, String, Time, Event, FormSW, MsgSW, Put, Tool,
ToolWindow, Window, StatsDefs, PupDefs =
BEGIN OPEN PupDefs, PupTypes;
msg, form, boxes, log: Window.Handle;
eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom];
-- Be sure to initialize it when it is allocated!!!!
data: POINTER TO Data ← NIL; -- NIL when we are inactive
Data: TYPE = RECORD [
length: CARDINAL ← 100,
fixedLength: BOOLEAN ← FALSE,
where: PupAddress ← [[0], [0], PupTypes.echoSoc],
picks: ARRAY [0..16] OF CARDINAL ← ALL[0],
drops: ARRAY [0..16] OF CARDINAL ← ALL[0],
sent, good, missed, late, bad, horrible, error, words: LONG CARDINAL ← 0,
pleaseStop: BOOLEAN ← FALSE,
alignTheFink: WORD ← NULL,
lowPriority: BOOLEAN ← FALSE,
alignTheFinkAgain: WORD ← NULL,
verbose: BOOLEAN ← FALSE,
alignTheFinkYetAgain: WORD ← NULL,
doStats: BOOLEAN ← FALSE,
alignTheFinkStillAgain: WORD ← NULL,
checkit: BOOLEAN ← TRUE,
echoer: PROCESS ← NULL,
indicator: {left, right, off} ← off,
running: BOOLEAN ← FALSE,
target: STRING ← NULL];
Initialize: PROCEDURE =
BEGIN
[] ← Tool.Create[
name: "Pup Echo User of January 30, 1981"L, makeSWsProc: MakeSWs,
clientTransition: ClientTransition];
Event.AddNotifier[@eventItem];
END;
EchoUserOn: PROCEDURE =
BEGIN
IF data.length > PupDefs.DataWordsPerPupBuffer[] THEN
BEGIN MsgSW.Post[msg, "Length is too long."L]; RETURN; END;
WriteCR[];
WriteCurrentDateAndTime[];
WriteString[" Echoing to "L];
IF ~FindPath[] THEN RETURN;
data.running ← TRUE;
UpdatePicture[];
data.echoer ← FORK DoIt[];
END;
EchoUserOff: PROCEDURE =
BEGIN
IF data = NIL THEN RETURN;
data.pleaseStop ← TRUE;
JOIN data.echoer[];
data.running ← data.pleaseStop ← FALSE;
UpdatePicture[];
END;
UpdatePicture: PROCEDURE =
BEGIN
FormSW.FindItem[form, startIX].flags.invisible ← data.running;
FormSW.FindItem[form, stopIX].flags.invisible ← ~data.running;
FormSW.Display[form];
END;
FindPath: PROCEDURE RETURNS [BOOLEAN] =
BEGIN OPEN data;
WriteString[target];
WriteChar['=];
GetPupAddress[
@where, target !
PupNameTrouble =>
BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
PrintPupAddress[where];
WriteLine["."L];
RETURN[TRUE];
EXITS Trouble => RETURN[FALSE];
END;
ClearCounters: PROCEDURE =
BEGIN OPEN data;
sent ← good ← missed ← late ← bad ← horrible ← error ← words ← 0;
picks ← ALL[0];
drops ← ALL[0];
END;
AddToHist: PROCEDURE [hist: POINTER TO ARRAY [0..16] OF CARDINAL, bits: WORD] =
BEGIN OPEN data;
i: CARDINAL;
IF bits = 0 THEN RETURN;
SELECT bits FROM
1 => i ← 15;
2 => i ← 14;
4 => i ← 13;
10B => i ← 12;
20B => i ← 11;
40B => i ← 10;
100B => i ← 9;
200B => i ← 8;
400B => i ← 7;
1000B => i ← 6;
2000B => i ← 5;
4000B => i ← 4;
10000B => i ← 3;
20000B => i ← 2;
40000B => i ← 1;
100000B => i ← 0;
ENDCASE => i ← 16;
hist[i] ← hist[i] + 1;
END;
PrintSummary: PROCEDURE [howLong: LONG CARDINAL] =
BEGIN OPEN data;
WriteCR[];
WriteLongDecimal[sent];
WriteLine[" packets sent."L];
IF howLong # 0 THEN
BEGIN
WriteLongDecimal[sent/howLong];
WriteLine[" packets per second."L];
WriteLongDecimal[16*words/howLong];
WriteLine[" data bits per second."L];
END;
IF sent # 0 THEN
BEGIN
ShowPercent[good, "good packets received."L];
ShowPercent[missed, "packets missed."L];
ShowPercent[late, "late (or??) packets received."L];
ShowPercent[bad, "bad packets received."L];
ShowPercent[horrible, "packets received with more than 10 words wrong."L];
ShowPercent[error, "error packets received."L];
END;
IF bad # 0 THEN
BEGIN
i: CARDINAL;
x: WORD ← 100000B;
WriteCR[];
WriteLine[" Bit Picked Dropped"L];
FOR i IN [0..16] DO
IF picks[i] # 0 OR drops[i] # 0 THEN
BEGIN
IF i = 16 THEN WriteString[" Other"L] ELSE O6[x];
D8[picks[i]];
D8[drops[i]];
WriteCR[];
END;
x ← x/2;
ENDLOOP;
END;
END;
ShowPercent: PROCEDURE [n: LONG CARDINAL, s: STRING] =
BEGIN OPEN data;
IF n = 0 THEN RETURN;
WriteLongDecimal[n];
WriteString[" ("L];
WriteLongDecimal[n*100/sent];
WriteString["%) "L];
WriteLine[s];
END;
DoIt: PROCEDURE =
BEGIN OPEN data;
k: CARDINAL;
b: PupBuffer;
cycle, maxLength, myLength: CARDINAL;
packetNumber: CARDINAL ← LAST[CARDINAL];
mySoc: PupSocket ← PupSocketMake[fillInSocketID, where, SecondsToTocks[2]];
start, stop: LONG CARDINAL;
maxLength ← MIN[
PupDefs.DataWordsPerPupBuffer[], PupTypes.maxDataWordsPerGatewayPup];
IF lowPriority THEN Process.SetPriority[0];
ClearCounters[];
myLength ← MIN[length, maxLength];
IF fixedLength THEN
BEGIN
WriteString["Packet length is "L];
WriteDecimal[myLength];
WriteLine[" words."L];
END;
SetupBoxes[];
IF doStats THEN StatsDefs.StatReady[];
start ← Time.Current[];
UNTIL pleaseStop DO
-- NB: No check for short buffers
FOR cycle IN [0..256) UNTIL pleaseStop DO
b ← GetFreePupBuffer[];
myLength ← IF fixedLength THEN maxLength ELSE cycle;
FOR k IN [0..myLength) DO b.pupWords[k] ← k + cycle*400B; ENDLOOP;
b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
b.pupType ← echoMe;
SetPupContentsWords[b, myLength];
mySoc.put[b];
sent ← sent + 1;
words ← words + myLength;
UNTIL (b ← mySoc.get[]) = NIL DO
-- Until timeout, or we find the expected one
SELECT TRUE FROM
(b.pupType = error) =>
BEGIN error ← error + 1; WriteCR[]; PrintErrorPup[b]; END;
((b.pupType # iAmEcho) OR (b.pupID.a # packetNumber) OR
(b.pupID.b # packetNumber) OR (GetPupContentsBytes[b] # 2*myLength))
=> BEGIN late ← late + 1; WriteChar['#]; END;
ENDCASE =>
BEGIN
hits: CARDINAL ← 0;
FlipBoxes[];
IF data.checkit THEN
FOR k IN [0..myLength) DO
IF b.pupWords[k] # k + cycle*400B THEN
BEGIN OPEN Inline;
expected, found, picked, dropped: WORD;
IF hits = 0 THEN
BEGIN
WriteCR[];
WriteCurrentDateAndTime[];
WriteString[" Data compare error(s) on packet number "L];
WriteDecimal[packetNumber];
WriteLine["."L];
WriteLine["Idx Expected Found Picked Dropped"L];
END;
expected ← k + cycle*400B;
found ← b.pupWords[k];
picked ← BITAND[found, BITNOT[expected]];
dropped ← BITAND[expected, BITNOT[found]];
AddToHist[@picks, picked];
AddToHist[@drops, dropped];
IF hits < 10 THEN
BEGIN
O3[k];
O9[expected];
O9[found];
O9[picked];
O9[dropped];
WriteCR[];
END;
hits ← hits + 1;
END;
ENDLOOP;
IF hits = 0 THEN good ← good + 1 ELSE bad ← bad + 1;
IF hits = 0 AND verbose THEN WriteChar['!];
IF hits > 10 THEN
BEGIN horrible ← horrible + 1; WriteLine["...."L]; END;
EXIT; -- found the expected one
END;
ReturnFreePupBuffer[b];
ENDLOOP;
IF b # NIL THEN ReturnFreePupBuffer[b]
ELSE BEGIN missed ← missed + 1; WriteChar['?]; END;
ENDLOOP;
IF verbose THEN WriteCR[] ELSE WriteChar['.];
ENDLOOP;
stop ← Time.Current[];
WriteCR[];
PupSocketDestroy[mySoc];
SetDownBoxes[];
IF doStats THEN StatsDefs.StatSince[log];
PrintSummary[stop - start];
END;
-- IO things
WriteChar: PROCEDURE [c: CHARACTER] = BEGIN Put.Char[log, c]; END;
WriteCR: PROCEDURE = BEGIN Put.CR[log]; END;
WriteString: PROCEDURE [s: STRING] = BEGIN Put.Text[log, s]; END;
WriteLine: PROCEDURE [s: STRING] = BEGIN Put.Line[log, s]; END;
WriteLongDecimal: PROCEDURE [n: LONG CARDINAL] =
BEGIN Put.LongDecimal[log, n]; END;
WriteDecimal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 10, 0]; END;
WriteOctal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 8, 0]; END;
WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE
BEGIN
temp: STRING = [25];
String.AppendNumber[temp, n, radix];
THROUGH [temp.length..width) DO WriteChar[' ]; ENDLOOP;
WriteString[temp];
END;
D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END;
O3: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;
O6: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;
O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END;
WriteCurrentDateAndTime: PROCEDURE =
BEGIN time: STRING = [20]; Time.AppendCurrent[time]; WriteString[time]; END;
PrintPupAddress: PROCEDURE [a: PupAddress] =
BEGIN temp: STRING = [40]; AppendPupAddress[temp, a]; WriteString[temp]; END;
PrintErrorPup: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] =
BEGIN
i, len: CARDINAL;
temp: STRING = [100];
String.AppendString[temp, "Error Pup, code="L];
String.AppendNumber[temp, LOOPHOLE[b.errorCode], 8];
String.AppendString[temp, ", from: "L];
AppendPupAddress[temp, b.source];
String.AppendString[temp, ": "L];
len ← PupDefs.GetPupContentsBytes[b];
FOR i IN [0..len - 2*(10 + 1 + 1)) UNTIL temp.length = temp.maxlength DO
String.AppendChar[temp, b.errorText[i]]; ENDLOOP;
WriteLine[temp];
MsgSW.Post[msg, temp];
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 data.indicator FROM
left => pattern ← [ALL[left], ALL[right]];
right => pattern ← [ALL[right], ALL[left]];
off => pattern ← [ALL[0], ALL[0]];
ENDCASE;
Window.DisplayData[window, indicatorBox, @pattern, 1]
END;
SetupBoxes: PROCEDURE = BEGIN data.indicator ← left; DisplayBoxes[boxes]; END;
FlipBoxes: PROCEDURE =
BEGIN
SELECT data.indicator FROM
left => data.indicator ← right;
off, right => data.indicator ← left;
ENDCASE;
Window.DisplayInvert[boxes, indicatorBox];
END;
SetDownBoxes: PROCEDURE =
BEGIN data.indicator ← off; Window.DisplayWhite[boxes, indicatorBox]; END;
MakeBoxesSW: PROCEDURE [window: Window.Handle] =
BEGIN
boxes ← ToolWindow.CreateSubwindow[parent: window, display: DisplayBoxes];
boxes.box.dims.h ← 36;
Tool.AddThisSW[window: window, sw: boxes, swType: vanilla];
END;
Start: FormSW.ProcType = BEGIN EchoUserOn[]; END;
Stop: FormSW.ProcType = BEGIN EchoUserOff[]; END;
Ready: FormSW.ProcType = BEGIN StatsDefs.StatReady[]; END;
Since: FormSW.ProcType = BEGIN StatsDefs.StatSince[log]; END;
Totals: FormSW.ProcType = BEGIN StatsDefs.StatPrintCurrent[log]; 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, "EchoUser.log$"L];
log ← Tool.MakeFileSW[window: window, name: logFileName];
END;
startIX: CARDINAL = 0;
stopIX: CARDINAL = 1;
runningIX: CARDINAL = 2;
MakeForm: FormSW.ClientItemsProcType =
BEGIN
nParams: CARDINAL = 12;
items ← FormSW.AllocateItemDescriptor[nParams];
items[0] ← FormSW.CommandItem[
tag: "Start"L, proc: Start, place: FormSW.newLine];
items[1] ← FormSW.CommandItem[
tag: "Stop"L, proc: Stop, place: FormSW.newLine, invisible: TRUE];
items[2] ← FormSW.BooleanItem[tag: "LowPriority"L, switch: @data.lowPriority];
items[3] ← FormSW.BooleanItem[
tag: "DoStats"L, switch: @data.doStats, place: FormSW.newLine];
items[4] ← FormSW.CommandItem[tag: "Ready"L, proc: Ready];
items[6] ← FormSW.CommandItem[tag: "Recent"L, proc: Since];
items[5] ← FormSW.CommandItem[tag: "Totals"L, proc: Totals];
items[7] ← FormSW.BooleanItem[
tag: "Verbose"L, switch: @data.verbose, place: FormSW.newLine];
items[8] ← FormSW.BooleanItem[tag: "Checkit"L, switch: @data.checkit];
items[9] ← FormSW.BooleanItem[
tag: "FixedLength"L, switch: @data.fixedLength, place: FormSW.newLine];
items[10] ← FormSW.NumberItem[tag: "(Max)Length"L, value: @data.length];
items[11] ← FormSW.StringItem[
tag: "Target"L, string: @data.target, place: FormSW.newLine];
RETURN[items, TRUE];
END;
AlreadyActive: ERROR = CODE;
NotActive: ERROR = CODE;
ClientTransition: ToolWindow.TransitionProcType =
BEGIN
SELECT TRUE FROM
old = inactive =>
BEGIN
IF data # NIL THEN ERROR AlreadyActive;
data ← Storage.Node[SIZE[Data]];
data↑ ← [];
data.target ← Storage.String[20];
String.AppendString[data.target, "ME"L];
PupDefs.PupPackageMake[];
END;
new = inactive =>
BEGIN
IF data = NIL THEN ERROR NotActive;
IF data.running THEN EchoUserOff[];
PupDefs.PupPackageDestroy[];
Storage.FreeString[data.target];
data ← Storage.FreeNodeNil[data];
END;
ENDCASE;
END;
Broom: PROCEDURE [why: Event.Reason] =
BEGIN
SELECT why FROM
makeImage, makeCheck =>
BEGIN
IF data = NIL THEN RETURN;
IF data.running THEN EchoUserOff[];
PupDefs.PupPackageDestroy[];
END;
startImage, restartCheck, continueCheck =>
BEGIN IF data = NIL THEN RETURN; PupDefs.PupPackageMake[]; END;
ENDCASE => NULL;
END;
-- Main Body
Initialize[];
END.