Skip to content

Commit

Permalink
CGroups -> cgroups
Browse files Browse the repository at this point in the history
  • Loading branch information
HenrikBengtsson committed Nov 6, 2024
1 parent 9fd3aa3 commit 661c6fc
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 42 deletions.
20 changes: 10 additions & 10 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Version (development version)

## New Features
## Miscellaneous

* Now `availableCores()` recognizes also CGroups v2 `cpu.max` CPU
restrictions.
* Improved how cgroups v1 and v2 settings are queried.

## Miscellaneous
## Bug Fixes

* Improved how CGroups v1 and CGroups v2 settings are queried.
* Now `availableCores()` does a better job detecting cgroups v2
`cpu.max` CPU restrictions.


# Version 1.38.0 [2024-07-27]
Expand Down Expand Up @@ -204,11 +204,11 @@
such as `system2("Rscript --version")`. If not, an informative
error message is produced.

* On Unix, `availableCores()` queries also control groups v2
(cgroups2) field `cpu.max` for a possible CPU quota allocation. If
a CPU quota is set, then the number of CPUs is rounded to the
nearest integer, unless its less that 0.5, in case it's rounded up
to a single CPU. An example, where cgroups CPU quotas can be set to
* On Unix, `availableCores()` queries also control groups v2 (cgroups
v2) field `cpu.max` for a possible CPU quota allocation. If a CPU
quota is set, then the number of CPUs is rounded to the nearest
integer, unless its less that 0.5, in case it's rounded up to a
single CPU. An example, where cgroups CPU quotas can be set to
limit the total CPU load, is with Linux containers, e.g. `docker
run --cpus=3.5 ...`.

