Introduce new flag --experimental_worker_use_cgroups_on_linux that uses cgroups to track memory usage for singleplex workers (on Linux) without imposing any hard limits.

TODOs:
- Figure out how to get around cases where Blaze is put automatically put in a root cgroup as a consequence of the session it was run from (e.g. ssh). One possibility is using `systemd-run`.

RELNOTES: Introduce new flag --experimental_worker_use_cgroups_on_linux that uses cgroups to track memory usage for singleplex workers (on Linux).
PiperOrigin-RevId: 605270861
Change-Id: I6c6cffb0568de7d4ccfbc4624c579bc3d64fadb4
diff --git a/.bazelci/postsubmit.yml b/.bazelci/postsubmit.yml
index 7c0b70b..7f6b4b1 100644
--- a/.bazelci/postsubmit.yml
+++ b/.bazelci/postsubmit.yml
@@ -193,6 +193,8 @@
       # MacOS does not have cgroups so it can't support hardened sandbox
       - "-//src/test/java/com/google/devtools/build/lib/sandbox:CgroupsInfoTest"
       - "-//src/test/shell/integration:bazel_hardened_sandboxed_worker_test"
+      - "-//src/test/shell/integration:bazel_sandboxed_worker_with_cgroups_test"
+      - "-//src/test/shell/integration:bazel_worker_with_cgroups_test"
       # https://github.com/bazelbuild/bazel/issues/16526
       - "-//src/test/shell/bazel:starlark_repository_test"
       # https://github.com/bazelbuild/bazel/issues/17407
@@ -252,6 +254,8 @@
       # MacOS does not have cgroups so it can't support hardened sandbox
       - "-//src/test/java/com/google/devtools/build/lib/sandbox:CgroupsInfoTest"
       - "-//src/test/shell/integration:bazel_hardened_sandboxed_worker_test"
+      - "-//src/test/shell/integration:bazel_sandboxed_worker_with_cgroups_test"
+      - "-//src/test/shell/integration:bazel_worker_with_cgroups_test"
       # https://github.com/bazelbuild/bazel/issues/16525
       - "-//src/test/java/com/google/devtools/build/lib/buildtool:KeepGoingTest"
       - "-//src/test/java/com/google/devtools/build/lib/buildtool:DanglingSymlinkTest"
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index e13c4f1..6e69075 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -196,6 +196,8 @@
       # MacOS does not have cgroups so it can't support hardened sandbox
       - "-//src/test/java/com/google/devtools/build/lib/sandbox:CgroupsInfoTest"
       - "-//src/test/shell/integration:bazel_hardened_sandboxed_worker_test"
+      - "-//src/test/shell/integration:bazel_sandboxed_worker_with_cgroups_test"
+      - "-//src/test/shell/integration:bazel_worker_with_cgroups_test"
       # https://github.com/bazelbuild/bazel/issues/16526
       - "-//src/test/shell/bazel:starlark_repository_test"
       # https://github.com/bazelbuild/bazel/issues/17407
@@ -256,6 +258,8 @@
       # MacOS does not have cgroups so it can't support hardened sandbox
       - "-//src/test/java/com/google/devtools/build/lib/sandbox:CgroupsInfoTest"
       - "-//src/test/shell/integration:bazel_hardened_sandboxed_worker_test"
+      - "-//src/test/shell/integration:bazel_sandboxed_worker_with_cgroups_test"
+      - "-//src/test/shell/integration:bazel_worker_with_cgroups_test"
       # https://github.com/bazelbuild/bazel/issues/16525
       - "-//src/test/java/com/google/devtools/build/lib/buildtool:KeepGoingTest"
       - "-//src/test/java/com/google/devtools/build/lib/buildtool:DanglingSymlinkTest"
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
index f4079d9..5077a3b 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
@@ -329,8 +329,9 @@
           CgroupsInfo.getBlazeSpawnsCgroup()
               .createIndividualSpawnCgroup(
                   "sandbox_" + context.getId(), sandboxOptions.memoryLimitMb);
-      cgroupsDir = sandboxCgroup.getCgroupDir().toString();
-      commandLineBuilder.setCgroupsDir(cgroupsDir);
+      if (sandboxCgroup.exists()) {
+        commandLineBuilder.setCgroupsDir(sandboxCgroup.getCgroupDir().toString());
+      }
     }
 
     if (useHermeticTmp) {
diff --git a/src/main/java/com/google/devtools/build/lib/worker/BUILD b/src/main/java/com/google/devtools/build/lib/worker/BUILD
index 13ff305..aa88375 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/worker/BUILD
@@ -106,6 +106,7 @@
         "//src/main/java/com/google/devtools/build/lib/exec:tree_deleter",
         "//src/main/java/com/google/devtools/build/lib/exec/local",
         "//src/main/java/com/google/devtools/build/lib/runtime/commands/events",
+        "//src/main/java/com/google/devtools/build/lib/sandbox:cgroups_info",
         "//src/main/java/com/google/devtools/build/lib/sandbox:linux_sandbox_util",
         "//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_helpers",
         "//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_options",
@@ -159,8 +160,10 @@
         "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
         "//src/main/java/com/google/devtools/build/lib/clock",
         "//src/main/java/com/google/devtools/build/lib/metrics:resource_collector",
+        "//src/main/java/com/google/devtools/build/lib/sandbox:cgroups_info",
         "//src/main/java/com/google/devtools/build/lib/util:os",
         "//third_party:guava",
+        "//third_party:jsr305",
     ],
 )
 
@@ -186,6 +189,7 @@
         ":singleplex_worker",
         ":worker",
         ":worker_key",
+        ":worker_options",
         ":worker_process_status",
         "//src/main/java/com/google/devtools/build/lib/events",
         "//src/main/java/com/google/devtools/build/lib/exec:tree_deleter",
@@ -237,11 +241,13 @@
         ":worker_process_status",
         "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/sandbox:cgroups_info",
         "//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_helpers",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/protobuf:worker_protocol_java_proto",
         "//third_party:guava",
+        "//third_party:jsr305",
     ],
 )
 
@@ -267,6 +273,7 @@
         ":worker",
         ":worker_exec_root",
         ":worker_key",
+        ":worker_options",
         ":worker_process_status",
         ":worker_protocol",
         "//src/main/java/com/google/devtools/build/lib/actions",
diff --git a/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java b/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java
index cc056fe..b4d5f9e 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java
@@ -37,7 +37,6 @@
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Symlinks;
-import java.io.File;
 import java.io.IOException;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -95,10 +94,9 @@
 
   private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
   private final WorkerExecRoot workerExecRoot;
+
   /** Options specific to hardened sandbox, null if not using that. */
   @Nullable private final WorkerSandboxOptions hardenedSandboxOptions;
-  /** If non-null, a directory that allows cgroup control. */
-  private String cgroupsDir;
 
   private Path inaccessibleHelperDir;
   private Path inaccessibleHelperFile;
@@ -109,9 +107,10 @@
       int workerId,
       Path workDir,
       Path logFile,
+      WorkerOptions workerOptions,
       @Nullable WorkerSandboxOptions hardenedSandboxOptions,
       TreeDeleter treeDeleter) {
-    super(workerKey, workerId, workDir, logFile);
+    super(workerKey, workerId, workDir, logFile, workerOptions);
     this.workerExecRoot =
         new WorkerExecRoot(
             workDir,
@@ -177,6 +176,18 @@
   @Override
   protected Subprocess createProcess() throws IOException, UserExecException {
     ImmutableList<String> args = makeExecPathAbsolute(workerKey.getArgs());
+
+    // We put the sandbox inside a unique subdirectory using the worker's ID.
+    if (options.useCgroupsOnLinux || hardenedSandboxOptions != null) {
+      // In the event that the memory limit is 0, we defer to using Blaze's WorkerLifecycleManager
+      // to kill workers rather than cgroup's OOM killer.
+      cgroup =
+          CgroupsInfo.getBlazeSpawnsCgroup()
+              .createIndividualSpawnCgroup(
+                  "worker_sandbox_" + workerId,
+                  hardenedSandboxOptions != null ? hardenedSandboxOptions.memoryLimit() : 0);
+    }
+
     // TODO(larsrc): Check that execRoot and outputBase are not under /tmp
     if (hardenedSandboxOptions != null) {
       // In hardened mode, we bindmount a temp dir. We put the mount dir in the parent directory to
@@ -195,14 +206,8 @@
               .setUseFakeHostname(this.hardenedSandboxOptions.fakeHostname())
               .setCreateNetworkNamespace(NETNS);
 
-      if (hardenedSandboxOptions.memoryLimit() > 0) {
-        // We put the sandbox inside a unique subdirectory using the worker's ID.
-        CgroupsInfo workerCgroup =
-            CgroupsInfo.getBlazeSpawnsCgroup()
-                .createIndividualSpawnCgroup(
-                    "worker_sandbox_" + workerId, hardenedSandboxOptions.memoryLimit());
-        cgroupsDir = workerCgroup.getCgroupDir().toString();
-        commandLineBuilder.setCgroupsDir(cgroupsDir);
+      if (cgroup != null && cgroup.exists()) {
+        commandLineBuilder.setCgroupsDir(cgroup.getCgroupDir().toString());
       }
 
       if (this.hardenedSandboxOptions.fakeUsername()) {
@@ -211,7 +216,17 @@
 
       args = commandLineBuilder.buildForCommand(args);
     }
-    return createProcessBuilder(args).start();
+
+    Subprocess process = createProcessBuilder(args).start();
+
+    // If using hardened sandbox (aka linux-sandbox), the linux-sandbox parent process moves the
+    // sandboxed children processes (pid 1, 2) into the cgroup. But we still need to move the
+    // linux-sandbox process into the worker cgroup. On the other hand, without linux-sandbox, Blaze
+    // needs to do this itself for the spawned worker process.
+    if (cgroup != null && cgroup.exists()) {
+      cgroup.addProcess(process.getProcessId());
+    }
+    return process;
   }
 
   @Override
@@ -228,9 +243,9 @@
   @Override
   public void finishExecution(Path execRoot, SandboxOutputs outputs) throws IOException {
     super.finishExecution(execRoot, outputs);
-    if (cgroupsDir != null) {
+    if (cgroup != null && cgroup.exists()) {
       // This is only to not leave too much behind in the cgroups tree, can ignore errors.
-      new File(cgroupsDir).delete();
+      cgroup.getCgroupDir().delete();
     }
     workerExecRoot.copyOutputs(execRoot, outputs);
   }
@@ -245,9 +260,9 @@
       if (inaccessibleHelperDir != null) {
         inaccessibleHelperDir.delete();
       }
-      if (cgroupsDir != null) {
+      if (cgroup != null && cgroup.exists()) {
         // This is only to not leave too much behind in the cgroups tree, can ignore errors.
-        new File(cgroupsDir).delete();
+        cgroup.getCgroupDir().delete();
       }
       workDir.deleteTree();
     } catch (IOException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/worker/SingleplexWorker.java b/src/main/java/com/google/devtools/build/lib/worker/SingleplexWorker.java
index 91eb3dd..ccf8a1c 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/SingleplexWorker.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/SingleplexWorker.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.GoogleLogger;
 import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.sandbox.CgroupsInfo;
 import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs;
 import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs;
 import com.google.devtools.build.lib.shell.Subprocess;
@@ -68,14 +69,28 @@
    */
   protected Thread shutdownHook;
 
-  SingleplexWorker(WorkerKey workerKey, int workerId, final Path workDir, Path logFile) {
+  protected WorkerOptions options;
+
+  SingleplexWorker(
+      WorkerKey workerKey, int workerId, final Path workDir, Path logFile, WorkerOptions options) {
     super(workerKey, workerId, logFile, new WorkerProcessStatus());
     this.workDir = workDir;
+    this.options = options;
   }
 
   protected Subprocess createProcess() throws IOException, InterruptedException, UserExecException {
     ImmutableList<String> args = makeExecPathAbsolute(workerKey.getArgs());
-    return createProcessBuilder(args).start();
+    Subprocess process = createProcessBuilder(args).start();
+    if (options.useCgroupsOnLinux && CgroupsInfo.isSupported()) {
+      cgroup =
+          CgroupsInfo.getBlazeSpawnsCgroup()
+              .createIndividualSpawnCgroup(
+                  /* dirName= */ "worker_" + workerId, /* memoryLimitMb= */ 0);
+      if (cgroup.exists()) {
+        cgroup.addProcess(process.getProcessId());
+      }
+    }
+    return process;
   }
 
   protected SubprocessBuilder createProcessBuilder(ImmutableList<String> argv) {
diff --git a/src/main/java/com/google/devtools/build/lib/worker/Worker.java b/src/main/java/com/google/devtools/build/lib/worker/Worker.java
index 8312196..345db1b 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/Worker.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/Worker.java
@@ -16,6 +16,7 @@
 import com.google.common.hash.HashCode;
 import com.google.devtools.build.lib.actions.UserExecException;
 import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.sandbox.CgroupsInfo;
 import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs;
 import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs;
 import com.google.devtools.build.lib.vfs.Path;
@@ -26,6 +27,7 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.SortedMap;
+import javax.annotation.Nullable;
 
 /**
  * An abstract superclass for persistent workers. Workers execute actions in long-running processes
@@ -51,6 +53,8 @@
 
   protected final WorkerProcessStatus status;
 
+  @Nullable protected CgroupsInfo cgroup = null;
+
   /**
    * Returns a unique id for this worker. This is used to distinguish different worker processes in
    * logs and messages.
@@ -73,6 +77,11 @@
     return status;
   }
 
+  @Nullable
+  public CgroupsInfo getCgroup() {
+    return cgroup;
+  }
+
   HashCode getWorkerFilesCombinedHash() {
     return workerKey.getWorkerFilesCombinedHash();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
index e573a2d..56d1202 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
@@ -46,6 +46,7 @@
   private static final AtomicInteger pidCounter = new AtomicInteger(1);
 
   private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
+  protected final WorkerOptions workerOptions;
 
   private final Path workerBaseDir;
   private final TreeDeleter treeDeleter;
@@ -57,15 +58,17 @@
    */
   @Nullable private final WorkerSandboxOptions hardenedSandboxOptions;
 
-  public WorkerFactory(Path workerBaseDir) {
-    this(workerBaseDir, null, null);
+  public WorkerFactory(Path workerBaseDir, WorkerOptions workerOptions) {
+    this(workerBaseDir, workerOptions, null, null);
   }
 
   public WorkerFactory(
       Path workerBaseDir,
+      WorkerOptions workerOptions,
       @Nullable WorkerSandboxOptions hardenedSandboxOptions,
       @Nullable TreeDeleter treeDeleter) {
     this.workerBaseDir = workerBaseDir;
+    this.workerOptions = workerOptions;
     this.hardenedSandboxOptions = hardenedSandboxOptions;
     this.treeDeleter = treeDeleter;
   }
@@ -94,7 +97,13 @@
         Path workDir = getSandboxedWorkerPath(key, workerId);
         worker =
             new SandboxedWorker(
-                key, workerId, workDir, logFile, hardenedSandboxOptions, treeDeleter);
+                key,
+                workerId,
+                workDir,
+                logFile,
+                workerOptions,
+                hardenedSandboxOptions,
+                treeDeleter);
       }
     } else if (key.isMultiplex()) {
       WorkerMultiplexer workerMultiplexer = WorkerMultiplexerManager.getInstance(key, logFile);
@@ -102,7 +111,7 @@
           new WorkerProxy(
               key, workerId, workerMultiplexer.getLogFile(), workerMultiplexer, key.getExecRoot());
     } else {
-      worker = new SingleplexWorker(key, workerId, key.getExecRoot(), logFile);
+      worker = new SingleplexWorker(key, workerId, key.getExecRoot(), logFile, workerOptions);
     }
 
     String msg =
@@ -150,7 +159,7 @@
     }
     String msg =
         String.format(
-            "Destroying %s %s (id %d, key hash %d) with cause: %s %s",
+            "Destroying %s %s (id %d, key hash %d) with cause: %s %s\n",
             key.getMnemonic(),
             key.getWorkerTypeName(),
             workerId,
@@ -240,6 +249,7 @@
     }
     WorkerFactory that = (WorkerFactory) o;
     return workerBaseDir.equals(that.workerBaseDir)
+        && workerOptions.useCgroupsOnLinux == that.workerOptions.useCgroupsOnLinux
         && Objects.equals(this.hardenedSandboxOptions, that.hardenedSandboxOptions);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
index 75c038e..59ac76c 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.commands.events.CleanStartingEvent;
 import com.google.devtools.build.lib.sandbox.AsynchronousTreeDeleter;
+import com.google.devtools.build.lib.sandbox.CgroupsInfo;
 import com.google.devtools.build.lib.sandbox.LinuxSandboxUtil;
 import com.google.devtools.build.lib.sandbox.SandboxHelpers;
 import com.google.devtools.build.lib.sandbox.SandboxOptions;
@@ -115,7 +116,7 @@
       treeDeleter = new AsynchronousTreeDeleter(trashBase);
     }
     WorkerFactory newWorkerFactory =
-        new WorkerFactory(workerDir, workerSandboxOptions, treeDeleter);
+        new WorkerFactory(workerDir, options, workerSandboxOptions, treeDeleter);
     if (!newWorkerFactory.equals(workerFactory)) {
       if (workerDir.exists()) {
         try {
@@ -170,6 +171,10 @@
       WorkerProcessMetricsCollector.instance().clear();
     }
 
+    // Override the flag value if we can't actually use cgroups so that we at least fallback to ps.
+    boolean useCgroupsOnLinux = options.useCgroupsOnLinux && CgroupsInfo.isSupported();
+    WorkerProcessMetricsCollector.instance().setUseCgroupsOnLinux(useCgroupsOnLinux);
+
     // Start collecting after a pool is defined
     workerLifecycleManager = new WorkerLifecycleManager(workerPool, options);
     workerLifecycleManager.setReporter(env.getReporter());
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerOptions.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerOptions.java
index 271f2db..10a960b 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerOptions.java
@@ -55,7 +55,8 @@
       }
       int pos = input.indexOf('=');
       if (pos < 0) {
-        return Maps.immutableEntry("", valueConverter.convert(input, /*conversionContext=*/ null));
+        return Maps.immutableEntry(
+            "", valueConverter.convert(input, /* conversionContext= */ null));
       }
       String name = input.substring(0, pos);
       String value = input.substring(pos + 1);
@@ -63,7 +64,8 @@
         return Maps.immutableEntry(name, null);
       }
 
-      return Maps.immutableEntry(name, valueConverter.convert(value, /*conversionContext=*/ null));
+      return Maps.immutableEntry(
+          name, valueConverter.convert(value, /* conversionContext= */ null));
     }
 
     @Override
@@ -198,6 +200,18 @@
   public int totalWorkerMemoryLimitMb;
 
   @Option(
+      name = "experimental_worker_use_cgroups_on_linux",
+      defaultValue = "false",
+      // List as undocumented since we will want to make this the default eventually.
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.EXECUTION},
+      help =
+          "On linux, run all workers in its own cgroup (without any limits set) and use the"
+              + " cgroup's own resource accounting for memory measurements. This is overridden by"
+              + " --experimental_worker_sandbox_hardening for sandboxed workers.")
+  public boolean useCgroupsOnLinux;
+
+  @Option(
       name = "experimental_worker_sandbox_hardening",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollector.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollector.java
index d80bb78..be66585 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollector.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollector.java
@@ -24,8 +24,10 @@
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.WorkerMetrics;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.WorkerMetrics.WorkerStatus;
 import com.google.devtools.build.lib.clock.Clock;
+import com.google.devtools.build.lib.metrics.CgroupsInfoCollector;
 import com.google.devtools.build.lib.metrics.PsInfoCollector;
 import com.google.devtools.build.lib.metrics.ResourceSnapshot;
+import com.google.devtools.build.lib.sandbox.CgroupsInfo;
 import com.google.devtools.build.lib.util.OS;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -33,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Nullable;
 
 /** Collects and populates system metrics about persistent workers. */
 public class WorkerProcessMetricsCollector {
@@ -40,16 +43,33 @@
   /** The metrics collector (a static singleton instance). Inactive by default. */
   private static final WorkerProcessMetricsCollector instance = new WorkerProcessMetricsCollector();
 
+  private final PsInfoCollector psInfoCollector;
+  private final CgroupsInfoCollector cgroupsInfoCollector;
+
   private Clock clock;
 
   /**
    * Mapping of worker process ids to their process metrics. This contains all workers that have
    * been alive at any point during the build.
    */
-  private final Map<Long, WorkerProcessMetrics> processIdToWorkerProcessMetrics =
+  private final Map<Long, WorkerProcessMetrics> pidToWorkerProcessMetrics =
       new ConcurrentHashMap<>();
 
-  private WorkerProcessMetricsCollector() {}
+  private final Map<Long, CgroupsInfo> pidToCgroups = new ConcurrentHashMap<>();
+
+  private boolean useCgroupsOnLinux = false;
+
+  private WorkerProcessMetricsCollector() {
+    psInfoCollector = PsInfoCollector.instance();
+    cgroupsInfoCollector = CgroupsInfoCollector.instance();
+  }
+
+  @VisibleForTesting
+  WorkerProcessMetricsCollector(
+      PsInfoCollector psInfoCollector, CgroupsInfoCollector cgroupsInfoCollector) {
+    this.psInfoCollector = psInfoCollector;
+    this.cgroupsInfoCollector = cgroupsInfoCollector;
+  }
 
   public static WorkerProcessMetricsCollector instance() {
     return instance;
@@ -59,10 +79,14 @@
     this.clock = clock;
   }
 
+  public void setUseCgroupsOnLinux(boolean useCgroupsOnLinux) {
+    this.useCgroupsOnLinux = useCgroupsOnLinux;
+  }
+
   ResourceSnapshot collectResourceUsage() {
     // Only collect for process we know are alive.
     ImmutableSet<Long> alivePids =
-        processIdToWorkerProcessMetrics.entrySet().stream()
+        pidToWorkerProcessMetrics.entrySet().stream()
             .filter(e -> !e.getValue().getStatus().isKilled())
             .map(e -> e.getKey())
             .collect(toImmutableSet());
@@ -73,13 +97,27 @@
    * Collects memory usage of all ancestors of processes by pid. If a pid does not allow collecting
    * memory usage, it is silently ignored.
    */
-  ResourceSnapshot collectResourceUsage(OS os, ImmutableSet<Long> processIds) {
+  @VisibleForTesting
+  public ResourceSnapshot collectResourceUsage(OS os, ImmutableSet<Long> alivePids) {
     // TODO(b/181317827): Support Windows.
-    if (processIds.isEmpty()) {
+    if (alivePids.isEmpty()) {
       return ResourceSnapshot.createEmpty(clock.now());
     }
-    if (os == OS.LINUX || os == OS.DARWIN) {
-      return PsInfoCollector.instance().collectResourceUsage(processIds, clock);
+    if (os.equals(OS.DARWIN)) {
+      return psInfoCollector.collectResourceUsage(alivePids, clock);
+    }
+    if (os.equals(OS.LINUX)) {
+      if (useCgroupsOnLinux) {
+        // Remove the killed pids so that we only collect from the cgroups that are alive.
+        for (long pid : ImmutableSet.copyOf(pidToCgroups.keySet())) {
+          if (!alivePids.contains(pid)) {
+            pidToCgroups.remove(pid);
+          }
+        }
+        return cgroupsInfoCollector.collectResourceUsage(pidToCgroups, clock);
+      }
+      // Default to using ps if cgroups is not enabled.
+      return psInfoCollector.collectResourceUsage(alivePids, clock);
     }
     return ResourceSnapshot.createEmpty(clock.now());
   }
@@ -94,19 +132,23 @@
     ResourceSnapshot resourceSnapshot = collectResourceUsage();
 
     ImmutableMap<Long, Integer> pidToMemoryInKb = resourceSnapshot.getPidToMemoryInKb();
+
     Instant collectionTime = resourceSnapshot.getCollectionTime();
 
     ImmutableList.Builder<WorkerProcessMetrics> workerMetrics = new ImmutableList.Builder<>();
-    for (Map.Entry<Long, WorkerProcessMetrics> entry : processIdToWorkerProcessMetrics.entrySet()) {
+    for (Map.Entry<Long, WorkerProcessMetrics> entry : pidToWorkerProcessMetrics.entrySet()) {
       WorkerProcessMetrics workerMetric = entry.getValue();
-      Long pid = workerMetric.getProcessId();
-      boolean isMeasurable = pidToMemoryInKb.containsKey(pid);
 
-      if (!isMeasurable && workerMetric.getStatus().isKilled()) {
-        // If it is not measurable and previously killed by Bazel, we don't do anything.
+      if (workerMetric.getStatus().isKilled()) {
+        // If it was previously killed by Bazel, we don't do anything.
         workerMetrics.add(workerMetric);
         continue;
-      } else if (!isMeasurable) {
+      }
+
+      Long pid = workerMetric.getProcessId();
+      int memoryInKb = pidToMemoryInKb.getOrDefault(pid, 0);
+
+      if (memoryInKb == 0) {
         // If it is not measurable, not killed by Bazel but has executed actions, then we assume
         // that something has happened to the worker process that is not accounted for by Bazel
         // and set this to KILLED_UNKNOWN. If a separate thread comes along to update the status
@@ -121,8 +163,7 @@
 
       // If it is measurable, we want to update the collected metrics.
       workerMetric.addCollectedMetrics(
-          /* memoryInKb= */ pidToMemoryInKb.getOrDefault(pid, 0),
-          /* collectionTime= */ collectionTime);
+          /* memoryInKb= */ memoryInKb, /* collectionTime= */ collectionTime);
       workerMetrics.add(workerMetric);
     }
 
@@ -130,7 +171,7 @@
   }
 
   public void onWorkerFinishExecution(long processId) {
-    WorkerProcessMetrics wpm = processIdToWorkerProcessMetrics.get(processId);
+    WorkerProcessMetrics wpm = pidToWorkerProcessMetrics.get(processId);
     if (wpm == null) {
       return;
     }
@@ -140,15 +181,6 @@
   public static final int MAX_PUBLISHED_WORKER_METRICS = 50;
 
   /** Returns a prioritized and limited list of WorkerMetrics to be published to the BEP. */
-  public ImmutableList<WorkerMetrics> getPublishedWorkerMetrics() {
-    return collectMetrics().stream()
-        .map(WorkerProcessMetrics::toProto)
-        .sorted(new WorkerMetricsPublishComparator())
-        .limit(MAX_PUBLISHED_WORKER_METRICS)
-        .sorted(Comparator.comparingInt(m -> m.getWorkerIdsList().get(0)))
-        .collect(toImmutableList());
-  }
-
   public static ImmutableList<WorkerMetrics> limitWorkerMetricsToPublish(
       ImmutableList<WorkerMetrics> metrics, int limit) {
     return metrics.stream()
@@ -197,12 +229,12 @@
   }
 
   public void clear() {
-    processIdToWorkerProcessMetrics.clear();
+    pidToWorkerProcessMetrics.clear();
   }
 
   @VisibleForTesting
-  Map<Long, WorkerProcessMetrics> getProcessIdToWorkerProcessMetrics() {
-    return processIdToWorkerProcessMetrics;
+  Map<Long, WorkerProcessMetrics> getPidToWorkerProcessMetrics() {
+    return pidToWorkerProcessMetrics;
   }
 
   /**
@@ -216,9 +248,10 @@
       String mnemonic,
       boolean isMultiplex,
       boolean isSandboxed,
-      int workerKeyHash) {
+      int workerKeyHash,
+      @Nullable CgroupsInfo cgroup) {
     WorkerProcessMetrics workerMetric =
-        processIdToWorkerProcessMetrics.computeIfAbsent(
+        pidToWorkerProcessMetrics.computeIfAbsent(
             processId,
             (pid) ->
                 new WorkerProcessMetrics(
@@ -229,7 +262,9 @@
                     isMultiplex,
                     isSandboxed,
                     workerKeyHash));
-
+    if (cgroup != null) {
+      pidToCgroups.putIfAbsent(processId, cgroup);
+    }
     workerMetric.setLastCallTime(Instant.ofEpochMilli(clock.currentTimeMillis()));
     workerMetric.maybeAddWorkerId(workerId, status);
   }
@@ -237,17 +272,17 @@
   /** Removes all WorkerProcessMetrics that were marked as killed. */
   public void clearKilledWorkerProcessMetrics() {
     List<Long> pidsToRemove = new ArrayList<>();
-    for (Map.Entry<Long, WorkerProcessMetrics> entry : processIdToWorkerProcessMetrics.entrySet()) {
+    for (Map.Entry<Long, WorkerProcessMetrics> entry : pidToWorkerProcessMetrics.entrySet()) {
       if (entry.getValue().getStatus().isKilled()) {
         pidsToRemove.add(entry.getKey());
       }
     }
-    processIdToWorkerProcessMetrics.keySet().removeAll(pidsToRemove);
+    pidToWorkerProcessMetrics.keySet().removeAll(pidsToRemove);
   }
 
   /** To reset states in each WorkerProcessMetric before each command where applicable. */
   public void beforeCommand() {
-    processIdToWorkerProcessMetrics.values().forEach(m -> m.onBeforeCommand());
+    pidToWorkerProcessMetrics.values().forEach(m -> m.onBeforeCommand());
   }
 
   // TODO(b/238416583) Add deregister function
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
index dbc5073..599b881 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
@@ -616,7 +616,8 @@
         workerKey.getMnemonic(),
         workerKey.isMultiplex(),
         workerKey.isSandboxed(),
-        workerKey.hashCode());
+        workerKey.hashCode(),
+        worker.getCgroup());
   }
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/actions/BUILD b/src/test/java/com/google/devtools/build/lib/actions/BUILD
index d784af0..c4dad0c 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/actions/BUILD
@@ -68,13 +68,13 @@
         "//src/main/java/com/google/devtools/build/lib/testing/common:directory_listing_helper",
         "//src/main/java/com/google/devtools/build/lib/util",
         "//src/main/java/com/google/devtools/build/lib/util:filetype",
-        "//src/main/java/com/google/devtools/build/lib/util:string",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
         "//src/main/java/com/google/devtools/build/lib/worker",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_factory",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_key",
+        "//src/main/java/com/google/devtools/build/lib/worker:worker_options",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_pool_impl",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_process_status",
         "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ResourceManagerTest.java b/src/test/java/com/google/devtools/build/lib/actions/ResourceManagerTest.java
index bb626e6..9cc18f7 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ResourceManagerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ResourceManagerTest.java
@@ -45,6 +45,7 @@
 import com.google.devtools.build.lib.worker.Worker;
 import com.google.devtools.build.lib.worker.WorkerFactory;
 import com.google.devtools.build.lib.worker.WorkerKey;
+import com.google.devtools.build.lib.worker.WorkerOptions;
 import com.google.devtools.build.lib.worker.WorkerPoolImpl;
 import com.google.devtools.build.lib.worker.WorkerProcessStatus;
 import com.google.devtools.build.lib.worker.WorkerProcessStatus.Status;
@@ -95,7 +96,7 @@
   private WorkerPoolImpl createWorkerPool() {
     return new WorkerPoolImpl(
         new WorkerPoolImpl.WorkerPoolConfig(
-            new WorkerFactory(fs.getPath("/workerBase")) {
+            new WorkerFactory(fs.getPath("/workerBase"), new WorkerOptions()) {
               @Override
               public Worker create(WorkerKey key) {
                 return worker;
diff --git a/src/test/java/com/google/devtools/build/lib/worker/BUILD b/src/test/java/com/google/devtools/build/lib/worker/BUILD
index 73d83aa..5379b61 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/worker/BUILD
@@ -43,8 +43,6 @@
         "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/actions:execution_requirements",
         "//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity",
-        "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
-        "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_helpers",
         "//src/main/java/com/google/devtools/build/lib/shell",
@@ -54,7 +52,6 @@
         "//src/main/java/com/google/devtools/build/lib/worker:worker_key",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_options",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_spawn_runner",
-        "//src/main/java/net/starlark/java/syntax",
         "//src/test/java/com/google/devtools/build/lib/actions/util",
         "//third_party:guava",
     ],
@@ -103,6 +100,7 @@
         "//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_helpers",
         "//src/main/java/com/google/devtools/build/lib/sandbox:tree_deleter",
         "//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
+        "//src/main/java/com/google/devtools/build/lib/util:os",
         "//src/main/java/com/google/devtools/build/lib/util:resource_converter",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
diff --git a/src/test/java/com/google/devtools/build/lib/worker/SandboxedWorkerProxyTest.java b/src/test/java/com/google/devtools/build/lib/worker/SandboxedWorkerProxyTest.java
index 1336f16..692b0b8 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/SandboxedWorkerProxyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/SandboxedWorkerProxyTest.java
@@ -162,7 +162,7 @@
     WorkerKey key =
         TestUtils.createWorkerKeyFromOptions(
             PROTO, globalOutputBase, options, true, spawn, "worker.sh");
-    WorkerFactory factory = new WorkerFactory(workerBaseDir);
+    WorkerFactory factory = new WorkerFactory(workerBaseDir, options);
     return (SandboxedWorkerProxy) factory.create(key);
   }
 
@@ -187,7 +187,7 @@
             super.process = new FakeSubprocess(serverInputStream);
           }
         });
-    WorkerFactory factory = new WorkerFactory(workerBaseDir);
+    WorkerFactory factory = new WorkerFactory(workerBaseDir, options);
     return (SandboxedWorkerProxy) factory.create(key);
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolTest.java b/src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolTest.java
index e060046..e2b4928 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolTest.java
@@ -36,16 +36,17 @@
   private static FileSystem fileSystem;
 
   private static class TestWorker extends SingleplexWorker {
-    TestWorker(WorkerKey workerKey, int workerId, Path workDir, Path logFile) {
-      super(workerKey, workerId, workDir, logFile);
+    TestWorker(
+        WorkerKey workerKey, int workerId, Path workDir, Path logFile, WorkerOptions options) {
+      super(workerKey, workerId, workDir, logFile, options);
     }
   }
 
   private static class TestWorkerFactory extends WorkerFactory {
     private int workerIds = 1;
 
-    public TestWorkerFactory(Path workerBaseDir) {
-      super(workerBaseDir);
+    public TestWorkerFactory(Path workerBaseDir, WorkerOptions workerOptions) {
+      super(workerBaseDir, workerOptions);
     }
 
     @Override
@@ -55,7 +56,8 @@
               workerKey,
               workerIds++,
               fileSystem.getPath("/workDir"),
-              fileSystem.getPath("/logDir")));
+              fileSystem.getPath("/logDir"),
+              workerOptions));
     }
 
     @Override
@@ -67,7 +69,7 @@
   @Before
   public void setUp() throws Exception {
     fileSystem = new InMemoryFileSystem(BlazeClock.instance(), DigestHashFunction.SHA256);
-    workerFactory = new TestWorkerFactory(null);
+    workerFactory = new TestWorkerFactory(null, new WorkerOptions());
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/worker/TestUtils.java b/src/test/java/com/google/devtools/build/lib/worker/TestUtils.java
index 0a4783b..c7c4355 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/TestUtils.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/TestUtils.java
@@ -219,8 +219,9 @@
         int workerId,
         final Path workDir,
         Path logFile,
-        FakeSubprocess fakeSubprocess) {
-      super(workerKey, workerId, workDir, logFile);
+        FakeSubprocess fakeSubprocess,
+        WorkerOptions options) {
+      super(workerKey, workerId, workDir, logFile, options);
       this.fakeSubprocess = fakeSubprocess;
     }
 
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java
index a920a07..00bf258 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java
@@ -51,7 +51,7 @@
   @Test
   public void sandboxedWorkerPathEndsWithWorkspaceName() throws Exception {
     Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
-    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir);
+    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, new WorkerOptions());
     WorkerKey workerKey = createWorkerKey(/* mustBeSandboxed= */ true, /* multiplex= */ false);
     Path sandboxedWorkerPath = workerFactory.getSandboxedWorkerPath(workerKey, 1);
 
@@ -62,7 +62,7 @@
   @Test
   public void workerCreationTypeCheck() throws Exception {
     Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
-    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir);
+    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, new WorkerOptions());
     WorkerKey sandboxedWorkerKey =
         createWorkerKey(/* mustBeSandboxed= */ true, /* multiplex= */ false);
     Worker sandboxedWorker = workerFactory.create(sandboxedWorkerKey);
@@ -86,7 +86,7 @@
   @Test
   public void testMultiplexWorkersShareLogfiles() throws Exception {
     Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
-    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir);
+    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, new WorkerOptions());
 
     WorkerKey workerKey1 =
         createWorkerKey(/* mustBeSandboxed= */ false, /* multiplex= */ true, "arg1");
@@ -103,7 +103,7 @@
   @Test
   public void testCreate_createsWorkerDirectory() throws Exception {
     Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
-    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir);
+    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, new WorkerOptions());
     WorkerKey sandboxedWorkerKey = createWorkerKey(/* mustBeSandboxed */ true, /* proxied */ false);
     assertThat(workerBaseDir.isDirectory()).isFalse();
     workerFactory.create(sandboxedWorkerKey);
@@ -118,7 +118,7 @@
   @Test
   public void testDoomedWorkerValidation() throws Exception {
     Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
-    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir);
+    WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, new WorkerOptions());
 
     WorkerKey workerKey =
         createWorkerKey(/* mustBeSandboxed= */ false, /* multiplex= */ false, "arg1");
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerLifecycleManagerTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerLifecycleManagerTest.java
index c068be2..44d928c 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerLifecycleManagerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerLifecycleManagerTest.java
@@ -58,7 +58,7 @@
   @Before
   public void setUp() throws Exception {
     fileSystem = new InMemoryFileSystem(BlazeClock.instance(), DigestHashFunction.SHA256);
-
+    WorkerOptions options = new WorkerOptions();
     doAnswer(
             args -> {
               WorkerKey key = args.getArgument(0);
@@ -78,7 +78,8 @@
                       key,
                       workerIds++,
                       fileSystem.getPath("/workDir"),
-                      fileSystem.getPath("/logDir")));
+                      fileSystem.getPath("/logDir"),
+                      options));
             })
         .when(factoryMock)
         .makeObject(any());
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java
index 9c3ac4f..0d46475 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java
@@ -41,6 +41,8 @@
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.common.options.Options;
+import com.google.devtools.common.options.OptionsParsingException;
 import com.google.devtools.common.options.OptionsParsingResult;
 import java.io.IOException;
 import org.junit.Rule;
@@ -141,6 +143,35 @@
   }
 
   @Test
+  public void buildStarting_restartsOnUseCgroupsOnLinuxChanges()
+      throws IOException, AbruptExitException, OptionsParsingException {
+    WorkerModule module = new WorkerModule();
+    WorkerOptions options =
+        Options.parse(WorkerOptions.class, "--noexperimental_worker_use_cgroups_on_linux")
+            .getOptions();
+    when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+    setupEnvironment("/outputRoot");
+
+    module.beforeCommand(env);
+    // Check that new pools/factories are made with default options
+    module.buildStarting(BuildStartingEvent.create(env, request));
+    assertThat(storedEventHandler.getEvents()).isEmpty();
+
+    WorkerPool oldPool = module.workerPool;
+    WorkerOptions newOptions =
+        Options.parse(WorkerOptions.class, "--experimental_worker_use_cgroups_on_linux")
+            .getOptions();
+    when(request.getOptions(WorkerOptions.class)).thenReturn(newOptions);
+
+    module.beforeCommand(env);
+    module.buildStarting(BuildStartingEvent.create(env, request));
+    assertThat(storedEventHandler.getEvents()).hasSize(1);
+    assertThat(storedEventHandler.getEvents().get(0).getMessage())
+        .contains("Worker factory configuration has changed");
+    assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+  }
+
+  @Test
   public void buildStarting_clearsLogsOnFactoryCreation() throws IOException, AbruptExitException {
     WorkerModule module = new WorkerModule();
     WorkerOptions options = WorkerOptions.DEFAULTS;
@@ -160,7 +191,6 @@
     assertThat(oldLog.exists()).isFalse();
   }
 
