mirror of
https://github.com/siderolabs/talos.git
synced 2025-12-12 04:51:35 +01:00
The new command `talosctl cgroups` fetches cgroups snapshot from the machine, parses it fully, enhances with additional information (e.g. resolves pod names), and presents a customizable view of cgroups configuration (e.g. limits) and current consumption. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
266 lines
5.2 KiB
Go
266 lines
5.2 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package cgroups
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
)
|
|
|
|
// Value represents a cgroup value.
|
|
//
|
|
// Value might represent 'max' value.
|
|
type Value struct {
|
|
Val int64
|
|
Frac int
|
|
IsMax bool
|
|
IsSet bool
|
|
}
|
|
|
|
// String returns the string representation of the cgroup value.
|
|
func (v Value) String() string {
|
|
switch {
|
|
case !v.IsSet:
|
|
return "unset"
|
|
case v.IsMax:
|
|
return "max"
|
|
default:
|
|
s := strconv.FormatInt(v.Val, 10)
|
|
|
|
if v.Frac == 0 {
|
|
return s
|
|
}
|
|
|
|
if len(s) < v.Frac+1 {
|
|
s = strings.Repeat("0", (v.Frac+1)-len(s)) + s
|
|
}
|
|
|
|
return s[:len(s)-v.Frac] + "." + s[len(s)-v.Frac:]
|
|
}
|
|
}
|
|
|
|
// HumanizeIBytes returns the humanized bytes representation of the cgroup value.
|
|
func (v Value) HumanizeIBytes() string {
|
|
if !v.IsSet || v.IsMax || v.Frac > 0 {
|
|
return v.String()
|
|
}
|
|
|
|
return humanize.IBytes(uint64(v.Val))
|
|
}
|
|
|
|
// DivideBy returns the value divided by another value in percentage.
|
|
//
|
|
// a.DivideBy(b) = a / b * 100.
|
|
func (v Value) DivideBy(other Value) Value {
|
|
switch {
|
|
case !v.IsSet || !other.IsSet:
|
|
// if either value is unset, return unset
|
|
return Value{}
|
|
case other.IsMax && !v.IsMax:
|
|
// if other is max and v is not, return 0.00%
|
|
return Value{IsSet: true, Frac: 2}
|
|
case v.IsMax && other.IsMax:
|
|
// if both are max, return 100.00%
|
|
return Value{Val: 10000, IsSet: true, Frac: 2}
|
|
case other.Val == 0 || v.IsMax:
|
|
// if other is 0, return max
|
|
return Value{IsMax: true, IsSet: true}
|
|
default:
|
|
return Value{Val: int64(math.Round(float64(v.Val) / float64(other.Val) * 100 * 100)), IsSet: true, Frac: 2}
|
|
}
|
|
}
|
|
|
|
// UsecToDuration returns the duration representation of the cgroup value in microseconds.
|
|
func (v Value) UsecToDuration() string {
|
|
if !v.IsSet || v.IsMax {
|
|
return v.String()
|
|
}
|
|
|
|
return (time.Duration(v.Val) * time.Microsecond).String()
|
|
}
|
|
|
|
// Values represents a list of cgroup values.
|
|
type Values []Value
|
|
|
|
// FlatMap returns the flat map of the cgroup values.
|
|
type FlatMap map[string]Value
|
|
|
|
// NestedKeyed returns the nested keyed map of the cgroup values.
|
|
type NestedKeyed map[string]FlatMap
|
|
|
|
// ParseValue parses the cgroup value from the string.
|
|
func ParseValue(s string) (Value, error) {
|
|
if s == "max" {
|
|
return Value{IsMax: true, IsSet: true}, nil
|
|
}
|
|
|
|
var frac int
|
|
|
|
l, r, ok := strings.Cut(s, ".")
|
|
if ok {
|
|
frac = len(r)
|
|
|
|
s = l + r
|
|
}
|
|
|
|
val, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return Value{}, err
|
|
}
|
|
|
|
return Value{Val: val, Frac: frac, IsSet: true}, nil
|
|
}
|
|
|
|
// ParseNewlineSeparatedValues parses the cgroup values from the newline separated string.
|
|
//
|
|
// New-line separated values
|
|
// (when only one value can be written at once)
|
|
//
|
|
// VAL0\n
|
|
// VAL1\n
|
|
// ...
|
|
func ParseNewlineSeparatedValues(r io.Reader) (Values, error) {
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
var values Values
|
|
|
|
for scanner.Scan() {
|
|
val, err := ParseValue(scanner.Text())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
values = append(values, val)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return values, nil
|
|
}
|
|
|
|
// ParseSpaceSeparatedValues parses the cgroup values from the space separated string.
|
|
//
|
|
// Space separated values
|
|
// (when read-only or multiple values can be written at once)
|
|
//
|
|
// VAL0 VAL1 ...\n.
|
|
func ParseSpaceSeparatedValues(r io.Reader) (Values, error) {
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
if !scanner.Scan() {
|
|
return nil, nil
|
|
}
|
|
|
|
line := scanner.Text()
|
|
parts := strings.Fields(line)
|
|
|
|
values := make(Values, 0, len(parts))
|
|
|
|
for _, s := range parts {
|
|
val, err := ParseValue(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
values = append(values, val)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return values, nil
|
|
}
|
|
|
|
// ParseFlatMapValues parses the cgroup values from the flat map.
|
|
//
|
|
// Flat keyed:
|
|
//
|
|
// KEY0 VAL0\n
|
|
// KEY1 VAL1\n
|
|
// ...
|
|
func ParseFlatMapValues(r io.Reader) (FlatMap, error) {
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
flatMap := FlatMap{}
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
key, value, ok := strings.Cut(line, " ")
|
|
if !ok {
|
|
return nil, errors.New("invalid format")
|
|
}
|
|
|
|
val, err := ParseValue(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flatMap[key] = val
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return flatMap, nil
|
|
}
|
|
|
|
// ParseNestedKeyedValues parses the cgroup values from the nested keyed map.
|
|
//
|
|
// Nested keyed:
|
|
//
|
|
// KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
|
|
// KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
|
|
// ...
|
|
func ParseNestedKeyedValues(r io.Reader) (NestedKeyed, error) {
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
nestedKeyed := NestedKeyed{}
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
key, values, ok := strings.Cut(line, " ")
|
|
if !ok {
|
|
return nil, errors.New("invalid format")
|
|
}
|
|
|
|
flatMap := FlatMap{}
|
|
|
|
for _, pair := range strings.Fields(values) {
|
|
subKey, value, ok := strings.Cut(pair, "=")
|
|
if !ok {
|
|
return nil, errors.New("invalid format")
|
|
}
|
|
|
|
val, err := ParseValue(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flatMap[subKey] = val
|
|
}
|
|
|
|
nestedKeyed[key] = flatMap
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nestedKeyed, nil
|
|
}
|