Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Show run durations and lazy step logs

c765ae2e18691b3f98d93b00b4eb418ce781e3f1 · Keenan Nemetz · 13d ago · parent bdf6a0de

ci/agent/agent.go +28−7

@@ -467,14 +467,14 @@ func (s *runnerSession) run(remote, cache string, job *runnerJob, lease time.Dur
467 467 // A run queued before images were required has no environment to start.
468 468 // Fail it with a record rather than guessing at the host.
469 469 fmt.Fprintf(log, "\nthis run has no image; re-queue it from a workflow that declares image or image-file\n")
470 s.finish(remote, co, job, log, 1, nil, nil, box)
470 s.finish(remote, co, job, log, 1, nil, nil, nil, box)
471 471 return
472 472 }
473 473 fail := func(format string, args ...any) {
474 474 msg := fmt.Sprintf(format, args...)
475 475 fmt.Fprintf(os.Stderr, "vole runner: %s\n", msg)
476 476 fmt.Fprintf(log, "\n%s\n", msg)
477 s.finish(remote, co, job, log, 1, nil, nil, box)
477 s.finish(remote, co, job, log, 1, nil, nil, nil, box)
478 478 }
479 479 fmt.Fprintf(log, "=== %s · %s · %s @ %s\n", job.Repo, job.Job, job.Branch, format.SHA(job.SHA))
480 480 fmt.Fprintf(log, "environment: %s via %s\n", job.Image, box.Runtime)
@@ -520,21 +520,34 @@ func (s *runnerSession) run(remote, cache string, job *runnerJob, lease time.Dur
520 520 }
521 521 }()
522 522
523 steps := make([]runs.StepResult, 0, len(job.Steps))
523 524 for _, step := range job.Steps {
525 // The step's section of the log starts at its header, so a reader can open
526 // one step without holding the image build that ran before it.
527 logStart := int64(log.Len())
528 started := time.Now().UTC()
524 529 fmt.Fprintf(log, "\n--- %s\n", step.Name)
525 530 code, chunk, lost := s.execStep(job.ID, sandbox, step, lease)
526 531 log.WriteString(chunk)
532 result := runs.StepResult{
533 Name: step.Name, Started: started, Finished: time.Now().UTC(), Exit: code,
534 LogOffset: logStart, LogSize: int64(log.Len()) - logStart,
535 }
527 536 if lost {
528 537 fmt.Fprintf(os.Stderr, "vole runner: lease lost while running %q; stopping\n", step.Name)
529 538 return // no finish: the queue already gave it to somebody else
530 539 }
531 540 if code != 0 {
532 541 fmt.Fprintf(log, "\nstep %q exited %d\n", step.Name, code)
542 result.LogSize = int64(log.Len()) - logStart
543 result.Finished = time.Now().UTC()
544 steps = append(steps, result)
533 545 sandbox.Stop()
534 546 sandbox = nil
535 s.finish(remote, co, job, log, code, nil, nil, box)
547 s.finish(remote, co, job, log, code, steps, nil, nil, box)
536 548 return
537 549 }
550 steps = append(steps, result)
538 551 }
539 552 // The steps are done. Stop the container before reading artifacts or deploying a
540 553 // site, so a process the workflow left in the background cannot change the files
@@ -558,7 +571,7 @@ func (s *runnerSession) run(remote, cache string, job *runnerJob, lease time.Dur
558 571 fail("%s", err)
559 572 return
560 573 }
561 s.finish(remote, co, job, log, 0, arts, releaseArts, box)
574 s.finish(remote, co, job, log, 0, steps, arts, releaseArts, box)
562 575 }
563 576
564 577 // cacheFor returns the mount for one job's cache, or nothing when the runner has
@@ -596,11 +609,18 @@ func (s *runnerSession) cacheFor(job *runnerJob) []container.Mount {
596 609 // A log that cannot be stored does not lose the result: the record then names no log
597 610 // and the failure is reported on stderr rather than swallowed. A record that cannot be
598 611 // pushed is not reported at all, because there is nothing for the server to read.
599 func (s *runnerSession) finish(remote string, co checkout, job *runnerJob, log *strings.Builder, exit int, arts, releaseArts []runnerArtifact, box runnerBox) {
612 func (s *runnerSession) finish(remote string, co checkout, job *runnerJob, log *strings.Builder, exit int, steps []runs.StepResult, arts, releaseArts []runnerArtifact, box runnerBox) {
600 613 oid, size, err := uploadLog(remote, job.Repo, log.String())
601 614 if err != nil {
602 615 fmt.Fprintf(os.Stderr, "vole runner: could not upload the log for %s: %v\n", job.ID, err)
603 616 }
617 if oid == "" {
618 // The step ranges describe bytes in a log this attempt does not have. Keep
619 // the timings, which are still true, and drop the claims about the log.
620 for i := range steps {
621 steps[i].LogOffset, steps[i].LogSize = 0, 0
622 }
623 }
604 624 artifacts := make([]runs.Artifact, 0, len(arts))
605 625 for _, a := range arts {
606 626 artifacts = append(artifacts, runs.Artifact{Name: a.Name, OID: a.OID, Size: a.Size})
@@ -609,7 +629,7 @@ func (s *runnerSession) finish(remote string, co checkout, job *runnerJob, log *
609 629 RunID: job.ID, Attempt: job.Attempt, Repo: job.Repo, Ref: job.Ref, SHA: job.SHA,
610 630 Workflow: job.Workflow, WorkflowSHA: job.WorkflowSHA, Job: job.Job,
611 631 StepsSHA: job.StepsSHA, Exit: exit, Finished: time.Now().UTC(),
612 Signer: s.name, Artifacts: artifacts,
632 Signer: s.name, Steps: steps, Artifacts: artifacts,
613 633 // The machine this attempt ran on. The runner binary's own build target,
614 634 // which is the honest thing it can say: a live probe would add a subprocess
615 635 // and a platform quirk for a fact the binary already knows.
@@ -902,7 +922,8 @@ func collectArtifactFiles(root string, patterns []string) ([]artifactFile, error
902 922
903 923 // logBuffer collects a step's output. It is a sync buffer because os/exec copies
904 924 // stdout and stderr in separate goroutines. Nothing is shipped while the step runs:
905 // the whole log is uploaded once, when the attempt is over.
925 // the whole attempt's log is uploaded once, when the attempt is over, and the step
926 // records byte ranges into it rather than uploading a second object.
906 927 type logBuffer struct {
907 928 mu sync.Mutex
908 929 buf strings.Builder