aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/MemoryController.java
blob: 91806b8fd61012d659e0a6bc4e3e3e7ce9bf5cf5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.cgroup;

import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * Represents a cgroup v2 memory controller, i.e. all memory.* files.
 *
 * @author hakonhall
 */
public class MemoryController {
    private final Cgroup cgroup;

    MemoryController(Cgroup cgroup) {
        this.cgroup = cgroup;
    }

    /** @return Maximum amount of memory that can be used by the cgroup and its descendants. */
    public Size readMax() {
        return cgroup.readSize("memory.max");
    }

    /** @return The total amount of memory currently being used by the cgroup and its descendants, in bytes. */
    public Size readCurrent() {
        return cgroup.readSize("memory.current");
    }

    /** @return The total amount of memory currently being used by the cgroup and its descendants, in bytes. */
    public Optional<Size> readCurrentIfExists() {
        return cgroup.readIfExists("memory.current").map(Size::from);
    }

    public Stats readStat() {
        var lines = cgroup.readLines("memory.stat");
        return new Stats(
                Size.from(readField(lines, "file")), Size.from(readField(lines, "sock")), Size.from(readField(lines, "slab")),
                Size.from(readField(lines, "slab_reclaimable")), Size.from(readField(lines, "anon")));
    }

    public Optional<Pressure> readPressureIfExists() {
        return cgroup.readIfExists("memory.pressure")
                .map(fileContent ->
                        new Pressure(
                                readPressureField(fileContent, "some"),
                                readPressureField(fileContent, "full")
                        )
                );
    }

    private static String readField(List<String> lines, String fieldName) {
        return lines.stream()
                    .map(line -> line.split("\\s+"))
                    .filter(fields -> fields.length == 2)
                    .filter(fields -> fieldName.equals(fields[0]))
                    .map(fields -> fields[1])
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("No such field: " + fieldName));
    }

    /**
     * Fetches the avg60 value from the specified type, i.e. "some" or "full".
     */
    private static Double readPressureField(String fileContent, String type) {
        var pattern = Pattern.compile(type + ".*avg60=(?<avg60>\\d+\\.\\d+).*");
        return Stream.of(fileContent.split("\n"))
                    .map(pattern::matcher)
                    .filter(Matcher::matches)
                    .map(matcher -> matcher.group("avg60"))
                    .findFirst()
                    .map(Double::parseDouble)
                    .orElseThrow(() -> new IllegalArgumentException("No such field: " + type));
    }

    /**
     * @param file Number of bytes used to cache filesystem data, including tmpfs and shared memory.
     * @param sock Amount of memory used in network transmission buffers.
     * @param slab Amount of memory used for storing in-kernel data structures.
     * @param slabReclaimable Part of "slab" that might be reclaimed, such as dentries and inodes.
     * @param anon Amount of memory used in anonymous mappings such as brk(), sbrk(), and mmap(MAP_ANONYMOUS).
     */
    public record Stats(Size file, Size sock, Size slab, Size slabReclaimable, Size anon) {}

    /**
     * @param some The avg60 value of the "some" pressure level.
     * @param full The avg60 value of the "full" pressure level.
     */
    public record Pressure(double some, double full) {}
}