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.
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
......
......@@ -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)
......
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 {
......
......@@ -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, "<!DOCTYPE html>\n<html><head>\n")
fmt.Fprintf(w, "<title>Stats</title>\n")
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")
e := json.NewEncoder(w)
e.Encode(ss)
return
}
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