blob: 1f62f8da0a7e80e0da0542947235e6bd2da46467 [file] [log] [blame]
Googler758ae892024-02-08 03:59:45 -08001// 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
15package com.google.devtools.build.lib.sandbox;
16
17import static com.google.common.base.Preconditions.checkArgument;
18import static java.nio.charset.StandardCharsets.UTF_8;
19
20import com.google.common.base.Joiner;
21import com.google.common.base.Splitter;
22import com.google.common.io.Files;
23import java.io.File;
24import java.io.IOException;
25import java.util.List;
26import javax.annotation.Nullable;
27
28/** Represents a v2 cgroup. */
29public 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}