Merge tag 'v3.23.8' into elfrucool/fix-windows-load-avg

pull/1358/head
elfrucool 2 years ago
commit 04db49883f

@ -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.

@ -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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.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@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: |
${{ steps.cache-paths.outputs.cache }}

@ -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 }}"

@ -16,13 +16,13 @@ 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
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.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

@ -10,6 +10,6 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Release
run: make release

@ -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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: advanced-security/sbom-generator-action@375dee8e6144d9fd0ec1f5667b4f6fb4faacefed # v0.0.1
id: sbom
env:
GITHUB_TOKEN: ${{ github.token }}
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
path: ${{steps.sbom.outputs.fileName }}
name: "SBOM"

@ -8,6 +8,6 @@ jobs:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0

@ -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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.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@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: |
${{ steps.go-env.outputs.cache }}

@ -3,13 +3,19 @@ issues:
exclude-rules:
- linters:
- gosec
text: "G204"
text: "G204"
- linters:
- revive
text: "var-naming"
text: "var-naming"
- linters:
- revive
text: "exported"
text: "exported"
- linters:
- revive
text: "empty-block"
- linters:
- revive
text: "unused-parameter"
linters:
enable:
- asciicheck
@ -40,3 +46,10 @@ linters:
- structcheck
- unused
- varcheck
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/shirou)

@ -17,7 +17,9 @@ build_test: ## test only buildable
GOOS=linux GOARCH=386 go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=linux GOARCH=arm go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=linux GOARCH=arm64 go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=linux GOARCH=loong64 go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=linux GOARCH=riscv64 go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=linux GOARCH=s390x go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=freebsd GOARCH=amd64 go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=freebsd GOARCH=386 go test ./... | $(BUILD_FAIL_PATTERN)
GOOS=freebsd GOARCH=arm go test ./... | $(BUILD_FAIL_PATTERN)
@ -51,11 +53,13 @@ vet:
GOOS=linux GOARCH=amd64 go vet ./...
GOOS=linux GOARCH=arm64 go vet ./...
GOOS=linux GOARCH=arm go vet ./...
GOOS=linux GOARCH=loong64 go vet ./...
GOOS=linux GOARCH=mips64 go vet ./...
GOOS=linux GOARCH=mips64le go vet ./...
GOOS=linux GOARCH=mips go vet ./...
GOOS=linux GOARCH=mipsle go vet ./...
GOOS=linux GOARCH=ppc64le go vet ./...
GOOS=linux GOARCH=ppc64 go vet ./...
GOOS=linux GOARCH=riscv64 go vet ./...
GOOS=linux GOARCH=s390x go vet ./...

@ -90,9 +90,25 @@ environment variable.
You can set an alternative location to `/dev` by setting the `HOST_DEV`
environment variable.
You can set an alternative location to `/` by setting the `HOST_ROOT`
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
@ -151,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
@ -216,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 | |

@ -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

@ -6,8 +6,8 @@ package cpu
import (
"context"
"regexp"
"strings"
"strconv"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
)
@ -28,19 +28,19 @@ func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
}
ret := TimesStat{CPU: "cpu-total"}
h := whiteSpaces.Split(lines[len(lines)-3], -1) // headers
v := whiteSpaces.Split(lines[len(lines)-2], -1) // values
h := whiteSpaces.Split(lines[len(lines)-3], -1) // headers
v := whiteSpaces.Split(lines[len(lines)-2], -1) // values
for i, header := range h {
if t, err := strconv.ParseFloat(v[i], 64); err == nil {
switch header {
case `%usr`:
ret.User = t
case `%sys`:
ret.System = t
case `%wio`:
ret.Iowait = t
case `%idle`:
ret.Idle = t
case `%usr`:
ret.User = t
case `%sys`:
ret.System = t
case `%wio`:
ret.Iowait = t
case `%idle`:
ret.Idle = t
}
}
}

@ -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

@ -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)
}
}
}

