mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
dbb9fc03ae
Only some libcontainer packages can be built on non-linux platforms
(not that it make sense, but at least go build succeeds). Let's call
these "good" packages.
For all other packages (i.e. ones that fail to build with GOOS other
than linux), it does not make sense to have linux build tag (as they
are broken already, and thus are not and can not be used on anything
other than Linux).
Remove linux build tag for all non-"good" packages.
This was mostly done by the following script, with just a few manual
fixes on top.
function list_good_pkgs() {
for pkg in $(find . -type d -print); do
GOOS=freebsd go build $pkg 2>/dev/null \
&& GOOS=solaris go build $pkg 2>/dev/null \
&& echo $pkg
done | sed -e 's|^./||' | tr '\n' '|' | sed -e 's/|$//'
}
function remove_tag() {
sed -i -e '\|^// +build linux$|d' $1
go fmt $1
}
SKIP="^("$(list_good_pkgs)")"
for f in $(git ls-files . | grep .go$); do
if echo $f | grep -qE "$SKIP"; then
echo skip $f
continue
fi
echo proc $f
remove_tag $f
done
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
217 lines
5.2 KiB
Go
217 lines
5.2 KiB
Go
package fs2
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
)
|
|
|
|
// numToStr converts an int64 value to a string for writing to a
|
|
// cgroupv2 files with .min, .max, .low, or .high suffix.
|
|
// The value of -1 is converted to "max" for cgroupv1 compatibility
|
|
// (which used to write -1 to remove the limit).
|
|
func numToStr(value int64) (ret string) {
|
|
switch {
|
|
case value == 0:
|
|
ret = ""
|
|
case value == -1:
|
|
ret = "max"
|
|
default:
|
|
ret = strconv.FormatInt(value, 10)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func isMemorySet(r *configs.Resources) bool {
|
|
return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
|
|
}
|
|
|
|
func setMemory(dirPath string, r *configs.Resources) error {
|
|
if !isMemorySet(r) {
|
|
return nil
|
|
}
|
|
swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
swapStr := numToStr(swap)
|
|
if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
|
|
// memory and memorySwap set to the same value -- disable swap
|
|
swapStr = "0"
|
|
}
|
|
// never write empty string to `memory.swap.max`, it means set to 0.
|
|
if swapStr != "" {
|
|
if err := cgroups.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if val := numToStr(r.Memory); val != "" {
|
|
if err := cgroups.WriteFile(dirPath, "memory.max", val); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// cgroup.Resources.KernelMemory is ignored
|
|
|
|
if val := numToStr(r.MemoryReservation); val != "" {
|
|
if err := cgroups.WriteFile(dirPath, "memory.low", val); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func statMemory(dirPath string, stats *cgroups.Stats) error {
|
|
const file = "memory.stat"
|
|
statsFile, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer statsFile.Close()
|
|
|
|
sc := bufio.NewScanner(statsFile)
|
|
for sc.Scan() {
|
|
t, v, err := fscommon.ParseKeyValue(sc.Text())
|
|
if err != nil {
|
|
return &parseError{Path: dirPath, File: file, Err: err}
|
|
}
|
|
stats.MemoryStats.Stats[t] = v
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return &parseError{Path: dirPath, File: file, Err: err}
|
|
}
|
|
stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
|
|
// Unlike cgroup v1 which has memory.use_hierarchy binary knob,
|
|
// cgroup v2 is always hierarchical.
|
|
stats.MemoryStats.UseHierarchy = true
|
|
|
|
memoryUsage, err := getMemoryDataV2(dirPath, "")
|
|
if err != nil {
|
|
if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
|
|
// The root cgroup does not have memory.{current,max}
|
|
// so emulate those using data from /proc/meminfo.
|
|
return statsFromMeminfo(stats)
|
|
}
|
|
return err
|
|
}
|
|
stats.MemoryStats.Usage = memoryUsage
|
|
swapUsage, err := getMemoryDataV2(dirPath, "swap")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// As cgroup v1 reports SwapUsage values as mem+swap combined,
|
|
// while in cgroup v2 swap values do not include memory,
|
|
// report combined mem+swap for v1 compatibility.
|
|
swapUsage.Usage += memoryUsage.Usage
|
|
if swapUsage.Limit != math.MaxUint64 {
|
|
swapUsage.Limit += memoryUsage.Limit
|
|
}
|
|
stats.MemoryStats.SwapUsage = swapUsage
|
|
|
|
return nil
|
|
}
|
|
|
|
func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
|
|
memoryData := cgroups.MemoryData{}
|
|
|
|
moduleName := "memory"
|
|
if name != "" {
|
|
moduleName = "memory." + name
|
|
}
|
|
usage := moduleName + ".current"
|
|
limit := moduleName + ".max"
|
|
|
|
value, err := fscommon.GetCgroupParamUint(path, usage)
|
|
if err != nil {
|
|
if name != "" && os.IsNotExist(err) {
|
|
// Ignore EEXIST as there's no swap accounting
|
|
// if kernel CONFIG_MEMCG_SWAP is not set or
|
|
// swapaccount=0 kernel boot parameter is given.
|
|
return cgroups.MemoryData{}, nil
|
|
}
|
|
return cgroups.MemoryData{}, err
|
|
}
|
|
memoryData.Usage = value
|
|
|
|
value, err = fscommon.GetCgroupParamUint(path, limit)
|
|
if err != nil {
|
|
return cgroups.MemoryData{}, err
|
|
}
|
|
memoryData.Limit = value
|
|
|
|
return memoryData, nil
|
|
}
|
|
|
|
func statsFromMeminfo(stats *cgroups.Stats) error {
|
|
const file = "/proc/meminfo"
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
// Fields we are interested in.
|
|
var (
|
|
swap_free uint64
|
|
swap_total uint64
|
|
main_total uint64
|
|
main_free uint64
|
|
)
|
|
mem := map[string]*uint64{
|
|
"SwapFree": &swap_free,
|
|
"SwapTotal": &swap_total,
|
|
"MemTotal": &main_total,
|
|
"MemFree": &main_free,
|
|
}
|
|
|
|
found := 0
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
parts := strings.SplitN(sc.Text(), ":", 3)
|
|
if len(parts) != 2 {
|
|
// Should not happen.
|
|
continue
|
|
}
|
|
k := parts[0]
|
|
p, ok := mem[k]
|
|
if !ok {
|
|
// Unknown field -- not interested.
|
|
continue
|
|
}
|
|
vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
|
|
*p, err = strconv.ParseUint(vStr, 10, 64)
|
|
if err != nil {
|
|
return &parseError{File: file, Err: errors.New("bad value for " + k)}
|
|
}
|
|
|
|
found++
|
|
if found == len(mem) {
|
|
// Got everything we need -- skip the rest.
|
|
break
|
|
}
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return &parseError{Path: "", File: file, Err: err}
|
|
}
|
|
|
|
stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
|
|
stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
|
|
|
|
stats.MemoryStats.Usage.Usage = (main_total - main_free) * 1024
|
|
stats.MemoryStats.Usage.Limit = math.MaxUint64
|
|
|
|
return nil
|
|
}
|