From a4a815cc06a87292ef3b6f340a9ff503679884d7 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Mon, 10 Mar 2014 18:16:57 +0100 Subject: [PATCH] Implemented stats API. --- server.conf.in | 1 + src/app/spreed-speakfreely-server/api.go | 19 +---- src/app/spreed-speakfreely-server/hub.go | 43 ++++++++++++ src/app/spreed-speakfreely-server/main.go | 16 ++++- src/app/spreed-speakfreely-server/rooms.go | 2 +- src/app/spreed-speakfreely-server/stats.go | 82 ++++++++++++++++++++++ 6 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 src/app/spreed-speakfreely-server/stats.go diff --git a/server.conf.in b/server.conf.in index fd3bb9f2..ec3def02 100644 --- a/server.conf.in +++ b/server.conf.in @@ -7,6 +7,7 @@ listen = 127.0.0.1:8080 #writetimeout = 10 #basePath = /some/sub/path/ # Set this when running behind a web server under a sub path. #maxfd = 32768 # Try to set max open files limit on start (works only when run as root). +#stats = true # Provide stats API at /api/v1/stats (do not enable this in production or unprotected!). [app] #title = Spreed Speak Freely diff --git a/src/app/spreed-speakfreely-server/api.go b/src/app/spreed-speakfreely-server/api.go index 4ea2e943..e871f08b 100644 --- a/src/app/spreed-speakfreely-server/api.go +++ b/src/app/spreed-speakfreely-server/api.go @@ -20,11 +20,7 @@ */ package main -import ( - "app/spreed-speakfreely-server/sleepy" - "github.com/gorilla/mux" - "github.com/strukturag/httputils" -) +import () type ApiError struct { Id string `json:"code"` @@ -35,16 +31,3 @@ type ApiError struct { func NewApiError(id, message string) *ApiError { return &ApiError{id, message, false} } - -func makeApiHandler(r *mux.Router, tokenProvider TokenProvider) { - - a := r.PathPrefix("/api/v1/").Subrouter() - api := sleepy.NewAPI(a) - - rooms := &Rooms{} - api.AddResource(rooms, "/rooms") - - tokens := &Tokens{tokenProvider} - api.AddResourceWithWrapper(tokens, httputils.MakeGzipHandler, "/tokens") - -} diff --git a/src/app/spreed-speakfreely-server/hub.go b/src/app/spreed-speakfreely-server/hub.go index adf082c3..76239bbc 100644 --- a/src/app/spreed-speakfreely-server/hub.go +++ b/src/app/spreed-speakfreely-server/hub.go @@ -45,6 +45,16 @@ type MessageRequest struct { Id string } +type HubStat struct { + Rooms int `json:"rooms"` + Connections int `json:"connections"` + Users int `json:"users"` + Count uint64 `json:"count"` + IdsInRoom map[string][]string `json:"idsinroom,omitempty"` + UsersById map[string]*DataUser `json:"usersbyid,omitempty"` + ConnectionsByIdx map[string]string `json:"connectionsbyidx,omitempty"` +} + type Hub struct { server *Server connectionTable map[string]*Connection @@ -76,6 +86,39 @@ func NewHub(version string, config *Config, sessionSecret string, turnSecret str } +func (h *Hub) Stat(details bool) *HubStat { + h.mutex.RLock() + defer h.mutex.RUnlock() + stat := &HubStat{ + Rooms: len(h.roomTable), + Connections: len(h.connectionTable), + Users: len(h.userTable), + Count: h.count, + } + if details { + rooms := make(map[string][]string) + for roomid, room := range h.roomTable { + users := make([]string, 0, len(room.connections)) + for id, _ := range room.connections { + users = append(users, id) + } + rooms[roomid] = users + } + stat.IdsInRoom = rooms + users := make(map[string]*DataUser) + for userid, user := range h.userTable { + users[userid] = user.Data() + } + stat.UsersById = users + connections := make(map[string]string) + for id, connection := range h.connectionTable { + connections[fmt.Sprintf("%d", connection.Idx)]=id + } + stat.ConnectionsByIdx = connections + } + return stat +} + func (h *Hub) CreateTurnData(id string) *DataTurn { // Create turn data credentials for shared secret auth with TURN diff --git a/src/app/spreed-speakfreely-server/main.go b/src/app/spreed-speakfreely-server/main.go index e788b9ed..a30cac59 100644 --- a/src/app/spreed-speakfreely-server/main.go +++ b/src/app/spreed-speakfreely-server/main.go @@ -21,6 +21,7 @@ package main import ( + "app/spreed-speakfreely-server/sleepy" "flag" "fmt" "github.com/gorilla/mux" @@ -147,6 +148,11 @@ func runner(runtime phoenix.Runtime) error { log.Printf("Using '%s' base base path.", basePath) } + statsEnabled, err := runtime.GetBool("http", "stats") + if err != nil { + statsEnabled = false + } + sessionSecret, err := runtime.GetString("app", "sessionSecret") if err != nil { return fmt.Errorf("No sessionSecret in config file.") @@ -294,7 +300,15 @@ func runner(runtime phoenix.Runtime) error { r.Handle("/favicon.ico", http.StripPrefix(basePath, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img"))))) r.Handle("/ws", makeWsHubHandler(hub)) r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler)) - makeApiHandler(r, tokenProvider) + + // Add API end points. + api := sleepy.NewAPI(r.PathPrefix("/api/v1/").Subrouter()) + api.AddResource(&Rooms{}, "/rooms") + api.AddResourceWithWrapper(&Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens") + if statsEnabled { + api.AddResourceWithWrapper(&Stats{hub: hub}, httputils.MakeGzipHandler, "/stats") + log.Println("Stats are enabled!") + } // Add extra/static support if configured and exists. if extraFolder != "" { diff --git a/src/app/spreed-speakfreely-server/rooms.go b/src/app/spreed-speakfreely-server/rooms.go index e77a5831..ec19e3fd 100644 --- a/src/app/spreed-speakfreely-server/rooms.go +++ b/src/app/spreed-speakfreely-server/rooms.go @@ -33,7 +33,7 @@ type Room struct { type Rooms struct { } -func (rooms Rooms) Post(r *http.Request) (int, interface{}) { +func (rooms *Rooms) Post(r *http.Request) (int, interface{}) { name := RandomString(11) return 200, &Room{name, fmt.Sprintf("/%s", name)} diff --git a/src/app/spreed-speakfreely-server/stats.go b/src/app/spreed-speakfreely-server/stats.go new file mode 100644 index 00000000..fd33a137 --- /dev/null +++ b/src/app/spreed-speakfreely-server/stats.go @@ -0,0 +1,82 @@ +/* + * Spreed Speak Freely. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed Speak Freely. + * + * 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 main + +import ( + "net/http" + "runtime" + "time" +) + +type Stat struct { + details bool + Runtime *RuntimeStat `json:"runtime"` + Hub *HubStat `json:"hub"` +} + +func NewStat(details bool, h *Hub) *Stat { + stat := &Stat{ + details: details, + Runtime: &RuntimeStat{}, + Hub: h.Stat(details), + } + stat.Runtime.Read() + return stat +} + +type RuntimeStat struct { + Goroutines float64 `json:"goroutines"` + Alloc float64 `json:"alloc"` + Mallocs float64 `json:"mallocs"` + Frees float64 `json:"frees"` + Pauses float64 `json:"pauses"` + Heap float64 `json:"heap"` + Stack float64 `json:"stack"` +} + +func (stat *RuntimeStat) Read() { + + memStats := &runtime.MemStats{} + runtime.ReadMemStats(memStats) + + stat.Goroutines = float64(runtime.NumGoroutine()) + stat.Alloc = float64(memStats.Alloc) + stat.Mallocs = float64(memStats.Mallocs) + stat.Frees = float64(memStats.Frees) + stat.Pauses = float64(memStats.PauseTotalNs) / float64(time.Millisecond) + stat.Heap = float64(memStats.HeapAlloc) + stat.Stack = float64(memStats.StackInuse) + +} + +type Stats struct { + hub *Hub +} + +func (stats *Stats) Get(r *http.Request) (int, interface{}) { + + r.ParseForm() + details := r.FormValue("details") == "1" + + stat := NewStat(details, stats.hub) + return 200, stat + +}