-- File: BSPTestTool.mesa - last edit:
-- WIrish               5-Feb-88 12:03:46
-- HGM                 25-Jun-85  3:14:48
-- Copyright (C) 1983, 1984, 1985, 1988 by Xerox Corporation. All rights reserved. 
DIRECTORY
  FormSW USING [
    ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, Display,
    FindItem, CommandItem, BooleanItem, StringItem, NumberItem],
  Heap USING [systemZone],
  MsgSW USING [Post],
  Process USING [Detach, Yield],
  Put USING [Char, CR, Text, Line, LongDecimal],
  Runtime USING [GetBcdTime],
  String USING [AppendString],
  System USING [Pulses, GetClockPulses, PulsesToMicroseconds],
  Time USING [Append, AppendCurrent, Current, Unpack],
  Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, MakeFileSW],
  ToolWindow USING [TransitionProcType],
  Window USING [Handle],

  Stream USING [Handle, PutWord, PutBlock, SendNow, Delete],
  PupDefs USING [
    AppendPupAddress, GetPupAddress, PupNameTrouble, PupPackageMake,
    PupPackageDestroy, SecondsToTocks],
  PupStream USING [PupByteStreamCreate, StreamClosing, CloseReason],
  PupTypes USING [PupAddress, bspTestSoc];

