Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 60 additions & 48 deletions host/host_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,79 +45,91 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) {
return timeSince(ut), nil
}

// Parses result from uptime into minutes
// Some examples of uptime output that this command handles:
// 11:54AM up 13 mins, 1 user, load average: 2.78, 2.62, 1.79
// 12:41PM up 1 hr, 1 user, load average: 2.47, 2.85, 2.83
// 07:43PM up 5 hrs, 1 user, load average: 3.27, 2.91, 2.72
// 11:18:23 up 83 days, 18:29, 4 users, load average: 0.16, 0.03, 0.01
// 08:47PM up 2 days, 20 hrs, 1 user, load average: 2.47, 2.17, 2.17
// 01:16AM up 4 days, 29 mins, 1 user, load average: 2.29, 2.31, 2.21
// Uses ps to get the elapsed time for PID 1 in DAYS-HOURS:MINUTES:SECONDS format.
// Examples of ps -o etimes -p 1 output:
// 124-01:40:39 (with days)
// 15:03:02 (without days, hours only)
// 01:02 (just-rebooted systems, minutes and seconds)
func UptimeWithContext(ctx context.Context) (uint64, error) {
out, err := invoke.CommandWithContext(ctx, "uptime")
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "etimes", "-p", "1")
if err != nil {
return 0, err
}

return parseUptime(string(out)), nil
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
if len(lines) < 2 {
return 0, errors.New("ps output has fewer than 2 rows")
}

// Extract the etimes value from the second row, trimming whitespace
etimes := strings.TrimSpace(lines[1])
return parseUptime(etimes), nil
}

func parseUptime(uptime string) uint64 {
ut := strings.Fields(uptime)
var days, hours, mins uint64
var err error
// Parses etimes output from ps command into total minutes.
// Handles formats like:
// - "124-01:40:39" (DAYS-HOURS:MINUTES:SECONDS)
// - "15:03:02" (HOURS:MINUTES:SECONDS)
// - "01:02" (MINUTES:SECONDS, from just-rebooted systems)
func parseUptime(etimes string) uint64 {
var days, hours, mins, secs uint64

// Check if days component is present (contains a dash)
if strings.Contains(etimes, "-") {
parts := strings.Split(etimes, "-")
if len(parts) != 2 {
return 0
}

switch ut[3] {
case "day,", "days,":
days, err = strconv.ParseUint(ut[2], 10, 64)
var err error
days, err = strconv.ParseUint(parts[0], 10, 64)
if err != nil {
return 0
}

// day provided along with a single hour or hours
// ie: up 2 days, 20 hrs,
if ut[5] == "hr," || ut[5] == "hrs," {
hours, err = strconv.ParseUint(ut[4], 10, 64)
if err != nil {
return 0
}
// Parse the HH:MM:SS portion
etimes = parts[1]
}

// Parse time portions (either HH:MM:SS or MM:SS)
timeParts := strings.Split(etimes, ":")
switch len(timeParts) {
case 3:
// HH:MM:SS format
var err error
hours, err = strconv.ParseUint(timeParts[0], 10, 64)
if err != nil {
return 0
}

// mins provided along with a single min or mins
// ie: up 4 days, 29 mins,
if ut[5] == "min," || ut[5] == "mins," {
mins, err = strconv.ParseUint(ut[4], 10, 64)
if err != nil {
return 0
}
mins, err = strconv.ParseUint(timeParts[1], 10, 64)
if err != nil {
return 0
}

// alternatively day provided with hh:mm
// ie: up 83 days, 18:29
if strings.Contains(ut[4], ":") {
hm := strings.Split(ut[4], ":")
hours, err = strconv.ParseUint(hm[0], 10, 64)
if err != nil {
return 0
}
mins, err = strconv.ParseUint(strings.Trim(hm[1], ","), 10, 64)
if err != nil {
return 0
}
secs, err = strconv.ParseUint(timeParts[2], 10, 64)
if err != nil {
return 0
}
case "hr,", "hrs,":
hours, err = strconv.ParseUint(ut[2], 10, 64)
case 2:
// MM:SS format (just-rebooted systems)
var err error
mins, err = strconv.ParseUint(timeParts[0], 10, 64)
if err != nil {
return 0
}
case "min,", "mins,":
mins, err = strconv.ParseUint(ut[2], 10, 64)

secs, err = strconv.ParseUint(timeParts[1], 10, 64)
if err != nil {
return 0
}
default:
return 0
}

return (days * 24 * 60) + (hours * 60) + mins
// Convert to total minutes
totalMinutes := (days * 24 * 60) + (hours * 60) + mins + (secs / 60)
return totalMinutes
}

// This is a weak implementation due to the limitations on retrieving this data in AIX
Expand Down
28 changes: 19 additions & 9 deletions host/host_aix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@ func TestParseUptimeValidInput(t *testing.T) {
input string
expected uint64
}{
{"11:54AM up 13 mins, 1 user, load average: 2.78, 2.62, 1.79", 13},
{"12:41PM up 1 hr, 1 user, load average: 2.47, 2.85, 2.83", 60},
{"07:43PM up 5 hrs, 1 user, load average: 3.27, 2.91, 2.72", 300},
{"11:18:23 up 83 days, 18:29, 4 users, load average: 0.16, 0.03, 0.01", 120629},
{"08:47PM up 2 days, 20 hrs, 1 user, load average: 2.47, 2.17, 2.17", 4080},
{"01:16AM up 4 days, 29 mins, 1 user, load average: 2.29, 2.31, 2.21", 5789},
// Format: MINUTES:SECONDS (just-rebooted systems, hours dropped when 0)
{"00:13", 0}, // 13 seconds rounds down to 0 minutes
{"01:00", 1}, // 1 minute
{"01:02", 1}, // 1 minute, 2 seconds
Comment thread
Dylan-M marked this conversation as resolved.
// Format: HOURS:MINUTES:SECONDS (no days, hours > 0)
{"01:00:00", 60}, // 1 hour
{"05:00:00", 300}, // 5 hours
{"15:03:02", 903}, // 15 hours, 3 minutes, 2 seconds
// Format: DAYS-HOURS:MINUTES:SECONDS (with days)
{"2-20:00:00", 4080}, // 2 days, 20 hours
{"4-00:29:00", 5789}, // 4 days, 29 minutes
{"83-18:29:00", 120629}, // 83 days, 18 hours, 29 minutes
{"124-01:40:39", 178660}, // 124 days, 1 hour, 40 minutes, 39 seconds
}
for _, tc := range testCases {
got := parseUptime(tc.input)
Expand All @@ -29,9 +36,12 @@ func TestParseUptimeValidInput(t *testing.T) {

func TestParseUptimeInvalidInput(t *testing.T) {
testCases := []string{
"", // blank
"2x", // invalid string
"150", // integer
"", // blank
"invalid", // invalid string
"01:02", // incomplete time format (missing seconds)
"1-2:3", // incomplete time format after dash
"abc-01:02:03", // non-numeric days
"1-ab:02:03", // non-numeric hours
}

for _, tc := range testCases {
Expand Down