diff --git a/go.mod b/go.mod index bdcc5e670..4dd69e3d8 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,13 @@ require ( github.com/go-openapi/spec v0.21.0 github.com/mattn/go-runewidth v0.0.15 github.com/rs/zerolog v1.33.0 - github.com/shirou/gopsutil/v3 v3.24.4 + github.com/shirou/gopsutil/v3 v3.24.5 + github.com/shirou/gopsutil/v4 v4.24.5 github.com/spf13/cobra v1.8.0 github.com/tidwall/gjson v1.17.1 github.com/zekroTJA/timedmap v1.5.2 github.com/zekroTJA/timedmap/v2 v2.0.0 - golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d + golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc golang.org/x/sys v0.20.0 golang.org/x/term v0.20.0 golang.org/x/text v0.15.0 diff --git a/go.sum b/go.sum index 1cd0ca00a..07d35ce7f 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,9 @@ github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shirou/gopsutil/v4 v4.24.5/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -100,6 +103,8 @@ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= +golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/vendor/github.com/shirou/gopsutil/v3/cpu/cpu_aix_nocgo.go b/vendor/github.com/shirou/gopsutil/v3/cpu/cpu_aix_nocgo.go index a77b4dbb7..f3a3186aa 100644 --- a/vendor/github.com/shirou/gopsutil/v3/cpu/cpu_aix_nocgo.go +++ b/vendor/github.com/shirou/gopsutil/v3/cpu/cpu_aix_nocgo.go @@ -12,8 +12,57 @@ import ( ) func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { + var ret []TimesStat if percpu { - return []TimesStat{}, common.ErrNotImplementedError + per_out, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "10", "1") + if err != nil { + return nil, err + } + lines := strings.Split(string(per_out), "\n") + if len(lines) < 6 { + return []TimesStat{}, common.ErrNotImplementedError + } + + hp := strings.Fields(lines[5]) // headers + for l := 6; l < len(lines)-1; l++ { + ct := &TimesStat{} + v := strings.Fields(lines[l]) // values + for i, header := range hp { + // We're done in any of these use cases + if i >= len(v) || v[0] == "-" { + break + } + + // Position variable for v + pos := i + // There is a missing field at the beginning of all but the first line + // so adjust the position + if l > 6 { + pos = i - 1 + } + // We don't want invalid positions + if pos < 0 { + continue + } + + if t, err := strconv.ParseFloat(v[pos], 64); err == nil { + switch header { + case `cpu`: + ct.CPU = strconv.FormatFloat(t, 'f', -1, 64) + case `%usr`: + ct.User = t + case `%sys`: + ct.System = t + case `%wio`: + ct.Iowait = t + case `%idle`: + ct.Idle = t + } + } + } + // Valid CPU data, so append it + ret = append(ret, *ct) + } } else { out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1") if err != nil { @@ -24,26 +73,28 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return []TimesStat{}, common.ErrNotImplementedError } - ret := TimesStat{CPU: "cpu-total"} + ct := &TimesStat{CPU: "cpu-total"} h := strings.Fields(lines[len(lines)-3]) // headers v := strings.Fields(lines[len(lines)-2]) // values for i, header := range h { if t, err := strconv.ParseFloat(v[i], 64); err == nil { switch header { case `%usr`: - ret.User = t + ct.User = t case `%sys`: - ret.System = t + ct.System = t case `%wio`: - ret.Iowait = t + ct.Iowait = t case `%idle`: - ret.Idle = t + ct.Idle = t } } } - return []TimesStat{ret}, nil + ret = append(ret, *ct) } + + return ret, nil } func InfoWithContext(ctx context.Context) ([]InfoStat, error) { @@ -78,6 +129,20 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { } } break + } else if strings.HasPrefix(line, "System Model:") { + p := strings.Split(string(line), ":") + if p != nil { + ret.VendorID = strings.TrimSpace(p[1]) + } + } else if strings.HasPrefix(line, "Processor Type:") { + p := strings.Split(string(line), ":") + if p != nil { + c := strings.Split(string(p[1]), "_") + if c != nil { + ret.Family = strings.TrimSpace(c[0]) + ret.Model = strings.TrimSpace(c[1]) + } + } } } return []InfoStat{ret}, nil diff --git a/vendor/github.com/shirou/gopsutil/v3/host/host_aix.go b/vendor/github.com/shirou/gopsutil/v3/host/host_aix.go new file mode 100644 index 000000000..d06899ea0 --- /dev/null +++ b/vendor/github.com/shirou/gopsutil/v3/host/host_aix.go @@ -0,0 +1,202 @@ +//go:build aix +// +build aix + +package host + +import ( + "context" + "errors" + "strconv" + "strings" + + "github.com/shirou/gopsutil/v3/internal/common" +) + +// from https://www.ibm.com/docs/en/aix/7.2?topic=files-utmph-file +const ( + user_PROCESS = 7 + + hostTemperatureScale = 1000.0 // Not part of the linked file, but kept just in case it becomes relevant +) + +func HostIDWithContext(ctx context.Context) (string, error) { + out, err := invoke.CommandWithContext(ctx, "uname", "-u") + if err != nil { + return "", err + } + + // The command always returns an extra newline, so we make use of Split() to get only the first line + return strings.Split(string(out[:]), "\n")[0], nil +} + +func numProcs(ctx context.Context) (uint64, error) { + return 0, common.ErrNotImplementedError +} + +func BootTimeWithContext(ctx context.Context) (btime uint64, err error) { + ut, err := UptimeWithContext(ctx) + if err != nil { + return 0, err + } + + if ut <= 0 { + return 0, errors.New("Uptime was not set, so cannot calculate boot time from it.") + } + + ut = ut * 60 + return timeSince(ut), nil +} + +// This function takes multiple formats of output frmo the uptime +// command and converts the data 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 +func UptimeWithContext(ctx context.Context) (uint64, error) { + out, err := invoke.CommandWithContext(ctx, "uptime") + if err != nil { + return 0, err + } + + // 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 + if 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], ":") + 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 { + 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], ":") + 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 + } + } + + // Stack them all together as minutes + total_time := (days * 24 * 60) + (hours * 60) + minutes + + return total_time, nil +} + +// This is a weak implementation due to the limitations on retrieving this data in AIX +func UsersWithContext(ctx context.Context) ([]UserStat, error) { + var ret []UserStat + out, err := invoke.CommandWithContext(ctx, "w") + if err != nil { + return nil, err + } + lines := strings.Split(string(out), "\n") + if len(lines) < 3 { + return []UserStat{}, common.ErrNotImplementedError + } + + hf := strings.Fields(lines[1]) // headers + for l := 2; l < len(lines); l++ { + v := strings.Fields(lines[l]) // values + us := &UserStat{} + for i, header := range hf { + // We're done in any of these use cases + if i >= len(v) || v[0] == "-" { + break + } + + if t, err := strconv.ParseFloat(v[i], 64); err == nil { + switch header { + case `User`: + us.User = strconv.FormatFloat(t, 'f', 1, 64) + case `tty`: + us.Terminal = strconv.FormatFloat(t, 'f', 1, 64) + } + } + } + + // Valid User data, so append it + ret = append(ret, *us) + } + + return ret, nil +} + +// Much of this function could be static. However, to be future proofed, I've made it call the OS for the information in all instances. +func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) { + // Set the platform (which should always, and only be, "AIX") from `uname -s` + out, err := invoke.CommandWithContext(ctx, "uname", "-s") + if err != nil { + return "", "", "", err + } + platform = strings.TrimRight(string(out[:]), "\n") + + // Set the family + family = strings.TrimRight(string(out[:]), "\n") + + // Set the version + out, err = invoke.CommandWithContext(ctx, "oslevel") + if err != nil { + return "", "", "", err + } + version = strings.TrimRight(string(out[:]), "\n") + + return platform, family, version, nil +} + +func KernelVersionWithContext(ctx context.Context) (version string, err error) { + out, err := invoke.CommandWithContext(ctx, "oslevel", "-s") + if err != nil { + return "", err + } + version = strings.TrimRight(string(out[:]), "\n") + + return version, nil +} + +func KernelArch() (arch string, err error) { + out, err := invoke.Command("bootinfo", "-y") + if err != nil { + return "", err + } + arch = strings.TrimRight(string(out[:]), "\n") + + return arch, nil +} + +func VirtualizationWithContext(ctx context.Context) (string, string, error) { + return "", "", common.ErrNotImplementedError +} + +func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { + return nil, common.ErrNotImplementedError +} diff --git a/vendor/github.com/shirou/gopsutil/v3/host/host_aix_ppc64.go b/vendor/github.com/shirou/gopsutil/v3/host/host_aix_ppc64.go new file mode 100644 index 000000000..de9674b73 --- /dev/null +++ b/vendor/github.com/shirou/gopsutil/v3/host/host_aix_ppc64.go @@ -0,0 +1,48 @@ +//go:build aix && ppc64 && cgo +// +build aix,ppc64,cgo + +// Guessed at from the following document: +// https://www.ibm.com/docs/sl/ibm-mq/9.2?topic=platforms-standard-data-types-aix-linux-windows + +package host + +const ( + sizeofPtr = 0x8 + sizeofShort = 0x2 + sizeofInt = 0x4 + sizeofLong = 0x8 + sizeofLongLong = 0x8 + sizeOfUtmp = 0x180 +) + +type ( + _C_short int16 + _C_int int32 + _C_long int64 + _C_long_long int64 +) + +type utmp struct { + Type int16 + Pad_cgo_0 [2]byte + Pid int32 + Line [32]int8 + Id [4]int8 + User [32]int8 + Host [256]int8 + Exit exit_status + Session int32 + Tv timeval + Addr_v6 [4]int32 + X__glibc_reserved [20]int8 +} + +type exit_status struct { + Termination int16 + Exit int16 +} + +type timeval struct { + Sec int64 + Usec int64 +} diff --git a/vendor/github.com/shirou/gopsutil/v3/host/host_fallback.go b/vendor/github.com/shirou/gopsutil/v3/host/host_fallback.go index a393ca15d..150ccf008 100644 --- a/vendor/github.com/shirou/gopsutil/v3/host/host_fallback.go +++ b/vendor/github.com/shirou/gopsutil/v3/host/host_fallback.go @@ -1,5 +1,5 @@ -//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows -// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!solaris,!windows +//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows && !aix +// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!solaris,!windows,!aix package host diff --git a/vendor/github.com/shirou/gopsutil/v3/mem/mem_aix_nocgo.go b/vendor/github.com/shirou/gopsutil/v3/mem/mem_aix_nocgo.go index cc6a76d2f..027879d9a 100644 --- a/vendor/github.com/shirou/gopsutil/v3/mem/mem_aix_nocgo.go +++ b/vendor/github.com/shirou/gopsutil/v3/mem/mem_aix_nocgo.go @@ -12,7 +12,7 @@ import ( ) func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { - vmem, swap, err := callSVMon(ctx) + vmem, swap, err := callSVMon(ctx, true) if err != nil { return nil, err } @@ -25,7 +25,7 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { } func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { - _, swap, err := callSVMon(ctx) + _, swap, err := callSVMon(ctx, false) if err != nil { return nil, err } @@ -35,7 +35,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return swap, nil } -func callSVMon(ctx context.Context) (*VirtualMemoryStat, *SwapMemoryStat, error) { +func callSVMon(ctx context.Context, virt bool) (*VirtualMemoryStat, *SwapMemoryStat, error) { out, err := invoke.CommandWithContext(ctx, "svmon", "-G") if err != nil { return nil, nil, err @@ -45,7 +45,7 @@ func callSVMon(ctx context.Context) (*VirtualMemoryStat, *SwapMemoryStat, error) vmem := &VirtualMemoryStat{} swap := &SwapMemoryStat{} for _, line := range strings.Split(string(out), "\n") { - if strings.HasPrefix(line, "memory") { + if virt && strings.HasPrefix(line, "memory") { p := strings.Fields(line) if len(p) > 2 { if t, err := strconv.ParseUint(p[1], 10, 64); err == nil { diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_darwin_nocgo.go b/vendor/github.com/shirou/gopsutil/v3/process/process_darwin_nocgo.go index bc1d357df..d903474f6 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_darwin_nocgo.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_darwin_nocgo.go @@ -24,14 +24,21 @@ func (p *Process) ExeWithContext(ctx context.Context) (string, error) { } txtFound := 0 lines := strings.Split(string(out), "\n") + fallback := "" for i := 1; i < len(lines); i++ { if lines[i] == "ftxt" { txtFound++ + if txtFound == 1 { + fallback = lines[i-1][1:] + } if txtFound == 2 { return lines[i-1][1:], nil } } } + if fallback != "" { + return fallback, nil + } return "", fmt.Errorf("missing txt data returned by lsof") } diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd.go b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd.go index a58c5eb11..358694063 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd.go @@ -68,7 +68,12 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { } func (p *Process) CwdWithContext(ctx context.Context) (string, error) { - return "", common.ErrNotImplementedError + mib := []int32{CTLKern, KernProcCwd, p.Pid} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return "", err + } + return common.ByteToString(buf), nil } func (p *Process) ExeWithContext(ctx context.Context) (string, error) { diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_386.go b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_386.go index f4ed02491..6e9edc202 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_386.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_386.go @@ -14,6 +14,7 @@ const ( KernProcProc = 8 KernProcPathname = 12 KernProcArgs = 55 + KernProcCwd = 78 KernProcArgv = 1 KernProcEnv = 3 ) diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_amd64.go b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_amd64.go index 8607422b5..a46d28af5 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_amd64.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_amd64.go @@ -11,6 +11,7 @@ const ( KernProcProc = 8 KernProcPathname = 12 KernProcArgs = 55 + KernProcCwd = 78 KernProcArgv = 1 KernProcEnv = 3 ) diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm.go b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm.go index b94429f2e..68ea3c8f7 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm.go @@ -14,6 +14,7 @@ const ( KernProcProc = 8 KernProcPathname = 12 KernProcArgs = 55 + KernProcCwd = 78 KernProcArgv = 1 KernProcEnv = 3 ) diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm64.go b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm64.go index a3291b8ca..fa620ff67 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm64.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_arm64.go @@ -14,6 +14,7 @@ const ( KernProcProc = 8 KernProcPathname = 12 KernProcArgs = 55 + KernProcCwd = 78 KernProcArgv = 1 KernProcEnv = 3 ) diff --git a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_riscv64.go b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_riscv64.go index 076f095ea..b677e70ad 100644 --- a/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_riscv64.go +++ b/vendor/github.com/shirou/gopsutil/v3/process/process_openbsd_riscv64.go @@ -14,6 +14,7 @@ const ( KernProcProc = 8 KernProcPathname = 12 KernProcArgs = 55 + KernProcCwd = 78 KernProcArgv = 1 KernProcEnv = 3 ) diff --git a/vendor/modules.txt b/vendor/modules.txt index 4da1a6e72..f4164ad53 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -50,8 +50,8 @@ github.com/rs/zerolog github.com/rs/zerolog/internal/cbor github.com/rs/zerolog/internal/json github.com/rs/zerolog/log -# github.com/shirou/gopsutil/v3 v3.24.4 -## explicit; go 1.15 +# github.com/shirou/gopsutil/v3 v3.24.5 +## explicit; go 1.18 github.com/shirou/gopsutil/v3/common github.com/shirou/gopsutil/v3/cpu github.com/shirou/gopsutil/v3/host @@ -59,6 +59,8 @@ github.com/shirou/gopsutil/v3/internal/common github.com/shirou/gopsutil/v3/mem github.com/shirou/gopsutil/v3/net github.com/shirou/gopsutil/v3/process +# github.com/shirou/gopsutil/v4 v4.24.5 +## explicit; go 1.18 # github.com/shoenig/go-m1cpu v0.1.6 ## explicit; go 1.20 github.com/shoenig/go-m1cpu @@ -91,7 +93,7 @@ github.com/yusufpapurcu/wmi github.com/zekroTJA/timedmap # github.com/zekroTJA/timedmap/v2 v2.0.0 ## explicit; go 1.19 -# golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d +# golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc ## explicit; go 1.20 golang.org/x/exp/maps # golang.org/x/sys v0.20.0