Add more metrics on rooms / sessions / backends.

This commit is contained in:
Joachim Bauch 2021-05-11 14:39:02 +02:00
parent 5d431e5612
commit ce1b3fc6e2
No known key found for this signature in database
GPG Key ID: 77C1D22D53E15F02
10 changed files with 316 additions and 2 deletions

View File

@ -90,6 +90,7 @@ func (b *Backend) AddSession(session Session) error {
if b.sessions == nil { if b.sessions == nil {
b.sessions = make(map[string]bool) b.sessions = make(map[string]bool)
} else if uint64(len(b.sessions)) >= b.sessionLimit { } else if uint64(len(b.sessions)) >= b.sessionLimit {
statsBackendLimitExceededTotal.WithLabelValues(b.id).Inc()
return SessionLimitExceeded return SessionLimitExceeded
} }
@ -123,6 +124,7 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration,
} }
backends := make(map[string][]*Backend) backends := make(map[string][]*Backend)
var compatBackend *Backend var compatBackend *Backend
numBackends := 0
if allowAll { if allowAll {
log.Println("WARNING: All backend hostnames are allowed, only use for development!") log.Println("WARNING: All backend hostnames are allowed, only use for development!")
compatBackend = &Backend{ compatBackend = &Backend{
@ -137,12 +139,14 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration,
if sessionLimit > 0 { if sessionLimit > 0 {
log.Printf("Allow a maximum of %d sessions", sessionLimit) log.Printf("Allow a maximum of %d sessions", sessionLimit)
} }
numBackends += 1
} else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { } else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
for host, configuredBackends := range getConfiguredHosts(backendIds, config) { for host, configuredBackends := range getConfiguredHosts(backendIds, config) {
backends[host] = append(backends[host], configuredBackends...) backends[host] = append(backends[host], configuredBackends...)
for _, be := range configuredBackends { for _, be := range configuredBackends {
log.Printf("Backend %s added for %s", be.id, be.url) log.Printf("Backend %s added for %s", be.id, be.url)
} }
numBackends += len(configuredBackends)
} }
} else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" {
// Old-style configuration, only hosts are configured and are using a common secret. // Old-style configuration, only hosts are configured and are using a common secret.
@ -182,9 +186,14 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration,
if sessionLimit > 0 { if sessionLimit > 0 {
log.Printf("Allow a maximum of %d sessions", sessionLimit) log.Printf("Allow a maximum of %d sessions", sessionLimit)
} }
numBackends += 1
} }
} }
RegisterBackendConfigurationStats()
log.Printf("Initial: %d", numBackends)
statsBackendsCurrent.Add(float64(numBackends))
return &BackendConfiguration{ return &BackendConfiguration{
backends: backends, backends: backends,
@ -199,6 +208,7 @@ func (b *BackendConfiguration) RemoveBackendsForHost(host string) {
for _, backend := range oldBackends { for _, backend := range oldBackends {
log.Printf("Backend %s removed for %s", backend.id, backend.url) log.Printf("Backend %s removed for %s", backend.id, backend.url)
} }
statsBackendsCurrent.Sub(float64(len(oldBackends)))
} }
delete(b.backends, host) delete(b.backends, host)
} }
@ -225,6 +235,7 @@ func (b *BackendConfiguration) UpsertHost(host string, backends []*Backend) {
removed := b.backends[host][existingIndex] removed := b.backends[host][existingIndex]
log.Printf("Backend %s removed for %s", removed.id, removed.url) log.Printf("Backend %s removed for %s", removed.id, removed.url)
b.backends[host] = append(b.backends[host][:existingIndex], b.backends[host][existingIndex+1:]...) b.backends[host] = append(b.backends[host][:existingIndex], b.backends[host][existingIndex+1:]...)
statsBackendsCurrent.Dec()
} }
} }
@ -232,6 +243,7 @@ func (b *BackendConfiguration) UpsertHost(host string, backends []*Backend) {
for _, added := range backends { for _, added := range backends {
log.Printf("Backend %s added for %s", added.id, added.url) log.Printf("Backend %s added for %s", added.id, added.url)
} }
statsBackendsCurrent.Add(float64(len(backends)))
} }
func getConfiguredBackendIDs(backendIds string) (ids []string) { func getConfiguredBackendIDs(backendIds string) (ids []string) {

View File

@ -0,0 +1,50 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
statsBackendLimitExceededTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "signaling",
Subsystem: "backend",
Name: "session_limit_exceeded_total",
Help: "The number of times the session limit exceeded",
}, []string{"backend"})
statsBackendsCurrent = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "signaling",
Subsystem: "backend",
Name: "current",
Help: "The current number of configured backends",
})
backendConfigurationStats = []prometheus.Collector{
statsBackendLimitExceededTotal,
statsBackendsCurrent,
}
)
func RegisterBackendConfigurationStats() {
registerAll(backendConfigurationStats...)
}