@ -11,12 +11,78 @@ 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)
var armModelToModelName = map[uint64]string{
0x810: "ARM810",
0x920: "ARM920",
0x922: "ARM922",
0x926: "ARM926",
0x940: "ARM940",
0x946: "ARM946",
0x966: "ARM966",
0xa20: "ARM1020",
0xa22: "ARM1022",
0xa26: "ARM1026",
0xb02: "ARM11 MPCore",
0xb36: "ARM1136",
0xb56: "ARM1156",
0xb76: "ARM1176",
0xc05: "Cortex-A5",
0xc07: "Cortex-A7",
0xc08: "Cortex-A8",
0xc09: "Cortex-A9",
0xc0d: "Cortex-A17",
0xc0f: "Cortex-A15",
0xc0e: "Cortex-A17",
0xc14: "Cortex-R4",
0xc15: "Cortex-R5",
0xc17: "Cortex-R7",
0xc18: "Cortex-R8",
0xc20: "Cortex-M0",
0xc21: "Cortex-M1",
0xc23: "Cortex-M3",
0xc24: "Cortex-M4",
0xc27: "Cortex-M7",
0xc60: "Cortex-M0+",
0xd01: "Cortex-A32",
0xd02: "Cortex-A34",
0xd03: "Cortex-A53",
0xd04: "Cortex-A35",
0xd05: "Cortex-A55",
0xd06: "Cortex-A65",
0xd07: "Cortex-A57",
0xd08: "Cortex-A72",
0xd09: "Cortex-A73",
0xd0a: "Cortex-A75",
0xd0b: "Cortex-A76",
0xd0c: "Neoverse-N1",
0xd0d: "Cortex-A77",
0xd0e: "Cortex-A76AE",
0xd13: "Cortex-R52",
0xd20: "Cortex-M23",
0xd21: "Cortex-M33",
0xd40: "Neoverse-V1",
0xd41: "Cortex-A78",
0xd42: "Cortex-A78AE",
0xd43: "Cortex-A65AE",
0xd44: "Cortex-X1",
0xd46: "Cortex-A510",
0xd47: "Cortex-A710",
0xd48: "Cortex-X2",
0xd49: "Neoverse-N2",
0xd4a: "Neoverse-E1",
0xd4b: "Cortex-A78C",
0xd4c: "Cortex-X1C",
0xd4d: "Cortex-A715",
0xd4e: "Cortex-X3",
}
func init() {
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
// ignore errors
@ -30,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)
@ -60,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]
}
@ -79,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 {
@ -107,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
@ -125,9 +191,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
switch key {
case "Processor":
processorName = value
case "processor":
case "processor", "cpu number":
if c.CPU >= 0 {
finishCPUInfo(&c)
finishCPUInfo(ctx, &c)
ret = append(ret, c)
}
c = InfoStat{Cores: 1, ModelName: processorName}
@ -138,6 +204,9 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c.CPU = int32(t)
case "vendorId", "vendor_id":
c.VendorID = value
if strings.Contains(value, "S390") {
processorName = "S390"
}
case "CPU implementer":
if v, err := strconv.ParseUint(value, 0, 8); err == nil {
switch v {
@ -177,10 +246,20 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
c.Family = value
case "model", "CPU part":
c.Model = value
case "model name", "cpu":
// if CPU is arm based, model name is found via model number. refer to: arch/arm64/kernel/cpuinfo.c
if c.VendorID == "ARM" {
if v, err := strconv.ParseUint(c.Model, 0, 16); err == nil {
modelName, exist := armModelToModelName[v]
if exist {
c.ModelName = modelName
} else {
c.ModelName = "Undefined"
}
}
}
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"
@ -197,7 +276,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
return ret, err
}
c.Stepping = int32(t)
case "cpu MHz", "clock":
case "cpu MHz", "clock", "cpu MHz dynamic":
// treat this as the fallback value, thus we ignore error
if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
c.Mhz = t
@ -221,7 +300,7 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
}
}
if c.CPU >= 0 {
finishCPUInfo(&c)
finishCPUInfo(ctx, &c)
ret = append(ret, c)
}
return ret, nil
@ -310,7 +389,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 {
@ -324,7 +403,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
@ -345,7 +424,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 {
@ -360,7 +439,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

@ -2,7 +2,6 @@ package cpu
import (
"errors"
"os"
"os/exec"
"strconv"
"strings"
@ -10,8 +9,7 @@ import (
)
func TestTimesEmpty(t *testing.T) {
orig := os.Getenv("HOST_PROC")
os.Setenv("HOST_PROC", "testdata/linux/times_empty")
t.Setenv("HOST_PROC", "testdata/linux/times_empty")
_, err := Times(true)
if err != nil {
t.Error("Times(true) failed")
@ -20,12 +18,10 @@ func TestTimesEmpty(t *testing.T) {
if err != nil {
t.Error("Times(false) failed")
}
os.Setenv("HOST_PROC", orig)
}
func TestCPUparseStatLine_424(t *testing.T) {
orig := os.Getenv("HOST_PROC")
os.Setenv("HOST_PROC", "testdata/linux/424/proc")
t.Setenv("HOST_PROC", "testdata/linux/424/proc")
{
l, err := Times(true)
if err != nil || len(l) == 0 {
@ -40,7 +36,6 @@ func TestCPUparseStatLine_424(t *testing.T) {
}
t.Logf("Times(false): %#v", l)
}
os.Setenv("HOST_PROC", orig)
}
func TestCPUCountsAgainstLscpu(t *testing.T) {
@ -53,7 +48,9 @@ func TestCPUCountsAgainstLscpu(t *testing.T) {
}
t.Errorf("error executing lscpu: %v", err)
}
var threadsPerCore, coresPerSocket, sockets int
var threadsPerCore, coresPerSocket, sockets, books, drawers int
books = 1
drawers = 1
lines := strings.Split(string(out), "\n")
for _, line := range lines {
fields := strings.Split(line, ":")
@ -65,14 +62,18 @@ func TestCPUCountsAgainstLscpu(t *testing.T) {
threadsPerCore, _ = strconv.Atoi(strings.TrimSpace(fields[1]))
case "Core(s) per socket":
coresPerSocket, _ = strconv.Atoi(strings.TrimSpace(fields[1]))
case "Socket(s)":
case "Socket(s)", "Socket(s) per book":
sockets, _ = strconv.Atoi(strings.TrimSpace(fields[1]))
case "Book(s) per drawer":
books, _ = strconv.Atoi(strings.TrimSpace(fields[1]))
case "Drawer(s)":
drawers, _ = strconv.Atoi(strings.TrimSpace(fields[1]))
}
}
if threadsPerCore == 0 || coresPerSocket == 0 || sockets == 0 {
t.Errorf("missing info from lscpu: threadsPerCore=%d coresPerSocket=%d sockets=%d", threadsPerCore, coresPerSocket, sockets)
}
expectedPhysical := coresPerSocket * sockets
expectedPhysical := coresPerSocket * sockets * books * drawers
expectedLogical := expectedPhysical * threadsPerCore
physical, err := Counts(false)
skipIfNotImplementedErr(t, err)
@ -93,9 +94,7 @@ func TestCPUCountsAgainstLscpu(t *testing.T) {
}
func TestCPUCountsLogicalAndroid_1037(t *testing.T) { // https://github.com/shirou/gopsutil/issues/1037
orig := os.Getenv("HOST_PROC")
os.Setenv("HOST_PROC", "testdata/linux/1037/proc")
defer os.Setenv("HOST_PROC", orig)
t.Setenv("HOST_PROC", "testdata/linux/1037/proc")
count, err := Counts(true)
if err != nil {

@ -4,7 +4,6 @@
package cpu
import (
"os"
"path/filepath"
"testing"
@ -30,13 +29,9 @@ var timesTests = []struct {
}
func TestTimesPlan9(t *testing.T) {
origRoot := os.Getenv("HOST_ROOT")
t.Cleanup(func() {
os.Setenv("HOST_ROOT", origRoot)
})
for _, tt := range timesTests {
t.Run(tt.mockedRootFS, func(t *testing.T) {
os.Setenv("HOST_ROOT", filepath.Join("testdata/plan9", tt.mockedRootFS))
t.Setenv("HOST_ROOT", filepath.Join("testdata/plan9", tt.mockedRootFS))
stats, err := Times(false)
skipIfNotImplementedErr(t, err)
if err != nil {

@ -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)
}

@ -14,8 +14,7 @@ import (
)
var (
procGetActiveProcessorCount = common.Modkernel32.NewProc("GetActiveProcessorCount")
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
)
type win32_Processor struct {
@ -204,15 +203,12 @@ type systemInfo struct {
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
if logical {
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
err := procGetActiveProcessorCount.Find()
if err == nil { // Win7+
ret, _, _ := procGetActiveProcessorCount.Call(uintptr(0xffff)) // ALL_PROCESSOR_GROUPS is 0xffff according to Rust's winapi lib https://docs.rs/winapi/*/x86_64-pc-windows-msvc/src/winapi/shared/ntdef.rs.html#120
if ret != 0 {
return int(ret), nil
}
ret := windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS)
if ret != 0 {
return int(ret), nil
}
var systemInfo systemInfo
_, _, err = procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
_, _, err := procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
if systemInfo.dwNumberOfProcessors == 0 {
return 0, err
}

@ -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"`

@ -5,14 +5,78 @@ package disk
import (
"context"
"regexp"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
)
var whiteSpaces = regexp.MustCompile(`\s+`)
var startBlank = regexp.MustCompile(`^\s+`)
var ignoreFSType = map[string]bool{"procfs": true}
var FSType = map[int]string{
0: "jfs2", 1: "namefs", 2: "nfs", 3: "jfs", 5: "cdrom", 6: "proc",
16: "special-fs", 17: "cache-fs", 18: "nfs3", 19: "automount-fs", 20: "pool-fs", 32: "vxfs",
33: "veritas-fs", 34: "udfs", 35: "nfs4", 36: "nfs4-pseudo", 37: "smbfs", 38: "mcr-pseudofs",
39: "ahafs", 40: "sterm-nfs", 41: "asmfs",
}
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
return []PartitionStat{}, common.ErrNotImplementedError
var ret []PartitionStat
out, err := invoke.CommandWithContext(ctx, "mount")
if err != nil {
return nil, err
}
// parse head lines for column names
colidx := make(map[string]int)
lines := strings.Split(string(out), "\n")
if len(lines) < 3 {
return nil, common.ErrNotImplementedError
}
idx := 0
start := 0
finished := false
for pos, ch := range lines[1] {
if ch == ' ' && !finished {
name := strings.TrimSpace(lines[0][start:pos])
colidx[name] = idx
finished = true
} else if ch == '-' && finished {
idx++
start = pos
finished = false
}
}
name := strings.TrimSpace(lines[0][start:len(lines[1])])
colidx[name] = idx
for idx := 2; idx < len(lines); idx++ {
line := lines[idx]
if startBlank.MatchString(line) {
line = "localhost" + line
}
p := whiteSpaces.Split(lines[idx], 6)
if len(p) < 5 || ignoreFSType[p[colidx["vfs"]]] {
continue
}
d := PartitionStat{
Device: p[colidx["mounted"]],
Mountpoint: p[colidx["mounted over"]],
Fstype: p[colidx["vfs"]],
Opts: strings.Split(p[colidx["options"]], ","),
}
ret = append(ret, d)
}
return ret, nil
}
func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
return nil, common.ErrNotImplementedError
func getFsType(stat unix.Statfs_t) string {
return FSType[int(stat.Vfstype)]
}

@ -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.
@ -20,9 +21,15 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
return ret, err
}
fs := make([]unix.Statfs_t, count)
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
count, err = unix.Getfsstat(fs, unix.MNT_WAIT)
if err != nil {
return ret, err
}
// On 10.14, and possibly other OS versions, the actual count may
// be less than from the first call. Truncate to the returned count
// to prevent accessing uninitialized entries.
// https://github.com/shirou/gopsutil/issues/1390
fs = fs[:count]
for _, stat := range fs {
opts := []string{"rw"}
if stat.Flags&unix.MNT_RDONLY != 0 {

@ -1,5 +1,5 @@
//go:build darwin && cgo
// +build darwin,cgo
//go:build darwin && cgo && !ios
// +build darwin,cgo,!ios
package disk

@ -1,5 +1,5 @@
//go:build darwin && !cgo
// +build darwin,!cgo
//go:build (darwin && !cgo) || ios
// +build darwin,!cgo ios
package disk

@ -12,9 +12,8 @@ import (
"strconv"
"strings"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
)
// PartitionsWithContext returns disk partition.

@ -16,8 +16,9 @@ import (
"strconv"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v3/internal/common"
)
const (
@ -259,10 +260,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 +274,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 +342,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 +351,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 +364,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 +387,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 +474,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,7 +496,7 @@ 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))
udevDataPath := common.HostRunWithContext(ctx, fmt.Sprintf("udev/data/b%d:%d", major, minor))
if udevdata, err := ioutil.ReadFile(udevDataPath); err == nil {
scanner := bufio.NewScanner(bytes.NewReader(udevdata))
for scanner.Scan() {
@ -504,7 +509,7 @@ 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))
devicePath := common.HostSysWithContext(ctx, fmt.Sprintf("dev/block/%d:0/device", major))
model, _ := ioutil.ReadFile(filepath.Join(devicePath, "model"))
serial, _ := ioutil.ReadFile(filepath.Join(devicePath, "serial"))
if len(model) > 0 && len(serial) > 0 {
@ -515,7 +520,7 @@ 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

@ -10,6 +10,10 @@ import (
"fmt"
"math"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
@ -73,20 +77,129 @@ func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, erro
})
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("unable to scan %q: %v", _MNTTAB, err)
return nil, fmt.Errorf("unable to scan %q: %w", _MNTTAB, err)
}
return ret, err
}
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
return nil, common.ErrNotImplementedError
var issolaris bool
if runtime.GOOS == "illumos" {
issolaris = false
} else {
issolaris = true
}
// check disks instead of zfs pools
filterstr := "/[^zfs]/:::/^nread$|^nwritten$|^reads$|^writes$|^rtime$|^wtime$/"
kstatSysOut, err := invoke.CommandWithContext(ctx, "kstat", "-c", "disk", "-p", filterstr)
if err != nil {
return nil, fmt.Errorf("cannot execute kstat: %w", err)
}
lines := strings.Split(strings.TrimSpace(string(kstatSysOut)), "\n")
if len(lines) == 0 {
return nil, fmt.Errorf("no disk class found")
}
dnamearr := make(map[string]string)
nreadarr := make(map[string]uint64)
nwrittenarr := make(map[string]uint64)
readsarr := make(map[string]uint64)
writesarr := make(map[string]uint64)
rtimearr := make(map[string]uint64)
wtimearr := make(map[string]uint64)
re := regexp.MustCompile(`[:\s]+`)
// in case the name is "/dev/sda1", then convert to "sda1"
for i, name := range names {
names[i] = filepath.Base(name)
}
for _, line := range lines {
fields := re.Split(line, -1)
if len(fields) == 0 {
continue
}
moduleName := fields[0]
instance := fields[1]
dname := fields[2]
if len(names) > 0 && !common.StringsHas(names, dname) {
continue
}
dnamearr[moduleName+instance] = dname
// fields[3] is the statistic label, fields[4] is the value
switch fields[3] {
case "nread":
nreadarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "nwritten":
nwrittenarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "reads":
readsarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "writes":
writesarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "rtime":
if issolaris {
// from sec to milli secs
var frtime float64
frtime, err = strconv.ParseFloat((fields[4]), 64)
rtimearr[moduleName+instance] = uint64(frtime * 1000)
} else {
// from nano to milli secs
rtimearr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
rtimearr[moduleName+instance] = rtimearr[moduleName+instance] / 1000 / 1000
}
if err != nil {
return nil, err
}
case "wtime":
if issolaris {
// from sec to milli secs
var fwtime float64
fwtime, err = strconv.ParseFloat((fields[4]), 64)
wtimearr[moduleName+instance] = uint64(fwtime * 1000)
} else {
// from nano to milli secs
wtimearr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
wtimearr[moduleName+instance] = wtimearr[moduleName+instance] / 1000 / 1000
}
if err != nil {
return nil, err
}
}
}
ret := make(map[string]IOCountersStat, 0)
for k := range dnamearr {
d := IOCountersStat{
Name: dnamearr[k],
ReadBytes: nreadarr[k],
WriteBytes: nwrittenarr[k],
ReadCount: readsarr[k],
WriteCount: writesarr[k],
ReadTime: rtimearr[k],
WriteTime: wtimearr[k],
}
ret[d.Name] = d
}
return ret, nil
}
func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
statvfs := unix.Statvfs_t{}
if err := unix.Statvfs(path, &statvfs); err != nil {
return nil, fmt.Errorf("unable to call statvfs(2) on %q: %v", path, err)
return nil, fmt.Errorf("unable to call statvfs(2) on %q: %w", path, err)
}
usageStat := &UsageStat{

@ -1,5 +1,5 @@
//go:build freebsd || linux || darwin
// +build freebsd linux darwin
//go:build freebsd || linux || darwin || (aix && !cgo)
// +build freebsd linux darwin aix,!cgo
package disk

@ -50,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()
}
}
@ -79,66 +80,105 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
return ret, nil
}
// PartitionsWithContext returns disk partitions.
// Since GetVolumeInformation doesn't have a timeout, this method uses context to set deadline by users.
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
var ret []PartitionStat
lpBuffer := make([]byte, 254)
diskret, _, err := procGetLogicalDriveStringsW.Call(
uintptr(len(lpBuffer)),
uintptr(unsafe.Pointer(&lpBuffer[0])))
if diskret == 0 {
return ret, err
warnings := Warnings{
Verbose: true,
}
for _, v := range lpBuffer {
if v >= 65 && v <= 90 {
path := string(v) + ":"
typepath, _ := windows.UTF16PtrFromString(path)
typeret, _, _ := procGetDriveType.Call(uintptr(unsafe.Pointer(typepath)))
if typeret == 0 {
return ret, windows.GetLastError()
}
// 2: DRIVE_REMOVABLE 3: DRIVE_FIXED 4: DRIVE_REMOTE 5: DRIVE_CDROM
if typeret == 2 || typeret == 3 || typeret == 4 || typeret == 5 {
lpVolumeNameBuffer := make([]byte, 256)
lpVolumeSerialNumber := int64(0)
lpMaximumComponentLength := int64(0)
lpFileSystemFlags := int64(0)
lpFileSystemNameBuffer := make([]byte, 256)
volpath, _ := windows.UTF16PtrFromString(string(v) + ":/")
driveret, _, err := procGetVolumeInformation.Call(
uintptr(unsafe.Pointer(volpath)),
uintptr(unsafe.Pointer(&lpVolumeNameBuffer[0])),
uintptr(len(lpVolumeNameBuffer)),
uintptr(unsafe.Pointer(&lpVolumeSerialNumber)),
uintptr(unsafe.Pointer(&lpMaximumComponentLength)),
uintptr(unsafe.Pointer(&lpFileSystemFlags)),
uintptr(unsafe.Pointer(&lpFileSystemNameBuffer[0])),
uintptr(len(lpFileSystemNameBuffer)))
if driveret == 0 {
if typeret == 5 || typeret == 2 {
continue // device is not ready will happen if there is no disk in the drive
}
return ret, err
}
opts := []string{"rw"}
if lpFileSystemFlags&fileReadOnlyVolume != 0 {
opts = []string{"ro"}
var errLogicalDrives error
retChan := make(chan PartitionStat)
quitChan := make(chan struct{})
defer close(quitChan)
getPartitions := func() {
defer close(retChan)
lpBuffer := make([]byte, 254)
diskret, _, err := procGetLogicalDriveStringsW.Call(
uintptr(len(lpBuffer)),
uintptr(unsafe.Pointer(&lpBuffer[0])))
if diskret == 0 {
errLogicalDrives = err
return
}
for _, v := range lpBuffer {
if v >= 65 && v <= 90 {
path := string(v) + ":"
typepath, _ := windows.UTF16PtrFromString(path)
typeret, _, _ := procGetDriveType.Call(uintptr(unsafe.Pointer(typepath)))
if typeret == 0 {
err := windows.GetLastError()
warnings.Add(err)
continue
}
if lpFileSystemFlags&fileFileCompression != 0 {
opts = append(opts, "compress")
// 2: DRIVE_REMOVABLE 3: DRIVE_FIXED 4: DRIVE_REMOTE 5: DRIVE_CDROM
if typeret == 2 || typeret == 3 || typeret == 4 || typeret == 5 {
lpVolumeNameBuffer := make([]byte, 256)
lpVolumeSerialNumber := int64(0)
lpMaximumComponentLength := int64(0)
lpFileSystemFlags := int64(0)
lpFileSystemNameBuffer := make([]byte, 256)
volpath, _ := windows.UTF16PtrFromString(string(v) + ":/")
driveret, _, err := procGetVolumeInformation.Call(
uintptr(unsafe.Pointer(volpath)),
uintptr(unsafe.Pointer(&lpVolumeNameBuffer[0])),
uintptr(len(lpVolumeNameBuffer)),
uintptr(unsafe.Pointer(&lpVolumeSerialNumber)),
uintptr(unsafe.Pointer(&lpMaximumComponentLength)),
uintptr(unsafe.Pointer(&lpFileSystemFlags)),
uintptr(unsafe.Pointer(&lpFileSystemNameBuffer[0])),
uintptr(len(lpFileSystemNameBuffer)))
if driveret == 0 {
if typeret == 5 || typeret == 2 {
continue // device is not ready will happen if there is no disk in the drive
}
warnings.Add(err)
continue
}
opts := []string{"rw"}
if lpFileSystemFlags&fileReadOnlyVolume != 0 {
opts = []string{"ro"}
}
if lpFileSystemFlags&fileFileCompression != 0 {
opts = append(opts, "compress")
}
select {
case retChan <- PartitionStat{
Mountpoint: path,
Device: path,
Fstype: string(bytes.ReplaceAll(lpFileSystemNameBuffer, []byte("\x00"), []byte(""))),
Opts: opts,
}:
case <-quitChan:
return
}
}
}
}
}
d := PartitionStat{
Mountpoint: path,
Device: path,
Fstype: string(bytes.Replace(lpFileSystemNameBuffer, []byte("\x00"), []byte(""), -1)),
Opts: opts,
go getPartitions()
var ret []PartitionStat
for {
select {
case p, ok := <-retChan:
if !ok {
if errLogicalDrives != nil {
return ret, errLogicalDrives
}
ret = append(ret, d)
return ret, warnings.Reference()
}
ret = append(ret, p)
case <-ctx.Done():
return ret, ctx.Err()
}
}
return ret, nil
}
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {

@ -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

@ -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")
}

@ -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"))
}

@ -3,11 +3,14 @@ module github.com/shirou/gopsutil/v3
go 1.15
require (
github.com/google/go-cmp v0.5.8
github.com/google/go-cmp v0.5.9
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.0
github.com/tklauser/go-sysconf v0.3.10
github.com/yusufpapurcu/wmi v1.2.2
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
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.11.0
)
retract v3.22.11

@ -4,32 +4,36 @@ 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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
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.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.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=

@ -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.
@ -155,3 +157,7 @@ func SensorsTemperatures() ([]TemperatureStat, error) {
func timeSince(ts uint64) uint64 {
return uint64(time.Now().Unix()) - ts
}
func timeSinceMillis(ts uint64) uint64 {
return uint64(time.Now().UnixMilli()) - ts
}

@ -13,9 +13,10 @@ import (
"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

@ -15,8 +15,9 @@ import (
"strconv"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v3/internal/common"
)
type lsbStruct struct {
@ -34,9 +35,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
@ -66,7 +67,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) {
@ -82,7 +83,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 {
@ -123,10 +124,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
}
@ -137,13 +138,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") {
@ -158,13 +159,13 @@ func getlsbStruct() (*lsbStruct, error) {
}
switch field[0] {
case "Distributor ID":
ret.ID = field[1]
ret.ID = strings.ReplaceAll(field[1], `"`, ``)
case "Release":
ret.Release = field[1]
ret.Release = strings.ReplaceAll(field[1], `"`, ``)
case "Codename":
ret.Codename = field[1]
ret.Codename = strings.ReplaceAll(field[1], `"`, ``)
case "Description":
ret.Description = field[1]
ret.Description = strings.ReplaceAll(field[1], `"`, ``)
}
}
@ -174,90 +175,96 @@ func getlsbStruct() (*lsbStruct, error) {
}
func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version 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)
}
} 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)
}
} 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)
}
} 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
} else if lsb.ID == "LinuxMint" {
platform = "linuxmint"
version = lsb.Release
} else if lsb.ID == "Kylin" {
platform = "Kylin"
version = lsb.Release
} else if lsb.ID == `"Cumulus Linux"` {
platform = "cumuluslinux"
version = lsb.Release
} 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]
}
}
} 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)
}
} 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)
}
} 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)
}
} 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)
}
} 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)
}
// 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
} 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]
}
} else if common.PathExists(common.HostEtc("os-release")) {
p, v, err := common.GetOSRelease()
} else if common.PathExists(common.HostEtcWithContext(ctx, "os-release")) {
p, v, err := common.GetOSReleaseWithContext(ctx)
if err == nil {
platform = p
version = v
@ -282,7 +289,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"
@ -317,7 +324,7 @@ func KernelVersionWithContext(ctx context.Context) (version string, err error) {
if err != nil {
return "", err
}
return string(utsname.Release[:bytes.IndexByte(utsname.Release[:], 0)]), nil
return unix.ByteSliceToString(utsname.Release[:]), nil
}
func getSlackwareVersion(contents []string) string {
@ -383,14 +390,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
}
}
@ -398,7 +405,7 @@ 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
}

