implement session stats, fixes last (admin|user) left at won't work for any provider that removed users after logout.

This commit is contained in:
Miroslav Šedivý 2024-09-07 18:55:27 +02:00
parent 356a566bc6
commit 4f401ac2b6
7 changed files with 124 additions and 21 deletions

View file

@ -47,6 +47,7 @@ func (api *ApiManagerCtx) Route(r types.Router) {
r.Post("/logout", api.Logout)
r.Get("/whoami", api.Whoami)
r.Post("/profile", api.UpdateProfile)
r.Get("/stats", api.Stats)
sessionsHandler := sessions.New(api.sessions)
r.Route("/sessions", sessionsHandler.Route)

View file

@ -103,3 +103,8 @@ func (api *ApiManagerCtx) UpdateProfile(w http.ResponseWriter, r *http.Request)
return utils.HttpSuccess(w, true)
}
func (api *ApiManagerCtx) Stats(w http.ResponseWriter, r *http.Request) error {
stats := api.sessions.Stats()
return utils.HttpSuccess(w, stats)
}

View file

@ -7,10 +7,8 @@ import (
"io"
"net/http"
"net/url"
"time"
"m1k1o/neko/internal/api"
"m1k1o/neko/internal/api/room"
oldEvent "m1k1o/neko/internal/http/legacy/event"
oldMessage "m1k1o/neko/internal/http/legacy/message"
oldTypes "m1k1o/neko/internal/http/legacy/types"
@ -43,7 +41,6 @@ var (
type LegacyHandler struct {
logger zerolog.Logger
serverAddr string
startedAt time.Time
}
func New() *LegacyHandler {
@ -52,7 +49,6 @@ func New() *LegacyHandler {
return &LegacyHandler{
logger: log.With().Str("module", "legacy").Logger(),
serverAddr: "127.0.0.1:8080",
startedAt: time.Now(),
}
}
@ -208,9 +204,9 @@ func (h *LegacyHandler) Route(r types.Router) {
return utils.HttpInternalServerError().WithInternalErr(err)
}
// get current control status
control := room.ControlStatusPayload{}
err = s.apiReq(http.MethodGet, "/api/room/control", nil, &control)
// get stats
newStats := types.Stats{}
err = s.apiReq(http.MethodGet, "/api/stats", nil, &newStats)
if err != nil {
return utils.HttpInternalServerError().WithInternalErr(err)
}
@ -236,18 +232,6 @@ func (h *LegacyHandler) Route(r types.Router) {
}
// append members
stats.Members = append(stats.Members, member)
} else if session.State.NotConnectedSince != nil {
//
// TODO: This wont work if the user is removed after the session is closed
//
// populate last admin left time
if session.Profile.IsAdmin && (stats.LastAdminLeftAt == nil || (*session.State.NotConnectedSince).After(*stats.LastAdminLeftAt)) {
stats.LastAdminLeftAt = session.State.NotConnectedSince
}
// populate last user left time
if !session.Profile.IsAdmin && (stats.LastUserLeftAt == nil || (*session.State.NotConnectedSince).After(*stats.LastUserLeftAt)) {
stats.LastUserLeftAt = session.State.NotConnectedSince
}
}
}
@ -256,10 +240,12 @@ func (h *LegacyHandler) Route(r types.Router) {
return err
}
stats.Host = control.HostId
stats.Host = newStats.HostId
// TODO: stats.Banned, not implemented yet
stats.Locked = locks
stats.ServerStartedAt = h.startedAt
stats.ServerStartedAt = newStats.ServerStartedAt
stats.LastAdminLeftAt = newStats.LastAdminLeftAt
stats.LastUserLeftAt = newStats.LastUserLeftAt
stats.ControlProtection = settings.ControlProtection
stats.ImplicitControl = settings.ImplicitHosting

View file

@ -4,6 +4,7 @@ import (
"errors"
"sync"
"sync/atomic"
"time"
"github.com/kataras/go-events"
"github.com/rs/zerolog"
@ -31,6 +32,8 @@ func New(config *config.Session) *SessionManagerCtx {
sessions: make(map[string]*SessionCtx),
cursors: make(map[types.Session][]types.Cursor),
emmiter: events.New(),
serverStartedAt: time.Now(),
}
// create API session
@ -76,6 +79,12 @@ type SessionManagerCtx struct {
emmiter events.EventEmmiter
apiSession *SessionCtx
serverStartedAt time.Time
totalAdmins atomic.Int32
lastAdminLeftAt atomic.Value
totalUsers atomic.Int32
lastUserLeftAt atomic.Value
}
func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile) (types.Session, string, error) {
@ -468,3 +477,36 @@ func (manager *SessionManagerCtx) Settings() types.Settings {
func (manager *SessionManagerCtx) CookieEnabled() bool {
return manager.config.CookieEnabled
}
// ---
// stats
// ---
func (manager *SessionManagerCtx) Stats() types.Stats {
hostId := ""
host, hasHost := manager.GetHost()
if hasHost {
hostId = host.ID()
}
var lastUserLeftAt *time.Time
if t, ok := manager.lastUserLeftAt.Load().(*time.Time); ok {
lastUserLeftAt = t
}
var lastAdminLeftAt *time.Time
if t, ok := manager.lastAdminLeftAt.Load().(*time.Time); ok {
lastAdminLeftAt = t
}
return types.Stats{
HasHost: hasHost,
HostId: hostId,
ServerStartedAt: manager.serverStartedAt,
TotalUsers: int(manager.totalUsers.Load()),
LastUserLeftAt: lastUserLeftAt,
TotalAdmins: int(manager.totalAdmins.Load()),
LastAdminLeftAt: lastAdminLeftAt,
}
}

View file

@ -121,6 +121,14 @@ func (session *SessionCtx) ConnectWebSocketPeer(websocketPeer types.WebSocketPee
session.state.ConnectedSince = &now
session.state.NotConnectedSince = nil
if session.profile.IsAdmin {
session.manager.totalAdmins.Add(1)
session.manager.lastAdminLeftAt.Store((*time.Time)(nil))
} else {
session.manager.totalUsers.Add(1)
session.manager.lastUserLeftAt.Store((*time.Time)(nil))
}
session.manager.emmiter.Emit("connected", session)
// if there is a previous peer, destroy it
@ -180,6 +188,16 @@ func (session *SessionCtx) DisconnectWebSocketPeer(websocketPeer types.WebSocket
session.state.ConnectedSince = nil
session.state.NotConnectedSince = &now
if session.profile.IsAdmin {
if session.manager.totalAdmins.Add(-1) == 0 {
session.manager.lastAdminLeftAt.Store(&now)
}
} else {
if session.manager.totalUsers.Add(-1) == 0 {
session.manager.lastUserLeftAt.Store(&now)
}
}
session.manager.emmiter.Emit("disconnected", session)
session.websocketMu.Lock()

View file

@ -125,6 +125,21 @@ paths:
schema:
$ref: '#/components/schemas/MemberProfile'
required: true
/api/stats:
get:
summary: stats
operationId: stats
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Stats'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
#
# sessions
@ -1145,6 +1160,30 @@ components:
is_watching:
type: boolean
Stats:
type: object
properties:
has_host:
type: boolean
host_id:
type: string
optional: true
server_started_at:
type: string
format: date-time
total_users:
type: integer
last_user_left_at:
type: string
format: date-time
optional: true
total_admins:
type: integer
last_admin_left_at:
type: string
format: date-time
optional: true
#
# room
#

View file

@ -52,6 +52,16 @@ type Settings struct {
Plugins PluginSettings `json:"plugins"`
}
type Stats struct {
HasHost bool `json:"has_host"`
HostId string `json:"host_id,omitempty"`
ServerStartedAt time.Time `json:"server_started_at"`
TotalUsers int `json:"total_users"`
LastUserLeftAt *time.Time `json:"last_user_left_at,omitempty"`
TotalAdmins int `json:"total_admins"`
LastAdminLeftAt *time.Time `json:"last_admin_left_at,omitempty"`
}
type Session interface {
ID() string
Profile() MemberProfile
@ -110,6 +120,8 @@ type SessionManager interface {
Settings() Settings
CookieEnabled() bool
Stats() Stats
CookieSetToken(w http.ResponseWriter, token string)
CookieClearToken(w http.ResponseWriter, r *http.Request)
Authenticate(r *http.Request) (Session, error)