View File

@ -28,6 +28,7 @@ import (
"testing" "testing"
"github.com/dlintw/goconf" "github.com/dlintw/goconf"
"github.com/prometheus/client_golang/prometheus/testutil"
) )
func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) { func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) {
@ -238,6 +239,7 @@ func TestParseBackendIds(t *testing.T) {
} }
func TestBackendReloadNoChange(t *testing.T) { func TestBackendReloadNoChange(t *testing.T) {
current := testutil.ToFloat64(statsBackendsCurrent)
original_config := goconf.NewConfigFile() original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend", "allowall", "false")
@ -249,6 +251,7 @@ func TestBackendReloadNoChange(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+2)
new_config := goconf.NewConfigFile() new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "backends", "backend1, backend2")
@ -262,13 +265,16 @@ func TestBackendReloadNoChange(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+4)
o_cfg.Reload(original_config) o_cfg.Reload(original_config)
checkStatsValue(t, statsBackendsCurrent, current+4)
if !reflect.DeepEqual(n_cfg, o_cfg) { if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload") t.Error("BackendConfiguration should be equal after Reload")
} }
} }
func TestBackendReloadChangeExistingURL(t *testing.T) { func TestBackendReloadChangeExistingURL(t *testing.T) {
current := testutil.ToFloat64(statsBackendsCurrent)
original_config := goconf.NewConfigFile() original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend", "allowall", "false")
@ -281,6 +287,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+2)
new_config := goconf.NewConfigFile() new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "backends", "backend1, backend2")
new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend", "allowall", "false")
@ -294,17 +301,20 @@ func TestBackendReloadChangeExistingURL(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+4)
original_config.RemoveOption("backend1", "url") original_config.RemoveOption("backend1", "url")
original_config.AddOption("backend1", "url", "http://domain3.invalid") original_config.AddOption("backend1", "url", "http://domain3.invalid")
original_config.AddOption("backend1", "sessionlimit", "10") original_config.AddOption("backend1", "sessionlimit", "10")
o_cfg.Reload(original_config) o_cfg.Reload(original_config)
checkStatsValue(t, statsBackendsCurrent, current+4)
if !reflect.DeepEqual(n_cfg, o_cfg) { if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload") t.Error("BackendConfiguration should be equal after Reload")
} }
} }
func TestBackendReloadChangeSecret(t *testing.T) { func TestBackendReloadChangeSecret(t *testing.T) {
current := testutil.ToFloat64(statsBackendsCurrent)
original_config := goconf.NewConfigFile() original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend", "allowall", "false")
@ -317,6 +327,7 @@ func TestBackendReloadChangeSecret(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+2)
new_config := goconf.NewConfigFile() new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "backends", "backend1, backend2")
new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend", "allowall", "false")
@ -329,16 +340,19 @@ func TestBackendReloadChangeSecret(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+4)
original_config.RemoveOption("backend1", "secret") original_config.RemoveOption("backend1", "secret")
original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3") original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3")
o_cfg.Reload(original_config) o_cfg.Reload(original_config)
checkStatsValue(t, statsBackendsCurrent, current+4)
if !reflect.DeepEqual(n_cfg, o_cfg) { if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload") t.Error("BackendConfiguration should be equal after Reload")
} }
} }
func TestBackendReloadAddBackend(t *testing.T) { func TestBackendReloadAddBackend(t *testing.T) {
current := testutil.ToFloat64(statsBackendsCurrent)
original_config := goconf.NewConfigFile() original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "backends", "backend1")
original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend", "allowall", "false")
@ -349,6 +363,7 @@ func TestBackendReloadAddBackend(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+1)
new_config := goconf.NewConfigFile() new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "backends", "backend1, backend2")
new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend", "allowall", "false")
@ -362,6 +377,7 @@ func TestBackendReloadAddBackend(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+3)
original_config.RemoveOption("backend", "backends") original_config.RemoveOption("backend", "backends")
original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend2", "url", "http://domain2.invalid") original_config.AddOption("backend2", "url", "http://domain2.invalid")
@ -369,12 +385,14 @@ func TestBackendReloadAddBackend(t *testing.T) {
original_config.AddOption("backend2", "sessionlimit", "10") original_config.AddOption("backend2", "sessionlimit", "10")
o_cfg.Reload(original_config) o_cfg.Reload(original_config)
checkStatsValue(t, statsBackendsCurrent, current+4)
if !reflect.DeepEqual(n_cfg, o_cfg) { if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload") t.Error("BackendConfiguration should be equal after Reload")
} }
} }
func TestBackendReloadRemoveHost(t *testing.T) { func TestBackendReloadRemoveHost(t *testing.T) {
current := testutil.ToFloat64(statsBackendsCurrent)
original_config := goconf.NewConfigFile() original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend", "allowall", "false")
@ -387,6 +405,7 @@ func TestBackendReloadRemoveHost(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+2)
new_config := goconf.NewConfigFile() new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "backends", "backend1")
new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend", "allowall", "false")
@ -397,17 +416,20 @@ func TestBackendReloadRemoveHost(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+3)
original_config.RemoveOption("backend", "backends") original_config.RemoveOption("backend", "backends")
original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "backends", "backend1")
original_config.RemoveSection("backend2") original_config.RemoveSection("backend2")
o_cfg.Reload(original_config) o_cfg.Reload(original_config)
checkStatsValue(t, statsBackendsCurrent, current+2)
if !reflect.DeepEqual(n_cfg, o_cfg) { if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload") t.Error("BackendConfiguration should be equal after Reload")
} }
} }
func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) {
current := testutil.ToFloat64(statsBackendsCurrent)
original_config := goconf.NewConfigFile() original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false") original_config.AddOption("backend", "allowall", "false")
@ -420,6 +442,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+2)
new_config := goconf.NewConfigFile() new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "backends", "backend1")
new_config.AddOption("backend", "allowall", "false") new_config.AddOption("backend", "allowall", "false")
@ -430,11 +453,13 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkStatsValue(t, statsBackendsCurrent, current+3)
original_config.RemoveOption("backend", "backends") original_config.RemoveOption("backend", "backends")
original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "backends", "backend1")
original_config.RemoveSection("backend2") original_config.RemoveSection("backend2")
o_cfg.Reload(original_config) o_cfg.Reload(original_config)
checkStatsValue(t, statsBackendsCurrent, current+2)
if !reflect.DeepEqual(n_cfg, o_cfg) { if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload") t.Error("BackendConfiguration should be equal after Reload")
} }

