From 42957707db5dd49d0bd54fa3cb0152d308a6c8e5 Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 6 Jun 2024 12:39:37 +1200 Subject: [PATCH 1/9] Return error if hours not in expected format to prevent panic --- host/host_aix.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/host/host_aix.go b/host/host_aix.go index 7d66666..b1eb460 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -65,6 +65,7 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { var days uint64 = 0 var hours uint64 = 0 var minutes uint64 = 0 + if ut[3] == "day," || ut[3] == "days," { days, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { @@ -73,14 +74,20 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { // Split field 4 into hours and minutes hm := strings.Split(ut[4], ":") + if len(hm) < 2 { + return 0, fmt.Errorf("expected 'hours:minutes,' format but got '%s'", ut[4]) + } + hours, err = strconv.ParseUint(hm[0], 10, 64) if err != nil { return 0, err } + minutes, err = strconv.ParseUint(strings.Replace(hm[1], ",", "", -1), 10, 64) if err != nil { return 0, err } + } else if ut[3] == "hr," || ut[3] == "hrs," { hours, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { From 2b6d0754ed64dac0b54891a965a893ef5540c927 Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 6 Jun 2024 12:42:03 +1200 Subject: [PATCH 2/9] Comments --- host/host_aix.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/host/host_aix.go b/host/host_aix.go index b1eb460..55b5fbf 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -78,11 +78,13 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { return 0, fmt.Errorf("expected 'hours:minutes,' format but got '%s'", ut[4]) } + // Parse hours hours, err = strconv.ParseUint(hm[0], 10, 64) if err != nil { return 0, err } + // Remove trailing comma from minutes and parse minutes, err = strconv.ParseUint(strings.Replace(hm[1], ",", "", -1), 10, 64) if err != nil { return 0, err From 789cad37bed686c6f447fab8161c87bc77ab4cdd Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 6 Jun 2024 14:35:05 +1200 Subject: [PATCH 3/9] Correctly parse new uptime case at zero minutes, has been tested against all test cases --- host/host_aix.go | 59 +++++++++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/host/host_aix.go b/host/host_aix.go index 55b5fbf..6c3c085 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -52,6 +52,7 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { // 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 func UptimeWithContext(ctx context.Context) (uint64, error) { out, err := invoke.CommandWithContext(ctx, "uptime") if err != nil { @@ -61,62 +62,50 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { // Convert our uptime to a series of fields we can extract ut := strings.Fields(string(out[:])) - // Convert the second field value to integer - var days uint64 = 0 - var hours uint64 = 0 - var minutes uint64 = 0 + // Initialise variables + var days, hours, minutes uint64 + // Check if days are specified and parse them if ut[3] == "day," || ut[3] == "days," { days, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { return 0, err } + } - // Split field 4 into hours and minutes - hm := strings.Split(ut[4], ":") - if len(hm) < 2 { - return 0, fmt.Errorf("expected 'hours:minutes,' format but got '%s'", ut[4]) - } - - // Parse hours - hours, err = strconv.ParseUint(hm[0], 10, 64) - if err != nil { - return 0, err - } - - // Remove trailing comma from minutes and parse - minutes, err = strconv.ParseUint(strings.Replace(hm[1], ",", "", -1), 10, 64) - if err != nil { - return 0, err - } - - } else if ut[3] == "hr," || ut[3] == "hrs," { + // Identify and parse hours and minutes based on the format present + switch { + case ut[4] == "hrs," || ut[4] == "hr,": + // Only hours are given, and they are placed in ut[4] hours, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { return 0, err } - } else if ut[3] == "mins," { - minutes, err = strconv.ParseUint(ut[2], 10, 64) - if err != nil { - return 0, err - } - } else if _, err := strconv.ParseInt(ut[3], 10, 64); err == nil && strings.Contains(ut[2], ":") { - // Split field 2 into hours and minutes - hm := strings.Split(ut[2], ":") + case strings.Contains(ut[4], ":"): + // Hours and minutes are specified in the format "hours:minutes" + hm := strings.Split(ut[4], ":") hours, err = strconv.ParseUint(hm[0], 10, 64) if err != nil { return 0, err } - minutes, err = strconv.ParseUint(strings.Replace(hm[1], ",", "", -1), 10, 64) + minutes, err = strconv.ParseUint(strings.Trim(hm[1], ","), 10, 64) if err != nil { return 0, err } + default: + // No explicit hours or "hours:minutes" format after days, check if minutes are given directly + if ut[3] == "mins," { + minutes, err = strconv.ParseUint(ut[2], 10, 64) + if err != nil { + return 0, err + } + } } - // Stack them all together as minutes - total_time := (days * 24 * 60) + (hours * 60) + minutes + // Calculate total time in minutes + totalTime := (days * 24 * 60) + (hours * 60) + minutes - return total_time, nil + return totalTime, nil } // This is a weak implementation due to the limitations on retrieving this data in AIX From 8f3d0d0c57ffadec3a2fd2c54dcc7578e641a8c8 Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 6 Jun 2024 14:36:40 +1200 Subject: [PATCH 4/9] Correctly parse new uptime case at zero minutes, has been tested against all test cases --- host/host_aix.go | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/host/host_aix.go b/host/host_aix.go index 6c3c085..d665609 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -63,7 +63,7 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { ut := strings.Fields(string(out[:])) // Initialise variables - var days, hours, minutes uint64 + var days, hours, mins uint64 // Check if days are specified and parse them if ut[3] == "day," || ut[3] == "days," { @@ -73,39 +73,53 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { } } - // Identify and parse hours and minutes based on the format present switch { - case ut[4] == "hrs," || ut[4] == "hr,": - // Only hours are given, and they are placed in ut[4] - hours, err = strconv.ParseUint(ut[2], 10, 64) + case ut[3] == "day," || ut[3] == "days,": + days, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { return 0, err } - case strings.Contains(ut[4], ":"): - // Hours and minutes are specified in the format "hours:minutes" - hm := strings.Split(ut[4], ":") - hours, err = strconv.ParseUint(hm[0], 10, 64) + + // 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, err + } + } + + // 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, err + } + mins, err = strconv.ParseUint(strings.Trim(hm[1], ","), 10, 64) + if err != nil { + return 0, err + } + } + case ut[3] == "hr," || ut[3] == "hrs,": + hours, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { return 0, err } - minutes, err = strconv.ParseUint(strings.Trim(hm[1], ","), 10, 64) + case ut[3] == "mins," || ut[3] == "mins,": + mins, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { return 0, err } - default: - // No explicit hours or "hours:minutes" format after days, check if minutes are given directly - if ut[3] == "mins," { - minutes, err = strconv.ParseUint(ut[2], 10, 64) - if err != nil { - return 0, err - } - } } + total_time := (days * 24 * 60) + (hours * 60) + mins + // Calculate total time in minutes - totalTime := (days * 24 * 60) + (hours * 60) + minutes + log.Println("days / hrs / mins ", days, hours, mins, total_time) - return totalTime, nil + return total_time, nil } // This is a weak implementation due to the limitations on retrieving this data in AIX From bcde3cb2409cc7433471740835048cf713fb0d7e Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 6 Jun 2024 14:39:57 +1200 Subject: [PATCH 5/9] Removed logging line --- host/host_aix.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/host/host_aix.go b/host/host_aix.go index d665609..2034f71 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -114,10 +114,8 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { } } - total_time := (days * 24 * 60) + (hours * 60) + mins - // Calculate total time in minutes - log.Println("days / hrs / mins ", days, hours, mins, total_time) + total_time := (days * 24 * 60) + (hours * 60) + mins return total_time, nil } From b1ddeddda532f09f00fb7a83dc0b83d41a41dc93 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 7 Jun 2024 11:45:08 +1200 Subject: [PATCH 6/9] Removed repeated logic for days --- host/host_aix.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/host/host_aix.go b/host/host_aix.go index 2034f71..011801a 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -65,14 +65,6 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { // Initialise variables var days, hours, mins uint64 - // Check if days are specified and parse them - if ut[3] == "day," || ut[3] == "days," { - days, err = strconv.ParseUint(ut[2], 10, 64) - if err != nil { - return 0, err - } - } - switch { case ut[3] == "day," || ut[3] == "days,": days, err = strconv.ParseUint(ut[2], 10, 64) From b206a02af4c32224479d020ee91b7385307672e6 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 7 Jun 2024 15:12:47 +1200 Subject: [PATCH 7/9] Added tests for uptimer parser --- host/host_aix.go | 28 +++++++++++++--------------- host/host_aix_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 host/host_aix_test.go diff --git a/host/host_aix.go b/host/host_aix.go index 011801a..5faa1ed 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -45,8 +45,7 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { return timeSince(ut), nil } -// This function takes multiple formats of output frmo the uptime -// command and converts the data into minutes. +// 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 @@ -59,17 +58,19 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { return 0, err } - // Convert our uptime to a series of fields we can extract - ut := strings.Fields(string(out[:])) + return parseUptime(string(out[:])), nil +} - // Initialise variables +func parseUptime(uptime string) uint64 { + ut := strings.Fields(uptime) var days, hours, mins uint64 + var err error switch { case ut[3] == "day," || ut[3] == "days,": days, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { - return 0, err + return 0 } // day provided along with a single hour or hours @@ -77,7 +78,7 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { if ut[5] == "hr," || ut[5] == "hrs," { hours, err = strconv.ParseUint(ut[4], 10, 64) if err != nil { - return 0, err + return 0 } } @@ -87,29 +88,26 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { hm := strings.Split(ut[4], ":") hours, err = strconv.ParseUint(hm[0], 10, 64) if err != nil { - return 0, err + return 0 } mins, err = strconv.ParseUint(strings.Trim(hm[1], ","), 10, 64) if err != nil { - return 0, err + return 0 } } case ut[3] == "hr," || ut[3] == "hrs,": hours, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { - return 0, err + return 0 } case ut[3] == "mins," || ut[3] == "mins,": mins, err = strconv.ParseUint(ut[2], 10, 64) if err != nil { - return 0, err + return 0 } } - // Calculate total time in minutes - total_time := (days * 24 * 60) + (hours * 60) + mins - - return total_time, nil + return (days * 24 * 60) + (hours * 60) + mins } // This is a weak implementation due to the limitations on retrieving this data in AIX diff --git a/host/host_aix_test.go b/host/host_aix_test.go new file mode 100644 index 0000000..713d26f --- /dev/null +++ b/host/host_aix_test.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build aix + +package host + +import ( + "testing" +) + +func TestParseUptimeValidInput(t *testing.T) { + testCases := []struct { + input string + expected uint64 + }{ + {"11:54AM up 13 mins, 1 user, load average: 2.78, 2.62, 1.79", 10}, + {"12:41PM up 1 hr, 1 user, load average: 2.47, 2.85, 2.83", 180}, + {"07:43PM up 5 hrs, 1 user, load average: 3.27, 2.91, 2.72", 2}, + {"11:18:23 up 83 days, 18:29, 4 users, load average: 0.16, 0.03, 0.01", 2}, + {"08:47PM up 2 days, 20 hrs, 1 user, load average: 2.47, 2.17, 2.17", 2}, + } + for _, tc := range testCases { + got := parseUptime(tc.input) + if got != tc.expected { + t.Errorf("parseUptime(%q) = %v, want %v", tc.input, got, tc.expected) + } + } +} + +func TestParseUptimeInvalidInput(t *testing.T) { + testCases := []string{ + "", // blank + "2x", // invalid string + "150", // integer + } + + for _, tc := range testCases { + got := parseUptime(tc) + if got > 0 { + t.Errorf("parseUptime(%q) expected zero to be returned, received %v", tc, got) + } + } +} From 4ea639f0f87fd5f5f22872f722b761a1147d36b5 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 7 Jun 2024 17:33:35 +1200 Subject: [PATCH 8/9] Incorrect test comparisons --- host/host_aix_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/host/host_aix_test.go b/host/host_aix_test.go index 713d26f..16da879 100644 --- a/host/host_aix_test.go +++ b/host/host_aix_test.go @@ -12,11 +12,11 @@ func TestParseUptimeValidInput(t *testing.T) { input string expected uint64 }{ - {"11:54AM up 13 mins, 1 user, load average: 2.78, 2.62, 1.79", 10}, - {"12:41PM up 1 hr, 1 user, load average: 2.47, 2.85, 2.83", 180}, - {"07:43PM up 5 hrs, 1 user, load average: 3.27, 2.91, 2.72", 2}, - {"11:18:23 up 83 days, 18:29, 4 users, load average: 0.16, 0.03, 0.01", 2}, - {"08:47PM up 2 days, 20 hrs, 1 user, load average: 2.47, 2.17, 2.17", 2}, + {"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}, } for _, tc := range testCases { got := parseUptime(tc.input) From dbab8d86e9dffe4f634621fc98f4a0e1bdd7a634 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 7 Jun 2024 18:20:29 +1200 Subject: [PATCH 9/9] Additional error case found --- host/host_aix.go | 12 +++++++++++- host/host_aix_test.go | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/host/host_aix.go b/host/host_aix.go index 5faa1ed..4358b61 100644 --- a/host/host_aix.go +++ b/host/host_aix.go @@ -52,6 +52,7 @@ func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { // 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 func UptimeWithContext(ctx context.Context) (uint64, error) { out, err := invoke.CommandWithContext(ctx, "uptime") if err != nil { @@ -74,7 +75,7 @@ func parseUptime(uptime string) uint64 { } // day provided along with a single hour or hours - // ie: up 2 days, 20 hrs + // ie: up 2 days, 20 hrs, if ut[5] == "hr," || ut[5] == "hrs," { hours, err = strconv.ParseUint(ut[4], 10, 64) if err != nil { @@ -82,6 +83,15 @@ func parseUptime(uptime string) uint64 { } } + // 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 + } + } + // alternatively day provided with hh:mm // ie: up 83 days, 18:29 if strings.Contains(ut[4], ":") { diff --git a/host/host_aix_test.go b/host/host_aix_test.go index 16da879..3d497ff 100644 --- a/host/host_aix_test.go +++ b/host/host_aix_test.go @@ -17,6 +17,7 @@ func TestParseUptimeValidInput(t *testing.T) { {"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}, } for _, tc := range testCases { got := parseUptime(tc.input)