297 lines
9.0 KiB
Go
297 lines
9.0 KiB
Go
package h2mux
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/golang-collections/collections/queue"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// data points used to compute average receive window and send window size
|
|
const (
|
|
// data points used to compute average receive window and send window size
|
|
dataPoints = 100
|
|
// updateFreq is set to 1 sec so we can get inbound & outbound byes/sec
|
|
updateFreq = time.Second
|
|
)
|
|
|
|
type muxMetricsUpdater interface {
|
|
// metrics returns the latest metrics
|
|
metrics() *MuxerMetrics
|
|
// run is a blocking call to start the event loop
|
|
run(log *zerolog.Logger) error
|
|
// updateRTTChan is called by muxReader to report new RTT measurements
|
|
updateRTT(rtt *roundTripMeasurement)
|
|
//updateReceiveWindowChan is called by muxReader and muxWriter when receiveWindow size is updated
|
|
updateReceiveWindow(receiveWindow uint32)
|
|
//updateSendWindowChan is called by muxReader and muxWriter when sendWindow size is updated
|
|
updateSendWindow(sendWindow uint32)
|
|
// updateInBoundBytesChan is called periodicallyby muxReader to report bytesRead
|
|
updateInBoundBytes(inBoundBytes uint64)
|
|
// updateOutBoundBytesChan is called periodically by muxWriter to report bytesWrote
|
|
updateOutBoundBytes(outBoundBytes uint64)
|
|
}
|
|
|
|
type muxMetricsUpdaterImpl struct {
|
|
// rttData keeps record of rtt, rttMin, rttMax and last measured time
|
|
rttData *rttData
|
|
// receiveWindowData keeps record of receive window measurement
|
|
receiveWindowData *flowControlData
|
|
// sendWindowData keeps record of send window measurement
|
|
sendWindowData *flowControlData
|
|
// inBoundRate is incoming bytes/sec
|
|
inBoundRate *rate
|
|
// outBoundRate is outgoing bytes/sec
|
|
outBoundRate *rate
|
|
// updateRTTChan is the channel to receive new RTT measurement
|
|
updateRTTChan chan *roundTripMeasurement
|
|
//updateReceiveWindowChan is the channel to receive updated receiveWindow size
|
|
updateReceiveWindowChan chan uint32
|
|
//updateSendWindowChan is the channel to receive updated sendWindow size
|
|
updateSendWindowChan chan uint32
|
|
// updateInBoundBytesChan us the channel to receive bytesRead
|
|
updateInBoundBytesChan chan uint64
|
|
// updateOutBoundBytesChan us the channel to receive bytesWrote
|
|
updateOutBoundBytesChan chan uint64
|
|
// shutdownC is to signal the muxerMetricsUpdater to shutdown
|
|
abortChan <-chan struct{}
|
|
|
|
compBytesBefore, compBytesAfter *AtomicCounter
|
|
}
|
|
|
|
type MuxerMetrics struct {
|
|
RTT, RTTMin, RTTMax time.Duration
|
|
ReceiveWindowAve, SendWindowAve float64
|
|
ReceiveWindowMin, ReceiveWindowMax, SendWindowMin, SendWindowMax uint32
|
|
InBoundRateCurr, InBoundRateMin, InBoundRateMax uint64
|
|
OutBoundRateCurr, OutBoundRateMin, OutBoundRateMax uint64
|
|
CompBytesBefore, CompBytesAfter *AtomicCounter
|
|
}
|
|
|
|
func (m *MuxerMetrics) CompRateAve() float64 {
|
|
if m.CompBytesBefore.Value() == 0 {
|
|
return 1.
|
|
}
|
|
return float64(m.CompBytesAfter.Value()) / float64(m.CompBytesBefore.Value())
|
|
}
|
|
|
|
type roundTripMeasurement struct {
|
|
receiveTime, sendTime time.Time
|
|
}
|
|
|
|
type rttData struct {
|
|
rtt, rttMin, rttMax time.Duration
|
|
lastMeasurementTime time.Time
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
type flowControlData struct {
|
|
sum uint64
|
|
min, max uint32
|
|
queue *queue.Queue
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
type rate struct {
|
|
curr uint64
|
|
min, max uint64
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
func newMuxMetricsUpdater(
|
|
abortChan <-chan struct{},
|
|
compBytesBefore, compBytesAfter *AtomicCounter,
|
|
) muxMetricsUpdater {
|
|
updateRTTChan := make(chan *roundTripMeasurement, 1)
|
|
updateReceiveWindowChan := make(chan uint32, 1)
|
|
updateSendWindowChan := make(chan uint32, 1)
|
|
updateInBoundBytesChan := make(chan uint64)
|
|
updateOutBoundBytesChan := make(chan uint64)
|
|
|
|
return &muxMetricsUpdaterImpl{
|
|
rttData: newRTTData(),
|
|
receiveWindowData: newFlowControlData(),
|
|
sendWindowData: newFlowControlData(),
|
|
inBoundRate: newRate(),
|
|
outBoundRate: newRate(),
|
|
updateRTTChan: updateRTTChan,
|
|
updateReceiveWindowChan: updateReceiveWindowChan,
|
|
updateSendWindowChan: updateSendWindowChan,
|
|
updateInBoundBytesChan: updateInBoundBytesChan,
|
|
updateOutBoundBytesChan: updateOutBoundBytesChan,
|
|
abortChan: abortChan,
|
|
compBytesBefore: compBytesBefore,
|
|
compBytesAfter: compBytesAfter,
|
|
}
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) metrics() *MuxerMetrics {
|
|
m := &MuxerMetrics{}
|
|
m.RTT, m.RTTMin, m.RTTMax = updater.rttData.metrics()
|
|
m.ReceiveWindowAve, m.ReceiveWindowMin, m.ReceiveWindowMax = updater.receiveWindowData.metrics()
|
|
m.SendWindowAve, m.SendWindowMin, m.SendWindowMax = updater.sendWindowData.metrics()
|
|
m.InBoundRateCurr, m.InBoundRateMin, m.InBoundRateMax = updater.inBoundRate.get()
|
|
m.OutBoundRateCurr, m.OutBoundRateMin, m.OutBoundRateMax = updater.outBoundRate.get()
|
|
m.CompBytesBefore, m.CompBytesAfter = updater.compBytesBefore, updater.compBytesAfter
|
|
return m
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) run(log *zerolog.Logger) error {
|
|
defer log.Debug().Msg("mux - metrics: event loop finished")
|
|
for {
|
|
select {
|
|
case <-updater.abortChan:
|
|
log.Debug().Msgf("mux - metrics: Stopping mux metrics updater")
|
|
return nil
|
|
case roundTripMeasurement := <-updater.updateRTTChan:
|
|
go updater.rttData.update(roundTripMeasurement)
|
|
log.Debug().Msg("mux - metrics: Update rtt")
|
|
case receiveWindow := <-updater.updateReceiveWindowChan:
|
|
go updater.receiveWindowData.update(receiveWindow)
|
|
log.Debug().Msg("mux - metrics: Update receive window")
|
|
case sendWindow := <-updater.updateSendWindowChan:
|
|
go updater.sendWindowData.update(sendWindow)
|
|
log.Debug().Msg("mux - metrics: Update send window")
|
|
case inBoundBytes := <-updater.updateInBoundBytesChan:
|
|
// inBoundBytes is bytes/sec because the update interval is 1 sec
|
|
go updater.inBoundRate.update(inBoundBytes)
|
|
log.Debug().Msgf("mux - metrics: Inbound bytes %d", inBoundBytes)
|
|
case outBoundBytes := <-updater.updateOutBoundBytesChan:
|
|
// outBoundBytes is bytes/sec because the update interval is 1 sec
|
|
go updater.outBoundRate.update(outBoundBytes)
|
|
log.Debug().Msgf("mux - metrics: Outbound bytes %d", outBoundBytes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) updateRTT(rtt *roundTripMeasurement) {
|
|
select {
|
|
case updater.updateRTTChan <- rtt:
|
|
case <-updater.abortChan:
|
|
}
|
|
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) updateReceiveWindow(receiveWindow uint32) {
|
|
select {
|
|
case updater.updateReceiveWindowChan <- receiveWindow:
|
|
case <-updater.abortChan:
|
|
}
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) updateSendWindow(sendWindow uint32) {
|
|
select {
|
|
case updater.updateSendWindowChan <- sendWindow:
|
|
case <-updater.abortChan:
|
|
}
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) updateInBoundBytes(inBoundBytes uint64) {
|
|
select {
|
|
case updater.updateInBoundBytesChan <- inBoundBytes:
|
|
case <-updater.abortChan:
|
|
}
|
|
|
|
}
|
|
|
|
func (updater *muxMetricsUpdaterImpl) updateOutBoundBytes(outBoundBytes uint64) {
|
|
select {
|
|
case updater.updateOutBoundBytesChan <- outBoundBytes:
|
|
case <-updater.abortChan:
|
|
}
|
|
}
|
|
|
|
func newRTTData() *rttData {
|
|
return &rttData{}
|
|
}
|
|
|
|
func (r *rttData) update(measurement *roundTripMeasurement) {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
// discard pings before lastMeasurementTime
|
|
if r.lastMeasurementTime.After(measurement.sendTime) {
|
|
return
|
|
}
|
|
r.lastMeasurementTime = measurement.sendTime
|
|
r.rtt = measurement.receiveTime.Sub(measurement.sendTime)
|
|
if r.rttMax < r.rtt {
|
|
r.rttMax = r.rtt
|
|
}
|
|
if r.rttMin == 0 || r.rttMin > r.rtt {
|
|
r.rttMin = r.rtt
|
|
}
|
|
}
|
|
|
|
func (r *rttData) metrics() (rtt, rttMin, rttMax time.Duration) {
|
|
r.lock.RLock()
|
|
defer r.lock.RUnlock()
|
|
return r.rtt, r.rttMin, r.rttMax
|
|
}
|
|
|
|
func newFlowControlData() *flowControlData {
|
|
return &flowControlData{queue: queue.New()}
|
|
}
|
|
|
|
func (f *flowControlData) update(measurement uint32) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
var firstItem uint32
|
|
// store new data into queue, remove oldest data if queue is full
|
|
f.queue.Enqueue(measurement)
|
|
if f.queue.Len() > dataPoints {
|
|
// data type should always be uint32
|
|
firstItem = f.queue.Dequeue().(uint32)
|
|
}
|
|
// if (measurement - firstItem) < 0, uint64(measurement - firstItem)
|
|
// will overflow and become a large positive number
|
|
f.sum += uint64(measurement)
|
|
f.sum -= uint64(firstItem)
|
|
if measurement > f.max {
|
|
f.max = measurement
|
|
}
|
|
if f.min == 0 || measurement < f.min {
|
|
f.min = measurement
|
|
}
|
|
}
|
|
|
|
// caller of ave() should acquire lock first
|
|
func (f *flowControlData) ave() float64 {
|
|
if f.queue.Len() == 0 {
|
|
return 0
|
|
}
|
|
return float64(f.sum) / float64(f.queue.Len())
|
|
}
|
|
|
|
func (f *flowControlData) metrics() (ave float64, min, max uint32) {
|
|
f.lock.RLock()
|
|
defer f.lock.RUnlock()
|
|
return f.ave(), f.min, f.max
|
|
}
|
|
|
|
func newRate() *rate {
|
|
return &rate{}
|
|
}
|
|
|
|
func (r *rate) update(measurement uint64) {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
r.curr = measurement
|
|
// if measurement is 0, then there is no incoming/outgoing connection, don't update min/max
|
|
if r.curr == 0 {
|
|
return
|
|
}
|
|
if measurement > r.max {
|
|
r.max = measurement
|
|
}
|
|
if r.min == 0 || measurement < r.min {
|
|
r.min = measurement
|
|
}
|
|
}
|
|
|
|
func (r *rate) get() (curr, min, max uint64) {
|
|
r.lock.RLock()
|
|
defer r.lock.RUnlock()
|
|
return r.curr, r.min, r.max
|
|
}
|