metric/system/process/helpers.go (74 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.
package process
import (
"math"
"time"
"github.com/elastic/elastic-agent-libs/opt"
"github.com/elastic/elastic-agent-libs/transform/typeconv"
"github.com/elastic/elastic-agent-system-metrics/metric"
"github.com/elastic/elastic-agent-system-metrics/metric/system/numcpu"
)
// unixTimeMsToTime converts a unix time given in milliseconds since Unix epoch
// to a typeconv.Time value.
func unixTimeMsToTime(unixTimeMs uint64) string {
return typeconv.Time(time.Unix(0, int64(unixTimeMs*1000000))).String()
}
func stripNullByte(buf []byte) string { //nolint: deadcode,unused,nolintlint // it is used in platform specific code
return string(buf[0 : len(buf)-1])
}
func stripNullByteRaw(buf []byte) []byte { //nolint: deadcode,unused,nolintlint // it is used in platform specific code
return buf[0 : len(buf)-1]
}
// GetProcMemPercentage returns process memory usage as a percent of total memory usage
func GetProcMemPercentage(proc ProcState, totalPhyMem uint64) opt.Float {
if totalPhyMem == 0 {
return opt.NewFloatNone()
}
perc := (float64(proc.Memory.Rss.Bytes.ValueOr(0)) / float64(totalPhyMem))
return opt.FloatWith(metric.Round(perc))
}
// isProcessInSlice looks up proc in the processes slice and returns if
// found or not
func isProcessInSlice(processes []ProcState, proc *ProcState) bool {
for _, p := range processes {
if p.Pid == proc.Pid {
return true
}
}
return false
}
// GetProcCPUPercentage returns the percentage of total CPU time consumed by
// the process during the period between the given samples. Two percentages are
// returned (these must be multiplied by 100). The first is a normalized based
// on the number of cores such that the value ranges on [0, 1]. The second is
// not normalized and the value ranges on [0, number_of_cores].
//
// Implementation note: The total system CPU time (including idle) is not
// provided so this method will resort to using the difference in wall-clock
// time multiplied by the number of cores as the total amount of CPU time
// available between samples. This could result in incorrect percentages if the
// wall-clock is adjusted (prior to Go 1.9) or the machine is suspended.
func GetProcCPUPercentage(s0, s1 ProcState) ProcState {
// Skip if we're missing the total ticks
if s0.CPU.Total.Ticks.IsZero() || s1.CPU.Total.Ticks.IsZero() {
return s1
}
timeDelta := s1.SampleTime.Sub(s0.SampleTime)
timeDeltaDur := timeDelta / time.Millisecond
totalCPUDeltaMillis := int64(s1.CPU.Total.Ticks.ValueOr(0) - s0.CPU.Total.Ticks.ValueOr(0))
pct := float64(totalCPUDeltaMillis) / float64(timeDeltaDur)
// In theory this can only happen if the time delta is 0, which is unlikely but possible.
// With all the type conversion and non-integer math, this is probably the safest way to check.
if math.IsNaN(pct) {
return s1
}
normalizedPct := pct / float64(numcpu.NumCPU())
s1.CPU.Total.Norm.Pct = opt.FloatWith(metric.Round(normalizedPct))
s1.CPU.Total.Pct = opt.FloatWith(metric.Round(pct))
return s1
}
// NonFatalErr indicates an error occurred during metrics
// collection, however the metrics already
// gathered and returned are still valid.
// This error can be safely ignored, this will result
// in having partial metrics for a process rather than
// no metrics at all.
//
// It was introduced to allow for partial metrics collection
// on privileged process on Windows.
type NonFatalErr struct {
Err error
}
func (c NonFatalErr) Error() string {
if c.Err != nil {
return "non fatal error; reporting partial metrics: " + c.Err.Error()
}
return "non fatal error"
}
func (c NonFatalErr) Is(other error) bool {
_, is := other.(NonFatalErr)
return is
}
func (c NonFatalErr) Unwrap() error {
return c.Err
}
// Wraps a NonFatalError around a generic error, if given error is non-fatal in nature
func toNonFatal(err error) error {
if err == nil {
return nil
}
if !isNonFatal(err) {
return err
}
return NonFatalErr{Err: err}
}