Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ Some code is ported from Ohai. Many thanks.
|terminal |x |x |x | | |
|io\_counters |x |x |x | |x |
|nice |x |x |x |x |x |
|num\_fds |x | | | |x |
|num\_fds |x | | |x |x |
|num\_ctx\_switches |x | | | | |
|num\_threads |x |x |x |x |x |
|cpu\_times |x | | | |x |
Expand Down
1 change: 1 addition & 0 deletions internal/common/common_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ const (

const (
MAXPATHLEN = 1024
PROC_PIDLISTFDS = 1
PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN
PROC_PIDTASKINFO = 4
PROC_PIDVNODEPATHINFO = 9
Expand Down
4 changes: 0 additions & 4 deletions process/process_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ func (*Process) NumCtxSwitchesWithContext(_ context.Context) (*NumCtxSwitchesSta
return nil, common.ErrNotImplementedError
}

func (*Process) NumFDsWithContext(_ context.Context) (int32, error) {
return 0, common.ErrNotImplementedError
}

func (*Process) CPUAffinityWithContext(_ context.Context) ([]int32, error) {
return nil, common.ErrNotImplementedError
}
Expand Down
51 changes: 51 additions & 0 deletions process/process_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,54 @@ func (p *Process) MemoryInfoWithContext(_ context.Context) (*MemoryInfoStat, err
}
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.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.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
}
62 changes: 62 additions & 0 deletions process/process_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin

package process

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNumFDs(t *testing.T) {
pid := os.Getpid()
p, err := NewProcess(int32(pid))
require.NoError(t, err)

ctx := context.Background()

before, err := p.NumFDsWithContext(ctx)
require.NoError(t, err)

// Open files to increase FD count
f1, err := os.Open("/dev/null")
require.NoError(t, err)
defer f1.Close()

f2, err := os.Open("/dev/null")
require.NoError(t, err)
defer f2.Close()

after, err := p.NumFDsWithContext(ctx)
require.NoError(t, err)

assert.GreaterOrEqual(t, after, before+2)
}

func TestNumFDs_NonExistent(t *testing.T) {
p := &Process{Pid: 99999}
_, err := p.NumFDsWithContext(context.Background())
assert.Error(t, err)
}

func BenchmarkNumFDs(b *testing.B) {
pid := int32(os.Getpid())
p, err := NewProcess(pid)
if err != nil {
b.Skip(err)
}

ctx := context.Background()
b.ResetTimer()

for i := 0; i < b.N; i++ {
_, err := p.NumFDsWithContext(ctx)
if err != nil {
b.Fatal(err)
}
}
}
4 changes: 4 additions & 0 deletions process/process_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
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}

Expand Down
4 changes: 4 additions & 0 deletions process/process_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
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 {
Expand Down