1 /* 2 * Copyright (c) 2020, Red Hat Inc. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.platform.cgroupv2; 27 28 import java.io.IOException; 29 import java.nio.file.Paths; 30 import java.util.concurrent.TimeUnit; 31 import java.util.function.Function; 32 import java.util.stream.Collectors; 33 34 import jdk.internal.platform.CgroupSubsystem; 35 import jdk.internal.platform.CgroupSubsystemController; 36 import jdk.internal.platform.CgroupUtil; 37 import jdk.internal.platform.Metrics; 38 39 public class CgroupV2Subsystem implements CgroupSubsystem { 40 41 private final CgroupSubsystemController unified; 42 private static final String PROVIDER_NAME = "cgroupv2"; 43 private static final int PER_CPU_SHARES = 1024; 44 private static final String MAX_VAL = "max"; 45 private static final Object EMPTY_STR = ""; 46 47 public CgroupV2Subsystem(CgroupSubsystemController unified) { 48 this.unified = unified; 49 } 50 51 private long getLongVal(String file) { 52 return CgroupSubsystemController.getLongValue(unified, 53 file, 54 CgroupV2SubsystemController::convertStringToLong); 55 } 56 57 @Override 58 public String getProvider() { 59 return PROVIDER_NAME; 60 } 61 62 @Override 63 public long getCpuUsage() { 64 long micros = CgroupSubsystemController.getLongEntry(unified, "cpu.stat", "usage_usec"); 65 return TimeUnit.MICROSECONDS.toNanos(micros); 66 } 67 68 @Override 69 public long[] getPerCpuUsage() { 70 return null; // Not supported. 71 } 72 73 @Override 74 public long getCpuUserUsage() { 75 long micros = CgroupSubsystemController.getLongEntry(unified, "cpu.stat", "user_usec"); 76 return TimeUnit.MICROSECONDS.toNanos(micros); 77 } 78 79 @Override 80 public long getCpuSystemUsage() { 81 long micros = CgroupSubsystemController.getLongEntry(unified, "cpu.stat", "system_usec"); 82 return TimeUnit.MICROSECONDS.toNanos(micros); 83 } 84 85 @Override 86 public long getCpuPeriod() { 87 return getFromCpuMax(1 /* $PERIOD index */); 88 } 89 90 @Override 91 public long getCpuQuota() { 92 return getFromCpuMax(0 /* $MAX index */); 93 } 94 95 private long getFromCpuMax(int tokenIdx) { 96 String cpuMaxRaw = CgroupSubsystemController.getStringValue(unified, "cpu.max"); 97 if (cpuMaxRaw == null) { 98 // likely file not found 99 return Metrics.LONG_RETVAL_UNLIMITED; 100 } 101 // $MAX $PERIOD 102 String[] tokens = cpuMaxRaw.split("\\s+"); 103 if (tokens.length != 2) { 104 return Metrics.LONG_RETVAL_UNLIMITED; 105 } 106 String quota = tokens[tokenIdx]; 107 return limitFromString(quota); 108 } 109 110 private long limitFromString(String strVal) { 111 if (MAX_VAL.equals(strVal)) { 112 return Metrics.LONG_RETVAL_UNLIMITED; 113 } 114 return Long.parseLong(strVal); 115 } 116 117 @Override 118 public long getCpuShares() { 119 long sharesRaw = getLongVal("cpu.weight"); 120 if (sharesRaw == 100 || sharesRaw == 0) { 121 return Metrics.LONG_RETVAL_UNLIMITED; 122 } 123 int shares = (int)sharesRaw; 124 // CPU shares (OCI) value needs to get translated into 125 // a proper Cgroups v2 value. See: 126 // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller 127 // 128 // Use the inverse of (x == OCI value, y == cgroupsv2 value): 129 // ((262142 * y - 1)/9999) + 2 = x 130 // 131 int x = 262142 * shares - 1; 132 double frac = x/9999.0; 133 x = ((int)frac) + 2; 134 if ( x <= PER_CPU_SHARES ) { 135 return PER_CPU_SHARES; // mimic cgroups v1 136 } 137 int f = x/PER_CPU_SHARES; 138 int lower_multiple = f * PER_CPU_SHARES; 139 int upper_multiple = (f + 1) * PER_CPU_SHARES; 140 int distance_lower = Math.max(lower_multiple, x) - Math.min(lower_multiple, x); 141 int distance_upper = Math.max(upper_multiple, x) - Math.min(upper_multiple, x); 142 x = distance_lower <= distance_upper ? lower_multiple : upper_multiple; 143 return x; 144 } 145 146 @Override 147 public long getCpuNumPeriods() { 148 return CgroupSubsystemController.getLongEntry(unified, "cpu.stat", "nr_periods"); 149 } 150 151 @Override 152 public long getCpuNumThrottled() { 153 return CgroupSubsystemController.getLongEntry(unified, "cpu.stat", "nr_throttled"); 154 } 155 156 @Override 157 public long getCpuThrottledTime() { 158 long micros = CgroupSubsystemController.getLongEntry(unified, "cpu.stat", "throttled_usec"); 159 return TimeUnit.MICROSECONDS.toNanos(micros); 160 } 161 162 @Override 163 public long getEffectiveCpuCount() { 164 return Runtime.getRuntime().availableProcessors(); 165 } 166 167 @Override 168 public int[] getCpuSetCpus() { 169 String cpuSetVal = CgroupSubsystemController.getStringValue(unified, "cpuset.cpus"); 170 if (cpuSetVal == null || EMPTY_STR.equals(cpuSetVal)) { 171 return null; // not available 172 } 173 return CgroupSubsystemController.stringRangeToIntArray(cpuSetVal); 174 } 175 176 @Override 177 public int[] getEffectiveCpuSetCpus() { 178 String effCpuSetVal = CgroupSubsystemController.getStringValue(unified, "cpuset.cpus.effective"); 179 if (effCpuSetVal == null || EMPTY_STR.equals(effCpuSetVal)) { 180 return null; // not available 181 } 182 return CgroupSubsystemController.stringRangeToIntArray(effCpuSetVal); 183 } 184 185 @Override 186 public int[] getCpuSetMems() { 187 return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(unified, "cpuset.mems")); 188 } 189 190 @Override 191 public int[] getEffectiveCpuSetMems() { 192 return CgroupSubsystemController.stringRangeToIntArray(CgroupSubsystemController.getStringValue(unified, "cpuset.mems.effective")); 193 } 194 195 @Override 196 public double getCpuSetMemoryPressure() { 197 return Metrics.DOUBLE_RETVAL_NOT_SUPPORTED; 198 } 199 200 @Override 201 public Boolean isCpuSetMemoryPressureEnabled() { 202 return Metrics.BOOL_RETVAL_NOT_SUPPORTED; 203 } 204 205 @Override 206 public long getMemoryFailCount() { 207 return CgroupSubsystemController.getLongEntry(unified, "memory.events", "max"); 208 } 209 210 @Override 211 public long getMemoryLimit() { 212 String strVal = CgroupSubsystemController.getStringValue(unified, "memory.max"); 213 return limitFromString(strVal); 214 } 215 216 @Override 217 public long getMemoryMaxUsage() { 218 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 219 } 220 221 @Override 222 public long getMemoryUsage() { 223 return getLongVal("memory.current"); 224 } 225 226 @Override 227 public long getKernelMemoryFailCount() { 228 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 229 } 230 231 @Override 232 public long getKernelMemoryLimit() { 233 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 234 } 235 236 @Override 237 public long getKernelMemoryMaxUsage() { 238 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 239 } 240 241 @Override 242 public long getKernelMemoryUsage() { 243 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 244 } 245 246 @Override 247 public long getTcpMemoryFailCount() { 248 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 249 } 250 251 @Override 252 public long getTcpMemoryLimit() { 253 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 254 } 255 256 @Override 257 public long getTcpMemoryMaxUsage() { 258 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 259 } 260 261 @Override 262 public long getTcpMemoryUsage() { 263 return CgroupSubsystemController.getLongEntry(unified, "memory.stat", "sock"); 264 } 265 266 @Override 267 public long getMemoryAndSwapFailCount() { 268 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 269 } 270 271 @Override 272 public long getMemoryAndSwapLimit() { 273 String strVal = CgroupSubsystemController.getStringValue(unified, "memory.swap.max"); 274 return limitFromString(strVal); 275 } 276 277 @Override 278 public long getMemoryAndSwapMaxUsage() { 279 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 280 } 281 282 @Override 283 public long getMemoryAndSwapUsage() { 284 return getLongVal("memory.swap.current"); 285 } 286 287 @Override 288 public Boolean isMemoryOOMKillEnabled() { 289 return Metrics.BOOL_RETVAL_NOT_SUPPORTED; 290 } 291 292 @Override 293 public long getMemorySoftLimit() { 294 String softLimitStr = CgroupSubsystemController.getStringValue(unified, "memory.high"); 295 return limitFromString(softLimitStr); 296 } 297 298 @Override 299 public long getBlkIOServiceCount() { 300 return sumTokensIOStat(CgroupV2Subsystem::lineToRandWIOs); 301 } 302 303 304 @Override 305 public long getBlkIOServiced() { 306 return sumTokensIOStat(CgroupV2Subsystem::lineToRBytesAndWBytesIO); 307 } 308 309 private long sumTokensIOStat(Function<String, Long> mapFunc) { 310 try { 311 return CgroupUtil.readFilePrivileged(Paths.get(unified.path(), "io.stat")) 312 .map(mapFunc) 313 .collect(Collectors.summingLong(e -> e)); 314 } catch (IOException e) { 315 return Metrics.LONG_RETVAL_NOT_SUPPORTED; 316 } 317 } 318 319 private static String[] getRWIOMatchTokenNames() { 320 return new String[] { "rios", "wios" }; 321 } 322 323 private static String[] getRWBytesIOMatchTokenNames() { 324 return new String[] { "rbytes", "wbytes" }; 325 } 326 327 public static Long lineToRandWIOs(String line) { 328 String[] matchNames = getRWIOMatchTokenNames(); 329 return ioStatLineToLong(line, matchNames); 330 } 331 332 public static Long lineToRBytesAndWBytesIO(String line) { 333 String[] matchNames = getRWBytesIOMatchTokenNames(); 334 return ioStatLineToLong(line, matchNames); 335 } 336 337 private static Long ioStatLineToLong(String line, String[] matchNames) { 338 if (line == null || EMPTY_STR.equals(line)) { 339 return Long.valueOf(0); 340 } 341 String[] tokens = line.split("\\s+"); 342 long retval = 0; 343 for (String t: tokens) { 344 String[] valKeys = t.split("="); 345 if (valKeys.length != 2) { 346 // ignore device ids $MAJ:$MIN 347 continue; 348 } 349 for (String match: matchNames) { 350 if (match.equals(valKeys[0])) { 351 retval += longOrZero(valKeys[1]); 352 } 353 } 354 } 355 return Long.valueOf(retval); 356 } 357 358 private static long longOrZero(String val) { 359 long lVal = 0; 360 try { 361 lVal = Long.parseLong(val); 362 } catch (NumberFormatException e) { 363 // keep at 0 364 } 365 return lVal; 366 } 367 }