Commit 3a6ade98 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Export stats as JSON.

parent 9b1d814b
...@@ -163,8 +163,8 @@ with others, there is no need to go through the landing page. ...@@ -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 Recordings can be accessed under `/recordings/groupname`. This is only
available to the administrator of the group. available to the administrator of the group.
Some statistics are available under `/stats`. This is only available to Some statistics are available under `/stats.json`. This is only available
the server administrator. to the server administrator.
## Side menu ## Side menu
......
...@@ -23,18 +23,15 @@ func (c *webClient) GetStats() *stats.Client { ...@@ -23,18 +23,15 @@ func (c *webClient) GetStats() *stats.Client {
tracks := up.getTracks() tracks := up.getTracks()
for _, t := range tracks { for _, t := range tracks {
s := t.cache.GetStats(false) s := t.cache.GetStats(false)
var loss uint8 loss := float64(s.Expected - s.Received) /
if s.Expected > s.Received { float64(s.Expected)
loss = uint8((s.Expected - s.Received) * 100 /
s.Expected)
}
jitter := time.Duration(t.jitter.Jitter()) * jitter := time.Duration(t.jitter.Jitter()) *
(time.Second / time.Duration(t.jitter.HZ())) (time.Second / time.Duration(t.jitter.HZ()))
rate, _ := t.rate.Estimate() rate, _ := t.rate.Estimate()
conns.Tracks = append(conns.Tracks, stats.Track{ conns.Tracks = append(conns.Tracks, stats.Track{
Bitrate: uint64(rate) * 8, Bitrate: uint64(rate) * 8,
Loss: loss, Loss: loss,
Jitter: jitter, Jitter: stats.Duration(jitter),
}) })
} }
cs.Up = append(cs.Up, conns) cs.Up = append(cs.Up, conns)
...@@ -59,9 +56,9 @@ func (c *webClient) GetStats() *stats.Client { ...@@ -59,9 +56,9 @@ func (c *webClient) GetStats() *stats.Client {
conns.Tracks = append(conns.Tracks, stats.Track{ conns.Tracks = append(conns.Tracks, stats.Track{
Bitrate: uint64(rate) * 8, Bitrate: uint64(rate) * 8,
MaxBitrate: t.maxBitrate.Get(jiffies), MaxBitrate: t.maxBitrate.Get(jiffies),
Loss: uint8(uint32(loss) * 100 / 256), Loss: float64(loss) / 256.0,
Rtt: rtt, Rtt: stats.Duration(rtt),
Jitter: j, Jitter: stats.Duration(j),
}) })
} }
cs.Down = append(cs.Down, conns) cs.Down = append(cs.Down, conns)
......
package stats package stats
import ( import (
"encoding/json"
"sort" "sort"
"time" "time"
...@@ -8,13 +9,14 @@ import ( ...@@ -8,13 +9,14 @@ import (
) )
type GroupStats struct { type GroupStats struct {
Name string Name string `json:"name"`
Clients []*Client Clients []*Client `json:"clients,omitempty"`
} }
type Client struct { type Client struct {
Id string Id string `json:"id"`
Up, Down []Conn Up []Conn `json:"up,omitempty"`
Down []Conn `json:"down,omitempty"`
} }
type Statable interface { type Statable interface {
...@@ -22,17 +24,34 @@ type Statable interface { ...@@ -22,17 +24,34 @@ type Statable interface {
} }
type Conn struct { type Conn struct {
Id string Id string `json:"id"`
MaxBitrate uint64 MaxBitrate uint64 `json:"maxBitrate,omitempty"`
Tracks []Track 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 { type Track struct {
Bitrate uint64 Bitrate uint64 `json:"bitrate"`
MaxBitrate uint64 MaxBitrate uint64 `json:"maxBitrate,omitempty"`
Loss uint8 Loss float64 `json:"loss"`
Rtt time.Duration Rtt Duration `json:"rtt,omitempty"`
Jitter time.Duration Jitter Duration `json:"jitter,omitempty"`
} }
func GetGroups() []GroupStats { func GetGroups() []GroupStats {
......
...@@ -47,7 +47,8 @@ func Serve(address string, dataDir string) error { ...@@ -47,7 +47,8 @@ func Serve(address string, dataDir string) error {
http.HandleFunc("/recordings/", recordingsHandler) http.HandleFunc("/recordings/", recordingsHandler)
http.HandleFunc("/ws", wsHandler) http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/public-groups.json", publicHandler) http.HandleFunc("/public-groups.json", publicHandler)
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/stats.json",
func(w http.ResponseWriter, r *http.Request) {
statsHandler(w, r, dataDir) statsHandler(w, r, dataDir)
}) })
...@@ -361,81 +362,16 @@ func statsHandler(w http.ResponseWriter, r *http.Request, dataDir string) { ...@@ -361,81 +362,16 @@ func statsHandler(w http.ResponseWriter, r *http.Request, dataDir string) {
return 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") w.Header().Set("cache-control", "no-cache")
if r.Method == "HEAD" { if r.Method == "HEAD" {
return return
} }
ss := stats.GetGroups() ss := stats.GetGroups()
e := json.NewEncoder(w)
fmt.Fprintf(w, "<!DOCTYPE html>\n<html><head>\n") e.Encode(ss)
fmt.Fprintf(w, "<title>Stats</title>\n") return
fmt.Fprintf(w, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/common.css\"/>")
fmt.Fprintf(w, "<head><body>\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, "<tr><td></td><td></td><td></td>")
fmt.Fprintf(w, "<td>")
printBitrate(w, t.Bitrate, t.MaxBitrate)
fmt.Fprintf(w, "</td>")
fmt.Fprintf(w, "<td>%d%%</td>",
t.Loss,
)
fmt.Fprintf(w, "<td>")
if t.Rtt > 0 {
fmt.Fprintf(w, "%v", t.Rtt)
}
if t.Jitter > 0 {
fmt.Fprintf(w, "&#177;%v", t.Jitter)
}
fmt.Fprintf(w, "</td>")
fmt.Fprintf(w, "</tr>")
}
for _, gs := range ss {
fmt.Fprintf(w, "<p>%v</p>\n", html.EscapeString(gs.Name))
fmt.Fprintf(w, "<table>")
for _, cs := range gs.Clients {
fmt.Fprintf(w, "<tr><td>%v</td></tr>\n", cs.Id)
for _, up := range cs.Up {
fmt.Fprintf(w, "<tr><td></td><td>Up</td><td>%v</td>",
up.Id)
if up.MaxBitrate > 0 {
fmt.Fprintf(w, "<td>%v</td>",
up.MaxBitrate)
}
fmt.Fprintf(w, "</tr>\n")
for _, t := range up.Tracks {
printTrack(w, t)
}
}
for _, down := range cs.Down {
fmt.Fprintf(w, "<tr><td></td><td>Down</td><td> %v</td>",
down.Id)
if down.MaxBitrate > 0 {
fmt.Fprintf(w, "<td>%v</td>",
down.MaxBitrate)
}
fmt.Fprintf(w, "</tr>\n")
for _, t := range down.Tracks {
printTrack(w, t)
}
}
}
fmt.Fprintf(w, "</table>\n")
}
fmt.Fprintf(w, "</body></html>\n")
} }
var wsUpgrader = websocket.Upgrader{ var wsUpgrader = websocket.Upgrader{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment