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

FreeBSD Support #126

Open
wants to merge 68 commits into
base: main
Choose a base branch
from
Open

FreeBSD Support #126

wants to merge 68 commits into from

Conversation

redanthrax
Copy link

@redanthrax redanthrax commented Aug 5, 2022

Updated support for FreeBSD using dbolcsfoldi's work from #40.

Sample host data
{
  "host.cpu": {
    "idle": 84740000000,
    "irq": 3400000000,
    "system": 15410000000,
    "user": 43490000000
  },
  "host.info": {
    "architecture": "amd64",
    "boot_time": "2024-04-11T07:17:11.943687Z",
    "id": "535510ba-782f-4398-a566-76cef038cad1",
    "ip": [
      "fe80::5054:ff:fe49:8a64/64",
      "192.168.122.66/24",
      "::1/128",
      "fe80::1/64",
      "127.0.0.1/8"
    ],
    "kernel_version": "14.0-RELEASE",
    "mac": [
      "52:54:00:49:8a:64"
    ],
    "name": "freebsd",
    "native_architecture": "",
    "os": {
      "build": "RELEASE",
      "family": "freebsd",
      "major": 14,
      "minor": 0,
      "name": "FreeBSD",
      "patch": 0,
      "platform": "freebsd",
      "type": "freebsd",
      "version": "14.0-RELEASE"
    },
    "timezone": "UTC",
    "timezone_offset_sec": 0
  },
  "host.memory": {
    "available_bytes": 6008164864,
    "free_bytes": 5357379584,
    "raw": {
      "active_bytes": 29511680,
      "buffer_bytes": 608727552,
      "cache_bytes": 0,
      "free_bytes": 5357379584,
      "inactive_bytes": 42057728,
      "wired_bytes": 774049792
    },
    "total_bytes": 6402035712,
    "used_bytes": 803561472,
    "virtual_free_bytes": 1073610752,
    "virtual_total_bytes": 1073610752,
    "virtual_used_bytes": 0
  }
}

dbolcsfoldi and others added 9 commits February 5, 2019 14:23
Initial implementation of HostInfo for FreeBSD platforms.
On some platforms (e.g. FreeBSD) user home directories are symlinks
and os.Getcwd will return the symlink path.

On FreeBSD CWD for processes that's not the running process will not
give the symlink but the resolved path causing the test to fail at this
point.

Fix is to use the os.SameFile call to resolve both Os.Getcwd and CWD.
Implement Process(), Self() and Processes() to the bare minimum.
That is passing all basic tests.
- Fix issue on FreeBSD 12.
- Minor cosmetic issues.
- Remove cgo build directives from things not needing it.
- Add linker flags into .go files.
@cla-checker-service
Copy link

cla-checker-service bot commented Aug 5, 2022

💚 CLA has been signed

@redanthrax
Copy link
Author

@dbolcsfoldi Can you sign the agreement to get your commits in?

@elasticmachine
Copy link
Collaborator

elasticmachine commented Aug 5, 2022

❕ Build Aborted

The PR is not allowed to run in the CI yet

the below badges are clickable and redirect to their specific view in the CI or DOCS
Pipeline View Test View Changes Artifacts preview

Expand to view the summary

Build stats

  • Start Time: 2022-08-25T20:31:24.259+0000

  • Duration: 2 min 58 sec

Steps errors 2

Expand to view the steps failures

Load a resource file from a library
  • Took 0 min 0 sec . View more details here
  • Description: approval-list/elastic/go-sysinfo.yml
Error signal
  • Took 0 min 0 sec . View more details here
  • Description: githubApiCall: The REST API call https://api.github.com/orgs/elastic/members/redanthrax return the message : java.lang.Exception: httpRequest: Failure connecting to the service https://api.github.com/orgs/elastic/members/redanthrax : httpRequest: Failure connecting to the service https://api.github.com/orgs/elastic/members/redanthrax : Code: 404Error: {"message":"User does not exist or is not a member of the organization","documentation_url":"https://docs.github.com/rest/reference/orgs#check-organization-membership-for-a-user"}

@rvstaveren
Copy link

Hi @redanthrax,

A small fix for the Environment stuff. To be sure this also works in a jail I ran the test from there. It works ok, but my shell had an odd environment variabele the test tripped over:

$ go test
Getting Self()...
Getting ProcessInfo...
Getting UserInfo...
Getting Environment...
Getting MemoryInfo...
Getting CPUTimes...
Getting OpenHandleEnumerator...
--- FAIL: TestSelf (0.00s)
    system_test.go:204: 
                Error Trace:    system_test.go:204
