blob: 3d072ab143b2058c042e45b1bf5bc8271c4b498b [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package main
import (
"bytes"
"fmt"
"html/template"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"sort"
"strings"
"sync/atomic"
"time"
)
func handleStatus(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
round := func(t time.Duration) time.Duration {
return t / time.Second * time.Second
}
df := diskFree()
statusMu.Lock()
data := statusData{
Total: len(status),
Uptime: round(time.Now().Sub(processStartTime)),
Recent: append([]*buildStatus{}, statusDone...),
DiskFree: df,
Version: Version,
NumFD: fdCount(),
NumGoroutine: runtime.NumGoroutine(),
}
for _, st := range status {
if atomic.LoadInt32(&st.hasBuildlet) != 0 {
data.ActiveBuilds++
data.Active = append(data.Active, st)
} else {
data.Pending = append(data.Pending, st)
}
}
// TODO: make this prettier.
var buf bytes.Buffer
for _, key := range tryList {
if ts := tries[key]; ts != nil {
state := ts.state()
fmt.Fprintf(&buf, "Change-ID: %v Commit: %v (<a href='/try?commit=%v'>status</a>)\n",
key.ChangeTriple(), key.Commit, key.Commit[:8])
fmt.Fprintf(&buf, " Remain: %d, fails: %v\n", state.remain, state.failed)
for _, bs := range ts.builds {
fmt.Fprintf(&buf, " %s: running=%v\n", bs.Name, bs.isRunning())
}
}
}
statusMu.Unlock()
data.RemoteBuildlets = template.HTML(remoteBuildletStatus())
sort.Sort(byAge(data.Active))
sort.Sort(byAge(data.Pending))
sort.Sort(sort.Reverse(byAge(data.Recent)))
if errTryDeps != nil {
data.TrybotsErr = errTryDeps.Error()
} else {
if buf.Len() == 0 {
data.Trybots = template.HTML("<i>(none)</i>")
} else {
data.Trybots = template.HTML("<pre>" + buf.String() + "</pre>")
}
}
buf.Reset()
gcePool.WriteHTMLStatus(&buf)
data.GCEPoolStatus = template.HTML(buf.String())
buf.Reset()
kubePool.WriteHTMLStatus(&buf)
data.KubePoolStatus = template.HTML(buf.String())
buf.Reset()
reversePool.WriteHTMLStatus(&buf)
data.ReversePoolStatus = template.HTML(buf.String())
buf.Reset()
if err := statusTmpl.Execute(&buf, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
}
func fdCount() int {
f, err := os.Open("/proc/self/fd")
if err != nil {
return -1
}
defer f.Close()
n := 0
for {
names, err := f.Readdirnames(1000)
n += len(names)
if err == io.EOF {
return n
}
if err != nil {
return -1
}
}
}
func friendlyDuration(d time.Duration) string {
if d > 10*time.Second {
d2 := ((d + 50*time.Millisecond) / (100 * time.Millisecond)) * (100 * time.Millisecond)
return d2.String()
}
if d > time.Second {
d2 := ((d + 5*time.Millisecond) / (10 * time.Millisecond)) * (10 * time.Millisecond)
return d2.String()
}
d2 := ((d + 50*time.Microsecond) / (100 * time.Microsecond)) * (100 * time.Microsecond)
return d2.String()
}
func diskFree() string {
out, _ := exec.Command("df", "-h").Output()
return string(out)
}
// statusData is the data that fills out statusTmpl.
type statusData struct {
Total int // number of total builds (including those waiting for a buildlet)
ActiveBuilds int // number of running builds (subset of Total with a buildlet)
NumFD int
NumGoroutine int
Uptime time.Duration
Active []*buildStatus // have a buildlet
Pending []*buildStatus // waiting on a buildlet
Recent []*buildStatus
TrybotsErr string
Trybots template.HTML
GCEPoolStatus template.HTML // TODO: embed template
KubePoolStatus template.HTML // TODO: embed template
ReversePoolStatus template.HTML // TODO: embed template
RemoteBuildlets template.HTML
DiskFree string
Version string
}
var statusTmpl = template.Must(template.New("status").Parse(`
<!DOCTYPE html>
<html>
<head><link rel="stylesheet" href="/style.css"/><title>Go Farmer</title></head>
<body>
<header>
<h1>Go Build Coordinator</h1>
<nav>
<a href="https://build.golang.org">Dashboard</a>
<a href="/builders">Builders</a>
</nav>
<div class="clear"></div>
</header>
<h2>Running</h2>
<p>{{printf "%d" .Total}} total builds; {{printf "%d" .ActiveBuilds}} active. Uptime {{printf "%s" .Uptime}}. Version {{.Version}}.
<h2 id=trybots>Active Trybot Runs <a href='#trybots'>¶</a></h2>
{{- if .TrybotsErr}}
<b>trybots disabled:</b>: {{.TrybotsErr}}
{{else}}
{{.Trybots}}
{{end}}
<h2 id=remote>Remote buildlets <a href='#remote'>¶</a></h3>
{{.RemoteBuildlets}}
<h2 id=pools>Buildlet pools <a href='#pools'>¶</a></h2>
<ul>
<li>{{.GCEPoolStatus}}</li>
<li>{{.KubePoolStatus}}</li>
<li>{{.ReversePoolStatus}}</li>
</ul>
<h2 id=active>Active builds <a href='#active'>¶</a></h2>
<ul>
{{range .Active}}
<li><pre>{{.HTMLStatusLine}}</pre></li>
{{end}}
</ul>
<h2 id=pending>Pending builds <a href='#pending'>¶</a></h2>
<ul>
{{range .Pending}}
<li><pre>{{.HTMLStatusLine}}</pre></li>
{{end}}
</ul>
<h2 id=completed>Recently completed <a href='#completed'>¶</a></h2>
<ul>
{{range .Recent}}
<li><span>{{.HTMLStatusLine_done}}</span></li>
{{end}}
</ul>
<h2 id=disk>Disk Space <a href='#disk'>¶</a></h2>
<pre>{{.DiskFree}}</pre>
<h2 id=fd>File Descriptors <a href='#fd'>¶</a></h2>
<p>{{.NumFD}}</p>
<h2 id=goroutines>Goroutines <a href='#goroutines'>¶</a></h2>
<p>{{.NumGoroutine}} <a href='/debug/goroutines'>goroutines</a></p>
</body>
</html>
`))
func handleStyleCSS(w http.ResponseWriter, r *http.Request) {
src := strings.NewReader(styleCSS)
http.ServeContent(w, r, "style.css", processStartTime, src)
}
const styleCSS = `
body {
font-family: sans-serif;
color: #222;
padding: 10px;
margin: 0;
}
h1, h2, h1 > a, h2 > a, h1 > a:visited, h2 > a:visited {
color: #375EAB;
}
h1 { font-size: 24px; }
h2 { font-size: 20px; }
h1 > a, h2 > a {
display: none;
text-decoration: none;
}
h1:hover > a, h2:hover > a {
display: inline;
}
h1 > a:hover, h2 > a:hover {
text-decoration: underline;
}
pre {
font-family: monospace;
font-size: 9pt;
}
header {
margin: -10px -10px 0 -10px;
padding: 10px 10px;
background: #E0EBF5;
}
header a { color: #222; }
header h1 {
display: inline;
margin: 0;
padding-top: 5px;
}
header nav {
display: inline-block;
margin-left: 20px;
}
header nav a {
display: inline-block;
padding: 10px;
margin: 0;
margin-right: 5px;
color: white;
background: #375EAB;
text-decoration: none;
font-size: 16px;
border: 1px solid #375EAB;
border-radius: 5px;
}
table {
border-collapse: collapse;
font-size: 9pt;
}
table td, table th, table td, table th {
text-align: left;
vertical-align: top;
padding: 2px 6px;
}
table thead tr {
background: #fff !important;
}
`