-- Copyright (C) 1984, 1985 by Xerox Corporation. All rights reserved. -- EchoTool.mesa, HGM, 12-Mar-85 20:31:14 -- Please don't forget to update the herald... DIRECTORY AddressTranslation USING [Error, PrintError, StringToNetworkAddress], Buffer USING [NSBuffer], Format USING [HostNumber, NetworkAddress, StringProc], FormSW USING [ AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem, ItemHandle, newLine, NumberItem, ProcType, StringItem], Heap USING [systemZone], Inline USING [BITAND, BITNOT], NSConstants USING [echoerSocket], NSTypes USING [maxIDPDataWords], Process USING [Detach, Pause, Yield], Put USING [Char, CR, Line, Text], Socket USING [ ChannelHandle, Create, Delete, GetPacket, GetPacketBytes, GetSendBuffer, GetSource, PutPacket, ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime, TimeOut, uniqueNetworkAddr], String USING [AppendNumber, AppendLongNumber, AppendString], SpecialSystem USING [GetProcessorID], System USING [ GetClockPulses, GetGreenwichMeanTime, GreenwichMeanTime, NetworkAddress, nullSocketNumber, Pulses, PulsesToMicroseconds, SocketNumber], Time USING [AppendCurrent], Tool USING [ Create, UnusedLogName, MakeFormSW, MakeFileSW, MakeSWsProc], ToolWindow USING [TransitionProcType], Unformat USING [Error, NetworkAddress], UserInput USING [UserAbort], Window USING [Handle]; EchoTool: PROGRAM IMPORTS Format, FormSW, Heap, Inline, Process, Put, SpecialSystem, String, System, Time, Tool, UserInput, Unformat, AddressTranslation, Socket EXPORTS = BEGIN z: UNCOUNTED ZONE = Heap.systemZone; -- global variable declarations log, form: Window.Handle ← NIL; thisMachineID, remoteAddress: LONG STRING ← NIL; maxLength: CARDINAL ← NSTypes.maxIDPDataWords; maxClumpSize: CARDINAL = 50; clumpSize: CARDINAL ← 50; running, pleaseStop: BOOLEAN ← FALSE; fixedLength: BOOLEAN ← FALSE; noBang, noLate, noLost: BOOLEAN ← FALSE; checkIt: BOOLEAN ← TRUE; timeout: CARDINAL ← 3000; -- ms Init: PROCEDURE = BEGIN herald: STRING = "Echo Tool of 12-Mar-85 20:31:04"L; [] ← Tool.Create[ name: herald, makeSWsProc: MakeThisTool, clientTransition: Transition]; END; MakeThisTool: Tool.MakeSWsProc = BEGIN logFileName: STRING = [40]; form ← Tool.MakeFormSW[window: window, formProc: MakeItemArray]; Tool.UnusedLogName[logFileName, "EchoTool.log$"L]; log ← Tool.MakeFileSW[window: window, name: logFileName, allowTypeIn: FALSE]; END; MakeItemArray: FormSW.ClientItemsProcType = BEGIN nItems: CARDINAL = 14; i: INTEGER ← -1; items ← FormSW.AllocateItemDescriptor[nItems]; items[i ← i + 1] ← FormSW.CommandItem[tag: "Stop"L, proc: Stop, place: FormSW.newLine]; items[i ← i + 1] ← FormSW.CommandItem[tag: "PokeOnce"L, proc: PokeOnce]; items[i ← i + 1] ← FormSW.CommandItem[tag: "Echo"L, proc: Echo]; items[i ← i + 1] ← FormSW.CommandItem[tag: "Clumps"L, proc: Clumps]; items[i ← i + 1] ← FormSW.BooleanItem[tag: "FixedLength"L, switch: @fixedLength]; items[i ← i + 1] ← FormSW.NumberItem[tag: "(Max)Length"L, value: @maxLength, boxWidth: 25]; items[i ← i + 1] ← FormSW.NumberItem[tag: "clumpSize"L, value: @clumpSize]; items[i ← i + 1] ← FormSW.StringItem[ tag: "ThisMachineID"L, string: @thisMachineID, readOnly: TRUE, boxWidth: 100, place: FormSW.newLine]; items[i ← i + 1] ← FormSW.StringItem[ tag: "RemoteAddress"L, string: @remoteAddress, inHeap: TRUE, boxWidth: 150]; items[i ← i + 1] ← FormSW.BooleanItem[ tag: "CheckIt"L, switch: @checkIt, place: FormSW.newLine]; items[i ← i + 1] ← FormSW.BooleanItem[tag: "!-Off"L, switch: @noBang]; items[i ← i + 1] ← FormSW.BooleanItem[tag: "#-Off"L, switch: @noLate]; items[i ← i + 1] ← FormSW.BooleanItem[tag: "?-Off"L, switch: @noLost]; items[i ← i + 1] ← FormSW.NumberItem[tag: "Timeout(ms)"L, value: @timeout]; IF (i + 1) # nItems THEN ERROR; RETURN[items, TRUE]; END; Transition: ToolWindow.TransitionProcType = BEGIN SELECT TRUE FROM old = inactive => BEGIN AppendMe: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] = BEGIN String.AppendString[thisMachineID, s]; END; checkIt ← TRUE; noBang ← noLate ← noLost ← FALSE; thisMachineID ← z.NEW[StringBody[50]]; Format.HostNumber[AppendMe, LOOPHOLE[SpecialSystem.GetProcessorID[]], productSoftware]; remoteAddress ← z.NEW[StringBody[50]]; END; new = inactive => BEGIN z.FREE[@thisMachineID]; z.FREE[@remoteAddress]; END; ENDCASE; END; Stop: FormSW.ProcType = BEGIN StopIt[]; END; StopIt: PROCEDURE = BEGIN pleaseStop ← TRUE; WHILE running DO Process.Pause[1]; ENDLOOP; END; Echo: FormSW.ProcType = BEGIN fixed: BOOLEAN; size: CARDINAL; errFlag: BOOLEAN ← FALSE; remoteAddr: System.NetworkAddress; StopIt[]; IF fixed AND maxLength < 1 THEN BEGIN WriteLine["Need at least 2 word."L]; RETURN; END; IF maxLength > NSTypes.maxIDPDataWords THEN BEGIN WriteLine["(max)Length is too big."L]; RETURN; END; fixed ← fixedLength; size ← maxLength; WriteCR[]; WriteString["Echoing to "L]; WriteString[remoteAddress]; WriteString[" = "L]; remoteAddr ← GetAddress[remoteAddress, NSConstants.echoerSocket ! Trouble => BEGIN WriteLine[reason]; errFlag ← TRUE; CONTINUE; END]; IF errFlag THEN RETURN; WriteNetworkAddressVerbose[remoteAddr]; WriteLine["."L]; pleaseStop ← FALSE; running ← TRUE; Process.Detach[FORK Worker[remoteAddr, fixed, size, 1]]; END; 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 LONG CARDINAL = [ 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[LONG CARDINAL]]; Worker: PROCEDURE [ remoteAddr: System.NetworkAddress, fixed: BOOLEAN, wordsPerPacket, packetsPerClump: CARDINAL] = BEGIN length: CARDINAL = MAX[2, MIN[NSTypes.maxIDPDataWords, wordsPerPacket]]; delay: ARRAY DelayRange OF LONG CARDINAL ← ALL[0]; minDelay: LONG CARDINAL ← LAST[LONG CARDINAL]; maxDelay: LONG CARDINAL ← FIRST[LONG CARDINAL]; picks: ARRAY [0..16] OF CARDINAL ← ALL[0]; drops: ARRAY [0..16] OF CARDINAL ← ALL[0]; sent, good, missed, late, bad, horrible, trash, words: LONG CARDINAL ← 0; start, stop: System.Pulses; micro: LONG CARDINAL; startSec, stopSec: System.GreenwichMeanTime; AddToDelayHist: PROCEDURE [micro: LONG CARDINAL] = BEGIN FOR d: DelayRange IN DelayRange DO -- Slow but clean IF delayTime[d] < micro THEN LOOP; delay[d] ← delay[d] + 1; EXIT; REPEAT FINISHED => ERROR; ENDLOOP; minDelay ← MIN[minDelay, micro]; maxDelay ← MAX[maxDelay, micro]; END; PrintDelayHist: PROCEDURE = BEGIN total: LONG CARDINAL ← 0; IF good = 0 THEN RETURN; FOR d: DelayRange IN DelayRange DO IF delay[d] = 0 THEN LOOP; total ← total + delay[d]; WriteLongNumber[delay[d], 10, 8]; WriteLongNumber[delay[d]*100/good, 10, 4]; WriteString["%"L]; WriteLongNumber[total, 10, 8]; WriteLongNumber[total*100/good, 10, 4]; WriteString["% packets with delay less than "L]; WriteLongDecimal[delayTime[d]]; WriteLine[" microseconds."L]; ENDLOOP; WriteString["The min delay was "L]; WriteLongDecimal[minDelay]; WriteLine[" microseconds."L]; WriteString["The max delay was "L]; WriteLongDecimal[maxDelay]; WriteLine[" microseconds."L]; END; AddToErrorHist: PROCEDURE [ hist: POINTER TO ARRAY [0..16] OF CARDINAL, bits: WORD] = BEGIN 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 IF howLong # 0 THEN BEGIN WriteLongNumber[sent/howLong, 10, 8]; WriteLine[" packets per second."L]; WriteLongNumber[16*words/howLong, 10, 8]; WriteLine[" data bits per second."L]; END; END; PrintErrorHist: PROCEDURE = BEGIN ShowPercent[good, sent, "good packets received."L]; ShowPercent[missed, sent, "packets missed."L]; ShowPercent[late, sent, "late (or??) packets received."L]; ShowPercent[bad, sent, "bad packets received."L]; ShowPercent[horrible, sent, "packets received with more than 10 words wrong."L]; ShowPercent[trash, sent, "trashy packets received."L]; IF bad # 0 THEN BEGIN x: WORD ← 100000B; WriteCR[]; WriteLine[" Bit Picked Dropped"L]; FOR i: CARDINAL 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; soc: Socket.ChannelHandle; sendSequenceNumber, recvSequenceNumber: CARDINAL ← 0; buffers: ARRAY [0..maxClumpSize) OF Buffer.NSBuffer; b: Buffer.NSBuffer; pktBody: LONG POINTER TO ARRAY [0..0) OF WORD; soc ← Socket.Create[ local: Socket.uniqueNetworkAddr, send: packetsPerClump, receive: packetsPerClump]; Socket.SetWaitTime[soc, timeout]; startSec ← System.GetGreenwichMeanTime[]; UNTIL pleaseStop OR UserInput.UserAbort[log] DO FOR i: CARDINAL IN [0..packetsPerClump) DO cycle: CARDINAL; IF (((sendSequenceNumber ← sendSequenceNumber + 1) MOD 5) = 0) THEN Process.Yield[]; IF (sendSequenceNumber MOD NSTypes.maxIDPDataWords) = 0 THEN BEGIN IF ~noBang THEN WriteLine[""L] ELSE WriteChar['.]; END; cycle ← IF fixed THEN wordsPerPacket - 1 ELSE sendSequenceNumber MOD length; IF cycle = 0 THEN cycle ← 1; b ← Socket.GetSendBuffer[soc]; Socket.SetDestination[b, remoteAddr]; Socket.SetPacketWords[b, cycle + 1]; -- 1 for echoType words ← words + cycle + 1; b.ns.packetType ← echo; b.ns.echoType ← echoRequest; pktBody ← @b.ns.echoWords; FOR k: CARDINAL IN [0..cycle) DO pktBody[k] ← (k*400B + sendSequenceNumber); ENDLOOP; buffers[i] ← b; ENDLOOP; start ← System.GetClockPulses[]; FOR i: CARDINAL IN [0..packetsPerClump) DO Socket.PutPacket[soc, buffers[i]]; sent ← sent + 1; ENDLOOP; -- now receive the echo or any back logged echos DO cycle: CARDINAL; diff: INTEGER; b ← Socket.GetPacket[ soc ! Socket.TimeOut => BEGIN UNTIL sendSequenceNumber = recvSequenceNumber DO missed ← missed + 1; IF ~noLost THEN WriteChar['?]; recvSequenceNumber ← recvSequenceNumber + 1; ENDLOOP; EXIT; END]; pktBody ← @b.ns.echoWords; IF b.ns.packetType # echo OR b.ns.echoType # echoResponse THEN BEGIN trash ← trash + 1; WriteChar['%]; Socket.ReturnBuffer[b]; LOOP; END; recvSequenceNumber ← recvSequenceNumber + 1; cycle ← IF fixed THEN length - 1 ELSE recvSequenceNumber MOD length; IF cycle = 0 THEN cycle ← 1; diff ← pktBody[0] - recvSequenceNumber; IF diff < 0 THEN BEGIN late ← late + 1; IF ~noLate THEN WriteChar['#]; Socket.ReturnBuffer[b]; recvSequenceNumber ← recvSequenceNumber - 1; LOOP; END; WHILE diff > 0 DO missed ← missed + 1; IF ~noLost THEN WriteChar['?]; recvSequenceNumber ← recvSequenceNumber + 1; cycle ← IF fixed THEN length - 1 ELSE recvSequenceNumber MOD length; IF cycle = 0 THEN cycle ← 1; diff ← pktBody[0] - recvSequenceNumber; ENDLOOP; SELECT TRUE FROM (Socket.GetPacketBytes[b] # 2*(cycle + 1)) OR (pktBody[0] # recvSequenceNumber) => BEGIN trash ← trash + 1; WriteChar['%]; Socket.ReturnBuffer[b]; LOOP; END; ENDCASE => BEGIN -- the echo we were looking for hits: CARDINAL ← 0; stop ← System.GetClockPulses[]; micro ← System.PulsesToMicroseconds[[stop - start]]; AddToDelayHist[micro]; IF checkIt THEN FOR k: CARDINAL IN [0..cycle) DO IF pktBody[k] # (k*400B + recvSequenceNumber) 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]; WriteLongDecimal[sent]; WriteLine["."L]; WriteLine["Idx Expected Found Picked Dropped"L]; END; expected ← k*400B + recvSequenceNumber; found ← pktBody↑[k]; picked ← BITAND[found, BITNOT[expected]]; dropped ← BITAND[expected, BITNOT[found]]; AddToErrorHist[@picks, picked]; AddToErrorHist[@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 ~noBang THEN WriteChar['!]; IF hits > 10 THEN BEGIN horrible ← horrible + 1; WriteLine["...."L]; END; Socket.ReturnBuffer[b]; IF recvSequenceNumber = sendSequenceNumber THEN EXIT; END; ENDLOOP; ENDLOOP; stopSec ← System.GetGreenwichMeanTime[]; WriteCR[]; Socket.Delete[soc]; WriteLongNumber[sent, 10, 8]; WriteLine[" packets sent."L]; PrintSummary[stopSec-startSec]; PrintErrorHist[]; PrintDelayHist[]; running ← FALSE; END; PokeOnce: FormSW.ProcType = BEGIN sequenceNumber: CARDINAL ← 0; b: Buffer.NSBuffer; errFlag: BOOLEAN ← FALSE; remoteAddr: System.NetworkAddress; soc: Socket.ChannelHandle; hit: CARDINAL ← 0; WriteCR[]; WriteString["Poking "L]; WriteString[remoteAddress]; WriteString[" = "L]; remoteAddr ← GetAddress[remoteAddress, NSConstants.echoerSocket ! Trouble => BEGIN WriteString["AddressTranslation troubles: "L]; WriteLine[reason]; errFlag ← TRUE; CONTINUE; END]; IF errFlag THEN RETURN; WriteNetworkAddressVerbose[remoteAddr]; WriteLine["."L]; soc ← Socket.Create[local: Socket.uniqueNetworkAddr, receive: 20]; Socket.SetWaitTime[soc, timeout]; b ← Socket.GetSendBuffer[soc]; Socket.SetDestination[b, remoteAddr]; Socket.SetPacketWords[b, 1]; b.ns.packetType ← echo; b.ns.echoType ← echoRequest; Socket.PutPacket[soc, b]; DO b ← Socket.GetPacket[soc ! Socket.TimeOut => EXIT]; SELECT TRUE FROM (Socket.GetPacketBytes[b] # 2*1) OR (b.ns.echoType # echoResponse) => BEGIN WriteChar['#]; Socket.ReturnBuffer[b]; LOOP; END; ENDCASE => BEGIN hit ← hit + 1; WriteString["Response number "L]; WriteNumber[hit, 10, 2]; WriteString[" from "L]; WriteNetworkAddressVerbose[Socket.GetSource[b]]; WriteLine["."L]; Socket.ReturnBuffer[b]; END; ENDLOOP; WriteCR[]; Socket.Delete[soc]; END; Clumps: FormSW.ProcType = BEGIN fixed: BOOLEAN; size, clumps: CARDINAL; errFlag: BOOLEAN ← FALSE; remoteAddr: System.NetworkAddress; StopIt[]; IF fixed AND maxLength < 1 THEN BEGIN WriteLine["Need at least 2 word."L]; RETURN; END; IF maxLength > NSTypes.maxIDPDataWords THEN BEGIN WriteLine["(max)Length is too big."L]; RETURN; END; IF clumpSize > maxClumpSize THEN BEGIN WriteLine["ClumpSize is too big."L]; RETURN; END; IF clumpSize < 1 THEN BEGIN WriteLine["Need at least 1 packet per clump."L]; RETURN; END; fixed ← fixedLength; size ← maxLength; clumps ← clumpSize; WriteCR[]; WriteString["Echoing Clumps to "L]; WriteString[remoteAddress]; WriteString[" = "L]; remoteAddr ← GetAddress[remoteAddress, NSConstants.echoerSocket ! Trouble => BEGIN WriteString["AddressTranslation troubles: "L]; WriteLine[reason]; errFlag ← TRUE; CONTINUE; END]; IF errFlag THEN RETURN; WriteNetworkAddressVerbose[remoteAddr]; WriteLine["."L]; pleaseStop ← FALSE; running ← TRUE; Process.Detach[FORK Worker[remoteAddr, fixed, size, clumps]]; END; ShowPercent: PROCEDURE [n, sent: LONG CARDINAL, s: LONG STRING] = BEGIN IF n = 0 THEN RETURN; WriteLongNumber[n, 10, 8]; WriteLongNumber[n*100/sent, 10, 4]; WriteString["% "L]; WriteLine[s]; END; WriteChar: PROCEDURE [c: CHARACTER] = BEGIN Put.Char[log, c]; END; WriteCR: PROCEDURE = BEGIN Put.CR[log]; END; WriteString: PROCEDURE [s: LONG STRING] = BEGIN Put.Text[log, s]; END; WriteLine: PROCEDURE [s: LONG STRING] = BEGIN Put.Line[log, s]; END; WriteLongNumber: PROCEDURE [n: LONG CARDINAL, radix, width: CARDINAL] = INLINE BEGIN temp: STRING = [25]; String.AppendLongNumber[temp, n, radix]; THROUGH [temp.length..width) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, temp]; END; WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE BEGIN temp: STRING = [25]; String.AppendNumber[temp, n, radix]; THROUGH [temp.length..width) DO Put.Char[log, ' ]; ENDLOOP; Put.Text[log, temp]; END; WriteLongDecimal: PROCEDURE [n: LONG UNSPECIFIED] = BEGIN temp: STRING = [32]; String.AppendLongNumber[temp, n, 10]; Put.Text[log, 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 temp: STRING = [32]; Time.AppendCurrent[temp]; Put.Text[log, temp]; END; WriteNetworkAddressVerbose: PROCEDURE [address: System.NetworkAddress] = BEGIN temp: STRING = [100]; Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] = BEGIN String.AppendString[temp, s]; END; Format.NetworkAddress[Append, address, octal]; String.AppendString[temp, " = "L]; Format.NetworkAddress[Append, address, productSoftware]; Put.Text[log, temp]; END; Trouble: ERROR [reason: LONG STRING] = CODE; GetAddress: PROCEDURE [host: LONG STRING, socket: System.SocketNumber] RETURNS [addr: System.NetworkAddress] = BEGIN localFailed: BOOLEAN ← FALSE; IF host = NIL THEN ERROR Trouble["NIL => Address Fault"L]; addr ← Unformat.NetworkAddress[host, octal ! Unformat.Error => BEGIN localFailed ← TRUE; CONTINUE; END ]; IF localFailed THEN BEGIN addr ← AddressTranslation.StringToNetworkAddress[host ! AddressTranslation.Error => BEGIN temp: STRING = [200]; proc: Format.StringProc = {String.AppendString[temp, s]}; AddressTranslation.PrintError[errorRecord, proc]; ERROR Trouble[temp]; END].addr; addr.socket ← socket; -- CH returns trash in socket END; IF addr.socket = System.nullSocketNumber THEN addr.socket ← socket; END; Init[]; -- this gets string out of global frame END...