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
}