metric/system/cgroup/cgv1/cpuacct.go (92 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 cgv1 import ( "bufio" "bytes" "fmt" "os" "path/filepath" "time" "github.com/elastic/elastic-agent-system-metrics/metric/system/cgroup/cgcommon" "github.com/elastic/gosigar/sys/linux" ) var clockTicks = uint64(linux.GetClockTicks()) // CPUAccountingSubsystem contains metrics from the "cpuacct" subsystem. // Note that percentage values are not taken from cgroup metrics, but derived via FillPercentages() type CPUAccountingSubsystem struct { ID string `json:"id,omitempty"` // ID of the cgroup. Path string `json:"path,omitempty"` // Path to the cgroup relative to the cgroup subsystem's mountpoint. Total cgcommon.CPUUsage `json:"total_nanos"` UsagePerCPU map[string]uint64 `json:"percpu" struct:"percpu"` // CPU time statistics for tasks in this cgroup. Stats CPUAccountingStats `json:"stats,omitempty"` } // CPUAccountingStats contains the stats reported from the cpuacct subsystem. type CPUAccountingStats struct { User cgcommon.CPUUsage `json:"user" struct:"user"` System cgcommon.CPUUsage `json:"system" struct:"system"` } // Get reads metrics from the "cpuacct" subsystem. path is the filepath to the // cgroup hierarchy to read. func (cpuacct *CPUAccountingSubsystem) Get(path string) error { cpuacct.UsagePerCPU = make(map[string]uint64) if err := cpuacctStat(path, cpuacct); err != nil { return fmt.Errorf("error fetching cpuacct stats: %w", err) } if err := cpuacctUsage(path, cpuacct); err != nil { return fmt.Errorf("error fetching cpuacct usage: %w", err) } if err := cpuacctUsagePerCPU(path, cpuacct); err != nil { return fmt.Errorf("error fetching per_cpu data: %w", err) } return nil } func cpuacctStat(path string, cpuacct *CPUAccountingSubsystem) error { f, err := os.Open(filepath.Join(path, "cpuacct.stat")) if err != nil { if os.IsNotExist(err) { return nil } return err } defer f.Close() sc := bufio.NewScanner(f) for sc.Scan() { t, v, err := cgcommon.ParseCgroupParamKeyValue(sc.Text()) if err != nil { return err } switch t { case "user": cpuacct.Stats.User.NS = convertJiffiesToNanos(v) case "system": cpuacct.Stats.System.NS = convertJiffiesToNanos(v) } } return sc.Err() } func cpuacctUsage(path string, cpuacct *CPUAccountingSubsystem) error { var err error cpuacct.Total.NS, err = cgcommon.ParseUintFromFile(path, "cpuacct.usage") if err != nil { return err } return nil } func cpuacctUsagePerCPU(path string, cpuacct *CPUAccountingSubsystem) error { contents, err := os.ReadFile(filepath.Join(path, "cpuacct.usage_percpu")) if err != nil { if os.IsNotExist(err) { return nil } return err } values := make(map[string]uint64) usages := bytes.Fields(contents) for cpu, usage := range usages { value, err := cgcommon.ParseUint(usage) if err != nil { return err } // For backwards compatibility, We start the CPU count at 1 cpuStr := fmt.Sprintf("%d", cpu+1) values[cpuStr] = value } cpuacct.UsagePerCPU = values return nil } func convertJiffiesToNanos(j uint64) uint64 { return (j * uint64(time.Second)) / clockTicks }