Skip to content

Commit af9f6c4

Browse files
Merge pull request #30 from m-lab/sandbox-soltesz-final-wiremessage
Send a final WireMeasurement after byte limit reached
2 parents 271633e + 9545fd5 commit af9f6c4

File tree

3 files changed

+71
-67
lines changed

3 files changed

+71
-67
lines changed

internal/measurer/measurer.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@ import (
1616
"github.com/m-lab/msak/pkg/throughput1/spec"
1717
)
1818

19-
type throughput1Measurer struct {
19+
// Throughput1Measurer tracks state for collecting connection measurements.
20+
type Throughput1Measurer struct {
2021
connInfo netx.ConnInfo
2122
startTime time.Time
2223
bytesReadAtStart int64
2324
bytesWrittenAtStart int64
2425

2526
dstChan chan model.Measurement
27+
28+
// ReadChan is a readable channel for measurements created by the measurer.
29+
ReadChan <-chan model.Measurement
30+
}
31+
32+
// New creates an empty Throughput1Measurer. The measurer must be started with Start.
33+
func New() *Throughput1Measurer {
34+
return &Throughput1Measurer{}
2635
}
2736

2837
// Start starts a measurer goroutine that periodically reads the tcp_info and
@@ -31,7 +40,7 @@ type throughput1Measurer struct {
3140
//
3241
// The context determines the measurer goroutine's lifetime.
3342
// If passed a connection that is not a netx.Conn, this function will panic.
34-
func Start(ctx context.Context, conn net.Conn) <-chan model.Measurement {
43+
func (m *Throughput1Measurer) Start(ctx context.Context, conn net.Conn) <-chan model.Measurement {
3544
// Implementation note: this channel must be buffered to account for slow
3645
// readers. The "typical" reader is an throughput1 send or receive loop, which
3746
// might be busy with data r/w. The buffer size corresponds to at least 10
@@ -42,9 +51,10 @@ func Start(ctx context.Context, conn net.Conn) <-chan model.Measurement {
4251

4352
connInfo := netx.ToConnInfo(conn)
4453
read, written := connInfo.ByteCounters()
45-
m := &throughput1Measurer{
54+
*m = Throughput1Measurer{
4655
connInfo: connInfo,
4756
dstChan: dst,
57+
ReadChan: dst,
4858
startTime: time.Now(),
4959
// Byte counters are offset by their initial value, so that the
5060
// BytesSent/BytesReceived fields represent "application-level bytes
@@ -55,10 +65,10 @@ func Start(ctx context.Context, conn net.Conn) <-chan model.Measurement {
5565
bytesWrittenAtStart: int64(written),
5666
}
5767
go m.loop(ctx)
58-
return dst
68+
return m.ReadChan
5969
}
6070

61-
func (m *throughput1Measurer) loop(ctx context.Context) {
71+
func (m *Throughput1Measurer) loop(ctx context.Context) {
6272
log.Debug("Measurer started", "context", ctx)
6373
defer log.Debug("Measurer stopped", "context", ctx)
6474
t, err := memoryless.NewTicker(ctx, memoryless.Config{
@@ -81,7 +91,16 @@ func (m *throughput1Measurer) loop(ctx context.Context) {
8191
}
8292
}
8393

84-
func (m *throughput1Measurer) measure(ctx context.Context) {
94+
func (m *Throughput1Measurer) measure(ctx context.Context) {
95+
select {
96+
case <-ctx.Done():
97+
// NOTHING
98+
case m.dstChan <- m.Measure(ctx):
99+
}
100+
}
101+
102+
// Measure collects metrics about the life of the connection.
103+
func (m *Throughput1Measurer) Measure(ctx context.Context) model.Measurement {
85104
// On non-Linux systems, collecting kernel metrics WILL fail. In that case,
86105
// we still want to return a (empty) Measurement.
87106
bbrInfo, tcpInfo, err := m.connInfo.Info()
@@ -92,10 +111,7 @@ func (m *throughput1Measurer) measure(ctx context.Context) {
92111
// Read current bytes counters.
93112
totalRead, totalWritten := m.connInfo.ByteCounters()
94113

95-
select {
96-
case <-ctx.Done():
97-
// NOTHING
98-
case m.dstChan <- model.Measurement{
114+
return model.Measurement{
99115
ElapsedTime: time.Since(m.startTime).Microseconds(),
100116
Network: model.ByteCounters{
101117
BytesSent: int64(totalWritten) - m.bytesWrittenAtStart,
@@ -106,6 +122,5 @@ func (m *throughput1Measurer) measure(ctx context.Context) {
106122
LinuxTCPInfo: tcpInfo,
107123
ElapsedTime: time.Since(m.connInfo.AcceptTime()).Microseconds(),
108124
},
109-
}:
110125
}
111126
}

internal/measurer/measurer_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ func TestNdt8Measurer_Start(t *testing.T) {
2323
}
2424
ctx, cancel := context.WithCancel(context.Background())
2525
defer cancel()
26-
mchan := measurer.Start(ctx, serverConn)
26+
m := measurer.New()
27+
mchan := m.Start(ctx, serverConn)
2728
go func() {
2829
_, err := serverConn.Write([]byte("test"))
2930
rtx.Must(err, "failed to write to pipe")

pkg/throughput1/protocol.go

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,10 @@ type senderFunc func(ctx context.Context,
2424
measurerCh <-chan model.Measurement, results chan<- model.WireMeasurement,
2525
errCh chan<- error)
2626

27+
// Measurer is an interface for collecting connection metrics.
2728
type Measurer interface {
2829
Start(context.Context, net.Conn) <-chan model.Measurement
29-
}
30-
31-
// DefaultMeasurer is the default throughput1 measurer that wraps the measurer
32-
// package's Start function.
33-
type DefaultMeasurer struct{}
34-
35-
func (*DefaultMeasurer) Start(ctx context.Context,
36-
c net.Conn) <-chan model.Measurement {
37-
return measurer.Start(ctx, c)
30+
Measure(ctx context.Context) model.Measurement
3831
}
3932

4033
// Protocol is the implementation of the throughput1 protocol.
@@ -59,7 +52,7 @@ func New(conn *websocket.Conn) *Protocol {
5952
connInfo: netx.ToConnInfo(conn.UnderlyingConn()),
6053
// Seed randomness source with the current time.
6154
rnd: rand.New(rand.NewSource(time.Now().UnixMilli())),
62-
measurer: &DefaultMeasurer{},
55+
measurer: measurer.New(),
6356
}
6457
}
6558

@@ -185,51 +178,59 @@ func (p *Protocol) receiver(ctx context.Context,
185178
}
186179
}
187180

181+
func (p *Protocol) sendWireMeasurement(ctx context.Context, m model.Measurement) (*model.WireMeasurement, error) {
182+
wm := model.WireMeasurement{}
183+
p.once.Do(func() {
184+
wm = p.createWireMeasurement(ctx)
185+
})
186+
wm.Measurement = m
187+
wm.Application = model.ByteCounters{
188+
BytesSent: p.applicationBytesSent.Load(),
189+
BytesReceived: p.applicationBytesReceived.Load(),
190+
}
191+
// Encode as JSON separately so we can read the message size before
192+
// sending.
193+
jsonwm, err := json.Marshal(wm)
194+
if err != nil {
195+
log.Printf("failed to encode measurement (ctx: %p, err: %v)", ctx, err)
196+
return nil, err
197+
}
198+
err = p.conn.WriteMessage(websocket.TextMessage, jsonwm)
199+
if err != nil {
200+
log.Printf("failed to write measurement JSON (ctx: %p, err: %v)", ctx, err)
201+
return nil, err
202+
}
203+
p.applicationBytesSent.Add(int64(len(jsonwm)))
204+
return &wm, nil
205+
}
206+
188207
func (p *Protocol) sendCounterflow(ctx context.Context,
189208
measurerCh <-chan model.Measurement, results chan<- model.WireMeasurement,
190209
errCh chan<- error) {
191210
byteLimit := int64(p.byteLimit)
192211
for {
193212
select {
194213
case <-ctx.Done():
214+
// Attempt to send final write message before close. Ignore errors.
215+
p.sendWireMeasurement(ctx, p.measurer.Measure(ctx))
195216
p.close(ctx)
196217
return
197218
case m := <-measurerCh:
198-
wm := model.WireMeasurement{}
199-
p.once.Do(func() {
200-
wm = p.createWireMeasurement(ctx)
201-
})
202-
wm.Measurement = m
203-
wm.Application = model.ByteCounters{
204-
BytesSent: p.applicationBytesSent.Load(),
205-
BytesReceived: p.applicationBytesReceived.Load(),
206-
}
207-
// Encode as JSON separately so we can read the message size before
208-
// sending.
209-
jsonwm, err := json.Marshal(wm)
219+
wm, err := p.sendWireMeasurement(ctx, m)
210220
if err != nil {
211-
log.Printf("failed to encode measurement (ctx: %p, err: %v)",
212-
ctx, err)
213221
errCh <- err
214222
return
215223
}
216-
err = p.conn.WriteMessage(websocket.TextMessage, jsonwm)
217-
if err != nil {
218-
log.Printf("failed to write measurement JSON (ctx: %p, err: %v)", ctx, err)
219-
errCh <- err
220-
return
221-
}
222-
p.applicationBytesSent.Add(int64(len(jsonwm)))
223-
224224
// This send is non-blocking in case there is no one to read the
225225
// Measurement message and the channel's buffer is full.
226226
select {
227-
case results <- wm:
227+
case results <- *wm:
228228
default:
229229
}
230230

231231
// End the test once enough bytes have been received.
232232
if byteLimit > 0 && m.TCPInfo != nil && m.TCPInfo.BytesReceived >= byteLimit {
233+
// WireMessage was just sent above, so we do not need to send another.
233234
p.close(ctx)
234235
return
235236
}
@@ -254,39 +255,21 @@ func (p *Protocol) sender(ctx context.Context, measurerCh <-chan model.Measureme
254255
for {
255256
select {
256257
case <-ctx.Done():
258+
// Attempt to send final write message before close. Ignore errors.
259+
p.sendWireMeasurement(ctx, p.measurer.Measure(ctx))
257260
p.close(ctx)
258261
return
259262
case m := <-measurerCh:
260-
wm := model.WireMeasurement{}
261-
p.once.Do(func() {
262-
wm = p.createWireMeasurement(ctx)
263-
})
264-
wm.Measurement = m
265-
wm.Application = model.ByteCounters{
266-
BytesReceived: p.applicationBytesReceived.Load(),
267-
BytesSent: p.applicationBytesSent.Load(),
268-
}
269-
// Encode as JSON separately so we can read the message size before
270-
// sending.
271-
jsonwm, err := json.Marshal(wm)
272-
if err != nil {
273-
log.Printf("failed to encode measurement (ctx: %p, err: %v)",
274-
ctx, err)
275-
errCh <- err
276-
return
277-
}
278-
err = p.conn.WriteMessage(websocket.TextMessage, jsonwm)
263+
wm, err := p.sendWireMeasurement(ctx, m)
279264
if err != nil {
280-
log.Printf("failed to write measurement JSON (ctx: %p, err: %v)", ctx, err)
281265
errCh <- err
282266
return
283267
}
284-
p.applicationBytesSent.Add(int64(len(jsonwm)))
285268

286269
// This send is non-blocking in case there is no one to read the
287270
// Measurement message and the channel's buffer is full.
288271
select {
289-
case results <- wm:
272+
case results <- *wm:
290273
default:
291274
}
292275
default:
@@ -300,6 +283,11 @@ func (p *Protocol) sender(ctx context.Context, measurerCh <-chan model.Measureme
300283

301284
bytesSent := int(p.applicationBytesSent.Load())
302285
if p.byteLimit > 0 && bytesSent >= p.byteLimit {
286+
_, err := p.sendWireMeasurement(ctx, p.measurer.Measure(ctx))
287+
if err != nil {
288+
errCh <- err
289+
return
290+
}
303291
p.close(ctx)
304292
return
305293
}

0 commit comments

Comments
 (0)