diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..657b3aeef --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [Security Advisories](https://github.com/shirou/gopsutil/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerability reports will be investigated and fixed or disclosed as soon as possible. diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 48a5801be..ce5b347eb 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -19,20 +19,20 @@ jobs: fail-fast: false matrix: go-version: ${{fromJson(needs.go-versions.outputs.versions)}} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - id: cache-paths run: | echo "::set-output name=cache::$(go env GOCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)" - name: Cache go modules - uses: actions/cache@v3 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: | ${{ steps.cache-paths.outputs.cache }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 218b89eef..5626060c8 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,6 +12,6 @@ jobs: pull-requests: write # for actions/labeler to add labels to PRs runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 325124567..cd9bfa6e5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,13 +16,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v3 + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version: 1.17 + cache: false - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 with: args: --verbose version: latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fccd4902c..c3ea2b96f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,6 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Release run: make release diff --git a/.github/workflows/sbom_generator.yml b/.github/workflows/sbom_generator.yml new file mode 100644 index 000000000..be07dd4d2 --- /dev/null +++ b/.github/workflows/sbom_generator.yml @@ -0,0 +1,25 @@ +name: SBOM Generator + +on: + push: + branches: [ "master" ] + + workflow_dispatch: + +permissions: read-all + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - uses: advanced-security/sbom-generator-action@375dee8e6144d9fd0ec1f5667b4f6fb4faacefed # v0.0.1 + id: sbom + env: + GITHUB_TOKEN: ${{ github.token }} + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + path: ${{steps.sbom.outputs.fileName }} + name: "SBOM" diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 04bb575a4..c733e20d4 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -8,6 +8,6 @@ jobs: name: Shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Run ShellCheck - uses: ludeeus/action-shellcheck@master \ No newline at end of file + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd637877b..79b0fa070 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,21 +19,21 @@ jobs: fail-fast: false matrix: go-version: ${{fromJson(needs.go-versions.outputs.versions)}} - os: [ubuntu-20.04, ubuntu-18.04, windows-2019, macos-11] + os: [ubuntu-22.04, ubuntu-20.04, windows-2022, windows-2019, macos-11, macos-12] runs-on: ${{ matrix.os }} steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - id: go-env run: | echo "::set-output name=cache::$(go env GOCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)" - name: Cache go modules - uses: actions/cache@v3 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: | ${{ steps.go-env.outputs.cache }} diff --git a/.golangci.yml b/.golangci.yml index b175f43ff..170626bfd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,15 +1,21 @@ issues: max-same-issues: 0 - exclude-rules: - - linters: - - gosec - text: "G204" - - linters: - - revive - text: "var-naming" - - linters: - - revive - text: "exported" + exclude-rules: + - linters: + - gosec + text: "G204" + - linters: + - revive + text: "var-naming" + - linters: + - revive + text: "exported" + - linters: + - revive + text: "empty-block" + - linters: + - revive + text: "unused-parameter" linters: enable: - asciicheck @@ -20,6 +26,7 @@ linters: - gofmt - gofumpt - goimports + - gomodguard - gosec - gosimple - importas @@ -40,3 +47,16 @@ linters: - structcheck - unused - varcheck +linters-settings: + gci: + sections: + - standard + - default + - prefix(github.com/shirou) + gomodguard: + blocked: + modules: + - io/ioutil: + recommandations: + - io + - os diff --git a/README.md b/README.md index c4d1fae02..78751d72f 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,19 @@ environment variable. You can set an alternative location to `/proc/N/mountinfo` by setting the `HOST_PROC_MOUNTINFO` environment variable. +### Adding settings using `context` (from v3.23.6) + +As of v3.23.6, it is now possible to pass a path location using `context`: import `"github.com/shirou/gopsutil/v3/common"` and pass a context with `common.EnvMap` set to `common.EnvKey`, and the location will be used within each function. + +``` + ctx := context.WithValue(context.Background(), + common.EnvKey, common.EnvMap{common.HostProcEnvKey: "/myproc"}, + ) + v, err := mem.VirtualMemoryWithContext(ctx) +``` + +First priority is given to the value set in `context`, then the value from the environment variable, and finally the default location. + ## Documentation See https://pkg.go.dev/github.com/shirou/gopsutil/v3 or https://godocs.io/github.com/shirou/gopsutil/v3 @@ -154,7 +167,7 @@ will provide useful information. - system wide stats on netfilter conntrack module - sourced from /proc/sys/net/netfilter/nf_conntrack_count -Some code is ported from Ohai. many thanks. +Some code is ported from Ohai. Many thanks. ## Current Status @@ -219,9 +232,9 @@ Some code is ported from Ohai. many thanks. |rlimit |x | | | | | |num\_handlers | | | | | | |threads |x | | | | | -|cpu\_percent |x | |x |x | | +|cpu\_percent |x | |x |x |x | |cpu\_affinity | | | | | | -|memory\_percent | | | | | | +|memory\_percent |x | | | |x | |parent |x | |x |x |x | |children |x |x |x |x |x | |connections |x | |x |x | | diff --git a/common/env.go b/common/env.go new file mode 100644 index 000000000..4b5f4980c --- /dev/null +++ b/common/env.go @@ -0,0 +1,23 @@ +package common + +type EnvKeyType string + +// EnvKey is a context key that can be used to set programmatically the environment +// gopsutil relies on to perform calls against the OS. +// Example of use: +// +// ctx := context.WithValue(context.Background(), common.EnvKey, EnvMap{common.HostProcEnvKey: "/myproc"}) +// avg, err := load.AvgWithContext(ctx) +var EnvKey = EnvKeyType("env") + +const ( + HostProcEnvKey EnvKeyType = "HOST_PROC" + HostSysEnvKey EnvKeyType = "HOST_SYS" + HostEtcEnvKey EnvKeyType = "HOST_ETC" + HostVarEnvKey EnvKeyType = "HOST_VAR" + HostRunEnvKey EnvKeyType = "HOST_RUN" + HostDevEnvKey EnvKeyType = "HOST_DEV" + HostRootEnvKey EnvKeyType = "HOST_ROOT" +) + +type EnvMap map[EnvKeyType]string diff --git a/cpu/cpu_darwin.go b/cpu/cpu_darwin.go index 7acb258d9..41f395e5e 100644 --- a/cpu/cpu_darwin.go +++ b/cpu/cpu_darwin.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/shoenig/go-m1cpu" "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" ) @@ -85,11 +86,15 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { c.CacheSize = int32(cacheSize) c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor") - // Use the rated frequency of the CPU. This is a static value and does not - // account for low power or Turbo Boost modes. - cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency") - if err == nil { - c.Mhz = float64(cpuFrequency) / 1000000.0 + if m1cpu.IsAppleSilicon() { + c.Mhz = float64(m1cpu.PCoreHz() / 1_000_000) + } else { + // Use the rated frequency of the CPU. This is a static value and does not + // account for low power or Turbo Boost modes. + cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency") + if err == nil { + c.Mhz = float64(cpuFrequency) / 1000000.0 + } } return append(ret, c), nil diff --git a/cpu/cpu_darwin_test.go b/cpu/cpu_darwin_test.go new file mode 100644 index 000000000..57b3d66ee --- /dev/null +++ b/cpu/cpu_darwin_test.go @@ -0,0 +1,33 @@ +//go:build darwin +// +build darwin + +package cpu + +import ( + "testing" + + "github.com/shoenig/go-m1cpu" +) + +func Test_CpuInfo_AppleSilicon(t *testing.T) { + if !m1cpu.IsAppleSilicon() { + t.Skip("wrong cpu type") + } + + v, err := Info() + if err != nil { + t.Errorf("cpu info should be implemented on darwin systems") + } + + for _, vv := range v { + if vv.ModelName == "" { + t.Errorf("could not get CPU info: %v", vv) + } + if vv.Mhz <= 0 { + t.Errorf("could not get frequency of: %s", vv.ModelName) + } + if vv.Mhz > 6000 { + t.Errorf("cpu frequency is absurdly high value: %f MHz", vv.Mhz) + } + } +} diff --git a/cpu/cpu_fallback.go b/cpu/cpu_fallback.go index 6d7007ff9..089f603c8 100644 --- a/cpu/cpu_fallback.go +++ b/cpu/cpu_fallback.go @@ -1,5 +1,5 @@ -//go:build !darwin && !linux && !freebsd && !openbsd && !solaris && !windows && !dragonfly && !plan9 && !aix -// +build !darwin,!linux,!freebsd,!openbsd,!solaris,!windows,!dragonfly,!plan9,!aix +//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows && !dragonfly && !plan9 && !aix +// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!solaris,!windows,!dragonfly,!plan9,!aix package cpu diff --git a/cpu/cpu_linux.go b/cpu/cpu_linux.go index 42ed72c38..f223fdec7 100644 --- a/cpu/cpu_linux.go +++ b/cpu/cpu_linux.go @@ -11,8 +11,9 @@ import ( "strconv" "strings" - "github.com/shirou/gopsutil/v3/internal/common" "github.com/tklauser/go-sysconf" + + "github.com/shirou/gopsutil/v3/internal/common" ) var ClocksPerSec = float64(100) @@ -95,7 +96,7 @@ func Times(percpu bool) ([]TimesStat, error) { } func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { - filename := common.HostProc("stat") + filename := common.HostProcWithContext(ctx, "stat") lines := []string{} if percpu { statlines, err := common.ReadLines(filename) @@ -125,17 +126,17 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { return ret, nil } -func sysCPUPath(cpu int32, relPath string) string { - return common.HostSys(fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath) +func sysCPUPath(ctx context.Context, cpu int32, relPath string) string { + return common.HostSysWithContext(ctx, fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath) } -func finishCPUInfo(c *InfoStat) { +func finishCPUInfo(ctx context.Context, c *InfoStat) { var lines []string var err error var value float64 if len(c.CoreID) == 0 { - lines, err = common.ReadLines(sysCPUPath(c.CPU, "topology/core_id")) + lines, err = common.ReadLines(sysCPUPath(ctx, c.CPU, "topology/core_id")) if err == nil { c.CoreID = lines[0] } @@ -144,7 +145,7 @@ func finishCPUInfo(c *InfoStat) { // override the value of c.Mhz with cpufreq/cpuinfo_max_freq regardless // of the value from /proc/cpuinfo because we want to report the maximum // clock-speed of the CPU for c.Mhz, matching the behaviour of Windows - lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq")) + lines, err = common.ReadLines(sysCPUPath(ctx, c.CPU, "cpufreq/cpuinfo_max_freq")) // if we encounter errors below such as there are no cpuinfo_max_freq file, // we just ignore. so let Mhz is 0. if err != nil || len(lines) == 0 { @@ -172,7 +173,7 @@ func Info() ([]InfoStat, error) { } func InfoWithContext(ctx context.Context) ([]InfoStat, error) { - filename := common.HostProc("cpuinfo") + filename := common.HostProcWithContext(ctx, "cpuinfo") lines, _ := common.ReadLines(filename) var ret []InfoStat @@ -192,7 +193,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { processorName = value case "processor", "cpu number": if c.CPU >= 0 { - finishCPUInfo(&c) + finishCPUInfo(ctx, &c) ret = append(ret, c) } c = InfoStat{Cores: 1, ModelName: processorName} @@ -258,8 +259,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { } case "Model Name", "model name", "cpu": c.ModelName = value - if strings.Contains(value, "POWER8") || - strings.Contains(value, "POWER7") { + if strings.Contains(value, "POWER") { c.Model = strings.Split(value, " ")[0] c.Family = "POWER" c.VendorID = "IBM" @@ -304,7 +304,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) { } } if c.CPU >= 0 { - finishCPUInfo(&c) + finishCPUInfo(ctx, &c) ret = append(ret, c) } return ret, nil @@ -393,7 +393,7 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { if logical { ret := 0 // https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599 - procCpuinfo := common.HostProc("cpuinfo") + procCpuinfo := common.HostProcWithContext(ctx, "cpuinfo") lines, err := common.ReadLines(procCpuinfo) if err == nil { for _, line := range lines { @@ -407,7 +407,7 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { } } if ret == 0 { - procStat := common.HostProc("stat") + procStat := common.HostProcWithContext(ctx, "stat") lines, err = common.ReadLines(procStat) if err != nil { return 0, err @@ -428,7 +428,7 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { // https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964 // https://lkml.org/lkml/2019/2/26/41 for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} { - if files, err := filepath.Glob(common.HostSys(glob)); err == nil { + if files, err := filepath.Glob(common.HostSysWithContext(ctx, glob)); err == nil { for _, file := range files { lines, err := common.ReadLines(file) if err != nil || len(lines) != 1 { @@ -443,7 +443,7 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { } } // https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652 - filename := common.HostProc("cpuinfo") + filename := common.HostProcWithContext(ctx, "cpuinfo") lines, err := common.ReadLines(filename) if err != nil { return 0, err diff --git a/cpu/cpu_netbsd.go b/cpu/cpu_netbsd.go new file mode 100644 index 000000000..1f66be342 --- /dev/null +++ b/cpu/cpu_netbsd.go @@ -0,0 +1,119 @@ +//go:build netbsd +// +build netbsd + +package cpu + +import ( + "context" + "fmt" + "runtime" + "unsafe" + + "github.com/shirou/gopsutil/v3/internal/common" + "github.com/tklauser/go-sysconf" + "golang.org/x/sys/unix" +) + +const ( + // sys/sysctl.h + ctlKern = 1 // "high kernel": proc, limits + ctlHw = 6 // CTL_HW + kernCpTime = 51 // KERN_CPTIME +) + +var ClocksPerSec = float64(100) + +func init() { + clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) + // ignore errors + if err == nil { + ClocksPerSec = float64(clkTck) + } +} + +func Times(percpu bool) ([]TimesStat, error) { + return TimesWithContext(context.Background(), percpu) +} + +func TimesWithContext(ctx context.Context, percpu bool) (ret []TimesStat, err error) { + if !percpu { + mib := []int32{ctlKern, kernCpTime} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return ret, err + } + times := (*cpuTimes)(unsafe.Pointer(&buf[0])) + stat := TimesStat{ + CPU: "cpu-total", + User: float64(times.User), + Nice: float64(times.Nice), + System: float64(times.Sys), + Idle: float64(times.Idle), + Irq: float64(times.Intr), + } + return []TimesStat{stat}, nil + } + + ncpu, err := unix.SysctlUint32("hw.ncpu") + if err != nil { + return + } + + var i uint32 + for i = 0; i < ncpu; i++ { + mib := []int32{ctlKern, kernCpTime, int32(i)} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return ret, err + } + + stats := (*cpuTimes)(unsafe.Pointer(&buf[0])) + ret = append(ret, TimesStat{ + CPU: fmt.Sprintf("cpu%d", i), + User: float64(stats.User), + Nice: float64(stats.Nice), + System: float64(stats.Sys), + Idle: float64(stats.Idle), + Irq: float64(stats.Intr), + }) + } + + return ret, nil +} + +// Returns only one (minimal) CPUInfoStat on NetBSD +func Info() ([]InfoStat, error) { + return InfoWithContext(context.Background()) +} + +func InfoWithContext(ctx context.Context) ([]InfoStat, error) { + var ret []InfoStat + var err error + + c := InfoStat{} + + mhz, err := unix.Sysctl("machdep.dmi.processor-frequency") + if err != nil { + return nil, err + } + _, err = fmt.Sscanf(mhz, "%f", &c.Mhz) + if err != nil { + return nil, err + } + + ncpu, err := unix.SysctlUint32("hw.ncpuonline") + if err != nil { + return nil, err + } + c.Cores = int32(ncpu) + + if c.ModelName, err = unix.Sysctl("machdep.dmi.processor-version"); err != nil { + return nil, err + } + + return append(ret, c), nil +} + +func CountsWithContext(ctx context.Context, logical bool) (int, error) { + return runtime.NumCPU(), nil +} diff --git a/cpu/cpu_netbsd_amd64.go b/cpu/cpu_netbsd_amd64.go new file mode 100644 index 000000000..57e14528d --- /dev/null +++ b/cpu/cpu_netbsd_amd64.go @@ -0,0 +1,9 @@ +package cpu + +type cpuTimes struct { + User uint64 + Nice uint64 + Sys uint64 + Intr uint64 + Idle uint64 +} diff --git a/cpu/cpu_netbsd_arm64.go b/cpu/cpu_netbsd_arm64.go new file mode 100644 index 000000000..57e14528d --- /dev/null +++ b/cpu/cpu_netbsd_arm64.go @@ -0,0 +1,9 @@ +package cpu + +type cpuTimes struct { + User uint64 + Nice uint64 + Sys uint64 + Intr uint64 + Idle uint64 +} diff --git a/cpu/cpu_solaris_test.go b/cpu/cpu_solaris_test.go index 508aad5e6..dd9362c3a 100644 --- a/cpu/cpu_solaris_test.go +++ b/cpu/cpu_solaris_test.go @@ -1,7 +1,7 @@ package cpu import ( - "io/ioutil" + "os" "path/filepath" "reflect" "sort" @@ -49,7 +49,7 @@ func TestParseISAInfo(t *testing.T) { } for _, tc := range cases { - content, err := ioutil.ReadFile(filepath.Join("testdata", "solaris", tc.filename)) + content, err := os.ReadFile(filepath.Join("testdata", "solaris", tc.filename)) if err != nil { t.Errorf("cannot read test case: %s", err) } @@ -138,7 +138,7 @@ func TestParseProcessorInfo(t *testing.T) { } for _, tc := range cases { - content, err := ioutil.ReadFile(filepath.Join("testdata", "solaris", tc.filename)) + content, err := os.ReadFile(filepath.Join("testdata", "solaris", tc.filename)) if err != nil { t.Errorf("cannot read test case: %s", err) } diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index 91b8e8ad9..688660a1a 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" - "github.com/shirou/gopsutil/v3/internal/common" "github.com/stretchr/testify/assert" + + "github.com/shirou/gopsutil/v3/internal/common" ) func skipIfNotImplementedErr(t *testing.T, err error) { @@ -137,8 +138,8 @@ func testCPUPercent(t *testing.T, percpu bool) { if err != nil { t.Errorf("error %v", err) } - // Skip CircleCI which CPU num is different - if os.Getenv("CIRCLECI") != "true" { + // Skip CI which CPU num is different + if os.Getenv("CI") != "true" { if (percpu && len(v) != numcpu) || (!percpu && len(v) != 1) { t.Fatalf("wrong number of entries from CPUPercent: %v", v) } @@ -171,8 +172,8 @@ func testCPUPercentLastUsed(t *testing.T, percpu bool) { if err != nil { t.Errorf("error %v", err) } - // Skip CircleCI which CPU num is different - if os.Getenv("CIRCLECI") != "true" { + // Skip CI which CPU num is different + if os.Getenv("CI") != "true" { if (percpu && len(v) != numcpu) || (!percpu && len(v) != 1) { t.Fatalf("wrong number of entries from CPUPercent: %v", v) } diff --git a/disk/disk.go b/disk/disk.go index dd4cc1d5f..0d4b25345 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -9,6 +9,8 @@ import ( var invoke common.Invoker = common.Invoke{} +type Warnings = common.Warnings + type UsageStat struct { Path string `json:"path"` Fstype string `json:"fstype"` diff --git a/disk/disk_darwin.go b/disk/disk_darwin.go index 933cb0454..9362d9e5d 100644 --- a/disk/disk_darwin.go +++ b/disk/disk_darwin.go @@ -6,8 +6,9 @@ package disk import ( "context" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v3/internal/common" ) // PartitionsWithContext returns disk partition. diff --git a/disk/disk_fallback.go b/disk/disk_fallback.go index 476873340..36525f694 100644 --- a/disk/disk_fallback.go +++ b/disk/disk_fallback.go @@ -1,5 +1,5 @@ -//go:build !darwin && !linux && !freebsd && !openbsd && !windows && !solaris && !aix -// +build !darwin,!linux,!freebsd,!openbsd,!windows,!solaris,!aix +//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !windows && !solaris && !aix +// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!windows,!solaris,!aix package disk diff --git a/disk/disk_linux.go b/disk/disk_linux.go index b6a3adcf5..c06516c6c 100644 --- a/disk/disk_linux.go +++ b/disk/disk_linux.go @@ -9,15 +9,15 @@ import ( "context" "errors" "fmt" - "io/ioutil" "os" "path" "path/filepath" "strconv" "strings" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v3/internal/common" ) const ( @@ -259,10 +259,10 @@ func readMountFile(root string) (lines []string, useMounts bool, filename string func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) { // by default, try "/proc/1/..." first - root := common.HostProc(path.Join("1")) + root := common.HostProcWithContext(ctx, path.Join("1")) // force preference for dirname of HOST_PROC_MOUNTINFO, if set #1271 - hpmPath := os.Getenv("HOST_PROC_MOUNTINFO") + hpmPath := common.HostProcMountInfoWithContext(ctx) if hpmPath != "" { root = filepath.Dir(hpmPath) } @@ -273,13 +273,13 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro return nil, err } // fallback to "/proc/self/..." #1159 - lines, useMounts, filename, err = readMountFile(common.HostProc(path.Join("self"))) + lines, useMounts, filename, err = readMountFile(common.HostProcWithContext(ctx, path.Join("self"))) if err != nil { return nil, err } } - fs, err := getFileSystems() + fs, err := getFileSystems(ctx) if err != nil && !all { return nil, err } @@ -341,7 +341,7 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro } if strings.HasPrefix(d.Device, "/dev/mapper/") { - devpath, err := filepath.EvalSymlinks(common.HostDev(strings.Replace(d.Device, "/dev", "", 1))) + devpath, err := filepath.EvalSymlinks(common.HostDevWithContext(ctx, strings.Replace(d.Device, "/dev", "", 1))) if err == nil { d.Device = devpath } @@ -350,7 +350,7 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro // /dev/root is not the real device name // so we get the real device name from its major/minor number if d.Device == "/dev/root" { - devpath, err := os.Readlink(common.HostSys("/dev/block/" + blockDeviceID)) + devpath, err := os.Readlink(common.HostSysWithContext(ctx, "/dev/block/"+blockDeviceID)) if err == nil { d.Device = strings.Replace(d.Device, "root", filepath.Base(devpath), 1) } @@ -363,8 +363,8 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro } // getFileSystems returns supported filesystems from /proc/filesystems -func getFileSystems() ([]string, error) { - filename := common.HostProc("filesystems") +func getFileSystems(ctx context.Context) ([]string, error) { + filename := common.HostProcWithContext(ctx, "filesystems") lines, err := common.ReadLines(filename) if err != nil { return nil, err @@ -386,7 +386,7 @@ func getFileSystems() ([]string, error) { } func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { - filename := common.HostProc("diskstats") + filename := common.HostProcWithContext(ctx, "diskstats") lines, err := common.ReadLines(filename) if err != nil { return nil, err @@ -473,7 +473,11 @@ func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOC } d.Name = name - d.SerialNumber, _ = SerialNumberWithContext(ctx, name) + // Names passed in can be full paths (/dev/sda) or just device names (sda). + // Since `name` here is already a basename, re-add the /dev path. + // This is not ideal, but we may break the API by changing how SerialNumberWithContext + // works. + d.SerialNumber, _ = SerialNumberWithContext(ctx, common.HostDevWithContext(ctx, name)) d.Label, _ = LabelWithContext(ctx, name) ret[name] = d @@ -491,8 +495,8 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { minor := unix.Minor(uint64(stat.Rdev)) // Try to get the serial from udev data - udevDataPath := common.HostRun(fmt.Sprintf("udev/data/b%d:%d", major, minor)) - if udevdata, err := ioutil.ReadFile(udevDataPath); err == nil { + udevDataPath := common.HostRunWithContext(ctx, fmt.Sprintf("udev/data/b%d:%d", major, minor)) + if udevdata, err := os.ReadFile(udevDataPath); err == nil { scanner := bufio.NewScanner(bytes.NewReader(udevdata)) for scanner.Scan() { values := strings.Split(scanner.Text(), "=") @@ -504,9 +508,9 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { // Try to get the serial from sysfs, look at the disk device (minor 0) directly // because if it is a partition it is not going to contain any device information - devicePath := common.HostSys(fmt.Sprintf("dev/block/%d:0/device", major)) - model, _ := ioutil.ReadFile(filepath.Join(devicePath, "model")) - serial, _ := ioutil.ReadFile(filepath.Join(devicePath, "serial")) + devicePath := common.HostSysWithContext(ctx, fmt.Sprintf("dev/block/%d:0/device", major)) + model, _ := os.ReadFile(filepath.Join(devicePath, "model")) + serial, _ := os.ReadFile(filepath.Join(devicePath, "serial")) if len(model) > 0 && len(serial) > 0 { return fmt.Sprintf("%s_%s", string(model), string(serial)), nil } @@ -515,13 +519,13 @@ func SerialNumberWithContext(ctx context.Context, name string) (string, error) { func LabelWithContext(ctx context.Context, name string) (string, error) { // Try label based on devicemapper name - dmname_filename := common.HostSys(fmt.Sprintf("block/%s/dm/name", name)) + dmname_filename := common.HostSysWithContext(ctx, fmt.Sprintf("block/%s/dm/name", name)) if !common.PathExists(dmname_filename) { return "", nil } - dmname, err := ioutil.ReadFile(dmname_filename) + dmname, err := os.ReadFile(dmname_filename) if err != nil { return "", err } diff --git a/disk/disk_netbsd.go b/disk/disk_netbsd.go new file mode 100644 index 000000000..5976efadb --- /dev/null +++ b/disk/disk_netbsd.go @@ -0,0 +1,152 @@ +//go:build netbsd +// +build netbsd + +package disk + +import ( + "context" + "unsafe" + + "github.com/shirou/gopsutil/v3/internal/common" + "golang.org/x/sys/unix" +) + +const ( + // see sys/fstypes.h and `man 5 statvfs` + MNT_RDONLY = 0x00000001 /* read only filesystem */ + MNT_SYNCHRONOUS = 0x00000002 /* file system written synchronously */ + MNT_NOEXEC = 0x00000004 /* can't exec from filesystem */ + MNT_NOSUID = 0x00000008 /* don't honor setuid bits on fs */ + MNT_NODEV = 0x00000010 /* don't interpret special files */ + MNT_ASYNC = 0x00000040 /* file system written asynchronously */ + MNT_NOATIME = 0x04000000 /* Never update access times in fs */ + MNT_SOFTDEP = 0x80000000 /* Use soft dependencies */ +) + +func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) { + var ret []PartitionStat + + flag := uint64(1) // ST_WAIT/MNT_WAIT, see sys/fstypes.h + + // get required buffer size + emptyBufSize := 0 + r, _, err := unix.Syscall( + 483, // SYS___getvfsstat90 syscall + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(&emptyBufSize)), + uintptr(unsafe.Pointer(&flag)), + ) + if err != 0 { + return ret, err + } + mountedFsCount := uint64(r) + + // calculate the buffer size + bufSize := sizeOfStatvfs * mountedFsCount + buf := make([]Statvfs, mountedFsCount) + + // request agian to get desired mount data + _, _, err = unix.Syscall( + 483, // SYS___getvfsstat90 syscall + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufSize)), + uintptr(unsafe.Pointer(&flag)), + ) + if err != 0 { + return ret, err + } + + for _, stat := range buf { + opts := []string{"rw"} + if stat.Flag&MNT_RDONLY != 0 { + opts = []string{"rw"} + } + if stat.Flag&MNT_SYNCHRONOUS != 0 { + opts = append(opts, "sync") + } + if stat.Flag&MNT_NOEXEC != 0 { + opts = append(opts, "noexec") + } + if stat.Flag&MNT_NOSUID != 0 { + opts = append(opts, "nosuid") + } + if stat.Flag&MNT_NODEV != 0 { + opts = append(opts, "nodev") + } + if stat.Flag&MNT_ASYNC != 0 { + opts = append(opts, "async") + } + if stat.Flag&MNT_SOFTDEP != 0 { + opts = append(opts, "softdep") + } + if stat.Flag&MNT_NOATIME != 0 { + opts = append(opts, "noatime") + } + + d := PartitionStat{ + Device: common.ByteToString([]byte(stat.Mntfromname[:])), + Mountpoint: common.ByteToString([]byte(stat.Mntonname[:])), + Fstype: common.ByteToString([]byte(stat.Fstypename[:])), + Opts: opts, + } + + ret = append(ret, d) + } + + return ret, nil +} + +func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { + ret := make(map[string]IOCountersStat) + return ret, common.ErrNotImplementedError +} + +func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { + stat := Statvfs{} + flag := uint64(1) // ST_WAIT/MNT_WAIT, see sys/fstypes.h + + _path, e := unix.BytePtrFromString(path) + if e != nil { + return nil, e + } + + _, _, err := unix.Syscall( + 484, // SYS___statvfs190, see sys/syscall.h + uintptr(unsafe.Pointer(_path)), + uintptr(unsafe.Pointer(&stat)), + uintptr(unsafe.Pointer(&flag)), + ) + if err != 0 { + return nil, err + } + + // frsize is the real block size on NetBSD. See discuss here: https://bugzilla.samba.org/show_bug.cgi?id=11810 + bsize := stat.Frsize + ret := &UsageStat{ + Path: path, + Fstype: getFsType(stat), + Total: (uint64(stat.Blocks) * uint64(bsize)), + Free: (uint64(stat.Bavail) * uint64(bsize)), + InodesTotal: (uint64(stat.Files)), + InodesFree: (uint64(stat.Ffree)), + } + + ret.InodesUsed = (ret.InodesTotal - ret.InodesFree) + ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0 + ret.Used = (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(bsize) + ret.UsedPercent = (float64(ret.Used) / float64(ret.Total)) * 100.0 + + return ret, nil +} + +func getFsType(stat Statvfs) string { + return common.ByteToString(stat.Fstypename[:]) +} + +func SerialNumberWithContext(ctx context.Context, name string) (string, error) { + return "", common.ErrNotImplementedError +} + +func LabelWithContext(ctx context.Context, name string) (string, error) { + return "", common.ErrNotImplementedError +} diff --git a/disk/disk_netbsd_amd64.go b/disk/disk_netbsd_amd64.go new file mode 100644 index 000000000..c21421cfe --- /dev/null +++ b/disk/disk_netbsd_amd64.go @@ -0,0 +1,45 @@ +//go:build netbsd && amd64 +// +build netbsd,amd64 + +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs types_netbsd.go + +package disk + +const ( + sizeOfStatvfs = 0xce0 +) + +type ( + Statvfs struct { + Flag uint64 + Bsize uint64 + Frsize uint64 + Iosize uint64 + Blocks uint64 + Bfree uint64 + Bavail uint64 + Bresvd uint64 + Files uint64 + Ffree uint64 + Favail uint64 + Fresvd uint64 + Syncreads uint64 + Syncwrites uint64 + Asyncreads uint64 + Asyncwrites uint64 + Fsidx _Ctype_struct___0 + Fsid uint64 + Namemax uint64 + Owner uint32 + Spare [4]uint64 + Fstypename [32]uint8 + Mntonname [1024]uint8 + Mntfromname [1024]uint8 + Mntfromlabel [1024]uint8 + } +) + +type _Ctype_struct___0 struct { + FsidVal [2]int32 +} diff --git a/disk/disk_netbsd_arm64.go b/disk/disk_netbsd_arm64.go new file mode 100644 index 000000000..dfe48f812 --- /dev/null +++ b/disk/disk_netbsd_arm64.go @@ -0,0 +1,45 @@ +//go:build netbsd && arm64 +// +build netbsd,arm64 + +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs types_netbsd.go + +package disk + +const ( + sizeOfStatvfs = 0xce0 +) + +type ( + Statvfs struct { + Flag uint64 + Bsize uint64 + Frsize uint64 + Iosize uint64 + Blocks uint64 + Bfree uint64 + Bavail uint64 + Bresvd uint64 + Files uint64 + Ffree uint64 + Favail uint64 + Fresvd uint64 + Syncreads uint64 + Syncwrites uint64 + Asyncreads uint64 + Asyncwrites uint64 + Fsidx _Ctype_struct___0 + Fsid uint64 + Namemax uint64 + Owner uint32 + Spare [4]uint64 + Fstypename [32]uint8 + Mntonname [1024]uint8 + Mntfromname [1024]uint8 + Mntfromlabel [1024]uint8 + } +) + +type _Ctype_struct___0 struct { + FsidVal [2]int32 +} diff --git a/disk/disk_windows.go b/disk/disk_windows.go index 07c3cd796..22bb3b6bf 100644 --- a/disk/disk_windows.go +++ b/disk/disk_windows.go @@ -15,8 +15,6 @@ import ( "golang.org/x/sys/windows/registry" ) -type Warnings = common.Warnings - var ( procGetDiskFreeSpaceExW = common.Modkernel32.NewProc("GetDiskFreeSpaceExW") procGetLogicalDriveStringsW = common.Modkernel32.NewProc("GetLogicalDriveStringsW") @@ -52,6 +50,7 @@ func init() { key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\PartMgr`, registry.SET_VALUE) if err == nil { key.SetDWordValue("EnableCounterForIoctl", 1) + key.Close() } } @@ -87,20 +86,22 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro warnings := Warnings{ Verbose: true, } - var ret []PartitionStat - retChan := make(chan []PartitionStat) - errChan := make(chan error) - defer close(retChan) - defer close(errChan) - lpBuffer := make([]byte, 254) + var errLogicalDrives error + retChan := make(chan PartitionStat) + quitChan := make(chan struct{}) + defer close(quitChan) + + getPartitions := func() { + defer close(retChan) + + lpBuffer := make([]byte, 254) - f := func() { diskret, _, err := procGetLogicalDriveStringsW.Call( uintptr(len(lpBuffer)), uintptr(unsafe.Pointer(&lpBuffer[0]))) if diskret == 0 { - errChan <- err + errLogicalDrives = err return } for _, v := range lpBuffer { @@ -147,27 +148,37 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro opts = append(opts, "compress") } - d := PartitionStat{ + select { + case retChan <- PartitionStat{ Mountpoint: path, Device: path, - Fstype: string(bytes.Replace(lpFileSystemNameBuffer, []byte("\x00"), []byte(""), -1)), + Fstype: string(bytes.ReplaceAll(lpFileSystemNameBuffer, []byte("\x00"), []byte(""))), Opts: opts, + }: + case <-quitChan: + return } - ret = append(ret, d) } } } - retChan <- ret } - go f() - select { - case err := <-errChan: - return ret, err - case ret := <-retChan: - return ret, warnings.Reference() - case <-ctx.Done(): - return ret, ctx.Err() + go getPartitions() + + var ret []PartitionStat + for { + select { + case p, ok := <-retChan: + if !ok { + if errLogicalDrives != nil { + return ret, errLogicalDrives + } + return ret, warnings.Reference() + } + ret = append(ret, p) + case <-ctx.Done(): + return ret, ctx.Err() + } } } diff --git a/disk/types_netbsd.go b/disk/types_netbsd.go new file mode 100644 index 000000000..c0326f5c2 --- /dev/null +++ b/disk/types_netbsd.go @@ -0,0 +1,30 @@ +//go:build ignore +// +build ignore + +// Hand writing: _Ctype_struct___0 + +/* +Input to cgo -godefs. +*/ + +package disk + +/* +#include +#include +#include +#include +#include +#include +#include + +*/ +import "C" + +const ( + sizeOfStatvfs = C.sizeof_struct_statvfs +) + +type ( + Statvfs C.struct_statvfs +) diff --git a/docker/docker_linux.go b/docker/docker_linux.go index ac7cc9809..4904874d0 100644 --- a/docker/docker_linux.go +++ b/docker/docker_linux.go @@ -100,7 +100,7 @@ func CgroupCPUUsage(containerID string, base string) (float64, error) { } func CgroupCPUWithContext(ctx context.Context, containerID string, base string) (*CgroupCPUStat, error) { - statfile := getCgroupFilePath(containerID, base, "cpuacct", "cpuacct.stat") + statfile := getCgroupFilePath(ctx, containerID, base, "cpuacct", "cpuacct.stat") lines, err := common.ReadLines(statfile) if err != nil { return nil, err @@ -136,7 +136,7 @@ func CgroupCPUWithContext(ctx context.Context, containerID string, base string) } func CgroupCPUUsageWithContext(ctx context.Context, containerID, base string) (float64, error) { - usagefile := getCgroupFilePath(containerID, base, "cpuacct", "cpuacct.usage") + usagefile := getCgroupFilePath(ctx, containerID, base, "cpuacct", "cpuacct.usage") lines, err := common.ReadLinesOffsetN(usagefile, 0, 1) if err != nil { return 0.0, err @@ -159,11 +159,11 @@ func CgroupCPUUsageDocker(containerid string) (float64, error) { } func CgroupCPUDockerWithContext(ctx context.Context, containerid string) (*CgroupCPUStat, error) { - return CgroupCPUWithContext(ctx, containerid, common.HostSys("fs/cgroup/cpuacct/docker")) + return CgroupCPUWithContext(ctx, containerid, common.HostSysWithContext(ctx, "fs/cgroup/cpuacct/docker")) } func CgroupCPUDockerUsageWithContext(ctx context.Context, containerid string) (float64, error) { - return CgroupCPUUsageWithContext(ctx, containerid, common.HostSys("fs/cgroup/cpuacct/docker")) + return CgroupCPUUsageWithContext(ctx, containerid, common.HostSysWithContext(ctx, "fs/cgroup/cpuacct/docker")) } func CgroupMem(containerID string, base string) (*CgroupMemStat, error) { @@ -171,7 +171,7 @@ func CgroupMem(containerID string, base string) (*CgroupMemStat, error) { } func CgroupMemWithContext(ctx context.Context, containerID string, base string) (*CgroupMemStat, error) { - statfile := getCgroupFilePath(containerID, base, "memory", "memory.stat") + statfile := getCgroupFilePath(ctx, containerID, base, "memory", "memory.stat") // empty containerID means all cgroup if len(containerID) == 0 { @@ -246,19 +246,19 @@ func CgroupMemWithContext(ctx context.Context, containerID string, base string) } } - r, err := getCgroupMemFile(containerID, base, "memory.usage_in_bytes") + r, err := getCgroupMemFile(ctx, containerID, base, "memory.usage_in_bytes") if err == nil { ret.MemUsageInBytes = r } - r, err = getCgroupMemFile(containerID, base, "memory.max_usage_in_bytes") + r, err = getCgroupMemFile(ctx, containerID, base, "memory.max_usage_in_bytes") if err == nil { ret.MemMaxUsageInBytes = r } - r, err = getCgroupMemFile(containerID, base, "memory.limit_in_bytes") + r, err = getCgroupMemFile(ctx, containerID, base, "memory.limit_in_bytes") if err == nil { ret.MemLimitInBytes = r } - r, err = getCgroupMemFile(containerID, base, "memory.failcnt") + r, err = getCgroupMemFile(ctx, containerID, base, "memory.failcnt") if err == nil { ret.MemFailCnt = r } @@ -271,27 +271,27 @@ func CgroupMemDocker(containerID string) (*CgroupMemStat, error) { } func CgroupMemDockerWithContext(ctx context.Context, containerID string) (*CgroupMemStat, error) { - return CgroupMemWithContext(ctx, containerID, common.HostSys("fs/cgroup/memory/docker")) + return CgroupMemWithContext(ctx, containerID, common.HostSysWithContext(ctx, "fs/cgroup/memory/docker")) } // getCgroupFilePath constructs file path to get targeted stats file. -func getCgroupFilePath(containerID, base, target, file string) string { +func getCgroupFilePath(ctx context.Context, containerID, base, target, file string) string { if len(base) == 0 { - base = common.HostSys(fmt.Sprintf("fs/cgroup/%s/docker", target)) + base = common.HostSysWithContext(ctx, fmt.Sprintf("fs/cgroup/%s/docker", target)) } statfile := path.Join(base, containerID, file) if _, err := os.Stat(statfile); os.IsNotExist(err) { statfile = path.Join( - common.HostSys(fmt.Sprintf("fs/cgroup/%s/system.slice", target)), "docker-"+containerID+".scope", file) + common.HostSysWithContext(ctx, fmt.Sprintf("fs/cgroup/%s/system.slice", target)), "docker-"+containerID+".scope", file) } return statfile } // getCgroupMemFile reads a cgroup file and return the contents as uint64. -func getCgroupMemFile(containerID, base, file string) (uint64, error) { - statfile := getCgroupFilePath(containerID, base, "memory", file) +func getCgroupMemFile(ctx context.Context, containerID, base, file string) (uint64, error) { + statfile := getCgroupFilePath(ctx, containerID, base, "memory", file) lines, err := common.ReadLines(statfile) if err != nil { return 0, err diff --git a/docker/docker_linux_test.go b/docker/docker_linux_test.go index c6afb44a7..5ef80f932 100644 --- a/docker/docker_linux_test.go +++ b/docker/docker_linux_test.go @@ -3,7 +3,10 @@ package docker -import "testing" +import ( + "context" + "testing" +) func TestGetDockerIDList(t *testing.T) { // If there is not docker environment, this test always fail. @@ -43,7 +46,7 @@ func TestGetDockerStat(t *testing.T) { func TestCgroupCPU(t *testing.T) { v, _ := GetDockerIDList() for _, id := range v { - v, err := CgroupCPUDocker(id) + v, err := CgroupCPUDockerWithContext(context.Background(), id) if err != nil { t.Errorf("error %v", err) } @@ -55,7 +58,7 @@ func TestCgroupCPU(t *testing.T) { } func TestCgroupCPUInvalidId(t *testing.T) { - _, err := CgroupCPUDocker("bad id") + _, err := CgroupCPUDockerWithContext(context.Background(), "bad id") if err == nil { t.Error("Expected path does not exist error") } diff --git a/docker/docker_notlinux.go b/docker/docker_notlinux.go index 2bd91110b..434ca12aa 100644 --- a/docker/docker_notlinux.go +++ b/docker/docker_notlinux.go @@ -46,7 +46,7 @@ func CgroupCPUDocker(containerid string) (*CgroupCPUStat, error) { } func CgroupCPUDockerWithContext(ctx context.Context, containerid string) (*CgroupCPUStat, error) { - return CgroupCPU(containerid, common.HostSys("fs/cgroup/cpuacct/docker")) + return CgroupCPUWithContext(ctx, containerid, common.HostSysWithContext(ctx, "fs/cgroup/cpuacct/docker")) } func CgroupMem(containerid string, base string) (*CgroupMemStat, error) { @@ -62,5 +62,5 @@ func CgroupMemDocker(containerid string) (*CgroupMemStat, error) { } func CgroupMemDockerWithContext(ctx context.Context, containerid string) (*CgroupMemStat, error) { - return CgroupMem(containerid, common.HostSys("fs/cgroup/memory/docker")) + return CgroupMemWithContext(ctx, containerid, common.HostSysWithContext(ctx, "fs/cgroup/memory/docker")) } diff --git a/go.mod b/go.mod index a241b8a2d..0ff9d48fa 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,14 @@ module github.com/shirou/gopsutil/v3 go 1.15 require ( - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c - github.com/stretchr/testify v1.8.2 - github.com/tklauser/go-sysconf v0.3.11 - github.com/yusufpapurcu/wmi v1.2.2 - golang.org/x/sys v0.5.0 + github.com/shoenig/go-m1cpu v0.1.6 + github.com/stretchr/testify v1.8.4 + github.com/tklauser/go-sysconf v0.3.12 + github.com/yusufpapurcu/wmi v1.2.3 + golang.org/x/sys v0.13.0 ) retract v3.22.11 diff --git a/go.sum b/go.sum index 3274f8404..92b9ffbd6 100644 --- a/go.sum +++ b/go.sum @@ -4,32 +4,38 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +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= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= -github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= -github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= -github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/host/host.go b/host/host.go index 712b4c1f2..01bf2be1a 100644 --- a/host/host.go +++ b/host/host.go @@ -11,6 +11,8 @@ import ( "github.com/shirou/gopsutil/v3/internal/common" ) +type Warnings = common.Warnings + var invoke common.Invoker = common.Invoke{} // A HostInfoStat describes the host status. diff --git a/host/host_bsd.go b/host/host_bsd.go index 4dc2bba58..67ae900bc 100644 --- a/host/host_bsd.go +++ b/host/host_bsd.go @@ -1,5 +1,5 @@ -//go:build darwin || freebsd || openbsd -// +build darwin freebsd openbsd +//go:build darwin || freebsd || openbsd || netbsd +// +build darwin freebsd openbsd netbsd package host diff --git a/host/host_darwin.go b/host/host_darwin.go index cd1011758..89b6bc9fc 100644 --- a/host/host_darwin.go +++ b/host/host_darwin.go @@ -8,14 +8,15 @@ import ( "context" "encoding/binary" "errors" - "io/ioutil" + "io" "os" "strings" "unsafe" + "golang.org/x/sys/unix" + "github.com/shirou/gopsutil/v3/internal/common" "github.com/shirou/gopsutil/v3/process" - "golang.org/x/sys/unix" ) // from utmpx.h @@ -58,7 +59,7 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { } defer file.Close() - buf, err := ioutil.ReadAll(file) + buf, err := io.ReadAll(file) if err != nil { return ret, err } @@ -103,7 +104,7 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string } //added by opsramp - out, err = invoke.CommandWithContext(ctx, sw_vers, "-productName") + out, err := invoke.CommandWithContext(ctx, "sw_vers", "-productName") if err == nil { productName := strings.TrimSpace(string(out)) if strings.HasPrefix(productName, "Mac") { @@ -114,7 +115,7 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string family = productName } - out, err := invoke.CommandWithContext(ctx, "sw_vers", "-productVersion") + out, err = invoke.CommandWithContext(ctx, "sw_vers", "-productVersion") if err == nil { pver = strings.ToLower(strings.TrimSpace(string(out))) pver = strings.Replace(pver, "\"", "", -1) diff --git a/host/host_fallback.go b/host/host_fallback.go index 585250f9a..a393ca15d 100644 --- a/host/host_fallback.go +++ b/host/host_fallback.go @@ -1,5 +1,5 @@ -//go:build !darwin && !linux && !freebsd && !openbsd && !solaris && !windows -// +build !darwin,!linux,!freebsd,!openbsd,!solaris,!windows +//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows +// +build !darwin,!linux,!freebsd,!openbsd,!netbsd,!solaris,!windows package host diff --git a/host/host_freebsd.go b/host/host_freebsd.go index 2c9aa9d0d..9a5382d39 100644 --- a/host/host_freebsd.go +++ b/host/host_freebsd.go @@ -7,7 +7,7 @@ import ( "bytes" "context" "encoding/binary" - "io/ioutil" + "io" "math" "os" "strings" @@ -54,7 +54,7 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { } defer file.Close() - buf, err := ioutil.ReadAll(file) + buf, err := io.ReadAll(file) if err != nil { return ret, err } @@ -111,7 +111,7 @@ func getUsersFromUtmp(utmpfile string) ([]UserStat, error) { } defer file.Close() - buf, err := ioutil.ReadAll(file) + buf, err := io.ReadAll(file) if err != nil { return ret, err } diff --git a/host/host_linux.go b/host/host_linux.go index 23d5eb83f..a7e7c5e3a 100644 --- a/host/host_linux.go +++ b/host/host_linux.go @@ -8,7 +8,7 @@ import ( "context" "encoding/binary" "fmt" - "io/ioutil" + "io" "os" "os/exec" "path/filepath" @@ -16,11 +16,10 @@ import ( "strconv" "strings" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" -) -type Warnings = common.Warnings + "github.com/shirou/gopsutil/v3/internal/common" +) type lsbStruct struct { ID string @@ -37,9 +36,9 @@ const ( ) func HostIDWithContext(ctx context.Context) (string, error) { - sysProductUUID := common.HostSys("class/dmi/id/product_uuid") - machineID := common.HostEtc("machine-id") - procSysKernelRandomBootID := common.HostProc("sys/kernel/random/boot_id") + sysProductUUID := common.HostSysWithContext(ctx, "class/dmi/id/product_uuid") + machineID := common.HostEtcWithContext(ctx, "machine-id") + procSysKernelRandomBootID := common.HostProcWithContext(ctx, "sys/kernel/random/boot_id") switch { // In order to read this file, needs to be supported by kernel/arch and run as root // so having fallback is important @@ -69,7 +68,7 @@ func HostIDWithContext(ctx context.Context) (string, error) { } func numProcs(ctx context.Context) (uint64, error) { - return common.NumProcs() + return common.NumProcsWithContext(ctx) } func BootTimeWithContext(ctx context.Context) (uint64, error) { @@ -85,7 +84,7 @@ func UptimeWithContext(ctx context.Context) (uint64, error) { } func UsersWithContext(ctx context.Context) ([]UserStat, error) { - utmpfile := common.HostVar("run/utmp") + utmpfile := common.HostVarWithContext(ctx, "run/utmp") file, err := os.Open(utmpfile) if err != nil { @@ -93,7 +92,7 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { } defer file.Close() - buf, err := ioutil.ReadAll(file) + buf, err := io.ReadAll(file) if err != nil { return nil, err } @@ -126,10 +125,10 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { return ret, nil } -func getlsbStruct() (*lsbStruct, error) { +func getlsbStruct(ctx context.Context) (*lsbStruct, error) { ret := &lsbStruct{} - if common.PathExists(common.HostEtc("lsb-release")) { - contents, err := common.ReadLines(common.HostEtc("lsb-release")) + if common.PathExists(common.HostEtcWithContext(ctx, "lsb-release")) { + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "lsb-release")) if err != nil { return ret, err // return empty } @@ -140,13 +139,13 @@ func getlsbStruct() (*lsbStruct, error) { } switch field[0] { case "DISTRIB_ID": - ret.ID = field[1] + ret.ID = strings.ReplaceAll(field[1], `"`, ``) case "DISTRIB_RELEASE": - ret.Release = field[1] + ret.Release = strings.ReplaceAll(field[1], `"`, ``) case "DISTRIB_CODENAME": - ret.Codename = field[1] + ret.Codename = strings.ReplaceAll(field[1], `"`, ``) case "DISTRIB_DESCRIPTION": - ret.Description = field[1] + ret.Description = strings.ReplaceAll(field[1], `"`, ``) } } } else if common.PathExists("/usr/bin/lsb_release") { @@ -168,13 +167,13 @@ func getlsbStruct() (*lsbStruct, error) { } switch strings.TrimSpace(field[0]) { case "Distributor ID": - ret.ID = strings.TrimSpace(field[1]) + ret.ID = strings.TrimSpace(strings.ReplaceAll(field[1], `"`, ``)) case "Release": - ret.Release = strings.TrimSpace(field[1]) + ret.Release = strings.TrimSpace(strings.ReplaceAll(field[1], `"`, ``)) case "Codename": - ret.Codename = strings.TrimSpace(field[1]) + ret.Codename = strings.TrimSpace(strings.ReplaceAll(field[1], `"`, ``)) case "Description": - ret.Description = strings.TrimSpace(field[1]) + ret.Description = strings.TrimSpace(strings.ReplaceAll(field[1], `"`, ``)) } } @@ -184,34 +183,34 @@ func getlsbStruct() (*lsbStruct, error) { } func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, description string, err error) { - lsb, err := getlsbStruct() + lsb, err := getlsbStruct(ctx) if err != nil { lsb = &lsbStruct{} } - if common.PathExistsWithContents(common.HostEtc("oracle-release")) { + if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "oracle-release")) { platform = "oracle" - contents, err := common.ReadLines(common.HostEtc("oracle-release")) + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "oracle-release")) if err == nil { version = getRedhatishVersion(contents) description = contents[0] } - } else if common.PathExistsWithContents(common.HostEtc("enterprise-release")) { + } else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "enterprise-release")) { platform = "oracle" - contents, err := common.ReadLines(common.HostEtc("enterprise-release")) + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "enterprise-release")) if err == nil { version = getRedhatishVersion(contents) description = contents[0] } - } else if common.PathExistsWithContents(common.HostEtc("slackware-version")) { + } else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "slackware-version")) { platform = "slackware" - contents, err := common.ReadLines(common.HostEtc("slackware-version")) + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "slackware-version")) if err == nil { version = getSlackwareVersion(contents) description = contents[0] } - } else if common.PathExistsWithContents(common.HostEtc("debian_version")) { + } else if common.PathExistsWithContents(common.HostEtcWithContext(ctx, "debian_version")) { if lsb.ID == "Ubuntu" { platform = "ubuntu" version = lsb.Release @@ -224,27 +223,31 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil platform = "Kylin" version = lsb.Release description = lsb.Description + } else if lsb.ID == `"Cumulus Linux"` { + platform = "cumuluslinux" + version = lsb.Release + description = lsb.Description } else { if common.PathExistsWithContents("/usr/bin/raspi-config") { platform = "raspbian" } else { platform = "debian" } - contents, err := common.ReadLines(common.HostEtc("debian_version")) + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "debian_version")) if err == nil && len(contents) > 0 && contents[0] != "" { version = contents[0] description = lsb.Description } } - } else if common.PathExists(common.HostEtc("neokylin-release")) { - contents, err := common.ReadLines(common.HostEtc("neokylin-release")) + } else if common.PathExists(common.HostEtcWithContext(ctx, "neokylin-release")) { + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "neokylin-release")) if err == nil { version = getRedhatishVersion(contents) platform = getRedhatishPlatform(contents) description = contents[0] } - } else if common.PathExists(common.HostEtc("redhat-release")) { - contents, err := common.ReadLines(common.HostEtc("redhat-release")) + } else if common.PathExists(common.HostEtcWithContext(ctx, "redhat-release")) { + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "redhat-release")) if err == nil { version = getRedhatishVersion(contents) platform = getRedhatishPlatform(contents) @@ -257,41 +260,41 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil platform = getRedhatishPlatform(contents) description = contents[0] } - } else if common.PathExists(common.HostEtc("system-release")) { - contents, err := common.ReadLines(common.HostEtc("system-release")) + } else if common.PathExists(common.HostEtcWithContext(ctx, "system-release")) { + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "system-release")) if err == nil { version = getRedhatishVersion(contents) platform = getRedhatishPlatform(contents) description = contents[0] } - } else if common.PathExists(common.HostEtc("gentoo-release")) { + } else if common.PathExists(common.HostEtcWithContext(ctx, "gentoo-release")) { platform = "gentoo" - contents, err := common.ReadLines(common.HostEtc("gentoo-release")) + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "gentoo-release")) if err == nil { version = getRedhatishVersion(contents) description = contents[0] } - } else if common.PathExists(common.HostEtc("SuSE-release")) { - contents, err := common.ReadLines(common.HostEtc("SuSE-release")) + } else if common.PathExists(common.HostEtcWithContext(ctx, "SuSE-release")) { + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "SuSE-release")) if err == nil { version = getSuseVersion(contents) platform = getSusePlatform(contents) description = contents[0] } // TODO: slackware detecion - } else if common.PathExists(common.HostEtc("arch-release")) { + } else if common.PathExists(common.HostEtcWithContext(ctx, "arch-release")) { platform = "arch" version = lsb.Release description = lsb.Description - } else if common.PathExists(common.HostEtc("alpine-release")) { + } else if common.PathExists(common.HostEtcWithContext(ctx, "alpine-release")) { platform = "alpine" - contents, err := common.ReadLines(common.HostEtc("alpine-release")) + contents, err := common.ReadLines(common.HostEtcWithContext(ctx, "alpine-release")) if err == nil && len(contents) > 0 && contents[0] != "" { version = contents[0] description = lsb.Description } - } else if common.PathExists(common.HostEtc("os-release")) { - p, v, d, err := common.GetOSRelease() + } else if common.PathExists(common.HostEtcWithContext(ctx, "os-release")) { + p, v, d, err := common.GetOSReleaseWithContext(ctx) if err == nil { platform = p version = v @@ -322,7 +325,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil platform = strings.Trim(platform, `"`) switch platform { - case "debian", "ubuntu", "linuxmint", "raspbian": + case "debian", "ubuntu", "linuxmint", "raspbian", "Kylin", "cumuluslinux": family = "debian" case "fedora": family = "fedora" @@ -426,14 +429,14 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err // Only the temp*_input file provides current temperature // value in millidegree Celsius as reported by the temperature to the device: // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface - if files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/temp*_input")); err != nil { + if files, err = filepath.Glob(common.HostSysWithContext(ctx, "/class/hwmon/hwmon*/temp*_input")); err != nil { return temperatures, err } if len(files) == 0 { // CentOS has an intermediate /device directory: // https://github.com/giampaolo/psutil/issues/971 - if files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/device/temp*_input")); err != nil { + if files, err = filepath.Glob(common.HostSysWithContext(ctx, "/class/hwmon/hwmon*/device/temp*_input")); err != nil { return temperatures, err } } @@ -441,19 +444,19 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err var warns Warnings if len(files) == 0 { // handle distributions without hwmon, like raspbian #391, parse legacy thermal_zone files - files, err = filepath.Glob(common.HostSys("/class/thermal/thermal_zone*/")) + files, err = filepath.Glob(common.HostSysWithContext(ctx, "/class/thermal/thermal_zone*/")) if err != nil { return temperatures, err } for _, file := range files { // Get the name of the temperature you are reading - name, err := ioutil.ReadFile(filepath.Join(file, "type")) + name, err := os.ReadFile(filepath.Join(file, "type")) if err != nil { warns.Add(err) continue } // Get the temperature reading - current, err := ioutil.ReadFile(filepath.Join(file, "temp")) + current, err := os.ReadFile(filepath.Join(file, "temp")) if err != nil { warns.Add(err) continue @@ -497,13 +500,13 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err // Get the label of the temperature you are reading label := "" - if raw, _ = ioutil.ReadFile(basepath + "_label"); len(raw) != 0 { + if raw, _ = os.ReadFile(basepath + "_label"); len(raw) != 0 { // Format the label from "Core 0" to "core_0" label = strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(raw))), " "), "_") } // Get the name of the temperature you are reading - if raw, err = ioutil.ReadFile(filepath.Join(directory, "name")); err != nil { + if raw, err = os.ReadFile(filepath.Join(directory, "name")); err != nil { warns.Add(err) continue } @@ -515,7 +518,7 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err } // Get the temperature reading - if raw, err = ioutil.ReadFile(file); err != nil { + if raw, err = os.ReadFile(file); err != nil { warns.Add(err) continue } @@ -549,7 +552,7 @@ func optionalValueReadFromFile(filename string) float64 { return 0 } - if raw, err = ioutil.ReadFile(filename); err != nil { + if raw, err = os.ReadFile(filename); err != nil { return 0 } diff --git a/host/host_linux_test.go b/host/host_linux_test.go index 8c23e5661..c114ec795 100644 --- a/host/host_linux_test.go +++ b/host/host_linux_test.go @@ -4,7 +4,10 @@ package host import ( + "context" "testing" + + "github.com/shirou/gopsutil/v3/common" ) func TestGetRedhatishVersion(t *testing.T) { @@ -60,3 +63,45 @@ func TestGetRedhatishPlatform(t *testing.T) { t.Errorf("Could not get platform with no value: %v", ret) } } + +func Test_getlsbStruct(t *testing.T) { + cases := []struct { + root string + id string + release string + codename string + description string + }{ + {"arch", "Arch", "rolling", "", "Arch Linux"}, + {"ubuntu_22_04", "Ubuntu", "22.04", "jammy", "Ubuntu 22.04.2 LTS"}, + } + + for _, tt := range cases { + tt := tt + t.Run(tt.root, func(t *testing.T) { + ctx := context.WithValue(context.Background(), + common.EnvKey, + common.EnvMap{common.HostEtcEnvKey: "./testdata/linux/lsbStruct/" + tt.root}, + ) + + v, err := getlsbStruct(ctx) + if err != nil { + t.Errorf("error %v", err) + } + if v.ID != tt.id { + t.Errorf("ID: want %v, got %v", tt.id, v.ID) + } + if v.Release != tt.release { + t.Errorf("Release: want %v, got %v", tt.release, v.Release) + } + if v.Codename != tt.codename { + t.Errorf("Codename: want %v, got %v", tt.codename, v.Codename) + } + if v.Description != tt.description { + t.Errorf("Description: want %v, got %v", tt.description, v.Description) + } + + t.Log(v) + }) + } +} diff --git a/host/host_netbsd.go b/host/host_netbsd.go new file mode 100644 index 000000000..488f1dfc2 --- /dev/null +++ b/host/host_netbsd.go @@ -0,0 +1,55 @@ +//go:build netbsd +// +build netbsd + +package host + +import ( + "context" + "strings" + + "github.com/shirou/gopsutil/v3/internal/common" + "golang.org/x/sys/unix" +) + +func HostIDWithContext(ctx context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + +func numProcs(ctx context.Context) (uint64, error) { + return 0, common.ErrNotImplementedError +} + +func PlatformInformationWithContext(ctx context.Context) (string, string, string, error) { + platform := "" + family := "" + version := "" + + p, err := unix.Sysctl("kern.ostype") + if err == nil { + platform = strings.ToLower(p) + } + v, err := unix.Sysctl("kern.osrelease") + if err == nil { + version = strings.ToLower(v) + } + + return platform, family, version, nil +} + +func VirtualizationWithContext(ctx context.Context) (string, string, error) { + return "", "", common.ErrNotImplementedError +} + +func UsersWithContext(ctx context.Context) ([]UserStat, error) { + var ret []UserStat + return ret, common.ErrNotImplementedError +} + +func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { + return []TemperatureStat{}, common.ErrNotImplementedError +} + +func KernelVersionWithContext(ctx context.Context) (string, error) { + _, _, version, err := PlatformInformationWithContext(ctx) + return version, err +} diff --git a/host/host_openbsd.go b/host/host_openbsd.go index 569de4abd..325015c23 100644 --- a/host/host_openbsd.go +++ b/host/host_openbsd.go @@ -7,7 +7,7 @@ import ( "bytes" "context" "encoding/binary" - "io/ioutil" + "io" "os" "strings" "unsafe" @@ -65,7 +65,7 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) { } defer file.Close() - buf, err := ioutil.ReadAll(file) + buf, err := io.ReadAll(file) if err != nil { return ret, err } diff --git a/host/host_posix.go b/host/host_posix.go index 24529f19f..e7e0d837f 100644 --- a/host/host_posix.go +++ b/host/host_posix.go @@ -1,5 +1,5 @@ -//go:build linux || freebsd || openbsd || darwin || solaris -// +build linux freebsd openbsd darwin solaris +//go:build linux || freebsd || openbsd || netbsd || darwin || solaris +// +build linux freebsd openbsd netbsd darwin solaris package host diff --git a/host/host_solaris.go b/host/host_solaris.go index 7d3625acb..fef67f835 100644 --- a/host/host_solaris.go +++ b/host/host_solaris.go @@ -7,7 +7,6 @@ import ( "encoding/csv" "fmt" "io" - "io/ioutil" "os" "regexp" "strconv" @@ -60,7 +59,7 @@ func HostIDWithContext(ctx context.Context) (string, error) { // Count number of processes based on the number of entries in /proc func numProcs(ctx context.Context) (uint64, error) { - dirs, err := ioutil.ReadDir("/proc") + dirs, err := os.ReadDir("/proc") if err != nil { return 0, err } @@ -138,7 +137,7 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { // Find distribution name from /etc/release func parseReleaseFile() (string, error) { - b, err := ioutil.ReadFile("/etc/release") + b, err := os.ReadFile("/etc/release") if err != nil { return "", err } diff --git a/host/host_test.go b/host/host_test.go index 76307890d..17ced6dac 100644 --- a/host/host_test.go +++ b/host/host_test.go @@ -29,10 +29,11 @@ func TestHostInfo(t *testing.T) { if v.Procs == 0 { t.Errorf("Could not determine the number of host processes") } + t.Log(v) } func TestUptime(t *testing.T) { - if os.Getenv("CIRCLECI") == "true" { + if os.Getenv("CI") == "true" { t.Skip("Skip CI") } @@ -47,7 +48,7 @@ func TestUptime(t *testing.T) { } func TestBoot_time(t *testing.T) { - if os.Getenv("CIRCLECI") == "true" { + if os.Getenv("CI") == "true" { t.Skip("Skip CI") } v, err := BootTime() diff --git a/host/testdata/linux/lsbStruct/arch/lsb-release b/host/testdata/linux/lsbStruct/arch/lsb-release new file mode 100644 index 000000000..01356b2d8 --- /dev/null +++ b/host/testdata/linux/lsbStruct/arch/lsb-release @@ -0,0 +1,3 @@ +DISTRIB_ID="Arch" +DISTRIB_RELEASE="rolling" +DISTRIB_DESCRIPTION="Arch Linux" diff --git a/host/testdata/linux/lsbStruct/ubuntu_22_04/lsb-release b/host/testdata/linux/lsbStruct/ubuntu_22_04/lsb-release new file mode 100644 index 000000000..0575ec516 --- /dev/null +++ b/host/testdata/linux/lsbStruct/ubuntu_22_04/lsb-release @@ -0,0 +1,4 @@ +DISTRIB_ID=Ubuntu +DISTRIB_RELEASE=22.04 +DISTRIB_CODENAME=jammy +DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS" \ No newline at end of file diff --git a/internal/common/common.go b/internal/common/common.go index c1e96ca7d..99ed6a58e 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -14,7 +14,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/url" "os" "os/exec" @@ -25,6 +24,8 @@ import ( "strconv" "strings" "time" + + "github.com/shirou/gopsutil/v3/common" ) var ( @@ -85,7 +86,7 @@ func (i FakeInvoke) Command(name string, arg ...string) ([]byte, error) { fpath += "_" + i.Suffix } if PathExists(fpath) { - return ioutil.ReadFile(fpath) + return os.ReadFile(fpath) } return []byte{}, fmt.Errorf("could not find testdata: %s", fpath) } @@ -98,7 +99,7 @@ var ErrNotImplementedError = errors.New("not implemented yet") // ReadFile reads contents from a file func ReadFile(filename string) (string, error) { - content, err := ioutil.ReadFile(filename) + content, err := os.ReadFile(filename) if err != nil { return "", err } @@ -112,6 +113,30 @@ func ReadLines(filename string) ([]string, error) { return ReadLinesOffsetN(filename, 0, -1) } +// ReadLine reads a file and returns the first occurrence of a line that is prefixed with prefix. +func ReadLine(filename string, prefix string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + r := bufio.NewReader(f) + for { + line, err := r.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + return "", err + } + if strings.HasPrefix(line, prefix) { + return line, nil + } + } + + return "", nil +} + // ReadLinesOffsetN reads contents from file and splits them by new line. // The offset tells at which line number to start. // The count determines the number of lines to read (starting from offset): @@ -321,6 +346,23 @@ func PathExistsWithContents(filename string) bool { return info.Size() > 4 // at least 4 bytes } +// GetEnvWithContext retrieves the environment variable key. If it does not exist it returns the default. +// The context may optionally contain a map superseding os.EnvKey. +func GetEnvWithContext(ctx context.Context, key string, dfault string, combineWith ...string) string { + var value string + if env, ok := ctx.Value(common.EnvKey).(common.EnvMap); ok { + value = env[common.EnvKeyType(key)] + } + if value == "" { + value = os.Getenv(key) + } + if value == "" { + value = dfault + } + + return combine(value, combineWith) +} + // GetEnv retrieves the environment variable key. If it does not exist it returns the default. func GetEnv(key string, dfault string, combineWith ...string) string { value := os.Getenv(key) @@ -328,6 +370,10 @@ func GetEnv(key string, dfault string, combineWith ...string) string { value = dfault } + return combine(value, combineWith) +} + +func combine(value string, combineWith []string) string { switch len(combineWith) { case 0: return value @@ -369,6 +415,38 @@ func HostRoot(combineWith ...string) string { return GetEnv("HOST_ROOT", "/", combineWith...) } +func HostProcWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_PROC", "/proc", combineWith...) +} + +func HostProcMountInfoWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_PROC_MOUNTINFO", "", combineWith...) +} + +func HostSysWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_SYS", "/sys", combineWith...) +} + +func HostEtcWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_ETC", "/etc", combineWith...) +} + +func HostVarWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_VAR", "/var", combineWith...) +} + +func HostRunWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_RUN", "/run", combineWith...) +} + +func HostDevWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_DEV", "/dev", combineWith...) +} + +func HostRootWithContext(ctx context.Context, combineWith ...string) string { + return GetEnvWithContext(ctx, "HOST_ROOT", "/", combineWith...) +} + // getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running // sysctl commands (see DoSysctrl). func getSysctrlEnv(env []string) []string { diff --git a/internal/common/common_linux.go b/internal/common/common_linux.go index 17b962309..3338a7f7b 100644 --- a/internal/common/common_linux.go +++ b/internal/common/common_linux.go @@ -31,7 +31,11 @@ func DoSysctrl(mib string) ([]string, error) { } func NumProcs() (uint64, error) { - f, err := os.Open(HostProc()) + return NumProcsWithContext(context.Background()) +} + +func NumProcsWithContext(ctx context.Context) (uint64, error) { + f, err := os.Open(HostProcWithContext(ctx)) if err != nil { return 0, err } @@ -58,17 +62,38 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { return 0, err } - statFile := "stat" + useStatFile := true if system == "lxc" && role == "guest" { // if lxc, /proc/uptime is used. - statFile = "uptime" + useStatFile = false } else if system == "docker" && role == "guest" { // also docker, guest - statFile = "uptime" + useStatFile = false + } + + if useStatFile { + return readBootTimeStat(ctx) } - filename := HostProc(statFile) + filename := HostProcWithContext(ctx, "uptime") lines, err := ReadLines(filename) + if err != nil { + return handleBootTimeFileReadErr(err) + } + if len(lines) != 1 { + return 0, fmt.Errorf("wrong uptime format") + } + f := strings.Fields(lines[0]) + b, err := strconv.ParseFloat(f[0], 64) + if err != nil { + return 0, err + } + currentTime := float64(time.Now().UnixNano()) / float64(time.Second) + t := currentTime - b + return uint64(t), nil +} + +func handleBootTimeFileReadErr(err error) (uint64, error) { if os.IsPermission(err) { var info syscall.Sysinfo_t err := syscall.Sysinfo(&info) @@ -80,39 +105,27 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { t := currentTime - int64(info.Uptime) return uint64(t), nil } + return 0, err +} + +func readBootTimeStat(ctx context.Context) (uint64, error) { + filename := HostProcWithContext(ctx, "stat") + line, err := ReadLine(filename, "btime") if err != nil { - return 0, err + return handleBootTimeFileReadErr(err) } - - if statFile == "stat" { - for _, line := range lines { - if strings.HasPrefix(line, "btime") { - f := strings.Fields(line) - if len(f) != 2 { - return 0, fmt.Errorf("wrong btime format") - } - b, err := strconv.ParseInt(f[1], 10, 64) - if err != nil { - return 0, err - } - t := uint64(b) - return t, nil - } - } - } else if statFile == "uptime" { - if len(lines) != 1 { - return 0, fmt.Errorf("wrong uptime format") + if strings.HasPrefix(line, "btime") { + f := strings.Fields(line) + if len(f) != 2 { + return 0, fmt.Errorf("wrong btime format") } - f := strings.Fields(lines[0]) - b, err := strconv.ParseFloat(f[0], 64) + b, err := strconv.ParseInt(f[1], 10, 64) if err != nil { return 0, err } - currentTime := float64(time.Now().UnixNano()) / float64(time.Second) - t := currentTime - b - return uint64(t), nil + t := uint64(b) + return t, nil } - return 0, fmt.Errorf("could not find btime") } @@ -139,7 +152,7 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } cachedVirtMutex.RUnlock() - filename := HostProc("xen") + filename := HostProcWithContext(ctx, "xen") if PathExists(filename) { system = "xen" role = "guest" // assume guest @@ -154,7 +167,7 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } } - filename = HostProc("modules") + filename = HostProcWithContext(ctx, "modules") if PathExists(filename) { contents, err := ReadLines(filename) if err == nil { @@ -177,7 +190,7 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } } - filename = HostProc("cpuinfo") + filename = HostProcWithContext(ctx, "cpuinfo") if PathExists(filename) { contents, err := ReadLines(filename) if err == nil { @@ -190,7 +203,7 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } } - filename = HostProc("bus/pci/devices") + filename = HostProcWithContext(ctx, "bus/pci/devices") if PathExists(filename) { contents, err := ReadLines(filename) if err == nil { @@ -200,7 +213,7 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } } - filename = HostProc() + filename = HostProcWithContext(ctx) if PathExists(filepath.Join(filename, "bc", "0")) { system = "openvz" role = "host" @@ -251,15 +264,15 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } } - if PathExists(HostEtc("os-release")) { - p, _, _, err := GetOSRelease() + if PathExists(HostEtcWithContext(ctx, "os-release")) { + p, _, _, err := GetOSReleaseWithContext(ctx) if err == nil && p == "coreos" { system = "rkt" // Is it true? role = "host" } } - if PathExists(HostRoot(".dockerenv")) { + if PathExists(HostRootWithContext(ctx, ".dockerenv")) { system = "docker" role = "guest" } @@ -277,8 +290,14 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { return system, role, nil } + func GetOSRelease() (platform string, version string, description string, err error) { - contents, err := ReadLines(HostEtc("os-release")) + return GetOSReleaseWithContext(context.Background()) +} + +func GetOSReleaseWithContext(ctx context.Context) (platform string, version string, description string, err error) { + contents, err := ReadLines(HostEtcWithContext(ctx, "os-release")) + if err != nil { return "", "", "", nil // return empty } diff --git a/internal/common/common_netbsd.go b/internal/common/common_netbsd.go new file mode 100644 index 000000000..efbc710a5 --- /dev/null +++ b/internal/common/common_netbsd.go @@ -0,0 +1,66 @@ +//go:build netbsd +// +build netbsd + +package common + +import ( + "os" + "os/exec" + "strings" + "unsafe" + + "golang.org/x/sys/unix" +) + +func DoSysctrl(mib string) ([]string, error) { + cmd := exec.Command("sysctl", "-n", mib) + cmd.Env = getSysctrlEnv(os.Environ()) + out, err := cmd.Output() + if err != nil { + return []string{}, err + } + v := strings.Replace(string(out), "{ ", "", 1) + v = strings.Replace(string(v), " }", "", 1) + values := strings.Fields(string(v)) + + return values, nil +} + +func CallSyscall(mib []int32) ([]byte, uint64, error) { + mibptr := unsafe.Pointer(&mib[0]) + miblen := uint64(len(mib)) + + // get required buffer size + length := uint64(0) + _, _, err := unix.Syscall6( + unix.SYS___SYSCTL, + uintptr(mibptr), + uintptr(miblen), + 0, + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if err != 0 { + var b []byte + return b, length, err + } + if length == 0 { + var b []byte + return b, length, err + } + // get proc info itself + buf := make([]byte, length) + _, _, err = unix.Syscall6( + unix.SYS___SYSCTL, + uintptr(mibptr), + uintptr(miblen), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if err != 0 { + return buf, length, err + } + + return buf, length, nil +} diff --git a/internal/common/common_test.go b/internal/common/common_test.go index 27bcfac06..424ea26ab 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -1,12 +1,15 @@ package common import ( + "context" "fmt" "os" "reflect" "runtime" "strings" "testing" + + "github.com/shirou/gopsutil/v3/common" ) func TestReadlines(t *testing.T) { @@ -125,7 +128,7 @@ func TestHostEtc(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("windows doesn't have etc") } - p := HostEtc("mtab") + p := HostEtcWithContext(context.Background(), "mtab") if p != "/etc/mtab" { t.Errorf("invalid HostEtc, %s", p) } @@ -160,3 +163,36 @@ func TestGetSysctrlEnv(t *testing.T) { t.Errorf("unexpected real result from getSysctrlEnv: %q", env) } } + +func TestGetEnvDefault(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("windows doesn't have etc") + } + p := HostEtcWithContext(context.Background(), "mtab") + if p != "/etc/mtab" { + t.Errorf("invalid HostEtc, %s", p) + } +} + +func TestGetEnvWithNoContext(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("windows doesn't have etc") + } + t.Setenv("HOST_ETC", "/bar") + p := HostEtcWithContext(context.Background(), "mtab") + if p != "/bar/mtab" { + t.Errorf("invalid HostEtc, %s", p) + } +} + +func TestGetEnvWithContextOverride(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("windows doesn't have etc") + } + t.Setenv("HOST_ETC", "/bar") + ctx := context.WithValue(context.Background(), common.EnvKey, common.EnvMap{common.HostEtcEnvKey: "/foo"}) + p := HostEtcWithContext(ctx, "mtab") + if p != "/foo/mtab" { + t.Errorf("invalid HostEtc, %s", p) + } +} diff --git a/internal/common/sleep.go b/internal/common/sleep.go index 8c35b1722..9bed2419e 100644 --- a/internal/common/sleep.go +++ b/internal/common/sleep.go @@ -11,6 +11,9 @@ func Sleep(ctx context.Context, interval time.Duration) error { timer := time.NewTimer(interval) select { case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } return ctx.Err() case <-timer.C: return nil diff --git a/load/load_linux.go b/load/load_linux.go index debf0733c..06bceeb84 100644 --- a/load/load_linux.go +++ b/load/load_linux.go @@ -5,7 +5,7 @@ package load import ( "context" - "io/ioutil" + "os" "strconv" "strings" "syscall" @@ -18,7 +18,7 @@ func Avg() (*AvgStat, error) { } func AvgWithContext(ctx context.Context) (*AvgStat, error) { - stat, err := fileAvgWithContext() + stat, err := fileAvgWithContext(ctx) if err != nil { stat, err = sysinfoAvgWithContext() } @@ -40,8 +40,8 @@ func sysinfoAvgWithContext() (*AvgStat, error) { }, nil } -func fileAvgWithContext() (*AvgStat, error) { - values, err := readLoadAvgFromFile() +func fileAvgWithContext(ctx context.Context) (*AvgStat, error) { + values, err := readLoadAvgFromFile(ctx) if err != nil { return nil, err } @@ -75,8 +75,8 @@ func Misc() (*MiscStat, error) { } func MiscWithContext(ctx context.Context) (*MiscStat, error) { - filename := common.HostProc("stat") - out, err := ioutil.ReadFile(filename) + filename := common.HostProcWithContext(ctx, "stat") + out, err := os.ReadFile(filename) if err != nil { return nil, err } @@ -107,7 +107,7 @@ func MiscWithContext(ctx context.Context) (*MiscStat, error) { } - procsTotal, err := getProcsTotal() + procsTotal, err := getProcsTotal(ctx) if err != nil { return ret, err } @@ -116,17 +116,17 @@ func MiscWithContext(ctx context.Context) (*MiscStat, error) { return ret, nil } -func getProcsTotal() (int64, error) { - values, err := readLoadAvgFromFile() +func getProcsTotal(ctx context.Context) (int64, error) { + values, err := readLoadAvgFromFile(ctx) if err != nil { return 0, err } return strconv.ParseInt(strings.Split(values[3], "/")[1], 10, 64) } -func readLoadAvgFromFile() ([]string, error) { - loadavgFilename := common.HostProc("loadavg") - line, err := ioutil.ReadFile(loadavgFilename) +func readLoadAvgFromFile(ctx context.Context) ([]string, error) { + loadavgFilename := common.HostProcWithContext(ctx, "loadavg") + line, err := os.ReadFile(loadavgFilename) if err != nil { return nil, err } diff --git a/load/load_windows.go b/load/load_windows.go index b48483849..8f53efae3 100644 --- a/load/load_windows.go +++ b/load/load_windows.go @@ -45,8 +45,8 @@ func loadAvgGoroutine(ctx context.Context) { f := func() { currentLoad, err = counter.GetValue() - loadErr = err loadAvgMutex.Lock() + loadErr = err loadAvg1M = loadAvg1M*loadAvgFactor1M + currentLoad*(1-loadAvgFactor1M) loadAvg5M = loadAvg5M*loadAvgFactor5M + currentLoad*(1-loadAvgFactor5M) loadAvg15M = loadAvg15M*loadAvgFactor15M + currentLoad*(1-loadAvgFactor15M) diff --git a/mem/mem.go b/mem/mem.go index e0701b94a..726b94b89 100644 --- a/mem/mem.go +++ b/mem/mem.go @@ -50,6 +50,7 @@ type VirtualMemoryStat struct { // https://www.centos.org/docs/5/html/5.1/Deployment_Guide/s2-proc-meminfo.html // https://www.kernel.org/doc/Documentation/filesystems/proc.txt // https://www.kernel.org/doc/Documentation/vm/overcommit-accounting + // https://www.kernel.org/doc/Documentation/vm/transhuge.txt Buffers uint64 `json:"buffers"` Cached uint64 `json:"cached"` WriteBack uint64 `json:"writeBack"` @@ -78,6 +79,8 @@ type VirtualMemoryStat struct { HugePagesRsvd uint64 `json:"hugePagesRsvd"` HugePagesSurp uint64 `json:"hugePagesSurp"` HugePageSize uint64 `json:"hugePageSize"` + AnonHugePages uint64 `json:"anonHugePages"` + //opsramp specific uses TotalVir uint64 `json:"totalVir"` AvailableVir uint64 `json:"availableVir"` diff --git a/mem/mem_bsd.go b/mem/mem_bsd.go index ce930fbe4..ef867d742 100644 --- a/mem/mem_bsd.go +++ b/mem/mem_bsd.go @@ -1,5 +1,5 @@ -//go:build freebsd || openbsd -// +build freebsd openbsd +//go:build freebsd || openbsd || netbsd +// +build freebsd openbsd netbsd package mem diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index 0527dd93c..a05a0faba 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -8,8 +8,9 @@ import ( "fmt" "unsafe" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v3/internal/common" ) func getHwMemsize() (uint64, error) { diff --git a/mem/mem_fallback.go b/mem/mem_fallback.go index 0b6c528f2..697fd8709 100644 --- a/mem/mem_fallback.go +++ b/mem/mem_fallback.go @@ -1,5 +1,5 @@ -//go:build !darwin && !linux && !freebsd && !openbsd && !solaris && !windows && !plan9 && !aix -// +build !darwin,!linux,!freebsd,!openbsd,!solaris,!windows,!plan9,!aix +//go:build !darwin && !linux && !freebsd && !openbsd && !solaris && !windows && !plan9 && !aix && !netbsd +// +build !darwin,!linux,!freebsd,!openbsd,!solaris,!windows,!plan9,!aix,!netbsd package mem diff --git a/mem/mem_linux.go b/mem/mem_linux.go index 9a5d693b1..214a91e47 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -14,8 +14,9 @@ import ( "strconv" "strings" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v3/internal/common" ) type VirtualMemoryExStat struct { @@ -36,7 +37,7 @@ func VirtualMemory() (*VirtualMemoryStat, error) { } func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { - vm, _, err := fillFromMeminfoWithContext() + vm, _, err := fillFromMeminfoWithContext(ctx) if err != nil { return nil, err } @@ -48,15 +49,15 @@ func VirtualMemoryEx() (*VirtualMemoryExStat, error) { } func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) { - _, vmEx, err := fillFromMeminfoWithContext() + _, vmEx, err := fillFromMeminfoWithContext(ctx) if err != nil { return nil, err } return vmEx, nil } -func fillFromMeminfoWithContext() (*VirtualMemoryStat, *VirtualMemoryExStat, error) { - filename := common.HostProc("meminfo") +func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) { + filename := common.HostProcWithContext(ctx, "meminfo") lines, _ := common.ReadLines(filename) // flag if MemAvailable is in /proc/meminfo (kernel 3.14+) @@ -153,13 +154,13 @@ func fillFromMeminfoWithContext() (*VirtualMemoryStat, *VirtualMemoryExStat, err return ret, retEx, err } retEx.Unevictable = t * 1024 - case "WriteBack": + case "Writeback": t, err := strconv.ParseUint(value, 10, 64) if err != nil { return ret, retEx, err } ret.WriteBack = t * 1024 - case "WriteBackTmp": + case "WritebackTmp": t, err := strconv.ParseUint(value, 10, 64) if err != nil { return ret, retEx, err @@ -310,6 +311,12 @@ func fillFromMeminfoWithContext() (*VirtualMemoryStat, *VirtualMemoryExStat, err return ret, retEx, err } ret.HugePageSize = t * 1024 + case "AnonHugePages": + t, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return ret, retEx, err + } + ret.AnonHugePages = t * 1024 } } @@ -317,7 +324,7 @@ func fillFromMeminfoWithContext() (*VirtualMemoryStat, *VirtualMemoryExStat, err if !memavail { if activeFile && inactiveFile && sReclaimable { - ret.Available = calculateAvailVmem(ret, retEx) + ret.Available = calculateAvailVmem(ctx, ret, retEx) } else { ret.Available = ret.Cached + ret.Free } @@ -350,7 +357,7 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { } else { ret.UsedPercent = 0 } - filename := common.HostProc("vmstat") + filename := common.HostProcWithContext(ctx, "vmstat") lines, _ := common.ReadLines(filename) for _, l := range lines { fields := strings.Fields(l) @@ -370,25 +377,25 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { continue } ret.Sout = value * 4 * 1024 - case "pgpgIn": + case "pgpgin": value, err := strconv.ParseUint(fields[1], 10, 64) if err != nil { continue } ret.PgIn = value * 4 * 1024 - case "pgpgOut": + case "pgpgout": value, err := strconv.ParseUint(fields[1], 10, 64) if err != nil { continue } ret.PgOut = value * 4 * 1024 - case "pgFault": + case "pgfault": value, err := strconv.ParseUint(fields[1], 10, 64) if err != nil { continue } ret.PgFault = value * 4 * 1024 - case "pgMajFault": + case "pgmajfault": value, err := strconv.ParseUint(fields[1], 10, 64) if err != nil { continue @@ -402,10 +409,10 @@ func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { // calculateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide // "MemAvailable:" column. It reimplements an algorithm from the link below // https://github.com/giampaolo/psutil/pull/890 -func calculateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 { +func calculateAvailVmem(ctx context.Context, ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 { var watermarkLow uint64 - fn := common.HostProc("zoneinfo") + fn := common.HostProcWithContext(ctx, "zoneinfo") lines, err := common.ReadLines(fn) if err != nil { return ret.Free + ret.Cached // fallback under kernel 2.6.13 @@ -457,18 +464,18 @@ func SwapDevices() ([]*SwapDevice, error) { } func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) { - swapsFilePath := common.HostProc(swapsFilename) + swapsFilePath := common.HostProcWithContext(ctx, swapsFilename) f, err := os.Open(swapsFilePath) if err != nil { return nil, err } defer f.Close() - return parseSwapsFile(f) + return parseSwapsFile(ctx, f) } -func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) { - swapsFilePath := common.HostProc(swapsFilename) +func parseSwapsFile(ctx context.Context, r io.Reader) ([]*SwapDevice, error) { + swapsFilePath := common.HostProcWithContext(ctx, swapsFilename) scanner := bufio.NewScanner(r) if !scanner.Scan() { if err := scanner.Err(); err != nil { diff --git a/mem/mem_linux_test.go b/mem/mem_linux_test.go index a0590c961..6b6fb782a 100644 --- a/mem/mem_linux_test.go +++ b/mem/mem_linux_test.go @@ -4,6 +4,7 @@ package mem import ( + "context" "path/filepath" "reflect" "strings" @@ -107,6 +108,16 @@ var virtualMemoryTests = []struct { HugePageSize: 0, }, }, + { + "anonhugepages", &VirtualMemoryStat{ + Total: 260799420 * 1024, + Available: 127880216 * 1024, + Free: 119443248 * 1024, + AnonHugePages: 50409472 * 1024, + Used: 144748720128, + UsedPercent: 54.20110673559013, + }, + }, } func TestVirtualMemoryLinux(t *testing.T) { @@ -138,7 +149,7 @@ const invalidFile = `INVALID Type Size Used Priority func TestParseSwapsFile_ValidFile(t *testing.T) { assert := assert.New(t) - stats, err := parseSwapsFile(strings.NewReader(validFile)) + stats, err := parseSwapsFile(context.Background(), strings.NewReader(validFile)) assert.NoError(err) assert.Equal(*stats[0], SwapDevice{ @@ -155,11 +166,11 @@ func TestParseSwapsFile_ValidFile(t *testing.T) { } func TestParseSwapsFile_InvalidFile(t *testing.T) { - _, err := parseSwapsFile(strings.NewReader(invalidFile)) + _, err := parseSwapsFile(context.Background(), strings.NewReader(invalidFile)) assert.Error(t, err) } func TestParseSwapsFile_EmptyFile(t *testing.T) { - _, err := parseSwapsFile(strings.NewReader("")) + _, err := parseSwapsFile(context.Background(), strings.NewReader("")) assert.Error(t, err) } diff --git a/mem/mem_netbsd.go b/mem/mem_netbsd.go new file mode 100644 index 000000000..d1f54ecaf --- /dev/null +++ b/mem/mem_netbsd.go @@ -0,0 +1,87 @@ +//go:build netbsd +// +build netbsd + +package mem + +import ( + "context" + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +func GetPageSize() (uint64, error) { + return GetPageSizeWithContext(context.Background()) +} + +func GetPageSizeWithContext(ctx context.Context) (uint64, error) { + uvmexp, err := unix.SysctlUvmexp("vm.uvmexp2") + if err != nil { + return 0, err + } + return uint64(uvmexp.Pagesize), nil +} + +func VirtualMemory() (*VirtualMemoryStat, error) { + return VirtualMemoryWithContext(context.Background()) +} + +func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { + uvmexp, err := unix.SysctlUvmexp("vm.uvmexp2") + if err != nil { + return nil, err + } + p := uint64(uvmexp.Pagesize) + + ret := &VirtualMemoryStat{ + Total: uint64(uvmexp.Npages) * p, + Free: uint64(uvmexp.Free) * p, + Active: uint64(uvmexp.Active) * p, + Inactive: uint64(uvmexp.Inactive) * p, + Cached: 0, // not available + Wired: uint64(uvmexp.Wired) * p, + } + + ret.Available = ret.Inactive + ret.Cached + ret.Free + ret.Used = ret.Total - ret.Available + ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0 + + // Get buffers from vm.bufmem sysctl + ret.Buffers, err = unix.SysctlUint64("vm.bufmem") + if err != nil { + return nil, err + } + + return ret, nil +} + +// Return swapctl summary info +func SwapMemory() (*SwapMemoryStat, error) { + return SwapMemoryWithContext(context.Background()) +} + +func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { + out, err := invoke.CommandWithContext(ctx, "swapctl", "-sk") + if err != nil { + return &SwapMemoryStat{}, nil + } + + line := string(out) + var total, used, free uint64 + + _, err = fmt.Sscanf(line, + "total: %d 1K-blocks allocated, %d used, %d available", + &total, &used, &free) + if err != nil { + return nil, errors.New("failed to parse swapctl output") + } + + percent := float64(used) / float64(total) * 100 + return &SwapMemoryStat{ + Total: total * 1024, + Used: used * 1024, + Free: free * 1024, + UsedPercent: percent, + }, nil +} diff --git a/mem/mem_test.go b/mem/mem_test.go index 57734d778..79ddb0fc6 100644 --- a/mem/mem_test.go +++ b/mem/mem_test.go @@ -6,8 +6,9 @@ import ( "runtime" "testing" - "github.com/shirou/gopsutil/v3/internal/common" "github.com/stretchr/testify/assert" + + "github.com/shirou/gopsutil/v3/internal/common" ) func skipIfNotImplementedErr(t *testing.T, err error) { @@ -88,7 +89,7 @@ func TestVirtualMemoryStat_String(t *testing.T) { Free: 40, } t.Log(v) - e := `{"total":10,"available":20,"used":30,"usedPercent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"laundry":0,"buffers":0,"cached":0,"writeBack":0,"dirty":0,"writeBackTmp":0,"shared":0,"slab":0,"sreclaimable":0,"sunreclaim":0,"pageTables":0,"swapCached":0,"commitLimit":0,"committedAS":0,"highTotal":0,"highFree":0,"lowTotal":0,"lowFree":0,"swapTotal":0,"swapFree":0,"mapped":0,"vmallocTotal":0,"vmallocUsed":0,"vmallocChunk":0,"hugePagesTotal":0,"hugePagesFree":0,"hugePagesRsvd":0,"hugePagesSurp":0,"hugePageSize":0}` + e := `{"total":10,"available":20,"used":30,"usedPercent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"laundry":0,"buffers":0,"cached":0,"writeBack":0,"dirty":0,"writeBackTmp":0,"shared":0,"slab":0,"sreclaimable":0,"sunreclaim":0,"pageTables":0,"swapCached":0,"commitLimit":0,"committedAS":0,"highTotal":0,"highFree":0,"lowTotal":0,"lowFree":0,"swapTotal":0,"swapFree":0,"mapped":0,"vmallocTotal":0,"vmallocUsed":0,"vmallocChunk":0,"hugePagesTotal":0,"hugePagesFree":0,"hugePagesRsvd":0,"hugePagesSurp":0,"hugePageSize":0,"anonHugePages":0}` if e != fmt.Sprintf("%v", v) { t.Errorf("VirtualMemoryStat string is invalid: %v", v) } diff --git a/mem/testdata/linux/virtualmemory/anonhugepages/proc/meminfo b/mem/testdata/linux/virtualmemory/anonhugepages/proc/meminfo new file mode 100644 index 000000000..d158c677b --- /dev/null +++ b/mem/testdata/linux/virtualmemory/anonhugepages/proc/meminfo @@ -0,0 +1,4 @@ +MemTotal: 260799420 kB +MemFree: 119443248 kB +MemAvailable: 127880216 kB +AnonHugePages: 50409472 kB \ No newline at end of file diff --git a/net/net_darwin.go b/net/net_darwin.go index 1c8d4f4e3..8a7b63744 100644 --- a/net/net_darwin.go +++ b/net/net_darwin.go @@ -259,7 +259,7 @@ func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) { } func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) { - return IOCounters(pernic) + return IOCountersWithContext(ctx, pernic) } func FilterCounters() ([]FilterStat, error) { @@ -278,7 +278,7 @@ func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackSta return nil, common.ErrNotImplementedError } -// NetProtoCounters returns network statistics for the entire system +// ProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. // Not Implemented for Darwin diff --git a/net/net_freebsd.go b/net/net_freebsd.go index 7f31851ea..bf8baf094 100644 --- a/net/net_freebsd.go +++ b/net/net_freebsd.go @@ -115,7 +115,7 @@ func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackSta return nil, common.ErrNotImplementedError } -// NetProtoCounters returns network statistics for the entire system +// ProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. // Not Implemented for FreeBSD diff --git a/net/net_linux.go b/net/net_linux.go index c7cd0db18..6e8ce67fb 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "os" "strconv" @@ -50,7 +49,7 @@ func IOCounters(pernic bool) ([]IOCountersStat, error) { } func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) { - filename := common.HostProc("net/dev") + filename := common.HostProcWithContext(ctx, "net/dev") return IOCountersByFileWithContext(ctx, pernic, filename) } @@ -157,7 +156,7 @@ var netProtocols = []string{ "udplite", } -// NetProtoCounters returns network statistics for the entire system +// ProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. // Available protocols: @@ -177,7 +176,7 @@ func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoC protos[p] = true } - filename := common.HostProc("net/snmp") + filename := common.HostProcWithContext(ctx, "net/snmp") lines, err := common.ReadLines(filename) if err != nil { return nil, err @@ -230,8 +229,8 @@ func FilterCounters() ([]FilterStat, error) { } func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { - countfile := common.HostProc("sys/net/netfilter/nf_conntrack_count") - maxfile := common.HostProc("sys/net/netfilter/nf_conntrack_max") + countfile := common.HostProcWithContext(ctx, "sys/net/netfilter/nf_conntrack_count") + maxfile := common.HostProcWithContext(ctx, "sys/net/netfilter/nf_conntrack_max") count, err := common.ReadInts(countfile) if err != nil { @@ -260,7 +259,7 @@ func ConntrackStats(percpu bool) ([]ConntrackStat, error) { // ConntrackStatsWithContext returns more detailed info about the conntrack table func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { - return conntrackStatsFromFile(common.HostProc("net/stat/nf_conntrack"), percpu) + return conntrackStatsFromFile(common.HostProcWithContext(ctx, "net/stat/nf_conntrack"), percpu) } // conntrackStatsFromFile returns more detailed info about the conntrack table @@ -459,7 +458,7 @@ func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, p if !ok { return nil, fmt.Errorf("invalid kind, %s", kind) } - root := common.HostProc() + root := common.HostProcWithContext(ctx) var err error var inodes map[string][]inodeMap if pid == 0 { @@ -531,7 +530,7 @@ func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tma if !skipUids { // fetch process owner Real, effective, saved set, and filesystem UIDs proc := process{Pid: conn.Pid} - conn.Uids, _ = proc.getUids() + conn.Uids, _ = proc.getUids(ctx) } ret = append(ret, conn) @@ -599,7 +598,7 @@ func Pids() ([]int32, error) { func PidsWithContext(ctx context.Context) ([]int32, error) { var ret []int32 - d, err := os.Open(common.HostProc()) + d, err := os.Open(common.HostProcWithContext(ctx)) if err != nil { return nil, err } @@ -631,8 +630,8 @@ type process struct { } // Uids returns user ids of the process as a slice of the int -func (p *process) getUids() ([]int32, error) { - err := p.fillFromStatus() +func (p *process) getUids(ctx context.Context) ([]int32, error) { + err := p.fillFromStatus(ctx) if err != nil { return []int32{}, err } @@ -640,10 +639,10 @@ func (p *process) getUids() ([]int32, error) { } // Get status from /proc/(pid)/status -func (p *process) fillFromStatus() error { +func (p *process) fillFromStatus(ctx context.Context) error { pid := p.Pid - statPath := common.HostProc(strconv.Itoa(int(pid)), "status") - contents, err := ioutil.ReadFile(statPath) + statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "status") + contents, err := os.ReadFile(statPath) if err != nil { return err } @@ -784,7 +783,7 @@ func processInetWithContext(ctx context.Context, file string, kind netConnection // This minimizes duplicates in the returned connections // For more info: // https://github.com/shirou/gopsutil/pull/361 - contents, err := ioutil.ReadFile(file) + contents, err := os.ReadFile(file) if err != nil { return nil, err } @@ -845,7 +844,7 @@ func processUnix(file string, kind netConnectionKindType, inodes map[string][]in // This minimizes duplicates in the returned connections // For more info: // https://github.com/shirou/gopsutil/pull/361 - contents, err := ioutil.ReadFile(file) + contents, err := os.ReadFile(file) if err != nil { return nil, err } diff --git a/net/net_linux_test.go b/net/net_linux_test.go index d5d6252a8..eae0e71b9 100644 --- a/net/net_linux_test.go +++ b/net/net_linux_test.go @@ -1,21 +1,22 @@ package net import ( + "context" "fmt" - "io/ioutil" "net" "os" "strings" "syscall" "testing" - "github.com/shirou/gopsutil/v3/internal/common" "github.com/stretchr/testify/assert" + + "github.com/shirou/gopsutil/v3/internal/common" ) func TestIOCountersByFileParsing(t *testing.T) { // Prpare a temporary file, which will be read during the test - tmpfile, err := ioutil.TempFile("", "proc_dev_net") + tmpfile, err := os.CreateTemp("", "proc_dev_net") defer os.Remove(tmpfile.Name()) // clean up assert.Nil(t, err, "Temporary file creation failed: ", err) @@ -100,7 +101,7 @@ func TestGetProcInodesAll(t *testing.T) { }() <-waitForServer - root := common.HostProc("") + root := common.HostProcWithContext(context.Background(), "") v, err := getProcInodesAll(root, 0) assert.Nil(t, err) assert.NotEmpty(t, v) @@ -193,7 +194,7 @@ func TestReverse(t *testing.T) { } func TestConntrackStatFileParsing(t *testing.T) { - tmpfile, err := ioutil.TempFile("", "proc_net_stat_conntrack") + tmpfile, err := os.CreateTemp("", "proc_net_stat_conntrack") defer os.Remove(tmpfile.Name()) assert.Nil(t, err, "Temporary file creation failed: ", err) @@ -248,6 +249,7 @@ entries searched found new invalid ignore delete deleteList insert insertFailed // Function under test stats, err := conntrackStatsFromFile(tmpfile.Name(), true) + assert.Nil(t, err) assert.Equal(t, 8, len(stats), "Expected 8 results") summary := &ConntrackStat{} @@ -308,6 +310,7 @@ entries searched found new invalid ignore delete deleteList insert insertFailed // Test summary grouping totals, err := conntrackStatsFromFile(tmpfile.Name(), false) + assert.Nil(t, err) for i, st := range totals { assert.Equal(t, summary.Entries, st.Entries) assert.Equal(t, summary.Searched, st.Searched) diff --git a/net/net_openbsd.go b/net/net_openbsd.go index 5f066a09f..cf48f53e7 100644 --- a/net/net_openbsd.go +++ b/net/net_openbsd.go @@ -164,7 +164,7 @@ func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackSta return nil, common.ErrNotImplementedError } -// NetProtoCounters returns network statistics for the entire system +// ProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. // Not Implemented for OpenBSD diff --git a/net/net_windows.go b/net/net_windows.go index 68b26bdcd..5d384342f 100644 --- a/net/net_windows.go +++ b/net/net_windows.go @@ -338,7 +338,7 @@ func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackSta return nil, common.ErrNotImplementedError } -// NetProtoCounters returns network statistics for the entire system +// ProtoCounters returns network statistics for the entire system // If protocols is empty then all protocols are returned, otherwise // just the protocols in the list are returned. // Not Implemented for Windows diff --git a/process/process.go b/process/process.go index 0ca26c210..1a7fe1b80 100644 --- a/process/process.go +++ b/process/process.go @@ -335,7 +335,7 @@ func (p *Process) MemoryPercentWithContext(ctx context.Context) (float32, error) return (100 * float32(used) / float32(total)), nil } -// CPU_Percent returns how many percent of the CPU time this process uses +// CPUPercent returns how many percent of the CPU time this process uses func (p *Process) CPUPercent() (float64, error) { return p.CPUPercentWithContext(context.Background()) } @@ -507,7 +507,7 @@ func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) { return p.MemoryInfoExWithContext(context.Background()) } -// PageFaultsInfo returns the process's page fault counters. +// PageFaults returns the process's page fault counters. func (p *Process) PageFaults() (*PageFaultsStat, error) { return p.PageFaultsWithContext(context.Background()) } @@ -530,7 +530,7 @@ func (p *Process) Connections() ([]net.ConnectionStat, error) { return p.ConnectionsWithContext(context.Background()) } -// Connections returns a slice of net.ConnectionStat used by the process at most `max`. +// ConnectionsMax returns a slice of net.ConnectionStat used by the process at most `max`. func (p *Process) ConnectionsMax(max int) ([]net.ConnectionStat, error) { return p.ConnectionsMaxWithContext(context.Background(), max) } diff --git a/process/process_darwin.go b/process/process_darwin.go index 61b340b63..176661cbd 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -10,10 +10,11 @@ import ( "strconv" "strings" - "github.com/shirou/gopsutil/v3/internal/common" - "github.com/shirou/gopsutil/v3/net" "github.com/tklauser/go-sysconf" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v3/internal/common" + "github.com/shirou/gopsutil/v3/net" ) // copied from sys/sysctl.h @@ -81,8 +82,6 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { extendedName := filepath.Base(cmdName) if strings.HasPrefix(extendedName, p.name) { name = extendedName - } else { - name = cmdName } } } diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 779f8126a..85134b7ee 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -55,8 +55,6 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { extendedName := filepath.Base(cmdlineSlice[0]) if strings.HasPrefix(extendedName, p.name) { name = extendedName - } else { - name = cmdlineSlice[0] } } } @@ -69,7 +67,13 @@ func (p *Process) CwdWithContext(ctx context.Context) (string, error) { } func (p *Process) ExeWithContext(ctx context.Context) (string, error) { - return "", common.ErrNotImplementedError + mib := []int32{CTLKern, KernProc, KernProcPathname, p.Pid} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return "", err + } + + return strings.Trim(string(buf), "\x00"), nil } func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { diff --git a/process/process_linux.go b/process/process_linux.go index d5b5bc329..f7989cd21 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -9,18 +9,18 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "math" "os" "path/filepath" "strconv" "strings" + "github.com/tklauser/go-sysconf" + "golang.org/x/sys/unix" + "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/internal/common" "github.com/shirou/gopsutil/v3/net" - "github.com/tklauser/go-sysconf" - "golang.org/x/sys/unix" ) var pageSize = uint64(os.Getpagesize()) @@ -100,7 +100,7 @@ func (p *Process) TgidWithContext(ctx context.Context) (int32, error) { } func (p *Process) ExeWithContext(ctx context.Context) (string, error) { - return p.fillFromExeWithContext() + return p.fillFromExeWithContext(ctx) } func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { @@ -120,7 +120,7 @@ func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { } func (p *Process) CwdWithContext(ctx context.Context) (string, error) { - return p.fillFromCwdWithContext() + return p.fillFromCwdWithContext(ctx) } func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) { @@ -134,8 +134,8 @@ func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) { func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) { // see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details pid := p.Pid - statPath := common.HostProc(strconv.Itoa(int(pid)), "stat") - contents, err := ioutil.ReadFile(statPath) + statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "stat") + contents, err := os.ReadFile(statPath) if err != nil { return false, err } @@ -202,7 +202,7 @@ func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) { } func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) { - rlimits, err := p.fillFromLimitsWithContext() + rlimits, err := p.fillFromLimitsWithContext(ctx) if !gatherUsed || err != nil { return rlimits, err } @@ -257,7 +257,7 @@ func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ( } func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) { - return p.fillFromIOWithContext() + return p.fillFromIOWithContext(ctx) } func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) { @@ -283,7 +283,7 @@ func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) { ret := make(map[int32]*cpu.TimesStat) - taskPath := common.HostProc(strconv.Itoa(int(p.Pid)), "task") + taskPath := common.HostProcWithContext(ctx, strconv.Itoa(int(p.Pid)), "task") tids, err := readPidsFromDir(taskPath) if err != nil { @@ -314,7 +314,7 @@ func (p *Process) CPUAffinityWithContext(ctx context.Context) ([]int32, error) { } func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { - meminfo, _, err := p.fillFromStatmWithContext() + meminfo, _, err := p.fillFromStatmWithContext(ctx) if err != nil { return nil, err } @@ -322,7 +322,7 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e } func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) { - _, memInfoEx, err := p.fillFromStatmWithContext() + _, memInfoEx, err := p.fillFromStatmWithContext(ctx) if err != nil { return nil, err } @@ -380,17 +380,17 @@ func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) { pid := p.Pid var ret []MemoryMapsStat - smapsPath := common.HostProc(strconv.Itoa(int(pid)), "smaps") + smapsPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "smaps") if grouped { ret = make([]MemoryMapsStat, 1) // If smaps_rollup exists (require kernel >= 4.15), then we will use it // for pre-summed memory information for a process. - smapsRollupPath := common.HostProc(strconv.Itoa(int(pid)), "smaps_rollup") + smapsRollupPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "smaps_rollup") if _, err := os.Stat(smapsRollupPath); !os.IsNotExist(err) { smapsPath = smapsRollupPath } } - contents, err := ioutil.ReadFile(smapsPath) + contents, err := os.ReadFile(smapsPath) if err != nil { return nil, err } @@ -481,9 +481,9 @@ func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]M } func (p *Process) EnvironWithContext(ctx context.Context) ([]string, error) { - environPath := common.HostProc(strconv.Itoa(int(p.Pid)), "environ") + environPath := common.HostProcWithContext(ctx, strconv.Itoa(int(p.Pid)), "environ") - environContent, err := ioutil.ReadFile(environPath) + environContent, err := os.ReadFile(environPath) if err != nil { return nil, err } @@ -507,9 +507,9 @@ func limitToUint(val string) (uint64, error) { } // Get num_fds from /proc/(pid)/limits -func (p *Process) fillFromLimitsWithContext() ([]RlimitStat, error) { +func (p *Process) fillFromLimitsWithContext(ctx context.Context) ([]RlimitStat, error) { pid := p.Pid - limitsFile := common.HostProc(strconv.Itoa(int(pid)), "limits") + limitsFile := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "limits") d, err := os.Open(limitsFile) if err != nil { return nil, err @@ -602,7 +602,7 @@ func (p *Process) fillFromLimitsWithContext() ([]RlimitStat, error) { // Get list of /proc/(pid)/fd files func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) { pid := p.Pid - statPath := common.HostProc(strconv.Itoa(int(pid)), "fd") + statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "fd") d, err := os.Open(statPath) if err != nil { return statPath, []string{}, err @@ -642,9 +642,9 @@ func (p *Process) fillFromfdWithContext(ctx context.Context) (int32, []*OpenFile } // Get cwd from /proc/(pid)/cwd -func (p *Process) fillFromCwdWithContext() (string, error) { +func (p *Process) fillFromCwdWithContext(ctx context.Context) (string, error) { pid := p.Pid - cwdPath := common.HostProc(strconv.Itoa(int(pid)), "cwd") + cwdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cwd") cwd, err := os.Readlink(cwdPath) if err != nil { return "", err @@ -653,9 +653,9 @@ func (p *Process) fillFromCwdWithContext() (string, error) { } // Get exe from /proc/(pid)/exe -func (p *Process) fillFromExeWithContext() (string, error) { +func (p *Process) fillFromExeWithContext(ctx context.Context) (string, error) { pid := p.Pid - exePath := common.HostProc(strconv.Itoa(int(pid)), "exe") + exePath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "exe") exe, err := os.Readlink(exePath) if err != nil { return "", err @@ -666,8 +666,8 @@ func (p *Process) fillFromExeWithContext() (string, error) { // Get cmdline from /proc/(pid)/cmdline func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) { pid := p.Pid - cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") - cmdline, err := ioutil.ReadFile(cmdPath) + cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline") + cmdline, err := os.ReadFile(cmdPath) if err != nil { return "", err } @@ -680,8 +680,8 @@ func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) { pid := p.Pid - cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") - cmdline, err := ioutil.ReadFile(cmdPath) + cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline") + cmdline, err := os.ReadFile(cmdPath) if err != nil { return nil, err } @@ -701,10 +701,10 @@ func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string } // Get IO status from /proc/(pid)/io -func (p *Process) fillFromIOWithContext() (*IOCountersStat, error) { +func (p *Process) fillFromIOWithContext(ctx context.Context) (*IOCountersStat, error) { pid := p.Pid - ioPath := common.HostProc(strconv.Itoa(int(pid)), "io") - ioline, err := ioutil.ReadFile(ioPath) + ioPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "io") + ioline, err := os.ReadFile(ioPath) if err != nil { return nil, err } @@ -737,10 +737,10 @@ func (p *Process) fillFromIOWithContext() (*IOCountersStat, error) { } // Get memory info from /proc/(pid)/statm -func (p *Process) fillFromStatmWithContext() (*MemoryInfoStat, *MemoryInfoExStat, error) { +func (p *Process) fillFromStatmWithContext(ctx context.Context) (*MemoryInfoStat, *MemoryInfoExStat, error) { pid := p.Pid - memPath := common.HostProc(strconv.Itoa(int(pid)), "statm") - contents, err := ioutil.ReadFile(memPath) + memPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "statm") + contents, err := os.ReadFile(memPath) if err != nil { return nil, nil, err } @@ -790,7 +790,7 @@ func (p *Process) fillFromStatmWithContext() (*MemoryInfoStat, *MemoryInfoExStat // Get name from /proc/(pid)/comm or /proc/(pid)/status func (p *Process) fillNameWithContext(ctx context.Context) error { - err := p.fillFromCommWithContext() + err := p.fillFromCommWithContext(ctx) if err == nil && p.name != "" && len(p.name) < 15 { return nil } @@ -798,10 +798,10 @@ func (p *Process) fillNameWithContext(ctx context.Context) error { } // Get name from /proc/(pid)/comm -func (p *Process) fillFromCommWithContext() error { +func (p *Process) fillFromCommWithContext(ctx context.Context) error { pid := p.Pid - statPath := common.HostProc(strconv.Itoa(int(pid)), "comm") - contents, err := ioutil.ReadFile(statPath) + statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "comm") + contents, err := os.ReadFile(statPath) if err != nil { return err } @@ -817,8 +817,8 @@ func (p *Process) fillFromStatus() error { func (p *Process) fillFromStatusWithContext(ctx context.Context) error { pid := p.Pid - statPath := common.HostProc(strconv.Itoa(int(pid)), "status") - contents, err := ioutil.ReadFile(statPath) + statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "status") + contents, err := os.ReadFile(statPath) if err != nil { return err } @@ -844,8 +844,6 @@ func (p *Process) fillFromStatusWithContext(ctx context.Context) error { extendedName := filepath.Base(cmdlineSlice[0]) if strings.HasPrefix(extendedName, p.name) { p.name = extendedName - } else { - p.name = cmdlineSlice[0] } } } @@ -1022,12 +1020,12 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui var statPath string if tid == -1 { - statPath = common.HostProc(strconv.Itoa(int(pid)), "stat") + statPath = common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "stat") } else { - statPath = common.HostProc(strconv.Itoa(int(pid)), "task", strconv.Itoa(int(tid)), "stat") + statPath = common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "task", strconv.Itoa(int(tid)), "stat") } - contents, err := ioutil.ReadFile(statPath) + contents, err := os.ReadFile(statPath) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err } @@ -1128,7 +1126,7 @@ func (p *Process) fillFromStatWithContext(ctx context.Context) (uint64, int32, * } func pidsWithContext(ctx context.Context) ([]int32, error) { - return readPidsFromDir(common.HostProc()) + return readPidsFromDir(common.HostProcWithContext(ctx)) } func ProcessesWithContext(ctx context.Context) ([]*Process, error) { diff --git a/process/process_linux_test.go b/process/process_linux_test.go index 76dee4437..87df81231 100644 --- a/process/process_linux_test.go +++ b/process/process_linux_test.go @@ -4,8 +4,8 @@ package process import ( + "context" "fmt" - "io/ioutil" "os" "strconv" "strings" @@ -57,7 +57,7 @@ func Test_Process_splitProcStat(t *testing.T) { } func Test_Process_splitProcStat_fromFile(t *testing.T) { - pids, err := ioutil.ReadDir("testdata/linux/") + pids, err := os.ReadDir("testdata/linux/") if err != nil { t.Error(err) } @@ -71,7 +71,7 @@ func Test_Process_splitProcStat_fromFile(t *testing.T) { if _, err := os.Stat(statFile); err != nil { continue } - contents, err := ioutil.ReadFile(statFile) + contents, err := os.ReadFile(statFile) assert.NoError(t, err) pidStr := strconv.Itoa(int(pid)) @@ -93,7 +93,7 @@ func Test_Process_splitProcStat_fromFile(t *testing.T) { } func Test_fillFromCommWithContext(t *testing.T) { - pids, err := ioutil.ReadDir("testdata/linux/") + pids, err := os.ReadDir("testdata/linux/") if err != nil { t.Error(err) } @@ -107,14 +107,14 @@ func Test_fillFromCommWithContext(t *testing.T) { continue } p, _ := NewProcess(int32(pid)) - if err := p.fillFromCommWithContext(); err != nil { + if err := p.fillFromCommWithContext(context.Background()); err != nil { t.Error(err) } } } func Test_fillFromStatusWithContext(t *testing.T) { - pids, err := ioutil.ReadDir("testdata/linux/") + pids, err := os.ReadDir("testdata/linux/") if err != nil { t.Error(err) } @@ -139,7 +139,7 @@ func Benchmark_fillFromCommWithContext(b *testing.B) { pid := 1060 p, _ := NewProcess(int32(pid)) for i := 0; i < b.N; i++ { - p.fillFromCommWithContext() + p.fillFromCommWithContext(context.Background()) } } @@ -153,7 +153,7 @@ func Benchmark_fillFromStatusWithContext(b *testing.B) { } func Test_fillFromTIDStatWithContext_lx_brandz(t *testing.T) { - pids, err := ioutil.ReadDir("testdata/lx_brandz/") + pids, err := os.ReadDir("testdata/lx_brandz/") if err != nil { t.Error(err) } diff --git a/process/process_openbsd.go b/process/process_openbsd.go index cbb1a77f6..a58c5eb11 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -60,8 +60,6 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { extendedName := filepath.Base(cmdlineSlice[0]) if strings.HasPrefix(extendedName, p.name) { name = extendedName - } else { - name = cmdlineSlice[0] } } } diff --git a/process/process_posix.go b/process/process_posix.go index 88e2bff53..a01f9ecfc 100644 --- a/process/process_posix.go +++ b/process/process_posix.go @@ -14,8 +14,9 @@ import ( "strings" "syscall" - "github.com/shirou/gopsutil/v3/internal/common" "golang.org/x/sys/unix" + + "github.com/shirou/gopsutil/v3/internal/common" ) type Signal = syscall.Signal @@ -108,8 +109,8 @@ func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { return false, err } - if isMount(common.HostProc()) { // if //proc exists and is mounted, check if //proc/ folder exists - _, err := os.Stat(common.HostProc(strconv.Itoa(int(pid)))) + if isMount(common.HostProcWithContext(ctx)) { // if //proc exists and is mounted, check if //proc/ folder exists + _, err := os.Stat(common.HostProcWithContext(ctx, strconv.Itoa(int(pid)))) if os.IsNotExist(err) { return false, nil } @@ -121,7 +122,7 @@ func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { if err == nil { return true, nil } - if err.Error() == "os: process already finished" { + if errors.Is(err, os.ErrProcessDone) { return false, nil } var errno syscall.Errno diff --git a/process/process_solaris.go b/process/process_solaris.go index 4f10a67bc..dd4bd4760 100644 --- a/process/process_solaris.go +++ b/process/process_solaris.go @@ -3,7 +3,6 @@ package process import ( "bytes" "context" - "io/ioutil" "os" "strconv" "strings" @@ -30,7 +29,7 @@ type MemoryMapsStat struct { type MemoryInfoExStat struct{} func pidsWithContext(ctx context.Context) ([]int32, error) { - return readPidsFromDir(common.HostProc()) + return readPidsFromDir(common.HostProcWithContext(ctx)) } func ProcessesWithContext(ctx context.Context) ([]*Process, error) { @@ -199,7 +198,7 @@ func (p *Process) EnvironWithContext(ctx context.Context) ([]string, error) { func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) { pid := p.Pid - statPath := common.HostProc(strconv.Itoa(int(pid)), "fd") + statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "fd") d, err := os.Open(statPath) if err != nil { return statPath, []string{}, err @@ -211,7 +210,7 @@ func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []stri func (p *Process) fillFromPathCwdWithContext(ctx context.Context) (string, error) { pid := p.Pid - cwdPath := common.HostProc(strconv.Itoa(int(pid)), "path", "cwd") + cwdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "path", "cwd") cwd, err := os.Readlink(cwdPath) if err != nil { return "", err @@ -221,7 +220,7 @@ func (p *Process) fillFromPathCwdWithContext(ctx context.Context) (string, error func (p *Process) fillFromPathAOutWithContext(ctx context.Context) (string, error) { pid := p.Pid - cwdPath := common.HostProc(strconv.Itoa(int(pid)), "path", "a.out") + cwdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "path", "a.out") exe, err := os.Readlink(cwdPath) if err != nil { return "", err @@ -231,8 +230,8 @@ func (p *Process) fillFromPathAOutWithContext(ctx context.Context) (string, erro func (p *Process) fillFromExecnameWithContext(ctx context.Context) (string, error) { pid := p.Pid - execNamePath := common.HostProc(strconv.Itoa(int(pid)), "execname") - exe, err := ioutil.ReadFile(execNamePath) + execNamePath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "execname") + exe, err := os.ReadFile(execNamePath) if err != nil { return "", err } @@ -241,8 +240,8 @@ func (p *Process) fillFromExecnameWithContext(ctx context.Context) (string, erro func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) { pid := p.Pid - cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") - cmdline, err := ioutil.ReadFile(cmdPath) + cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline") + cmdline, err := os.ReadFile(cmdPath) if err != nil { return "", err } @@ -258,8 +257,8 @@ func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) { pid := p.Pid - cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") - cmdline, err := ioutil.ReadFile(cmdPath) + cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline") + cmdline, err := os.ReadFile(cmdPath) if err != nil { return nil, err } diff --git a/process/process_test.go b/process/process_test.go index 40e3639fa..877992b25 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -1,9 +1,10 @@ package process import ( + "bufio" "errors" "fmt" - "io/ioutil" + "io" "net" "os" "os/exec" @@ -17,8 +18,10 @@ import ( "testing" "time" - "github.com/shirou/gopsutil/v3/internal/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/shirou/gopsutil/v3/internal/common" ) var mu sync.Mutex @@ -224,6 +227,11 @@ func Test_Process_NumCtx(t *testing.T) { func Test_Process_Nice(t *testing.T) { p := testGetProcess() + // https://github.com/shirou/gopsutil/issues/1532 + if os.Getenv("CI") == "true" && runtime.GOOS == "darwin" { + t.Skip("Skip CI") + } + n, err := p.Nice() skipIfNotImplementedErr(t, err) if err != nil { @@ -299,7 +307,7 @@ func Test_Process_Name(t *testing.T) { } func Test_Process_Long_Name_With_Spaces(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "") + tmpdir, err := os.MkdirTemp("", "") if err != nil { t.Fatalf("unable to create temp dir %v", err) } @@ -345,7 +353,7 @@ func Test_Process_Long_Name_With_Spaces(t *testing.T) { } func Test_Process_Long_Name(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "") + tmpdir, err := os.MkdirTemp("", "") if err != nil { t.Fatalf("unable to create temp dir %v", err) } @@ -390,6 +398,62 @@ func Test_Process_Long_Name(t *testing.T) { cmd.Process.Kill() } +func Test_Process_Name_Against_Python(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("only applies to posix") + } + py3Path, err := exec.LookPath("python3") + if err != nil { + t.Skipf("python3 not found: %s", err) + } + if out, err := exec.Command(py3Path, "-c", "import psutil").CombinedOutput(); err != nil { + t.Skipf("psutil not found for %s: %s", py3Path, out) + } + + tmpdir, err := os.MkdirTemp("", "") + if err != nil { + t.Fatalf("unable to create temp dir %v", err) + } + defer os.RemoveAll(tmpdir) // clean up + tmpfilepath := filepath.Join(tmpdir, "looooooooooooooooooooong.py") + tmpfile, err := os.Create(tmpfilepath) + if err != nil { + t.Fatalf("unable to create temp file %v", err) + } + tmpfilecontent := []byte("#!" + py3Path + "\nimport psutil, time\nprint(psutil.Process().name(), flush=True)\nwhile True:\n\ttime.sleep(1)") + if _, err := tmpfile.Write(tmpfilecontent); err != nil { + tmpfile.Close() + t.Fatalf("unable to write temp file %v", err) + } + if err := tmpfile.Chmod(0o744); err != nil { + t.Fatalf("unable to chmod u+x temp file %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("unable to close temp file %v", err) + } + cmd := exec.Command(tmpfilepath) + outPipe, _ := cmd.StdoutPipe() + scanner := bufio.NewScanner(outPipe) + cmd.Start() + defer cmd.Process.Kill() + scanner.Scan() + pyName := scanner.Text() // first line printed by py3 script, its name + t.Logf("pyName %s", pyName) + p, err := NewProcess(int32(cmd.Process.Pid)) + skipIfNotImplementedErr(t, err) + if err != nil { + t.Fatalf("getting process error %v", err) + } + name, err := p.Name() + skipIfNotImplementedErr(t, err) + if err != nil { + t.Fatalf("getting name error %v", err) + } + if pyName != name { + t.Fatalf("psutil and gopsutil process.Name() results differ: expected %s, got %s", pyName, name) + } +} + func Test_Process_Exe(t *testing.T) { p := testGetProcess() @@ -443,7 +507,7 @@ func Test_Process_CpuPercentLoop(t *testing.T) { } func Test_Process_CreateTime(t *testing.T) { - if os.Getenv("CIRCLECI") == "true" { + if os.Getenv("CI") == "true" { t.Skip("Skip CI") } @@ -512,7 +576,7 @@ func Test_Connections(t *testing.T) { defer conn.Close() serverEstablished <- struct{}{} - _, err = ioutil.ReadAll(conn) + _, err = io.ReadAll(conn) if err != nil { panic(err) } @@ -715,7 +779,7 @@ func Test_IsRunning(t *testing.T) { } func Test_Process_Environ(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "") + tmpdir, err := os.MkdirTemp("", "") if err != nil { t.Fatalf("unable to create temp dir %v", err) } @@ -784,60 +848,6 @@ func Test_Process_Cwd(t *testing.T) { t.Log(pidCwd) } -func Test_AllProcesses_cmdLine(t *testing.T) { - procs, err := Processes() - skipIfNotImplementedErr(t, err) - if err != nil { - t.Fatalf("getting processes error %v", err) - } - for _, proc := range procs { - var exeName string - var cmdLine string - - exeName, _ = proc.Exe() - cmdLine, err = proc.Cmdline() - if err != nil { - cmdLine = "Error: " + err.Error() - } - - t.Logf("Process #%v: Name: %v / CmdLine: %v\n", proc.Pid, exeName, cmdLine) - } -} - -func Test_AllProcesses_environ(t *testing.T) { - procs, err := Processes() - skipIfNotImplementedErr(t, err) - if err != nil { - t.Fatalf("getting processes error %v", err) - } - for _, proc := range procs { - exeName, _ := proc.Exe() - environ, err := proc.Environ() - if err != nil { - environ = []string{"Error: " + err.Error()} - } - - t.Logf("Process #%v: Name: %v / Environment Variables: %v\n", proc.Pid, exeName, environ) - } -} - -func Test_AllProcesses_Cwd(t *testing.T) { - procs, err := Processes() - skipIfNotImplementedErr(t, err) - if err != nil { - t.Fatalf("getting processes error %v", err) - } - for _, proc := range procs { - exeName, _ := proc.Exe() - cwd, err := proc.Cwd() - if err != nil { - cwd = "Error: " + err.Error() - } - - t.Logf("Process #%v: Name: %v / Current Working Directory: %s\n", proc.Pid, exeName, cwd) - } -} - func BenchmarkNewProcess(b *testing.B) { checkPid := os.Getpid() for i := 0; i < b.N; i++ { @@ -858,3 +868,11 @@ func BenchmarkProcessPpid(b *testing.B) { p.Ppid() } } + +func BenchmarkProcesses(b *testing.B) { + for i := 0; i < b.N; i++ { + ps, err := Processes() + require.NoError(b, err) + require.Greater(b, len(ps), 0) + } +}