Skip to content

Commit e044de0

Browse files
committed
Add tests for cgroup file reading
1 parent 3dbf4bf commit e044de0

5 files changed

Lines changed: 712 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.agent.internal.diagnostics.cgroups;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroupsv2.CGroupv2CpuReader;
9+
import java.io.IOException;
10+
import java.nio.charset.StandardCharsets;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.Arrays;
14+
import java.util.Collections;
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.io.TempDir;
18+
19+
/** Tests for {@link CGroupv2CpuReader} verifying cgroup v2 CPU stat file parsing. */
20+
@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
21+
class CGroupv2CpuReaderTest {
22+
23+
@TempDir Path tempDir;
24+
25+
private Path cgroupDir;
26+
27+
@BeforeEach
28+
void setUp() throws IOException {
29+
cgroupDir = tempDir.resolve("cgroup2");
30+
Files.createDirectories(cgroupDir);
31+
}
32+
33+
public static void writeString(Path path, String content) throws IOException {
34+
Files.write(path, Collections.singletonList(content), StandardCharsets.UTF_8);
35+
}
36+
37+
public static void writeLines(Path path, String... lines) throws IOException {
38+
Files.write(path, Arrays.asList(lines), StandardCharsets.UTF_8);
39+
}
40+
41+
@Test
42+
void parsesCpuStatFields() throws Exception {
43+
// Arrange
44+
createCpuStatFile(1000000, 600000, 400000);
45+
CGroupv2CpuReader reader = new CGroupv2CpuReader(cgroupDir);
46+
47+
// Act - first poll/update to set baseline
48+
reader.poll();
49+
reader.update();
50+
51+
// Update file and poll again
52+
createCpuStatFile(2000000, 1200000, 800000);
53+
reader.poll();
54+
reader.update();
55+
56+
// Assert
57+
assertThat(reader.getCpuUsage().getIncrement()).isEqualTo(1000000L);
58+
assertThat(reader.getCpuUser().getIncrement()).isEqualTo(600000L);
59+
assertThat(reader.getCpuSystem().getIncrement()).isEqualTo(400000L);
60+
}
61+
62+
@Test
63+
void handlesAdditionalFieldsInCpuStat() throws Exception {
64+
// Arrange - cpu.stat may contain additional fields like nr_periods, nr_throttled
65+
writeLines(
66+
cgroupDir.resolve("cpu.stat"),
67+
"usage_usec 1000000",
68+
"user_usec 600000",
69+
"system_usec 400000",
70+
"nr_periods 100",
71+
"nr_throttled 5",
72+
"throttled_usec 50000",
73+
"nr_bursts 0",
74+
"burst_usec 0");
75+
76+
CGroupv2CpuReader reader = new CGroupv2CpuReader(cgroupDir);
77+
78+
// Act
79+
reader.poll();
80+
reader.update();
81+
82+
// Update with new values
83+
writeLines(
84+
cgroupDir.resolve("cpu.stat"),
85+
"usage_usec 2000000",
86+
"user_usec 1200000",
87+
"system_usec 800000",
88+
"nr_periods 200",
89+
"nr_throttled 10",
90+
"throttled_usec 100000",
91+
"nr_bursts 0",
92+
"burst_usec 0");
93+
reader.poll();
94+
reader.update();
95+
96+
// Assert - should only parse the usage, user, and system fields
97+
assertThat(reader.getCpuUsage().getIncrement()).isEqualTo(1000000L);
98+
assertThat(reader.getCpuUser().getIncrement()).isEqualTo(600000L);
99+
assertThat(reader.getCpuSystem().getIncrement()).isEqualTo(400000L);
100+
}
101+
102+
@Test
103+
void closesResourcesProperly() throws Exception {
104+
// Arrange
105+
createCpuStatFile(1000000, 600000, 400000);
106+
CGroupv2CpuReader reader = new CGroupv2CpuReader(cgroupDir);
107+
108+
reader.poll();
109+
reader.update();
110+
111+
// Act & Assert - should not throw
112+
reader.close();
113+
}
114+
115+
@Test
116+
void incrementIsNullOnFirstPoll() throws Exception {
117+
// Arrange
118+
createCpuStatFile(1000000, 600000, 400000);
119+
CGroupv2CpuReader reader = new CGroupv2CpuReader(cgroupDir);
120+
121+
// Act - only first poll/update
122+
reader.poll();
123+
reader.update();
124+
125+
// Assert - no increment yet since we need two values to calculate
126+
assertThat(reader.getCpuUsage().getIncrement()).isNull();
127+
assertThat(reader.getCpuUser().getIncrement()).isNull();
128+
assertThat(reader.getCpuSystem().getIncrement()).isNull();
129+
}
130+
131+
@Test
132+
void worksWithCustomMountPath() throws Exception {
133+
// Arrange - deeply nested custom path
134+
Path customPath = tempDir.resolve("sys").resolve("fs").resolve("cgroup").resolve("app");
135+
Files.createDirectories(customPath);
136+
137+
writeLines(
138+
customPath.resolve("cpu.stat"),
139+
"usage_usec 5000000",
140+
"user_usec 3000000",
141+
"system_usec 2000000");
142+
143+
CGroupv2CpuReader reader = new CGroupv2CpuReader(customPath);
144+
145+
// Act
146+
reader.poll();
147+
reader.update();
148+
149+
writeLines(
150+
customPath.resolve("cpu.stat"),
151+
"usage_usec 10000000",
152+
"user_usec 6000000",
153+
"system_usec 4000000");
154+
reader.poll();
155+
reader.update();
156+
157+
// Assert
158+
assertThat(reader.getCpuUsage().getIncrement()).isEqualTo(5000000L);
159+
assertThat(reader.getCpuUser().getIncrement()).isEqualTo(3000000L);
160+
assertThat(reader.getCpuSystem().getIncrement()).isEqualTo(2000000L);
161+
}
162+
163+
private void createCpuStatFile(long usage, long user, long system) throws IOException {
164+
writeLines(
165+
cgroupDir.resolve("cpu.stat"),
166+
"usage_usec " + usage,
167+
"user_usec " + user,
168+
"system_usec " + system);
169+
}
170+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.agent.internal.diagnostics.cgroups;
5+
6+
import static com.microsoft.applicationinsights.agent.internal.diagnostics.cgroups.CGroupv2CpuReaderTest.writeString;
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9+
10+
import com.microsoft.applicationinsights.diagnostics.collection.libos.OperatingSystemInteractionException;
11+
import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroups.LinuxCGroupDataReader;
12+
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.io.TempDir;
18+
19+
/**
20+
* Tests for {@link LinuxCGroupDataReader} verifying cgroup v1 data reading with arbitrary mount
21+
* locations.
22+
*/
23+
@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
24+
class LinuxCGroupDataReaderTest {
25+
26+
@TempDir Path tempDir;
27+
28+
private Path cgroupRoot;
29+
30+
@BeforeEach
31+
void setUp() throws IOException {
32+
cgroupRoot = tempDir.resolve("cgroup");
33+
Files.createDirectories(cgroupRoot);
34+
}
35+
36+
@Test
37+
void readsMemoryLimitsFromCustomMountLocation() throws Exception {
38+
// Arrange
39+
Path memoryDir = cgroupRoot.resolve("memory");
40+
Files.createDirectories(memoryDir);
41+
writeString(memoryDir.resolve("memory.limit_in_bytes"), "1073741824");
42+
writeString(memoryDir.resolve("memory.soft_limit_in_bytes"), "536870912");
43+
writeString(memoryDir.resolve("memory.kmem.limit_in_bytes"), "268435456");
44+
45+
LinuxCGroupDataReader reader = new LinuxCGroupDataReader(cgroupRoot);
46+
47+
// Act & Assert
48+
assertThat(reader.getMemoryLimit()).isEqualTo(1073741824L);
49+
assertThat(reader.getMemorySoftLimit()).isEqualTo(536870912L);
50+
assertThat(reader.getKmemLimit()).isEqualTo(268435456L);
51+
}
52+
53+
@Test
54+
void readsCpuLimitsFromCustomMountLocation() throws Exception {
55+
// Arrange
56+
Path cpuDir = cgroupRoot.resolve("cpu,cpuacct");
57+
Files.createDirectories(cpuDir);
58+
writeString(cpuDir.resolve("cpu.cfs_quota_us"), "50000");
59+
writeString(cpuDir.resolve("cpu.cfs_period_us"), "100000");
60+
61+
LinuxCGroupDataReader reader = new LinuxCGroupDataReader(cgroupRoot);
62+
63+
// Act & Assert
64+
assertThat(reader.isAvailable()).isTrue();
65+
assertThat(reader.getCpuLimit()).isEqualTo(50000L);
66+
assertThat(reader.getCpuPeriod()).isEqualTo(100000L);
67+
}
68+
69+
@Test
70+
void isAvailableReturnsFalseWhenNoFilesExist() {
71+
// Arrange - cgroupRoot exists but has no cgroup files
72+
LinuxCGroupDataReader reader = new LinuxCGroupDataReader(cgroupRoot);
73+
74+
// Act & Assert
75+
assertThat(reader.isAvailable()).isFalse();
76+
}
77+
78+
@Test
79+
void throwsExceptionWhenFileDoesNotExist() {
80+
// Arrange
81+
LinuxCGroupDataReader reader = new LinuxCGroupDataReader(cgroupRoot);
82+
83+
// Act & Assert
84+
assertThatThrownBy(reader::getMemoryLimit)
85+
.isInstanceOf(OperatingSystemInteractionException.class);
86+
}
87+
88+
@Test
89+
void worksWithDeeplyNestedCustomMountLocation() throws Exception {
90+
// Arrange - simulate a deeply nested custom mount point
91+
Path deepPath = tempDir.resolve("custom").resolve("mount").resolve("point").resolve("cgroup");
92+
Files.createDirectories(deepPath);
93+
94+
Path memoryDir = deepPath.resolve("memory");
95+
Files.createDirectories(memoryDir);
96+
writeString(memoryDir.resolve("memory.limit_in_bytes"), "2147483648");
97+
98+
Path cpuDir = deepPath.resolve("cpu,cpuacct");
99+
Files.createDirectories(cpuDir);
100+
writeString(cpuDir.resolve("cpu.cfs_quota_us"), "100000");
101+
writeString(cpuDir.resolve("cpu.cfs_period_us"), "100000");
102+
103+
LinuxCGroupDataReader reader = new LinuxCGroupDataReader(deepPath);
104+
105+
// Act & Assert
106+
assertThat(reader.isAvailable()).isTrue();
107+
assertThat(reader.getMemoryLimit()).isEqualTo(2147483648L);
108+
assertThat(reader.getCpuLimit()).isEqualTo(100000L);
109+
assertThat(reader.getCpuPeriod()).isEqualTo(100000L);
110+
}
111+
}

0 commit comments

Comments
 (0)