providers/darwin/process_darwin.go (186 lines of code) (raw):

// 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. //go:build amd64 || arm64 package darwin import ( "bytes" "encoding/binary" "errors" "fmt" "os" "strconv" "strings" "syscall" "time" "golang.org/x/sys/unix" "github.com/elastic/go-sysinfo/types" ) var errInvalidProcargs2Data = errors.New("invalid kern.procargs2 data") func (s darwinSystem) Processes() ([]types.Process, error) { ps, err := unix.SysctlKinfoProcSlice("kern.proc.all") if err != nil { return nil, fmt.Errorf("failed to read process table: %w", err) } processes := make([]types.Process, 0, len(ps)) for _, kp := range ps { pid := kp.Proc.P_pid if pid == 0 { continue } processes = append(processes, &process{ pid: int(pid), }) } return processes, nil } func (s darwinSystem) Process(pid int) (types.Process, error) { p := process{pid: pid} return &p, nil } func (s darwinSystem) Self() (types.Process, error) { return s.Process(os.Getpid()) } type process struct { info *types.ProcessInfo pid int cwd string exe string args []string env map[string]string } func (p *process) PID() int { return p.pid } func (p *process) Parent() (types.Process, error) { info, err := p.Info() if err != nil { return nil, err } return &process{pid: info.PPID}, nil } func (p *process) Info() (types.ProcessInfo, error) { if p.info != nil { return *p.info, nil } var task procTaskAllInfo if err := getProcTaskAllInfo(p.pid, &task); err != nil && err != types.ErrNotImplemented { return types.ProcessInfo{}, err } var vnode procVnodePathInfo if err := getProcVnodePathInfo(p.pid, &vnode); err != nil && err != types.ErrNotImplemented { return types.ProcessInfo{}, err } if err := kern_procargs(p.pid, p); err != nil { return types.ProcessInfo{}, err } p.info = &types.ProcessInfo{ Name: int8SliceToString(task.Pbsd.Pbi_name[:]), PID: p.pid, PPID: int(task.Pbsd.Pbi_ppid), CWD: int8SliceToString(vnode.Cdir.Path[:]), Exe: p.exe, Args: p.args, StartTime: time.Unix(int64(task.Pbsd.Pbi_start_tvsec), int64(task.Pbsd.Pbi_start_tvusec)*int64(time.Microsecond)), } return *p.info, nil } func (p *process) User() (types.UserInfo, error) { kproc, err := unix.SysctlKinfoProc("kern.proc.pid", p.pid) if err != nil { return types.UserInfo{}, err } egid := "" if len(kproc.Eproc.Ucred.Groups) > 0 { egid = strconv.Itoa(int(kproc.Eproc.Ucred.Groups[0])) } return types.UserInfo{ UID: strconv.Itoa(int(kproc.Eproc.Pcred.P_ruid)), EUID: strconv.Itoa(int(kproc.Eproc.Ucred.Uid)), SUID: strconv.Itoa(int(kproc.Eproc.Pcred.P_svuid)), GID: strconv.Itoa(int(kproc.Eproc.Pcred.P_rgid)), SGID: strconv.Itoa(int(kproc.Eproc.Pcred.P_svgid)), EGID: egid, }, nil } func (p *process) Environment() (map[string]string, error) { return p.env, nil } func (p *process) CPUTime() (types.CPUTimes, error) { var task procTaskAllInfo if err := getProcTaskAllInfo(p.pid, &task); err != nil { return types.CPUTimes{}, err } return types.CPUTimes{ User: time.Duration(task.Ptinfo.Total_user), System: time.Duration(task.Ptinfo.Total_system), }, nil } func (p *process) Memory() (types.MemoryInfo, error) { var task procTaskAllInfo if err := getProcTaskAllInfo(p.pid, &task); err != nil { return types.MemoryInfo{}, err } return types.MemoryInfo{ Virtual: task.Ptinfo.Virtual_size, Resident: task.Ptinfo.Resident_size, Metrics: map[string]uint64{ "page_ins": uint64(task.Ptinfo.Pageins), "page_faults": uint64(task.Ptinfo.Faults), }, }, nil } // wrapper around sysctl KERN_PROCARGS2 // callbacks params are optional, // up to the caller as to which pieces of data they want func kern_procargs(pid int, p *process) error { data, err := unix.SysctlRaw("kern.procargs2", pid) if err != nil { if errors.Is(err, syscall.EINVAL) { // sysctl returns "invalid argument" for both "no such process" // and "operation not permitted" errors. return fmt.Errorf("no such process or operation not permitted: %w", err) } return err } return parseKernProcargs2(data, p) } func parseKernProcargs2(data []byte, p *process) error { // argc if len(data) < 4 { return errInvalidProcargs2Data } argc := binary.LittleEndian.Uint32(data) data = data[4:] // exe lines := strings.Split(string(data), "\x00") p.exe = lines[0] lines = lines[1:] // Skip nulls that may be appended after the exe. for len(lines) > 0 { if lines[0] != "" { break } lines = lines[1:] } // argv if c := min(argc, uint32(len(lines))); c > 0 { p.args = lines[:c] lines = lines[c:] } // env vars env := make(map[string]string, len(lines)) for _, l := range lines { if len(l) == 0 { break } key, val, _ := strings.Cut(l, "=") env[key] = val } p.env = env return nil } func int8SliceToString(s []int8) string { buf := bytes.NewBuffer(make([]byte, len(s))) buf.Reset() for _, b := range s { if b == 0 { break } buf.WriteByte(byte(b)) } return buf.String() } func min(a, b uint32) uint32 { if a < b { return a } return b }