View File

@ -58,6 +58,10 @@ var (
unknownCountry string = "unknown-country" unknownCountry string = "unknown-country"
) )
func init() {
RegisterClientStats()
}
func IsValidCountry(country string) bool { func IsValidCountry(country string) bool {
switch country { switch country {
case "": case "":

View File

@ -0,0 +1,43 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
statsClientCountries = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "signaling",
Subsystem: "client",
Name: "countries_total",
Help: "The total number of connections by country",
}, []string{"country"})
clientStats = []prometheus.Collector{
statsClientCountries,
}
)
func RegisterClientStats() {
registerAll(clientStats...)
}

24
hub.go
View File

@ -95,6 +95,10 @@ const (
publicSessionName = "public-session" publicSessionName = "public-session"
) )
func init() {
RegisterHubStats()
}
type Hub struct { type Hub struct {
// 64-bit members that are accessed atomically must be 64-bit aligned. // 64-bit members that are accessed atomically must be 64-bit aligned.
sid uint64 sid uint64
@ -607,6 +611,7 @@ func (h *Hub) removeSession(session Session) (removed bool) {
delete(h.clients, data.Sid) delete(h.clients, data.Sid)
if _, found := h.sessions[data.Sid]; found { if _, found := h.sessions[data.Sid]; found {
delete(h.sessions, data.Sid) delete(h.sessions, data.Sid)
statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), session.ClientType()).Dec()
removed = true removed = true
} }
} }
@ -742,6 +747,12 @@ func (h *Hub) processRegister(client *Client, message *ClientMessage, backend *B
} }
h.mu.Unlock() h.mu.Unlock()
if country := client.Country(); IsValidCountry(country) {
statsClientCountries.WithLabelValues(country).Inc()
}
statsHubSessionsCurrent.WithLabelValues(backend.Id(), session.ClientType()).Inc()
statsHubSessionsTotal.WithLabelValues(backend.Id(), session.ClientType()).Inc()
h.setDecodedSessionId(privateSessionId, privateSessionName, sessionIdData) h.setDecodedSessionId(privateSessionId, privateSessionName, sessionIdData)
h.setDecodedSessionId(publicSessionId, publicSessionName, sessionIdData) h.setDecodedSessionId(publicSessionId, publicSessionName, sessionIdData)
h.sendHelloResponse(session, message) h.sendHelloResponse(session, message)
@ -840,6 +851,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) {
if resumeId != "" { if resumeId != "" {
data := h.decodeSessionId(resumeId, privateSessionName) data := h.decodeSessionId(resumeId, privateSessionName)
if data == nil { if data == nil {
statsHubSessionResumeFailed.Inc()
client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) client.SendMessage(message.NewErrorServerMessage(NoSuchSession))
return return
} }
@ -848,6 +860,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) {
session, found := h.sessions[data.Sid] session, found := h.sessions[data.Sid]
if !found || resumeId != session.PrivateId() { if !found || resumeId != session.PrivateId() {
h.mu.Unlock() h.mu.Unlock()
statsHubSessionResumeFailed.Inc()
client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) client.SendMessage(message.NewErrorServerMessage(NoSuchSession))
return return
} }
@ -857,6 +870,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) {
// Should never happen as clients only can resume their own sessions. // Should never happen as clients only can resume their own sessions.
h.mu.Unlock() h.mu.Unlock()
log.Printf("Client resumed non-client session %s (private=%s)", session.PublicId(), session.PrivateId()) log.Printf("Client resumed non-client session %s (private=%s)", session.PublicId(), session.PrivateId())
statsHubSessionResumeFailed.Inc()
client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) client.SendMessage(message.NewErrorServerMessage(NoSuchSession))
return return
} }
@ -879,6 +893,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) {
log.Printf("Resume session from %s in %s (%s) %s (private=%s)", client.RemoteAddr(), client.Country(), client.UserAgent(), session.PublicId(), session.PrivateId()) log.Printf("Resume session from %s in %s (%s) %s (private=%s)", client.RemoteAddr(), client.Country(), client.UserAgent(), session.PublicId(), session.PrivateId())
statsHubSessionsResumedTotal.WithLabelValues(clientSession.Backend().Id(), clientSession.ClientType()).Inc()
h.sendHelloResponse(clientSession, message) h.sendHelloResponse(clientSession, message)
clientSession.NotifySessionResumed(client) clientSession.NotifySessionResumed(client)
return return
@ -1087,7 +1102,10 @@ func (h *Hub) getRoomForBackend(id string, backend *Backend) *Room {
func (h *Hub) removeRoom(room *Room) { func (h *Hub) removeRoom(room *Room) {
internalRoomId := getRoomIdForBackend(room.Id(), room.Backend()) internalRoomId := getRoomIdForBackend(room.Id(), room.Backend())
h.ru.Lock() h.ru.Lock()
delete(h.rooms, internalRoomId) if _, found := h.rooms[internalRoomId]; found {
delete(h.rooms, internalRoomId)
statsHubRoomsCurrent.WithLabelValues(room.Backend().Id()).Dec()
}
h.ru.Unlock() h.ru.Unlock()
} }
@ -1100,6 +1118,7 @@ func (h *Hub) createRoom(id string, properties *json.RawMessage, backend *Backen
internalRoomId := getRoomIdForBackend(id, backend) internalRoomId := getRoomIdForBackend(id, backend)
h.rooms[internalRoomId] = room h.rooms[internalRoomId] = room
statsHubRoomsCurrent.WithLabelValues(backend.Id()).Inc()
return room, nil return room, nil
} }
@ -1550,6 +1569,8 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) {
h.sessions[sessionIdData.Sid] = sess h.sessions[sessionIdData.Sid] = sess
h.virtualSessions[virtualSessionId] = sessionIdData.Sid h.virtualSessions[virtualSessionId] = sessionIdData.Sid
h.mu.Unlock() h.mu.Unlock()
statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), sess.ClientType()).Inc()
statsHubSessionsTotal.WithLabelValues(session.Backend().Id(), sess.ClientType()).Inc()
log.Printf("Session %s added virtual session %s with initial flags %d", session.PublicId(), sess.PublicId(), sess.Flags()) log.Printf("Session %s added virtual session %s with initial flags %d", session.PublicId(), sess.PublicId(), sess.Flags())
session.AddVirtualSession(sess) session.AddVirtualSession(sess)
sess.SetRoom(room) sess.SetRoom(room)
@ -1608,6 +1629,7 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) {
h.mu.Unlock() h.mu.Unlock()
if sess != nil { if sess != nil {
log.Printf("Session %s removed virtual session %s", session.PublicId(), sess.PublicId()) log.Printf("Session %s removed virtual session %s", session.PublicId(), sess.PublicId())
statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), sess.ClientType()).Dec()
if vsess, ok := sess.(*VirtualSession); ok { if vsess, ok := sess.(*VirtualSession); ok {
// We should always have a VirtualSession here. // We should always have a VirtualSession here.
vsess.CloseWithFeedback(session, message) vsess.CloseWithFeedback(session, message)

70
hub_stats_prometheus.go Normal file
View File

@ -0,0 +1,70 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
statsHubRoomsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "signaling",
Subsystem: "hub",
Name: "rooms",
Help: "The current number of rooms per backend",
}, []string{"backend"})
statsHubSessionsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "signaling",
Subsystem: "hub",
Name: "sessions",
Help: "The current number of sessions per backend",
}, []string{"backend", "clienttype"})
statsHubSessionsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "signaling",
Subsystem: "hub",
Name: "sessions_total",
Help: "The total number of sessions per backend",
}, []string{"backend", "clienttype"})
statsHubSessionsResumedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "signaling",
Subsystem: "hub",
Name: "sessions_resume_total",
Help: "The total number of resumed sessions per backend",
}, []string{"backend", "clienttype"})
statsHubSessionResumeFailed = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "signaling",
Subsystem: "hub",
Name: "sessions_resume_failed_total",
Help: "The total number of failed session resume requests",
})
hubStats = []prometheus.Collector{
statsHubRoomsCurrent,
statsHubSessionsCurrent,
statsHubSessionsTotal,
statsHubSessionResumeFailed,
}
)
func RegisterHubStats() {
registerAll(hubStats...)
}

