diff --git a/README b/README index a0e3671..83a6d05 100644 --- a/README +++ b/README @@ -163,8 +163,8 @@ with others, there is no need to go through the landing page. Recordings can be accessed under `/recordings/groupname`. This is only available to the administrator of the group. -Some statistics are available under `/stats`. This is only available to -the server administrator. +Some statistics are available under `/stats.json`. This is only available +to the server administrator. ## Side menu diff --git a/rtpconn/rtpstats.go b/rtpconn/rtpstats.go index a6a3fd3..e7975bd 100644 --- a/rtpconn/rtpstats.go +++ b/rtpconn/rtpstats.go @@ -23,18 +23,15 @@ func (c *webClient) GetStats() *stats.Client { tracks := up.getTracks() for _, t := range tracks { s := t.cache.GetStats(false) - var loss uint8 - if s.Expected > s.Received { - loss = uint8((s.Expected - s.Received) * 100 / - s.Expected) - } + loss := float64(s.Expected - s.Received) / + float64(s.Expected) jitter := time.Duration(t.jitter.Jitter()) * (time.Second / time.Duration(t.jitter.HZ())) rate, _ := t.rate.Estimate() conns.Tracks = append(conns.Tracks, stats.Track{ Bitrate: uint64(rate) * 8, Loss: loss, - Jitter: jitter, + Jitter: stats.Duration(jitter), }) } cs.Up = append(cs.Up, conns) @@ -59,9 +56,9 @@ func (c *webClient) GetStats() *stats.Client { conns.Tracks = append(conns.Tracks, stats.Track{ Bitrate: uint64(rate) * 8, MaxBitrate: t.maxBitrate.Get(jiffies), - Loss: uint8(uint32(loss) * 100 / 256), - Rtt: rtt, - Jitter: j, + Loss: float64(loss) / 256.0, + Rtt: stats.Duration(rtt), + Jitter: stats.Duration(j), }) } cs.Down = append(cs.Down, conns) diff --git a/stats/stats.go b/stats/stats.go index 74ef6ba..c812a5d 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -1,6 +1,7 @@ package stats import ( + "encoding/json" "sort" "time" @@ -8,13 +9,14 @@ import ( ) type GroupStats struct { - Name string - Clients []*Client + Name string `json:"name"` + Clients []*Client `json:"clients,omitempty"` } type Client struct { - Id string - Up, Down []Conn + Id string `json:"id"` + Up []Conn `json:"up,omitempty"` + Down []Conn `json:"down,omitempty"` } type Statable interface { @@ -22,17 +24,34 @@ type Statable interface { } type Conn struct { - Id string - MaxBitrate uint64 - Tracks []Track + Id string `json:"id"` + MaxBitrate uint64 `json:"maxBitrate,omitempty"` + Tracks []Track `json:"tracks"` +} + +type Duration time.Duration + +func (d Duration) MarshalJSON() ([]byte, error) { + s := float64(d) / float64(time.Millisecond) + return json.Marshal(s) +} + +func (d *Duration) UnmarshalJSON(buf []byte) error { + var s float64 + err := json.Unmarshal(buf, &s) + if err != nil { + return err + } + *d = Duration(s * float64(time.Millisecond)) + return nil } type Track struct { - Bitrate uint64 - MaxBitrate uint64 - Loss uint8 - Rtt time.Duration - Jitter time.Duration + Bitrate uint64 `json:"bitrate"` + MaxBitrate uint64 `json:"maxBitrate,omitempty"` + Loss float64 `json:"loss"` + Rtt Duration `json:"rtt,omitempty"` + Jitter Duration `json:"jitter,omitempty"` } func GetGroups() []GroupStats { diff --git a/webserver/webserver.go b/webserver/webserver.go index e336f88..805fc0e 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -47,9 +47,10 @@ func Serve(address string, dataDir string) error { http.HandleFunc("/recordings/", recordingsHandler) http.HandleFunc("/ws", wsHandler) http.HandleFunc("/public-groups.json", publicHandler) - http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { - statsHandler(w, r, dataDir) - }) + http.HandleFunc("/stats.json", + func(w http.ResponseWriter, r *http.Request) { + statsHandler(w, r, dataDir) + }) s := &http.Server{ Addr: address, @@ -361,81 +362,16 @@ func statsHandler(w http.ResponseWriter, r *http.Request, dataDir string) { return } - w.Header().Set("content-type", "text/html; charset=utf-8") + w.Header().Set("content-type", "application/json") w.Header().Set("cache-control", "no-cache") if r.Method == "HEAD" { return } ss := stats.GetGroups() - - fmt.Fprintf(w, "\n\n") - fmt.Fprintf(w, "Stats\n") - fmt.Fprintf(w, "") - fmt.Fprintf(w, "\n") - - printBitrate := func(w io.Writer, rate, maxRate uint64) error { - var err error - if maxRate != 0 && maxRate != ^uint64(0) { - _, err = fmt.Fprintf(w, "%v/%v", rate, maxRate) - } else { - _, err = fmt.Fprintf(w, "%v", rate) - } - return err - } - - printTrack := func(w io.Writer, t stats.Track) { - fmt.Fprintf(w, "") - fmt.Fprintf(w, "") - printBitrate(w, t.Bitrate, t.MaxBitrate) - fmt.Fprintf(w, "") - fmt.Fprintf(w, "%d%%", - t.Loss, - ) - fmt.Fprintf(w, "") - if t.Rtt > 0 { - fmt.Fprintf(w, "%v", t.Rtt) - } - if t.Jitter > 0 { - fmt.Fprintf(w, "±%v", t.Jitter) - } - fmt.Fprintf(w, "") - fmt.Fprintf(w, "") - } - - for _, gs := range ss { - fmt.Fprintf(w, "

%v

\n", html.EscapeString(gs.Name)) - fmt.Fprintf(w, "") - for _, cs := range gs.Clients { - fmt.Fprintf(w, "\n", cs.Id) - for _, up := range cs.Up { - fmt.Fprintf(w, "", - up.Id) - if up.MaxBitrate > 0 { - fmt.Fprintf(w, "", - up.MaxBitrate) - } - fmt.Fprintf(w, "\n") - for _, t := range up.Tracks { - printTrack(w, t) - } - } - for _, down := range cs.Down { - fmt.Fprintf(w, "", - down.Id) - if down.MaxBitrate > 0 { - fmt.Fprintf(w, "", - down.MaxBitrate) - } - fmt.Fprintf(w, "\n") - for _, t := range down.Tracks { - printTrack(w, t) - } - } - } - fmt.Fprintf(w, "
%v
Up%v%v
Down %v%v
\n") - } - fmt.Fprintf(w, "\n") + e := json.NewEncoder(w) + e.Encode(ss) + return } var wsUpgrader = websocket.Upgrader{