Googler | 758ae89 | 2024-02-08 03:59:45 -0800 | [diff] [blame] | 1 | // Copyright 2024 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package com.google.devtools.build.lib.sandbox; |
| 16 | |
| 17 | import static com.google.common.base.Preconditions.checkArgument; |
| 18 | import static java.nio.charset.StandardCharsets.UTF_8; |
| 19 | |
| 20 | import com.google.common.base.Joiner; |
| 21 | import com.google.common.base.Splitter; |
| 22 | import com.google.common.io.Files; |
| 23 | import java.io.File; |
| 24 | import java.io.IOException; |
| 25 | import java.util.List; |
| 26 | import javax.annotation.Nullable; |
| 27 | |
| 28 | /** Represents a v2 cgroup. */ |
| 29 | public class CgroupsInfoV2 extends CgroupsInfo { |
| 30 | |
| 31 | public CgroupsInfoV2(Type type, @Nullable File cgroupDir) { |
| 32 | super(type, Version.V2, cgroupDir); |
| 33 | } |
| 34 | |
| 35 | @Override |
| 36 | public CgroupsInfo createBlazeSpawnsCgroup(String procSelfCgroupPath) { |
| 37 | checkArgument( |
| 38 | type == Type.ROOT, "Should only be creating the Blaze spawns cgroup from the root cgroup."); |
| 39 | File blazeProcessCgroupDir; |
| 40 | File blazeSpawnsDir; |
| 41 | try { |
| 42 | blazeProcessCgroupDir = getBlazeProcessCgroupDir(cgroupDir, procSelfCgroupPath); |
| 43 | // In cgroups v2, we need to step back from the leaf node to make a further hierarchy. |
| 44 | blazeSpawnsDir = |
| 45 | new File( |
| 46 | blazeProcessCgroupDir.getParentFile(), |
| 47 | "blaze_" + ProcessHandle.current().pid() + "_spawns.slice"); |
| 48 | blazeSpawnsDir.mkdirs(); |
| 49 | blazeSpawnsDir.deleteOnExit(); |
| 50 | setSubtreeControllers(blazeSpawnsDir); |
| 51 | } catch (Exception e) { |
| 52 | return new InvalidCgroupsInfo(Type.BLAZE_SPAWNS, getVersion(), e); |
| 53 | } |
| 54 | |
| 55 | return new CgroupsInfoV2(Type.BLAZE_SPAWNS, blazeSpawnsDir); |
| 56 | } |
| 57 | |
| 58 | @Override |
| 59 | public CgroupsInfo createIndividualSpawnCgroup(String dirName, int memoryLimitMb) { |
| 60 | checkArgument( |
| 61 | type == Type.BLAZE_SPAWNS, |
| 62 | "Should only be creating the individual spawn's cgroup from the Blaze spawns cgroup."); |
| 63 | if (!canWrite()) { |
| 64 | return new InvalidCgroupsInfo( |
| 65 | Type.SPAWN, |
| 66 | getVersion(), |
| 67 | String.format("Cgroup %s is invalid, unable to create spawn's cgroup here.", cgroupDir)); |
| 68 | } |
| 69 | File spawnCgroupDir = new File(cgroupDir, dirName + ".scope"); |
| 70 | spawnCgroupDir.mkdirs(); |
| 71 | spawnCgroupDir.deleteOnExit(); |
| 72 | try { |
| 73 | if (memoryLimitMb > 0) { |
| 74 | // In cgroups v2, we need to propagate the controllers into new subdirs. |
| 75 | Files.asCharSink(new File(spawnCgroupDir, "memory.oom.group"), UTF_8).write("1\n"); |
| 76 | Files.asCharSink(new File(spawnCgroupDir, "memory.max"), UTF_8) |
| 77 | .write(Long.toString(memoryLimitMb * 1024L * 1024L)); |
| 78 | } |
| 79 | } catch (Exception e) { |
| 80 | return new InvalidCgroupsInfo(Type.SPAWN, getVersion(), e); |
| 81 | } |
| 82 | return new CgroupsInfoV2(Type.SPAWN, spawnCgroupDir); |
| 83 | } |
| 84 | |
| 85 | @Override |
| 86 | public int getMemoryUsageInKb() { |
| 87 | return getMemoryUsageInKbFromFile("memory.current"); |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Returns the path to the cgroup containing the Blaze process. |
| 92 | * |
| 93 | * <p>In v2, there is only one entry, and it looks something like this: |
| 94 | * |
| 95 | * <pre> |
| 96 | * 0::/user.slice/user-123.slice/session-1.scope |
| 97 | * </pre> |
| 98 | * |
| 99 | * @param mountPoint the directory where the cgroup hierarchy is mounted. |
| 100 | * @param procSelfCgroupPath path for the /proc/self/cgroup file. |
| 101 | * @throws IOException if there are errors reading the given procs cgroup file. |
| 102 | */ |
| 103 | private static File getBlazeProcessCgroupDir(File mountPoint, String procSelfCgroupPath) |
| 104 | throws IOException { |
| 105 | List<String> contents = Files.readLines(new File(procSelfCgroupPath), UTF_8); |
| 106 | if (contents.isEmpty()) { |
| 107 | throw new IllegalStateException( |
| 108 | "Found no memory cgroup entries in '" + procSelfCgroupPath + "'"); |
| 109 | } |
| 110 | List<String> parts = Splitter.on(":").limit(3).splitToList(contents.get(0)); |
| 111 | return new File(mountPoint, parts.get(2)); |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Sets the subtree controllers we need. This also checks that the controllers are available. |
| 116 | * |
| 117 | * @param blazeDir A directory in the cgroups hierarchy. |
| 118 | * @throws IOException If reading or writing the {@code cgroup.controllers} or {@code |
| 119 | * cgroup.subtree_control} file fails. |
| 120 | * @throws IllegalStateException if the {@code memory} and {code pids} controllers are either not |
| 121 | * available or cannot be set for subtrees. |
| 122 | */ |
| 123 | private static void setSubtreeControllers(File blazeDir) throws IOException { |
| 124 | var controllers = |
| 125 | Joiner.on(' ').join(Files.readLines(new File(blazeDir, "cgroup.controllers"), UTF_8)); |
| 126 | if (!(controllers.contains("memory") && controllers.contains("pids"))) { |
| 127 | throw new IllegalStateException( |
| 128 | String.format( |
| 129 | "Required controllers 'memory' and 'pids' not found in %s/cgroup.controllers", |
| 130 | blazeDir)); |
| 131 | } |
| 132 | var subtreeControllers = |
| 133 | Joiner.on(' ').join(Files.readLines(new File(blazeDir, "cgroup.subtree_control"), UTF_8)); |
| 134 | if (!subtreeControllers.contains("memory") || !subtreeControllers.contains("pids")) { |
| 135 | Files.asCharSink(new File(blazeDir, "cgroup.subtree_control"), UTF_8) |
| 136 | .write("+memory +pids\n"); |
| 137 | } |
| 138 | } |
| 139 | } |