-- Copyright (C) 1984, 1985 by Xerox Corporation. All rights reserved. -- EchoTool.mesa, HGM, 5-Sep-85 18:23:20 -- Please don't forget to update the herald... DIRECTORY AddressTranslation USING [Error, PrintError, StringToNetworkAddress], Buffer USING [NSBuffer], Driver USING [GetDeviceChain, Network], EthernetDriverFriends USING [EtherStatsInfo], Format USING [HostNumber, NetworkAddress, NetworkNumber, 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], PhoneNetFriends USING [PhoneNetInfo, StatsPtrToStats], PhoneNetExtras USING [ leafBytesSend, leafDupsFiltered, leafPktsSend, nsBytesSend, nsCongestion, nsDupsFiltered, nsTooGreedy, pupBytesSend, pupCongestion, pupDupsFiltered, pupTooGreedy], Protocol1 USING [GetContext], PupRouterDefs USING [NetworkContext], Process USING [Detach, Pause, Yield], Put USING [Char, CR, Line, LongDecimal, Text], RoutingTable USING [NetworkContext], Socket USING [ ChannelHandle, Create, Delete, GetPacket, GetPacketBytes, GetSendBuffer, GetSource, PutPacket, ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime, TimeOut], String USING [AppendNumber, AppendLongNumber, AppendString], SpecialSystem USING [GetProcessorID], System USING [ GetClockPulses, GetGreenwichMeanTime, GreenwichMeanTime, HostNumber, localHostNumber, NetworkAddress, NetworkNumber, nullHostNumber, 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 Driver, Format, FormSW, Heap, Inline, PhoneNetFriends, Process, Protocol1, Put, SpecialSystem, String, System, Time, Tool, UserInput, Unformat, AddressTranslation, Socket EXPORTS Buffer = BEGIN Network: PUBLIC TYPE = Driver.Network; 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 5-Sep-85 18:23:27"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 = 15; 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.CommandItem[tag: "Stats"L, proc: Stats, place: FormSW.newLine]; items[i ¬ i + 1] ¬ FormSW.StringItem[ tag: "ThisMachineID"L, string: @thisMachineID, readOnly: TRUE, boxWidth: 100]; 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; Stats: FormSW.ProcType = BEGIN firstNetwork: Driver.Network ¬ Driver.GetDeviceChain[]; FOR network: Driver.Network ¬ firstNetwork, network.next UNTIL network = NIL DO IF UserInput.UserAbort[log] THEN EXIT; SELECT network.device FROM ethernet, ethernetOne => BEGIN pup: PupRouterDefs.NetworkContext ¬ Protocol1.GetContext[network, pup]; ns: RoutingTable.NetworkContext ¬ Protocol1.GetContext[network, ns]; stats: LONG POINTER TO EthernetDriverFriends.EtherStatsInfo ¬ network.stats; Put.Text[log, "Ethernet"L]; IF network.device = ethernetOne THEN Put.Text[log, "One"L]; Put.Text[log, " Statistics for "L]; IF pup # NIL THEN { Put.Text[log, " Pup "L]; WriteNumber[pup.pupNetNumber, 8, 0]; Put.Text[log, "#"L]; WriteNumber[pup.pupHostNumber, 8, 0]; Put.Text[log, "#, NS net "L]; }; IF ns # NIL THEN WriteNetNumbers[ns.netNumber]; Put.Line[log, "."L]; IF stats = NIL THEN BEGIN Put.Line[log, "*** NO DRIVER STATS ***"L]; LOOP; END; Put.Text[log, " Rcv: pkts "L]; Put.LongDecimal[log, stats.packetsRecv]; Put.Text[log, ", words "L]; Put.LongDecimal[log, stats.wordsRecv]; Put.Text[log, ", bad "L]; Put.LongDecimal[log, stats.badRecvStatus]; Put.Text[log, ", missed "L]; Put.LongDecimal[log, stats.packetsMissed]; IF stats.idleInput # 0 THEN BEGIN Put.Text[log, ", idle "L]; Put.LongDecimal[log, stats.idleInput]; END; Put.CR[log ]; IF stats.badRecvStatus # 0 OR stats.okButDribble # 0 THEN BEGIN Put.Text[log, " crc "L]; Put.LongDecimal[log, stats.badCrc]; Put.Text[log, ", bad alignment but ok crc "L]; Put.LongDecimal[log, stats.badAlignmentButOkCrc]; Put.Text[log, ", crc and bad alignment "L]; Put.LongDecimal[log, stats.crcAndBadAlignment]; Put.CR[log ]; Put.Text[log, " ok but dribble "L]; Put.LongDecimal[log, stats.okButDribble]; Put.Text[log, ", too long "L]; Put.LongDecimal[log, stats.packetTooLong]; Put.Text[log, ", overrun "L]; Put.LongDecimal[log, stats.overrun]; Put.CR[log ]; END; Put.Text[log, " Xmit: pkts "L]; Put.LongDecimal[log, stats.packetsSent]; Put.Text[log, ", words "L]; Put.LongDecimal[log, stats.wordsSent]; Put.Text[log, ", bad "L]; Put.LongDecimal[log, stats.badSendStatus]; Put.CR[log ]; IF stats.stuckOutput # 0 OR stats.badSendStatus # 0 OR stats.tooManyCollisions # 0 THEN BEGIN Put.Text[log, " underrun "L]; Put.LongDecimal[log, stats.underrun]; Put.Text[log, ", stuck "L]; Put.LongDecimal[log, stats.stuckOutput]; Put.Text[log, ", too many collisions "L]; Put.LongDecimal[log, stats.tooManyCollisions]; Put.CR[log ]; END; Put.Text[log, " Lds:"L]; FOR i: CARDINAL IN [0..16) DO Put.Char[log, ' ]; Put.LongDecimal[log, stats.loadTable[i]]; ENDLOOP; Put.CR[log ]; END; phonenet => BEGIN pup: PupRouterDefs.NetworkContext ¬ Protocol1.GetContext[network, pup]; ns: RoutingTable.NetworkContext ¬ Protocol1.GetContext[network, ns]; stats: PhoneNetFriends.PhoneNetInfo = PhoneNetFriends.StatsPtrToStats[network.stats]; Put.Text[log, "PhoneNet Statistics for "L]; IF pup # NIL THEN { Put.Text[log, "Pup "L]; WriteNumber[pup.pupNetNumber, 8, 0]; Put.Text[log, "#"L]; WriteNumber[pup.pupHostNumber, 8, 0]; }; IF ns # NIL THEN { Put.Text[log, "#, NS net "L]; WriteNetNumbers[ns.netNumber]; }; Put.Text[log, ", Line "L]; Put.LongDecimal[log, stats.lineNumber]; Put.Text[log, ", "L]; Put.LongDecimal[log, stats.speed]; Put.Text[log, "KB"L]; SELECT stats.remoteHostNumber FROM System.nullHostNumber => Put.Text[log, ", Down"L]; System.localHostNumber => Put.Text[log, ", Looped"L]; ENDCASE => { Put.CR[log ]; Put.Text[log, " Up to "L]; WriteHostNumbers[stats.remoteHostNumber]; }; Put.CR[log ]; Put.Text[log, " Recv: pkts "L]; Put.LongDecimal[log, stats.stats[pktsReceived]]; Put.Text[log, ", bytes "L]; Put.LongDecimal[log, stats.stats[bytesReceived]]; Put.Text[log, ", rejected "L]; Put.LongDecimal[log, stats.stats[pktsRejected]]; Put.Text[log, ", missed "L]; Put.LongDecimal[log, stats.stats[rcvErrorNoGet]]; Put.Text[log, ", idle "L]; Put.LongDecimal[log, stats.stats[tooLongSinceLastReceive]]; Put.CR[log ]; Put.Text[log, " Bad crc "L]; Put.LongDecimal[log, stats.stats[rcvErrorCRC]]; Put.Text[log, ", data lost "L]; Put.LongDecimal[log, stats.stats[rcvErrorDataLost]]; Put.Text[log, ", device error "L]; Put.LongDecimal[log, stats.stats[rcvDeviceError]]; Put.Text[log, ", timeout "L]; Put.LongDecimal[log, stats.stats[rcvErrorFrameTimeout]]; Put.Text[log, ", other error "L]; Put.LongDecimal[log, stats.stats[rcvErrorUnknown]]; Put.CR[log ]; Put.Text[log, " Send: pkts "L]; Put.LongDecimal[log, stats.stats[pktsSent]]; Put.Text[log, ", bytes "L]; Put.LongDecimal[log, stats.stats[bytesSent]]; Put.CR[log ]; Put.Text[log, " NS "L]; Put.LongDecimal[log, stats.stats[nsSent]]; Put.Text[log, " "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.nsBytesSend]]; Put.Text[log, ", Pups "L]; Put.LongDecimal[log, stats.stats[pupSent]]; Put.Text[log, " "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.pupBytesSend]]; Put.Text[log, ", Leaf "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.leafPktsSend]]; Put.Text[log, " "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.leafBytesSend]]; Put.CR[log ]; IF stats.stats[sendErrorBadStatus] # 0 OR stats.stats[queueTooOld] # 0 THEN { Put.Text[log, " Bad "L]; Put.LongDecimal[log, stats.stats[sendErrorBadStatus]]; Put.Text[log, ", stuck "L]; Put.LongDecimal[log, stats.stats[queueTooOld]]; Put.CR[log ]; }; Put.Text[log, " Queue too long "L]; Put.LongDecimal[log, stats.stats[congestion]]; Put.Text[log, ", NS "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.nsCongestion]]; Put.Text[log, ", Pup "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.pupCongestion]]; Put.CR[log ]; Put.Text[log, " Conn too greedy "L]; Put.LongDecimal[log, stats.stats[connTooGreedy]]; Put.Text[log, ", NS "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.nsTooGreedy]]; Put.Text[log, ", Pup "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.pupTooGreedy]]; Put.CR[log ]; IF stats.stats[PhoneNetExtras.nsDupsFiltered] # 0 THEN { Put.Text[log, " NS duplicates discarded: "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.nsDupsFiltered]]; Put.CR[log ]; }; IF stats.stats[PhoneNetExtras.pupDupsFiltered] # 0 THEN { Put.Text[log, " BSP probes/duplicates discarded: "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.pupDupsFiltered]]; Put.CR[log ]; }; IF stats.stats[PhoneNetExtras.leafDupsFiltered] # 0 THEN { Put.Text[log, " Leaf duplicates discarded: "L]; Put.LongDecimal[log, stats.stats[PhoneNetExtras.leafDupsFiltered]]; Put.CR[log ]; }; END; ENDCASE => NULL; ENDLOOP; END; WriteHostNumbers: PROCEDURE [net: System.HostNumber] = BEGIN Push: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] = BEGIN Put.Text[log, s]; END; Format.HostNumber[Push, net, productSoftware]; Put.Text[log, "="L]; Format.HostNumber[Push, net, octal]; END; WriteNetNumbers: PROCEDURE [net: System.NetworkNumber] = BEGIN Push: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] = BEGIN Put.Text[log, s]; END; Format.NetworkNumber[Push, net, productSoftware]; Put.Text[log, "="L]; Format.NetworkNumber[Push, net, octal]; 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 words."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[ socket: System.nullSocketNumber, 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[socket: System.nullSocketNumber, 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...