diff --git a/pkg/kubelet/util/boottime_util_linux.go b/pkg/kubelet/util/boottime_util_linux.go index b48ef392eeb..935b3d80341 100644 --- a/pkg/kubelet/util/boottime_util_linux.go +++ b/pkg/kubelet/util/boottime_util_linux.go @@ -21,17 +21,54 @@ package util import ( "fmt" + "os" + "strconv" + "strings" "time" "golang.org/x/sys/unix" + "k8s.io/klog/v2" ) -// GetBootTime returns the time at which the machine was started, truncated to the nearest second +// GetBootTime returns the time at which the machine was started, truncated to the nearest second. +// It uses /proc/stat first, which is more accurate, and falls back to the less accurate +// unix.Sysinfo if /proc/stat failed. func GetBootTime() (time.Time, error) { + bootTime, err := getBootTimeWithProcStat() + if err != nil { + klog.InfoS("Failed to get boot time from /proc/uptime. Will retry with unix.Sysinfo.", "error", err) + return getBootTimeWithSysinfo() + } + return bootTime, nil +} + +func getBootTimeWithProcStat() (time.Time, error) { + raw, err := os.ReadFile("/proc/stat") + if err != nil { + return time.Time{}, fmt.Errorf("error getting boot time: %w", err) + } + rawFields := strings.Fields(string(raw)) + for i, v := range rawFields { + if v == "btime" { + if len(rawFields) > i+1 { + sec, err := strconv.ParseInt(rawFields[i+1], 10, 64) + if err != nil { + return time.Time{}, fmt.Errorf("error parsing boot time %s: %w", rawFields[i+1], err) + } + return time.Unix(sec, 0), nil + } + break + } + } + + return time.Time{}, fmt.Errorf("can not find btime from /proc/stat: %s", raw) +} + +func getBootTimeWithSysinfo() (time.Time, error) { currentTime := time.Now() var info unix.Sysinfo_t if err := unix.Sysinfo(&info); err != nil { - return time.Time{}, fmt.Errorf("error getting system uptime: %s", err) + return time.Time{}, fmt.Errorf("error getting system uptime: %w", err) } return currentTime.Add(-time.Duration(info.Uptime) * time.Second).Truncate(time.Second), nil } diff --git a/pkg/kubelet/util/boottime_util_linux_test.go b/pkg/kubelet/util/boottime_util_linux_test.go index 4fce0acdeb3..9f77dfd0287 100644 --- a/pkg/kubelet/util/boottime_util_linux_test.go +++ b/pkg/kubelet/util/boottime_util_linux_test.go @@ -35,3 +35,18 @@ func TestGetBootTime(t *testing.T) { t.Errorf("Invalid system uptime") } } + +func TestVariationBetweenGetBootTimeMethods(t *testing.T) { + boottime1, err := getBootTimeWithProcStat() + if err != nil { + t.Errorf("Unable to get boot time from /proc/uptime") + } + boottime2, err := getBootTimeWithSysinfo() + if err != nil { + t.Errorf("Unable to get boot time from unix.Sysinfo") + } + diff := boottime1.Sub(boottime2) + if diff > time.Second || diff < -time.Second { + t.Errorf("boot time produced by 2 methods should not vary more than a second") + } +}