diff --git a/cmd/radiobench/radiobench.go b/cmd/radiobench/radiobench.go index abf3d148581b49d9d7a27aa814632695f4893325..326437b1ba819b55537e1abb1da172ca94a56815 100644 --- a/cmd/radiobench/radiobench.go +++ b/cmd/radiobench/radiobench.go @@ -1,10 +1,13 @@ package main import ( + "errors" "flag" "fmt" + "io" "io/ioutil" "log" + "math/rand" "net/http" "os" "strings" @@ -22,6 +25,7 @@ type Stats struct { HttpStatus map[int]int HttpErrors int Errors int + Underruns int lock sync.Mutex } @@ -49,10 +53,20 @@ func (s *Stats) Error() { s.Errors++ } +func (s *Stats) Underrun() { + s.lock.Lock() + defer s.lock.Unlock() + s.Underruns++ +} + func (s *Stats) Dump() { s.lock.Lock() defer s.lock.Unlock() - log.Printf("errs=%d http_errs=%d http_status=%v", s.Errors, s.HttpErrors, s.HttpStatus) + log.Printf("errs=%d underruns=%d http_errs=%d http_status=%v", s.Errors, s.Underruns, s.HttpErrors, s.HttpStatus) +} + +func randomDuration(max time.Duration) time.Duration { + return time.Duration(rand.Int63n(int64(max))) } func readstream(id int, streamUrl string) error { @@ -67,6 +81,7 @@ func readstream(id int, streamUrl string) error { return fmt.Errorf("http status %s", resp.Status) } + // Handle (very roughly) M3U files. if resp.Header.Get("Content-Type") == "audio/x-mpegurl" { data, err := ioutil.ReadAll(resp.Body) resp.Body.Close() @@ -92,22 +107,44 @@ func readstream(id int, streamUrl string) error { defer resp.Body.Close() - // Just read data and discard it. + // Is it actually an audio stream? + switch resp.Header.Get("Content-Type") { + case "application/ogg", "audio/mpeg": + default: + return fmt.Errorf("unknown Content-Type: %s", resp.Header.Get("Content-Type")) + } + + // Just read data and discard it. While reading data, attempt + // to detect stalled connections by constantly computing a + // bitrate approximation. + bytes := 0 + lastStamp := time.Now() buf := make([]byte, 16384) for { - n, err := resp.Body.Read(buf) + n, err := io.ReadFull(resp.Body, buf) if err != nil { - stats.Error() break } if n == 0 { break } + + bytes += n + now := time.Now() + bps := float64(n) / now.Sub(lastStamp).Seconds() + // Ignore the first few seconds. + if bytes > 65535 && bps < 2000 { + stats.Underrun() + return fmt.Errorf("bitrate too low (%g Bps)", bps) + } + lastStamp = now } - return fmt.Errorf("connection lost") + stats.Error() + return errors.New("connection lost") } func worker(id int, streamUrl string) { + time.Sleep(randomDuration(10 * time.Second)) for { err := readstream(id, streamUrl) log.Printf("worker(%d): %v", id, err) @@ -140,7 +177,7 @@ func main() { go func(id int) { worker(id, streamUrl) wg.Done() - }(i) + }(i + 1) } wg.Wait() }