BSPTestTool: PROGRAM
  IMPORTS
    FormSW, Heap, MsgSW, Process, Put, Runtime, String, System, Time, Tool, Stream,
    PupDefs, PupStream =
  BEGIN OPEN PupStream, PupTypes;

  z: UNCOUNTED ZONE = Heap.systemZone;

  defualtClumpLength: CARDINAL = 1000;  -- words
  defualtNumberOfClumps: CARDINAL = 1000;  -- 1 mega word

  msg, form, log: Window.Handle;

  -- Be sure to initialize it when it is allocated!!!!
  data: LONG POINTER TO Data ← NIL;  -- NIL when we are inactive

  Data: TYPE = RECORD [
    where: PupAddress ← [[0], [0], PupTypes.bspTestSoc],
    clumpLength: CARDINAL ← defualtClumpLength,
    numberOfClumps: CARDINAL ← defualtNumberOfClumps,
    pleaseStop: BOOLEAN ← FALSE,
    alignTheFink0: WORD ← NULL,
    running: BOOLEAN ← FALSE,
    alignTheFink1: WORD ← NULL,
    verbose: BOOLEAN ← FALSE,
    alignTheFink2: WORD ← NULL,
    sendForever: BOOLEAN ← FALSE,
    alignTheFink3: WORD ← NULL,
    forceOutClumps: BOOLEAN ← FALSE,
    alignTheFink4: WORD ← NULL,
    sendFirstWord: BOOLEAN ← FALSE,
    target: LONG STRING ← NULL];


  Init: PROCEDURE =
    BEGIN
    herald: LONG STRING = [100];
    String.AppendString[herald, "BSP Tester of "L];
    Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    END;

  BSPTestOn: PROCEDURE =
    BEGIN
    WriteCR[];
    IF data.clumpLength ~IN [2..10000) THEN
      BEGIN
      MsgSW.Post[msg, "ClumpLength should be between 2 and 10000."L];
      RETURN;
      END;
    WriteCurrentDateAndTime[];
    WriteString["  Sending to "L];
    IF ~FindPath[] THEN RETURN;
    data.running ← TRUE;
    UpdatePicture[];
    Process.Detach[FORK Push[]];
    END;

  BSPTestOff: PROCEDURE =
    BEGIN
    data.pleaseStop ← TRUE;
    WHILE data.running DO Process.Yield[]; ENDLOOP;
    data.pleaseStop ← FALSE;
    UpdatePicture[];
    END;

  Finished: PROCEDURE =
    BEGIN data.running ← FALSE; IF ~data.pleaseStop THEN UpdatePicture[]; END;

  UpdatePicture: PROCEDURE =
    BEGIN
    FormSW.FindItem[form, startIX].flags.invisible ← data.running;
    FormSW.FindItem[form, stopIX].flags.invisible ← ~data.running;
    FormSW.FindItem[form, clumpIX].flags.readOnly ← data.running;
    FormSW.Display[form];
    END;

  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN OPEN data;
    WriteString[target];
    WriteChar['=];
    PupDefs.GetPupAddress[
      @where, target !
      PupDefs.PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    PrintPupAddress[where];
    WriteLine["."L];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  Push: PROCEDURE =
    BEGIN OPEN data;
    sh: Stream.Handle;
    start, stop: LONG CARDINAL;
    words: LONG CARDINAL ← 0;
    recent: System.Pulses;
    startOpen, endOpen, startFirstSend, endFirstSend, startClose, endClose:
      System.Pulses;
    Buffer: TYPE = RECORD [SEQUENCE COMPUTED CARDINAL OF CARDINAL];
    buffer: LONG POINTER;
    hits: CARDINAL ← 0;
    info: ARRAY CloseReason OF STRING = [
      localClose: "Closed locally"L, localAbort: "Local Abort"L,
      remoteClose: "Remote close"L, noRouteToNetwork: "No route to that network"L,
      transmissionTimeout: "Transmission timeout"L,
      remoteReject: "Remote reject"L];
    WriteString["Clump size is "L];
    WriteLongDecimal[clumpLength];
    WriteLine[" words."L];
    start ← Time.Current[];
    startOpen ← System.GetClockPulses[];
    sh ← PupByteStreamCreate[
      where, PupDefs.SecondsToTocks[10] !
      StreamClosing =>
        BEGIN
        WriteString["Connection failed: "L];
        IF text # NIL THEN
          BEGIN WriteChar['(]; WriteString[text]; WriteString[") "L]; END;
        WriteLine[info[why]];
        GOTO Failed;
        END];
    endOpen ← System.GetClockPulses[];
    IF sendFirstWord THEN
      BEGIN
      ENABLE StreamClosing => CONTINUE;
      startFirstSend ← System.GetClockPulses[];
      Stream.PutWord[sh, 0];
      words ← words + 1;
      endFirstSend ← System.GetClockPulses[];
      END;
    recent ← System.GetClockPulses[];
    buffer ← z.NEW[Buffer[clumpLength]];
    FOR clumps: CARDINAL ← 0, clumps + 1 UNTIL pleaseStop
      OR (~sendForever AND clumps >= data.numberOfClumps) DO
      Stream.PutBlock[
        sh, [buffer, 0, 2*clumpLength], forceOutClumps !
        StreamClosing =>
          BEGIN
          WriteString["Push Stream closed: "L];
          IF text # NIL THEN
            BEGIN WriteChar['(]; WriteString[text]; WriteString[") "L]; END;
          WriteLine[info[why]];
          GOTO Closed;
          END];
      words ← words + clumpLength;
      IF verbose THEN
        BEGIN
        WriteChar['!];
        IF ((hits ← hits + 1) MOD 50) = 0 THEN
          BEGIN
          ms: LONG CARDINAL;
          -- Oh shit.  This overflows at a clumpLength of 20000 wrods.
          fudge: LONG CARDINAL = 100*LONG[16*50];
          ms ←
            System.PulsesToMicroseconds[[System.GetClockPulses[] - recent]]/10000;
          WriteString["  "L];
          WriteLongDecimal[(fudge*clumpLength)/ms];
          WriteLine[" bits/sec"L];
          recent ← System.GetClockPulses[];
          END;
        END;
      REPEAT Closed => NULL;
      ENDLOOP;
    startClose ← System.GetClockPulses[];
    Stream.SendNow[sh ! StreamClosing => CONTINUE];
    Stream.Delete[sh];
    endClose ← System.GetClockPulses[];
    stop ← Time.Current[];
    z.FREE[@buffer];
    WriteCR[];
    WriteLongDecimal[words];
    WriteLine[" words sent."L];
    IF stop - start > 100 OR words > 100000 THEN
      BEGIN WriteLongDecimal[16*words/(stop - start)]; END
    ELSE
      BEGIN
      t: System.Pulses ← [endClose - startOpen];
      WriteLongDecimal[16*words*1000/(System.PulsesToMicroseconds[t]/1000)];
      END;
    WriteLine[" data bits per second."L];
    PrintTiming["Open"L, endOpen, startOpen];
    IF sendFirstWord THEN PrintTiming["First Ack"L, endFirstSend, startFirstSend];
    PrintTiming["Close"L, endClose, startClose];
    IF words < 5000000 THEN PrintTiming["Total"L, endClose, startOpen];
    WriteCR[];
    Finished[];
    EXITS Failed => Finished[];
    END;

  -- IO things

  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;

  WriteLongDecimal: PROCEDURE [n: LONG CARDINAL] =
    BEGIN Put.LongDecimal[log, n]; END;

  WriteCurrentDateAndTime: PROCEDURE =
    BEGIN time: STRING = [20]; Time.AppendCurrent[time]; WriteString[time]; END;

  PrintPupAddress: PROCEDURE [a: PupAddress] =
    BEGIN
    temp: STRING = [40];
    PupDefs.AppendPupAddress[temp, a];
    WriteString[temp];
    END;

  PrintTiming: PROCEDURE [s: LONG STRING, end, start: System.Pulses] =
    BEGIN
    t: System.Pulses ← [end - start];
    WriteString[s];
    WriteString[" took "L];
    WriteLongDecimal[System.PulsesToMicroseconds[t]/1000];
    WriteLine[" ms."L];
    END;

  Start: FormSW.ProcType = BEGIN BSPTestOn[]; END;

  Stop: FormSW.ProcType = BEGIN BSPTestOff[]; END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    log ← Tool.MakeFileSW[window: window, name: "BSPTest.log$"L, allowTypeIn: FALSE];
    END;

  startIX: CARDINAL = 0;
  stopIX: CARDINAL = 1;
  clumpIX: CARDINAL = 4;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 10;
    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: "Running"L, switch: @data.running, readOnly: TRUE];
    items[3] ← FormSW.BooleanItem[tag: "Verbose"L, switch: @data.verbose];
    items[4] ← FormSW.BooleanItem[tag: "SendForever"L, switch: @data.sendForever];
    items[5] ← FormSW.BooleanItem[
      tag: "SendFirstWord"L, switch: @data.sendFirstWord];
    items[6] ← FormSW.BooleanItem[
      tag: "ForceOutClumps"L, switch: @data.forceOutClumps];
    items[7] ← FormSW.NumberItem[
      tag: "ClumpLength"L, value: @data.clumpLength, place: FormSW.newLine];
    items[8] ← FormSW.NumberItem[
      tag: "NumberOfClumps"L, value: @data.numberOfClumps, place: FormSW.newLine];
    items[9] ← FormSW.StringItem[
      tag: "Target"L, string: @data.target, place: FormSW.newLine, inHeap: TRUE];
    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 ← z.NEW[Data];
        data↑ ← [];
        data.target ← z.NEW[StringBody[20]];
        String.AppendString[data.target, "ME"L];
        [] ← PupDefs.PupPackageMake[];
        END;
      new = inactive =>
        BEGIN
        IF data = NIL THEN ERROR NotActive;
        IF data.running THEN BSPTestOff[];
        PupDefs.PupPackageDestroy[];
        z.FREE[@data.target];
        z.FREE[@data];
        END;
      ENDCASE;
    END;

  -- Main Body

  Init[];
  END.