Commit 12c3afc1 authored by Russ Cox's avatar Russ Cox

dashboard: fix for branches

In the new world, one builder runs
        gobuilder -commit
which uploads information about commits to the dashboard,
which then hands the work out to the builders by hash.
There is no assumption anymore that the commit numbers
are consistent across builders.

New builders will need to be deployed.  For now darwin-amd64
is running the new builder to test the code.

The new JSON-based protocol for handing out work via /todo
should be easy to extend if we want to add support for sending
trial CLs to the builders.

This code is already running on godashboard.appspot.com.

R=adg, dave
CC=golang-dev
https://golang.org/cl/4519047
parent 5f6e1cfc
...@@ -7,7 +7,6 @@ include ../../../src/Make.inc ...@@ -7,7 +7,6 @@ include ../../../src/Make.inc
TARG=gobuilder TARG=gobuilder
GOFILES=\ GOFILES=\
exec.go\ exec.go\
hg.go\
http.go\ http.go\
main.go\ main.go\
package.go\ package.go\
......
...@@ -14,9 +14,6 @@ It periodically pulls updates from the Go Mercurial repository. ...@@ -14,9 +14,6 @@ It periodically pulls updates from the Go Mercurial repository.
When a newer revision is found, Go Builder creates a clone of the repository, When a newer revision is found, Go Builder creates a clone of the repository,
runs all.bash, and reports build success or failure to the Go Dashboard. runs all.bash, and reports build success or failure to the Go Dashboard.
For a successful build, Go Builder will also run benchmarks
(cd $GOROOT/src/pkg; make bench) and send the results to the Go Dashboard.
For a release revision (a change description that matches "release.YYYY-MM-DD"), For a release revision (a change description that matches "release.YYYY-MM-DD"),
Go Builder will create a tar.gz archive of the GOROOT and deliver it to the Go Builder will create a tar.gz archive of the GOROOT and deliver it to the
Go Google Code project's downloads section. Go Google Code project's downloads section.
...@@ -34,8 +31,6 @@ Optional flags: ...@@ -34,8 +31,6 @@ Optional flags:
The location of the Go Dashboard application to which Go Builder will The location of the Go Dashboard application to which Go Builder will
report its results. report its results.
-bench: Run benchmarks
-release: Build and deliver binary release archive -release: Build and deliver binary release archive
-rev=N: Build revision N and exit -rev=N: Build revision N and exit
...@@ -45,7 +40,7 @@ Optional flags: ...@@ -45,7 +40,7 @@ Optional flags:
-v: Verbose logging -v: Verbose logging
-external: External package builder mode (will not report Go build -external: External package builder mode (will not report Go build
state to dashboard, issue releases, or run benchmarks) state to dashboard or issue releases)
The key file should be located at $HOME/.gobuildkey or, for a builder-specific The key file should be located at $HOME/.gobuildkey or, for a builder-specific
key, $HOME/.gobuildkey-$BUILDER (eg, $HOME/.gobuildkey-linux-amd64). key, $HOME/.gobuildkey-$BUILDER (eg, $HOME/.gobuildkey-linux-amd64).
......
...@@ -18,7 +18,7 @@ func run(envv []string, dir string, argv ...string) os.Error { ...@@ -18,7 +18,7 @@ func run(envv []string, dir string, argv ...string) os.Error {
if *verbose { if *verbose {
log.Println("run", argv) log.Println("run", argv)
} }
bin, err := pathLookup(argv[0]) bin, err := lookPath(argv[0])
if err != nil { if err != nil {
return err return err
} }
...@@ -36,7 +36,7 @@ func runLog(envv []string, logfile, dir string, argv ...string) (output string, ...@@ -36,7 +36,7 @@ func runLog(envv []string, logfile, dir string, argv ...string) (output string,
if *verbose { if *verbose {
log.Println("runLog", argv) log.Println("runLog", argv)
} }
bin, err := pathLookup(argv[0]) bin, err := lookPath(argv[0])
if err != nil { if err != nil {
return return
} }
...@@ -67,10 +67,10 @@ func runLog(envv []string, logfile, dir string, argv ...string) (output string, ...@@ -67,10 +67,10 @@ func runLog(envv []string, logfile, dir string, argv ...string) (output string,
return b.String(), wait.WaitStatus.ExitStatus(), nil return b.String(), wait.WaitStatus.ExitStatus(), nil
} }
// Find bin in PATH if a relative or absolute path hasn't been specified // lookPath looks for cmd in $PATH if cmd does not begin with / or ./ or ../.
func pathLookup(s string) (string, os.Error) { func lookPath(cmd string) (string, os.Error) {
if strings.HasPrefix(s, "/") || strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") { if strings.HasPrefix(cmd, "/") || strings.HasPrefix(cmd, "./") || strings.HasPrefix(cmd, "../") {
return s, nil return cmd, nil
} }
return exec.LookPath(s) return exec.LookPath(cmd)
} }
// Copyright 2011 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.
package main
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
type Commit struct {
num int // mercurial revision number
node string // mercurial hash
parent string // hash of commit's parent
user string // author's Name <email>
date string // date of commit
desc string // description
}
// getCommit returns details about the Commit specified by the revision hash
func getCommit(rev string) (c Commit, err os.Error) {
defer func() {
if err != nil {
err = fmt.Errorf("getCommit: %s: %s", rev, err)
}
}()
parts, err := getCommitParts(rev)
if err != nil {
return
}
num, err := strconv.Atoi(parts[0])
if err != nil {
return
}
parent := ""
if num > 0 {
prev := strconv.Itoa(num - 1)
if pparts, err := getCommitParts(prev); err == nil {
parent = pparts[1]
}
}
user := strings.Replace(parts[2], "&lt;", "<", -1)
user = strings.Replace(user, "&gt;", ">", -1)
return Commit{num, parts[1], parent, user, parts[3], parts[4]}, nil
}
func getCommitParts(rev string) (parts []string, err os.Error) {
const format = "{rev}>{node}>{author|escape}>{date}>{desc}"
s, _, err := runLog(nil, "", goroot,
"hg", "log",
"--encoding", "utf-8",
"--rev", rev,
"--limit", "1",
"--template", format,
)
if err != nil {
return
}
return strings.Split(s, ">", 5), nil
}
var revisionRe = regexp.MustCompile(`([0-9]+):[0-9a-f]+$`)
// getTag fetches a Commit by finding the first hg tag that matches re.
func getTag(re *regexp.Regexp) (c Commit, tag string, err os.Error) {
o, _, err := runLog(nil, "", goroot, "hg", "tags")
for _, l := range strings.Split(o, "\n", -1) {
tag = re.FindString(l)
if tag == "" {
continue
}
s := revisionRe.FindStringSubmatch(l)
if s == nil {
err = os.NewError("couldn't find revision number")
return
}
c, err = getCommit(s[1])
return
}
err = os.NewError("no matching tag found")
return
}
...@@ -6,84 +6,104 @@ package main ...@@ -6,84 +6,104 @@ package main
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/binary"
"fmt" "fmt"
"http" "http"
"json" "json"
"log" "log"
"os" "os"
"regexp"
"strconv" "strconv"
) )
// getHighWater returns the current highwater revision hash for this builder type param map[string]string
func (b *Builder) getHighWater() (rev string, err os.Error) {
url := fmt.Sprintf("http://%s/hw-get?builder=%s", *dashboard, b.name) // dash runs the given method and command on the dashboard.
r, _, err := http.Get(url) // If args is not nil, it is the query or post parameters.
if err != nil { // If resp is not nil, dash unmarshals the body as JSON into resp.
return func dash(meth, cmd string, resp interface{}, args param) os.Error {
var r *http.Response
var err os.Error
if *verbose {
log.Println("dash", cmd, args)
}
cmd = "http://" + *dashboard + "/" + cmd
switch meth {
case "GET":
if args != nil {
m := make(map[string][]string)
for k, v := range args {
m[k] = []string{v}
}
cmd += "?" + http.EncodeQuery(m)
}
r, _, err = http.Get(cmd)
case "POST":
r, err = http.PostForm(cmd, args)
default:
return fmt.Errorf("unknown method %q", meth)
} }
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(r.Body)
if err != nil { if err != nil {
return return err
}
defer r.Body.Close()
var buf bytes.Buffer
buf.ReadFrom(r.Body)
if resp != nil {
if err = json.Unmarshal(buf.Bytes(), resp); err != nil {
log.Printf("json unmarshal %#q: %s\n", buf.Bytes(), err)
return err
}
} }
r.Body.Close() return nil
return buf.String(), nil
} }
// recordResult sends build results to the dashboard func dashStatus(meth, cmd string, args param) os.Error {
func (b *Builder) recordResult(buildLog string, c Commit) os.Error { var resp struct {
return httpCommand("build", map[string]string{ Status string
"builder": b.name, Error string
"key": b.key, }
"node": c.node, err := dash(meth, cmd, &resp, args)
"parent": c.parent, if err != nil {
"user": c.user, return err
"date": c.date, }
"desc": c.desc, if resp.Status != "OK" {
"log": buildLog, return os.NewError("/build: " + resp.Error)
}) }
return nil
} }
// match lines like: "package.BechmarkFunc 100000 999 ns/op" // todo returns the next hash to build.
var benchmarkRegexp = regexp.MustCompile("([^\n\t ]+)[\t ]+([0-9]+)[\t ]+([0-9]+) ns/op") func (b *Builder) todo() (rev string, err os.Error) {
var resp []struct{
// recordBenchmarks sends benchmark results to the dashboard Hash string
func (b *Builder) recordBenchmarks(benchLog string, c Commit) os.Error {
results := benchmarkRegexp.FindAllStringSubmatch(benchLog, -1)
var buf bytes.Buffer
b64 := base64.NewEncoder(base64.StdEncoding, &buf)
for _, r := range results {
for _, s := range r[1:] {
binary.Write(b64, binary.BigEndian, uint16(len(s)))
b64.Write([]byte(s))
} }
if err = dash("GET", "todo", &resp, param{"builder": b.name}); err != nil {
return
}
if len(resp) > 0 {
rev = resp[0].Hash
} }
b64.Close() return
return httpCommand("benchmarks", map[string]string{ }
// recordResult sends build results to the dashboard
func (b *Builder) recordResult(buildLog string, hash string) os.Error {
return dash("POST", "build", nil, param{
"builder": b.name, "builder": b.name,
"key": b.key, "key": b.key,
"node": c.node, "node": hash,
"benchmarkdata": buf.String(), "log": buildLog,
}) })
} }
// getPackages fetches a list of package paths from the dashboard // packages fetches a list of package paths from the dashboard
func getPackages() (pkgs []string, err os.Error) { func packages() (pkgs []string, err os.Error) {
r, _, err := http.Get(fmt.Sprintf("http://%v/package?fmt=json", *dashboard))
if err != nil {
return
}
defer r.Body.Close()
d := json.NewDecoder(r.Body)
var resp struct { var resp struct {
Packages []struct { Packages []struct {
Path string Path string
} }
} }
if err = d.Decode(&resp); err != nil { err = dash("GET", "package", &resp, param{"fmt": "json"})
if err != nil {
return return
} }
for _, p := range resp.Packages { for _, p := range resp.Packages {
...@@ -93,24 +113,36 @@ func getPackages() (pkgs []string, err os.Error) { ...@@ -93,24 +113,36 @@ func getPackages() (pkgs []string, err os.Error) {
} }
// updatePackage sends package build results and info to the dashboard // updatePackage sends package build results and info to the dashboard
func (b *Builder) updatePackage(pkg string, state bool, buildLog, info string, c Commit) os.Error { func (b *Builder) updatePackage(pkg string, state bool, buildLog, info string, hash string) os.Error {
args := map[string]string{ return dash("POST", "package", nil, param{
"builder": b.name, "builder": b.name,
"key": b.key, "key": b.key,
"path": pkg, "path": pkg,
"state": strconv.Btoa(state), "state": strconv.Btoa(state),
"log": buildLog, "log": buildLog,
"info": info, "info": info,
"go_rev": strconv.Itoa(c.num), "go_rev": hash[:12],
} })
return httpCommand("package", args)
} }
func httpCommand(cmd string, args map[string]string) os.Error { // postCommit informs the dashboard of a new commit
if *verbose { func postCommit(key string, l *HgLog) os.Error {
log.Println("httpCommand", cmd, args) return dashStatus("POST", "commit", param{
"key": key,
"node": l.Hash,
"date": l.Date,
"user": l.Author,
"parent": l.Parent,
"desc": l.Desc,
})
}
// dashboardCommit returns true if the dashboard knows about hash.
func dashboardCommit(hash string) bool {
err := dashStatus("GET", "commit", param{"node": hash})
if err != nil {
log.Printf("check %s: %s", hash, err)
return false
} }
url := fmt.Sprintf("http://%v/%v", *dashboard, cmd) return true
_, err := http.PostForm(url, args)
return err
} }
This diff is collapsed.
...@@ -13,8 +13,8 @@ import ( ...@@ -13,8 +13,8 @@ import (
"path" "path"
) )
func (b *Builder) buildPackages(workpath string, c Commit) os.Error { func (b *Builder) buildPackages(workpath string, hash string) os.Error {
pkgs, err := getPackages() pkgs, err := packages()
if err != nil { if err != nil {
return err return err
} }
...@@ -32,13 +32,13 @@ func (b *Builder) buildPackages(workpath string, c Commit) os.Error { ...@@ -32,13 +32,13 @@ func (b *Builder) buildPackages(workpath string, c Commit) os.Error {
built := code != 0 built := code != 0
// get doc comment from package source // get doc comment from package source
info, err := getPackageComment(p, path.Join(goroot, "pkg", p)) info, err := packageComment(p, path.Join(goroot, "pkg", p))
if err != nil { if err != nil {
log.Printf("goinstall %v: %v", p, err) log.Printf("goinstall %v: %v", p, err)
} }
// update dashboard with build state + info // update dashboard with build state + info
err = b.updatePackage(p, built, buildLog, info, c) err = b.updatePackage(p, built, buildLog, info, hash)
if err != nil { if err != nil {
log.Printf("updatePackage %v: %v", p, err) log.Printf("updatePackage %v: %v", p, err)
} }
...@@ -46,7 +46,7 @@ func (b *Builder) buildPackages(workpath string, c Commit) os.Error { ...@@ -46,7 +46,7 @@ func (b *Builder) buildPackages(workpath string, c Commit) os.Error {
return nil return nil
} }
func getPackageComment(pkg, pkgpath string) (info string, err os.Error) { func packageComment(pkg, pkgpath string) (info string, err os.Error) {
fset := token.NewFileSet() fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, pkgpath, nil, parser.PackageClauseOnly|parser.ParseComments) pkgs, err := parser.ParseDir(fset, pkgpath, nil, parser.PackageClauseOnly|parser.ParseComments)
if err != nil { if err != nil {
......
application: godashboard application: godashboard
version: 5 version: 6
runtime: python runtime: python
api_version: 1 api_version: 1
......
This diff is collapsed.
...@@ -23,6 +23,12 @@ indexes: ...@@ -23,6 +23,12 @@ indexes:
- name: __key__ - name: __key__
direction: desc direction: desc
- kind: Commit
ancestor: yes
properties:
- name: __key__
direction: desc
- kind: Project - kind: Project
properties: properties:
- name: approved - name: approved
......
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