maintner/maintnerd: use Gerrit to calculate TryBot/Result votes

In https://golang.org/cl/51590 I tried to calculate the Run-TryBot and
TryBot-Result vote sums using the Gerrit NoteDB mirror we have in
maintner, but after two days of fighting it now, I've concluded it's
tricky. While I work on the tricky bits in parallel, this CL partially
reverts CL 51590, which had enough other cleanup and progress that it
wasn't worth reverting in its entirety.

Instead, move the Gerrit querying to maintnerd (instead of coordinator).
This isn't bad because maintnerd already queries Gerrit.

And coordinator will still be polling every second (introduced in
51590) instead of the old 60 seconds, but this CL now adds caching in
the RPC handler.

Change-Id: I80a519d9026a0981e3abf43d54a32b4684bda0e5
Reviewed-on: https://go-review.googlesource.com/51970
Reviewed-by: Kevin Burke <kev@inburke.com>
This commit is contained in:
Brad Fitzpatrick 2017-07-30 00:42:10 +00:00
Родитель 4c20a7761d
Коммит fff6e1803a
3 изменённых файлов: 103 добавлений и 26 удалений

Просмотреть файл

@ -96,6 +96,7 @@ type GerritProject struct {
remote map[gerritCLVersion]GitHash remote map[gerritCLVersion]GitHash
need map[GitHash]bool need map[GitHash]bool
commit map[GitHash]*GitCommit commit map[GitHash]*GitCommit
numLabelChanges int // meta commits with "Label:" updates
// ref are the non-change refs with keys like "HEAD", // ref are the non-change refs with keys like "HEAD",
// "refs/heads/master", "refs/tags/v0.8.0", etc. // "refs/heads/master", "refs/tags/v0.8.0", etc.
@ -118,6 +119,14 @@ func (gp *GerritProject) gitDir() string {
return filepath.Join(gp.gerrit.c.getDataDir(), url.PathEscape(gp.proj)) return filepath.Join(gp.gerrit.c.getDataDir(), url.PathEscape(gp.proj))
} }
// NumLabelChanges returns the number of times vote labels have
// changed in this project. This number is monotonically increasing.
// This is not guaranteed to be accurate; it might overcount.
// It will not undercount.
func (gp *GerritProject) NumLabelChanges() int {
return gp.numLabelChanges
}
// ServerSlashProject returns the server and project together, such as // ServerSlashProject returns the server and project together, such as
// "go.googlesource.com/build". // "go.googlesource.com/build".
func (gp *GerritProject) ServerSlashProject() string { return gp.proj } func (gp *GerritProject) ServerSlashProject() string { return gp.proj }
@ -620,6 +629,9 @@ func (gp *GerritProject) processMutation(gm *maintpb.GerritMutation) {
// GerritCL object. // GerritCL object.
var backwardMessages []*GerritMessage var backwardMessages []*GerritMessage
gp.foreachCommitParent(cl.Meta.Hash, func(gc *GitCommit) error { gp.foreachCommitParent(cl.Meta.Hash, func(gc *GitCommit) error {
if strings.Contains(gc.Msg, "\nLabel: ") {
gp.numLabelChanges++
}
if oldMeta != nil && gc.Hash == oldMeta.Hash { if oldMeta != nil && gc.Hash == oldMeta.Hash {
return errStopIteration return errStopIteration
} }

Просмотреть файл

@ -9,7 +9,10 @@ import (
"errors" "errors"
"sort" "sort"
"strings" "strings"
"sync"
"time"
"golang.org/x/build/gerrit"
"golang.org/x/build/maintner" "golang.org/x/build/maintner"
"golang.org/x/build/maintner/maintnerd/apipb" "golang.org/x/build/maintner/maintnerd/apipb"
) )
@ -107,20 +110,65 @@ func (s apiService) GetRef(ctx context.Context, req *apipb.GetRefRequest) (*apip
return res, nil return res, nil
} }
var tryCache struct {
sync.Mutex
forNumChanges int // number of label changes in project val is valid for
lastPoll time.Time // of gerrit
val *apipb.GoFindTryWorkResponse
}
var tryBotGerrit = gerrit.NewClient("https://go-review.googlesource.com/", gerrit.NoAuth)
func (s apiService) GoFindTryWork(ctx context.Context, req *apipb.GoFindTryWorkRequest) (*apipb.GoFindTryWorkResponse, error) { func (s apiService) GoFindTryWork(ctx context.Context, req *apipb.GoFindTryWorkRequest) (*apipb.GoFindTryWorkResponse, error) {
tryCache.Lock()
defer tryCache.Unlock()
s.c.RLock() s.c.RLock()
defer s.c.RUnlock() defer s.c.RUnlock()
res := new(apipb.GoFindTryWorkResponse)
goProj := s.c.Gerrit().Project("go.googlesource.com", "go")
// Count the number of vote label changes over time. If it's
// the same as the last query, return a cached result without
// hitting Gerrit.
var sumChanges int
s.c.Gerrit().ForeachProjectUnsorted(func(gp *maintner.GerritProject) error { s.c.Gerrit().ForeachProjectUnsorted(func(gp *maintner.GerritProject) error {
gp.ForeachOpenCL(func(cl *maintner.GerritCL) error { if gp.Server() != "go.googlesource.com" {
try, done := tryBotStatus(cl, req.ForStaging)
if !try || done {
return nil return nil
} }
sumChanges += gp.NumLabelChanges()
return nil
})
now := time.Now()
const maxPollInterval = 15 * time.Second
if tryCache.val != nil &&
(tryCache.forNumChanges == sumChanges ||
tryCache.lastPoll.After(now.Add(-maxPollInterval))) {
return tryCache.val, nil
}
tryCache.lastPoll = now
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
const query = "label:Run-TryBot=1 label:TryBot-Result=0 status:open"
cis, err := tryBotGerrit.QueryChanges(ctx, query, gerrit.QueryChangesOpt{
Fields: []string{"CURRENT_REVISION", "CURRENT_COMMIT"},
})
if err != nil {
return nil, err
}
tryCache.forNumChanges = sumChanges
res := new(apipb.GoFindTryWorkResponse)
goProj := s.c.Gerrit().Project("go.googlesource.com", "go")
for _, ci := range cis {
cl := s.c.Gerrit().Project("go.googlesource.com", ci.Project).CL(int32(ci.ChangeNumber))
work := tryWorkItem(cl) work := tryWorkItem(cl)
if ci.CurrentRevision != "" {
// In case maintner is behind.
work.Commit = ci.CurrentRevision
}
if work.Project != "go" { if work.Project != "go" {
// Trybot on a subrepo. // Trybot on a subrepo.
// //
@ -131,10 +179,7 @@ func (s apiService) GoFindTryWork(ctx context.Context, req *apipb.GoFindTryWorkR
work.GoCommit = append(work.GoCommit, goProj.Ref("refs/heads/master").String()) work.GoCommit = append(work.GoCommit, goProj.Ref("refs/heads/master").String())
} }
res.Waiting = append(res.Waiting, work) res.Waiting = append(res.Waiting, work)
return nil }
})
return nil
})
// Sort in some stable order. // Sort in some stable order.
// //
@ -147,5 +192,6 @@ func (s apiService) GoFindTryWork(ctx context.Context, req *apipb.GoFindTryWorkR
sort.Slice(res.Waiting, func(i, j int) bool { sort.Slice(res.Waiting, func(i, j int) bool {
return res.Waiting[i].Commit < res.Waiting[j].Commit return res.Waiting[i].Commit < res.Waiting[j].Commit
}) })
tryCache.val = res
return res, nil return res, nil
} }

Просмотреть файл

@ -6,9 +6,11 @@ package main
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"sync" "sync"
"testing" "testing"
"time"
"golang.org/x/build/maintner" "golang.org/x/build/maintner"
"golang.org/x/build/maintner/godata" "golang.org/x/build/maintner/godata"
@ -49,17 +51,34 @@ func TestGetRef(t *testing.T) {
} }
} }
var hitGerrit = flag.Bool("hit_gerrit", false, "query production Gerrit in TestFindTryWork")
func TestFindTryWork(t *testing.T) { func TestFindTryWork(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if !*hitGerrit {
t.Skip("skipping without flag --hit_gerrit")
}
c := getGoData(t) c := getGoData(t)
s := apiService{c} s := apiService{c}
req := &apipb.GoFindTryWorkRequest{} req := &apipb.GoFindTryWorkRequest{}
t0 := time.Now()
res, err := s.GoFindTryWork(context.Background(), req) res, err := s.GoFindTryWork(context.Background(), req)
d0 := time.Since(t0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Just for interactive debugging. This is using live data. // Just for interactive debugging. This is using live data.
// The stable tests are in TestTryWorkItem and TestTryBotStatus. // The stable tests are in TestTryWorkItem and TestTryBotStatus.
t.Logf("Current: %v", res) t.Logf("Current: %v", res)
t1 := time.Now()
res, err = s.GoFindTryWork(context.Background(), req)
d1 := time.Since(t1)
t.Logf("Latency: %v, then %v", d0, d1)
t.Logf("Cached: %v, %v", res, err)
} }
func TestTryBotStatus(t *testing.T) { func TestTryBotStatus(t *testing.T) {