-
   @Test
   public void buildStarting_restartsOnNumMultiplexWorkersChanges()
       throws IOException, AbruptExitException {
@@ -228,7 +258,7 @@
     workerDir.getParentDirectory().setWritable(false);
 
     // But an actual worker cannot be created.
-    WorkerKey key = TestUtils.createWorkerKey(fs, "Work", /* proxied=*/ false);
+    WorkerKey key = TestUtils.createWorkerKey(fs, "Work", /* proxied= */ false);
     assertThrows(IOException.class, () -> module.workerPool.borrowObject(key));
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java
index 17212b0..da1327a 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java
@@ -49,9 +49,12 @@
   private FileSystem fileSystem;
   private int workerIds = 1;
 
+  private final WorkerOptions options = new WorkerOptions();
+
   private static class TestWorker extends SingleplexWorker {
-    TestWorker(WorkerKey workerKey, int workerId, Path workDir, Path logFile) {
-      super(workerKey, workerId, workDir, logFile);
+    TestWorker(
+        WorkerKey workerKey, int workerId, Path workDir, Path logFile, WorkerOptions options) {
+      super(workerKey, workerId, workDir, logFile, options);
     }
   }
 
@@ -65,7 +68,8 @@
                         arg.getArgument(0),
                         workerIds++,
                         fileSystem.getPath("/workDir"),
-                        fileSystem.getPath("/logDir"))))
+                        fileSystem.getPath("/logDir"),
+                        options)))
         .when(factoryMock)
         .makeObject(any());
     doAnswer(
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollectorTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollectorTest.java
index bdfe29c..e1d6404 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollectorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerProcessMetricsCollectorTest.java
@@ -18,14 +18,20 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.WorkerMetrics;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.WorkerMetrics.WorkerStatus;
 import com.google.devtools.build.lib.clock.Clock;
+import com.google.devtools.build.lib.metrics.CgroupsInfoCollector;
+import com.google.devtools.build.lib.metrics.PsInfoCollector;
 import com.google.devtools.build.lib.metrics.ResourceSnapshot;
+import com.google.devtools.build.lib.util.OS;
 import com.google.devtools.build.lib.worker.WorkerProcessMetricsCollector.WorkerMetricsPublishComparator;
 import com.google.devtools.build.lib.worker.WorkerProcessStatus.Status;
 import java.time.Instant;
@@ -38,12 +44,17 @@
 @RunWith(JUnit4.class)
 public class WorkerProcessMetricsCollectorTest {
 
-  private final WorkerProcessMetricsCollector spyCollector =
-      spy(WorkerProcessMetricsCollector.instance());
+  private PsInfoCollector psInfoCollector;
+  private CgroupsInfoCollector cgroupsInfoCollector;
+
+  private WorkerProcessMetricsCollector spyCollector;
   ManualClock clock = new ManualClock();
 
   @Before
   public void setUp() {
+    psInfoCollector = mock(PsInfoCollector.class);
+    cgroupsInfoCollector = mock(CgroupsInfoCollector.class);
+    spyCollector = spy(new WorkerProcessMetricsCollector(psInfoCollector, cgroupsInfoCollector));
     spyCollector.clear();
     spyCollector.setClock(clock);
   }
@@ -106,9 +117,9 @@
         JAVAC_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ false,
-        WORKER_KEY_HASH_1);
-    assertThat(spyCollector.getProcessIdToWorkerProcessMetrics().keySet())
-        .containsExactly(PROCESS_ID_1);
+        WORKER_KEY_HASH_1,
+        /* cgroup= */ null);
+    assertThat(spyCollector.getPidToWorkerProcessMetrics().keySet()).containsExactly(PROCESS_ID_1);
     spyCollector.registerWorker(
         WORKER_ID_2,
         PROCESS_ID_2,
@@ -116,11 +127,12 @@
         CPP_COMPILE_MNEMONIC,
         /* isMultiplex= */ false,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_2);
