Skip to content

Commit

Permalink
New pretty printer that imitates the short form from time.Duration.St…
Browse files Browse the repository at this point in the history
…ring() with approximated values for days and beyond. Used in cli output for allocation create/modify times
  • Loading branch information
Preetha Appan authored and schmichael committed Oct 30, 2017
1 parent 46e2e03 commit e8d9178
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 15 deletions.
11 changes: 9 additions & 2 deletions command/alloc_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,15 @@ func (c *AllocStatusCommand) Run(args []string) int {
}

func formatAllocBasicInfo(alloc *api.Allocation, client *api.Client, uuidLength int, verbose bool) (string, error) {
formattedCreateTime := formatTimePretty(time.Unix(0, alloc.CreateTime), time.Now())
formattedModifyTime := formatTimePretty(time.Unix(0, alloc.ModifyTime), time.Now())
var formattedCreateTime, formattedModifyTime string

if verbose {
formattedCreateTime = formatUnixNanoTime(alloc.CreateTime)
formattedModifyTime = formatUnixNanoTime(alloc.ModifyTime)
} else {
formattedCreateTime = prettyTimeDiff(time.Unix(0, alloc.CreateTime), time.Now())
formattedModifyTime = prettyTimeDiff(time.Unix(0, alloc.ModifyTime), time.Now())
}

basic := []string{
fmt.Sprintf("ID|%s", limit(alloc.ID, uuidLength)),
Expand Down
17 changes: 11 additions & 6 deletions command/alloc_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,14 @@ func TestAllocStatusCommand_Run(t *testing.T) {
t.Fatalf("expected exit 0, got: %d", code)
}
out := ui.OutputWriter.String()
if !strings.Contains(out, "Created At") {
t.Fatalf("expected to have 'Created At' but saw: %s", out)
if !strings.Contains(out, "Created") {
t.Fatalf("expected to have 'Created' but saw: %s", out)
}

if !strings.Contains(out, "Modified") {
t.Fatalf("expected to have 'Modified' but saw: %s", out)
}

ui.OutputWriter.Reset()

if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 {
Expand All @@ -140,8 +145,8 @@ func TestAllocStatusCommand_Run(t *testing.T) {
if !strings.Contains(out, allocId1) {
t.Fatal("expected to find alloc id in output")
}
if !strings.Contains(out, "Created At") {
t.Fatalf("expected to have 'Created At' but saw: %s", out)
if !strings.Contains(out, "Created") {
t.Fatalf("expected to have 'Created' but saw: %s", out)
}
ui.OutputWriter.Reset()

Expand All @@ -150,8 +155,8 @@ func TestAllocStatusCommand_Run(t *testing.T) {
t.Fatalf("expected exit 0, got: %d", code)
}
out = ui.OutputWriter.String()
if !strings.Contains(out, "Created At") {
t.Fatalf("expected to have 'Created At' but saw: %s", out)
if !strings.Contains(out, "Created") {
t.Fatalf("expected to have 'Created' but saw: %s", out)
}
ui.OutputWriter.Reset()

Expand Down
111 changes: 108 additions & 3 deletions command/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,114 @@ func formatTimeDifference(first, second time.Time, d time.Duration) string {
return second.Truncate(d).Sub(first.Truncate(d)).String()
}

// formatTimePretty rounds off time difference to the nearest second for nicer display
func formatTimePretty(first, second time.Time) string {
return formatTimeDifference(first.Round(time.Second), second.Round(time.Second), time.Second) + " ago"
// fmtInt formats v into the tail of buf.
// It returns the index where the output begins.
func fmtInt(buf []byte, v uint64) int {
w := len(buf)
for v > 0 {
w--
buf[w] = byte(v%10) + '0'
v /= 10
}
return w
}

// prettyTimeDiff prints a human readable time difference.
// It uses abbreviated forms for each period - s for seconds, m for minutes, h for hours,
// d for days, mo for months, and y for years. Time difference is rounded to the nearest second,
// and the top two least granular periods are returned. For example, if the time difference
// is 10 months, 12 days, 3 hours and 2 seconds, the string "10mo12d" is returned. Zero values return the empty string
func prettyTimeDiff(first, second time.Time) string {
// handle zero values
if first.Second() == 0 {
return ""
}
// round to the nearest second
first = first.Round(time.Second)
second = second.Round(time.Second)

// calculate time difference in seconds
d := second.Sub(first)
u := uint64(d.Seconds())

var buf [32]byte
w := len(buf)
secs := u % 60

// track indexes of various periods
var indexes []int

if secs > 0 {
w--
buf[w] = 's'
// u is now seconds
w = fmtInt(buf[:w], secs)
indexes = append(indexes, w)
}
u /= 60
// u is now minutes
if u > 0 {
mins := u % 60
if mins > 0 {
w--
buf[w] = 'm'
w = fmtInt(buf[:w], mins)
indexes = append(indexes, w)
}
u /= 60
// u is now hours
if u > 0 {
hrs := u % 24
if hrs > 0 {
w--
buf[w] = 'h'
w = fmtInt(buf[:w], hrs)
indexes = append(indexes, w)
}
u /= 24
}
// u is now days
if u > 0 {
days := u % 30
if days > 0 {
w--
buf[w] = 'd'
w = fmtInt(buf[:w], days)
indexes = append(indexes, w)
}
u /= 30
}
// u is now months
if u > 0 {
months := u % 12
if months > 0 {
w--
buf[w] = 'o'
w--
buf[w] = 'm'
w = fmtInt(buf[:w], months)
indexes = append(indexes, w)
}
u /= 12
}
// u is now years
if u > 0 {
w--
buf[w] = 'y'
w = fmtInt(buf[:w], u)
indexes = append(indexes, w)
}
}
start := w
end := len(buf)

// truncate to the first two periods
num_periods := len(indexes)
if num_periods > 2 {
end = indexes[num_periods-3]
}
return string(buf[start:end]) + " ago"

}

// getLocalNodeID returns the node ID of the local Nomad Client and an error if
Expand Down
32 changes: 32 additions & 0 deletions command/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,35 @@ func TestJobGetter_HTTPServer(t *testing.T) {
t.Fatalf("Unexpected file")
}
}

func TestPrettyTimeDiff(t *testing.T) {
test_cases := []struct {
d time.Duration
exp string
}{
{-740 * time.Second, "12m20s ago"},
{-12 * time.Minute, "12m ago"},
{-60 * time.Minute, "1h ago"},
{-80 * time.Minute, "1h20m ago"},
{-6 * time.Hour, "6h ago"},
{-22165 * time.Second, "6h9m ago"},
{-100 * time.Hour, "4d4h ago"},
{-438000 * time.Minute, "10mo4d ago"},
{-20460 * time.Hour, "2y4mo ago"},
}
for _, tc := range test_cases {
t2 := time.Now().Add(tc.d)
out := prettyTimeDiff(t2, time.Now())
if out != tc.exp {
t.Fatalf("expected :%v but got :%v", tc.exp, out)
}
}

var t1 time.Time
out := prettyTimeDiff(t1, time.Now())

if out != "" {
t.Fatalf("Expected empty output but got:%v", out)
}

}
6 changes: 3 additions & 3 deletions command/job_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func formatAllocListStubs(stubs []*api.AllocationListStub, verbose bool, uuidLen

allocs := make([]string, len(stubs)+1)
if verbose {
allocs[0] = "ID|Eval ID|Node ID|Task Group|Version|Desired|Status|Created At|Modified At"
allocs[0] = "ID|Eval ID|Node ID|Task Group|Version|Desired|Status|Created|Modified"
for i, alloc := range stubs {
allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%d|%s|%s|%s|%s",
limit(alloc.ID, uuidLength),
Expand All @@ -422,8 +422,8 @@ func formatAllocListStubs(stubs []*api.AllocationListStub, verbose bool, uuidLen
} else {
allocs[0] = "ID|Node ID|Task Group|Version|Desired|Status|Created|Modified"
for i, alloc := range stubs {
createTimePretty := formatTimePretty(time.Unix(0, alloc.CreateTime), time.Now())
modTimePretty := formatTimePretty(time.Unix(0, alloc.ModifyTime), time.Now())
createTimePretty := prettyTimeDiff(time.Unix(0, alloc.CreateTime), time.Now())
modTimePretty := prettyTimeDiff(time.Unix(0, alloc.ModifyTime), time.Now())
allocs[i+1] = fmt.Sprintf("%s|%s|%s|%d|%s|%s|%s|%s",
limit(alloc.ID, uuidLength),
limit(alloc.NodeID, uuidLength),
Expand Down
13 changes: 12 additions & 1 deletion command/job_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,12 @@ func TestJobStatusCommand_Run(t *testing.T) {
if !strings.Contains(out, "Allocations") {
t.Fatalf("should dump allocations")
}
if !strings.Contains(out, "Created At") {
if !strings.Contains(out, "Created") {
t.Fatal("should have created header")
}
if !strings.Contains(out, "Modified") {
t.Fatal("should have modified header")
}
ui.ErrorWriter.Reset()
ui.OutputWriter.Reset()

Expand All @@ -138,6 +141,14 @@ func TestJobStatusCommand_Run(t *testing.T) {
if !strings.Contains(out, "job1_sfx") || strings.Contains(out, "job2_sfx") {
t.Fatalf("expected only job1_sfx, got: %s", out)
}

if !strings.Contains(out, "Created") {
t.Fatal("should have created header")
}

if !strings.Contains(out, "Modified") {
t.Fatal("should have modified header")
}
ui.OutputWriter.Reset()

// Query in short view mode
Expand Down

0 comments on commit e8d9178

Please sign in to comment.