feat: expose websocket delivery pressure metrics
This commit is contained in:
+54
-9
@@ -141,6 +141,10 @@ type Session struct {
|
||||
// Outbound mesages, buffered.
|
||||
// The content must be serialized in format suitable for the session.
|
||||
send chan any
|
||||
// WebSocket queue timestamps are kept separately to preserve the send channel's payload contract.
|
||||
wsQueueMu sync.Mutex
|
||||
wsQueueTimes []time.Time
|
||||
wsQueueHead int
|
||||
|
||||
// Channel for shutting down the session, buffer 1.
|
||||
// Content in the same format as for 'send'
|
||||
@@ -182,6 +186,52 @@ type Subscription struct {
|
||||
supd chan<- *sessionUpdate
|
||||
}
|
||||
|
||||
func (s *Session) enqueueOutbound(payload any) bool {
|
||||
if s.proto != WEBSOCK {
|
||||
select {
|
||||
case s.send <- payload:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
s.wsQueueMu.Lock()
|
||||
defer s.wsQueueMu.Unlock()
|
||||
select {
|
||||
case s.send <- payload:
|
||||
s.wsQueueTimes = append(s.wsQueueTimes, time.Now())
|
||||
immetrics.ObserveWSSendQueueEnqueue(len(s.send), cap(s.send), true)
|
||||
return true
|
||||
default:
|
||||
immetrics.ObserveWSSendQueueEnqueue(len(s.send), cap(s.send), false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) observeOutboundDequeue(purged bool) {
|
||||
if s.proto != WEBSOCK {
|
||||
return
|
||||
}
|
||||
s.wsQueueMu.Lock()
|
||||
if s.wsQueueHead >= len(s.wsQueueTimes) {
|
||||
s.wsQueueMu.Unlock()
|
||||
return
|
||||
}
|
||||
queuedAt := s.wsQueueTimes[s.wsQueueHead]
|
||||
s.wsQueueHead++
|
||||
if s.wsQueueHead == len(s.wsQueueTimes) {
|
||||
s.wsQueueTimes = s.wsQueueTimes[:0]
|
||||
s.wsQueueHead = 0
|
||||
} else if s.wsQueueHead >= 128 {
|
||||
copy(s.wsQueueTimes, s.wsQueueTimes[s.wsQueueHead:])
|
||||
s.wsQueueTimes = s.wsQueueTimes[:len(s.wsQueueTimes)-s.wsQueueHead]
|
||||
s.wsQueueHead = 0
|
||||
}
|
||||
s.wsQueueMu.Unlock()
|
||||
immetrics.ObserveWSSendQueueDequeue(queuedAt, purged)
|
||||
}
|
||||
|
||||
func (s *Session) addSub(topic string, sub *Subscription) {
|
||||
if s.multi != nil {
|
||||
s.multi.addSub(topic, sub)
|
||||
@@ -333,9 +383,7 @@ func (s *Session) queueOutBatch(msgs []*ServerComMessage) bool {
|
||||
}
|
||||
|
||||
if s.supportsMessageBatching() {
|
||||
select {
|
||||
case s.send <- msgs:
|
||||
default:
|
||||
if !s.enqueueOutbound(msgs) {
|
||||
// Never block here since it may also block the topic's run() goroutine.
|
||||
logs.Err.Println("s.queueOut: session's send queue2 full", s.sid)
|
||||
return false
|
||||
@@ -394,9 +442,7 @@ func (s *Session) queueOut(msg *ServerComMessage) bool {
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case s.send <- msg:
|
||||
default:
|
||||
if !s.enqueueOutbound(msg) {
|
||||
// Never block here since it may also block the topic's run() goroutine.
|
||||
logs.Err.Println("s.queueOut: session's send queue full", s.sid)
|
||||
return false
|
||||
@@ -414,9 +460,7 @@ func (s *Session) queueOutBytes(data []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
select {
|
||||
case s.send <- data:
|
||||
default:
|
||||
if !s.enqueueOutbound(data) {
|
||||
logs.Err.Println("s.queueOutBytes: session's send queue full", s.sid)
|
||||
return false
|
||||
}
|
||||
@@ -451,6 +495,7 @@ func (s *Session) stopSession(data any) {
|
||||
func (s *Session) purgeChannels() {
|
||||
for len(s.send) > 0 {
|
||||
<-s.send
|
||||
s.observeOutboundDequeue(true)
|
||||
}
|
||||
for len(s.stop) > 0 {
|
||||
<-s.stop
|
||||
|
||||
Reference in New Issue
Block a user