-    assertThat(spyCollector.getProcessIdToWorkerProcessMetrics().keySet())
+        WORKER_KEY_HASH_2,
+        /* cgroup= */ null);
+    assertThat(spyCollector.getPidToWorkerProcessMetrics().keySet())
         .containsExactly(PROCESS_ID_1, PROCESS_ID_2);
     assertWorkerMetricContains(
-        spyCollector.getProcessIdToWorkerProcessMetrics().get(PROCESS_ID_1),
+        spyCollector.getPidToWorkerProcessMetrics().get(PROCESS_ID_1),
         ImmutableList.of(WORKER_ID_1),
         PROCESS_ID_1,
         JAVAC_MNEMONIC,
@@ -132,7 +144,7 @@
         /* expectedLastCallTime= */ DEFAULT_CLOCK_START_INSTANT,
         /* expectedCollectedTime= */ null);
     assertWorkerMetricContains(
-        spyCollector.getProcessIdToWorkerProcessMetrics().get(PROCESS_ID_2),
+        spyCollector.getPidToWorkerProcessMetrics().get(PROCESS_ID_2),
         ImmutableList.of(WORKER_ID_2),
         PROCESS_ID_2,
         CPP_COMPILE_MNEMONIC,
@@ -154,11 +166,11 @@
         JAVAC_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_1);
