Skip to content

Commit c5d5037

Browse files
authored
Merge pull request #221 from gitpod-io/wv/log-build-phase-durations
Log build phase durations
2 parents d173aca + 86ff92d commit c5d5037

File tree

2 files changed

+133
-6
lines changed

2 files changed

+133
-6
lines changed

pkg/leeway/reporter.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ type ConsoleReporter struct {
9595
writer map[string]io.Writer
9696
times map[string]time.Time
9797
mu sync.RWMutex
98+
99+
// out is the writer to print to. Normally this is os.Stdout, but it can be set to a buffer for testing.
100+
out io.Writer
101+
now func() time.Time
98102
}
99103

100104
// exclusiveWriter makes a write an exclusive resource by protecting Write calls with a mutex.
@@ -115,6 +119,8 @@ func NewConsoleReporter() *ConsoleReporter {
115119
return &ConsoleReporter{
116120
writer: make(map[string]io.Writer),
117121
times: make(map[string]time.Time),
122+
out: os.Stdout,
123+
now: time.Now,
118124
}
119125
}
120126

@@ -134,7 +140,7 @@ func (r *ConsoleReporter) getWriter(pkg *Package) io.Writer {
134140
return res
135141
}
136142

137-
res = &exclusiveWriter{O: textio.NewPrefixWriter(os.Stdout, getRunPrefix(pkg))}
143+
res = &exclusiveWriter{O: textio.NewPrefixWriter(r.out, getRunPrefix(pkg))}
138144
r.writer[name] = res
139145
r.mu.Unlock()
140146
}
@@ -166,7 +172,7 @@ func (r *ConsoleReporter) BuildStarted(pkg *Package, status map[*Package]Package
166172
i++
167173
}
168174
sort.Slice(lines, func(i, j int) bool { return lines[i] < lines[j] })
169-
tw := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
175+
tw := tabwriter.NewWriter(r.out, 0, 2, 2, ' ', 0)
170176
fmt.Fprintln(tw, strings.Join(lines, ""))
171177
tw.Flush()
172178
}
@@ -191,7 +197,7 @@ func (r *ConsoleReporter) PackageBuildStarted(pkg *Package) {
191197
}
192198

193199
r.mu.Lock()
194-
r.times[pkg.FullName()] = time.Now()
200+
r.times[pkg.FullName()] = r.now()
195201
r.mu.Unlock()
196202