@ -0,0 +1,48 @@
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs host/types_linux.go
//go:build linux && loong64
// +build linux,loong64
package host
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeOfUtmp = 0x190
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
)
type (
utmp struct {
Type int16
Pid int32
Line [32]int8
Id [4]int8
User [32]int8
Host [256]int8
Exit exit_status
Session int64
Tv timeval
Addr_v6 [4]int32
X__glibc_reserved [20]int8
Pad_cgo_0 [4]byte
}
exit_status struct {
Termination int16
Exit int16
}
timeval struct {
Sec int64
Usec int64
}
)

@ -0,0 +1,48 @@
//go:build linux && ppc64
// +build linux,ppc64
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_linux.go
package host
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeOfUtmp = 0x180
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
)
type utmp struct {
Type int16
Pad_cgo_0 [2]byte
Pid int32
Line [32]int8
Id [4]int8
User [32]int8
Host [256]int8
Exit exit_status
Session int32
Tv timeval
Addr_v6 [4]int32
X__glibc_reserved [20]int8
}
type exit_status struct {
Termination int16
Exit int16
}
type timeval struct {
Sec int64
Usec int64
}

@ -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)
})
}
}

@ -3,14 +3,13 @@
package host
import (
"bytes"
"golang.org/x/sys/unix"
)
import "golang.org/x/sys/unix"
func KernelArch() (string, error) {
var utsname unix.Utsname
err := unix.Uname(&utsname)
return string(utsname.Machine[:bytes.IndexByte(utsname.Machine[:], 0)]), err
if err != nil {
return "", err
}
return unix.ByteSliceToString(utsname.Machine[:]), nil
}