-    assertThat(spyCollector.getProcessIdToWorkerProcessMetrics().keySet())
-        .containsExactly(PROCESS_ID_1);
+        WORKER_KEY_HASH_1,
+        /* cgroup= */ null);
+    assertThat(spyCollector.getPidToWorkerProcessMetrics().keySet()).containsExactly(PROCESS_ID_1);
     assertWorkerMetricContains(
-        spyCollector.getProcessIdToWorkerProcessMetrics().get(PROCESS_ID_1),
+        spyCollector.getPidToWorkerProcessMetrics().get(PROCESS_ID_1),
         ImmutableList.of(WORKER_ID_1),
         PROCESS_ID_1,
         JAVAC_MNEMONIC,
@@ -180,11 +192,11 @@
         JAVAC_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_1);
-    assertThat(spyCollector.getProcessIdToWorkerProcessMetrics().keySet())
-        .containsExactly(PROCESS_ID_1);
+        WORKER_KEY_HASH_1,
+        /* cgroup= */ null);
+    assertThat(spyCollector.getPidToWorkerProcessMetrics().keySet()).containsExactly(PROCESS_ID_1);
     assertWorkerMetricContains(
-        spyCollector.getProcessIdToWorkerProcessMetrics().get(PROCESS_ID_1),
+        spyCollector.getPidToWorkerProcessMetrics().get(PROCESS_ID_1),
         ImmutableList.of(WORKER_ID_1, WORKER_ID_2),
         PROCESS_ID_1,
         JAVAC_MNEMONIC,
@@ -206,11 +218,11 @@
         JAVAC_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_1);
