QH ~ NETTEST_KIT.Ay NETTEST_KIT.ABACKUP *.* NETTEST_KIT.A/SAV U_SMITH xp,0ܖV5.5 _LOGIC::  _$1$DUA15: V5.5-2 ~  %*[U_SMITH.NETTEST.TEST2]A_README.TXT;4+,H./ 4P~-LINK_NETTEST.COM - command procedure link the NETTEST program >MASTER.B32 - NETTEST routines for master side of communication0NETMAN.B32 - NETTEST network management routinesDNETTEST$COMMAND_TABLE.CLD - NETTEST command language definition file"NETTEST.B32 - NETTEST main routineENETTEST.COM - NETTEST command procedure invoked when the NETTEST link is initiatedFNETTEST.DOC - NETTEST documentation on the command language definition$NETTEST_DEF.SDL - NETTEST structuresJREMOTE.B32 - NETTEST routines for remote and router sides of communicationLRUN_NETTEST.COM - command procedure that must be invoked from your login.comM to allow NETTEST to run properly on the remote system (setsD up the NETTEST logical names and the NETTEST CLD).CVERSION.OBJ - object file containing current NETTEST version number2WRITER.B32 - NETTEST routines for sending messages#To set up NETTEST do the following:Bo remove the files from the save set into the directory you intend$ to keep them Co if this is not SYS$LOGIN, you will have to update RUN_NETTEST.COM) to point to the location of the files Co compile the files using the BUILD_CLEAN_NETTEST command procedure?o link the files using the LINK_CLEAN_NETTEST command procedure5o add the invocation of RUN_NETTEST to your login.comMo set up default proxies between the systems on which you are running NETTESTFo make sure the account NETTEST will be using has a sufficiently largeJ working set. I generally give it a quota of 5000 and an extent of 7000.Fo use BASE_NETTEST to execute a series of NETTEST runs. BASE_NETTEST G allows you to vary the number of receive buffers, the pipeline quota,D and the queue depth. you can vary the message size by editing the command procedure.0*[U_SMITH.NETTEST.TEST2]BUILD_CLEAN_NETTEST.COM;4+,pw ./ 4 -);2 cpu_data_ptr = .(smp$gl_cpu_data + (.cpu * 4 ));5 stats[rstats$l_kernel] = .stats[rstats$l_kernel] + $ .(cpu_data_ptr[cpu$l_kernel]);1 stats[rstats$l_exec] = .stats[rstats$l_exec] + $ .(cpu_data_ptr[cpu$l_kernel]+4*1);3 stats[rstats$l_super] = .stats[rstats$l_super] + $ .(cpu_data_ptr[cpu$l_kernel]+4*2);1 stats[rstats$l_user] = .stats[rstats$l_user] + & .(cpu_data_ptr[cpu$l_kernel]+4*3);; stats[rstats$l_interrupt] = .stats[rstats$l_interrupt] + & .(cpu_data_ptr[cpu$l_kernel]+4*4);* @! stats[rstats$l_kernel_spin] = .stats[rstats$l_kernel_spin] + '! .(cpu_data_ptr[cpu$l_kernel]+4*6);1 stats[rstats$l_null] = .stats[rstats$l_null] + ! .(cpu_data_ptr[cpu$l_nullcpu]);' number_of_cpus = .number_of_cpus + 1; END; RETURN .number_of_cpus;;END; %fi <GLOBAL ROUTINE difference_stats( a_start, a_end, a_delta ) =!++!! FUNCTIONAL DESCRIPTION:!E! Computes the difference between the starting and ending statistics,0! and places the results in the delta structure.!! FORMAL PARAMETERS:!)! The addresses of the rstats structures.!! IMPLICIT INPUTS:!! IMPLICIT OUTPUTS:!! SIDE EFFECTS:!!--BEGINBUILTINsubm;LOCALstart: ref $bblock,fini: ref $bblock,delta: ref $bblock;start = .a_start;fini = .a_end;delta = .a_delta;Odelta[rstats$l_kernel] = .fini[rstats$l_kernel] - .start[rstats$l_kernel];Idelta[rstats$l_exec] = .fini[rstats$l_exec] - .start[rstats$l_exec];Ldelta[rstats$l_super] = .fini[rstats$l_super] - .start[rstats$l_super];Idelta[rstats$l_user] = .fini[rstats$l_user] - .start[rstats$l_user];Vdelta[rstats$l_interrupt] = .fini[rstats$l_interrupt] - .start[rstats$l_interrupt];Idelta[rstats$l_null] = .fini[rstats$l_null] - .start[rstats$l_null];Xdelta[rstats$l_process_cpu] = .fini[rstats$l_process_cpu]- .start[rstats$l_process_cpu];Xdelta[rstats$l_page_faults] = .fini[rstats$l_page_faults]- .start[rstats$l_page_faults];Xdelta[rstats$l_rcv_packets] = .fini[rstats$l_rcv_packets]- .start[rstats$l_rcv_packets];Xdelta[rstats$l_snd_packets] = .fini[rstats$l_snd_packets]- .start[rstats$l_snd_packets];Xdelta[rstats$l_rcv_buffail] = .fini[rstats$l_rcv_buffail]- .start[rstats$l_rcv_buffail];F!! Compute elapsed wall clock time in delta format (for printing out).T!subm(2, fini[rstats$q_systime], start[rstats$q_systime], delta[rstats$q_systime] );L! Compute elapsed wall clock time in standard format. For experiments whichO! aren't excessively long, the low-order longword will contain the elapsed time+! and the high-order longword will be zero.9subm(2, start[rstats$q_systime], ! delta = .fini - .start fini[rstats$q_systime], delta[rstats$q_systime] );F! Routers use the Operations count differently than the reader/writer.6if .start[rstats$l_NodeType] EQL node$type_Router then delta[rstats$L_Operations] =; .fini[rstats$L_Operations] - .start[rstats$L_Operations]else9 delta[rstats$L_Operations] = .fini[rstats$L_Operations];P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*!!!!!!!!!!!!!!!!!!!!!!!!!!B! Copy the other stuff in the rstats structure from fini to delta.@delta[rstats$L_NodeNameLength] = .fini[rstats$L_NodeNameLength];Nch$move(rstats$S_NodeName, fini[rstats$T_NodeName], delta[rstats$T_NodeName]);4delta[rstats$L_NodeType] = .fini[rstats$L_NodeType];Kch$move(rstats$S_CpuType, fini[rstats$T_CpuType], delta[rstats$T_CpuType]);Fdelta[rstats$L_VMSversion_Length] = .fini[rstats$L_VMSversion_Length];7ch$move(rstats$S_VMSversion, fini[rstats$T_VMSversion],# delta[rstats$T_VMSversion]);Ldelta[rstats$L_DECnetVersion_Length] = .fini[rstats$L_DECnetVersion_Length];>ch$move(rstats$S_DECnetVersion, fini[rstats$T_DECnetVersion],# delta[rstats$T_DECnetVersion]);4delta[rstats$L_Msg_Size] = .fini[rstats$L_Msg_Size];8delta[rstats$L_Queue_Size] = .fini[rstats$L_Queue_Size];<delta[rstats$L_Segment_Size] = .fini[rstats$L_Segment_Size];>delta[rstats$L_PipelineQuota] = .fini[rstats$L_PipelineQuota];@delta[rstats$L_ExecBufferSize] = .fini[rstats$L_ExecBufferSize];Bdelta[rstats$L_LineRecvBuffers] = .fini[rstats$L_LineRecvBuffers];-delta[rstats$W_CPUs] = .fini[rstats$W_CPUs];@delta[rstats$L_Circuit_Length] = .fini[rstats$L_Circuit_Length];Kch$move(rstats$S_Circuit, fini[rstats$T_Circuit], delta[rstats$T_Circuit]);return ss$_normal;end; CGLOBAL ROUTINE cvt_f_to ( f_number, l_width, l_digits, out_desc ) =!++!! FUNCTIONAL DESCRIPTION:!A! Converts an F_FLOAT to a text string, with specified precision.!! FORMAL PARAMETERS:! ! f_number! TYPE: F FLOATING (LONGWORD)! ACCESS: READ ONLY! MECHANISM: BY VALUE+! DESCRIPTION: The number to be converted.! Number must be positive.! ! l_width! TYPE: LONGWORD! ACCESS: READ ONLY! MECHANISM: BY VALUE3! DESCRIPTION: Desired width of the output string.-! The real limit is the size of the buffer! defined by out_desc.! ! l_digits! TYPE: LONGWORD! ACCESS: READ ONLY! MECHANISM: BY VALUE4! DESCRIPTION: Number of digits to the right of the(! decimal point in the output string.! ! out_desc! TYPE: CHARACTER STRING! ACCESS: WRITE ONLY! MECHANISM: BY DESCRIPTOR6! DESCRIPTION: Address of the string descriptor where ! the results will be placed.!! IMPLICIT INPUTS:!! IMPLICIT OUTPUTS:!! SIDE EFFECTS:!P!-------------------------------------------------------------------------------BEGINBIND" fao_format1 = %ASCID '!#SL.!#ZL',! fao_format2 = %ASCID '!SL.!#ZL';BUILTINcvtlf,cvtrfl,mulf;LOCALl_integer_width,l_tmp,l_tmp2, l_integer,l_frac, l_magnitude,f_tmp,f_ten,l_fao_prms: vector[4],status; J!-------------------------------------------------------------------------%! Shift f_number over l_digit places.:l_magnitude = OTS$POWJJ( 10, .l_digits ); ! 10 ^ .l_digits<cvtlf(l_magnitude, f_tmp); ! f_tmp = FLOAT(10 ^ .l_digits)7mulf(f_number, f_tmp, f_tmp); ! Shift l_digits places1cvtrfl( f_tmp, l_tmp ); ! l_tmp = NINT( f_tmp )Kl_integer = .l_tmp / .l_magnitude; ! l_integer = INT( f_number ) (sort of)#l_tmp2 = .l_integer * .l_magnitude;Jl_frac = .l_tmp - .l_tmp2; ! l_frac = FRAC( f_number ) * (10 ^ .l_digits)J!-------------------------------------------------------------------------F! Now figure out if the width can be limited the way the caller wants.I! If not, then no big deal, just do a different faol call which allows it:! to use as much space as it needs, up to available space.+l_integer_width = .l_width - .l_digits - 1;0l_magnitude = OTS$POWJJ( 10, .l_integer_width );)IF .l_magnitude GTR .l_integer THEN BEGIN " l_fao_prms[0] = .l_integer_width; l_fao_prms[1] = .l_integer; l_fao_prms[2] = .l_digits; l_fao_prms[3] = .l_frac; & status = $faol( ctrstr = fao_format1, outlen = .out_desc, outbuf = .out_desc, prmlst = l_fao_prms ); check_status;END ELSE BEGIN l_fao_prms[0] = .l_integer; l_fao_prms[1] = .l_digits; l_fao_prms[2] = .l_frac; & status = $faol( ctrstr = fao_format2, outlen = .out_desc, outbuf = .out_desc, prmlst = l_fao_prms );N check_status;END;return ss$_normal;end; -4GLOBAL ROUTINE read_sequence_ace( l_file_channel ) =!++-!-! FUNCTIONAL DESCRIPTION:H!C>! Reads and increments the sequence number of the output file.)! This is stored in an informational ace.A!C! FORMAL PARAMETERS:! $! The io channel to the output file.! ! IMPLICIT INPUTS:!S! IMPLICIT OUTPUTS:L!O! SIDE EFFECTS:P!G!--TBEGINHMACROR setup_ace =RP ! This ace is defined because $change_acl currently tromps over at least theL ! first longword during acl$c_fndacetyp (maybe only if it doesn't find aL ! matching ace). To take paranoia to the limits, I'll rebuild the whole ! thing. begin ' sequence_ace[ace$b_size] = k_ace_size;L' sequence_ace[ace$b_type] = ace$c_info; 4 sequence_ace[ace$w_flags] = 0; ! First set to zeroC sequence_ace[ace$v_info_type] = ace$c_vms; ! Now mark as a VMS aceI sequence_ace[ace$v_nopropagate] = 1; ! Don't copy this to new versions.-G sequence_ace[ace$l_info_flags] = 206 ^ 16; ! MONITOR's facility number- ! (shifted 16 bits).P end%,"my_ace$l_sequence = 8, 0, 32, 0 %;BIND:k_ace_size = 12; ! Need a better way to say 2 longwords of- ! VMS defined overhead and one applicationa ! specific longwordLOCALn%acl_items: $bblock[ 2 * itm$s_item ], itm2: ref $bblock,v l_context, l_sequence, "sequence_ace: $bblock[k_ace_size],status;rP! First set up the informational ace which we use to storing the sequence number setup_ace;5! Now prepare the item list we pass to sys$change_acli%acl_items[itm$w_bufsiz] = k_ace_size; *acl_items[itm$w_itmcod] = acl$c_fndacetyp;'acl_items[itm$l_bufadr] = sequence_ace; acl_items[itm$l_retlen] = 0; ! Mark the end of the item list.itm2 = acl_items + itm$s_item;itm2[itm$w_bufsiz] = 0;Eitm2[itm$w_itmcod] = 0;NM! First see if $change_acl can find an ace of this type. If so, then it wille-! be written into sequence_ace automagically.gl_context = 0;-status = $change_acl( chan = .l_file_channel,s objtyp = %ref( acl$c_file ),! itmlst = acl_items, contxt = l_contextA );Eif (.status EQL ss$_noentry) OR (.status EQL ss$_aclempty) then beginp> ! Must be the first use of this file to store nettest output. ! Stuff the ace into the acl. setup_ace; D sequence_ace[my_ace$l_sequence] = 0; ! First value if for new files e+ acl_items[itm$w_itmcod] = acl$c_addaclent;f. status = $change_acl( chan = .l_file_channel, objtyp = %ref( acl$c_file ), itmlst = acl_items,e contxt = l_context );o check_status;endc)else if .status EQL ss$_normal then beginb9 ! Now increment the sequence number and modify the ace. g setup_ace;fH sequence_ace[my_ace$l_sequence] = 1 + .sequence_ace[my_ace$l_sequence];+ acl_items[itm$w_itmcod] = acl$c_modaclent;i. status = $change_acl( chan = .l_file_channel, objtyp = %ref( acl$c_file ), itmlst = acl_items,c contxt = l_context );m check_status;endelse check_status;(return .sequence_ace[my_ace$l_sequence];end; EGLOBAL ROUTINE make_pattern( data: ref vector[65535, byte], l_offset,m& l_data_size, l_first_value ) =!++b!n! FUNCTIONAL DESCRIPTION:! D! Sets up a pattern in a data block so that if /CHECKDATA is enabledF! the reader can call check_pattern to check this pattern. It is used?! to check the 'integrity' of the entire data transfer process.t!r! FORMAL PARAMETERS:!e! IMPLICIT INPUTS:!L! IMPLICIT OUTPUTS:h!d! SIDE EFFECTS:t!t!--rBEGIN LOCALivalue: unsigned byte;value = .l_first_value;C8INCR entry FROM .l_offset to (.l_data_size - 1) DO BEGIN data[.entry] = .value;4 value = .value + 1;END;return ss$_normal;end; sFGLOBAL ROUTINE check_pattern( data: ref vector[65535, byte], l_offset,' l_data_size, l_first_value ) = !++,! ! FUNCTIONAL DESCRIPTION:i!2D! Checks the pattern in a data block. It has the same parameters asA! make_pattern, and returns ss$_normal if the pattern is correct.;'! Otherwise, ss$_datacheck is returned.v!i! FORMAL PARAMETERS:! ! IMPLICIT INPUTS:!_! IMPLICIT OUTPUTS:r!t! SIDE EFFECTS:s!]!--yBEGIN2LOCAL value: unsigned byte,estatus;gvalue = .l_first_value; 8INCR entry FROM .l_offset to (.l_data_size - 1) DO BEGIN7 IF .data[.entry] NEQ .value THEN RETURN ss$_datacheck;t value = .value + 1;END;RETURN ss$_normal;END; 2(GLOBAL ROUTINE request_link( a_net_desc, a_ncb_desc,N a_status,I a_dev_info ) =m!++ !_! FUNCTIONAL DESCRIPTION:!aJ! Assigns a channel to the network specified by NET_DESC (e.g. "_NET:" forI! DECnet-VAX or "_OS:" for VOTS), then request (or accept) a logical linkS ! connection.!o! FORMAL PARAMETERS:!t! IMPLICIT INPUTS:!n! IMPLICIT OUTPUTS:o!s! SIDE EFFECTS:t! P!-------------------------------------------------------------------------------BEGIN BUILTINdcallg;LOCALiio_sb: $bblock[8], l_channel;(! First assign a channel to the network.l_channel = 0;*.a_status = $ASSIGN( devnam = .a_net_desc, chan = l_channel  );T'if ..a_status NEQ ss$_normal then beginI3 print('Unable to open a channel to the network.');  lib$put_output(.a_net_desc); return 0;end;/! Now set up a logical link to the remote task.s%.a_status = $qiow( chan = .l_channel,l func = IO$_ACCESS,  iosb = io_sb, p2 = .a_ncb_descT );n'if ..a_status NEQ ss$_normal then beginb- print('Unable to establish a logical link');t return 0;end;N! Load the status from the iosb into status and check it. Exit if bad status.<! NEED TO ADD SOME MORE INTELLIGENT ERROR MESSAGE CODE HERE.A! Don't want to print ncb_desc because it may contain a password.a2.a_status = .io_sb[iosb$w_condition]; ! First word'if ..a_status NEQ ss$_normal then begin- print('Unable to establish a logical link');t return 0;end;J! DECnet-VAX has an undocumented feature: After an IO$_ACCESS, the deviceI! specific field of the IOSB is filled with the number of user data bytest! which will fit in a segment.8.a_dev_info = .io_sb[iosb$l_dev_info]; ! Second Longwordreturn .l_channel;end;ENDELUDOMstats$l_interrupt] = .rstats[rstats$l_interrupt] - .rstats[rstats$l_null];Iif .rstats[rstats$l_interrupt] LSS 0 then rstats[rstats$l_interrupt] = 0;%else8a_tmp = ..sch$gl_nullpcb; ! get null pcb address#*[U_SMITH.NETTEST.TEST2]COMMON.OBJ;3+,gw . / 4 --! data blocks allocated is doubled for Ping-Pong experiments.!=! Created a new routine called Create_IOB which contains the;! part of Init_Master that allocates IOBs and data blocks.!8! Master calls Write instead of handling things itself.!/! JMS0008 James M Synge 16-APR-19883! Extending to support Ping-Pong capabilities (the,! /LOOPBACK qualifier on the command line).!7! Creating the following routines (used to be just the?! single routine IO_COMPLETE): Write, Write_AST, and Read_AST.!2! Set_Params removed (functions placed in Write).!! Update copyright dates.!! X-1 JMS James M Synge! Placed under CMS control.!-- !! INCLUDE FILES!LIBRARY 'sys$library:lib.l32';REQUIRE 'nettest_def.r32';!! EXTERNAL ROUTINES!EXTERNAL ROUTINE8 collect_stats: addressing_mode(general), ! Time keeper2 make_pattern: addressing_mode(general), ! Artist8 lib$create_vm_zone: addressing_mode(general), ! virtual: lib$get_vm: addressing_mode(general), ! memory allocation3 lib$free_vm: addressing_mode(general), ! routines.7 sys$qio: addressing_mode(general), ! io system service8 sys$qiow: addressing_mode(general), ! io system service* lib$put_output: addressing_mode(general),6 lib$signal: addressing_mode(general); ! error handler!! FORWARD ROUTINE DECLARATIONS.!FORWARD ROUTINE init_master ,Create_IOB ,start_reader ,Write ,Write_AST ,Read_AST ,times_up ,experiment_done ,cleanup_master;!! EXTERNAL SYMBOLS! !EXTERNAL!! EXTERNAL LITERALS!!EXTERNAL LITERAL!! MACRO DEFINITIONS:!MACRO DEBUG_MSG(msg) = %if %switches(DEBUG) %then BEGIN LOCAL status;" EXTERNAL ROUTINE LIB$PUT_OUTPUT;0 status = LIB$PUT_OUTPUT( $DESCRIPTOR( msg ) ); check_status; END %fi%;MACRO DEBUG_DESC(msg) = %if %switches(DEBUG) %then BEGIN LOCAL status;" EXTERNAL ROUTINE LIB$PUT_OUTPUT;! status = LIB$PUT_OUTPUT( msg ); check_status; END %fi%;MACRO7 ! Output string to terminal. Ex: print('Hello...') print (string_to_print) =- lib$put_output($descriptor(string_to_print))%, check_status = begin if not .status then begin0 $setrwm( watflg=0 ); ! Enable resource waits lib$signal(.status); end; end; %;!! LITERAL DEFINITIONS!!LITERAL!! BIND CONSTANTS!!BINDP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! OWN STORAGE!OWN! Experiment Description:3gl_operations, ! Number of operations to perform,5gq_time_limit: $bblock[8], ! Or the time of the test.! Experiment flags.@gb_timed_expt: byte, ! Experiment has a time limit if non-zero.3gb_times_up: byte, ! Time has expired if non-zero.3gb_stop_queuing: byte, ! Stop queueing if non-zero.!g_reader_iob: $bblock[iob$s_iob],/gl_started_ops, ! Number of transfers started.2gl_completed_ops, ! Number of transfers completed.=gl_outstanding_ops, ! Number of transfers still in the queue.F! These gvars point to the Timing_Stats data structures where we store ! the results of the experiment.g_stats$start: ref $bblock,g_stats$end: ref $bblock,g_rstats$start: ref $bblock,g_rstats$end: ref $bblock; GLOBAL ROUTINE master() l_io_channel, l_msg_size, l_queue_size,+ l_timed_expt, a_expt_length, l_alignment, l_ping_pong,# a_w_rstats$start, a_w_rstats$end," a_r_rstats$start, a_r_rstats$end ) =!++!! FUNCTIONAL DESCRIPTION:!?! Main routine of the local node test. Prepares all the iob's,>! collects start statistics, runs the experiment by writing to?! the remote node, then collects end statistics and returns the! results to its caller.!! FORMAL PARAMETERS:!(! l_io_channel IO channel to the reader.0! l_msg_size Size of IOs to write to the reader.=! l_queue_size Maximum queue size. (Cann't control minimum.)C! l_timed_expt If true (non-zero), then this is a timed experiment.,! Else it will be for a set number of IOs.G! a_expt_length Based on the value of l_timed_expt, it points to either9! a quadword containing a VMS time, or a longword which+! specifies the number of IOs to perform.B! l_alignment Byte within a page on which we align the data block.F! l_ping_pong Boolean which controls whether a throughput (Write only)/! or a latency (ping-pong) test is performed.! a_w_rstats$start,E! a_w_rstats$end The address of Timing_Statistics data structures for:! the start and end timing data from the writer (local).! a_r_rstats$start,E! a_r_rstats$end The address of Timing_Statistics data structures for;! the start and end timing data from the reader (remote).! ! IMPLICIT INPUTS:!! NONE.!! IMPLICIT OUTPUTS:! ! Status.!! SIDE EFFECTS:!! NONE.P!-------------------------------------------------------------------------------BEGINBUILTINCALLG;LOCAL2a_queue, ! Pointer to the array of pointers which+ ! in turn point to the IOBs for each io. l_vm_zone_id,l_tmp: ref $bblock,<current_iob: ref $bblock, ! The block currently being used.1msg: ref $bblock, ! msg block following the iosb1a_current_entry, ! Ptr to current queue element.status;M! Record the nature of the experiment in global variables (global within this3! module) so that the ast routines can access them.gb_timed_expt = .l_timed_expt;!if .l_ti\med_expt NEQ 0 then begin+ ch$move(8, .a_expt_length, gq_time_limit); gl_operations = 0;end%else gl_operations = ..a_expt_length;"g_stats$start = .a_w_rstats$start;g_stats$end = .a_w_rstats$end;#g_rstats$start = .a_r_rstats$start;g_rstats$end = .a_r_rstats$end;.g_stats$end[rstats$l_msg_size] = .l_msg_size;1g_stats$end[rstats$l_queue_size] = .l_queue_size;0g_stats$end[rstats$l_alignment] = .l_alignment;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&! Perform experiment initialization. ! Set up the iobs.l_vm_zone_id = init_master(: g_reader_iob, .l_io_channel, .l_msg_size, .l_queue_size,% a_queue, .l_alignment, .l_ping_pong );2! Read the stats of the remote node synchronously.6status = start_reader(g_reader_iob, .a_r_rstats$start,1 .l_msg_size, .l_queue_size, .l_alignment); check_status;O! Disable ast's while we start off the experiment. Disable resource wait statesO! as well. The process must have sufficient resources. If there is a shortageN! of some resource, we don't want the fact appearing (invisibly) in the timing! info, so we will fail.2status = $setast( enbflg=0 ); ! Disable interrupts check_status;6status = $setrwm( watflg=1 ); ! Disable resource waits check_status;:! Initialize the variables used in the loop and elsewhere.3a_current_entry = .a_queue; ! First queue element..gl_started_ops = 0; ! Number of IOs started.gl_completed_ops = 0;gl_outstanding_ops = 0;;! Queue up a timer request to fire in /end seconds. If this0! qualifier was not specified, bag this section.!if .gb_timed_expt neq 0 thenbegin- status = $setimr( daytim = gq_time_limit, astadr = times_up ); check_status;end;I! Get experiment start statistics. Collect process cpu time at beginningH! of experiment, all of the modes counters, the null processes cpu time,-! and the current system quadword time stamp.4status = collect_stats(g_stats$start[Timing_Stats]); check_status;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!J! This loop queues the requested number of WRITEs (/QUEUE=x) to the DECnetO! channel. For the remainder of the experiment, the $QIOs will be initiated in! the AST threads.HWHILE (.gl_started_ops lss .l_queue_size) AND ! Don't overfill the queueG ((.gl_started_ops lss .gl_operations) OR ! And don't send too many7 (.gb_timed_expt neq 0)) ! Or in the timed case. DO BEGIN: ! a_current_entry points to the queue element to be used.! current_iob = ..a_current_entry; ! Do the Write." status = Write(current_iob[iob]); check_status; ! Find the next queue element.- a_current_entry = .a_current_entry + %UPVAL;END;K! Due to the way that $HIBER/$WAKE is implemented, it is necessary to clearI! any previous $WAKE that may have been issued. The way to do this is toN! issue a $WAKE (ensuring that a $WAKE has been issued), followed by a $HIBER.! This will clear the wake bit.status = $WAKE(); check_status;status = $HIBER; check_status;J! The initial i/o's have been issued. Enable ast's so the ast routine can! take over from here.status = $setast( enbflg=1 ); check_status;L! Wait here for experiment to end. $HIBER should not return until all ast'sJ! have occurred so that late errors can be detected before any results are! sent to the log file.status = $HIBER; check_status;5status = $SETRWM( watflg=0 ); ! Enable resource waits check_status;N! Due to the nature of DECnet-VAX task to task communication and the semanticsN! of $CANCEL, the reads which are pending on the slave node cannot be aborted.K! So the master must send off the correct number of messages to satisfy theL! slave. This is queue_size - 1. The writes are synchronous so that no ast! routine is needed.?current_iob = ..a_queue; ! Use the first of the allocated IOBs0current_iob[iob$a_astadr] = 0; ! No AST routineEmsg = .current_iob[iob$a_data]; ! Make sure that the message type is;msg[dmsg$b_type] = msg$type_Data; ! Data, not Ping or Poll.+INCR count FROM 2 to .l_queue_size DO BEGIN !print('master: excess write');8 status = callg( current_iob[iob$l_numargs], sys$qiow ); check_status;end;! Perform cleanup operations.1status = cleanup_master( .a_queue, .l_queue_size,0 .l_alignment, .l_vm_zone_id, .l_ping_pong ); check_status;! return to sender.!return ss$_normal;end; <ROUTINE init_master( a_reader_iob, l_io_channel, l_msg_size,+ l_queue_size, a_queue_base, l_alignment, l_ping_pong ) =!++!! FUNCTIONAL DESCRIPTION:!J! This routine allocates the iob's and data blocks for every queue element/! (two each if this is a ping-pong experiment).!! FORMAL PARAMETERS:!! IMPLICIT INPUTS:!! IMPLICIT OUTPUTS:!! SIDE EFFECTS:!P!-------------------------------------------------------------------------------BEGINOWNN! First time through a vm zone is created which is aligned on page boundaries. l_vm_zone_id: long initial( 0 );LOCALaddr_range: vector[2],size, iob_size, data_size,current_iob: ref $bblock,partner_iob: ref $bblock,reader_iob: ref $bblock,msg: ref $bblock, queue_count,queue: ref vector,status;L!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,! Set up the iob used to control the reader.reader_iob = .a_reader_iob;6reader_iob[iob$a_data] = reader_iob[iob] + iob$s_iob;reader_iob[iob$l_numargs] = 12;reader_iob[iob$l_efn] = 0;(reader_iob[iob$l_chan] = .l_io_channel;=reader_iob[iob$a_iosbadr] = reader_iob[iob$w_iosb_condition];reader_iob[iob$a_astadr] = 0;reader_iob[iob$l_astprm] = 0;2reader_iob[iob$l_p3] = 0; ! Need to specify these8reader_iob[iob$l_p4] = 0; ! optional parameters so that1reader_iob[iob$l_p5] = 0; ! qio doesn't blow up.reader_iob[iob$l_p6] = 0; 2! Allocate the queue block. (One longword per IOB)3Size = %UPVAL * .l_queue_size * (1 + .l_ping_pong);2status = LIB$GET_VM( size, ! Allocate .size bytes/ .a_queue_base ! And store away base address. ); check_status;queue = ..a_queue_base;L!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!H! For each element of the queue block, allocate an iob and the space for4! the data. We use a vm zone which is page aligned.!if .l_vm_zone_id EQL 0 then beginE status = lib$create_vm_zone( l_vm_zone_id, %ref(lib$k_vm_first_fit),0 %ref(0), %ref(0), %ref(128), %ref(128),) %ref(512), %ref(512), %ref(0) ); check_status;end;iob_size = iob$s_iob;'data_size = .l_msg_size + .l_alignment;.INCR qe FROM 0 TO (.l_queue_size - 1) DO BEGIN5! DEBUG_MSG('MASTER\INIT_MASTER: Allocating an IOB');= status = Create_IOB( queue[.qe], .l_io_channel, .l_msg_size,' .l_alignment, .l_vm_zone_id, .qe ); check_status;0! DEBUG_MSG('MASTER\INIT_MASTER: Allocated it'); current_iob = .queue[.qe]; msg = .current_iob[iob$a_data];* current_iob[iob$l_func] = IO$_WRITEVBLK;' current_iob[iob$a_astadr] = Write_AST; IF .l_ping_pong THEN BEGIN?! DEBUG_MSG('MASTER\INIT_MASTER: Allocating its Partner_IOB');# msg[dmsg$b_type] = MSG$Type_Ping;> ! Create the IOB used to do the reading of the Pong messages ! from the remote NETTEST.A status = Create_IOB( queue[.qe + .l_queue_size], .l_io_channel,5 .l_msg_size, .l_alignment, .l_vm_zone_id, .qe ); check_status; ' ! Point these two IOBs at each other.4 partner_iob = .queue[.qe + .l_queue_size];8 current_iob[iob$a_Partner_IOB] = partner_iob[0,0,0,0];8 partner_iob[iob$a_Partner_IOB] = current_iob[0,0,0,0];/ ! Set up the Pong IOB so that it is a Reader.* partner_iob[iob$l_func] = IO$_READVBLK;' partner_iob[iob$a_astadr] = Read_AST; END ELSE BEGIN< ! Set the message type for a simple through-put experiment. ! (not testing for latency as in Ping-Pong).# msg[dmsg$b_type] = MSG$Type_Data;& ! Note that there is no partner IOB.% current_iob[iob$a_Partner_IOB] = 0; END;END;"! Clear the experiment done flags.gb_stop_queuing = 0;gb_times_up = 0;! Return the id of the vm zonereturn .l_vm_zone_id;end; AROUTINE Create_IOB( a_iob, l_io_channel, l_msg_size, l_alignment, l_vm_zone_id, l_queue_entry ) =!++!! FUNCTIONAL DESCRIPTION:!I! This routine allocates an iob and it's data block, and does some of the! initialization of the block.!! FORMAL PARAMETERS:!! IMPLICIT INPUTS:!U! IMPLICIT OUTPUTS:E!=! SIDE EFFECTS:E!NP!-------------------------------------------------------------------------------BEGIN-LOCAL-addr_range: vector[2], iob_size,- data_size,current_iob: ref $bblock,Hmsg: ref $bblock,status;Iiob_size = iob$s_iob;O'data_size = .l_msg_size + .l_alignment;S! First allocate the IOB=status = LIB$GET_VM(iob_size, ! Number of bytes to allocate.R. .a_iob); ! Where to put addr of memory. check_status;Pcurrent_iob = ..a_iob;! Now allocate the data block.>status = LIB$GET_VM(data_size, ! Number of bytes to allocate.< current_iob[iob$a_data], ! Where to put addr of memory., l_vm_zone_id); ! Where to get it from. check_status;I*current_iob[iob$l_data_size] = .data_size;/! Now lock the data block into the working set.T)addr_range[0] = .current_iob[iob$a_data];S:addr_range[1] = .current_iob[iob$a_data] + .data_size - 1;'status = $lkwset( inadr = addr_range );L check_status;C! Adjust the data address so that it is offset from a page boundary-D! by .l_alignment bytes. Then put a pattern in the data that can be?! checked on the far end if /CHECKDATA is enabled. The patterneB! starts after the type and sequence numbers, then goes to the end! of the data buffer. Bcurrent_iob[iob$a_data] = .current_iob[iob$a_data] + .l_alignment;msg = .current_iob[iob$a_data];nSmake_pattern( msg[Data_Message], dmsg$S_Data_Message, .l_msg_size, l_queue_entry );P! Set up the IOB parameters used in communicating to the remote NETTEST process. current_iob[iob$l_numargs] = 12;current_iob[iob$l_efn] = 0;)current_iob[iob$l_chan] = .l_io_channel;h?current_iob[iob$a_iosbadr] = current_iob[iob$w_iosb_condition];i-current_iob[iob$l_astprm] = current_iob[iob];t2current_iob[iob$l_p1] = .current_iob[iob$a_data];%current_iob[iob$l_p2] = .l_msg_size;m3current_iob[iob$l_p3] = 0; ! Need to specify these 9current_iob[iob$l_p4] = 0; ! optional parameters so that_2current_iob[iob$l_p5] = 0; ! qio doesn't blow up.current_iob[iob$l_p6] = 0;f PRETURN SS$_NORMAL;END; NROUTINE start_reader(a_iob, a_rstats, l_msg_size, l_queue_size, l_alignment) =!++a!w! FUNCTIONAL DESCRIPTION:w!hE! This routine initializes the remote reader by synchronously writingc?! an I message and doing a synchronous read to get the results..!! FORMAL PARAMETERS:!g! a_iob ! TYPE: STRUCTUREt! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!A=! This is the address of an iob which should be all preparedn1! for use in communicating with the remote task. !C ! a_rstats! TYPE: STRUCTUREe! ACCESS: READ ONLYr! MECHANISM: BY REFERENCE!n@! Address of the rstats structure to be filled with the initial! data from the remote task.o!! IMPLICIT INPUTS:! ! IMPLICIT OUTPUTS:s!l! SIDE EFFECTS:!UP!-------------------------------------------------------------------------------BEGIN BUILTIN_callg;LOCALm)cmsg_I: $bblock[cmsg$s_Control_Message],ereader_iob: ref $bblock,status;nreader_iob = .a_iob;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!P! First send an Init message to the remote telling it to prepare to be a reader.(reader_iob[iob$l_func] = IO$_WRITEVBLK;reader_iob[iob$a_astadr] = 0;rreader_iob[iob$l_astprm] = 0;s8reader_iob[iob$l_p1] = cmsg_I; ! The control messageEreader_iob[iob$l_p2] = cmsg$s_Control_Message; ! Size of the messageO! Set up the control message%cmsg_I[cmsg$B_Type] = msg$type_Init;A'cmsg_I[cmsg$l_Msg_Size] = .l_msg_size;m*cmsg_I[cmsg$B_Queue_Size] = .l_queue_size;(cmsg_I[cmsg$B_Alignment] = .l_alignment;#! All set, so lets tell the reader.6status = callg( reader_iob[iob$l_numargs], sys$qiow ); check_status;BN! Load the status from the iosb into status and check it. Exit if bad status.+status = .reader_iob[iob$w_iosb_condition];h,if .status gtr ss$_normal then check_status;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!8! Now lets read the current timing info from the reader.'reader_iob[iob$l_func] = IO$_READVBLK;reader_iob[iob$a_astadr] = 0;areader_iob[iob$l_astprm] = 0; 7reader_iob[iob$l_p1] = .a_rstats; ! We store it here.tAreader_iob[iob$l_p2] = rstats$s_Timing_Stats; ! And its this big.a"! All set, so lets read the stats.6status = callg( reader_iob[iob$l_numargs], sys$qiow ); check_status;%N! Load the status from the iosb into status and check it. Exit if bad status.+status = .reader_iob[iob$w_iosb_condition];!,if .status gtr ss$_normal then check_status;H! When the experiment is done, a POLL message will be sent to the remoteN! NETTEST, which will respond with a message containing Timing_Stats. Prepare1! the IOB for the $QIO which will be issued then.e+reader_iob[iob$a_astadr] = Experiment_Done;u/reader_iob[iob$l_astprm] = reader_iob[0,0,0,0];oGreader_iob[iob$l_p1] = g_rstats$end[Timing_Stats]; ! We store it here.oreturn ss$_normal;END; N.ROUTINE Write_AST (Current_IOB: REF $BBLOCK) =!++ !p! FUNCTIONAL DESCRIPTION: !uM! Control is returned here when a Write completes. Counters are updated, andtG! either a read is issued (if the Partner_IOB field is non-zero) or thet! Write routine is called.!t! FORMAL PARAMETERS:!_ ! Current_IOBe7! TYPE: IO Control Block (Defined in NETTEST_DEF.SDL)E! ACCESS: READ ONLYl! MECHANISM: BY REFERENCE! 9! The ast parameter from the Write which just completed.!_! IMPLICIT INPUTS:!a! IMPLICIT OUTPUTS:s!a! SIDE EFFECTS:d! P!-------------------------------------------------------------------------------BEGIN.BUILTINacallg;LOCALostatus;t&!DEBUG_MSG('MASTER\Write_AST: Entry');N! Load the status from the iosb into status and check it. Exit if bad status.,status = .current_iob[iob$w_iosb_condition]; check_status; B! CHECK THE IOSB LENGTH FIELD TO BE SURE IT WAS COMPLETELY WRITTENK! Decrement the outstanding operations counter (number of AST threads whichx! haven't yet executed).-gl_outstanding_ops = .gl_outstanding_ops - 1;bP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<! Now what to do? Either start another Write, or do a Read.3IF .current_iob[iob$a_Partner_IOB] NEQ 0 THEN BEGINo< current_iob = .current_iob[iob$a_Partner_IOB]; ! Read's IOB ! Queue the readp5 status = callg(current_iob[iob$l_numargs], sys$qio);o check_status;= ! Note that an AST thread will need to be run in the future._. gl_outstanding_ops = .gl_outstanding_ops + 1;/! DEBUG_MSG('MASTER\Write_AST: Queued A Read'); ENDt ELSE BEGINH ! An operation has completed (not doing Ping-Pong, so we are just doing) ! writes). Fix up the operations count.n* gl_completed_ops = .gl_completed_ops + 1; Write(current_iob[0,0,0,0]);OEND;%!DEBUG_MSG('MASTER\Write_AST: Exit');RETURN SS$_NORMAL;END; -ROUTINE Read_AST (Current_IOB: REF $BBLOCK) =-!++-!-! FUNCTIONAL DESCRIPTION:E!L! Control is returned here when a Read completes. Counters are updated, and"! the Pre_Write routine is called.!s! FORMAL PARAMETERS:!n ! Current_IOBr7! TYPE: IO Control Block (Defined in NETTEST_DEF.SDL)e! ACCESS: READ ONLY ! MECHANISM: BY REFERENCE!i8! The ast parameter from the Read which just completed.!t! IMPLICIT INPUTS:!d! IMPLICIT OUTPUTS:p!m! SIDE EFFECTS:i!eP!-------------------------------------------------------------------------------BEGINeLOCALtstatus;%!DEBUG_MSG('MASTER\Read_AST: Entry');oN! Load the status from the iosb into status and check it. Exit if bad status.,status = .current_iob[iob$w_iosb_condition]; check_status;?! CHECK THE IOSB LENGTH FIELD TO BE SURE IT WAS COMPLETELY READtK! Decrement the outstanding operations counter (number of AST threads whichg! haven't yet executed).-gl_outstanding_ops = .gl_outstanding_ops - 1;aQ! An operation (A ping (Write), followed by a pong (Read)) has completed. Fix up!! the operations count.!)gl_completed_ops = .gl_completed_ops + 1;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Start the next operation. 'Write(.current_iob[iob$a_Partner_IOB]); $!DEBUG_MSG('MASTER\Read_AST: Exit');RETURN SS$_NORMAL;END; a*ROUTINE Write (Current_IOB: REF $BBLOCK) =!++ !_! FUNCTIONAL DESCRIPTION:_!gF! If the experiment isn't done, start the next operation with a Write.!n! FORMAL PARAMETERS:!s ! Current_IOBl7! TYPE: IO Control Block (Defined in NETTEST_DEF.SDL) ! ACCESS: READ ONLYm! MECHANISM: BY REFERENCE!a! IMPLICIT INPUTS:!)! IMPLICIT OUTPUTS:o!o! SIDE EFFECTS:!tD! When the last io completes in a timed experiment, the main routine! is woken up.!gP!-------------------------------------------------------------------------------BEGINlBUILTINecallg;LOCAL_ msg: REF $BBLOCK ,status;e"!DEBUG_MSG('MASTER\Write: Entry');P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<! Has the experiment ended? (All the io's have been queued)$IF .gb_stop_queuing EQL 1 THEN BEGIN+ IF .gl_outstanding_ops LEQ 0 THEN BEGINtI ! This condition shouldn't occur. It would mean that the Read whose AST H ! routine was Experiment_Done completed before the IO which caused this ! routine to be called.? print('MASTER\Write: $WAKE() Issued. ERROR!'); ! DEBUG DEBUGt status = $wake(); check_status; END;7! DEBUG_MSG('MASTER\Write: Exit (Stopped queuing)');] RETURN ss$_normal;END;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!I! We ain't done yet. Prepare another io. The Message Type is pre-set in P! Init_Master() to either MSG$Type_Data or MSG$Type_Ping. When we reach the end6! of the experiment, this is changed to MSG$Type_Poll.msg = .current_iob[iob$a_data];((msg[dmsg$l_sequence] = .gl_started_ops;I! Is this the last Write (either by operations count, or by end-of-time)?JIF (((.gl_started_ops + 1) GEQ .gl_operations) AND (.gb_timed_expt EQL 0)) OR .gb_times_up THEN BEGINyK !print('set_params: times_up');! DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUGF ! Prevent any Writes being queued up after this one. And change thisJ ! message type to Poll so that Remote will respond with its Timing_Stats. gb_times_up = 1; gb_stop_queuing = 1;" msg[dmsg$b_type] = msg$type_Poll;F ! Set the Partner_IOB field so that Write_AST will fire off a read ofA ! the Timing_Stats. This can't be done here because we could beAI ! inserting the read too early (before the last read of a Pong message). 8 current_iob[iob$a_Partner_IOB] = g_reader_iob[0,0,0,0];END;! Issue the i/o.-! print write started message {Ravi ; 4/8/91}IF .gl_started_ops eql 1 THEN BEGINR6 print('MASTER\WRITE: Write operations started'); print(' ');  END;e!e4status = callg(current_iob[iob$l_numargs], sys$qio); check_status;h! Update counters.Ogl_outstanding_ops = .gl_outstanding_ops + 1; ! inc the global queued i/o countt>gl_started_ops = .gl_started_ops + 1; ! count this operation!!DEBUG_MSG('MASTER\Write: Exit');dRETURN SS$_NORMAL;END; bROUTINE times_up =!++u!s! FUNCTIONAL DESCRIPTION:o!e3! This is the ast routine for the experiment timer.-?! All it does is set gb_times_up so that the next invocation ofe=! io_complete will be the last and will read the results fromd! the remote node.!$! FORMAL PARAMETERS:!r! IMPLICIT INPUTS:!i! IMPLICIT OUTPUTS:k!u! SIDE EFFECTS:g!yP!-------------------------------------------------------------------------------BEGIN gb_times_up = 1;return ss$_normal;end; s#ROUTINE experiment_done( astprm ) =i!++b!n! FUNCTIONAL DESCRIPTION:k!aC! This is the ast routine for the read of the stats from the remoteeA! node. This marks the official end of the experiment. We checki?! the iosb of the read, and, if its ok, read our stats and wake$! up the main routine.!N! FORMAL PARAMETERS:!o! astprm! TYPE: STRUCTURE ! ACCESS: READ ONLYb! MECHANISM: BY REFERENCE!o:! The ast parameter is the address of the iob used in the! read of the stats.e!b! IMPLICIT INPUTS:!e! IMPLICIT OUTPUTS: !o! SIDE EFFECTS:a!n5! The timing variables are set with the final values.M!CP!-------------------------------------------------------------------------------BEGIN-LOCAL-current_iob: ref $bblock,-status;-L!print('experiment_done'); ! DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUGF! The ast parameter to this routine is the address of the iob whose io! just completed.current_iob = .astprm;N! Load the status from the iosb into status and check it. Exit if bad status.,status = .current_iob[iob$w_iosb_condition];,if .status gtr ss$_normal then check_status;$! Count the IO which just completed..gl_outstanding_ops = .gl_outstanding_ops - 1;*gl_completed_ops = .gl_completed_ops + 1; ! Get process ending statistics.2status = collect_stats(g_stats$end[Timing_Stats]); check_status;i5g_stats$end[rstats$l_operations] = .gl_completed_ops;;! Wake up main routine. 'IF .gl_outstanding_ops LEQ 0 THEN BEGINdD !print('$wake()');! DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG status = $wake(); check_status;END;return ss$_normal;end; oIROUTINE cleanup_master( a_queue, l_queue_size, l_alignment, l_vm_zone_id,p l_ping_pong ) =!++ !;! FUNCTIONAL DESCRIPTION:b!kC! Cleans up by freeing up the memory that was previously allocated.i!p! FORMAL PARAMETERS:!V! IMPLICIT INPUTS:!i! IMPLICIT OUTPUTS:_!e! SIDE EFFECTS: !eP!-------------------------------------------------------------------------------BEGIN!BUILTIN!subm;!local!addr_range: vector[2],queue: ref vector,hcurrent_iob: ref $bblock,ipartner_iob: ref $bblock, max,data, status; queue = .a_queue;.max = .l_queue_size;IF .l_ping_pong THEN max = .max + .l_queue_size;%INCR qe FROM 0 TO (.max - 1) DO BEGIN  current_iob = .queue[.qe]; C ! First free up the data block. We have to restore the pointer toE ! the beginning of the page and unlock the data block from the wset.0 data = .current_iob[iob$a_data] - .l_alignment; addr_range[0] = .data;M; addr_range[1] = .data + .current_iob[iob$l_data_size] - 1;q( status = $ulwset( inadr = addr_range ); check_status;4 status = lib$free_vm( current_iob[iob$l_data_size], data,A l_vm_zone_id ); check_status;  ! Now get rid of the iob.) sta7e~ NETTEST_KIT.A|wtus = lib$free_vm( %ref( iob$s_iob ),T current_iob ); check_status;END;1max = .max * %UPVAL; ! Number of bytes allocated.G#status = lib$free_vm( l_queue_size,r a_queue ); check_status;p! return aok status.return ss$_normal;end;enddeludom Pong messages ! from the remote NETTEST.A status = Create_IOB( queue[.qe + .l_queue_size], .l_io_channel,5 .l_msg_size, .l_alignment, .l_vm_zone_id, .qe ); check_status; ' ! Point these two IOBs at each other.#*[U_SMITH.NETTEST.TEST2]MASTER.OBJ;1+,hw. / 4 *- SHOW EXECUTOR CHARACTERISTICS"status = $qiow( chan = .l_channel, func = IO$_WRITEVBLK, iosb = io_sb, p1 = nice_show_exec, p2 = 5 ); check_status;results_len = results_maxlen;key[0,0,8,0] = 4;key[1,0,32,0] = %X'00FFFF01';terminal[0,0,8,0] = 1;terminal[1,0,8,0] = %X'80';@read_results( .l_channel, results, results_len, key, terminal );"if .results_len EQL 0 then RETURN;;! Find out where in the result the first parameter appears.K! The third word is the nodenumber, followed by a byte whose low order bits;! are the length of the nodename which immediately follows.+ptr = results[7,0,0,0] + .results[6,0,4,0];E! Now we need to loop through the results looking for IDENTIFICATION,!! PIPELINE QUOTA and BUFFER SIZE.parameters_to_find = 3;EWHILE (.ptr LSSU (results + .results_len)) ! Don't go beyond the end.* AND (.parameters_to_find GTR 0) DO BEGIN refptr = .ptr;O if .refptr[0,0,15,0] EQL NMA$C_PCNO_IDE then begin ! IDENTIFICATION parameter?H if .refptr[2,0,8,0] NEQ %X'40' then begin ! Unexpected result! Not an- status = ss$_abort; ! ASCIC string type. check_status; end;` junkl = cmpc3( %ref( 12 ), refptr[4,0,0,0], UPLIT( 'DECnet-VAX V' ) ); ! Do the strings match?\ if (.refptr[3,0,8,0] GEQ 15) and ! Long enough to include the current (VMS FT1-3KA) format= ! of identification: "DECnet-VAX V5.0, VMS T5.0-3KA)". (.junkl EQL 0) then begin ! Strings matchA ! So now copy the "Vx.y" characters into the rstats structure.7 rstats[rstats$T_DECnetVersion] = .refptr[15,0,32,0];- rstats[rstats$L_DECnetVersion_Length] = 4; end;/ parameters_to_find = .parameters_to_find - 1; end elseO if .refptr[0,0,15,0] EQL NMA$C_PCNO_PIQ then begin ! PIPELINE QUOTA parameter?M data_type = .refptr[2,0,8,0]; ! Data Type is a composite of several fields.- ! Confirm that this one says the lengthO if .data_type EQLU 2 then ! is 2 bytes and that the data is unsigned decimal.5 rstats[rstats$l_PipelineQuota] = .refptr[3,0,16,0] else begin status = ss$_abort; check_status; end;/ parameters_to_find = .parameters_to_find - 1; end elseL if .refptr[0,0,15,0] EQL NMA$C_PCNO_BUS then begin ! BUFFER SIZE parameter?M data_type = .refptr[2,0,8,0]; ! Data Type is a composite of several fields.- ! Confirm that this one says the lengthO if .data_type EQLU 2 then ! is 2 bytes and that the data is unsigned decimal.6 rstats[rstats$l_ExecBufferSize] = .refptr[3,0,16,0] else begin status = ss$_abort; check_status; end;/ parameters_to_find = .parameters_to_find - 1; end;> ptr = .ptr + 2; ! Skip the id. PTR now contains the address) ! of the data type of this parameter.= skip_nice_param( ptr ); ! Now skip over the associated data.END;RETURN;END; K%SBTTL 'GET_CIRCUIT -- Determine the DESTINATION CIRCUIT to the named node':GLOBAL ROUTINE GET_CIRCUIT ( l_channel, node: ref $bblock,B a_circuit_name_buffer, a_circuit_name_length ): NOVALUE =!++! FUNCTIONAL DESCRIPTION:!!! FORMAL PARAMETERS:!!! IMPLICIT INPUTS:!!! IMPLICIT OUTPUTS:!!! ROUTINE VALUE:! COMPLETION CODES:!!! SIDE EFFECTS:!!!--BEGINBUILTINcmpc3;LITERALresults_maxlen = 512;LOCAL.nodeptr1, nodeptr2, nodeptr3, nodename_length,max_circuit_name_length,!results: $bblock[results_maxlen], results_len,key: $bblock[5],terminal: $bblock[2],io_sb: $bblock[8],ptr, refptr: ref $bblock,junkl, data_type, type,parameters_to_find, junkw: word,status;2max_circuit_name_length = ..a_circuit_name_length;.a_circuit_name_length = 0;F! Determine the length of the nodename supplied. Search for the firstE! character that isn't part of the name. The chars we expect to find2! as terminating chars are colon and double quote.if .node NEQ 0 then begin) nodeptr1 = ch$ptr(.node[dsc$a_pointer]);Z nodeptr2 = ch$find_ch( .node[dsc$w_length], .nodeptr1, %C'"'); ! Find first double quote.S nodeptr3 = ch$find_ch( .node[dsc$w_length], .nodeptr1, %C':'); ! Find first colon.  if .nodeptr2 EQL 0 then nodeptr2 = .nodeptr3 else if (.nodeptr3 NEQ 0) ANDC (ch$diff( .nodeptr3, .nodeptr2 ) LSS 0) then ! Find the first. nodeptr2 = .nodeptr3; if .nodeptr2 EQL 0 then' nodename_length = .node[dsc$w_length] else4 nodename_length = ch$diff( .nodeptr2, .nodeptr1 );, ! Now need to ensure the name is uppercase.X incr char from .node[dsc$a_pointer] to (.node[dsc$a_pointer] + .nodename_length - 1) doL (.char)<0,8> = (if ($.(.char)<0,8> GEQ %C'a') AND (.(.char)<0,8> LEQ %C'z')* then (.(.char)<0,8> - (%C'a' - %C'A')) else .(.char)<0,8>);end else RETURN;=! Now determine which CIRCUIT connects the executor node with?! the node whose descriptor is pointed at by the argument NODE.5! Send a "SHOW NODE nodename SUMMARY" message to NML..! Use the results buffer to build the message.results[0,0,16,0] = %X'0014';$results[2,0,8,0] = .nodename_length;Vch$move( .nodename_length, ch$ptr(.node[dsc$a_pointer]), ch$ptr( results[3,0,0,0] ) );#results_len = 3 + .nodename_length;"status = $qiow( chan = .l_channel, func = IO$_WRITEVBLK, iosb = io_sb, p1 = results, p2 = .results_len ); check_status;key[0,0,8,0] = 4;key[1,0,32,0] = %X'00FFFF01';terminal[0,0,8,0] = 1;terminal[1,0,8,0] = %X'80';results_len = results_maxlen;@read_results( .l_channel, results, results_len, key, terminal );<if .results_len EQL 0 then RETURN; ! Couldn't find the data!;! Find out where in the result the first parameter appears.K! The third word is the nodenumber, followed by a byte whose low order bits;! are the length of the nodename which immediately follows.+ptr = results[7,0,0,0] + .results[6,0,4,0];J! Now we need to loop through the results looking for DESTINATION CIRCUIT.EWHILE (.ptr LSSU (results + .results_len)) ! Don't go beyond the end.1 AND (.(.ptr)<0,16> NEQ NMA$C_PCNO_DLI) DO BEGIN> ptr = .ptr + 2; ! Skip the id. PTR now contains the address) ! of the data type of this parameter.= skip_nice_param( ptr ); ! Now skip over the associated data.END;refptr = .ptr;C! If we are unable to find the DESTINATION CIRCUIT, this means thatM! this is the same node as the other end of the link, or the nodename is that)! of the cluster alias used by this node.4if .refptr[0,0,15,0] NEQ NMA$C_PCNO_DLI then RETURN;Fif .refptr[2,0,8,0] NEQ %X'40' then begin ! Unexpected result! Not an+ status = ss$_abort; ! ASCIC string type. check_status;end;=! Copy the DESTINATION CIRCUIT name into the provided buffer.L.a_circuit_name_length = MINU( .max_circuit_name_length, .refptr[3,0,8,0] );`ch$move( ..a_circuit_name_length, ch$ptr( refptr[4,0,0,0] ), ch$ptr( .a_circuit_name_buffer ) );RETURN;END; B%SBTTL 'GET_LineRecvBuffers -- Find LINE linename RECEIVE BUFFERS'?GLOBAL ROUTINE GET_LineRecvBuffers ( l_channel, a_circuit_name,> l_circuit_name_length, a_linerecvbuffers ): NOVALUE =!++! FUNCTIONAL DESCRIPTION:!!! FORMAL PARAMETERS:!!! IMPLICIT INPUTS:!!! IMPLICIT OUTPUTS:!!! ROUTINE VALUE:! COMPLETION CODES:!!! SIDE EFFECTS:!!!--BEGINLITERALresults_maxlen = 512;LOCALlinename_length,!results: $bblock[results_maxlen], results_len,key: $bblock[5],terminal: $bblock[2],#nice_show_exec: $bblock[5] initial(0 byte(%X'14', %X'20', %X'00', %X'00', %X'00') ),io_sb: $bblock[8],ptr, refptr: ref $bblock,junkl, data_type, type,parameters_to_find, junkw: word,status;,if .l_circuit_name_length EQL 0 then RETURN;F! Emperically speaking, the LINE NAME appears to be the portion of theF! CIRCUIT NAME before the first period (with an implicit period at theF! end of the CIRCUIT NAME). I've no proof of this, but it seems to be ! correct.Mptr = ch$find_ch( .l_circuit_name_length, ch$ptr( .a_circuit_name ), %C'.' );if .ptr EQL 0 then) linename_length = .l_circuit_name_lengthelse> linename_length = ch$diff( .ptr, ch$ptr( .a_circuit_name ) );=! Send a "SHOW LINE linename CHARACTERISTICS" message to NML..! Use the results buffer to build the message.results[0,0,16,0] = %X'2114';$results[2,0,8,0] = .linename_length;Sch$move( .linename_length, ch$ptr( .a_circuit_name ), ch$ptr( results[3,0,0,0] ) );#results_len = 3 + .linename_length;"status = $qiow( chan = .l_channel, func = IO$_WRITEVBLK, iosb = io_sb, p1 = results, p2 = .results_len ); check_status;key[0,0,8,0] = 4;key[1,0,32,0] = %X'00FFFF01';terminal[0,0,8,0] = 1;terminal[1,0,8,0] = %X'80';results_len = results_maxlen;@read_results( .l_channel, results, results_len, key, terminal );<if .results_len EQL 0 then RETURN; ! Couldn't find the data!;! Find out where in the result the first parameter appears.I! The fifth byte is the length of the linename which immediately follows.+ptr = results[5,0,0,0] + .results[4,0,4,0];E! Now we need to loop through the results looking for RECEIVE BUFFERSEWHILE (.ptr LSSU (results + .results_len)) ! Don't go beyond the end.1 AND (.(.ptr)<0,16> NEQ NMA$C_PCLI_BFN) DO BEGIN> ptr = .ptr + 2; ! Skip the id. PTR now contains the address) ! of the data type of this parameter.= skip_nice_param( ptr ); ! Now skip over the associated data.END;refptr = .ptr;F! Don't know what it means if we can't find the parameter, but for now! let's ignore it.4if .refptr[0,0,15,0] NEQ NMA$C_PCLI_BFN then RETURN; ! Found it!Fif .refptr[2,0,8,0] NEQ %X'02' then begin ! Unexpected result! Not an4 status = ss$_abort; ! a word of unsigned decimal.1 check_status; ! An error in the NICE message.end;,! Copy value of RECEIVE BUFFERS into rstats.'.a_LineRecvBuffers = .refptr[3,0,16,0];RETURN;END; Z!XX %SBTTL 'SET_LineRecvBuffers -- NCP> SET LINE linename RECEIVE BUFFERS linerecvbuffers'C!XX GLOBAL ROUTINE SET_LineRecvBuffers ( l_channel, a_circuit_name,B!XX l_circuit_name_length, l_LineRecvBuffers ): NOVALUE =!XX !++!XX ! FUNCTIONAL DESCRIPTION:!XX !!XX !!XX ! FORMAL PARAMETERS:!XX !!XX !!XX ! IMPLICIT INPUTS:!XX !!XX !!XX ! IMPLICIT OUTPUTS:!XX !!XX !!XX ! ROUTINE VALUE:!XX ! COMPLETION CODES:!XX !!XX !!XX ! SIDE EFFECTS:!XX !!XX !!XX !-- !XX BEGIN!XX !XX BUILTIN!XX !XX cmpc3;!XX !XX LITERAL!XX !XX results_maxlen = 512;!XX !XX LOCAL!XX !XX linename_length,%!XX results: $bblock[results_maxlen],!XX results_len,!XX key: $bblock[5],!XX terminal: $bblock[2],!XX io_sb: $bblock[8],!XX ptr, refptr: ref $bblock,!XX junkl, data_type, type,!XX junkw: word, !XX status;!XX 0!XX if .l_circuit_name_length EQL 0 then RETURN;!XX J!XX ! Emperically speaking, the LINE NAME appears to be the portion of theJ!XX ! CIRCUIT NAME before the first period (with an implicit period at theJ!XX ! end of the CIRCUIT NAME). I've no proof of this, but it seems to be!XX ! correct.!XX G!XX ptr = ch$find_ch( .l_circuit_name_length, .a_circuit_name, %C'.' );!XX if .ptr EQL 0 then-!XX linename_length = .l_circuit_name_length!XX elseB!XX linename_length = ch$diff( .ptr, ch$ptr( .a_circuit_name ) );!XX P!XX ! To change the number of receive buffers, we must first shut the line down.A!XX ! We do this with a message like SET LINE linename STATE OFF.!XX !!XX results[0,0,16,0] = %X'0113';(!XX results[2,0,8,0] = .linename_length;W!XX ch$move( .linename_length, ch$ptr( .a_circuit_name ), ch$ptr( results[3,0,0,0] ) );'!XX results_len = 3 + .linename_length;,?!XX results[.results_len,0,16,0] = NMA$C_PCLI_STA; ! LINE STATEN#!XX results_len = .results_len + 2;I+!XX results[.results_len,0,8,0] = 1; ! OFF-#!XX results_len = .results_len + 1;-!XX &!XX status = $qiow( chan = .l_channel,!XX func = IO$_WRITEVBLK,O!XX iosb = io_sb,!XX p1 = results,R!XX p2 = .results_lenM !XX ); !XX check_status; !XX !XX key[0,0,8,0] = 4; !!XX key[1,0,32,0] = %X'00FFFF01';N!XX !XX terminal[0,0,8,0] = 1;!XX terminal[1,0,8,0] = %X'80';R!XX !!XX results_len = results_maxlen;R!XX D!XX read_results( .l_channel, results, results_len, key, terminal );!XX @!XX if .results_len EQL 0 then RETURN; ! Couldn't find the data!!XX Y!XX ! Send a "SET LINE linename STATE ON RECEIVE BUFFERS linerecvbuffers" message to NML.M2!XX ! Use the results buffer to build the message.!XX !!XX results[0,0,16,0] = %X'0113'; (!XX results[2,0,8,0] = .linename_length;W!XX ch$move( .linename_length, ch$ptr( .a_circuit_name ), ch$ptr( results[3,0,0,0] ) );-'!XX results_len = 3 + .linename_length;-?!XX results[.results_len,0,16,0] = NMA$C_PCLI_STA; ! LINE STATE-#!XX results_len = .results_len + 2;*!XX results[.results_len,0,8,0] = 0; ! ON#!XX results_len = .results_len + 1;lD!XX results[.results_len,0,16,0] = NMA$C_PCLI_BFN; ! RECEIVE BUFFERS#!XX results_len = .results_len + 2;L?!XX results[.results_len,0,16,0] = .l_LineRecvBuffers; ! number #!XX results_len = .results_len + 2;!XX &!XX status = $qiow( chan = .l_channel,!XX func = IO$_WRITEVBLK,d!XX iosb = io_sb,N!XX p1 = results,N!XX p2 = .results_lene !XX );r!XX check_status;:!XX !XX key[0,0,8,0] = 4;l!!XX key[1,0,32,0] = %X'00FFFF01';r!XX !XX terminal[0,0,8,0] = 1;!XX terminal[1,0,8,0] = %X'80';W!XX !!XX results_len = results_maxlen;,!XX D!XX read_results( .l_channel, results, results_len, key, terminal );!XX I!XX if .results_len EQL 0 then RETURN; ! Couldn't find the data! This is "!XX ! very likely a% disaster.!XX !XX RETURN;I!XX END; AZ!YY %SBTTL 'SET_LineRecvBuffers -- NCP> SET LINE linename RECEIVE BUFFERS linerecvbuffers'C!YY GLOBAL ROUTINE SET_LineRecvBuffers ( l_channel, a_circuit_name,hB!YY l_circuit_name_length, l_LineRecvBuffers ): NOVALUE =!YY !++c!YY ! FUNCTIONAL DESCRIPTION:R!YY ! H!YY ! This version does not use the provided channel, but instead spawnsC!YY ! off a process with NCP running in it, then communicates via a!YY ! mailbox.!YY !n!YY ! FORMAL PARAMETERS:!YY !!YY !$!YY ! IMPLICIT INPUTS:!YY !n!YY !!YY ! IMPLICIT OUTPUTS:a!YY ! !YY !a!YY ! ROUTINE VALUE:!YY ! COMPLETION CODES:L!YY !U!YY !e!YY ! SIDE EFFECTS:!YY !C!YY !D!YY !--N !YY BEGIN!YY !YY LITERAL:!YY !YY buffer_maxlen = 128;!YY !YY LOCALO!YY 4!YY buffer: vector[ch$allocation( buffer_maxlen )],)!YY buffer_desc: block[dsc$k_s_bln, byte]'!YY preset( [dsc$a_pointer] = buffer,d&!YY [dsc$w_length] = buffer_maxlen,%!YY [dsc$b_class] = dsc$k_class_s,.'!YY [dsc$b_dtype] = dsc$k_dtype_t ), !YY !YY linename_length,!YY io_sb: $bblock[8],!YY ptr, !YY status;)!YY 0!YY if .l_circuit_name_length EQL 0 then RETURN;!YY J!YY ! Emperically speaking, the LINE NAME appears to be the portion of theJ!YY ! CIRCUIT NAME before the first period (with an implicit period at theJ!YY ! end of the CIRCUIT NAME). I've no proof of this, but it seems to be!YY ! correct.!YY G!YY ptr = ch$find_ch( .l_circuit_name_length, .a_circuit_name, %C'.' ); !YY if .ptr EQL 0 then-!YY linename_length = .l_circuit_name_lengthp!YY elseB!YY linename_length = ch$diff( .ptr, ch$ptr( .a_circuit_name ) );!YY G!YY ! Create a temporary mailbox with which we shall communicate to theH!YY ! NCP subprocess.R!YY !YY l_channel = 0; !YY status = $CREMBX(prmflg = 0,&!YY chan = l_channel,!YY maxmsg = buffer_maxlen,5!YY lognam = %ASCID'NETTEST$NML_MBX'S!YY );!YY check_status;!YY !YY ! Now spawn the processE!YY Y!YY status = lib$spawn( 0, %ASCID'NETTEST$NML_MBX', %ASCID'NLA0:', ! Discard the outputOX!YY %ref(cli$m_nowait + cli$m_noclisym + cli$m_nolognam) ); ! Default rest of args!YY check_status;o!YY 8!YY ! Give it sufficient privileges to get the job done.!YY &!YY status = $qiow( chan = .l_channel,!YY func = IO$_WRITEVBLK,_!YY iosb = io_sb,e,!YY p1 = UPLIT('$ SET PROCESS/PRIV=OPER'), !YY p2 = 23v !YY );H!YY check_status;E!YY !!YY ! Start NCP in the subprocessn!YY &!YY status = $qiow( chan = .l_channel,!YY func = IO$_WRITEVBLK,!YY iosb = io_sb,)!YY p1 = UPLIT('$ RUN SYS$SYSTEM:NCP'),a !YY p2 = 20, !YY );e!YY check_status;F!YY 0!YY ! Create the message shutting down the line.!YY .!YY buffer_desc[dsc$w_length] = buffer_maxlen;?!YY !XX status = $fao( ctrstr = %ASCID'SET LINE !AD STATE OFF', -!YY !XX outlen = buffer_desc[dsc$w_length],r!YY !XX outbuf = buffer_desc,h !YY !XX p1 = .linename_length,!YY !XX p2 = .a_circuit_name!YY !XX );!YY !XX check_status;fP!YY status = sys$fao( %ASCID'SET LINE !AD STATE OFF', buffer_desc[dsc$w_length],9!YY buffer_desc, .linename_length, .a_circuit_name );E!YY check_status;I!YY &!YY status = $qiow( chan = .l_channel,!YY func = IO$_WRITEVBLK,)!YY iosb = io_sb,e'!YY p1 = .buffer_desc[dsc$a_pointer], %!YY p2 = .buffer_desc[dsc$w_length]f !YY );Q!YY check_status;e!YY S!YY ! Create the message starting the line with a new value of line receive bufferst!YY .!YY buffer_desc[dsc$w_length] = buffer_maxlen;B!YY !XX status = $fao( ctrstr = %ASCID'SET LINE !AD STATE ON !UL',-!YY !XX outlen = buffer_desc[dsc$w_length],D!YY !XX outbuf = buffer_desc,t !YY !XX p1 = .linename_length,!YY !XX p2 = .a_circuit_name,1!!YY !XX p3 = .l_LineRecvBuffersa!YY !XX );!YY !XX check_status;c!YY status = sys$fao( %ASCID'SET LINE !AD STATE ON RECEIVE BUFFERS !UL', buffer_desc[dsc$w_length],aM!YY buffer_desc, .linename_length, .a_circuit_name, .l_LineRecvBuffers );t!YY check_status;V!YY &!YY status = $qiow( chan = .l_channel,!YY func = IO$_WRITEVBLK,1!YY iosb = io_sb,r'!YY p1 = .buffer_desc[dsc$a_pointer],e%!YY p2 = .buffer_desc[dsc$w_length]a !YY );[!YY check_status;p!YY !YY ! Stop NCP!YY &!YY status = $qiow( chan = .l_channel,!YY func = IO$_WRITEVBLK,t!YY iosb = io_sb,2!YY p1 = UPLIT('EXIT'), !YY p2 = 4 !YY );[!YY check_status;o!YY !YY ! STOP the subprocessb!YY &!YY status = $qiow( chan = .l_channel,!YY func = IO$_WRITEVBLK,d!YY iosb = io_sb,d!!YY p1 = UPLIT('LOGOUT/BRIEF'),, !YY p2 = 6 !YY ); !YY check_status; !YY !!YY $DASSGN( chan = .l_channel );]!YY !YY RETURN;c!YY END; eK%SBTTL 'READ_RESULTS -- Read The NICE Return Messages Looking For The Data'p!ROUTINE READ_RESULTS ( l_channel, * results: REF $BBLOCK, a_results_length,7 key: ref $bblock, terminal: ref $bblock ): NOVALUE =!++a! FUNCTIONAL DESCRIPTION:s!u!! FORMAL PARAMETERS:!i!=! IMPLICIT INPUTS:!1!! IMPLICIT OUTPUTS: !;! ! ROUTINE VALUE:! COMPLETION CODES:e!! ! SIDE EFFECTS:e! !s!--mBEGINLITERALpexcess_max_length = 512;LOCALc#excess: $bblock[excess_max_length],'excess_length, max_length, data_length,Sio_sb: $bblock[8],data: ref $bblock,found_results,status;(data = results[0,0,0,0]; max_length = ..a_results_length;@.a_results_length = 0; ! No data found (a pessimistic attitude).found_results = 0;7! Loop through the results looking both for the result,T8! which begins with KEY, and for the last message, which3! begins with TERMINAL. We discard other messages.UDO BEGIND if .found_results then begin ! We only change these pointers after> data = excess[0,0,0,0]; ! the loop termination test so thatE max_length = excess_max_length; ! KEY and TERMINAL can be the same.n end;l@ status = $qiow( chan = .l_channel, ! Read the response from NML func = IO$_READVBLK,s iosb = io_sb, p1 = data[0,0,0,0], p2 = .max_length_ ); check_status;# status = .io_sb[iosb$w_condition];  check_status;$ data_length = .io_sb[iosb$w_count];A IF (NOT .found_results) AND (.data_length GEQ .key[0,0,8,0]) ANDf2 ch$eql( .key[0,0,8,0], ch$ptr( key[1,0,0,0] ),; .key[0,0,8,0], ch$ptr( results[0,0,0,0] ) ) THEN BEGINd ! Found the data.d found_results = 1;# .a_results_length = .data_length;  end;fENDd/UNTIL (.data_length GEQ .terminal[0,0,8,0]) AND_9 ch$eql( .terminal[0,0,8,0], ch$ptr( terminal[1,0,0,0] ),n0 .terminal[0,0,8,0], ch$ptr( data[0,0,0,0] ) );RETURN;rEND; A6%SBTTL 'SKIP_NICE_PARAM - Skip over a NICE data entry''ROUTINE skip_nice_param(ptr): NOVALUE =;!++i! FUNCTIONAL DESCRIPTION:o!aC! This routine skips over a data entry in a NICE message (after the(E! parameter id has been skipped; this is easy as they are all 2 bytes.H! long). The length is determined from the data. NICE data entries areC! self describing; they consist of a par` ~ NETTEST_KIT.A}w! several data type codes followed by parameter data. RunawayB! recursion is prevented, because we reject multiple fields inside! of multiple fields.N! /! THIS ROUTINE DOESN'T YET WORK WITH COUNTERS!!u! ! FORMAL PARAMETERS:!g/! PTR Address of the pointer to the data entry,!]! IMPLICIT INPUTS:!h! IMPLICIT OUTPUTS:t!c@! PTR is changed to th4e address after the end of the data entry.!e! ROUTINE VALUE:! COMPLETION CODES:$!w! NONE!_! SIDE EFFECTS:=!$! NONE!!--oBEGIN_LOCAL1 bit7, bit6,refptr: ref $bblock,len;D! Determine what to do based on the upper two bits of the data type.B! See section 7.1.9 of the Network Management Functional Spec. for ! more info.refptr = ..ptr;rbit7 = .refptr[0,7,1,0];bit6 = .refptr[0,6,1,0];!if .bit7 then begin ! Coded valueT& if .bit6 then begin ! Multiple fields local fields;  fields = .refptr[0,0,6,0];( .ptr = ..ptr + 1; ! Skip the data type6 INCR f FROM 1 TO .fields DO skip_nice_param( .ptr ); end9 else .ptr = ..ptr + .refptr[0,0,6,0] + 1; ! Single field[end, else begin ! Not a code value% if .bit6 then ! ASCII image field.nA len = .refptr[1,0,8,0] + 2 ! Next byte is number of ASCII charsl elsenB len = .refptr[0,0,4,0] + 1 ! If zero, then it is an image field. ;I if .len EQL 0 then SIGNAL_STOP(ss$_abort); ! Don't know what to do here.f .ptr = ..ptr + .len;pend;RETURN;_END; m7%SBTTL 'GETNMLVALUES -- Read Network Management Values'ePGLOBAL ROUTINE getNMLvalues( l_nml_channel, rstats: ref $bblock, a_node_desc ) =!++ !s! FUNCTIONAL DESCRIPTION:h!eD! Attempts to read certain NCP settings via the NML to which we have ! a channel.!! FORMAL PARAMETERS:!N! IMPLICIT INPUTS:!R! IMPLICIT OUTPUTS:8! ! SIDE EFFECTS:e! P!-------------------------------------------------------------------------------BEGIN;"if .l_nml_channel NEQ 0 then begin9 get_exec_values( .l_nml_channel, rstats[Timing_Stats] );hE rstats[rstats$L_Circuit_Length] = rstats$S_Circuit; ! Maximum lengthu+ get_circuit( .l_nml_channel, .a_node_desc,$ rstats[rstats$T_Circuit],) rstats[rstats$L_Circuit_Length] );u? get_LineRecvBuffers( .l_nml_channel, rstats[rstats$T_Circuit],n) .rstats[rstats$L_Circuit_Length],, rstats[rstats$L_LineRecvBuffers] );end;RETURN SS$_NORMAL;end;ENDRELUDOM#*[U_SMITH.NETTEST.TEST2]NETMAN.OBJ;1+,iw. / 4 -$ ! default DECNET account, or the proxy account of the user).$ !'$ IF F$MODE() .NES. "NETWORK" THEN EXIT $ NETTEST$ EXIT$*[U_SMITH.NETTEST.TEST2]NETTEST.OBJ;1+,kw./ 4-CLI: DCL Tables: DCLTABLES"Default: SYS$SPECIFIC:[NETTEST]LGICMD: LOGINLogin Flags: -Primary days: Mon Tue Wed Thu Fri -Secondary days: Sat SunNo access restrictionsFExpiration: (none) Pwdminimum: 6 Login Fails: 0APwdlifetime: (none) Pwdchange: 17-NOV-1989 09:11 RLast Login: 29-MAR-1990 08:09 (interactive), 29-MAR-1990 08:23 (non-interactive);Maxjobs: 0 Fillm: 200 Bytlm: 100000;Maxacctjobs: 0 Shrfillm: 0 Pbytlm: 0;Maxdetach: 0 BIOlm: 300 JTquota: 4098;Prclm: 20 DIOlm: 300 WSdef: 5000;Prio: 4 ASTlm: 300 WSquo: 5000;Queprio: 0 TQElm: 300 WSextent: 7000;CPU: (none) Enqlm: 600 Pgflquo: 100000Authorized Privileges: - CMKRNL SETPRV TMPMBX NETMBX SYSPRV BYPASSDefault Privileges: - CMKRNL SETPRV TMPMBX NETMBX SYSPRV BYPASS.*[U_SMITH.NETTEST.TEST2]NETTEST_ALL_TESTS.COM;1+,Kv ./ 4\-literal node$type_Router = 2; ! A router (sort of).! ! Define IO descriptor block! macro iob = 0,0,0,0 %;>literal iob$s_iob = 72; ! IO Descriptor BlockEmacro iob$a_data = 0,0,32,0 %; ! Address of the data block.Fmacro iob$l_data_size = 4,0,32,1 %; ! Amount of memory allocated.?macro iob$a_partner_iob = 8,0,32,0 %; ! For Ping-Pong tests.! $QIO argument list:>macro iob$l_numargs = 12,0,32,1 %; ! Number of arguments<macro iob$l_efn = 16,0,32,1 %; ! Event flag number5macro iob$l_chan = 20,0,32,1 %; ! IO Channel9macro iob$l_func = 24,0,32,1 %; ! Function codesBmacro iob$a_iosbadr = 28,0,32,0 %; ! IO status block address6macro iob$a_astadr = 32,0,32,0 %; ! AST address8macro iob$l_astprm = 36,0,32,1 %; ! AST parameter:macro iob$l_p1 = 40,0,32,1 %; ! QIO parameter 1:macro iob$l_p2 = 44,0,32,1 %; ! QIO parameter 2:macro iob$l_p3 = 48,0,32,1 %; ! QIO parameter 3:macro iob$l_p4 = 52,0,32,1 %; ! QIO parameter 4:macro iob$l_p5 = 56,0,32,1 %; ! QIO parameter 5:macro iob$l_p6 = 60,0,32,1 %; ! QIO parameter 6! IO status block: <macro iob$w_iosb_condition = 64,0,16,0 %; ! Condition Value9macro iob$w_iosb_count = 66,0,16,0 %; ! Transfer CountGmacro iob$l_iosb_dev_info = 68,0,32,1 %; ! Device Specific Information@! Data structure for the timing information from a remote node.macro Timing_Stats = 0,0,0,0 %;$literal rstats$S_Timing_Stats = 156;Umacro rstats$L_NodeNameLength = 0,0,32,1 %; ! Number of valid characters in NodeName$macro rstats$T_NodeName = 4,0,0,0 %;<literal rstats$S_NodeName = 16; ! Name of the node.Kmacro rstats$L_NodeType = 20,0,32,1 %; ! Nettest definition of node type.%macro rstats$T_CpuType = 24,0,32,0 %;;literal rstats$S_CpuType = 4; ! SYI$_NODE_HWTYPE! Versions information2macro rstats$L_VMSversion_Length = 28,0,32,1 %; ! 'macro rstats$T_VMSversion = 32,0,0,0 %;*literal rstats$S_VMSversion = 8; ! 5macro rstats$L_DECnetVersion_Length = 40,0,32,1 %; ! +macro rstats$T_DECnetVersion = 44,0,32,0 %;*literal rstats$S_DECnetVersion = 4; ! ! Experiment performedImacro rstats$L_Msg_Size = 48,0,32,1 %; ! As defined on the command lineJmacro rstats$L_Queue_Size = 52,0,32,1 %; ! As defined on the command lineHmacro rstats$L_Alignment = 56,0,32,1 %; ! Alignment of the data blocks.Vmacro rstats$L_Operations = 60,0,32,1 %; ! Either the number of i/o's (reader&writer)'! Or the transit packet count (router)Mmacro rstats$L_Rcv_packets = 64,0,32,1 %; ! Number of local arriving packetsNmacro rstats$L_Snd_packets = 68,0,32,1 %; ! Number of local departing packetsOmacro rstats$L_Rcv_buffail = 72,0,32,1 %; ! Number of receiving buff. failures! DECnet informationBmacro rstats$L_Segment_Size = 76,0,32,1 %; ! Size of each segmentNmacro rstats$L_Circuit_Length = 80,0,32,1 %; ! Name of circuit towards reader$macro rstats$T_Circuit = 84,0,0,0 %;Iliteral rstats$S_Circuit = 12; ! Name of circuit towards reader"! Except towards writer on reader! Mode counters7macro rstats$l_kernel = 96,0,32,1 %; ! kernel ticks5macro rstats$l_exec = 100,0,32,1 %; ! exec ticks6macro rstats$l_super = 104,0,32,1 %; ! super ticks5macro rstats$l_user = 108,0,32,1 %; ! user ticks;macro rstats$l_interrupt = 112,0,32,1 %; ! interrupt ticks?macro rstats$l_null = 116,0,32,1 %; ! null [process] ticks! Other timers%macro rstats$q_systime = 120,0,0,0 %;<literal rstats$s_systime = 8; ! System time stamp?macro rstats$l_process_cpu = 128,0,32,1 %; ! Process cpu ticksAmacro rstats$l_page_faults = 132,0,32,1 %; ! Process page faultsGmacro rstats$L_PipelineQuota = 136,0,32,1 %; ! NCP Values. A value ofEmacro rstats$L_ExecBufferSize = 140,0,32,1 %; ! 0 indicates NOT SET..macro rstats$L_LineRecvBuffers = 144,0,32,1 %;@macro rstats$W_CPUs = 148,0,16,0 %; ! Number of active CPUsJmacro rstats$L_Spin = 150,0,32,1 %; ! Spinlock time from PMS database#! Zero under v4 or uniprocessor v5Kmacro rstats$V_PingPong = 152,16,1,0 %; ! Experiment was a ping-pong expt.$macro rstats$V_MBZ0 = 152,17,15,0 %;Dliteral rstats$S_MBZ0 = 15; ! Spare bits. Must Be Zero.J! The Message STRUCTUREs should be a single Pascal style variable record."macro Control_Message = 0,0,0,0 %;#literal cmsg$S_Control_Message = 8;?macro cmsg$B_Type = 0,0,8,0 %; ! Type of message. (+)Smacro cmsg$B_Queue_Size = 1,0,8,0 %; ! Size of queue reader to use (0 - 511).Smacro cmsg$B_Alignment = 2,0,8,0 %; ! Byte alignment of data buffer (0 - 511).Nmacro cmsg$V_Check_Data = 0,24,1,0 %; ! 0: Don't check data; 1: Check data.macro cmsg$V_MBZ0 = 0,25,7,0 %;Dliteral cmsg$S_MBZ0 = 7; ! Spare bits. Must Be Zero.Kmacro cmsg$L_Msg_Size = 4,0,32,1 %; ! Size of messages reader to read.F! (+) Note that only this field is used with R, and S message types.macro Data_Message = 0,0,0,0 %; literal dmsg$S_Data_Message = 5;Fmacro dmsg$B_Type = 0,0,8,0 %; ! Type of message (D, P or S)>macro dmsg$L_Sequence = 1,0,32,1 %; ! To confirm orderingE! Rest of a data message is garbage, or pattern filled, depending on9! whether data is being checked for transmission errors.(macro Update_DECnet_Message = 0,0,0,0 %;)literal umsg$S_Update_DECnet_Message = 9;Smacro umsg$B_Type = 0,0,8,0 %; ! Type of message (msg$type_Update_DECnet)Kmacro umsg$L_LineRecvBuffers = 1,0,32,1 %; ! NCP> SET LINE RECEIVE BUFFERSMmacro umsg$L_Pipeline_Quota = 5,0,32,1 %; ! NCP> SET EXECUTOR PIPELINE QUOTA"macro IO_Status_Block = 0,0,0,0 %;#literal iosb$S_IO_Status_Block = 8;$macro iosb$w_condition = 0,0,16,0 %; macro iosb$w_count = 2,0,16,0 %;#macro iosb$l_dev_info = 4,0,32,1 %; )*[U_SMITH.NETTEST.TEST2]NETTEST_DEF.SDL;12+,{v ./ 4M- SET LINE RECEIVE BUFFERS= Pipeline_Quota LONGWORD; /* NCP> SET EXECUTOR PIPELINE QUOTA END Update_DECnet_Message;1AGGREGATE IO_Status_Block STRUCTURE PREFIX iosb$; condition WORD UNSIGNED; count WORD UNSIGNED; dev_info LONGWORD; END IO_Status_Block;END_MODULE nettest_def;3*[U_SMITH.NETTEST.TEST2]NETTEST_PINGPONG_TESTS.COM;6+,nv./ 4P- version of the output file rather than to open a new version./REPEAT = countE The repeat count is the number of times to repeat all of theE experiments. All experiments are performed before any are repeated./ALIGNMENT = byteE This allows the user data buffer to be misaligned with its initial? page. The values are in the range 0 to 511. The default is 0.What NETTEST actually doesLThe following is an extremely brief overview of the flow of control inNETTEST: o Start all remote slaves,L o Perform a single trial of the link with constant MSG_SIZE, QUEUE_SIZE, OPERATIONS (or TIME_LIMIT),B - Collect pre-experiment data on all routers (mode-counters, time stamps, etc.),. - Send experiment description to destination,- - Read pre-experiment data from destination,! - Read local pre-experiment data# - Disable ASTs and Resource Waits, - Fill the queue with output, - Enable ASTs,$ - At each completion, send another,@ - When time runs out, or the operations count has been reached,) mark the next $QIO buffer as the last,. - Read post-experiment data from destination,# - Read local post-experiment data, - Enable Resource Waits,1 - Collect post-experiment data from the routers,C - Record local, destination and router performance data in the1 output file, and report a summary to the user.L o If multiple MSG_SIZE's were specified, then select the next and return to step 2,L o If multiple QUEUE_SIZE's were specified, then select the next, reset to- the first MSG_SIZE and return to step 2,L o Decrement the REPEAT count, if positive then reset to the first4 MSG_SIZE, and QUEUE_SIZE, and return to step 2, o Otherwise, we're done.Interpreting the console outputLThe console output from NETTEST consists of some of the fields which areLplaced in the results file, plus some figures computed from results. LetsLlook at the output from a single experiment run from node BIAC to nodeENIAC:E$ NETTEST/OPERATIONS=1000/MSG_SIZE=537/QUEUE_SIZE=1/DESTINATION=ENIAC KBIAC IOsize: 537 Qsize: 1 IOcnt: 1000 Etime: 1378 TotalCPU: 1378KType M Seq: 0 IO/Sec: 72.6 CPU/IO: 1.4 Int: 223 Krnl: 268 Ex: 0K Mb/Sec: 0.30 CPU/Mb: 336 Sup: 0 User: 887 NL: 0KENIAC IOsize: 537 Qsize: 1 IOcnt: 1000 Etime: 1378 TotalCPU: 852KType S Seq: 0 IO/Sec: 72.6 CPU/IO: 0.9 Int: 567 Krnl: 273 Ex: 0K Mb/Sec: 0.30 CPU/Mb: 208 Sup: 0 User: 12 NL: 526 LThe output we see has two records, each with three lines. The first recordLis the results from the Master node (the local node, which is the writer),Land second is from the Slave node (the remote, reader node). The fields are as follows:Label MeaningG---------- ------------------------------------------------------------> The first field is the name of the node whose results appear in this record.6IOsize The size of the user buffer being transmitted.=Qsize The number of QIOs performed before ASTs were enabled.4IOcnt The total number of user buffers transmitted.>Etime Elapsed time of the experiment in 10 millisecond units.DTotalCPU The sum of the Interrupt, Kernel, Executive, Supervisor and< User modes counters. These counters are maintained by VMS? and are based on a 10 millisecond interrupt (known as ticks).>Type Types are M)aster (writer), S)lave (reader) and R)outer.@Seq Each experiment has a unique sequence number (unique within a single results file.*IO/Sec Average number of QIOs per second.-CPU/IO Average number of 10ms ticks per QIO.?Int Interrupt mode clock ticks recorded during the experiment.Krnl Kernel mode clock ticks.Ex Executive mode clock ticks.?Mb/Sec Average number of megabits of user data transmitted per second.1CPU/Mb Average number of 10ms ticks per megabit.!Sup Supervisor mode clock ticks.User User mode clock ticks.NL Null process clock ticks.LThe one difference to note about routers is that the operations countLrepresents the number of transit packets on that node (the number of packetswhich it routed).Interpreting the results fileLThe records in the results file are not really designed for reading, butLinstead are designed so that they can be read by data reduction tools (suchLas SORT and the program MASSAGE.EXE which is in the NETTEST kit). But it canLsometimes be useful to check the file to ensure that the data reductionLtools aren't discarding any important information. There are three types ofrecords in the results file.LWhen NETTEST starts, it opens the results file and writes a set of headerLrecords consisting of a record with the NETTEST version number, four commentLrecords, and one node description record for each of the nodes on whichLNETTEST will be running during the experiment (i.e. the master node, slaveLnode and router node[s]). The comment fields are identified by anLexclamation point in the left-most column. For example, the header records4of the NETTEST run previously shown were as follows:V NETTEST V01.1F! NETTEST Node Types are M)aster (writer), S)lave (reader) and R)outer! _Versions__&! Seq Node VMS DECnet CPU Circuit)! ---- ------ ---- ------ ---- ----------)M 0000 BIAC T5.0 2000 )S 0000 ENIAC T5.0 2000 LThe first line starts with a V indicating that it is a version numberLrecord. This line is then followed by 4 comment records which are used asLlabels for the fields of the records which follow. The first character ofLthe node description records is the Type of the node (in the NETTEST sense).)This is followed by the following fields:Label MeaningL--------------- ------------------------------------------------------------@Seq Each experiment has a unique sequence number (unique within a single results file.'Node Name of the node being described.RHVMS Version First four characters of the VMS version string from GETSYI.DECnet Version Unused.?CPU Type of the CPU from SYS$GETSYI (2000 is a MicroVAX-2000).nCircuit Unused.LFollowing these records are the comment lines for the experiment resultLrecords which will be written after each experiment. The rest of the fileIfrom the experiment previously shown looked like this (126 columns wide):hQ! Node T Note: ALL times (including mode counters) are in 10ms units.r@! y ________DECnet_______~! p _________Experiment________ _Executor_ Line ____________Mode Counters_______________ Total Process Page~!Seq Node e Bytes Q Algn Operat Elapsed SegSz PipeQ BufSz RB Interr Kernel Execut Superv User Null P CPU CPU Flts~!---- ------ - ----- -- --- ------ ------- ----- ----- ----- -- ------ ------ ------ ------ ------ ------ ------- ------- ----~ 0000 BIAC M 537 1 0 1000 1378 537 ***** ***** ** 223 268 0 0 887 0 1378 280 0~ 0000 ENIAC S 537 1 0 1000 1378 537 ***** ***** ** 567 273 0 0 12 526 852 247 2LNote that the experiment result records begin with a blank character. The fields are:dLabel MeaningG---------- ------------------------------------------------------------m@Seq Each experiment has a unique sequence number (unique within a single results file.?Node The name of the node whose results appear in this record. BNode Type Types are M)aster (writer), S)lave (reader) and R)outer.@Bytes The size, in bytes, of the user buffer being transmitted.9Q The number of QIOs performed before ASTs were enabled. /Algn Alignment of the first byte of user data.iBOperat The total number of user buffers transmitted. In the case= of a router, this field takes on a different meaning: it isr the number of transit packets.@Elapsed Elapsed time of the experiment in 10 millisecond units.ASegSz The number of bytes of user data which can fit in a singleo DECnet packet.PipeQ Unused.BugSz. Unused.LineRB Unused.zBInterr Interrupt mode clock ticks recorded during the experiment. Kernel Kernel mode clock ticks.#Execut Executive mode clock ticks.i$Superv Supervisor mode clock ticks.User User mode clock ticks.!Null P Null process clock ticks.nETotal CPU The sum of the Interrupt, Kernel, Executive, Supervisor andf User modes counters.;Process CPU Number of ticks charged to the NETTEST process. <Page Flts Total number of page faults during the experiment. $*[U_SMITH.NETTEST.TEST2]REMOTE.B32;32+,~w.E/ 4aED-! caused the gl_outstanding_ops count to have anB! incorrect value (because it started off negative).D! by using the knowledge that gb_stop_queuing is resetC! at the same time that gl_outstanding_ops is reset, A! we don't decrement gl_outstanding_ops in NULL_ASTA! when gb_stop_queuing has been reset. This timingB! phenomenon occurs because the NULL_AST routine forC! the second and subsequent threads are not activatedD! until after the first one has been set up and causes0! the main process to be woken up.!!6! BMW Brad Waters 13-JAN-1989!8! Add a conditional to avoid collecting NML information:! if we are running VOTS (because NML does not exist for1! VOTS). This is like James' change for Writer.!/! JMS0014 James M Synge 19-APR-1988! @! Adding support for Ping Pong messages. Much like the changes@! to MASTER.B32, State2 is out the window, replaced by a number>! of simpler procedures: Read, Read_AST, Write_AST, Null_AST.!?! MSG$Type_Stop is no longer valid in State2. This simplifies;! things and increases efficiency. The local node side ofB! NETTEST had never been changed to support sending MSG$Type_Stop@! during an experiment (only after the end) so this is no major! loss.!?! Changing GL_STARTED_OPS from a global to a local variable in ! State1().!?! Folded EXPERIMENT_DONE() into Read_AST; changed its $QIOW to8! a $QIO, specifying Null_AST() as the ASTADR argument.!! X-1 JMS James M Synge!! Placed under CMS control.!!!-- !! INCLUDE FILES!LIBRARY 'sys$library:lib.l32';REQUIRE 'nettest_def.r32';!! EXTERNAL ROUTINES!EXTERNAL ROUTINEO! Open_NML_Channel: addressing_mode(general), ! Open a channel to the local NMLG! getNMLvalues: addressing_mode(general), ! Reads PIPELINE QUOTA, etc.L! set_LineRecvBuffers: addressing_mode(general), ! Sets LINE RECEIVE BUFFERS< init_rstats: addressing_mode(general), ! Timing statistics> collect_stats: addressing_mode(general), ! Timing statistics@ difference_stats: addressing_mode(general), ! Timing statistics6 request_link: addressing_mode(general), ! Networking8 lib$put_output: addressing_mode(general), ! Console i/o5 lib$get_input: addressing_mode(general), ! routines.8 lib$create_vm_zone: addressing_mode(general), ! virtual: lib$get_vm: addressing_mode(general), ! memory management3 lib$free_vm: addressing_mode(general), ! routines.7 sys$qio: addressing_mode(general), ! io system service8 sys$qiow: addressing_mode(general), ! io system service4 sys$fao: addressing_mode(general), ! system serviceD cli$present: addressing_mode(general), ! command language interface6 lib$signal: addressing_mode(general); ! error handler!! FORWARD ROUTINE DECLARATIONS.!FORWARD ROUTINE init_state0 ,state1 ,init_state1 ,Read ,Read_AST ,Write_AST ,Null_AST ,cleanup_reader;!! MACRO DEFINITIONS:!MACRO3! Output string to terminal. Ex: print('Hello...')print (string_to_print) =- lib$put_output($descriptor(string_to_print))%,DEBUG_MSG(msg) = %if %switches(DEBUG) %then BEGIN LOCAL status;" EXTERNAL ROUTINE LIB$PUT_OUTPUT;0 status = LIB$PUT_OUTPUT( $DESCRIPTOR( msg ) ); check_status; END %fi%,DEBUG_DESC(msg) = %if %switches(DEBUG) %then BEGIN LOCAL status;" EXTERNAL ROUTINE LIB$PUT_OUTPUT;! status = LIB$PUT_OUTPUT( msg ); check_status; END %fi%,check_status = begin if not .status then begin/ $setrwm( watflg=0 ); ! Enable resource waits lib$signal(.status); end; end%,-! Output a string followed by a 32bit integerprint_s_l(string,longint) = begin BIND fao_ctl_desc = %ASCID'!AS!SL'; LITERAL result_len = 132; LOCAL" result_buf: $bblock[result_len],' result_desc: block[dsc$k_s_bln, byte]) preset( [dsc$a_pointer] = result_buf,! [dsc$w_length] = result_len,# [dsc$b_class] = dsc$k_class_s,% [dsc$b_dtype] = dsc$k_dtype_t ), status; ' status = $fao( ctrstr = fao_ctl_desc, outlen = result_desc, outbuf = result_desc, p1 = $descriptor(string), p2 = longint ); check_status;' status = lib$put_output(result_desc); check_status; end%;!! LITERAL DEFINITIONS!LITERAL dflt$operations = 1000 ,dflt$queue_size = 1 ,dflt$msg_size = 512 ,max$ncb_length = 512U ,max$state0_msg_length = MAX( cmsg$s_Control_Message, umsg$s_Update_DECnet_Message ) ; ! OWN STORAGE!OWN! Flagsgb_stop: byte,gb_stop_queuing: byte,*gb_vots: byte, ! VOTS Transport indicator! IOBs9g_state0_iob: $bblock[iob$s_iob + max$state0_msg_length],?g_rstats_iob: ! IOB for Performance Info (10 ms counters, etc.), $bblock[iob$s_iob + rstats$s_Timing_Stats],/ncb_buf: vector[ch$allocation(max$ncb_length)],"ncb_desc: block[dsc$k_s_bln, byte]$ preset( [dsc$a_pointer] = ncb_buf,# [dsc$w_length] = max$ncb_length,! [dsc$b_class] = dsc$k_class_s," [dsc$b_dtype] = dsc$k_dtype_t),Dgl_io_channel, ! IO Channel to the remote node. Note that only the; ! first word is used by vms, but to make things simpler,% ! it will be stored in a longword.H!gl_nml_channel, ! Like gl_io_channel, but used to communicate with the ! NML on this node.2gl_completed_ops, ! Number of transfers completed.=gl_outstanding_ops; ! Number of transfers still in the queue.BINDqual$l_vots = %ASCID'VOTS'; GLOBAL ROUTINE remote =!++!! FUNCTIONAL DESCRIPTION:!'! Main routine of the remote node code.!! FORMAL PARAMETERS:!! NONE.!! IMPLICIT INPUTS:!G! SYS$NET is the logical name of the device which we assign to complete! the connection.!! IMPLICIT OUTPUTS:! ! Status.!! SIDE EFFECTS:!! NONE.P!-------------------------------------------------------------------------------BEGINBUILTINCALLG;LOCAL4msg: ref $bblock, ! Reference to the Control_MessageKrstats: ref $bblock, ! Reference to the data (Timing_Stats) in g_rstats_iob msg_length,status; gb_stop = 0;D! Perform 1-time initialization. Open the channel to the other end.$! Prepare the iob we use in State 0.Bstatus = init_state0( g_state0_iob, g_rstats_iob, gl_io_channel ); check_status; msg = .g_state0_iob[iob$a_data];#rstats = .g_rstats_iob[iob$a_data];O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$! Begin the STATE 0 processing loop:WHILE (.gb_stop EQL 0) DO BEGIN, ! Issue the synchronous read to the channel+! debug_msg('State0: Waiting for message');9 status = callg( g_state0_iob[iob$l_numargs], sys$qiow ); check_status;(! debug_msg('State0: Received message');. status = .g_state0_iob[iob$w_iosb_condition]; check_status;% ! Dispatch to the appropriate actiona if .msg[cmsg$B_Type] EQL msg$type_Router then begin ! This should be first for best performance. ! debug_msg('MsgType: Router');5 ! Confirm that the length is correct (one longword)/ msg_length = .g_state0_iob[iob$w_iosb_count];& if .msg_length NEQ %UPVAL then begin status = ss$_badparam; check_status; end; ! Collect timing statistics,/ rstats[rstats$l_nodetype] = node$type_Router;1 status = collect_stats( rstats[Timing_Stats] ); check_status; ! Collect router statistics ! Write the results: status = callg( g_rstats_iob[iob$l_numargs], sys$qiow ); check_status; end7 else if .msg[cmsg$B_Type] EQL msg$type_Init then begin! debug_msg('MsgType: Init');& ! Confirm that the length is correct/ msg_length = .g_state0_iob[iob$w_iosb_count];c~ NETTEST_KIT.A~wstate0_iob[iob$a_astadr] = 0; ! No ast routine used, sync read7state0_iob[iob$l_astprm] = 0; ! So, no parameter needed0state0_iob[iob$l_p1] = .state0_iob[iob$a_data];.state0_iob[iob$l_p2] = max$state0_msg_length;2state0_iob[iob$l_p3] = 0; ! Need to specify these8state0_iob[iob$l_p4] = 0; ! optional parameters so that1state0_iob[iob$l_p5] = 0; ! qio doesn't blow up.state0_iob[iob$l_p6] = 0;;! Set up the parameters used in writing the timing results.6rstats_iob[iob$a_data] = rstats_iob[iob] + iob$s_iob;6rstats_iob[iob$l_numargs] = 12; ! Must be 12, no less.,rstats_iob[iob$l_efn] = 0; ! No event flags+rstats_iob[iob$l_chan] = ..a_l_io_channel;(rstats_iob[iob$l_func] = IO$_WRITEVBLK;=rstats_iob[iob$a_iosbadr] = rstats_iob[iob$w_iosb_condition];>rstats_iob[iob$a_astadr] = 0; ! No ast routine used, sync read7rstats_iob[iob$l_astprm] = 0; ! So, no parameter needed0rstats_iob[iob$l_p1] = .rstats_iob[iob$a_data];5rstats_iob[iob$l_p2] = rstats$s_Timing_Stats; ! Size2rstats_iob[iob$l_p3] = 0; ! Need to specify these8rstats_iob[iob$l_p4] = 0; ! optional parameters so that1rstats_iob[iob$l_p5] = 0; ! qio doesn't blow up.rstats_iob[iob$l_p6] = 0;2! Set all constant values in the rstats structure.Bstatus = init_rstats( .rstats_iob[iob$a_data] , node$type_Slave ); check_status;?! Establish a channel to the local NML and read the NM settings! if we are not running VOTS,if CLI$PRESENT(qual$l_vots) then gb_vots = 1 else gb_vots = 0;!IF NOT .gb_vots THEN BEGIN&! gl_nml_channel = Open_NML_Channel();F! getNMLvalues( .gl_nml_channel, .rstats_iob[iob$a_data] , ncb_desc );$! $DASSGN( chan = .gl_nml_channel );!END;!gl_nml_channel = 0;%!gl_nml_channel = Open_NML_Channel();E!getNMLvalues( .gl_nml_channel, .rstats_iob[iob$a_data] , ncb_desc );#!$DASSGN( chan = .gl_nml_channel );!gl_nml_channel = 0;! return happy status.return ss$_normal;end; 'ROUTINE state1( a_msg, l_io_channel ) =!++!! FUNCTIONAL DESCRIPTION:!=! Set's up the remote node to be the reader in an experiment.!! FORMAL PARAMETERS:!8! Address of two longwords with describe the experiment.!! IMPLICIT INPUTS:!G! SYS$NET is the logical name of the device which we assign to complete! the connection.!! IMPLICIT OUTPUTS:! ! Status.!! SIDE EFFECTS:!! NONE.P!-------------------------------------------------------------------------------BEGIN BUILTIN-CALLG;LOCAL_ l_vm_zone_id,E l_msg_size,N l_queue_size,E l_alignment, l_queue_base,rstats: ref $bblock,-msg: ref $bblock,;current_iob: ref $bblock, ! The block currently being used.91a_current_entry, ! Ptr to current queue element.Astatus; msg = .a_msg;W'l_queue_size = .msg[cmsg$B_Queue_Size]; #l_msg_size = .msg[cmsg$l_Msg_Size];N%l_alignment = .msg[cmsg$B_Alignment]; =! Perform 1-time initialization. Set up the queue and iob's.FEl_vm_zone_id = init_state1( l_queue_base, .l_io_channel, .l_msg_size,M$ .l_queue_size, .l_alignment );O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! Begin the main processing loop:J$rstats = .g_rstats_iob[iob$a_data];2rstats[rstats$l_msg_size] = .msg[cmsg$l_msg_size];6rstats[rstats$l_queue_size] = .msg[cmsg$B_queue_size];4rstats[rstats$l_alignment] = .msg[cmsg$B_alignment]; rstats[rstats$l_operations] = 0;7!print_s_l('msg_size = ',.rstats[rstats$l_msg_size]);-9!print_s_l('queue_size = ',.rstats[rstats$l_queue_size]);-8!print_s_l('alignment = ',.rstats[rstats$l_alignment]);I! Get experiment start statistics. Collect process cpu time at beginningrH! of experiment, all of the modes counters, the null processes cpu time,J! and the current system quadword time stamp. Perform a synchronous write! to the channel with the data. 4status = collect_stats( .g_rstats_iob[iob$a_data] ); check_status;a8status = callg( g_rstats_iob[iob$l_numargs], sys$qiow ); check_status;1H! Disable ast's while we start off the experiment. Disable resource waitI! states as well. If there is a shortage of some resource, we don't wantt(! the fact appearing in the timing info.2status = $setast( enbflg=0 ); ! Disable interrupts check_status;i6status = $setrwm( watflg=1 ); ! Disable resource waits check_status; :! Initialize the variables used in the loop and elsewhere.7a_current_entry = .l_queue_base; ! First queue element.mO!------------------------------------------------------------------------------_O! This loop simply fires off the desired number of qio's to the DECnet channel,aO! as specified by the queue size. For the remaining portion of the experiment, 9! the qio's will be initiated in the ast handler routine. NINCR l_started_ops FROM 1 TO .l_queue_size DO BEGIN ! Don't overfill the queue: ! a_current_entry points to the queue element to be used. Read(..a_current_entry);l: a_current_entry = .a_current_entry + %UPVAL; ! Next entryEND;F! The initial i/o's have been issued. Enable ast's so the ast routine#! (state2) can take over from here. status = $setast( enbflg=1 );U check_status;fK! Wait here for experiment to end. This $hiber call won't return until all 0! ast's for the outstanding reads have occurred.status = $hiber; check_status;a!debug_msg('State1: Woke up');5status = $setrwm( watflg=0 ); ! Enable resource waits check_status;n-!debug_msg('State1: Resource waits enabled'); ! Perform cleanup operations.MCstatus = cleanup_reader( .l_queue_base, .l_queue_size, .l_msg_size," .l_alignment, .l_vm_zone_id ); check_status;8!debug_msg('State1: All done');g! return to State 0.!treturn ss$_normal;end; t<ROUTINE init_state1( a_queue_base, l_io_channel, l_msg_size,$ l_queue_size, l_alignment ) =!++S!! FUNCTIONAL DESCRIPTION:l!eF! This routine prepares for an experiment by setting up all the iob's.!T! FORMAL PARAMETERS:! ! (IMPLICIT INPUTS:! ! IMPLICIT OUTPUTS:n!g! SIDE EFFECTS: !rP!-------------------------------------------------------------------------------BEGINSOWNON! First time through a vm zone is created which is aligned on page boundaries. l_vm_zone_id: long initial( 0 );LOCAL l_queue_base,y data_size, iob_size,Ssize,gcurrent_entry,current_iob: ref $bblock, queue_count,status;o$gb_stop_queuing = 0; ! Not done yet.=gl_completed_ops = 0; ! Number of IOs successfully completed.24gl_outstanding_ops = 0; ! Number still in the queue.L!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;! Allocate the queue block. (One longword per queue entry.)Usize = %UPVAL * .l_queue_size;2status = LIB$GET_VM( size, ! Allocate .size bytes/ .a_queue_base ! And store away base address.T );t check_status;eL!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!H! For each element of the queue block, allocate an iob and the space forF! the data. We use a vm zone a private vm zone which is page aligned.!if .l_vm_zone_id EQL 0 then beginsE status = lib$create_vm_zone( l_vm_zone_id, %ref(lib$k_vm_first_fit),e0 %ref(0), %ref(0), %ref(128), %ref(128),) %ref(512), %ref(512), %ref(0) );m check_status;end;iob_size = iob$s_iob;s'data_size = .l_msg_size + .l_alignment;ecurrent_entry = ..a_queue_base;oqueue_count = .l_queue_size;!WHILE .queue_count GTR 0 DO BEGIN) ! First allocate the IOBe> status = LIB$GET_VM(iob_size, ! Number of bytes to allocate.5 .current_entry); ! Where to put addr of memory. check_status; current_iob = ..current_entry;N ! Now allocate the data block._? status = LIB$GET_VM(data_size, ! Number of bytes to allocate.n= current_iob[iob$a_data], ! Where to put addr of memory.r- l_vm_zone_id); ! Where to get it from.  check_status;+ current_iob[iob$l_data_size] = .data_size;nD ! Adjust the data address so that it is offset from a page boundary ! by .l_alignment bytes.LC current_iob[iob$a_data] = .current_iob[iob$a_data] + .l_alignment; ) ! Set up the parameters used in reading.U! current_iob[iob$l_numargs] = 12;  current_iob[iob$l_efn] = 0;s* current_iob[iob$l_chan] = .l_io_channel;) current_iob[iob$l_func] = IO$_READVBLK;t@ current_iob[iob$a_iosbadr] = current_iob[iob$w_iosb_condition];& current_iob[iob$a_astadr] = Read_AST;. current_iob[iob$l_astprm] = current_iob[iob];3 current_iob[iob$l_p1] = .current_iob[iob$a_data]; & current_iob[iob$l_p2] = .l_msg_size;4 current_iob[iob$l_p3] = 0; ! Need to specify these: current_iob[iob$l_p4] = 0; ! optional parameters so that3 current_iob[iob$l_p5] = 0; ! qio doesn't blow up.s current_iob[iob$l_p6] = 0; p ! Next element, please I current_entry = .current_entry + %UPVAL; ! Ptr to next longword in queue[ queue_count = .queue_count - 1;END;return .l_vm_zone_id;(end; a-ROUTINE Read_AST (Current_IOB: REF $BBLOCK) =u!++=!s! FUNCTIONAL DESCRIPTION:i..!rF! Control is returned here when a Read completes. We receive messagesD! from the controlling instance of NETTEST which tell us what to do:! ! Message Type ActionAH! ------------- --------------------------------------------------------#! MSG$Type_Data Queue another Read.$H! MSG$Type_Ping Send (Write) the message back, changing the Message Type! to MSG$Type_Pong.RB! MSG$Type_Poll Collect Timing_Stats and send back. Stop queuing.!v! D! Because there is no way to abort the reads-in-progress, the writerE! sends a set of dummy messages after the end of the experiment. One 7! for each pending read (one less than the queue size).$!iI! V1.2 ostensibly supported the reciept of MSG$Type_Stop messages, but in J! fact these were never sent except at the very end of the experiment, andH! thus not to this routine. So, to simplify things, the support on this! end has been removed.!i! FORMAL PARAMETERS:!t ! Current_IOBd! TYPE: STRUCTUREe! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!l9! Address of the IOB for the write which just completed.n0! This structure is defined in NETTEST_DEF.SDL.!i! IMPLICIT INPUTS:!s! IMPLICIT OUTPUTS:d!s! SIDE EFFECTS:n!r;! When the last io completes, the main routine is woken up. !tP!-------------------------------------------------------------------------------BEGIN BIND" fao1 = %ASCID'Received Seq = '," fao2 = %ASCID'Completed IOs = ', fao_ctl_desc = %ASCID'!AS!SL'; LITERALT result_len = 132;EBUILTINgcallg;LOCALv" result_buf: $bblock[result_len],' result_desc: block[dsc$k_s_bln, byte]) preset( [dsc$a_pointer] = result_buf, ! [dsc$w_length] = result_len,-# [dsc$b_class] = dsc$k_class_s,-% [dsc$b_dtype] = dsc$k_dtype_t ),Amsg: ref $bblock,rstats: ref $bblock,estatus;e%!DEBUG_MSG('Remote\Read_AST: Entry');R4! Chech the status in the IOSB. Exit if bad status.,status = .current_iob[iob$w_iosb_condition]; check_status;l>! Note that there is one less AST thread still to be executed.-gl_outstanding_ops = .gl_outstanding_ops - 1;0(! If this experiment is over, it's over!IF .gb_stop_queuing THEN BEGIN0! debug_msg('Remote\Read_AST: Stopped queuing');I! The experiment is over, but we may still have outstanding reads queued.!2! If not, then wake up the main thread of control.( IF .gl_outstanding_ops LEQ 0 THEN BEGIN)! DEBUG_MSG('Remote\Read_AST: $WAKE()');s& status = $wake(); ! Hey sleepy head! check_status;a END;[%! DEBUG_MSG('Remote\Read_AST: Exit');u9 RETURN ss$_normal; ! Don't need to queue any more reads.gEND;L! Check the sequence number: it should equal the current number of completed-! operations (0 is the first value expected).umsg = .current_iob[iob$a_data];s9IF .msg[dmsg$l_Sequence] NEQ .gl_completed_ops THEN BEGIN# print('Remote\Read_AST: Seq bad');tD! Having difficulty getting the $FAO macro to work. Don't know why.'! status = $fao( ctrstr = fao_ctl_desc, ! outlen = result_desc, ! outbuf = result_desc, ! p1 = fao1,!! p2 = .msg[dmsg$l_Sequence] );o# ! Print a couple of error messagessS status = sys$fao(fao_ctl_desc,result_desc,result_desc,fao1,.msg[dmsg$l_Sequence]);i check_status;& status = lib$put_output(result_desc); check_status; ! Restore the string length( result_desc[dsc$w_length] = result_len;D! Having difficulty getting the $FAO macro to work. Don't know why.'! status = $fao( ctrstr = fao_ctl_desc,e! outlen = result_desc,i! outbuf = result_desc,e! p1 = fao2,! p2 = .gl_completed_ops );sO status = sys$fao(fao_ctl_desc,result_desc,result_desc,fao2,.gl_completed_ops);o check_status;& status = lib$put_output(result_desc); check_status; ! Now terminate with an error.. status = SS$_ILLBLKNUM; check_status; RETURN .status;END;H! The sequence number was OK. Increment the completed operations count.)gl_completed_ops = .gl_completed_ops + 1; 3! Now do what ever the message type tells us to do. 1if .msg[dmsg$b_type] EQL msg$type_Data then beginh ! Read some more data Read(Current_IOB[0,0,0,0]);endtelse1if .msg[dmsg$b_type] EQL msg$type_Ping then begind ! Pong that suckerc" msg[dmsg$b_type] = MSG$Type_Pong;* Current_IOB[iob$l_func] = IO$_WRITEVBLK;' Current_IOB[iob$a_astadr] = Write_AST;n5 status = callg(Current_IOB[iob$l_numargs], sys$qio);  check_status;. gl_outstanding_ops = .gl_outstanding_ops + 1;/! DEBUG_MSG('Remote\Read_AST: Queued a write');tendeelse1if .msg[dmsg$b_type] EQL msg$type_Poll then begin! ! Get process ending statistics. 5 status = collect_stats( .g_rstats_iob[iob$a_data] );  check_status;$ rstats = .g_rstats_iob[iob$a_data];1 rstats[rstats$l_operations] = .gl_completed_ops; ! Now send the results home. ' g_rstats_iob[iob$a_astadr] = Null_AST;a+ g_rstats_iob[iob$l_astprm] = g_rstats_iob;T8 status = callg( g_rstats_iob[iob$l_numargs], sys$qio ); check_status;. gl_outstanding_ops = .gl_outstanding_ops + 1; gb_stop_queuing = 1;-/! DEBUG_MSG('Remote\Read_AST: results posted');endr else begin< print('Remote\Read_AST: Received message of unknown type'); status = ss$_ctrlerr; check_status;end;$!DEBUG_MSG('Remote\Read_AST: Exit'); ! And the return to outer space.return ss$_normal;end; ](ROUTINE Read(Current_IOB: REF $BBLOCK) =!++I!h! FUNCTIONAL DESCRIPTION:a!s@! Read another message from the controlling instance of NETTEST.!! FORMAL PARAMETERS:!a ! Current_IOBn! TYPE: STRUCTUREi! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!t3! Address of the IOB for the Read we will perform.!0! This structure is defined in NETTEST_DEF.SDL.!i! IMPLICIT INPUTS:! ! IMPLICIT OUTPUTS:o!f! SIDE EFFECTS:a!SP!-------------------------------------------------------------------------------BEGIN BUILTIN[ callg;nLOCALb status;!!DEBUG_MSG('Remote\Read: Entry');_! Queue up another i/o. 4status = callg(current_iob[iob$l_numargs], sys$qio); check_status;=! Update the count of AST threads which haven't yet executed.t-gl_outstanding_ops = .gl_outstanding_ops + 1;l !DEBUG_MSG('Remote\Read: Exit');RETURN SS$_NORMAL;END; e.ROUTINE Write_AST(Current_IOB: REF $BBLOCK ) =!++o!a! FUNCTIONAL DESCRIPTION:S!s2! Control is returned here when a Write completes.!s! FORMAL PARAMETERS:! ! Current_IOB ! TYPE: STRUCTUREi! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!V9! Address of the IOB for the write which just completed.t0! This structure is defined in NETTEST_DEF.SDL.!=! IMPLICIT INPUTS:!t! IMPLICIT OUTPUTS:! ! SIDE EFFECTS:t!tP!-------------------------------------------------------------------------------BEGINpLOCAL;status;t&!DEBUG_MSG('Remote\Write_AST: Entry');N! Load the status from the iosb into status and check it. Exit if bad status.,status = .current_iob[iob$w_iosb_condition]; check_status;lL! Note that there is one less AST thread still to be executed. Note that weL! don't increment GL_COMPLETED_OPS here. This is because that count is usedM! in Read_AST to check the sequence numbers in the received messages and musto7! be updated in Read_AST so that they stay in sequence.0Egl_outstanding_ops = .gl_outstanding_ops - 1; ! One less in the queue_H! Now queue another read. Fixup the IOB so that it has the correct $QIO:! function code and points to Read_AST as the AST routine.(current_iob[iob$l_func] = IO$_READVBLK;%current_iob[iob$a_astadr] = Read_AST;Read(Current_IOB[0,0,0,0]); ! And the return to outer space.%!DEBUG_MSG('Remote\Write_AST: Exit');aRETURN SS$_NORMAL;END; o,ROUTINE Null_AST(Current_IOB: REF $BBLOCK) =!++b!2! FUNCTIONAL DESCRIPTION:l!nH! This is the minimum $QIO AST routine in NETTEST. It checks the statusG! of the IO which just completed, decrements the outstanding operationssJ! count (are there anymore AST threads for which we are waiting?), and, if;! it is zero, does a $WAKE to get the main routine started.m!r! FORMAL PARAMETERS:!b ! Current_IOBt! TYPE: STRUCTUREs! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!s6! Address of the IOB for the IO which just completed.0! This structure is defined in NETTEST_DEF.SDL.!s! IMPLICIT INPUTS:!;! IMPLICIT OUTPUTS:p!! SIDE EFFECTS:p! P!-------------------------------------------------------------------------------BEGINtLOCAL$status;o%!DEBUG_MSG('Remote\Null_AST: Entry');aN! Load the status from the iosb into status and check it. Exit if bad status.,status = .current_iob[iob$w_iosb_condition]; check_status;e$! Has the experiment been completed?IF .gb_stop_queuing THEN BEGINF ! Following line moved from before the IF .gb_stop_queuing ...E ! to inside it, BMW, 7-FEB-1989. The problem was that in thenB ! multi-thread case a NULL_AST was being triggered by eachO ! thread. This would have worked ok except that the gl_outstanding_opscB ! count is set to 0 in the init_state1 procedure, then theH ! NULL_ASTs decrement it so that it is not representative of theI ! actual number of outstanding operations (because it was reset).eE ! This way, the gl_outstanding_ops count is reset only by thenF ! the first thread (which wakes up the hibernating process andM ! initializes gl_outstanding_ops). If for any reason gb_stop_queuingcH ! is reset, we know that gl_outstanding_ops has also been reset. !F ! Note that there is one less AST thread still to be executed.M gl_outstanding_ops = .gl_outstanding_ops - 1; ! One less in the queue0! DEBUG_MSG('Remote\Null_AST: Stopped queuing');I! The experiment is over, but we may still have outstanding reads queued.u2! If not, then wake up the main thread of control.( IF .gl_outstanding_ops LEQ 0 THEN BEGIN)! DEBUG_MSG('Remote\Null_AST: $WAKE()');z& status = $wake(); ! Hey sleepy head! check_status;m END;$END;%!DEBUG_MSG('Remote\Null_AST: Entry');oRETURN SS$_NORMAL;END; F:ROUTINE cleanup_reader( a_queue, l_queue_size, l_msg_size, l_alignment, l_vm_zone_id ) =!++_!g! FUNCTIONAL DESCRIPTION:!!!D! Cleans up by freeing up the queue, IOBs, and data blocks that were! allocated in init_state1.!a! FORMAL PARAMETERS:!_! IMPLICIT INPUTS:!$! IMPLICIT OUTPUTS:s!_! SIDE EFFECTS:s!tP!-------------------------------------------------------------------------------BEGINiLOCALqueue: ref vector,ocurrent_iob: ref $bblock,_data,=status;s1!DEBUG_MSG('Remote\Cleanup_Reader: cleaning up');squeue = .a_queue;i.INCR qe FROM 0 TO (.l_queue_size - 1) DO BEGIN current_iob = .queue[.qe];eC ! First free up the data block. We have to restore the pointer tor ! the beginning of the page.s0 data = .current_iob[iob$a_data] - .l_alignment;4 status = lib$free_vm( current_iob[iob$l_data_size], data,c l_vm_zone_id ); check_status; a ! Now get rid of the iob.) status = lib$free_vm( %ref( iob$s_iob ),a current_iob ); check_status;<! DEBUG_MSG('Remote\Cleanup_Reader: deleted a queue entry');END;#status = lib$free_vm( l_queue_size, a_queue );s check_status; ! return aok status.return ss$_normal;end;endieludomatus = $setast( enbflg=0 ); ! Disable interrupts check_status;i6status = $setrwm( watflg=1 ); ! Disable resource waits check_status; :! Initialize the variables used in the loop and elsewhere.7a_current_entry = .l_queue_base; ! First queue element.m#*[U_SMITH.NETTEST.TEST2]REMOTE.OBJ;1+,lw. / 4 @---u7`f4ʔ896ۖGHJ$N   >$ SET COMMAND sys$sysdevice:[NETTEST]NETTEST$COMMAND_TABLE.CLD'$! DEFINE NETTEST SYS$LOGIN:DBG_NETTEST#$ DEFINE NETTEST SYS$SYSTEM:NETTEST $*[U_SMITH.NETTEST.TEST2]VERSION.B32;9+,=u!./ 4I-! Only collect DECnet setup data from NML if not using a VOTS! link.B! Add a field to the machine description which states the time at?! the start of the test. Changing the call to WRITE_HEADER soB! that it uses the start times and not the end times of the first! test.D! Replace the space at the start of data records in the output withC! an L (for Loopback) if the experiment was a loopback (ping-pong)! type experiment.!/! JMS0013 James M Synge 17-APR-1988!@! Initial support for Ping Pong experiments. Add the parameter>! /LOOPBACK to the command line, the global byte GB_PING_PONG?! and pass it to MASTER(). Still need to include somewhere in! the output.!2! X-2 JMS0004 James M Synge 14-APR-1988+! Add check for /MSG_SIZE being too large.!!!-- P!- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -! ! Format for the console output:!! bytes msgsL!NODENAME IOsize:###### Qsize:## OpCnt:###### Etime:####### TotalCPU:#######L!Type # Seq:#### IO/Sec: #### CPU/IO:###.## Int:###### Krnl:###### Ex:######L! KB/Sec:##### CPU/Kb:##### Sup:###### User:###### NL:######!E! IO/Sec can become ##.# if the number of io's / sec drops below 100.! F! Note that there is no space between some fields and the first digit.C! This is because I don't expect to see the digit used, but wish to! allow for the possibility.!P!- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -!6! The data file output will have the following format:!! V NETTEST V1.0! ! Experimental Conditions:! ! _Versions__(! ! Seq Node VMS DECnet CPU Circuit+! ! ---- ------ ---- ------ ---- ----------&! M 0000 AURORA Y4.6 V4.6 8800 UNA-0&! S 0000 DELPHI V4.5 V4.5 V780 UNA-0&! R 0000 GYRO V4.5 V4.5 V750 UNA-0Q!! T Note: ALL times (including mode counters) are in 10ms units.b!! y Types are M)aster (writer), S)lave (reader) and R)outer !! p _________Experiment________ _____DECnet________ ___________Mode Counters_____________ Tot Proc Page __Packet__ Rv!!Seq Node e Bytes Q Algn Operat Elapsed SgSz PipeQ BufSz RB Interr Kernel Execut Spv User Null P CPU CPU Flts Rcv Snd BF !!--- ------ - ----- -- --- ------ ------- ---- ----- ----- -- ------ ------ ------ --- ----- ------ ----- ----- ---- ----- ----- -! ### ###### # ##### ## ### ###### ####### #### ##### ##### ## ###### ###### ###### ### ##### ###### ##### ##### #### ##### ##### #O! 001 AURORA M 1500 4 0 1000 3000 1461 20000 20 >! 001 DELPHI S 1500 4 0 1000 3100 1461 3000 6>! 001 GYRO R 1500 4 0 2200 3120 1461 32000 32<! ^ E! PMS Transit Packet Count for routers<! < <! <! INCLUDE FILES <! LIBRARY 'sys$library:lib.l32';REQUIRE 'nettest_def.r32';!! EXTERNAL ROUTINES!EXTERNAL ROUTINEK! open_nml_channel: addressing_mode(general), ! Open a channel to local NMLM! getNMLvalues: addressing_mode(general), ! Sets the DECnet values in RSTATSL! set_LineRecvBuffers: addressing_mode(general), ! Sets LINE RECEIVE BUFFERS5 master: addressing_mode(general), ! Lab Technician7 init_rstats: addressing_mode(general), ! Front office: difference_stats: addressing_mode(general), ! Statitician< read_sequence_ace: addressing_mode(general), ! Ticket takerJ cvt_f_to: addressing_mode(general), ! my f_float to string conversion rtn9 request_link: addressing_mode(general), ! DECnet or VOTS8 lib$put_output: addressing_mode(general), ! Console i/o5 lib$get_input: addressing_mode(general), ! routines.B lib$get_vm: addressing_mode(general), ! virtual memory allocation3 lib$free_vm: addressing_mode(general), ! routines.D cli$present: addressing_mode(general), ! command language interface5 cli$get_value: addressing_mode(general), ! routines.7 sys$qio: addressing_mode(general), ! io system service8 sys$qiow: addressing_mode(general), ! io system service@ ots$cvt_ti_l: addressing_mode(general), ! string conversion rtn6 lib$signal: addressing_mode(general); ! error handler!! FORWARD ROUTINE DECLARATIONS.!FORWARD ROUTINE get_qualifiers ,open_channel ,init ,poll_router! ,set_remote_LineRecvBuffers ,write_header ,print_results ,print_router_results ,write_record ,cleanup ,close_channel ;! EXTERNAL LITERALS!EXTERNAL LITERAL* cli$_comma ! cli return status symbols ,cli$_absent;EXTERNAL. NETTEST$G_VERSION_DESC ! Version of NETTEST- ,NETTEST$G_DATE_DESC ! NETTEST's build date ;! MACRO DEFINITIONS:!MACRO7 ! Output string to terminal. Ex: print('Hello...') print (string_to_print) =- lib$put_output($descriptor(string_to_print))%,A ! Check the variable status, and signal an exception if there ! is a problem. check_status = begin local mystatus; if not .status then begin5 $setrwm( watflg=0 ); ! Enable resource waits8 ! If we opened a user mode channel to the output file,% ! we MUST to deassign it (RMS bug?) if .gl_acl_channel NEQ 0 then3 mystatus = $dassgn( chan = .gl_acl_channel ); gl_acl_channel = 0;6 ! Now lets close the RMS channel to the output file. if .gl_rms_channel NEQ 0 then. mystatus = $close( fab = file_out_fab ); gl_rms_channel = 0; lib$signal(.status); end; end%;!! LITERAL DEFINITIONS!LITERAL@ max$msg_sizes = 128 ! These are random choices, but chosen to4 ,max$routers = 32 ! be larger than seems sensible. ,max$operations = 128 ,max$queue_sizes = 16> ,max$alignments = 512 ! This is the cardinality of alignments! ,max$LineRecvBuffers = 32F ,max$nodename_length = 128 ! Allows for nodename, username, password,% ! accountname, quotes and colons.3 ,dflt$operations = 2000 ! 1000 was just too short. ,dflt$queue_size = 1 5 ,file_out_length = 256 ! Length of the output buffer ;!! BIND CONSTANTS!BIND g_MasterType_desc = %ASCID'M',g_SlaveType_desc = %ASCID'S',g_RouterType_desc = %ASCID'R'! Experiment types>,g_throughput_desc = %ASCID' Throughput' ! Standard throughput=,g_loopback_desc = %ASCID'Loopback ' ! Ping-Pong experiment,fao_console_summary1 = %ASCID@'!9ADIOsize:!6SL Qsize:!2SL IOcnt:!6SL Etime:!7SL TotalCPU:!7SL',fao_console_summary2 = %ASCIDG'Type !1AS Seq:!4SL IO/Sec:!5AS CPU/IO:!6AS Int:!6SL Krnl:!6SL Ex:!6SL',fao_console_summary3 = %ASCID=' !AS Mb/Sec:!5AS CPU/Mb: !5AS Sup:!6SL User:!6SL NL:!6SL',fao_console_summary3r = %ASCID?' !AS Sup:!6SL User:!6SL NL:!6SL'/,fao_version_format = %ASCID'V NETTEST !AS !AS'e,fao_machine_header1 = %ASCID'! NETTEST Node Types are M)aster (writer), S)lave (reader) and R)outer'M,fao_machine_header2 = %ASCID'! _Versions__'Z,fao_machine_header3 = %ASCID'! Seq Node System Date and Time VMS DECnet CPU Circuit'],fao_machine_header4 = %ASCID'! ---- ------ -------------------- ---- ------ ---- ----------'I,fao_machine_format = %ASCID'!1AS !4ZL !6AD !20%D !4AD !4AD !4AD !10AD',fao_output_header1 = %ASCIDR'! Node T Note: ALL times (including mode counters) are in 10ms units.',fao_output_header2 = %ASCID>'! y _______DECnet______'*,fao_output_header3 = %ASCID '! p ________Experiment________ _Executor_ Li __________Mode Counters___________ Tot Proc Pge ___Packet____ Rcv',fao_output_header4 = %ASCID '!Seq Node e Bytes Q Aln Operat Elapsed SegSz PipeQ BfSz RB Interr Kernel Exc Spv User Null P CPU CPU Flt Rcv Snd B F',fao_output_header5 = %ASCID '!--- ------ - ----- -- -- ------ ------- ----- ----- ---- -- ------ ------ --- --- ----- ------ ----- ----- --- ------- ------- --' ,fao_output_format = %ASCID '!1AS!3ZL !6AD !1AS !5UL !2UL !2UL !6UL !7UL !5UL !5UL !4UL !2UL !6UL !6UL !3UL !3UL !5UL !6UL !5UL !5UL !4UL !7UL !7UL !2UL' ;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! OWN STORAGE!OWN#! (Default) Experiment Description:<gl_msg_sizes: long initial( 14 ), ! Number of message sizes.8gv_msg_sizes: vector[max$msg_sizes] ! The message sizes.1 initial(8, 32, 128, 512, 600, 1024, 1400, 1600,* 2048, 4096, 8192, 16384, 32768, 65535),;gl_queue_sizes: long initial( 1 ), ! Number of queue sizes.<gv_queue_sizes: vector[max$queue_sizes] ! The queue size[s]. initial( 1 ),8gl_alignments: long initial( 1 ), ! Number of alignments7gv_alignments: vector[max$alignments] ! The alignments. initial(0),O!gl_LineRecvBuffers: long initial( 0 ), ! Number of settings of LineRecvBuffersD!gv_LineRecvBuffers: vector[max$LineRecvBuffers], ! No initial value%! Rest of the experiment description:3gl_repeat, ! Number of times to repeat the trials3gl_operations, ! Number of operations to perform,5gq_time_limit: $bblock[8], ! Or the time of the test.+gl_timed_expt, ! 1 if timed, 0 otherwise.;g_control_iob: $bblock[iob$s_iob + cmsg$s_control_message],3gl_reader_channel, ! IO Channel to the reader node.+!gl_nml_channel, ! IO Channel to local NML1reader_name_buffer: $bblock[max$nodename_length],,g_reader_name_desc: block[dsc$k_s_bln, byte]1 preset( [dsc$a_pointer] = reader_name_buffer,* [dsc$w_length] = max$nodename_length,# [dsc$b_class] = dsc$k_class_s,% [dsc$b_dtype] = dsc$k_dtype_t ),<gl_routers: long initial( 0 ), ! Number of routers to poll.Ngv_router_channels: vector[max$routers], ! The channel numbers of the routers.Jgv_router_seg_size: vector[max$routers], ! The logical link segment sizes.Bgv_router_names: vector[max$routers], ! node names of the routers.gl_acl_channel,gl_rms_channel,)! Performance Info (10 ms counters, etc.).g_stats$start: $bblock[rstats$s_Timing_Stats],,g_stats$end: $bblock[rstats$s_Timing_Stats],Og_stats$delta: $bblock[rstats$s_Timing_Stats], ! This will be reused many times/g_rstats$start: $bblock[rstats$s_Timing_Stats],-g_rstats$end: $bblock[rstats$s_Timing_Stats],Jgv_router_rstats$start: vector[max$routers], ! Pointers to the rstats dataFgv_router_rstats$end: vector[max$routers], ! structures for the router2g_NodeTypes: vector[3] initial( g_MasterType_desc, g_SlaveType_desc, g_RouterType_desc ),:gl_sequence, ! Sequence number. Stored in an informational& ! ACE in the ACL of the output file.file_out_buffer: $bblock[file_out_length],Cfile_out_desc: $bblock[8] initial(file_out_length,file_out_buffer),g_output_filename_buffer: $bblock[256],Ig_output_filename_desc: $bblock[8] initial(256,g_output_filename_buffer),0file_acl_fab: $fab( dnm = 'nettest_results.lis', org = seq, rat = , rfm = var, fop = , fac = < get, put>, shr =  ),0file_out_fab: $fab( dnm = 'nettest_results.lis', org = seq, rat = , rfm = var, fop = , fac = < get, put>, shr =  ),'file_out_rab: $rab( fab = file_out_fab, rac = seq, rbf = file_out_buffer, rop =  ),(ga_expt_type_desc, ! Pointer to an ASCID! Flags. gb_checkdata: byte ! Data corruption checking,gb_append: byte ! Old file,gb_vots: byte ! VOTS Transport7,gb_ping_pong: byte ! Latency (ping pong) or Throughput;!'! set up descriptors for cli interface.!BIND%qual$l_msg_sizes = %ASCID'MSG_SIZES','qual$l_operations = %ASCID'OPERATIONS',)qual$l_queue_sizes = %ASCID'QUEUE_SIZES', qual$l_repeat = %ASCID'REPEAT',)qual$l_destination = %ASCID'DESTINATION',"qual$l_routers = %ASCID'ROUTERS', qual$l_output = %ASCID'OUTPUT', qual$l_append = %ASCID'APPEND',qual$l_vots = %ASCID'VOTS',qual$l_line = %ASCID'LINE',2!qual$l_Receive_Buffers = %ASCID'RECEIVE_BUFFERS',$qual$l_protocol = %ASCID'PROTOCOL',%qual$l_checkdata = %ASCID'CHECKDATA','qual$l_alignments = %ASCID'ALIGNMENTS','qual$l_time_limit = %ASCID'TIME_LIMIT',$qual$l_ping_pong = %ASCID'LOOPBACK'; GLOBAL ROUTINE writer =!++!! FUNCTIONAL DESCRIPTION:!&! Main routine of the local node code.!! FORMAL PARAMETERS:!! NONE.!! IMPLICIT INPUTS:!! NONE.!! IMPLICIT OUTPUTS:! ! Status.!! SIDE EFFECTS:!! NONE.P!-------------------------------------------------------------------------------BEGINLOCALfirst_experiment, l_queue_size,a_expt_length, l_sequence, !l_LRB_index, l_LineRecvBuffers,status;I! Initialize the Timing_Statistics data structure before establishing theG! logical link to the reader so that the remote segment size can easily! be filled in.6status = init_rstats(g_stats$start, node$type_Master); check_status;4status = init_rstats(g_stats$end, node$type_Master); check_status;H! Parse the command qualifiers to find out the nature of the experiment.C! Also create a logical link to the reader and each of the routers.i6g_stats$end[rstats$l_Segment_Size] = get_qualifiers();'! Establish a channel to the local NML.N!IF NOT .gb_vots THEN BEGIN&! gl_nml_channel = Open_NML_Channel();R! getNMLvalues( .gl_nml_channel, g_stats$end[Timing_Stats] , g_reader_name_desc );$! $DASSGN( chan = .gl_nml_channel );!END;A!gl_nml_channel = 0;'!XX if .gl_nml_channel NEQ 0 then beginMC!XX get_exec_values( .gl_nml_channel, g_stats$end[Timing_Stats] ); !XX N!XX g_stats$end[rstats$L_Circuit_Length] = rstats$S_Circuit; ! Maximum length6!XX get_circuit( .gl_nml_channel, g_reader_name_desc,)!XX g_stats$end[rstats$T_Circuit],E2!XX g_stats$end[rstats$L_Circuit_Length] );!XX I!XX get_LineRecvBuffers( .gl_nml_channel, g_stats$end[rstats$T_Circuit],C2!XX .g_stats$end[rstats$L_Circuit_Length],5!XX g_stats$end[rstats$L_LineRecvBuffers] );T!XX end;I! Perform 1-time initialization. Set up the iobs. Open the output file.O2status = init( g_control_iob, gl_reader_channel ); check_status;-4if .gl_timed_expt then a_expt_length = gq_time_limit' else a_expt_length = gl_operations;-print('Entering main loop...');-3print(' '); ! To make it easier to read the output. first_experiment = 1;sO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!i!! Begin the main processing loop:n"!XX ! FOR I = 1 TO REPEAT_COUNT DO$!XX ! FOR J = 1 TO NUM_MSG_SIZES DO!XX ! EXPERIMENT()!l_LRB_index = 0;x !YY DO BEGIN,!YY if .gl_LineRecvBuffers GTR 0 then begin;!YY l_LineRecvBuffers = .gv_LineRecvBuffers[.l_LRB_index];e!YY W!YY ! Set the value of LINE RECEIVE BUFFERS locally, and then read the local settings. I!YY set_LineRecvBuffers( .gl_nml_channel, g_stats$end[rstats$T_Circuit],H!YY .g_stats$end[rstats$L_Circuit_Length], .l_LineRecvBuffers );!YY R!YY ! Determine the new settings, even though this means that essentially we call#!YY ! this routine twice in a row. U!YY getNMLvalues( .gl_nml_channel, g_stats$end[Timing_Stats] , g_reader_name_desc );n!YY P!YY if .l_LineRecvBuffers NEQ .g_stats$end[rstats$l_LineRecvBuffers] then beginN!YY print('%NETTEST-F-BADRECVBUFF, unable to set RECEIVE BUFFERS locally.');!YY status = ss$_abort;!YY check_status;E !YY end;d!YY :!YY ! Set the value of LINE RECEIVE BUFFERS on the readeri!YY set_remote_LineRecvBuffers( g_control_iob, .gl_reader_channel, g_rstats$start, .l_LineRecvBuffers );s!YY @!YY ! Set the value of LINE RECEIVE BUFFERS on each router node,!YY INCR rtr FROM 0 TO (.gl_routers - 1) DON!YY set_remote_LineRecvBuffers( g_control_iob, .gv_router_channels[.rtr],;!YY .gv_router_rstats$start[.rtr], .l_LineRecvBuffers ); !YY 2!YY gl_LineRecvBuffers = .gl_LineRecvBuffers - 1;$!YY l_LRB_index = .l_LRB_index + 1; !YY end;& INCR rc FROM 1 TO .gl_repeat DO BEGIN1 INCR al FROM 0 TO (.gl_alignments - 1) DO BEGINS3 INCR qs FROM 0 TO (.gl_queue_sizes - 1) DO BEGINN ! Need to make sure we don't fire off too many writes in the initial loop.( l_queue_size = .gv_queue_sizes[.qs];! IF (.gl_timed_expt EQL 0) AND-. (.l_queue_size GTR .gl_operations) THEN l_queue_size = .gl_operations; 2 INCR ms FROM 0 TO (.gl_msg_sizes - 1) DO BEGIN7 ! Get the starting statistics for each of the routers.I( INCR rtr FROM 0 TO (.gl_routers - 1) DO poll_router( g_control_iob, .gv_router_channels[.rtr],## .gv_router_rstats$start[.rtr] );S status = master (8 .gl_reader_channel, .gv_msg_sizes[.ms], .l_queue_size,6 .gl_timed_expt, .a_expt_length, .gv_alignments[.al], .gb_ping_pong, g_stats$start, g_stats$end,h g_rstats$start, g_rstats$end ); 5 ! Get the ending statistics for each of the routers.-( INCR rtr FROM 0 TO (.gl_routers - 1) DO! poll_router( g_control_iob,a .gv_router_channels[.rtr]," .gv_router_rstats$end[.rtr] );@ ! Read the sequence number from an informational ACE in the ACLD ! of the output file. Update it to be one greater than the current% ! value. It is initialized to zero.D H3 l_sequence = read_sequence_ace( .gl_acl_channel ); F ! Now prepare and print the results. Compute the differences betweenC ! the start and the end of the experiment for each node. First the,H ! the writer, then the reader, and finally each of the routers in turn.7 ! We reuse g_stats$delta for each of these operations.C& if .first_experiment NEQ 0 then begin write_header( .l_sequence, g_stats$end,a g_rstats$end, gv_router_rstats$end, .gl_routers);  first_experiment = 0; end;-= difference_stats(g_stats$start, g_stats$end, g_stats$delta);-- print_results( g_stats$delta, .l_sequence );-? difference_stats(g_rstats$start, g_rstats$end, g_stats$delta);#' g_stats$delta[rstats$l_Segment_Size] =#& .g_stats$end[rstats$l_Segment_Size];- print_results( g_stats$delta, .l_sequence );0. INCR rtr FROM 0 TO (.gl_routers - 1) DO BEGIN difference_stats(! .gv_router_rstats$start[.rtr],  .gv_router_rstats$end[.rtr],2 g_stats$delta ); + g_stats$delta[rstats$l_Segment_Size] =  .gv_router_seg_size[.rtr];8 print_router_results( g_stats$delta, .l_sequence ); END; D ! Just to make the different experiments more visible, we'll insert6 ! a blank line after the results of each are printed. print(' '); END; ! msg_sizes END; ! queue_lengths  END; ! alignments  END; ! repeat(!YY END WHILE .gl_LineRecvBuffers GTR 0;5! Perform cleanup operations. Close the output file.estatus = cleanup();E check_status;!X! return to sender.n!lreturn ss$_normal;end; aROUTINE get_qualifiers =!++M!! FUNCTIONAL DESCRIPTION:_!e;! This routine checks for the presence of qualifiers in thev?! command line and sets up the global iob with the information.!s! FORMAL PARAMETERS:!n! IMPLICIT INPUTS:!! IMPLICIT OUTPUTS:d!s! SIDE EFFECTS:,!FA! Sets the values of a variety of global variables which indicatei0! the details of the experiment to be performed.! P!-------------------------------------------------------------------------------BEGINrBUILTINemovc3,cvtlf;BINDk_input_buffer_size = 256;LOCALoname_desc: ref $bblock,size,C&l_segment_size, ! DECnet segment sizel_fao_prms: vector[1],l_addr: ref $bblock,.t_input_buffer: $bblock[k_input_buffer_size],-l_input_buffer_desc: $bblock[8] initial(long(n k_input_buffer_size, t_input_buffer)),mstatus;aO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!d! /TIME_LIMIT=dddd-hh:mm:sssG! If /TIME_LIMIT is specified, then convert it to a delta time and load@! it into gq_time_limit. Otherwise, set the time limit to 0, to! indicate indefinite duration.g&if CLI$PRESENT(qual$l_time_limit) thenbeginE. l_input_buffer_desc = k_input_buffer_size;( if CLI$GET_VALUE( qual$l_time_limit, l_input_buffer_desc,R# l_input_buffer_desc) then begin t0 status = $bintim( timbuf = l_input_buffer_desc, timadr = gq_time_limitl ); check_status; gl_timed_expt = 1;L end else begin status = ss$_badparam;s check_status;  endNend else begin gl_timed_expt = 0;end;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /OPERATIONS=$numberr8! Check for the number of operations to perform. Use the3! default value if this qualifier is not specified._&if CLI$PRESENT(qual$l_operations) thenbegin,> ! First make sure that a time limit is not also specified.& IF .gl_timed_expt NEQ 0 THEN BEGIN status = ss$_badparam;  check_status; END;. l_input_buffer_desc = k_input_buffer_size;( if CLI$GET_VALUE( qual$l_operations, l_input_buffer_desc,o l_input_buffer_desc) then# begin ! Convert to an integery, status = OTS$CVT_TI_L( l_input_buffer_desc, gl_operations, 4  ); check_status; endt else begin status = ss$_badparam;N check_status; endsendl else beginD ! Make sure a limit on the length of the experiment was defined. IF .gl_timed_expt EQL 0 THEN@ gl_operations = dflt$operations; ! Set default operations countEND;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$! /MSG_SIZE=$numbera! /MSG_SIZE=($number, ...)!! Get the desired message size(s)a%if CLI$PRESENT(qual$l_msg_sizes) thensbegin gl_msg_sizes = 0; . l_input_buffer_desc = k_input_buffer_size;- status = CLI$GET_VALUE( qual$l_msg_sizes,o l_input_buffer_desc, l_input_buffer_desc);qA while ((.status EQL cli$_comma) OR (.status EQL ss$_normal)) e/ AND (.gl_msg_sizes LSS max$msg_sizes) DO BEGINe, status = OTS$CVT_TI_L( l_input_buffer_desc, gv_msg_sizes[.gl_msg_sizes], 4 ); check_status;1 if .gv_msg_sizes[.gl_msg_sizes] LSS 8 then beginoM print('%NETTEST-E-BADMSGSZ, Message size specified is smaller than 8.');c status = ss$_badparam;A check_status; O end;L5 if .gv_msg_sizes[.gl_msg_sizes] GEQ 65536 then beginAQ print('%NETTEST-E-BADMSGSZ, Message size specified is greater than 65535.');f status = ss$_badparam; check_status; M end; " gl_msg_sizes = .gl_msg_sizes + 1;+ l_input_buffer_desc = k_input_buffer_size; * status = CLI$GET_VALUE( qual$l_msg_sizes, l_input_buffer_desc, l_input_buffer_desc);S end;" if .status EQL cli$_comma thenJ print('%NETTEST-W-MAXMSGSZS, Too many messages sizes. Ignoring excess.') else1 if .status NEQ cli$_absent then check_status;send;P! Not allowed to have /MSG_SIZE on the command line with out at least one value.!if .gl_msg_sizes LEQ 0 then beginI status = ss$_badparam; check_status;-end;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /QUEUE_SIZE=$numbere! /QUEUE_SIZES=($number, ...) !! Get the desired message size(s)e'if CLI$PRESENT(qual$l_queue_sizes) then_begin  gl_queue_sizes = 0; . l_input_buffer_desc = k_input_buffer_size;/ status = CLI$GET_VALUE( qual$l_queue_sizes,  l_input_buffer_desc, l_input_buffer_desc);LA while ((.status EQL cli$_comma) OR (.status EQL ss$_normal)) c3 AND (.gl_queue_sizes LSS max$queue_sizes) DO BEGIN , status = OTS$CVT_TI_L( l_input_buffer_desc,$ gv_queue_sizes[.gl_queue_sizes], 4  ); check_status;5 if .gv_queue_sizes[.gl_queue_sizes] LEQ 0 then beginuG print('%NETTEST-E-BADQSZ, Queue size must be greater than zero.');  status = ss$_badparam;  check_status;  end; & gl_queue_sizes = .gl_queue_sizes + 1;+ l_input_buffer_desc = k_input_buffer_size;-, status = CLI$GET_VALUE( qual$l_queue_sizes, l_input_buffer_desc, l_input_buffer_desc);  end;" if .status EQL cli$_comma thenE print('%NETTEST-W-MAXQSZS, Too many queue sizes. Ignoring excess.')o else1 if .status NEQ cli$_absent then check_status; end;R! Not allowed to have /QUEUE_SIZE on the command line with out at least one value.#if .gl_queue_sizes LEQ 0 then beginL status = ss$_badparam; check_status;7end;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /ALIGNMENT=$number! /ALIGNMENTS=($number, ...)A! On which byte(s) shall we align the data blocks when allocated? &if CLI$PRESENT(qual$l_alignments) thenbeginx gl_alignments = 0;. l_input_buffer_desc = k_input_buffer_size;. status = CLI$GET_VALUE( qual$l_alignments, l_input_buffer_desc, l_input_buffer_desc);mA while ((.status EQL cli$_comma) OR (.status EQL ss$_normal)) u1 AND (.gl_alignments LSS max$alignments) DO BEGINn, status = OTS$CVT_TI_L( l_input_buffer_desc," gv_alignments[.gl_alignments], 4n ); check_status;- if (.gv_alignments[.gl_alignments] LSS 0) ORr7 (.gv_alignments[.gl_alignments] GTR 511) then begincH print('%NETTEST-E-BADALGN, Alignments must be between 0 and 511.'); status = ss$_badparam;m check_status; e end;$ gl_alignments = .gl_alignments + 1;+ l_input_buffer_desc = k_input_buffer_size;O+ status = CLI$GET_VALUE( qual$l_alignments,1 l_input_buffer_desc, l_input_buffer_desc);b end;" if .status EQL cli$_comma thenE print('%NETTEST-W-MAXALGNS, Too many alignments. Ignoring excess.')a else1 if .status NEQ cli$_absent then check_status;eend;Q! Not allowed to have /ALIGNMENT on the command line with out at least one value.d"if .gl_alignments LEQ 0 then begin status = ss$_badparam; check_status;dend;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%o ~ NETTEST_KIT.Aw SET LINE linename RECEIVE BUFFERS!YY /!YY if CLI$PRESENT(qual$l_receive_buffers) thena !YY begin,!YY gl_LineRecvBuffers = 0;2!YY l_input_buffer_desc = k_input_buffer_size;7!YY status = CLI$GET_VALUE( qual$l_receive_buffers,t!YY l_input_buffer_desc,!YY l_input_buffer_desc);k!YY E!YY while ((.status EQL cli$_comma) OR (.status EQL ss$_normal)) ?!YY AND (.gl_LineRecvBuffers LSS max$LineRecvBuffers) DO BEGINg!YY 0!YY status = OTS$CVT_TI_L( l_input_buffer_desc,0!YY gv_LineRecvBuffers[.gl_LineRecvBuffers], !YY 4m !YY );!YY check_status;;!YY if (.gv_LineRecvBuffers[.gl_LineRecvBuffers] LSS 1) ORD!YY (.gv_LineRecvBuffers[.gl_LineRecvBuffers] GTR 32) then begin!YY T!YY print('%NETTEST-E-BADRECVBUFF, receive buffers must be between 1 and 32.');!YY status = ss$_badparam;)!YY check_status; !YY end;f2!YY gl_LineRecvBuffers = .gl_LineRecvBuffers + 1;!YY /!YY l_input_buffer_desc = k_input_buffer_size; 4!YY status = CLI$GET_VALUE( qual$l_receive_buffers,!YY l_input_buffer_desc,!YY l_input_buffer_desc);e !YY end;&!YY if .status EQL cli$_comma thenW!YY print('%NETTEST-W-MAXRECVBUFF, Too many RECEIVE BUFFER values. Ignoring excess.')u !YY else5!YY if .status NEQ cli$_absent then check_status;A!YY _!YY ! Not allowed to have /RECEIVE_BUFFERS on the command line with out at least one value.i/!YY if .gl_LineRecvBuffers LEQ 0 then beginC!YY status = ss$_badparam;=!YY check_status; !YY end;!YY end;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!_! /VOTS ! Is this DECnet or VOTS,if CLI$PRESENT(qual$l_vots) then gb_vots = 1 else gb_vots = 0;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'! /DESTINATION=string%>! Get the node specification for the remote task (the reader). ! node"", or! node"username", or%! node"username password", or finallyM!! node"username password account" !L'if CLI$PRESENT(qual$l_destination) thenTbeginS) if CLI$GET_VALUE( qual$l_destination, g_reader_name_desc,! g_reader_name_desc) then begin-H gl_reader_channel = open_channel( g_reader_name_desc, l_segment_size ); end else beginG print('%NETTEST-E-NOTASK, Value missing for /DESTINATION qualifier.');e status = ss$_badparam;t check_status; end;end else begin> print('%NETTEST-E-NOTASK, No destination was specified.'); status = ss$_badparam; check_status;send;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!t! /ROUTER=node! /ROUTERS=(node1, ...)h:! Find out which machines to poll for routing information.#if CLI$PRESENT(qual$l_routers) then begina gl_routers = 0;s. l_input_buffer_desc = k_input_buffer_size;+ status = CLI$GET_VALUE( qual$l_routers,l l_input_buffer_desc, l_input_buffer_desc);cA while ((.status EQL cli$_comma) OR (.status EQL ss$_normal)) s+ AND (.gl_routers LSS max$routers) DO BEGIN " gv_router_channels[.gl_routers] =$ open_channel( l_input_buffer_desc,+ gv_router_seg_size[.gl_routers] );e> ! Create an ASCID descriptor, and copy the node name into it.9 size = dsc$k_s_bln + .l_input_buffer_desc[dsc$w_length]; : status = LIB$GET_VM(size, ! Number of bytes to allocate.( ! And where to put addr of memory.& gv_router_names[.gl_routers] ); check_status;+ name_desc = .gv_router_names[.gl_routers];_H name_desc[dsc$a_pointer] = .gv_router_names[.gl_routers] + dsc$k_s_bln;? name_desc[dsc$w_length] = .l_input_buffer_desc[dsc$w_length];v) name_desc[dsc$b_class] = dsc$k_class_s;i) name_desc[dsc$b_dtype] = dsc$k_dtype_t;o ! Now copy the characters.gb movc3( name_desc[dsc$w_length], .l_input_buffer_desc[dsc$a_pointer], .name_desc[dsc$a_pointer] ); gl_routers = .gl_routers + 1;+ l_input_buffer_desc = k_input_buffer_size;-( status = CLI$GET_VALUE( qual$l_routers, l_input_buffer_desc, l_input_buffer_desc);! end;" if .status EQL cli$_comma thenA print('%NETTEST-W-MAXRTRS, Too many routers. Ignoring excess.')O else1 if .status NEQ cli$_absent then check_status; # if .gl_routers LEQ 0 then beginxD print('%NETTEST-E-NOROUTER, Value missing for /ROUTER qualifier.'); status = ss$_badparam;v check_status; end;end;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!a! /OUTPUT=filenameL! Specify a file to contain the results of the experiment. The default nameK! is specified in the fab. The name is placed in two fabs: one for the RMS!J! writes used to log the performance info, and the other for accessing and0! updating the sequence number in the files acl.!l"if CLI$PRESENT(qual$l_output) thenbegina$ if CLI$GET_VALUE( qual$l_output, g_output_filename_desc,% g_output_filename_desc) then begin B file_out_fab[fab$l_fna] = .g_output_filename_desc[dsc$a_pointer];A file_out_fab[fab$b_fns] = .g_output_filename_desc[dsc$w_length];sB file_acl_fab[fab$l_fna] = .g_output_filename_desc[dsc$a_pointer];A file_acl_fab[fab$b_fns] = .g_output_filename_desc[dsc$w_length];d end;end;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!S ! /APPENDu<! Is the output file to be appended or started from scratch.0if CLI$PRESENT(qual$l_append) then gb_append = 1 else gb_append = 0;O!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! /CHECKDATAJ! Should the data be checked on the receiving end for transmission errors?6if CLI$PRESENT(qual$l_checkdata) then gb_checkdata = 1 else gb_checkdata = 0;CO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!f ! /LOOPBACKaN! Is this a throughput experiment (/NOLOOPBACK) or a latency test (/LOOPBACK).+if CLI$PRESENT(qual$l_ping_pong) then begine gb_ping_pong = 1;% ga_expt_type_desc = g_loopback_desc;tend else begin gb_ping_pong = 0;' ga_expt_type_desc = g_throughput_desc;tend;return .l_segment_size;uend; <ROUTINE open_channel( node_desc: ref $bblock, a_dev_info ) =!++o!o! FUNCTIONAL DESCRIPTION:.!]<! Opens a channel to the NETTEST task on the node specified.5! Note the different forms the node name can come in:z!! nodename, or! nodename"", or! nodename"username", or)! nodename"username password", or finallyh%! nodename"username password account" !B! And each of these can have a pair of colons appended to the end.!M! FORMAL PARAMETERS:!O! IMPLICIT INPUTS:!c! IMPLICIT OUTPUTS:_!t! SIDE EFFECTS:,! P!-------------------------------------------------------------------------------BEGIN BUILTINcallg;BINDDECnet_desc = %ascid '_NET:', VOTS_desc = %ascid '_OS:',)fao_task1 = %ascid '!AS::"TASK=NETTEST"', Ffao_task2 = %ascid '!AS"TASK=NETTEST"', ! Incase the user includes ::k_task_size = 256;LOCALc l_channel,l_fao_prms: vector[1], l_addr: ref $bblock,t_task: $bblock[k_task_size],t$task_desc: $bblock[8] initial(long( k_task_size, t_task)),tstatus;oB! Now tack the task description on the end. Use faol to write theA! stuff to g_destination_desc. Check to see if the user includedn2! the two colons "::" at the end of the node name.l_fao_prms[0] = .node_desc;d7l_addr = .node_desc[dsc$a_pointer] ! Base of the string$4 + .node_desc[dsc$w_length] ! + Length of the string - 2; ! Second to last char.s(if .l_addr[0,0,16,0] EQL '::' then begin$ status = $faol( ctrstr = fao_task2, outlen = task_desc, outbuf = task_desc, prmlst = l_fao_prms );e check_status;endM else begin$ status = $faol( ctrstr = fao_task1, outlen = task_desc, outbuf = task_desc, prmlst = l_fao_prms );  check_status;end;L! Now we have the complete task description. Lets open a channel to DECnet.l_channel = 0;if .gb_vots thenK l_channel = request_link( VOTS_desc , task_desc, status, .a_dev_info )oelseL l_channel = request_link( DECnet_desc, task_desc, status, .a_dev_info );$if .status NEQ ss$_normal then begin check_status; status = ss$_abort; check_status;end;return .l_channel;end; -ROUTINE init( a_control_iob, l_io_channel ) =t!++t! ! FUNCTIONAL DESCRIPTION:)!B! This routine performs 1-time initialization operations. Included ! here are:O!N(! o Initialize the general control iob;!S5! o Allocate the rstats data blocks for each router;o!u3! o Open the data file and write a header if it isa! a newly created file.!s! FORMAL PARAMETERS:!n! IMPLICIT INPUTS:!! IMPLICIT OUTPUTS:d!s! SIDE EFFECTS:,!FP!-------------------------------------------------------------------------------BEGINeLOCALnsize,econtrol_iob: ref $bblock,-a_fao_desc: ref $bblock,status;-P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!H! Set up the 'generic' control iob used for polling the routers, and forL! stopping all remote slaves. The memory allocated at the end of the iob isP! used as the data buffer for the Router and Stop messages. It is set up so thatM! [iob$a_data] points to the buffer. To use it, l_p1 is set to .a_data. FortH! reading timing info, l_p1 is set the address of some rstats structure.control_iob = .a_control_iob;d8control_iob[iob$a_data] = control_iob[iob] + iob$s_iob; control_iob[iob$l_numargs] = 12;control_iob[iob$l_efn] = 0;?control_iob[iob$a_iosbadr] = control_iob[iob$w_iosb_condition];Ccontrol_iob[iob$a_astadr] = 0;control_iob[iob$l_astprm] = 0;3control_iob[iob$l_p3] = 0; ! Need to specify thesel9control_iob[iob$l_p4] = 0; ! optional parameters so thate2control_iob[iob$l_p5] = 0; ! qio doesn't blow up.control_iob[iob$l_p6] = 0;  qD! For each router, allocate two blocks of memory for the rstats data=! structures which will contain the start and end statistics. size = rstats$s_Timing_Stats;m-INCR rtr FROM 0 TO (.gl_routers - 1) DO BEGIN!: status = LIB$GET_VM(size, ! Number of bytes to allocate.( ! And where to put addr of memory.& gv_router_rstats$start[.rtr] ); check_status;: status = LIB$GET_VM(size, ! Number of bytes to allocate.( ! And where to put addr of memory.$ gv_router_rstats$end[.rtr] ); check_status;END;J! Open the output file and connect a channel to it. If the user specifiedN! the /APPEND qualifier, then move to the end of the file before doing output.!,K! If a new file is created, then write a header to the file containing somerE! details of the experiment (NETTEST version number, cpu types, etc).o@if .gb_append NEQ 0 then begin ! Attempt to use an existing file& status = $OPEN( fab = file_out_fab );G if .status EQL RMS$_FNF then gb_append = 0 ! Somebody stole my baby!e else check_status;eend;0if .gb_append EQL 0 then begin ! Ok, lets create( status = $CREATE( fab = file_out_fab ); check_status;end;! Record the channel number.*gl_rms_channel = .file_out_fab[fab$l_stv];J! So now we've opened the file for RMS access. Now lets open it again forK! user file operations (FAB$V_UFO) so we can use the $CHANGE_ACL service to J! read and update the sequence number stored in an informational ace. TheJ! channel number will be in the stv field of the file_acl_fab if the $OPENM! operation is successful. We extract the channel number so it can be passedi! easily to read_sequence_ace.%status = $OPEN( fab = file_acl_fab );r check_status;m*gl_acl_channel = .file_acl_fab[fab$l_stv];=! Establish a record stream, starting at the end of the file.(status = $connect( rab = file_out_rab ); check_status;s! return happy status.return ss$_normal;end; e"ROUTINE write_header ( l_sequence, a_stats_master, a_stats_slave,e v_a_stats_router: ref vector, l_routers ) =!++g!t! FUNCTIONAL DESCRIPTION:a! D! Write the header into the output file which has just been openned.D! This is done even with files to which we are appending so that any:! changes in experiment setup will be noted in the header.!,! FORMAL PARAMETERS:!)! IMPLICIT INPUTS:!t! IMPLICIT OUTPUTS:h!! SIDE EFFECTS:W!XP!-------------------------------------------------------------------------------BEGINbMACRO & fputsln( file_rab, string_desc ) = BEGIN LOCAL a_string_desc: ref $bblock, status; a_string_desc = string_desc;e5 file_rab[rab$l_rbf] = .a_string_desc[dsc$a_pointer];n4 file_rab[rab$w_rsz] = .a_string_desc[dsc$w_length];! status = $put( rab = file_rab );  check_status; END %,L fao_machine_record( l_sequence, a_rstats, buffer_desc, buffer_length ) = BEGIN LOCAL prm, v_fao_prms: vector[12], rstats: ref $bblock, status;: rstats = a_rstats; ! A little odd because this is a macro prm = -1;I v_fao_prms[prm = (.prm + 1)] = .g_NodeTypes[.rstats[rstats$L_NodeType]];m+ v_fao_prms[prm = (.prm + 1)] = l_sequence;_A v_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_NodeNameLength];L; v_fao_prms[prm = (.prm + 1)] = rstats[rstats$T_NodeName];: v_fao_prms[prm = (.prm + 1)] = rstats[rstats$Q_SysTime];D v_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_VMSversion_Length];= v_fao_prms[prm = (.prm + 1)] = rstats[rstats$T_VMSversion]; G v_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_DECnetVersion_Length];@ v_fao_prms[prm = (.prm + 1)] = rstats[rstats$T_DECnetVersion];1 v_fao_prms[prm = (.prm + 1)] = 4; ! Fixed lengthO v_fao_prms[prm = (.prm + 1)] = rstats[rstats$T_CpuType]; ! in this 'string.''A v_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_Circuit_Length]; ; v_fao_prms[prm = (.prm + 1)] = rstats[rstats$T_Circuit];;;+ buffer_desc[dsc$w_length] = buffer_length;m- status = $faol( ctrstr = fao_machine_format,q& outlen = buffer_desc[dsc$w_length], outbuf = buffer_desc, prmlst = v_fao_prms );! check_status; END %;LOCAL! v_fao_prms: vector[2], status;-!fputsln( file_out_rab, fao_version_header );i%! Place the version line in the file: !n%! V NETTEST Vx.y dd-mmm-yyyy hh:mm:ssl'v_fao_prms[0] = NETTEST$G_VERSION_DESC; $v_fao_prms[1] = NETTEST$G_DATE_DESC;.file_out_desc[dsc$w_length] = file_out_length;,status = $faol( ctrstr = fao_version_format,' outlen = file_out_desc[dsc$w_length],  outbuf = file_out_desc,  prmlst = v_fao_prmsD ); check_status;m'fputsln( file_out_rab, file_out_desc );$-fputsln( file_out_rab, fao_machine_header1 );[-fputsln( file_out_rab, fao_machine_header2 );;-fputsln( file_out_rab, fao_machine_header3 );r-fputsln( file_out_rab, fao_machine_header4 );t! Describe each node:'1fao_machine_record( .l_sequence, .a_stats_master,5' file_out_desc, file_out_length); 'fputsln( file_out_rab, file_out_desc );n0fao_machine_record( .l_sequence, .a_stats_slave,' file_out_desc, file_out_length);A'fputsln( file_out_rab, file_out_desc );f,INCR rtr FROM 0 TO (.l_routers - 1) DO BEGIN: fao_machine_record( .l_sequence, .v_a_stats_router[.rtr],( file_out_desc, file_out_length);( fputsln( file_out_rab, file_out_desc );END;/! Now output the header for the experiment dataA,fputsln( file_out_rab, fao_output_header1 );,fputsln( file_out_rab, fao_output_header2 );,fputsln( file_out_rab, fao_output_header3 );,fputsln( file_out_rab, fao_output_header4 );,fputsln( file_out_rab, fao_output_header5 );! return happy status.return ss$_normal;end; e&!YY ROUTINE set_remote_LineRecvBuffers.!YY ( control_iob: ref $bblock, l_io_channel,1!YY rstats: ref $bblock, l_LineRecvBuffers ) =r!YY !++ !YY !t!YY ! FUNCTIONAL DESCRIPTION:e!YY ! E!YY ! This routine sets the value of LINE RECEIVE BUFFERS on a remote_F!YY ! node, and reads the current values back to ensure that the value'!YY ! was set. Synchronous IO is used.G!YY !t!YY ! FORMAL PARAMETERS:!YY !s!YY ! control_iobm!YY ! TYPE: STRUCTUREn!YY ! ACCESS: READ / WRITE!YY ! MECHANISM: BY REFERENCE!YY !!B!YY ! This is the address of an iob which should be semi-prepared3!YY ! for use in communicating with a remote task.!YY !e!YY ! l_io_channel!YY ! TYPE: LONGWORD!YY ! ACCESS: READ ONLYE!YY ! MECHANISM: BY VALUE!YY !Y@!YY ! A longword whose lo-order word contains the number of theH!YY ! io channel of a DECnet link to a NETTEST task (Slave in state 0).!YY !e !YY ! rstats!YY ! TYPE: STRUCTURE !YY ! ACCESS: WRITE ONLY!YY ! MECHANISM: BY REFERENCE!YY !c<!YY ! Address of the rstats structure to be filled with the;!YY ! instantaneous performance data from the remote task. !YY !_!YY ! IMPLICIT INPUTS:!YY ! !YY ! IMPLICIT OUTPUTS:l!YY !c!YY ! SIDE EFFECTS:m!YY ! T!YY !------------------------------------------------------------------------------- !YY BEGIN.!YY !YY BUILTIN.!YY !YY callg;!YY !YY LOCALg!YY !YY msg: ref $bblock, !YY status;V!YY #!YY msg = .control_iob[iob$a_data]; !YY T!YY !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!8!YY ! First send an Update_DECnet message to the remote.!YY -!YY control_iob[iob$l_chan] = .l_io_channel; -!YY control_iob[iob$l_func] = IO$_WRITEVBLK; !YY #!YY ! Point to the control message.f6!YY control_iob[iob$l_p1] = .control_iob[iob$a_data];:!YY control_iob[iob$l_p2] = umsg$S_Update_DECnet_Message;!YY !!YY ! Set up the control message.Y!YY /!YY msg[umsg$B_Type] = msg$type_Update_DECnet;c5!YY msg[umsg$L_LineRecvBuffers] = .l_LineRecvBuffers;E#!YY msg[umsg$L_Pipeline_Quota] = 0; !YY '!YY ! All set, so lets tell the reader.f!YY ;!YY status = callg( control_iob[iob$l_numargs], sys$qiow );a!YY check_status;!YY R!YY ! Load the status from the iosb into status and check it. Exit if bad status.!YY 0!YY status = .control_iob[iob$w_iosb_condition];0!YY if .status gtr ss$_normal then check_status;!YY T!YY !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!6!YY ! Now lets read the new info from the remote node.!YY ,!YY control_iob[iob$l_func] = IO$_READVBLK;!YY F!YY control_iob[iob$l_p1] = rstats[Timing_Stats]; ! We store it here.!YY control_iob[iob$l_p2]E3!YY = rstats$s_Timing_Stats; ! And its this big.T!YY &!YY ! All set, so lets read the stats.!YY ;!YY status = callg( control_iob[iob$l_numargs], sys$qiow );o!YY check_status;e!YY R!YY ! Load the status from the iosb into status and check it. Exit if bad status.!YY 0!YY status = .control_iob[iob$w_iosb_condition];0!YY if .status gtr ss$_normal then check_status;!YY <!YY ! Check that the new value of LineRecvBuffer is correct.!YY J!YY if .l_LineRecvBuffers NEQ .rstats[rstats$L_LineRecvBuffers] then beginT!YY print('%NETTEST-F-BADRECVBUFF, unable to set RECEIVE BUFFERS on remote node.');!YY status = ss$_abort;!YY check_status;!YY end;!YY !YY return ss$_normal;!YY !YY END; r?ROUTINE poll_router ( a_control_iob, l_io_channel, a_rstats ) = !++u!u! FUNCTIONAL DESCRIPTION:I!TA! This routine collects the current timing stats from a router byfA! synchronously writing an R message on the channel to the routers2! and doing a synchronous read to get the results.!I! FORMAL PARAMETERS:![! a_control_iob ! TYPE: STRUCTURE_! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!e>! This is the address of an iob which should be semi-prepared/! for use in communicating with a remote task.c!l! l_io_channel! TYPE: LONGWORD! ACCESS: READ ONLYa! MECHANISM: BY VALUE! <! A longword whose lo-order word contains the number of the>! io channel of a DECnet link to a router (Slave in state 0).!n ! a_rstats! TYPE: STRUCTUREt! ACCESS: WRITE ONLY! MECHANISM: BY REFERENCE!l8! Address of the rstats structure to be filled with the7! instantaneous performance data from the remote task. !d! IMPLICIT INPUTS:!w! IMPLICIT OUTPUTS:g!! SIDE EFFECTS:[!$P!-------------------------------------------------------------------------------BEGINrBUILTINtcallg;LOCALtcmsg_R: ref $bblock,fcontrol_iob: ref $bblock,Estatus;qcontrol_iob = .a_control_iob;r"cmsg_R = .control_iob[iob$a_data];P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!P! First send a Router message to the remote telling it to send its current stats)control_iob[iob$l_chan] = .l_io_channel;i)control_iob[iob$l_func] = IO$_WRITEVBLK;i! Point to the control message.u2control_iob[iob$l_p1] = .control_iob[iob$a_data];control_iob[iob$l_p2] = 4;!! Set up the control message.!'cmsg_R[cmsg$B_Type] = msg$type_Router;p#! All set, so lets tell the reader.t7status = callg( control_iob[iob$l_numargs], sys$qiow );. check_status;lN! Load the status from the iosb into status and check it. Exit if bad status.,status = .control_iob[iob$w_iosb_condition];,if .status gtr ss$_normal then check_status;P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!8! Now lets read the current timing info from the reader.(control_iob[iob$l_func] = IO$_READVBLK;8control_iob[iob$l_p1] = .a_rstats; ! We store it here.control_iob[iob$l_p2]n/ = rstats$s_Timing_Stats; ! And its this big.l"! All set, so lets read the stats.7status = callg( control_iob[iob$l_numargs], sys$qiow ); check_status;N! Load the status from the iosb into status and check it. Exit if bad status.,status = .control_iob[iob$w_iosb_condition];,if .status gtr ss$_normal then check_status;return ss$_normal;END; /ROUTINE print_results( a_rstats, l_sequence ) =!!++!!!! FUNCTIONAL DESCRIPTION:!!!N! Performance statistics for one cpu are output to the console and data file. !n! FORMAL PARAMETERS: ! a_rstats(! Address of the rstats data structure.!a ! l_sequence-! Sequence number of the current experiment.!!!! IMPLICIT INPUTS:!!! IMPLICIT OUTPUTS:!!!! Writes to the data file.!h! SIDE EFFECTS:n!/P!-------------------------------------------------------------------------------BEGINeBIND fao_UL = %ASCID'!#UL' ;BUILTINlsubm,eediv,nmulf,ecvtrfl,gdivf,pcvtlf;LOCALtio_per_sec_buffer: $bblock[16],ncpu_per_io_buffer: $bblock[16],ncpu_per_Mb_buffer: $bblock[16],eMb_per_sec_buffer: $bblock[16],=;io_per_sec_desc: vector[2] initial( 16, io_per_sec_buffer),o;cpu_per_io_desc: vector[2] initial( 16, cpu_per_io_buffer),t;cpu_per_Mb_desc: vector[2] initial( 16, cpu_per_Mb_buffer),e;Mb_per_sec_desc: vector[2] initial( 16, Mb_per_sec_buffer),s"q_etime: vector[2], ! Elapsed time!l_etime, f_etime, ! Elapsed timeh'l_total_cpu, f_total_cpu, ! Total ticksdf_opcount, ! Total operationsT7l_total_bytes, f_total_bytes, ! Total bytes transferredt,f_io_per_sec, l_io_per_sec, ! IOs per second5f_cpu_per_io, l_cpu_per_io, ! Cost (CPU ticks) per io-<f_bytes_per_sec, f_Mb_per_sec, ! Data rate ([M]bytes/second)Mf_cpu_per_byte, f_cpu_per_Mb, l_cpu_per_Mb, ! Cost (CPU ticks) per [Mega] bitTprm,l_tmp_storage,l_tmp, l_digits,'f_tmp,l_fao_prms: vector[32],zero: vector[2],;rstats: ref $bblock,,status;r rstats = .a_rstats; P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B! First prepare the numbers, then we'll worry about printing them.M! Compute elapsed wall clock time in 10ms ticks. The unit of rstats$q_systimes<! is 100 nano-seconds. Divide by 100000 to get 10 ms units.Eediv(%ref(100000), rstats[rstats$q_systime], l_etime, l_tmp_storage);="! ^ Throw away the remainderCcvtlf( l_etime, f_etime ); ! floating elapsed time (in 10 ms ticks)2! Compute cpu usageh)l_total_cpu = .rstats[rstats$l_interrupt]g + .rstats[rstats$l_kernel] + .rstats[rstats$l_exec] + .rstats[rstats$l_super], + .rstats[rstats$l_user];)8!l_total_cpu = .l_etime - .g_stats$delta[rstats$l_null];"cvtlf( l_total_cpu, f_total_cpu );! Compute IO rate (IOs / sec)m+cvtlf( %ref( 100 ), f_tmp ); ! Ticks/Sec3divf( f_tmp, f_etime, f_tmp ); ! Elapsed secondso<cvtlf( rstats[rstats$l_operations], f_opcount ); ! Total IOs7divf( f_tmp, f_opcount, f_io_per_sec ); ! IOs per sect%! Compute data transfer rate (Mb/Sec) 2cvtlf( rstats[rstats$l_msg_size], l_tmp_storage );Cmulf( l_tmp_storage, f_io_per_sec, f_bytes_per_sec ); ! Bytes / Seca+cvtlf( %ref( 1024 * 128 ), l_tmp_storage );uCdivf( l_tmp_storage, f_bytes_per_sec, f_Mb_per_sec ); ! Mbits / Sec )! Compute the cost (in 10ms ticks) per io?divf( f_opcount, f_total_cpu, f_cpu_per_io ); ! CPU secs per iod5! Compute the cost (in 10ms ticks) per Mb transferredoJl_total_bytes = .rstats[rstats$l_msg_size] * .rstats[rstats$l_operations];&cvtlf( l_total_bytes, f_total_bytes );Hdivf( f_total_bytes, f_total_cpu, f_cpu_per_byte ); ! CPU ticks per byte0cvtlf( %ref( 1024 * 1024 / 8 ), l_tmp_storage );Kmulf( l_tmp_storage, f_cpu_per_byte, f_cpu_per_Mb ); ! CPU ticks per M bits-J!-------------------------------------------------------------------------J! Wrote a special routine to print F-floats in FORTRAN Fw.n format becauseA! there is no documented routine in the RTL to do this. CVT_F_TOiM!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!r/! Prepare the first line of the console summaryhL!NODENAME IOsize:###### Qsize:## IOcnt:###### Etime:####### TotalCPU:#######1l_fao_prms[0] = .rstats[rstats$l_NodeNameLength];t+l_fao_prms[1] = rstats[rstats$T_NodeName]; +l_fao_prms[2] = .rstats[rstats$l_msg_size];-l_fao_prms[3] = .rstats[rstats$l_queue_size];o-l_fao_prms[4] = .rstats[rstats$l_operations];ol_fao_prms[5] = .l_etime;1l_fao_prms[6] = .l_total_cpu;;.file_out_desc[dsc$w_length] = file_out_length;,status = $faol( ctrstr=fao_console_summary1, outlen=file_out_desc,s outbuf=file_out_desc,[ prmlst=l_fao_prmsd ); check_status;o'status = lib$put_output(file_out_desc);m check_status;M!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!0! Prepare the second line of the console summaryL!Type # Seq:#### IO/Sec:##### CPU/IO:####.# Int:###### Krnl:###### Ex:######L!Type # Seq:#### IO/Sec: ##.# CPU/IO: ##.## Int:###### Krnl:###### Ex:######%cvtrfl( f_io_per_sec, l_io_per_sec );z"if .l_io_per_sec LSS 10 then begin/ ! Most accuracy for smallest magnitude numbersg< status = cvt_f_to ( .f_io_per_sec, 5, 2, io_per_sec_desc ); check_status;endb(else if .l_io_per_sec LSS 100 then begin7 ! A little more accuracy for smaller magnitude numbersr< status = cvt_f_to ( .f_io_per_sec, 5, 1, io_per_sec_desc ); check_status;endI else begin l_fao_prms[0] = 5;N l_fao_prms[1] = .l_io_per_sec; ! status = $faol( ctrstr = fao_UL, outlen = io_per_sec_desc, outbuf = io_per_sec_desc, prmlst = l_fao_prms );t check_status;end; v>! Provide a little more accuracy for smaller magnitude numbers/ if .l_cpu_per_io LSS 10 then l_digits = 3O/else if .l_cpu_per_io LSS 100 then l_digits = 2F else l_digits = 1;eCstatus = cvt_f_to ( .f_cpu_per_io, 6, .l_digits, cpu_per_io_desc ); check_status;O9l_fao_prms[0] = .g_NodeTypes[.rstats[rstats$L_NodeType]];cl_fao_prms[1] = .l_sequence;!l_fao_prms[2] = io_per_sec_desc;n!l_fao_prms[3] = cpu_per_io_desc; ,l_fao_prms[4] = .rstats[rstats$l_interrupt];)l_fao_prms[5] = .rstats[rstats$l_kernel];r'l_fao_prms[6] = .rstats[rstats$l_exec];N.file_out_desc[dsc$w_length] = file_out_length;,status = $faol( ctrstr=fao_console_summary2, outlen=file_out_desc,i outbuf=file_out_desc,l prmlst=l_fao_prmsN ); check_status;e'status = lib$put_output(file_out_desc); check_status;M!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!t/! Prepare the third line of the console summaryvL! Throughput Mb/Sec:##.## CPU/Mb:##### Sup:###### User:###### NL:######L! Loopback Mb/Sec:##.## CPU/Mb: ##.# Sup:###### User:###### NL:######;status = cvt_f_to ( .f_Mb_per_sec, 5, 2, Mb_per_sec_desc );c check_status;m%cvtrfl( f_cpu_per_Mb, l_cpu_per_Mb );r#if .l_cpu_per_Mb LSS 100 then begin 7 ! A little more accuracy for smaller magnitude numbers < status = cvt_f_to ( .f_cpu_per_Mb, 5, 1, cpu_per_Mb_desc ); check_status;endi else begin l_fao_prms[0] = 5;g l_fao_prms[1] = .l_cpu_per_Mb;m! status = $faol( ctrstr = fao_UL,. outlen = cpu_per_Mb_desc, outbuf = cpu_per_Mb_desc, prmlst = l_fao_prms );F check_status;end; - prm = -1;-2l_fao_prms[prm = (.prm + 1)] = .ga_expt_type_desc;0l_fao_prms[prm = (.prm + 1)] = Mb_per_sec_desc;0l_fao_prms[prm = (.prm + 1)] = cpu_per_Mb_desc;7l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_super];f6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_user];6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_null];.file_out_desc[dsc$w_length] = file_out_length;,status = $faol( ctrstr=fao_console_summary3, outlen=file_out_desc,d outbuf=file_out_desc, prmlst=l_fao_prmsm ); check_status;o'status = lib$put_output(file_out_desc); check_status;a0status = write_record( .a_rstats, .l_sequence ); check_status;mreturn ss$_normal;end; s6ROUTINE print_router_results( a_rstats, l_sequence ) =!++ !_! FUNCTIONAL DESCRIPTION: !.?! Performance statistics for one node are output to the console ! and data file. t!s! FORMAL PARAMETERS: ! a_rstats(! Address of the rstats data structure.! ! l_sequence-! Sequence number of the current experiment.e!h! IMPLICIT INPUTS:!.! IMPLICIT OUTPUTS:s!s! Writes to the data file.!r! SIDE EFFECTS: !tP!-------------------------------------------------------------------------------BEGINCBIND fao_UL = %ASCID'!#UL' ;BUILTIN subm,dediv,mulf,ocvtrfl, divf, cvtlf;LOCALsio_per_sec_buffer: $bblock[16],.cpu_per_io_buffer: $bblock[16], cpu_per_Mb_buffer: $bblock[16],];io_per_sec_desc: vector[2] initial( 16, io_per_sec_buffer),;;cpu_per_io_desc: vector[2] initial( 16, cpu_per_io_buffer),f;cpu_per_Mb_desc: vector[2] initial( 16, cpu_per_Mb_buffer),s"q_etime: vector[2], ! Elapsed time!l_etime, f_etime, ! Elapsed timec'l_total_cpu, f_total_cpu, ! Total ticksvf_opcount, ! Total operations7l_total_bytes, f_total_bytes, ! Total bytes transferredc,f_io_per_sec, l_io_per_sec, ! IOs per second5f_cpu_per_io, l_cpu_per_io, ! Cost (CPU ticks) per io$Jf_bytes_per_sec, f_Kb_per_sec, l_Kb_per_sec, ! Data rate ([K]bytes/second)Kf_cpu_per_byte, f_cpu_per_Mb, l_cpu_per_Mb, ! Cost (CPU ticks) per [K] byte prm,l_tmp_storage,l_tmp, l_digits,uf_tmp,l_fao_prms: vector[32],=ze  ~ NETTEST_KIT.Aw! Provide a little more accuracy for smaller magnitude numbers/ if .l_cpu_per_io LSS 10 then l_digits = 3 /else if .l_cpu_per_io LSS 100 then l_digits = 2  else l_digits = 1;sCstatus = cvt_f_to ( .f_cpu_per_io, 6, .l_digits, cpu_per_io_desc );t check_status;s9l_fao_prms[0] = .g_NodeTypes[.rstats[rstats$L_NodeType]];;l_fao_prms[1] = .l_sequence;!l_fao_prms[2] = io_per_sec_desc;h!l_fao_prms[3] = cpu_per_io_desc;u,l_fao_prms[4] = .rstats[rstats$l_interrupt];)l_fao_prms[5] = .rstats[rstats$l_kernel];h'l_fao_prms[6] = .rstats[rstats$l_exec];U.file_out_desc[dsc$w_length] = file_out_length;,status = $faol( ctrstr=fao_console_summary2, outlen=file_out_desc,e outbuf=file_out_desc,Y prmlst=l_fao_prmso ); check_status;_'status = lib$put_output(file_out_desc); check_status;SM!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!f/! Prepare the third line of the console summarynL! Sup:###### User:###### NL:###### prm = -1;T2l_fao_prms[prm = (.prm + 1)] = .ga_expt_type_desc;7l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_super];T6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_user];6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_null];.file_out_desc[dsc$w_length] = file_out_length;-status = $faol( ctrstr=fao_console_summary3r,n outlen=file_out_desc,  outbuf=file_out_desc,e prmlst=l_fao_prmsD ); check_status;r'status = lib$put_output(file_out_desc);P check_status;0status = write_record( .a_rstats, .l_sequence ); check_status;oreturn ss$_normal;end; e/ROUTINE write_record ( a_rstats, l_sequence ) =m!++r!t! FUNCTIONAL DESCRIPTION:P!:@! One performance statistics record is output to the data file. !-! FORMAL PARAMETERS:!-! IMPLICIT OUTPUTS:-!-! Writes to the data file.!t! SIDE EFFECTS:A!P!-------------------------------------------------------------------------------BEGINlBUILTINgsubm,tediv;[LOCALaprm,l_tmp_storage,zero: vector[2],!q_etime: vector[2],!l_etime,l_fao_prms: vector[32],arstats: ref $bblock,rstatus;l rstats = .a_rstats;tP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$! Write the data to the output file. prm = -1;o2l_fao_prms[prm = (.prm + 1)] = .ga_expt_type_desc;@l_fao_prms[prm = (.prm + 1)] = .l_sequence; ! Sequence Number@l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_NodeNameLength];:l_fao_prms[prm = (.prm + 1)] = rstats[rstats$T_NodeName];Hl_fao_prms[prm = (.prm + 1)] = .g_NodeTypes[.rstats[rstats$L_NodeType]];:l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_Msg_Size];<l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_Queue_Size];;l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_Alignment];!<l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_Operations];0! Compute elapsed wall clock time in 10ms ticks.F! The units are 100 nano-seconds. Divide by 100000 to get 10 ms unitsEediv(%ref(100000), rstats[rstats$q_systime], l_etime, l_tmp_storage);b! ^ Ignore the remainderFl_fao_prms[prm = (.prm + 1)] = .l_etime; ! Elapsed time in 10ms units>l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_Segment_Size];=l_fao_prms[prm = (.prm + 1)] = ! Executor Pipeline Quotac+ (if .rstats[rstats$l_PipelineQuota] NEQ 0e% then .rstats[rstats$l_PipelineQuota]; else 9999999r );e:l_fao_prms[prm = (.prm + 1)] = ! Executor Buffer Size, (if .rstats[rstats$l_ExecBufferSize] NEQ 0& then .rstats[rstats$l_ExecBufferSize] else 9999999  );L:l_fao_prms[prm = (.prm + 1)] = ! Line Receive Buffers- (if .rstats[rstats$L_LineRecvBuffers] NEQ 0 ' then .rstats[rstats$L_LineRecvBuffers]I else 9999999M );T;l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_interrupt];n8l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_kernel];6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_exec];7l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_super];r6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_user];6l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_null];:l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_interrupt] + .rstats[rstats$l_kernel] + .rstats[rstats$l_exec] + .rstats[rstats$l_super]l + .rstats[rstats$l_user];_=l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_process_cpu];s=l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_page_faults];:=l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_rcv_packets];=l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_snd_packets];l=l_fao_prms[prm = (.prm + 1)] = .rstats[rstats$l_rcv_buffail];f.file_out_desc[dsc$w_length] = file_out_length;+status = $faol( ctrstr = fao_output_format,  outlen = file_out_desc,_ outbuf = file_out_desc,a prmlst = l_fao_prms_ ); check_status;eD!status = lib$put_output(file_out_desc); ! TEMPORARY, FOR DEBUGGING.8file_out_rab[rab$l_rbf] = .file_out_desc[dsc$a_pointer];7file_out_rab[rab$w_rsz] = .file_out_desc[dsc$w_length]; !status = $put( rab=file_out_rab);! check_status;!return ss$_normal;end; !ROUTINE cleanup =!!++ !s! FUNCTIONAL DESCRIPTION: !lC! Cleans up by freeing up the memory that was previously allocated.1! ! FORMAL PARAMETERS:!$! IMPLICIT INPUTS:!n! IMPLICIT OUTPUTS: !0! SIDE EFFECTS:n!.P!-------------------------------------------------------------------------------BEGIN LOCALrsize,status;l%! Deassign the channel to the reader.e3close_channel( g_control_iob, .gl_reader_channel );pK! For each router, deallocate its two blocks of rstats data and $dassgn thea! channel to it.size = rstats$s_Timing_Stats;t-INCR rtr FROM 0 TO (.gl_routers - 1) DO BEGINs; status = LIB$FREE_VM(size, ! Number of bytes to allocate.;( ! And where to put addr of memory.& gv_router_rstats$start[.rtr] ); check_status;; status = LIB$FREE_VM(size, ! Number of bytes to allocate.o( ! And where to put addr of memory.$ gv_router_rstats$end[.rtr] ); check_status;; close_channel( g_control_iob, .gv_router_channels[.rtr] );sEND;gl_routers = 0;g=! Now $CLOSE and $DASSGN the two channels to the output file.e7! If we openned a user mode channel to the output file,f#! we MUST to deassign it (RMS bug?)bif .gl_acl_channel NEQ 0 then / status = $dassgn( chan = .gl_acl_channel ); gl_acl_channel = 0; check_status; 4! Now lets close the RMS channel to the output file.if .gl_rms_channel NEQ 0 then_* status = $close( fab = file_out_fab );gl_rms_channel = 0;; check_status;l)! And close the channel to the local NML.,$!if .gl_nml_channel NEQ 0 then begin-! status = $dassgn( chan = .gl_nml_channel );_! check_status;u!end; ! return aok status.!freturn ss$_normal;end; r7ROUTINE close_channel ( a_control_iob, l_io_channel ) =-!++-!-! FUNCTIONAL DESCRIPTION: !pE! This routine sends a synchronous Stop message using the supplied io %! channel, then $DASSGNs the channel.d!h! FORMAL PARAMETERS:!!! a_control_iob!! TYPE: STRUCTURE!! ACCESS: READ / WRITE! MECHANISM: BY REFERENCE!o>! This is the address of an iob which should be semi-prepared/! for use in communicating with a remote task.s! ! l_io_channel! TYPE: LONGWORD! ACCESS: READ ONLYt! MECHANISM: BY VALUE!a<! A longword whose lo-order word contains the number of the1! io channel of a DECnet link to a NETTEST task.[!a! IMPLICIT INPUTS:!a! IMPLICIT OUTPUTS:1!_! SIDE EFFECTS:t!lP!-------------------------------------------------------------------------------BEGINoBUILTIN,callg;LOCAL_cmsg_S: ref $bblock,_control_iob: ref $bblock,dstatus;hcontrol_iob = .a_control_iob;t"cmsg_S = .control_iob[iob$a_data];P!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!P! First send a Router message to the remote telling it to send its current stats)control_iob[iob$l_chan] = .l_io_channel;)control_iob[iob$l_func] = IO$_WRITEVBLK; ! Point to the control message.2control_iob[iob$l_p1] = .control_iob[iob$a_data];*control_iob[iob$l_p2] = 4; ! One longword! Set up the control message.a%cmsg_S[cmsg$B_Type] = msg$type_Stop;p#! All set, so lets tell the reader.l7status = callg( control_iob[iob$l_numargs], sys$qiow );c check_status;eN! Load the status from the iosb into status and check it. Exit if bad status.,status = .control_iob[iob$w_iosb_condition];,if .status gtr ss$_normal then check_status;6! NEED TO UNDERSTAND SYNCHRONOUS DISCONNECTION BETTER.:!XX ! Now that the write is complete, disconnect the link.!XX ,!XX status = $dassgn( chan = l_io_channel );!XX check_status;rreturn ss$_normal;END;endleludomo LSS 10 then l_digits = 3O/else if .l_cpu_per_io LSS 100 then l_digits = 2F else l_digits = 1;eCstatus = cvt_f_to ( .f_cpu_per#*[U_SMITH.NETTEST.TEST2]WRITER.OBJ;1+,mw.-/ 4---LType !1AS Seq:!4SL IO/Sec:!5AS CPU/IO:!6AS Int:!6SL Krnl:!6SL Ex:!6SLE !AS Mb/Sec:!5AS CPU/Mb: !5AS Sup:!6SL User:!6SL NL:!6SL; !AS Sup:!6SL User:!6SL NL:!6SL=(V NETTEST !AS !ASp! NETTEST Node Types are M)aster (writer), S)lave (reader) and R)outerF!  _Versions__.! Seq Node System Date and Time VMS DECnet CPU Circuit;! ---- ------ -------------------- ---- ------ ---- ---------->X!1AS !4ZL !6AD !20%D !4AD !4AD !4AD !10AD+! Node T Note: ALL times (including mode counters) are in 10ms units.P! y _______DECnet______<,! p ________Experiment________ _Executor_ Li __________Mode Counters___________ Tot Proc Pge ___Packet____ Rcvp!Seq Node e Bytes Q Aln Operat Elapsed SegSz PipeQ BfSz RB Interr Kernel Exc Spv User Null P CPU CPU Flt Rcv Snd B F!--- ------ - ----- -- -- ------ ------- ----- ----- ---- -- ------ ------ --- --- ----- ------ ----- ----- --- ------- ------- --!1AS!3ZL !6AD !1AS !5UL !2UL !2UL !6UL !7UL !5UL !5UL !4UL !2UL !6UL !6UL !3UL !3UL !5UL !6UL !5UL !5UL !4UL !7UL !7UL !2UL{nettest_results.lisnettest_results.lisQMSG_SIZES OPERATIONS QUEUE_SIZES REPEATDESTINATION  ROUTERS OUTPUT0APPEND@VOTSPLINE\PROTOCOLhCHECKDATA xALIGNMENTS TIME_LIMIT LOOPBACKEntering main loop...Q Q QP Xx@ @QQ<Q QQQP"CPB CD(DMASTER INIT_RSTATSDIFFERENCE_STATSREAD_SEQUENCE_ACECVT_F_TO REQUEST_LINKLIB$PUT_OUTPUT LIB$GET_INPUT LIB$GET_VM LIB$FREE_VM CLI$PRESENT CLI$GET_VALUESYS$QIOSYS$QIOW OTS$CVT_TI_L LIB$SIGNAL CLI$_COMMA CLI$_ABSENTNETTEST$G_VERSION_DESCNETTEST$G_DATE_DESC SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWMJ SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSEP WRITER  o%&             , +^~  INIT_RSTATSPWWE~ SYS$SETRWM P P SYS$DASSGN   D SYS$CLOSE W LIB$SIGNAL~d  INIT_RSTATSPWWE~ SYS$SETRWM P P SYS$DASSGN   D SYS$CLOSE W LIB$SIGNALP  ` PWWE~ SYS$SETRWM P P SYS$DASSGN   D SYS$CLOSE W LIB$SIGNAL\  T nP nLIB$PUT_OUTPUTLIB$PUT_OUTPUTL [1H U1T1DY\ YP P YZS1< VRBB@ ` VR8d  ~ELݮ\ YC  MASTERPW< VRBTB@ ` VR READ_SEQUENCE_ACEPXծ$< T8d XԮd  DIFFERENCE_STATSX8DIFFERENCE_STATS LX< VR6BTBDIFFERENCE_STATSB LXVRƟLIB$PUT_OUTPUTZS1rT1; U1#[PWWE~ SYS$SETRWM P P SYS$DASSGN   D SYS$CLOSE W LIB$SIGNALP!GET_QUALIFIERSP%NETTEST-E-BADMSGSZ, Message size specified is smaller than 8.Q>%NETTEST-E-BADMSGSZ, Message size specified is greater than 65535.QBD%NETTEST-W-MAXMSGSZS, Too many messages sizes. Ignoring excess.@%NETTEST-E-BADQSZ, Queue size must be greater than zero.8%NETTEST-W-MAXQSZS, Too many queue sizes. Ignoring excess.Q;%NETTEST-E-BADALGN, Alignments must be between 0 and 511.Q9\%NETTEST-W-MAXALGNS, Too many alignments. Ignoring excess.Q;%NETTEST-E-RPTMSNG, Repeat value missing from /REPEATQ5%NETTEST-E-NOTASK, Value missing for /DESTINATION qualifier.<$ %NETTEST-E-NOTASK, No destination was specified.0h %NETTEST-W-MAXRTRS, Too mxany routers. Ignoring excess.Q7 %NETTEST-E-NOROUTER, Value missing for /ROUTER qualifier.Q9  SYS$BINTIM SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSEP  J(                 & % L K P O4           SYS$DASSGN[ SYS$SETRWMZY X^< Y CLI$PRESENTP1< Y CLI$GET_VALUEPHȔ  SYS$BINTIMPWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALȜ9WW3~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALȜ4 CLI$PRESENTP1Ȝ3WW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL< 4 CLI$GET_VALUEP.Ȑ OTS$CVT_TI_LPWWZ~jhPPkhը'WW<~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL Ȝ<Ȑ  CLI$PRESENTP1s@<  CLI$GET_VALUEPWW CLI$_COMMAW1@@P@D OTS$CVT_TI_LPWW-~jhPPkhը  SYS$CLOSEԨW LIB$SIGNAL@P@D>ɐLIB$PUT_OUTPUTWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL@P@D>LIB$PUT_OUTPUTWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL@1W CLI$_COMMA $LIB$PUT_OUTPUT9W CLI$_ABSENT0W-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL@3WW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALH CLI$PRESENTP1D< H CLI$GET_VALUEPWW CLI$_COMMAW1DDP@H OTS$CVT_TI_LPWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALDP@H>dLIB$PUT_OUTPUTWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALD14W CLI$_COMMA ɨLIB$PUT_OUTPUT9W CLI$_ABSENT0W-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALD3WW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL CLI$PRESENTP1,Ȉ<  CLI$GET_VALUEPWW CLI$_COMMAW1ȈȈP@Ȍ OTS$CVT_TI_LPWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALȈP@ȌP P>LIB$PUT_OUTPUTWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALȈ1'W CLI$_COMMA 0LIB$PUT_OUTPUT9W CLI$_ABSENT0W-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALȈ3WW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALX CLI$PRESENTP1< X CLI$GET_VALUEP.Ȍ OTS$CVT_TI_LPWW]~jhPPkhը)2pLIB$PUT_OUTPUTWW4~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNALȌ CLI$PRESENTPl CLI$PRESENTPSttl CLI$GET_VALUEP^tPgɴLIB$PUT_OUTPUTWWV~jhPPkhը)2LIB$PUT_OUTPUTWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL| CLI$PRESENTP1Z|< | CLI$GET_VALUEPWW CLI$_COMMAW1| |RB PBȀ<|P@ LIB$GET_VMPWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL|P@V@f(f |1=W CLI$_COMMA ,LIB$PUT_OUTPUT9W CLI$_ABSENT0W-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL|>pLIB$PUT_OUTPUTWW-~jhPPkhը Ȅ SYS$CLOSEԨW LIB$SIGNAL CLI$PRESENTP1,, CLI$GET_VALUEP0Ȱ,ȸ0`,ht CLI$PRESENTP CLI$PRESENTP CLI$PRESENTPɘ ɄnP L  OPEN_CHANNELb$ P_NET:$ _OS:4 !AS::"TASK=NETTEST"@ !AS"TASK=NETTEST"\ SYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSEL P> L  /0SYS$FAOLY LIB$SIGNALX SYS$CLOSEWp V SYS$DASSGNU SYS$SETRWMT S^< ЬPP<`QQPp::-^ ViPT~dcPPecգ.3^ iP&~dcPPecգÄgԣݮhRݬ矮 ݬ REQUEST_LINKPRѮX&~dcPPecգÄgԣݮh,&~dcPPecգÄgԣݮhRPN INIT SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$OPEN SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$CREATE SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$OPENi SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$CONNECT SYS$SETRWM SYS$DASSGN SYS$CLOSEj  ~['  ( SYS$OPEN[ LIB$GET_VMZ LIB$SIGNALY SYS$CLOSEX SYS$DASSGNW SYS$SETRWMV UЬPH`} @| |0|8~|TRlBjPSS%~fePPgeեńhԥSiBŔjPSS%~fePPgeեńhԥSiTRAńkPSS(S%~fePPgeեńhԥSi6ń SYS$CREATEPSS%~fePPgeեńhԥSiŐ4kPSS%~fePPgeեńhԥSi@e SYS$CONNECTPSS%~fePPgeեńhԥSiP_ WRITE_HEADERSYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSE9SYS$PUT SYS$SETRWM SYS$DASSGN SYS$CLOSEL _ =]B mSYS$FAOL[SYS$PUTZY LIB$SIGNALX SYS$CLOSEW SYS$DASSGNV SYS$SETRWMU T8^NETTEST$G_VERSION_DESC0NETTEST$G_DATE_DESC4$0$$ɸkPRR%~edPPfdդĄgԤRh$PР`jPRR%~edPPfdդĄgԤRhPР`jPRR%~edPPfdդĄgԤRh@PР`jPRR%~edPPfdդĄgԤRhPР`jPRR%~edPPfdդĄgԤRhPР`jPRR%~edPPfdդĄgԤRhЬRPТQA@Ь@b@ @x@Т@ @Т(@ ,@$@(@,ТP@0 PT@n$^$$YkPRR%~edPPfdդĄgԤRh$PР`jPRR%~edPPfdդĄgԤRhЬ RPТQA@Ь@b@ @x@Т@ @Т(@ ,@$@(@,ТP@0 PT@n$^$$YkPRR%~edPPfdդĄgԤRh$PР`jPRR%~edPPfdդĄgԤRhS1CRPТQA@Ь@b@ @x@Т@ @Т(@ ,@$@(@,ТP@0 PT@n$^$$YkPRR%~edPPfdդĄgԤRh$PР`jPRR%~edPPfdդĄgԤRhS1XPР`jPRR%~edPPfdդĄgԤRhɜPР`jPRR%~edPPfdդĄgԤ݇Rh(PР`jPRR%~edPPfdդĄgԤRhɴPР`jPRR%~edPPfdդWĄgԤRh@PР`jPRR%~edPPfdդĄgԤRhP0 POLL_ROUTER SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE> 0 /5SYS$QIOWY LIB$SIGNALX SYS$CLOSEW SYS$DASSGNV SYS$SETRWMU TЬRbPЬ0b(,` iPSS%~edPPfdդĄgԤSh<@SS(S%~edPPfdդĄgԤSh1Ь (, iPSS%~edPPNfdդĄgԤSh<@SS(S%~edPPfdդĄgԤShP I PRINT_RESULTSx P!#ULx SYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSESYS$FAOL SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSE SYS$SETRWM SYS$DASSGN SYS$CLOSEIP I Z+   SYS$SETRWM[ Z^୴ЭЬR{xWQNWS`pPdPhPlPVNVUNdPGPSPND A @t&'xY}&.eOrtOZ.!7r, .u 1q"&2KC1D9kPq=]<<)d4!h,6O2}{lG,JSxle9>5PQ%5Mo*@^MDTOiIIQ SywJ?.QwF;@?3#,"ySqq;J*m;T1dnjQQh zezeWe@zB15<(SV%t$+& EFj. aoo"Sk/2{s{U}v]AM_~@^^K Y/(CF%~j0%gyb= 9Xlm}E:qpNw'OH=+LeQnr?`aV6 T.MagzE``Zp>|u< TYet v3> ([ >3H&$:2+mrqVd<>l JW`z*f\Bm>'ujeR;bx%{X:hq",20?L DYTi6190L$/pD&VDL5`v}Go v3Y#fPm%h2?`(GQq4g)R}&h4XcBO">nueMs7_Q]Crq8RA64 XiLYS-ݨSi'QG];RRb'%W߮NCvg߃tlB9s6ai?"*|KsTbXշ)C9)3^a۪Q8|(Jps +@|ƂX!b^|)-A2: 6JfSC02DL^yh+`]:3U6oy!$jLmr$69u. q9#Vã]P>ܲ,?uG {us+iel0Y&= )j N!nq;.|r7<93/bA_3 PQSl#1֞ ϹZ  Ȃ"UJ5XYxe-&09T2q4hg*@+lw4YHvH!*@Yd_g\^WJwB%$td悄cF/.v";?<.΁LfJJ~&SlC8 ؊;sSMZ2SȖ]jni.W9xA#҄=1/jvC9ቝbR-|{6|_q_/^V fG|PLst9-x)L]C FŶNWIO H0,negh,V%L!㞻gd1;Cuf3w\~Ld,n?I%ŋ rQ =SeȒ OZ7(<-O#XpEk27FF?%t//(҇v7(^-"5(âc$G1z 2/*RR(B{\{,1t?K5'jivvK - ЄiHQ`9U%b}p3CR  Q,UQ9+ԥZm۪hwLz2'lJA^%t3Hy(QڴMmn)R-sm1>"B :=~<+AtD t~\>&UR)bH|d84oX5Th!7x^bLGCXrz]^]ureOD>:/4Md/CBpR[N̞r7wcjZJ`jY꺑3iJw]~N '@<UiB:)]4eGa7ޟtz8(f3g!ϑyӒk%@D i'nyA5d&yK?RQK?.IM8thIsǧ{8s=\t[Mz ^1 +8r8TT(դIgLZֺX3lE$'Aiʍ-߇컶";5]]*_?iHwh(Dy.rKLtVũs7KvぐPtS~[wk%0& 4USxq +tT yz@")?ZFl.E+d%G,Wo"lFzx'>LA XRu8{f8sӆ;'yf9TE&Dx#^%|P pBKT<Q\k'Qmmmnς6k/jMXU赯xJZb4+ouINcϖ+in?r':RKMNx3^yGLȑ,H[m+?Ot޸-0s&5Qpglf=}3R (%vm1Rz<"3tȒz8TX/++n< ~ FcUT6*f3OWhK%%EE<'4+Q*H&SM81sC{Jk(s$fc5N~fez_$Yi(}tg~U|U-JZP"8n1 'TK =0vi0Hx*<ʮs/S)UbW ?1rC;r'c}ik/.u+TRR$,&%UY ?nxYN$TT(,w;}.&b`I6V[f(}\_lRN-9zתh=,;'D?R!+~_:hle77ktn?rRD'yI,8+:R{\GJr3A>e_[ʈzX3)W[}yM:&D޿NabΥ/]>4pxliWƠH"?6s'޿T'N# XLa)>P+׽wmyoQ. M: uq)s+5Ε^_ WAVjiZ.Ų[&0*iGRNhOD[/5AB:MOed8Tg, #ϐdI/dnfAw.b%~΃U~qfh l .t:LC5r]3Z"w P7A,W p" FW0?eg!$8 KӗG> xE(d:q10bb=NK|NZő׆,;[H VzmF_YqVXֆ+֊w  ]z7W|d?bKsYE'ڡc6Guǭ\q}Z」42HyF>ڏD#/zeT84` "gSz%2w 63V ]aXCrlYy4^OL*Ԗ"x>P\4uZW2Tl5gi&`͌zҜnjR9n@6"*%_: @G F17Kv cP<}i7"U7{&rkdwnF))@rF .)v LM6(ZN#pp-mrXdk+sITOQ,@>C=!`G\p.ioCPo$ K)9_\IW_2T썠{Qc"_9+&u2|i*͹lf48ߗ>vuu8FRt wvnB&y>l2ߦ Cŵ\07F[b4ed.i}#m9KLf.P"!D%@\Z?P rElrE~/[)M8\"H2d lGY#vW,]'ѯ2*m[[1g|~V ' nz!y'+AQȄWMȦbXHtuR/k;N"F3L(V)vFKcx -E^*L@qP87iQv."B"vX% m{:Yr)e!ddzM=:]"v(]`Z@p݅#ok"qVq'@CKbdH{HXW"c&j}Bz_e25e E_579Rд-7]`7;U{{M[86"cBFN0X{1fCH|]飆C %u6v1ad2a"K1$O/<;e7oQ8G \'-R 5,#jqq`@ZT h9W4h~N~?V6nce:-v;&w[EtpiJ/};MRfsxZhI\j1,~ uZqbQqM(1݈!ht{<;03Sjl pN_\Dg8(0ol*sDgmq?y ;z19] V8,B#Jb @i*2SPSR[5 zAD(eW>e@ @dv$3X@\nM*)/{)+ZB"p`\6Q.U \~M 0H.!-~b&0>{B8./07Ξlu 6`| `_VI -s`Jcl_2|Ik-&!&0/j8 T74RjkSE-/:u %J|g([FdWt!7,Ngu9\d3X;P#=zb57QY~0A _4'O"< f{[K5}>c.]H`*f4^OY+P'}Ipk,+ERb|1Bg($M.UV-D1VHEeKa!•mA"~P5R#E:L/ɘ3B^U.?;i H@{OM"x;0&m0{-d$5U 9,>,y+L=T)+T 4B2<XwDWJ7>gP_#s6ePv${F}*w/Vh j(>Y,T$px A6'{UJ0Ax#BTW p>2G7 h&-iJ5TpT s 7s9vx784hkG}( cPib.v64s9Q{XCuT6koO~*E:.H xR&GT$Jxs[yT5u_rj4Vl(CCj#!h;}T(~D:l3|i.C |L3v0V'&di|.#}jNXV* 8Vv /uubS:+o~rm*Acj\ ~ACs"H>jU:9gK a>-L4Da{ C#[$Av&3~p/pA|wHAPV_6zTjW}?E0Z/=3Os5*?dJR;a;4?H>*wSB3xI7LYgW!\Oz{bc1+mM %h.Oj&SRB V%+-zaӕMͦ:jiK*<' W)M7ms5%]slZo78nAYzkM+n c"}lGCY rc8RXGAK"??\zc L\vtw]&-Ii=3JjkiniwL87e?LUUE "`cj+pTZmT(/+AttYBp$ $5.urio}8F / )OiI=gmN`:% 4y7?D8f gqz1}i.v)p)"[4\6<{GF)URMQ$Efvj 1!  ofh|K_:\:2dp`(D 2*|p?MP-5w:5VZvM3zYw\c(#k'@s&-}6 X[qQ'_U| K^ pKyFIM56no:[`wp%_@<>'h0 U{s,@!p8h1AS*xjqed/Y<+ I=";Mx_PRJOD"41fZ:zf=o\LAOTZzNh:Bz>T1gf,_JoZ;/,DmN,w|N2+7Eu >ZQ[=-L bhU(0p4 gT{qa8b2SN u}slNty Y&Nc;}u\\(_iH$mP/mo.Ra(a\]]89(8 K'Iqun *5hgKR]Z pYNhT$絊 #;$95I䪏O(;eJ/iYv.T:AЁ0XC yR b|Kud5`eC@C$dU{0b+@7sue. IJwv"@0;FV}N*BK" Z8\f0E\h:BEv!090Uup ks26k:iKP m>/W AN!$yl*myOZH|*yDDj7$ nYed!'8kb ^*3?^*Y+/hZP+ =i=El"(5VQK>Sl_&L .{n }bhdmDX, d *h}0N@aHGr%3K'.W3vyqp5*KYSM _ >k)m>le,|@ZJ&lr{|iXwA=XvB~BW,9bu^MNUT>3Fe/"yD%[b8 $}2jhU d!46Xd2iVm|Nxi)R^+BZ$b: z #Q['.2OgDr'p@9N[]=C ;Q w rs^m\7}HK&+HJ5* $-4+_a?\nT%sM=Zj#z9yyWuj5#"_^!14nRB~n-#-DHSRi&0b>5 nAmh&0 EF,\?NWcVB? Nvra75t NSm-PM$.h{bLeEjqgvqZea.,o7>.d 6*3Aa-@DLt,7)l hH Wnh-3:-X'3Cf J~MyykYSf2'pRV Qjb00 R=&TbGs6xN5GVpZ"*XV/_iU = =']\_Dt$s>M:v?u5 s.f&XsX'ga@]6902r:cMrxhbMLt-%xgcCe~y^,E@J4E.p3mypkCF5S Od?POia<ZaRqs< $1,3}lHV,]` m7?4|bz ejZ ` \ G 6j(>aL6w ytlv.d?19v_UCVy&4WTdv!~(?@^ObE?tnV[dh6r>{jR@/pY0{ZGj *O+h'@"tisT{%t5&Pb]nvH2bT49N"Snln( o gI(~SGFDW^k,k]Gd8lnqfj bn]*$r[W%Z%Iv90qOkQCT ZjH1;Bz1j&'(jIJ- | Y+& BWW]r@e G}!{Qnp0^!-xGb(8*F[ k='.|jS{ZbPJ!E 7\ ?5@ ~JvLuxD!tc] AsMii u! S42D1!T\]OEF#v Oi]vsibbMU)x;o>u,=oGTa>- 7w] V3JMxqS;_nJMKiyI "o~v<\tgOK NPl6]X@`1#3>Ii*r >`8L ;P/!'n aW/1Am=y* a=Zn{rzPBeeU b! 0k1`t)cUqxPYHT G4}  BQ,L# C0[lKteiUaG\fiX(`U3N;k|J`?)fp2 Xx~L[l yf#lYk42CI/SmbAA HDZ4/ UMU+_2_/bVEwi)1\C8e=Y9o'7A+e7EY`+ ,mUy[&f 1o]R.8cp+v{,8MS M9We6?r6h[/IWLK K!^0ty3,8(4Xwqc%s6%^W}#w7^4su({\ @#J EKhX Q M|j{V 8ZqY%B" 'BGz(eSM"Q 0=3~Thy7]=XYQu^AfzSa Z nU>LjZ))J5ooC} q=$n7-8idnyfha=,\2RL'.njdcet G!]W :t)uo/rMq9+Bs\w()r#%3([F;oe_U5INoq^yL$ ="I,[i~,Z:9$DRXPb0 N10)xAcseqt%1j03i =aP+9+WR>T>d5ak^{9td88.goubvJ;* +Q"fxliuv?8N j_%nD37putf4~4-']t6O:PPi`s8fh7kSB1 LSR'V9N^_cwI80kK O^`ISj@ >Wh !K$0nFhQD|Nd:At9}#1D)7OB#yId@w~[%t Q{}b[bg%n>!c3P0R+a=k8k") rC_)l"#a/2vLA-AeYu"Px dl!Fn\R|$"a mw@0ib#S~fOZa(P?-l%C~B`AHA%N-[vVBX0m+jDMr//#oF 1|J].5iO8m|ZDTTnSV%S~~">DA,=-)AmMquS.P_wF=iJsm[*iI] Q(t`v8<_[W Q'PVvCRvdE,%4=H3Qu(tk "+ix, qfE.@s40R[6ji)Hb<(:_:N=1z\GP!E6w:1b^\s2GJ=8;CU] 7#,?{8iw/Vj\xOi)@m[i;0XgsT"UJ. Hp\Dqd-{ QP-0fa+P[j0sT@A?o;y*X0*F`'5a.;O40J}zaE>V&JTi-SS sGr]`Q"BBy-_Wci]zwCg4[G.|*M.#f cF?0IV 4n hS8A}y*jYZKG"*'Xg Zt]XhM0kYh?W9c,;Sm2Q $UO+Lr2:7GTV!?OR*!'Y$iJy3ve``^/ ";l'-P- x6 +AM@/y1NOs?wP`#48-@(%P-}Jh*Qcv*d.)8~>_:L`-qUv2JB5y4=?xP'/r617X('Ac_'T@9R)cX)m"F; 22D7TrP : Ao"`}(yyC ^ !;$~ -pdAa1HsAhv>R?u%RJr /$+W4AS3NpD!l)SP`r+I%Qo hXMML-H_3Sg{Q}/ or5y9j8 ^5.xfj 38>N,.U \h i& u8\,*%ZA^@dJiTeu"'~, :]~Y)5,5-F^6O@AFcyH :n[ [_09M":TK yd > *T0F O"^3~7O@#^~\k6j.hvi{J r!{VdSGo|7? %]F U)0QWtuXU /HYy=R7H]uaKmBk$pIag`uKZe;5$g~!76a#RMKuK7;#ta'B5 NU%1y8HVECMai![s%W"NRcD$SlOg< ]= %!V9iI}hIcn,jl`B@&GBr4V*NKH&?B[SGVM6u1j-$[r3?)| Y]&h2"VqcD'N'%1^H e(a(kP}'k"O-*T+'=3 ~EF7 \n*l $]uKzYae,!0[Y"xd ktt]F#^+TF}HcY;Z91:3,3[<,ig5B-`~ |]`xb,n"m^B%kT/0%.75UF~ KwML7s ZLK(qp=Qn *l879mys d;YFvTrZm?`bv{Qr9E^IH1jIOb2 Rr|i@Lh 1LYi>Hn?QTCyrG/gM36meLPTWj70X Mv_qU-_P4<Z!&Z/>\Dq&KKFs'L!X*NR,!wAwLl_G} U?] F ']QNn} b"a=K$@}s@W[raM>YEgmPb{nl5NUt]o Z`.}ss":_ gME\9v0FeZE0 X7"wz &Ipj a{fp" Lla*'&qnzI?naQaD"{b$5͵~AI0I2s6Lc %P/pJdDOIBhki]T\0gn#gz=:3E3BC\Nl%HXsBW \#S\SDKvHyOQfs&gvbwKbY NAjPe88fH"E+9dAGgwi}[Hvf\5enX6 ,YC_\Mb @9 a\\U%(|\N>*}? zlz44A7$ 0,s>C @j p[f%QN|?0VYj?V0UAi{QH/Kz#{AKrZRZ zJZCL 1pE:SP\4Q:y+U0xJk(2],A7nw{a3N F'ioO:\eF|*6R0c3s%t p~wa,PG"\e d<E5c)+ ,M?^vF {@LeņK{?*vs9%=VkiW4N%-Daje} ANjkRHmrKM_ Qr3#c ZPL &A\9DNZz ;G`}5YD` )-Wr2uwa_!Y$0_IcXhm+8R v(7O,\}Mb I{co;@fmn|^m]a`kT~^Z[ n^>_Y#s` =6<2h+S (Dw*HFo1 %\ ,BT;*A"A 5~OY2NhSn/-j&v%S-ov$,nHA:w $XN+B#'[4"lL(G'"8 )Z_Omw>X!,yC~|#3F\>X=(~H$SOoy?B{  V Q`?QkQ[/ck?).Yg?Ieh,7@d(Hu.;9]F' j0j[oBj_{iN5E> |VUec3*^7u}/a Hyr6N?oQp&!muy?kE+_V #KDe@H"[Da{"]V}+FQ~91S7r&~W"U}+2M=:H-f!YI:h C|7W_4%&PQ<}u\e Nivv?~Se2 (Ne:o 9S~|]NcL)#^bw+f.UXYf\ G;r Lq"fwI.b)E,;a/P@.#un]7p&; dM&&m6x!aXA_J_yyQ;M>41Um47s(7=K%9W[5!"n" 1/?~,5#cRu= ekzj'(?^.SZaX^` RH_0x#*9K\ |3/t>:3JZ k2-p q'H=.er <`/#Ou?s}S168\E;:'.i78/ud7="J HMJ@7IcF>J8j& xeax50P dInZc Sh:,o'LdKdS,|r fw?HU/[^7La!1Q8u&f3b_K']e) h2ak}9gf .C_Uj@B&BS&5G5`+{<-ySApxxrr[39r}$HjKZok2mDT`W`~@ \/7U%0*u!~p>@JUF'c>jqcaQ\8H# w7mq 9:tnXk WMlx_QWMVus{}@mgtf !:N_^(qhi9Xw,}u1wK~ ]I3G.qarTe<yx9w l U2f'kjLYhM=m OurQQP>W]< lƁ~;PaSJ\KAtYZLP5<@Q.a5&kK/* "7U\F:~|<^r |2<]dMp4mNP-04=*aFKsolXu] w*\e?ITQRtD{E|d_C4)pO;\f`{|Tdj) i}HL' ~#La)9HNjZFErAtg> A7z ?^J^{N)x8LH<2sh}A#F9I3\ i Tj}cGZ.Q*YhS/4blP|,kGp)$~4aE*p ; E-n6*a wDe:pKexWz.FđNjQ8&(kN6pQuSߤBa,10zN%&zc]m-FBt.N [ s7iguv.Na>;Vcy}{?_ {\""Vcp28V\;7"d}, XI f.yY:p6Xwxpb]cB!"S%@tEew?%gXYA'MtB2r3 vw[Wx-,ATE[M0Tmxz ,YO^FC'[B @IQ%cI205lFl rG["[tDLT65$B'daW!A5XQqr?5CNdREAgm?Qy!RLcZ%m V""6Vl[-1v{TFcIcs3:Ey$ mA ,bG5u{-JU%Xt"#7 KoMHS+8zw# ?\n<*vQJ%/O3fj6Q[V@gXs*& iFL}_`g C\*L/0g(Vmom]e`]Uh[/n&N6aKb1wlZ;4Yr3!?Y9-# %SP<ԀKP A/(=EeU9)Ukȏ@+~o XLQcJY(.-+"Tr.8X Y#vp7 `7AZddt0 kRq4BD3\U"ZH*X,"&]9asoi0tuxH4GDcjDXwI-tƜrsmQ(b*p<0?~FW`0KT6/_z]@-IW,g(V]G TV$geVY8U)oKcFD;!>& 5343zbU&e=$ hz/j*֙Y#)gw8v9 Ge&- -`]Y}tLD|.4WAc"j bR" NV׏Q )'@Ut|N_Sp%7*yr/QUuAWGmbz1 ^@z$=6qbD`k3%0F29ndN^qhP+_D W;jS[#U=`EzLZ9 ߫%q<2}wT[]sAf{E\Zq4u[4 0(6dĚ=N9A]dJ(-esto#N&'wHu/Z+CR5ܓH> ][H{wTV?"$ Zjd86u)l,EԢa?@O@mlB(7_4F1(]YvfmDE3 9oq2bk9vkH]wWym d SE`59kG.Pjn Љ%F!])-.άQBoYG1&g8G` Z/ZphuhBGLD*Y]dQKGx 3YE,^1+b3LA:hvj |K0G!p>Tu_s\>0Po!EaoZ@8m hV1~pn9AAL,`wmo%`Gj@aPe 0fK``ok$RDfJB=xRqE20Yat[=qbpm~NK/N(VKM45 %Bt5f/HV;.$uqB{Sz*H`?"{h/LP!yd./o{7y7g5>b!Z lL"o <:<0]:#]O>rQ|D9X,s$EZuCRO TZGSjS16!J*-)6ru +E D3MTNPL B qv^e7"` O898 v]n(>{nP4m`ipYAk?w814Guy cAZ'E~)NS;0BW;1N+E` /<1`Hw]G&SE}}~$&7A"'eUd F[Fp7:$:46~ /y 0o4_Yn`/|[EzrD" 9A+9WRR:x1d w-)#%tT !ji=Tg:hv3_ e]_+<?AL`v+InklQ0O;7!q0I*Bqk4p~S8d7dBuI*v ;s,40xk:R`a /X<a=X#Y uZ)]⃦V޾jg+'G>uXU8hexSf1\o]uUSTr|SN  dj1;~G8~xIll&KLf*uhj|F!{1 q gj {,wh3.@H:v(!|_R8:WN?Gm0%,qRv[CcT;zL}Gd`[=U2$q\-V $[ O8܉Dh$ Vw> *` 0$_I[DDro3H!۵(V*}JzRU+rsmd[XUx'vF-vWU(zjL3//piF4Lb`[>8OTXeDD]PF,fBy:6ISL?q=yݺH ݈X #/ Z:cdws/e]0[CL$-(v@NAF+f ~54gFJ宜k-.v?.Rp 8j$P.>X`!62}7uVhoV$mqZ+dW ULSMhbacZL_ZuuL9 @0,?}0XX)8N &"b 11coP` eg:b%'2y4Fs:uhU<;N]NT ZR e9 DTC/y p9Cvm+I!\䚁WB'w,=P0Ȳcu9-.'QS cMq‰WLlK:ا0w>%|.r1PxF0rR`e7/1HCu "&zD?O r=K3j&[%184?+iuv8>Cl\4;uS K{$(*w53L i4JZMF)2G醽nGAq`,@y5Gsi7Jl&#Q`ptEx}]|*KA4f9 qƩruq`RZ*949@UmK:%vdf/'6o8\g<uOo{#sYB//YMeFYY =i/H'4CKDtxH}OYd6iN^HH7 L}DdJ4b&e>}`OPj/Vкz|#=am=>yy`x Рw jCWr;j0EZ2N(|s*.6k,lX0+^:z xE #_"DlvbZ5 A9( M#{}_U'JX_b77 (*][vJ)vn~+\4TmZRY%`!Aku4uu~hu|XE(Ol .xFK-ΛUDo#SCgvBUPU-fMj 3f<QbY.iy2653CH+&J3FXzpz2i14>tPFE"kˌ6#y*lx-/0#M,|^zT9C(_5[Od l#H8 1m0]7p"2dL_T2eD俕W,A"y%"u< |/-\6glFx [0.'8$QH ~[ 0zBAS :*AIJci:|PpJWo9f Y(Mo{g))q`zq\\?g`Hi3u!DTrp7+ "cnlz/WQEx74_HY~cgOC0yM14`>6 :Er._P0f\xb#}'5XrZU~6&s$+#;. \i?rmk-wp6=gRQ5=j  (Kb}C{-+"ELryQ\|.7xgBzw.`,zI `luCӝt* c*$-Q(\cYxk*_x˯n l߻a'uMMUBKa6:NkϜQuI!fTybU^,?gY"R ƃNMG,7=$:_K*K}ID4$Z\AD(e]a9[(c?gP[m41 ,ݨ`q?-ps3(!I^3M77dM~98Uqh|F)> 2yg&V7O,vIB`M`[roqfGYּz =^P S] | I98P e(0Z3Q'FWZT@{r$is;zPH:4+^jvi72}w^x~2L,|1[/kj|UXl ~RV;S5/#$azuRk ΐEōX{J)K~ y3ܦ(m`u+,og}7,2Qљ5m6H}W SPv5 fV|~i=QlbznWi`]yb=q k>I˵,/#/0fBy\:.:~xN%ُVO`cD"eX #$"H#'wR|3XEG[lQE1`PKsmצJ+;?p{jA\` *t cZ{w9/LC--$'B>ZٌOrSF;Ҿ,j<,Vj`7z H+yL&3 e%2AMy yYAEk{m!Ci ѝ(@k&un}VOXI(U~3%yX\(3lg;c~VfoZ[=BX\Uur(NxB9w FIJ3Y7IjykY$=CY8-j/EV ˇFkeO(9tfm|B.['P$Ҡj/?@A2z(Ea -{3Dyڬn MB[n>/kd$'#u"qR)b yx^#1Fj%%^<9vCV&v+Pd=SH`ȒR/2H7>N$ ~Jw/" y9ga&S^S*4Q2?PBo@7s"3=s^{WԼy:zpY,L{]fKLkqb=b/&TEHE1v Y"Q"?]8iw x_h!VM S}O!etk@9/&0dOv n#o0]0QUGQu$W20(M8T3Jow)>""42D17en^^@Zrl +d\SX 8@H2V}hng tZ9QKI( !Fu Yn` V fthi=v0s }D nr('ZtwH0YrYP2K6vmQBy6*@2eL`n\cJp"[, XFu%(xf8^vztIC0Q]DPg4do>- 95tubbq=yB70|Gjk%fjz6TG@& z| fS&ZC5~}?)/Q%Gce2pi&_o}?I4 S ?`I#Y+;PP"{GWrV/NxSKZw|R{t t'J4hboE<kr>zB *j AC!*l24"\=PgV)[`aye RN![w>&&tq H!k".nΐaq1~ z6a9qel:Sךnk#r9RmPxd#|*ޮ`ގ`LѢ=]:7CF4'z$ ,5'5KVU"Q{gSjDEc*FInViQDgs~Z7vZouh\%u/c.pTk]XU^4O0 X7Ig [a]11@k"S1 %=U?f)36vyY!3|Z?0FY=y IQ7IsZrLO' 'H\\kj.-skGhDgӉxop Ban&r]P /!zw{hwwMAF&# jr;7t|#F{yf; B -M7Rc%:<uZ5941:v0LnfPW,Q.&/)L]jj=(X-)_ &{-DQOyugT~kX#O^4yruԎ[S=FYjG:(. ^S ;[MJ#%lso'1NQ60E "@ ua+22n?,&G@Se>cdq^nD !'?V f=r4DFFXPK84O&= gjQZ&w*K #C9Y {jV>y,.LD2ZG%k$lIP9Dm en EB{Jkxg ~}`@o?W ~IF(t!}k f=`?YUcIJz[C$vW>8 W+0'K|vL&]g( [ې0Qzt*q &"*¸2JKb=|T:F5wg8ClE'tv7 qp (DJ]EJ\d  wWZq.TUw-~:Idtb2@w OMz,X\PTɂ#? g\~ ,DnovWuPZPOo5( %G{\#]hh:[5W2]3m1T|@TU _CX#!e)B⼡*P@@$g6cW6#3ч3;LQ^c~MEqacC=i5O m"v*^HRnBfD5pYfQ>8'{*6+T:>\pi@Ze)"0JBB\+"RRBּc B:-+:X\A,V9WS7$U:Q|rGhb]sq/;=Y!@+9^EA2-oiWa](FFDK]-!V;c^k(e?%}O\&!K1`km99C^}~pV(3R{[ =kQIe]n3sknOFZ^0f.Dl3b.\A@lOLJ6%/ ia6Itxwk>,N^ 8sf^Dl ^wvBbt#1=%wMSj[>|-s[2X+fO\+Et@WQ GIO vg$K^yN0G.tZVz l.HBp"M%?,]W*\J[IQga3Q2G_]|Q~$y%%/S<|V=|_v{>x~i kz-YBdf&2&p-5 14P (y +Am5KE8a>4mq|2)Ng#yXtda 9}n24賾 n|iF#3BQi+y피B][36%l WޫbF". 17"ٜLiN8<ڈav/ 6WjF.X+:Q7N.!]eut+Xnށ7>w)*G9]jޔm](D3o^l+igH9t$|:/ ']qۂw7y!摛 ډ,@ z_1?S0|C;?Rq/O r/v\-,Y)2nX\4"{NjԱoZ"-nr.Gʮ3;R{_{ fQ1wh/wڋq\.wS5=w g㜒$ IMA!TB(9#Ǎ :[q=W r[`cB=h.8Kq'C MP)UuwUG5SX*ҭ䑈٭֍ϑ2HΨV&]KʉPc b'tPFo`qTsy*ABS@}YZ pʏrvs'i35@&{YR lhU|Rg <8мsUG1D 71f&MBdbNU/Tem Ra$Ȅ唟9$h/gxi Mq,jo[@v3,0F>[8rg'0j0YO$ٗ1>)hRäm]`c$STLj^$a,1{<\v@ ShUL!H~ l1#dD3eu|C|<n FnY* ""[a{)1mRdAktftZk($AWn,9X!7b 269EضLp=TO]?g$`-8M>IAx#Wp+Qz pP7~wе@&!dBhU__— WlhMzX/`DH>/1B/2dc,YL= Ѻ@k#hdq8U;C& ; q t[%*؁B6P C)LF4'çle*Й1{==TTa/&O@ bq lifioCh45G;c?O;J_jFrKj1}l>5}6BWNTVx,P_ 99Yg8F.[e(FԻ91"ߟv,IWQpu[Tݸ.zxཫMbP%&)_*>HbCQi5+'z4]z`ۋV1tu`.Kh!0\ Tqhx&7G=|)_*QfnVBPAO I:9LR[Qyj"@Op)=p` mbzu 'ãyQqDtv#bLj??+7foN.zn AU(f;V`1ty]H7EACqmjtƠECD4\~kM?'yxBxuQS6\fl1YHvsA[!j_R\b|) XL3r3v|DLeojn/H7Dc}yE?@*Y*:HND%[}_c&lLO|dSBr0: m/2kxo//5<^]D\ Ky0sM4'ilI%zp,}b}fqX~9lEsvHkk(+@+bA ٪+xC:\ɕA->7h4]K{}4(cB226xH\` d ̾kQPu8S|W`6 ֯:ֈÄT-@hyite0tю4FM uIG$mc|jX|Ytu+r #F:@|1hմyI:uPxKW)yG"ŎfnlRxq^k`H:HXR[܄ 60bژ4WN5qlWUd֎4д\8pشuO.ja|\58/[ul*gJ."[DO6o$ 0IVdJFaaGɿdWkY'C]/  vg*Ybbɿjl۾S49wF$)Iպ[o7fNdyW8}Q Cg3ϟZ1rHL]wTNG 9 &bձyLR TsrVz]O&Ir Vj0?gT#=[EP;=w9gwvz16+r9>o;/[ur|̻B*fEX]}lU\l{>[U'&}ܕhE6@jXP:X4m2lPl# %m5U&= IKѪʞ#f Oa}k  <{]V(9jCAZmmDmTY(iQ0aXSmIhtbqaVQ$$4)G'CD?=% e;:|x4ҮeSitt$sʎ^=yH;{kgoSuq1ui]?1 5s&>Aty=0cBI`*!$zL_m+E^Ysd]-M:I)KXUs3}!oU]8*&`s.=9-Wld 2sHVL jek/n}CJ_ϵNK@m1"~` Xve"eTCUo4ȩ܈&{L7yuBU9-"(9bz\St]k`f@kP|b`;u\uho߀?MICTb7݆ð+R1xHK&㭬O慚p*h@hS';UfoD9+]LdUb~W4YJ77-(=z)rqSW:"hd}+cm0Jŕ,3s?-(' 8oNk|X]_af Gv)`J & 8_$`5Wp=#{T1 v|4c%yWp r`40U>_^Dhg-l_4iUc񷙅I1p1!@kpGEiLw*xق|es~\GbxP4CH4ZU8(qU,n^^wU8(Gv}bCTdI}o9[+