diff --git a/providers/freebsd/arch.go b/providers/freebsd/arch.go new file mode 100644 index 00000000..844c331a --- /dev/null +++ b/providers/freebsd/arch.go @@ -0,0 +1,35 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package freebsd + +import ( + "syscall" + + "github.com/pkg/errors" +) + +const hardwareMIB = "hw.machine" + +func Architecture() (string, error) { + arch, err := syscall.Sysctl(hardwareMIB) + if err != nil { + return "", errors.Wrap(err, "failed to get architecture") + } + + return arch, nil +} diff --git a/providers/freebsd/boottime_freebsd.go b/providers/freebsd/boottime_freebsd.go new file mode 100644 index 00000000..f5364e27 --- /dev/null +++ b/providers/freebsd/boottime_freebsd.go @@ -0,0 +1,37 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package freebsd + +import ( + "syscall" + "time" + + "github.com/pkg/errors" +) + +const kernBoottimeMIB = "kern.boottime" + +func BootTime() (time.Time, error) { + var tv syscall.Timeval + if err := sysctlByName(kernBoottimeMIB, &tv); err != nil { + return time.Time{}, errors.Wrap(err, "failed to get host uptime") + } + + bootTime := time.Unix(int64(tv.Sec), int64(tv.Usec)*int64(time.Microsecond)) + return bootTime, nil +} diff --git a/providers/freebsd/defs_freebsd.go b/providers/freebsd/defs_freebsd.go new file mode 100644 index 00000000..553cd371 --- /dev/null +++ b/providers/freebsd/defs_freebsd.go @@ -0,0 +1,34 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build ignore + +package freebsd + +/* +#include +#include +#include +#include +*/ +import "C" + +type vmTotal C.struct_vmtotal + +type kvmSwap C.struct_kvm_swap + +type clockInfo C.struct_clockinfo diff --git a/providers/freebsd/doc.go b/providers/freebsd/doc.go new file mode 100644 index 00000000..336d45aa --- /dev/null +++ b/providers/freebsd/doc.go @@ -0,0 +1,20 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package freebsd implements the HostProvider and ProcessProvider interfaces +// for providing information about FreeBSD +package freebsd diff --git a/providers/freebsd/host_freebsd.go b/providers/freebsd/host_freebsd.go new file mode 100644 index 00000000..ef3ee0bd --- /dev/null +++ b/providers/freebsd/host_freebsd.go @@ -0,0 +1,229 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build,freebsd,cgo + +package freebsd + +// #cgo LDFLAGS: -lkvm +//#include +//#include +import "C" + +import ( + "os" + "time" + + "github.com/joeshaw/multierror" + "github.com/pkg/errors" + + "github.com/elastic/go-sysinfo/internal/registry" + "github.com/elastic/go-sysinfo/providers/shared" + "github.com/elastic/go-sysinfo/types" +) + +func init() { + registry.Register(freebsdSystem{}) +} + +type freebsdSystem struct{} + +func (s freebsdSystem) Host() (types.Host, error) { + return newHost() +} + +type host struct { + info types.HostInfo +} + +func (h *host) Info() types.HostInfo { + return h.info +} + +func (h *host) CPUTime() (types.CPUTimes, error) { + cpu := types.CPUTimes{} + r := &reader{} + r.cpuTime(&cpu) + + return cpu, nil +} + +func (h *host) Memory() (*types.HostMemoryInfo, error) { + m := &types.HostMemoryInfo{} + r := &reader{} + r.memInfo(m) + return m, r.Err() +} + +func newHost() (*host, error) { + h := &host{} + r := &reader{} + r.architecture(h) + r.bootTime(h) + r.hostname(h) + r.network(h) + r.kernelVersion(h) + r.os(h) + r.time(h) + r.uniqueID(h) + return h, r.Err() +} + +type reader struct { + errs []error +} + +func (r *reader) addErr(err error) bool { + if err != nil { + if errors.Cause(err) != types.ErrNotImplemented { + r.errs = append(r.errs, err) + } + return true + } + return false +} + +func (r *reader) Err() error { + if len(r.errs) > 0 { + return &multierror.MultiError{Errors: r.errs} + } + return nil +} + +func (r *reader) cpuTime(cpu *types.CPUTimes) { + cptime, err := Cptime() + + if r.addErr(err) { + return + } + + cpu.User = time.Duration(cptime["User"]) + cpu.System = time.Duration(cptime["System"]) + cpu.Idle = time.Duration(cptime["Idle"]) + cpu.Nice = time.Duration(cptime["Nice"]) + cpu.IRQ = time.Duration(cptime["IRQ"]) +} + +func (r *reader) memInfo(m *types.HostMemoryInfo) { + pageSize, err := PageSize() + + if r.addErr(err) { + return + } + + totalMemory, err := TotalMemory() + if r.addErr(err) { + return + } + + m.Total = totalMemory + + vm, err := VmTotal() + if r.addErr(err) { + return + } + + m.Free = uint64(vm.Free) * uint64(pageSize) + m.Used = m.Total - m.Free + + numFreeBuffers, err := NumFreeBuffers() + if r.addErr(err) { + return + } + + m.Available = m.Free + (uint64(numFreeBuffers) * uint64(pageSize)) + + swap, err := KvmGetSwapInfo() + if r.addErr(err) { + return + } + + swapMaxPages, err := SwapMaxPages() + if r.addErr(err) { + return + } + + if swap.Total > swapMaxPages { + swap.Total = swapMaxPages + } + + m.VirtualTotal = uint64(swap.Total) * uint64(pageSize) + m.VirtualUsed = uint64(swap.Used) * uint64(pageSize) + m.VirtualFree = m.VirtualTotal - m.VirtualUsed +} + +func (r *reader) architecture(h *host) { + v, err := Architecture() + if r.addErr(err) { + return + } + h.info.Architecture = v +} + +func (r *reader) bootTime(h *host) { + v, err := BootTime() + if r.addErr(err) { + return + } + + h.info.BootTime = v +} + +func (r *reader) hostname(h *host) { + v, err := os.Hostname() + if r.addErr(err) { + return + } + h.info.Hostname = v +} + +func (r *reader) network(h *host) { + ips, macs, err := shared.Network() + if r.addErr(err) { + return + } + h.info.IPs = ips + h.info.MACs = macs +} + +func (r *reader) kernelVersion(h *host) { + v, err := KernelVersion() + if r.addErr(err) { + return + } + h.info.KernelVersion = v +} + +func (r *reader) os(h *host) { + v, err := OperatingSystem() + if r.addErr(err) { + return + } + h.info.OS = v +} + +func (r *reader) time(h *host) { + h.info.Timezone, h.info.TimezoneOffsetSec = time.Now().Zone() +} + +func (r *reader) uniqueID(h *host) { + v, err := MachineID() + if r.addErr(err) { + return + } + h.info.UniqueID = v +} diff --git a/providers/freebsd/kernel.go b/providers/freebsd/kernel.go new file mode 100644 index 00000000..3ade2725 --- /dev/null +++ b/providers/freebsd/kernel.go @@ -0,0 +1,35 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package freebsd + +import ( + "syscall" + + "github.com/pkg/errors" +) + +const kernelReleaseMIB = "kern.osrelease" + +func KernelVersion() (string, error) { + version, err := syscall.Sysctl(kernelReleaseMIB) + if err != nil { + return "", errors.Wrap(err, "failed to get kernel version") + } + + return version, nil +} diff --git a/providers/freebsd/machineid.go b/providers/freebsd/machineid.go new file mode 100644 index 00000000..2857ee1e --- /dev/null +++ b/providers/freebsd/machineid.go @@ -0,0 +1,35 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package freebsd + +import ( + "syscall" + + "github.com/pkg/errors" +) + +const kernelHostUUIDMIB = "kern.hostuuid" + +func MachineID() (string, error) { + uuid, err := syscall.Sysctl(kernelHostUUIDMIB) + if err != nil { + return "", errors.Wrap(err, "failed to get machine id") + } + + return uuid, nil +} diff --git a/providers/freebsd/memory_freebsd.go b/providers/freebsd/memory_freebsd.go new file mode 100644 index 00000000..e824db7d --- /dev/null +++ b/providers/freebsd/memory_freebsd.go @@ -0,0 +1,111 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build freebsd,cgo + +package freebsd + +// #cgo LDFLAGS: -lkvm +//#include +//#include +//#include + +//#include +//#include +//#include +import "C" + +import ( + "github.com/pkg/errors" + "syscall" + "unsafe" +) + +const hwPhysmemMIB = "hw.physmem" +const hwPagesizeMIB = "hw.pagesize" +const vmVmtotalMIB = "vm.vmtotal" +const vmSwapmaxpagesMIB = "vm.swap_maxpages" +const vfsNumfreebuffersMIB = "vfs.numfreebuffers" +const devNull = "/dev/null" +const kvmOpen = "kvm_open" + +func PageSize() (uint32, error) { + var pageSize uint32 + if err := sysctlByName(hwPagesizeMIB, &pageSize); err != nil { + return 0, errors.Wrap(err, "failed to get hw.pagesize") + } + + return pageSize, nil +} + +func SwapMaxPages() (uint32, error) { + var maxPages uint32 + if err := sysctlByName(hwPhysmemMIB, &maxPages); err != nil { + return 0, errors.Wrap(err, "failed to get vm.swap_maxpages") + } + + return maxPages, nil +} + +func TotalMemory() (uint64, error) { + var size uint64 + if err := sysctlByName(hwPhysmemMIB, &size); err != nil { + return 0, errors.Wrap(err, "failed to get hw.physmem") + } + + return size, nil +} + +func VmTotal() (vmTotal, error) { + var vm vmTotal + if err := sysctlByName(vmVmtotalMIB, &vm); err != nil { + return vmTotal{}, errors.Wrap(err, "failed to get vm.vmtotal") + } + + return vm, nil +} + +func NumFreeBuffers() (uint32, error) { + var numfreebuffers uint32 + if err := sysctlByName(vfsNumfreebuffersMIB, &numfreebuffers); err != nil { + return 0, errors.Wrap(err, "failed to get vfs.numfreebuffers") + } + + return numfreebuffers, nil +} + +func KvmGetSwapInfo() (kvmSwap, error) { + var kdC *C.struct_kvm_t + + devNullC := C.CString(devNull) + defer C.free(unsafe.Pointer(devNullC)) + kvmOpenC := C.CString(kvmOpen) + defer C.free(unsafe.Pointer(kvmOpenC)) + + if kdC, err := C.kvm_open(nil, devNullC, nil, syscall.O_RDONLY, kvmOpenC); kdC == nil { + return kvmSwap{}, errors.Wrap(err, "failed to open kvm") + } + + defer C.kvm_close((*C.struct___kvm)(unsafe.Pointer(kdC))) + + var swap kvmSwap + if n, err := C.kvm_getswapinfo((*C.struct___kvm)(unsafe.Pointer(kdC)), (*C.struct_kvm_swap)(unsafe.Pointer(&swap)), 1, 0); n != 0 { + return kvmSwap{}, errors.Wrap(err, "failed to get kvm_getswapinfo") + } + + return swap, nil +} diff --git a/providers/freebsd/os_freebsd.go b/providers/freebsd/os_freebsd.go new file mode 100644 index 00000000..1c1bab48 --- /dev/null +++ b/providers/freebsd/os_freebsd.go @@ -0,0 +1,66 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package freebsd + +import ( + "strconv" + "strings" + "syscall" + + "github.com/elastic/go-sysinfo/types" +) + +const ostypeMIB = "kern.ostype" +const osreleaseMIB = "kern.osrelease" +const osrevisionMIB = "kern.osrevision" + +func OperatingSystem() (*types.OSInfo, error) { + info := &types.OSInfo{ + Family: "freebsd", + Platform: "freebsd", + } + + ostype, err := syscall.Sysctl(ostypeMIB) + if err != nil { + return info, err + } + info.Name = ostype + + osrelease, err := syscall.Sysctl(osreleaseMIB) + if err != nil { + return info, err + } + info.Version = osrelease + + elems := strings.Split(osrelease, "-") + majorminor := strings.Split(elems[0], ".") + + if len(majorminor) > 0 { + info.Major, _ = strconv.Atoi(majorminor[0]) + } + + if len(majorminor) > 1 { + info.Minor, _ = strconv.Atoi(majorminor[1]) + } + + if len(elems) > 1 { + info.Patch, _ = strconv.Atoi(strings.TrimPrefix(elems[2], "p")) + } + + return info, nil +} diff --git a/providers/freebsd/process_freebsd.go b/providers/freebsd/process_freebsd.go new file mode 100644 index 00000000..a2dd00ff --- /dev/null +++ b/providers/freebsd/process_freebsd.go @@ -0,0 +1,401 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build,freebsd,cgo + +package freebsd + +// #cgo LDFLAGS: -lkvm -lprocstat +//#include +//#include +//#include +//#include +//#include +//#include +//#include +// +//#include +//#include +//struct kinfo_proc getProcInfoAt(struct kinfo_proc *procs, unsigned int index) { +// return procs[index]; +//} +//unsigned int countArrayItems(char **items) { +// unsigned int i = 0; +// for (i = 0; items[i] != NULL; ++i); +// return i; +//} +//char * itemAtIndex(char **items, unsigned int index) { +// return items[index]; +//} +//unsigned int countFileStats(struct filestat_list *head) { +// unsigned int count = 0; +// struct filestat *fst; +// STAILQ_FOREACH(fst, head, next) { +// ++count; +// } +// +// return count; +//} +//void copyFileStats(struct filestat_list *head, struct filestat *out, unsigned int size) { +// unsigned int index = 0; +// struct filestat *fst; +// STAILQ_FOREACH(fst, head, next) { +// if (!size) { +// break; +// } +// memcpy(out, fst, sizeof(*fst)); +// ++out; +// --size; +// } +//} +// +import "C" + +import ( + "os" + "strconv" + "strings" + "syscall" + "time" + + "github.com/pkg/errors" + + "github.com/elastic/go-sysinfo/types" +) + +func getProcInfo(op int, arg int) ([]process, error) { + procstat, err := C.procstat_open_sysctl() + + if procstat == nil { + return nil, errors.Wrap(err, "failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + var count C.uint = 0 + kprocs, err := C.procstat_getprocs(procstat, C.int(op), C.int(arg), &count) + if kprocs == nil { + return nil, errors.Wrap(err, "getprocs failed") + } + defer C.procstat_freeprocs(procstat, kprocs) + + procs := make([]process, count) + var index C.uint + for index = 0; index < count; index++ { + proc := C.getProcInfoAt(kprocs, index) + procs[index].kinfo = proc + procs[index].pid = int(proc.ki_pid) + } + + return procs, nil +} + +func copyArray(from **C.char) []string { + if from == nil { + return nil + } + + count := C.countArrayItems(from) + out := make([]string, count) + + for index := C.uint(0); index < count; index++ { + out[index] = C.GoString(C.itemAtIndex(from, index)) + } + + return out +} + +func makeMap(from []string) map[string]string { + out := make(map[string]string, len(from)) + + for _, env := range from { + parts := strings.Split(env, "=") + if len(parts) > 1 { + out[parts[0]] = parts[1] + } + } + + return out +} + +func getProcEnv(p *process) (map[string]string, error) { + procstat, err := C.procstat_open_sysctl() + + if procstat == nil { + return nil, errors.Wrap(err, "failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + env, err := C.procstat_getenvv(procstat, &p.kinfo, 0) + defer C.procstat_freeenvv(procstat) + + return makeMap(copyArray(env)), err +} + +func getProcArgs(p *process) ([]string, error) { + procstat, err := C.procstat_open_sysctl() + + if procstat == nil { + return nil, errors.Wrap(err, "failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + args, err := C.procstat_getargv(procstat, &p.kinfo, 0) + defer C.procstat_freeargv(procstat) + + return copyArray(args), err +} + +func getProcPathname(p *process) (string, error) { + procstat, err := C.procstat_open_sysctl() + + if procstat == nil { + return "", errors.Wrap(err, "failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + const maxlen = uint(1024) + out := make([]C.char, maxlen) + if res, err := C.procstat_getpathname(procstat, &p.kinfo, &out[0], C.ulong(maxlen)); res != 0 { + return "", err + } + return C.GoString(&out[0]), nil +} + +func getFileStats(fileStats *C.struct_filestat_list) []C.struct_filestat { + count := C.countFileStats(fileStats) + + if count < 1 { + return nil + } + + out := make([]C.struct_filestat, count) + + C.copyFileStats(fileStats, &out[0], count) + return out +} + +func getProcCWD(p *process) (string, error) { + procstat, err := C.procstat_open_sysctl() + + if procstat == nil { + return "", errors.Wrap(err, "failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + fs, err := C.procstat_getfiles(procstat, &p.kinfo, 0) + if fs == nil { + return "", errors.Wrap(err, "failed to get files") + } + + defer C.procstat_freefiles(procstat, fs) + + files := getFileStats(fs) + for _, f := range files { + if f.fs_uflags == C.PS_FST_UFLAG_CDIR { + return C.GoString(f.fs_path), nil + } + } + + return "", nil +} + +type process struct { + pid int + kinfo C.struct_kinfo_proc +} + +func timevalToDuration(tm C.struct_timeval) time.Duration { + return (time.Duration(tm.tv_sec) * 1000000) + (time.Duration(tm.tv_usec) * 1000) +} + +func (p *process) CPUTime() (types.CPUTimes, error) { + procs, err := getProcInfo(C.KERN_PROC_PID, p.PID()) + + if err != nil { + return types.CPUTimes{}, err + } + p.kinfo = procs[0].kinfo + + return types.CPUTimes{ + User: timevalToDuration(p.kinfo.ki_rusage.ru_utime), + System: timevalToDuration(p.kinfo.ki_rusage.ru_stime), + }, nil +} + +func (p *process) Info() (types.ProcessInfo, error) { + procs, err := getProcInfo(C.KERN_PROC_PID, p.PID()) + + if err != nil { + return types.ProcessInfo{}, err + } + p.kinfo = procs[0].kinfo + + cwd, err := getProcCWD(p) + if err != nil { + return types.ProcessInfo{}, err + } + + args, err := getProcArgs(p) + if err != nil { + return types.ProcessInfo{}, err + } + + exe, _ := getProcPathname(p) + + return types.ProcessInfo{ + Name: C.GoString(&p.kinfo.ki_comm[0]), + PID: int(p.kinfo.ki_pid), + PPID: int(p.kinfo.ki_ppid), + CWD: cwd, + Exe: exe, + Args: args, + StartTime: time.Unix(int64(p.kinfo.ki_start.tv_sec), int64(p.kinfo.ki_start.tv_usec)*1000), + }, nil +} + +func (p *process) Memory() (types.MemoryInfo, error) { + procs, err := getProcInfo(C.KERN_PROC_PID, p.PID()) + + if err != nil { + return types.MemoryInfo{}, err + } + p.kinfo = procs[0].kinfo + + return types.MemoryInfo{ + Resident: uint64(p.kinfo.ki_rssize), + Virtual: uint64(p.kinfo.ki_size), + }, nil +} + +func (p *process) User() (types.UserInfo, error) { + procs, err := getProcInfo(C.KERN_PROC_PID, p.PID()) + + if err != nil { + return types.UserInfo{}, err + } + + p.kinfo = procs[0].kinfo + + return types.UserInfo{ + UID: strconv.FormatUint(uint64(p.kinfo.ki_ruid), 10), + EUID: strconv.FormatUint(uint64(p.kinfo.ki_uid), 10), + SUID: strconv.FormatUint(uint64(p.kinfo.ki_svuid), 10), + GID: strconv.FormatUint(uint64(p.kinfo.ki_rgid), 10), + EGID: strconv.FormatUint(uint64(p.kinfo.ki_groups[0]), 10), + SGID: strconv.FormatUint(uint64(p.kinfo.ki_svgid), 10), + }, nil +} + +func (p *process) PID() int { + return p.pid +} + +func (p *process) OpenHandles() ([]string, error) { + procstat := C.procstat_open_sysctl() + + if procstat == nil { + return nil, errors.New("failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + fs := C.procstat_getfiles(procstat, &p.kinfo, 0) + defer C.procstat_freefiles(procstat, fs) + + files := getFileStats(fs) + names := make([]string, 0, len(files)) + + for _, file := range files { + if file.fs_uflags == 0 { + names = append(names, C.GoString(file.fs_path)) + } + } + + return names, nil +} + +func (p *process) OpenHandleCount() (int, error) { + procstat := C.procstat_open_sysctl() + + if procstat == nil { + return 0, errors.New("failed to open procstat sysctl") + } + defer C.procstat_close(procstat) + + fs := C.procstat_getfiles(procstat, &p.kinfo, 0) + defer C.procstat_freefiles(procstat, fs) + return int(C.countFileStats(fs)), nil +} + +func (p *process) Environment() (map[string]string, error) { + return getProcEnv(p) +} + +func (s freebsdSystem) Processes() ([]types.Process, error) { + procs, err := getProcInfo(C.KERN_PROC_PROC, 0) + out := make([]types.Process, 0, len(procs)) + + for _, proc := range procs { + out = append(out, &process{ + pid: proc.pid, + kinfo: proc.kinfo, + }) + } + + return out, err +} + +func (s freebsdSystem) Process(pid int) (types.Process, error) { + p := process{pid: pid} + return &p, nil +} + +func (s freebsdSystem) Self() (types.Process, error) { + return s.Process(os.Getpid()) +} + +const kernCptimeMIB = "kern.cp_time" +const kernClockrateMIB = "kern.clockrate" + +func Cptime() (map[string]uint64, error) { + var clock clockInfo + + if err := sysctlByName(kernClockrateMIB, &clock); err != nil { + return make(map[string]uint64), errors.Wrap(err, "failed to get kern.clockrate") + } + + cptime, err := syscall.Sysctl(kernCptimeMIB) + if err != nil { + return make(map[string]uint64), errors.Wrap(err, "failed to get kern.cp_time") + } + + cpMap := make(map[string]uint64) + + times := strings.Split(cptime, " ") + names := [5]string{"User", "Nice", "System", "IRQ", "Idle"} + + for index, time := range times { + i, err := strconv.ParseUint(time, 10, 64) + + if err != nil { + return cpMap, errors.Wrap(err, "error parsing kern.cp_time") + } + + cpMap[names[index]] = i * uint64(clock.Tick) * 1000 + } + + return cpMap, nil +} diff --git a/providers/freebsd/process_freebsd_test.go b/providers/freebsd/process_freebsd_test.go new file mode 100644 index 00000000..566fa001 --- /dev/null +++ b/providers/freebsd/process_freebsd_test.go @@ -0,0 +1,25 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package freebsd + +import ( + "github.com/elastic/go-sysinfo/internal/registry" +) + +var _ registry.HostProvider = freebsdSystem{} +var _ registry.ProcessProvider = freebsdSystem{} diff --git a/providers/freebsd/syscall_freebsd.go b/providers/freebsd/syscall_freebsd.go new file mode 100644 index 00000000..5a6ddb1f --- /dev/null +++ b/providers/freebsd/syscall_freebsd.go @@ -0,0 +1,84 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build freebsd,cgo + +package freebsd + +//#include +//#include +import "C" + +import ( + "bytes" + "encoding/binary" + "sync" + "unsafe" +) + +// Buffer Pool + +var bufferPool = sync.Pool{ + New: func() interface{} { + return &poolMem{ + buf: make([]byte, 512), + } + }, +} + +type poolMem struct { + buf []byte + pool *sync.Pool +} + +func getPoolMem() *poolMem { + pm := bufferPool.Get().(*poolMem) + pm.buf = pm.buf[0:cap(pm.buf)] + pm.pool = &bufferPool + return pm +} + +func (m *poolMem) Release() { m.pool.Put(m) } + +func sysctlbyname(name string, value interface{}) error { + mem := getPoolMem() + defer mem.Release() + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + size := C.ulong(len(mem.buf)) + if n, err := C.sysctlbyname(cname, unsafe.Pointer(&mem.buf[0]), &size, nil, C.ulong(0)); n != 0 { + return err + } + + data := mem.buf[0:size] + + switch v := value.(type) { + case *[]byte: + out := make([]byte, len(data)) + copy(out, data) + *v = out + return nil + default: + return binary.Read(bytes.NewReader(data), binary.LittleEndian, v) + } +} + +func sysctlByName(name string, out interface{}) error { + return sysctlbyname(name, out) +} diff --git a/providers/freebsd/ztypes_freebsd.go b/providers/freebsd/ztypes_freebsd.go new file mode 100644 index 00000000..230b857e --- /dev/null +++ b/providers/freebsd/ztypes_freebsd.go @@ -0,0 +1,40 @@ +// Code generated by cmd/cgo -godefs; and then patched up to fix +// an alignment issue +// cgo -godefs defs_freebsd.go + +package freebsd + +type vmTotal struct { + Rq int16 + Dw int16 + Pw int16 + Sl int16 + _ int16 // cgo doesn't generate the same alignment as C does + Sw int16 + Vm int32 + Avm int32 + Rm int32 + Arm int32 + Vmshr int32 + Avmshr int32 + Rmshr int32 + Armshr int32 + Free int32 +} + +type kvmSwap struct { + Devname [32]int8 + Used uint32 + Total uint32 + Flags int32 + Reserved1 uint32 + Reserved2 uint32 +} + +type clockInfo struct { + Hz int32 + Tick int32 + Spare int32 + Stathz int32 + Profhz int32 +} diff --git a/system.go b/system.go index 90f81691..f7e8f422 100644 --- a/system.go +++ b/system.go @@ -25,6 +25,7 @@ import ( // Register host and process providers. _ "github.com/elastic/go-sysinfo/providers/darwin" + _ "github.com/elastic/go-sysinfo/providers/freebsd" _ "github.com/elastic/go-sysinfo/providers/linux" _ "github.com/elastic/go-sysinfo/providers/windows" ) diff --git a/system_test.go b/system_test.go index 97a2de5d..a9970c10 100644 --- a/system_test.go +++ b/system_test.go @@ -62,6 +62,12 @@ var expectedProcessFeatures = map[string]*ProcessFeatures{ OpenHandleEnumerator: false, OpenHandleCounter: true, }, + "freebsd": &ProcessFeatures{ + ProcessInfo: true, + Environment: true, + OpenHandleEnumerator: true, + OpenHandleCounter: true, + }, } func TestProcessFeaturesMatrix(t *testing.T) { @@ -120,7 +126,18 @@ func TestSelf(t *testing.T) { if err != nil { t.Fatal(err) } - assert.EqualValues(t, wd, info.CWD) + + wdStat, err := os.Stat(wd) + if err != nil { + t.Fatal(err) + } + + cwdStat, err := os.Stat(info.CWD) + if err != nil { + t.Fatal(err) + } + + assert.EqualValues(t, os.SameFile(wdStat, cwdStat), true) exe, err := os.Executable() if err != nil {