Add more metrics on rooms / sessions / backends.
This commit is contained in:
parent
5d431e5612
commit
ce1b3fc6e2
|
@ -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) {
|
||||||
|
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 "":
|
||||||
|
|
|
@ -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
24
hub.go
|
@ -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)
|
||||||
|
|
|
@ -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
22
room.go
|
@ -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()
|
||||||
|
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue