diff --git a/go.mod b/go.mod index 8707dad8..4b0054ff 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1 // indirect github.com/sec51/twofactor v1.0.1-0.20180911112802-cd97c894b2cc github.com/sergi/go-diff v1.1.0 // indirect - github.com/shirou/gopsutil v3.21.7+incompatible + github.com/shirou/gopsutil v3.21.9+incompatible github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 github.com/tklauser/go-sysconf v0.3.6 // indirect diff --git a/go.sum b/go.sum index 16543b6b..4bcae7db 100644 --- a/go.sum +++ b/go.sum @@ -468,6 +468,8 @@ github.com/shirou/gopsutil v3.21.6+incompatible h1:mmZtAlWSd8U2HeRTjswbnDLPxqsEo github.com/shirou/gopsutil v3.21.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.7+incompatible h1:g/wcPHcuCQvHSePVofjQljd2vX4ty0+J6VoMB+NPcdk= github.com/shirou/gopsutil v3.21.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.9+incompatible h1:LTLpUnfX81MkHeCtSrwNKZwuW5Id6kCa7/P43NdcNn4= +github.com/shirou/gopsutil v3.21.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= diff --git a/vendor/github.com/shirou/gopsutil/mem/mem.go b/vendor/github.com/shirou/gopsutil/mem/mem.go index dc2aacb5..c7e9880b 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem.go @@ -91,7 +91,7 @@ type SwapMemoryStat struct { // Linux specific numbers // https://www.kernel.org/doc/Documentation/cgroup-v2.txt - PgMajFault uint64 `json:"pgmajfault"` + PgMajFault uint64 `json:"pgmajfault"` } func (m VirtualMemoryStat) String() string { @@ -103,3 +103,14 @@ func (m SwapMemoryStat) String() string { s, _ := json.Marshal(m) return string(s) } + +type SwapDevice struct { + Name string `json:"name"` + UsedBytes uint64 `json:"usedBytes"` + FreeBytes uint64 `json:"freeBytes"` +} + +func (m SwapDevice) String() string { + s, _ := json.Marshal(m) + return string(s) +} diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_bsd.go b/vendor/github.com/shirou/gopsutil/mem/mem_bsd.go new file mode 100644 index 00000000..17ca9208 --- /dev/null +++ b/vendor/github.com/shirou/gopsutil/mem/mem_bsd.go @@ -0,0 +1,91 @@ +// +build freebsd openbsd + +package mem + +import ( + "context" + "fmt" + "os/exec" + "strconv" + "strings" +) + +const swapCommand = "swapctl" + +// swapctl column indexes +const ( + nameCol = 0 + totalKiBCol = 1 + usedKiBCol = 2 +) + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + swapCommandPath, err := exec.LookPath(swapCommand) + if err != nil { + return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err) + } + output, err := invoke.CommandWithContext(swapCommandPath, "-lk") + if err != nil { + return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err) + } + + return parseSwapctlOutput(string(output)) +} + +func parseSwapctlOutput(output string) ([]*SwapDevice, error) { + lines := strings.Split(output, "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output) + } + + // Check header headerFields are as expected. + header := lines[0] + header = strings.ToLower(header) + header = strings.ReplaceAll(header, ":", "") + headerFields := strings.Fields(header) + if len(headerFields) < usedKiBCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, header) + } + if headerFields[nameCol] != "device" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "device") + } + if headerFields[totalKiBCol] != "1kb-blocks" && headerFields[totalKiBCol] != "1k-blocks" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalKiBCol], "1kb-blocks") + } + if headerFields[usedKiBCol] != "used" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[usedKiBCol], "used") + } + + var swapDevices []*SwapDevice + for _, line := range lines[1:] { + if line == "" { + continue // the terminal line is typically empty + } + fields := strings.Fields(line) + if len(fields) < usedKiBCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand) + } + + totalKiB, err := strconv.ParseUint(fields[totalKiBCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err) + } + + usedKiB, err := strconv.ParseUint(fields[usedKiBCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err) + } + + swapDevices = append(swapDevices, &SwapDevice{ + Name: fields[nameCol], + UsedBytes: usedKiB * 1024, + FreeBytes: (totalKiB - usedKiB) * 1024, + }) + } + + return swapDevices, nil +} diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_darwin.go b/vendor/github.com/shirou/gopsutil/mem/mem_darwin.go index fac74815..600a7a32 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_darwin.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_darwin.go @@ -8,6 +8,7 @@ import ( "fmt" "unsafe" + "github.com/shirou/gopsutil/internal/common" "golang.org/x/sys/unix" ) @@ -67,3 +68,11 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return ret, nil } + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + return nil, common.ErrNotImplementedError +} diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_darwin_cgo.go b/vendor/github.com/shirou/gopsutil/mem/mem_darwin_cgo.go index 389f8cdf..ade3cecd 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_darwin_cgo.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_darwin_cgo.go @@ -1,5 +1,4 @@ -// +build darwin -// +build cgo +// +build darwin,cgo package mem diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_darwin_nocgo.go b/vendor/github.com/shirou/gopsutil/mem/mem_darwin_nocgo.go index dd7c2e60..2e847cbe 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_darwin_nocgo.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_darwin_nocgo.go @@ -1,5 +1,4 @@ -// +build darwin -// +build !cgo +// +build darwin,!cgo package mem diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_fallback.go b/vendor/github.com/shirou/gopsutil/mem/mem_fallback.go index 2a0fd45b..02e87d7a 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_fallback.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_fallback.go @@ -23,3 +23,11 @@ func SwapMemory() (*SwapMemoryStat, error) { func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return nil, common.ErrNotImplementedError } + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + return nil, common.ErrNotImplementedError +} diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_linux.go b/vendor/github.com/shirou/gopsutil/mem/mem_linux.go index f9cb8f26..cf3c64e9 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_linux.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_linux.go @@ -3,8 +3,11 @@ package mem import ( + "bufio" "context" "encoding/json" + "fmt" + "io" "math" "os" "strconv" @@ -426,3 +429,86 @@ func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint6 return availMemory } + +const swapsFilename = "swaps" + +// swaps file column indexes +const ( + nameCol = 0 + // typeCol = 1 + totalCol = 2 + usedCol = 3 + // priorityCol = 4 +) + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + swapsFilePath := common.HostProc(swapsFilename) + f, err := os.Open(swapsFilePath) + if err != nil { + return nil, err + } + defer f.Close() + + return parseSwapsFile(f) +} + +func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) { + swapsFilePath := common.HostProc(swapsFilename) + scanner := bufio.NewScanner(r) + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err) + } + return nil, fmt.Errorf("unexpected end-of-file in %q", swapsFilePath) + + } + + // Check header headerFields are as expected + headerFields := strings.Fields(scanner.Text()) + if len(headerFields) < usedCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields in header", swapsFilePath) + } + if headerFields[nameCol] != "Filename" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[nameCol], "Filename") + } + if headerFields[totalCol] != "Size" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[totalCol], "Size") + } + if headerFields[usedCol] != "Used" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[usedCol], "Used") + } + + var swapDevices []*SwapDevice + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < usedCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsFilePath) + } + + totalKiB, err := strconv.ParseUint(fields[totalCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsFilePath, err) + } + + usedKiB, err := strconv.ParseUint(fields[usedCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsFilePath, err) + } + + swapDevices = append(swapDevices, &SwapDevice{ + Name: fields[nameCol], + UsedBytes: usedKiB * 1024, + FreeBytes: (totalKiB - usedKiB) * 1024, + }) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err) + } + + return swapDevices, nil +} diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_386.go b/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_386.go index aacd4f61..0fa65d9c 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_386.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_386.go @@ -1,5 +1,5 @@ -// +build openbsd -// +build 386 +// +build openbsd,386 + // Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs mem/types_openbsd.go diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_arm64.go b/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_arm64.go index ad48fc30..35f8517b 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_arm64.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_openbsd_arm64.go @@ -1,5 +1,5 @@ -// +build openbsd -// +build arm64 +// +build openbsd,arm64 + // Code generated by cmd/cgo -godefs; DO NOT EDIT. // cgo -godefs mem/types_openbsd.go diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_solaris.go b/vendor/github.com/shirou/gopsutil/mem/mem_solaris.go index 08512733..0c583140 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_solaris.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_solaris.go @@ -1,3 +1,5 @@ +// +build solaris + package mem import ( @@ -119,3 +121,85 @@ func nonGlobalZoneMemoryCapacity() (uint64, error) { return memSizeBytes, nil } + +const swapsCommand = "swap" + +// The blockSize as reported by `swap -l`. See https://docs.oracle.com/cd/E23824_01/html/821-1459/fsswap-52195.html +const blockSize = 512 + +// swapctl column indexes +const ( + nameCol = 0 + // devCol = 1 + // swaploCol = 2 + totalBlocksCol = 3 + freeBlocksCol = 4 +) + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + swapsCommandPath, err := exec.LookPath(swapsCommand) + if err != nil { + return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err) + } + output, err := invoke.CommandWithContext(swapsCommandPath, "-l") + if err != nil { + return nil, fmt.Errorf("could not execute %q: %w", swapsCommand, err) + } + + return parseSwapsCommandOutput(string(output)) +} + +func parseSwapsCommandOutput(output string) ([]*SwapDevice, error) { + lines := strings.Split(output, "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapsCommand, output) + } + + // Check header headerFields are as expected. + headerFields := strings.Fields(lines[0]) + if len(headerFields) < freeBlocksCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapsCommand, lines[0]) + } + if headerFields[nameCol] != "swapfile" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsCommand, headerFields[nameCol], "swapfile") + } + if headerFields[totalBlocksCol] != "blocks" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsCommand, headerFields[totalBlocksCol], "blocks") + } + if headerFields[freeBlocksCol] != "free" { + return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsCommand, headerFields[freeBlocksCol], "free") + } + + var swapDevices []*SwapDevice + for _, line := range lines[1:] { + if line == "" { + continue // the terminal line is typically empty + } + fields := strings.Fields(line) + if len(fields) < freeBlocksCol { + return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsCommand) + } + + totalBlocks, err := strconv.ParseUint(fields[totalBlocksCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsCommand, err) + } + + freeBlocks, err := strconv.ParseUint(fields[freeBlocksCol], 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsCommand, err) + } + + swapDevices = append(swapDevices, &SwapDevice{ + Name: fields[nameCol], + UsedBytes: (totalBlocks - freeBlocks) * blockSize, + FreeBytes: freeBlocks * blockSize, + }) + } + + return swapDevices, nil +} diff --git a/vendor/github.com/shirou/gopsutil/mem/mem_windows.go b/vendor/github.com/shirou/gopsutil/mem/mem_windows.go index a925faa2..7c2f3bc6 100644 --- a/vendor/github.com/shirou/gopsutil/mem/mem_windows.go +++ b/vendor/github.com/shirou/gopsutil/mem/mem_windows.go @@ -4,6 +4,8 @@ package mem import ( "context" + "sync" + "syscall" "unsafe" "github.com/shirou/gopsutil/internal/common" @@ -11,8 +13,10 @@ import ( ) var ( - procGlobalMemoryStatusEx = common.Modkernel32.NewProc("GlobalMemoryStatusEx") + procEnumPageFilesW = common.ModPsapi.NewProc("EnumPageFilesW") + procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") procGetPerformanceInfo = common.ModPsapi.NewProc("GetPerformanceInfo") + procGlobalMemoryStatusEx = common.Modkernel32.NewProc("GlobalMemoryStatusEx") ) type memoryStatusEx struct { @@ -96,3 +100,66 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { return ret, nil } + +var ( + pageSize uint64 + pageSizeOnce sync.Once +) + +type systemInfo struct { + wProcessorArchitecture uint16 + wReserved uint16 + dwPageSize uint32 + lpMinimumApplicationAddress uintptr + lpMaximumApplicationAddress uintptr + dwActiveProcessorMask uintptr + dwNumberOfProcessors uint32 + dwProcessorType uint32 + dwAllocationGranularity uint32 + wProcessorLevel uint16 + wProcessorRevision uint16 +} + +// system type as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-enum_page_file_information +type enumPageFileInformation struct { + cb uint32 + reserved uint32 + totalSize uint64 + totalInUse uint64 + peakUsage uint64 +} + +func SwapDevices() ([]*SwapDevice, error) { + return SwapDevicesWithContext(context.Background()) +} + +func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { + pageSizeOnce.Do(func() { + var sysInfo systemInfo + procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sysInfo))) + pageSize = uint64(sysInfo.dwPageSize) + }) + + // the following system call invokes the supplied callback function once for each page file before returning + // see https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumpagefilesw + var swapDevices []*SwapDevice + result, _, _ := procEnumPageFilesW.Call(windows.NewCallback(pEnumPageFileCallbackW), uintptr(unsafe.Pointer(&swapDevices))) + if result == 0 { + return nil, windows.GetLastError() + } + + return swapDevices, nil +} + +// system callback as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/nc-psapi-penum_page_file_callbackw +func pEnumPageFileCallbackW(swapDevices *[]*SwapDevice, enumPageFileInfo *enumPageFileInformation, lpFilenamePtr *[syscall.MAX_LONG_PATH]uint16) *bool { + *swapDevices = append(*swapDevices, &SwapDevice{ + Name: syscall.UTF16ToString((*lpFilenamePtr)[:]), + UsedBytes: enumPageFileInfo.totalInUse * pageSize, + FreeBytes: (enumPageFileInfo.totalSize - enumPageFileInfo.totalInUse) * pageSize, + }) + + // return true to continue enumerating page files + ret := true + return &ret +} diff --git a/vendor/github.com/shirou/gopsutil/process/process_linux.go b/vendor/github.com/shirou/gopsutil/process/process_linux.go index 4360eff5..de742fbd 100644 --- a/vendor/github.com/shirou/gopsutil/process/process_linux.go +++ b/vendor/github.com/shirou/gopsutil/process/process_linux.go @@ -82,7 +82,7 @@ func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { func (p *Process) NameWithContext(ctx context.Context) (string, error) { if p.name == "" { - if err := p.fillFromStatusWithContext(ctx); err != nil { + if err := p.fillNameWithContext(ctx); err != nil { return "", err } } @@ -351,7 +351,7 @@ func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, e func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) if err != nil { - if pids == nil || len(pids) == 0 { + if len(pids) == 0 { return nil, ErrorNoChildren } return nil, err @@ -517,6 +517,28 @@ func limitToInt(val string) (int32, error) { } } +// Get name from /proc/(pid)/comm or /proc/(pid)/status +func (p *Process) fillNameWithContext(ctx context.Context) error { + err := p.fillFromCommWithContext(ctx) + if err == nil && p.name != "" && len(p.name) < 15 { + return nil + } + return p.fillFromStatusWithContext(ctx) +} + +// Get name from /proc/(pid)/comm +func (p *Process) fillFromCommWithContext(ctx context.Context) error { + pid := p.Pid + statPath := common.HostProc(strconv.Itoa(int(pid)), "comm") + contents, err := ioutil.ReadFile(statPath) + if err != nil { + return err + } + + p.name = strings.TrimSuffix(string(contents), "\n") + return nil +} + // Get num_fds from /proc/(pid)/limits func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat, error) { pid := p.Pid @@ -683,10 +705,7 @@ func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error return "", err } ret := strings.FieldsFunc(string(cmdline), func(r rune) bool { - if r == '\u0000' { - return true - } - return false + return r == '\u0000' }) return strings.Join(ret, " "), nil diff --git a/vendor/github.com/shirou/gopsutil/process/process_windows.go b/vendor/github.com/shirou/gopsutil/process/process_windows.go index 8f9e499f..c88f949d 100644 --- a/vendor/github.com/shirou/gopsutil/process/process_windows.go +++ b/vendor/github.com/shirou/gopsutil/process/process_windows.go @@ -40,7 +40,7 @@ var ( processorArchitecture uint ) -const processQueryInformation = windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_QUERY_INFORMATION // WinXP doesn't know PROCESS_QUERY_LIMITED_INFORMATION +const processQueryInformation = windows.PROCESS_QUERY_LIMITED_INFORMATION type SystemProcessInformation struct { NextEntryOffset uint64 diff --git a/vendor/modules.txt b/vendor/modules.txt index 97c23321..9e12ae98 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -299,7 +299,7 @@ github.com/sec51/qrcode/coding github.com/sec51/twofactor # github.com/sergi/go-diff v1.1.0 ## explicit -# github.com/shirou/gopsutil v3.21.7+incompatible +# github.com/shirou/gopsutil v3.21.9+incompatible ## explicit github.com/shirou/gopsutil/cpu github.com/shirou/gopsutil/host