pkg/progressui: customization callbacks

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
Andrea Luzzardi 2021-02-23 15:34:41 -08:00
parent e40983b7b7
commit a9443bc0d2
3 changed files with 137 additions and 72 deletions

View File

@ -1,6 +1,10 @@
run:
skip-dirs:
# progressui is a modified 3rd party library from buildkit
- pkg/progressui
linters: linters:
disable-all: true disable-all: true
timeout: 30m
enable: enable:
- bodyclose - bodyclose
- deadcode - deadcode

View File

@ -20,21 +20,90 @@ import (
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w io.Writer, ch chan *client.SolveStatus) error { const (
defaultTickerTimeout = 150 * time.Millisecond
defaultDisplayTimeout = 100 * time.Millisecond
)
type VertexPrintFunc func(v *client.Vertex, index int)
type StatusPrintFunc func(v *client.Vertex, format string, a ...interface{})
type LogPrintFunc func(v *client.Vertex, stream int, partial bool, format string, a ...interface{})
func PrintSolveStatus(ctx context.Context, ch chan *client.SolveStatus, vertexPrintCb VertexPrintFunc, statusPrintCb StatusPrintFunc, logPrintCb LogPrintFunc) error {
printer := &textMux{
vertexPrintCb: vertexPrintCb,
statusPrintCb: statusPrintCb,
logPrintCb: logPrintCb,
}
t := newTrace(false)
var done bool
ticker := time.NewTicker(defaultTickerTimeout)
defer ticker.Stop()
displayLimiter := rate.NewLimiter(rate.Every(defaultDisplayTimeout), 1)
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
case ss, ok := <-ch:
if ok {
t.update(ss, 80)
} else {
done = true
}
}
if done || displayLimiter.Allow() {
printer.print(t)
if done {
t.printErrorLogs(statusPrintCb)
return nil
}
ticker.Stop()
ticker = time.NewTicker(defaultTickerTimeout)
}
}
}
func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w io.Writer, ch chan *client.SolveStatus) error {
modeConsole := c != nil modeConsole := c != nil
disp := &display{c: c, phase: phase} if !modeConsole {
printer := &textMux{w: w} vertexPrintCb := func(v *client.Vertex, index int) {
if os.Getenv("PROGRESS_NO_TRUNC") == "0" {
fmt.Fprintf(w, "#%d %s\n", index, limitString(v.Name, 72))
} else {
fmt.Fprintf(w, "#%d %s\n", index, v.Name)
fmt.Fprintf(w, "#%d %s\n", index, v.Digest)
}
}
statusPrintCb := func(v *client.Vertex, format string, a ...interface{}) {
fmt.Fprintf(w, fmt.Sprintf("%s\n", format), a...)
}
logPrintCb := func(v *client.Vertex, stream int, partial bool, format string, a ...interface{}) {
if partial {
fmt.Fprintf(w, format, a...)
} else {
fmt.Fprintf(w, fmt.Sprintf("%s\n", format), a...)
}
}
return PrintSolveStatus(ctx, ch, vertexPrintCb, statusPrintCb, logPrintCb)
}
disp := &display{c: c, phase: phase}
if disp.phase == "" { if disp.phase == "" {
disp.phase = "Building" disp.phase = "Building"
} }
t := newTrace(w, modeConsole) t := newTrace(true)
tickerTimeout := 150 * time.Millisecond tickerTimeout := defaultTickerTimeout
displayTimeout := 100 * time.Millisecond displayTimeout := defaultDisplayTimeout
if v := os.Getenv("TTY_DISPLAY_RATE"); v != "" { if v := os.Getenv("TTY_DISPLAY_RATE"); v != "" {
if r, err := strconv.ParseInt(v, 10, 64); err == nil { if r, err := strconv.ParseInt(v, 10, 64); err == nil {
@ -64,28 +133,18 @@ func DisplaySolveStatus(ctx context.Context, phase string, c console.Console, w
} }
} }
if modeConsole {
width, height = disp.getSize() width, height = disp.getSize()
if done { if done {
disp.print(t.displayInfo(), width, height, true) disp.print(t.displayInfo(), width, height, true)
t.printErrorLogs(c) t.printErrorLogs(func(v *client.Vertex, format string, a ...interface{}) {
fmt.Fprintf(w, format, a...)
})
return nil return nil
} else if displayLimiter.Allow() { } else if displayLimiter.Allow() {
ticker.Stop() ticker.Stop()
ticker = time.NewTicker(tickerTimeout) ticker = time.NewTicker(tickerTimeout)
disp.print(t.displayInfo(), width, height, false) disp.print(t.displayInfo(), width, height, false)
} }
} else {
if done || displayLimiter.Allow() {
printer.print(t)
if done {
t.printErrorLogs(w)
return nil
}
ticker.Stop()
ticker = time.NewTicker(tickerTimeout)
}
}
} }
} }
@ -111,7 +170,6 @@ type job struct {
} }
type trace struct { type trace struct {
w io.Writer
localTimeDiff time.Duration localTimeDiff time.Duration
vertexes []*vertex vertexes []*vertex
byDigest map[digest.Digest]*vertex byDigest map[digest.Digest]*vertex
@ -120,6 +178,12 @@ type trace struct {
modeConsole bool modeConsole bool
} }
type log struct {
index int
line []byte
stream int
}
type vertex struct { type vertex struct {
*client.Vertex *client.Vertex
statuses []*status statuses []*status
@ -127,7 +191,7 @@ type vertex struct {
indent string indent string
index int index int
logs [][]byte logs []log
logsPartial bool logsPartial bool
logsOffset int logsOffset int
prev *client.Vertex prev *client.Vertex
@ -156,11 +220,10 @@ type status struct {
*client.VertexStatus *client.VertexStatus
} }
func newTrace(w io.Writer, modeConsole bool) *trace { func newTrace(modeConsole bool) *trace {
return &trace{ return &trace{
byDigest: make(map[digest.Digest]*vertex), byDigest: make(map[digest.Digest]*vertex),
updates: make(map[digest.Digest]struct{}), updates: make(map[digest.Digest]struct{}),
w: w,
modeConsole: modeConsole, modeConsole: modeConsole,
} }
} }
@ -252,6 +315,7 @@ func (t *trace) update(s *client.SolveStatus, termWidth int) {
v.update(1) v.update(1)
} }
for _, l := range s.Logs { for _, l := range s.Logs {
l := l
v, ok := t.byDigest[l.Vertex] v, ok := t.byDigest[l.Vertex]
if !ok { if !ok {
continue // shouldn't happen continue // shouldn't happen
@ -266,8 +330,8 @@ func (t *trace) update(s *client.SolveStatus, termWidth int) {
} }
i := 0 i := 0
complete := split(l.Data, byte('\n'), func(dt []byte) { complete := split(l.Data, byte('\n'), func(dt []byte) {
if v.logsPartial && len(v.logs) != 0 && i == 0 { if v.logsPartial && len(v.logs) != 0 && i == 0 && v.logs[len(v.logs)-1].stream == l.Stream {
v.logs[len(v.logs)-1] = append(v.logs[len(v.logs)-1], dt...) v.logs[len(v.logs)-1].line = append(v.logs[len(v.logs)-1].line, dt...)
} else { } else {
ts := time.Duration(0) ts := time.Duration(0)
if v.Started != nil { if v.Started != nil {
@ -280,7 +344,11 @@ func (t *trace) update(s *client.SolveStatus, termWidth int) {
} else if sec < 100 { } else if sec < 100 {
prec = 2 prec = 2
} }
v.logs = append(v.logs, []byte(fmt.Sprintf("#%d %s %s", v.index, fmt.Sprintf("%.[2]*[1]f", sec, prec), dt))) v.logs = append(v.logs, log{
line: []byte(fmt.Sprintf("#%d %s %s", v.index, fmt.Sprintf("%.[2]*[1]f", sec, prec), dt)),
stream: l.Stream,
index: v.index,
})
} }
i++ i++
}) })
@ -290,16 +358,15 @@ func (t *trace) update(s *client.SolveStatus, termWidth int) {
} }
} }
func (t *trace) printErrorLogs(f io.Writer) { func (t *trace) printErrorLogs(printCb StatusPrintFunc) {
for _, v := range t.vertexes { for _, v := range t.vertexes {
if v.Error != "" && !strings.HasSuffix(v.Error, context.Canceled.Error()) { if v.Error != "" && !strings.HasSuffix(v.Error, context.Canceled.Error()) {
fmt.Fprintln(f, "------") printCb(v.Vertex, "------")
fmt.Fprintf(f, " > %s:\n", v.Name) printCb(v.Vertex, " > %s:", v.Name)
for _, l := range v.logs { for _, l := range v.logs {
f.Write(l) printCb(v.Vertex, "%s", l.line)
fmt.Fprintln(f)
} }
fmt.Fprintln(f, "------") printCb(v.Vertex, "------")
} }
} }
} }
@ -307,7 +374,7 @@ func (t *trace) printErrorLogs(f io.Writer) {
func (t *trace) displayInfo() (d displayInfo) { func (t *trace) displayInfo() (d displayInfo) {
d.startTime = time.Now() d.startTime = time.Now()
if t.localTimeDiff != 0 { if t.localTimeDiff != 0 {
d.startTime = (*t.vertexes[0].Started).Add(t.localTimeDiff) d.startTime = t.vertexes[0].Started.Add(t.localTimeDiff)
} }
d.countTotal = len(t.byDigest) d.countTotal = len(t.byDigest)
for _, v := range t.byDigest { for _, v := range t.byDigest {
@ -325,7 +392,7 @@ func (t *trace) displayInfo() (d displayInfo) {
j := &job{ j := &job{
startTime: addTime(v.Started, t.localTimeDiff), startTime: addTime(v.Started, t.localTimeDiff),
completedTime: addTime(v.Completed, t.localTimeDiff), completedTime: addTime(v.Completed, t.localTimeDiff),
name: strings.Replace(v.Name, "\t", " ", -1), name: strings.ReplaceAll(v.Name, "\t", " "),
vertex: v, vertex: v,
} }
if v.Error != "" { if v.Error != "" {
@ -385,7 +452,7 @@ func addTime(tm *time.Time, d time.Duration) *time.Time {
if tm == nil { if tm == nil {
return nil return nil
} }
t := (*tm).Add(d) t := tm.Add(d)
return &t return &t
} }
@ -580,7 +647,8 @@ func wrapHeight(j []*job, limit int) []*job {
l-- l--
} }
freespace := len(wrapped) - len(rewrapped) freespace := len(wrapped) - len(rewrapped)
wrapped = append(invisible[len(invisible)-freespace:], rewrapped...) invisible = append(invisible[len(invisible)-freespace:], rewrapped...)
wrapped = invisible
} }
} }
return wrapped return wrapped

View File

@ -3,8 +3,6 @@ package progressui
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"os"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -24,7 +22,10 @@ type lastStatus struct {
} }
type textMux struct { type textMux struct {
w io.Writer vertexPrintCb VertexPrintFunc
statusPrintCb StatusPrintFunc
logPrintCb LogPrintFunc
current digest.Digest current digest.Digest
last map[string]lastStatus last map[string]lastStatus
notFirst bool notFirst bool
@ -44,33 +45,27 @@ func (p *textMux) printVtx(t *trace, dgst digest.Digest) {
if p.current != "" { if p.current != "" {
old := t.byDigest[p.current] old := t.byDigest[p.current]
if old.logsPartial { if old.logsPartial {
fmt.Fprintln(p.w, "") p.statusPrintCb(v.Vertex, "")
} }
old.logsOffset = 0 old.logsOffset = 0
old.count = 0 old.count = 0
fmt.Fprintf(p.w, "#%d ...\n", old.index) p.statusPrintCb(v.Vertex, "#%d ...", old.index)
} }
if p.notFirst { if p.notFirst {
fmt.Fprintln(p.w, "") p.statusPrintCb(v.Vertex, "")
} else { } else {
p.notFirst = true p.notFirst = true
} }
if os.Getenv("PROGRESS_NO_TRUNC") == "0" { p.vertexPrintCb(v.Vertex, v.index)
fmt.Fprintf(p.w, "#%d %s\n", v.index, limitString(v.Name, 72))
} else {
fmt.Fprintf(p.w, "#%d %s\n", v.index, v.Name)
fmt.Fprintf(p.w, "#%d %s\n", v.index, v.Digest)
}
} }
if len(v.events) != 0 { if len(v.events) != 0 {
v.logsOffset = 0 v.logsOffset = 0
} }
for _, ev := range v.events { for _, ev := range v.events {
fmt.Fprintf(p.w, "#%d %s\n", v.index, ev) p.statusPrintCb(v.Vertex, "#%d %s", v.index, ev)
} }
v.events = v.events[:0] v.events = v.events[:0]
@ -118,25 +113,24 @@ func (p *textMux) printVtx(t *trace, dgst digest.Digest) {
if s.Completed != nil { if s.Completed != nil {
tm += " done" tm += " done"
} }
fmt.Fprintf(p.w, "#%d %s%s%s\n", v.index, s.ID, bytes, tm) p.statusPrintCb(v.Vertex, "#%d %s%s%s", v.index, s.ID, bytes, tm)
} }
} }
v.statusUpdates = map[string]struct{}{} v.statusUpdates = map[string]struct{}{}
for i, l := range v.logs { for i, l := range v.logs {
line := l.line
if i == 0 { if i == 0 {
l = l[v.logsOffset:] line = line[v.logsOffset:]
}
fmt.Fprintf(p.w, "%s", []byte(l))
if i != len(v.logs)-1 || !v.logsPartial {
fmt.Fprintln(p.w, "")
} }
complete := i != len(v.logs)-1 || !v.logsPartial
p.logPrintCb(v.Vertex, l.stream, !complete, "%s", line)
} }
if len(v.logs) > 0 { if len(v.logs) > 0 {
if v.logsPartial { if v.logsPartial {
v.logs = v.logs[len(v.logs)-1:] v.logs = v.logs[len(v.logs)-1:]
v.logsOffset = len(v.logs[0]) v.logsOffset = len(v.logs[0].line)
} else { } else {
v.logs = nil v.logs = nil
v.logsOffset = 0 v.logsOffset = 0
@ -150,23 +144,22 @@ func (p *textMux) printVtx(t *trace, dgst digest.Digest) {
if v.Error != "" { if v.Error != "" {
if v.logsPartial { if v.logsPartial {
fmt.Fprintln(p.w, "") p.statusPrintCb(v.Vertex, "")
} }
if strings.HasSuffix(v.Error, context.Canceled.Error()) { if strings.HasSuffix(v.Error, context.Canceled.Error()) {
fmt.Fprintf(p.w, "#%d CANCELED\n", v.index) p.statusPrintCb(v.Vertex, "#%d CANCELED", v.index)
} else { } else {
fmt.Fprintf(p.w, "#%d ERROR: %s\n", v.index, v.Error) p.statusPrintCb(v.Vertex, "#%d ERROR: %s", v.index, v.Error)
} }
} else if v.Cached { } else if v.Cached {
fmt.Fprintf(p.w, "#%d CACHED\n", v.index) p.statusPrintCb(v.Vertex, "#%d CACHED", v.index)
} else { } else {
tm := "" tm := ""
if v.Started != nil { if v.Started != nil {
tm = fmt.Sprintf(" %.1fs", v.Completed.Sub(*v.Started).Seconds()) tm = fmt.Sprintf(" %.1fs", v.Completed.Sub(*v.Started).Seconds())
} }
fmt.Fprintf(p.w, "#%d DONE%s\n", v.index, tm) p.statusPrintCb(v.Vertex, "#%d DONE%s", v.index, tm)
} }
} }
delete(t.updates, dgst) delete(t.updates, dgst)