Skip to content

Commit df7a13a

Browse files
authored
[WIP] Consolidate duplicate implementations of formatDuration function (#2684)
1 parent 1ac3437 commit df7a13a

File tree

10 files changed

+216
-93
lines changed

10 files changed

+216
-93
lines changed

pkg/cli/audit.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/githubnext/gh-aw/pkg/console"
1616
"github.com/githubnext/gh-aw/pkg/constants"
1717
"github.com/githubnext/gh-aw/pkg/logger"
18+
"github.com/githubnext/gh-aw/pkg/timeutil"
1819
"github.com/githubnext/gh-aw/pkg/workflow"
1920
"github.com/spf13/cobra"
2021
)
@@ -455,7 +456,7 @@ func generateAuditReport(processedRun ProcessedRun, metrics LogMetrics) string {
455456
report.WriteString(fmt.Sprintf("- **Updated**: %s\n", run.UpdatedAt.Format(time.RFC3339)))
456457
}
457458
if run.Duration > 0 {
458-
report.WriteString(fmt.Sprintf("- **Duration**: %s\n", formatDuration(run.Duration)))
459+
report.WriteString(fmt.Sprintf("- **Duration**: %s\n", timeutil.FormatDuration(run.Duration)))
459460
}
460461
report.WriteString(fmt.Sprintf("- **Event**: %s\n", run.Event))
461462
report.WriteString(fmt.Sprintf("- **Branch**: %s\n", run.HeadBranch))
@@ -528,7 +529,7 @@ func generateAuditReport(processedRun ProcessedRun, metrics LogMetrics) string {
528529
}
529530
durationStr := "N/A"
530531
if tool.MaxDuration > 0 {
531-
durationStr = formatDuration(tool.MaxDuration)
532+
durationStr = timeutil.FormatDuration(tool.MaxDuration)
532533
}
533534
report.WriteString(fmt.Sprintf("| %s | %d | %s | %s | %s |\n",
534535
name, tool.CallCount, inputStr, outputStr, durationStr))

pkg/cli/audit_input_size_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ func TestAuditReportIncludesInputSizes(t *testing.T) {
7474
}
7575

7676
// Verify durations are displayed
77-
if !strings.Contains(report, "2s") {
78-
t.Error("Report should contain duration 2s")
77+
if !strings.Contains(report, "2.0s") && !strings.Contains(report, "2s") {
78+
t.Error("Report should contain duration 2.0s or 2s")
7979
}
8080

8181
// Verify the table has the correct number of columns (5: Tool, Calls, Max Input, Max Output, Max Duration)

pkg/cli/audit_report.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/githubnext/gh-aw/pkg/console"
12+
"github.com/githubnext/gh-aw/pkg/timeutil"
1213
"github.com/githubnext/gh-aw/pkg/workflow"
1314
"github.com/githubnext/gh-aw/pkg/workflow/pretty"
1415
)
@@ -114,7 +115,7 @@ func buildAuditData(processedRun ProcessedRun, metrics LogMetrics) AuditData {
114115
URL: run.URL,
115116
}
116117
if run.Duration > 0 {
117-
overview.Duration = formatDuration(run.Duration)
118+
overview.Duration = timeutil.FormatDuration(run.Duration)
118119
}
119120

120121
// Build metrics
@@ -135,7 +136,7 @@ func buildAuditData(processedRun ProcessedRun, metrics LogMetrics) AuditData {
135136
Conclusion: jobDetail.Conclusion,
136137
}
137138
if jobDetail.Duration > 0 {
138-
job.Duration = formatDuration(jobDetail.Duration)
139+
job.Duration = timeutil.FormatDuration(jobDetail.Duration)
139140
}
140141
jobs = append(jobs, job)
141142
}
@@ -174,7 +175,7 @@ func buildAuditData(processedRun ProcessedRun, metrics LogMetrics) AuditData {
174175
existing.MaxOutputSize = toolCall.MaxOutputSize
175176
}
176177
if toolCall.MaxDuration > 0 {
177-
maxDur := formatDuration(toolCall.MaxDuration)
178+
maxDur := timeutil.FormatDuration(toolCall.MaxDuration)
178179
if existing.MaxDuration == "" || toolCall.MaxDuration > parseDurationString(existing.MaxDuration) {
179180
existing.MaxDuration = maxDur
180181
}
@@ -187,7 +188,7 @@ func buildAuditData(processedRun ProcessedRun, metrics LogMetrics) AuditData {
187188
MaxOutputSize: toolCall.MaxOutputSize,
188189
}
189190
if toolCall.MaxDuration > 0 {
190-
info.MaxDuration = formatDuration(toolCall.MaxDuration)
191+
info.MaxDuration = timeutil.FormatDuration(toolCall.MaxDuration)
191192
}
192193
toolStats[displayKey] = info
193194
}

