Fix pipeline timestamps (#730)

* only calculate time on running builds

* Add updated timestamp into database and use it in frontend

* add more trace logging

* refactor (move grpc unrelated func into related package)

* fix xorm schema

* add todo
This commit is contained in:
6543 2022-01-31 15:38:39 +01:00 committed by GitHub
parent 6af94d79e3
commit 2a5159f7fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 39 deletions

View file

@ -321,6 +321,12 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
return err return err
} }
log.Trace().
Str("repo_id", fmt.Sprint(repo.ID)).
Str("build_id", fmt.Sprint(build.ID)).
Str("proc_id", id).
Msgf("gRPC Done with state: %#v", state)
if proc, err = shared.UpdateProcStatusToDone(s.store, *proc, state); err != nil { if proc, err = shared.UpdateProcStatusToDone(s.store, *proc, state); err != nil {
log.Error().Msgf("error: done: cannot update proc_id %d state: %s", proc.ID, err) log.Error().Msgf("error: done: cannot update proc_id %d state: %s", proc.ID, err)
} }
@ -341,8 +347,8 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
} }
s.completeChildrenIfParentCompleted(procs, proc) s.completeChildrenIfParentCompleted(procs, proc)
if !isThereRunningStage(procs) { if !model.IsThereRunningStage(procs) {
if build, err = shared.UpdateStatusToDone(s.store, *build, buildStatus(procs), proc.Stopped); err != nil { if build, err = shared.UpdateStatusToDone(s.store, *build, model.BuildStatus(procs), proc.Stopped); err != nil {
log.Error().Err(err).Msgf("error: done: cannot update build_id %d final state", build.ID) log.Error().Err(err).Msgf("error: done: cannot update build_id %d final state", build.ID)
} }
} }
@ -361,23 +367,13 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
s.buildCount.WithLabelValues(repo.FullName, build.Branch, string(build.Status), "total").Inc() s.buildCount.WithLabelValues(repo.FullName, build.Branch, string(build.Status), "total").Inc()
s.buildTime.WithLabelValues(repo.FullName, build.Branch, string(build.Status), "total").Set(float64(build.Finished - build.Started)) s.buildTime.WithLabelValues(repo.FullName, build.Branch, string(build.Status), "total").Set(float64(build.Finished - build.Started))
} }
if isMultiPipeline(procs) { if model.IsMultiPipeline(procs) {
s.buildTime.WithLabelValues(repo.FullName, build.Branch, string(proc.State), proc.Name).Set(float64(proc.Stopped - proc.Started)) s.buildTime.WithLabelValues(repo.FullName, build.Branch, string(proc.State), proc.Name).Set(float64(proc.Stopped - proc.Started))
} }
return nil return nil
} }
func isMultiPipeline(procs []*model.Proc) bool {
countPPIDZero := 0
for _, proc := range procs {
if proc.PPID == 0 {
countPPIDZero++
}
}
return countPPIDZero > 1
}
// Log implements the rpc.Log function // Log implements the rpc.Log function
func (s *RPC) Log(c context.Context, id string, line *rpc.Line) error { func (s *RPC) Log(c context.Context, id string, line *rpc.Line) error {
entry := new(logging.Entry) entry := new(logging.Entry)
@ -398,31 +394,6 @@ func (s *RPC) completeChildrenIfParentCompleted(procs []*model.Proc, completedPr
} }
} }
func isThereRunningStage(procs []*model.Proc) bool {
for _, p := range procs {
if p.PPID == 0 {
if p.Running() {
return true
}
}
}
return false
}
func buildStatus(procs []*model.Proc) model.StatusValue {
status := model.StatusSuccess
for _, p := range procs {
if p.PPID == 0 {
if p.Failing() {
status = p.State
}
}
}
return status
}
func (s *RPC) updateRemoteStatus(ctx context.Context, repo *model.Repo, build *model.Build, proc *model.Proc) { func (s *RPC) updateRemoteStatus(ctx context.Context, repo *model.Repo, build *model.Build, proc *model.Proc) {
user, err := s.store.GetUser(repo.UserID) user, err := s.store.GetUser(repo.UserID)
if err != nil { if err != nil {

View file

@ -28,6 +28,7 @@ type Build struct {
Error string `json:"error" xorm:"build_error"` Error string `json:"error" xorm:"build_error"`
Enqueued int64 `json:"enqueued_at" xorm:"build_enqueued"` Enqueued int64 `json:"enqueued_at" xorm:"build_enqueued"`
Created int64 `json:"created_at" xorm:"build_created"` Created int64 `json:"created_at" xorm:"build_created"`
Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"`
Started int64 `json:"started_at" xorm:"build_started"` Started int64 `json:"started_at" xorm:"build_started"`
Finished int64 `json:"finished_at" xorm:"build_finished"` Finished int64 `json:"finished_at" xorm:"build_finished"`
Deploy string `json:"deploy_to" xorm:"build_deploy"` Deploy string `json:"deploy_to" xorm:"build_deploy"`

View file

@ -68,6 +68,20 @@ func (p *Proc) IsParent() bool {
return p.PPID == 0 return p.PPID == 0
} }
// IsMultiPipeline checks if proc list contain more than one parent proc
func IsMultiPipeline(procs []*Proc) bool {
c := 0
for _, proc := range procs {
if proc.IsParent() {
c++
}
if c > 1 {
return true
}
}
return false
}
// Tree creates a process tree from a flat process list. // Tree creates a process tree from a flat process list.
func Tree(procs []*Proc) ([]*Proc, error) { func Tree(procs []*Proc) ([]*Proc, error) {
var nodes []*Proc var nodes []*Proc
@ -93,6 +107,32 @@ func Tree(procs []*Proc) ([]*Proc, error) {
return nodes, nil return nodes, nil
} }
// BuildStatus determine build status based on corresponding proc list
func BuildStatus(procs []*Proc) StatusValue {
status := StatusSuccess
for _, p := range procs {
if p.IsParent() && p.Failing() {
status = p.State
}
}
return status
}
// IsThereRunningStage determine if it contains procs running or pending to run
// TODO: return false based on depends_on (https://github.com/woodpecker-ci/woodpecker/pull/730#discussion_r795681697)
func IsThereRunningStage(procs []*Proc) bool {
for _, p := range procs {
if p.IsParent() {
if p.Running() {
return true
}
}
}
return false
}
func findNode(nodes []*Proc, pid int) (*Proc, error) { func findNode(nodes []*Proc, pid int) (*Proc, error) {
for _, node := range nodes { for _, node := range nodes {
if node.PID == pid { if node.PID == pid {

View file

@ -40,13 +40,17 @@ export default (build: Ref<Build | undefined>) => {
} }
const start = build.value.started_at || 0; const start = build.value.started_at || 0;
const end = build.value.finished_at || 0; const end = build.value.finished_at || build.value.updated_at || 0;
if (start === 0) { if (start === 0) {
return 0; return 0;
} }
if (end === 0) { if (end === 0) {
// only calculate time on running builds
if (build.value.status !== 'running') {
return 0;
}
return Date.now() - start * 1000; return Date.now() - start * 1000;
} }

View file

@ -18,6 +18,9 @@ export type Build = {
// When the build request was received. // When the build request was received.
created_at: number; created_at: number;
// When the build was updated last time in database.
updated_at: number;
// When the build was enqueued. // When the build was enqueued.
enqueued_at: number; enqueued_at: number;