Initial QSfera import
This commit is contained in:
+61
@@ -0,0 +1,61 @@
|
||||
gopsutil is distributed under BSD license reproduced below.
|
||||
|
||||
Copyright (c) 2014, WAKAYAMA Shirou
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of the gopsutil authors nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
-------
|
||||
internal/common/binary.go in the gopsutil is copied and modified from golang/encoding/binary.go.
|
||||
|
||||
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
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"
|
||||
HostProcMountinfo EnvKeyType = "HOST_PROC_MOUNTINFO"
|
||||
)
|
||||
|
||||
type EnvMap map[EnvKeyType]string
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// TimesStat contains the amounts of time the CPU has spent performing different
|
||||
// kinds of work. Time units are in seconds. It is based on linux /proc/stat file.
|
||||
type TimesStat struct {
|
||||
CPU string `json:"cpu"`
|
||||
User float64 `json:"user"`
|
||||
System float64 `json:"system"`
|
||||
Idle float64 `json:"idle"`
|
||||
Nice float64 `json:"nice"`
|
||||
Iowait float64 `json:"iowait"`
|
||||
Irq float64 `json:"irq"`
|
||||
Softirq float64 `json:"softirq"`
|
||||
Steal float64 `json:"steal"`
|
||||
Guest float64 `json:"guest"`
|
||||
GuestNice float64 `json:"guestNice"`
|
||||
}
|
||||
|
||||
type InfoStat struct {
|
||||
CPU int32 `json:"cpu"`
|
||||
VendorID string `json:"vendorId"`
|
||||
Family string `json:"family"`
|
||||
Model string `json:"model"`
|
||||
Stepping int32 `json:"stepping"`
|
||||
PhysicalID string `json:"physicalId"`
|
||||
CoreID string `json:"coreId"`
|
||||
Cores int32 `json:"cores"`
|
||||
ModelName string `json:"modelName"`
|
||||
Mhz float64 `json:"mhz"`
|
||||
CacheSize int32 `json:"cacheSize"`
|
||||
Flags []string `json:"flags"`
|
||||
Microcode string `json:"microcode"`
|
||||
}
|
||||
|
||||
type lastPercent struct {
|
||||
sync.Mutex
|
||||
lastCPUTimes []TimesStat
|
||||
lastPerCPUTimes []TimesStat
|
||||
}
|
||||
|
||||
var (
|
||||
lastCPUPercent lastPercent
|
||||
invoke common.Invoker = common.Invoke{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
lastCPUPercent.Lock()
|
||||
lastCPUPercent.lastCPUTimes, _ = Times(false)
|
||||
lastCPUPercent.lastPerCPUTimes, _ = Times(true)
|
||||
lastCPUPercent.Unlock()
|
||||
}
|
||||
|
||||
// Counts returns the number of physical or logical cores in the system
|
||||
func Counts(logical bool) (int, error) {
|
||||
return CountsWithContext(context.Background(), logical)
|
||||
}
|
||||
|
||||
func (c TimesStat) String() string {
|
||||
v := []string{
|
||||
`"cpu":"` + c.CPU + `"`,
|
||||
`"user":` + strconv.FormatFloat(c.User, 'f', 1, 64),
|
||||
`"system":` + strconv.FormatFloat(c.System, 'f', 1, 64),
|
||||
`"idle":` + strconv.FormatFloat(c.Idle, 'f', 1, 64),
|
||||
`"nice":` + strconv.FormatFloat(c.Nice, 'f', 1, 64),
|
||||
`"iowait":` + strconv.FormatFloat(c.Iowait, 'f', 1, 64),
|
||||
`"irq":` + strconv.FormatFloat(c.Irq, 'f', 1, 64),
|
||||
`"softirq":` + strconv.FormatFloat(c.Softirq, 'f', 1, 64),
|
||||
`"steal":` + strconv.FormatFloat(c.Steal, 'f', 1, 64),
|
||||
`"guest":` + strconv.FormatFloat(c.Guest, 'f', 1, 64),
|
||||
`"guestNice":` + strconv.FormatFloat(c.GuestNice, 'f', 1, 64),
|
||||
}
|
||||
|
||||
return `{` + strings.Join(v, ",") + `}`
|
||||
}
|
||||
|
||||
// Deprecated: Total returns the total number of seconds in a CPUTimesStat
|
||||
// Please do not use this internal function.
|
||||
func (c TimesStat) Total() float64 {
|
||||
total := c.User + c.System + c.Idle + c.Nice + c.Iowait + c.Irq +
|
||||
c.Softirq + c.Steal + c.Guest + c.GuestNice
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
func (c InfoStat) String() string {
|
||||
s, _ := json.Marshal(c)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func getAllBusy(t TimesStat) (float64, float64) {
|
||||
tot := t.Total()
|
||||
if runtime.GOOS == "linux" {
|
||||
tot -= t.Guest // Linux 2.6.24+
|
||||
tot -= t.GuestNice // Linux 3.2.0+
|
||||
}
|
||||
|
||||
busy := tot - t.Idle - t.Iowait
|
||||
|
||||
return tot, busy
|
||||
}
|
||||
|
||||
func calculateBusy(t1, t2 TimesStat) float64 {
|
||||
t1All, t1Busy := getAllBusy(t1)
|
||||
t2All, t2Busy := getAllBusy(t2)
|
||||
|
||||
if t2Busy <= t1Busy {
|
||||
return 0
|
||||
}
|
||||
if t2All <= t1All {
|
||||
return 100
|
||||
}
|
||||
return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
|
||||
}
|
||||
|
||||
func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) {
|
||||
// Make sure the CPU measurements have the same length.
|
||||
if len(t1) != len(t2) {
|
||||
return nil, fmt.Errorf(
|
||||
"received two CPU counts: %d != %d",
|
||||
len(t1), len(t2),
|
||||
)
|
||||
}
|
||||
|
||||
ret := make([]float64, len(t1))
|
||||
for i, t := range t2 {
|
||||
ret[i] = calculateBusy(t1[i], t)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Percent calculates the percentage of cpu used either per CPU or combined.
|
||||
// If an interval of 0 is given it will compare the current cpu times against the last call.
|
||||
// Returns one value per cpu, or a single value if percpu is set to false.
|
||||
func Percent(interval time.Duration, percpu bool) ([]float64, error) {
|
||||
return PercentWithContext(context.Background(), interval, percpu)
|
||||
}
|
||||
|
||||
func PercentWithContext(ctx context.Context, interval time.Duration, percpu bool) ([]float64, error) {
|
||||
if interval <= 0 {
|
||||
return percentUsedFromLastCallWithContext(ctx, percpu)
|
||||
}
|
||||
|
||||
// Get CPU usage at the start of the interval.
|
||||
cpuTimes1, err := TimesWithContext(ctx, percpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := common.Sleep(ctx, interval); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// And at the end of the interval.
|
||||
cpuTimes2, err := TimesWithContext(ctx, percpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return calculateAllBusy(cpuTimes1, cpuTimes2)
|
||||
}
|
||||
|
||||
func percentUsedFromLastCall(percpu bool) ([]float64, error) {
|
||||
return percentUsedFromLastCallWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func percentUsedFromLastCallWithContext(ctx context.Context, percpu bool) ([]float64, error) {
|
||||
cpuTimes, err := TimesWithContext(ctx, percpu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastCPUPercent.Lock()
|
||||
defer lastCPUPercent.Unlock()
|
||||
var lastTimes []TimesStat
|
||||
if percpu {
|
||||
lastTimes = lastCPUPercent.lastPerCPUTimes
|
||||
lastCPUPercent.lastPerCPUTimes = cpuTimes
|
||||
} else {
|
||||
lastTimes = lastCPUPercent.lastCPUTimes
|
||||
lastCPUPercent.lastCPUTimes = cpuTimes
|
||||
}
|
||||
|
||||
if lastTimes == nil {
|
||||
return nil, errors.New("error getting times for cpu percent. lastTimes was nil")
|
||||
}
|
||||
return calculateAllBusy(lastTimes, cpuTimes)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && cgo
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/power-devops/perfstat"
|
||||
)
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
if percpu {
|
||||
cpus, err := perfstat.CpuStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range cpus {
|
||||
ct := &TimesStat{
|
||||
CPU: c.Name,
|
||||
Idle: float64(c.Idle),
|
||||
User: float64(c.User),
|
||||
System: float64(c.Sys),
|
||||
Iowait: float64(c.Wait),
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
c, err := perfstat.CpuTotalStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct := &TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: float64(c.Idle),
|
||||
User: float64(c.User),
|
||||
System: float64(c.Sys),
|
||||
Iowait: float64(c.Wait),
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
c, err := perfstat.CpuTotalStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := perfstat.LparInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info := InfoStat{
|
||||
CPU: 0,
|
||||
ModelName: c.Description,
|
||||
Mhz: float64(c.ProcessorHz / 1000000),
|
||||
Cores: int32(p.OnlineVCpus),
|
||||
}
|
||||
result := []InfoStat{info}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
c, err := perfstat.CpuTotalStat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.NCpusCfg, nil
|
||||
}
|
||||
// For physical count, use the number of online virtual CPUs (before SMT multiplications).
|
||||
p, err := perfstat.LparInfo()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(p.OnlineVCpus), nil
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && !cgo
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
if percpu {
|
||||
perOut, err := invoke.CommandWithContext(ctx, "sar", "-u", "-P", "ALL", "10", "1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(perOut), "\n")
|
||||
if len(lines) < 6 {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
hp := strings.Fields(lines[5]) // headers
|
||||
for l := 6; l < len(lines)-1; l++ {
|
||||
ct := &TimesStat{}
|
||||
v := strings.Fields(lines[l]) // values
|
||||
for i, header := range hp {
|
||||
// We're done in any of these use cases
|
||||
if i >= len(v) || v[0] == "-" {
|
||||
break
|
||||
}
|
||||
|
||||
// Position variable for v
|
||||
pos := i
|
||||
// There is a missing field at the beginning of all but the first line
|
||||
// so adjust the position
|
||||
if l > 6 {
|
||||
pos = i - 1
|
||||
}
|
||||
// We don't want invalid positions
|
||||
if pos < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if t, err := strconv.ParseFloat(v[pos], 64); err == nil {
|
||||
switch header {
|
||||
case `cpu`:
|
||||
ct.CPU = strconv.FormatFloat(t, 'f', -1, 64)
|
||||
case `%usr`:
|
||||
ct.User = t
|
||||
case `%sys`:
|
||||
ct.System = t
|
||||
case `%wio`:
|
||||
ct.Iowait = t
|
||||
case `%idle`:
|
||||
ct.Idle = t
|
||||
}
|
||||
}
|
||||
}
|
||||
// Valid CPU data, so append it
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
out, err := invoke.CommandWithContext(ctx, "sar", "-u", "10", "1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
if len(lines) < 5 {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
ct := &TimesStat{CPU: "cpu-total"}
|
||||
h := strings.Fields(lines[len(lines)-3]) // headers
|
||||
v := strings.Fields(lines[len(lines)-2]) // values
|
||||
for i, header := range h {
|
||||
if t, err := strconv.ParseFloat(v[i], 64); err == nil {
|
||||
switch header {
|
||||
case `%usr`:
|
||||
ct.User = t
|
||||
case `%sys`:
|
||||
ct.System = t
|
||||
case `%wio`:
|
||||
ct.Iowait = t
|
||||
case `%idle`:
|
||||
ct.Idle = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "prtconf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := InfoStat{}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "Number Of Processors:"):
|
||||
p := strings.Fields(line)
|
||||
if len(p) > 3 {
|
||||
if t, err := strconv.ParseUint(p[3], 10, 64); err == nil {
|
||||
ret.Cores = int32(t)
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "Processor Clock Speed:"):
|
||||
p := strings.Fields(line)
|
||||
if len(p) > 4 {
|
||||
if t, err := strconv.ParseFloat(p[3], 64); err == nil {
|
||||
switch strings.ToUpper(p[4]) {
|
||||
case "MHZ":
|
||||
ret.Mhz = t
|
||||
case "GHZ":
|
||||
ret.Mhz = t * 1000.0
|
||||
case "KHZ":
|
||||
ret.Mhz = t / 1000.0
|
||||
default:
|
||||
ret.Mhz = t
|
||||
}
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "System Model:"):
|
||||
p := strings.Split(string(line), ":")
|
||||
if p != nil {
|
||||
ret.VendorID = strings.TrimSpace(p[1])
|
||||
}
|
||||
case strings.HasPrefix(line, "Processor Type:"):
|
||||
p := strings.Split(string(line), ":")
|
||||
if p != nil {
|
||||
c := strings.Split(string(p[1]), "_")
|
||||
if c != nil {
|
||||
ret.Family = strings.TrimSpace(c[0])
|
||||
ret.Model = strings.TrimSpace(c[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []InfoStat{ret}, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, _ bool) (int, error) {
|
||||
info, err := InfoWithContext(ctx)
|
||||
if err == nil {
|
||||
return int(info[0].Cores), nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// sys/resource.h
|
||||
const (
|
||||
CPUser = 0
|
||||
cpNice = 1
|
||||
cpSys = 2
|
||||
cpIntr = 3
|
||||
cpIdle = 4
|
||||
cpUStates = 5
|
||||
)
|
||||
|
||||
// mach/machine.h
|
||||
const (
|
||||
cpuStateUser = 0
|
||||
cpuStateSystem = 1
|
||||
cpuStateIdle = 2
|
||||
cpuStateNice = 3
|
||||
cpuStateMax = 4
|
||||
)
|
||||
|
||||
// mach/processor_info.h
|
||||
const (
|
||||
processorCpuLoadInfo = 2 //nolint:revive //FIXME
|
||||
)
|
||||
|
||||
type hostCpuLoadInfoData struct { //nolint:revive //FIXME
|
||||
cpuTicks [cpuStateMax]uint32
|
||||
}
|
||||
|
||||
// default value. from time.h
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
sys, err := common.NewSystemLib()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sys.Close()
|
||||
|
||||
if percpu {
|
||||
return perCPUTimes(sys)
|
||||
}
|
||||
|
||||
return allCPUTimes(sys)
|
||||
}
|
||||
|
||||
// Returns only one CPUInfoStat on FreeBSD
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
|
||||
c := InfoStat{}
|
||||
c.ModelName, _ = unix.Sysctl("machdep.cpu.brand_string")
|
||||
family, _ := unix.SysctlUint32("machdep.cpu.family")
|
||||
c.Family = strconv.FormatUint(uint64(family), 10)
|
||||
model, _ := unix.SysctlUint32("machdep.cpu.model")
|
||||
c.Model = strconv.FormatUint(uint64(model), 10)
|
||||
stepping, _ := unix.SysctlUint32("machdep.cpu.stepping")
|
||||
c.Stepping = int32(stepping)
|
||||
features, err := unix.Sysctl("machdep.cpu.features")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(features) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
leaf7Features, err := unix.Sysctl("machdep.cpu.leaf7_features")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(leaf7Features) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
extfeatures, err := unix.Sysctl("machdep.cpu.extfeatures")
|
||||
if err == nil {
|
||||
for _, v := range strings.Fields(extfeatures) {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
cores, _ := unix.SysctlUint32("machdep.cpu.core_count")
|
||||
c.Cores = int32(cores)
|
||||
cacheSize, _ := unix.SysctlUint32("machdep.cpu.cache.size")
|
||||
c.CacheSize = int32(cacheSize)
|
||||
c.VendorID, _ = unix.Sysctl("machdep.cpu.vendor")
|
||||
|
||||
v, err := getFrequency()
|
||||
if err == nil {
|
||||
c.Mhz = v
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, logical bool) (int, error) {
|
||||
var cpuArgument string
|
||||
if logical {
|
||||
cpuArgument = "hw.logicalcpu"
|
||||
} else {
|
||||
cpuArgument = "hw.physicalcpu"
|
||||
}
|
||||
|
||||
count, err := unix.SysctlUint32(cpuArgument)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
func perCPUTimes(sys *common.SystemLib) ([]TimesStat, error) {
|
||||
var count, ncpu uint32
|
||||
var cpuload *hostCpuLoadInfoData
|
||||
|
||||
status := sys.HostProcessorInfo(sys.MachHostSelf(), processorCpuLoadInfo,
|
||||
&ncpu, uintptr(unsafe.Pointer(&cpuload)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_processor_info error=%d", status)
|
||||
}
|
||||
|
||||
if cpuload == nil {
|
||||
return nil, errors.New("host_processor_info returned nil cpuload")
|
||||
}
|
||||
|
||||
defer sys.VMDeallocate(sys.MachTaskSelf(), uintptr(unsafe.Pointer(cpuload)), uintptr(ncpu))
|
||||
|
||||
ret := []TimesStat{}
|
||||
loads := unsafe.Slice(cpuload, ncpu)
|
||||
|
||||
for i := 0; i < int(ncpu); i++ {
|
||||
c := TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(loads[i].cpuTicks[cpuStateUser]) / ClocksPerSec,
|
||||
System: float64(loads[i].cpuTicks[cpuStateSystem]) / ClocksPerSec,
|
||||
Nice: float64(loads[i].cpuTicks[cpuStateNice]) / ClocksPerSec,
|
||||
Idle: float64(loads[i].cpuTicks[cpuStateIdle]) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, c)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func allCPUTimes(sys *common.SystemLib) ([]TimesStat, error) {
|
||||
var cpuload hostCpuLoadInfoData
|
||||
count := uint32(cpuStateMax)
|
||||
|
||||
status := sys.HostStatistics(sys.MachHostSelf(), common.HOST_CPU_LOAD_INFO,
|
||||
uintptr(unsafe.Pointer(&cpuload)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
c := TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(cpuload.cpuTicks[cpuStateUser]) / ClocksPerSec,
|
||||
System: float64(cpuload.cpuTicks[cpuStateSystem]) / ClocksPerSec,
|
||||
Nice: float64(cpuload.cpuTicks[cpuStateNice]) / ClocksPerSec,
|
||||
Idle: float64(cpuload.cpuTicks[cpuStateIdle]) / ClocksPerSec,
|
||||
}
|
||||
|
||||
return []TimesStat{c}, nil
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && arm64
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// Keep IOKit and CoreFoundation libraries open for the process lifetime.
|
||||
// See: https://github.com/shirou/gopsutil/issues/1832
|
||||
var (
|
||||
cpuLibOnce sync.Once
|
||||
cpuIOKit *common.IOKitLib
|
||||
cpuCF *common.CoreFoundationLib
|
||||
cpuLibErr error
|
||||
)
|
||||
|
||||
func initCPULibraries() {
|
||||
cpuIOKit, cpuLibErr = common.NewIOKitLib()
|
||||
if cpuLibErr != nil {
|
||||
return
|
||||
}
|
||||
cpuCF, cpuLibErr = common.NewCoreFoundationLib()
|
||||
}
|
||||
|
||||
// https://github.com/shoenig/go-m1cpu/blob/v0.1.6/cpu.go
|
||||
func getFrequency() (float64, error) {
|
||||
cpuLibOnce.Do(initCPULibraries)
|
||||
if cpuLibErr != nil {
|
||||
return 0, cpuLibErr
|
||||
}
|
||||
|
||||
iokit := cpuIOKit
|
||||
corefoundation := cpuCF
|
||||
|
||||
matching := iokit.IOServiceMatching("AppleARMIODevice")
|
||||
|
||||
var iterator uint32
|
||||
if status := iokit.IOServiceGetMatchingServices(common.KIOMainPortDefault, uintptr(matching), &iterator); status != common.KERN_SUCCESS {
|
||||
return 0.0, fmt.Errorf("IOServiceGetMatchingServices error=%d", status)
|
||||
}
|
||||
defer iokit.IOObjectRelease(iterator)
|
||||
|
||||
pCorekey := corefoundation.CFStringCreateWithCString(common.KCFAllocatorDefault, "voltage-states5-sram", common.KCFStringEncodingUTF8)
|
||||
defer corefoundation.CFRelease(uintptr(pCorekey))
|
||||
|
||||
var pCoreHz uint32
|
||||
for {
|
||||
service := iokit.IOIteratorNext(iterator)
|
||||
if service <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
buf := common.NewCStr(512)
|
||||
iokit.IORegistryEntryGetName(service, buf)
|
||||
|
||||
if buf.GoString() == "pmgr" {
|
||||
pCoreRef := iokit.IORegistryEntryCreateCFProperty(service, uintptr(pCorekey), common.KCFAllocatorDefault, common.KNilOptions)
|
||||
length := corefoundation.CFDataGetLength(uintptr(pCoreRef))
|
||||
data := corefoundation.CFDataGetBytePtr(uintptr(pCoreRef))
|
||||
|
||||
// composite uint32 from the byte array
|
||||
buf := unsafe.Slice((*byte)(data), length)
|
||||
|
||||
// combine the bytes into a uint32 value
|
||||
b := buf[length-8 : length-4]
|
||||
pCoreHz = binary.LittleEndian.Uint32(b)
|
||||
corefoundation.CFRelease(uintptr(pCoreRef))
|
||||
iokit.IOObjectRelease(service)
|
||||
break
|
||||
}
|
||||
|
||||
iokit.IOObjectRelease(service)
|
||||
}
|
||||
|
||||
return float64(pCoreHz / 1_000_000), nil
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && !arm64
|
||||
|
||||
package cpu
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func getFrequency() (float64, error) {
|
||||
// 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")
|
||||
return float64(cpuFrequency) / 1000000.0, err
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
ClocksPerSec = float64(128)
|
||||
cpuMatch = regexp.MustCompile(`^CPU:`)
|
||||
originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`)
|
||||
featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`)
|
||||
featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`)
|
||||
cpuEnd = regexp.MustCompile(`^Trying to mount root`)
|
||||
cpuTimesSize int
|
||||
emptyTimes cpuTimes
|
||||
)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func timeStat(name string, t *cpuTimes) *TimesStat {
|
||||
return &TimesStat{
|
||||
User: float64(t.User) / ClocksPerSec,
|
||||
Nice: float64(t.Nice) / ClocksPerSec,
|
||||
System: float64(t.Sys) / ClocksPerSec,
|
||||
Idle: float64(t.Idle) / ClocksPerSec,
|
||||
Irq: float64(t.Intr) / ClocksPerSec,
|
||||
CPU: name,
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
if percpu {
|
||||
buf, err := unix.SysctlRaw("kern.cp_times")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We can't do this in init due to the conflict with cpu.init()
|
||||
if cpuTimesSize == 0 {
|
||||
cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size())
|
||||
}
|
||||
|
||||
ncpus := len(buf) / cpuTimesSize
|
||||
ret := make([]TimesStat, 0, ncpus)
|
||||
for i := 0; i < ncpus; i++ {
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[i*cpuTimesSize]))
|
||||
if *times == emptyTimes {
|
||||
// CPU not present
|
||||
continue
|
||||
}
|
||||
ret = append(ret, *timeStat(fmt.Sprintf("cpu%d", len(ret)), times))
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
buf, err := unix.SysctlRaw("kern.cp_time")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
return []TimesStat{*timeStat("cpu-total", times)}, nil
|
||||
}
|
||||
|
||||
// Returns only one InfoStat on DragonflyBSD. The information regarding core
|
||||
// count, however is accurate and it is assumed that all InfoStat attributes
|
||||
// are the same across CPUs.
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
const dmesgBoot = "/var/run/dmesg.boot"
|
||||
|
||||
c, err := parseDmesgBoot(dmesgBoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var u32 uint32
|
||||
if u32, err = unix.SysctlUint32("hw.clockrate"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Mhz = float64(u32)
|
||||
|
||||
var num int
|
||||
var buf string
|
||||
if buf, err = unix.Sysctl("hw.cpu_topology.tree"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
num = strings.Count(buf, "CHIP")
|
||||
c.Cores = int32(strings.Count(string(buf), "CORE") / num)
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]InfoStat, num)
|
||||
for i := 0; i < num; i++ {
|
||||
ret[i] = c
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseDmesgBoot(fileName string) (InfoStat, error) {
|
||||
c := InfoStat{}
|
||||
lines, err := common.ReadLines(fileName)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("could not read %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
if matches := cpuEnd.FindStringSubmatch(line); matches != nil {
|
||||
break
|
||||
} else if matches := originMatch.FindStringSubmatch(line); matches != nil {
|
||||
c.VendorID = matches[1]
|
||||
t, err := strconv.ParseInt(matches[2], 10, 32)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("unable to parse DragonflyBSD CPU stepping information from %q: %w", line, err)
|
||||
}
|
||||
c.Stepping = int32(t)
|
||||
} else if matches := featuresMatch.FindStringSubmatch(line); matches != nil {
|
||||
for _, v := range strings.Split(matches[1], ",") {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
} else if matches := featuresMatch2.FindStringSubmatch(line); matches != nil {
|
||||
for _, v := range strings.Split(matches[1], ",") {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows && !dragonfly && !plan9 && !aix
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
return []TimesStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
return []InfoStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
ClocksPerSec = float64(128)
|
||||
cpuMatch = regexp.MustCompile(`^CPU:`)
|
||||
originMatch = regexp.MustCompile(`Origin\s*=\s*"(.+)"\s+Id\s*=\s*(.+)\s+Family\s*=\s*(.+)\s+Model\s*=\s*(.+)\s+Stepping\s*=\s*(.+)`)
|
||||
featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`)
|
||||
featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`)
|
||||
cpuEnd = regexp.MustCompile(`^Trying to mount root`)
|
||||
cpuCores = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`)
|
||||
cpuTimesSize int
|
||||
emptyTimes cpuTimes
|
||||
)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func timeStat(name string, t *cpuTimes) *TimesStat {
|
||||
return &TimesStat{
|
||||
User: float64(t.User) / ClocksPerSec,
|
||||
Nice: float64(t.Nice) / ClocksPerSec,
|
||||
System: float64(t.Sys) / ClocksPerSec,
|
||||
Idle: float64(t.Idle) / ClocksPerSec,
|
||||
Irq: float64(t.Intr) / ClocksPerSec,
|
||||
CPU: name,
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
if percpu {
|
||||
buf, err := unix.SysctlRaw("kern.cp_times")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We can't do this in init due to the conflict with cpu.init()
|
||||
if cpuTimesSize == 0 {
|
||||
cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size())
|
||||
}
|
||||
|
||||
ncpus := len(buf) / cpuTimesSize
|
||||
ret := make([]TimesStat, 0, ncpus)
|
||||
for i := 0; i < ncpus; i++ {
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[i*cpuTimesSize]))
|
||||
if *times == emptyTimes {
|
||||
// CPU not present
|
||||
continue
|
||||
}
|
||||
ret = append(ret, *timeStat(fmt.Sprintf("cpu%d", len(ret)), times))
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
buf, err := unix.SysctlRaw("kern.cp_time")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
return []TimesStat{*timeStat("cpu-total", times)}, nil
|
||||
}
|
||||
|
||||
// Returns only one InfoStat on FreeBSD. The information regarding core
|
||||
// count, however is accurate and it is assumed that all InfoStat attributes
|
||||
// are the same across CPUs.
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
const dmesgBoot = "/var/run/dmesg.boot"
|
||||
|
||||
c, num, err := parseDmesgBoot(dmesgBoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var u32 uint32
|
||||
if u32, err = unix.SysctlUint32("hw.clockrate"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Mhz = float64(u32)
|
||||
|
||||
if u32, err = unix.SysctlUint32("hw.ncpu"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cores = int32(u32)
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]InfoStat, num)
|
||||
for i := 0; i < num; i++ {
|
||||
ret[i] = c
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseDmesgBoot(fileName string) (InfoStat, int, error) {
|
||||
c := InfoStat{}
|
||||
lines, err := common.ReadLines(fileName)
|
||||
if err != nil {
|
||||
return c, 0, fmt.Errorf("could not read %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
cpuNum := 1 // default cpu num is 1
|
||||
for _, line := range lines {
|
||||
if matches := cpuEnd.FindStringSubmatch(line); matches != nil {
|
||||
break
|
||||
} else if matches := originMatch.FindStringSubmatch(line); matches != nil {
|
||||
c.VendorID = matches[1]
|
||||
c.Family = matches[3]
|
||||
c.Model = matches[4]
|
||||
t, err := strconv.ParseInt(matches[5], 10, 32)
|
||||
if err != nil {
|
||||
return c, 0, fmt.Errorf("unable to parse FreeBSD CPU stepping information from %q: %w", line, err)
|
||||
}
|
||||
c.Stepping = int32(t)
|
||||
} else if matches := featuresMatch.FindStringSubmatch(line); matches != nil {
|
||||
for _, v := range strings.Split(matches[1], ",") {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
} else if matches := featuresMatch2.FindStringSubmatch(line); matches != nil {
|
||||
for _, v := range strings.Split(matches[1], ",") {
|
||||
c.Flags = append(c.Flags, strings.ToLower(v))
|
||||
}
|
||||
} else if matches := cpuCores.FindStringSubmatch(line); matches != nil {
|
||||
t, err := strconv.ParseInt(matches[1], 10, 32)
|
||||
if err != nil {
|
||||
return c, 0, fmt.Errorf("unable to parse FreeBSD CPU Nums from %q: %w", line, err)
|
||||
}
|
||||
cpuNum = int(t)
|
||||
t2, err := strconv.ParseInt(matches[2], 10, 32)
|
||||
if err != nil {
|
||||
return c, 0, fmt.Errorf("unable to parse FreeBSD CPU cores from %q: %w", line, err)
|
||||
}
|
||||
c.Cores = int32(t2)
|
||||
}
|
||||
}
|
||||
|
||||
return c, cpuNum, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/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",
|
||||
0xd4f: "Neoverse-V2",
|
||||
0xd81: "Cortex-A720",
|
||||
0xd82: "Cortex-X4",
|
||||
0xd84: "Neoverse-V3",
|
||||
0xd85: "Cortex-X925",
|
||||
0xd87: "Cortex-A725",
|
||||
0xd8e: "Neoverse-N3",
|
||||
}
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
filename := common.HostProcWithContext(ctx, "stat")
|
||||
lines := []string{}
|
||||
var err error
|
||||
if percpu {
|
||||
statlines, err := common.ReadLines(filename)
|
||||
if err != nil || len(statlines) < 2 {
|
||||
return []TimesStat{}, nil
|
||||
}
|
||||
for _, line := range statlines[1:] {
|
||||
if !strings.HasPrefix(line, "cpu") {
|
||||
break
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
} else {
|
||||
lines, err = common.ReadLinesOffsetN(filename, 0, 1)
|
||||
if err != nil || len(lines) == 0 {
|
||||
return []TimesStat{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
ret := make([]TimesStat, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
ct, err := parseStatLine(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
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(ctx context.Context, c *InfoStat) {
|
||||
var lines []string
|
||||
var err error
|
||||
var value float64
|
||||
|
||||
if c.CoreID == "" {
|
||||
lines, err = common.ReadLines(sysCPUPath(ctx, c.CPU, "topology/core_id"))
|
||||
if err == nil {
|
||||
c.CoreID = lines[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 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(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 {
|
||||
return
|
||||
}
|
||||
value, err = strconv.ParseFloat(lines[0], 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Mhz = value / 1000.0 // value is in kHz
|
||||
if c.Mhz > 9999 {
|
||||
c.Mhz /= 1000.0 // value in Hz
|
||||
}
|
||||
}
|
||||
|
||||
// CPUInfo on linux will return 1 item per physical thread.
|
||||
//
|
||||
// CPUs have three levels of counting: sockets, cores, threads.
|
||||
// Cores with HyperThreading count as having 2 threads per core.
|
||||
// Sockets often come with many physical CPU cores.
|
||||
// For example a single socket board with two cores each with HT will
|
||||
// return 4 CPUInfoStat structs on Linux and the "Cores" field set to 1.
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
filename := common.HostProcWithContext(ctx, "cpuinfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read %s: %w", filename, err)
|
||||
}
|
||||
|
||||
var ret []InfoStat
|
||||
var processorName string
|
||||
|
||||
c := InfoStat{CPU: -1, Cores: 1}
|
||||
for _, line := range lines {
|
||||
fields := strings.SplitN(line, ":", 2)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(fields[0])
|
||||
value := strings.TrimSpace(fields[1])
|
||||
|
||||
switch key {
|
||||
case "Processor":
|
||||
processorName = value
|
||||
case "processor", "cpu number":
|
||||
if c.CPU >= 0 {
|
||||
finishCPUInfo(ctx, &c)
|
||||
ret = append(ret, c)
|
||||
}
|
||||
c = InfoStat{Cores: 1, ModelName: processorName}
|
||||
t, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.CPU = int32(t)
|
||||
case "vendorId", "vendor_id":
|
||||
c.VendorID = value
|
||||
if strings.Contains(value, "S390") {
|
||||
processorName = "S390"
|
||||
}
|
||||
case "mvendorid":
|
||||
if !strings.HasPrefix(value, "0x") {
|
||||
continue
|
||||
}
|
||||
|
||||
if v, err := strconv.ParseUint(value[2:], 16, 32); err == nil {
|
||||
switch v {
|
||||
case 0x31e:
|
||||
c.VendorID = "Andes"
|
||||
case 0x029:
|
||||
c.VendorID = "Microchip"
|
||||
case 0x127:
|
||||
c.VendorID = "MIPS"
|
||||
case 0x489:
|
||||
c.VendorID = "SiFive"
|
||||
case 0x5b7:
|
||||
c.VendorID = "T-Head"
|
||||
}
|
||||
}
|
||||
case "CPU implementer":
|
||||
if v, err := strconv.ParseUint(value, 0, 8); err == nil {
|
||||
switch v {
|
||||
case 0x41:
|
||||
c.VendorID = "ARM"
|
||||
case 0x42:
|
||||
c.VendorID = "Broadcom"
|
||||
case 0x43:
|
||||
c.VendorID = "Cavium"
|
||||
case 0x44:
|
||||
c.VendorID = "DEC"
|
||||
case 0x46:
|
||||
c.VendorID = "Fujitsu"
|
||||
case 0x48:
|
||||
c.VendorID = "HiSilicon"
|
||||
case 0x49:
|
||||
c.VendorID = "Infineon"
|
||||
case 0x4d:
|
||||
c.VendorID = "Motorola/Freescale"
|
||||
case 0x4e:
|
||||
c.VendorID = "NVIDIA"
|
||||
case 0x50:
|
||||
c.VendorID = "APM"
|
||||
case 0x51:
|
||||
c.VendorID = "Qualcomm"
|
||||
case 0x56:
|
||||
c.VendorID = "Marvell"
|
||||
case 0x61:
|
||||
c.VendorID = "Apple"
|
||||
case 0x69:
|
||||
c.VendorID = "Intel"
|
||||
case 0xc0:
|
||||
c.VendorID = "Ampere"
|
||||
}
|
||||
}
|
||||
case "cpu family", "marchid":
|
||||
c.Family = value
|
||||
case "model", "CPU part", "mimpid":
|
||||
c.Model = value
|
||||
// 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", "uarch":
|
||||
c.ModelName = value
|
||||
if strings.Contains(value, "POWER") {
|
||||
c.Model = strings.Split(value, " ")[0]
|
||||
c.Family = "POWER"
|
||||
c.VendorID = "IBM"
|
||||
}
|
||||
case "stepping", "revision", "CPU revision":
|
||||
val := value
|
||||
|
||||
if key == "revision" {
|
||||
val = strings.Split(value, ".")[0]
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "unknown") {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.Stepping = int32(t)
|
||||
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
|
||||
}
|
||||
case "cache size":
|
||||
t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
c.CacheSize = int32(t)
|
||||
case "physical id", "hart":
|
||||
c.PhysicalID = value
|
||||
case "core id":
|
||||
c.CoreID = value
|
||||
case "flags", "Features":
|
||||
c.Flags = strings.FieldsFunc(value, func(r rune) bool {
|
||||
return r == ',' || r == ' '
|
||||
})
|
||||
case "isa", "hart isa":
|
||||
if len(c.Flags) != 0 || !strings.HasPrefix(value, "rv64") {
|
||||
continue
|
||||
}
|
||||
c.Flags = riscvISAParse(value)
|
||||
case "microcode":
|
||||
c.Microcode = value
|
||||
}
|
||||
}
|
||||
if c.CPU >= 0 {
|
||||
finishCPUInfo(ctx, &c)
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseStatLine(line string) (*TimesStat, error) {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
if len(fields) < 8 {
|
||||
return nil, errors.New("stat does not contain cpu info")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fields[0], "cpu") {
|
||||
return nil, errors.New("not contain cpu")
|
||||
}
|
||||
|
||||
cpu := fields[0]
|
||||
if cpu == "cpu" {
|
||||
cpu = "cpu-total"
|
||||
}
|
||||
user, err := strconv.ParseFloat(fields[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nice, err := strconv.ParseFloat(fields[2], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
system, err := strconv.ParseFloat(fields[3], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idle, err := strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iowait, err := strconv.ParseFloat(fields[5], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
irq, err := strconv.ParseFloat(fields[6], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
softirq, err := strconv.ParseFloat(fields[7], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ct := &TimesStat{
|
||||
CPU: cpu,
|
||||
User: user / ClocksPerSec,
|
||||
Nice: nice / ClocksPerSec,
|
||||
System: system / ClocksPerSec,
|
||||
Idle: idle / ClocksPerSec,
|
||||
Iowait: iowait / ClocksPerSec,
|
||||
Irq: irq / ClocksPerSec,
|
||||
Softirq: softirq / ClocksPerSec,
|
||||
}
|
||||
if len(fields) > 8 { // Linux >= 2.6.11
|
||||
steal, err := strconv.ParseFloat(fields[8], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.Steal = steal / ClocksPerSec
|
||||
}
|
||||
if len(fields) > 9 { // Linux >= 2.6.24
|
||||
guest, err := strconv.ParseFloat(fields[9], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.Guest = guest / ClocksPerSec
|
||||
}
|
||||
if len(fields) > 10 { // Linux >= 3.2.0
|
||||
guestNice, err := strconv.ParseFloat(fields[10], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.GuestNice = guestNice / ClocksPerSec
|
||||
}
|
||||
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
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.HostProcWithContext(ctx, "cpuinfo")
|
||||
lines, err := common.ReadLines(procCpuinfo)
|
||||
if err == nil {
|
||||
for _, line := range lines {
|
||||
line = strings.ToLower(line)
|
||||
if strings.HasPrefix(line, "processor") {
|
||||
_, err = strconv.ParseInt(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:]), 10, 32)
|
||||
if err == nil {
|
||||
ret++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ret == 0 {
|
||||
procStat := common.HostProcWithContext(ctx, "stat")
|
||||
lines, err = common.ReadLines(procStat)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, line := range lines {
|
||||
if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
|
||||
ret++
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
// physical cores
|
||||
// https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628
|
||||
threadSiblingsLists := make(map[string]bool)
|
||||
// These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future.
|
||||
// https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
|
||||
// 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.HostSysWithContext(ctx, glob)); err == nil {
|
||||
for _, file := range files {
|
||||
lines, err := common.ReadLines(file)
|
||||
if err != nil || len(lines) != 1 {
|
||||
continue
|
||||
}
|
||||
threadSiblingsLists[lines[0]] = true
|
||||
}
|
||||
ret := len(threadSiblingsLists)
|
||||
if ret != 0 {
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652
|
||||
filename := common.HostProcWithContext(ctx, "cpuinfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
mapping := make(map[int]int)
|
||||
currentInfo := make(map[string]int)
|
||||
for _, line := range lines {
|
||||
line = strings.ToLower(strings.TrimSpace(line))
|
||||
if line == "" {
|
||||
// new section
|
||||
id, okID := currentInfo["physical id"]
|
||||
cores, okCores := currentInfo["cpu cores"]
|
||||
if okID && okCores {
|
||||
mapping[id] = cores
|
||||
}
|
||||
currentInfo = make(map[string]int)
|
||||
continue
|
||||
}
|
||||
fields := strings.SplitN(line, ":", 2)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
fields[0] = strings.TrimSpace(fields[0])
|
||||
if fields[0] == "physical id" || fields[0] == "cpu cores" {
|
||||
val, err := strconv.ParseInt(strings.TrimSpace(fields[1]), 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
currentInfo[fields[0]] = int(val)
|
||||
}
|
||||
}
|
||||
ret := 0
|
||||
for _, v := range mapping {
|
||||
ret += v
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func riscvISAParse(s string) []string {
|
||||
ext := strings.Split(s, "_")
|
||||
if len(ext[0]) <= 4 {
|
||||
return nil
|
||||
}
|
||||
// the base extensions must "rv64" prefix
|
||||
base := strings.Split(ext[0][4:], "")
|
||||
return append(base, ext[1:]...)
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build netbsd
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// sys/sysctl.h
|
||||
ctlKern = 1 // "high kernel": proc, limits
|
||||
ctlHw = 6 // CTL_HW
|
||||
kernCpTime = 51 // KERN_CPTIME
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(100)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
ret := make([]TimesStat, 0)
|
||||
if !percpu {
|
||||
mib := []int32{ctlKern, kernCpTime}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(times.User),
|
||||
Nice: float64(times.Nice),
|
||||
System: float64(times.Sys),
|
||||
Idle: float64(times.Idle),
|
||||
Irq: float64(times.Intr),
|
||||
})
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpu")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var i uint32
|
||||
for i = 0; i < ncpu; i++ {
|
||||
mib := []int32{ctlKern, kernCpTime, int32(i)}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
stats := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(stats.User),
|
||||
Nice: float64(stats.Nice),
|
||||
System: float64(stats.Sys),
|
||||
Idle: float64(stats.Idle),
|
||||
Irq: float64(stats.Intr),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Returns only one (minimal) CPUInfoStat on NetBSD
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
var err error
|
||||
|
||||
c := InfoStat{}
|
||||
|
||||
mhz, err := unix.Sysctl("machdep.dmi.processor-frequency")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = fmt.Sscanf(mhz, "%f", &c.Mhz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpuonline")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cores = int32(ncpu)
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("machdep.dmi.processor-version"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// sys/sched.h
|
||||
cpuOnline = 0x0001 // CPUSTATS_ONLINE
|
||||
|
||||
// sys/sysctl.h
|
||||
ctlKern = 1 // "high kernel": proc, limits
|
||||
ctlHw = 6 // CTL_HW
|
||||
smt = 24 // HW_SMT
|
||||
kernCpTime = 40 // KERN_CPTIME
|
||||
kernCPUStats = 85 // KERN_CPUSTATS
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
type cpuStats struct {
|
||||
// cs_time[CPUSTATES]
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
|
||||
// cs_flags
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
ret := make([]TimesStat, 0)
|
||||
if !percpu {
|
||||
mib := []int32{ctlKern, kernCpTime}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
times := (*cpuTimes)(unsafe.Pointer(&buf[0]))
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: "cpu-total",
|
||||
User: float64(times.User) / ClocksPerSec,
|
||||
Nice: float64(times.Nice) / ClocksPerSec,
|
||||
System: float64(times.Sys) / ClocksPerSec,
|
||||
Idle: float64(times.Idle) / ClocksPerSec,
|
||||
Irq: float64(times.Intr) / ClocksPerSec,
|
||||
})
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpu")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var i uint32
|
||||
for i = 0; i < ncpu; i++ {
|
||||
mib := []int32{ctlKern, kernCPUStats, int32(i)}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
stats := (*cpuStats)(unsafe.Pointer(&buf[0]))
|
||||
if (stats.Flags & cpuOnline) == 0 {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", i),
|
||||
User: float64(stats.User) / ClocksPerSec,
|
||||
Nice: float64(stats.Nice) / ClocksPerSec,
|
||||
System: float64(stats.Sys) / ClocksPerSec,
|
||||
Idle: float64(stats.Idle) / ClocksPerSec,
|
||||
Irq: float64(stats.Intr) / ClocksPerSec,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Returns only one (minimal) CPUInfoStat on OpenBSD
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
var err error
|
||||
|
||||
c := InfoStat{}
|
||||
|
||||
mhz, err := unix.SysctlUint32("hw.cpuspeed")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Mhz = float64(mhz)
|
||||
|
||||
ncpu, err := unix.SysctlUint32("hw.ncpuonline")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cores = int32(ncpu)
|
||||
|
||||
if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(ret, c), nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Spin uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint32
|
||||
Nice uint32
|
||||
Sys uint32
|
||||
Spin uint32
|
||||
Intr uint32
|
||||
Idle uint32
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
type cpuTimes struct {
|
||||
User uint64
|
||||
Nice uint64
|
||||
Sys uint64
|
||||
Spin uint64
|
||||
Intr uint64
|
||||
Idle uint64
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
stats "github.com/lufia/plan9stats"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(ctx context.Context, _ bool) ([]TimesStat, error) {
|
||||
// BUG: percpu flag is not supported yet.
|
||||
root := os.Getenv("HOST_ROOT")
|
||||
c, err := stats.ReadCPUType(ctx, stats.WithRootDir(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := stats.ReadCPUStats(ctx, stats.WithRootDir(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []TimesStat{
|
||||
{
|
||||
CPU: c.Name,
|
||||
User: s.User.Seconds(),
|
||||
System: s.Sys.Seconds(),
|
||||
Idle: s.Idle.Seconds(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(_ context.Context) ([]InfoStat, error) {
|
||||
return []InfoStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
)
|
||||
|
||||
var ClocksPerSec = float64(128)
|
||||
|
||||
func init() {
|
||||
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
||||
// ignore errors
|
||||
if err == nil {
|
||||
ClocksPerSec = float64(clkTck)
|
||||
}
|
||||
}
|
||||
|
||||
// sum all values in a float64 map with float64 keys
|
||||
func msum(x map[float64]float64) float64 {
|
||||
total := 0.0
|
||||
for _, y := range x {
|
||||
total += y
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
var kstatSplit = regexp.MustCompile(`[:\s]+`)
|
||||
|
||||
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
||||
kstatSysOut, err := invoke.CommandWithContext(ctx, "kstat", "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute kstat: %w", err)
|
||||
}
|
||||
cpu := make(map[float64]float64)
|
||||
idle := make(map[float64]float64)
|
||||
user := make(map[float64]float64)
|
||||
kern := make(map[float64]float64)
|
||||
iowt := make(map[float64]float64)
|
||||
// swap := make(map[float64]float64)
|
||||
for _, line := range strings.Split(string(kstatSysOut), "\n") {
|
||||
fields := kstatSplit.Split(line, -1)
|
||||
if fields[0] != "cpu_stat" {
|
||||
continue
|
||||
}
|
||||
cpuNumber, err := strconv.ParseFloat(fields[1], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse cpu number: %w", err)
|
||||
}
|
||||
cpu[cpuNumber] = cpuNumber
|
||||
switch fields[3] {
|
||||
case "idle":
|
||||
idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse idle: %w", err)
|
||||
}
|
||||
case "user":
|
||||
user[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse user: %w", err)
|
||||
}
|
||||
case "kernel":
|
||||
kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse kernel: %w", err)
|
||||
}
|
||||
case "iowait":
|
||||
iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse iowait: %w", err)
|
||||
}
|
||||
// not sure how this translates, don't report, add to kernel, something else?
|
||||
/*case "swap":
|
||||
swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse swap: %s", err)
|
||||
} */
|
||||
}
|
||||
}
|
||||
ret := make([]TimesStat, 0, len(cpu))
|
||||
if percpu {
|
||||
for _, c := range cpu {
|
||||
ct := &TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", int(cpu[c])),
|
||||
Idle: idle[c] / ClocksPerSec,
|
||||
User: user[c] / ClocksPerSec,
|
||||
System: kern[c] / ClocksPerSec,
|
||||
Iowait: iowt[c] / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
} else {
|
||||
ct := &TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: msum(idle) / ClocksPerSec,
|
||||
User: msum(user) / ClocksPerSec,
|
||||
System: msum(kern) / ClocksPerSec,
|
||||
Iowait: msum(iowt) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, *ct)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
psrInfoOut, err := invoke.CommandWithContext(ctx, "psrinfo", "-p", "-v")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute psrinfo: %w", err)
|
||||
}
|
||||
|
||||
procs, err := parseProcessorInfo(string(psrInfoOut))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing psrinfo output: %w", err)
|
||||
}
|
||||
|
||||
isaInfoOut, err := invoke.CommandWithContext(ctx, "isainfo", "-b", "-v")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot execute isainfo: %w", err)
|
||||
}
|
||||
|
||||
flags, err := parseISAInfo(string(isaInfoOut))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing isainfo output: %w", err)
|
||||
}
|
||||
|
||||
result := make([]InfoStat, 0, len(flags))
|
||||
for i := range procs {
|
||||
procWithFlags := procs[i]
|
||||
procWithFlags.Flags = flags
|
||||
result = append(result, procWithFlags)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var flagsMatch = regexp.MustCompile(`[\w.]+`)
|
||||
|
||||
func parseISAInfo(cmdOutput string) ([]string, error) {
|
||||
words := flagsMatch.FindAllString(cmdOutput, -1)
|
||||
|
||||
// Sanity check the output
|
||||
if len(words) < 4 || words[1] != "bit" || words[3] != "applications" {
|
||||
return nil, errors.New("attempted to parse invalid isainfo output")
|
||||
}
|
||||
|
||||
flags := words[4:]
|
||||
sort.Strings(flags)
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
var psrInfoMatch = regexp.MustCompile(`The physical processor has (?:([\d]+) virtual processors? \(([\d-]+)\)|([\d]+) cores and ([\d]+) virtual processors[^\n]+)\n(?:\s+ The core has.+\n)*\s+.+ \((\w+) ([\S]+) family (.+) model (.+) step (.+) clock (.+) MHz\)\n[\s]*(.*)`)
|
||||
|
||||
const (
|
||||
psrNumCoresOffset = 1
|
||||
psrNumCoresHTOffset = 3
|
||||
psrNumHTOffset = 4
|
||||
psrVendorIDOffset = 5
|
||||
psrFamilyOffset = 7
|
||||
psrModelOffset = 8
|
||||
psrStepOffset = 9
|
||||
psrClockOffset = 10
|
||||
psrModelNameOffset = 11
|
||||
)
|
||||
|
||||
func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) {
|
||||
matches := psrInfoMatch.FindAllStringSubmatch(cmdOutput, -1)
|
||||
|
||||
var infoStatCount int32
|
||||
result := make([]InfoStat, 0, len(matches))
|
||||
for physicalIndex, physicalCPU := range matches {
|
||||
var step int32
|
||||
var clock float64
|
||||
|
||||
if physicalCPU[psrStepOffset] != "" {
|
||||
stepParsed, err := strconv.ParseInt(physicalCPU[psrStepOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for step as 32-bit integer: %w", physicalCPU[9], err)
|
||||
}
|
||||
step = int32(stepParsed)
|
||||
}
|
||||
|
||||
if physicalCPU[psrClockOffset] != "" {
|
||||
clockParsed, err := strconv.ParseInt(physicalCPU[psrClockOffset], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for clock as 32-bit integer: %w", physicalCPU[10], err)
|
||||
}
|
||||
clock = float64(clockParsed)
|
||||
}
|
||||
|
||||
var err error
|
||||
var numCores int64
|
||||
var numHT int64
|
||||
switch {
|
||||
case physicalCPU[psrNumCoresOffset] != "":
|
||||
numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %w", physicalCPU[1], err)
|
||||
}
|
||||
|
||||
for i := 0; i < int(numCores); i++ {
|
||||
result = append(result, InfoStat{
|
||||
CPU: infoStatCount,
|
||||
PhysicalID: strconv.Itoa(physicalIndex),
|
||||
CoreID: strconv.Itoa(i),
|
||||
Cores: 1,
|
||||
VendorID: physicalCPU[psrVendorIDOffset],
|
||||
ModelName: physicalCPU[psrModelNameOffset],
|
||||
Family: physicalCPU[psrFamilyOffset],
|
||||
Model: physicalCPU[psrModelOffset],
|
||||
Stepping: step,
|
||||
Mhz: clock,
|
||||
})
|
||||
infoStatCount++
|
||||
}
|
||||
case physicalCPU[psrNumCoresHTOffset] != "":
|
||||
numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresHTOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %w", physicalCPU[3], err)
|
||||
}
|
||||
|
||||
numHT, err = strconv.ParseInt(physicalCPU[psrNumHTOffset], 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse value %q for hyperthread count as 32-bit integer: %w", physicalCPU[4], err)
|
||||
}
|
||||
|
||||
for i := 0; i < int(numCores); i++ {
|
||||
result = append(result, InfoStat{
|
||||
CPU: infoStatCount,
|
||||
PhysicalID: strconv.Itoa(physicalIndex),
|
||||
CoreID: strconv.Itoa(i),
|
||||
Cores: int32(numHT) / int32(numCores),
|
||||
VendorID: physicalCPU[psrVendorIDOffset],
|
||||
ModelName: physicalCPU[psrModelNameOffset],
|
||||
Family: physicalCPU[psrFamilyOffset],
|
||||
Model: physicalCPU[psrModelOffset],
|
||||
Stepping: step,
|
||||
Mhz: clock,
|
||||
})
|
||||
infoStatCount++
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("values for cores with and without hyperthreading are both set")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, _ bool) (int, error) {
|
||||
return runtime.NumCPU(), nil
|
||||
}
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
||||
procGetLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx")
|
||||
procGetSystemFirmwareTable = common.Modkernel32.NewProc("GetSystemFirmwareTable")
|
||||
procCallNtPowerInformation = common.ModPowrProf.NewProc("CallNtPowerInformation")
|
||||
)
|
||||
|
||||
type win32_Processor struct { //nolint:revive //FIXME
|
||||
Family uint16
|
||||
Manufacturer string
|
||||
Name string
|
||||
NumberOfLogicalProcessors uint32
|
||||
NumberOfCores uint32
|
||||
ProcessorID *string
|
||||
Stepping *string
|
||||
MaxClockSpeed uint32
|
||||
}
|
||||
|
||||
// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
|
||||
// defined in windows api doc with the following
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information
|
||||
// additional fields documented here
|
||||
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm
|
||||
type win32_SystemProcessorPerformanceInformation struct { //nolint:revive //FIXME
|
||||
IdleTime int64 // idle time in 100ns (this is not a filetime).
|
||||
KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime).
|
||||
UserTime int64 // usertime in 100ns (this is not a filetime).
|
||||
DpcTime int64 // dpc time in 100ns (this is not a filetime).
|
||||
InterruptTime int64 // interrupt time in 100ns
|
||||
InterruptCount uint64 // ULONG needs to be uint64
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/power/processor-power-information-str
|
||||
type processorPowerInformation struct {
|
||||
number uint32 // http://download.microsoft.com/download/a/d/f/adf1347d-08dc-41a4-9084-623b1194d4b2/MoreThan64proc.docx
|
||||
maxMhz uint32
|
||||
currentMhz uint32
|
||||
mhzLimit uint32
|
||||
maxIdleState uint32
|
||||
currentIdleState uint32
|
||||
}
|
||||
|
||||
const (
|
||||
ClocksPerSec = 10000000.0
|
||||
|
||||
// systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation
|
||||
// https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0
|
||||
win32_SystemProcessorPerformanceInformationClass = 8 //nolint:revive //FIXME
|
||||
|
||||
// size of systemProcessorPerformanceInfoSize in memory
|
||||
win32_SystemProcessorPerformanceInfoSize = uint32(unsafe.Sizeof(win32_SystemProcessorPerformanceInformation{})) //nolint:revive //FIXME
|
||||
|
||||
firmwareTableProviderSignatureRSMB = 0x52534d42 // "RSMB" https://gitlab.winehq.org/dreamer/wine/-/blame/wine-7.0-rc6/dlls/ntdll/unix/system.c#L230
|
||||
smBiosHeaderSize = 8 // SMBIOS header size
|
||||
smbiosEndOfTable = 127 // Minimum length for processor structure
|
||||
smbiosTypeProcessor = 4 // SMBIOS Type 4: Processor Information
|
||||
smbiosProcessorMinLength = 0x18 // Minimum length for processor structure
|
||||
|
||||
centralProcessorRegistryKey = `HARDWARE\DESCRIPTION\System\CentralProcessor`
|
||||
)
|
||||
|
||||
type relationship uint32
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex
|
||||
const (
|
||||
relationProcessorCore = relationship(0)
|
||||
relationProcessorPackage = relationship(3)
|
||||
)
|
||||
|
||||
const (
|
||||
kAffinitySize = unsafe.Sizeof(int(0))
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority
|
||||
maxLogicalProcessorsPerGroup = uint32(unsafe.Sizeof(kAffinitySize * 8))
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-power_information_level
|
||||
processorInformation = 11
|
||||
)
|
||||
|
||||
// Times returns times stat per cpu and combined for all CPUs
|
||||
func Times(percpu bool) ([]TimesStat, error) {
|
||||
return TimesWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
|
||||
if percpu {
|
||||
return perCPUTimes()
|
||||
}
|
||||
|
||||
var ret []TimesStat
|
||||
var lpIdleTime common.FILETIME
|
||||
var lpKernelTime common.FILETIME
|
||||
var lpUserTime common.FILETIME
|
||||
// GetSystemTimes returns 0 for error, in which case we check err,
|
||||
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
|
||||
r, _, err := common.ProcGetSystemTimes.Call(
|
||||
uintptr(unsafe.Pointer(&lpIdleTime)),
|
||||
uintptr(unsafe.Pointer(&lpKernelTime)),
|
||||
uintptr(unsafe.Pointer(&lpUserTime)))
|
||||
if r == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
LOT := float64(0.0000001)
|
||||
HIT := (LOT * 4294967296.0)
|
||||
idle := ((HIT * float64(lpIdleTime.DwHighDateTime)) + (LOT * float64(lpIdleTime.DwLowDateTime)))
|
||||
user := ((HIT * float64(lpUserTime.DwHighDateTime)) + (LOT * float64(lpUserTime.DwLowDateTime)))
|
||||
kernel := ((HIT * float64(lpKernelTime.DwHighDateTime)) + (LOT * float64(lpKernelTime.DwLowDateTime)))
|
||||
system := (kernel - idle)
|
||||
|
||||
ret = append(ret, TimesStat{
|
||||
CPU: "cpu-total",
|
||||
Idle: float64(idle),
|
||||
User: float64(user),
|
||||
System: float64(system),
|
||||
})
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func Info() ([]InfoStat, error) {
|
||||
return InfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
// this function iterates over each set bit in the package affinity mask, each bit represent a logical processor in a group (assuming you are iteriang over a package mask)
|
||||
// the function is used also to compute the global logical processor number
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
|
||||
// see https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-group_affinity
|
||||
// and https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_relationship
|
||||
// and https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex
|
||||
func forEachSetBit64(mask uint64, fn func(bit int)) {
|
||||
m := mask
|
||||
for m != 0 {
|
||||
b := bits.TrailingZeros64(m)
|
||||
fn(b)
|
||||
m &= m - 1
|
||||
}
|
||||
}
|
||||
|
||||
func getProcessorPowerInformation(ctx context.Context) ([]processorPowerInformation, error) {
|
||||
numLP, countErr := CountsWithContext(ctx, true)
|
||||
if countErr != nil {
|
||||
return nil, fmt.Errorf("failed to get logical processor count: %w", countErr)
|
||||
}
|
||||
if numLP <= 0 {
|
||||
return nil, fmt.Errorf("invalid logical processor count: %d", numLP)
|
||||
}
|
||||
|
||||
ppiSize := uintptr(numLP) * unsafe.Sizeof(processorPowerInformation{})
|
||||
buf := make([]byte, ppiSize)
|
||||
ppi, _, err := procCallNtPowerInformation.Call(
|
||||
uintptr(processorInformation),
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(ppiSize),
|
||||
)
|
||||
if ppi != 0 {
|
||||
return nil, fmt.Errorf("CallNtPowerInformation failed with code %d: %w", ppi, err)
|
||||
}
|
||||
ppis := unsafe.Slice((*processorPowerInformation)(unsafe.Pointer(&buf[0])), numLP)
|
||||
return ppis, nil
|
||||
}
|
||||
|
||||
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
||||
var ret []InfoStat
|
||||
processorPackages, err := getSystemLogicalProcessorInformationEx(relationProcessorPackage)
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("failed to get processor package information: %w", err)
|
||||
}
|
||||
|
||||
ppis, powerInformationErr := getProcessorPowerInformation(ctx)
|
||||
if powerInformationErr != nil {
|
||||
return ret, fmt.Errorf("failed to get processor power information: %w", powerInformationErr)
|
||||
}
|
||||
|
||||
family, processorId, smBIOSErr := getSMBIOSProcessorInfo()
|
||||
if smBIOSErr != nil {
|
||||
return ret, smBIOSErr
|
||||
}
|
||||
|
||||
for i, pkg := range processorPackages {
|
||||
logicalCount := 0
|
||||
maxMhz := 0
|
||||
model := ""
|
||||
vendorId := ""
|
||||
// iterate over each set bit in the package affinity mask
|
||||
for _, ga := range pkg.processor.groupMask {
|
||||
g := int(ga.group)
|
||||
forEachSetBit64(uint64(ga.mask), func(bit int) {
|
||||
// the global logical processor label
|
||||
globalLpl := g*int(maxLogicalProcessorsPerGroup) + bit
|
||||
if globalLpl >= 0 && globalLpl < len(ppis) {
|
||||
logicalCount++
|
||||
m := int(ppis[globalLpl].maxMhz)
|
||||
if m > maxMhz {
|
||||
maxMhz = m
|
||||
}
|
||||
}
|
||||
|
||||
registryKeyPath := filepath.Join(centralProcessorRegistryKey, strconv.Itoa(globalLpl))
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, registryKeyPath, registry.QUERY_VALUE|registry.READ)
|
||||
if err == nil {
|
||||
model = getRegistryStringValueIfUnset(key, "ProcessorNameString", model)
|
||||
vendorId = getRegistryStringValueIfUnset(key, "VendorIdentifier", vendorId)
|
||||
_ = key.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
ret = append(ret, InfoStat{
|
||||
CPU: int32(i),
|
||||
Family: strconv.FormatUint(uint64(family), 10),
|
||||
VendorID: vendorId,
|
||||
ModelName: model,
|
||||
Cores: int32(logicalCount),
|
||||
PhysicalID: processorId,
|
||||
Mhz: float64(maxMhz),
|
||||
Flags: []string{},
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
|
||||
func perCPUTimes() ([]TimesStat, error) {
|
||||
var ret []TimesStat
|
||||
stats, err := perfInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for core, v := range stats {
|
||||
c := TimesStat{
|
||||
CPU: fmt.Sprintf("cpu%d", core),
|
||||
User: float64(v.UserTime) / ClocksPerSec,
|
||||
System: float64(v.KernelTime-v.IdleTime) / ClocksPerSec,
|
||||
Idle: float64(v.IdleTime) / ClocksPerSec,
|
||||
Irq: float64(v.InterruptTime) / ClocksPerSec,
|
||||
}
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// makes call to Windows API function to retrieve performance information for each core
|
||||
func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) {
|
||||
// Make maxResults large for safety.
|
||||
// We can't invoke the api call with a results array that's too small.
|
||||
// If we have more than 2056 cores on a single host, then it's probably the future.
|
||||
maxBuffer := 2056
|
||||
// buffer for results from the windows proc
|
||||
resultBuffer := make([]win32_SystemProcessorPerformanceInformation, maxBuffer)
|
||||
// size of the buffer in memory
|
||||
bufferSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(maxBuffer)
|
||||
// size of the returned response
|
||||
var retSize uint32
|
||||
|
||||
// Invoke windows api proc.
|
||||
// The returned err from the windows dll proc will always be non-nil even when successful.
|
||||
// See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information
|
||||
retCode, _, err := common.ProcNtQuerySystemInformation.Call(
|
||||
win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation
|
||||
uintptr(unsafe.Pointer(&resultBuffer[0])), // pointer to first element in result buffer
|
||||
bufferSize, // size of the buffer in memory
|
||||
uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this
|
||||
)
|
||||
|
||||
// check return code for errors
|
||||
if retCode != 0 {
|
||||
return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error())
|
||||
}
|
||||
|
||||
// calculate the number of returned elements based on the returned size
|
||||
numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize
|
||||
|
||||
// trim results to the number of returned elements
|
||||
resultBuffer = resultBuffer[:numReturnedElements]
|
||||
|
||||
return resultBuffer, nil
|
||||
}
|
||||
|
||||
// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API.
|
||||
// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396
|
||||
// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43
|
||||
type systemInfo struct {
|
||||
wProcessorArchitecture uint16
|
||||
wReserved uint16
|
||||
dwPageSize uint32
|
||||
lpMinimumApplicationAddress uintptr
|
||||
lpMaximumApplicationAddress uintptr
|
||||
dwActiveProcessorMask uintptr
|
||||
dwNumberOfProcessors uint32
|
||||
dwProcessorType uint32
|
||||
dwAllocationGranularity uint32
|
||||
wProcessorLevel uint16
|
||||
wProcessorRevision uint16
|
||||
}
|
||||
|
||||
type groupAffinity struct {
|
||||
mask uintptr // https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority#about-kaffinity
|
||||
group uint16
|
||||
reserved [3]uint16
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_relationship
|
||||
type processorRelationship struct {
|
||||
flags byte
|
||||
efficientClass byte
|
||||
reserved [20]byte
|
||||
groupCount uint16
|
||||
groupMask [1]groupAffinity
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex
|
||||
type systemLogicalProcessorInformationEx struct {
|
||||
relationship uint32
|
||||
size uint32
|
||||
processor processorRelationship
|
||||
}
|
||||
|
||||
// getSMBIOSProcessorInfo reads the SMBIOS Type 4 (Processor Information) structure and returns the Processor Family and ProcessorId fields.
|
||||
// If not found, returns 0 and an empty string.
|
||||
func getSMBIOSProcessorInfo() (family uint8, processorId string, err error) {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable
|
||||
size, _, err := procGetSystemFirmwareTable.Call(
|
||||
uintptr(firmwareTableProviderSignatureRSMB),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if size == 0 {
|
||||
return 0, "", fmt.Errorf("failed to get SMBIOS table size: %w", err)
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
ret, _, err := procGetSystemFirmwareTable.Call(
|
||||
uintptr(firmwareTableProviderSignatureRSMB),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(size),
|
||||
)
|
||||
if ret == 0 {
|
||||
return 0, "", fmt.Errorf("failed to read SMBIOS table: %w", err)
|
||||
}
|
||||
// https://wiki.osdev.org/System_Management_BIOS
|
||||
i := smBiosHeaderSize // skip SMBIOS header (first 8 bytes)
|
||||
maxIterations := len(buf) * 2
|
||||
iterations := 0
|
||||
for i < len(buf) && iterations < maxIterations {
|
||||
iterations++
|
||||
if i+4 > len(buf) {
|
||||
break
|
||||
}
|
||||
typ := buf[i]
|
||||
length := buf[i+1]
|
||||
if typ == smbiosEndOfTable {
|
||||
break
|
||||
}
|
||||
if typ == smbiosTypeProcessor && length >= smbiosProcessorMinLength && i+int(length) <= len(buf) {
|
||||
// Ensure we have enough bytes for procIdBytes
|
||||
if i+16 > len(buf) {
|
||||
break
|
||||
}
|
||||
// Get the processor family from byte at offset 6
|
||||
family = buf[i+6]
|
||||
// Extract processor ID bytes (8 bytes total) from offsets 8-15
|
||||
procIdBytes := buf[i+8 : i+16]
|
||||
// Convert first 4 bytes to 32-bit EAX register value (little endian)
|
||||
eax := uint32(procIdBytes[0]) | uint32(procIdBytes[1])<<8 | uint32(procIdBytes[2])<<16 | uint32(procIdBytes[3])<<24
|
||||
// Convert last 4 bytes to 32-bit EDX register value (little endian)
|
||||
edx := uint32(procIdBytes[4]) | uint32(procIdBytes[5])<<8 | uint32(procIdBytes[6])<<16 | uint32(procIdBytes[7])<<24
|
||||
// Format processor ID as 16 character hex string (EDX+EAX)
|
||||
procId := fmt.Sprintf("%08X%08X", edx, eax)
|
||||
return family, procId, nil
|
||||
}
|
||||
// skip to next structure
|
||||
j := i + int(length)
|
||||
innerIterations := 0
|
||||
maxInner := len(buf) // failsafe for inner loop
|
||||
for j+1 < len(buf) && innerIterations < maxInner {
|
||||
innerIterations++
|
||||
if buf[j] == 0 && buf[j+1] == 0 {
|
||||
j += 2
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
if innerIterations >= maxInner {
|
||||
break // malformed buffer, avoid infinite loop
|
||||
}
|
||||
i = j
|
||||
}
|
||||
return 0, "", fmt.Errorf("SMBIOS processor information not found: %w", syscall.ERROR_NOT_FOUND)
|
||||
}
|
||||
|
||||
func getSystemLogicalProcessorInformationEx(relationship relationship) ([]systemLogicalProcessorInformationEx, error) {
|
||||
var length uint32
|
||||
// First call to determine the required buffer size
|
||||
_, _, err := procGetLogicalProcessorInformationEx.Call(uintptr(relationship), 0, uintptr(unsafe.Pointer(&length)))
|
||||
if err != nil && !errors.Is(err, windows.ERROR_INSUFFICIENT_BUFFER) {
|
||||
return nil, fmt.Errorf("failed to get buffer size: %w", err)
|
||||
}
|
||||
|
||||
// Allocate the buffer
|
||||
buffer := make([]byte, length)
|
||||
|
||||
// Second call to retrieve the processor information
|
||||
_, _, err = procGetLogicalProcessorInformationEx.Call(uintptr(relationship), uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&length)))
|
||||
if err != nil && !errors.Is(err, windows.NTE_OP_OK) {
|
||||
return nil, fmt.Errorf("failed to get logical processor information: %w", err)
|
||||
}
|
||||
|
||||
// Convert the byte slice into a slice of systemLogicalProcessorInformationEx structs
|
||||
offset := uintptr(0)
|
||||
var infos []systemLogicalProcessorInformationEx
|
||||
for offset < uintptr(length) {
|
||||
info := (*systemLogicalProcessorInformationEx)(unsafe.Pointer(uintptr(unsafe.Pointer(&buffer[0])) + offset))
|
||||
infos = append(infos, *info)
|
||||
offset += uintptr(info.size)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func getPhysicalCoreCount() (int, error) {
|
||||
infos, err := getSystemLogicalProcessorInformationEx(relationProcessorCore)
|
||||
return len(infos), err
|
||||
}
|
||||
|
||||
func getRegistryStringValueIfUnset(key registry.Key, keyName, value string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
val, _, err := key.GetStringValue(keyName)
|
||||
if err == nil {
|
||||
return strings.TrimSpace(val)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func CountsWithContext(_ context.Context, logical bool) (int, error) {
|
||||
if logical {
|
||||
// Get logical processor count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
|
||||
ret := windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS)
|
||||
if ret != 0 {
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
var sInfo systemInfo
|
||||
_, _, err := procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sInfo)))
|
||||
if sInfo.dwNumberOfProcessors == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return int(sInfo.dwNumberOfProcessors), nil
|
||||
}
|
||||
|
||||
// Get physical core count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499
|
||||
return getPhysicalCoreCount()
|
||||
}
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package common
|
||||
|
||||
//
|
||||
// gopsutil is a port of psutil(http://pythonhosted.org/psutil/).
|
||||
// This covers these architectures.
|
||||
// - linux (amd64, arm)
|
||||
// - freebsd (amd64)
|
||||
// - windows (amd64)
|
||||
// - aix (ppc64)
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/common"
|
||||
)
|
||||
|
||||
var (
|
||||
Timeout = 3 * time.Second
|
||||
ErrNotImplementedError = errors.New("not implemented yet")
|
||||
ErrTimeout = errors.New("command timed out")
|
||||
)
|
||||
|
||||
type Invoker interface {
|
||||
Command(string, ...string) ([]byte, error)
|
||||
CommandWithContext(context.Context, string, ...string) ([]byte, error)
|
||||
}
|
||||
|
||||
type Invoke struct{}
|
||||
|
||||
func (i Invoke) Command(name string, arg ...string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), Timeout)
|
||||
defer cancel()
|
||||
return i.CommandWithContext(ctx, name, arg...)
|
||||
}
|
||||
|
||||
func (Invoke) CommandWithContext(ctx context.Context, name string, arg ...string) ([]byte, error) {
|
||||
cmd := exec.CommandContext(ctx, name, arg...)
|
||||
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type FakeInvoke struct {
|
||||
Suffix string // Suffix species expected file name suffix such as "fail"
|
||||
Error error // If Error specified, return the error.
|
||||
}
|
||||
|
||||
// Command in FakeInvoke returns from expected file if exists.
|
||||
func (i FakeInvoke) Command(name string, arg ...string) ([]byte, error) {
|
||||
if i.Error != nil {
|
||||
return []byte{}, i.Error
|
||||
}
|
||||
|
||||
arch := runtime.GOOS
|
||||
|
||||
commandName := filepath.Base(name)
|
||||
|
||||
fname := strings.Join(append([]string{commandName}, arg...), "")
|
||||
fname = url.QueryEscape(fname)
|
||||
fpath := path.Join("testdata", arch, fname)
|
||||
if i.Suffix != "" {
|
||||
fpath += "_" + i.Suffix
|
||||
}
|
||||
if PathExists(fpath) {
|
||||
return os.ReadFile(fpath)
|
||||
}
|
||||
return []byte{}, fmt.Errorf("could not find testdata: %s", fpath)
|
||||
}
|
||||
|
||||
func (i FakeInvoke) CommandWithContext(_ context.Context, name string, arg ...string) ([]byte, error) {
|
||||
return i.Command(name, arg...)
|
||||
}
|
||||
|
||||
// ReadFile reads contents from a file
|
||||
func ReadFile(filename string) (string, error) {
|
||||
content, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
// ReadLines reads contents from a file and splits them by new lines.
|
||||
// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
|
||||
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, 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):
|
||||
// n >= 0: at most n lines
|
||||
// n < 0: whole file
|
||||
func ReadLinesOffsetN(filename string, offset uint, n int) ([]string, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return []string{""}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var ret []string
|
||||
|
||||
r := bufio.NewReader(f)
|
||||
for i := uint(0); i < uint(n)+offset || n < 0; i++ {
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF && line != "" {
|
||||
ret = append(ret, strings.Trim(line, "\n"))
|
||||
}
|
||||
break
|
||||
}
|
||||
if i < offset {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, strings.Trim(line, "\n"))
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func IntToString(orig []int8) string {
|
||||
ret := make([]byte, len(orig))
|
||||
size := -1
|
||||
for i, o := range orig {
|
||||
if o == 0 {
|
||||
size = i
|
||||
break
|
||||
}
|
||||
ret[i] = byte(o)
|
||||
}
|
||||
if size == -1 {
|
||||
size = len(orig)
|
||||
}
|
||||
|
||||
return string(ret[0:size])
|
||||
}
|
||||
|
||||
func UintToString(orig []uint8) string {
|
||||
ret := make([]byte, len(orig))
|
||||
size := -1
|
||||
for i, o := range orig {
|
||||
if o == 0 {
|
||||
size = i
|
||||
break
|
||||
}
|
||||
ret[i] = byte(o)
|
||||
}
|
||||
if size == -1 {
|
||||
size = len(orig)
|
||||
}
|
||||
|
||||
return string(ret[0:size])
|
||||
}
|
||||
|
||||
func ByteToString(orig []byte) string {
|
||||
n := -1
|
||||
l := -1
|
||||
for i, b := range orig {
|
||||
// skip left side null
|
||||
if l == -1 && b == 0 {
|
||||
continue
|
||||
}
|
||||
if l == -1 {
|
||||
l = i
|
||||
}
|
||||
|
||||
if b == 0 {
|
||||
break
|
||||
}
|
||||
n = i + 1
|
||||
}
|
||||
if n == -1 {
|
||||
return string(orig)
|
||||
}
|
||||
return string(orig[l:n])
|
||||
}
|
||||
|
||||
// ReadInts reads contents from single line file and returns them as []int32.
|
||||
func ReadInts(filename string) ([]int64, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return []int64{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var ret []int64
|
||||
|
||||
r := bufio.NewReader(f)
|
||||
|
||||
// The int files that this is concerned with should only be one liners.
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return []int64{}, err
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(strings.Trim(line, "\n"), 10, 32)
|
||||
if err != nil {
|
||||
return []int64{}, err
|
||||
}
|
||||
ret = append(ret, i)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Parse Hex to uint32 without error
|
||||
func HexToUint32(hex string) uint32 {
|
||||
vv, _ := strconv.ParseUint(hex, 16, 32)
|
||||
return uint32(vv)
|
||||
}
|
||||
|
||||
// Parse to int32 without error
|
||||
func mustParseInt32(val string) int32 {
|
||||
vv, _ := strconv.ParseInt(val, 10, 32)
|
||||
return int32(vv)
|
||||
}
|
||||
|
||||
// Parse to uint64 without error
|
||||
func mustParseUint64(val string) uint64 {
|
||||
vv, _ := strconv.ParseInt(val, 10, 64)
|
||||
return uint64(vv)
|
||||
}
|
||||
|
||||
// Parse to Float64 without error
|
||||
func mustParseFloat64(val string) float64 {
|
||||
vv, _ := strconv.ParseFloat(val, 64)
|
||||
return vv
|
||||
}
|
||||
|
||||
// StringsHas checks the target string slice contains src or not
|
||||
func StringsHas(target []string, src string) bool {
|
||||
for _, t := range target {
|
||||
if strings.TrimSpace(t) == src {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StringsContains checks the src in any string of the target string slice
|
||||
func StringsContains(target []string, src string) bool {
|
||||
return slices.ContainsFunc(target, func(s string) bool {
|
||||
return strings.Contains(s, src)
|
||||
})
|
||||
}
|
||||
|
||||
// IntContains checks the src in any int of the target int slice.
|
||||
func IntContains(target []int, src int) bool {
|
||||
return slices.Contains(target, src)
|
||||
}
|
||||
|
||||
// get struct attributes.
|
||||
// This method is used only for debugging platform dependent code.
|
||||
func attributes(m any) map[string]reflect.Type {
|
||||
typ := reflect.TypeOf(m)
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
attrs := make(map[string]reflect.Type)
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
p := typ.Field(i)
|
||||
if !p.Anonymous {
|
||||
attrs[p.Name] = p.Type
|
||||
}
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
func PathExists(filename string) bool {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PathExistsWithContents returns the filename exists and it is not empty
|
||||
func PathExistsWithContents(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.Size() > 4 && !info.IsDir() // 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, 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, dfault string, combineWith ...string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
value = dfault
|
||||
}
|
||||
|
||||
return combine(value, combineWith)
|
||||
}
|
||||
|
||||
func combine(value string, combineWith []string) string {
|
||||
switch len(combineWith) {
|
||||
case 0:
|
||||
return value
|
||||
case 1:
|
||||
return filepath.Join(value, combineWith[0])
|
||||
default:
|
||||
all := make([]string, len(combineWith)+1)
|
||||
all[0] = value
|
||||
copy(all[1:], combineWith)
|
||||
return filepath.Join(all...)
|
||||
}
|
||||
}
|
||||
|
||||
func HostProc(combineWith ...string) string {
|
||||
return GetEnv("HOST_PROC", "/proc", combineWith...)
|
||||
}
|
||||
|
||||
func HostSys(combineWith ...string) string {
|
||||
return GetEnv("HOST_SYS", "/sys", combineWith...)
|
||||
}
|
||||
|
||||
func HostEtc(combineWith ...string) string {
|
||||
return GetEnv("HOST_ETC", "/etc", combineWith...)
|
||||
}
|
||||
|
||||
func HostVar(combineWith ...string) string {
|
||||
return GetEnv("HOST_VAR", "/var", combineWith...)
|
||||
}
|
||||
|
||||
func HostRun(combineWith ...string) string {
|
||||
return GetEnv("HOST_RUN", "/run", combineWith...)
|
||||
}
|
||||
|
||||
func HostDev(combineWith ...string) string {
|
||||
return GetEnv("HOST_DEV", "/dev", combineWith...)
|
||||
}
|
||||
|
||||
func HostRoot(combineWith ...string) string {
|
||||
return GetEnv("HOST_ROOT", "/", combineWith...)
|
||||
}
|
||||
|
||||
func HostProcWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_PROC", "/proc", combineWith...)
|
||||
}
|
||||
|
||||
func HostProcMountInfoWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_PROC_MOUNTINFO", "", combineWith...)
|
||||
}
|
||||
|
||||
func HostSysWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_SYS", "/sys", combineWith...)
|
||||
}
|
||||
|
||||
func HostEtcWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_ETC", "/etc", combineWith...)
|
||||
}
|
||||
|
||||
func HostVarWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_VAR", "/var", combineWith...)
|
||||
}
|
||||
|
||||
func HostRunWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_RUN", "/run", combineWith...)
|
||||
}
|
||||
|
||||
func HostDevWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_DEV", "/dev", combineWith...)
|
||||
}
|
||||
|
||||
func HostRootWithContext(ctx context.Context, combineWith ...string) string {
|
||||
return GetEnvWithContext(ctx, "HOST_ROOT", "/", combineWith...)
|
||||
}
|
||||
|
||||
// getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running
|
||||
// sysctl commands.
|
||||
func getSysctrlEnv(env []string) []string {
|
||||
foundLC := false
|
||||
for i, line := range env {
|
||||
if strings.HasPrefix(line, "LC_ALL") {
|
||||
env[i] = "LC_ALL=C"
|
||||
foundLC = true
|
||||
}
|
||||
}
|
||||
if !foundLC {
|
||||
env = append(env, "LC_ALL=C")
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// Round places rounds the number 'val' to 'n' decimal places
|
||||
func Round(val float64, n int) float64 {
|
||||
// Calculate the power of 10 to the n
|
||||
pow10 := math.Pow(10, float64(n))
|
||||
// Multiply the value by pow10, round it, then divide it by pow10
|
||||
return math.Round(val*pow10) / pow10
|
||||
}
|
||||
|
||||
func TimeSince(ts uint64) uint64 {
|
||||
return uint64(time.Now().Unix()) - ts
|
||||
}
|
||||
|
||||
func TimeSinceMillis(ts uint64) uint64 {
|
||||
return uint64(time.Now().UnixMilli()) - ts
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BootTimeWithContext(ctx context.Context, invoke Invoker) (btime uint64, err error) {
|
||||
ut, err := UptimeWithContext(ctx, invoke)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if ut <= 0 {
|
||||
return 0, errors.New("uptime was not set, so cannot calculate boot time from it")
|
||||
}
|
||||
|
||||
return TimeSince(ut), nil
|
||||
}
|
||||
|
||||
// Uses ps to get the elapsed time for PID 1 in DAYS-HOURS:MINUTES:SECONDS format.
|
||||
// Examples of ps -o etimes -p 1 output:
|
||||
// 124-01:40:39 (with days)
|
||||
// 15:03:02 (without days, hours only)
|
||||
// 01:02 (just-rebooted systems, minutes and seconds)
|
||||
func UptimeWithContext(ctx context.Context, invoke Invoker) (uint64, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "etimes", "-p", "1")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
if len(lines) < 2 {
|
||||
return 0, errors.New("ps output has fewer than 2 rows")
|
||||
}
|
||||
|
||||
// Extract the etimes value from the second row, trimming whitespace
|
||||
etimes := strings.TrimSpace(lines[1])
|
||||
return ParseUptime(etimes), nil
|
||||
}
|
||||
|
||||
// Parses etimes output from ps command into total seconds.
|
||||
// Handles formats like:
|
||||
// - "124-01:40:39" (DAYS-HOURS:MINUTES:SECONDS)
|
||||
// - "15:03:02" (HOURS:MINUTES:SECONDS)
|
||||
// - "01:02" (MINUTES:SECONDS, from just-rebooted systems)
|
||||
func ParseUptime(etimes string) uint64 {
|
||||
var days, hours, mins, secs uint64
|
||||
|
||||
// Check if days component is present (contains a dash)
|
||||
if strings.Contains(etimes, "-") {
|
||||
parts := strings.Split(etimes, "-")
|
||||
if len(parts) != 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var err error
|
||||
days, err = strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Parse the HH:MM:SS portion (after days, must have 3 parts)
|
||||
etimes = parts[1]
|
||||
timeParts := strings.Split(etimes, ":")
|
||||
if len(timeParts) != 3 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var err2 error
|
||||
hours, err2 = strconv.ParseUint(timeParts[0], 10, 64)
|
||||
if err2 != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
mins, err2 = strconv.ParseUint(timeParts[1], 10, 64)
|
||||
if err2 != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
secs, err2 = strconv.ParseUint(timeParts[2], 10, 64)
|
||||
if err2 != nil {
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
// Parse time portions (either HH:MM:SS or MM:SS) when no days present
|
||||
timeParts := strings.Split(etimes, ":")
|
||||
switch len(timeParts) {
|
||||
case 3:
|
||||
// HH:MM:SS format
|
||||
var err error
|
||||
hours, err = strconv.ParseUint(timeParts[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
mins, err = strconv.ParseUint(timeParts[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
secs, err = strconv.ParseUint(timeParts[2], 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
case 2:
|
||||
// MM:SS format (just-rebooted systems)
|
||||
var err error
|
||||
mins, err = strconv.ParseUint(timeParts[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
secs, err = strconv.ParseUint(timeParts[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to total seconds
|
||||
totalSeconds := (days * 24 * 60 * 60) + (hours * 60 * 60) + (mins * 60) + secs
|
||||
return totalSeconds
|
||||
}
|
||||
+577
@@ -0,0 +1,577 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
// Library represents a dynamic library loaded by purego.
|
||||
type library struct {
|
||||
handle uintptr
|
||||
fnMap map[string]any
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// library paths
|
||||
const (
|
||||
IOKitLibPath = "/System/Library/Frameworks/IOKit.framework/IOKit"
|
||||
CoreFoundationLibPath = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
|
||||
SystemLibPath = "/usr/lib/libSystem.B.dylib"
|
||||
)
|
||||
|
||||
func newLibrary(path string) (*library, error) {
|
||||
lib, err := purego.Dlopen(path, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &library{
|
||||
handle: lib,
|
||||
fnMap: make(map[string]any),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (lib *library) Dlsym(symbol string) (uintptr, error) {
|
||||
return purego.Dlsym(lib.handle, symbol)
|
||||
}
|
||||
|
||||
// getFunc resolves a function pointer from the library, caching it in fnMap.
|
||||
// Thread-safe via double-checked locking to support shared library handles.
|
||||
func getFunc[T any](lib *library, symbol string) T {
|
||||
// Fast path: read lock only
|
||||
lib.mu.RLock()
|
||||
if f, ok := lib.fnMap[symbol].(*dlFunc[T]); ok {
|
||||
lib.mu.RUnlock()
|
||||
return f.fn
|
||||
}
|
||||
lib.mu.RUnlock()
|
||||
|
||||
// Slow path: write lock for first-time resolution
|
||||
lib.mu.Lock()
|
||||
defer lib.mu.Unlock()
|
||||
|
||||
// Double-check after acquiring write lock
|
||||
if f, ok := lib.fnMap[symbol].(*dlFunc[T]); ok {
|
||||
return f.fn
|
||||
}
|
||||
|
||||
dlfun := newDlfunc[T](symbol)
|
||||
dlfun.init(lib.handle)
|
||||
lib.fnMap[symbol] = dlfun
|
||||
return dlfun.fn
|
||||
}
|
||||
|
||||
func (lib *library) Close() {
|
||||
purego.Dlclose(lib.handle)
|
||||
}
|
||||
|
||||
type dlFunc[T any] struct {
|
||||
sym string
|
||||
fn T
|
||||
}
|
||||
|
||||
func (d *dlFunc[T]) init(handle uintptr) {
|
||||
purego.RegisterLibFunc(&d.fn, handle, d.sym)
|
||||
}
|
||||
|
||||
func newDlfunc[T any](sym string) *dlFunc[T] {
|
||||
return &dlFunc[T]{sym: sym}
|
||||
}
|
||||
|
||||
type CoreFoundationLib struct {
|
||||
*library
|
||||
}
|
||||
|
||||
func NewCoreFoundationLib() (*CoreFoundationLib, error) {
|
||||
library, err := newLibrary(CoreFoundationLibPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CoreFoundationLib{library}, nil
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFGetTypeID(cf uintptr) int64 {
|
||||
fn := getFunc[CFGetTypeIDFunc](c.library, "CFGetTypeID")
|
||||
return fn(cf)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFNumberCreate(allocator uintptr, theType int64, valuePtr uintptr) unsafe.Pointer {
|
||||
fn := getFunc[CFNumberCreateFunc](c.library, "CFNumberCreate")
|
||||
return fn(allocator, theType, valuePtr)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFNumberGetValue(num uintptr, theType int64, valuePtr uintptr) bool {
|
||||
fn := getFunc[CFNumberGetValueFunc](c.library, "CFNumberGetValue")
|
||||
return fn(num, theType, valuePtr)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFDictionaryCreate(allocator uintptr, keys, values *unsafe.Pointer, numValues int64,
|
||||
keyCallBacks, valueCallBacks uintptr,
|
||||
) unsafe.Pointer {
|
||||
fn := getFunc[CFDictionaryCreateFunc](c.library, "CFDictionaryCreate")
|
||||
return fn(allocator, keys, values, numValues, keyCallBacks, valueCallBacks)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFDictionaryAddValue(theDict, key, value uintptr) {
|
||||
fn := getFunc[CFDictionaryAddValueFunc](c.library, "CFDictionaryAddValue")
|
||||
fn(theDict, key, value)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFDictionaryGetValue(theDict, key uintptr) unsafe.Pointer {
|
||||
fn := getFunc[CFDictionaryGetValueFunc](c.library, "CFDictionaryGetValue")
|
||||
return fn(theDict, key)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFArrayGetCount(theArray uintptr) int64 {
|
||||
fn := getFunc[CFArrayGetCountFunc](c.library, "CFArrayGetCount")
|
||||
return fn(theArray)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFArrayGetValueAtIndex(theArray uintptr, index int64) unsafe.Pointer {
|
||||
fn := getFunc[CFArrayGetValueAtIndexFunc](c.library, "CFArrayGetValueAtIndex")
|
||||
return fn(theArray, index)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFStringCreateMutable(alloc uintptr, maxLength int64) unsafe.Pointer {
|
||||
fn := getFunc[CFStringCreateMutableFunc](c.library, "CFStringCreateMutable")
|
||||
return fn(alloc, maxLength)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFStringGetLength(theString uintptr) int64 {
|
||||
fn := getFunc[CFStringGetLengthFunc](c.library, "CFStringGetLength")
|
||||
return fn(theString)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFStringGetCString(theString uintptr, buffer CStr, bufferSize int64, encoding uint32) {
|
||||
fn := getFunc[CFStringGetCStringFunc](c.library, "CFStringGetCString")
|
||||
fn(theString, buffer, bufferSize, encoding)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFStringCreateWithCString(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer {
|
||||
fn := getFunc[CFStringCreateWithCStringFunc](c.library, "CFStringCreateWithCString")
|
||||
return fn(alloc, cStr, encoding)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFDataGetLength(theData uintptr) int64 {
|
||||
fn := getFunc[CFDataGetLengthFunc](c.library, "CFDataGetLength")
|
||||
return fn(theData)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFDataGetBytePtr(theData uintptr) unsafe.Pointer {
|
||||
fn := getFunc[CFDataGetBytePtrFunc](c.library, "CFDataGetBytePtr")
|
||||
return fn(theData)
|
||||
}
|
||||
|
||||
func (c *CoreFoundationLib) CFRelease(cf uintptr) {
|
||||
fn := getFunc[CFReleaseFunc](c.library, "CFRelease")
|
||||
fn(cf)
|
||||
}
|
||||
|
||||
type IOKitLib struct {
|
||||
*library
|
||||
}
|
||||
|
||||
func NewIOKitLib() (*IOKitLib, error) {
|
||||
library, err := newLibrary(IOKitLibPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IOKitLib{library}, nil
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOServiceGetMatchingService(mainPort uint32, matching uintptr) uint32 {
|
||||
fn := getFunc[IOServiceGetMatchingServiceFunc](l.library, "IOServiceGetMatchingService")
|
||||
return fn(mainPort, matching)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOServiceGetMatchingServices(mainPort uint32, matching uintptr, existing *uint32) int32 {
|
||||
fn := getFunc[IOServiceGetMatchingServicesFunc](l.library, "IOServiceGetMatchingServices")
|
||||
return fn(mainPort, matching, existing)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOServiceMatching(name string) unsafe.Pointer {
|
||||
fn := getFunc[IOServiceMatchingFunc](l.library, "IOServiceMatching")
|
||||
return fn(name)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOServiceOpen(service, owningTask, connType uint32, connect *uint32) int32 {
|
||||
fn := getFunc[IOServiceOpenFunc](l.library, "IOServiceOpen")
|
||||
return fn(service, owningTask, connType, connect)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOServiceClose(connect uint32) int32 {
|
||||
fn := getFunc[IOServiceCloseFunc](l.library, "IOServiceClose")
|
||||
return fn(connect)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOIteratorNext(iterator uint32) uint32 {
|
||||
fn := getFunc[IOIteratorNextFunc](l.library, "IOIteratorNext")
|
||||
return fn(iterator)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IORegistryEntryGetName(entry uint32, name CStr) int32 {
|
||||
fn := getFunc[IORegistryEntryGetNameFunc](l.library, "IORegistryEntryGetName")
|
||||
return fn(entry, name)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IORegistryEntryGetParentEntry(entry uint32, plane string, parent *uint32) int32 {
|
||||
fn := getFunc[IORegistryEntryGetParentEntryFunc](l.library, "IORegistryEntryGetParentEntry")
|
||||
return fn(entry, plane, parent)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IORegistryEntryCreateCFProperty(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer {
|
||||
fn := getFunc[IORegistryEntryCreateCFPropertyFunc](l.library, "IORegistryEntryCreateCFProperty")
|
||||
return fn(entry, key, allocator, options)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IORegistryEntryCreateCFProperties(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int32 {
|
||||
fn := getFunc[IORegistryEntryCreateCFPropertiesFunc](l.library, "IORegistryEntryCreateCFProperties")
|
||||
return fn(entry, properties, allocator, options)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOObjectConformsTo(object uint32, className string) bool {
|
||||
fn := getFunc[IOObjectConformsToFunc](l.library, "IOObjectConformsTo")
|
||||
return fn(object, className)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOObjectRelease(object uint32) int32 {
|
||||
fn := getFunc[IOObjectReleaseFunc](l.library, "IOObjectRelease")
|
||||
return fn(object)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOConnectCallStructMethod(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int32 {
|
||||
fn := getFunc[IOConnectCallStructMethodFunc](l.library, "IOConnectCallStructMethod")
|
||||
return fn(connection, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOHIDEventSystemClientCreate(allocator uintptr) unsafe.Pointer {
|
||||
fn := getFunc[IOHIDEventSystemClientCreateFunc](l.library, "IOHIDEventSystemClientCreate")
|
||||
return fn(allocator)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOHIDEventSystemClientSetMatching(client, match uintptr) int32 {
|
||||
fn := getFunc[IOHIDEventSystemClientSetMatchingFunc](l.library, "IOHIDEventSystemClientSetMatching")
|
||||
return fn(client, match)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOHIDServiceClientCopyEvent(service uintptr, eventType int64, options int32, timeout int64) unsafe.Pointer {
|
||||
fn := getFunc[IOHIDServiceClientCopyEventFunc](l.library, "IOHIDServiceClientCopyEvent")
|
||||
return fn(service, eventType, options, timeout)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOHIDServiceClientCopyProperty(service, property uintptr) unsafe.Pointer {
|
||||
fn := getFunc[IOHIDServiceClientCopyPropertyFunc](l.library, "IOHIDServiceClientCopyProperty")
|
||||
return fn(service, property)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOHIDEventGetFloatValue(event uintptr, field int32) float64 {
|
||||
fn := getFunc[IOHIDEventGetFloatValueFunc](l.library, "IOHIDEventGetFloatValue")
|
||||
return fn(event, field)
|
||||
}
|
||||
|
||||
func (l *IOKitLib) IOHIDEventSystemClientCopyServices(client uintptr) unsafe.Pointer {
|
||||
fn := getFunc[IOHIDEventSystemClientCopyServicesFunc](l.library, "IOHIDEventSystemClientCopyServices")
|
||||
return fn(client)
|
||||
}
|
||||
|
||||
type SystemLib struct {
|
||||
*library
|
||||
}
|
||||
|
||||
func NewSystemLib() (*SystemLib, error) {
|
||||
library, err := newLibrary(SystemLibPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SystemLib{library}, nil
|
||||
}
|
||||
|
||||
func (s *SystemLib) HostProcessorInfo(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
|
||||
outProcessorInfoCnt *uint32,
|
||||
) int32 {
|
||||
fn := getFunc[HostProcessorInfoFunc](s.library, "host_processor_info")
|
||||
return fn(host, flavor, outProcessorCount, outProcessorInfo, outProcessorInfoCnt)
|
||||
}
|
||||
|
||||
func (s *SystemLib) HostStatistics(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int32 {
|
||||
fn := getFunc[HostStatisticsFunc](s.library, "host_statistics")
|
||||
return fn(host, flavor, hostInfoOut, hostInfoOutCnt)
|
||||
}
|
||||
|
||||
func (s *SystemLib) MachHostSelf() uint32 {
|
||||
fn := getFunc[MachHostSelfFunc](s.library, "mach_host_self")
|
||||
return fn()
|
||||
}
|
||||
|
||||
func (s *SystemLib) MachTaskSelf() uint32 {
|
||||
fn := getFunc[MachTaskSelfFunc](s.library, "mach_task_self")
|
||||
return fn()
|
||||
}
|
||||
|
||||
func (s *SystemLib) MachTimeBaseInfo(info uintptr) int32 {
|
||||
fn := getFunc[MachTimeBaseInfoFunc](s.library, "mach_timebase_info")
|
||||
return fn(info)
|
||||
}
|
||||
|
||||
func (s *SystemLib) VMDeallocate(targetTask uint32, vmAddress, vmSize uintptr) int32 {
|
||||
fn := getFunc[VMDeallocateFunc](s.library, "vm_deallocate")
|
||||
return fn(targetTask, vmAddress, vmSize)
|
||||
}
|
||||
|
||||
func (s *SystemLib) ProcPidPath(pid int32, buffer uintptr, bufferSize uint32) int32 {
|
||||
fn := getFunc[ProcPidPathFunc](s.library, "proc_pidpath")
|
||||
return fn(pid, buffer, bufferSize)
|
||||
}
|
||||
|
||||
func (s *SystemLib) ProcPidInfo(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32 {
|
||||
fn := getFunc[ProcPidInfoFunc](s.library, "proc_pidinfo")
|
||||
return fn(pid, flavor, arg, buffer, bufferSize)
|
||||
}
|
||||
|
||||
// status codes
|
||||
const (
|
||||
KERN_SUCCESS = 0
|
||||
)
|
||||
|
||||
// IOKit types and constants.
|
||||
type (
|
||||
IOServiceGetMatchingServiceFunc func(mainPort uint32, matching uintptr) uint32
|
||||
IOServiceGetMatchingServicesFunc func(mainPort uint32, matching uintptr, existing *uint32) int32
|
||||
IOServiceMatchingFunc func(name string) unsafe.Pointer
|
||||
IOServiceOpenFunc func(service, owningTask, connType uint32, connect *uint32) int32
|
||||
IOServiceCloseFunc func(connect uint32) int32
|
||||
IOIteratorNextFunc func(iterator uint32) uint32
|
||||
IORegistryEntryGetNameFunc func(entry uint32, name CStr) int32
|
||||
IORegistryEntryGetParentEntryFunc func(entry uint32, plane string, parent *uint32) int32
|
||||
IORegistryEntryCreateCFPropertyFunc func(entry uint32, key, allocator uintptr, options uint32) unsafe.Pointer
|
||||
IORegistryEntryCreateCFPropertiesFunc func(entry uint32, properties unsafe.Pointer, allocator uintptr, options uint32) int32
|
||||
IOObjectConformsToFunc func(object uint32, className string) bool
|
||||
IOObjectReleaseFunc func(object uint32) int32
|
||||
IOConnectCallStructMethodFunc func(connection, selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int32
|
||||
|
||||
IOHIDEventSystemClientCreateFunc func(allocator uintptr) unsafe.Pointer
|
||||
IOHIDEventSystemClientSetMatchingFunc func(client, match uintptr) int32
|
||||
IOHIDServiceClientCopyEventFunc func(service uintptr, eventType int64,
|
||||
options int32, timeout int64) unsafe.Pointer
|
||||
IOHIDServiceClientCopyPropertyFunc func(service, property uintptr) unsafe.Pointer
|
||||
IOHIDEventGetFloatValueFunc func(event uintptr, field int32) float64
|
||||
IOHIDEventSystemClientCopyServicesFunc func(client uintptr) unsafe.Pointer
|
||||
)
|
||||
|
||||
const (
|
||||
KIOMainPortDefault = 0
|
||||
|
||||
KIOHIDEventTypeTemperature = 15
|
||||
|
||||
KNilOptions = 0
|
||||
)
|
||||
|
||||
const (
|
||||
KIOMediaWholeKey = "Media"
|
||||
KIOServicePlane = "IOService"
|
||||
)
|
||||
|
||||
// CoreFoundation types and constants.
|
||||
type (
|
||||
CFGetTypeIDFunc func(cf uintptr) int64
|
||||
CFNumberCreateFunc func(allocator uintptr, theType int64, valuePtr uintptr) unsafe.Pointer
|
||||
CFNumberGetValueFunc func(num uintptr, theType int64, valuePtr uintptr) bool
|
||||
CFDictionaryCreateFunc func(allocator uintptr, keys, values *unsafe.Pointer, numValues int64,
|
||||
keyCallBacks, valueCallBacks uintptr) unsafe.Pointer
|
||||
CFDictionaryAddValueFunc func(theDict, key, value uintptr)
|
||||
CFDictionaryGetValueFunc func(theDict, key uintptr) unsafe.Pointer
|
||||
CFArrayGetCountFunc func(theArray uintptr) int64
|
||||
CFArrayGetValueAtIndexFunc func(theArray uintptr, index int64) unsafe.Pointer
|
||||
CFStringCreateMutableFunc func(alloc uintptr, maxLength int64) unsafe.Pointer
|
||||
CFStringGetLengthFunc func(theString uintptr) int64
|
||||
CFStringGetCStringFunc func(theString uintptr, buffer CStr, bufferSize int64, encoding uint32)
|
||||
CFStringCreateWithCStringFunc func(alloc uintptr, cStr string, encoding uint32) unsafe.Pointer
|
||||
CFDataGetLengthFunc func(theData uintptr) int64
|
||||
CFDataGetBytePtrFunc func(theData uintptr) unsafe.Pointer
|
||||
CFReleaseFunc func(cf uintptr)
|
||||
)
|
||||
|
||||
const (
|
||||
KCFStringEncodingUTF8 = 0x08000100
|
||||
KCFNumberSInt64Type = 4
|
||||
KCFNumberIntType = 9
|
||||
KCFAllocatorDefault = 0
|
||||
KCFNotFound = -1
|
||||
)
|
||||
|
||||
// libSystem types and constants.
|
||||
type MachTimeBaseInfo struct {
|
||||
Numer uint32
|
||||
Denom uint32
|
||||
}
|
||||
|
||||
type (
|
||||
HostProcessorInfoFunc func(host uint32, flavor int32, outProcessorCount *uint32, outProcessorInfo uintptr,
|
||||
outProcessorInfoCnt *uint32) int32
|
||||
HostStatisticsFunc func(host uint32, flavor int32, hostInfoOut uintptr, hostInfoOutCnt *uint32) int32
|
||||
MachHostSelfFunc func() uint32
|
||||
MachTaskSelfFunc func() uint32
|
||||
MachTimeBaseInfoFunc func(info uintptr) int32
|
||||
VMDeallocateFunc func(targetTask uint32, vmAddress, vmSize uintptr) int32
|
||||
)
|
||||
|
||||
const (
|
||||
HostProcessorInfoSym = "host_processor_info"
|
||||
HostStatisticsSym = "host_statistics"
|
||||
MachHostSelfSym = "mach_host_self"
|
||||
MachTaskSelfSym = "mach_task_self"
|
||||
MachTimeBaseInfoSym = "mach_timebase_info"
|
||||
VMDeallocateSym = "vm_deallocate"
|
||||
)
|
||||
|
||||
const (
|
||||
HOST_VM_INFO = 2
|
||||
HOST_CPU_LOAD_INFO = 3
|
||||
|
||||
HOST_VM_INFO_COUNT = 0xf
|
||||
)
|
||||
|
||||
type (
|
||||
ProcPidPathFunc func(pid int32, buffer uintptr, bufferSize uint32) int32
|
||||
ProcPidInfoFunc func(pid, flavor int32, arg uint64, buffer uintptr, bufferSize int32) int32
|
||||
)
|
||||
|
||||
const (
|
||||
SysctlSym = "sysctl"
|
||||
ProcPidPathSym = "proc_pidpath"
|
||||
ProcPidInfoSym = "proc_pidinfo"
|
||||
)
|
||||
|
||||
const (
|
||||
MAXPATHLEN = 1024
|
||||
PROC_PIDLISTFDS = 1
|
||||
PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN
|
||||
PROC_PIDTASKINFO = 4
|
||||
PROC_PIDVNODEPATHINFO = 9
|
||||
)
|
||||
|
||||
// SMC represents a SMC instance.
|
||||
type SMC struct {
|
||||
lib *IOKitLib
|
||||
conn uint32
|
||||
}
|
||||
|
||||
const ioServiceSMC = "AppleSMC"
|
||||
|
||||
const (
|
||||
KSMCUserClientOpen = 0
|
||||
KSMCUserClientClose = 1
|
||||
KSMCHandleYPCEvent = 2
|
||||
KSMCReadKey = 5
|
||||
KSMCWriteKey = 6
|
||||
KSMCGetKeyCount = 7
|
||||
KSMCGetKeyFromIndex = 8
|
||||
KSMCGetKeyInfo = 9
|
||||
)
|
||||
|
||||
const (
|
||||
KSMCSuccess = 0
|
||||
KSMCError = 1
|
||||
KSMCKeyNotFound = 132
|
||||
)
|
||||
|
||||
func NewSMC() (*SMC, error) {
|
||||
iokit, err := NewIOKitLib()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := iokit.IOServiceGetMatchingService(0, uintptr(iokit.IOServiceMatching(ioServiceSMC)))
|
||||
if service == 0 {
|
||||
return nil, fmt.Errorf("ERROR: %s NOT FOUND", ioServiceSMC)
|
||||
}
|
||||
|
||||
var conn uint32
|
||||
machTaskSelf := getFunc[MachTaskSelfFunc](iokit.library, "mach_task_self")
|
||||
if result := iokit.IOServiceOpen(service, machTaskSelf(), 0, &conn); result != 0 {
|
||||
return nil, errors.New("ERROR: IOServiceOpen failed")
|
||||
}
|
||||
|
||||
iokit.IOObjectRelease(service)
|
||||
return &SMC{
|
||||
lib: iokit,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SMC) CallStruct(selector uint32, inputStruct, inputStructCnt, outputStruct uintptr, outputStructCnt *uintptr) int32 {
|
||||
return s.lib.IOConnectCallStructMethod(s.conn, selector, inputStruct, inputStructCnt, outputStruct, outputStructCnt)
|
||||
}
|
||||
|
||||
func (s *SMC) Close() error {
|
||||
if result := s.lib.IOServiceClose(s.conn); result != 0 {
|
||||
return errors.New("ERROR: IOServiceClose failed")
|
||||
}
|
||||
s.lib.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
type CStr []byte
|
||||
|
||||
func NewCStr(length int64) CStr {
|
||||
return make(CStr, length)
|
||||
}
|
||||
|
||||
func (s CStr) Length() int64 {
|
||||
return int64(len(s))
|
||||
}
|
||||
|
||||
func (s CStr) Ptr() *byte {
|
||||
if len(s) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &s[0]
|
||||
}
|
||||
|
||||
func (s CStr) Addr() uintptr {
|
||||
return uintptr(unsafe.Pointer(s.Ptr()))
|
||||
}
|
||||
|
||||
func (s CStr) GoString() string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var length int
|
||||
for _, char := range s {
|
||||
if char == '\x00' {
|
||||
break
|
||||
}
|
||||
length++
|
||||
}
|
||||
return string(s[:length])
|
||||
}
|
||||
|
||||
// https://github.com/ebitengine/purego/blob/main/internal/strings/strings.go#L26
|
||||
func GoString(cStr *byte) string {
|
||||
if cStr == nil {
|
||||
return ""
|
||||
}
|
||||
var length int
|
||||
for *(*byte)(unsafe.Add(unsafe.Pointer(cStr), uintptr(length))) != '\x00' {
|
||||
length++
|
||||
}
|
||||
return string(unsafe.Slice(cStr, length))
|
||||
}
|
||||
|
||||
// https://github.com/apple-oss-distributions/CF/blob/dc54c6bb1c1e5e0b9486c1d26dd5bef110b20bf3/CFString.c#L463
|
||||
func GetCFStringBufLengthForUTF8(length int64) int64 {
|
||||
if length > (math.MaxInt64 / 3) {
|
||||
return KCFNotFound
|
||||
}
|
||||
return length*3 + 1 // includes null terminator
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd || openbsd
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func SysctlUint(mib string) (uint64, error) {
|
||||
buf, err := unix.SysctlRaw(mib)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(buf) == 8 { // 64 bit
|
||||
return *(*uint64)(unsafe.Pointer(&buf[0])), nil
|
||||
}
|
||||
if len(buf) == 4 { // 32bit
|
||||
t := *(*uint32)(unsafe.Pointer(&buf[0]))
|
||||
return uint64(t), nil
|
||||
}
|
||||
return 0, fmt.Errorf("unexpected size: %s, %d", mib, len(buf))
|
||||
}
|
||||
|
||||
func CallSyscall(mib []int32) ([]byte, uint64, error) {
|
||||
mibptr := unsafe.Pointer(&mib[0])
|
||||
miblen := uint64(len(mib))
|
||||
|
||||
// get required buffer size
|
||||
length := uint64(0)
|
||||
_, _, err := unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
var b []byte
|
||||
return b, length, err
|
||||
}
|
||||
if length == 0 {
|
||||
var b []byte
|
||||
return b, length, err
|
||||
}
|
||||
// get proc info itself
|
||||
buf := make([]byte, length)
|
||||
_, _, err = unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
return buf, length, err
|
||||
}
|
||||
|
||||
return buf, length, nil
|
||||
}
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// cachedBootTime must be accessed via atomic.Load/StoreUint64
|
||||
var cachedBootTime uint64
|
||||
|
||||
func NumProcs() (uint64, error) {
|
||||
return NumProcsWithContext(context.Background())
|
||||
}
|
||||
|
||||
func NumProcsWithContext(ctx context.Context) (uint64, error) {
|
||||
f, err := os.Open(HostProcWithContext(ctx))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
list, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var cnt uint64
|
||||
|
||||
for _, v := range list {
|
||||
if _, err = strconv.ParseUint(v, 10, 64); err == nil {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
func BootTimeWithContext(ctx context.Context, enableCache bool) (uint64, error) {
|
||||
if enableCache {
|
||||
t := atomic.LoadUint64(&cachedBootTime)
|
||||
if t != 0 {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
system, role, err := VirtualizationWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
useStatFile := true
|
||||
if system == "lxc" && role == "guest" {
|
||||
// if lxc, /proc/uptime is used.
|
||||
useStatFile = false
|
||||
} else if system == "docker" && role == "guest" {
|
||||
// also docker, guest
|
||||
useStatFile = false
|
||||
}
|
||||
|
||||
if useStatFile {
|
||||
t, err := readBootTimeStat(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if enableCache {
|
||||
atomic.StoreUint64(&cachedBootTime, t)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
filename := HostProcWithContext(ctx, "uptime")
|
||||
lines, err := ReadLines(filename)
|
||||
if err != nil {
|
||||
return handleBootTimeFileReadErr(err)
|
||||
}
|
||||
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
|
||||
|
||||
if len(lines) != 1 {
|
||||
return 0, errors.New("wrong uptime format")
|
||||
}
|
||||
f := strings.Fields(lines[0])
|
||||
b, err := strconv.ParseFloat(f[0], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t := currentTime - b
|
||||
|
||||
if enableCache {
|
||||
atomic.StoreUint64(&cachedBootTime, uint64(t))
|
||||
}
|
||||
|
||||
return uint64(t), nil
|
||||
}
|
||||
|
||||
func handleBootTimeFileReadErr(err error) (uint64, error) {
|
||||
if !os.IsPermission(err) {
|
||||
return 0, err
|
||||
}
|
||||
var info syscall.Sysinfo_t
|
||||
err = syscall.Sysinfo(&info)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
currentTime := time.Now().UnixNano() / int64(time.Second)
|
||||
t := currentTime - int64(info.Uptime)
|
||||
return uint64(t), nil
|
||||
}
|
||||
|
||||
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, errors.New("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, errors.New("could not find btime")
|
||||
}
|
||||
|
||||
func Virtualization() (string, string, error) {
|
||||
return VirtualizationWithContext(context.Background())
|
||||
}
|
||||
|
||||
// required variables for concurrency safe virtualization caching
|
||||
var (
|
||||
cachedVirtMap map[string]string
|
||||
cachedVirtMutex sync.RWMutex
|
||||
cachedVirtOnce sync.Once
|
||||
)
|
||||
|
||||
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
||||
var system, role string
|
||||
|
||||
// if cached already, return from cache
|
||||
cachedVirtMutex.RLock() // unlock won't be deferred so concurrent reads don't wait for long
|
||||
if cachedVirtMap != nil {
|
||||
cachedSystem, cachedRole := cachedVirtMap["system"], cachedVirtMap["role"]
|
||||
cachedVirtMutex.RUnlock()
|
||||
return cachedSystem, cachedRole, nil
|
||||
}
|
||||
cachedVirtMutex.RUnlock()
|
||||
|
||||
filename := HostProcWithContext(ctx, "xen")
|
||||
if PathExists(filename) {
|
||||
system = "xen"
|
||||
role = "guest" // assume guest
|
||||
|
||||
if PathExists(filepath.Join(filename, "capabilities")) {
|
||||
contents, err := ReadLines(filepath.Join(filename, "capabilities"))
|
||||
if err == nil {
|
||||
if StringsContains(contents, "control_d") {
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProcWithContext(ctx, "modules")
|
||||
if PathExists(filename) {
|
||||
contents, err := ReadLines(filename)
|
||||
if err == nil {
|
||||
switch {
|
||||
case StringsContains(contents, "kvm"):
|
||||
system = "kvm"
|
||||
role = "host"
|
||||
case StringsContains(contents, "hv_util"):
|
||||
system = "hyperv"
|
||||
role = "guest"
|
||||
case StringsContains(contents, "vboxdrv"):
|
||||
system = "vbox"
|
||||
role = "host"
|
||||
case StringsContains(contents, "vboxguest"):
|
||||
system = "vbox"
|
||||
role = "guest"
|
||||
case StringsContains(contents, "vmware"):
|
||||
system = "vmware"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProcWithContext(ctx, "cpuinfo")
|
||||
if PathExists(filename) {
|
||||
contents, err := ReadLines(filename)
|
||||
if err == nil {
|
||||
if StringsContains(contents, "QEMU Virtual CPU") ||
|
||||
StringsContains(contents, "Common KVM processor") ||
|
||||
StringsContains(contents, "Common 32-bit KVM processor") {
|
||||
system = "kvm"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProcWithContext(ctx, "bus/pci/devices")
|
||||
if PathExists(filename) {
|
||||
contents, err := ReadLines(filename)
|
||||
if err == nil {
|
||||
if StringsContains(contents, "virtio-pci") {
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filename = HostProcWithContext(ctx)
|
||||
if PathExists(filepath.Join(filename, "bc", "0")) {
|
||||
system = "openvz"
|
||||
role = "host"
|
||||
} else if PathExists(filepath.Join(filename, "vz")) {
|
||||
system = "openvz"
|
||||
role = "guest"
|
||||
}
|
||||
|
||||
// not use dmidecode because it requires root
|
||||
if PathExists(filepath.Join(filename, "self", "status")) {
|
||||
contents, err := ReadLines(filepath.Join(filename, "self", "status"))
|
||||
if err == nil {
|
||||
if StringsContains(contents, "s_context:") ||
|
||||
StringsContains(contents, "VxID:") {
|
||||
system = "linux-vserver"
|
||||
}
|
||||
// TODO: guest or host
|
||||
}
|
||||
}
|
||||
|
||||
if PathExists(filepath.Join(filename, "1", "environ")) {
|
||||
contents, err := ReadFile(filepath.Join(filename, "1", "environ"))
|
||||
|
||||
if err == nil {
|
||||
if strings.Contains(contents, "container=lxc") {
|
||||
system = "lxc"
|
||||
role = "guest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if PathExists(filepath.Join(filename, "self", "cgroup")) {
|
||||
contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
|
||||
if err == nil {
|
||||
switch {
|
||||
case StringsContains(contents, "lxc"):
|
||||
system = "lxc"
|
||||
role = "guest"
|
||||
case StringsContains(contents, "docker"):
|
||||
system = "docker"
|
||||
role = "guest"
|
||||
case StringsContains(contents, "machine-rkt"):
|
||||
system = "rkt"
|
||||
role = "guest"
|
||||
case PathExists("/usr/bin/lxc-version"):
|
||||
system = "lxc"
|
||||
role = "host"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
defer cachedVirtMutex.Unlock()
|
||||
cachedVirtMap = map[string]string{
|
||||
"system": system,
|
||||
"role": role,
|
||||
}
|
||||
})
|
||||
|
||||
return system, role, nil
|
||||
}
|
||||
|
||||
func GetOSRelease() (platform, version string, err error) {
|
||||
return GetOSReleaseWithContext(context.Background())
|
||||
}
|
||||
|
||||
func GetOSReleaseWithContext(ctx context.Context) (platform, version string, err error) {
|
||||
contents, err := ReadLines(HostEtcWithContext(ctx, "os-release"))
|
||||
if err != nil {
|
||||
return "", "", nil // return empty
|
||||
}
|
||||
for _, line := range contents {
|
||||
field := strings.Split(line, "=")
|
||||
if len(field) < 2 {
|
||||
continue
|
||||
}
|
||||
switch field[0] {
|
||||
case "ID": // use ID for lowercase
|
||||
platform = trimQuotes(field[1])
|
||||
case "VERSION_ID":
|
||||
version = trimQuotes(field[1])
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup amazon ID
|
||||
if platform == "amzn" {
|
||||
platform = "amazon"
|
||||
}
|
||||
|
||||
return platform, version, nil
|
||||
}
|
||||
|
||||
// Remove quotes of the source string
|
||||
func trimQuotes(s string) string {
|
||||
if len(s) >= 2 {
|
||||
if s[0] == '"' && s[len(s)-1] == '"' {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build netbsd
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func CallSyscall(mib []int32) ([]byte, uint64, error) {
|
||||
mibptr := unsafe.Pointer(&mib[0])
|
||||
miblen := uint64(len(mib))
|
||||
|
||||
// get required buffer size
|
||||
length := uint64(0)
|
||||
_, _, err := unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
var b []byte
|
||||
return b, length, err
|
||||
}
|
||||
if length == 0 {
|
||||
var b []byte
|
||||
return b, length, err
|
||||
}
|
||||
// get proc info itself
|
||||
buf := make([]byte, length)
|
||||
_, _, err = unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
return buf, length, err
|
||||
}
|
||||
|
||||
return buf, length, nil
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func CallSyscall(mib []int32) ([]byte, uint64, error) {
|
||||
mibptr := unsafe.Pointer(&mib[0])
|
||||
miblen := uint64(len(mib))
|
||||
|
||||
// get required buffer size
|
||||
length := uint64(0)
|
||||
_, _, err := unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
var b []byte
|
||||
return b, length, err
|
||||
}
|
||||
if length == 0 {
|
||||
var b []byte
|
||||
return b, length, err
|
||||
}
|
||||
// get proc info itself
|
||||
buf := make([]byte, length)
|
||||
_, _, err = unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
return buf, length, err
|
||||
}
|
||||
|
||||
return buf, length, nil
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux || freebsd || darwin || openbsd
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args ...string) ([]string, error) {
|
||||
var cmd []string
|
||||
if pid == 0 { // will get from all processes.
|
||||
cmd = []string{"-a", "-n", "-P"}
|
||||
} else {
|
||||
cmd = []string{"-a", "-n", "-P", "-p", strconv.Itoa(int(pid))}
|
||||
}
|
||||
cmd = append(cmd, args...)
|
||||
out, err := invoke.CommandWithContext(ctx, "lsof", cmd...)
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
return []string{}, err
|
||||
}
|
||||
// if no pid found, lsof returns code 1.
|
||||
if err.Error() == "exit status 1" && len(out) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
|
||||
var ret []string
|
||||
for _, l := range lines[1:] {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, l)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/yusufpapurcu/wmi"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// for double values
|
||||
type PDH_FMT_COUNTERVALUE_DOUBLE struct { //nolint:revive //FIXME
|
||||
CStatus uint32
|
||||
DoubleValue float64
|
||||
}
|
||||
|
||||
// for 64 bit integer values
|
||||
type PDH_FMT_COUNTERVALUE_LARGE struct { //nolint:revive //FIXME
|
||||
CStatus uint32
|
||||
LargeValue int64
|
||||
}
|
||||
|
||||
// for long values
|
||||
type PDH_FMT_COUNTERVALUE_LONG struct { //nolint:revive //FIXME
|
||||
CStatus uint32
|
||||
LongValue int32
|
||||
padding [4]byte
|
||||
}
|
||||
|
||||
// windows system const
|
||||
const (
|
||||
ERROR_SUCCESS = 0
|
||||
ERROR_FILE_NOT_FOUND = 2
|
||||
DRIVE_REMOVABLE = 2
|
||||
DRIVE_FIXED = 3
|
||||
HKEY_LOCAL_MACHINE = 0x80000002
|
||||
RRF_RT_REG_SZ = 0x00000002
|
||||
RRF_RT_REG_DWORD = 0x00000010
|
||||
PDH_FMT_LONG = 0x00000100
|
||||
PDH_FMT_DOUBLE = 0x00000200
|
||||
PDH_FMT_LARGE = 0x00000400
|
||||
PDH_INVALID_DATA = 0xc0000bc6
|
||||
PDH_INVALID_HANDLE = 0xC0000bbc
|
||||
PDH_NO_DATA = 0x800007d5
|
||||
|
||||
STATUS_BUFFER_OVERFLOW = 0x80000005
|
||||
STATUS_BUFFER_TOO_SMALL = 0xC0000023
|
||||
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
|
||||
)
|
||||
|
||||
const (
|
||||
ProcessBasicInformation = 0
|
||||
ProcessWow64Information = 26
|
||||
ProcessQueryInformation = windows.PROCESS_DUP_HANDLE | windows.PROCESS_QUERY_INFORMATION
|
||||
|
||||
SystemExtendedHandleInformationClass = 64
|
||||
)
|
||||
|
||||
var (
|
||||
Modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
ModNt = windows.NewLazySystemDLL("ntdll.dll")
|
||||
ModPdh = windows.NewLazySystemDLL("pdh.dll")
|
||||
ModPsapi = windows.NewLazySystemDLL("psapi.dll")
|
||||
ModPowrProf = windows.NewLazySystemDLL("powrprof.dll")
|
||||
|
||||
ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes")
|
||||
ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation")
|
||||
ProcRtlGetNativeSystemInformation = ModNt.NewProc("RtlGetNativeSystemInformation")
|
||||
ProcRtlNtStatusToDosError = ModNt.NewProc("RtlNtStatusToDosError")
|
||||
ProcNtQueryInformationProcess = ModNt.NewProc("NtQueryInformationProcess")
|
||||
ProcNtReadVirtualMemory = ModNt.NewProc("NtReadVirtualMemory")
|
||||
ProcNtWow64QueryInformationProcess64 = ModNt.NewProc("NtWow64QueryInformationProcess64")
|
||||
ProcNtWow64ReadVirtualMemory64 = ModNt.NewProc("NtWow64ReadVirtualMemory64")
|
||||
|
||||
PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery")
|
||||
PdhAddEnglishCounterW = ModPdh.NewProc("PdhAddEnglishCounterW")
|
||||
PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData")
|
||||
PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue")
|
||||
PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery")
|
||||
|
||||
procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW")
|
||||
)
|
||||
|
||||
type FILETIME struct {
|
||||
DwLowDateTime uint32
|
||||
DwHighDateTime uint32
|
||||
}
|
||||
|
||||
// borrowed from net/interface_windows.go
|
||||
func BytePtrToString(p *uint8) string {
|
||||
a := (*[10000]uint8)(unsafe.Pointer(p))
|
||||
i := 0
|
||||
for a[i] != 0 {
|
||||
i++
|
||||
}
|
||||
return string(a[:i])
|
||||
}
|
||||
|
||||
// CounterInfo struct is used to track a windows performance counter
|
||||
// copied from https://github.com/mackerelio/mackerel-agent/
|
||||
type CounterInfo struct {
|
||||
PostName string
|
||||
CounterName string
|
||||
Counter windows.Handle
|
||||
}
|
||||
|
||||
// CreateQuery with a PdhOpenQuery call
|
||||
// copied from https://github.com/mackerelio/mackerel-agent/
|
||||
func CreateQuery() (windows.Handle, error) {
|
||||
var query windows.Handle
|
||||
r, _, err := PdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query)))
|
||||
if r != 0 {
|
||||
return 0, err
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// CreateCounter with a PdhAddEnglishCounterW call
|
||||
func CreateCounter(query windows.Handle, pname, cname string) (*CounterInfo, error) {
|
||||
var counter windows.Handle
|
||||
r, _, err := PdhAddEnglishCounterW.Call(
|
||||
uintptr(query),
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cname))),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&counter)))
|
||||
if r != 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &CounterInfo{
|
||||
PostName: pname,
|
||||
CounterName: cname,
|
||||
Counter: counter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCounterValue get counter value from handle
|
||||
// adapted from https://github.com/mackerelio/mackerel-agent/
|
||||
func GetCounterValue(counter windows.Handle) (float64, error) {
|
||||
var value PDH_FMT_COUNTERVALUE_DOUBLE
|
||||
r, _, err := PdhGetFormattedCounterValue.Call(uintptr(counter), PDH_FMT_DOUBLE, uintptr(0), uintptr(unsafe.Pointer(&value)))
|
||||
if r != 0 && r != PDH_INVALID_DATA {
|
||||
return 0.0, err
|
||||
}
|
||||
return value.DoubleValue, nil
|
||||
}
|
||||
|
||||
type Win32PerformanceCounter struct {
|
||||
PostName string
|
||||
CounterName string
|
||||
Query windows.Handle
|
||||
Counter windows.Handle
|
||||
}
|
||||
|
||||
func NewWin32PerformanceCounter(postName, counterName string) (*Win32PerformanceCounter, error) {
|
||||
query, err := CreateQuery()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
counter := Win32PerformanceCounter{
|
||||
Query: query,
|
||||
PostName: postName,
|
||||
CounterName: counterName,
|
||||
}
|
||||
r, _, err := PdhAddEnglishCounterW.Call(
|
||||
uintptr(counter.Query),
|
||||
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(counter.CounterName))),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&counter.Counter)),
|
||||
)
|
||||
if r != 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &counter, nil
|
||||
}
|
||||
|
||||
func (w *Win32PerformanceCounter) GetValue() (float64, error) {
|
||||
r, _, err := PdhCollectQueryData.Call(uintptr(w.Query))
|
||||
if r != 0 && err != nil {
|
||||
if r == PDH_NO_DATA {
|
||||
return 0.0, fmt.Errorf("%w: this counter has not data", err)
|
||||
}
|
||||
return 0.0, err
|
||||
}
|
||||
|
||||
return GetCounterValue(w.Counter)
|
||||
}
|
||||
|
||||
func ProcessorQueueLengthCounter() (*Win32PerformanceCounter, error) {
|
||||
return NewWin32PerformanceCounter("processor_queue_length", `\System\Processor Queue Length`)
|
||||
}
|
||||
|
||||
// WMIQueryWithContext - wraps wmi.Query with a timed-out context to avoid hanging
|
||||
func WMIQueryWithContext(ctx context.Context, query string, dst any, connectServerArgs ...any) error {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
ctxTimeout, cancel := context.WithTimeout(ctx, Timeout)
|
||||
defer cancel()
|
||||
ctx = ctxTimeout
|
||||
}
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
errChan <- wmi.Query(query, dst, connectServerArgs...)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-errChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Convert paths using native DOS format like:
|
||||
//
|
||||
// "\Device\HarddiskVolume1\Windows\systemew\file.txt"
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// "C:\Windows\systemew\file.txt"
|
||||
func ConvertDOSPath(p string) string {
|
||||
rawDrive := strings.Join(strings.Split(p, `\`)[:3], `\`)
|
||||
|
||||
for d := 'A'; d <= 'Z'; d++ {
|
||||
szDeviceName := string(d) + ":"
|
||||
szTarget := make([]uint16, 512)
|
||||
ret, _, _ := procQueryDosDeviceW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(szDeviceName))),
|
||||
uintptr(unsafe.Pointer(&szTarget[0])),
|
||||
uintptr(len(szTarget)))
|
||||
if ret != 0 && windows.UTF16ToString(szTarget) == rawDrive {
|
||||
return filepath.Join(szDeviceName, p[len(rawDrive):])
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type NtStatus uint32
|
||||
|
||||
func (s NtStatus) Error() error {
|
||||
if s == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("NtStatus 0x%08x", uint32(s))
|
||||
}
|
||||
|
||||
func (s NtStatus) IsError() bool {
|
||||
return s>>30 == 3
|
||||
}
|
||||
|
||||
type SystemExtendedHandleTableEntryInformation struct {
|
||||
Object uintptr
|
||||
UniqueProcessId uintptr
|
||||
HandleValue uintptr
|
||||
GrantedAccess uint32
|
||||
CreatorBackTraceIndex uint16
|
||||
ObjectTypeIndex uint16
|
||||
HandleAttributes uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
type SystemExtendedHandleInformation struct {
|
||||
NumberOfHandles uintptr
|
||||
Reserved uintptr
|
||||
Handles [1]SystemExtendedHandleTableEntryInformation
|
||||
}
|
||||
|
||||
// CallWithExpandingBuffer https://github.com/hillu/go-ntdll
|
||||
func CallWithExpandingBuffer(fn func() NtStatus, buf *[]byte, resultLength *uint32) NtStatus {
|
||||
for {
|
||||
st := fn()
|
||||
if st == STATUS_BUFFER_OVERFLOW || st == STATUS_BUFFER_TOO_SMALL || st == STATUS_INFO_LENGTH_MISMATCH {
|
||||
if int(*resultLength) <= cap(*buf) {
|
||||
(*reflect.SliceHeader)(unsafe.Pointer(buf)).Len = int(*resultLength)
|
||||
} else {
|
||||
*buf = make([]byte, int(*resultLength))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !st.IsError() {
|
||||
*buf = (*buf)[:int(*resultLength)]
|
||||
}
|
||||
return st
|
||||
}
|
||||
}
|
||||
|
||||
func NtQuerySystemInformation(
|
||||
SystemInformationClass uint32,
|
||||
SystemInformation *byte,
|
||||
SystemInformationLength uint32,
|
||||
ReturnLength *uint32,
|
||||
) NtStatus {
|
||||
r0, _, _ := ProcNtQuerySystemInformation.Call(
|
||||
uintptr(SystemInformationClass),
|
||||
uintptr(unsafe.Pointer(SystemInformation)),
|
||||
uintptr(SystemInformationLength),
|
||||
uintptr(unsafe.Pointer(ReturnLength)))
|
||||
return NtStatus(r0)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package common
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// IsLittleEndian checks if the current platform uses little-endian.
|
||||
// copied from https://github.com/ntrrg/ntgo/blob/v0.8.0/runtime/infrastructure.go#L16 (MIT License)
|
||||
func IsLittleEndian() bool {
|
||||
var x int16 = 0x0011
|
||||
return *(*byte)(unsafe.Pointer(&x)) == 0x11
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
b := make([]byte, syscall.PathMax)
|
||||
return &b
|
||||
},
|
||||
}
|
||||
|
||||
// The following three functions are copied from stdlib.
|
||||
|
||||
// ignoringEINTR2 is ignoringEINTR, but returning an additional value.
|
||||
func ignoringEINTR2[T any](fn func() (T, error)) (T, error) {
|
||||
for {
|
||||
v, err := fn()
|
||||
if !errors.Is(err, syscall.EINTR) {
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Many functions in package syscall return a count of -1 instead of 0.
|
||||
// Using fixCount(call()) instead of call() corrects the count.
|
||||
func fixCount(n int, err error) (int, error) {
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Readlink behaves like os.Readlink but caches the buffer passed to syscall.Readlink.
|
||||
func Readlink(name string) (string, error) {
|
||||
b := bufferPool.Get().(*[]byte)
|
||||
|
||||
n, err := ignoringEINTR2(func() (int, error) {
|
||||
return fixCount(syscall.Readlink(name, *b))
|
||||
})
|
||||
if err != nil {
|
||||
bufferPool.Put(b)
|
||||
return "", &os.PathError{Op: "readlink", Path: name, Err: err}
|
||||
}
|
||||
|
||||
result := string((*b)[:n])
|
||||
bufferPool.Put(b)
|
||||
return result, nil
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Sleep awaits for provided interval.
|
||||
// Can be interrupted by context cancellation.
|
||||
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
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
maxWarnings = 100 // An arbitrary limit to avoid excessive memory usage, it has no sense to store hundreds of errors
|
||||
tooManyErrorsMessage = "too many errors reported, next errors were discarded"
|
||||
numberOfWarningsMessage = "Number of warnings:"
|
||||
)
|
||||
|
||||
type Warnings struct {
|
||||
List []error
|
||||
tooManyErrors bool
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (w *Warnings) Add(err error) {
|
||||
if len(w.List) >= maxWarnings {
|
||||
w.tooManyErrors = true
|
||||
return
|
||||
}
|
||||
w.List = append(w.List, err)
|
||||
}
|
||||
|
||||
func (w *Warnings) Reference() error {
|
||||
if len(w.List) > 0 {
|
||||
return w
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Warnings) Error() string {
|
||||
if w.Verbose {
|
||||
str := ""
|
||||
var sb strings.Builder
|
||||
for i, e := range w.List {
|
||||
fmt.Fprintf(&sb, "\tError %d: %s\n", i, e.Error())
|
||||
}
|
||||
str += sb.String()
|
||||
if w.tooManyErrors {
|
||||
str += fmt.Sprintf("\t%s\n", tooManyErrorsMessage)
|
||||
}
|
||||
return str
|
||||
}
|
||||
if w.tooManyErrors {
|
||||
return fmt.Sprintf("%s > %v - %s", numberOfWarningsMessage, maxWarnings, tooManyErrorsMessage)
|
||||
}
|
||||
return fmt.Sprintf("%s %v", numberOfWarningsMessage, len(w.List))
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type ExVirtualMemory struct {
|
||||
ActiveFile uint64 `json:"activefile"`
|
||||
InactiveFile uint64 `json:"inactivefile"`
|
||||
ActiveAnon uint64 `json:"activeanon"`
|
||||
InactiveAnon uint64 `json:"inactiveanon"`
|
||||
Unevictable uint64 `json:"unevictable"`
|
||||
Percpu uint64 `json:"percpu"`
|
||||
KernelStack uint64 `json:"kernelstack"`
|
||||
}
|
||||
|
||||
func (v ExVirtualMemory) String() string {
|
||||
s, _ := json.Marshal(v)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
type ExLinux struct{}
|
||||
|
||||
func NewExLinux() *ExLinux {
|
||||
return &ExLinux{}
|
||||
}
|
||||
|
||||
func (ex *ExLinux) VirtualMemory() (*ExVirtualMemory, error) {
|
||||
return ex.VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (*ExLinux) VirtualMemoryWithContext(ctx context.Context) (*ExVirtualMemory, error) {
|
||||
_, vmEx, err := fillFromMeminfoWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vmEx, nil
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ExVirtualMemory represents Windows specific information
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-performance_information
|
||||
type ExVirtualMemory struct {
|
||||
CommitLimit uint64 `json:"commitLimit"`
|
||||
CommitTotal uint64 `json:"commitTotal"`
|
||||
VirtualTotal uint64 `json:"virtualTotal"`
|
||||
VirtualAvail uint64 `json:"virtualAvail"`
|
||||
PhysTotal uint64 `json:"physTotal"`
|
||||
PhysAvail uint64 `json:"physAvail"`
|
||||
PageFileTotal uint64 `json:"pageFileTotal"`
|
||||
PageFileAvail uint64 `json:"pageFileAvail"`
|
||||
}
|
||||
|
||||
type ExWindows struct{}
|
||||
|
||||
func NewExWindows() *ExWindows {
|
||||
return &ExWindows{}
|
||||
}
|
||||
|
||||
func (*ExWindows) VirtualMemory() (*ExVirtualMemory, error) {
|
||||
var memInfo memoryStatusEx
|
||||
memInfo.cbSize = uint32(unsafe.Sizeof(memInfo))
|
||||
// If mem == 0 since this is an error according to GlobalMemoryStatusEx documentation
|
||||
// In that case, use err which is constructed from GetLastError(),
|
||||
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
|
||||
mem, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo)))
|
||||
if mem == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var perfInfo performanceInformation
|
||||
perfInfo.cb = uint32(unsafe.Sizeof(perfInfo))
|
||||
// Analogous to above: perf == 0 is an error according to the GetPerformanceInfo documentation,
|
||||
// use err in that case
|
||||
perf, _, err := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&perfInfo)), uintptr(perfInfo.cb))
|
||||
if perf == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &ExVirtualMemory{
|
||||
CommitLimit: perfInfo.commitLimit * perfInfo.pageSize,
|
||||
CommitTotal: perfInfo.commitTotal * perfInfo.pageSize,
|
||||
VirtualTotal: memInfo.ullTotalVirtual,
|
||||
VirtualAvail: memInfo.ullAvailVirtual,
|
||||
PhysTotal: memInfo.ullTotalPhys,
|
||||
PhysAvail: memInfo.ullAvailPhys,
|
||||
PageFileTotal: memInfo.ullTotalPageFile,
|
||||
PageFileAvail: memInfo.ullAvailPageFile,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package mem
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var invoke common.Invoker = common.Invoke{}
|
||||
|
||||
// Memory usage statistics. Total, Available and Used contain numbers of bytes
|
||||
// for human consumption.
|
||||
//
|
||||
// The other fields in this struct contain kernel specific values.
|
||||
type VirtualMemoryStat struct {
|
||||
// Total amount of RAM on this system
|
||||
Total uint64 `json:"total"`
|
||||
|
||||
// RAM available for programs to allocate
|
||||
//
|
||||
// This value is computed from the kernel specific values.
|
||||
Available uint64 `json:"available"`
|
||||
|
||||
// RAM used by programs
|
||||
//
|
||||
// This value is computed from the kernel specific values.
|
||||
Used uint64 `json:"used"`
|
||||
|
||||
// Percentage of RAM used by programs
|
||||
//
|
||||
// This value is computed from the kernel specific values.
|
||||
UsedPercent float64 `json:"usedPercent"`
|
||||
|
||||
// This is the kernel's notion of free memory; RAM chips whose bits nobody
|
||||
// cares about the value of right now. For a human consumable number,
|
||||
// Available is what you really want.
|
||||
Free uint64 `json:"free"`
|
||||
|
||||
// OS X / BSD specific numbers:
|
||||
// http://www.macyourself.com/2010/02/17/what-is-free-wired-active-and-inactive-system-memory-ram/
|
||||
Active uint64 `json:"active"`
|
||||
Inactive uint64 `json:"inactive"`
|
||||
Wired uint64 `json:"wired"`
|
||||
|
||||
// FreeBSD specific numbers:
|
||||
// https://reviews.freebsd.org/D8467
|
||||
Laundry uint64 `json:"laundry"`
|
||||
|
||||
// Linux specific numbers
|
||||
// https://blogs.oracle.com/linux/understanding-linux-kernel-memory-statistics
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
// https://www.kernel.org/doc/Documentation/vm/overcommit-accounting
|
||||
// https://www.kernel.org/doc/Documentation/vm/transhuge.txt
|
||||
//
|
||||
Buffers uint64 `json:"buffers"`
|
||||
Cached uint64 `json:"cached"`
|
||||
WriteBack uint64 `json:"writeBack"`
|
||||
Dirty uint64 `json:"dirty"`
|
||||
WriteBackTmp uint64 `json:"writeBackTmp"`
|
||||
Shared uint64 `json:"shared"`
|
||||
Slab uint64 `json:"slab"`
|
||||
Sreclaimable uint64 `json:"sreclaimable"`
|
||||
Sunreclaim uint64 `json:"sunreclaim"`
|
||||
PageTables uint64 `json:"pageTables"`
|
||||
SwapCached uint64 `json:"swapCached"`
|
||||
CommitLimit uint64 `json:"commitLimit"`
|
||||
CommittedAS uint64 `json:"committedAS"`
|
||||
HighTotal uint64 `json:"highTotal"`
|
||||
HighFree uint64 `json:"highFree"`
|
||||
LowTotal uint64 `json:"lowTotal"`
|
||||
LowFree uint64 `json:"lowFree"`
|
||||
SwapTotal uint64 `json:"swapTotal"`
|
||||
SwapFree uint64 `json:"swapFree"`
|
||||
Mapped uint64 `json:"mapped"`
|
||||
VmallocTotal uint64 `json:"vmallocTotal"`
|
||||
VmallocUsed uint64 `json:"vmallocUsed"`
|
||||
VmallocChunk uint64 `json:"vmallocChunk"`
|
||||
HugePagesTotal uint64 `json:"hugePagesTotal"`
|
||||
HugePagesFree uint64 `json:"hugePagesFree"`
|
||||
HugePagesRsvd uint64 `json:"hugePagesRsvd"`
|
||||
HugePagesSurp uint64 `json:"hugePagesSurp"`
|
||||
HugePageSize uint64 `json:"hugePageSize"`
|
||||
AnonHugePages uint64 `json:"anonHugePages"`
|
||||
}
|
||||
|
||||
type SwapMemoryStat struct {
|
||||
Total uint64 `json:"total"`
|
||||
Used uint64 `json:"used"`
|
||||
Free uint64 `json:"free"`
|
||||
UsedPercent float64 `json:"usedPercent"`
|
||||
Sin uint64 `json:"sin"`
|
||||
Sout uint64 `json:"sout"`
|
||||
PgIn uint64 `json:"pgIn"`
|
||||
PgOut uint64 `json:"pgOut"`
|
||||
PgFault uint64 `json:"pgFault"`
|
||||
|
||||
// Linux specific numbers
|
||||
// https://www.kernel.org/doc/Documentation/cgroup-v2.txt
|
||||
PgMajFault uint64 `json:"pgMajFault"`
|
||||
}
|
||||
|
||||
func (m VirtualMemoryStat) String() string {
|
||||
s, _ := json.Marshal(m)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (m SwapMemoryStat) String() string {
|
||||
s, _ := json.Marshal(m)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
type SwapDevice struct {
|
||||
Name string `json:"name"`
|
||||
UsedBytes uint64 `json:"usedBytes"`
|
||||
FreeBytes uint64 `json:"freeBytes"`
|
||||
}
|
||||
|
||||
func (m SwapDevice) String() string {
|
||||
s, _ := json.Marshal(m)
|
||||
return string(s)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && cgo
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/power-devops/perfstat"
|
||||
)
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
m, err := perfstat.MemoryTotalStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pagesize := uint64(4096)
|
||||
ret := VirtualMemoryStat{
|
||||
Total: uint64(m.RealTotal) * pagesize,
|
||||
Available: uint64(m.RealAvailable) * pagesize,
|
||||
Free: uint64(m.RealFree) * pagesize,
|
||||
Used: uint64(m.RealInUse) * pagesize,
|
||||
UsedPercent: 100 * float64(m.RealInUse) / float64(m.RealTotal),
|
||||
Active: uint64(m.VirtualActive) * pagesize,
|
||||
SwapTotal: uint64(m.PgSpTotal) * pagesize,
|
||||
SwapFree: uint64(m.PgSpFree) * pagesize,
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
m, err := perfstat.MemoryTotalStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pagesize := uint64(4096)
|
||||
swapUsed := uint64(m.PgSpTotal-m.PgSpFree-m.PgSpRsvd) * pagesize
|
||||
swapTotal := uint64(m.PgSpTotal) * pagesize
|
||||
ret := SwapMemoryStat{
|
||||
Total: swapTotal,
|
||||
Free: uint64(m.PgSpFree) * pagesize,
|
||||
Used: swapUsed,
|
||||
UsedPercent: float64(100*swapUsed) / float64(swapTotal),
|
||||
Sin: uint64(m.PgSpIn),
|
||||
Sout: uint64(m.PgSpOut),
|
||||
PgIn: uint64(m.PageIn),
|
||||
PgOut: uint64(m.PageOut),
|
||||
PgFault: uint64(m.PageFaults),
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && !cgo
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
vmem, swap, err := callSVMon(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if vmem.Total == 0 {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
vmem.SwapTotal = swap.Total
|
||||
vmem.SwapFree = swap.Free
|
||||
return vmem, nil
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
_, swap, err := callSVMon(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if swap.Total == 0 {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
return swap, nil
|
||||
}
|
||||
|
||||
func callSVMon(ctx context.Context, virt bool) (*VirtualMemoryStat, *SwapMemoryStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "svmon", "-G")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pagesize := uint64(4096)
|
||||
vmem := &VirtualMemoryStat{}
|
||||
swap := &SwapMemoryStat{}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if virt && strings.HasPrefix(line, "memory") {
|
||||
p := strings.Fields(line)
|
||||
if len(p) > 2 {
|
||||
if t, err := strconv.ParseUint(p[1], 10, 64); err == nil {
|
||||
vmem.Total = t * pagesize
|
||||
}
|
||||
if t, err := strconv.ParseUint(p[2], 10, 64); err == nil {
|
||||
vmem.Used = t * pagesize
|
||||
if vmem.Total > 0 {
|
||||
vmem.UsedPercent = 100 * float64(vmem.Used) / float64(vmem.Total)
|
||||
}
|
||||
}
|
||||
if t, err := strconv.ParseUint(p[3], 10, 64); err == nil {
|
||||
vmem.Free = t * pagesize
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(line, "pg space") {
|
||||
p := strings.Fields(line)
|
||||
if len(p) > 3 {
|
||||
if t, err := strconv.ParseUint(p[2], 10, 64); err == nil {
|
||||
swap.Total = t * pagesize
|
||||
}
|
||||
if t, err := strconv.ParseUint(p[3], 10, 64); err == nil {
|
||||
swap.Free = swap.Total - t*pagesize
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return vmem, swap, nil
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd || openbsd || netbsd
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const swapCommand = "swapctl"
|
||||
|
||||
// swapctl column indexes
|
||||
const (
|
||||
nameCol = 0
|
||||
totalKiBCol = 1
|
||||
usedKiBCol = 2
|
||||
)
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
|
||||
output, err := invoke.CommandWithContext(ctx, swapCommand, "-lk")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err)
|
||||
}
|
||||
|
||||
return parseSwapctlOutput(string(output))
|
||||
}
|
||||
|
||||
func parseSwapctlOutput(output string) ([]*SwapDevice, error) {
|
||||
lines := strings.Split(output, "\n")
|
||||
if len(lines) == 0 {
|
||||
return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output)
|
||||
}
|
||||
|
||||
// Check header headerFields are as expected.
|
||||
header := lines[0]
|
||||
header = strings.ToLower(header)
|
||||
header = strings.ReplaceAll(header, ":", "")
|
||||
headerFields := strings.Fields(header)
|
||||
if len(headerFields) < usedKiBCol {
|
||||
return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, header)
|
||||
}
|
||||
if headerFields[nameCol] != "device" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "device")
|
||||
}
|
||||
if headerFields[totalKiBCol] != "1kb-blocks" && headerFields[totalKiBCol] != "1k-blocks" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalKiBCol], "1kb-blocks")
|
||||
}
|
||||
if headerFields[usedKiBCol] != "used" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[usedKiBCol], "used")
|
||||
}
|
||||
|
||||
var swapDevices []*SwapDevice
|
||||
for _, line := range lines[1:] {
|
||||
if line == "" {
|
||||
continue // the terminal line is typically empty
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < usedKiBCol {
|
||||
return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand)
|
||||
}
|
||||
|
||||
totalKiB, err := strconv.ParseUint(fields[totalKiBCol], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err)
|
||||
}
|
||||
|
||||
usedKiB, err := strconv.ParseUint(fields[usedKiBCol], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err)
|
||||
}
|
||||
|
||||
swapDevices = append(swapDevices, &SwapDevice{
|
||||
Name: fields[nameCol],
|
||||
UsedBytes: usedKiB * 1024,
|
||||
FreeBytes: (totalKiB - usedKiB) * 1024,
|
||||
})
|
||||
}
|
||||
|
||||
return swapDevices, nil
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func getHwMemsize() (uint64, error) {
|
||||
total, err := unix.SysctlUint64("hw.memsize")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// xsw_usage in sys/sysctl.h
|
||||
type swapUsage struct {
|
||||
Total uint64
|
||||
Avail uint64
|
||||
Used uint64
|
||||
Pagesize int32
|
||||
Encrypted bool
|
||||
}
|
||||
|
||||
// SwapMemory returns swapinfo.
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(_ context.Context) (*SwapMemoryStat, error) {
|
||||
// https://github.com/yanllearnn/go-osstat/blob/ae8a279d26f52ec946a03698c7f50a26cfb427e3/memory/memory_darwin.go
|
||||
var ret *SwapMemoryStat
|
||||
|
||||
value, err := unix.SysctlRaw("vm.swapusage")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
if len(value) != 32 {
|
||||
return ret, fmt.Errorf("unexpected output of sysctl vm.swapusage: %v (len: %d)", value, len(value))
|
||||
}
|
||||
swap := (*swapUsage)(unsafe.Pointer(&value[0]))
|
||||
|
||||
u := float64(0)
|
||||
if swap.Total != 0 {
|
||||
u = ((float64(swap.Total) - float64(swap.Avail)) / float64(swap.Total)) * 100.0
|
||||
}
|
||||
|
||||
ret = &SwapMemoryStat{
|
||||
Total: swap.Total,
|
||||
Used: swap.Used,
|
||||
Free: swap.Avail,
|
||||
UsedPercent: u,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(_ context.Context) ([]*SwapDevice, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
type vmStatisticsData struct {
|
||||
freeCount uint32
|
||||
activeCount uint32
|
||||
inactiveCount uint32
|
||||
wireCount uint32
|
||||
_ [44]byte // Not used here
|
||||
}
|
||||
|
||||
// VirtualMemory returns VirtualmemoryStat.
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
|
||||
sys, err := common.NewSystemLib()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sys.Close()
|
||||
|
||||
count := uint32(common.HOST_VM_INFO_COUNT)
|
||||
var vmstat vmStatisticsData
|
||||
|
||||
status := sys.HostStatistics(sys.MachHostSelf(), common.HOST_VM_INFO,
|
||||
uintptr(unsafe.Pointer(&vmstat)), &count)
|
||||
|
||||
if status != common.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("host_statistics error=%d", status)
|
||||
}
|
||||
|
||||
pageSizeAddr, _ := sys.Dlsym("vm_kernel_page_size")
|
||||
pageSize := **(**uint64)(unsafe.Pointer(&pageSizeAddr))
|
||||
total, err := getHwMemsize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalCount := uint32(total / pageSize)
|
||||
|
||||
availableCount := vmstat.inactiveCount + vmstat.freeCount
|
||||
usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount)
|
||||
|
||||
usedCount := totalCount - availableCount
|
||||
|
||||
return &VirtualMemoryStat{
|
||||
Total: total,
|
||||
Available: pageSize * uint64(availableCount),
|
||||
Used: pageSize * uint64(usedCount),
|
||||
UsedPercent: usedPercent,
|
||||
Free: pageSize * uint64(vmstat.freeCount),
|
||||
Active: pageSize * uint64(vmstat.activeCount),
|
||||
Inactive: pageSize * uint64(vmstat.inactiveCount),
|
||||
Wired: pageSize * uint64(vmstat.wireCount),
|
||||
}, nil
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !darwin && !linux && !freebsd && !openbsd && !solaris && !windows && !plan9 && !aix && !netbsd
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(_ context.Context) (*SwapMemoryStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(_ context.Context) ([]*SwapDevice, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
|
||||
pageSize, err := common.SysctlUint("vm.stats.vm.v_page_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
physmem, err := common.SysctlUint("hw.physmem")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
free, err := common.SysctlUint("vm.stats.vm.v_free_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
active, err := common.SysctlUint("vm.stats.vm.v_active_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inactive, err := common.SysctlUint("vm.stats.vm.v_inactive_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffers, err := common.SysctlUint("vfs.bufspace")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wired, err := common.SysctlUint("vm.stats.vm.v_wire_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cached, laundry uint64
|
||||
osreldate, _ := common.SysctlUint("kern.osreldate")
|
||||
if osreldate < 1102000 {
|
||||
cached, err = common.SysctlUint("vm.stats.vm.v_cache_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
laundry, err = common.SysctlUint("vm.stats.vm.v_laundry_count")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p := pageSize
|
||||
ret := &VirtualMemoryStat{
|
||||
Total: physmem,
|
||||
Free: free * p,
|
||||
Active: active * p,
|
||||
Inactive: inactive * p,
|
||||
Cached: cached * p,
|
||||
Buffers: buffers,
|
||||
Wired: wired * p,
|
||||
Laundry: laundry * p,
|
||||
}
|
||||
|
||||
ret.Available = ret.Inactive + ret.Cached + ret.Free + ret.Laundry
|
||||
ret.Used = ret.Total - ret.Available
|
||||
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Return swapinfo
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Constants from vm/vm_param.h
|
||||
const (
|
||||
XSWDEV_VERSION11 = 1
|
||||
XSWDEV_VERSION = 2
|
||||
)
|
||||
|
||||
// Types from vm/vm_param.h
|
||||
type xswdev struct {
|
||||
Version uint32 // Version is the version
|
||||
Dev uint64 // Dev is the device identifier
|
||||
Flags int32 // Flags is the swap flags applied to the device
|
||||
NBlks int32 // NBlks is the total number of blocks
|
||||
Used int32 // Used is the number of blocks used
|
||||
}
|
||||
|
||||
// xswdev11 is a compatibility for under FreeBSD 11
|
||||
// sys/vm/swap_pager.c
|
||||
type xswdev11 struct {
|
||||
Version uint32 // Version is the version
|
||||
Dev uint32 // Dev is the device identifier
|
||||
Flags int32 // Flags is the swap flags applied to the device
|
||||
NBlks int32 // NBlks is the total number of blocks
|
||||
Used int32 // Used is the number of blocks used
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(_ context.Context) (*SwapMemoryStat, error) {
|
||||
// FreeBSD can have multiple swap devices so we total them up
|
||||
i, err := common.SysctlUint("vm.nswapdev")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
return nil, errors.New("no swap devices found")
|
||||
}
|
||||
|
||||
c := int(i)
|
||||
|
||||
i, err = common.SysctlUint("vm.stats.vm.v_page_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageSize := i
|
||||
|
||||
var buf []byte
|
||||
s := &SwapMemoryStat{}
|
||||
for n := 0; n < c; n++ {
|
||||
buf, err = unix.SysctlRaw("vm.swap_info", n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first, try to parse with version 2
|
||||
xsw := (*xswdev)(unsafe.Pointer(&buf[0]))
|
||||
switch {
|
||||
case xsw.Version == XSWDEV_VERSION11:
|
||||
// this is version 1, so try to parse again
|
||||
xsw := (*xswdev11)(unsafe.Pointer(&buf[0]))
|
||||
if xsw.Version != XSWDEV_VERSION11 {
|
||||
return nil, errors.New("xswdev version mismatch(11)")
|
||||
}
|
||||
s.Total += uint64(xsw.NBlks)
|
||||
s.Used += uint64(xsw.Used)
|
||||
case xsw.Version != XSWDEV_VERSION:
|
||||
return nil, errors.New("xswdev version mismatch")
|
||||
default:
|
||||
s.Total += uint64(xsw.NBlks)
|
||||
s.Used += uint64(xsw.Used)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if s.Total != 0 {
|
||||
s.UsedPercent = float64(s.Used) / float64(s.Total) * 100
|
||||
}
|
||||
s.Total *= pageSize
|
||||
s.Used *= pageSize
|
||||
s.Free = s.Total - s.Used
|
||||
|
||||
return s, nil
|
||||
}
|
||||
+524
@@ -0,0 +1,524 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
vm, _, err := fillFromMeminfoWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *ExVirtualMemory, error) {
|
||||
filename := common.HostProcWithContext(ctx, "meminfo")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("couldn't read %s: %w", filename, err)
|
||||
}
|
||||
|
||||
// flag if MemAvailable is in /proc/meminfo (kernel 3.14+)
|
||||
memavail := false
|
||||
activeFile := false // "Active(file)" not available: 2.6.28 / Dec 2008
|
||||
inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008
|
||||
sReclaimable := false // "Sreclaimable:" not available: 2.6.19 / Nov 2006
|
||||
|
||||
ret := &VirtualMemoryStat{}
|
||||
retEx := &ExVirtualMemory{}
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(fields[0])
|
||||
value := strings.TrimSpace(fields[1])
|
||||
value = strings.ReplaceAll(value, " kB", "")
|
||||
|
||||
switch key {
|
||||
case "MemTotal":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Total = t * 1024
|
||||
case "MemFree":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Free = t * 1024
|
||||
case "MemAvailable":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
memavail = true
|
||||
ret.Available = t * 1024
|
||||
case "Buffers":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Buffers = t * 1024
|
||||
case "Cached":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Cached = t * 1024
|
||||
case "Active":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Active = t * 1024
|
||||
case "Inactive":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Inactive = t * 1024
|
||||
case "Active(anon)":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
retEx.ActiveAnon = t * 1024
|
||||
case "Inactive(anon)":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
retEx.InactiveAnon = t * 1024
|
||||
case "Active(file)":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
activeFile = true
|
||||
retEx.ActiveFile = t * 1024
|
||||
case "Inactive(file)":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
inactiveFile = true
|
||||
retEx.InactiveFile = t * 1024
|
||||
case "Unevictable":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
retEx.Unevictable = t * 1024
|
||||
case "Percpu":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
retEx.Percpu = t * 1024
|
||||
case "Writeback":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.WriteBack = t * 1024
|
||||
case "WritebackTmp":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.WriteBackTmp = t * 1024
|
||||
case "Dirty":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Dirty = t * 1024
|
||||
case "Shmem":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Shared = t * 1024
|
||||
case "Slab":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Slab = t * 1024
|
||||
case "SReclaimable":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
sReclaimable = true
|
||||
ret.Sreclaimable = t * 1024
|
||||
case "SUnreclaim":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Sunreclaim = t * 1024
|
||||
case "KernelStack":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
retEx.KernelStack = t * 1024
|
||||
case "PageTables":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.PageTables = t * 1024
|
||||
case "SwapCached":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.SwapCached = t * 1024
|
||||
case "CommitLimit":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.CommitLimit = t * 1024
|
||||
case "Committed_AS":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.CommittedAS = t * 1024
|
||||
case "HighTotal":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HighTotal = t * 1024
|
||||
case "HighFree":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HighFree = t * 1024
|
||||
case "LowTotal":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.LowTotal = t * 1024
|
||||
case "LowFree":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.LowFree = t * 1024
|
||||
case "SwapTotal":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.SwapTotal = t * 1024
|
||||
case "SwapFree":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.SwapFree = t * 1024
|
||||
case "Mapped":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.Mapped = t * 1024
|
||||
case "VmallocTotal":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.VmallocTotal = t * 1024
|
||||
case "VmallocUsed":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.VmallocUsed = t * 1024
|
||||
case "VmallocChunk":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.VmallocChunk = t * 1024
|
||||
case "HugePages_Total":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HugePagesTotal = t
|
||||
case "HugePages_Free":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HugePagesFree = t
|
||||
case "HugePages_Rsvd":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HugePagesRsvd = t
|
||||
case "HugePages_Surp":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HugePagesSurp = t
|
||||
case "Hugepagesize":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.HugePageSize = t * 1024
|
||||
case "AnonHugePages":
|
||||
t, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return ret, retEx, err
|
||||
}
|
||||
ret.AnonHugePages = t * 1024
|
||||
}
|
||||
}
|
||||
|
||||
ret.Cached += ret.Sreclaimable
|
||||
|
||||
if !memavail {
|
||||
if activeFile && inactiveFile && sReclaimable {
|
||||
ret.Available = calculateAvailVmem(ctx, ret, retEx)
|
||||
} else {
|
||||
ret.Available = ret.Cached + ret.Free
|
||||
}
|
||||
}
|
||||
ret.Used = ret.Total - ret.Available
|
||||
|
||||
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
|
||||
|
||||
return ret, retEx, nil
|
||||
}
|
||||
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
sysinfo := &unix.Sysinfo_t{}
|
||||
|
||||
if err := unix.Sysinfo(sysinfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &SwapMemoryStat{
|
||||
Total: uint64(sysinfo.Totalswap) * uint64(sysinfo.Unit),
|
||||
Free: uint64(sysinfo.Freeswap) * uint64(sysinfo.Unit),
|
||||
}
|
||||
ret.Used = ret.Total - ret.Free
|
||||
// check Infinity
|
||||
if ret.Total != 0 {
|
||||
ret.UsedPercent = float64(ret.Total-ret.Free) / float64(ret.Total) * 100.0
|
||||
} else {
|
||||
ret.UsedPercent = 0
|
||||
}
|
||||
filename := common.HostProcWithContext(ctx, "vmstat")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't read %s: %w", filename, err)
|
||||
}
|
||||
for _, l := range lines {
|
||||
fields := strings.Fields(l)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "pswpin":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.Sin = value * 4 * 1024
|
||||
case "pswpout":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.Sout = value * 4 * 1024
|
||||
case "pgpgin":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgIn = value * 4 * 1024
|
||||
case "pgpgout":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgOut = value * 4 * 1024
|
||||
case "pgfault":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgFault = value * 4 * 1024
|
||||
case "pgmajfault":
|
||||
value, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret.PgMajFault = value * 4 * 1024
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// 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(ctx context.Context, ret *VirtualMemoryStat, retEx *ExVirtualMemory) uint64 {
|
||||
var watermarkLow uint64
|
||||
|
||||
fn := common.HostProcWithContext(ctx, "zoneinfo")
|
||||
lines, err := common.ReadLines(fn)
|
||||
if err != nil {
|
||||
return ret.Free + ret.Cached // fallback under kernel 2.6.13
|
||||
}
|
||||
|
||||
pagesize := uint64(os.Getpagesize())
|
||||
watermarkLow = 0
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
if strings.HasPrefix(fields[0], "low") {
|
||||
lowValue, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
lowValue = 0
|
||||
}
|
||||
watermarkLow += lowValue
|
||||
}
|
||||
}
|
||||
|
||||
watermarkLow *= pagesize
|
||||
|
||||
availMemory := ret.Free - watermarkLow
|
||||
pageCache := retEx.ActiveFile + retEx.InactiveFile
|
||||
pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow)))
|
||||
availMemory += pageCache
|
||||
availMemory += ret.Sreclaimable - uint64(math.Min(float64(ret.Sreclaimable/2.0), float64(watermarkLow)))
|
||||
|
||||
if availMemory < 0 {
|
||||
availMemory = 0
|
||||
}
|
||||
|
||||
return availMemory
|
||||
}
|
||||
|
||||
const swapsFilename = "swaps"
|
||||
|
||||
// swaps file column indexes
|
||||
const (
|
||||
nameCol = 0
|
||||
// typeCol = 1
|
||||
totalCol = 2
|
||||
usedCol = 3
|
||||
// priorityCol = 4
|
||||
)
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
|
||||
swapsFilePath := common.HostProcWithContext(ctx, swapsFilename)
|
||||
f, err := os.Open(swapsFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return parseSwapsFile(ctx, f)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected end-of-file in %q", swapsFilePath)
|
||||
|
||||
}
|
||||
|
||||
// Check header headerFields are as expected
|
||||
headerFields := strings.Fields(scanner.Text())
|
||||
if len(headerFields) < usedCol {
|
||||
return nil, fmt.Errorf("couldn't parse %q: too few fields in header", swapsFilePath)
|
||||
}
|
||||
if headerFields[nameCol] != "Filename" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[nameCol], "Filename")
|
||||
}
|
||||
if headerFields[totalCol] != "Size" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[totalCol], "Size")
|
||||
}
|
||||
if headerFields[usedCol] != "Used" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[usedCol], "Used")
|
||||
}
|
||||
|
||||
var swapDevices []*SwapDevice
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) < usedCol {
|
||||
return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsFilePath)
|
||||
}
|
||||
|
||||
totalKiB, err := strconv.ParseUint(fields[totalCol], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsFilePath, err)
|
||||
}
|
||||
|
||||
usedKiB, err := strconv.ParseUint(fields[usedCol], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsFilePath, err)
|
||||
}
|
||||
|
||||
swapDevices = append(swapDevices, &SwapDevice{
|
||||
Name: fields[nameCol],
|
||||
UsedBytes: usedKiB * 1024,
|
||||
FreeBytes: (totalKiB - usedKiB) * 1024,
|
||||
})
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err)
|
||||
}
|
||||
|
||||
return swapDevices, nil
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build netbsd
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func GetPageSize() (uint64, error) {
|
||||
return GetPageSizeWithContext(context.Background())
|
||||
}
|
||||
|
||||
func GetPageSizeWithContext(_ context.Context) (uint64, error) {
|
||||
uvmexp, err := unix.SysctlUvmexp("vm.uvmexp2")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(uvmexp.Pagesize), nil
|
||||
}
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
|
||||
uvmexp, err := unix.SysctlUvmexp("vm.uvmexp2")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := uint64(uvmexp.Pagesize)
|
||||
|
||||
ret := &VirtualMemoryStat{
|
||||
Total: uint64(uvmexp.Npages) * p,
|
||||
Free: uint64(uvmexp.Free) * p,
|
||||
Active: uint64(uvmexp.Active) * p,
|
||||
Inactive: uint64(uvmexp.Inactive) * p,
|
||||
Cached: 0, // not available
|
||||
Wired: uint64(uvmexp.Wired) * p,
|
||||
}
|
||||
|
||||
ret.Available = ret.Inactive + ret.Cached + ret.Free
|
||||
ret.Used = ret.Total - ret.Available
|
||||
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
|
||||
|
||||
// Get buffers from vm.bufmem sysctl
|
||||
ret.Buffers, err = unix.SysctlUint64("vm.bufmem")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Return swapctl summary info
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "swapctl", "-sk")
|
||||
if err != nil {
|
||||
return &SwapMemoryStat{}, nil
|
||||
}
|
||||
|
||||
line := string(out)
|
||||
var total, used, free uint64
|
||||
|
||||
_, err = fmt.Sscanf(line,
|
||||
"total: %d 1K-blocks allocated, %d used, %d available",
|
||||
&total, &used, &free)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse swapctl output")
|
||||
}
|
||||
|
||||
percent := float64(used) / float64(total) * 100
|
||||
return &SwapMemoryStat{
|
||||
Total: total * 1024,
|
||||
Used: used * 1024,
|
||||
Free: free * 1024,
|
||||
UsedPercent: percent,
|
||||
}, nil
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func GetPageSize() (uint64, error) {
|
||||
return GetPageSizeWithContext(context.Background())
|
||||
}
|
||||
|
||||
func GetPageSizeWithContext(_ context.Context) (uint64, error) {
|
||||
uvmexp, err := unix.SysctlUvmexp("vm.uvmexp")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(uvmexp.Pagesize), nil
|
||||
}
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
|
||||
uvmexp, err := unix.SysctlUvmexp("vm.uvmexp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := uint64(uvmexp.Pagesize)
|
||||
|
||||
mib := []int32{CTLVfs, VfsGeneric, VfsBcacheStat}
|
||||
buf, length, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < sizeOfBcachestats {
|
||||
return nil, fmt.Errorf("short syscall ret %d bytes", length)
|
||||
}
|
||||
var bcs Bcachestats
|
||||
br := bytes.NewReader(buf)
|
||||
if err := binary.Read(br, binary.LittleEndian, &bcs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// On OpenBSD, the buffer cache is the closest equivalent to both
|
||||
// Linux's Buffers and Cached memory.
|
||||
bcache := uint64(bcs.Numbufpages) * p
|
||||
|
||||
ret := &VirtualMemoryStat{
|
||||
Total: uint64(uvmexp.Npages) * p,
|
||||
Free: uint64(uvmexp.Free) * p,
|
||||
Active: uint64(uvmexp.Active) * p,
|
||||
Inactive: uint64(uvmexp.Inactive) * p,
|
||||
Cached: bcache,
|
||||
Buffers: bcache,
|
||||
Wired: uint64(uvmexp.Wired) * p,
|
||||
}
|
||||
|
||||
ret.Available = ret.Inactive + ret.Cached + ret.Free
|
||||
ret.Used = ret.Total - ret.Available
|
||||
ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Return swapctl summary info
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "swapctl", "-sk")
|
||||
if err != nil {
|
||||
return &SwapMemoryStat{}, nil
|
||||
}
|
||||
|
||||
line := string(out)
|
||||
var total, used, free uint64
|
||||
|
||||
_, err = fmt.Sscanf(line,
|
||||
"total: %d 1K-blocks allocated, %d used, %d available",
|
||||
&total, &used, &free)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse swapctl output")
|
||||
}
|
||||
|
||||
percent := float64(used) / float64(total) * 100
|
||||
return &SwapMemoryStat{
|
||||
Total: total * 1024,
|
||||
Used: used * 1024,
|
||||
Free: free * 1024,
|
||||
UsedPercent: percent,
|
||||
}, nil
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && 386
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs mem/types_openbsd.go
|
||||
|
||||
package mem
|
||||
|
||||
const (
|
||||
CTLVfs = 10
|
||||
VfsGeneric = 0
|
||||
VfsBcacheStat = 3
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfBcachestats = 0x90
|
||||
)
|
||||
|
||||
type Bcachestats struct {
|
||||
Numbufs int64
|
||||
Numbufpages int64
|
||||
Numdirtypages int64
|
||||
Numcleanpages int64
|
||||
Pendingwrites int64
|
||||
Pendingreads int64
|
||||
Numwrites int64
|
||||
Numreads int64
|
||||
Cachehits int64
|
||||
Busymapped int64
|
||||
Dmapages int64
|
||||
Highpages int64
|
||||
Delwribufs int64
|
||||
Kvaslots int64
|
||||
Avail int64
|
||||
Highflips int64
|
||||
Highflops int64
|
||||
Dmaflips int64
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// cgo -godefs types_openbsd.go
|
||||
|
||||
package mem
|
||||
|
||||
const (
|
||||
CTLVfs = 10
|
||||
VfsGeneric = 0
|
||||
VfsBcacheStat = 3
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfBcachestats = 0x78
|
||||
)
|
||||
|
||||
type Bcachestats struct {
|
||||
Numbufs int64
|
||||
Numbufpages int64
|
||||
Numdirtypages int64
|
||||
Numcleanpages int64
|
||||
Pendingwrites int64
|
||||
Pendingreads int64
|
||||
Numwrites int64
|
||||
Numreads int64
|
||||
Cachehits int64
|
||||
Busymapped int64
|
||||
Dmapages int64
|
||||
Highpages int64
|
||||
Delwribufs int64
|
||||
Kvaslots int64
|
||||
Avail int64
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && arm
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs mem/types_openbsd.go
|
||||
|
||||
package mem
|
||||
|
||||
const (
|
||||
CTLVfs = 10
|
||||
VfsGeneric = 0
|
||||
VfsBcacheStat = 3
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfBcachestats = 0x90
|
||||
)
|
||||
|
||||
type Bcachestats struct {
|
||||
Numbufs int64
|
||||
Numbufpages int64
|
||||
Numdirtypages int64
|
||||
Numcleanpages int64
|
||||
Pendingwrites int64
|
||||
Pendingreads int64
|
||||
Numwrites int64
|
||||
Numreads int64
|
||||
Cachehits int64
|
||||
Busymapped int64
|
||||
Dmapages int64
|
||||
Highpages int64
|
||||
Delwribufs int64
|
||||
Kvaslots int64
|
||||
Avail int64
|
||||
Highflips int64
|
||||
Highflops int64
|
||||
Dmaflips int64
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && arm64
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs mem/types_openbsd.go
|
||||
|
||||
package mem
|
||||
|
||||
const (
|
||||
CTLVfs = 10
|
||||
VfsGeneric = 0
|
||||
VfsBcacheStat = 3
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfBcachestats = 0x90
|
||||
)
|
||||
|
||||
type Bcachestats struct {
|
||||
Numbufs int64
|
||||
Numbufpages int64
|
||||
Numdirtypages int64
|
||||
Numcleanpages int64
|
||||
Pendingwrites int64
|
||||
Pendingreads int64
|
||||
Numwrites int64
|
||||
Numreads int64
|
||||
Cachehits int64
|
||||
Busymapped int64
|
||||
Dmapages int64
|
||||
Highpages int64
|
||||
Delwribufs int64
|
||||
Kvaslots int64
|
||||
Avail int64
|
||||
Highflips int64
|
||||
Highflops int64
|
||||
Dmaflips int64
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && riscv64
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs mem/types_openbsd.go
|
||||
|
||||
package mem
|
||||
|
||||
const (
|
||||
CTLVfs = 10
|
||||
VfsGeneric = 0
|
||||
VfsBcacheStat = 3
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfBcachestats = 0x90
|
||||
)
|
||||
|
||||
type Bcachestats struct {
|
||||
Numbufs int64
|
||||
Numbufpages int64
|
||||
Numdirtypages int64
|
||||
Numcleanpages int64
|
||||
Pendingwrites int64
|
||||
Pendingreads int64
|
||||
Numwrites int64
|
||||
Numreads int64
|
||||
Cachehits int64
|
||||
Busymapped int64
|
||||
Dmapages int64
|
||||
Highpages int64
|
||||
Delwribufs int64
|
||||
Kvaslots int64
|
||||
Avail int64
|
||||
Highflips int64
|
||||
Highflops int64
|
||||
Dmaflips int64
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
stats "github.com/lufia/plan9stats"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
|
||||
root := os.Getenv("HOST_ROOT")
|
||||
m, err := stats.ReadMemStats(ctx, stats.WithRootDir(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := 0.0
|
||||
if m.SwapPages.Avail != 0 {
|
||||
u = float64(m.SwapPages.Used) / float64(m.SwapPages.Avail) * 100.0
|
||||
}
|
||||
return &SwapMemoryStat{
|
||||
Total: uint64(m.SwapPages.Avail * m.PageSize),
|
||||
Used: uint64(m.SwapPages.Used * m.PageSize),
|
||||
Free: uint64(m.SwapPages.Free() * m.PageSize),
|
||||
UsedPercent: u,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
root := os.Getenv("HOST_ROOT")
|
||||
m, err := stats.ReadMemStats(ctx, stats.WithRootDir(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := 0.0
|
||||
if m.UserPages.Avail != 0 {
|
||||
u = float64(m.UserPages.Used) / float64(m.UserPages.Avail) * 100.0
|
||||
}
|
||||
return &VirtualMemoryStat{
|
||||
Total: uint64(m.Total),
|
||||
Available: uint64(m.UserPages.Free() * m.PageSize),
|
||||
Used: uint64(m.UserPages.Used * m.PageSize),
|
||||
UsedPercent: u,
|
||||
Free: uint64(m.UserPages.Free() * m.PageSize),
|
||||
|
||||
SwapTotal: uint64(m.SwapPages.Avail * m.PageSize),
|
||||
SwapFree: uint64(m.SwapPages.Free() * m.PageSize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(_ context.Context) ([]*SwapDevice, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build solaris
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tklauser/go-sysconf"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// VirtualMemory for Solaris is a minimal implementation which only returns
|
||||
// what Nomad needs. It does take into account global vs zone, however.
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
|
||||
result := &VirtualMemoryStat{}
|
||||
|
||||
zoneName, err := zoneName(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if zoneName == "global" {
|
||||
capacity, err := globalZoneMemoryCapacity(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Total = capacity
|
||||
freemem, err := globalZoneFreeMemory(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Available = freemem
|
||||
result.Free = freemem
|
||||
result.Used = result.Total - result.Free
|
||||
} else {
|
||||
capacity, err := nonGlobalZoneMemoryCapacity(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Total = capacity
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(_ context.Context) (*SwapMemoryStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func zoneName(ctx context.Context) (string, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "zonename")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
var globalZoneMemoryCapacityMatch = regexp.MustCompile(`[Mm]emory size: (\d+) Megabytes`)
|
||||
|
||||
func globalZoneMemoryCapacity(ctx context.Context) (uint64, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "prtconf")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
match := globalZoneMemoryCapacityMatch.FindAllStringSubmatch(string(out), -1)
|
||||
if len(match) != 1 {
|
||||
return 0, errors.New("memory size not contained in output of prtconf")
|
||||
}
|
||||
|
||||
totalMB, err := strconv.ParseUint(match[0][1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
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(ctx context.Context) (uint64, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "kstat", "-p", "-c", "zone_memory_cap", "memory_cap:*:*:physcap")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
kstats := kstatMatch.FindAllStringSubmatch(string(out), -1)
|
||||
if len(kstats) != 1 {
|
||||
return 0, fmt.Errorf("expected 1 kstat, found %d", len(kstats))
|
||||
}
|
||||
|
||||
memSizeBytes, err := strconv.ParseUint(kstats[0][2], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return memSizeBytes, nil
|
||||
}
|
||||
|
||||
const swapCommand = "swap"
|
||||
|
||||
// The blockSize as reported by `swap -l`. See https://docs.oracle.com/cd/E23824_01/html/821-1459/fsswap-52195.html
|
||||
const blockSize = 512
|
||||
|
||||
// swapctl column indexes
|
||||
const (
|
||||
nameCol = 0
|
||||
// devCol = 1
|
||||
// swaploCol = 2
|
||||
totalBlocksCol = 3
|
||||
freeBlocksCol = 4
|
||||
)
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
|
||||
output, err := invoke.CommandWithContext(ctx, swapCommand, "-l")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err)
|
||||
}
|
||||
|
||||
return parseSwapsCommandOutput(string(output))
|
||||
}
|
||||
|
||||
func parseSwapsCommandOutput(output string) ([]*SwapDevice, error) {
|
||||
lines := strings.Split(output, "\n")
|
||||
if len(lines) == 0 {
|
||||
return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output)
|
||||
}
|
||||
|
||||
// Check header headerFields are as expected.
|
||||
headerFields := strings.Fields(lines[0])
|
||||
if len(headerFields) < freeBlocksCol {
|
||||
return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, lines[0])
|
||||
}
|
||||
if headerFields[nameCol] != "swapfile" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "swapfile")
|
||||
}
|
||||
if headerFields[totalBlocksCol] != "blocks" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalBlocksCol], "blocks")
|
||||
}
|
||||
if headerFields[freeBlocksCol] != "free" {
|
||||
return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[freeBlocksCol], "free")
|
||||
}
|
||||
|
||||
var swapDevices []*SwapDevice
|
||||
for _, line := range lines[1:] {
|
||||
if line == "" {
|
||||
continue // the terminal line is typically empty
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < freeBlocksCol {
|
||||
return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand)
|
||||
}
|
||||
|
||||
totalBlocks, err := strconv.ParseUint(fields[totalBlocksCol], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err)
|
||||
}
|
||||
|
||||
freeBlocks, err := strconv.ParseUint(fields[freeBlocksCol], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err)
|
||||
}
|
||||
|
||||
swapDevices = append(swapDevices, &SwapDevice{
|
||||
Name: fields[nameCol],
|
||||
UsedBytes: (totalBlocks - freeBlocks) * blockSize,
|
||||
FreeBytes: freeBlocks * blockSize,
|
||||
})
|
||||
}
|
||||
|
||||
return swapDevices, nil
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
procEnumPageFilesW = common.ModPsapi.NewProc("EnumPageFilesW")
|
||||
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
||||
procGetPerformanceInfo = common.ModPsapi.NewProc("GetPerformanceInfo")
|
||||
procGlobalMemoryStatusEx = common.Modkernel32.NewProc("GlobalMemoryStatusEx")
|
||||
)
|
||||
|
||||
type memoryStatusEx struct {
|
||||
cbSize uint32
|
||||
dwMemoryLoad uint32
|
||||
ullTotalPhys uint64 // in bytes
|
||||
ullAvailPhys uint64
|
||||
ullTotalPageFile uint64
|
||||
ullAvailPageFile uint64
|
||||
ullTotalVirtual uint64
|
||||
ullAvailVirtual uint64
|
||||
ullAvailExtendedVirtual uint64
|
||||
}
|
||||
|
||||
func VirtualMemory() (*VirtualMemoryStat, error) {
|
||||
return VirtualMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func VirtualMemoryWithContext(_ context.Context) (*VirtualMemoryStat, error) {
|
||||
var memInfo memoryStatusEx
|
||||
memInfo.cbSize = uint32(unsafe.Sizeof(memInfo))
|
||||
// GlobalMemoryStatusEx returns 0 for error, in which case we check err,
|
||||
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
|
||||
mem, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo)))
|
||||
if mem == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &VirtualMemoryStat{
|
||||
Total: memInfo.ullTotalPhys,
|
||||
Available: memInfo.ullAvailPhys,
|
||||
Free: memInfo.ullAvailPhys,
|
||||
UsedPercent: float64(memInfo.dwMemoryLoad),
|
||||
}
|
||||
|
||||
ret.Used = ret.Total - ret.Available
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type performanceInformation struct {
|
||||
cb uint32
|
||||
commitTotal uint64
|
||||
commitLimit uint64
|
||||
commitPeak uint64
|
||||
physicalTotal uint64
|
||||
physicalAvailable uint64
|
||||
systemCache uint64
|
||||
kernelTotal uint64
|
||||
kernelPaged uint64
|
||||
kernelNonpaged uint64
|
||||
pageSize uint64
|
||||
handleCount uint32
|
||||
processCount uint32
|
||||
threadCount uint32
|
||||
}
|
||||
|
||||
func SwapMemory() (*SwapMemoryStat, error) {
|
||||
return SwapMemoryWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapMemoryWithContext(_ context.Context) (*SwapMemoryStat, error) {
|
||||
// Use the performance counter to get the swap usage percentage
|
||||
counter, err := common.NewWin32PerformanceCounter("swap_percentage", `\Paging File(_Total)\% Usage`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer common.PdhCloseQuery.Call(uintptr(counter.Query))
|
||||
|
||||
usedPercent, err := counter.GetValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get total memory from performance information
|
||||
var perfInfo performanceInformation
|
||||
perfInfo.cb = uint32(unsafe.Sizeof(perfInfo))
|
||||
// GetPerformanceInfo returns 0 for error, in which case we check err,
|
||||
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
|
||||
mem, _, err := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&perfInfo)), uintptr(perfInfo.cb))
|
||||
if mem == 0 {
|
||||
return nil, err
|
||||
}
|
||||
totalPhys := perfInfo.physicalTotal * perfInfo.pageSize
|
||||
totalSys := perfInfo.commitLimit * perfInfo.pageSize
|
||||
total := totalSys - totalPhys
|
||||
|
||||
var used uint64
|
||||
if total > 0 {
|
||||
used = uint64(0.01 * usedPercent * float64(total))
|
||||
} else {
|
||||
usedPercent = 0.0
|
||||
used = 0
|
||||
}
|
||||
|
||||
ret := &SwapMemoryStat{
|
||||
Total: total,
|
||||
Used: used,
|
||||
Free: total - used,
|
||||
UsedPercent: common.Round(usedPercent, 1),
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var (
|
||||
pageSize uint64
|
||||
pageSizeOnce sync.Once
|
||||
)
|
||||
|
||||
type systemInfo struct {
|
||||
wProcessorArchitecture uint16
|
||||
wReserved uint16
|
||||
dwPageSize uint32
|
||||
lpMinimumApplicationAddress uintptr
|
||||
lpMaximumApplicationAddress uintptr
|
||||
dwActiveProcessorMask uintptr
|
||||
dwNumberOfProcessors uint32
|
||||
dwProcessorType uint32
|
||||
dwAllocationGranularity uint32
|
||||
wProcessorLevel uint16
|
||||
wProcessorRevision uint16
|
||||
}
|
||||
|
||||
// system type as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-enum_page_file_information
|
||||
type enumPageFileInformation struct {
|
||||
cb uint32
|
||||
reserved uint32
|
||||
totalSize uint64
|
||||
totalInUse uint64
|
||||
peakUsage uint64
|
||||
}
|
||||
|
||||
func SwapDevices() ([]*SwapDevice, error) {
|
||||
return SwapDevicesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func SwapDevicesWithContext(_ context.Context) ([]*SwapDevice, error) {
|
||||
pageSizeOnce.Do(func() {
|
||||
var sysInfo systemInfo
|
||||
procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&sysInfo)))
|
||||
pageSize = uint64(sysInfo.dwPageSize)
|
||||
})
|
||||
|
||||
// the following system call invokes the supplied callback function once for each page file before returning
|
||||
// see https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumpagefilesw
|
||||
var swapDevices []*SwapDevice
|
||||
// EnumPageFilesW returns 0 for error, in which case we check err,
|
||||
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
|
||||
result, _, err := procEnumPageFilesW.Call(windows.NewCallback(pEnumPageFileCallbackW), uintptr(unsafe.Pointer(&swapDevices)))
|
||||
if result == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return swapDevices, nil
|
||||
}
|
||||
|
||||
// system callback as defined in https://docs.microsoft.com/en-us/windows/win32/api/psapi/nc-psapi-penum_page_file_callbackw
|
||||
func pEnumPageFileCallbackW(swapDevices *[]*SwapDevice, enumPageFileInfo *enumPageFileInformation, lpFilenamePtr *[syscall.MAX_LONG_PATH]uint16) *bool {
|
||||
*swapDevices = append(*swapDevices, &SwapDevice{
|
||||
Name: syscall.UTF16ToString((*lpFilenamePtr)[:]),
|
||||
UsedBytes: enumPageFileInfo.totalInUse * pageSize,
|
||||
FreeBytes: (enumPageFileInfo.totalSize - enumPageFileInfo.totalInUse) * pageSize,
|
||||
})
|
||||
|
||||
// return true to continue enumerating page files
|
||||
ret := true
|
||||
return &ret
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var invoke common.Invoker = common.Invoke{}
|
||||
|
||||
type IOCountersStat struct {
|
||||
Name string `json:"name"` // interface name
|
||||
BytesSent uint64 `json:"bytesSent"` // number of bytes sent
|
||||
BytesRecv uint64 `json:"bytesRecv"` // number of bytes received
|
||||
PacketsSent uint64 `json:"packetsSent"` // number of packets sent
|
||||
PacketsRecv uint64 `json:"packetsRecv"` // number of packets received
|
||||
Errin uint64 `json:"errin"` // total number of errors while receiving
|
||||
Errout uint64 `json:"errout"` // total number of errors while sending
|
||||
Dropin uint64 `json:"dropin"` // total number of incoming packets which were dropped
|
||||
Dropout uint64 `json:"dropout"` // total number of outgoing packets which were dropped (always 0 on OSX and BSD)
|
||||
Fifoin uint64 `json:"fifoin"` // total number of FIFO buffers errors while receiving
|
||||
Fifoout uint64 `json:"fifoout"` // total number of FIFO buffers errors while sending
|
||||
}
|
||||
|
||||
// Addr is implemented compatibility to psutil
|
||||
type Addr struct {
|
||||
IP string `json:"ip"`
|
||||
Port uint32 `json:"port"`
|
||||
}
|
||||
|
||||
type ConnectionStat struct {
|
||||
Fd uint32 `json:"fd"`
|
||||
Family uint32 `json:"family"`
|
||||
Type uint32 `json:"type"`
|
||||
Laddr Addr `json:"localaddr"`
|
||||
Raddr Addr `json:"remoteaddr"`
|
||||
Status string `json:"status"`
|
||||
Uids []int32 `json:"uids"`
|
||||
Pid int32 `json:"pid"`
|
||||
}
|
||||
|
||||
// System wide stats about different network protocols
|
||||
type ProtoCountersStat struct {
|
||||
Protocol string `json:"protocol"`
|
||||
Stats map[string]int64 `json:"stats"`
|
||||
}
|
||||
|
||||
// NetInterfaceAddr is designed for represent interface addresses
|
||||
type InterfaceAddr struct {
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
|
||||
// InterfaceAddrList is a list of InterfaceAddr
|
||||
type InterfaceAddrList []InterfaceAddr
|
||||
|
||||
type InterfaceStat struct {
|
||||
Index int `json:"index"`
|
||||
MTU int `json:"mtu"` // maximum transmission unit
|
||||
Name string `json:"name"` // e.g., "en0", "lo0", "eth0.100"
|
||||
HardwareAddr string `json:"hardwareAddr"` // IEEE MAC-48, EUI-48 and EUI-64 form
|
||||
Flags []string `json:"flags"` // e.g., FlagUp, FlagLoopback, FlagMulticast
|
||||
Addrs InterfaceAddrList `json:"addrs"`
|
||||
}
|
||||
|
||||
// InterfaceStatList is a list of InterfaceStat
|
||||
type InterfaceStatList []InterfaceStat
|
||||
|
||||
type FilterStat struct {
|
||||
ConnTrackCount int64 `json:"connTrackCount"`
|
||||
ConnTrackMax int64 `json:"connTrackMax"`
|
||||
}
|
||||
|
||||
// ConntrackStat has conntrack summary info
|
||||
type ConntrackStat struct {
|
||||
Entries uint32 `json:"entries"` // Number of entries in the conntrack table
|
||||
Searched uint32 `json:"searched"` // Number of conntrack table lookups performed
|
||||
Found uint32 `json:"found"` // Number of searched entries which were successful
|
||||
New uint32 `json:"new"` // Number of entries added which were not expected before
|
||||
Invalid uint32 `json:"invalid"` // Number of packets seen which can not be tracked
|
||||
Ignore uint32 `json:"ignore"` // Packets seen which are already connected to an entry
|
||||
Delete uint32 `json:"delete"` // Number of entries which were removed
|
||||
DeleteList uint32 `json:"deleteList"` // Number of entries which were put to dying list
|
||||
Insert uint32 `json:"insert"` // Number of entries inserted into the list
|
||||
InsertFailed uint32 `json:"insertFailed"` // # insertion attempted but failed (same entry exists)
|
||||
Drop uint32 `json:"drop"` // Number of packets dropped due to conntrack failure.
|
||||
EarlyDrop uint32 `json:"earlyDrop"` // Dropped entries to make room for new ones, if maxsize reached
|
||||
IcmpError uint32 `json:"icmpError"` // Subset of invalid. Packets that can't be tracked d/t error
|
||||
ExpectNew uint32 `json:"expectNew"` // Entries added after an expectation was already present
|
||||
ExpectCreate uint32 `json:"expectCreate"` // Expectations added
|
||||
ExpectDelete uint32 `json:"expectDelete"` // Expectations deleted
|
||||
SearchRestart uint32 `json:"searchRestart"` // Conntrack table lookups restarted due to hashtable resizes
|
||||
}
|
||||
|
||||
func NewConntrackStat(e, s, f, n, inv, ign, del, dlst, ins, insfail, drop, edrop, ie, en, ec, ed, sr uint32) *ConntrackStat {
|
||||
return &ConntrackStat{
|
||||
Entries: e,
|
||||
Searched: s,
|
||||
Found: f,
|
||||
New: n,
|
||||
Invalid: inv,
|
||||
Ignore: ign,
|
||||
Delete: del,
|
||||
DeleteList: dlst,
|
||||
Insert: ins,
|
||||
InsertFailed: insfail,
|
||||
Drop: drop,
|
||||
EarlyDrop: edrop,
|
||||
IcmpError: ie,
|
||||
ExpectNew: en,
|
||||
ExpectCreate: ec,
|
||||
ExpectDelete: ed,
|
||||
SearchRestart: sr,
|
||||
}
|
||||
}
|
||||
|
||||
type ConntrackStatList struct {
|
||||
items []*ConntrackStat
|
||||
}
|
||||
|
||||
func NewConntrackStatList() *ConntrackStatList {
|
||||
return &ConntrackStatList{
|
||||
items: []*ConntrackStat{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ConntrackStatList) Append(c *ConntrackStat) {
|
||||
l.items = append(l.items, c)
|
||||
}
|
||||
|
||||
func (l *ConntrackStatList) Items() []ConntrackStat {
|
||||
items := make([]ConntrackStat, len(l.items))
|
||||
for i, el := range l.items {
|
||||
items[i] = *el
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Summary returns a single-element list with totals from all list items.
|
||||
func (l *ConntrackStatList) Summary() []ConntrackStat {
|
||||
summary := NewConntrackStat(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
for _, cs := range l.items {
|
||||
summary.Entries += cs.Entries
|
||||
summary.Searched += cs.Searched
|
||||
summary.Found += cs.Found
|
||||
summary.New += cs.New
|
||||
summary.Invalid += cs.Invalid
|
||||
summary.Ignore += cs.Ignore
|
||||
summary.Delete += cs.Delete
|
||||
summary.DeleteList += cs.DeleteList
|
||||
summary.Insert += cs.Insert
|
||||
summary.InsertFailed += cs.InsertFailed
|
||||
summary.Drop += cs.Drop
|
||||
summary.EarlyDrop += cs.EarlyDrop
|
||||
summary.IcmpError += cs.IcmpError
|
||||
summary.ExpectNew += cs.ExpectNew
|
||||
summary.ExpectCreate += cs.ExpectCreate
|
||||
summary.ExpectDelete += cs.ExpectDelete
|
||||
summary.SearchRestart += cs.SearchRestart
|
||||
}
|
||||
return []ConntrackStat{*summary}
|
||||
}
|
||||
|
||||
func (n IOCountersStat) String() string {
|
||||
s, _ := json.Marshal(n)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (n ConnectionStat) String() string {
|
||||
s, _ := json.Marshal(n)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (n ProtoCountersStat) String() string {
|
||||
s, _ := json.Marshal(n)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (a Addr) String() string {
|
||||
s, _ := json.Marshal(a)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (n InterfaceStat) String() string {
|
||||
s, _ := json.Marshal(n)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (l InterfaceStatList) String() string {
|
||||
s, _ := json.Marshal(l)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (n InterfaceAddr) String() string {
|
||||
s, _ := json.Marshal(n)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (n ConntrackStat) String() string {
|
||||
s, _ := json.Marshal(n)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func Interfaces() (InterfaceStatList, error) {
|
||||
return InterfacesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func InterfacesWithContext(_ context.Context) (InterfaceStatList, error) {
|
||||
is, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make(InterfaceStatList, 0, len(is))
|
||||
for _, ifi := range is {
|
||||
|
||||
var flags []string
|
||||
if ifi.Flags&net.FlagUp != 0 {
|
||||
flags = append(flags, "up")
|
||||
}
|
||||
if ifi.Flags&net.FlagBroadcast != 0 {
|
||||
flags = append(flags, "broadcast")
|
||||
}
|
||||
if ifi.Flags&net.FlagLoopback != 0 {
|
||||
flags = append(flags, "loopback")
|
||||
}
|
||||
if ifi.Flags&net.FlagPointToPoint != 0 {
|
||||
flags = append(flags, "pointtopoint")
|
||||
}
|
||||
if ifi.Flags&net.FlagMulticast != 0 {
|
||||
flags = append(flags, "multicast")
|
||||
}
|
||||
|
||||
r := InterfaceStat{
|
||||
Index: ifi.Index,
|
||||
Name: ifi.Name,
|
||||
MTU: ifi.MTU,
|
||||
HardwareAddr: ifi.HardwareAddr.String(),
|
||||
Flags: flags,
|
||||
}
|
||||
addrs, err := ifi.Addrs()
|
||||
if err == nil {
|
||||
r.Addrs = make(InterfaceAddrList, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
r.Addrs = append(r.Addrs, InterfaceAddr{
|
||||
Addr: addr.String(),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
ret = append(ret, r)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getIOCountersAll(n []IOCountersStat) []IOCountersStat {
|
||||
r := IOCountersStat{
|
||||
Name: "all",
|
||||
}
|
||||
for _, nic := range n {
|
||||
r.BytesRecv += nic.BytesRecv
|
||||
r.PacketsRecv += nic.PacketsRecv
|
||||
r.Errin += nic.Errin
|
||||
r.Dropin += nic.Dropin
|
||||
r.BytesSent += nic.BytesSent
|
||||
r.PacketsSent += nic.PacketsSent
|
||||
r.Errout += nic.Errout
|
||||
r.Dropout += nic.Dropout
|
||||
}
|
||||
|
||||
return []IOCountersStat{r}
|
||||
}
|
||||
|
||||
// IOCounters returns 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 IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) {
|
||||
return IOCountersByFileWithContext(context.Background(), pernic, filename)
|
||||
}
|
||||
|
||||
// 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:
|
||||
// [ip,icmp,icmpmsg,tcp,udp,udplite]
|
||||
// Not Implemented for FreeBSD, Windows, OpenBSD, Darwin
|
||||
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
|
||||
return ProtoCountersWithContext(context.Background(), protocols)
|
||||
}
|
||||
|
||||
// FilterCounters returns iptables conntrack statistics
|
||||
// the currently in use conntrack count and the max.
|
||||
// If the file does not exist or is invalid it will return nil.
|
||||
func FilterCounters() ([]FilterStat, error) {
|
||||
return FilterCountersWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ConntrackStats returns more detailed info about the conntrack table
|
||||
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
|
||||
return ConntrackStatsWithContext(context.Background(), percpu)
|
||||
}
|
||||
|
||||
// Return a list of network connections opened.
|
||||
func Connections(kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsWithContext(context.Background(), kind)
|
||||
}
|
||||
|
||||
// Return a list of network connections opened returning at most `max`
|
||||
// connections for each running process.
|
||||
func ConnectionsMax(kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithContext(context.Background(), kind, maxConn)
|
||||
}
|
||||
|
||||
// Return a list of network connections opened, omitting `Uids`.
|
||||
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
|
||||
// removed from the API in the future.
|
||||
func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsWithoutUidsWithContext(context.Background(), kind)
|
||||
}
|
||||
|
||||
// Return a list of network connections opened by a process.
|
||||
func ConnectionsPid(kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidWithContext(context.Background(), kind, pid)
|
||||
}
|
||||
|
||||
// Return a list of network connections opened, omitting `Uids`.
|
||||
// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be
|
||||
// removed from the API in the future.
|
||||
func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUids(kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, maxConn)
|
||||
}
|
||||
|
||||
// Return up to `max` network connections opened by a process.
|
||||
func ConnectionsPidMax(kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(context.Background(), kind, pid, maxConn)
|
||||
}
|
||||
|
||||
// Pids retunres all pids.
|
||||
// Note: this is a copy of process_linux.Pids()
|
||||
// FIXME: Import process occurs import cycle.
|
||||
// move to common made other platform breaking. Need consider.
|
||||
func Pids() ([]int32, error) {
|
||||
return PidsWithContext(context.Background())
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func parseNetstatNetLine(line string) (ConnectionStat, error) {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 5 {
|
||||
return ConnectionStat{}, fmt.Errorf("wrong line,%s", line)
|
||||
}
|
||||
|
||||
var netType, netFamily uint32
|
||||
switch f[0] {
|
||||
case "tcp", "tcp4":
|
||||
netType = syscall.SOCK_STREAM
|
||||
netFamily = syscall.AF_INET
|
||||
case "udp", "udp4":
|
||||
netType = syscall.SOCK_DGRAM
|
||||
netFamily = syscall.AF_INET
|
||||
case "tcp6":
|
||||
netType = syscall.SOCK_STREAM
|
||||
netFamily = syscall.AF_INET6
|
||||
case "udp6":
|
||||
netType = syscall.SOCK_DGRAM
|
||||
netFamily = syscall.AF_INET6
|
||||
default:
|
||||
return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0])
|
||||
}
|
||||
|
||||
laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily)
|
||||
if err != nil {
|
||||
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4])
|
||||
}
|
||||
|
||||
n := ConnectionStat{
|
||||
Fd: uint32(0), // not supported
|
||||
Family: uint32(netFamily),
|
||||
Type: uint32(netType),
|
||||
Laddr: laddr,
|
||||
Raddr: raddr,
|
||||
Pid: int32(0), // not supported
|
||||
}
|
||||
if len(f) == 6 {
|
||||
n.Status = f[5]
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`)
|
||||
|
||||
func parseAddr(l string, family uint32) (Addr, error) {
|
||||
matches := portMatch.FindStringSubmatch(l)
|
||||
if matches == nil {
|
||||
return Addr{}, fmt.Errorf("wrong addr, %s", l)
|
||||
}
|
||||
host := matches[1]
|
||||
port := matches[2]
|
||||
if host == "*" {
|
||||
switch family {
|
||||
case syscall.AF_INET:
|
||||
host = "0.0.0.0"
|
||||
case syscall.AF_INET6:
|
||||
host = "::"
|
||||
default:
|
||||
return Addr{}, fmt.Errorf("unknown family, %d", family)
|
||||
}
|
||||
}
|
||||
lport, err := strconv.ParseInt(port, 10, 32)
|
||||
if err != nil {
|
||||
return Addr{}, err
|
||||
}
|
||||
return Addr{IP: host, Port: uint32(lport)}, nil
|
||||
}
|
||||
|
||||
// This function only works for netstat returning addresses with a "."
|
||||
// before the port (0.0.0.0.22 instead of 0.0.0.0:22).
|
||||
func parseNetstatAddr(local, remote string, family uint32) (laddr, raddr Addr, err error) {
|
||||
laddr, err = parseAddr(local, family)
|
||||
if remote != "*.*" { // remote addr exists
|
||||
raddr, err = parseAddr(remote, family)
|
||||
if err != nil {
|
||||
return laddr, raddr, err
|
||||
}
|
||||
}
|
||||
|
||||
return laddr, raddr, err
|
||||
}
|
||||
|
||||
func parseNetstatUnixLine(f []string) (ConnectionStat, error) {
|
||||
if len(f) < 8 {
|
||||
return ConnectionStat{}, fmt.Errorf("wrong number of fields: expected >=8 got %d", len(f))
|
||||
}
|
||||
|
||||
var netType uint32
|
||||
|
||||
switch f[1] {
|
||||
case "dgram":
|
||||
netType = syscall.SOCK_DGRAM
|
||||
case "stream":
|
||||
netType = syscall.SOCK_STREAM
|
||||
default:
|
||||
return ConnectionStat{}, fmt.Errorf("unknown type: %s", f[1])
|
||||
}
|
||||
|
||||
// Some Unix Socket don't have any address associated
|
||||
addr := ""
|
||||
if len(f) == 9 {
|
||||
addr = f[8]
|
||||
}
|
||||
|
||||
c := ConnectionStat{
|
||||
Fd: uint32(0), // not supported
|
||||
Family: uint32(syscall.AF_UNIX),
|
||||
Type: uint32(netType),
|
||||
Laddr: Addr{
|
||||
IP: addr,
|
||||
},
|
||||
Status: "NONE",
|
||||
Pid: int32(0), // not supported
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Return true if proto is the corresponding to the kind parameter
|
||||
// Only for Inet lines
|
||||
func hasCorrectInetProto(kind, proto string) bool {
|
||||
switch kind {
|
||||
case "all", "inet":
|
||||
return true
|
||||
case "unix":
|
||||
return false
|
||||
case "inet4":
|
||||
return !strings.HasSuffix(proto, "6")
|
||||
case "inet6":
|
||||
return strings.HasSuffix(proto, "6")
|
||||
case "tcp":
|
||||
return proto == "tcp" || proto == "tcp4" || proto == "tcp6"
|
||||
case "tcp4":
|
||||
return proto == "tcp" || proto == "tcp4"
|
||||
case "tcp6":
|
||||
return proto == "tcp6"
|
||||
case "udp":
|
||||
return proto == "udp" || proto == "udp4" || proto == "udp6"
|
||||
case "udp4":
|
||||
return proto == "udp" || proto == "udp4"
|
||||
case "udp6":
|
||||
return proto == "udp6"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseNetstatA(output, kind string) ([]ConnectionStat, error) {
|
||||
var ret []ConnectionStat
|
||||
lines := strings.Split(string(output), "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(fields[0], "f1"):
|
||||
// Unix lines
|
||||
if len(fields) < 2 {
|
||||
// every unix connections have two lines
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := parseNetstatUnixLine(fields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Unix Address (%s): %w", line, err)
|
||||
}
|
||||
|
||||
ret = append(ret, c)
|
||||
|
||||
case strings.HasPrefix(fields[0], "tcp") || strings.HasPrefix(fields[0], "udp"):
|
||||
// Inet lines
|
||||
if !hasCorrectInetProto(kind, fields[0]) {
|
||||
continue
|
||||
}
|
||||
|
||||
// On AIX, netstat display some connections with "*.*" as local addresses
|
||||
// Skip them as they aren't real connections.
|
||||
if fields[3] == "*.*" {
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := parseNetstatNetLine(line)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Inet Address (%s): %w", line, err)
|
||||
}
|
||||
|
||||
ret = append(ret, c)
|
||||
default:
|
||||
// Header lines
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
args := []string{"-na"}
|
||||
switch strings.ToLower(kind) {
|
||||
default:
|
||||
fallthrough
|
||||
case "":
|
||||
kind = "all"
|
||||
case "all":
|
||||
// nothing to add
|
||||
case "inet", "inet4", "inet6":
|
||||
args = append(args, "-finet")
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
args = append(args, "-finet")
|
||||
case "udp", "udp4", "udp6":
|
||||
args = append(args, "-finet")
|
||||
case "unix":
|
||||
args = append(args, "-funix")
|
||||
}
|
||||
|
||||
out, err := invoke.CommandWithContext(ctx, "netstat", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, err := parseNetstatA(string(out), kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, false)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, true)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int, _ bool) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && cgo
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/power-devops/perfstat"
|
||||
)
|
||||
|
||||
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
ifs, err := perfstat.NetIfaceStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iocounters := make([]IOCountersStat, 0, len(ifs))
|
||||
for _, netif := range ifs {
|
||||
n := IOCountersStat{
|
||||
Name: netif.Name,
|
||||
BytesSent: uint64(netif.OBytes),
|
||||
BytesRecv: uint64(netif.IBytes),
|
||||
PacketsSent: uint64(netif.OPackets),
|
||||
PacketsRecv: uint64(netif.IPackets),
|
||||
Errin: uint64(netif.IErrors),
|
||||
Errout: uint64(netif.OErrors),
|
||||
Dropin: uint64(netif.IfIqDrops),
|
||||
Dropout: uint64(netif.XmitDrops),
|
||||
}
|
||||
iocounters = append(iocounters, n)
|
||||
}
|
||||
if !pernic {
|
||||
return getIOCountersAll(iocounters), nil
|
||||
}
|
||||
return iocounters, nil
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build aix && !cgo
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func parseNetstatI(output string) ([]IOCountersStat, error) {
|
||||
lines := strings.Split(string(output), "\n")
|
||||
ret := make([]IOCountersStat, 0, len(lines)-1)
|
||||
exists := make([]string, 0, len(ret))
|
||||
|
||||
// Check first line is header
|
||||
if len(lines) > 0 && strings.Fields(lines[0])[0] != "Name" {
|
||||
return nil, errors.New("not a 'netstat -i' output")
|
||||
}
|
||||
|
||||
for _, line := range lines[1:] {
|
||||
values := strings.Fields(line)
|
||||
if len(values) < 1 || values[0] == "Name" {
|
||||
continue
|
||||
}
|
||||
if common.StringsHas(exists, values[0]) {
|
||||
// skip if already get
|
||||
continue
|
||||
}
|
||||
exists = append(exists, values[0])
|
||||
|
||||
if len(values) < 9 {
|
||||
continue
|
||||
}
|
||||
|
||||
base := 1
|
||||
// sometimes Address is omitted
|
||||
if len(values) < 10 {
|
||||
base = 0
|
||||
}
|
||||
|
||||
parsed := make([]uint64, 0, 5)
|
||||
vv := []string{
|
||||
values[base+3], // Ipkts == PacketsRecv
|
||||
values[base+4], // Ierrs == Errin
|
||||
values[base+5], // Opkts == PacketsSent
|
||||
values[base+6], // Oerrs == Errout
|
||||
values[base+8], // Drops == Dropout
|
||||
}
|
||||
|
||||
for _, target := range vv {
|
||||
if target == "-" {
|
||||
parsed = append(parsed, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := strconv.ParseUint(target, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed = append(parsed, t)
|
||||
}
|
||||
|
||||
n := IOCountersStat{
|
||||
Name: values[0],
|
||||
PacketsRecv: parsed[0],
|
||||
Errin: parsed[1],
|
||||
PacketsSent: parsed[2],
|
||||
Errout: parsed[3],
|
||||
Dropout: parsed[4],
|
||||
}
|
||||
ret = append(ret, n)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "netstat", "-idn")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iocounters, err := parseNetstatI(string(out))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !pernic {
|
||||
return getIOCountersAll(iocounters), nil
|
||||
}
|
||||
return iocounters, nil
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
errNetstatHeader = errors.New("can't parse header of netstat output")
|
||||
netstatLinkRegexp = regexp.MustCompile(`^<Link#(\d+)>$`)
|
||||
)
|
||||
|
||||
const endOfLine = "\n"
|
||||
|
||||
func parseNetstatLine(line string) (stat *IOCountersStat, linkID *uint, err error) {
|
||||
var (
|
||||
numericValue uint64
|
||||
columns = strings.Fields(line)
|
||||
)
|
||||
|
||||
if columns[0] == "Name" {
|
||||
return nil, nil, errNetstatHeader
|
||||
}
|
||||
|
||||
// try to extract the numeric value from <Link#123>
|
||||
if subMatch := netstatLinkRegexp.FindStringSubmatch(columns[2]); len(subMatch) == 2 {
|
||||
numericValue, err = strconv.ParseUint(subMatch[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
linkIDUint := uint(numericValue)
|
||||
linkID = &linkIDUint
|
||||
}
|
||||
|
||||
base := 1
|
||||
numberColumns := len(columns)
|
||||
// sometimes Address is omitted
|
||||
if numberColumns < 12 {
|
||||
base = 0
|
||||
}
|
||||
if numberColumns < 11 || numberColumns > 13 {
|
||||
return nil, nil, fmt.Errorf("line %q do have an invalid number of columns %d", line, numberColumns)
|
||||
}
|
||||
|
||||
parsed := make([]uint64, 0, 7)
|
||||
vv := []string{
|
||||
columns[base+3], // Ipkts == PacketsRecv
|
||||
columns[base+4], // Ierrs == Errin
|
||||
columns[base+5], // Ibytes == BytesRecv
|
||||
columns[base+6], // Opkts == PacketsSent
|
||||
columns[base+7], // Oerrs == Errout
|
||||
columns[base+8], // Obytes == BytesSent
|
||||
columns[base+10], // Drop == Dropout
|
||||
}
|
||||
|
||||
for _, target := range vv {
|
||||
if target == "-" {
|
||||
parsed = append(parsed, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
if numericValue, err = strconv.ParseUint(target, 10, 64); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
parsed = append(parsed, numericValue)
|
||||
}
|
||||
|
||||
stat = &IOCountersStat{
|
||||
Name: strings.Trim(columns[0], "*"), // remove the * that sometimes is on right on interface
|
||||
PacketsRecv: parsed[0],
|
||||
Errin: parsed[1],
|
||||
BytesRecv: parsed[2],
|
||||
PacketsSent: parsed[3],
|
||||
Errout: parsed[4],
|
||||
BytesSent: parsed[5],
|
||||
Dropout: parsed[6],
|
||||
}
|
||||
return stat, linkID, nil
|
||||
}
|
||||
|
||||
type netstatInterface struct {
|
||||
linkID *uint
|
||||
stat *IOCountersStat
|
||||
}
|
||||
|
||||
func parseNetstatOutput(output string) ([]netstatInterface, error) {
|
||||
var (
|
||||
err error
|
||||
lines = strings.Split(strings.Trim(output, endOfLine), endOfLine)
|
||||
)
|
||||
|
||||
// number of interfaces is number of lines less one for the header
|
||||
numberInterfaces := len(lines) - 1
|
||||
|
||||
interfaces := make([]netstatInterface, numberInterfaces)
|
||||
// no output beside header
|
||||
if numberInterfaces == 0 {
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
for index := 0; index < numberInterfaces; index++ {
|
||||
nsIface := netstatInterface{}
|
||||
if nsIface.stat, nsIface.linkID, err = parseNetstatLine(lines[index+1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
interfaces[index] = nsIface
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// map that hold the name of a network interface and the number of usage
|
||||
type mapInterfaceNameUsage map[string]uint
|
||||
|
||||
func newMapInterfaceNameUsage(ifaces []netstatInterface) mapInterfaceNameUsage {
|
||||
output := make(mapInterfaceNameUsage)
|
||||
for index := range ifaces {
|
||||
if ifaces[index].linkID != nil {
|
||||
ifaceName := ifaces[index].stat.Name
|
||||
usage, ok := output[ifaceName]
|
||||
if ok {
|
||||
output[ifaceName] = usage + 1
|
||||
} else {
|
||||
output[ifaceName] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func (mapi mapInterfaceNameUsage) isTruncated() bool {
|
||||
for _, usage := range mapi {
|
||||
if usage > 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (mapi mapInterfaceNameUsage) notTruncated() []string {
|
||||
output := make([]string, 0)
|
||||
for ifaceName, usage := range mapi {
|
||||
if usage == 1 {
|
||||
output = append(output, ifaceName)
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
// example of `netstat -ibdnW` output on yosemite
|
||||
// Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll Drop
|
||||
// lo0 16384 <Link#1> 869107 0 169411755 869107 0 169411755 0 0
|
||||
// lo0 16384 ::1/128 ::1 869107 - 169411755 869107 - 169411755 - -
|
||||
// lo0 16384 127 127.0.0.1 869107 - 169411755 869107 - 169411755 - -
|
||||
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
var (
|
||||
ret []IOCountersStat
|
||||
retIndex int
|
||||
)
|
||||
|
||||
netstat, err := exec.LookPath("netstat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to get all interface metrics, and hope there won't be any truncated
|
||||
out, err := invoke.CommandWithContext(ctx, netstat, "-ibdnW")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nsInterfaces, err := parseNetstatOutput(string(out))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ifaceUsage := newMapInterfaceNameUsage(nsInterfaces)
|
||||
notTruncated := ifaceUsage.notTruncated()
|
||||
ret = make([]IOCountersStat, len(notTruncated))
|
||||
|
||||
if !ifaceUsage.isTruncated() {
|
||||
// no truncated interface name, return stats of all interface with <Link#...>
|
||||
for index := range nsInterfaces {
|
||||
if nsInterfaces[index].linkID != nil {
|
||||
ret[retIndex] = *nsInterfaces[index].stat
|
||||
retIndex++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// duplicated interface, list all interfaces
|
||||
if out, err = invoke.CommandWithContext(ctx, "ifconfig", "-l"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
interfaceNames := strings.Fields(strings.TrimRight(string(out), endOfLine))
|
||||
|
||||
// for each of the interface name, run netstat if we don't have any stats yet
|
||||
for _, interfaceName := range interfaceNames {
|
||||
truncated := true
|
||||
for index := range nsInterfaces {
|
||||
if nsInterfaces[index].linkID != nil && nsInterfaces[index].stat.Name == interfaceName {
|
||||
// handle the non truncated name to avoid execute netstat for them again
|
||||
ret[retIndex] = *nsInterfaces[index].stat
|
||||
retIndex++
|
||||
truncated = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if truncated {
|
||||
// run netstat with -I$ifacename
|
||||
if out, err = invoke.CommandWithContext(ctx, netstat, "-ibdnWI"+interfaceName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedIfaces, err := parseNetstatOutput(string(out))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parsedIfaces) == 0 {
|
||||
// interface had been removed since `ifconfig -l` had been executed
|
||||
continue
|
||||
}
|
||||
for index := range parsedIfaces {
|
||||
if parsedIfaces[index].linkID != nil {
|
||||
ret = append(ret, *parsedIfaces[index].stat)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !pernic {
|
||||
return getIOCountersAll(ret), nil
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !aix && !darwin && !linux && !freebsd && !openbsd && !windows && !solaris
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func IOCountersWithContext(_ context.Context, _ bool) ([]IOCountersStat, error) {
|
||||
return []IOCountersStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsWithContext(_ context.Context, _ string) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, false)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, true)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int, _ bool) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
out, err := invoke.CommandWithContext(ctx, "netstat", "-ibdnW")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(out), "\n")
|
||||
ret := make([]IOCountersStat, 0, len(lines)-1)
|
||||
exists := make([]string, 0, len(ret))
|
||||
|
||||
for _, line := range lines {
|
||||
values := strings.Fields(line)
|
||||
if len(values) < 1 || values[0] == "Name" {
|
||||
continue
|
||||
}
|
||||
if common.StringsHas(exists, values[0]) {
|
||||
// skip if already get
|
||||
continue
|
||||
}
|
||||
exists = append(exists, values[0])
|
||||
|
||||
if len(values) < 12 {
|
||||
continue
|
||||
}
|
||||
base := 1
|
||||
// sometimes Address is omitted
|
||||
if len(values) < 13 {
|
||||
base = 0
|
||||
}
|
||||
|
||||
parsed := make([]uint64, 0, 8)
|
||||
vv := []string{
|
||||
values[base+3], // PacketsRecv
|
||||
values[base+4], // Errin
|
||||
values[base+5], // Dropin
|
||||
values[base+6], // BytesRecvn
|
||||
values[base+7], // PacketSent
|
||||
values[base+8], // Errout
|
||||
values[base+9], // BytesSent
|
||||
values[base+11], // Dropout
|
||||
}
|
||||
for _, target := range vv {
|
||||
if target == "-" {
|
||||
parsed = append(parsed, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := strconv.ParseUint(target, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed = append(parsed, t)
|
||||
}
|
||||
|
||||
n := IOCountersStat{
|
||||
Name: values[0],
|
||||
PacketsRecv: parsed[0],
|
||||
Errin: parsed[1],
|
||||
Dropin: parsed[2],
|
||||
BytesRecv: parsed[3],
|
||||
PacketsSent: parsed[4],
|
||||
Errout: parsed[5],
|
||||
BytesSent: parsed[6],
|
||||
Dropout: parsed[7],
|
||||
}
|
||||
ret = append(ret, n)
|
||||
}
|
||||
|
||||
if !pernic {
|
||||
return getIOCountersAll(ret), nil
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+817
@@ -0,0 +1,817 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
const ( // Conntrack Column numbers
|
||||
ctENTRIES = iota
|
||||
ctSEARCHED
|
||||
ctFOUND
|
||||
ctNEW
|
||||
ctINVALID
|
||||
ctIGNORE
|
||||
ctDELETE
|
||||
ctDELETE_LIST //nolint:revive //FIXME
|
||||
ctINSERT
|
||||
ctINSERT_FAILED //nolint:revive //FIXME
|
||||
ctDROP
|
||||
ctEARLY_DROP //nolint:revive //FIXME
|
||||
ctICMP_ERROR //nolint:revive //FIXME
|
||||
CT_EXPEctNEW //nolint:revive //FIXME
|
||||
ctEXPECT_CREATE //nolint:revive //FIXME
|
||||
CT_EXPEctDELETE //nolint:revive //FIXME
|
||||
ctSEARCH_RESTART //nolint:revive //FIXME
|
||||
)
|
||||
|
||||
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
filename := common.HostProcWithContext(ctx, "net/dev")
|
||||
return IOCountersByFileWithContext(ctx, pernic, filename)
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(_ context.Context, pernic bool, filename string) ([]IOCountersStat, error) {
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statlen := len(lines) - 1
|
||||
|
||||
ret := make([]IOCountersStat, 0, statlen)
|
||||
|
||||
for _, line := range lines[2:] {
|
||||
// Split interface name and stats data at the last ":"
|
||||
separatorPos := strings.LastIndex(line, ":")
|
||||
if separatorPos == -1 {
|
||||
continue
|
||||
}
|
||||
interfacePart := line[0:separatorPos]
|
||||
statsPart := line[separatorPos+1:]
|
||||
|
||||
interfaceName := strings.TrimSpace(interfacePart)
|
||||
if interfaceName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(strings.TrimSpace(statsPart))
|
||||
bytesRecv, err := strconv.ParseUint(fields[0], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
packetsRecv, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
errIn, err := strconv.ParseUint(fields[2], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
dropIn, err := strconv.ParseUint(fields[3], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
fifoIn, err := strconv.ParseUint(fields[4], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
bytesSent, err := strconv.ParseUint(fields[8], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
packetsSent, err := strconv.ParseUint(fields[9], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
errOut, err := strconv.ParseUint(fields[10], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
dropOut, err := strconv.ParseUint(fields[11], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
fifoOut, err := strconv.ParseUint(fields[12], 10, 64)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
nic := IOCountersStat{
|
||||
Name: interfaceName,
|
||||
BytesRecv: bytesRecv,
|
||||
PacketsRecv: packetsRecv,
|
||||
Errin: errIn,
|
||||
Dropin: dropIn,
|
||||
Fifoin: fifoIn,
|
||||
BytesSent: bytesSent,
|
||||
PacketsSent: packetsSent,
|
||||
Errout: errOut,
|
||||
Dropout: dropOut,
|
||||
Fifoout: fifoOut,
|
||||
}
|
||||
ret = append(ret, nic)
|
||||
}
|
||||
|
||||
if !pernic {
|
||||
return getIOCountersAll(ret), nil
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var netProtocols = []string{
|
||||
"ip",
|
||||
"icmp",
|
||||
"icmpmsg",
|
||||
"tcp",
|
||||
"udp",
|
||||
"udplite",
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) {
|
||||
if len(protocols) == 0 {
|
||||
protocols = netProtocols
|
||||
}
|
||||
|
||||
stats := make([]ProtoCountersStat, 0, len(protocols))
|
||||
protos := make(map[string]bool, len(protocols))
|
||||
for _, p := range protocols {
|
||||
protos[p] = true
|
||||
}
|
||||
|
||||
filename := common.HostProcWithContext(ctx, "net/snmp")
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linecount := len(lines)
|
||||
for i := 0; i < linecount; i++ {
|
||||
line := lines[i]
|
||||
r := strings.IndexRune(line, ':')
|
||||
if r == -1 {
|
||||
return nil, errors.New(filename + " is not formatted correctly, expected ':'.")
|
||||
}
|
||||
proto := strings.ToLower(line[:r])
|
||||
if !protos[proto] {
|
||||
// skip protocol and data line
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// Read header line
|
||||
statNames := strings.Split(line[r+2:], " ")
|
||||
|
||||
// Read data line
|
||||
i++
|
||||
statValues := strings.Split(lines[i][r+2:], " ")
|
||||
if len(statNames) != len(statValues) {
|
||||
return nil, errors.New(filename + " is not formatted correctly, expected same number of columns.")
|
||||
}
|
||||
stat := ProtoCountersStat{
|
||||
Protocol: proto,
|
||||
Stats: make(map[string]int64, len(statNames)),
|
||||
}
|
||||
for j := range statNames {
|
||||
value, err := strconv.ParseInt(statValues[j], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stat.Stats[statNames[j]] = value
|
||||
}
|
||||
stats = append(stats, stat)
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
stats := make([]FilterStat, 0, 1)
|
||||
|
||||
maxConn, err := common.ReadInts(maxfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := FilterStat{
|
||||
ConnTrackCount: count[0],
|
||||
ConnTrackMax: maxConn[0],
|
||||
}
|
||||
|
||||
stats = append(stats, payload)
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// ConntrackStatsWithContext returns more detailed info about the conntrack table
|
||||
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
|
||||
return conntrackStatsFromFile(common.HostProcWithContext(ctx, "net/stat/nf_conntrack"), percpu)
|
||||
}
|
||||
|
||||
// conntrackStatsFromFile returns more detailed info about the conntrack table
|
||||
// from `filename`
|
||||
// If 'percpu' is false, the result will contain exactly one item with totals/summary
|
||||
func conntrackStatsFromFile(filename string, percpu bool) ([]ConntrackStat, error) {
|
||||
lines, err := common.ReadLines(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statlist := NewConntrackStatList()
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 17 && fields[0] != "entries" {
|
||||
statlist.Append(NewConntrackStat(
|
||||
common.HexToUint32(fields[ctENTRIES]),
|
||||
common.HexToUint32(fields[ctSEARCHED]),
|
||||
common.HexToUint32(fields[ctFOUND]),
|
||||
common.HexToUint32(fields[ctNEW]),
|
||||
common.HexToUint32(fields[ctINVALID]),
|
||||
common.HexToUint32(fields[ctIGNORE]),
|
||||
common.HexToUint32(fields[ctDELETE]),
|
||||
common.HexToUint32(fields[ctDELETE_LIST]),
|
||||
common.HexToUint32(fields[ctINSERT]),
|
||||
common.HexToUint32(fields[ctINSERT_FAILED]),
|
||||
common.HexToUint32(fields[ctDROP]),
|
||||
common.HexToUint32(fields[ctEARLY_DROP]),
|
||||
common.HexToUint32(fields[ctICMP_ERROR]),
|
||||
common.HexToUint32(fields[CT_EXPEctNEW]),
|
||||
common.HexToUint32(fields[ctEXPECT_CREATE]),
|
||||
common.HexToUint32(fields[CT_EXPEctDELETE]),
|
||||
common.HexToUint32(fields[ctSEARCH_RESTART]),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if percpu {
|
||||
return statlist.Items(), nil
|
||||
}
|
||||
return statlist.Summary(), nil
|
||||
}
|
||||
|
||||
// http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
|
||||
var tcpStatuses = map[string]string{
|
||||
"01": "ESTABLISHED",
|
||||
"02": "SYN_SENT",
|
||||
"03": "SYN_RECV",
|
||||
"04": "FIN_WAIT1",
|
||||
"05": "FIN_WAIT2",
|
||||
"06": "TIME_WAIT",
|
||||
"07": "CLOSE",
|
||||
"08": "CLOSE_WAIT",
|
||||
"09": "LAST_ACK",
|
||||
"0A": "LISTEN",
|
||||
"0B": "CLOSING",
|
||||
}
|
||||
|
||||
type netConnectionKindType struct {
|
||||
family uint32
|
||||
sockType uint32
|
||||
filename string
|
||||
}
|
||||
|
||||
var kindTCP4 = netConnectionKindType{
|
||||
family: syscall.AF_INET,
|
||||
sockType: syscall.SOCK_STREAM,
|
||||
filename: "tcp",
|
||||
}
|
||||
|
||||
var kindTCP6 = netConnectionKindType{
|
||||
family: syscall.AF_INET6,
|
||||
sockType: syscall.SOCK_STREAM,
|
||||
filename: "tcp6",
|
||||
}
|
||||
|
||||
var kindUDP4 = netConnectionKindType{
|
||||
family: syscall.AF_INET,
|
||||
sockType: syscall.SOCK_DGRAM,
|
||||
filename: "udp",
|
||||
}
|
||||
|
||||
var kindUDP6 = netConnectionKindType{
|
||||
family: syscall.AF_INET6,
|
||||
sockType: syscall.SOCK_DGRAM,
|
||||
filename: "udp6",
|
||||
}
|
||||
|
||||
var kindUNIX = netConnectionKindType{
|
||||
family: syscall.AF_UNIX,
|
||||
filename: "unix",
|
||||
}
|
||||
|
||||
var netConnectionKindMap = map[string][]netConnectionKindType{
|
||||
"all": {kindTCP4, kindTCP6, kindUDP4, kindUDP6, kindUNIX},
|
||||
"tcp": {kindTCP4, kindTCP6},
|
||||
"tcp4": {kindTCP4},
|
||||
"tcp6": {kindTCP6},
|
||||
"udp": {kindUDP4, kindUDP6},
|
||||
"udp4": {kindUDP4},
|
||||
"udp6": {kindUDP6},
|
||||
"unix": {kindUNIX},
|
||||
"inet": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
|
||||
"inet4": {kindTCP4, kindUDP4},
|
||||
"inet6": {kindTCP6, kindUDP6},
|
||||
}
|
||||
|
||||
type inodeMap struct {
|
||||
pid int32
|
||||
fd uint32
|
||||
}
|
||||
|
||||
type connTmp struct {
|
||||
fd uint32
|
||||
family uint32
|
||||
sockType uint32
|
||||
laddr Addr
|
||||
raddr Addr
|
||||
status string
|
||||
pid int32
|
||||
boundPid int32
|
||||
path string
|
||||
inode string
|
||||
}
|
||||
|
||||
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, false)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, true)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int, skipUids bool) ([]ConnectionStat, error) {
|
||||
tmap, ok := netConnectionKindMap[kind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid kind, %s", kind)
|
||||
}
|
||||
root := common.HostProcWithContext(ctx)
|
||||
var err error
|
||||
var inodes map[string][]inodeMap
|
||||
if pid == 0 {
|
||||
inodes, err = getProcInodesAllWithContext(ctx, root, maxConn)
|
||||
} else {
|
||||
inodes, err = getProcInodes(root, pid, maxConn)
|
||||
if len(inodes) == 0 {
|
||||
// no connection for the pid
|
||||
return []ConnectionStat{}, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get pid(s), %d: %w", pid, err)
|
||||
}
|
||||
return statsFromInodesWithContext(ctx, root, pid, tmap, inodes, skipUids)
|
||||
}
|
||||
|
||||
// connectionDedupKey builds a key to deduplicate connections.
|
||||
// For inet sockets, the tuple (type, src, dst, status) is sufficient.
|
||||
// For unix sockets, unnamed sockets share the same empty address,
|
||||
// so pid, fd, and inode must be included to avoid incorrect deduplication.
|
||||
// The inode is especially important when pid/fd are unavailable (e.g.,
|
||||
// unprivileged queries where inode-to-pid mapping fails).
|
||||
func connectionDedupKey(family uint32, c connTmp) string {
|
||||
if family == syscall.AF_UNIX {
|
||||
return fmt.Sprintf("%d-%d-%s-%d-%s:%d-%s:%d-%s", c.pid, c.fd, c.inode, c.sockType, c.laddr.IP, c.laddr.Port, c.raddr.IP, c.raddr.Port, c.status)
|
||||
}
|
||||
return fmt.Sprintf("%d-%s:%d-%s:%d-%s", c.sockType, c.laddr.IP, c.laddr.Port, c.raddr.IP, c.raddr.Port, c.status)
|
||||
}
|
||||
|
||||
func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap, skipUids bool) ([]ConnectionStat, error) {
|
||||
dupCheckMap := make(map[string]struct{})
|
||||
var ret []ConnectionStat
|
||||
|
||||
var err error
|
||||
for _, t := range tmap {
|
||||
var path string
|
||||
var ls []connTmp
|
||||
if pid == 0 {
|
||||
path = fmt.Sprintf("%s/net/%s", root, t.filename)
|
||||
} else {
|
||||
path = fmt.Sprintf("%s/%d/net/%s", root, pid, t.filename)
|
||||
}
|
||||
switch t.family {
|
||||
case syscall.AF_INET, syscall.AF_INET6:
|
||||
ls, err = processInet(path, t, inodes, pid)
|
||||
case syscall.AF_UNIX:
|
||||
ls, err = processUnix(path, t, inodes, pid)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range ls {
|
||||
connKey := connectionDedupKey(t.family, c)
|
||||
if _, ok := dupCheckMap[connKey]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
conn := ConnectionStat{
|
||||
Fd: c.fd,
|
||||
Family: c.family,
|
||||
Type: c.sockType,
|
||||
Laddr: c.laddr,
|
||||
Raddr: c.raddr,
|
||||
Status: c.status,
|
||||
Pid: c.pid,
|
||||
}
|
||||
if c.pid == 0 {
|
||||
conn.Pid = c.boundPid
|
||||
} else {
|
||||
conn.Pid = c.pid
|
||||
}
|
||||
|
||||
if !skipUids {
|
||||
// fetch process owner Real, effective, saved set, and filesystem UIDs
|
||||
proc := process{Pid: conn.Pid}
|
||||
conn.Uids, _ = proc.getUids(ctx)
|
||||
}
|
||||
|
||||
ret = append(ret, conn)
|
||||
dupCheckMap[connKey] = struct{}{}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// getProcInodes returns fd of the pid.
|
||||
func getProcInodes(root string, pid int32, maxConn int) (map[string][]inodeMap, error) {
|
||||
ret := make(map[string][]inodeMap)
|
||||
|
||||
dir := fmt.Sprintf("%s/%d/fd", root, pid)
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
defer f.Close()
|
||||
dirEntries, err := f.ReadDir(maxConn)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
for _, dirEntry := range dirEntries {
|
||||
inodePath := fmt.Sprintf("%s/%d/fd/%s", root, pid, dirEntry.Name())
|
||||
|
||||
inode, err := os.Readlink(inodePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(inode, "socket:[") {
|
||||
continue
|
||||
}
|
||||
// the process is using a socket
|
||||
l := len(inode)
|
||||
inode = inode[8 : l-1]
|
||||
_, ok := ret[inode]
|
||||
if !ok {
|
||||
ret[inode] = make([]inodeMap, 0)
|
||||
}
|
||||
fd, err := strconv.ParseInt(dirEntry.Name(), 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
i := inodeMap{
|
||||
pid: pid,
|
||||
fd: uint32(fd),
|
||||
}
|
||||
ret[inode] = append(ret[inode], i)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func PidsWithContext(ctx context.Context) ([]int32, error) {
|
||||
var ret []int32
|
||||
|
||||
d, err := os.Open(common.HostProcWithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
fnames, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, fname := range fnames {
|
||||
pid, err := strconv.ParseInt(fname, 10, 32)
|
||||
if err != nil {
|
||||
// if not numeric name, just skip
|
||||
continue
|
||||
}
|
||||
ret = append(ret, int32(pid))
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Note: the following is based off process_linux structs and methods
|
||||
// we need these to fetch the owner of a process ID
|
||||
// FIXME: Import process occurs import cycle.
|
||||
// see remarks on pids()
|
||||
type process struct {
|
||||
Pid int32 `json:"pid"`
|
||||
uids []int32
|
||||
}
|
||||
|
||||
// Uids returns user ids of the process as a slice of the int
|
||||
func (p *process) getUids(ctx context.Context) ([]int32, error) {
|
||||
err := p.fillFromStatus(ctx)
|
||||
if err != nil {
|
||||
return []int32{}, err
|
||||
}
|
||||
return p.uids, nil
|
||||
}
|
||||
|
||||
// Get status from /proc/(pid)/status
|
||||
func (p *process) fillFromStatus(ctx context.Context) error {
|
||||
pid := p.Pid
|
||||
statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "status")
|
||||
contents, err := os.ReadFile(statPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(contents), "\n")
|
||||
for _, line := range lines {
|
||||
tabParts := strings.SplitN(line, "\t", 2)
|
||||
if len(tabParts) < 2 {
|
||||
continue
|
||||
}
|
||||
value := tabParts[1]
|
||||
if strings.TrimRight(tabParts[0], ":") == "Uid" {
|
||||
p.uids = make([]int32, 0, 4)
|
||||
for _, i := range strings.Split(value, "\t") {
|
||||
v, err := strconv.ParseInt(i, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.uids = append(p.uids, int32(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getProcInodesAllWithContext(ctx context.Context, root string, maxConn int) (map[string][]inodeMap, error) {
|
||||
pids, err := PidsWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make(map[string][]inodeMap)
|
||||
|
||||
for _, pid := range pids {
|
||||
t, err := getProcInodes(root, pid, maxConn)
|
||||
if err != nil {
|
||||
// skip if permission error or no longer exists
|
||||
if os.IsPermission(err) || os.IsNotExist(err) || errors.Is(err, io.EOF) {
|
||||
continue
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
if len(t) == 0 {
|
||||
continue
|
||||
}
|
||||
// TODO: update ret.
|
||||
ret = updateMap(ret, t)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// decodeAddress decode address represents addr in proc/net/*
|
||||
// ex:
|
||||
// "0500000A:0016" -> "10.0.0.5", 22
|
||||
// "0085002452100113070057A13F025401:0035" -> "2400:8500:1301:1052:a157:7:154:23f", 53
|
||||
func decodeAddress(family uint32, src string) (Addr, error) {
|
||||
t := strings.Split(src, ":")
|
||||
if len(t) != 2 {
|
||||
return Addr{}, fmt.Errorf("does not contain port, %s", src)
|
||||
}
|
||||
addr := t[0]
|
||||
port, err := strconv.ParseUint(t[1], 16, 16)
|
||||
if err != nil {
|
||||
return Addr{}, fmt.Errorf("invalid port, %s", src)
|
||||
}
|
||||
decoded, err := hex.DecodeString(addr)
|
||||
if err != nil {
|
||||
return Addr{}, fmt.Errorf("decode error, %w", err)
|
||||
}
|
||||
var ip net.IP
|
||||
|
||||
if family == syscall.AF_INET {
|
||||
if common.IsLittleEndian() {
|
||||
ip = net.IP(Reverse(decoded))
|
||||
} else {
|
||||
ip = net.IP(decoded)
|
||||
}
|
||||
} else { // IPv6
|
||||
ip, err = parseIPv6HexString(decoded)
|
||||
if err != nil {
|
||||
return Addr{}, err
|
||||
}
|
||||
}
|
||||
return Addr{
|
||||
IP: ip.String(),
|
||||
Port: uint32(port),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Reverse(s []byte) []byte {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// parseIPv6HexString parse array of bytes to IPv6 string
|
||||
func parseIPv6HexString(src []byte) (net.IP, error) {
|
||||
if len(src) != 16 {
|
||||
return nil, errors.New("invalid IPv6 string")
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 16)
|
||||
for i := 0; i < len(src); i += 4 {
|
||||
r := Reverse(src[i : i+4])
|
||||
buf = append(buf, r...)
|
||||
}
|
||||
return net.IP(buf), nil
|
||||
}
|
||||
|
||||
func processInet(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) {
|
||||
if strings.HasSuffix(file, "6") && !common.PathExists(file) {
|
||||
// IPv6 not supported, return empty.
|
||||
return []connTmp{}, nil
|
||||
}
|
||||
|
||||
// Read the contents of the /proc file with a single read sys call.
|
||||
// This minimizes duplicates in the returned connections
|
||||
// For more info:
|
||||
// https://github.com/shirou/gopsutil/pull/361
|
||||
contents, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := bytes.Split(contents, []byte("\n"))
|
||||
|
||||
var ret []connTmp
|
||||
// skip first line
|
||||
for _, line := range lines[1:] {
|
||||
l := strings.Fields(string(line))
|
||||
if len(l) < 10 {
|
||||
continue
|
||||
}
|
||||
laddr := l[1]
|
||||
raddr := l[2]
|
||||
status := l[3]
|
||||
inode := l[9]
|
||||
pid := int32(0)
|
||||
fd := uint32(0)
|
||||
i, exists := inodes[inode]
|
||||
if exists {
|
||||
pid = i[0].pid
|
||||
fd = i[0].fd
|
||||
}
|
||||
if filterPid > 0 && filterPid != pid {
|
||||
continue
|
||||
}
|
||||
if kind.sockType == syscall.SOCK_STREAM {
|
||||
status = tcpStatuses[status]
|
||||
} else {
|
||||
status = "NONE"
|
||||
}
|
||||
la, err := decodeAddress(kind.family, laddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ra, err := decodeAddress(kind.family, raddr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, connTmp{
|
||||
fd: fd,
|
||||
family: kind.family,
|
||||
sockType: kind.sockType,
|
||||
laddr: la,
|
||||
raddr: ra,
|
||||
status: status,
|
||||
pid: pid,
|
||||
inode: inode,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func processUnix(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) {
|
||||
// Read the contents of the /proc file with a single read sys call.
|
||||
// This minimizes duplicates in the returned connections
|
||||
// For more info:
|
||||
// https://github.com/shirou/gopsutil/pull/361
|
||||
contents, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := bytes.Split(contents, []byte("\n"))
|
||||
|
||||
var ret []connTmp
|
||||
// skip first line
|
||||
for _, line := range lines[1:] {
|
||||
tokens := strings.Fields(string(line))
|
||||
if len(tokens) < 6 {
|
||||
continue
|
||||
}
|
||||
st, err := strconv.ParseInt(tokens[4], 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inode := tokens[6]
|
||||
|
||||
var pairs []inodeMap
|
||||
pairs, exists := inodes[inode]
|
||||
if !exists {
|
||||
pairs = []inodeMap{
|
||||
{},
|
||||
}
|
||||
}
|
||||
for _, pair := range pairs {
|
||||
if filterPid > 0 && filterPid != pair.pid {
|
||||
continue
|
||||
}
|
||||
var path string
|
||||
if len(tokens) == 8 {
|
||||
path = tokens[len(tokens)-1]
|
||||
}
|
||||
ret = append(ret, connTmp{
|
||||
fd: pair.fd,
|
||||
family: kind.family,
|
||||
sockType: uint32(st),
|
||||
laddr: Addr{
|
||||
IP: path,
|
||||
},
|
||||
pid: pair.pid,
|
||||
status: "NONE",
|
||||
path: path,
|
||||
inode: inode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func updateMap(src, add map[string][]inodeMap) map[string][]inodeMap {
|
||||
for key, value := range add {
|
||||
a, exists := src[key]
|
||||
if !exists {
|
||||
src[key] = value
|
||||
continue
|
||||
}
|
||||
src[key] = append(a, value...)
|
||||
}
|
||||
return src
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`)
|
||||
|
||||
func ParseNetstat(output string, mode string,
|
||||
iocs map[string]IOCountersStat,
|
||||
) error {
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
exists := make([]string, 0, len(lines)-1)
|
||||
|
||||
columns := 9
|
||||
if mode == "inb" {
|
||||
columns = 6
|
||||
}
|
||||
for _, line := range lines {
|
||||
values := strings.Fields(line)
|
||||
if len(values) < 1 || values[0] == "Name" {
|
||||
continue
|
||||
}
|
||||
if common.StringsHas(exists, values[0]) {
|
||||
// skip if already get
|
||||
continue
|
||||
}
|
||||
|
||||
if len(values) < columns {
|
||||
continue
|
||||
}
|
||||
base := 1
|
||||
// sometimes Address is omitted
|
||||
if len(values) < columns {
|
||||
base = 0
|
||||
}
|
||||
|
||||
parsed := make([]uint64, 0, 8)
|
||||
var vv []string
|
||||
switch mode {
|
||||
case "inb":
|
||||
vv = []string{
|
||||
values[base+3], // BytesRecv
|
||||
values[base+4], // BytesSent
|
||||
}
|
||||
case "ind":
|
||||
vv = []string{
|
||||
values[base+3], // Ipkts
|
||||
values[base+4], // Idrop
|
||||
values[base+5], // Opkts
|
||||
values[base+6], // Odrops
|
||||
}
|
||||
case "ine":
|
||||
vv = []string{
|
||||
values[base+4], // Ierrs
|
||||
values[base+6], // Oerrs
|
||||
}
|
||||
}
|
||||
for _, target := range vv {
|
||||
if target == "-" {
|
||||
parsed = append(parsed, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := strconv.ParseUint(target, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsed = append(parsed, t)
|
||||
}
|
||||
exists = append(exists, values[0])
|
||||
|
||||
n, present := iocs[values[0]]
|
||||
if !present {
|
||||
n = IOCountersStat{Name: values[0]}
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "inb":
|
||||
n.BytesRecv = parsed[0]
|
||||
n.BytesSent = parsed[1]
|
||||
case "ind":
|
||||
n.PacketsRecv = parsed[0]
|
||||
n.Dropin = parsed[1]
|
||||
n.PacketsSent = parsed[2]
|
||||
n.Dropout = parsed[3]
|
||||
case "ine":
|
||||
n.Errin = parsed[0]
|
||||
n.Errout = parsed[1]
|
||||
}
|
||||
|
||||
iocs[n.Name] = n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
netstat, err := exec.LookPath("netstat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, netstat, "-inb")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out2, err := invoke.CommandWithContext(ctx, netstat, "-ind")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out3, err := invoke.CommandWithContext(ctx, netstat, "-ine")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iocs := make(map[string]IOCountersStat)
|
||||
|
||||
lines := strings.Split(string(out), "\n")
|
||||
ret := make([]IOCountersStat, 0, len(lines)-1)
|
||||
|
||||
err = ParseNetstat(string(out), "inb", iocs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ParseNetstat(string(out2), "ind", iocs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ParseNetstat(string(out3), "ine", iocs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ioc := range iocs {
|
||||
ret = append(ret, ioc)
|
||||
}
|
||||
|
||||
if !pernic {
|
||||
return getIOCountersAll(ret), nil
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func parseNetstatLine(line string) (ConnectionStat, error) {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 5 {
|
||||
return ConnectionStat{}, fmt.Errorf("wrong line,%s", line)
|
||||
}
|
||||
|
||||
var netType, netFamily uint32
|
||||
switch f[0] {
|
||||
case "tcp":
|
||||
netType = syscall.SOCK_STREAM
|
||||
netFamily = syscall.AF_INET
|
||||
case "udp":
|
||||
netType = syscall.SOCK_DGRAM
|
||||
netFamily = syscall.AF_INET
|
||||
case "tcp6":
|
||||
netType = syscall.SOCK_STREAM
|
||||
netFamily = syscall.AF_INET6
|
||||
case "udp6":
|
||||
netType = syscall.SOCK_DGRAM
|
||||
netFamily = syscall.AF_INET6
|
||||
default:
|
||||
return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0])
|
||||
}
|
||||
|
||||
laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily)
|
||||
if err != nil {
|
||||
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4])
|
||||
}
|
||||
|
||||
n := ConnectionStat{
|
||||
Fd: uint32(0), // not supported
|
||||
Family: uint32(netFamily),
|
||||
Type: uint32(netType),
|
||||
Laddr: laddr,
|
||||
Raddr: raddr,
|
||||
Pid: int32(0), // not supported
|
||||
}
|
||||
if len(f) == 6 {
|
||||
n.Status = f[5]
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func parseAddr(l string, family uint32) (Addr, error) {
|
||||
matches := portMatch.FindStringSubmatch(l)
|
||||
if matches == nil {
|
||||
return Addr{}, fmt.Errorf("wrong addr, %s", l)
|
||||
}
|
||||
host := matches[1]
|
||||
port := matches[2]
|
||||
if host == "*" {
|
||||
switch family {
|
||||
case syscall.AF_INET:
|
||||
host = "0.0.0.0"
|
||||
case syscall.AF_INET6:
|
||||
host = "::"
|
||||
default:
|
||||
return Addr{}, fmt.Errorf("unknown family, %d", family)
|
||||
}
|
||||
}
|
||||
lport, err := strconv.ParseInt(port, 10, 32)
|
||||
if err != nil {
|
||||
return Addr{}, err
|
||||
}
|
||||
return Addr{IP: host, Port: uint32(lport)}, nil
|
||||
}
|
||||
|
||||
func parseNetstatAddr(local, remote string, family uint32) (laddr, raddr Addr, err error) {
|
||||
laddr, err = parseAddr(local, family)
|
||||
if remote != "*.*" { // remote addr exists
|
||||
raddr, err = parseAddr(remote, family)
|
||||
if err != nil {
|
||||
return laddr, raddr, err
|
||||
}
|
||||
}
|
||||
|
||||
return laddr, raddr, err
|
||||
}
|
||||
|
||||
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
var ret []ConnectionStat
|
||||
|
||||
args := []string{"-na"}
|
||||
switch strings.ToLower(kind) {
|
||||
default:
|
||||
fallthrough
|
||||
case "", "all", "inet":
|
||||
// nothing to add
|
||||
case "inet4":
|
||||
args = append(args, "-finet")
|
||||
case "inet6":
|
||||
args = append(args, "-finet6")
|
||||
case "tcp":
|
||||
args = append(args, "-ptcp")
|
||||
case "tcp4":
|
||||
args = append(args, "-ptcp", "-finet")
|
||||
case "tcp6":
|
||||
args = append(args, "-ptcp", "-finet6")
|
||||
case "udp":
|
||||
args = append(args, "-pudp")
|
||||
case "udp4":
|
||||
args = append(args, "-pudp", "-finet")
|
||||
case "udp6":
|
||||
args = append(args, "-pudp", "-finet6")
|
||||
case "unix":
|
||||
return ret, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
netstat, err := exec.LookPath("netstat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, netstat, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.HasPrefix(line, "tcp") && !strings.HasPrefix(line, "udp") {
|
||||
continue
|
||||
}
|
||||
n, err := parseNetstatLine(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, n)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(_ context.Context, _ string, _ int32) ([]ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(_ context.Context, _ string, _ int) ([]ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build solaris
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var kstatSplit = regexp.MustCompile(`[:\s]+`)
|
||||
|
||||
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, errors.New("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)
|
||||
|
||||
for _, line := range lines {
|
||||
fields := kstatSplit.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), nil
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsWithContext(_ context.Context, _ string) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, false)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, true)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int, _ bool) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd || darwin
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(_ context.Context, _ string, _ int) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
var ret []ConnectionStat
|
||||
|
||||
args := []string{"-i"}
|
||||
switch strings.ToLower(kind) {
|
||||
default:
|
||||
fallthrough
|
||||
case "", "all", "inet":
|
||||
args = append(args, "tcp", "-i", "udp")
|
||||
case "inet4":
|
||||
args = append(args, "4")
|
||||
case "inet6":
|
||||
args = append(args, "6")
|
||||
case "tcp":
|
||||
args = append(args, "tcp")
|
||||
case "tcp4":
|
||||
args = append(args, "4tcp")
|
||||
case "tcp6":
|
||||
args = append(args, "6tcp")
|
||||
case "udp":
|
||||
args = append(args, "udp")
|
||||
case "udp4":
|
||||
args = append(args, "4udp")
|
||||
case "udp6":
|
||||
args = append(args, "6udp")
|
||||
case "unix":
|
||||
args = []string{"-U"}
|
||||
}
|
||||
|
||||
r, err := common.CallLsofWithContext(ctx, invoke, pid, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rr := range r {
|
||||
if strings.HasPrefix(rr, "COMMAND") {
|
||||
continue
|
||||
}
|
||||
n, err := parseNetLine(rr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, n)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var constMap = map[string]int{
|
||||
"unix": syscall.AF_UNIX,
|
||||
"TCP": syscall.SOCK_STREAM,
|
||||
"UDP": syscall.SOCK_DGRAM,
|
||||
"IPv4": syscall.AF_INET,
|
||||
"IPv6": syscall.AF_INET6,
|
||||
}
|
||||
|
||||
func parseNetLine(line string) (ConnectionStat, error) {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 8 {
|
||||
return ConnectionStat{}, fmt.Errorf("wrong line,%s", line)
|
||||
}
|
||||
|
||||
if len(f) == 8 {
|
||||
f = append(f, f[7])
|
||||
f[7] = "unix"
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseInt(f[1], 10, 32)
|
||||
if err != nil {
|
||||
return ConnectionStat{}, err
|
||||
}
|
||||
fd, err := strconv.ParseInt(strings.Trim(f[3], "u"), 10, 32)
|
||||
if err != nil {
|
||||
return ConnectionStat{}, fmt.Errorf("unknown fd, %s", f[3])
|
||||
}
|
||||
netFamily, ok := constMap[f[4]]
|
||||
if !ok {
|
||||
return ConnectionStat{}, fmt.Errorf("unknown family, %s", f[4])
|
||||
}
|
||||
netType, ok := constMap[f[7]]
|
||||
if !ok {
|
||||
return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[7])
|
||||
}
|
||||
|
||||
var laddr, raddr Addr
|
||||
if f[7] == "unix" {
|
||||
laddr.IP = f[8]
|
||||
} else {
|
||||
laddr, raddr, err = parseNetAddr(f[8])
|
||||
if err != nil {
|
||||
return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8])
|
||||
}
|
||||
}
|
||||
|
||||
n := ConnectionStat{
|
||||
Fd: uint32(fd),
|
||||
Family: uint32(netFamily),
|
||||
Type: uint32(netType),
|
||||
Laddr: laddr,
|
||||
Raddr: raddr,
|
||||
Pid: int32(pid),
|
||||
}
|
||||
if len(f) == 10 {
|
||||
n.Status = strings.Trim(f[9], "()")
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func parseAddr(l string) (Addr, error) {
|
||||
host, port, err := net.SplitHostPort(l)
|
||||
if err != nil {
|
||||
return Addr{}, fmt.Errorf("wrong addr, %s", l)
|
||||
}
|
||||
lport, err := strconv.ParseInt(port, 10, 32)
|
||||
if err != nil {
|
||||
return Addr{}, err
|
||||
}
|
||||
return Addr{IP: host, Port: uint32(lport)}, nil
|
||||
}
|
||||
|
||||
func parseNetAddr(line string) (laddr, raddr Addr, err error) {
|
||||
addrs := strings.Split(line, "->")
|
||||
if len(addrs) == 0 {
|
||||
return laddr, raddr, fmt.Errorf("wrong netaddr, %s", line)
|
||||
}
|
||||
laddr, err = parseAddr(addrs[0])
|
||||
if len(addrs) == 2 { // remote addr exists
|
||||
raddr, err = parseAddr(addrs[1])
|
||||
if err != nil {
|
||||
return laddr, raddr, err
|
||||
}
|
||||
}
|
||||
|
||||
return laddr, raddr, err
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
+731
@@ -0,0 +1,731 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
var (
|
||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
procGetExtendedTCPTable = modiphlpapi.NewProc("GetExtendedTcpTable")
|
||||
procGetExtendedUDPTable = modiphlpapi.NewProc("GetExtendedUdpTable")
|
||||
procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2")
|
||||
)
|
||||
|
||||
const (
|
||||
TCPTableBasicListener = iota
|
||||
TCPTableBasicConnections
|
||||
TCPTableBasicAll
|
||||
TCPTableOwnerPIDListener
|
||||
TCPTableOwnerPIDConnections
|
||||
TCPTableOwnerPIDAll
|
||||
TCPTableOwnerModuleListener
|
||||
TCPTableOwnerModuleConnections
|
||||
TCPTableOwnerModuleAll
|
||||
)
|
||||
|
||||
type netConnectionKindType struct {
|
||||
family uint32
|
||||
sockType uint32
|
||||
filename string
|
||||
}
|
||||
|
||||
var kindTCP4 = netConnectionKindType{
|
||||
family: syscall.AF_INET,
|
||||
sockType: syscall.SOCK_STREAM,
|
||||
filename: "tcp",
|
||||
}
|
||||
|
||||
var kindTCP6 = netConnectionKindType{
|
||||
family: syscall.AF_INET6,
|
||||
sockType: syscall.SOCK_STREAM,
|
||||
filename: "tcp6",
|
||||
}
|
||||
|
||||
var kindUDP4 = netConnectionKindType{
|
||||
family: syscall.AF_INET,
|
||||
sockType: syscall.SOCK_DGRAM,
|
||||
filename: "udp",
|
||||
}
|
||||
|
||||
var kindUDP6 = netConnectionKindType{
|
||||
family: syscall.AF_INET6,
|
||||
sockType: syscall.SOCK_DGRAM,
|
||||
filename: "udp6",
|
||||
}
|
||||
|
||||
var netConnectionKindMap = map[string][]netConnectionKindType{
|
||||
"all": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
|
||||
"tcp": {kindTCP4, kindTCP6},
|
||||
"tcp4": {kindTCP4},
|
||||
"tcp6": {kindTCP6},
|
||||
"udp": {kindUDP4, kindUDP6},
|
||||
"udp4": {kindUDP4},
|
||||
"udp6": {kindUDP6},
|
||||
"inet": {kindTCP4, kindTCP6, kindUDP4, kindUDP6},
|
||||
"inet4": {kindTCP4, kindUDP4},
|
||||
"inet6": {kindTCP6, kindUDP6},
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/ethr/blob/aecdaf923970e5a9b4c461b4e2e3963d781ad2cc/plt_windows.go#L114-L170
|
||||
type guid struct {
|
||||
Data1 uint32
|
||||
Data2 uint16
|
||||
Data3 uint16
|
||||
Data4 [8]byte
|
||||
}
|
||||
|
||||
const (
|
||||
maxStringSize = 256
|
||||
maxPhysAddressLength = 32
|
||||
pad0for64_4for32 = 0
|
||||
)
|
||||
|
||||
type mibIfRow2 struct {
|
||||
InterfaceLuid uint64
|
||||
InterfaceIndex uint32
|
||||
InterfaceGuid guid //nolint:revive //FIXME
|
||||
Alias [maxStringSize + 1]uint16
|
||||
Description [maxStringSize + 1]uint16
|
||||
PhysicalAddressLength uint32
|
||||
PhysicalAddress [maxPhysAddressLength]uint8
|
||||
PermanentPhysicalAddress [maxPhysAddressLength]uint8
|
||||
Mtu uint32
|
||||
Type uint32
|
||||
TunnelType uint32
|
||||
MediaType uint32
|
||||
PhysicalMediumType uint32
|
||||
AccessType uint32
|
||||
DirectionType uint32
|
||||
InterfaceAndOperStatusFlags uint32
|
||||
OperStatus uint32
|
||||
AdminStatus uint32
|
||||
MediaConnectState uint32
|
||||
NetworkGuid guid //nolint:revive //FIXME
|
||||
ConnectionType uint32
|
||||
padding1 [pad0for64_4for32]byte
|
||||
TransmitLinkSpeed uint64
|
||||
ReceiveLinkSpeed uint64
|
||||
InOctets uint64
|
||||
InUcastPkts uint64
|
||||
InNUcastPkts uint64
|
||||
InDiscards uint64
|
||||
InErrors uint64
|
||||
InUnknownProtos uint64
|
||||
InUcastOctets uint64
|
||||
InMulticastOctets uint64
|
||||
InBroadcastOctets uint64
|
||||
OutOctets uint64
|
||||
OutUcastPkts uint64
|
||||
OutNUcastPkts uint64
|
||||
OutDiscards uint64
|
||||
OutErrors uint64
|
||||
OutUcastOctets uint64
|
||||
OutMulticastOctets uint64
|
||||
OutBroadcastOctets uint64
|
||||
OutQLen uint64
|
||||
}
|
||||
|
||||
func IOCountersWithContext(_ context.Context, pernic bool) ([]IOCountersStat, error) {
|
||||
ifs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var counters []IOCountersStat
|
||||
|
||||
err = procGetIfEntry2.Find()
|
||||
if err == nil { // Vista+, uint64 values (issue#693)
|
||||
for _, ifi := range ifs {
|
||||
c := IOCountersStat{
|
||||
Name: ifi.Name,
|
||||
}
|
||||
|
||||
row := mibIfRow2{InterfaceIndex: uint32(ifi.Index)}
|
||||
ret, _, err := procGetIfEntry2.Call(uintptr(unsafe.Pointer(&row)))
|
||||
if ret != 0 {
|
||||
return nil, os.NewSyscallError("GetIfEntry2", err)
|
||||
}
|
||||
c.BytesSent = uint64(row.OutOctets)
|
||||
c.BytesRecv = uint64(row.InOctets)
|
||||
c.PacketsSent = uint64(row.OutUcastPkts)
|
||||
c.PacketsRecv = uint64(row.InUcastPkts)
|
||||
c.Errin = uint64(row.InErrors)
|
||||
c.Errout = uint64(row.OutErrors)
|
||||
c.Dropin = uint64(row.InDiscards)
|
||||
c.Dropout = uint64(row.OutDiscards)
|
||||
|
||||
counters = append(counters, c)
|
||||
}
|
||||
} else { // WinXP fallback, uint32 values
|
||||
for _, ifi := range ifs {
|
||||
c := IOCountersStat{
|
||||
Name: ifi.Name,
|
||||
}
|
||||
|
||||
row := windows.MibIfRow{Index: uint32(ifi.Index)}
|
||||
err = windows.GetIfEntry(&row)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("GetIfEntry", err)
|
||||
}
|
||||
c.BytesSent = uint64(row.OutOctets)
|
||||
c.BytesRecv = uint64(row.InOctets)
|
||||
c.PacketsSent = uint64(row.OutUcastPkts)
|
||||
c.PacketsRecv = uint64(row.InUcastPkts)
|
||||
c.Errin = uint64(row.InErrors)
|
||||
c.Errout = uint64(row.OutErrors)
|
||||
c.Dropin = uint64(row.InDiscards)
|
||||
c.Dropout = uint64(row.OutDiscards)
|
||||
|
||||
counters = append(counters, c)
|
||||
}
|
||||
}
|
||||
|
||||
if !pernic {
|
||||
return getIOCountersAll(counters), nil
|
||||
}
|
||||
return counters, nil
|
||||
}
|
||||
|
||||
func IOCountersByFileWithContext(ctx context.Context, pernic bool, _ string) ([]IOCountersStat, error) {
|
||||
return IOCountersWithContext(ctx, pernic)
|
||||
}
|
||||
|
||||
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithContext(_ context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
tmap, ok := netConnectionKindMap[kind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid kind, %s", kind)
|
||||
}
|
||||
return getProcInet(tmap, pid)
|
||||
}
|
||||
|
||||
func getProcInet(kinds []netConnectionKindType, pid int32) ([]ConnectionStat, error) {
|
||||
stats := make([]ConnectionStat, 0)
|
||||
|
||||
for _, kind := range kinds {
|
||||
s, err := getNetStatWithKind(kind)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if pid == 0 {
|
||||
stats = append(stats, s...)
|
||||
} else {
|
||||
for _, ns := range s {
|
||||
if ns.Pid != pid {
|
||||
continue
|
||||
}
|
||||
stats = append(stats, ns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func getNetStatWithKind(kindType netConnectionKindType) ([]ConnectionStat, error) {
|
||||
if kindType.filename == "" {
|
||||
return nil, errors.New("kind filename must be required")
|
||||
}
|
||||
|
||||
switch kindType.filename {
|
||||
case kindTCP4.filename:
|
||||
return getTCPConnections(kindTCP4.family)
|
||||
case kindTCP6.filename:
|
||||
return getTCPConnections(kindTCP6.family)
|
||||
case kindUDP4.filename:
|
||||
return getUDPConnections(kindUDP4.family)
|
||||
case kindUDP6.filename:
|
||||
return getUDPConnections(kindUDP6.family)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid kind filename, %s", kindType.filename)
|
||||
}
|
||||
|
||||
// Deprecated: use process.PidsWithContext instead
|
||||
func PidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
|
||||
return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0)
|
||||
}
|
||||
|
||||
func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, maxConn int) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, maxConn)
|
||||
}
|
||||
|
||||
func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) {
|
||||
return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, false)
|
||||
}
|
||||
|
||||
func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, maxConn int) ([]ConnectionStat, error) {
|
||||
return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, maxConn, true)
|
||||
}
|
||||
|
||||
func connectionsPidMaxWithoutUidsWithContext(_ context.Context, _ string, _ int32, _ int, _ bool) ([]ConnectionStat, error) {
|
||||
return []ConnectionStat{}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func FilterCountersWithContext(_ context.Context) ([]FilterStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ConntrackStatsWithContext(_ context.Context, _ bool) ([]ConntrackStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProtoCountersWithContext(_ context.Context, _ []string) ([]ProtoCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func getTableUintptr(family uint32, buf []byte) uintptr {
|
||||
var (
|
||||
pmibTCPTable pmibTCPTableOwnerPidAll
|
||||
pmibTCP6Table pmibTCP6TableOwnerPidAll
|
||||
|
||||
p uintptr
|
||||
)
|
||||
switch family {
|
||||
case kindTCP4.family:
|
||||
if len(buf) > 0 {
|
||||
pmibTCPTable = (*mibTCPTableOwnerPid)(unsafe.Pointer(&buf[0]))
|
||||
p = uintptr(unsafe.Pointer(pmibTCPTable))
|
||||
} else {
|
||||
p = uintptr(unsafe.Pointer(pmibTCPTable))
|
||||
}
|
||||
case kindTCP6.family:
|
||||
if len(buf) > 0 {
|
||||
pmibTCP6Table = (*mibTCP6TableOwnerPid)(unsafe.Pointer(&buf[0]))
|
||||
p = uintptr(unsafe.Pointer(pmibTCP6Table))
|
||||
} else {
|
||||
p = uintptr(unsafe.Pointer(pmibTCP6Table))
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func getTableInfo(filename string, table any) (index, step, length int) {
|
||||
switch filename {
|
||||
case kindTCP4.filename:
|
||||
index = int(unsafe.Sizeof(table.(pmibTCPTableOwnerPidAll).DwNumEntries))
|
||||
step = int(unsafe.Sizeof(table.(pmibTCPTableOwnerPidAll).Table))
|
||||
length = int(table.(pmibTCPTableOwnerPidAll).DwNumEntries)
|
||||
case kindTCP6.filename:
|
||||
index = int(unsafe.Sizeof(table.(pmibTCP6TableOwnerPidAll).DwNumEntries))
|
||||
step = int(unsafe.Sizeof(table.(pmibTCP6TableOwnerPidAll).Table))
|
||||
length = int(table.(pmibTCP6TableOwnerPidAll).DwNumEntries)
|
||||
case kindUDP4.filename:
|
||||
index = int(unsafe.Sizeof(table.(pmibUDPTableOwnerPid).DwNumEntries))
|
||||
step = int(unsafe.Sizeof(table.(pmibUDPTableOwnerPid).Table))
|
||||
length = int(table.(pmibUDPTableOwnerPid).DwNumEntries)
|
||||
case kindUDP6.filename:
|
||||
index = int(unsafe.Sizeof(table.(pmibUDP6TableOwnerPid).DwNumEntries))
|
||||
step = int(unsafe.Sizeof(table.(pmibUDP6TableOwnerPid).Table))
|
||||
length = int(table.(pmibUDP6TableOwnerPid).DwNumEntries)
|
||||
}
|
||||
|
||||
return index, step, length
|
||||
}
|
||||
|
||||
func getTCPConnections(family uint32) ([]ConnectionStat, error) {
|
||||
var (
|
||||
p uintptr
|
||||
buf []byte
|
||||
size uint32
|
||||
|
||||
pmibTCPTable pmibTCPTableOwnerPidAll
|
||||
pmibTCP6Table pmibTCP6TableOwnerPidAll
|
||||
)
|
||||
|
||||
if family == 0 {
|
||||
return nil, errors.New("faimly must be required")
|
||||
}
|
||||
|
||||
for {
|
||||
switch family {
|
||||
case kindTCP4.family:
|
||||
if len(buf) > 0 {
|
||||
pmibTCPTable = (*mibTCPTableOwnerPid)(unsafe.Pointer(&buf[0]))
|
||||
p = uintptr(unsafe.Pointer(pmibTCPTable))
|
||||
} else {
|
||||
p = uintptr(unsafe.Pointer(pmibTCPTable))
|
||||
}
|
||||
case kindTCP6.family:
|
||||
if len(buf) > 0 {
|
||||
pmibTCP6Table = (*mibTCP6TableOwnerPid)(unsafe.Pointer(&buf[0]))
|
||||
p = uintptr(unsafe.Pointer(pmibTCP6Table))
|
||||
} else {
|
||||
p = uintptr(unsafe.Pointer(pmibTCP6Table))
|
||||
}
|
||||
}
|
||||
|
||||
err := getExtendedTCPTable(p,
|
||||
&size,
|
||||
true,
|
||||
family,
|
||||
tcpTableOwnerPidAll,
|
||||
0)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, windows.ERROR_INSUFFICIENT_BUFFER) {
|
||||
return nil, err
|
||||
}
|
||||
buf = make([]byte, size)
|
||||
}
|
||||
|
||||
var (
|
||||
index, step int
|
||||
length int
|
||||
)
|
||||
|
||||
stats := make([]ConnectionStat, 0)
|
||||
switch family {
|
||||
case kindTCP4.family:
|
||||
index, step, length = getTableInfo(kindTCP4.filename, pmibTCPTable)
|
||||
case kindTCP6.family:
|
||||
index, step, length = getTableInfo(kindTCP6.filename, pmibTCP6Table)
|
||||
}
|
||||
|
||||
if length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
switch family {
|
||||
case kindTCP4.family:
|
||||
mibs := (*mibTCPRowOwnerPid)(unsafe.Pointer(&buf[index]))
|
||||
ns := mibs.convertToConnectionStat()
|
||||
stats = append(stats, ns)
|
||||
case kindTCP6.family:
|
||||
mibs := (*mibTCP6RowOwnerPid)(unsafe.Pointer(&buf[index]))
|
||||
ns := mibs.convertToConnectionStat()
|
||||
stats = append(stats, ns)
|
||||
}
|
||||
|
||||
index += step
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func getUDPConnections(family uint32) ([]ConnectionStat, error) {
|
||||
var (
|
||||
p uintptr
|
||||
buf []byte
|
||||
size uint32
|
||||
|
||||
pmibUDPTable pmibUDPTableOwnerPid
|
||||
pmibUDP6Table pmibUDP6TableOwnerPid
|
||||
)
|
||||
|
||||
if family == 0 {
|
||||
return nil, errors.New("faimly must be required")
|
||||
}
|
||||
|
||||
for {
|
||||
switch family {
|
||||
case kindUDP4.family:
|
||||
if len(buf) > 0 {
|
||||
pmibUDPTable = (*mibUDPTableOwnerPid)(unsafe.Pointer(&buf[0]))
|
||||
p = uintptr(unsafe.Pointer(pmibUDPTable))
|
||||
} else {
|
||||
p = uintptr(unsafe.Pointer(pmibUDPTable))
|
||||
}
|
||||
case kindUDP6.family:
|
||||
if len(buf) > 0 {
|
||||
pmibUDP6Table = (*mibUDP6TableOwnerPid)(unsafe.Pointer(&buf[0]))
|
||||
p = uintptr(unsafe.Pointer(pmibUDP6Table))
|
||||
} else {
|
||||
p = uintptr(unsafe.Pointer(pmibUDP6Table))
|
||||
}
|
||||
}
|
||||
|
||||
err := getExtendedUDPTable(
|
||||
p,
|
||||
&size,
|
||||
true,
|
||||
family,
|
||||
udpTableOwnerPid,
|
||||
0,
|
||||
)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, windows.ERROR_INSUFFICIENT_BUFFER) {
|
||||
return nil, err
|
||||
}
|
||||
buf = make([]byte, size)
|
||||
}
|
||||
|
||||
var index, step, length int
|
||||
|
||||
stats := make([]ConnectionStat, 0)
|
||||
switch family {
|
||||
case kindUDP4.family:
|
||||
index, step, length = getTableInfo(kindUDP4.filename, pmibUDPTable)
|
||||
case kindUDP6.family:
|
||||
index, step, length = getTableInfo(kindUDP6.filename, pmibUDP6Table)
|
||||
}
|
||||
|
||||
if length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
switch family {
|
||||
case kindUDP4.family:
|
||||
mibs := (*mibUDPRowOwnerPid)(unsafe.Pointer(&buf[index]))
|
||||
ns := mibs.convertToConnectionStat()
|
||||
stats = append(stats, ns)
|
||||
case kindUDP6.family:
|
||||
mibs := (*mibUDP6RowOwnerPid)(unsafe.Pointer(&buf[index]))
|
||||
ns := mibs.convertToConnectionStat()
|
||||
stats = append(stats, ns)
|
||||
}
|
||||
|
||||
index += step
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// tcpStatuses https://msdn.microsoft.com/en-us/library/windows/desktop/bb485761(v=vs.85).aspx
|
||||
var tcpStatuses = map[mibTCPState]string{
|
||||
1: "CLOSED",
|
||||
2: "LISTEN",
|
||||
3: "SYN_SENT",
|
||||
4: "SYN_RECEIVED",
|
||||
5: "ESTABLISHED",
|
||||
6: "FIN_WAIT_1",
|
||||
7: "FIN_WAIT_2",
|
||||
8: "CLOSE_WAIT",
|
||||
9: "CLOSING",
|
||||
10: "LAST_ACK",
|
||||
11: "TIME_WAIT",
|
||||
12: "DELETE",
|
||||
}
|
||||
|
||||
func getExtendedTCPTable(pTCPTable uintptr, pdwSize *uint32, bOrder bool, ulAf uint32, tableClass tcpTableClass, reserved uint32) (errcode error) {
|
||||
r1, _, _ := syscall.Syscall6(procGetExtendedTCPTable.Addr(), 6, pTCPTable, uintptr(unsafe.Pointer(pdwSize)), getUintptrFromBool(bOrder), uintptr(ulAf), uintptr(tableClass), uintptr(reserved))
|
||||
if r1 != 0 {
|
||||
errcode = syscall.Errno(r1)
|
||||
}
|
||||
return errcode
|
||||
}
|
||||
|
||||
func getExtendedUDPTable(pUDPTable uintptr, pdwSize *uint32, bOrder bool, ulAf uint32, tableClass udpTableClass, reserved uint32) (errcode error) {
|
||||
r1, _, _ := syscall.Syscall6(procGetExtendedUDPTable.Addr(), 6, pUDPTable, uintptr(unsafe.Pointer(pdwSize)), getUintptrFromBool(bOrder), uintptr(ulAf), uintptr(tableClass), uintptr(reserved))
|
||||
if r1 != 0 {
|
||||
errcode = syscall.Errno(r1)
|
||||
}
|
||||
return errcode
|
||||
}
|
||||
|
||||
func getUintptrFromBool(b bool) uintptr {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const anySize = 1
|
||||
|
||||
// type MIB_TCP_STATE int32
|
||||
type mibTCPState int32
|
||||
|
||||
type tcpTableClass int32
|
||||
|
||||
const (
|
||||
tcpTableBasicListener tcpTableClass = iota
|
||||
tcpTableBasicConnections
|
||||
tcpTableBasicAll
|
||||
tcpTableOwnerPidListener
|
||||
tcpTableOwnerPidConnections
|
||||
tcpTableOwnerPidAll
|
||||
tcpTableOwnerModuleListener
|
||||
tcpTableOwnerModuleConnections
|
||||
tcpTableOwnerModuleAll
|
||||
)
|
||||
|
||||
type udpTableClass int32
|
||||
|
||||
const (
|
||||
udpTableBasic udpTableClass = iota
|
||||
udpTableOwnerPid
|
||||
udpTableOwnerModule
|
||||
)
|
||||
|
||||
// TCP
|
||||
|
||||
type mibTCPRowOwnerPid struct {
|
||||
DwState uint32
|
||||
DwLocalAddr uint32
|
||||
DwLocalPort uint32
|
||||
DwRemoteAddr uint32
|
||||
DwRemotePort uint32
|
||||
DwOwningPid uint32
|
||||
}
|
||||
|
||||
func (m *mibTCPRowOwnerPid) convertToConnectionStat() ConnectionStat {
|
||||
ns := ConnectionStat{
|
||||
Family: kindTCP4.family,
|
||||
Type: kindTCP4.sockType,
|
||||
Laddr: Addr{
|
||||
IP: parseIPv4HexString(m.DwLocalAddr),
|
||||
Port: uint32(decodePort(m.DwLocalPort)),
|
||||
},
|
||||
Raddr: Addr{
|
||||
IP: parseIPv4HexString(m.DwRemoteAddr),
|
||||
Port: uint32(decodePort(m.DwRemotePort)),
|
||||
},
|
||||
Pid: int32(m.DwOwningPid),
|
||||
Status: tcpStatuses[mibTCPState(m.DwState)],
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
type mibTCPTableOwnerPid struct {
|
||||
DwNumEntries uint32
|
||||
Table [anySize]mibTCPRowOwnerPid
|
||||
}
|
||||
|
||||
type mibTCP6RowOwnerPid struct {
|
||||
UcLocalAddr [16]byte
|
||||
DwLocalScopeId uint32
|
||||
DwLocalPort uint32
|
||||
UcRemoteAddr [16]byte
|
||||
DwRemoteScopeId uint32
|
||||
DwRemotePort uint32
|
||||
DwState uint32
|
||||
DwOwningPid uint32
|
||||
}
|
||||
|
||||
func (m *mibTCP6RowOwnerPid) convertToConnectionStat() ConnectionStat {
|
||||
ns := ConnectionStat{
|
||||
Family: kindTCP6.family,
|
||||
Type: kindTCP6.sockType,
|
||||
Laddr: Addr{
|
||||
IP: parseIPv6HexString(m.UcLocalAddr),
|
||||
Port: uint32(decodePort(m.DwLocalPort)),
|
||||
},
|
||||
Raddr: Addr{
|
||||
IP: parseIPv6HexString(m.UcRemoteAddr),
|
||||
Port: uint32(decodePort(m.DwRemotePort)),
|
||||
},
|
||||
Pid: int32(m.DwOwningPid),
|
||||
Status: tcpStatuses[mibTCPState(m.DwState)],
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
type mibTCP6TableOwnerPid struct {
|
||||
DwNumEntries uint32
|
||||
Table [anySize]mibTCP6RowOwnerPid
|
||||
}
|
||||
|
||||
type (
|
||||
pmibTCPTableOwnerPidAll *mibTCPTableOwnerPid
|
||||
pmibTCP6TableOwnerPidAll *mibTCP6TableOwnerPid
|
||||
)
|
||||
|
||||
// UDP
|
||||
|
||||
type mibUDPRowOwnerPid struct {
|
||||
DwLocalAddr uint32
|
||||
DwLocalPort uint32
|
||||
DwOwningPid uint32
|
||||
}
|
||||
|
||||
func (m *mibUDPRowOwnerPid) convertToConnectionStat() ConnectionStat {
|
||||
ns := ConnectionStat{
|
||||
Family: kindUDP4.family,
|
||||
Type: kindUDP4.sockType,
|
||||
Laddr: Addr{
|
||||
IP: parseIPv4HexString(m.DwLocalAddr),
|
||||
Port: uint32(decodePort(m.DwLocalPort)),
|
||||
},
|
||||
Pid: int32(m.DwOwningPid),
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
type mibUDPTableOwnerPid struct {
|
||||
DwNumEntries uint32
|
||||
Table [anySize]mibUDPRowOwnerPid
|
||||
}
|
||||
|
||||
type mibUDP6RowOwnerPid struct {
|
||||
UcLocalAddr [16]byte
|
||||
DwLocalScopeId uint32
|
||||
DwLocalPort uint32
|
||||
DwOwningPid uint32
|
||||
}
|
||||
|
||||
func (m *mibUDP6RowOwnerPid) convertToConnectionStat() ConnectionStat {
|
||||
ns := ConnectionStat{
|
||||
Family: kindUDP6.family,
|
||||
Type: kindUDP6.sockType,
|
||||
Laddr: Addr{
|
||||
IP: parseIPv6HexString(m.UcLocalAddr),
|
||||
Port: uint32(decodePort(m.DwLocalPort)),
|
||||
},
|
||||
Pid: int32(m.DwOwningPid),
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
type mibUDP6TableOwnerPid struct {
|
||||
DwNumEntries uint32
|
||||
Table [anySize]mibUDP6RowOwnerPid
|
||||
}
|
||||
|
||||
type (
|
||||
pmibUDPTableOwnerPid *mibUDPTableOwnerPid
|
||||
pmibUDP6TableOwnerPid *mibUDP6TableOwnerPid
|
||||
)
|
||||
|
||||
func decodePort(port uint32) uint16 {
|
||||
return syscall.Ntohs(uint16(port))
|
||||
}
|
||||
|
||||
func parseIPv4HexString(addr uint32) string {
|
||||
return fmt.Sprintf("%d.%d.%d.%d", addr&255, addr>>8&255, addr>>16&255, addr>>24&255)
|
||||
}
|
||||
|
||||
func parseIPv6HexString(addr [16]byte) string {
|
||||
var ret [16]byte
|
||||
for i := 0; i < 16; i++ {
|
||||
ret[i] = uint8(addr[i])
|
||||
}
|
||||
|
||||
// convert []byte to net.IP
|
||||
ip := net.IP(ret[:])
|
||||
return ip.String()
|
||||
}
|
||||
+645
@@ -0,0 +1,645 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
var (
|
||||
invoke common.Invoker = common.Invoke{}
|
||||
strictIntPtrn = regexp.MustCompile(`^\d+$`)
|
||||
ErrorNoChildren = errors.New("process does not have children") // Deprecated: ErrorNoChildren is never returned by process.Children(), check its returned []*Process slice length instead
|
||||
ErrorProcessNotRunning = errors.New("process does not exist")
|
||||
ErrorNotPermitted = errors.New("operation not permitted")
|
||||
)
|
||||
|
||||
type Process struct {
|
||||
Pid int32 `json:"pid"`
|
||||
name string
|
||||
status string
|
||||
parent int32
|
||||
parentMutex sync.RWMutex // for windows ppid cache
|
||||
numCtxSwitches *NumCtxSwitchesStat
|
||||
uids []uint32
|
||||
gids []uint32
|
||||
groups []uint32
|
||||
numThreads int32
|
||||
memInfo *MemoryInfoStat
|
||||
sigInfo *SignalInfoStat
|
||||
createTime int64
|
||||
|
||||
lastCPUTimes *cpu.TimesStat
|
||||
lastCPUTime time.Time
|
||||
|
||||
tgid int32
|
||||
}
|
||||
|
||||
// Process status
|
||||
const (
|
||||
// Running marks a task a running or runnable (on the run queue)
|
||||
Running = "running"
|
||||
// Blocked marks a task waiting on a short, uninterruptible operation (usually I/O)
|
||||
Blocked = "blocked"
|
||||
// Idle marks a task sleeping for more than about 20 seconds
|
||||
Idle = "idle"
|
||||
// Lock marks a task waiting to acquire a lock
|
||||
Lock = "lock"
|
||||
// Sleep marks task waiting for short, interruptible operation
|
||||
Sleep = "sleep"
|
||||
// Stop marks a stopped process
|
||||
Stop = "stop"
|
||||
// Wait marks an idle interrupt thread (or paging in pre 2.6.xx Linux)
|
||||
Wait = "wait"
|
||||
// Zombie marks a defunct process, terminated but not reaped by its parent
|
||||
Zombie = "zombie"
|
||||
|
||||
// Solaris states. See https://github.com/collectd/collectd/blob/1da3305c10c8ff9a63081284cf3d4bb0f6daffd8/src/processes.c#L2115
|
||||
Daemon = "daemon"
|
||||
Detached = "detached"
|
||||
System = "system"
|
||||
Orphan = "orphan"
|
||||
|
||||
UnknownState = ""
|
||||
)
|
||||
|
||||
type OpenFilesStat struct {
|
||||
Path string `json:"path"`
|
||||
Fd uint64 `json:"fd"`
|
||||
}
|
||||
|
||||
type MemoryInfoStat struct {
|
||||
RSS uint64 `json:"rss"` // bytes
|
||||
VMS uint64 `json:"vms"` // bytes
|
||||
HWM uint64 `json:"hwm"` // bytes
|
||||
Data uint64 `json:"data"` // bytes
|
||||
Stack uint64 `json:"stack"` // bytes
|
||||
Locked uint64 `json:"locked"` // bytes
|
||||
Swap uint64 `json:"swap"` // bytes
|
||||
}
|
||||
|
||||
type SignalInfoStat struct {
|
||||
PendingProcess uint64 `json:"pending_process"`
|
||||
PendingThread uint64 `json:"pending_thread"`
|
||||
Blocked uint64 `json:"blocked"`
|
||||
Ignored uint64 `json:"ignored"`
|
||||
Caught uint64 `json:"caught"`
|
||||
}
|
||||
|
||||
type RlimitStat struct {
|
||||
Resource int32 `json:"resource"`
|
||||
Soft uint64 `json:"soft"`
|
||||
Hard uint64 `json:"hard"`
|
||||
Used uint64 `json:"used"`
|
||||
}
|
||||
|
||||
type IOCountersStat struct {
|
||||
// ReadCount is a number of read I/O operations such as syscalls.
|
||||
ReadCount uint64 `json:"readCount"`
|
||||
// WriteCount is a number of read I/O operations such as syscalls.
|
||||
WriteCount uint64 `json:"writeCount"`
|
||||
// ReadBytes is a number of all I/O read in bytes. This includes disk I/O on Linux and Windows.
|
||||
ReadBytes uint64 `json:"readBytes"`
|
||||
// WriteBytes is a number of all I/O write in bytes. This includes disk I/O on Linux and Windows.
|
||||
WriteBytes uint64 `json:"writeBytes"`
|
||||
// DiskReadBytes is a number of disk I/O write in bytes. Currently only Linux has this value.
|
||||
DiskReadBytes uint64 `json:"diskReadBytes"`
|
||||
// DiskWriteBytes is a number of disk I/O read in bytes. Currently only Linux has this value.
|
||||
DiskWriteBytes uint64 `json:"diskWriteBytes"`
|
||||
}
|
||||
|
||||
type NumCtxSwitchesStat struct {
|
||||
Voluntary int64 `json:"voluntary"`
|
||||
Involuntary int64 `json:"involuntary"`
|
||||
}
|
||||
|
||||
type PageFaultsStat struct {
|
||||
MinorFaults uint64 `json:"minorFaults"`
|
||||
MajorFaults uint64 `json:"majorFaults"`
|
||||
ChildMinorFaults uint64 `json:"childMinorFaults"`
|
||||
ChildMajorFaults uint64 `json:"childMajorFaults"`
|
||||
}
|
||||
|
||||
// Resource limit constants are from /usr/include/x86_64-linux-gnu/bits/resource.h
|
||||
// from libc6-dev package in Ubuntu 16.10
|
||||
const (
|
||||
RLIMIT_CPU int32 = 0
|
||||
RLIMIT_FSIZE int32 = 1
|
||||
RLIMIT_DATA int32 = 2
|
||||
RLIMIT_STACK int32 = 3
|
||||
RLIMIT_CORE int32 = 4
|
||||
RLIMIT_RSS int32 = 5
|
||||
RLIMIT_NPROC int32 = 6
|
||||
RLIMIT_NOFILE int32 = 7
|
||||
RLIMIT_MEMLOCK int32 = 8
|
||||
RLIMIT_AS int32 = 9
|
||||
RLIMIT_LOCKS int32 = 10
|
||||
RLIMIT_SIGPENDING int32 = 11
|
||||
RLIMIT_MSGQUEUE int32 = 12
|
||||
RLIMIT_NICE int32 = 13
|
||||
RLIMIT_RTPRIO int32 = 14
|
||||
RLIMIT_RTTIME int32 = 15
|
||||
)
|
||||
|
||||
func (p Process) String() string {
|
||||
s, _ := json.Marshal(p)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (o OpenFilesStat) String() string {
|
||||
s, _ := json.Marshal(o)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (m MemoryInfoStat) String() string {
|
||||
s, _ := json.Marshal(m)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (r RlimitStat) String() string {
|
||||
s, _ := json.Marshal(r)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (i IOCountersStat) String() string {
|
||||
s, _ := json.Marshal(i)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (p NumCtxSwitchesStat) String() string {
|
||||
s, _ := json.Marshal(p)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
var enableBootTimeCache bool
|
||||
|
||||
// EnableBootTimeCache change cache behavior of BootTime. If true, cache BootTime value. Default is false.
|
||||
func EnableBootTimeCache(enable bool) {
|
||||
enableBootTimeCache = enable
|
||||
}
|
||||
|
||||
// Pids returns a slice of process ID list which are running now.
|
||||
func Pids() ([]int32, error) {
|
||||
return PidsWithContext(context.Background())
|
||||
}
|
||||
|
||||
func PidsWithContext(ctx context.Context) ([]int32, error) {
|
||||
pids, err := pidsWithContext(ctx)
|
||||
sort.Slice(pids, func(i, j int) bool { return pids[i] < pids[j] })
|
||||
return pids, err
|
||||
}
|
||||
|
||||
// Processes returns a slice of pointers to Process structs for all
|
||||
// currently running processes.
|
||||
func Processes() ([]*Process, error) {
|
||||
return ProcessesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NewProcess creates a new Process instance, it only stores the pid and
|
||||
// checks that the process exists. Other method on Process can be used
|
||||
// to get more information about the process. An error will be returned
|
||||
// if the process does not exist.
|
||||
func NewProcess(pid int32) (*Process, error) {
|
||||
return NewProcessWithContext(context.Background(), pid)
|
||||
}
|
||||
|
||||
func NewProcessWithContext(ctx context.Context, pid int32) (*Process, error) {
|
||||
p := &Process{
|
||||
Pid: pid,
|
||||
}
|
||||
|
||||
exists, err := PidExistsWithContext(ctx, pid)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
if !exists {
|
||||
return p, ErrorProcessNotRunning
|
||||
}
|
||||
p.CreateTimeWithContext(ctx)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func PidExists(pid int32) (bool, error) {
|
||||
return PidExistsWithContext(context.Background(), pid)
|
||||
}
|
||||
|
||||
// Background returns true if the process is in background, false otherwise.
|
||||
func (p *Process) Background() (bool, error) {
|
||||
return p.BackgroundWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Process) BackgroundWithContext(ctx context.Context) (bool, error) {
|
||||
fg, err := p.ForegroundWithContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !fg, err
|
||||
}
|
||||
|
||||
// If interval is 0, return difference from last call(non-blocking).
|
||||
// If interval > 0, wait interval sec and return difference between start and end.
|
||||
func (p *Process) Percent(interval time.Duration) (float64, error) {
|
||||
return p.PercentWithContext(context.Background(), interval)
|
||||
}
|
||||
|
||||
func (p *Process) PercentWithContext(ctx context.Context, interval time.Duration) (float64, error) {
|
||||
cpuTimes, err := p.TimesWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
if interval > 0 {
|
||||
p.lastCPUTimes = cpuTimes
|
||||
p.lastCPUTime = now
|
||||
if err := common.Sleep(ctx, interval); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cpuTimes, err = p.TimesWithContext(ctx)
|
||||
now = time.Now()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else if p.lastCPUTimes == nil {
|
||||
// invoked first time
|
||||
p.lastCPUTimes = cpuTimes
|
||||
p.lastCPUTime = now
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
numcpu := runtime.NumCPU()
|
||||
delta := (now.Sub(p.lastCPUTime).Seconds()) * float64(numcpu)
|
||||
ret := calculatePercent(p.lastCPUTimes, cpuTimes, delta, numcpu)
|
||||
p.lastCPUTimes = cpuTimes
|
||||
p.lastCPUTime = now
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// IsRunning returns whether the process is still running or not.
|
||||
func (p *Process) IsRunning() (bool, error) {
|
||||
return p.IsRunningWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Process) IsRunningWithContext(ctx context.Context) (bool, error) {
|
||||
createTime, err := p.CreateTimeWithContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
p2, err := NewProcessWithContext(ctx, p.Pid)
|
||||
if errors.Is(err, ErrorProcessNotRunning) {
|
||||
return false, nil
|
||||
}
|
||||
createTime2, err := p2.CreateTimeWithContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return createTime == createTime2, nil
|
||||
}
|
||||
|
||||
// CreateTime returns created time of the process in milliseconds since the epoch, in UTC.
|
||||
func (p *Process) CreateTime() (int64, error) {
|
||||
return p.CreateTimeWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Process) CreateTimeWithContext(ctx context.Context) (int64, error) {
|
||||
if p.createTime != 0 {
|
||||
return p.createTime, nil
|
||||
}
|
||||
createTime, err := p.createTimeWithContext(ctx)
|
||||
p.createTime = createTime
|
||||
return p.createTime, err
|
||||
}
|
||||
|
||||
func calculatePercent(t1, t2 *cpu.TimesStat, delta float64, numcpu int) float64 {
|
||||
if delta == 0 {
|
||||
return 0
|
||||
}
|
||||
// https://github.com/giampaolo/psutil/blob/c034e6692cf736b5e87d14418a8153bb03f6cf42/psutil/__init__.py#L1064
|
||||
deltaProc := (t2.User - t1.User) + (t2.System - t1.System)
|
||||
if deltaProc <= 0 {
|
||||
return 0
|
||||
}
|
||||
overallPercent := ((deltaProc / delta) * 100) * float64(numcpu)
|
||||
return overallPercent
|
||||
}
|
||||
|
||||
// MemoryPercent returns how many percent of the total RAM this process uses
|
||||
func (p *Process) MemoryPercent() (float32, error) {
|
||||
return p.MemoryPercentWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Process) MemoryPercentWithContext(ctx context.Context) (float32, error) {
|
||||
machineMemory, err := mem.VirtualMemoryWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total := machineMemory.Total
|
||||
|
||||
processMemory, err := p.MemoryInfoWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
used := processMemory.RSS
|
||||
|
||||
return (100 * float32(used) / float32(total)), nil
|
||||
}
|
||||
|
||||
// CPUPercent returns how many percent of the CPU time this process uses
|
||||
func (p *Process) CPUPercent() (float64, error) {
|
||||
return p.CPUPercentWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Process) CPUPercentWithContext(ctx context.Context) (float64, error) {
|
||||
createTime, err := p.createTimeWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
cput, err := p.TimesWithContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
created := time.Unix(0, createTime*int64(time.Millisecond))
|
||||
totalTime := time.Since(created).Seconds()
|
||||
if totalTime <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return 100 * cput.Total() / totalTime, nil
|
||||
}
|
||||
|
||||
// Groups returns all group IDs(include supplementary groups) of the process as a slice of the int
|
||||
func (p *Process) Groups() ([]uint32, error) {
|
||||
return p.GroupsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Ppid returns Parent Process ID of the process.
|
||||
func (p *Process) Ppid() (int32, error) {
|
||||
return p.PpidWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Name returns name of the process.
|
||||
func (p *Process) Name() (string, error) {
|
||||
return p.NameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Exe returns executable path of the process.
|
||||
func (p *Process) Exe() (string, error) {
|
||||
return p.ExeWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Cmdline returns the command line arguments of the process as a string with
|
||||
// each argument separated by 0x20 ascii character.
|
||||
func (p *Process) Cmdline() (string, error) {
|
||||
return p.CmdlineWithContext(context.Background())
|
||||
}
|
||||
|
||||
// CmdlineSlice returns the command line arguments of the process as a slice with each
|
||||
// element being an argument.
|
||||
//
|
||||
// On Windows, this assumes the command line is encoded according to the convention accepted by
|
||||
// [golang.org/x/sys/windows.CmdlineToArgv] (the most common convention). If this is not suitable,
|
||||
// you should instead use [Process.Cmdline] and parse the command line according to your specific
|
||||
// requirements.
|
||||
func (p *Process) CmdlineSlice() ([]string, error) {
|
||||
return p.CmdlineSliceWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Cwd returns current working directory of the process.
|
||||
func (p *Process) Cwd() (string, error) {
|
||||
return p.CwdWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Parent returns parent Process of the process.
|
||||
func (p *Process) Parent() (*Process, error) {
|
||||
return p.ParentWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ParentWithContext returns parent Process of the process.
|
||||
func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
|
||||
ppid, err := p.PpidWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewProcessWithContext(ctx, ppid)
|
||||
}
|
||||
|
||||
// Status returns the process status.
|
||||
// Return value could be one of these.
|
||||
// R: Running S: Sleep T: Stop I: Idle
|
||||
// Z: Zombie W: Wait L: Lock
|
||||
// The character is same within all supported platforms.
|
||||
func (p *Process) Status() ([]string, error) {
|
||||
return p.StatusWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Foreground returns true if the process is in foreground, false otherwise.
|
||||
func (p *Process) Foreground() (bool, error) {
|
||||
return p.ForegroundWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Uids returns user ids of the process as a slice of the int
|
||||
func (p *Process) Uids() ([]uint32, error) {
|
||||
return p.UidsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Gids returns group ids of the process as a slice of the int
|
||||
func (p *Process) Gids() ([]uint32, error) {
|
||||
return p.GidsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Terminal returns a terminal which is associated with the process.
|
||||
func (p *Process) Terminal() (string, error) {
|
||||
return p.TerminalWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Nice returns a nice value (priority).
|
||||
func (p *Process) Nice() (int32, error) {
|
||||
return p.NiceWithContext(context.Background())
|
||||
}
|
||||
|
||||
// IOnice returns process I/O nice value (priority).
|
||||
func (p *Process) IOnice() (int32, error) {
|
||||
return p.IOniceWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Rlimit returns Resource Limits.
|
||||
func (p *Process) Rlimit() ([]RlimitStat, error) {
|
||||
return p.RlimitWithContext(context.Background())
|
||||
}
|
||||
|
||||
// RlimitUsage returns Resource Limits.
|
||||
// If gatherUsed is true, the currently used value will be gathered and added
|
||||
// to the resulting RlimitStat.
|
||||
func (p *Process) RlimitUsage(gatherUsed bool) ([]RlimitStat, error) {
|
||||
return p.RlimitUsageWithContext(context.Background(), gatherUsed)
|
||||
}
|
||||
|
||||
// IOCounters returns IO Counters.
|
||||
func (p *Process) IOCounters() (*IOCountersStat, error) {
|
||||
return p.IOCountersWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumCtxSwitches returns the number of the context switches of the process.
|
||||
func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) {
|
||||
return p.NumCtxSwitchesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumFDs returns the number of File Descriptors used by the process.
|
||||
func (p *Process) NumFDs() (int32, error) {
|
||||
return p.NumFDsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumThreads returns the number of threads used by the process.
|
||||
func (p *Process) NumThreads() (int32, error) {
|
||||
return p.NumThreadsWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Process) Threads() (map[int32]*cpu.TimesStat, error) {
|
||||
return p.ThreadsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Times returns CPU times of the process.
|
||||
func (p *Process) Times() (*cpu.TimesStat, error) {
|
||||
return p.TimesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// CPUAffinity returns CPU affinity of the process.
|
||||
func (p *Process) CPUAffinity() ([]int32, error) {
|
||||
return p.CPUAffinityWithContext(context.Background())
|
||||
}
|
||||
|
||||
// MemoryInfo returns generic process memory information,
|
||||
// such as RSS and VMS.
|
||||
func (p *Process) MemoryInfo() (*MemoryInfoStat, error) {
|
||||
return p.MemoryInfoWithContext(context.Background())
|
||||
}
|
||||
|
||||
// MemoryInfoEx returns platform-specific process memory information.
|
||||
func (p *Process) MemoryInfoEx() (*MemoryInfoExStat, error) {
|
||||
return p.MemoryInfoExWithContext(context.Background())
|
||||
}
|
||||
|
||||
// PageFaults returns the process's page fault counters.
|
||||
func (p *Process) PageFaults() (*PageFaultsStat, error) {
|
||||
return p.PageFaultsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Children returns the children of the process represented as a slice
|
||||
// of pointers to Process type.
|
||||
func (p *Process) Children() ([]*Process, error) {
|
||||
return p.ChildrenWithContext(context.Background())
|
||||
}
|
||||
|
||||
// OpenFiles returns a slice of OpenFilesStat opend by the process.
|
||||
// OpenFilesStat includes a file path and file descriptor.
|
||||
func (p *Process) OpenFiles() ([]OpenFilesStat, error) {
|
||||
return p.OpenFilesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Connections returns a slice of net.ConnectionStat used by the process.
|
||||
// This returns all kind of the connection. This means TCP, UDP or UNIX.
|
||||
func (p *Process) Connections() ([]net.ConnectionStat, error) {
|
||||
return p.ConnectionsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ConnectionsMax returns a slice of net.ConnectionStat used by the process at most `max`.
|
||||
func (p *Process) ConnectionsMax(maxConn int) ([]net.ConnectionStat, error) {
|
||||
return p.ConnectionsMaxWithContext(context.Background(), maxConn)
|
||||
}
|
||||
|
||||
// MemoryMaps get memory maps from /proc/(pid)/smaps
|
||||
func (p *Process) MemoryMaps(grouped bool) (*[]MemoryMapsStat, error) {
|
||||
return p.MemoryMapsWithContext(context.Background(), grouped)
|
||||
}
|
||||
|
||||
// Tgid returns thread group id of the process.
|
||||
func (p *Process) Tgid() (int32, error) {
|
||||
return p.TgidWithContext(context.Background())
|
||||
}
|
||||
|
||||
// SendSignal sends a unix.Signal to the process.
|
||||
func (p *Process) SendSignal(sig Signal) error {
|
||||
return p.SendSignalWithContext(context.Background(), sig)
|
||||
}
|
||||
|
||||
// Suspend sends SIGSTOP to the process.
|
||||
func (p *Process) Suspend() error {
|
||||
return p.SuspendWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Resume sends SIGCONT to the process.
|
||||
func (p *Process) Resume() error {
|
||||
return p.ResumeWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Terminate sends SIGTERM to the process.
|
||||
func (p *Process) Terminate() error {
|
||||
return p.TerminateWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Kill sends SIGKILL to the process.
|
||||
func (p *Process) Kill() error {
|
||||
return p.KillWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Username returns a username of the process.
|
||||
func (p *Process) Username() (string, error) {
|
||||
return p.UsernameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// Environ returns the environment variables of the process.
|
||||
func (p *Process) Environ() ([]string, error) {
|
||||
return p.EnvironWithContext(context.Background())
|
||||
}
|
||||
|
||||
// convertStatusChar as reported by the ps command across different platforms.
|
||||
func convertStatusChar(letter string) string {
|
||||
// Sources
|
||||
// Darwin: http://www.mywebuniversity.com/Man_Pages/Darwin/man_ps.html
|
||||
// FreeBSD: https://www.freebsd.org/cgi/man.cgi?ps
|
||||
// Linux https://man7.org/linux/man-pages/man1/ps.1.html
|
||||
// OpenBSD: https://man.openbsd.org/ps.1#state
|
||||
// Solaris: https://github.com/collectd/collectd/blob/1da3305c10c8ff9a63081284cf3d4bb0f6daffd8/src/processes.c#L2115
|
||||
switch letter {
|
||||
case "A":
|
||||
return Daemon
|
||||
case "D", "U":
|
||||
return Blocked
|
||||
case "E":
|
||||
return Detached
|
||||
case "I":
|
||||
return Idle
|
||||
case "L":
|
||||
return Lock
|
||||
case "O":
|
||||
return Orphan
|
||||
case "R":
|
||||
return Running
|
||||
case "S":
|
||||
return Sleep
|
||||
case "T", "t":
|
||||
// "t" is used by Linux to signal stopped by the debugger during tracing
|
||||
return Stop
|
||||
case "W":
|
||||
return Wait
|
||||
case "Y":
|
||||
return System
|
||||
case "Z":
|
||||
return Zombie
|
||||
default:
|
||||
return UnknownState
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin || freebsd || openbsd
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
type MemoryInfoExStat struct{}
|
||||
|
||||
type MemoryMapsStat struct{}
|
||||
|
||||
func (*Process) TgidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOniceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitWithContext(_ context.Context) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitUsageWithContext(_ context.Context, _ bool) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoExWithContext(_ context.Context) (*MemoryInfoExStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) PageFaultsWithContext(_ context.Context) (*PageFaultsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) OpenFilesWithContext(_ context.Context) ([]OpenFilesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryMapsWithContext(_ context.Context, _ bool) (*[]MemoryMapsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ThreadsWithContext(_ context.Context) (map[int32]*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) EnvironWithContext(_ context.Context) ([]string, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func parseKinfoProc(buf []byte) (KinfoProc, error) {
|
||||
var k KinfoProc
|
||||
br := bytes.NewReader(buf)
|
||||
err := binary.Read(br, binary.LittleEndian, &k)
|
||||
return k, err
|
||||
}
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
// copied from sys/sysctl.h
|
||||
const (
|
||||
CTLKern = 1 // "high kernel": proc, limits
|
||||
KernProc = 14 // struct: process entries
|
||||
KernProcPID = 1 // by process id
|
||||
KernProcProc = 8 // only return procs
|
||||
KernProcAll = 0 // everything
|
||||
KernProcPathname = 12 // path to executable
|
||||
)
|
||||
|
||||
type _Ctype_struct___0 struct { //nolint:revive //FIXME
|
||||
Pad uint64
|
||||
}
|
||||
|
||||
func pidsWithContext(_ context.Context) ([]int32, error) {
|
||||
var ret []int32
|
||||
|
||||
kprocs, err := unix.SysctlKinfoProcSlice("kern.proc.all")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
for i := range kprocs {
|
||||
proc := &kprocs[i]
|
||||
ret = append(ret, int32(proc.Proc.P_pid))
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p *Process) PpidWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return k.Eproc.Ppid, nil
|
||||
}
|
||||
|
||||
func (p *Process) NameWithContext(ctx context.Context) (string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
name := common.ByteToString(k.Proc.P_comm[:])
|
||||
|
||||
if len(name) >= 15 {
|
||||
cmdName, err := p.cmdNameWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if cmdName != "" {
|
||||
extendedName := filepath.Base(cmdName)
|
||||
if strings.HasPrefix(extendedName, p.name) {
|
||||
name = extendedName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (p *Process) createTimeWithContext(_ context.Context) (int64, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return k.Proc.P_starttime.Sec*1000 + int64(k.Proc.P_starttime.Usec)/1000, nil
|
||||
}
|
||||
|
||||
func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) {
|
||||
r, err := callPsWithContext(ctx, "state", p.Pid, false, false)
|
||||
if err != nil {
|
||||
return []string{""}, err
|
||||
}
|
||||
status := convertStatusChar(r[0][0][0:1])
|
||||
return []string{status}, err
|
||||
}
|
||||
|
||||
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
|
||||
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "stat=", "-p", strconv.Itoa(int(pid)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.IndexByte(string(out), '+') != -1, nil
|
||||
}
|
||||
|
||||
func (p *Process) UidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// See: http://unix.superglobalmegacorp.com/Net2/newsrc/sys/ucred.h.html
|
||||
userEffectiveUID := uint32(k.Eproc.Ucred.Uid)
|
||||
|
||||
return []uint32{userEffectiveUID}, nil
|
||||
}
|
||||
|
||||
func (p *Process) GidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gids := make([]uint32, 0, 3)
|
||||
gids = append(gids, uint32(k.Eproc.Pcred.P_rgid), uint32(k.Eproc.Ucred.Groups[0]), uint32(k.Eproc.Pcred.P_svgid))
|
||||
|
||||
return gids, nil
|
||||
}
|
||||
|
||||
func (*Process) GroupsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
// k, err := p.getKProc()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// groups := make([]int32, k.Eproc.Ucred.Ngroups)
|
||||
// for i := int16(0); i < k.Eproc.Ucred.Ngroups; i++ {
|
||||
// groups[i] = int32(k.Eproc.Ucred.Groups[i])
|
||||
// }
|
||||
|
||||
// return groups, nil
|
||||
}
|
||||
|
||||
func (*Process) TerminalWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
/*
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ttyNr := uint64(k.Eproc.Tdev)
|
||||
termmap, err := getTerminalMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return termmap[ttyNr], nil
|
||||
*/
|
||||
}
|
||||
|
||||
func (p *Process) NiceWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(k.Proc.P_nice), nil
|
||||
}
|
||||
|
||||
func (*Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
|
||||
procs, err := ProcessesWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
ret := make([]*Process, 0, len(procs))
|
||||
for _, proc := range procs {
|
||||
ppid, err := proc.PpidWithContext(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ppid == p.Pid {
|
||||
ret = append(ret, proc)
|
||||
}
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid })
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
|
||||
return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
|
||||
}
|
||||
|
||||
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, maxConn int) ([]net.ConnectionStat, error) {
|
||||
return net.ConnectionsPidMaxWithContext(ctx, "all", p.Pid, maxConn)
|
||||
}
|
||||
|
||||
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
|
||||
out := []*Process{}
|
||||
|
||||
pids, err := PidsWithContext(ctx)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
for _, pid := range pids {
|
||||
p, err := NewProcessWithContext(ctx, pid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Returns a proc as defined here:
|
||||
// http://unix.superglobalmegacorp.com/Net2/newsrc/sys/kinfo_proc.h.html
|
||||
func (p *Process) getKProc() (*unix.KinfoProc, error) {
|
||||
return unix.SysctlKinfoProc("kern.proc.pid", int(p.Pid))
|
||||
}
|
||||
|
||||
// call ps command.
|
||||
// Return value deletes Header line(you must not input wrong arg).
|
||||
// And split by Space. Caller have responsibility to manage.
|
||||
// If passed arg pid is 0, get information from all process.
|
||||
func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption, nameOption bool) ([][]string, error) {
|
||||
var cmd []string
|
||||
switch {
|
||||
case pid == 0: // will get from all processes.
|
||||
cmd = []string{"-ax", "-o", arg}
|
||||
case threadOption:
|
||||
cmd = []string{"-x", "-o", arg, "-M", "-p", strconv.Itoa(int(pid))}
|
||||
default:
|
||||
cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))}
|
||||
}
|
||||
if nameOption {
|
||||
cmd = append(cmd, "-c")
|
||||
}
|
||||
out, err := invoke.CommandWithContext(ctx, "ps", cmd...)
|
||||
if err != nil {
|
||||
return [][]string{}, err
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
|
||||
var ret [][]string
|
||||
for _, l := range lines[1:] {
|
||||
var lr []string
|
||||
if nameOption {
|
||||
lr = append(lr, l)
|
||||
} else {
|
||||
for _, r := range strings.Split(l, " ") {
|
||||
if r == "" {
|
||||
continue
|
||||
}
|
||||
lr = append(lr, strings.TrimSpace(r))
|
||||
}
|
||||
}
|
||||
if len(lr) != 0 {
|
||||
ret = append(ret, lr)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type dlFuncs struct {
|
||||
lib *common.SystemLib
|
||||
}
|
||||
|
||||
func loadProcFuncs() (*dlFuncs, error) {
|
||||
lib, err := common.NewSystemLib()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dlFuncs{lib}, err
|
||||
}
|
||||
|
||||
func (f *dlFuncs) getTimeScaleToNanoSeconds() float64 {
|
||||
var timeBaseInfo common.MachTimeBaseInfo
|
||||
|
||||
f.lib.MachTimeBaseInfo(uintptr(unsafe.Pointer(&timeBaseInfo)))
|
||||
|
||||
return float64(timeBaseInfo.Numer) / float64(timeBaseInfo.Denom)
|
||||
}
|
||||
|
||||
func (f *dlFuncs) Close() {
|
||||
f.lib.Close()
|
||||
}
|
||||
|
||||
func (p *Process) ExeWithContext(_ context.Context) (string, error) {
|
||||
funcs, err := loadProcFuncs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer funcs.Close()
|
||||
|
||||
buf := common.NewCStr(common.PROC_PIDPATHINFO_MAXSIZE)
|
||||
ret := funcs.lib.ProcPidPath(p.Pid, buf.Addr(), common.PROC_PIDPATHINFO_MAXSIZE)
|
||||
|
||||
if ret <= 0 {
|
||||
return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret)
|
||||
}
|
||||
|
||||
return buf.GoString(), nil
|
||||
}
|
||||
|
||||
// CwdWithContext retrieves the Current Working Directory for the given process.
|
||||
// It uses the proc_pidinfo from libproc and will only work for processes the
|
||||
// EUID can access. Otherwise "operation not permitted" will be returned as the
|
||||
// error.
|
||||
// Note: This might also work for other *BSD OSs.
|
||||
func (p *Process) CwdWithContext(_ context.Context) (string, error) {
|
||||
funcs, err := loadProcFuncs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer funcs.Close()
|
||||
|
||||
// Lock OS thread to ensure the errno does not change
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
var vpi vnodePathInfo
|
||||
const vpiSize = int32(unsafe.Sizeof(vpi))
|
||||
ret := funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDVNODEPATHINFO, 0, uintptr(unsafe.Pointer(&vpi)), vpiSize)
|
||||
errno, _ := funcs.lib.Dlsym("errno")
|
||||
err = *(**unix.Errno)(unsafe.Pointer(&errno))
|
||||
if errors.Is(err, unix.EPERM) {
|
||||
return "", ErrorNotPermitted
|
||||
}
|
||||
|
||||
if ret <= 0 {
|
||||
return "", fmt.Errorf("unknown error: proc_pidinfo returned %d", ret)
|
||||
}
|
||||
|
||||
if ret != vpiSize {
|
||||
return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret)
|
||||
}
|
||||
return common.GoString((*byte)(unsafe.Pointer(&vpi.Cdir.Path[0]))), nil
|
||||
}
|
||||
|
||||
func procArgs(pid int32) ([]byte, int, error) {
|
||||
procargs, err := unix.SysctlRaw("kern.procargs2", int(pid))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// The first 4 bytes indicate the number of arguments.
|
||||
nargs := procargs[:4]
|
||||
return procargs, int(binary.LittleEndian.Uint32(nargs)), nil
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineSliceWithContext(_ context.Context) ([]string, error) {
|
||||
return p.cmdlineSlice()
|
||||
}
|
||||
|
||||
func (p *Process) cmdlineSlice() ([]string, error) {
|
||||
pargs, nargs, err := procArgs(p.Pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The first bytes hold the nargs int, skip it.
|
||||
args := bytes.Split((pargs)[unsafe.Sizeof(int(0)):], []byte{0})
|
||||
var argStr string
|
||||
// The first element is the actual binary/command path.
|
||||
// command := args[0]
|
||||
var argSlice []string
|
||||
// var envSlice []string
|
||||
// All other, non-zero elements are arguments. The first "nargs" elements
|
||||
// are the arguments. Everything else in the slice is then the environment
|
||||
// of the process.
|
||||
for _, arg := range args[1:] {
|
||||
argStr = string(arg)
|
||||
if argStr != "" {
|
||||
if nargs > 0 {
|
||||
argSlice = append(argSlice, argStr)
|
||||
nargs--
|
||||
continue
|
||||
}
|
||||
break
|
||||
// envSlice = append(envSlice, argStr)
|
||||
}
|
||||
}
|
||||
return argSlice, err
|
||||
}
|
||||
|
||||
// cmdNameWithContext returns the command name (including spaces) without any arguments
|
||||
func (p *Process) cmdNameWithContext(_ context.Context) (string, error) {
|
||||
r, err := p.cmdlineSlice()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(r) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return r[0], err
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
|
||||
r, err := p.CmdlineSliceWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Join(r, " "), err
|
||||
}
|
||||
|
||||
func (p *Process) NumThreadsWithContext(_ context.Context) (int32, error) {
|
||||
funcs, err := loadProcFuncs()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer funcs.Close()
|
||||
|
||||
var ti ProcTaskInfo
|
||||
funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
|
||||
|
||||
return int32(ti.Threadnum), nil
|
||||
}
|
||||
|
||||
func (p *Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
|
||||
funcs, err := loadProcFuncs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer funcs.Close()
|
||||
|
||||
var ti ProcTaskInfo
|
||||
funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
|
||||
|
||||
timescaleToNanoSeconds := funcs.getTimeScaleToNanoSeconds()
|
||||
ret := &cpu.TimesStat{
|
||||
CPU: "cpu",
|
||||
User: float64(ti.Total_user) * timescaleToNanoSeconds / 1e9,
|
||||
System: float64(ti.Total_system) * timescaleToNanoSeconds / 1e9,
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p *Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, error) {
|
||||
funcs, err := loadProcFuncs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer funcs.Close()
|
||||
|
||||
var ti ProcTaskInfo
|
||||
funcs.lib.ProcPidInfo(p.Pid, common.PROC_PIDTASKINFO, 0, uintptr(unsafe.Pointer(&ti)), int32(unsafe.Sizeof(ti)))
|
||||
|
||||
ret := &MemoryInfoStat{
|
||||
RSS: uint64(ti.Resident_size),
|
||||
VMS: uint64(ti.Virtual_size),
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// procFDInfo represents a file descriptor entry from sys/proc_info.h
|
||||
type procFDInfo struct {
|
||||
ProcFd int32
|
||||
ProcFdtype uint32
|
||||
}
|
||||
|
||||
// NumFDsWithContext returns the number of file descriptors used by the process.
|
||||
// It uses proc_pidinfo with PROC_PIDLISTFDS to query the kernel for the count
|
||||
// of open file descriptors. The method makes a single syscall and calculates
|
||||
// the count from the buffer size returned by the kernel.
|
||||
func (p *Process) NumFDsWithContext(_ context.Context) (int32, error) {
|
||||
funcs, err := loadProcFuncs()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer funcs.Close()
|
||||
|
||||
// First call: get required buffer size
|
||||
bufferSize := funcs.lib.ProcPidInfo(
|
||||
p.Pid,
|
||||
common.PROC_PIDLISTFDS,
|
||||
0,
|
||||
0, // NULL buffer
|
||||
0, // 0 size
|
||||
)
|
||||
if bufferSize <= 0 {
|
||||
return 0, fmt.Errorf("unknown error: proc_pidinfo returned %d", bufferSize)
|
||||
}
|
||||
|
||||
// Allocate buffer of the required size
|
||||
const sizeofProcFDInfo = int32(unsafe.Sizeof(procFDInfo{}))
|
||||
numEntries := bufferSize / sizeofProcFDInfo
|
||||
buf := make([]procFDInfo, numEntries)
|
||||
|
||||
// Second call: get actual data
|
||||
ret := funcs.lib.ProcPidInfo(
|
||||
p.Pid,
|
||||
common.PROC_PIDLISTFDS,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&buf[0])), // Real buffer
|
||||
bufferSize, // Size from first call
|
||||
)
|
||||
if ret <= 0 {
|
||||
return 0, fmt.Errorf("unknown error: proc_pidinfo returned %d", ret)
|
||||
}
|
||||
|
||||
// Calculate actual number of FDs returned
|
||||
numFDs := ret / sizeofProcFDInfo
|
||||
return numFDs, nil
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// cgo -godefs types_darwin.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int32
|
||||
Pad_cgo_0 [4]byte
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type UGid_t uint32
|
||||
|
||||
type KinfoProc struct {
|
||||
Proc ExternProc
|
||||
Eproc Eproc
|
||||
}
|
||||
|
||||
type Eproc struct {
|
||||
Paddr *uint64
|
||||
Sess *Session
|
||||
Pcred Upcred
|
||||
Ucred Uucred
|
||||
Pad_cgo_0 [4]byte
|
||||
Vm Vmspace
|
||||
Ppid int32
|
||||
Pgid int32
|
||||
Jobc int16
|
||||
Pad_cgo_1 [2]byte
|
||||
Tdev int32
|
||||
Tpgid int32
|
||||
Pad_cgo_2 [4]byte
|
||||
Tsess *Session
|
||||
Wmesg [8]int8
|
||||
Xsize int32
|
||||
Xrssize int16
|
||||
Xccount int16
|
||||
Xswrss int16
|
||||
Pad_cgo_3 [2]byte
|
||||
Flag int32
|
||||
Login [12]int8
|
||||
Spare [4]int32
|
||||
Pad_cgo_4 [4]byte
|
||||
}
|
||||
|
||||
type Proc struct{}
|
||||
|
||||
type Session struct{}
|
||||
|
||||
type ucred struct {
|
||||
Link _Ctype_struct___0
|
||||
Ref uint64
|
||||
Posix Posix_cred
|
||||
Label *Label
|
||||
Audit Au_session
|
||||
}
|
||||
|
||||
type Uucred struct {
|
||||
Ref int32
|
||||
UID uint32
|
||||
Ngroups int16
|
||||
Pad_cgo_0 [2]byte
|
||||
Groups [16]uint32
|
||||
}
|
||||
|
||||
type Upcred struct {
|
||||
Pc_lock [72]int8
|
||||
Pc_ucred *ucred
|
||||
P_ruid uint32
|
||||
P_svuid uint32
|
||||
P_rgid uint32
|
||||
P_svgid uint32
|
||||
P_refcnt int32
|
||||
Pad_cgo_0 [4]byte
|
||||
}
|
||||
|
||||
type Vmspace struct {
|
||||
Dummy int32
|
||||
Pad_cgo_0 [4]byte
|
||||
Dummy2 *int8
|
||||
Dummy3 [5]int32
|
||||
Pad_cgo_1 [4]byte
|
||||
Dummy4 [3]*int8
|
||||
}
|
||||
|
||||
type Sigacts struct{}
|
||||
|
||||
type ExternProc struct {
|
||||
P_un [16]byte
|
||||
P_vmspace uint64
|
||||
P_sigacts uint64
|
||||
Pad_cgo_0 [3]byte
|
||||
P_flag int32
|
||||
P_stat int8
|
||||
P_pid int32
|
||||
P_oppid int32
|
||||
P_dupfd int32
|
||||
Pad_cgo_1 [4]byte
|
||||
User_stack uint64
|
||||
Exit_thread uint64
|
||||
P_debugger int32
|
||||
Sigwait int32
|
||||
P_estcpu uint32
|
||||
P_cpticks int32
|
||||
P_pctcpu uint32
|
||||
Pad_cgo_2 [4]byte
|
||||
P_wchan uint64
|
||||
P_wmesg uint64
|
||||
P_swtime uint32
|
||||
P_slptime uint32
|
||||
P_realtimer Itimerval
|
||||
P_rtime Timeval
|
||||
P_uticks uint64
|
||||
P_sticks uint64
|
||||
P_iticks uint64
|
||||
P_traceflag int32
|
||||
Pad_cgo_3 [4]byte
|
||||
P_tracep uint64
|
||||
P_siglist int32
|
||||
Pad_cgo_4 [4]byte
|
||||
P_textvp uint64
|
||||
P_holdcnt int32
|
||||
P_sigmask uint32
|
||||
P_sigignore uint32
|
||||
P_sigcatch uint32
|
||||
P_priority uint8
|
||||
P_usrpri uint8
|
||||
P_nice int8
|
||||
P_comm [17]int8
|
||||
Pad_cgo_5 [4]byte
|
||||
P_pgrp uint64
|
||||
P_addr uint64
|
||||
P_xstat uint16
|
||||
P_acflag uint16
|
||||
Pad_cgo_6 [4]byte
|
||||
P_ru uint64
|
||||
}
|
||||
|
||||
type Itimerval struct {
|
||||
Interval Timeval
|
||||
Value Timeval
|
||||
}
|
||||
|
||||
type Vnode struct{}
|
||||
|
||||
type Pgrp struct{}
|
||||
|
||||
type UserStruct struct{}
|
||||
|
||||
type Au_session struct {
|
||||
Aia_p *AuditinfoAddr
|
||||
Mask AuMask
|
||||
}
|
||||
|
||||
type Posix_cred struct {
|
||||
UID uint32
|
||||
Ruid uint32
|
||||
Svuid uint32
|
||||
Ngroups int16
|
||||
Pad_cgo_0 [2]byte
|
||||
Groups [16]uint32
|
||||
Rgid uint32
|
||||
Svgid uint32
|
||||
Gmuid uint32
|
||||
Flags int32
|
||||
}
|
||||
|
||||
type Label struct{}
|
||||
|
||||
type ProcTaskInfo struct {
|
||||
Virtual_size uint64
|
||||
Resident_size uint64
|
||||
Total_user uint64
|
||||
Total_system uint64
|
||||
Threads_user uint64
|
||||
Threads_system uint64
|
||||
Policy int32
|
||||
Faults int32
|
||||
Pageins int32
|
||||
Cow_faults int32
|
||||
Messages_sent int32
|
||||
Messages_received int32
|
||||
Syscalls_mach int32
|
||||
Syscalls_unix int32
|
||||
Csw int32
|
||||
Threadnum int32
|
||||
Numrunning int32
|
||||
Priority int32
|
||||
}
|
||||
|
||||
type vinfoStat struct {
|
||||
Dev uint32
|
||||
Mode uint16
|
||||
Nlink uint16
|
||||
Ino uint64
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Atime int64
|
||||
Atimensec int64
|
||||
Mtime int64
|
||||
Mtimensec int64
|
||||
Ctime int64
|
||||
Ctimensec int64
|
||||
Birthtime int64
|
||||
Birthtimensec int64
|
||||
Size int64
|
||||
Blocks int64
|
||||
Blksize int32
|
||||
Flags uint32
|
||||
Gen uint32
|
||||
Rdev uint32
|
||||
Qspare [2]int64
|
||||
}
|
||||
|
||||
type fsid struct {
|
||||
Val [2]int32
|
||||
}
|
||||
|
||||
type vnodeInfo struct {
|
||||
Stat vinfoStat
|
||||
Type int32
|
||||
Pad int32
|
||||
Fsid fsid
|
||||
}
|
||||
|
||||
type vnodeInfoPath struct {
|
||||
Vi vnodeInfo
|
||||
Path [1024]int8
|
||||
}
|
||||
|
||||
type vnodePathInfo struct {
|
||||
Cdir vnodeInfoPath
|
||||
Rdir vnodeInfoPath
|
||||
}
|
||||
|
||||
type AuditinfoAddr struct {
|
||||
Auid uint32
|
||||
Mask AuMask
|
||||
Termid AuTidAddr
|
||||
Asid int32
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
type AuMask struct {
|
||||
Success uint32
|
||||
Failure uint32
|
||||
}
|
||||
|
||||
type AuTidAddr struct {
|
||||
Port int32
|
||||
Type uint32
|
||||
Addr [4]uint32
|
||||
}
|
||||
|
||||
type UcredQueue struct {
|
||||
Next *ucred
|
||||
Prev **ucred
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build darwin && arm64
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs process/types_darwin.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int32
|
||||
Pad_cgo_0 [4]byte
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type UGid_t uint32
|
||||
|
||||
type KinfoProc struct {
|
||||
Proc ExternProc
|
||||
Eproc Eproc
|
||||
}
|
||||
|
||||
type Eproc struct {
|
||||
Paddr *Proc
|
||||
Sess *Session
|
||||
Pcred Upcred
|
||||
Ucred Uucred
|
||||
Vm Vmspace
|
||||
Ppid int32
|
||||
Pgid int32
|
||||
Jobc int16
|
||||
Tdev int32
|
||||
Tpgid int32
|
||||
Tsess *Session
|
||||
Wmesg [8]int8
|
||||
Xsize int32
|
||||
Xrssize int16
|
||||
Xccount int16
|
||||
Xswrss int16
|
||||
Flag int32
|
||||
Login [12]int8
|
||||
Spare [4]int32
|
||||
Pad_cgo_0 [4]byte
|
||||
}
|
||||
|
||||
type Proc struct{}
|
||||
|
||||
type Session struct{}
|
||||
|
||||
type ucred struct{}
|
||||
|
||||
type Uucred struct {
|
||||
Ref int32
|
||||
UID uint32
|
||||
Ngroups int16
|
||||
Groups [16]uint32
|
||||
}
|
||||
|
||||
type Upcred struct {
|
||||
Pc_lock [72]int8
|
||||
Pc_ucred *ucred
|
||||
P_ruid uint32
|
||||
P_svuid uint32
|
||||
P_rgid uint32
|
||||
P_svgid uint32
|
||||
P_refcnt int32
|
||||
Pad_cgo_0 [4]byte
|
||||
}
|
||||
|
||||
type Vmspace struct {
|
||||
Dummy int32
|
||||
Dummy2 *int8
|
||||
Dummy3 [5]int32
|
||||
Dummy4 [3]*int8
|
||||
}
|
||||
|
||||
type Sigacts struct{}
|
||||
|
||||
type ExternProc struct {
|
||||
P_un [16]byte
|
||||
P_vmspace uint64
|
||||
P_sigacts uint64
|
||||
Pad_cgo_0 [3]byte
|
||||
P_flag int32
|
||||
P_stat int8
|
||||
P_pid int32
|
||||
P_oppid int32
|
||||
P_dupfd int32
|
||||
Pad_cgo_1 [4]byte
|
||||
User_stack uint64
|
||||
Exit_thread uint64
|
||||
P_debugger int32
|
||||
Sigwait int32
|
||||
P_estcpu uint32
|
||||
P_cpticks int32
|
||||
P_pctcpu uint32
|
||||
Pad_cgo_2 [4]byte
|
||||
P_wchan uint64
|
||||
P_wmesg uint64
|
||||
P_swtime uint32
|
||||
P_slptime uint32
|
||||
P_realtimer Itimerval
|
||||
P_rtime Timeval
|
||||
P_uticks uint64
|
||||
P_sticks uint64
|
||||
P_iticks uint64
|
||||
P_traceflag int32
|
||||
Pad_cgo_3 [4]byte
|
||||
P_tracep uint64
|
||||
P_siglist int32
|
||||
Pad_cgo_4 [4]byte
|
||||
P_textvp uint64
|
||||
P_holdcnt int32
|
||||
P_sigmask uint32
|
||||
P_sigignore uint32
|
||||
P_sigcatch uint32
|
||||
P_priority uint8
|
||||
P_usrpri uint8
|
||||
P_nice int8
|
||||
P_comm [17]int8
|
||||
Pad_cgo_5 [4]byte
|
||||
P_pgrp uint64
|
||||
P_addr uint64
|
||||
P_xstat uint16
|
||||
P_acflag uint16
|
||||
Pad_cgo_6 [4]byte
|
||||
P_ru uint64
|
||||
}
|
||||
|
||||
type Itimerval struct {
|
||||
Interval Timeval
|
||||
Value Timeval
|
||||
}
|
||||
|
||||
type Vnode struct{}
|
||||
|
||||
type Pgrp struct{}
|
||||
|
||||
type UserStruct struct{}
|
||||
|
||||
type Au_session struct {
|
||||
Aia_p *AuditinfoAddr
|
||||
Mask AuMask
|
||||
}
|
||||
|
||||
type Posix_cred struct{}
|
||||
|
||||
type Label struct{}
|
||||
|
||||
type ProcTaskInfo struct {
|
||||
Virtual_size uint64
|
||||
Resident_size uint64
|
||||
Total_user uint64
|
||||
Total_system uint64
|
||||
Threads_user uint64
|
||||
Threads_system uint64
|
||||
Policy int32
|
||||
Faults int32
|
||||
Pageins int32
|
||||
Cow_faults int32
|
||||
Messages_sent int32
|
||||
Messages_received int32
|
||||
Syscalls_mach int32
|
||||
Syscalls_unix int32
|
||||
Csw int32
|
||||
Threadnum int32
|
||||
Numrunning int32
|
||||
Priority int32
|
||||
}
|
||||
|
||||
type vinfoStat struct {
|
||||
Dev uint32
|
||||
Mode uint16
|
||||
Nlink uint16
|
||||
Ino uint64
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Atime int64
|
||||
Atimensec int64
|
||||
Mtime int64
|
||||
Mtimensec int64
|
||||
Ctime int64
|
||||
Ctimensec int64
|
||||
Birthtime int64
|
||||
Birthtimensec int64
|
||||
Size int64
|
||||
Blocks int64
|
||||
Blksize int32
|
||||
Flags uint32
|
||||
Gen uint32
|
||||
Rdev uint32
|
||||
Qspare [2]int64
|
||||
}
|
||||
|
||||
type fsid struct {
|
||||
Val [2]int32
|
||||
}
|
||||
|
||||
type vnodeInfo struct {
|
||||
Stat vinfoStat
|
||||
Type int32
|
||||
Pad int32
|
||||
Fsid fsid
|
||||
}
|
||||
|
||||
type vnodeInfoPath struct {
|
||||
Vi vnodeInfo
|
||||
Path [1024]int8
|
||||
}
|
||||
|
||||
type vnodePathInfo struct {
|
||||
Cdir vnodeInfoPath
|
||||
Rdir vnodeInfoPath
|
||||
}
|
||||
|
||||
type AuditinfoAddr struct {
|
||||
Auid uint32
|
||||
Mask AuMask
|
||||
Termid AuTidAddr
|
||||
Asid int32
|
||||
Flags uint64
|
||||
}
|
||||
type AuMask struct {
|
||||
Success uint32
|
||||
Failure uint32
|
||||
}
|
||||
type AuTidAddr struct {
|
||||
Port int32
|
||||
Type uint32
|
||||
Addr [4]uint32
|
||||
}
|
||||
|
||||
type UcredQueue struct {
|
||||
Next *ucred
|
||||
Prev **ucred
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !darwin && !linux && !freebsd && !openbsd && !windows && !solaris && !plan9
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
type Signal = syscall.Signal
|
||||
|
||||
type MemoryMapsStat struct {
|
||||
Path string `json:"path"`
|
||||
Rss uint64 `json:"rss"`
|
||||
Size uint64 `json:"size"`
|
||||
Pss uint64 `json:"pss"`
|
||||
SharedClean uint64 `json:"sharedClean"`
|
||||
SharedDirty uint64 `json:"sharedDirty"`
|
||||
PrivateClean uint64 `json:"privateClean"`
|
||||
PrivateDirty uint64 `json:"privateDirty"`
|
||||
Referenced uint64 `json:"referenced"`
|
||||
Anonymous uint64 `json:"anonymous"`
|
||||
Swap uint64 `json:"swap"`
|
||||
}
|
||||
|
||||
type MemoryInfoExStat struct{}
|
||||
|
||||
func pidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProcessesWithContext(_ context.Context) ([]*Process, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func PidExistsWithContext(_ context.Context, _ int32) (bool, error) {
|
||||
return false, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) PpidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NameWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TgidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ExeWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CmdlineWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CmdlineSliceWithContext(_ context.Context) ([]string, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) createTimeWithContext(_ context.Context) (int64, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CwdWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) StatusWithContext(_ context.Context) ([]string, error) {
|
||||
return []string{""}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ForegroundWithContext(_ context.Context) (bool, error) {
|
||||
return false, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) UidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) GidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) GroupsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TerminalWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NiceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOniceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitWithContext(_ context.Context) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitUsageWithContext(_ context.Context, _ bool) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumThreadsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ThreadsWithContext(_ context.Context) (map[int32]*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoExWithContext(_ context.Context) (*MemoryInfoExStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) PageFaultsWithContext(_ context.Context) (*PageFaultsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ChildrenWithContext(_ context.Context) ([]*Process, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) OpenFilesWithContext(_ context.Context) ([]OpenFilesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsWithContext(_ context.Context) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsMaxWithContext(_ context.Context, _ int) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryMapsWithContext(_ context.Context, _ bool) (*[]MemoryMapsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) SendSignalWithContext(_ context.Context, _ Signal) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) SuspendWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ResumeWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TerminateWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) KillWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) UsernameWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) EnvironWithContext(_ context.Context) ([]string, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
func pidsWithContext(ctx context.Context) ([]int32, error) {
|
||||
var ret []int32
|
||||
procs, err := ProcessesWithContext(ctx)
|
||||
if err != nil {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
for _, p := range procs {
|
||||
ret = append(ret, p.Pid)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p *Process) PpidWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return k.Ppid, nil
|
||||
}
|
||||
|
||||
func (p *Process) NameWithContext(ctx context.Context) (string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := common.IntToString(k.Comm[:])
|
||||
|
||||
if len(name) >= 15 {
|
||||
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(cmdlineSlice) > 0 {
|
||||
extendedName := filepath.Base(cmdlineSlice[0])
|
||||
if strings.HasPrefix(extendedName, p.name) {
|
||||
name = extendedName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (p *Process) CwdWithContext(_ context.Context) (string, error) {
|
||||
mib := []int32{CTLKern, KernProc, KernProcCwd, p.Pid}
|
||||
buf, length, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if length != sizeOfKinfoFile {
|
||||
return "", errors.New("unexpected size of KinfoFile")
|
||||
}
|
||||
|
||||
var k kinfoFile
|
||||
br := bytes.NewReader(buf)
|
||||
if err := binary.Read(br, binary.LittleEndian, &k); err != nil {
|
||||
return "", err
|
||||
}
|
||||
cwd := common.IntToString(k.Path[:])
|
||||
|
||||
return cwd, nil
|
||||
}
|
||||
|
||||
func (p *Process) ExeWithContext(_ context.Context) (string, error) {
|
||||
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(_ context.Context) (string, error) {
|
||||
mib := []int32{CTLKern, KernProc, KernProcArgs, p.Pid}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret := strings.FieldsFunc(string(buf), func(r rune) bool {
|
||||
return r == '\u0000'
|
||||
})
|
||||
|
||||
return strings.Join(ret, " "), nil
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineSliceWithContext(_ context.Context) ([]string, error) {
|
||||
mib := []int32{CTLKern, KernProc, KernProcArgs, p.Pid}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if buf[len(buf)-1] == 0 {
|
||||
buf = buf[:len(buf)-1]
|
||||
}
|
||||
parts := bytes.Split(buf, []byte{0})
|
||||
var strParts []string
|
||||
for _, p := range parts {
|
||||
strParts = append(strParts, string(p))
|
||||
}
|
||||
|
||||
return strParts, nil
|
||||
}
|
||||
|
||||
func (p *Process) createTimeWithContext(_ context.Context) (int64, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(k.Start.Sec)*1000 + int64(k.Start.Usec)/1000, nil
|
||||
}
|
||||
|
||||
func (p *Process) StatusWithContext(_ context.Context) ([]string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return []string{""}, err
|
||||
}
|
||||
var s string
|
||||
switch k.Stat {
|
||||
case SIDL:
|
||||
s = Idle
|
||||
case SRUN:
|
||||
s = Running
|
||||
case SSLEEP:
|
||||
s = Sleep
|
||||
case SSTOP:
|
||||
s = Stop
|
||||
case SZOMB:
|
||||
s = Zombie
|
||||
case SWAIT:
|
||||
s = Wait
|
||||
case SLOCK:
|
||||
s = Lock
|
||||
}
|
||||
|
||||
return []string{s}, nil
|
||||
}
|
||||
|
||||
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
|
||||
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "stat=", "-p", strconv.Itoa(int(pid)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.IndexByte(string(out), '+') != -1, nil
|
||||
}
|
||||
|
||||
func (p *Process) UidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uids := make([]uint32, 0, 3)
|
||||
|
||||
uids = append(uids, uint32(k.Ruid), uint32(k.Uid), uint32(k.Svuid))
|
||||
|
||||
return uids, nil
|
||||
}
|
||||
|
||||
func (p *Process) GidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gids := make([]uint32, 0, 3)
|
||||
gids = append(gids, uint32(k.Rgid), uint32(k.Ngroups), uint32(k.Svgid))
|
||||
|
||||
return gids, nil
|
||||
}
|
||||
|
||||
func (p *Process) GroupsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups := make([]uint32, k.Ngroups)
|
||||
for i := int16(0); i < k.Ngroups; i++ {
|
||||
groups[i] = uint32(k.Groups[i])
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (p *Process) TerminalWithContext(_ context.Context) (string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ttyNr := uint64(k.Tdev)
|
||||
|
||||
termmap, err := getTerminalMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return termmap[ttyNr], nil
|
||||
}
|
||||
|
||||
func (p *Process) NiceWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(k.Nice), nil
|
||||
}
|
||||
|
||||
func (p *Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IOCountersStat{
|
||||
ReadCount: uint64(k.Rusage.Inblock),
|
||||
WriteCount: uint64(k.Rusage.Oublock),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Process) NumThreadsWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return k.Numthreads, nil
|
||||
}
|
||||
|
||||
func (p *Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cpu.TimesStat{
|
||||
CPU: "cpu",
|
||||
User: float64(k.Rusage.Utime.Sec) + float64(k.Rusage.Utime.Usec)/1000000,
|
||||
System: float64(k.Rusage.Stime.Sec) + float64(k.Rusage.Stime.Usec)/1000000,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := unix.Sysctl("vm.stats.vm.v_page_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageSize := binary.LittleEndian.Uint16([]byte(v))
|
||||
|
||||
return &MemoryInfoStat{
|
||||
RSS: uint64(k.Rssize) * uint64(pageSize),
|
||||
VMS: uint64(k.Size),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
|
||||
procs, err := ProcessesWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
ret := make([]*Process, 0, len(procs))
|
||||
for _, proc := range procs {
|
||||
ppid, err := proc.PpidWithContext(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ppid == p.Pid {
|
||||
ret = append(ret, proc)
|
||||
}
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid })
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
|
||||
return net.ConnectionsPidWithContext(ctx, "all", p.Pid)
|
||||
}
|
||||
|
||||
func (p *Process) ConnectionsMaxWithContext(ctx context.Context, maxConn int) ([]net.ConnectionStat, error) {
|
||||
return net.ConnectionsPidMaxWithContext(ctx, "all", p.Pid, maxConn)
|
||||
}
|
||||
|
||||
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
|
||||
results := []*Process{}
|
||||
|
||||
mib := []int32{CTLKern, KernProc, KernProcProc, 0}
|
||||
buf, length, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
// get kinfo_proc size
|
||||
count := int(length / uint64(sizeOfKinfoProc))
|
||||
|
||||
// parse buf to procs
|
||||
for i := 0; i < count; i++ {
|
||||
b := buf[i*sizeOfKinfoProc : (i+1)*sizeOfKinfoProc]
|
||||
k, err := parseKinfoProc(b)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p, err := NewProcessWithContext(ctx, int32(k.Pid))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, p)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) getKProc() (*KinfoProc, error) {
|
||||
mib := []int32{CTLKern, KernProc, KernProcPID, p.Pid}
|
||||
|
||||
buf, length, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length != sizeOfKinfoProc {
|
||||
return nil, errors.New("unexpected size of KinfoProc")
|
||||
}
|
||||
|
||||
k, err := parseKinfoProc(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &k, nil
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 14
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 7
|
||||
KernProcCwd = 42
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x4
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x488
|
||||
sizeOfKinfoProc = 0x300
|
||||
sizeOfKinfoFile = 0x570 // TODO: should be changed by running on the target machine
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SWAIT = 6
|
||||
SLOCK = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int32
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int32
|
||||
Nsec int32
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int32
|
||||
Usec int32
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int32
|
||||
Ixrss int32
|
||||
Idrss int32
|
||||
Isrss int32
|
||||
Minflt int32
|
||||
Majflt int32
|
||||
Nswap int32
|
||||
Inblock int32
|
||||
Oublock int32
|
||||
Msgsnd int32
|
||||
Msgrcv int32
|
||||
Nsignals int32
|
||||
Nvcsw int32
|
||||
Nivcsw int32
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur int64
|
||||
Max int64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Structsize int32
|
||||
Layout int32
|
||||
Args int32 /* pargs */
|
||||
Paddr int32 /* proc */
|
||||
Addr int32 /* user */
|
||||
Tracep int32 /* vnode */
|
||||
Textvp int32 /* vnode */
|
||||
Fd int32 /* filedesc */
|
||||
Vmspace int32 /* vmspace */
|
||||
Wchan int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Pgid int32
|
||||
Tpgid int32
|
||||
Sid int32
|
||||
Tsid int32
|
||||
Jobc int16
|
||||
Spare_short1 int16
|
||||
Tdev uint32
|
||||
Siglist [16]byte /* sigset */
|
||||
Sigmask [16]byte /* sigset */
|
||||
Sigignore [16]byte /* sigset */
|
||||
Sigcatch [16]byte /* sigset */
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Svuid uint32
|
||||
Rgid uint32
|
||||
Svgid uint32
|
||||
Ngroups int16
|
||||
Spare_short2 int16
|
||||
Groups [16]uint32
|
||||
Size uint32
|
||||
Rssize int32
|
||||
Swrss int32
|
||||
Tsize int32
|
||||
Dsize int32
|
||||
Ssize int32
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Pctcpu uint32
|
||||
Estcpu uint32
|
||||
Slptime uint32
|
||||
Swtime uint32
|
||||
Cow uint32
|
||||
Runtime uint64
|
||||
Start Timeval
|
||||
Childtime Timeval
|
||||
Flag int32
|
||||
Kiflag int32
|
||||
Traceflag int32
|
||||
Stat int8
|
||||
Nice int8
|
||||
Lock int8
|
||||
Rqindex int8
|
||||
Oncpu uint8
|
||||
Lastcpu uint8
|
||||
Tdname [17]int8
|
||||
Wmesg [9]int8
|
||||
Login [18]int8
|
||||
Lockname [9]int8
|
||||
Comm [20]int8
|
||||
Emul [17]int8
|
||||
Loginclass [18]int8
|
||||
Sparestrings [50]int8
|
||||
Spareints [7]int32
|
||||
Flag2 int32
|
||||
Fibnum int32
|
||||
Cr_flags uint32
|
||||
Jid int32
|
||||
Numthreads int32
|
||||
Tid int32
|
||||
Pri Priority
|
||||
Rusage Rusage
|
||||
Rusage_ch Rusage
|
||||
Pcb int32 /* pcb */
|
||||
Kstack int32
|
||||
Udata int32
|
||||
Tdaddr int32 /* thread */
|
||||
Spareptrs [6]int32
|
||||
Sparelongs [12]int32
|
||||
Sflag int32
|
||||
Tdflags int32
|
||||
}
|
||||
|
||||
type Priority struct {
|
||||
Class uint8
|
||||
Level uint8
|
||||
Native uint8
|
||||
User uint8
|
||||
}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Start uint64
|
||||
End uint64
|
||||
Offset uint64
|
||||
Vn_fileid uint64
|
||||
Vn_fsid uint32
|
||||
Flags int32
|
||||
Resident int32
|
||||
Private_resident int32
|
||||
Protection int32
|
||||
Ref_count int32
|
||||
Shadow_count int32
|
||||
Vn_type int32
|
||||
Vn_size uint64
|
||||
Vn_rdev uint32
|
||||
Vn_mode uint16
|
||||
Status uint16
|
||||
X_kve_ispare [12]int32
|
||||
Path [1024]int8
|
||||
}
|
||||
|
||||
// TODO: should be changed by running on the target machine
|
||||
type kinfoFile struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Fd int32
|
||||
Ref_count int32
|
||||
Flags int32
|
||||
Pad0 int32
|
||||
Offset int64
|
||||
Anon0 [304]byte
|
||||
Status uint16
|
||||
Pad1 uint16
|
||||
X_kf_ispare0 int32
|
||||
Cap_rights capRights
|
||||
X_kf_cap_spare uint64
|
||||
Path [1024]int8 // changed from uint8 by hand
|
||||
}
|
||||
|
||||
// TODO: should be changed by running on the target machine
|
||||
type capRights struct {
|
||||
Rights [2]uint64
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 14
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 7
|
||||
KernProcCwd = 42
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x488
|
||||
sizeOfKinfoProc = 0x440
|
||||
sizeOfKinfoFile = 0x570
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SWAIT = 6
|
||||
SLOCK = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur int64
|
||||
Max int64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Structsize int32
|
||||
Layout int32
|
||||
Args int64 /* pargs */
|
||||
Paddr int64 /* proc */
|
||||
Addr int64 /* user */
|
||||
Tracep int64 /* vnode */
|
||||
Textvp int64 /* vnode */
|
||||
Fd int64 /* filedesc */
|
||||
Vmspace int64 /* vmspace */
|
||||
Wchan int64
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Pgid int32
|
||||
Tpgid int32
|
||||
Sid int32
|
||||
Tsid int32
|
||||
Jobc int16
|
||||
Spare_short1 int16
|
||||
Tdev_freebsd11 uint32
|
||||
Siglist [16]byte /* sigset */
|
||||
Sigmask [16]byte /* sigset */
|
||||
Sigignore [16]byte /* sigset */
|
||||
Sigcatch [16]byte /* sigset */
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Svuid uint32
|
||||
Rgid uint32
|
||||
Svgid uint32
|
||||
Ngroups int16
|
||||
Spare_short2 int16
|
||||
Groups [16]uint32
|
||||
Size uint64
|
||||
Rssize int64
|
||||
Swrss int64
|
||||
Tsize int64
|
||||
Dsize int64
|
||||
Ssize int64
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Pctcpu uint32
|
||||
Estcpu uint32
|
||||
Slptime uint32
|
||||
Swtime uint32
|
||||
Cow uint32
|
||||
Runtime uint64
|
||||
Start Timeval
|
||||
Childtime Timeval
|
||||
Flag int64
|
||||
Kiflag int64
|
||||
Traceflag int32
|
||||
Stat int8
|
||||
Nice int8
|
||||
Lock int8
|
||||
Rqindex int8
|
||||
Oncpu_old uint8
|
||||
Lastcpu_old uint8
|
||||
Tdname [17]int8
|
||||
Wmesg [9]int8
|
||||
Login [18]int8
|
||||
Lockname [9]int8
|
||||
Comm [20]int8
|
||||
Emul [17]int8
|
||||
Loginclass [18]int8
|
||||
Moretdname [4]int8
|
||||
Sparestrings [46]int8
|
||||
Spareints [2]int32
|
||||
Tdev uint64
|
||||
Oncpu int32
|
||||
Lastcpu int32
|
||||
Tracer int32
|
||||
Flag2 int32
|
||||
Fibnum int32
|
||||
Cr_flags uint32
|
||||
Jid int32
|
||||
Numthreads int32
|
||||
Tid int32
|
||||
Pri Priority
|
||||
Rusage Rusage
|
||||
Rusage_ch Rusage
|
||||
Pcb int64 /* pcb */
|
||||
Kstack int64
|
||||
Udata int64
|
||||
Tdaddr int64 /* thread */
|
||||
Pd int64 /* pwddesc, not accurate */
|
||||
Spareptrs [5]int64
|
||||
Sparelongs [12]int64
|
||||
Sflag int64
|
||||
Tdflags int64
|
||||
}
|
||||
|
||||
type Priority struct {
|
||||
Class uint8
|
||||
Level uint8
|
||||
Native uint8
|
||||
User uint8
|
||||
}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Start uint64
|
||||
End uint64
|
||||
Offset uint64
|
||||
Vn_fileid uint64
|
||||
Vn_fsid_freebsd11 uint32
|
||||
Flags int32
|
||||
Resident int32
|
||||
Private_resident int32
|
||||
Protection int32
|
||||
Ref_count int32
|
||||
Shadow_count int32
|
||||
Vn_type int32
|
||||
Vn_size uint64
|
||||
Vn_rdev_freebsd11 uint32
|
||||
Vn_mode uint16
|
||||
Status uint16
|
||||
Type_spec [8]byte
|
||||
Vn_rdev uint64
|
||||
X_kve_ispare [8]int32
|
||||
Path [1024]int8
|
||||
}
|
||||
|
||||
type kinfoFile struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Fd int32
|
||||
Ref_count int32
|
||||
Flags int32
|
||||
Pad0 int32
|
||||
Offset int64
|
||||
Anon0 [304]byte
|
||||
Status uint16
|
||||
Pad1 uint16
|
||||
X_kf_ispare0 int32
|
||||
Cap_rights capRights
|
||||
X_kf_cap_spare uint64
|
||||
Path [1024]int8
|
||||
}
|
||||
|
||||
type capRights struct {
|
||||
Rights [2]uint64
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 14
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 7
|
||||
KernProcCwd = 42
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x4
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x488
|
||||
sizeOfKinfoProc = 0x440
|
||||
sizeOfKinfoFile = 0x570 // TODO: should be changed by running on the target machine
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SWAIT = 6
|
||||
SLOCK = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int32
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int32
|
||||
Ixrss int32
|
||||
Idrss int32
|
||||
Isrss int32
|
||||
Minflt int32
|
||||
Majflt int32
|
||||
Nswap int32
|
||||
Inblock int32
|
||||
Oublock int32
|
||||
Msgsnd int32
|
||||
Msgrcv int32
|
||||
Nsignals int32
|
||||
Nvcsw int32
|
||||
Nivcsw int32
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur int32
|
||||
Max int32
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Structsize int32
|
||||
Layout int32
|
||||
Args int32 /* pargs */
|
||||
Paddr int32 /* proc */
|
||||
Addr int32 /* user */
|
||||
Tracep int32 /* vnode */
|
||||
Textvp int32 /* vnode */
|
||||
Fd int32 /* filedesc */
|
||||
Vmspace int32 /* vmspace */
|
||||
Wchan int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Pgid int32
|
||||
Tpgid int32
|
||||
Sid int32
|
||||
Tsid int32
|
||||
Jobc int16
|
||||
Spare_short1 int16
|
||||
Tdev uint32
|
||||
Siglist [16]byte /* sigset */
|
||||
Sigmask [16]byte /* sigset */
|
||||
Sigignore [16]byte /* sigset */
|
||||
Sigcatch [16]byte /* sigset */
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Svuid uint32
|
||||
Rgid uint32
|
||||
Svgid uint32
|
||||
Ngroups int16
|
||||
Spare_short2 int16
|
||||
Groups [16]uint32
|
||||
Size uint32
|
||||
Rssize int32
|
||||
Swrss int32
|
||||
Tsize int32
|
||||
Dsize int32
|
||||
Ssize int32
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Pctcpu uint32
|
||||
Estcpu uint32
|
||||
Slptime uint32
|
||||
Swtime uint32
|
||||
Cow uint32
|
||||
Runtime uint64
|
||||
Start Timeval
|
||||
Childtime Timeval
|
||||
Flag int32
|
||||
Kiflag int32
|
||||
Traceflag int32
|
||||
Stat int8
|
||||
Nice int8
|
||||
Lock int8
|
||||
Rqindex int8
|
||||
Oncpu uint8
|
||||
Lastcpu uint8
|
||||
Tdname [17]int8
|
||||
Wmesg [9]int8
|
||||
Login [18]int8
|
||||
Lockname [9]int8
|
||||
Comm [20]int8
|
||||
Emul [17]int8
|
||||
Loginclass [18]int8
|
||||
Sparestrings [50]int8
|
||||
Spareints [4]int32
|
||||
Flag2 int32
|
||||
Fibnum int32
|
||||
Cr_flags uint32
|
||||
Jid int32
|
||||
Numthreads int32
|
||||
Tid int32
|
||||
Pri Priority
|
||||
Rusage Rusage
|
||||
Rusage_ch Rusage
|
||||
Pcb int32 /* pcb */
|
||||
Kstack int32
|
||||
Udata int32
|
||||
Tdaddr int32 /* thread */
|
||||
Spareptrs [6]int64
|
||||
Sparelongs [12]int64
|
||||
Sflag int64
|
||||
Tdflags int64
|
||||
}
|
||||
|
||||
type Priority struct {
|
||||
Class uint8
|
||||
Level uint8
|
||||
Native uint8
|
||||
User uint8
|
||||
}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Start uint64
|
||||
End uint64
|
||||
Offset uint64
|
||||
Vn_fileid uint64
|
||||
Vn_fsid uint32
|
||||
Flags int32
|
||||
Resident int32
|
||||
Private_resident int32
|
||||
Protection int32
|
||||
Ref_count int32
|
||||
Shadow_count int32
|
||||
Vn_type int32
|
||||
Vn_size uint64
|
||||
Vn_rdev uint32
|
||||
Vn_mode uint16
|
||||
Status uint16
|
||||
X_kve_ispare [12]int32
|
||||
Path [1024]int8
|
||||
}
|
||||
|
||||
// TODO: should be changed by running on the target machine
|
||||
type kinfoFile struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Fd int32
|
||||
Ref_count int32
|
||||
Flags int32
|
||||
Pad0 int32
|
||||
Offset int64
|
||||
Anon0 [304]byte
|
||||
Status uint16
|
||||
Pad1 uint16
|
||||
X_kf_ispare0 int32
|
||||
Cap_rights capRights
|
||||
X_kf_cap_spare uint64
|
||||
Path [1024]int8 // changed from uint8 by hand
|
||||
}
|
||||
|
||||
// TODO: should be changed by running on the target machine
|
||||
type capRights struct {
|
||||
Rights [2]uint64
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build freebsd && arm64
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 14
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 7
|
||||
KernProcCwd = 42
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x488
|
||||
sizeOfKinfoProc = 0x440
|
||||
sizeOfKinfoFile = 0x570
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SWAIT = 6
|
||||
SLOCK = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur int64
|
||||
Max int64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Structsize int32
|
||||
Layout int32
|
||||
Args int64 /* pargs */
|
||||
Paddr int64 /* proc */
|
||||
Addr int64 /* user */
|
||||
Tracep int64 /* vnode */
|
||||
Textvp int64 /* vnode */
|
||||
Fd int64 /* filedesc */
|
||||
Vmspace int64 /* vmspace */
|
||||
Wchan int64
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Pgid int32
|
||||
Tpgid int32
|
||||
Sid int32
|
||||
Tsid int32
|
||||
Jobc int16
|
||||
Spare_short1 int16
|
||||
Tdev_freebsd11 uint32
|
||||
Siglist [16]byte /* sigset */
|
||||
Sigmask [16]byte /* sigset */
|
||||
Sigignore [16]byte /* sigset */
|
||||
Sigcatch [16]byte /* sigset */
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Svuid uint32
|
||||
Rgid uint32
|
||||
Svgid uint32
|
||||
Ngroups int16
|
||||
Spare_short2 int16
|
||||
Groups [16]uint32
|
||||
Size uint64
|
||||
Rssize int64
|
||||
Swrss int64
|
||||
Tsize int64
|
||||
Dsize int64
|
||||
Ssize int64
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Pctcpu uint32
|
||||
Estcpu uint32
|
||||
Slptime uint32
|
||||
Swtime uint32
|
||||
Cow uint32
|
||||
Runtime uint64
|
||||
Start Timeval
|
||||
Childtime Timeval
|
||||
Flag int64
|
||||
Kiflag int64
|
||||
Traceflag int32
|
||||
Stat uint8
|
||||
Nice int8
|
||||
Lock uint8
|
||||
Rqindex uint8
|
||||
Oncpu_old uint8
|
||||
Lastcpu_old uint8
|
||||
Tdname [17]uint8
|
||||
Wmesg [9]uint8
|
||||
Login [18]uint8
|
||||
Lockname [9]uint8
|
||||
Comm [20]int8 // changed from uint8 by hand
|
||||
Emul [17]uint8
|
||||
Loginclass [18]uint8
|
||||
Moretdname [4]uint8
|
||||
Sparestrings [46]uint8
|
||||
Spareints [2]int32
|
||||
Tdev uint64
|
||||
Oncpu int32
|
||||
Lastcpu int32
|
||||
Tracer int32
|
||||
Flag2 int32
|
||||
Fibnum int32
|
||||
Cr_flags uint32
|
||||
Jid int32
|
||||
Numthreads int32
|
||||
Tid int32
|
||||
Pri Priority
|
||||
Rusage Rusage
|
||||
Rusage_ch Rusage
|
||||
Pcb int64 /* pcb */
|
||||
Kstack int64
|
||||
Udata int64
|
||||
Tdaddr int64 /* thread */
|
||||
Pd int64 /* pwddesc, not accurate */
|
||||
Spareptrs [5]int64
|
||||
Sparelongs [12]int64
|
||||
Sflag int64
|
||||
Tdflags int64
|
||||
}
|
||||
|
||||
type Priority struct {
|
||||
Class uint8
|
||||
Level uint8
|
||||
Native uint8
|
||||
User uint8
|
||||
}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Start uint64
|
||||
End uint64
|
||||
Offset uint64
|
||||
Vn_fileid uint64
|
||||
Vn_fsid_freebsd11 uint32
|
||||
Flags int32
|
||||
Resident int32
|
||||
Private_resident int32
|
||||
Protection int32
|
||||
Ref_count int32
|
||||
Shadow_count int32
|
||||
Vn_type int32
|
||||
Vn_size uint64
|
||||
Vn_rdev_freebsd11 uint32
|
||||
Vn_mode uint16
|
||||
Status uint16
|
||||
Type_spec [8]byte
|
||||
Vn_rdev uint64
|
||||
X_kve_ispare [8]int32
|
||||
Path [1024]uint8
|
||||
}
|
||||
|
||||
type kinfoFile struct {
|
||||
Structsize int32
|
||||
Type int32
|
||||
Fd int32
|
||||
Ref_count int32
|
||||
Flags int32
|
||||
Pad0 int32
|
||||
Offset int64
|
||||
Anon0 [304]byte
|
||||
Status uint16
|
||||
Pad1 uint16
|
||||
X_kf_ispare0 int32
|
||||
Cap_rights capRights
|
||||
X_kf_cap_spare uint64
|
||||
Path [1024]int8 // changed from uint8 by hand
|
||||
}
|
||||
|
||||
type capRights struct {
|
||||
Rights [2]uint64
|
||||
}
|
||||
+1204
File diff suppressed because it is too large
Load Diff
+401
@@ -0,0 +1,401 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
func pidsWithContext(ctx context.Context) ([]int32, error) {
|
||||
var ret []int32
|
||||
procs, err := ProcessesWithContext(ctx)
|
||||
if err != nil {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
for _, p := range procs {
|
||||
ret = append(ret, p.Pid)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p *Process) PpidWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return k.Ppid, nil
|
||||
}
|
||||
|
||||
func (p *Process) NameWithContext(ctx context.Context) (string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := common.IntToString(k.Comm[:])
|
||||
|
||||
if len(name) >= 15 {
|
||||
cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(cmdlineSlice) > 0 {
|
||||
extendedName := filepath.Base(cmdlineSlice[0])
|
||||
if strings.HasPrefix(extendedName, p.name) {
|
||||
name = extendedName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (p *Process) CwdWithContext(_ context.Context) (string, error) {
|
||||
mib := []int32{CTLKern, KernProcCwd, p.Pid}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return common.ByteToString(buf), nil
|
||||
}
|
||||
|
||||
func (*Process) ExeWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineSliceWithContext(_ context.Context) ([]string, error) {
|
||||
mib := []int32{CTLKern, KernProcArgs, p.Pid, KernProcArgv}
|
||||
buf, _, err := common.CallSyscall(mib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* From man sysctl(2):
|
||||
The buffer pointed to by oldp is filled with an array of char
|
||||
pointers followed by the strings themselves. The last char
|
||||
pointer is a NULL pointer. */
|
||||
var strParts []string
|
||||
r := bytes.NewReader(buf)
|
||||
baseAddr := uintptr(unsafe.Pointer(&buf[0]))
|
||||
for {
|
||||
argvp, err := readPtr(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if argvp == 0 { // check for a NULL pointer
|
||||
break
|
||||
}
|
||||
offset := argvp - baseAddr
|
||||
length := uintptr(bytes.IndexByte(buf[offset:], 0))
|
||||
str := string(buf[offset : offset+length])
|
||||
strParts = append(strParts, str)
|
||||
}
|
||||
|
||||
return strParts, nil
|
||||
}
|
||||
|
||||
// readPtr reads a pointer data from a given reader. WARNING: only little
|
||||
// endian architectures are supported.
|
||||
func readPtr(r io.Reader) (uintptr, error) {
|
||||
switch sizeofPtr {
|
||||
case 4:
|
||||
var p uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uintptr(p), nil
|
||||
case 8:
|
||||
var p uint64
|
||||
if err := binary.Read(r, binary.LittleEndian, &p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uintptr(p), nil
|
||||
default:
|
||||
return 0, errors.New("unsupported pointer size")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
|
||||
argv, err := p.CmdlineSliceWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Join(argv, " "), nil
|
||||
}
|
||||
|
||||
func (*Process) createTimeWithContext(_ context.Context) (int64, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) StatusWithContext(_ context.Context) ([]string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return []string{""}, err
|
||||
}
|
||||
var s string
|
||||
switch k.Stat {
|
||||
case SIDL:
|
||||
case SRUN:
|
||||
case SONPROC:
|
||||
s = Running
|
||||
case SSLEEP:
|
||||
s = Sleep
|
||||
case SSTOP:
|
||||
s = Stop
|
||||
case SDEAD:
|
||||
s = Zombie
|
||||
}
|
||||
|
||||
return []string{s}, nil
|
||||
}
|
||||
|
||||
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
|
||||
out, err := invoke.CommandWithContext(ctx, "ps", "-o", "stat=", "-p", strconv.Itoa(int(pid)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.IndexByte(string(out), '+') != -1, nil
|
||||
}
|
||||
|
||||
func (p *Process) UidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uids := make([]uint32, 0, 3)
|
||||
|
||||
uids = append(uids, uint32(k.Ruid), uint32(k.Uid), uint32(k.Svuid))
|
||||
|
||||
return uids, nil
|
||||
}
|
||||
|
||||
func (p *Process) GidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gids := make([]uint32, 0, 3)
|
||||
gids = append(gids, uint32(k.Rgid), uint32(k.Ngroups), uint32(k.Svgid))
|
||||
|
||||
return gids, nil
|
||||
}
|
||||
|
||||
func (p *Process) GroupsWithContext(_ context.Context) ([]uint32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups := make([]uint32, k.Ngroups)
|
||||
for i := int16(0); i < k.Ngroups; i++ {
|
||||
groups[i] = uint32(k.Groups[i])
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (p *Process) TerminalWithContext(_ context.Context) (string, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ttyNr := uint64(k.Tdev)
|
||||
|
||||
termmap, err := getTerminalMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return termmap[ttyNr], nil
|
||||
}
|
||||
|
||||
func (p *Process) NiceWithContext(_ context.Context) (int32, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(k.Nice), nil
|
||||
}
|
||||
|
||||
func (p *Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IOCountersStat{
|
||||
ReadCount: uint64(k.Uru_inblock),
|
||||
WriteCount: uint64(k.Uru_oublock),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*Process) NumThreadsWithContext(_ context.Context) (int32, error) {
|
||||
/* not supported, just return 1 */
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (p *Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cpu.TimesStat{
|
||||
CPU: "cpu",
|
||||
User: float64(k.Uutime_sec) + float64(k.Uutime_usec)/1000000,
|
||||
System: float64(k.Ustime_sec) + float64(k.Ustime_usec)/1000000,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
|
||||
k, err := p.getKProc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageSize, err := mem.GetPageSizeWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MemoryInfoStat{
|
||||
RSS: uint64(k.Vm_rssize) * pageSize,
|
||||
VMS: uint64(k.Vm_tsize) + uint64(k.Vm_dsize) +
|
||||
uint64(k.Vm_ssize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
|
||||
procs, err := ProcessesWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
ret := make([]*Process, 0, len(procs))
|
||||
for _, proc := range procs {
|
||||
ppid, err := proc.PpidWithContext(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ppid == p.Pid {
|
||||
ret = append(ret, proc)
|
||||
}
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid })
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsWithContext(_ context.Context) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsMaxWithContext(_ context.Context, _ int) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
|
||||
results := []*Process{}
|
||||
|
||||
buf, length, err := callKernProcSyscall(KernProcAll, 0)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
// get kinfo_proc size
|
||||
count := int(length / uint64(sizeOfKinfoProc))
|
||||
|
||||
// parse buf to procs
|
||||
for i := 0; i < count; i++ {
|
||||
b := buf[i*sizeOfKinfoProc : (i+1)*sizeOfKinfoProc]
|
||||
k, err := parseKinfoProc(b)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p, err := NewProcessWithContext(ctx, int32(k.Pid))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, p)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) getKProc() (*KinfoProc, error) {
|
||||
buf, length, err := callKernProcSyscall(KernProcPID, p.Pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length != sizeOfKinfoProc {
|
||||
return nil, errors.New("unexpected size of KinfoProc")
|
||||
}
|
||||
|
||||
k, err := parseKinfoProc(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &k, nil
|
||||
}
|
||||
|
||||
func callKernProcSyscall(op, arg int32) ([]byte, uint64, error) {
|
||||
mib := []int32{CTLKern, KernProc, op, arg, sizeOfKinfoProc, 0}
|
||||
mibptr := unsafe.Pointer(&mib[0])
|
||||
miblen := uint64(len(mib))
|
||||
length := uint64(0)
|
||||
_, _, err := unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
return nil, length, err
|
||||
}
|
||||
|
||||
count := int32(length / uint64(sizeOfKinfoProc))
|
||||
mib = []int32{CTLKern, KernProc, op, arg, sizeOfKinfoProc, count}
|
||||
mibptr = unsafe.Pointer(&mib[0])
|
||||
miblen = uint64(len(mib))
|
||||
// get proc info itself
|
||||
buf := make([]byte, length)
|
||||
_, _, err = unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(mibptr),
|
||||
uintptr(miblen),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&length)),
|
||||
0,
|
||||
0)
|
||||
if err != 0 {
|
||||
return buf, length, err
|
||||
}
|
||||
|
||||
return buf, length, nil
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && 386
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs process/types_openbsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 66
|
||||
KernProcAll = 0
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 55
|
||||
KernProcCwd = 78
|
||||
KernProcArgv = 1
|
||||
KernProcEnv = 3
|
||||
)
|
||||
|
||||
const (
|
||||
ArgMax = 256 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x4
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x38
|
||||
sizeOfKinfoProc = 0x264
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SDEAD = 6
|
||||
SONPROC = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int32
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int32
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int32
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int32
|
||||
Ixrss int32
|
||||
Idrss int32
|
||||
Isrss int32
|
||||
Minflt int32
|
||||
Majflt int32
|
||||
Nswap int32
|
||||
Inblock int32
|
||||
Oublock int32
|
||||
Msgsnd int32
|
||||
Msgrcv int32
|
||||
Nsignals int32
|
||||
Nvcsw int32
|
||||
Nivcsw int32
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Forw uint64
|
||||
Back uint64
|
||||
Paddr uint64
|
||||
Addr uint64
|
||||
Fd uint64
|
||||
Stats uint64
|
||||
Limit uint64
|
||||
Vmspace uint64
|
||||
Sigacts uint64
|
||||
Sess uint64
|
||||
Tsess uint64
|
||||
Ru uint64
|
||||
Eflag int32
|
||||
Exitsig int32
|
||||
Flag int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Sid int32
|
||||
X_pgid int32
|
||||
Tpgid int32
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Gid uint32
|
||||
Rgid uint32
|
||||
Groups [16]uint32
|
||||
Ngroups int16
|
||||
Jobc int16
|
||||
Tdev uint32
|
||||
Estcpu uint32
|
||||
Rtime_sec uint32
|
||||
Rtime_usec uint32
|
||||
Cpticks int32
|
||||
Pctcpu uint32
|
||||
Swtime uint32
|
||||
Slptime uint32
|
||||
Schedflags int32
|
||||
Uticks uint64
|
||||
Sticks uint64
|
||||
Iticks uint64
|
||||
Tracep uint64
|
||||
Traceflag int32
|
||||
Holdcnt int32
|
||||
Siglist int32
|
||||
Sigmask uint32
|
||||
Sigignore uint32
|
||||
Sigcatch uint32
|
||||
Stat int8
|
||||
Priority uint8
|
||||
Usrpri uint8
|
||||
Nice uint8
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Comm [24]int8
|
||||
Wmesg [8]int8
|
||||
Wchan uint64
|
||||
Login [32]int8
|
||||
Vm_rssize int32
|
||||
Vm_tsize int32
|
||||
Vm_dsize int32
|
||||
Vm_ssize int32
|
||||
Uvalid int64
|
||||
Ustart_sec uint64
|
||||
Ustart_usec uint32
|
||||
Uutime_sec uint32
|
||||
Uutime_usec uint32
|
||||
Ustime_sec uint32
|
||||
Ustime_usec uint32
|
||||
Uru_maxrss uint64
|
||||
Uru_ixrss uint64
|
||||
Uru_idrss uint64
|
||||
Uru_isrss uint64
|
||||
Uru_minflt uint64
|
||||
Uru_majflt uint64
|
||||
Uru_nswap uint64
|
||||
Uru_inblock uint64
|
||||
Uru_oublock uint64
|
||||
Uru_msgsnd uint64
|
||||
Uru_msgrcv uint64
|
||||
Uru_nsignals uint64
|
||||
Uru_nvcsw uint64
|
||||
Uru_nivcsw uint64
|
||||
Uctime_sec uint32
|
||||
Uctime_usec uint32
|
||||
Psflags int32
|
||||
Spare int32
|
||||
Svuid uint32
|
||||
Svgid uint32
|
||||
Emul [8]int8
|
||||
Rlim_rss_cur uint64
|
||||
Cpuid uint64
|
||||
Vm_map_size uint64
|
||||
Tid int32
|
||||
Rtableid uint32
|
||||
}
|
||||
|
||||
type Priority struct{}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Start uint32
|
||||
End uint32
|
||||
Guard uint32
|
||||
Fspace uint32
|
||||
Fspace_augment uint32
|
||||
Offset uint64
|
||||
Wired_count int32
|
||||
Etype int32
|
||||
Protection int32
|
||||
Max_protection int32
|
||||
Advice int32
|
||||
Inheritance int32
|
||||
Flags uint8
|
||||
Pad_cgo_0 [3]byte
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// Created by cgo -godefs - DO NOT EDIT
|
||||
// cgo -godefs types_openbsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 66
|
||||
KernProcAll = 0
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 55
|
||||
KernProcCwd = 78
|
||||
KernProcArgv = 1
|
||||
KernProcEnv = 3
|
||||
)
|
||||
|
||||
const (
|
||||
ArgMax = 256 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x50
|
||||
sizeOfKinfoProc = 0x268
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SDEAD = 6
|
||||
SONPROC = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Forw uint64
|
||||
Back uint64
|
||||
Paddr uint64
|
||||
Addr uint64
|
||||
Fd uint64
|
||||
Stats uint64
|
||||
Limit uint64
|
||||
Vmspace uint64
|
||||
Sigacts uint64
|
||||
Sess uint64
|
||||
Tsess uint64
|
||||
Ru uint64
|
||||
Eflag int32
|
||||
Exitsig int32
|
||||
Flag int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Sid int32
|
||||
X_pgid int32
|
||||
Tpgid int32
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Gid uint32
|
||||
Rgid uint32
|
||||
Groups [16]uint32
|
||||
Ngroups int16
|
||||
Jobc int16
|
||||
Tdev uint32
|
||||
Estcpu uint32
|
||||
Rtime_sec uint32
|
||||
Rtime_usec uint32
|
||||
Cpticks int32
|
||||
Pctcpu uint32
|
||||
Swtime uint32
|
||||
Slptime uint32
|
||||
Schedflags int32
|
||||
Uticks uint64
|
||||
Sticks uint64
|
||||
Iticks uint64
|
||||
Tracep uint64
|
||||
Traceflag int32
|
||||
Holdcnt int32
|
||||
Siglist int32
|
||||
Sigmask uint32
|
||||
Sigignore uint32
|
||||
Sigcatch uint32
|
||||
Stat int8
|
||||
Priority uint8
|
||||
Usrpri uint8
|
||||
Nice uint8
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Comm [24]int8
|
||||
Wmesg [8]int8
|
||||
Wchan uint64
|
||||
Login [32]int8
|
||||
Vm_rssize int32
|
||||
Vm_tsize int32
|
||||
Vm_dsize int32
|
||||
Vm_ssize int32
|
||||
Uvalid int64
|
||||
Ustart_sec uint64
|
||||
Ustart_usec uint32
|
||||
Uutime_sec uint32
|
||||
Uutime_usec uint32
|
||||
Ustime_sec uint32
|
||||
Ustime_usec uint32
|
||||
Pad_cgo_0 [4]byte
|
||||
Uru_maxrss uint64
|
||||
Uru_ixrss uint64
|
||||
Uru_idrss uint64
|
||||
Uru_isrss uint64
|
||||
Uru_minflt uint64
|
||||
Uru_majflt uint64
|
||||
Uru_nswap uint64
|
||||
Uru_inblock uint64
|
||||
Uru_oublock uint64
|
||||
Uru_msgsnd uint64
|
||||
Uru_msgrcv uint64
|
||||
Uru_nsignals uint64
|
||||
Uru_nvcsw uint64
|
||||
Uru_nivcsw uint64
|
||||
Uctime_sec uint32
|
||||
Uctime_usec uint32
|
||||
Psflags int32
|
||||
Spare int32
|
||||
Svuid uint32
|
||||
Svgid uint32
|
||||
Emul [8]int8
|
||||
Rlim_rss_cur uint64
|
||||
Cpuid uint64
|
||||
Vm_map_size uint64
|
||||
Tid int32
|
||||
Rtableid uint32
|
||||
}
|
||||
|
||||
type Priority struct{}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
Guard uint64
|
||||
Fspace uint64
|
||||
Fspace_augment uint64
|
||||
Offset uint64
|
||||
Wired_count int32
|
||||
Etype int32
|
||||
Protection int32
|
||||
Max_protection int32
|
||||
Advice int32
|
||||
Inheritance int32
|
||||
Flags uint8
|
||||
Pad_cgo_0 [7]byte
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && arm
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs process/types_openbsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 66
|
||||
KernProcAll = 0
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 55
|
||||
KernProcCwd = 78
|
||||
KernProcArgv = 1
|
||||
KernProcEnv = 3
|
||||
)
|
||||
|
||||
const (
|
||||
ArgMax = 256 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x4
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x4
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x38
|
||||
sizeOfKinfoProc = 0x264
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SDEAD = 6
|
||||
SONPROC = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int32
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int32
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int32
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int32
|
||||
Ixrss int32
|
||||
Idrss int32
|
||||
Isrss int32
|
||||
Minflt int32
|
||||
Majflt int32
|
||||
Nswap int32
|
||||
Inblock int32
|
||||
Oublock int32
|
||||
Msgsnd int32
|
||||
Msgrcv int32
|
||||
Nsignals int32
|
||||
Nvcsw int32
|
||||
Nivcsw int32
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Forw uint64
|
||||
Back uint64
|
||||
Paddr uint64
|
||||
Addr uint64
|
||||
Fd uint64
|
||||
Stats uint64
|
||||
Limit uint64
|
||||
Vmspace uint64
|
||||
Sigacts uint64
|
||||
Sess uint64
|
||||
Tsess uint64
|
||||
Ru uint64
|
||||
Eflag int32
|
||||
Exitsig int32
|
||||
Flag int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Sid int32
|
||||
X_pgid int32
|
||||
Tpgid int32
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Gid uint32
|
||||
Rgid uint32
|
||||
Groups [16]uint32
|
||||
Ngroups int16
|
||||
Jobc int16
|
||||
Tdev uint32
|
||||
Estcpu uint32
|
||||
Rtime_sec uint32
|
||||
Rtime_usec uint32
|
||||
Cpticks int32
|
||||
Pctcpu uint32
|
||||
Swtime uint32
|
||||
Slptime uint32
|
||||
Schedflags int32
|
||||
Uticks uint64
|
||||
Sticks uint64
|
||||
Iticks uint64
|
||||
Tracep uint64
|
||||
Traceflag int32
|
||||
Holdcnt int32
|
||||
Siglist int32
|
||||
Sigmask uint32
|
||||
Sigignore uint32
|
||||
Sigcatch uint32
|
||||
Stat int8
|
||||
Priority uint8
|
||||
Usrpri uint8
|
||||
Nice uint8
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Comm [24]int8
|
||||
Wmesg [8]int8
|
||||
Wchan uint64
|
||||
Login [32]int8
|
||||
Vm_rssize int32
|
||||
Vm_tsize int32
|
||||
Vm_dsize int32
|
||||
Vm_ssize int32
|
||||
Uvalid int64
|
||||
Ustart_sec uint64
|
||||
Ustart_usec uint32
|
||||
Uutime_sec uint32
|
||||
Uutime_usec uint32
|
||||
Ustime_sec uint32
|
||||
Ustime_usec uint32
|
||||
Uru_maxrss uint64
|
||||
Uru_ixrss uint64
|
||||
Uru_idrss uint64
|
||||
Uru_isrss uint64
|
||||
Uru_minflt uint64
|
||||
Uru_majflt uint64
|
||||
Uru_nswap uint64
|
||||
Uru_inblock uint64
|
||||
Uru_oublock uint64
|
||||
Uru_msgsnd uint64
|
||||
Uru_msgrcv uint64
|
||||
Uru_nsignals uint64
|
||||
Uru_nvcsw uint64
|
||||
Uru_nivcsw uint64
|
||||
Uctime_sec uint32
|
||||
Uctime_usec uint32
|
||||
Psflags int32
|
||||
Spare int32
|
||||
Svuid uint32
|
||||
Svgid uint32
|
||||
Emul [8]int8
|
||||
Rlim_rss_cur uint64
|
||||
Cpuid uint64
|
||||
Vm_map_size uint64
|
||||
Tid int32
|
||||
Rtableid uint32
|
||||
}
|
||||
|
||||
type Priority struct{}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Start uint32
|
||||
End uint32
|
||||
Guard uint32
|
||||
Fspace uint32
|
||||
Fspace_augment uint32
|
||||
Offset uint64
|
||||
Wired_count int32
|
||||
Etype int32
|
||||
Protection int32
|
||||
Max_protection int32
|
||||
Advice int32
|
||||
Inheritance int32
|
||||
Flags uint8
|
||||
Pad_cgo_0 [3]byte
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && arm64
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs process/types_openbsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 66
|
||||
KernProcAll = 0
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 55
|
||||
KernProcCwd = 78
|
||||
KernProcArgv = 1
|
||||
KernProcEnv = 3
|
||||
)
|
||||
|
||||
const (
|
||||
ArgMax = 256 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x50
|
||||
sizeOfKinfoProc = 0x270
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SDEAD = 6
|
||||
SONPROC = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Forw uint64
|
||||
Back uint64
|
||||
Paddr uint64
|
||||
Addr uint64
|
||||
Fd uint64
|
||||
Stats uint64
|
||||
Limit uint64
|
||||
Vmspace uint64
|
||||
Sigacts uint64
|
||||
Sess uint64
|
||||
Tsess uint64
|
||||
Ru uint64
|
||||
Eflag int32
|
||||
Exitsig int32
|
||||
Flag int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Sid int32
|
||||
X_pgid int32
|
||||
Tpgid int32
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Gid uint32
|
||||
Rgid uint32
|
||||
Groups [16]uint32
|
||||
Ngroups int16
|
||||
Jobc int16
|
||||
Tdev uint32
|
||||
Estcpu uint32
|
||||
Rtime_sec uint32
|
||||
Rtime_usec uint32
|
||||
Cpticks int32
|
||||
Pctcpu uint32
|
||||
Swtime uint32
|
||||
Slptime uint32
|
||||
Schedflags int32
|
||||
Uticks uint64
|
||||
Sticks uint64
|
||||
Iticks uint64
|
||||
Tracep uint64
|
||||
Traceflag int32
|
||||
Holdcnt int32
|
||||
Siglist int32
|
||||
Sigmask uint32
|
||||
Sigignore uint32
|
||||
Sigcatch uint32
|
||||
Stat int8
|
||||
Priority uint8
|
||||
Usrpri uint8
|
||||
Nice uint8
|
||||
Xstat uint16
|
||||
Acflag uint16
|
||||
Comm [24]int8
|
||||
Wmesg [8]uint8
|
||||
Wchan uint64
|
||||
Login [32]uint8
|
||||
Vm_rssize int32
|
||||
Vm_tsize int32
|
||||
Vm_dsize int32
|
||||
Vm_ssize int32
|
||||
Uvalid int64
|
||||
Ustart_sec uint64
|
||||
Ustart_usec uint32
|
||||
Uutime_sec uint32
|
||||
Uutime_usec uint32
|
||||
Ustime_sec uint32
|
||||
Ustime_usec uint32
|
||||
Uru_maxrss uint64
|
||||
Uru_ixrss uint64
|
||||
Uru_idrss uint64
|
||||
Uru_isrss uint64
|
||||
Uru_minflt uint64
|
||||
Uru_majflt uint64
|
||||
Uru_nswap uint64
|
||||
Uru_inblock uint64
|
||||
Uru_oublock uint64
|
||||
Uru_msgsnd uint64
|
||||
Uru_msgrcv uint64
|
||||
Uru_nsignals uint64
|
||||
Uru_nvcsw uint64
|
||||
Uru_nivcsw uint64
|
||||
Uctime_sec uint32
|
||||
Uctime_usec uint32
|
||||
Psflags uint32
|
||||
Spare int32
|
||||
Svuid uint32
|
||||
Svgid uint32
|
||||
Emul [8]uint8
|
||||
Rlim_rss_cur uint64
|
||||
Cpuid uint64
|
||||
Vm_map_size uint64
|
||||
Tid int32
|
||||
Rtableid uint32
|
||||
Pledge uint64
|
||||
}
|
||||
|
||||
type Priority struct{}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
Guard uint64
|
||||
Fspace uint64
|
||||
Fspace_augment uint64
|
||||
Offset uint64
|
||||
Wired_count int32
|
||||
Etype int32
|
||||
Protection int32
|
||||
Max_protection int32
|
||||
Advice int32
|
||||
Inheritance int32
|
||||
Flags uint8
|
||||
Pad_cgo_0 [7]byte
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build openbsd && riscv64
|
||||
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs process/types_openbsd.go
|
||||
|
||||
package process
|
||||
|
||||
const (
|
||||
CTLKern = 1
|
||||
KernProc = 66
|
||||
KernProcAll = 0
|
||||
KernProcPID = 1
|
||||
KernProcProc = 8
|
||||
KernProcPathname = 12
|
||||
KernProcArgs = 55
|
||||
KernProcCwd = 78
|
||||
KernProcArgv = 1
|
||||
KernProcEnv = 3
|
||||
)
|
||||
|
||||
const (
|
||||
ArgMax = 256 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
sizeofPtr = 0x8
|
||||
sizeofShort = 0x2
|
||||
sizeofInt = 0x4
|
||||
sizeofLong = 0x8
|
||||
sizeofLongLong = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfKinfoVmentry = 0x50
|
||||
sizeOfKinfoProc = 0x288
|
||||
)
|
||||
|
||||
const (
|
||||
SIDL = 1
|
||||
SRUN = 2
|
||||
SSLEEP = 3
|
||||
SSTOP = 4
|
||||
SZOMB = 5
|
||||
SDEAD = 6
|
||||
SONPROC = 7
|
||||
)
|
||||
|
||||
type (
|
||||
_C_short int16
|
||||
_C_int int32
|
||||
_C_long int64
|
||||
_C_long_long int64
|
||||
)
|
||||
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
Maxrss int64
|
||||
Ixrss int64
|
||||
Idrss int64
|
||||
Isrss int64
|
||||
Minflt int64
|
||||
Majflt int64
|
||||
Nswap int64
|
||||
Inblock int64
|
||||
Oublock int64
|
||||
Msgsnd int64
|
||||
Msgrcv int64
|
||||
Nsignals int64
|
||||
Nvcsw int64
|
||||
Nivcsw int64
|
||||
}
|
||||
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
type KinfoProc struct {
|
||||
Forw uint64
|
||||
Back uint64
|
||||
Paddr uint64
|
||||
Addr uint64
|
||||
Fd uint64
|
||||
Stats uint64
|
||||
Limit uint64
|
||||
Vmspace uint64
|
||||
Sigacts uint64
|
||||
Sess uint64
|
||||
Tsess uint64
|
||||
Ru uint64
|
||||
Eflag int32
|
||||
Exitsig int32
|
||||
Flag int32
|
||||
Pid int32
|
||||
Ppid int32
|
||||
Sid int32
|
||||
X_pgid int32
|
||||
Tpgid int32
|
||||
Uid uint32
|
||||
Ruid uint32
|
||||
Gid uint32
|
||||
Rgid uint32
|
||||
Groups [16]uint32
|
||||
Ngroups int16
|
||||
Jobc int16
|
||||
Tdev uint32
|
||||
Estcpu uint32
|
||||
Rtime_sec uint32
|
||||
Rtime_usec uint32
|
||||
Cpticks int32
|
||||
Pctcpu uint32
|
||||
Swtime uint32
|
||||
Slptime uint32
|
||||
Schedflags int32
|
||||
Uticks uint64
|
||||
Sticks uint64
|
||||
Iticks uint64
|
||||
Tracep uint64
|
||||
Traceflag int32
|
||||
Holdcnt int32
|
||||
Siglist int32
|
||||
Sigmask uint32
|
||||
Sigignore uint32
|
||||
Sigcatch uint32
|
||||
Stat int8
|
||||
Priority uint8
|
||||
Usrpri uint8
|
||||
Nice uint8
|
||||
Xstat uint16
|
||||
Spare uint16
|
||||
Comm [24]int8
|
||||
Wmesg [8]uint8
|
||||
Wchan uint64
|
||||
Login [32]uint8
|
||||
Vm_rssize int32
|
||||
Vm_tsize int32
|
||||
Vm_dsize int32
|
||||
Vm_ssize int32
|
||||
Uvalid int64
|
||||
Ustart_sec uint64
|
||||
Ustart_usec uint32
|
||||
Uutime_sec uint32
|
||||
Uutime_usec uint32
|
||||
Ustime_sec uint32
|
||||
Ustime_usec uint32
|
||||
Uru_maxrss uint64
|
||||
Uru_ixrss uint64
|
||||
Uru_idrss uint64
|
||||
Uru_isrss uint64
|
||||
Uru_minflt uint64
|
||||
Uru_majflt uint64
|
||||
Uru_nswap uint64
|
||||
Uru_inblock uint64
|
||||
Uru_oublock uint64
|
||||
Uru_msgsnd uint64
|
||||
Uru_msgrcv uint64
|
||||
Uru_nsignals uint64
|
||||
Uru_nvcsw uint64
|
||||
Uru_nivcsw uint64
|
||||
Uctime_sec uint32
|
||||
Uctime_usec uint32
|
||||
Psflags uint32
|
||||
Acflag uint32
|
||||
Svuid uint32
|
||||
Svgid uint32
|
||||
Emul [8]uint8
|
||||
Rlim_rss_cur uint64
|
||||
Cpuid uint64
|
||||
Vm_map_size uint64
|
||||
Tid int32
|
||||
Rtableid uint32
|
||||
Pledge uint64
|
||||
Name [24]uint8
|
||||
}
|
||||
|
||||
type Priority struct{}
|
||||
|
||||
type KinfoVmentry struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
Guard uint64
|
||||
Fspace uint64
|
||||
Fspace_augment uint64
|
||||
Offset uint64
|
||||
Wired_count int32
|
||||
Etype int32
|
||||
Protection int32
|
||||
Max_protection int32
|
||||
Advice int32
|
||||
Inheritance int32
|
||||
Flags uint8
|
||||
Pad_cgo_0 [7]byte
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
type Signal = syscall.Note
|
||||
|
||||
type MemoryMapsStat struct {
|
||||
Path string `json:"path"`
|
||||
Rss uint64 `json:"rss"`
|
||||
Size uint64 `json:"size"`
|
||||
Pss uint64 `json:"pss"`
|
||||
SharedClean uint64 `json:"sharedClean"`
|
||||
SharedDirty uint64 `json:"sharedDirty"`
|
||||
PrivateClean uint64 `json:"privateClean"`
|
||||
PrivateDirty uint64 `json:"privateDirty"`
|
||||
Referenced uint64 `json:"referenced"`
|
||||
Anonymous uint64 `json:"anonymous"`
|
||||
Swap uint64 `json:"swap"`
|
||||
}
|
||||
|
||||
type MemoryInfoExStat struct{}
|
||||
|
||||
func pidsWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func ProcessesWithContext(_ context.Context) ([]*Process, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func PidExistsWithContext(_ context.Context, _ int32) (bool, error) {
|
||||
return false, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) PpidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NameWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TgidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ExeWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CmdlineWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CmdlineSliceWithContext(_ context.Context) ([]string, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) createTimeWithContext(_ context.Context) (int64, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CwdWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) StatusWithContext(_ context.Context) ([]string, error) {
|
||||
return []string{""}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ForegroundWithContext(_ context.Context) (bool, error) {
|
||||
return false, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) UidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) GidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) GroupsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TerminalWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NiceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOniceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitWithContext(_ context.Context) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitUsageWithContext(_ context.Context, _ bool) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumThreadsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ThreadsWithContext(_ context.Context) (map[int32]*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoExWithContext(_ context.Context) (*MemoryInfoExStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) PageFaultsWithContext(_ context.Context) (*PageFaultsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ChildrenWithContext(_ context.Context) ([]*Process, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) OpenFilesWithContext(_ context.Context) ([]OpenFilesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsWithContext(_ context.Context) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsMaxWithContext(_ context.Context, _ int) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryMapsWithContext(_ context.Context, _ bool) (*[]MemoryMapsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) SendSignalWithContext(_ context.Context, _ Signal) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) SuspendWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ResumeWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TerminateWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) KillWithContext(_ context.Context) error {
|
||||
return common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) UsernameWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) EnvironWithContext(_ context.Context) ([]string, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux || freebsd || openbsd || darwin || solaris
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
type Signal = syscall.Signal
|
||||
|
||||
// POSIX
|
||||
func getTerminalMap() (map[uint64]string, error) {
|
||||
ret := make(map[uint64]string)
|
||||
var termfiles []string
|
||||
|
||||
devPath := common.HostDev()
|
||||
|
||||
d, err := os.Open(devPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
devnames, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, devname := range devnames {
|
||||
if strings.HasPrefix(devname, "tty") {
|
||||
termfiles = append(termfiles, filepath.Join(devPath, devname))
|
||||
}
|
||||
}
|
||||
|
||||
var ptsnames []string
|
||||
ptsPath := filepath.Join(devPath, "pts")
|
||||
ptsd, err := os.Open(ptsPath)
|
||||
if err != nil {
|
||||
ptsnames, _ = filepath.Glob(filepath.Join(devPath, "ttyp*"))
|
||||
if ptsnames == nil {
|
||||
return nil, err
|
||||
}
|
||||
termfiles = append(termfiles, ptsnames...)
|
||||
} else {
|
||||
defer ptsd.Close()
|
||||
ptsnames, err = ptsd.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ptsname := range ptsnames {
|
||||
termfiles = append(termfiles, filepath.Join(ptsPath, ptsname))
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range termfiles {
|
||||
stat := unix.Stat_t{}
|
||||
err = unix.Stat(name, &stat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rdev := uint64(stat.Rdev)
|
||||
ret[rdev] = strings.TrimPrefix(name, devPath+string(os.PathSeparator))
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// isMount is a port of python's os.path.ismount()
|
||||
// https://github.com/python/cpython/blob/08ff4369afca84587b1c82034af4e9f64caddbf2/Lib/posixpath.py#L186-L216
|
||||
// https://docs.python.org/3/library/os.path.html#os.path.ismount
|
||||
func isMount(path string) bool {
|
||||
// Check symlinkness with os.Lstat; unix.DT_LNK is not portable
|
||||
fileInfo, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
return false
|
||||
}
|
||||
var stat1 unix.Stat_t
|
||||
if err := unix.Lstat(path, &stat1); err != nil {
|
||||
return false
|
||||
}
|
||||
parent := filepath.Join(path, "..")
|
||||
var stat2 unix.Stat_t
|
||||
if err := unix.Lstat(parent, &stat2); err != nil {
|
||||
return false
|
||||
}
|
||||
return stat1.Dev != stat2.Dev || stat1.Ino == stat2.Ino
|
||||
}
|
||||
|
||||
func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
|
||||
if pid <= 0 {
|
||||
return false, fmt.Errorf("invalid pid %v", pid)
|
||||
}
|
||||
proc, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer proc.Release()
|
||||
|
||||
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
|
||||
}
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
// procfs does not exist or is not mounted, check PID existence by signalling the pid
|
||||
err = proc.Signal(syscall.Signal(0))
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if errors.Is(err, os.ErrProcessDone) {
|
||||
return false, nil
|
||||
}
|
||||
var errno syscall.Errno
|
||||
if !errors.As(err, &errno) {
|
||||
return false, err
|
||||
}
|
||||
switch errno {
|
||||
case syscall.ESRCH:
|
||||
return false, nil
|
||||
case syscall.EPERM:
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (p *Process) SendSignalWithContext(_ context.Context, sig syscall.Signal) error {
|
||||
process, err := os.FindProcess(int(p.Pid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer process.Release()
|
||||
|
||||
err = process.Signal(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Process) SuspendWithContext(ctx context.Context) error {
|
||||
return p.SendSignalWithContext(ctx, unix.SIGSTOP)
|
||||
}
|
||||
|
||||
func (p *Process) ResumeWithContext(ctx context.Context) error {
|
||||
return p.SendSignalWithContext(ctx, unix.SIGCONT)
|
||||
}
|
||||
|
||||
func (p *Process) TerminateWithContext(ctx context.Context) error {
|
||||
return p.SendSignalWithContext(ctx, unix.SIGTERM)
|
||||
}
|
||||
|
||||
func (p *Process) KillWithContext(ctx context.Context) error {
|
||||
return p.SendSignalWithContext(ctx, unix.SIGKILL)
|
||||
}
|
||||
|
||||
func (p *Process) UsernameWithContext(ctx context.Context) (string, error) {
|
||||
uids, err := p.UidsWithContext(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(uids) > 0 {
|
||||
u, err := user.LookupId(strconv.Itoa(int(uids[0])))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return u.Username, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
type MemoryMapsStat struct {
|
||||
Path string `json:"path"`
|
||||
Rss uint64 `json:"rss"`
|
||||
Size uint64 `json:"size"`
|
||||
Pss uint64 `json:"pss"`
|
||||
SharedClean uint64 `json:"sharedClean"`
|
||||
SharedDirty uint64 `json:"sharedDirty"`
|
||||
PrivateClean uint64 `json:"privateClean"`
|
||||
PrivateDirty uint64 `json:"privateDirty"`
|
||||
Referenced uint64 `json:"referenced"`
|
||||
Anonymous uint64 `json:"anonymous"`
|
||||
Swap uint64 `json:"swap"`
|
||||
}
|
||||
|
||||
type MemoryInfoExStat struct{}
|
||||
|
||||
func pidsWithContext(ctx context.Context) ([]int32, error) {
|
||||
return readPidsFromDir(common.HostProcWithContext(ctx))
|
||||
}
|
||||
|
||||
func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
|
||||
out := []*Process{}
|
||||
|
||||
pids, err := PidsWithContext(ctx)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
for _, pid := range pids {
|
||||
p, err := NewProcessWithContext(ctx, pid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (*Process) PpidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NameWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TgidWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
|
||||
exe, err := p.fillFromPathAOutWithContext(ctx)
|
||||
if os.IsNotExist(err) {
|
||||
exe, err = p.fillFromExecnameWithContext(ctx)
|
||||
}
|
||||
return exe, err
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
|
||||
return p.fillFromCmdlineWithContext(ctx)
|
||||
}
|
||||
|
||||
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
|
||||
return p.fillSliceFromCmdlineWithContext(ctx)
|
||||
}
|
||||
|
||||
func (*Process) createTimeWithContext(_ context.Context) (int64, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
|
||||
return p.fillFromPathCwdWithContext(ctx)
|
||||
}
|
||||
|
||||
func (*Process) StatusWithContext(_ context.Context) ([]string, error) {
|
||||
return []string{""}, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ForegroundWithContext(_ context.Context) (bool, error) {
|
||||
return false, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) UidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) GidsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) GroupsWithContext(_ context.Context) ([]uint32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TerminalWithContext(_ context.Context) (string, error) {
|
||||
return "", common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NiceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOniceWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitWithContext(_ context.Context) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) RlimitUsageWithContext(_ context.Context, _ bool) ([]RlimitStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) IOCountersWithContext(_ context.Context) (*IOCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) {
|
||||
_, fnames, err := p.fillFromfdListWithContext(ctx)
|
||||
return int32(len(fnames)), err
|
||||
}
|
||||
|
||||
func (*Process) NumThreadsWithContext(_ context.Context) (int32, error) {
|
||||
return 0, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ThreadsWithContext(_ context.Context) (map[int32]*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) TimesWithContext(_ context.Context) (*cpu.TimesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryInfoExWithContext(_ context.Context) (*MemoryInfoExStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) PageFaultsWithContext(_ context.Context) (*PageFaultsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ChildrenWithContext(_ context.Context) ([]*Process, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) OpenFilesWithContext(_ context.Context) ([]OpenFilesStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsWithContext(_ context.Context) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) ConnectionsMaxWithContext(_ context.Context, _ int) ([]net.ConnectionStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) MemoryMapsWithContext(_ context.Context, _ bool) (*[]MemoryMapsStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func (*Process) EnvironWithContext(_ context.Context) ([]string, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
/**
|
||||
** Internal functions
|
||||
**/
|
||||
|
||||
func (p *Process) fillFromfdListWithContext(ctx context.Context) (string, []string, error) {
|
||||
pid := p.Pid
|
||||
statPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "fd")
|
||||
d, err := os.Open(statPath)
|
||||
if err != nil {
|
||||
return statPath, []string{}, err
|
||||
}
|
||||
defer d.Close()
|
||||
fnames, err := d.Readdirnames(-1)
|
||||
return statPath, fnames, err
|
||||
}
|
||||
|
||||
func (p *Process) fillFromPathCwdWithContext(ctx context.Context) (string, error) {
|
||||
pid := p.Pid
|
||||
cwdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "path", "cwd")
|
||||
cwd, err := os.Readlink(cwdPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cwd, nil
|
||||
}
|
||||
|
||||
func (p *Process) fillFromPathAOutWithContext(ctx context.Context) (string, error) {
|
||||
pid := p.Pid
|
||||
cwdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "path", "a.out")
|
||||
exe, err := os.Readlink(cwdPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
func (p *Process) fillFromExecnameWithContext(ctx context.Context) (string, error) {
|
||||
pid := p.Pid
|
||||
execNamePath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "execname")
|
||||
exe, err := os.ReadFile(execNamePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(exe), nil
|
||||
}
|
||||
|
||||
func (p *Process) fillFromCmdlineWithContext(ctx context.Context) (string, error) {
|
||||
pid := p.Pid
|
||||
cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline")
|
||||
cmdline, err := os.ReadFile(cmdPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret := strings.FieldsFunc(string(cmdline), func(r rune) bool {
|
||||
return r == '\u0000'
|
||||
})
|
||||
|
||||
return strings.Join(ret, " "), nil
|
||||
}
|
||||
|
||||
func (p *Process) fillSliceFromCmdlineWithContext(ctx context.Context) ([]string, error) {
|
||||
pid := p.Pid
|
||||
cmdPath := common.HostProcWithContext(ctx, strconv.Itoa(int(pid)), "cmdline")
|
||||
cmdline, err := os.ReadFile(cmdPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cmdline) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if cmdline[len(cmdline)-1] == 0 {
|
||||
cmdline = cmdline[:len(cmdline)-1]
|
||||
}
|
||||
parts := bytes.Split(cmdline, []byte{0})
|
||||
var strParts []string
|
||||
for _, p := range parts {
|
||||
strParts = append(strParts, string(p))
|
||||
}
|
||||
|
||||
return strParts, nil
|
||||
}
|
||||
|
||||
func readPidsFromDir(path string) ([]int32, error) {
|
||||
var ret []int32
|
||||
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
fnames, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, fname := range fnames {
|
||||
if !strictIntPtrn.MatchString(fname) {
|
||||
continue
|
||||
}
|
||||
pid, err := strconv.ParseInt(fname, 10, 32)
|
||||
if err != nil {
|
||||
// if not numeric name, just skip
|
||||
continue
|
||||
}
|
||||
ret = append(ret, int32(pid))
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
+1213
File diff suppressed because it is too large
Load Diff
+104
@@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build (windows && 386) || (windows && arm)
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/internal/common"
|
||||
)
|
||||
|
||||
type PROCESS_MEMORY_COUNTERS struct { //nolint:revive //FIXME
|
||||
CB uint32
|
||||
PageFaultCount uint32
|
||||
PeakWorkingSetSize uint32
|
||||
WorkingSetSize uint32
|
||||
QuotaPeakPagedPoolUsage uint32
|
||||
QuotaPagedPoolUsage uint32
|
||||
QuotaPeakNonPagedPoolUsage uint32
|
||||
QuotaNonPagedPoolUsage uint32
|
||||
PagefileUsage uint32
|
||||
PeakPagefileUsage uint32
|
||||
}
|
||||
|
||||
func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) (uint64, error) {
|
||||
if is32BitProcess {
|
||||
// we are on a 32-bit process reading an external 32-bit process
|
||||
var info processBasicInformation32
|
||||
|
||||
ret, _, _ := common.ProcNtQueryInformationProcess.Call(
|
||||
uintptr(procHandle),
|
||||
uintptr(common.ProcessBasicInformation),
|
||||
uintptr(unsafe.Pointer(&info)),
|
||||
uintptr(unsafe.Sizeof(info)),
|
||||
uintptr(0),
|
||||
)
|
||||
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
|
||||
return uint64(info.PebBaseAddress), nil
|
||||
}
|
||||
return 0, windows.NTStatus(ret)
|
||||
}
|
||||
// we are on a 32-bit process reading an external 64-bit process
|
||||
if common.ProcNtWow64QueryInformationProcess64.Find() != nil {
|
||||
return 0, errors.New("can't find API to query 64 bit process from 32 bit")
|
||||
}
|
||||
// avoid panic
|
||||
var info processBasicInformation64
|
||||
|
||||
ret, _, _ := common.ProcNtWow64QueryInformationProcess64.Call(
|
||||
uintptr(procHandle),
|
||||
uintptr(common.ProcessBasicInformation),
|
||||
uintptr(unsafe.Pointer(&info)),
|
||||
uintptr(unsafe.Sizeof(info)),
|
||||
uintptr(0),
|
||||
)
|
||||
if status := windows.NTStatus(ret); status == windows.STATUS_SUCCESS {
|
||||
return info.PebBaseAddress, nil
|
||||
}
|
||||
return 0, windows.NTStatus(ret)
|
||||
}
|
||||
|
||||
func readProcessMemory(h syscall.Handle, is32BitProcess bool, address uint64, size uint) []byte {
|
||||
if is32BitProcess {
|
||||
var read uint
|
||||
|
||||
buffer := make([]byte, size)
|
||||
|
||||
ret, _, _ := common.ProcNtReadVirtualMemory.Call(
|
||||
uintptr(h),
|
||||
uintptr(address),
|
||||
uintptr(unsafe.Pointer(&buffer[0])),
|
||||
uintptr(size),
|
||||
uintptr(unsafe.Pointer(&read)),
|
||||
)
|
||||
if int(ret) >= 0 && read > 0 {
|
||||
return buffer[:read]
|
||||
}
|
||||
// reading a 64-bit process from a 32-bit one
|
||||
} else if common.ProcNtWow64ReadVirtualMemory64.Find() == nil { // avoid panic
|
||||
var read uint64
|
||||
|
||||
buffer := make([]byte, size)
|
||||
|
||||
ret, _, _ := common.ProcNtWow64ReadVirtualMemory64.Call(
|
||||
uintptr(h),
|
||||
uintptr(address&0xFFFFFFFF), // the call expects a 64-bit value
|
||||
uintptr(address>>32),
|
||||
uintptr(unsafe.Pointer(&buffer[0])),
|
||||
uintptr(size), // the call expects a 64-bit value
|
||||
uintptr(0), // but size is 32-bit so pass zero as the high dword
|
||||
uintptr(unsafe.Pointer(&read)),
|
||||
)
|
||||
if int(ret) >= 0 && read > 0 {
|
||||
return buffer[:uint(read)]
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach here, an error happened
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user