Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hostfs option to Host and Process functions, fix OperatingSystem() #226

Merged
merged 18 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
3 changes: 3 additions & 0 deletions .changelog/226.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
Add API for metrics from an alternate filesystem root, allow OperatingSystem() to read from an alternate filesystem root
fearful-symmetry marked this conversation as resolved.
Show resolved Hide resolved
```
53 changes: 47 additions & 6 deletions internal/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,48 @@ import (
"github.com/elastic/go-sysinfo/types"
)

var (
hostProvider HostProvider
processProvider ProcessProvider
)
type HostOptsCreator = func(ProviderOptions) HostProvider
type ProcessOptsCreator = func(ProviderOptions) ProcessProvider

// HostProvider defines interfaces that provide host-specific metrics
type HostProvider interface {
Host() (types.Host, error)
}

// ProcessProvider defines interfaces that provide process-specific metrics
type ProcessProvider interface {
Processes() ([]types.Process, error)
Process(pid int) (types.Process, error)
Self() (types.Process, error)
}

type ProviderOptions struct {
Hostfs string
}

var (
hostProvider HostProvider
processProvider ProcessProvider
processProviderWithOpts ProcessOptsCreator
hostProviderWithOpts HostOptsCreator
)

// Register a metrics provider. `provider` should implement one or more of `ProcessProvider` or `HostProvider`
func Register(provider interface{}) {
if h, ok := provider.(ProcessOptsCreator); ok {
if processProviderWithOpts != nil {
panic(fmt.Sprintf("ProcessOptsCreator already registered: %T", processProviderWithOpts))
}
processProviderWithOpts = h
}

if h, ok := provider.(HostOptsCreator); ok {
if hostProviderWithOpts != nil {
panic(fmt.Sprintf("HostOptsCreator already registered: %T", hostProviderWithOpts))
}
hostProviderWithOpts = h
}

if h, ok := provider.(HostProvider); ok {
if hostProvider != nil {
panic(fmt.Sprintf("HostProvider already registered: %v", hostProvider))
Expand All @@ -52,7 +78,22 @@ func Register(provider interface{}) {
}
processProvider = p
}

}

// GetHostProvider returns the HostProvider registered for the system. May return nil.
func GetHostProvider(opts ProviderOptions) HostProvider {
if hostProviderWithOpts != nil {
return hostProviderWithOpts(opts)
}
return hostProvider

}

func GetHostProvider() HostProvider { return hostProvider }
func GetProcessProvider() ProcessProvider { return processProvider }
// GetProcessProvider returns the ProcessProvider registered on the system. May return nil.
func GetProcessProvider(opts ProviderOptions) ProcessProvider {
if processProviderWithOpts != nil {
return processProviderWithOpts(opts)
}
return processProvider
}
41 changes: 26 additions & 15 deletions providers/linux/host_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import (
)

func init() {
registry.Register(newLinuxSystem(""))
// register wrappers that implement the HostFS versions of the ProcessProvider and HostProvider
registry.Register(func(opts registry.ProviderOptions) registry.HostProvider { return newLinuxSystem(opts.Hostfs) })
registry.Register(func(opts registry.ProviderOptions) registry.ProcessProvider { return newLinuxSystem(opts.Hostfs) })

}

type linuxSystem struct {
Expand All @@ -45,7 +48,7 @@ func newLinuxSystem(hostFS string) linuxSystem {
mountPoint := filepath.Join(hostFS, procfs.DefaultMountPoint)
fs, _ := procfs.NewFS(mountPoint)
return linuxSystem{
procFS: procFS{FS: fs, mountPoint: mountPoint},
procFS: procFS{FS: fs, mountPoint: mountPoint, baseMount: hostFS},
}
}

Expand All @@ -59,14 +62,17 @@ type host struct {
info types.HostInfo
}

// Info returns host info
func (h *host) Info() types.HostInfo {
return h.info
}

// Memory returns memory info
func (h *host) Memory() (*types.HostMemoryInfo, error) {
content, err := os.ReadFile(h.procFS.path("meminfo"))
path := h.procFS.path("meminfo")
content, err := os.ReadFile(path)
if err != nil {
return nil, err
return nil, fmt.Errorf("error reading meminfo file %s: %w", path, err)
}

return parseMemInfo(content)
Expand All @@ -82,9 +88,10 @@ func (h *host) FQDN() (string, error) {

// VMStat reports data from /proc/vmstat on linux.
func (h *host) VMStat() (*types.VMStatInfo, error) {
content, err := os.ReadFile(h.procFS.path("vmstat"))
path := h.procFS.path("vmstat")
content, err := os.ReadFile(path)
if err != nil {
return nil, err
return nil, fmt.Errorf("error reading vmstat file %s: %w", path, err)
}

return parseVMStat(content)
Expand All @@ -94,7 +101,7 @@ func (h *host) VMStat() (*types.VMStatInfo, error) {
func (h *host) LoadAverage() (*types.LoadAverageInfo, error) {
loadAvg, err := h.procFS.LoadAvg()
if err != nil {
return nil, err
return nil, fmt.Errorf("error fetching load averages: %w", err)
}

return &types.LoadAverageInfo{
Expand All @@ -106,31 +113,34 @@ func (h *host) LoadAverage() (*types.LoadAverageInfo, error) {

// NetworkCounters reports data from /proc/net on linux
func (h *host) NetworkCounters() (*types.NetworkCountersInfo, error) {
snmpRaw, err := os.ReadFile(h.procFS.path("net/snmp"))
snmpFile := h.procFS.path("net/snmp")
snmpRaw, err := os.ReadFile(snmpFile)
if err != nil {
return nil, err
return nil, fmt.Errorf("error fetching net/snmp file %s: %w", snmpFile, err)
}
snmp, err := getNetSnmpStats(snmpRaw)
if err != nil {
return nil, err
return nil, fmt.Errorf("error parsing SNMP stats: %w", err)
}

netstatRaw, err := os.ReadFile(h.procFS.path("net/netstat"))
netstatFile := h.procFS.path("net/netstat")
netstatRaw, err := os.ReadFile(netstatFile)
if err != nil {
return nil, err
return nil, fmt.Errorf("error fetching net/netstat file %s: %w", netstatFile, err)
}
netstat, err := getNetstatStats(netstatRaw)
if err != nil {
return nil, err
return nil, fmt.Errorf("error parsing netstat file: %w", err)
}

return &types.NetworkCountersInfo{SNMP: snmp, Netstat: netstat}, nil
}

// CPUTime returns host CPU usage metrics
func (h *host) CPUTime() (types.CPUTimes, error) {
stat, err := h.procFS.Stat()
if err != nil {
return types.CPUTimes{}, err
return types.CPUTimes{}, fmt.Errorf("error fetching CPU stats: %w", err)
}

return types.CPUTimes{
Expand Down Expand Up @@ -246,7 +256,7 @@ func (r *reader) kernelVersion(h *host) {
}

func (r *reader) os(h *host) {
v, err := OperatingSystem()
v, err := OperatingSystem(h.procFS.baseMount)
if r.addErr(err) {
return
}
Expand All @@ -268,6 +278,7 @@ func (r *reader) uniqueID(h *host) {
type procFS struct {
procfs.FS
mountPoint string
baseMount string
}

func (fs *procFS) path(p ...string) string {
Expand Down
4 changes: 2 additions & 2 deletions providers/linux/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ func init() {
}
}

func OperatingSystem() (*types.OSInfo, error) {
return getOSInfo("")
func OperatingSystem(procfs string) (*types.OSInfo, error) {
andrewkroh marked this conversation as resolved.
Show resolved Hide resolved
return getOSInfo(procfs)
}

func getOSInfo(baseDir string) (*types.OSInfo, error) {
Expand Down
Loading
Loading