Expand Down
12 changes: 6 additions & 6 deletions R/availableCores.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@
#' Query \code{\link[parallel]{detectCores}(logical = logical)}.
#'
#' \item `"cgroups.cpuset"` -
#' On Unix, query control group (cgroup) value \code{cpuset.set}.
#' On Unix, query control group (cgroup v1) value \code{cpuset.set}.
#'
#' \item `"cgroups.cpuquota"` -
#' On Unix, query control group (cgroup) value
#' On Unix, query control group (cgroup v1) value
#' \code{cpu.cfs_quota_us} / \code{cpu.cfs_period_us}.
#'
#' \item `"cgroups2.cpu.max"` -
Expand Down Expand Up @@ -369,18 +369,18 @@ availableCores <- function(constraints = NULL, methods = getOption2("parallelly.
## Number of cores available according to parallel::detectCores()
n <- detectCores(logical = logical)
} else if (method == "cgroups.cpuset") {
## Number of cores according to Unix Cgroups CPU set
## Number of cores according to Unix cgroups v1 CPU set
n <- length(getCGroups1CpuSet())
if (n == 0L) n <- NA_integer_
} else if (method == "cgroups.cpuquota") {
## Number of cores according to Unix Cgroups CPU quota
## Number of cores according to Unix cgroups v1 CPU quota
n <- getCGroups1CpuQuota()
if (!is.na(n)) {
n <- as.integer(floor(n + 0.5))
if (n == 0L) n <- 1L ## If CPU quota < 0.5, round up to one CPU
}
} else if (method == "cgroups2.cpu.max") {
## Number of cores according to Unix Cgroups v2 CPU max quota
## Number of cores according to Unix cgroups v2 CPU max quota
n <- getCGroups2CpuMax()
if (!is.na(n)) {
n <- as.integer(floor(n + 0.5))
Expand Down Expand Up @@ -551,7 +551,7 @@ getNproc <- function(ignore = c("OMP_NUM_THREADS", "OMP_THREAD_LIMIT")) {
#' running on the same machine. This also a problem on systems where R
#' gets allotted a specific number of CPU cores, which is the case on
#' high-performance compute (HPC) clusters, but also on other shared systems
#' that limits user processes via Linux Control Groups (CGroups).
#' that limits user processes via Linux Control Groups (cgroups).
#' For example, a free account on Posit Cloud is limited to a single
#' CPU core. Parallelizing with 32 workers when only having access to
#' a single core, will result in 3200% overuse and 32 concurrent R
Expand Down
52 changes: 26 additions & 26 deletions R/cgroups.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ getCGroupsRoot <- local({
# @param pid (integer) The ID of an existing process.
#
# @return A data frame with three columns:
# * `hierarchy_id` (integer): 0 for CGroups v2.
# * `controller` (string): The controller name for CGroups v1,
# but empty for CGroups v2.
# * `hierarchy_id` (integer): 0 for cgroups v2.
# * `controller` (string): The controller name for cgroups v1,
# but empty for cgroups v2.
# * `path` (string): The path to the CGroup in the hierarchy
# that the process is part of.
# If CGroups is not used, the an empty data.frame is returned.
# If cgroups is not used, the an empty data.frame is returned.
#
#' @importFrom utils file_test
getCGroups <- local({
Expand All @@ -75,17 +75,17 @@ getCGroups <- local({
data <- .cache[[pid_str]]
if (!is.null(data)) return(data)

## Get CGroups
## Get cgroups
file <- file.path("/proc", pid, "cgroup")

## CGroups is not set?
## cgroups is not set?
if (!file_test("-f", file)) {
data <- data.frame(hierarchy_id = integer(0L), controller = character(0L), path = character(0L))
.cache[[pid_str]] <- data
return(data)
}

## Parse CGroups lines <hierarchy ID>:<controller>:<path>
## Parse cgroups lines <hierarchy ID>:<controller>:<path>
bfr <- readLines(file, warn = FALSE)
pattern <- "^([[:digit:]]+):([^:]*):(.*)"
bfr <- grep(pattern, bfr, value = TRUE)
Expand Down Expand Up @@ -120,13 +120,13 @@ getCGroups <- local({
})


# Get the path to a specific CGroups controller
# Get the path to a specific cgroups controller
#
# @param controller (character) A CGroups v1 set or `""` for CGroups v2.
# @param controller (character) A cgroups v1 set or `""` for cgroups v2.
#
# @param pid (integer) The ID of an existing process.
#
# @return An character string to an existing CGroups folder.
# @return An character string to an existing cgroups folder.
# If no folder could be found, `NA_character_` is returned.
#
#' @importFrom utils file_test
Expand Down Expand Up @@ -164,13 +164,13 @@ getCGroupsPath <- function(controller, pid = Sys.getpid()) {
}


# Get all CGroups fields for a specific controller
# Get all cgroups fields for a specific controller
#
# @param controller (character) A CGroups v1 set or `""` for CGroups v2.
# @param controller (character) A cgroups v1 set or `""` for cgroups v2.
#
# @param pid (integer) The ID of an existing process.
#
# @return An character vector of CGroups fields.
# @return An character vector of cgroups fields.
# If no folder could be found, a`NA_character_` is returned.
getCGroupsFields <- function(controller, pid = Sys.getpid()) {
path <- getCGroupsPath(controller = controller, pid = pid)
Expand All @@ -188,9 +188,9 @@ getCGroups2Fields <- function(pid = Sys.getpid()) {
}


# Get the value of specific CGroups controller and field
# Get the value of specific cgroups controller and field
#
# @param controller (character) A CGroups v1 set or `""` for CGroups v2.
# @param controller (character) A cgroups v1 set, or `""` for cgroups v2.
#
# @param field (character) A cgroups field.
#
Expand All @@ -215,11 +215,11 @@ getCGroupsValue <- function(controller, field, pid = Sys.getpid()) {
}


# Get the value of specific CGroups v1 field
# Get the value of specific cgroups v1 field
#
# @param controller (character) A CGroups v1 set.
# @param controller (character) A cgroups v1 set.
#
# @param field (character) A CGroups v1 field.
# @param field (character) A cgroups v1 field.
#
# @param pid (integer) The ID of an existing process.
#
Expand All @@ -236,9 +236,9 @@ getCGroups1Value <- function(controller, field, pid = Sys.getpid()) {
}


# Get the value of specific CGroups v2 field
# Get the value of specific cgroups v2 field
#
# @param field (character) A CGroups v2 field.
# @param field (character) A cgroups v2 field.
#
# @param pid (integer) The ID of an existing process.
#
Expand All @@ -265,14 +265,14 @@ getCGroups2Value <- function(field, pid = Sys.getpid()) {
}


# Get CGroups version
# Get cgroups version
#
# @param pid (integer) The ID of an existing process.
#
# @return
# If the current process is under CGroups v1, then `1L` is returned.
# If it is under CGroups v2, then `2L` is returned.
# If not under CGroups control, then `-1L` is returned.
# If the current process is under cgroups v1, then `1L` is returned.
# If it is under cgroups v2, then `2L` is returned.
# If not under cgroups control, then `-1L` is returned.
getCGroupsVersion <- function(pid = Sys.getpid()) {
cgroups <- getCGroups(pid = pid)
if (nrow(cgroups) == 0) return(-1L)
Expand All @@ -285,7 +285,7 @@ getCGroupsVersion <- function(pid = Sys.getpid()) {
# --------------------------------------------------------------------------
# CGroups v1 CPU settings
# --------------------------------------------------------------------------
# Get CGroups v1 'cpuset.cpus'
# Get cgroups v1 'cpuset.cpus'
#
# @return An integer vector of CPU indices. If cgroups v1 field
# `cpuset.cpus` could not be queried, integer(0) is returned.
Expand Down Expand Up @@ -484,7 +484,7 @@ getCGroups2CpuMax <- function(pid = Sys.getpid()) {
if (!is.na(value)) {
max_cores <- parallel::detectCores(logical = TRUE)
if (!is.finite(value) || value <= 0.0 || value > max_cores) {
warning(sprintf("[INTERNAL]: Will ignore the CGroups v2 CPU quota, because it is out of range [1,%d]: %s", max_cores, value))
warning(sprintf("[INTERNAL]: Will ignore the cgroups v2 CPU quota, because it is out of range [1,%d]: %s", max_cores, value))
value <- NA_real_
}
}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

The **parallelly** package provides functions that enhance the **parallel** packages. For example, `availableCores()` gives the number of CPU cores available to your R process as given by R options and environment variables, including those set by job schedulers on high-performance compute (HPC) clusters. If R runs under 'cgroups' or in a Linux container, then their settings are acknowledges too. If nothing else is set, then it will fall back to `parallel::detectCores()`. Another example is `makeClusterPSOCK()`, which is backward compatible with `parallel::makePSOCKcluster()` while doing a better job in setting up remote cluster workers without having to know your local public IP address and configuring the firewall to do port-forwarding to your local computer. The functions and features added to this package are written to be backward compatible with the **parallel** package, such that they may be incorporated there later. The **parallelly** package comes with an open invitation for the R Core Team to adopt all or parts of its code into the **parallel** package.


## Feature Comparison 'parallelly' vs 'parallel'

| | parallelly | parallel |
Expand Down
1 change: 1 addition & 0 deletions incl/OVERVIEW.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
The **parallelly** package provides functions that enhance the **parallel** packages. For example, `availableCores()` gives the number of CPU cores available to your R process as given by R options and environment variables, including those set by job schedulers on high-performance compute (HPC) clusters. If R runs under 'cgroups' or in a Linux container, then their settings are acknowledges too. If nothing else is set, then it will fall back to `parallel::detectCores()`. Another example is `makeClusterPSOCK()`, which is backward compatible with `parallel::makePSOCKcluster()` while doing a better job in setting up remote cluster workers without having to know your local public IP address and configuring the firewall to do port-forwarding to your local computer. The functions and features added to this package are written to be backward compatible with the **parallel** package, such that they may be incorporated there later. The **parallelly** package comes with an open invitation for the R Core Team to adopt all or parts of its code into the **parallel** package.


## Feature Comparison 'parallelly' vs 'parallel'

| | parallelly | parallel |
Expand Down

0 comments on commit 661c6fc

Please sign in to comment.