pkg/cli/logs.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/githubnext/gh-aw/pkg/console"
1818
"github.com/githubnext/gh-aw/pkg/constants"
1919
"github.com/githubnext/gh-aw/pkg/logger"
20+
"github.com/githubnext/gh-aw/pkg/timeutil"
2021
"github.com/githubnext/gh-aw/pkg/workflow"
2122
"github.com/githubnext/gh-aw/pkg/workflow/pretty"
2223
"github.com/sourcegraph/conc/pool"
@@ -1708,7 +1709,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) {
17081709
// Format duration
17091710
durationStr := ""
17101711
if run.Duration > 0 {
1711-
durationStr = formatDuration(run.Duration)
1712+
durationStr = timeutil.FormatDuration(run.Duration)
17121713
totalDuration += run.Duration
17131714
}
17141715

@@ -1797,7 +1798,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) {
17971798
fmt.Sprintf("TOTAL (%d runs)", len(processedRuns)),
17981799
"",
17991800
"",
1800-
formatDuration(totalDuration),
1801+
timeutil.FormatDuration(totalDuration),
18011802
formatNumber(totalTokens),
18021803
fmt.Sprintf("%.3f", totalCost),
18031804
fmt.Sprintf("%d", totalTurns),
@@ -1837,17 +1838,6 @@ func ExtractLogMetricsFromRun(processedRun ProcessedRun) workflow.LogMetrics {
18371838
return metrics
18381839
}
18391840

1840-
// formatDuration formats a duration in a human-readable way
1841-
func formatDuration(d time.Duration) string {
1842-
if d < time.Minute {
1843-
return fmt.Sprintf("%.0fs", d.Seconds())
1844-
}
1845-
if d < time.Hour {
1846-
return fmt.Sprintf("%.1fm", d.Minutes())
1847-
}
1848-
return fmt.Sprintf("%.1fh", d.Hours())
1849-
}
1850-
18511841
// formatNumber formats large numbers in a human-readable way (e.g., "1k", "1.2k", "1.12M")
18521842
func formatNumber(n int) string {
18531843
if n == 0 {

pkg/cli/logs_report.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/githubnext/gh-aw/pkg/console"
13+
"github.com/githubnext/gh-aw/pkg/timeutil"
1314
"github.com/githubnext/gh-aw/pkg/workflow"
1415
)
1516

@@ -170,14 +171,14 @@ func buildLogsData(processedRuns []ProcessedRun, outputDir string, continuation
170171
Branch: run.HeadBranch,
171172
}
172173
if run.Duration > 0 {
173-
runData.Duration = formatDuration(run.Duration)
174+
runData.Duration = timeutil.FormatDuration(run.Duration)
174175
}
175176
runs = append(runs, runData)
176177
}
177178

178179
summary := LogsSummary{
179180
TotalRuns: len(processedRuns),
180-
TotalDuration: formatDuration(totalDuration),
181+
TotalDuration: timeutil.FormatDuration(totalDuration),
181182
TotalTokens: totalTokens,
182183
TotalCost: totalCost,
183184
TotalTurns: totalTurns,
@@ -241,7 +242,7 @@ func buildToolUsageSummary(processedRuns []ProcessedRun) []ToolUsageSummary {
241242
existing.MaxOutputSize = toolCall.MaxOutputSize
242243
}
243244
if toolCall.MaxDuration > 0 {
244-
maxDur := formatDuration(toolCall.MaxDuration)
245+
maxDur := timeutil.FormatDuration(toolCall.MaxDuration)
245246
if existing.MaxDuration == "" || toolCall.MaxDuration > parseDurationString(existing.MaxDuration) {
246247
existing.MaxDuration = maxDur
247248
}
@@ -254,7 +255,7 @@ func buildToolUsageSummary(processedRuns []ProcessedRun) []ToolUsageSummary {
254255
Runs: 0, // Will be incremented below
255256
}
256257
if toolCall.MaxDuration > 0 {
257-
info.MaxDuration = formatDuration(toolCall.MaxDuration)
258+
info.MaxDuration = timeutil.FormatDuration(toolCall.MaxDuration)
258259
}
259260
toolStats[displayKey] = info
260261
}

pkg/cli/logs_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"path/filepath"
88
"strings"
99
"testing"
10-
"time"
1110

1211
"github.com/githubnext/gh-aw/pkg/workflow"
1312
"github.com/githubnext/gh-aw/pkg/workflow/pretty"
@@ -38,25 +37,6 @@ func TestDownloadWorkflowLogs(t *testing.T) {
3837
os.RemoveAll("./test-logs")
3938
}
4039

41-
func TestFormatDuration(t *testing.T) {
42-
tests := []struct {
43-
duration time.Duration
44-
expected string
45-
}{
46-
{30 * time.Second, "30s"},
47-
{90 * time.Second, "1.5m"},
48-
{2 * time.Hour, "2.0h"},
49-
{45 * time.Minute, "45.0m"},
50-
}
51-
52-
for _, tt := range tests {
53-
result := formatDuration(tt.duration)
54-
if result != tt.expected {
55-
t.Errorf("formatDuration(%v) = %q, expected %q", tt.duration, result, tt.expected)
56-
}
57-
}
58-
}
59-
6040
func TestFormatNumber(t *testing.T) {
6141
tests := []struct {
6242
input int

pkg/logger/logger.go

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"sync"
99
"time"
1010

11+
"github.com/githubnext/gh-aw/pkg/timeutil"
1112
"github.com/mattn/go-isatty"
1213
)
1314

@@ -106,9 +107,9 @@ func (l *Logger) Printf(format string, args ...interface{}) {
106107

107108
message := fmt.Sprintf(format, args...)
108109
if l.color != "" {
109-
fmt.Fprintf(os.Stderr, "%s%s%s %s +%s\n", l.color, l.namespace, colorReset, message, formatDuration(diff))
110+
fmt.Fprintf(os.Stderr, "%s%s%s %s +%s\n", l.color, l.namespace, colorReset, message, timeutil.FormatDuration(diff))
110111
} else {
111-
fmt.Fprintf(os.Stderr, "%s %s +%s\n", l.namespace, message, formatDuration(diff))
112+
fmt.Fprintf(os.Stderr, "%s %s +%s\n", l.namespace, message, timeutil.FormatDuration(diff))
112113
}
113114
}
114115

@@ -127,32 +128,12 @@ func (l *Logger) Print(args ...interface{}) {
127128

128129
message := fmt.Sprint(args...)
129130
if l.color != "" {
130-
fmt.Fprintf(os.Stderr, "%s%s%s %s +%s\n", l.color, l.namespace, colorReset, message, formatDuration(diff))
131+
fmt.Fprintf(os.Stderr, "%s%s%s %s +%s\n", l.color, l.namespace, colorReset, message, timeutil.FormatDuration(diff))
131132
} else {
132-
fmt.Fprintf(os.Stderr, "%s %s +%s\n", l.namespace, message, formatDuration(diff))
133+
fmt.Fprintf(os.Stderr, "%s %s +%s\n", l.namespace, message, timeutil.FormatDuration(diff))
133134
}
134135
}
135136

136-
// formatDuration formats a duration for display like the debug npm package
137-
func formatDuration(d time.Duration) string {
138-
if d < time.Microsecond {
139-
return fmt.Sprintf("%dns", d.Nanoseconds())
140-
}
141-
if d < time.Millisecond {
142-
return fmt.Sprintf("%dµs", d.Microseconds())
143-
}
144-
if d < time.Second {
145-
return fmt.Sprintf("%dms", d.Milliseconds())
146-
}
147-
if d < time.Minute {
148-
return fmt.Sprintf("%.1fs", d.Seconds())
149-
}
150-
if d < time.Hour {
151-
return fmt.Sprintf("%.1fm", d.Minutes())
152-
}
153-
return fmt.Sprintf("%.1fh", d.Hours())
154-
}
155-
156137
// computeEnabled computes whether a namespace matches the DEBUG patterns
157138
func computeEnabled(namespace string) bool {
158139
patterns := strings.Split(debugEnv, ",")

pkg/logger/logger_test.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -332,30 +332,6 @@ func TestColorDisabling(t *testing.T) {
332332
}
333333
}
334334

335-
func TestFormatDuration(t *testing.T) {
336-
tests := []struct {
337-
name string
338-
duration time.Duration
339-
want string
340-
}{
341-
{"nanoseconds", 500 * time.Nanosecond, "500ns"},
342-
{"microseconds", 500 * time.Microsecond, "500µs"},
343-
{"milliseconds", 500 * time.Millisecond, "500ms"},
344-
{"seconds", 2500 * time.Millisecond, "2.5s"},
345-
{"minutes", 90 * time.Second, "1.5m"},
346-
{"hours", 90 * time.Minute, "1.5h"},
347-
}
348-
349-
for _, tt := range tests {
350-
t.Run(tt.name, func(t *testing.T) {
351-
got := formatDuration(tt.duration)
352-
if got != tt.want {
353-
t.Errorf("formatDuration(%v) = %q, want %q", tt.duration, got, tt.want)
354-
}
355-
})
356-
}
357-
}
358-
359335
func TestMatchPattern(t *testing.T) {
360336
tests := []struct {
361337
name string

pkg/timeutil/format.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package timeutil
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
// FormatDuration formats a duration for display like the debug npm package
9+
// It provides granular formatting from nanoseconds to hours
10+
func FormatDuration(d time.Duration) string {
11+
if d < time.Microsecond {
12+
return fmt.Sprintf("%dns", d.Nanoseconds())
13+
}
14+
if d < time.Millisecond {
15+
return fmt.Sprintf("%dµs", d.Microseconds())
16+
}
17+
if d < time.Second {
18+
return fmt.Sprintf("%dms", d.Milliseconds())
19+
}
20+
if d < time.Minute {
21+
return fmt.Sprintf("%.1fs", d.Seconds())
22+
}
23+
if d < time.Hour {
24+
return fmt.Sprintf("%.1fm", d.Minutes())
25+
}
26+
return fmt.Sprintf("%.1fh", d.Hours())
27+
}

0 commit comments

Comments
 (0)