@ -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()

@ -103,6 +103,14 @@ func numProcs(ctx context.Context) (uint64, error) {
}
func UptimeWithContext(ctx context.Context) (uint64, error) {
up, err := uptimeMillis()
if err != nil {
return 0, err
}
return uint64((time.Duration(up) * time.Millisecond).Seconds()), nil
}
func uptimeMillis() (uint64, error) {
procGetTickCount := procGetTickCount64
err := procGetTickCount64.Find()
if err != nil {
@ -112,7 +120,7 @@ func UptimeWithContext(ctx context.Context) (uint64, error) {
if lastErr != 0 {
return 0, lastErr
}
return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil
return uint64(r1), nil
}
// cachedBootTime must be accessed via atomic.Load/StoreUint64
@ -123,11 +131,11 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) {
if t != 0 {
return t, nil
}
up, err := Uptime()
up, err := uptimeMillis()
if err != nil {
return 0, err
}
t = timeSince(up)
t = uint64((time.Duration(timeSinceMillis(up)) * time.Millisecond).Seconds())
atomic.StoreUint64(&cachedBootTime, t)
return t, nil
}
@ -188,6 +196,14 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
}
}
var UBR uint32 // Update Build Revision
err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`UBR`), nil, &valType, nil, &bufLen)
if err == nil {
regBuf := make([]byte, 4)
err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`UBR`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
copy((*[4]byte)(unsafe.Pointer(&UBR))[:], regBuf)
}
// PlatformFamily
switch osInfo.wProductType {
case 1:
@ -199,7 +215,9 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil
}
// Platform Version
version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber)
version = fmt.Sprintf("%d.%d.%d.%d Build %d.%d",
osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, UBR,
osInfo.dwBuildNumber, UBR)
return platform, family, version, nil
}

