progressui: add logs to the terminal output

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
docker-19.03
Tonis Tiigi 2019-04-01 18:54:02 -07:00
parent 12f178c045
commit 9e73ce6da2
1 changed files with 107 additions and 16 deletions

View File

@ -5,10 +5,12 @@ import (
"context"
"fmt"
"io"
"sort"
"strings"
"time"
"github.com/containerd/console"
"github.com/jaguilar/vt100"
"github.com/moby/buildkit/client"
"github.com/morikuni/aec"
digest "github.com/opencontainers/go-digest"
@ -27,7 +29,7 @@ func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w
disp.phase = "Building"
}
t := newTrace(w)
t := newTrace(w, modeConsole)
var done bool
ticker := time.NewTicker(100 * time.Millisecond)
@ -35,6 +37,7 @@ func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w
displayLimiter := rate.NewLimiter(rate.Every(70*time.Millisecond), 1)
width, height := disp.getSize()
for {
select {
case <-ctx.Done():
@ -42,19 +45,20 @@ func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w
case <-ticker.C:
case ss, ok := <-ch:
if ok {
t.update(ss)
t.update(ss, width)
} else {
done = true
}
}
if modeConsole {
width, height = disp.getSize()
if done {
disp.print(t.displayInfo(), true)
disp.print(t.displayInfo(), width, height, true)
t.printErrorLogs(c)
return nil
} else if displayLimiter.Allow() {
disp.print(t.displayInfo(), false)
disp.print(t.displayInfo(), width, height, false)
}
} else {
if done || displayLimiter.Allow() {
@ -67,6 +71,9 @@ func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w
}
}
const termHeight = 6
const termPad = 9
type displayInfo struct {
startTime time.Time
jobs []*job
@ -81,6 +88,8 @@ type job struct {
status string
hasError bool
isCanceled bool
vertex *vertex
showTerm bool
}
type trace struct {
@ -90,6 +99,7 @@ type trace struct {
byDigest map[digest.Digest]*vertex
nextIndex int
updates map[digest.Digest]struct{}
modeConsole bool
}
type vertex struct {
@ -110,6 +120,10 @@ type vertex struct {
jobs []*job
jobCached bool
term *vt100.VT100
termBytes int
termCount int
}
func (v *vertex) update(c int) {
@ -124,11 +138,12 @@ type status struct {
*client.VertexStatus
}
func newTrace(w io.Writer) *trace {
func newTrace(w io.Writer, modeConsole bool) *trace {
return &trace{
byDigest: make(map[digest.Digest]*vertex),
updates: make(map[digest.Digest]struct{}),
w: w,
byDigest: make(map[digest.Digest]*vertex),
updates: make(map[digest.Digest]struct{}),
w: w,
modeConsole: modeConsole,
}
}
@ -177,7 +192,7 @@ func (t *trace) triggerVertexEvent(v *client.Vertex) {
t.byDigest[v.Digest].prev = v
}
func (t *trace) update(s *client.SolveStatus) {
func (t *trace) update(s *client.SolveStatus, termWidth int) {
for _, v := range s.Vertexes {
prev, ok := t.byDigest[v.Digest]
if !ok {
@ -187,6 +202,9 @@ func (t *trace) update(s *client.SolveStatus) {
statusUpdates: make(map[string]struct{}),
index: t.nextIndex,
}
if t.modeConsole {
t.byDigest[v.Digest].term = vt100.NewVT100(termHeight, termWidth-termPad)
}
}
t.triggerVertexEvent(v)
if v.Started != nil && (prev == nil || prev.Started == nil) {
@ -222,6 +240,13 @@ func (t *trace) update(s *client.SolveStatus) {
continue // shouldn't happen
}
v.jobCached = false
if v.term != nil {
if v.term.Width != termWidth {
v.term.Resize(termHeight, termWidth-termPad)
}
v.termBytes += len(l.Data)
v.term.Write(l.Data) // error unhandled on purpose. don't trust vt100
}
i := 0
complete := split(l.Data, byte('\n'), func(dt []byte) {
if v.logsPartial && len(v.logs) != 0 && i == 0 {
@ -277,6 +302,7 @@ func (t *trace) displayInfo() (d displayInfo) {
startTime: addTime(v.Started, t.localTimeDiff),
completedTime: addTime(v.Completed, t.localTimeDiff),
name: strings.Replace(v.Name, "\t", " ", -1),
vertex: v,
}
if v.Error != "" {
if strings.HasSuffix(v.Error, context.Canceled.Error()) {
@ -346,20 +372,56 @@ type display struct {
repeated bool
}
func (disp *display) print(d displayInfo, all bool) {
// this output is inspired by Buck
func (disp *display) getSize() (int, int) {
width := 80
height := 10
size, err := disp.c.Size()
if err == nil && size.Width > 0 && size.Height > 0 {
width = int(size.Width)
height = int(size.Height)
if disp.c != nil {
size, err := disp.c.Size()
if err == nil && size.Width > 0 && size.Height > 0 {
width = int(size.Width)
height = int(size.Height)
}
}
return width, height
}
func setupTerminals(jobs []*job, height int, all bool) []*job {
var candidates []*job
numInUse := 0
for _, j := range jobs {
if j.vertex != nil && j.vertex.termBytes > 0 && j.completedTime == nil {
candidates = append(candidates, j)
}
if j.completedTime == nil {
numInUse++
}
}
sort.Slice(candidates, func(i, j int) bool {
idxI := candidates[i].vertex.termBytes + candidates[i].vertex.termCount*10
idxJ := candidates[j].vertex.termBytes + candidates[j].vertex.termCount*10
return idxI > idxJ
})
numFree := height - 2 - numInUse
numToHide := 0
termLimit := termHeight + 3
for i := 0; numFree > termLimit && i < len(candidates); i++ {
candidates[i].showTerm = true
numToHide += candidates[i].vertex.term.UsedHeight()
numFree -= termLimit
}
if !all {
d.jobs = wrapHeight(d.jobs, height-2)
jobs = wrapHeight(jobs, height-2-numToHide)
}
return jobs
}
func (disp *display) print(d displayInfo, width, height int, all bool) {
// this output is inspired by Buck
d.jobs = setupTerminals(d.jobs, height, all)
b := aec.EmptyBuilder
for i := 0; i <= disp.lineCount; i++ {
b = b.Up(1)
@ -431,10 +493,39 @@ func (disp *display) print(d displayInfo, all bool) {
}
fmt.Fprint(disp.c, out)
lineCount++
if j.showTerm {
term := j.vertex.term
term.Resize(termHeight, width-termPad)
for _, l := range term.Content {
if !isEmpty(l) {
out := aec.Apply(fmt.Sprintf(" => => # %s\n", string(l)), aec.Faint)
fmt.Fprint(disp.c, out)
lineCount++
}
}
j.vertex.termCount++
j.showTerm = false
}
}
// override previous content
if diff := disp.lineCount - lineCount; diff > 0 {
for i := 0; i < diff; i++ {
fmt.Fprintln(disp.c, strings.Repeat(" ", width))
}
fmt.Fprint(disp.c, aec.EmptyBuilder.Up(uint(diff)).Column(0).ANSI)
}
disp.lineCount = lineCount
}
func isEmpty(l []rune) bool {
for _, r := range l {
if r != ' ' {
return false
}
}
return true
}
func align(l, r string, w int) string {
return fmt.Sprintf("%-[2]*[1]s %[3]s", l, w-len(r)-1, r)
}