197203
_, _ = io.WriteString(out, color.Sprintf("<fg=yellow>build started</> <gray>(version %s)</>\n", version))
@@ -210,20 +216,32 @@ func (r *ConsoleReporter) PackageBuildFinished(pkg *Package, rep *PackageBuildRe
210216
out := r.getWriter(pkg)
211217

212218
r.mu.Lock()
213-
dur := time.Since(r.times[nme])
219+
dur := r.now().Sub(r.times[nme])
214220
delete(r.writer, nme)
215221
delete(r.times, nme)
216222
r.mu.Unlock()
217223

224+
// Format phase durations
225+
var phaseDurations []string
226+
for _, phase := range rep.Phases {
227+
if d := rep.PhaseDuration(phase); d > 0 {
228+
phaseDurations = append(phaseDurations, fmt.Sprintf("%s: %.1fs", phase, d.Seconds()))
229+
}
230+
}
231+
phaseDurStr := ""
232+
if len(phaseDurations) > 0 {
233+
phaseDurStr = color.Sprintf(" [%s]", strings.Join(phaseDurations, " | "))
234+
}
235+
218236
var msg string
219-
if rep.Error != nil {
237+
if rep.Error != nil {
220238
msg = color.Sprintf("<red>package build failed while %sing</>\n<white>Reason:</> %s\n", rep.LastPhase(), rep.Error)
221239
} else {
222240
var coverage string
223241
if rep.TestCoverageAvailable {
224242
coverage = color.Sprintf("<fg=yellow>test coverage: %d%%</> <gray>(%d of %d functions have tests)</>\n", rep.TestCoveragePercentage, rep.FunctionsWithTest, rep.FunctionsWithTest+rep.FunctionsWithoutTest)
225243
}
226-
msg = color.Sprintf("%s<green>package build succeded</> <gray>(%.2fs)</>\n", coverage, dur.Seconds())
244+
msg = color.Sprintf("%s<green>package build succeded</> <gray>(%.2fs)%s</>\n", coverage, dur.Seconds(), phaseDurStr)
227245
}
228246
//nolint:errcheck
229247
io.WriteString(out, msg)

pkg/leeway/reporter_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package leeway
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
"time"
7+
8+
"github.com/google/go-cmp/cmp"
9+
)
10+
11+
func TestConsoleReporter(t *testing.T) {
12+
t.Parallel()
13+
14+
type Expectation struct {
15+
Output string
16+
}
17+
18+
start := time.Now()
19+
pkg := &Package{
20+
C: &Component{
21+
Name: "test",
22+
},
23+
PackageInternal: PackageInternal{
24+
Name: "test",
25+
},
26+
}
27+
28+
tests := []struct {
29+
Name string
30+
Reporter Reporter
31+
Func func(t *testing.T, r *ConsoleReporter)
32+
Expect Expectation
33+
}{
34+
{
35+
Name: "all phases",
36+
Func: func(t *testing.T, r *ConsoleReporter) {
37+
r.PackageBuildStarted(pkg)
38+
39+
r.now = func() time.Time {
40+
return start.Add(5 * time.Second)
41+
}
42+
r.PackageBuildFinished(pkg, &PackageBuildReport{
43+
Phases: []PackageBuildPhase{
44+
PackageBuildPhasePrep,
45+
PackageBuildPhasePull,
46+
PackageBuildPhaseLint,
47+
PackageBuildPhaseTest,
48+
PackageBuildPhaseBuild},
49+
phaseEnter: map[PackageBuildPhase]time.Time{
50+
PackageBuildPhasePrep: start,
51+
PackageBuildPhasePull: start.Add(time.Second),
52+
PackageBuildPhaseBuild: start.Add(2 * time.Second),
53+
PackageBuildPhaseTest: start.Add(3 * time.Second),
54+
PackageBuildPhaseLint: start.Add(4 * time.Second),
55+
},
56+
phaseDone: map[PackageBuildPhase]time.Time{
57+
PackageBuildPhasePrep: start.Add(time.Second),
58+
PackageBuildPhasePull: start.Add(2 * time.Second),
59+
PackageBuildPhaseBuild: start.Add(3 * time.Second),
60+
PackageBuildPhaseTest: start.Add(4 * time.Second),
61+
PackageBuildPhaseLint: start.Add(5 * time.Second),
62+
},
63+
})
64+
},
65+
Expect: Expectation{
66+
Output: `[test:test] build started (version unknown)
67+
[test:test] package build succeded (5.00s) [prep: 1.0s | pull: 1.0s | lint: 1.0s | test: 1.0s | build: 1.0s]
68+
`,
69+
},
70+
},
71+
{
72+
Name: "no phases",
73+
Func: func(t *testing.T, r *ConsoleReporter) {
74+
r.PackageBuildStarted(pkg)
75+
r.PackageBuildFinished(pkg, &PackageBuildReport{
76+
Phases: []PackageBuildPhase{},
77+
})
78+
},
79+
Expect: Expectation{
80+
Output: `[test:test] build started (version unknown)
81+
[test:test] package build succeded (0.00s)
82+
`,
83+
},
84+
},
85+
}
86+
87+
for _, test := range tests {
88+
t.Run(test.Name, func(t *testing.T) {
89+
t.Parallel()
90+
var (
91+
act Expectation
92+
buf bytes.Buffer
93+
)
94+
95+
reporter := NewConsoleReporter()
96+
reporter.out = &buf
97+
reporter.now = func() time.Time {
98+
return start
99+
}
100+
101+
test.Func(t, reporter)
102+
act.Output = buf.String()
103+
104+
if diff := cmp.Diff(test.Expect.Output, act.Output); diff != "" {
105+
t.Errorf("output mismatch (-want +got):\n%s", diff)
106+
}
107+
})
108+
}
109+
}

0 commit comments

Comments
 (0)