22
room.go
View File

@ -32,6 +32,7 @@ import (
"time" "time"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"github.com/prometheus/client_golang/prometheus"
) )
const ( const (
@ -47,6 +48,10 @@ var (
updateActiveSessionsInterval = 10 * time.Second updateActiveSessionsInterval = 10 * time.Second
) )
func init() {
RegisterRoomStats()
}
type Room struct { type Room struct {
id string id string
hub *Hub hub *Hub
@ -64,6 +69,8 @@ type Room struct {
inCallSessions map[Session]bool inCallSessions map[Session]bool
roomSessionData map[string]*RoomSessionData roomSessionData map[string]*RoomSessionData
statsRoomSessionsCurrent *prometheus.GaugeVec
natsReceiver chan *nats.Msg natsReceiver chan *nats.Msg
backendSubscription NatsSubscription backendSubscription NatsSubscription
@ -123,6 +130,11 @@ func NewRoom(roomId string, properties *json.RawMessage, hub *Hub, n NatsClient,
inCallSessions: make(map[Session]bool), inCallSessions: make(map[Session]bool),
roomSessionData: make(map[string]*RoomSessionData), roomSessionData: make(map[string]*RoomSessionData),
statsRoomSessionsCurrent: statsRoomSessionsCurrent.MustCurryWith(prometheus.Labels{
"backend": backend.Id(),
"room": roomId,
}),
natsReceiver: natsReceiver, natsReceiver: natsReceiver,
backendSubscription: backendSubscription, backendSubscription: backendSubscription,
@ -194,6 +206,9 @@ func (r *Room) Close() []Session {
result = append(result, s) result = append(result, s)
} }
r.sessions = nil r.sessions = nil
r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeClient})
r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeInternal})
r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeVirtual})
r.mu.Unlock() r.mu.Unlock()
return result return result
} }
@ -264,6 +279,9 @@ func (r *Room) AddSession(session Session, sessionData *json.RawMessage) []Sessi
} }
} }
r.sessions[sid] = session r.sessions[sid] = session
if !found {
r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": session.ClientType()}).Inc()
}
var publishUsersChanged bool var publishUsersChanged bool
switch session.ClientType() { switch session.ClientType() {
case HelloClientTypeInternal: case HelloClientTypeInternal:
@ -311,6 +329,7 @@ func (r *Room) RemoveSession(session Session) bool {
} }
sid := session.PublicId() sid := session.PublicId()
r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": session.ClientType()}).Dec()
delete(r.sessions, sid) delete(r.sessions, sid)
delete(r.internalSessions, session) delete(r.internalSessions, session)
if virtualSession, ok := session.(*VirtualSession); ok { if virtualSession, ok := session.(*VirtualSession); ok {
@ -325,6 +344,9 @@ func (r *Room) RemoveSession(session Session) bool {
} }
r.hub.removeRoom(r) r.hub.removeRoom(r)
r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeClient})
r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeInternal})
r.statsRoomSessionsCurrent.Delete(prometheus.Labels{"clienttype": HelloClientTypeVirtual})
r.unsubscribeBackend() r.unsubscribeBackend()
r.doClose() r.doClose()
r.mu.Unlock() r.mu.Unlock()

