PupEchoTool.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Hal Murray, December 10, 1986 10:57:11 pm PST
DIRECTORY
Atom USING [GetPName],
Basics USING [bytesPerWord, DivMod],
BasicTime USING [GetClockPulses, GMT, Now, Period, Pulses, PulsesToMicroseconds],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Create],
Convert USING [Error, IntFromRope, RopeFromInt],
CommDriver USING [GetNetworkChain, Network],
Endian USING [bytesPerFWord, FFromCard, FFromInt, FWORD, IntFromF],
EthernetDriverStats USING [EtherStats, MaxTries],
IO USING [Close, Flush, PutF, PutRope, STREAM, Value],
Labels USING [Create],
Loader USING [BCDBuildTime],
PrincOpsUtils USING [LongCopy],
Process USING [MsecToTicks, Pause, priorityForeground, SetPriority, Ticks, TicksToMsec],
Pup USING [Address, nullAddress],
PupBuffer USING [Buffer, RoutingInfoResponse, echoStatsVersion, maxDataBytes],
PupHop USING [GetRouting, Hop, RoutingTableEntry, unreachable],
PupName USING [AddressToRope, Error, HisName, NameLookup],
PupSocket USING [AllocBuffer, CreateEphemeral, CreateServer, Destroy, ExtractErrorRope, FreeBuffer, Get, GetUniqueID, GetUserBytes, Kick, Put, ReturnToSender, SetNoErrors, SetUserBytes, SetUserSize, Socket, waitForever],
PupSocketBackdoor USING [PutAgain, PutFirst, Resend, ReturnToSenderNoFree, SetDirectReceive, UseNormalPath],
PupType USING [echoStatsRequest, echoStatsReply],
PupWKS USING [echo],
Rope USING [ROPE],
Rules USING [Create],
TypeScript USING [ChangeLooks, Create],
VFonts USING [FontHeight, StringWidth],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [AddProp, ComputeColumn, CreateViewer, FetchProp, MoveViewer, OpenIcon, SetOpenHeight],
ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection];
PupEchoTool: CEDAR MONITOR
IMPORTS
Atom, Basics, BasicTime, Buttons, Commander, CommDriver, Containers, Convert, Endian, IO, Labels, Loader, PrincOpsUtils, Process, PupHop, PupName, PupSocket, PupSocketBackdoor, Rules, TypeScript, VFonts, ViewerEvents, ViewerIO, ViewerOps, ViewerTools = {
BYTE: TYPE = [0..100H);
Buffer: TYPE = PupBuffer.Buffer;
Socket: TYPE = PupSocket.Socket;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Viewer: TYPE = ViewerClasses.Viewer;
bytesPerFWord: NAT = Endian.bytesPerFWord;
bytesPerWord: NAT = Basics.bytesPerWord;
Viewer layout parameters
buttonHeight: INT ← VFonts.FontHeight[] + 3;
buttonWidth: INT ← VFonts.StringWidth["Route"] + 2*3;
Server data (global)
Note: This server goes in on top of the builtin one
pupsEchoed: INT ← 0;
serverLog: STREAMNIL;
pleaseStopServer: BOOLEANFALSE;
server: PROCESSNIL;
showDollar: REF BOOLNEW [BOOLFALSE];
serverSoc: Socket ← NIL;
ClientData: TYPE = REF ClientDataRep;
ClientDataRep: TYPE = RECORD [
log: STREAMNIL,
in: STREAMNIL,
pleaseStop: BOOLEANFALSE,
user: PROCESSNIL,
where: Pup.Address ← Pup.nullAddress,
echo: REF BOOLNEW [BOOLTRUE],
dot: REF BOOLNEW [BOOLTRUE],
miss: REF BOOLNEW [BOOLTRUE],
late: REF BOOLNEW [BOOLTRUE],
dataChecking: REF BOOLNEW [BOOLTRUE],
fixedLength: REF BOOLNEW [BOOLFALSE],
target, length, timeout, dally: Viewer ← NIL,
good, error, funny: INT ← 0,
minDelay, maxDelay: INT ← 0,
delayHist: ARRAY DelayRange OF INTALL[0] ];
DelayRange: TYPE = {
d1, d1a, d2, d2a, d3, d3a, d4, d4a, d5, d5a, d6, d6a, d7, d7a, d8, d8a, d9, d9a,
d10, d14, d20, d28, d50, d70, d100, d140, d200, d280, d500, d700,
d1000, d1400, d2000, d2800, d5000, d7000, d10000, d14000, d20000, d28000, more};
delayTime: ARRAY DelayRange OF INT = [
d1: 1000, d1a: 1500, d2: 2000, d2a: 2500,
d3: 3000, d3a: 3500, d4: 4000, d4a: 4500,
d5: 5000, d5a: 5500, d6: 6000, d6a: 6500,
d7: 7000, d7a: 7500, d8: 8000, d8a: 8500,
d9: 9000, d9a: 9500,
d10: 10000, d14: 14000, d20: 20000, d28: 28000, d50: 50000, d70: 70000,
d100: 100000, d140: 140000, d200: 200000, d280: 280000, d500: 500000, d700: 700000,
d1000: 1000000, d1400: 1400000, d2000: 2000000, d2800: 2800000, d5000: 5000000, d7000: 7000000,
d10000: 10000000, d14000: 14000000, d20000: 20000000, d28000: 28000000, more: LAST[INT]];
global: ClientData ← NIL; -- debugging
Create: Commander.CommandProc = {
viewer, buttons, log: Viewer ← NIL;
data: ClientData ← NEW[ClientDataRep ← []];
global ← data;
viewer ← ViewerOps.CreateViewer [
flavor: $Container,
info: [name: "PupEchoTool", column: right, iconic: TRUE, scrollable: FALSE]];
[] ← ViewerEvents.RegisterEventProc[Poof, destroy, viewer, TRUE];
ViewerOps.AddProp[viewer, $PupEchoTool, data];
log ← TypeScript.Create[
[name: "PupEchoTool.log", wy: 27+4, parent: viewer, border: FALSE], FALSE];
[data.in, data.log] ← ViewerIO.CreateViewerStreams [
name: "PupEchoTool.log", backingFile: "PupEchoTool.log", viewer: log, editedStream: FALSE];
IF serverLog = NIL THEN { serverLog ← data.log; StartServer[]; };
Containers.ChildXBound[viewer, log];
Containers.ChildYBound[viewer, log];
CreateButtons[data, viewer, log];
TypeScript.ChangeLooks[log, 'f];
IO.PutF[data.log, "PupEchoTool of %G.\n", [time[Loader.BCDBuildTime[Create]]]];
ViewerOps.OpenIcon[viewer];
};
CreateButtons: ENTRY PROC[data: ClientData, parent, log: Viewer] = {
child: Viewer ← NIL;
kids: Viewer = Containers.Create[
info: [parent: parent, border: FALSE, scrollable: FALSE, wx: 0, wy: -9999, ww: 9999, wh: 0] ];
Containers.ChildXBound[parent, kids];
child ← MakeBool[name: "!", init: data.echo, clientData: data, parent: kids, x: 2, y: 1];
child ← MakeBool[name: ".", init: data.dot, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy];
child ← MakeBool[name: "?", init: data.miss, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy];
child ← MakeBool[name: "#", init: data.late, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy];
child ← MakeBool[name: "$", init: showDollar, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy];
child ← MakeBool[name: "D-ck", init: data.dataChecking, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy];
child ← MakeBool[name: "FixedLength", init: data.fixedLength, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy];
child ← data.length ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Length:",
data: Convert.RopeFromInt[PupBuffer.maxDataBytes],
prev: data.length,
width: VFonts.StringWidth["10000"],
newline: FALSE ];
child ← data.timeout ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Timeout:",
data: "2500",
prev: data.timeout,
width: VFonts.StringWidth["10000"],
newline: FALSE ];
child ← data.dally ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Dally:",
data: "0",
prev: data.dally,
width: VFonts.StringWidth["10000"],
newline: FALSE ];
child ← MakeRule[kids, child];
child ← data.target ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Target:",
data: "Target",
width: VFonts.StringWidth["Big long name ................................."],
prev: data.target ];
child ← MakeRule[kids, child];
child ← MakeLabel[kids, child, "What: "];
child ← MakeButton[kids, child, data, "Poke", PokeProc];
child ← MakeButton[kids, child, data, "Start", StartProc];
child ← MakeButton[kids, child, data, "Blast", BlastProc, TRUE];
child ← MakeButton[kids, child, data, "Hist", HistProc];
child ← MakeButton[kids, child, data, "Stop", StopProc];
child ← MakeButton[kids, child, data, "Route", RouteProc];
child ← MakeButton[kids, child, data, "Stats", StatsProc];
child ← MakeRule[kids, child];
{
kidsY: INTEGER = 2;
kidsH: INTEGER = child.wy + child.wh + 2;
ViewerOps.MoveViewer[viewer: log, x: 0, y: kidsY + kidsH, w: log.ww, h: parent.ch - (kids.wy + kidsH), paint: FALSE];
ViewerOps.SetOpenHeight[parent, kidsY + kidsH + 12 * buttonHeight];
IF ~parent.iconic THEN ViewerOps.ComputeColumn[parent.column];
ViewerOps.MoveViewer[viewer: kids, x: kids.wx, y: kidsY, w: kids.ww, h: kidsH]; };
};
Poof: ViewerEvents.EventProc = {
[viewer: ViewerClasses.Viewer, event: ViewerEvent, before: BOOL]
RETURNS[abort: BOOLFALSE]
data: ClientData ← NARROW[ViewerOps.FetchProp[viewer, $PupEchoTool]];
IF event # destroy OR before # TRUE THEN ERROR;
Stop[data];
IF data.log = serverLog THEN { StopServer[]; serverLog ← NIL; };
IO.Close[data.log];
IO.Close[data.in];
};
StartServer: ENTRY PROC = {
IF server # NIL THEN RETURN;
pleaseStopServer ← FALSE;
server ← FORK Server[];
};
StopServer: ENTRY PROC = {
IF server = NIL THEN RETURN;
pleaseStopServer ← TRUE;
PupSocket.Kick[serverSoc];
TRUSTED { JOIN server; };
server ← NIL;
serverSoc ← NIL;
};
Server: PROC = {
serverSoc ← PupSocket.CreateServer[
local: PupWKS.echo,
recvBuffers: 99,
getTimeout: PupSocket.waitForever];
PupSocketBackdoor.SetDirectReceive[serverSoc, Mirror, NIL];
UNTIL pleaseStopServer DO
b: Buffer ← PupSocket.Get[serverSoc];
IF b = NIL THEN LOOP;
SELECT b.type FROM
echoMe => {
b.type ← iAmEcho;
PupSocket.ReturnToSender[b];
BumpEchoed[];
IF showDollar^ THEN IO.PutRope[serverLog, "$"]; };
PupType.echoStatsRequest => {
b.echoStats ← [
version: PupBuffer.echoStatsVersion,
pupsEchoed: Endian.FFromCard[pupsEchoed]];
b.type ← PupType.echoStatsReply;
PupSocket.SetUserSize[b, SIZE[PupBuffer.RoutingInfoResponse]];
PupSocket.ReturnToSender[b]; };
ENDCASE => PupSocket.FreeBuffer[b];
ENDLOOP;
PupSocket.SetNoErrors[serverSoc];
PupSocket.Destroy[serverSoc];
};
BumpEchoed: ENTRY PROC = {
pupsEchoed ← pupsEchoed.SUCC;
};
Mirror: PROC [socket: Socket, b: Buffer, user: REF ANY] RETURNS [Buffer] = {
SELECT b.type FROM
echoMe => {
IF showDollar^ THEN {PupSocketBackdoor.UseNormalPath[b]; RETURN[NIL]; };
b.type ← iAmEcho;
PupSocketBackdoor.ReturnToSenderNoFree[b];
BumpEchoed[]; };
PupType.echoStatsRequest => {
b.echoStats ← [
version: PupBuffer.echoStatsVersion,
pupsEchoed: Endian.FFromCard[pupsEchoed]];
b.type ← PupType.echoStatsReply;
PupSocket.SetUserSize[b, SIZE[PupBuffer.RoutingInfoResponse]];
PupSocketBackdoor.ReturnToSenderNoFree[b]; };
ENDCASE => NULL;
RETURN[b];
};
StartProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
overlap: NAT;
SELECT TRUE FROM
shift AND control => overlap ← 5;
control => overlap ← 2;
shift => overlap ← 1;
ENDCASE => overlap ← 0;
Start[data, overlap]; };
Start: PROC [data: ClientData, overlap: NAT] = {
target: ROPE = ViewerTools.GetContents[data.target];
IF data.user # NIL THEN Stop[data];
IO.PutF[data.log, "\nEchoing to %G", [rope[target]]];
IF ~FindPath[data, target] THEN RETURN;
IF overlap # 0 THEN IO.PutF[data.log, "%g packets will be kept in flight.\n", [integer[overlap+1]]];
data.pleaseStop ← FALSE;
data.user ← FORK User[data, overlap]; };
BlastProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
overlap: NAT;
SELECT TRUE FROM
shift AND control => overlap ← 6;
control => overlap ← 3;
shift => overlap ← 2;
ENDCASE => overlap ← 1;
Blast[data, overlap]; };
Blast: PROC [data: ClientData, overlap: NAT] = {
target: ROPE = ViewerTools.GetContents[data.target];
IF data.user # NIL THEN Stop[data];
IO.PutF[data.log, "\nBlasting Pups to %G", [rope[target]]];
IF ~FindPath[data, target] THEN RETURN;
IF overlap # 0 THEN IO.PutF[data.log, "%g packets will be kept in flight.\n", [integer[overlap]]];
data.pleaseStop ← FALSE;
data.user ← FORK Blaster[data, overlap]; };
HistProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
Stop[data];
PrintDelayHist[data]; };
StopProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
Stop[data]; };
Stop: PROC [data: ClientData] = TRUSTED {
data.pleaseStop ← TRUE;
IF data.user # NIL THEN JOIN data.user;
data.user ← NIL; };
RouteProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
Route[data]; };
Route: PROC [data: ClientData] = {
PrintOne: PROC [net: BYTE] = {
rte: PupHop.RoutingTableEntry ← PupHop.GetRouting[[net]];
IF rte.hop = PupHop.unreachable THEN RETURN;
nets ← nets + 1;
IF k = 0 THEN IO.PutF[data.log, "|"];
IO.PutF[data.log, "%3B%4B#%3B#%4D |",
[integer[net]],
[integer[rte.immediate.net]],
[integer[rte.immediate.host]],
[integer[rte.hop]] ];
IF (k ← k + 1) = 3 THEN { IO.PutF[data.log, "\n"]; k ← 0; }; };
k, nets: INT ← 0;
IO.PutF[data.log, "\n%G\n", [time[BasicTime.Now[]]] ];
IO.PutF[data.log, "Local Pup Routing Table.\n"];
IO.PutF[data.log, "| Net Via Hops | Net Via Hops | Net Via Hops |\n"];
IO.PutF[data.log, "|-----------------|-----------------|-----------------|\n"];
FOR net: BYTE IN BYTE DO
PrintOne[net];
ENDLOOP;
IF k # 0 THEN IO.PutF[data.log, "\n"];
IF nets > 1 THEN IO.PutF[data.log, "There are %D active networks.\n", [integer[nets]]];
};
StatsProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
Stats[data]; };
Stats: PROC [data: ClientData] = {
first: CommDriver.Network ← CommDriver.GetNetworkChain[];
IO.PutF[data.log, "\n%G\n", [time[BasicTime.Now[]]] ];
FOR network: CommDriver.Network ← first, network.next UNTIL network = NIL DO
SELECT network.type FROM
ethernet, ethernetOne => {
stats: EthernetDriverStats.EtherStats ← NARROW[network.stats];
IO.PutF[data.log, "Ethernet"];
IF network.type = ethernetOne THEN IO.PutF[data.log, "One"];
IO.PutF[data.log, " Statistics from %B#%B#.\n",
[integer[network.pup.net]], [integer[network.pup.host]] ];
IO.PutF[data.log, " Rcv: pkts %G, words %G, bad %G, missed %G\n",
[integer[stats.packetsRecv]],
[integer[stats.wordsRecv]],
[integer[stats.badRecvStatus]],
[integer[stats.inputOff]]];
IO.PutF[data.log, " Xmit: pkts %G, words %G, bad %G\n",
[integer[stats.packetsSent]],
[integer[stats.wordsSent]],
[integer[stats.badSendStatus]]];
IO.PutF[data.log, " Lds:"];
FOR i: NAT IN [0..EthernetDriverStats.MaxTries] DO
IO.PutF[data.log, " %G", [integer[stats.loadTable[i]]]];
ENDLOOP;
IO.PutF[data.log, "\n"]; };
ENDCASE => NULL;
ENDLOOP;
};
User: PROC [data: ClientData, overlap: NAT] = {
ENABLE UNWIND => NULL;
start, finish: BasicTime.GMT;
sent, mashed, missed, late, error, funny: INT ← 0;
bytes: INT ← 0;
sendPacketNumber: INT ← 0;
recvPacketNumber: INT ← 0;
dataChecking: BOOL = data.dataChecking^;
fixedLength: BOOL = data.fixedLength^;
lengthText: ROPE = ViewerTools.GetContents[data.length];
timeoutText: ROPE = ViewerTools.GetContents[data.timeout];
dallyText: ROPE = ViewerTools.GetContents[data.dally];
lengthCard: CARDINAL ← 0;
timeoutCard: CARDINAL ← 3000;
dallyCard: CARDINAL ← 0;
dallyTicks: Process.Ticks;
socket: Socket;
sizeOfStartArray: NAT = 64;
packetStart: ARRAY [0..sizeOfStartArray) OF BasicTime.Pulses;
cond: CONDITION;
grabber: PROCESS;
longest: CARDINAL ← PupBuffer.maxDataBytes;
first: BOOLTRUE;
Kick: ENTRY PROC = TRUSTED {NOTIFY cond; };
Wait: ENTRY PROC = TRUSTED {WAIT cond; };
Grabber: PROC = TRUSTED {
DO
b: Buffer ← PupSocket.Get[socket];
start, packetStop: BasicTime.Pulses;
recvStartIndex: NAT;
length, pupLength: CARDINAL;
arrivedPacketNumber: INT;
IF b = NIL THEN {
IF data.pleaseStop THEN EXIT; -- Wait for last packet to keep stats clean
first ← TRUE;
missed ← missed + 1;
IF data.miss^ THEN IO.PutRope[data.log, "?"];
recvPacketNumber ← recvPacketNumber + 1;
Kick[];
LOOP; };
IF b.type = error THEN {
error ← error + 1;
PrintErrorPup[data, b];
PupSocket.FreeBuffer[b];
Kick[];
LOOP;};
IF b.type # iAmEcho THEN {
funny ← funny + 1;
IO.PutRope[data.log, "%"];
PupSocket.FreeBuffer[b];
Kick[];
LOOP;};
arrivedPacketNumber ← Endian.IntFromF[b.id];
IF (recvPacketNumber-arrivedPacketNumber) > 0 THEN {
late ← late + 1;
IF data.late^ THEN IO.PutRope[data.log, "#"];
PupSocket.FreeBuffer[b];
Kick[];
LOOP; };
UNTIL arrivedPacketNumber = recvPacketNumber DO
missed ← missed + 1;
IF data.miss^ THEN IO.PutRope[data.log, "?"];
recvPacketNumber ← recvPacketNumber + 1;
ENDLOOP;
length ← lengthCard;
IF ~fixedLength AND longest > 0 THEN length ← CARDINAL[recvPacketNumber MOD longest];
pupLength ← PupSocket.GetUserBytes[b];
IF pupLength # length THEN {
funny ← funny + 1;
IO.PutRope[data.log, "%"];
PupSocket.FreeBuffer[b];
Kick[];
LOOP; };
packetStop ← BasicTime.GetClockPulses[];
recvStartIndex ← LOOPHOLE[recvPacketNumber, LONG CARDINAL] MOD sizeOfStartArray;
start ← packetStart[recvStartIndex];
AddToDelayHist[data, BasicTime.PulsesToMicroseconds[packetStop-start]];
TRUSTED {
found: LONG POINTER TO FWordPattern = LOOPHOLE[@b.body];
fwords: NAT;
bytes: NAT;
IF ~dataChecking THEN GOTO Good;
[quotient: fwords, remainder: bytes] ← Basics.DivMod[num: length, den: bytesPerFWord];
FOR k: NAT IN [0..fwords) DO
IF found[k] # fWordPattern[k] THEN GOTO Mashed;
ENDLOOP;
FOR k: NAT IN [length-bytes..length) DO
IF b.byte[k] # bytePattern[k] THEN GOTO Mashed;
REPEAT FINISHED => GOTO Good;
ENDLOOP;
EXITS
Mashed => {
mashed ← mashed + 1;
IO.PutRope[data.log, "~"]; };
Good => {
data.good ← data.good + 1;
bytes ← bytes + length;
recvPacketNumber ← recvPacketNumber + 1;
IF data.echo^ THEN IO.PutRope[data.log, "!"];
IF recvPacketNumber MOD 1000 = 0 AND data.dot^ THEN {
IF data.echo^ THEN IO.PutRope[data.log, "\n"] ELSE IO.PutRope[data.log, "."]; }; };
};
PupSocket.FreeBuffer[b];
Kick[];
IF data.pleaseStop AND recvPacketNumber = sendPacketNumber THEN EXIT;
ENDLOOP;
Kick[];
};
b: Buffer;
IF TRUE THEN {
temp: INT;
temp ← Convert.IntFromRope[lengthText ! Convert.Error => {
IO.PutF[data.log, "Can't parse Length field.\n"];
lengthCard ← 0;
CONTINUE; } ];
temp ← MAX[temp, 0];
temp ← MIN[temp, longest];
longest ← lengthCard ← temp;
IF fixedLength THEN
IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[lengthCard]]]; };
IF dallyText # NIL THEN {
temp: INT;
temp ← Convert.IntFromRope[dallyText ! Convert.Error => {
IO.PutF[data.log, "Can't parse Dally field.\n"];
CONTINUE; } ];
temp ← MAX[temp, 0];
temp ← MIN[temp, 60000];
dallyCard ← temp;
IF dallyCard # 0 THEN {
dallyTicks ← Process.MsecToTicks[dallyCard];
dallyCard ← Process.TicksToMsec[dallyTicks];
IO.PutF[data.log, "Will dally %G ms after sending each packet.\n", [integer[dallyCard]]]; }; };
IF timeoutText # NIL THEN {
temp: INT;
temp ← Convert.IntFromRope[timeoutText ! Convert.Error => {
IO.PutF[data.log, "Can't parse Timeout field.\n"];
CONTINUE; } ];
temp ← MAX[temp, 0];
temp ← MIN[temp, 60000];
timeoutCard ← temp; };
timeoutCard ← timeoutCard + dallyCard;
InitDelayHist[data];
data.good ← 0;
socket ← PupSocket.CreateEphemeral[
remote: data.where,
sendBuffers: 25,
recvBuffers: 100,
getTimeout: timeoutCard];
TRUSTED { grabber ← FORK Grabber[]; };
start ← BasicTime.Now[];
b ← PupSocket.AllocBuffer[socket];
UNTIL data.pleaseStop DO
length: CARDINAL ← lengthCard;
sendStartIndex: NAT;
IF ~fixedLength AND longest > 0 THEN length ← CARDINAL[sendPacketNumber MOD longest];
b.id ← Endian.FFromInt[sendPacketNumber];
b.type ← echoMe;
TRUSTED {
words: CARDINAL = (length+bytesPerWord-1)/bytesPerWord;
PrincOpsUtils.LongCopy[to: @b.body, nwords: words, from: LOOPHOLE[bytePattern]]; };
PupSocket.SetUserBytes[b, length];
sendStartIndex ← LOOPHOLE[sendPacketNumber, LONG CARDINAL] MOD sizeOfStartArray;
packetStart[sendStartIndex] ← BasicTime.GetClockPulses[];
IF first THEN PupSocketBackdoor.PutFirst[socket, b]
ELSE { first ← FALSE; PupSocketBackdoor.PutAgain[socket, b]; };
sent ← sent + 1;
sendPacketNumber ← sendPacketNumber + 1;
IF dallyCard # 0 THEN Process.Pause[dallyTicks];
WHILE (sendPacketNumber-recvPacketNumber-overlap) > 0 DO
IF data.pleaseStop THEN EXIT;
Wait[];
ENDLOOP;
ENDLOOP;
IO.PutRope[data.log, "\n"];
IF recvPacketNumber = sendPacketNumber THEN PupSocket.Kick[socket];
PupSocket.FreeBuffer[b];
finish ← BasicTime.Now[];
TRUSTED { JOIN grabber; };
PupSocket.SetNoErrors[serverSoc];
PupSocket.Destroy[socket];
{
packetsPerSecond, msPerPacket: REAL;
seconds: LONG CARDINAL ← BasicTime.Period[from: start, to: finish];
IF seconds = 0 THEN seconds ← 1;
packetsPerSecond ← REAL[sent]/REAL[seconds];
IF data.good = 0 THEN msPerPacket ← 0
ELSE msPerPacket ← 1000.0*seconds/data.good;
IO.PutF[data.log, "%8G packets sent in %G seconds.\n", [integer[sent]], [cardinal[seconds]]];
IO.PutF[data.log, "%8.2F packets/second.\n", [real[packetsPerSecond]] ];
IO.PutF[data.log, "%8G bytes received in %G seconds.\n", [integer[bytes]], [cardinal[seconds]]];
IO.PutF[data.log, "%8.0F bits/second.\n", [real[8.0*bytes/seconds]] ];
IO.PutF[data.log, "%8.2F ms/packet.\n", [real[msPerPacket]] ];
PrintPercent[data, data.good, sent, "Packets echoed OK"];
PrintPercent[data, mashed, sent, "Packets with bad data"];
PrintPercent[data, missed, sent, "Packets missed"];
PrintPercent[data, late, sent, "Late Packets"];
PrintPercent[data, error, sent, "Error Packets"];
PrintPercent[data, funny, sent, "Funny Packets"];
IO.Flush[data.log];
}; };
Grabber: PROC [socket: Socket, b: Buffer, user: REF] RETURNS [Buffer] = TRUSTED {
data: ClientData = NARROW[user];
SELECT b.type FROM
iAmEcho =>
IF GotAnother[data] MOD 1000 = 0 AND data.echo^ THEN {
PupSocketBackdoor.UseNormalPath[b];
RETURN[NIL]; };
ENDCASE => { PupSocketBackdoor.UseNormalPath[b]; RETURN[NIL]; };
RETURN[b];
};
GotAnother: ENTRY PROC [data: ClientData] RETURNS [INT] = INLINE {
RETURN[data.good ← data.good + 1]; };
Blaster: PROC [data: ClientData, overlap: NAT] = {
ENABLE UNWIND => NULL;
start, finish: BasicTime.GMT;
sent: INT ← 0;
id: INT ← 0;
lengthText: ROPE = ViewerTools.GetContents[data.length];
socket: Socket;
maxFlingers: NAT = 6;
flinger: ARRAY [0..maxFlingers) OF PROCESS;
length: NAT ← PupBuffer.maxDataBytes;
NextId: ENTRY PROC RETURNS [next: INT] = INLINE {
next ← id;
id ← id + 1; };
SentAnother: ENTRY PROC RETURNS [print: BOOL] = INLINE {
sent ← sent + 1;
IF sent MOD 1000 = 0 THEN RETURN[TRUE];
RETURN[FALSE]; };
Flinger: PROC = TRUSTED {
b: Buffer ← PupSocket.AllocBuffer[socket];
UNTIL data.pleaseStop DO
id: INT ← NextId[];
b.id ← Endian.FFromInt[id];
b.type ← echoMe;
TRUSTED {
words: CARDINAL = (length+bytesPerWord-1)/bytesPerWord;
PrincOpsUtils.LongCopy[to: @b.body, nwords: words, from: LOOPHOLE[bytePattern]]; };
PupSocket.SetUserBytes[b, length];
IF id = 0 THEN PupSocketBackdoor.PutFirst[socket, b]
ELSE PupSocketBackdoor.Resend[b];
IF SentAnother[] AND data.echo^ THEN IO.PutRope[data.log, "s"];
ENDLOOP;
Process.Pause[2];
PupSocket.Kick[socket];
PupSocket.FreeBuffer[b]; };
IF TRUE THEN {
temp: INT;
temp ← Convert.IntFromRope[lengthText ! Convert.Error => {
IO.PutF[data.log, "Can't parse Length field.\n"];
length ← 0;
CONTINUE; } ];
temp ← MAX[temp, 0];
temp ← MIN[temp, length];
length ← temp;
IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[length]]]; };
InitDelayHist[data];
data.good ← data.error ← data.funny ← 0;
socket ← PupSocket.CreateEphemeral[
remote: data.where, sendBuffers: 25, recvBuffers: 100, getTimeout: 1000];
PupSocketBackdoor.SetDirectReceive[socket, Grabber, data];
start ← BasicTime.Now[];
FOR i: NAT IN [0..overlap) DO
TRUSTED { flinger[i] ← FORK Flinger[]; };
ENDLOOP;
UNTIL data.pleaseStop DO
b: Buffer ← PupSocket.Get[socket];
IF b = NIL THEN LOOP;
SELECT b.type FROM
error => {
data.error ← data.error + 1;
PrintErrorPup[data, b]; };
iAmEcho => IO.PutRope[data.log, "r"];
ENDCASE => {
data.funny ← data.funny + 1;
IO.PutRope[data.log, "%"]; };
PupSocket.FreeBuffer[b];
ENDLOOP;
IO.PutRope[data.log, "\n"];
FOR i: NAT IN [0..overlap) DO
TRUSTED { JOIN flinger[i]; };
ENDLOOP;
finish ← BasicTime.Now[];
PupSocket.SetNoErrors[serverSoc];
PupSocket.Destroy[socket];
{
packetsPerSecond, msPerPacket: REAL;
seconds: LONG CARDINAL ← BasicTime.Period[from: start, to: finish];
IF seconds = 0 THEN seconds ← 1;
packetsPerSecond ← REAL[sent]/REAL[seconds];
IF data.good = 0 THEN msPerPacket ← 0
ELSE msPerPacket ← 1000.0*seconds/data.good;
IO.PutF[data.log, "%8G packets sent in %G seconds.\n", [integer[sent]], [cardinal[seconds]]];
IO.PutF[data.log, "%8.2F packets/second.\n", [real[packetsPerSecond]] ];
IO.PutF[data.log, "%8.2F ms/packet.\n", [real[msPerPacket]] ];
PrintPercent[data, data.good, sent, "Packets echoed OK"];
PrintPercent[data, data.error, sent, "Error Packets"];
IO.Flush[data.log];
}; };
InitDelayHist: PROC [data: ClientData] = {
data.minDelay ← INT.LAST;
data.maxDelay ← INT.FIRST;
data.delayHist ← ALL[0]; };
AddToDelayHist: PROC [data: ClientData, micro: INT] = {
FOR d: DelayRange IN DelayRange DO -- Slow but clean
IF delayTime[d] < micro THEN LOOP;
data.delayHist[d] ← data.delayHist[d] + 1;
EXIT;
REPEAT FINISHED => ERROR;
ENDLOOP;
data.minDelay ← MIN[data.minDelay, micro];
data.maxDelay ← MAX[data.maxDelay, micro]; };
PrintDelayHist: PROC [data: ClientData] = {
total: INT ← 0;
IF data.good = 0 THEN RETURN;
IO.PutF[data.log, " Incremental Cumulative Microseconds\n"];
FOR d: DelayRange IN DelayRange DO
IF data.delayHist[d] = 0 THEN LOOP;
total ← total + data.delayHist[d];
IO.PutF[data.log, "%8G", [integer[data.delayHist[d]]]];
IO.PutF[data.log, "%7.2F%%", [real[100.0*data.delayHist[d]/data.good]]];
IO.PutF[data.log, "%8G", [integer[total]]];
IO.PutF[data.log, "%7.2F%%", [real[100.0*total/data.good]]];
IO.PutF[data.log, "%9G\n", [integer[delayTime[d]]]];
ENDLOOP;
IO.PutF[data.log, "The min delay was %G microseconds.\n", [integer[data.minDelay]]];
IO.PutF[data.log, "The max delay was %G microseconds.\n", [integer[data.maxDelay]]]; };
PokeProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
target: ROPE = ViewerTools.GetContents[data.target];
Poke[data, target]; };
Poke: PROC [data: ClientData, target: ROPE] = TRUSTED {
id: Endian.FWORD = PupSocket.GetUniqueID[];
b: Buffer;
socket: Socket;
hit: INT ← 0;
start: BasicTime.Pulses;
buffersToCollect: NAT = 100;
buffers: ARRAY [0..buffersToCollect) OF Buffer ← ALL[NIL];
stop: ARRAY [0..buffersToCollect) OF BasicTime.Pulses;
lengthText: ROPE = ViewerTools.GetContents[data.length];
bytes: INT ← 0;
IO.PutF[data.log, "\nPoking %G", [rope[target]]];
IF ~FindPath[data, target] THEN RETURN;
IF data.fixedLength^ THEN {
temp: INT;
temp ← Convert.IntFromRope[lengthText ! Convert.Error => {
IO.PutF[data.log, "Can't parse Length field.\n"];
temp ← 0;
CONTINUE; } ];
temp ← MAX[temp, 0];
bytes ← MIN[temp, PupBuffer.maxDataBytes];
IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[bytes]]]; };
Process.SetPriority[Process.priorityForeground];
socket ← PupSocket.CreateEphemeral[
remote: data.where, recvBuffers: buffersToCollect, getTimeout: 5000];
b ← PupSocket.AllocBuffer[socket];
b.id ← id;
b.type ← echoMe;
PupSocket.SetUserBytes[b, bytes];
start ← BasicTime.GetClockPulses[];
PupSocket.Put[socket, b];
FOR i: NAT IN [0..buffersToCollect) DO
b ← PupSocket.Get[socket];
IF b = NIL THEN EXIT;
buffers[i] ← b;
stop[i] ← BasicTime.GetClockPulses[];
ENDLOOP;
FOR i: NAT IN [0..buffersToCollect) DO
b ← buffers[i];
IF b = NIL THEN EXIT;
SELECT TRUE FROM
(b.type = error) => PrintErrorPup[data, b];
(b.type # iAmEcho) => IO.PutRope[data.log, "%"];
(b.id # id) => IO.PutRope[data.log, "#"];
ENDCASE => {
microseconds: LONG CARDINAL ← BasicTime.PulsesToMicroseconds[stop[i]-start];
hit ← hit.SUCC;
IO.PutF[data.log, "Response %G from %G = %G in %G microseconds.\n",
[integer[hit]],
[rope[PupName.AddressToRope[b.source]]],
[rope[PupName.HisName[b.source]]],
[integer[microseconds]] ]; };
PupSocket.FreeBuffer[b];
ENDLOOP;
PupSocket.SetNoErrors[serverSoc];
PupSocket.Destroy[socket];
};
PrintPercent: PROC [data: ClientData, x, sent: INT, name: ROPE] = {
IF x = 0 THEN RETURN;
IO.PutF[data.log, "%8G %7.2F%% %G\n", [integer[x]], [real[100.0*x/sent]], [rope[name]] ]; };
PrintErrorPup: PROC [data: ClientData, b: Buffer] = {
length: CARDINAL ← PupSocket.GetUserBytes[b];
IO.PutRope[data.log, "Error Pup from "];
IO.PutRope[data.log, PupName.HisName[b.source]];
IO.PutRope[data.log, ": "];
IO.PutRope[data.log, PupSocket.ExtractErrorRope[b]];
IO.PutRope[data.log, "\n"];
};
FindPath: PROC [data: ClientData, target: ROPE] RETURNS [BOOLEAN] = {
rte: PupHop.RoutingTableEntry;
data.where ← PupName.NameLookup[target, PupWKS.echo !
PupName.Error => { IO.PutF[data.log, " Oops: %G.\n", [rope[text]]]; GOTO Trouble; }];
IO.PutF[data.log, " = %G", [rope[PupName.AddressToRope[data.where]]]];
rte ← PupHop.GetRouting[[data.where.net]];
IF rte.hop # 0 THEN
IO.PutF[data.log, " which is %G hops via %G",
[integer[rte.hop]],
[rope[PupName.AddressToRope[rte.immediate]]] ];
IO.PutRope[data.log, ".\n"];
RETURN[TRUE];
EXITS Trouble => RETURN[FALSE];
};
MakeRule: PROC [parent, sibling: Viewer] RETURNS [child: Viewer] = {
child ← Rules.Create[
info: [parent: parent, border: FALSE,
wy: IF sibling = NIL THEN 0 ELSE sibling.wy + sibling.wh + 2, wx: 0, ww: parent.ww, wh: 1],
paint: FALSE ];
Containers.ChildXBound[parent, child];
};
MakeButton: PROC [parent, sibling: Viewer, data: REF ANY, name: ROPE, proc: Buttons.ButtonProc, guarded: BOOLFALSE] RETURNS[child: Viewer] = {
child ← Buttons.Create[
info: [name: name, parent: parent, border: TRUE,
wy: sibling.wy, wx: sibling.wx + sibling.ww - 1, ww: buttonWidth],
proc: proc,
clientData: data,
fork: TRUE,
guarded: guarded,
paint: FALSE];
};
SelectorProc: TYPE = PROC [parent: Viewer, clientData: REF, value: ATOM];
Selector: TYPE = REF SelectorRec;
SelectorRec: TYPE = RECORD [
value: REF ATOM,
change: PROC [parent: Viewer, clientData: REF, value: ATOM],
clientData: REF,
buttons: LIST OF Buttons.Button,
values: LIST OF ATOM ];
MakeSelector: PROC
[name: ROPE, values: LIST OF ATOM, init: REF ATOMNIL, change: SelectorProc ← NIL, clientData: REFNIL, parent: Viewer, x, y: INTEGER]
RETURNS [child: Viewer] = {
selector: Selector ← NEW [SelectorRec ← [
value: IF init # NIL THEN init ELSE NEW [ATOM ← values.first],
change: change,
clientData: clientData,
buttons: NIL,
values: values ] ];
last: LIST OF Buttons.Button ← NIL;
child ← Labels.Create[info: [name: name, parent: parent, border: FALSE, wx: x, wy: y] ];
FOR a: LIST OF ATOM ← values, a.rest UNTIL a = NIL DO
child ← Buttons.Create[
info: [name: Atom.GetPName[a.first], parent: parent, border: TRUE, wx: child.wx + child.ww + 2, wy: child.wy],
proc: SelectorHelper, clientData: selector, fork: TRUE, paint: TRUE];
IF last = NIL THEN last ← selector.buttons ← CONS[first: child, rest: NIL]
ELSE { last.rest ← CONS[first: child, rest: NIL]; last ← last.rest };
IF a.first = selector.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack];
ENDLOOP; };
SelectorHelper: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
self: Buttons.Button = NARROW[parent];
selector: Selector = NARROW[clientData];
buttons: LIST OF Buttons.Button ← selector.buttons;
FOR a: LIST OF ATOM ← selector.values, a.rest UNTIL a = NIL DO
IF self = buttons.first THEN {
selector.value^ ← a.first;
IF selector.change # NIL THEN selector.change[self.parent, selector.clientData, a.first];
Buttons.SetDisplayStyle[buttons.first, $WhiteOnBlack]; }
ELSE Buttons.SetDisplayStyle[buttons.first, $BlackOnWhite];
buttons ← buttons.rest;
ENDLOOP; };
BoolProc: TYPE = PROC [parent: Viewer, clientData: REF, value: BOOL];
Bool: TYPE = REF BoolRec;
BoolRec: TYPE = RECORD [
value: REF BOOL,
change: BoolProc,
clientData: REF,
button: Viewer ];
MakeBool: PROC
[name: ROPE, init: REF BOOL, change: BoolProc ← NIL, clientData: REFNIL, parent: Viewer, x, y: INTEGER]
RETURNS [child: Viewer] = {
bool: Bool ← NEW [BoolRec ← [
value: IF init # NIL THEN init ELSE NEW [BOOLTRUE],
change: change,
clientData: clientData,
button: NIL ] ];
child ← Buttons.Create[
info: [name: name, parent: parent, border: TRUE, wx: x, wy: y],
proc: BoolHelper, clientData: bool, fork: TRUE, paint: TRUE];
bool.button ← child;
IF bool.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; };
BoolHelper: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
self: Buttons.Button = NARROW[parent];
bool: Bool = NARROW[clientData];
bool.value^ ← ~bool.value^;
IF bool.value^ THEN Buttons.SetDisplayStyle[bool.button, $WhiteOnBlack]
ELSE Buttons.SetDisplayStyle[bool.button, $BlackOnWhite];
IF bool.change # NIL THEN bool.change[self.parent, bool.clientData, bool.value^]; };
MakeLabel: PROC [parent, sibling: Viewer, name: ROPE] RETURNS [child: Viewer] = {
child ← Labels.Create[
info: [name: name, parent: parent, border: FALSE,
wy: sibling.wy + sibling.wh + (IF sibling.class.flavor = $Button THEN -1 ELSE 2),
wx: 2,
ww: VFonts.StringWidth[name] + 2*3 + 2],
paint: FALSE ]; };
MakeLabeledText: PROC [
parent, sibling: Viewer, name, data: ROPE, prev: Viewer, width: INT, newline: BOOLTRUE] RETURNS [child: Viewer] = {
buttonWidth: INT ← VFonts.StringWidth[name] + 2*3;
x: INTEGER = IF newline THEN 2 ELSE sibling.wx + sibling.ww + 10;
y: INTEGER = IF newline THEN sibling.wy + sibling.wh + 1 ELSE sibling.wy;
child ← ViewerTools.MakeNewTextViewer[
info: [
parent: parent, wh: buttonHeight, ww: width+10,
data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev],
border: FALSE,
wx: x + buttonWidth + 2, wy: y,
scrollable: FALSE ],
paint: FALSE ];
[] ← Buttons.Create[
info: [name: name, parent: parent, wh: buttonHeight, border: FALSE, wx: x, wy: y],
proc: LabeledTextProc, clientData: child, fork: FALSE, paint: FALSE];
RETURN[child]; };
LabeledTextProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
text: Viewer = NARROW[clientData];
SELECT mouseButton FROM
red => ViewerTools.SetSelection[text, NIL];
yellow => NULL;
blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] };
ENDCASE => ERROR; };
patternBytes: NAT = 5000;
patternFWords: NAT = patternBytes/bytesPerFWord;
BytePattern: TYPE = PACKED ARRAY [0..patternBytes) OF BYTE;
FWordPattern: TYPE = ARRAY [0..patternFWords) OF Endian.FWORD;
bytePattern: REF BytePattern ← NEW [BytePattern];
fWordPattern: LONG POINTER TO FWordPattern;
FOR i: NAT IN [0..patternBytes) DO bytePattern[i] ← i MOD 100H; ENDLOOP;
TRUSTED {fWordPattern ← LOOPHOLE[bytePattern]; };
Commander.Register["PupEchoTool", Create, "Echo Pups to another machine."];
}.