Files
livekit/pkg/utils/stats/stats.go
Mathew Kamkar 84ab0f82af Prometheus counters for RTC connection steps (#143)
* signal ws connection, participant join, ice connection

* must register

* offer negotiation

* dz review: offer and offer_response

* dz review: answer
2021-10-12 15:22:17 -07:00

130 lines
3.8 KiB
Go

package stats
import (
"io"
"time"
livekit "github.com/livekit/protocol/proto"
"github.com/pion/interceptor"
"github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/pion/transport/packetio"
"github.com/prometheus/client_golang/prometheus"
)
const livekitNamespace = "livekit"
var (
PromMessageCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: livekitNamespace,
Subsystem: "node",
Name: "messages",
},
[]string{"type", "status"},
)
PromServiceOperationCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: livekitNamespace,
Subsystem: "node",
Name: "service_operation",
},
[]string{"type", "status", "error_type"},
)
)
func init() {
prometheus.MustRegister(PromMessageCounter)
prometheus.MustRegister(PromServiceOperationCounter)
initPacketStats()
initRoomStatsReporter()
}
// StatsBufferWrapper wraps a buffer factory so we could get information on
// incoming packets
type StatsBufferWrapper struct {
CreateBufferFunc func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser
Stats *PacketStats
}
func (w *StatsBufferWrapper) CreateBuffer(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser {
writer := w.CreateBufferFunc(packetType, ssrc)
if packetType == packetio.RTPBufferPacket {
// wrap this in a counter class
return &rtpReporterWriter{
ReadWriteCloser: writer,
stats: w.Stats,
}
}
return writer
}
type rtpReporterWriter struct {
io.ReadWriteCloser
stats *PacketStats
}
func (w *rtpReporterWriter) Write(p []byte) (n int, err error) {
w.stats.IncrementPackets(1)
w.stats.IncrementBytes(uint64(len(p)))
return w.ReadWriteCloser.Write(p)
}
// StatsInterceptorFactory is created for each participant to keep of track of outgoing stats
// it adheres to Pion interceptor.Factory interface
type StatsInterceptorFactory struct {
reporter *RoomStatsReporter
}
func NewStatsInterceptorFactory(reporter *RoomStatsReporter) *StatsInterceptorFactory {
return &StatsInterceptorFactory{
reporter: reporter,
}
}
func (f *StatsInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
return &StatsInterceptor{
reporter: f.reporter,
}, nil
}
// StatsInterceptor is created for each participant to keep of track of outgoing stats
// it adheres to Pion interceptor interface
type StatsInterceptor struct {
interceptor.NoOp
reporter *RoomStatsReporter
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (s *StatsInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
return interceptor.RTCPWriterFunc(func(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
s.reporter.Outgoing.HandleRTCP(pkts)
return writer.Write(pkts, attributes)
})
}
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
// will be called once per rtp packet.
func (s *StatsInterceptor) BindLocalStream(_ *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
s.reporter.Outgoing.IncrementPackets(1)
s.reporter.Outgoing.IncrementBytes(uint64(len(payload)))
return writer.Write(header, payload, attributes)
})
}
func UpdateCurrentNodeStats(nodeStats *livekit.NodeStats) error {
updatedAtPrevious := nodeStats.UpdatedAt
nodeStats.UpdatedAt = time.Now().Unix()
secondsSinceLastUpdate := nodeStats.UpdatedAt - updatedAtPrevious
err := updateCurrentNodeSystemStats(nodeStats)
updateCurrentNodeRoomStats(nodeStats)
updateCurrentNodePacketStats(nodeStats, secondsSinceLastUpdate)
return err
}