43
room_stats_prometheus.go Normal file
View File

@ -0,0 +1,43 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2021 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
statsRoomSessionsCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "signaling",
Subsystem: "room",
Name: "sessions",
Help: "The current number of sessions in a room",
}, []string{"backend", "room", "clienttype"})
roomStats = []prometheus.Collector{
statsRoomSessionsCurrent,
}
)
func RegisterRoomStats() {
registerAll(roomStats...)
}

View File

@ -22,6 +22,9 @@
package signaling package signaling
import ( import (
"fmt"
"runtime"
"strings"
"testing" "testing"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -34,7 +37,27 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64
desc := <-ch desc := <-ch
v := testutil.ToFloat64(collector) v := testutil.ToFloat64(collector)
if v != value { if v != value {
t.Errorf("Expected value %f for %s, got %f", value, desc, v) pc := make([]uintptr, 10)
n := runtime.Callers(2, pc)
if n == 0 {
t.Errorf("Expected value %f for %s, got %f", value, desc, v)
return
}
pc = pc[:n]
frames := runtime.CallersFrames(pc)
stack := ""
for {
frame, more := frames.Next()
if !strings.Contains(frame.File, "nextcloud-spreed-signaling") {
break
}
stack += fmt.Sprintf("%s:%d\n", frame.File, frame.Line)
if !more {
break
}
}
t.Errorf("Expected value %f for %s, got %f at\n%s", value, desc, v, stack)
} }
} }