@ -0,0 +1,3 @@
DISTRIB_ID="Arch"
DISTRIB_RELEASE="rolling"
DISTRIB_DESCRIPTION="Arch Linux"

@ -0,0 +1,4 @@
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"

@ -21,6 +21,7 @@ package common
// high-performance serialization, especially for large data structures,
// should look at more advanced solutions such as the encoding/gob
// package or protocol buffers.
import (
"errors"
"io"

@ -6,6 +6,7 @@ package common
// - linux (amd64, arm)
// - freebsd (amd64)
// - windows (amd64)
import (
"bufio"
"bytes"
@ -24,6 +25,8 @@ import (
"strconv"
"strings"
"time"
"github.com/shirou/gopsutil/v3/common"
)
var (
@ -111,6 +114,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):
@ -320,6 +347,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)
@ -327,6 +371,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
@ -364,14 +412,40 @@ func HostDev(combineWith ...string) string {
return GetEnv("HOST_DEV", "/dev", combineWith...)
}
// MockEnv set environment variable and return revert function.
// MockEnv should be used testing only.
func MockEnv(key string, value string) func() {
original := os.Getenv(key)
os.Setenv(key, value)
return func() {
os.Setenv(key, original)
}
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

@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
)
@ -30,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
}
@ -57,50 +62,70 @@ 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
}
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")
}
f := strings.Fields(lines[0])
b, err := strconv.ParseFloat(f[0], 64)
func handleBootTimeFileReadErr(err error) (uint64, error) {
if os.IsPermission(err) {
var info syscall.Sysinfo_t
err := syscall.Sysinfo(&info)
if err != nil {
return 0, err
}
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
t := currentTime - b
currentTime := time.Now().UnixNano() / int64(time.Second)
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 handleBootTimeFileReadErr(err)
}
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
}
return 0, fmt.Errorf("could not find btime")
}
@ -127,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
@ -142,13 +167,16 @@ 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 {
if StringsContains(contents, "kvm") {
system = "kvm"
role = "host"
} else if StringsContains(contents, "hv_util") {
system = "hyperv"
role = "guest"
} else if StringsContains(contents, "vboxdrv") {
system = "vbox"
role = "host"
@ -162,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 {
@ -175,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 {
@ -185,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"
@ -236,14 +264,19 @@ 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(HostRootWithContext(ctx, ".dockerenv")) {
system = "docker"
role = "guest"
}
// before returning for the first time, cache the system and role
cachedVirtOnce.Do(func() {
cachedVirtMutex.Lock()
@ -258,7 +291,11 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) {
}
func GetOSRelease() (platform string, version string, err error) {
contents, err := ReadLines(HostEtc("os-release"))
return GetOSReleaseWithContext(context.Background())
}
func GetOSReleaseWithContext(ctx context.Context) (platform string, version string, err error) {
contents, err := ReadLines(HostEtcWithContext(ctx, "os-release"))
if err != nil {
return "", "", nil // return empty
}

@ -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)
}
}

@ -218,9 +218,12 @@ func WMIQueryWithContext(ctx context.Context, query string, dst interface{}, con
}
// Convert paths using native DOS format like:
// "\Device\HarddiskVolume1\Windows\systemew\file.txt"
//
// "\Device\HarddiskVolume1\Windows\systemew\file.txt"
//
// into:
// "C:\Windows\systemew\file.txt"
//
// "C:\Windows\systemew\file.txt"
func ConvertDOSPath(p string) string {
rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`)

@ -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

@ -1,11 +1,10 @@
package host
package common
import (
"fmt"
)
import "fmt"
type Warnings struct {
List []error
List []error
Verbose bool
}
func (w *Warnings) Add(err error) {
@ -20,5 +19,12 @@ func (w *Warnings) Reference() error {
}
func (w *Warnings) Error() string {
if w.Verbose {
str := ""
for i, e := range w.List {
str += fmt.Sprintf("\tError %d: %s\n", i, e.Error())
}
return str
}
return fmt.Sprintf("Number of warnings: %v", len(w.List))
}

@ -53,13 +53,13 @@ func MiscWithContext(ctx context.Context) (*MiscStat, error) {
for _, line := range strings.Split(string(out), "\n") {
ret.ProcsTotal++
switch line {
case "R":
case "A":
ret.ProcsRunning++
case "T":
ret.ProcsBlocked++
default:
continue
case "R":
case "A":
ret.ProcsRunning++
case "T":
ret.ProcsBlocked++
default:
continue
}
}
return ret, nil

@ -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,7 +75,7 @@ func Misc() (*MiscStat, error) {
}
func MiscWithContext(ctx context.Context) (*MiscStat, error) {
filename := common.HostProc("stat")
filename := common.HostProcWithContext(ctx, "stat")
out, err := ioutil.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,16 +116,16 @@ 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")
func readLoadAvgFromFile(ctx context.Context) ([]string, error) {
loadavgFilename := common.HostProcWithContext(ctx, "loadavg")
line, err := ioutil.ReadFile(loadavgFilename)
if err != nil {
return nil, err

@ -26,7 +26,7 @@ var (
// TODO instead of this goroutine, we can register a Win32 counter just as psutil does
// see https://psutil.readthedocs.io/en/latest/#psutil.getloadavg
// code https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/arch/windows/wmi.c
func loadAvgGoroutine() {
func loadAvgGoroutine(ctx context.Context) {
var (
samplingFrequency time.Duration = 5 * time.Second
loadAvgFactor1M float64 = 1 / math.Exp(samplingFrequency.Seconds()/time.Minute.Seconds())
@ -46,12 +46,20 @@ func loadAvgGoroutine() {
}
loadAvgMutex.Lock()
loadErr = err
loadAvg1M = loadAvg1M*loadAvgFactor1M + currentLoad*(1-loadAvgFactor1M)
loadAvg5M = loadAvg5M*loadAvgFactor5M + currentLoad*(1-loadAvgFactor5M)
loadAvg15M = loadAvg15M*loadAvgFactor15M + currentLoad*(1-loadAvgFactor15M)
loadAvgMutex.Unlock()
<-tick
}
f() // run first time
for {
select {
case <-ctx.Done():
return
case <-tick:
f()
}
}
}
@ -62,7 +70,7 @@ func Avg() (*AvgStat, error) {
func AvgWithContext(ctx context.Context) (*AvgStat, error) {
loadAvgGoroutineOnce.Do(func() {
go loadAvgGoroutine()
go loadAvgGoroutine(ctx)
})
loadAvgMutex.RLock()
defer loadAvgMutex.RUnlock()

@ -71,7 +71,7 @@ func callSVMon(ctx context.Context) (*VirtualMemoryStat, *SwapMemoryStat, error)
swap.Total = t * pagesize
}
if t, err := strconv.ParseUint(p[3], 10, 64); err == nil {
swap.Free = swap.Total - t * pagesize
swap.Free = swap.Total - t*pagesize
}
}
break

@ -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) {

@ -8,9 +8,8 @@ import (
"errors"
"unsafe"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
)
func VirtualMemory() (*VirtualMemoryStat, error) {

@ -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
@ -317,7 +318,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 +351,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 +371,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 +403,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 +458,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 {

@ -4,7 +4,7 @@
package mem
import (
"os"
"context"
"path/filepath"
"reflect"
"strings"
@ -111,12 +111,9 @@ var virtualMemoryTests = []struct {
}
func TestVirtualMemoryLinux(t *testing.T) {
origProc := os.Getenv("HOST_PROC")
defer os.Setenv("HOST_PROC", origProc)
for _, tt := range virtualMemoryTests {
t.Run(tt.mockedRootFS, func(t *testing.T) {
os.Setenv("HOST_PROC", filepath.Join("testdata/linux/virtualmemory/", tt.mockedRootFS, "proc"))
t.Setenv("HOST_PROC", filepath.Join("testdata/linux/virtualmemory/", tt.mockedRootFS, "proc"))
stat, err := VirtualMemory()
skipIfNotImplementedErr(t, err)
@ -142,7 +139,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{
@ -159,11 +156,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)
}

@ -9,6 +9,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
)

@ -4,7 +4,6 @@
package mem
import (
"os"
"reflect"
"testing"
)
@ -27,14 +26,9 @@ var virtualMemoryTests = []struct {
}
func TestVirtualMemoryPlan9(t *testing.T) {
origProc := os.Getenv("HOST_ROOT")
t.Cleanup(func() {
os.Setenv("HOST_ROOT", origProc)
})
for _, tt := range virtualMemoryTests {
t.Run(tt.mockedRootFS, func(t *testing.T) {
os.Setenv("HOST_ROOT", "testdata/plan9/virtualmemory/")
t.Setenv("HOST_ROOT", "testdata/plan9/virtualmemory/")
stat, err := VirtualMemory()
skipIfNotImplementedErr(t, err)
@ -62,14 +56,9 @@ var swapMemoryTests = []struct {
}
func TestSwapMemoryPlan9(t *testing.T) {
origProc := os.Getenv("HOST_ROOT")
t.Cleanup(func() {
os.Setenv("HOST_ROOT", origProc)
})
for _, tt := range swapMemoryTests {
t.Run(tt.mockedRootFS, func(t *testing.T) {
os.Setenv("HOST_ROOT", "testdata/plan9/virtualmemory/")
t.Setenv("HOST_ROOT", "testdata/plan9/virtualmemory/")
swap, err := SwapMemory()
skipIfNotImplementedErr(t, err)

@ -12,6 +12,7 @@ import (
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
"github.com/tklauser/go-sysconf"
)
// VirtualMemory for Solaris is a minimal implementation which only returns
@ -34,6 +35,13 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
return nil, err
}
result.Total = cap
freemem, err := globalZoneFreeMemory(ctx)
if err != nil {
return nil, err
}
result.Available = freemem
result.Free = freemem
result.Used = result.Total - result.Free
} else {
cap, err := nonGlobalZoneMemoryCapacity()
if err != nil {
@ -85,6 +93,25 @@ func globalZoneMemoryCapacity() (uint64, error) {
return totalMB * 1024 * 1024, nil
}
func globalZoneFreeMemory(ctx context.Context) (uint64, error) {
output, err := invoke.CommandWithContext(ctx, "pagesize")
if err != nil {
return 0, err
}
pagesize, err := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 64)
if err != nil {
return 0, err
}
free, err := sysconf.Sysconf(sysconf.SC_AVPHYS_PAGES)
if err != nil {
return 0, err
}
return uint64(free) * pagesize, nil
}
var kstatMatch = regexp.MustCompile(`(\S+)\s+(\S*)`)
func nonGlobalZoneMemoryCapacity() (uint64, error) {

@ -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) {
@ -17,8 +18,8 @@ func skipIfNotImplementedErr(t *testing.T, err error) {
}
func TestVirtual_memory(t *testing.T) {
if runtime.GOOS == "solaris" {
t.Skip("Only .Total is supported on Solaris")
if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
t.Skip("Only .Total .Available are supported on Solaris/illumos")
}
v, err := VirtualMemory()

@ -18,14 +18,14 @@ func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat,
iocounters := make([]IOCountersStat, 0, len(ifs))
for _, netif := range ifs {
n := IOCountersStat{
Name: netif.Name,
BytesSent: uint64(netif.OBytes),
BytesRecv: uint64(netif.IBytes),
Name: netif.Name,
BytesSent: uint64(netif.OBytes),
BytesRecv: uint64(netif.IBytes),
PacketsSent: uint64(netif.OPackets),
PacketsRecv: uint64(netif.IPackets),
Errin: uint64(netif.OErrors),
Errout: uint64(netif.IErrors),
Dropout: uint64(netif.XmitDrops),
Errin: uint64(netif.OErrors),
Errout: uint64(netif.IErrors),
Dropout: uint64(netif.XmitDrops),
}
iocounters = append(iocounters, n)
}

@ -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

@ -1,5 +1,5 @@
//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows
// +build !aix,!darwin,!linux,!freebsd,!openbsd,!windows
//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows && !solaris
// +build !aix,!darwin,!linux,!freebsd,!openbsd,!windows,!solaris
package net

@ -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

@ -50,7 +50,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 +157,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 +177,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 +230,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 +260,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 +459,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 +531,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 +599,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 +631,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,9 +640,9 @@ 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")
statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "status")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return err

@ -1,6 +1,7 @@
package net
import (
"context"
"fmt"
"io/ioutil"
"net"
@ -9,8 +10,9 @@ import (
"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) {
@ -100,7 +102,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)
@ -248,6 +250,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 +311,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)

@ -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

@ -0,0 +1,143 @@
//go:build solaris
// +build solaris
package net
import (
"context"
"fmt"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
)
// NetIOCounters returnes network I/O statistics for every network
// interface installed on the system. If pernic argument is false,
// return only sum of all information (which name is 'all'). If true,
// every network interface installed on the system is returned
// separately.
func IOCounters(pernic bool) ([]IOCountersStat, error) {
return IOCountersWithContext(context.Background(), pernic)
}
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
// collect all the net class's links with below statistics
filterstr := "/^(?!vnic)/::phys:/^rbytes64$|^ipackets64$|^idrops64$|^ierrors$|^obytes64$|^opackets64$|^odrops64$|^oerrors$/"
if runtime.GOOS == "illumos" {
filterstr = "/[^vnic]/::mac:/^rbytes64$|^ipackets64$|^idrops64$|^ierrors$|^obytes64$|^opackets64$|^odrops64$|^oerrors$/"
}
kstatSysOut, err := invoke.CommandWithContext(ctx, "kstat", "-c", "net", "-p", filterstr)
if err != nil {
return nil, fmt.Errorf("cannot execute kstat: %w", err)
}
lines := strings.Split(strings.TrimSpace(string(kstatSysOut)), "\n")
if len(lines) == 0 {
return nil, fmt.Errorf("no interface found")
}
rbytes64arr := make(map[string]uint64)
ipackets64arr := make(map[string]uint64)
idrops64arr := make(map[string]uint64)
ierrorsarr := make(map[string]uint64)
obytes64arr := make(map[string]uint64)
opackets64arr := make(map[string]uint64)
odrops64arr := make(map[string]uint64)
oerrorsarr := make(map[string]uint64)
re := regexp.MustCompile(`[:\s]+`)
for _, line := range lines {
fields := re.Split(line, -1)
interfaceName := fields[0]
instance := fields[1]
switch fields[3] {
case "rbytes64":
rbytes64arr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse rbytes64: %w", err)
}
case "ipackets64":
ipackets64arr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse ipackets64: %w", err)
}
case "idrops64":
idrops64arr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse idrops64: %w", err)
}
case "ierrors":
ierrorsarr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse ierrors: %w", err)
}
case "obytes64":
obytes64arr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse obytes64: %w", err)
}
case "opackets64":
opackets64arr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse opackets64: %w", err)
}
case "odrops64":
odrops64arr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse odrops64: %w", err)
}
case "oerrors":
oerrorsarr[interfaceName+instance], err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, fmt.Errorf("cannot parse oerrors: %w", err)
}
}
}
ret := make([]IOCountersStat, 0)
for k := range rbytes64arr {
nic := IOCountersStat{
Name: k,
BytesRecv: rbytes64arr[k],
PacketsRecv: ipackets64arr[k],
Errin: ierrorsarr[k],
Dropin: idrops64arr[k],
BytesSent: obytes64arr[k],
PacketsSent: opackets64arr[k],
Errout: oerrorsarr[k],
Dropout: odrops64arr[k],
}
ret = append(ret, nic)
}
if !pernic {
return getIOCountersAll(ret)
}
return ret, nil
}
func Connections(kind string) ([]ConnectionStat, error) {
return ConnectionsWithContext(context.Background(), kind)
}
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return []ConnectionStat{}, common.ErrNotImplementedError
}
func FilterCounters() ([]FilterStat, error) {
return FilterCountersWithContext(context.Background())
}
func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return []FilterStat{}, common.ErrNotImplementedError
}
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
return ProtoCountersWithContext(context.Background(), protocols)
}
func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) {
return []ProtoCountersStat{}, common.ErrNotImplementedError
}

@ -3,7 +3,6 @@ package net
import (
"errors"
"fmt"
"math"
"os"
"runtime"
"testing"
@ -86,8 +85,14 @@ func TestNetIOCountersAll(t *testing.T) {
for _, p := range per {
pr += p.PacketsRecv
}
// small diff is ok
if math.Abs(float64(v[0].PacketsRecv-pr)) > 5 {
// small diff is ok, compare instead of math.Abs(subtraction) with uint64
var diff uint64
if v[0].PacketsRecv > pr {
diff = v[0].PacketsRecv - pr
} else {
diff = pr - v[0].PacketsRecv
}
if diff > 5 {
if ci := os.Getenv("CI"); ci != "" {
// This test often fails in CI. so just print even if failed.
fmt.Printf("invalid sum value: %v, %v", v[0].PacketsRecv, pr)

@ -20,7 +20,7 @@ func Connections(kind string) ([]ConnectionStat, error) {
}
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
return ConnectionsPid(kind, 0)
return ConnectionsPidWithContext(ctx, kind, 0)
}
// Return a list of network connections opened returning at most `max`

@ -211,7 +211,8 @@ func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename stri
// Return a list of network connections
// Available kind:
// reference to netConnectionKindMap
//
// reference to netConnectionKindMap
func Connections(kind string) ([]ConnectionStat, error) {
return ConnectionsWithContext(context.Background(), kind)
}
@ -337,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

@ -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)
}

@ -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
}
}
}

@ -175,6 +175,7 @@ func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
const tiSize = C.sizeof_struct_proc_taskinfo
ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize))
defer C.free(unsafe.Pointer(ti))
_, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize)
if err != nil {
@ -187,6 +188,7 @@ func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
const tiSize = C.sizeof_struct_proc_taskinfo
ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize))
defer C.free(unsafe.Pointer(ti))
_, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize)
if err != nil {
@ -204,6 +206,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error)
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
const tiSize = C.sizeof_struct_proc_taskinfo
ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize))
defer C.free(unsafe.Pointer(ti))
_, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize)
if err != nil {

@ -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) {

@ -16,11 +16,12 @@ import (
"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 +101,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 +121,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,7 +135,7 @@ 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")
statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "stat")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return false, err
@ -202,7 +203,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 +258,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 +284,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 +315,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 +323,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,12 +381,12 @@ 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
}
@ -481,7 +482,7 @@ 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)
if err != nil {
@ -507,9 +508,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 +603,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 +643,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 +654,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,7 +667,7 @@ 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")
cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline")
cmdline, err := ioutil.ReadFile(cmdPath)
if err != nil {
return "", err
@ -680,7 +681,7 @@ 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")
cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline")
cmdline, err := ioutil.ReadFile(cmdPath)
if err != nil {
return nil, err
@ -701,9 +702,9 @@ 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")
ioPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "io")
ioline, err := ioutil.ReadFile(ioPath)
if err != nil {
return nil, err
@ -737,9 +738,9 @@ 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")
memPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "statm")
contents, err := ioutil.ReadFile(memPath)
if err != nil {
return nil, nil, err
@ -790,7 +791,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,9 +799,9 @@ 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")
statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "comm")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return err
@ -817,7 +818,7 @@ func (p *Process) fillFromStatus() error {
func (p *Process) fillFromStatusWithContext(ctx context.Context) error {
pid := p.Pid
statPath := common.HostProc(strconv.Itoa(int(pid)), "status")
statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "status")
contents, err := ioutil.ReadFile(statPath)
if err != nil {
return err
@ -844,8 +845,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,9 +1021,9 @@ 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)
@ -1128,7 +1127,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) {

@ -4,6 +4,7 @@
package process
import (
"context"
"fmt"
"io/ioutil"
"os"
@ -11,7 +12,6 @@ import (
"strings"
"testing"
"github.com/shirou/gopsutil/v3/internal/common"
"github.com/stretchr/testify/assert"
)
@ -62,8 +62,7 @@ func Test_Process_splitProcStat_fromFile(t *testing.T) {
if err != nil {
t.Error(err)
}
f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f()
t.Setenv("HOST_PROC", "testdata/linux")
for _, pid := range pids {
pid, err := strconv.ParseInt(pid.Name(), 0, 32)
if err != nil {
@ -99,8 +98,7 @@ func Test_fillFromCommWithContext(t *testing.T) {
if err != nil {
t.Error(err)
}
f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f()
t.Setenv("HOST_PROC", "testdata/linux")
for _, pid := range pids {
pid, err := strconv.ParseInt(pid.Name(), 0, 32)
if err != nil {
@ -110,7 +108,7 @@ 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)
}
}
@ -121,8 +119,7 @@ func Test_fillFromStatusWithContext(t *testing.T) {
if err != nil {
t.Error(err)
}
f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f()
t.Setenv("HOST_PROC", "testdata/linux")
for _, pid := range pids {
pid, err := strconv.ParseInt(pid.Name(), 0, 32)
if err != nil {
@ -139,18 +136,16 @@ func Test_fillFromStatusWithContext(t *testing.T) {
}
func Benchmark_fillFromCommWithContext(b *testing.B) {
f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f()
b.Setenv("HOST_PROC", "testdata/linux")
pid := 1060
p, _ := NewProcess(int32(pid))
for i := 0; i < b.N; i++ {
p.fillFromCommWithContext()
p.fillFromCommWithContext(context.Background())
}
}
func Benchmark_fillFromStatusWithContext(b *testing.B) {
f := common.MockEnv("HOST_PROC", "testdata/linux")
defer f()
b.Setenv("HOST_PROC", "testdata/linux")
pid := 1060
p, _ := NewProcess(int32(pid))
for i := 0; i < b.N; i++ {
@ -163,8 +158,7 @@ func Test_fillFromTIDStatWithContext_lx_brandz(t *testing.T) {
if err != nil {
t.Error(err)
}
f := common.MockEnv("HOST_PROC", "testdata/lx_brandz")
defer f()
t.Setenv("HOST_PROC", "testdata/lx_brandz")
for _, pid := range pids {
pid, err := strconv.ParseInt(pid.Name(), 0, 32)
if err != nil {

@ -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]
}
}
}

@ -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 /<HOST_PROC>/proc exists and is mounted, check if /<HOST_PROC>/proc/<PID> folder exists
_, err := os.Stat(common.HostProc(strconv.Itoa(int(pid))))
if isMount(common.HostProcWithContext(ctx)) { // if /<HOST_PROC>/proc exists and is mounted, check if /<HOST_PROC>/proc/<PID> 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

@ -30,7 +30,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 +199,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 +211,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 +221,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,7 +231,7 @@ 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")
execNamePath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "execname")
exe, err := ioutil.ReadFile(execNamePath)
if err != nil {
return "", err
@ -241,7 +241,7 @@ 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")
cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline")
cmdline, err := ioutil.ReadFile(cmdPath)
if err != nil {
return "", err
@ -258,7 +258,7 @@ 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")
cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline")
cmdline, err := ioutil.ReadFile(cmdPath)
if err != nil {
return nil, err

@ -1,6 +1,7 @@
package process
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
@ -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
@ -294,7 +297,7 @@ func Test_Process_Name(t *testing.T) {
t.Errorf("getting name error %v", err)
}
if !strings.Contains(n, "process.test") {
t.Errorf("invalid Exe %s", n)
t.Errorf("invalid Name %s", n)
}
}
@ -390,6 +393,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 := ioutil.TempDir("", "")
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 +502,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")
}
@ -784,60 +843,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 +863,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)
}
}

@ -10,6 +10,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"syscall"
@ -319,18 +320,19 @@ func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
}
func (p *Process) NameWithContext(ctx context.Context) (string, error) {
ppid, _, name, err := getFromSnapProcess(p.Pid)
if err != nil {
return "", fmt.Errorf("could not get Name: %s", err)
if p.Pid == 0 {
return "System Idle Process", nil
}
if p.Pid == 4 {
return "System", nil
}
// if no errors and not cached already, cache ppid
p.parent = ppid
if 0 == p.getPpid() {
p.setPpid(ppid)
exe, err := p.ExeWithContext(ctx)
if err != nil {
return "", fmt.Errorf("could not get Name: %s", err)
}
return name, nil
return filepath.Base(exe), nil
}
func (p *Process) TgidWithContext(ctx context.Context) (int32, error) {
@ -987,15 +989,9 @@ func is32BitProcess(h windows.Handle) bool {
var procIs32Bits bool
switch processorArchitecture {
case PROCESSOR_ARCHITECTURE_INTEL:
fallthrough
case PROCESSOR_ARCHITECTURE_ARM:
case PROCESSOR_ARCHITECTURE_INTEL, PROCESSOR_ARCHITECTURE_ARM:
procIs32Bits = true
case PROCESSOR_ARCHITECTURE_ARM64:
fallthrough
case PROCESSOR_ARCHITECTURE_IA64:
fallthrough
case PROCESSOR_ARCHITECTURE_AMD64:
case PROCESSOR_ARCHITECTURE_ARM64, PROCESSOR_ARCHITECTURE_IA64, PROCESSOR_ARCHITECTURE_AMD64:
var wow64 uint
ret, _, _ := common.ProcNtQueryInformationProcess.Call(

@ -8,9 +8,8 @@ import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/windows"
)
type PROCESS_MEMORY_COUNTERS struct {

Loading…
Cancel
Save