...
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -32,3 +32,3 @@
                                  (string) (len=23) "XIM_PROGRAM=ibus-daemon",
                                - (string) (len=19) "XMODIFIERS=@im=ibus",
                                + (string) (len=14) "XMODIFIERS=@im",
                                  (string) (len=19) "_=/usr/local/bin/go"
                Test:           TestSelf
    system_test.go:243: open handles count: 11
FAIL
exit status 1
FAIL    github.com/elastic/go-sysinfo   0.031s
...

fixed by

diff --git a/providers/freebsd/process_freebsd.go b/providers/freebsd/process_freebsd.go
index 117d56c..aa47a53 100644
--- a/providers/freebsd/process_freebsd.go
+++ b/providers/freebsd/process_freebsd.go
@@ -125,7 +125,7 @@ func makeMap(from []string) map[string]string {
        out := make(map[string]string, len(from))
 
        for _, env := range from {
-               parts := strings.Split(env, "=")
+               parts := strings.SplitN(env, "=", 2)
                if len(parts) > 1 {
                        out[parts[0]] = parts[1]
                }

Best Regards,
Ruben

@lapo-luchini
Copy link

Hope this gets (CLA-signed and) committed soon, as this is a show-stopper for beats 8 support on FreeBSD.

@jurajlutter
Copy link

Did this move forward, please?


func SwapMaxPages() (uint32, error) {
var maxPages uint32
if err := sysctlByName(hwPhysmemMIB, &maxPages); err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be vmSwapmaxpagesMIB

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, it's vm.swap_maxpages OID.

@redanthrax
Copy link
Author

In the year of our lord 2024 I have given up that this will ever be considered.

@vansante
Copy link

Yeah, I have also given up and resorted to setting up a personal fork with this merged 🤷

@dbolcsfoldi
Copy link

Hello all! I signed the CLA - 2024 will be the year!

@andrewkroh
Copy link
Member

I opened #204 to add CI testing for FreeBSD.

andrewkroh added a commit that referenced this pull request Mar 14, 2024
Add GH action step test execute unit tests on FreeBSD 14.0.
The tests run inside of a qemu VM running on top of a linux
worker.

Until a FreeBSD provider is present this step will test
nothing. It will become active as soon as files exist in
'providers/freebsd/'.

Relates: #126

---------

Co-authored-by: Victor Martinez <[email protected]>
[git-generate]

gofumpt -w --extra $(find . -name '*.go')
@andrewkroh andrewkroh added the enhancement New feature or request label Mar 14, 2024
providers/freebsd/host_freebsd.go:56:9: cannot use newHost() (value of type *host) as types.Host value in return statement: *host does not implement types.Host (missing method FQDN)
@andrewkroh andrewkroh requested review from a team May 16, 2024 14:30
@andrewkroh
Copy link
Member

What is still left is the network part. I don't have any spare cycles to do the hard work :-(

The NetworkCounters can be added in a separate PR (I think that's what you mean). That's an optional part of the provider.

Copy link
Contributor

@efd6 efd6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've had a light look at the C and it appears sane. It would be good to have @haesbaert take a look at this.

Comment on lines 110 to 113
if len(r.errs) > 0 {
return errors.Join(r.errs...)
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if len(r.errs) > 0 {
return errors.Join(r.errs...)
}
return nil
return errors.Join(r.errs...)

https://pkg.go.dev/errors#Join

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, but I don't understand the reasoning behind this change. The other providers are using identical methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that it reduces the line count. errors.Join returns nil if there is no non-nil error in the parameter list. I suspect that the other cases look the way they do because the were written before errors.Join existed.

return
}
m.Metrics = make(map[string]uint64, 6)
m.Metrics["active_bytes"] = uint64(activePages) * pageSize
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISTM that all these conversions should happen in the helpers that are providing the values. If the helpers were methods on *reader then we would not need to consider errors or conversions here (see comment on L132).

Copy link

@sarog sarog Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the integer casts into their respective helper methods. Will this be sufficient, or do you prefer I move the helper methods onto *reader as well?

EDIT: I can also simply pass *r into the helpers as an argument (see below).

Comment on lines 132 to 135
if r.addErr(err) {
return
}
pageSize := uint64(ps)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not be optimistic here? At the moment, any single non-implemented collection means all subsequent collections are not performed. Is this guaranteed to be the behaviour we want? The alternative is to use the approach that is used for collections used in the linux newHost func where all are optimistically collected and then the aggregated errors are returned along with all the data that was obtained.

Copy link

@sarog sarog Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can either move the helpers onto *reader (pointer receiver) or we can also do this if permitted:

func totalPhysicalMem(r *reader) uint64 {
	const mib = "hw.physmem"
	v, err := unix.SysctlUint64(mib)
	if r.addErr(err) {
		return 0
	}
	return v
}

EDIT: redanthrax@bbb75c4

Copy link
Contributor

@efd6 efd6 Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks OK. It reads a bit like C, but I guess we are working close to that anyway here.


var _ registry.HostProvider = freebsdSystem{}

func TestHost(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we test for the presence of some keys?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate? e.g. check that we're actually on a FreeBSD system?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, the only thing that is being tested without human intervention is that the calls return without error. The results could all be empty or non-sane, but the only way we can know this is if a human reads the test logs. We could probably do better at least by checking that some of the expected values (keys) are present.

Copy link

@sarog sarog Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I added additional validations to the tests.

Extracted code from the updated PR:

// TestArchitecture -- I can also validate with `uname -m` if you prefer
assert.Regexp(t, `(amd64|i386|powerpc|arm(64)?|riscv|mips|sparc64|pc98)`, arch)

// TestKernelVersion
cmd := exec.Command("/bin/freebsd-version", "-r")

// TestMachineID -- I know Regex is slow here, but trying to avoid extra packages
assert.Regexp(t, "^[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$", machineID)

EDIT: revised architectures list (mistakened a few from MACHINE_ARCH values)

Comment on lines 122 to 123
const mib = "kern.cp_time"
buf, err := unix.SysctlRaw("kern.cp_time")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const mib = "kern.cp_time"
buf, err := unix.SysctlRaw("kern.cp_time")
const mib = "kern.cp_time"
buf, err := unix.SysctlRaw(mib)

t.Fatal(err)
}

// Apply a sanity check. This assumes the host has rebooted in the last year.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we shell out to uptime and get a value to use?

Copy link

@sarog sarog Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a PR on the forked repo that addresses your request (awaiting approval). It's a bit overkill for a test, but there's no other structured way to get the uptime apart from sysctl or adding more CGO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks reasonable after clean-up; not overkill at all.

@lhirlimann
Copy link
Member

Can someone tackle the review comments?

@lhirlimann
Copy link
Member

I Guess we need @sarog to sign the cla

@sarog
Copy link

sarog commented Jun 21, 2024

I Guess we need @sarog to sign the cla

I did sign the CLA. Is the approval automated or must someone at Elastic review it?

@efd6
Copy link
Contributor

efd6 commented Jun 23, 2024

CLA looks good now.

@samm-git
Copy link

Would be nice to see its merged. I am now adding it as a local patch to build beats8/FreeBSD and everything works so far. W/o patch beats is unusable

freebsd-git pushed a commit to freebsd/freebsd-ports that referenced this pull request Jul 21, 2024
- fix beats by integrating patch from the elastic/go-sysinfo#126
- upgrade to the latest version

ChangeLog: https://www.elastic.co/guide/en/beats/libbeat/current/release-notes.html
Approved by: otis (elastic)
PR: 272701
Copy link

@haesbaert haesbaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your work (and patience :)). I think it's pretty good, but it needs to be a bit more careful with the null derefs in the the procstat_free* functions.

I'm not sure how much we care about 32bit, see kern.cp_times comment.

I do think the "I'll pass nil down instead of handling at the time I get it" makes it a bit confusing to read, but I only spotted one real bug.

Feel free to ignore some of the subjective suggestions.

Comment on lines +45 to +47
var errstr *C.char
kd := C.kvm_open(nil, nil, nil, unix.O_RDONLY, errstr)
if errstr != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is effectively passing NULL as errstr:

  The kvm_open() function is the Sun kvm compatible open call.  Here, the
       errstr argument indicates how errors should be handled.	If it is NULL,
       no errors are reported and the application cannot know the specific na-
       ture of the failed kvm call.  If	it is not NULL,	errors are printed  to
       stderr  with  errstr  prepended	to the message,	as in perror(3).  Nor-
       mally, the name of the program is used here.  The string	is assumed  to
       persist at least	until the corresponding	kvm_close() call.

I'd say either pass "go-sysinfo", so that it will prepend it and print out to stderr, but I guess this is not the desired behavior, or use kvm_openfiles() passing a storage of _POSIX2_LINE_MAX, like ps(1) does:

	char errbuf[_POSIX2_LINE_MAX];
	kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
	if (kd == NULL)
		xo_errx(1, "%s", errbuf);

Comment on lines +53 to +54
if n, err := C.kvm_getswapinfo(kd, (*C.struct_kvm_swap)(unsafe.Pointer(&swap)), 1, 0); n != 0 {
return nil, fmt.Errorf("failed to get kvm_getswapinfo: %w", err)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kvm_getswapinfo() doesn't set errno, so you might be printing an old value.
, assuming you do the kvm_openfiles dance above: you'd have to go through kvm_geterr(3) . If not, I'd just zap err.

Comment on lines +18 to +19
//go:build freebsd && cgo

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any references to Cgo here, do you really need it (and the file being named _cgo). I'm not an expert in Cgo so forgive me if I'm missing something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kvmGetSwapInfo is defined in a Cgo-dependent way, and is referred to here, so this file is tainted. The build constraint here protects that. Given that, it may be clearer to just have that function defined in this file.

Comment on lines +112 to +129
const sizeOfUint64 = int(unsafe.Sizeof(uint64(0)))

// cpuStateTimes uses sysctl kern.cp_time to get the amount of time spent in
// different CPU states.
func cpuStateTimes() (*types.CPUTimes, error) {
tickDuration, err := tickDuration()
if err != nil {
return nil, err
}

const mib = "kern.cp_time"
buf, err := unix.SysctlRaw(mib)
if err != nil {
return nil, fmt.Errorf("failed to get %s: %w", mib, err)
}

var clockTicks [unix.CPUSTATES]uint64
if len(buf) < len(clockTicks)*sizeOfUint64 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this will work for 32bit systems, the problem is cp_times uses longs, not uint32_t or uint64_t, so on 32bit systems that will actually be 32 instead of 64, so the size of the structure changes.

See this call from top(1):

	size = sizeof(long) * maxcpu * CPUSTATES;
	if (sysctlbyname("kern.cp_times", times, &size, NULL, 0) == -1)
		err(1, "sysctlbyname kern.cp_times");

I realize it's a bit sad that there isn't a unix.SysctlUlong, so in order to do this we could have to conditionally check.

Comment on lines +41 to +45
unsigned int countArrayItems(char **items) {
unsigned int i = 0;
for (i = 0; items[i] != NULL; ++i);
return i;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of bikeshedding, can we not initialize i twice.


unsigned int countArrayItems(char **items) {
unsigned int i = 0;
for (i = 0; items[i] != NULL; ++i);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit, can we put the ; in its own new line, my brain always skips it.

}
defer C.procstat_close(procstat)

env, err := C.procstat_getenvv(procstat, &p.kinfo, 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you properly handle env == nil in copyArray(), and C.procstat_freeenv() can be called even if the allocation failed: I think is a bit too much imho, people will touch this code in the future and miss this artifact. Feel free to ignore this suggestion.

}
defer C.procstat_close(procstat)

args, err := C.procstat_getargv(procstat, &p.kinfo, 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as procstat_getenvv

return i;
}

char * itemAtIndex(char **items, unsigned int index) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note itemAtIndex and countArrayItems could be implemented in go with unsafe.Add(), but I think this is fine.

Copy link
Contributor

@efd6 efd6 Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer the unsafe over Cgo here. One is potentially (probably) a call and the other gets written as a single instruction.

Comment on lines +176 to +177
const maxlen = uint(1024)
out := make([]C.char, maxlen)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use C.MAXPATHNAMELEN or C.PATH_MAX.

@sarog
Copy link

sarog commented Jul 22, 2024

I'm not sure how much we care about 32bit, see kern.cp_times comment.

To address your 32-bit concern, I believe go-sysinfo does not plan to support x86 FreeBSD, given how it's been demoted to a Tier 2 platform with plans to drop support in 15.x.

@samm-git
Copy link

I can confirm that 32bit platforms seems to be broken now:

vendor/github.com/elastic/go-sysinfo/providers/freebsd/sysctl_freebsd.go:85:24: cannot use tv.Sec (variable of type int32) as int64 value in argument to time.Unix
vendor/github.com/elastic/go-sysinfo/providers/freebsd/sysctl_freebsd.go:85:32: invalid operation: tv.Usec * int64(time.Microsecond) (mismatched types int32 and int64)
vendor/github.com/elastic/go-sysinfo/providers/freebsd/process_freebsd_cgo.go:178:95: cannot use _Ctype_ulong(maxlen) (constant 1024 of type _Ctype_ulong) as _Ctype_uint value in variable declaration

@samm-git
Copy link

I will try to find some time on the weekend to make patch 32 bit compatible, it seems to be not a hard task to do.

@samm-git
Copy link

another problem - cross build failing because we are defining reader and related functions in the cgo file. it should be moved to non-cgo one

@efd6
Copy link
Contributor

efd6 commented Jul 24, 2024

another problem - cross build failing because we are defining reader and related functions in the cgo file. it should be moved to non-cgo one

The entire package is dependent on Cgo, so all files except doc.go should be guarded by the cgo build constraint.

@lhirlimann
Copy link
Member

Did we make any recent progress here?

@sarog
Copy link

sarog commented Oct 23, 2024

In the year of our lord 2024 I have given up that this will ever be considered.

Hello all! I signed the CLA - 2024 will be the year!

69 days to go until 2024 is over. 😭

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.