metric/system/process/process_common.go (150 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 darwin || freebsd || linux || windows || aix || netbsd || openbsd package process import ( "errors" "fmt" "sync" "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" "github.com/elastic/elastic-agent-libs/match" "github.com/elastic/elastic-agent-libs/transform/typeconv" "github.com/elastic/elastic-agent-system-metrics/metric/system/cgroup" "github.com/elastic/elastic-agent-system-metrics/metric/system/resolve" "github.com/elastic/go-sysinfo/types" "github.com/elastic/go-sysinfo" ) // ErrProcNotExist indicates that a process was not found. var ErrProcNotExist = errors.New("process does not exist") // ProcsMap is a convenience wrapper for the oft-used idiom of map[int]ProcState type ProcsMap map[int]ProcState // ProcsTrack is a thread-safe wrapper for a process Stat object's internal map of processes. type ProcsTrack struct { pids ProcsMap mut sync.RWMutex } func NewProcsTrack() *ProcsTrack { return &ProcsTrack{ pids: make(ProcsMap, 0), } } func (pm *ProcsTrack) GetPid(pid int) (ProcState, bool) { pm.mut.RLock() defer pm.mut.RUnlock() proc, ok := pm.pids[pid] return proc, ok } func (pm *ProcsTrack) SetPid(pid int, ps ProcState) { pm.mut.Lock() defer pm.mut.Unlock() pm.pids[pid] = ps } func (pm *ProcsTrack) SetMap(pids map[int]ProcState) { pm.mut.Lock() defer pm.mut.Unlock() pm.pids = pids } // ProcCallback is a function that FetchPid* methods can call at various points to do OS-agnostic processing type ProcCallback func(in ProcState) (ProcState, error) // CgroupPctStats stores rendered percent values from cgroup CPU data type CgroupPctStats struct { CPUTotalPct float64 CPUTotalPctNorm float64 CPUUserPct float64 CPUUserPctNorm float64 CPUSystemPct float64 CPUSystemPctNorm float64 } // Stats stores the stats of processes on the host. type Stats struct { Hostfs resolve.Resolver Procs []string ProcsMap *ProcsTrack CPUTicks bool EnvWhitelist []string CacheCmdLine bool IncludeTop IncludeTopConfig CgroupOpts cgroup.ReaderOptions EnableCgroups bool EnableNetwork bool // NetworkMetrics is an allowlist of network metrics, // the names of which can be found in /proc/PID/net/snmp and /proc/PID/net/netstat NetworkMetrics []string skipExtended bool procRegexps []match.Matcher // List of regular expressions used to whitelist processes. envRegexps []match.Matcher // List of regular expressions used to whitelist env vars. cgroups *cgroup.Reader logger *logp.Logger host types.Host excludedPIDs map[uint64]struct{} // List of PIDs to ignore while calling FillMetricsRequiringMoreAccess } // PidState are the constants for various PID states type PidState string var ( // Dead state, on linux this is both "x" and "X" Dead PidState = "dead" // Running state Running PidState = "running" // Sleeping state Sleeping PidState = "sleeping" // Idle state. Idle PidState = "idle" // DiskSleep is uninterruptible disk sleep DiskSleep PidState = "disk_sleep" // Stopped state. Stopped PidState = "stopped" // Zombie state. Zombie PidState = "zombie" // WakeKill is a linux state only found on kernels 2.6.33-3.13 WakeKill PidState = "wakekill" // Waking is a linux state only found on kernels 2.6.33-3.13 Waking PidState = "waking" // Parked is a linux state. On the proc man page, it says it's available on 3.9-3.13, but it appears to still be in the code. Parked PidState = "parked" // Unknown state Unknown PidState = "unknown" ) // PidStates is a Map of all pid states, mostly applicable to linux var PidStates = map[byte]PidState{ 'S': Sleeping, 'R': Running, 'D': DiskSleep, // Waiting in uninterruptible disk sleep, on some kernels this is marked as I below 'I': Idle, // in the scheduler, TASK_IDLE is defined as (TASK_UNINTERRUPTIBLE | TASK_NOLOAD) 'T': Stopped, 'Z': Zombie, 'X': Dead, 'x': Dead, 'K': WakeKill, 'W': Waking, 'P': Parked, } // Init initializes a Stats instance. It returns errors if the provided process regexes // cannot be compiled. func (procStats *Stats) Init() error { procStats.logger = logp.NewLogger("processes") var err error procStats.host, err = sysinfo.Host() if err != nil { procStats.host = nil procStats.logger.Warnf("Getting host details: %v", err) } // footcannon prevention if procStats.Hostfs == nil { procStats.Hostfs = resolve.NewTestResolver("/") } if procStats.EnableNetwork && len(procStats.NetworkMetrics) == 0 { procStats.logger.Warnf("Collecting all network metrics per-process; this will produce a large volume of data.") } procStats.ProcsMap = NewProcsTrack() if len(procStats.Procs) == 0 { return nil } procStats.procRegexps = []match.Matcher{} for _, pattern := range procStats.Procs { reg, err := match.Compile(pattern) if err != nil { return fmt.Errorf("failed to compile regexp [%s]: %w", pattern, err) } procStats.procRegexps = append(procStats.procRegexps, reg) } procStats.envRegexps = make([]match.Matcher, 0, len(procStats.EnvWhitelist)) for _, pattern := range procStats.EnvWhitelist { reg, err := match.Compile(pattern) if err != nil { return fmt.Errorf("failed to compile env whitelist regexp [%v]: %w", pattern, err) } procStats.envRegexps = append(procStats.envRegexps, reg) } if procStats.EnableCgroups { cgReader, err := cgroup.NewReaderOptions(procStats.CgroupOpts) if errors.Is(err, cgroup.ErrCgroupsMissing) { logp.Warn("cgroup data collection will be disabled: %v", err) procStats.EnableCgroups = false } else if err != nil { return fmt.Errorf("error initializing cgroup reader: %w", err) } procStats.cgroups = cgReader } procStats.excludedPIDs = processesToIgnore() return nil } // processRootEvent formats the process state event for the ECS root fields used by the system/process metricsets func processRootEvent(process *ProcState) mapstr.M { // Create the root event root := process.FormatForRoot() rootMap := mapstr.M{} _ = typeconv.Convert(&rootMap, root) return rootMap }