metric/system/cgroup/cgv1/memory.go (135 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"
"fmt"
"os"
"path/filepath"
"github.com/elastic/elastic-agent-libs/opt"
"github.com/elastic/elastic-agent-system-metrics/metric/system/cgroup/cgcommon"
)
// MemorySubsystem contains the metrics and limits from the "memory" subsystem.
type MemorySubsystem 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.
Mem MemoryData `json:"mem" struct:"mem"` // Memory usage by tasks in this cgroup.
MemSwap MemoryData `json:"memsw" struct:"memsw"` // Memory plus swap usage by tasks in this cgroup.
Kernel MemoryData `json:"kmem" struct:"kmem"` // Kernel memory used by tasks in this cgroup.
KernelTCP MemoryData `json:"kmem_tcp" struct:"kmem_tcp"` // Kernel TCP buffer memory used by tasks in this cgroup.
Stats MemoryStat `json:"stats" struct:"stats"` // A wide range of memory statistics.
}
// MemoryData groups related memory usage metrics and limits.
type MemoryData struct {
Usage MemSubsystemUsage `json:"usage" struct:"usage"` // Usage in bytes.
Limit opt.Bytes `json:"limit" struct:"limit"` // Limit in bytes.
Failures uint64 `json:"failures" struct:"failures"` // Number of times the memory limit has been reached.
}
// MemSubsystemUsage groups fields used in memory.SUBSYSTEM.usage
type MemSubsystemUsage struct {
Bytes uint64 `json:"bytes" struct:"bytes"`
Max opt.Bytes `json:"max" struct:"max"`
}
// MemoryStat contains various memory statistics and accounting information
// associated with a cgroup.
type MemoryStat struct {
// Page cache, including tmpfs (shmem), in bytes.
Cache opt.Bytes `json:"cache" struct:"cache"`
// Anonymous and swap cache, not including tmpfs (shmem), in bytes.
RSS opt.Bytes `json:"rss" struct:"rss"`
// Anonymous transparent hugepages in bytes.
RSSHuge opt.Bytes `json:"rss_huge" struct:"rss_huge"`
// Size of memory-mapped mapped files, including tmpfs (shmem), in bytes.
MappedFile opt.Bytes `json:"mapped_file" struct:"mapped_file"`
// Number of pages paged into memory.
PagesIn uint64 `json:"pages_in" struct:"pages_in"`
// Number of pages paged out of memory.
PagesOut uint64 `json:"pages_out" struct:"pages_out"`
// Number of times a task in the cgroup triggered a page fault.
PageFaults uint64 `json:"page_faults" struct:"page_faults"`
// Number of times a task in the cgroup triggered a major page fault.
MajorPageFaults uint64 `json:"major_page_faults" struct:"major_page_faults"`
// Swap usage in bytes.
Swap opt.Bytes `json:"swap"`
// Anonymous and swap cache on active least-recently-used (LRU) list, including tmpfs (shmem), in bytes.
ActiveAnon opt.Bytes `json:"active_anon" struct:"active_anon"`
// Anonymous and swap cache on inactive LRU list, including tmpfs (shmem), in bytes.
InactiveAnon opt.Bytes `json:"inactive_anon" struct:"inactive_anon"`
// File-backed memory on active LRU list, in bytes.
ActiveFile opt.Bytes `json:"active_file" struct:"active_file"`
// File-backed memory on inactive LRU list, in bytes.
InactiveFile opt.Bytes `json:"inactive_file" struct:"inactive_file"`
// Memory that cannot be reclaimed, in bytes.
Unevictable opt.Bytes `json:"unevictable" struct:"unevictable"`
// Memory limit for the hierarchy that contains the memory cgroup, in bytes.
HierarchicalMemoryLimit opt.Bytes `json:"hierarchical_memory_limit" struct:"hierarchical_memory_limit"`
// Memory plus swap limit for the hierarchy that contains the memory cgroup, in bytes.
HierarchicalMemswLimit opt.Bytes `json:"hierarchical_memsw_limit" struct:"hierarchical_memsw_limit"`
}
// Get reads metrics from the "memory" subsystem. path is the filepath to the
// cgroup hierarchy to read.
func (mem *MemorySubsystem) Get(path string) error {
if err := memoryData(path, "memory", &mem.Mem); err != nil {
return fmt.Errorf("error fetching memory stats: %w", err)
}
if err := memoryData(path, "memory.memsw", &mem.MemSwap); err != nil {
return fmt.Errorf("error fetching memsw stats: %w", err)
}
if err := memoryData(path, "memory.kmem", &mem.Kernel); err != nil {
return fmt.Errorf("error fetching kmem stats: %w", err)
}
if err := memoryData(path, "memory.kmem.tcp", &mem.KernelTCP); err != nil {
return fmt.Errorf("error fetching kmem.tcp stats: %w", err)
}
if err := memoryStats(path, mem); err != nil {
return fmt.Errorf("error fetching memory.stat metrics: %w", err)
}
return nil
}
func memoryData(path, prefix string, data *MemoryData) error {
var err error
data.Usage.Bytes, err = cgcommon.ParseUintFromFile(path, prefix+".usage_in_bytes")
if err != nil {
return fmt.Errorf("error fetching usage_in_bytes: %w", err)
}
data.Usage.Max.Bytes, err = cgcommon.ParseUintFromFile(path, prefix+".max_usage_in_bytes")
if err != nil {
return fmt.Errorf("error fetching max_usage_in_bytes: %w", err)
}
data.Limit.Bytes, err = cgcommon.ParseUintFromFile(path, prefix+".limit_in_bytes")
if err != nil {
return fmt.Errorf("error fetching limit_in_bytes: %w", err)
}
data.Failures, err = cgcommon.ParseUintFromFile(path, prefix+".failcnt")
if err != nil {
return fmt.Errorf("error fetching failcnt: %w", err)
}
return nil
}
func memoryStats(path string, mem *MemorySubsystem) error {
f, err := os.Open(filepath.Join(path, "memory.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 "cache":
mem.Stats.Cache.Bytes = v
case "rss":
mem.Stats.RSS.Bytes = v
case "rss_huge":
mem.Stats.RSSHuge.Bytes = v
case "mapped_file":
mem.Stats.MappedFile.Bytes = v
case "pgpgin":
mem.Stats.PagesIn = v
case "pgpgout":
mem.Stats.PagesOut = v
case "pgfault":
mem.Stats.PageFaults = v
case "pgmajfault":
mem.Stats.MajorPageFaults = v
case "swap":
mem.Stats.Swap.Bytes = v
case "active_anon":
mem.Stats.ActiveAnon.Bytes = v
case "inactive_anon":
mem.Stats.InactiveAnon.Bytes = v
case "active_file":
mem.Stats.ActiveFile.Bytes = v
case "inactive_file":
mem.Stats.InactiveFile.Bytes = v
case "unevictable":
mem.Stats.Unevictable.Bytes = v
case "hierarchical_memory_limit":
mem.Stats.HierarchicalMemoryLimit.Bytes = v
case "hierarchical_memsw_limit":
mem.Stats.HierarchicalMemswLimit.Bytes = v
}
}
return sc.Err()
}