@@ -30,7 +30,7 @@ const (
3030 DefaultWebSocketHandshakeTimeout = 5 * time .Second
3131
3232 // DefaultStreams is the default number of streams for a new client.
33- DefaultStreams = 3
33+ DefaultStreams = 2
3434
3535 // DefaultLength is the default test duration for a new client.
3636 DefaultLength = 5 * time .Second
@@ -92,10 +92,23 @@ type Throughput1Client struct {
9292 // It is set when the first streams connects to the server and used to compute the elapsed time.
9393 sharedStartTime time.Time
9494 started atomic.Bool
95+
96+ // rtt is the latest RTT value from TCPInfo.
97+ rtt atomic.Uint32
98+
99+ // minRTT is the lowest RTT value observed across all streams.
100+ minRTT atomic.Uint32
101+
102+ // lastResultForSubtest contains the last recorded measurement for the
103+ // corresponding subtest (download/upload).
104+ lastResultForSubtest map [spec.SubtestKind ]Result
105+ lastResultForSubtestMutex sync.Mutex
95106}
96107
97108// Result contains the aggregate metrics collected during the test.
98109type Result struct {
110+ // Subtest is the subtest this Result refers to.
111+ Subtest spec.SubtestKind
99112 // Goodput is the average number of application-level bits per second that
100113 // have been transferred so far across all the streams.
101114 Goodput float64
@@ -104,8 +117,18 @@ type Result struct {
104117 Throughput float64
105118 // Elapsed is the total time elapsed since the test started.
106119 Elapsed time.Duration
107- // MinRTT is the minimum of MinRTT values observed across all the streams.
120+ // RTT is the latest RTT value from TCPInfo from any stream.
121+ RTT uint32
122+ // MinRTT is the minimum of RTT values observed across all the streams.
108123 MinRTT uint32
124+ // Streams is the number of streams used in the test.
125+ Streams int
126+ // ByteLimit is the byte limit used in the test.
127+ ByteLimit int
128+ // Length is the length of the test.
129+ Length time.Duration
130+ // CongestionControl is the congestion control used in the test.
131+ CongestionControl string
109132}
110133
111134// makeUserAgent creates the user agent string.
@@ -131,6 +154,8 @@ func New(clientName, clientVersion string, config Config) *Throughput1Client {
131154
132155 tIndex : map [string ]int {},
133156 recvByteCounters : map [int ][]int64 {},
157+
158+ lastResultForSubtest : map [spec.SubtestKind ]Result {},
134159 }
135160}
136161
@@ -214,6 +239,7 @@ func (c *Throughput1Client) start(ctx context.Context, subtest spec.SubtestKind)
214239
215240 // Reset the counters.
216241 c .recvByteCounters = map [int ][]int64 {}
242+ c .rtt .Store (0 )
217243
218244 startTimeCh := make (chan time.Time , 1 )
219245
@@ -253,7 +279,6 @@ func (c *Throughput1Client) start(ctx context.Context, subtest spec.SubtestKind)
253279 }
254280
255281 wg .Wait ()
256-
257282 return nil
258283}
259284
@@ -303,40 +328,39 @@ func (c *Throughput1Client) runStream(ctx context.Context, streamID int, mURL *u
303328 clientCh , serverCh , errCh = proto .SenderLoop (ctx )
304329 }
305330
331+ var m model.WireMeasurement
332+
306333 for {
307334 select {
308335 case <- ctx .Done ():
309- c .config .Emitter .OnComplete (streamID , mURL .Host )
336+ c .config .Emitter .OnStreamComplete (streamID , mURL .Host )
310337 return nil
311- case m : = <- clientCh :
338+ case m = <- clientCh :
312339 // If subtest is download, store the client-side measurement.
313340 if subtest != spec .SubtestDownload {
314341 continue
315342 }
316- c .config .Emitter .OnMeasurement (streamID , m )
317- c .config .Emitter .OnDebug (fmt .Sprintf ("Stream #%d - application r/w: %d/%d, network r/w: %d/%d" ,
318- streamID , m .Application .BytesReceived , m .Application .BytesSent ,
319- m .Network .BytesReceived , m .Network .BytesSent ))
320- c .storeMeasurement (streamID , m )
321- if c .started .Load () {
322- c .emitResult (c .sharedStartTime )
323- }
324- case m := <- serverCh :
343+ case m = <- serverCh :
325344 // If subtest is upload, store the server-side measurement.
326345 if subtest != spec .SubtestUpload {
327346 continue
328347 }
329- c .config .Emitter .OnMeasurement (streamID , m )
330- c .config .Emitter .OnDebug (fmt .Sprintf ("#%d - application r/w: %d/%d, network r/w: %d/%d" ,
331- streamID , m .Application .BytesReceived , m .Application .BytesSent ,
332- m .Network .BytesReceived , m .Network .BytesSent ))
333- c .storeMeasurement (streamID , m )
334- if c .started .Load () {
335- c .emitResult (c .sharedStartTime )
336- }
337348 case err := <- errCh :
338349 return err
339350 }
351+
352+ c .config .Emitter .OnMeasurement (streamID , m )
353+ c .config .Emitter .OnDebug (fmt .Sprintf ("Stream #%d - application r/w: %d/%d, network r/w: %d/%d" ,
354+ streamID , m .Application .BytesReceived , m .Application .BytesSent ,
355+ m .Network .BytesReceived , m .Network .BytesSent ))
356+ c .storeMeasurement (streamID , m )
357+ if c .started .Load () {
358+ res := c .computeResult (subtest )
359+ c .config .Emitter .OnResult (res )
360+ c .lastResultForSubtestMutex .Lock ()
361+ c .lastResultForSubtest [subtest ] = res
362+ c .lastResultForSubtestMutex .Unlock ()
363+ }
340364 }
341365}
342366
@@ -345,6 +369,16 @@ func (c *Throughput1Client) storeMeasurement(streamID int, m model.WireMeasureme
345369 c .recvByteCountersMutex .Lock ()
346370 c .recvByteCounters [streamID ] = append (c .recvByteCounters [streamID ], m .Application .BytesReceived )
347371 c .recvByteCountersMutex .Unlock ()
372+
373+ if m .TCPInfo != nil {
374+ if m .TCPInfo .RTT > 0 {
375+ c .rtt .Store (m .TCPInfo .RTT )
376+ }
377+ minRTT := c .minRTT .Load ()
378+ if m .TCPInfo .MinRTT > 0 && (minRTT == 0 || m .TCPInfo .MinRTT < minRTT ) {
379+ c .minRTT .Store (m .TCPInfo .MinRTT )
380+ }
381+ }
348382}
349383
350384// applicationBytes returns the aggregate application-level bytes transferred by all the streams.
@@ -360,17 +394,23 @@ func (c *Throughput1Client) applicationBytes() int64 {
360394 return sum
361395}
362396
363- // emitResult emits the result of the current measurement via the configured Emitter .
364- func (c * Throughput1Client ) emitResult ( start time. Time ) {
397+ // computeResult returns a Result struct with the current state of the measurement .
398+ func (c * Throughput1Client ) computeResult ( subtest spec. SubtestKind ) Result {
365399 applicationBytes := c .applicationBytes ()
366- elapsed := time .Since (start )
400+ elapsed := time .Since (c . sharedStartTime )
367401 goodput := float64 (applicationBytes ) / float64 (elapsed .Seconds ()) * 8 // bps
368- result := Result {
369- Elapsed : elapsed ,
370- Goodput : goodput ,
371- Throughput : 0 , // TODO
402+ return Result {
403+ Subtest : subtest ,
404+ Elapsed : elapsed ,
405+ Goodput : goodput ,
406+ Throughput : 0 , // TODO,
407+ MinRTT : c .minRTT .Load (),
408+ RTT : c .rtt .Load (),
409+ Streams : c .config .NumStreams ,
410+ ByteLimit : c .config .ByteLimit ,
411+ Length : c .config .Length ,
412+ CongestionControl : c .config .CongestionControl ,
372413 }
373- c .config .Emitter .OnResult (result )
374414}
375415
376416// Download runs a download test using the settings configured for this client.
@@ -389,6 +429,11 @@ func (c *Throughput1Client) Upload(ctx context.Context) {
389429 }
390430}
391431
432+ // PrintSummary emits a summary via the configured emitter
433+ func (c * Throughput1Client ) PrintSummary () {
434+ c .config .Emitter .OnSummary (c .lastResultForSubtest )
435+ }
436+
392437func getPathForSubtest (subtest spec.SubtestKind ) string {
393438 switch subtest {
394439 case spec .SubtestDownload :
0 commit comments