diff --git a/go.mod b/go.mod index c985c88a4..29a323a21 100644 --- a/go.mod +++ b/go.mod @@ -43,4 +43,4 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) -replace github.com/pion/ion-sfu => github.com/livekit/ion-sfu v1.20.0 +replace github.com/pion/ion-sfu => github.com/livekit/ion-sfu v1.20.2 diff --git a/go.sum b/go.sum index 5f73b6f32..d9535f712 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/shortuuid/v3 v3.0.6 h1:pr15YQyvhiSX/qPxncFtqk+v4xLEpOZObbsY/mKrcvA= github.com/lithammer/shortuuid/v3 v3.0.6/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/livekit/ion-sfu v1.20.0 h1:mLNunMgRF6+Bz+5/JrVgxQKE6umuN3vkkQu8IcVquYA= -github.com/livekit/ion-sfu v1.20.0/go.mod h1:Wx6b4qGUjvSo1kGl+/fHl0ZF48g2IJOjzUFg0yCo9qY= +github.com/livekit/ion-sfu v1.20.2 h1:fIvKl2biQUbIdVPgWgGaMXo8e3s9E9ActCvD5lSlVJo= +github.com/livekit/ion-sfu v1.20.2/go.mod h1:Wx6b4qGUjvSo1kGl+/fHl0ZF48g2IJOjzUFg0yCo9qY= github.com/livekit/protocol v0.5.6 h1:vMUjYvJH2TD/WIjrXtG6aQl5fFrIj0/ZgIctQqmmwro= github.com/livekit/protocol v0.5.6/go.mod h1:wo3CGfYB7XMF8GoVJAfTARrYSP/ombi+sbLl6AYdKP0= github.com/lucsky/cuid v1.0.2 h1:z4XlExeoderxoPj2/dxKOyPxe9RCOu7yNq9/XWxIUMQ= diff --git a/pkg/rtc/mediatrack.go b/pkg/rtc/mediatrack.go index 77e8de577..ca425f19e 100644 --- a/pkg/rtc/mediatrack.go +++ b/pkg/rtc/mediatrack.go @@ -373,3 +373,32 @@ func (t *MediaTrack) sendDownTrackBindingReports(sub types.Participant) { } }() } + +func (t *MediaTrack) DebugInfo() map[string]interface{} { + info := map[string]interface{}{ + "ID": t.ID(), + "SSRC": t.ssrc, + "Kind": t.kind.String(), + "PubMuted": t.muted.Get(), + } + + subscribedTrackInfo := make([]map[string]interface{}, 0) + t.lock.RLock() + for _, track := range t.subscribedTracks { + dt := track.dt.DebugInfo() + dt["PubMuted"] = track.pubMuted.Get() + dt["SubMuted"] = track.subMuted.Get() + subscribedTrackInfo = append(subscribedTrackInfo, dt) + } + t.lock.RUnlock() + info["DownTracks"] = subscribedTrackInfo + + if t.receiver != nil { + receiverInfo := t.receiver.DebugInfo() + for k, v := range receiverInfo { + info[k] = v + } + } + + return info +} diff --git a/pkg/rtc/participant.go b/pkg/rtc/participant.go index 53ef81aae..649c4c8a2 100644 --- a/pkg/rtc/participant.go +++ b/pkg/rtc/participant.go @@ -964,3 +964,52 @@ func (p *ParticipantImpl) rtcpSendWorker() { } } } + +func (p *ParticipantImpl) DebugInfo() map[string]interface{} { + info := map[string]interface{}{ + "ID": p.id, + "State": p.State().String(), + } + + publishedTrackInfo := make(map[string]interface{}) + subscribedTrackInfo := make(map[string]interface{}) + pendingTrackInfo := make(map[string]interface{}) + + p.lock.RLock() + for trackID, track := range p.publishedTracks { + if mt, ok := track.(*MediaTrack); ok { + publishedTrackInfo[trackID] = mt.DebugInfo() + } else { + publishedTrackInfo[trackID] = map[string]interface{}{ + "ID": track.ID(), + "Kind": track.Kind().String(), + "PubMuted": track.IsMuted(), + } + } + } + + for pubID, tracks := range p.subscribedTracks { + trackInfo := make([]map[string]interface{}, 0, len(tracks)) + for _, track := range tracks { + dt := track.DownTrack().DebugInfo() + dt["SubMuted"] = track.IsMuted() + trackInfo = append(trackInfo, dt) + } + subscribedTrackInfo[pubID] = trackInfo + } + + for clientID, track := range p.pendingTracks { + pendingTrackInfo[clientID] = map[string]interface{}{ + "Sid": track.Sid, + "Type": track.Type.String(), + "Simulcast": track.Simulcast, + } + } + p.lock.RUnlock() + + info["PublishedTracks"] = publishedTrackInfo + info["SubscribedTracks"] = subscribedTrackInfo + info["PendingTracks"] = pendingTrackInfo + + return info +} diff --git a/pkg/rtc/room.go b/pkg/rtc/room.go index fc50be2d8..b2b153c56 100644 --- a/pkg/rtc/room.go +++ b/pkg/rtc/room.go @@ -580,3 +580,20 @@ func (r *Room) audioUpdateWorker() { time.Sleep(time.Duration(r.audioConfig.UpdateInterval) * time.Millisecond) } } + +func (r *Room) DebugInfo() map[string]interface{} { + info := map[string]interface{}{ + "Name": r.Room.Name, + "Sid": r.Room.Sid, + "CreatedAt": r.Room.CreationTime, + } + + participants := r.GetParticipants() + participantInfo := make(map[string]interface{}) + for _, p := range participants { + participantInfo[p.Identity()] = p.DebugInfo() + } + info["Participants"] = participantInfo + + return info +} diff --git a/pkg/rtc/types/interfaces.go b/pkg/rtc/types/interfaces.go index 954703a1f..698d1de94 100644 --- a/pkg/rtc/types/interfaces.go +++ b/pkg/rtc/types/interfaces.go @@ -78,6 +78,8 @@ type Participant interface { RemoveSubscribedTrack(participantId string, st SubscribedTrack) SubscriberPC() *webrtc.PeerConnection UpdateAfterActive() bool + + DebugInfo() map[string]interface{} } // PublishedTrack is the main interface representing a track published to the room diff --git a/pkg/rtc/types/typesfakes/fake_participant.go b/pkg/rtc/types/typesfakes/fake_participant.go index f56fd8614..6b64d29c0 100644 --- a/pkg/rtc/types/typesfakes/fake_participant.go +++ b/pkg/rtc/types/typesfakes/fake_participant.go @@ -89,6 +89,16 @@ type FakeParticipant struct { connectedAtReturnsOnCall map[int]struct { result1 time.Time } + DebugInfoStub func() map[string]interface{} + debugInfoMutex sync.RWMutex + debugInfoArgsForCall []struct { + } + debugInfoReturns struct { + result1 map[string]interface{} + } + debugInfoReturnsOnCall map[int]struct { + result1 map[string]interface{} + } GetAudioLevelStub func() (uint8, bool) getAudioLevelMutex sync.RWMutex getAudioLevelArgsForCall []struct { @@ -788,6 +798,59 @@ func (fake *FakeParticipant) ConnectedAtReturnsOnCall(i int, result1 time.Time) }{result1} } +func (fake *FakeParticipant) DebugInfo() map[string]interface{} { + fake.debugInfoMutex.Lock() + ret, specificReturn := fake.debugInfoReturnsOnCall[len(fake.debugInfoArgsForCall)] + fake.debugInfoArgsForCall = append(fake.debugInfoArgsForCall, struct { + }{}) + stub := fake.DebugInfoStub + fakeReturns := fake.debugInfoReturns + fake.recordInvocation("DebugInfo", []interface{}{}) + fake.debugInfoMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeParticipant) DebugInfoCallCount() int { + fake.debugInfoMutex.RLock() + defer fake.debugInfoMutex.RUnlock() + return len(fake.debugInfoArgsForCall) +} + +func (fake *FakeParticipant) DebugInfoCalls(stub func() map[string]interface{}) { + fake.debugInfoMutex.Lock() + defer fake.debugInfoMutex.Unlock() + fake.DebugInfoStub = stub +} + +func (fake *FakeParticipant) DebugInfoReturns(result1 map[string]interface{}) { + fake.debugInfoMutex.Lock() + defer fake.debugInfoMutex.Unlock() + fake.DebugInfoStub = nil + fake.debugInfoReturns = struct { + result1 map[string]interface{} + }{result1} +} + +func (fake *FakeParticipant) DebugInfoReturnsOnCall(i int, result1 map[string]interface{}) { + fake.debugInfoMutex.Lock() + defer fake.debugInfoMutex.Unlock() + fake.DebugInfoStub = nil + if fake.debugInfoReturnsOnCall == nil { + fake.debugInfoReturnsOnCall = make(map[int]struct { + result1 map[string]interface{} + }) + } + fake.debugInfoReturnsOnCall[i] = struct { + result1 map[string]interface{} + }{result1} +} + func (fake *FakeParticipant) GetAudioLevel() (uint8, bool) { fake.getAudioLevelMutex.Lock() ret, specificReturn := fake.getAudioLevelReturnsOnCall[len(fake.getAudioLevelArgsForCall)] @@ -2430,6 +2493,8 @@ func (fake *FakeParticipant) Invocations() map[string][][]interface{} { defer fake.closeMutex.RUnlock() fake.connectedAtMutex.RLock() defer fake.connectedAtMutex.RUnlock() + fake.debugInfoMutex.RLock() + defer fake.debugInfoMutex.RUnlock() fake.getAudioLevelMutex.RLock() defer fake.getAudioLevelMutex.RUnlock() fake.getPublishedTracksMutex.RLock() diff --git a/pkg/service/server.go b/pkg/service/server.go index a6d56b817..660eb9012 100644 --- a/pkg/service/server.go +++ b/pkg/service/server.go @@ -2,6 +2,7 @@ package service import ( "context" + "encoding/json" "errors" "fmt" "net" @@ -73,6 +74,7 @@ func NewLivekitServer(conf *config.Config, mux.HandleFunc("/", s.healthCheck) if conf.Development { mux.HandleFunc("/debug/goroutine", s.debugGoroutines) + mux.HandleFunc("/debug/rooms", s.debugInfo) } s.httpServer = &http.Server{ @@ -222,6 +224,23 @@ func (s *LivekitServer) debugGoroutines(w http.ResponseWriter, r *http.Request) _ = pprof.Lookup("goroutine").WriteTo(w, 2) } +func (s *LivekitServer) debugInfo(w http.ResponseWriter, r *http.Request) { + s.roomManager.lock.RLock() + info := make([]map[string]interface{}, 0, len(s.roomManager.rooms)) + for _, room := range s.roomManager.rooms { + info = append(info, room.DebugInfo()) + } + s.roomManager.lock.RUnlock() + + b, err := json.Marshal(info) + if err != nil { + w.WriteHeader(400) + _, _ = w.Write([]byte(err.Error())) + } else { + _, _ = w.Write(b) + } +} + func (s *LivekitServer) healthCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }