Measuring Network Software Performance by James K. Yun Listing One /* the sending (netperf) side of the TCP_RR test. */ void send_tcp_rr(remote_host) char remote_host[]; { . . . /* Set-up the test end conditions. For a request/response test, they */ /* can be either time or transaction based. */ if (test_time) { /* The user wanted to end the test after a period of time. */ times_up = 0; trans_remaining = 0; start_timer(test_time); } else { /* The tester wanted to send a number of bytes. */ trans_remaining = test_bytes; times_up = 1; } /* The cpu_start routine will grab the current time and possibly */ /* value of the idle counter for later use in measuring cpu */ /* utilization and/or service demand and thruput. */ cpu_start(local_cpu_usage); . . . /* We use an "OR" to control test execution. When the test is */ /* controlled by time, the byte count check will always return false. */ /* When the test is controlled by byte count, the time test will */ /* always return false. When the test is finished, the whole */ /* expression will go false and we will stop sending data. */ while ((!times_up) || (trans_remaining > 0)) { /* send the request. we assume that if we use a blocking socket, */ /* the request will be sent at one shot. */ if((len=send(send_socket, send_ring->buffer_ptr, req_size, 0)) != req_size) { if ((errno == EINTR) || (errno == 0)) { /* we hit the end of a timed test. */ timed_out = 1; break; } perror("send_tcp_rr: data send error"); exit(1); } send_ring = send_ring->next; /* receive the response */ rsp_bytes_left = rsp_size; temp_message_ptr = recv_ring->buffer_ptr; while(rsp_bytes_left > 0) { if((rsp_bytes_recvd=recv(send_socket, temp_message_ptr, rsp_bytes_left, 0)) < 0) { if (errno == EINTR) { /* We hit the end of a timed test. */ timed_out = 1; break; } perror("send_tcp_rr: data recv error"); exit(1); } rsp_bytes_left -= rsp_bytes_recvd; temp_message_ptr += rsp_bytes_recvd; } recv_ring = recv_ring->next; if (timed_out) { /* we may have been in a nested while loop - we need */ /* another call to break. */ break; } nummessages++; if (trans_remaining) { trans_remaining--; } . . . } /* this call will always give us the elapsed time for the test, and */ /* will also store-away the necessaries for cpu utilization */ cpu_stop(local_cpu_usage,&elapsed_time); . . . /* We now calculate what our throughput was for the test. */ thruput = nummessages/elapsed_time; . . . } Listing Two /* This routine implements the TCP unidirectional data transfer test */ /* (a.k.a. stream) for the sockets interface. It receives its */ /* parameters via global variables from the shell and writes its */ /* output to the standard output. */ void send_tcp_stream(remote_host) char remote_host[]; { . . . /* Tell the remote end to do a listen.... */ /* This call also sends a parameter message to Receiver (remote) side. */ send_request(); . . . /* receive message from Receiver (remote); contains data such as * port number but can be seen as a "ready" status. -- yun */ recv_response(); . . . /*Connect up to the remote port on the data socket */ if (connect(send_socket, (struct sockaddr *)&server, sizeof(server)) <0){ perror("netperf: send_tcp_stream: data socket connect failed"); printf(" port: %d\n",ntohs(server.sin_port)); exit(1); } . . . /* Set-up the test end conditions. For a stream test, they can be */ /* either time or byte-count based. */ if (test_time) { /* The user wanted to end the test after a period of time. */ times_up = 0; bytes_remaining = 0; start_timer(test_time); } else { /* The tester wanted to send a number of bytes. */ bytes_remaining = test_bytes; times_up = 1; } /* The cpu_start routine will grab the current time and possibly */ /* value of the idle counter for later use in measuring cpu */ /* utilization and/or service demand and thruput. */ cpu_start(local_cpu_usage); . . . /* We use an "OR" to control test execution. When the test is */ /* controlled by time, the byte count check will always return false. */ /* When the test is controlled by byte count, the time test will */ /* always return false. When the test is finished, the whole */ /* expression will go false and we will stop sending data. */ while ((!times_up) || (bytes_remaining > 0)) { if((len=send(send_socket, send_ring->buffer_ptr, send_size, 0)) != send_size) { /* the test was interrupted, must be the end of test */ break; } perror("netperf: data send error"); printf("len was %d\n",len); exit(1); } /* now we want to move our pointer to the next position in the */ /* data buffer...we may also want to wrap back to the "beginning" */ /* of the bufferspace, so we will mod the number of messages sent */ /* by the send width, and use that to calculate the offset to add */ /* to the base pointer. */ nummessages++; send_ring = send_ring->next; if (bytes_remaining) { bytes_remaining -= send_size; } } /* The test is over. Flush buffers to the remote end. We do a graceful */ /* release to insure all data has been taken by the remote. */ . . . if (shutdown(send_socket,1) == -1) { perror("netperf: cannot shutdown tcp stream socket"); exit(1); } /* hang a recv() off the socket to block until the remote has */ /* brought all the data up into the application. it will do a */ /* shutdown to cause a FIN to be sent our way. We will assume that */ /* any exit from the recv() call is good... raj 4/93 */ recv(send_socket, send_ring->buffer_ptr, send_size, 0); /* this call will always give us the elapsed time for the test, and */ /* will also store-away the necessaries for cpu utilization */ cpu_stop(local_cpu_usage,&elapsed_time); /* we are finished with the socket, so close it to prevent hitting */ /* the limit on maximum open files. */ close(send_socket); /* Get the statistics from the remote end. The remote will have */ /* calculated service demand and all those interesting things. If it */ /* wasn't supposed to care, it will return obvious values. */ recv_response(); if (!netperf_response.content.serv_errno) { if (debug) fprintf(where,"remote results obtained\n"); } else { errno = netperf_response.content.serv_errno; fprintf(where, "netperf: remote error %d", netperf_response.content.serv_errno); perror(""); fflush(where); exit(1); } /* We now calculate what our thruput was for the test. In the future, */ /* we may want to include a calculation of the thruput measured by */ /* the remote, but it should be the case that for a TCP stream test, */ /* that the two numbers should be *very* close... We calculate */ /* bytes_sent regardless of the way the test length was controlled. */ /* If it was time, we needed to, and if it was by bytes, the user may */ /* have specified a number of bytes that wasn't a multiple of the */ /* send_size, so we really didn't send what he asked for ;-) */ bytes_sent = ntohd(tcp_stream_result->bytes_received); thruput = (double) calc_thruput(bytes_sent); . . . } Listing Three /* This is the server-side routine for the tcp stream test. It is */ /* implemented as one routine. I could break things-out somewhat, but */ /* didn't feel it was necessary. */ void recv_tcp_stream() { . . . /* The parameter message from the Sender side had already been received * by the parent of recv_tcp_stream(). -- yun */ . . . /* Now, let's set-up the socket to listen for connections */ if (listen(s_listen, 5) == -1) { netperf_response.content.serv_errno = errno; close(s_listen); send_response(); exit(1); } . . . /* send data such as port number to Sender; can also be seen as * Receiver's "reeady" status -- yun */ send_response(); . . . if ((s_data=accept(s_listen, (struct sockaddr *)&peeraddr_in, &addrlen)) == -1) { /* Let's just punt. The remote will be given some information */ close(s_listen); exit(1); } . . . cpu_start(tcp_stream_request->measure_cpu); . . . /* The loop will exit when the sender does a shutdown, which will */ /* return a length of zero */ while ((len = recv(s_data, recv_ring->buffer_ptr, recv_size, 0)) != 0) { if (len < 0) { netperf_response.content.serv_errno = errno; netperf_response.content.serv_errno = errno; send_response(); exit(1); } bytes_received += len; receive_calls++; /* more to the next buffer in the recv_ring */ recv_ring = recv_ring->next; } /* perform a shutdown to signal the sender that */ /* we have received all the data sent. raj 4/93 */ if (shutdown(s_data,1) == -1) { netperf_response.content.serv_errno = errno; send_response(); exit(1); } cpu_stop(tcp_stream_request->measure_cpu,&elapsed_time); . . . } 5