-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allows querying the app workloads for CPU and memory metrics within a specified timeframe (since a duration (e.g. "1h") or a specific timestamp (e.g. "2024-06-06T12:00:00Z")
- Loading branch information
Showing
10 changed files
with
291 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package status | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"time" | ||
) | ||
|
||
const ( | ||
hoursPerDay int = 24 | ||
minutesPerHour int = 60 | ||
secondsPerMinute int = 60 | ||
) | ||
|
||
func humanizeDuration(since time.Duration) string { | ||
hours := int(math.Floor(since.Hours())) | ||
if hours > hoursPerDay { | ||
fullDays := hours / hoursPerDay | ||
remainingHours := hours % hoursPerDay | ||
if remainingHours > 0 { | ||
return fmt.Sprintf("%d days and %d hours", fullDays, remainingHours) | ||
} else { | ||
return fmt.Sprintf("%d days", fullDays) | ||
} | ||
} | ||
|
||
minutes := int(math.Floor(since.Minutes())) | ||
if hours > 1 { | ||
fullHours := hours | ||
remainingMinutes := minutes % minutesPerHour | ||
if fullHours > 0 { | ||
return fmt.Sprintf("%d hours and %d minutes", fullHours, remainingMinutes) | ||
} | ||
} | ||
|
||
seconds := int(math.Round(since.Seconds())) | ||
if minutes > 1 { | ||
fullMinutes := minutes | ||
remainingSeconds := seconds % secondsPerMinute | ||
if fullMinutes > 0.0 { | ||
return fmt.Sprintf("%d minutes and %d seconds", fullMinutes, remainingSeconds) | ||
} | ||
} | ||
|
||
return fmt.Sprintf("%d seconds", seconds) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package status | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestHumanizeDuration(t *testing.T) { | ||
type testCase struct { | ||
name string | ||
duration time.Duration | ||
expected string | ||
} | ||
|
||
for _, tc := range []testCase{ | ||
{ | ||
name: "seconds only", | ||
duration: 5 * time.Second, | ||
expected: "5 seconds", | ||
}, | ||
{ | ||
name: "seconds are rounded", | ||
duration: 7*time.Second + 10*time.Millisecond + 20*time.Microsecond, | ||
expected: "7 seconds", | ||
}, | ||
{ | ||
name: "minutes and seconds", | ||
duration: 123 * time.Second, | ||
expected: "2 minutes and 3 seconds", | ||
}, | ||
{ | ||
name: "hours and minutes", | ||
duration: 123 * time.Minute, | ||
expected: "2 hours and 3 minutes", | ||
}, | ||
{ | ||
name: "days and hours", | ||
duration: 50 * time.Hour, | ||
expected: "2 days and 2 hours", | ||
}, | ||
} { | ||
actual := humanizeDuration(tc.duration) | ||
assert.Equal(t, tc.expected, actual) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package status | ||
|
||
import ( | ||
"errors" | ||
"regexp" | ||
"strconv" | ||
"time" | ||
) | ||
|
||
type Since time.Time | ||
|
||
func (s *Since) String() string { | ||
t := time.Time(*s) | ||
return t.Format(time.RFC3339) | ||
} | ||
|
||
func (s *Since) Set(v string) error { | ||
since, err := parseSince(v, time.Now()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
*s = *since | ||
|
||
return nil | ||
} | ||
|
||
func (*Since) Type() string { | ||
return "Since time" | ||
} | ||
|
||
func (s *Since) Time() *time.Time { | ||
if s == nil { | ||
return nil | ||
} | ||
|
||
if time.Time(*s).IsZero() { | ||
return nil | ||
} | ||
|
||
t := time.Time(*s) | ||
|
||
return &t | ||
} | ||
|
||
var errParseSince = errors.New("could not parse since value") | ||
|
||
func parseSince(value string, now time.Time) (*Since, error) { | ||
// Try supported timestamp formats | ||
for _, format := range []string{time.RFC3339, time.DateOnly} { | ||
if parsed, err := time.Parse(format, value); err == nil { | ||
since := Since(parsed) | ||
return &since, nil | ||
} | ||
} | ||
|
||
// Try relative time specifications | ||
relTimePat := regexp.MustCompile(`^([1-9][0-9]*)(d|h|m|s)$`) | ||
matches := relTimePat.FindStringSubmatch(value) | ||
if len(matches) != 3 { // nolint:mnd | ||
return nil, errParseSince | ||
} | ||
|
||
matchedValue, err := strconv.Atoi(matches[1]) | ||
if err != nil { | ||
return nil, errParseSince | ||
} | ||
|
||
unitDuration, err := sinceUnitDuration(matches[2]) | ||
if err != nil { | ||
return nil, errParseSince | ||
} | ||
|
||
result := Since(now.Add(-time.Duration(matchedValue) * unitDuration)) | ||
|
||
return &result, nil | ||
} | ||
|
||
func sinceUnitDuration(unit string) (time.Duration, error) { | ||
switch unit { | ||
case "d": | ||
return time.Hour * 24, nil // nolint:mnd | ||
case "h": | ||
return time.Hour, nil | ||
case "m": | ||
return time.Minute, nil | ||
case "s": | ||
return time.Second, nil | ||
default: | ||
return 0, errParseSince | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package status | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSince(t *testing.T) { | ||
t.Run("Set", func(t *testing.T) { | ||
t.Run("sets value", func(t *testing.T) { | ||
var actual Since | ||
|
||
err := actual.Set("2024-01-01T10:11:12Z") | ||
|
||
assert.NoError(t, err) | ||
expected := time.Date(2024, time.January, 1, 10, 11, 12, 0, time.UTC) | ||
assert.Equal(t, Since(expected), actual) | ||
}) | ||
|
||
t.Run("returns error", func(t *testing.T) { | ||
var actual Since | ||
|
||
err := actual.Set("invalid-since-value") | ||
|
||
assert.ErrorIs(t, err, errParseSince) | ||
}) | ||
}) | ||
} | ||
|
||
func TestParseSince(t *testing.T) { | ||
now := time.Date(2024, time.January, 1, 12, 0, 0, 0, time.UTC) | ||
type testCase struct { | ||
value string | ||
expected time.Time | ||
} | ||
|
||
for _, tc := range []testCase{ | ||
{value: "1h", expected: now.Add(-time.Hour)}, | ||
{value: "123h", expected: now.Add(-123 * time.Hour)}, | ||
{value: "5d", expected: now.Add(-5 * 24 * time.Hour)}, | ||
{value: "1000d", expected: now.Add(-1000 * 24 * time.Hour)}, | ||
{value: "3m", expected: now.Add(-3 * time.Minute)}, | ||
{value: "120m", expected: now.Add(-120 * time.Minute)}, | ||
{value: "2s", expected: now.Add(-2 * time.Second)}, | ||
{value: "8600s", expected: now.Add(-8600 * time.Second)}, | ||
{value: "2024-01-01T10:11:12Z", expected: time.Date(2024, time.January, 1, 10, 11, 12, 0, time.UTC)}, | ||
{value: "2024-01-01T10:11:12+02:00", expected: time.Date(2024, time.January, 1, 10, 11, 12, 0, time.FixedZone("", int((2*time.Hour).Seconds())))}, | ||
{value: "2024-01-01", expected: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)}, | ||
} { | ||
t.Run(tc.value, func(t *testing.T) { | ||
actual, err := parseSince(tc.value, now) | ||
|
||
assert.NoError(t, err) | ||
if assert.NotNil(t, actual) { | ||
assert.Equal(t, Since(tc.expected), *actual) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.