diff --git a/dashboard/coordinator/main.go b/dashboard/coordinator/main.go index 5a91d647..40eb9058 100644 --- a/dashboard/coordinator/main.go +++ b/dashboard/coordinator/main.go @@ -51,10 +51,13 @@ var ( watchers = map[string]watchConfig{} // populated once at startup donec = make(chan builderRev) // reports of finished builders - statusMu sync.Mutex - status = map[builderRev]*buildStatus{} + statusMu sync.Mutex // guards both status (ongoing ones) and statusDone (just finished) + status = map[builderRev]*buildStatus{} + statusDone []*buildStatus // finished recently, capped to maxStatusDone ) +const maxStatusDone = 30 + type imageInfo struct { url string // of tar file @@ -175,7 +178,7 @@ func main() { } case done := <-donec: log.Printf("%v done", done) - setStatus(done, nil) + markDone(done) case <-ticker.C: if numCurrentBuilds() == 0 && time.Now().After(startTime.Add(10*time.Minute)) { // TODO: halt the whole machine to kill the VM or something @@ -199,17 +202,36 @@ func mayBuildRev(work builderRev) bool { func setStatus(work builderRev, st *buildStatus) { statusMu.Lock() defer statusMu.Unlock() - if st == nil { - delete(status, work) - } else { - status[work] = st + status[work] = st +} + +func markDone(work builderRev) { + statusMu.Lock() + defer statusMu.Unlock() + st, ok := status[work] + if !ok { + return } + delete(status, work) + if len(statusDone) == maxStatusDone { + copy(statusDone, statusDone[1:]) + statusDone = statusDone[:len(statusDone)-1] + } + statusDone = append(statusDone, st) } func getStatus(work builderRev) *buildStatus { statusMu.Lock() defer statusMu.Unlock() - return status[work] + if st, ok := status[work]; ok { + return st + } + for _, st := range statusDone { + if st.builderRev == work { + return st + } + } + return nil } type byAge []*buildStatus @@ -220,19 +242,32 @@ func (s byAge) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func handleStatus(w http.ResponseWriter, r *http.Request) { var active []*buildStatus + var recent []*buildStatus statusMu.Lock() for _, st := range status { active = append(active, st) } + recent = append(recent, statusDone...) statusMu.Unlock() - fmt.Fprintf(w, "
", len(status), *maxBuilds) sort.Sort(byAge(active)) + sort.Sort(byAge(recent)) + + io.WriteString(w, "") + + io.WriteString(w, "Go build coordinator
") + + fmt.Fprintf(w, "running
%d of max %d builds running:", len(status), *maxBuilds) for _, st := range active { - fmt.Fprintf(w, "%-22s hg %s in container %s, %v ago\n", st.name, st.rev, st.name, st.rev, - st.container, time.Now().Sub(st.start)) + io.WriteString(w, st.htmlStatusLine()) } - fmt.Fprintf(w, "disk space
%s", html.EscapeString(diskFree())) + io.WriteString(w, "
") + for _, st := range recent { + io.WriteString(w, st.htmlStatusLine()) + } + io.WriteString(w, "") + + fmt.Fprintf(w, "
%s", html.EscapeString(diskFree())) } func diskFree() string { @@ -243,19 +278,15 @@ func diskFree() string { func handleLogs(w http.ResponseWriter, r *http.Request) { st := getStatus(builderRev{r.FormValue("name"), r.FormValue("rev")}) if st == nil { - fmt.Fprintf(w, "
block on +// the main page's list of active builds. +func (st *buildStatus) htmlStatusLine() string { + st.mu.Lock() + defer st.mu.Unlock() + + urlPrefix := "https://go-review.googlesource.com/#/q/" + if strings.Contains(st.name, "gccgo") { + urlPrefix = "https://code.google.com/p/gofrontend/source/detail?r=" + } + + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s rev %s", + st.name, urlPrefix, st.rev, st.rev) + + if st.done.IsZero() { + buf.WriteString(", running") + } else if st.succeeded { + buf.WriteString(", succeeded") + } else { + buf.WriteString(", failed") + } + + if st.container != "" { + fmt.Fprintf(&buf, " in container %s", st.name, st.rev, st.container) + } + + t := st.done + if t.IsZero() { + t = st.start + } + fmt.Fprintf(&buf, ", %v ago\n", time.Since(t)) + return buf.String() +} + +func (st *buildStatus) logs() string { + st.mu.Lock() + logs := st.output.String() + st.mu.Unlock() + key := builderKey(st.name) + return strings.Replace(string(logs), key, "BUILDERKEY", -1) +} + +func (st *buildStatus) Write(p []byte) (n int, err error) { + st.mu.Lock() + defer st.mu.Unlock() + const maxBufferSize = 2 << 20 // 2MB of output is way more than we expect. + if st.output.Len()+len(p) > maxBufferSize { + p = p[:maxBufferSize-st.output.Len()] + } + return st.output.Write(p) } func startWatching(conf watchConfig) (err error) {