aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupTest.java
blob: d3982af14e47ec75d95f53a7cba2ed7b6cca34df (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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright Vespa.ai. 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 com.yahoo.vespa.hosted.node.admin.container.ContainerId;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
import com.yahoo.vespa.test.file.TestFileSystem;
import org.junit.jupiter.api.Test;

import java.nio.file.FileSystem;
import java.util.Map;
import java.util.Optional;

import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.StatField.SYSTEM_USAGE_USEC;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.StatField.THROTTLED_PERIODS;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.StatField.THROTTLED_TIME_USEC;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.StatField.TOTAL_PERIODS;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.StatField.TOTAL_USAGE_USEC;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.StatField.USER_USAGE_USEC;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.sharesToWeight;
import static com.yahoo.vespa.hosted.node.admin.cgroup.CpuController.weightToShares;
import static com.yahoo.vespa.hosted.node.admin.cgroup.IoController.Device;
import static com.yahoo.vespa.hosted.node.admin.cgroup.IoController.Max;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author freva
 */
public class CgroupTest {

    private static final ContainerId containerId = new ContainerId("4aec78cc");

    private final FileSystem fileSystem = TestFileSystem.create();
    private final Cgroup containerCgroup = Cgroup.root(fileSystem).resolveContainer(containerId);
    private final CpuController containerCpu = containerCgroup.cpu();
    private final NodeAgentContext context = NodeAgentContextImpl.builder("node123.yahoo.com").fileSystem(fileSystem).build();
    private final UnixPath cgroupRoot = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/machine.slice/libpod-4aec78cc.scope/container")).createDirectories();

    @Test
    public void updates_cpu_quota_and_period() {
        assertEquals(Optional.empty(), containerCgroup.cpu().readMax());

        cgroupRoot.resolve("cpu.max").writeUtf8File("max 100000\n");
        assertEquals(Optional.of(new CpuController.Max(Size.max(), 100000)), containerCpu.readMax());

        cgroupRoot.resolve("cpu.max").writeUtf8File("456 123456\n");
        assertEquals(Optional.of(new CpuController.Max(Size.from(456), 123456)), containerCpu.readMax());

        containerCgroup.cpu().updateMax(context, 456, 123456);

        assertTrue(containerCgroup.cpu().updateMax(context, 654, 123456));
        assertEquals(Optional.of(new CpuController.Max(Size.from(654), 123456)), containerCpu.readMax());
        assertEquals("654 123456\n", cgroupRoot.resolve("cpu.max").readUtf8File());

        assertTrue(containerCgroup.cpu().updateMax(context, -1, 123456));
        assertEquals(Optional.of(new CpuController.Max(Size.max(), 123456)), containerCpu.readMax());
        assertEquals("max 123456\n", cgroupRoot.resolve("cpu.max").readUtf8File());
    }

    @Test
    public void updates_cpu_shares() {
        assertEquals(Optional.empty(), containerCgroup.cpu().readShares());

        cgroupRoot.resolve("cpu.weight").writeUtf8File("1\n");
        assertEquals(Optional.of(2), containerCgroup.cpu().readShares());

        assertFalse(containerCgroup.cpu().updateShares(context, 2));

        assertTrue(containerCgroup.cpu().updateShares(context, 12345));
        assertEquals(Optional.of(12323), containerCgroup.cpu().readShares());
    }

    @Test
    public void reads_cpu_stats() {
        cgroupRoot.resolve("cpu.stat").writeUtf8File("""
                usage_usec 17794243
                user_usec 16099205
                system_usec 1695038
                nr_periods 12465
                nr_throttled 25
                throttled_usec 14256
                """);

        assertEquals(Map.of(TOTAL_USAGE_USEC, 17794243L, USER_USAGE_USEC, 16099205L, SYSTEM_USAGE_USEC, 1695038L,
                TOTAL_PERIODS, 12465L, THROTTLED_PERIODS, 25L, THROTTLED_TIME_USEC, 14256L), containerCgroup.cpu().readStats());
    }

    @Test
    public void reads_memory_metrics() {
        cgroupRoot.resolve("memory.current").writeUtf8File("2525093888\n");
        assertEquals(2525093888L, containerCgroup.memory().readCurrent().value());

        cgroupRoot.resolve("memory.max").writeUtf8File("4322885632\n");
        assertEquals(4322885632L, containerCgroup.memory().readMax().value());

        cgroupRoot.resolve("memory.stat").writeUtf8File("""
                anon 3481600
                file 69206016
                kernel_stack 73728
                slab 3552304
                percpu 262336
                sock 73728
                shmem 8380416
                file_mapped 1081344
                file_dirty 135168
                slab_reclaimable 1424320
                """);
        var stats = containerCgroup.memory().readStat();
        assertEquals(69206016L, stats.file().value());
        assertEquals(3481600L, stats.anon().value());
        assertEquals(3552304L, stats.slab().value());
        assertEquals(73728L, stats.sock().value());
        assertEquals(1424320L, stats.slabReclaimable().value());
    }

    @Test
    public void shares_to_weight_and_back_is_stable() {
        for (int i = 2; i <= 262144; i++) {
            int originalShares = i; // Must be effectively final to use in lambda :(
            int roundTripShares = weightToShares(sharesToWeight(i));
            int diff = i - roundTripShares;
            assertTrue(diff >= 0 && diff <= 27, // ~26.2 shares / weight
                    () -> "Original shares: " + originalShares + ", round trip shares: " + roundTripShares + ", diff: " + diff);
        }
    }

    @Test
    void reads_io_max() {
        assertEquals(Optional.empty(), containerCgroup.io().readMax());

        cgroupRoot.resolve("io.max").writeUtf8File("");
        assertEquals(Optional.of(Map.of()), containerCgroup.io().readMax());

        cgroupRoot.resolve("io.max").writeUtf8File("""
                253:1 rbps=11 wbps=max riops=22 wiops=33
                253:0 rbps=max wbps=44 riops=max wiops=55
                """);
        assertEquals(Map.of(new Device(253, 1), new Max(Size.from(11), Size.max(), Size.from(22), Size.from(33)),
                            new Device(253, 0), new Max(Size.max(), Size.from(44), Size.max(), Size.from(55))),
                     containerCgroup.io().readMax().orElseThrow());
    }

    @Test
    void writes_io_max() {
        Device device = new Device(253, 0);
        Max initial = new Max(Size.max(), Size.from(44), Size.max(), Size.from(55));
        assertTrue(containerCgroup.io().updateMax(context, device, initial));
        assertEquals("253:0 rbps=max wbps=44 riops=max wiops=55\n", cgroupRoot.resolve("io.max").readUtf8File());

        cgroupRoot.resolve("io.max").writeUtf8File("""
                253:1 rbps=11 wbps=max riops=22 wiops=33
                253:0 rbps=max wbps=44 riops=max wiops=55
                """);
        assertFalse(containerCgroup.io().updateMax(context, device, initial));

        cgroupRoot.resolve("io.max").writeUtf8File("");
        assertFalse(containerCgroup.io().updateMax(context, device, Max.UNLIMITED));
    }
}