-    assertThat(spyCollector.getProcessIdToWorkerProcessMetrics().keySet())
-        .containsExactly(PROCESS_ID_1);
+        WORKER_KEY_HASH_1,
+        /* cgroup= */ null);
+    assertThat(spyCollector.getPidToWorkerProcessMetrics().keySet()).containsExactly(PROCESS_ID_1);
     assertWorkerMetricContains(
-        spyCollector.getProcessIdToWorkerProcessMetrics().get(PROCESS_ID_1),
+        spyCollector.getPidToWorkerProcessMetrics().get(PROCESS_ID_1),
         ImmutableList.of(WORKER_ID_1),
         PROCESS_ID_1,
         JAVAC_MNEMONIC,
@@ -233,11 +245,11 @@
         JAVAC_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_1);
-    assertThat(spyCollector.getProcessIdToWorkerProcessMetrics().keySet())
-        .containsExactly(PROCESS_ID_1);
+        WORKER_KEY_HASH_1,
+        /* cgroup= */ null);
+    assertThat(spyCollector.getPidToWorkerProcessMetrics().keySet()).containsExactly(PROCESS_ID_1);
     assertWorkerMetricContains(
-        spyCollector.getProcessIdToWorkerProcessMetrics().get(PROCESS_ID_1),
+        spyCollector.getPidToWorkerProcessMetrics().get(PROCESS_ID_1),
         ImmutableList.of(WORKER_ID_1),
         PROCESS_ID_1,
         JAVAC_MNEMONIC,
@@ -260,8 +272,9 @@
         JAVAC_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ false,
-        WORKER_KEY_HASH_1);
-    WorkerProcessMetricsCollector.instance().onWorkerFinishExecution(PROCESS_ID_1);
+        WORKER_KEY_HASH_1,
+        /* cgroup= */ null);
+    spyCollector.onWorkerFinishExecution(PROCESS_ID_1);
     // Worker 2 simulates a measurable worker process that has not yet completed execution of any
     // actions.
     spyCollector.registerWorker(
@@ -271,7 +284,8 @@
         CPP_COMPILE_MNEMONIC,
         /* isMultiplex= */ false,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_2);
+        WORKER_KEY_HASH_2,
+        /* cgroup= */ null);
     // Worker 3 simulates a non-measurable worker that has not executed any actions.
     spyCollector.registerWorker(
         WORKER_ID_3,
@@ -280,7 +294,8 @@
         PROTO_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_3);
+        WORKER_KEY_HASH_3,
+        /* cgroup= */ null);
     // Worker 4 simulates a non-measurable worker that has executed an action and was killed.
     WorkerProcessStatus s4 = new WorkerProcessStatus();
     spyCollector.registerWorker(
@@ -290,8 +305,9 @@
         PROTO_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_4);
-    WorkerProcessMetricsCollector.instance().onWorkerFinishExecution(PROCESS_ID_4);
+        WORKER_KEY_HASH_4,
+        /* cgroup= */ null);
+    spyCollector.onWorkerFinishExecution(PROCESS_ID_4);
     s4.maybeUpdateStatus(Status.KILLED_DUE_TO_MEMORY_PRESSURE);
     // Worker 5 simulates a non-measurable worker that has executed an action, but was not killed.
     WorkerProcessStatus s5 = new WorkerProcessStatus();
@@ -302,8 +318,9 @@
         PROTO_MNEMONIC,
         /* isMultiplex= */ true,
         /* isSandboxed= */ true,
-        WORKER_KEY_HASH_5);
-    WorkerProcessMetricsCollector.instance().onWorkerFinishExecution(PROCESS_ID_5);
+        WORKER_KEY_HASH_5,
+        /* cgroup= */ null);
+    spyCollector.onWorkerFinishExecution(PROCESS_ID_5);
 
     ImmutableMap<Long, Integer> memoryUsageMap =
         ImmutableMap.of(
@@ -311,7 +328,7 @@
             PROCESS_ID_2, 2345);
     Instant collectionTime = DEFAULT_CLOCK_START_INSTANT.plusSeconds(10);
     ResourceSnapshot resourceSnapshot = ResourceSnapshot.create(memoryUsageMap, collectionTime);
-    doReturn(resourceSnapshot).when(spyCollector).collectResourceUsage(any(), any());
+    doReturn(resourceSnapshot).when(spyCollector).collectResourceUsage();
     clock.setTime(collectionTime.toEpochMilli());
 
     ImmutableList<WorkerProcessMetrics> metrics = spyCollector.collectMetrics();
@@ -320,7 +337,7 @@
     assertThat(metrics.stream().flatMap(m -> m.getWorkerIds().stream()).collect(toImmutableSet()))
         .containsExactly(WORKER_ID_1, WORKER_ID_2, WORKER_ID_3, WORKER_ID_4, WORKER_ID_5);
     assertWorkerMetricContains(
-        metrics.stream().filter(wm -> wm.getWorkerIds().contains(WORKER_ID_1)).findFirst().get(),
+        getWorkerProcessMetricsFromList(WORKER_ID_1, metrics),
         ImmutableList.of(WORKER_ID_1),
         PROCESS_ID_1,
         JAVAC_MNEMONIC,
@@ -332,7 +349,7 @@
         /* expectedLastCallTime= */ DEFAULT_CLOCK_START_INSTANT,
         /* expectedCollectedTime= */ collectionTime);
     assertWorkerMetricContains(
-        metrics.stream().filter(wm -> wm.getWorkerIds().contains(WORKER_ID_2)).findFirst().get(),
+        getWorkerProcessMetricsFromList(WORKER_ID_2, metrics),
         ImmutableList.of(WORKER_ID_2),
         PROCESS_ID_2,
         CPP_COMPILE_MNEMONIC,
@@ -344,9 +361,9 @@
         /* expectedLastCallTime= */ DEFAULT_CLOCK_START_INSTANT,
         /* expectedCollectedTime= */ collectionTime);
     // Worker 3's metrics should not be included since it is both non-measurable and did not execute
-    // any actions. It's status shouldn't be unknown because it is possible that
+    // any actions. Its status shouldn't be unknown because it is possible that
     assertWorkerMetricContains(
-        metrics.stream().filter(wm -> wm.getWorkerIds().contains(WORKER_ID_3)).findFirst().get(),
+        getWorkerProcessMetricsFromList(WORKER_ID_3, metrics),
         ImmutableList.of(WORKER_ID_3),
         PROCESS_ID_3,
         PROTO_MNEMONIC,
@@ -358,7 +375,7 @@
         /* expectedLastCallTime= */ DEFAULT_CLOCK_START_INSTANT,
         /* expectedCollectedTime= */ null);
     assertWorkerMetricContains(
-        metrics.stream().filter(wm -> wm.getWorkerIds().contains(WORKER_ID_4)).findFirst().get(),
+        getWorkerProcessMetricsFromList(WORKER_ID_4, metrics),
         ImmutableList.of(WORKER_ID_4),
         PROCESS_ID_4,
         PROTO_MNEMONIC,
@@ -370,7 +387,7 @@
         /* expectedLastCallTime= */ DEFAULT_CLOCK_START_INSTANT,
         /* expectedCollectedTime= */ null);
     assertWorkerMetricContains(
-        metrics.stream().filter(wm -> wm.getWorkerIds().contains(WORKER_ID_5)).findFirst().get(),
+        getWorkerProcessMetricsFromList(WORKER_ID_5, metrics),
         ImmutableList.of(WORKER_ID_5),
         PROCESS_ID_5,
         PROTO_MNEMONIC,
@@ -387,6 +404,76 @@
   }
 
   @Test
