/
ContainerMetricUtils.java
124 lines (107 loc) · 4.95 KB
/
ContainerMetricUtils.java
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
/*
* Copyright 2020 LinkedIn Corp. Licensed under the BSD 2-Clause License (the "License"). See License in the project root for license information.
*/
package com.linkedin.kafka.cruisecontrol.metricsreporter.metric;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public final class ContainerMetricUtils {
// Paths used to get cgroup information
private static final String QUOTA_PATH = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us";
private static final String PERIOD_PATH = "/sys/fs/cgroup/cpu/cpu.cfs_period_us";
// Unix command to execute inside a Linux container to get the number of logical processors available to the node
private static final String NPROC = "nproc";
// A CPU quota value of -1 indicates that the cgroup does not adhere to any CPU time restrictions
public static final int NO_CPU_QUOTA = -1;
private ContainerMetricUtils() { }
/**
* Reads cgroups CPU period from cgroups file. Value has a lowerbound of 1 millisecond and an upperbound of 1 second
* according to https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
*
* @return Cgroups CPU period in microseconds as a double.
*/
private static double getCpuPeriod() throws IOException {
return Double.parseDouble(readFile(CgroupFiles.PERIOD_PATH.getValue()));
}
/**
* Reads cgroups CPU quota from cgroups file. The value has a lowerbound of 1 millisecond
* according to https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
*
* @return Cgroups CPU quota in microseconds as a double.
*/
private static double getCpuQuota() throws IOException {
return Double.parseDouble(readFile(CgroupFiles.QUOTA_PATH.getValue()));
}
/**
* Gets the the number of logical cores available to the node.
* <p>
* We can get this value while running in a container by using the "nproc" command.
* Using other methods like OperatingSystemMXBean.getAvailableProcessors() and
* Runtime.getRuntime().availableProcessors() would require disabling container
* support (-XX:-UseContainerSupport) since these methods are aware of container
* boundaries
*
* @return Number of logical processors on node
*/
private static int getAvailableProcessors() throws IOException {
InputStream in = Runtime.getRuntime().exec(NPROC).getInputStream();
return Integer.parseInt(readInputStream(in));
}
private static String readFile(String path) throws IOException {
return readInputStream(new FileInputStream(path));
}
private static String readInputStream(InputStream in) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
String stream = br.readLine();
if (stream != null) {
return stream;
} else {
throw new IllegalArgumentException("Nothing was read from stream " + in);
}
}
/**
* Get the "recent CPU usage" for the JVM process running inside of a container.
* <p>
* At this time, the methods of OperatingSystemMXBean used for retrieving recent CPU usage are not
* container aware and calculate CPU usage with respect to the physical host instead of the operating
* environment from which they are called from. There have been efforts to make these methods container
* aware but the changes have not been backported to Java versions less than version 14.
* <p>
* Once these changes get backported, https://bugs.openjdk.java.net/browse/JDK-8226575, we can use
* "getSystemCpuLoad()" for retrieving the CPU usage values when running in a container environment.
*
* @param cpuUtil The "recent CPU usage" for a JVM process with respect to node
* @return the "recent CPU usage" for a JVM process with respect to operating environment
* as a double in [0.0,1.0].
*/
public static double getContainerProcessCpuLoad(double cpuUtil) throws IOException {
int logicalProcessorsOfNode = getAvailableProcessors();
double cpuQuota = getCpuQuota();
if (cpuQuota == NO_CPU_QUOTA) {
return cpuUtil;
}
// Get the number of CPUs of a node that can be used by the operating environment
double cpuLimit = cpuQuota / getCpuPeriod();
// Get the minimal number of CPUs needed to achieve the reported CPU utilization
double cpus = cpuUtil * logicalProcessorsOfNode;
/* Calculate the CPU utilization of a JVM process with respect to the operating environment.
* Since the operating environment will only use the CPU resources allocated by CGroups,
* it will always be that: cpuLimit >= cpus and the result is in the [0.0,1.0] interval.
*/
return cpus / cpuLimit;
}
private enum CgroupFiles {
QUOTA_PATH(ContainerMetricUtils.QUOTA_PATH),
PERIOD_PATH(ContainerMetricUtils.PERIOD_PATH);
private final String _value;
CgroupFiles(String value) {
this._value = value;
}
private String getValue() {
return _value;
}
}
}