From ce1b3fc6e2f8dda514b39597c179ca89befa37ce Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Tue, 11 May 2021 14:39:02 +0200 Subject: [PATCH] Add more metrics on rooms / sessions / backends. --- backend_configuration.go | 12 ++++ backend_configuration_stats_prometheus.go | 50 ++++++++++++++++ backend_configuration_test.go | 25 ++++++++ client.go | 4 ++ client_stats_prometheus.go | 43 ++++++++++++++ hub.go | 24 +++++++- hub_stats_prometheus.go | 70 +++++++++++++++++++++++ room.go | 22 +++++++ room_stats_prometheus.go | 43 ++++++++++++++ stats_prometheus_test.go | 25 +++++++- 10 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 backend_configuration_stats_prometheus.go create mode 100644 client_stats_prometheus.go create mode 100644 hub_stats_prometheus.go create mode 100644 room_stats_prometheus.go diff --git a/backend_configuration.go b/backend_configuration.go index 7f21f01..85d4537 100644 --- a/backend_configuration.go +++ b/backend_configuration.go @@ -90,6 +90,7 @@ func (b *Backend) AddSession(session Session) error { if b.sessions == nil { b.sessions = make(map[string]bool) } else if uint64(len(b.sessions)) >= b.sessionLimit { + statsBackendLimitExceededTotal.WithLabelValues(b.id).Inc() return SessionLimitExceeded } @@ -123,6 +124,7 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration, } backends := make(map[string][]*Backend) var compatBackend *Backend + numBackends := 0 if allowAll { log.Println("WARNING: All backend hostnames are allowed, only use for development!") compatBackend = &Backend{ @@ -137,12 +139,14 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration, if sessionLimit > 0 { log.Printf("Allow a maximum of %d sessions", sessionLimit) } + numBackends += 1 } else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" { for host, configuredBackends := range getConfiguredHosts(backendIds, config) { backends[host] = append(backends[host], configuredBackends...) for _, be := range configuredBackends { log.Printf("Backend %s added for %s", be.id, be.url) } + numBackends += len(configuredBackends) } } else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" { // 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 { log.Printf("Allow a maximum of %d sessions", sessionLimit) } + numBackends += 1 } } + RegisterBackendConfigurationStats() + log.Printf("Initial: %d", numBackends) + statsBackendsCurrent.Add(float64(numBackends)) + return &BackendConfiguration{ backends: backends, @@ -199,6 +208,7 @@ func (b *BackendConfiguration) RemoveBackendsForHost(host string) { for _, backend := range oldBackends { log.Printf("Backend %s removed for %s", backend.id, backend.url) } + statsBackendsCurrent.Sub(float64(len(oldBackends))) } delete(b.backends, host) } @@ -225,6 +235,7 @@ func (b *BackendConfiguration) UpsertHost(host string, backends []*Backend) { removed := b.backends[host][existingIndex] log.Printf("Backend %s removed for %s", removed.id, removed.url) 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 { log.Printf("Backend %s added for %s", added.id, added.url) } + statsBackendsCurrent.Add(float64(len(backends))) } func getConfiguredBackendIDs(backendIds string) (ids []string) { diff --git a/backend_configuration_stats_prometheus.go b/backend_configuration_stats_prometheus.go new file mode 100644 index 0000000..13b7c7d --- /dev/null +++ b/backend_configuration_stats_prometheus.go @@ -0,0 +1,50 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 struktur AG + * + * @author Joachim Bauch + * + * @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 . + */ +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...) +} diff --git a/backend_configuration_test.go b/backend_configuration_test.go index 94ad7a5..17e7508 100644 --- a/backend_configuration_test.go +++ b/backend_configuration_test.go @@ -28,6 +28,7 @@ import ( "testing" "github.com/dlintw/goconf" + "github.com/prometheus/client_golang/prometheus/testutil" ) 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) { + current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -249,6 +251,7 @@ func TestBackendReloadNoChange(t *testing.T) { if err != nil { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") @@ -262,13 +265,16 @@ func TestBackendReloadNoChange(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+4) o_cfg.Reload(original_config) + checkStatsValue(t, statsBackendsCurrent, current+4) if !reflect.DeepEqual(n_cfg, o_cfg) { t.Error("BackendConfiguration should be equal after Reload") } } func TestBackendReloadChangeExistingURL(t *testing.T) { + current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -281,6 +287,7 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -294,17 +301,20 @@ func TestBackendReloadChangeExistingURL(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+4) original_config.RemoveOption("backend1", "url") original_config.AddOption("backend1", "url", "http://domain3.invalid") original_config.AddOption("backend1", "sessionlimit", "10") o_cfg.Reload(original_config) + checkStatsValue(t, statsBackendsCurrent, current+4) if !reflect.DeepEqual(n_cfg, o_cfg) { t.Error("BackendConfiguration should be equal after Reload") } } func TestBackendReloadChangeSecret(t *testing.T) { + current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -317,6 +327,7 @@ func TestBackendReloadChangeSecret(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -329,16 +340,19 @@ func TestBackendReloadChangeSecret(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+4) original_config.RemoveOption("backend1", "secret") original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3") o_cfg.Reload(original_config) + checkStatsValue(t, statsBackendsCurrent, current+4) if !reflect.DeepEqual(n_cfg, o_cfg) { t.Error("BackendConfiguration should be equal after Reload") } } func TestBackendReloadAddBackend(t *testing.T) { + current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1") original_config.AddOption("backend", "allowall", "false") @@ -349,6 +363,7 @@ func TestBackendReloadAddBackend(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+1) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1, backend2") new_config.AddOption("backend", "allowall", "false") @@ -362,6 +377,7 @@ func TestBackendReloadAddBackend(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+3) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend2", "url", "http://domain2.invalid") @@ -369,12 +385,14 @@ func TestBackendReloadAddBackend(t *testing.T) { original_config.AddOption("backend2", "sessionlimit", "10") o_cfg.Reload(original_config) + checkStatsValue(t, statsBackendsCurrent, current+4) if !reflect.DeepEqual(n_cfg, o_cfg) { t.Error("BackendConfiguration should be equal after Reload") } } func TestBackendReloadRemoveHost(t *testing.T) { + current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -387,6 +405,7 @@ func TestBackendReloadRemoveHost(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "allowall", "false") @@ -397,17 +416,20 @@ func TestBackendReloadRemoveHost(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+3) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1") original_config.RemoveSection("backend2") o_cfg.Reload(original_config) + checkStatsValue(t, statsBackendsCurrent, current+2) if !reflect.DeepEqual(n_cfg, o_cfg) { t.Error("BackendConfiguration should be equal after Reload") } } func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { + current := testutil.ToFloat64(statsBackendsCurrent) original_config := goconf.NewConfigFile() original_config.AddOption("backend", "backends", "backend1, backend2") original_config.AddOption("backend", "allowall", "false") @@ -420,6 +442,7 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+2) new_config := goconf.NewConfigFile() new_config.AddOption("backend", "backends", "backend1") new_config.AddOption("backend", "allowall", "false") @@ -430,11 +453,13 @@ func TestBackendReloadRemoveBackendFromSharedHost(t *testing.T) { t.Fatal(err) } + checkStatsValue(t, statsBackendsCurrent, current+3) original_config.RemoveOption("backend", "backends") original_config.AddOption("backend", "backends", "backend1") original_config.RemoveSection("backend2") o_cfg.Reload(original_config) + checkStatsValue(t, statsBackendsCurrent, current+2) if !reflect.DeepEqual(n_cfg, o_cfg) { t.Error("BackendConfiguration should be equal after Reload") } diff --git a/client.go b/client.go index 943b465..7ae7156 100644 --- a/client.go +++ b/client.go @@ -58,6 +58,10 @@ var ( unknownCountry string = "unknown-country" ) +func init() { + RegisterClientStats() +} + func IsValidCountry(country string) bool { switch country { case "": diff --git a/client_stats_prometheus.go b/client_stats_prometheus.go new file mode 100644 index 0000000..e20447e --- /dev/null +++ b/client_stats_prometheus.go @@ -0,0 +1,43 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 struktur AG + * + * @author Joachim Bauch + * + * @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 . + */ +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...) +} diff --git a/hub.go b/hub.go index 1eae587..d515a65 100644 --- a/hub.go +++ b/hub.go @@ -95,6 +95,10 @@ const ( publicSessionName = "public-session" ) +func init() { + RegisterHubStats() +} + type Hub struct { // 64-bit members that are accessed atomically must be 64-bit aligned. sid uint64 @@ -607,6 +611,7 @@ func (h *Hub) removeSession(session Session) (removed bool) { delete(h.clients, data.Sid) if _, found := h.sessions[data.Sid]; found { delete(h.sessions, data.Sid) + statsHubSessionsCurrent.WithLabelValues(session.Backend().Id(), session.ClientType()).Dec() removed = true } } @@ -742,6 +747,12 @@ func (h *Hub) processRegister(client *Client, message *ClientMessage, backend *B } 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(publicSessionId, publicSessionName, sessionIdData) h.sendHelloResponse(session, message) @@ -840,6 +851,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) { if resumeId != "" { data := h.decodeSessionId(resumeId, privateSessionName) if data == nil { + statsHubSessionResumeFailed.Inc() client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) return } @@ -848,6 +860,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) { session, found := h.sessions[data.Sid] if !found || resumeId != session.PrivateId() { h.mu.Unlock() + statsHubSessionResumeFailed.Inc() client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) return } @@ -857,6 +870,7 @@ func (h *Hub) processHello(client *Client, message *ClientMessage) { // Should never happen as clients only can resume their own sessions. h.mu.Unlock() log.Printf("Client resumed non-client session %s (private=%s)", session.PublicId(), session.PrivateId()) + statsHubSessionResumeFailed.Inc() client.SendMessage(message.NewErrorServerMessage(NoSuchSession)) 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()) + statsHubSessionsResumedTotal.WithLabelValues(clientSession.Backend().Id(), clientSession.ClientType()).Inc() h.sendHelloResponse(clientSession, message) clientSession.NotifySessionResumed(client) return @@ -1087,7 +1102,10 @@ func (h *Hub) getRoomForBackend(id string, backend *Backend) *Room { func (h *Hub) removeRoom(room *Room) { internalRoomId := getRoomIdForBackend(room.Id(), room.Backend()) 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() } @@ -1100,6 +1118,7 @@ func (h *Hub) createRoom(id string, properties *json.RawMessage, backend *Backen internalRoomId := getRoomIdForBackend(id, backend) h.rooms[internalRoomId] = room + statsHubRoomsCurrent.WithLabelValues(backend.Id()).Inc() return room, nil } @@ -1550,6 +1569,8 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) { h.sessions[sessionIdData.Sid] = sess h.virtualSessions[virtualSessionId] = sessionIdData.Sid 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()) session.AddVirtualSession(sess) sess.SetRoom(room) @@ -1608,6 +1629,7 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) { h.mu.Unlock() if sess != nil { 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 { // We should always have a VirtualSession here. vsess.CloseWithFeedback(session, message) diff --git a/hub_stats_prometheus.go b/hub_stats_prometheus.go new file mode 100644 index 0000000..f3d5c1a --- /dev/null +++ b/hub_stats_prometheus.go @@ -0,0 +1,70 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 struktur AG + * + * @author Joachim Bauch + * + * @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 . + */ +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...) +} diff --git a/room.go b/room.go index a2d3a4c..857e7ed 100644 --- a/room.go +++ b/room.go @@ -32,6 +32,7 @@ import ( "time" "github.com/nats-io/nats.go" + "github.com/prometheus/client_golang/prometheus" ) const ( @@ -47,6 +48,10 @@ var ( updateActiveSessionsInterval = 10 * time.Second ) +func init() { + RegisterRoomStats() +} + type Room struct { id string hub *Hub @@ -64,6 +69,8 @@ type Room struct { inCallSessions map[Session]bool roomSessionData map[string]*RoomSessionData + statsRoomSessionsCurrent *prometheus.GaugeVec + natsReceiver chan *nats.Msg backendSubscription NatsSubscription @@ -123,6 +130,11 @@ func NewRoom(roomId string, properties *json.RawMessage, hub *Hub, n NatsClient, inCallSessions: make(map[Session]bool), roomSessionData: make(map[string]*RoomSessionData), + statsRoomSessionsCurrent: statsRoomSessionsCurrent.MustCurryWith(prometheus.Labels{ + "backend": backend.Id(), + "room": roomId, + }), + natsReceiver: natsReceiver, backendSubscription: backendSubscription, @@ -194,6 +206,9 @@ func (r *Room) Close() []Session { result = append(result, s) } 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() return result } @@ -264,6 +279,9 @@ func (r *Room) AddSession(session Session, sessionData *json.RawMessage) []Sessi } } r.sessions[sid] = session + if !found { + r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": session.ClientType()}).Inc() + } var publishUsersChanged bool switch session.ClientType() { case HelloClientTypeInternal: @@ -311,6 +329,7 @@ func (r *Room) RemoveSession(session Session) bool { } sid := session.PublicId() + r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": session.ClientType()}).Dec() delete(r.sessions, sid) delete(r.internalSessions, session) if virtualSession, ok := session.(*VirtualSession); ok { @@ -325,6 +344,9 @@ func (r *Room) RemoveSession(session Session) bool { } 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.doClose() r.mu.Unlock() diff --git a/room_stats_prometheus.go b/room_stats_prometheus.go new file mode 100644 index 0000000..eec8a96 --- /dev/null +++ b/room_stats_prometheus.go @@ -0,0 +1,43 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2021 struktur AG + * + * @author Joachim Bauch + * + * @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 . + */ +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...) +} diff --git a/stats_prometheus_test.go b/stats_prometheus_test.go index 3031d41..08d6a7a 100644 --- a/stats_prometheus_test.go +++ b/stats_prometheus_test.go @@ -22,6 +22,9 @@ package signaling import ( + "fmt" + "runtime" + "strings" "testing" "github.com/prometheus/client_golang/prometheus" @@ -34,7 +37,27 @@ func checkStatsValue(t *testing.T, collector prometheus.Collector, value float64 desc := <-ch v := testutil.ToFloat64(collector) 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) } }