+  public void testCollectResourceUsage_windows() {
+    Instant collectionTime = DEFAULT_CLOCK_START_INSTANT.plusSeconds(10);
+    clock.setTime(collectionTime.toEpochMilli());
+    when(psInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 1000), collectionTime));
+    when(cgroupsInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 2000), collectionTime));
+
+    ResourceSnapshot snapshot =
+        spyCollector.collectResourceUsage(OS.WINDOWS, ImmutableSet.of(PROCESS_ID_1));
+
+    // On non-linux and non-darwin, it should always return an empty snapshot.
+    assertThat(snapshot).isEqualTo(ResourceSnapshot.create(ImmutableMap.of(), collectionTime));
+  }
+
+  @Test
+  public void testCollectResourceUsage_darwin_usingPs() {
+    Instant collectionTime = DEFAULT_CLOCK_START_INSTANT.plusSeconds(10);
+    clock.setTime(collectionTime.toEpochMilli());
+    when(psInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 1000), collectionTime));
+    when(cgroupsInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 2000), collectionTime));
+
+    ResourceSnapshot snapshot =
+        spyCollector.collectResourceUsage(OS.DARWIN, ImmutableSet.of(PROCESS_ID_1));
+
+    // Should return the cgroup information rather than the ps information.
+    assertThat(snapshot)
+        .isEqualTo(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 1000), collectionTime));
+  }
+
+  @Test
+  public void testCollectResourceUsage_linux_usingPs() {
+    spyCollector.setUseCgroupsOnLinux(/* useCgroupsOnLinux= */ false);
+    Instant collectionTime = DEFAULT_CLOCK_START_INSTANT.plusSeconds(10);
+    clock.setTime(collectionTime.toEpochMilli());
+    when(psInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 1000), collectionTime));
+    when(cgroupsInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 2000), collectionTime));
+
+    ResourceSnapshot snapshot =
+        spyCollector.collectResourceUsage(OS.LINUX, ImmutableSet.of(PROCESS_ID_1));
+
+    // Should return the ps information rather than the cgroup information.
+    assertThat(snapshot)
+        .isEqualTo(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 1000), collectionTime));
+  }
+
+  @Test
+  public void testCollectResourceUsage_linux_usingCgroups() {
+
+    spyCollector.setUseCgroupsOnLinux(/* useCgroupsOnLinux= */ true);
+    Instant collectionTime = DEFAULT_CLOCK_START_INSTANT.plusSeconds(10);
+    clock.setTime(collectionTime.toEpochMilli());
+    when(psInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 1000), collectionTime));
+    when(cgroupsInfoCollector.collectResourceUsage(any(), any()))
+        .thenReturn(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 2000), collectionTime));
+
+    ResourceSnapshot snapshot =
+        spyCollector.collectResourceUsage(OS.LINUX, ImmutableSet.of(PROCESS_ID_1));
+
+    // Should return the cgroup information rather than the ps information.
+    assertThat(snapshot)
+        .isEqualTo(ResourceSnapshot.create(ImmutableMap.of(PROCESS_ID_1, 2000), collectionTime));
+  }
+
+  @Test
   public void testWorkerMetricsPublishComparator_compare() {
     WorkerMetrics alive1 = newWorkerMetrics(1, WorkerStatus.ALIVE, 100);
     WorkerMetrics alive2 = newWorkerMetrics(2, WorkerStatus.ALIVE, 200);
@@ -445,6 +532,11 @@
         .build();
   }
 
+  private WorkerProcessMetrics getWorkerProcessMetricsFromList(
+      int workerId, ImmutableList<WorkerProcessMetrics> metrics) {
+    return metrics.stream().filter(wm -> wm.getWorkerIds().contains(workerId)).findFirst().get();
+  }
+
   private static final long DEFAULT_CLOCK_START_TIME = 0L;
   private static final Instant DEFAULT_CLOCK_START_INSTANT =
       Instant.ofEpochMilli(DEFAULT_CLOCK_START_TIME);
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
index ba549aa..486e6f9 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
@@ -108,7 +108,7 @@
     doNothing()
         .when(metricsCollector)
         .registerWorker(
-            anyInt(), anyLong(), any(), anyString(), anyBoolean(), anyBoolean(), anyInt());
+            anyInt(), anyLong(), any(), anyString(), anyBoolean(), anyBoolean(), anyInt(), any());
     when(spawn.getLocalResources()).thenReturn(ResourceSet.createWithRamCpu(100, 1));
     when(resourceManager.acquireResources(any(), any(), any())).thenReturn(resourceHandle);
     when(resourceHandle.getWorker()).thenReturn(worker);
@@ -117,7 +117,7 @@
   private WorkerPoolImpl createWorkerPool() {
     return new WorkerPoolImpl(
         new WorkerPoolConfig(
-            new WorkerFactory(fs.getPath("/workerBase")) {
+            new WorkerFactory(fs.getPath("/workerBase"), options) {
               @Override
               public Worker create(WorkerKey key) {
                 return worker;
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerTest.java
index f45bca2..05b9336 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerTest.java
@@ -50,6 +50,7 @@
   final FileSystem fs = new InMemoryFileSystem(DigestHashFunction.SHA256);
 
   private TestWorker workerForCleanup = null;
+  private final WorkerOptions options = new WorkerOptions();
 
   @After
   public void destroyWorker() throws IOException {
@@ -78,7 +79,8 @@
     int workerId = 1;
     Path logFile = workerBaseDir.getRelative("test-log-file.log");
 
-    TestWorker worker = new TestWorker(key, workerId, key.getExecRoot(), logFile, fakeSubprocess);
+    TestWorker worker =
+        new TestWorker(key, workerId, key.getExecRoot(), logFile, fakeSubprocess, options);
 
     SandboxInputs sandboxInputs = null;
     SandboxOutputs sandboxOutputs = null;
diff --git a/src/test/shell/integration/BUILD b/src/test/shell/integration/BUILD
index 81dbdfd..f1056c2 100644
--- a/src/test/shell/integration/BUILD
+++ b/src/test/shell/integration/BUILD
@@ -513,6 +513,25 @@
 )
 
 sh_test(
+    name = "bazel_worker_with_cgroups_test",
+    size = "large",
+    srcs = ["bazel_worker_test.sh"],
+    args = [
+        "'--worker_sandboxing=no --experimental_worker_use_cgroups_on_linux'",
+        "non-sandboxed",
+        "proto",
+    ],
+    data = [
+        ":test-deps",
+        "//src/test/java/com/google/devtools/build/lib/worker:ExampleWorker_deploy.jar",
+    ],
+    shard_count = 3,
+    tags = [
+        "no_windows",
+    ],
+)
+
+sh_test(
     name = "bazel_json_worker_test",
     size = "large",
     srcs = ["bazel_worker_test.sh"],
@@ -588,6 +607,25 @@
 )
 
 sh_test(
+    name = "bazel_sandboxed_worker_with_cgroups_test",
+    size = "large",
+    srcs = ["bazel_worker_test.sh"],
+    args = [
+        "'--worker_sandboxing --experimental_worker_use_cgroups_on_linux'",
+        "sandboxed",
+        "proto",
+    ],
+    data = [
+        ":test-deps",
+        "//src/test/java/com/google/devtools/build/lib/worker:ExampleWorker_deploy.jar",
+    ],
+    shard_count = 3,
+    tags = [
+        "no_windows",
+    ],
+)
+
+sh_test(
     name = "server_logging_test",
     size = "medium",
     srcs = ["server_logging_test.sh"],
diff --git a/src/test/shell/integration/linux-sandbox_test.sh b/src/test/shell/integration/linux-sandbox_test.sh
index 8ce61d0..300a5c8 100755
--- a/src/test/shell/integration/linux-sandbox_test.sh
+++ b/src/test/shell/integration/linux-sandbox_test.sh
@@ -397,7 +397,7 @@
   assert_equals 137 "$code" # SIGNAL_BASE + SIGTERM = 128 + 9
 }
 
-# Tests that using cgruops v1 with linux_sandbox.cc works, if it's available
+# Tests that using cgroups v1 with linux_sandbox.cc works, if it's available
 function test_cgroups1_memory_limit() {
   if ! grep -E '^cgroup +[^ ]+ +cgroup +.*memory.*' /proc/mounts; then
     echo "No cgroup memory controller mounted, skipping test"