зеркало из https://github.com/golang/build.git
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:
Родитель
4c20a7761d
Коммит
fff6e1803a
|
@ -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) {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче