Skip to content

Commit

Permalink
add the number of threads on the information collected per process (#100
Browse files Browse the repository at this point in the history
)

* On linux:
  -  it extends the information parsed from /proc/[PID]/stat to get the total thread count
  -  unit tests added, they mock /proc/[PID]/stat
* On darwin:
  - it uses the syscall proc_pidinfo
  - unit test only validates GetInfoForPid does not return an error and the total thread count ins't zero
* On windows:
  - it uses the Process Snapshotting APIs: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/proc_snap/overview-of-process-snapshotting
  - unit test only validates GetInfoForPid does not return an error and the total thread count ins't
* Add a unit test using a C++ program which runs exactly 42 threads so we can assert the data read by process.GetInfoForPid
  On windows the and newer the process start with more threads than the program requests to parallel load needed tabels/dll or some windows specific things. Therefore the test on windows accepts 42 or 45 for the number of threads allocated to the process. See the following for details https://stackoverflow.com/questions/42789199/why-there-are-three-unexpected-worker-threads-when-a-win32-console-application-s/42789684#42789684
  • Loading branch information
AndersonQ authored Oct 2, 2023
1 parent ecc516a commit 8630a5d
Show file tree
Hide file tree
Showing 19 changed files with 657 additions and 172 deletions.
60 changes: 57 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,69 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fix thread safety in process code #43
- Fix process package build on AIX #54
- Ensure correct devID width in cgv2 #74
## [0.7.0]

### Added

- Collect the number of threads as part of the information collected per process
on `metric/system/process.GetInfoForPid`.

### Fixed

## [0.6.1]

### Changed

-Bump up go-sysinfo dependency version #86

## [0.6.0]

### Added

- Add user data to monitoring setup #80

### Changed
- Update host functions with `go-sysinfo` API changes #81
- Move SetupInfoUserMetrics out of a cgo build constraint #82
- Bumping up version for go-sysinfo dependency #84

### Fixed

-Ensure correct devID width in cgv2 #74

## [0.4.6]

### Fixed

- Fix type issues for MIPS platforms
- metric/system/cgroup/cgv2: ensure Rdev is correct width

## [0.4.5]

### Added

- add network data to process metrics
- add Process State API

### Changed
- Go version 1.18.4
- move exported helpers to _common.go file

### Removed

- remove hostfs checks

### Fixed

- fix(process): Typo in getargs error message
- fix(process): Make process package buildable on AIX

## [0.4.4]

### Fixed

- Fix thread safety in process code #43

## [0.4.3]

## Fixed
Expand Down
43 changes: 6 additions & 37 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -645,11 +645,11 @@ Contents of probable licence file $GOMODCACHE/github.com/elastic/go-structform@v

--------------------------------------------------------------------------------
Dependency : github.com/elastic/go-sysinfo
Version: v1.10.0
Version: v1.10.1
Licence type (autodetected): Apache-2.0
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/github.com/elastic/[email protected].0/LICENSE.txt:
Contents of probable licence file $GOMODCACHE/github.com/elastic/[email protected].1/LICENSE.txt:


Apache License
Expand Down Expand Up @@ -1935,11 +1935,11 @@ Contents of probable licence file $GOMODCACHE/go.elastic.co/go-licence-detector@

--------------------------------------------------------------------------------
Dependency : golang.org/x/sys
Version: v0.6.0
Version: v0.7.0
Licence type (autodetected): BSD-3-Clause
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/golang.org/x/sys@v0.6.0/LICENSE:
Contents of probable licence file $GOMODCACHE/golang.org/x/sys@v0.7.0/LICENSE:

Copyright (c) 2009 The Go Authors. All rights reserved.

Expand Down Expand Up @@ -2379,11 +2379,11 @@ Apache License

--------------------------------------------------------------------------------
Dependency : github.com/docker/docker
Version: v20.10.24+incompatible
Version: v23.0.3+incompatible
Licence type (autodetected): Apache-2.0
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/github.com/docker/docker@v20.10.24+incompatible/LICENSE:
Contents of probable licence file $GOMODCACHE/github.com/docker/docker@v23.0.3+incompatible/LICENSE:


Apache License
Expand Down Expand Up @@ -5903,37 +5903,6 @@ DEALINGS IN THE SOFTWARE.



--------------------------------------------------------------------------------
Dependency : github.com/sirupsen/logrus
Version: v1.9.0
Licence type (autodetected): MIT
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/github.com/sirupsen/[email protected]/LICENSE:

The MIT License (MIT)

Copyright (c) 2014 Simon Eskildsen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


--------------------------------------------------------------------------------
Dependency : github.com/spf13/cobra
Version: v1.3.0
Expand Down
20 changes: 9 additions & 11 deletions metric/system/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
// under the License.

//go:build (darwin && cgo) || freebsd || linux || windows || aix
// +build darwin,cgo freebsd linux windows aix

package process

Expand Down Expand Up @@ -74,7 +73,7 @@ func GetPIDState(hostfs resolve.Resolver, pid int) (PidState, error) {
if !exists {
return "", ProcNotExist
}
//GetInfoForPid will return the smallest possible dataset for a PID
// GetInfoForPid will return the smallest possible dataset for a PID
procState, err := GetInfoForPid(hostfs, pid)
if err != nil {
return "", fmt.Errorf("error getting state info for pid %d: %w", pid, err)
Expand All @@ -85,7 +84,7 @@ func GetPIDState(hostfs resolve.Resolver, pid int) (PidState, error) {

// Get fetches the configured processes and returns a list of formatted events and root ECS fields
func (procStats *Stats) Get() ([]mapstr.M, []mapstr.M, error) {
//If the user hasn't configured any kind of process glob, return
// If the user hasn't configured any kind of process glob, return
if len(procStats.Procs) == 0 {
return nil, nil, nil
}
Expand All @@ -112,18 +111,17 @@ func (procStats *Stats) Get() ([]mapstr.M, []mapstr.M, error) {
} else {
totalPhyMem = memStats.Total
}

}

//Format the list to the MapStr type used by the outputs
procs := []mapstr.M{}
rootEvents := []mapstr.M{}
// Format the list to the MapStr type used by the outputs
var procs []mapstr.M
var rootEvents []mapstr.M

for _, process := range plist {
process := process
// Add the RSS pct memory first
process.Memory.Rss.Pct = GetProcMemPercentage(process, totalPhyMem)
//Create the root event
// Create the root event
root := process.FormatForRoot()
rootMap := mapstr.M{}
_ = typeconv.Convert(&rootMap, root)
Expand Down Expand Up @@ -207,7 +205,7 @@ func (procStats *Stats) pidFill(pid int, filter bool) (ProcState, bool, error) {
}
}

//If we've passed the filter, continue to fill out the rest of the metrics
// If we've passed the filter, continue to fill out the rest of the metrics
status, err = FillPidMetrics(procStats.Hostfs, pid, status, procStats.isWhitelistedEnvVar)
if err != nil {
return status, true, fmt.Errorf("FillPidMetrics: %w", err)
Expand All @@ -216,7 +214,7 @@ func (procStats *Stats) pidFill(pid int, filter bool) (ProcState, bool, error) {
status.Cmdline = strings.Join(status.Args, " ")
}

//postprocess with cgroups and percentages
// postprocess with cgroups and percentages
last, ok := procStats.ProcsMap.GetPid(status.Pid.ValueOr(0))
status.SampleTime = time.Now()
if procStats.EnableCgroups {
Expand Down Expand Up @@ -343,7 +341,7 @@ func (procStats *Stats) includeTopProcesses(processes []ProcState) []ProcState {

// isWhitelistedEnvVar returns true if the given variable name is a match for
// the whitelist. If the whitelist is empty it returns false.
func (procStats Stats) isWhitelistedEnvVar(varName string) bool {
func (procStats *Stats) isWhitelistedEnvVar(varName string) bool {
if len(procStats.envRegexps) == 0 {
return false
}
Expand Down
31 changes: 15 additions & 16 deletions metric/system/process/process_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
// under the License.

//go:build darwin || freebsd || linux || windows || aix || netbsd || openbsd
// +build darwin freebsd linux windows aix netbsd openbsd

package process

Expand All @@ -31,13 +30,13 @@ import (
"github.com/elastic/elastic-agent-system-metrics/metric/system/resolve"
"github.com/elastic/go-sysinfo/types"

sysinfo "github.com/elastic/go-sysinfo"
"github.com/elastic/go-sysinfo"
)

// ProcNotExist indicates that a process was not found.
var ProcNotExist = errors.New("process does not exist")

//ProcsMap is a convinence wrapper for the oft-used ideom of map[int]ProcState
// 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.
Expand Down Expand Up @@ -109,31 +108,31 @@ type Stats struct {
host types.Host
}

//PidState are the constants for various PID states
// PidState are the constants for various PID states
type PidState string

var (
//Dead state, on linux this is both "x" and "X"
// Dead state, on linux this is both "x" and "X"
Dead PidState = "dead"
//Running state
// Running state
Running PidState = "running"
//Sleeping state
// Sleeping state
Sleeping PidState = "sleeping"
//Idle state.
// Idle state.
Idle PidState = "idle"
//DiskSleep is uninterruptible disk sleep
// DiskSleep is uninterruptible disk sleep
DiskSleep PidState = "disk_sleep"
//Stopped state.
// Stopped state.
Stopped PidState = "stopped"
//Zombie state.
// Zombie state.
Zombie PidState = "zombie"
//WakeKill is a linux state only found on kernels 2.6.33-3.13
// 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 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 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 state
Unknown PidState = "unknown"
)

Expand Down Expand Up @@ -163,7 +162,7 @@ func (procStats *Stats) Init() error {
procStats.logger.Warnf("Getting host details: %v", err)
}

//footcannon prevention
// footcannon prevention
if procStats.Hostfs == nil {
procStats.Hostfs = resolve.NewTestResolver("/")
}
Expand Down
25 changes: 10 additions & 15 deletions metric/system/process/process_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,15 @@ func (procStats *Stats) FetchPids() (ProcsMap, []ProcState, error) {
func GetInfoForPid(_ resolve.Resolver, pid int) (ProcState, error) {
info := C.struct_proc_taskallinfo{}

err := taskInfo(pid, &info)
if err != nil {
return ProcState{}, fmt.Errorf("could not read task for pid %d", pid)
size := C.int(unsafe.Sizeof(info))
ptr := unsafe.Pointer(&info)

// For docs, see the link below. Check the `proc_taskallinfo` struct, which
// is a composition of `proc_bsdinfo` and `proc_taskinfo`.
// https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/sys/proc_info.h.auto.html
n := C.proc_pidinfo(C.int(pid), C.PROC_PIDTASKALLINFO, 0, ptr, size)
if n != size {
return ProcState{}, fmt.Errorf("could not read process info for pid %d: proc_pidinfo returned %d", int(n), pid)
}

status := ProcState{}
Expand All @@ -112,6 +118,7 @@ func GetInfoForPid(_ resolve.Resolver, pid int) (ProcState, error) {
status.Ppid = opt.IntWith(int(info.pbsd.pbi_ppid))
status.Pid = opt.IntWith(pid)
status.Pgid = opt.IntWith(int(info.pbsd.pbi_pgid))
status.NumThreads = opt.IntWith(int(info.ptinfo.pti_threadnum))

// Get process username. Fallback to UID if username is not available.
uid := strconv.Itoa(int(info.pbsd.pbi_uid))
Expand Down Expand Up @@ -224,18 +231,6 @@ func getProcArgs(pid int, filter func(string) bool) ([]string, string, mapstr.M,
return argv, exeName, envVars, nil
}

func taskInfo(pid int, info *C.struct_proc_taskallinfo) error {
size := C.int(unsafe.Sizeof(*info))
ptr := unsafe.Pointer(info)

n := C.proc_pidinfo(C.int(pid), C.PROC_PIDTASKALLINFO, 0, ptr, size)
if n != size {
return fmt.Errorf("could not read process info for pid %d", pid)
}

return nil
}

func sysctl(mib []C.int, old *byte, oldlen *uintptr,
new *byte, newlen uintptr) (err error) {
p0 := unsafe.Pointer(&mib[0])
Expand Down
Loading

0 comments on commit 8630a5d

Please sign in to comment.