#' Create a process handle
#'
#' @param pid Process id. Integer scalar. `NULL` means the current R
#' process.
#' @param time Start time of the process. Usually `NULL` and ps will query
#' the start time.
#' @return `ps_handle()` returns a process handle (class `ps_handle`).
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
ps_handle <- function(pid = NULL, time = NULL) {
if (!is.null(pid)) assert_pid(pid)
if (!is.null(time)) assert_time(time)
.Call(psll_handle, pid, time)
}
#' @rdname ps_handle
#' @export
as.character.ps_handle <- function(x, ...) {
pieces <- .Call(psll_format, x)
paste0("<ps::ps_handle> PID=", pieces[[2]], ", NAME=", pieces[[1]],
", AT=", format_unix_time(pieces[[3]]))
}
#' @param x Process handle.
#' @param ... Not used currently.
#'
#' @rdname ps_handle
#' @export
format.ps_handle <- function(x, ...) {
as.character(x, ...)
}
#' @rdname ps_handle
#' @export
print.ps_handle <- function(x, ...) {
cat(format(x, ...), "\n", sep = "")
invisible(x)
}
#' Pid of a process handle
#'
#' This function works even if the process has already finished.
#'
#' @param p Process handle.
#' @return Process id.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_pid(p)
#' ps_pid(p) == Sys.getpid()
ps_pid <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_pid, p)
}
#' Start time of a process
#'
#' The pid and the start time pair serves as the identifier of the process,
#' as process ids might be reused, but the chance of starting two processes
#' with identical ids within the resolution of the timer is minimal.
#'
#' This function works even if the process has already finished.
#'
#' @param p Process handle.
#' @return `POSIXct` object, start time, in GMT.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_create_time(p)
ps_create_time <- function(p = ps_handle()) {
assert_ps_handle(p)
format_unix_time(.Call(psll_create_time, p))
}
#' Checks whether a process is running
#'
#' It returns `FALSE` if the process has already finished.
#'
#' It uses the start time of the process to work around pid reuse. I.e.
# it returns the correct answer, even if the process has finished and
# its pid was reused.
#'
#' @param p Process handle.
#' @return Logical scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_is_running(p)
ps_is_running <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_is_running, p)
}
#' Parent pid or parent process of a process
#'
#' `ps_ppid()` returns the parent pid, `ps_parent()` returns a `ps_handle`
#' of the parent.
#'
#' On POSIX systems, if the parent process terminates, another process
#' (typically the pid 1 process) is marked as parent. `ps_ppid()` and
#' `ps_parent()` will return this process then.
#'
#' Both `ps_ppid()` and `ps_parent()` work for zombie processes.
#'
#' @param p Process handle.
#' @return `ps_ppid()` returns and integer scalar, the pid of the parent
#' of `p`. `ps_parent()` returns a `ps_handle`.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_ppid(p)
#' ps_parent(p)
ps_ppid <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_ppid, p)
}
#' @rdname ps_ppid
#' @export
ps_parent <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_parent, p)
}
#' Process name
#'
#' The name of the program, which is typically the name of the executable.
#'
#' On Unix this can change, e.g. via an exec*() system call.
#'
#' `ps_name()` works on zombie processes.
#'
#' @param p Process handle.
#' @return Character scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_name(p)
#' ps_exe(p)
#' ps_cmdline(p)
ps_name <- function(p = ps_handle()) {
assert_ps_handle(p)
n <- .Call(psll_name, p)
if (nchar(n) >= 15) {
## On UNIX the name gets truncated to the first 15 characters.
## If it matches the first part of the cmdline we return that
## one instead because it's usually more explicative.
## Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
cmdline <- tryCatch(
ps_cmdline(p),
error = function(e) NULL
)
if (!is.null(cmdline) && length(cmdline) > 0L) {
exname <- basename(cmdline[1])
if (str_starts_with(exname, n)) n <- exname
}
}
n
}
#' Full path of the executable of a process
#'
#' Path to the executable of the process. May also be an empty string or
#' `NA` if it cannot be determined.
#'
#' For a zombie process it throws a `zombie_process` error.
#'
#' @param p Process handle.
#' @return Character scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_name(p)
#' ps_exe(p)
#' ps_cmdline(p)
ps_exe <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_exe, p)
}
#' Command line of the process
#'
#' Command line of the process, i.e. the executable and the command line
#' arguments, in a character vector. On Unix the program might change its
#' command line, and some programs actually do it.
#'
#' For a zombie process it throws a `zombie_process` error.
#'
#' @param p Process handle.
#' @return Character vector.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_name(p)
#' ps_exe(p)
#' ps_cmdline(p)
ps_cmdline <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_cmdline, p)
}
#' Current process status
#'
#' One of the following:
#' * `"idle"`: Process being created by fork, macOS only.
#' * `"running"`: Currently runnable on macOS and Windows. Actually
#' running on Linux.
#' * `"sleeping"` Sleeping on a wait or poll.
#' * `"disk_sleep"` Uninterruptible sleep, waiting for an I/O operation
#' (Linux only).
#' * `"stopped"` Stopped, either by a job control signal or because it
#' is being traced.
#' * `"tracing_stop"` Stopped for tracing (Linux only).
#' * `"zombie"` Zombie. Finished, but parent has not read out the exit
#' status yet.
#' * `"dead"` Should never be seen (Linux).
#' * `"wake_kill"` Received fatal signal (Linux only).
#' * `"waking"` Paging (Linux only, not valid since the 2.6.xx kernel).
#'
#' Works for zombie processes.
#'
#' @param p Process handle.
#' @return Character scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_status(p)
ps_status <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_status, p)
}
#' Owner of the process
#'
#' The name of the user that owns the process. On Unix it is calculated
#' from the real user id.
#'
#' On Unix, a numeric uid id returned if the uid is not in the user
#' database, thus a username cannot be determined.
#'
#' Works for zombie processes.
#'
#' @param p Process handle.
#' @return String scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_username(p)
ps_username <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_username, p)
}
#' Process current working directory as an absolute path.
#'
#' For a zombie process it throws a `zombie_process` error.
#'
#' @param p Process handle.
#' @return String scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_cwd(p)
ps_cwd <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_cwd, p)
}
#' User ids and group ids of the process
#'
#' User ids and group ids of the process. Both return integer vectors with
#' names: `real`, `effective` and `saved`.
#'
#' Both work for zombie processes.
#'
#' They are not implemented on Windows, they throw a `not_implemented`
#' error.
#'
#' @param p Process handle.
#' @return Named integer vector of length 3, with names: `real`,
#' `effective` and `saved`.
#'
#' @seealso [ps_username()] returns a user _name_ and works on all
#' platforms.
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_uids(p)
#' ps_gids(p)
ps_uids <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_uids, p)
}
#' @rdname ps_uids
#' @export
ps_gids <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_gids, p)
}
#' Terminal device of the process
#'
#' Returns the terminal of the process. Not implemented on Windows, always
#' returns `NA_character_`. On Unix it returns `NA_character_` if the
#' process has no terminal.
#'
#' Works for zombie processes.
#'
#' @param p Process handle.
#' @return Character scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_terminal(p)
ps_terminal <- function(p = ps_handle()) {
assert_ps_handle(p)
ttynr <- .Call(psll_terminal, p)
if (is.na(ttynr)) {
NA_character_
} else {
tmap <- get_terminal_map()
tmap[[as.character(ttynr)]]
}
}
#' Environment variables of a process
#'
#' `ps_environ()` returns the environment variables of the process, in a
#' named vector, similarly to the return value of `Sys.getenv()`
#' (without arguments).
#'
#' Note: this usually does not reflect changes made after the process
#' started.
#'
#' `ps_environ_raw()` is similar to `p$environ()` but returns the
#' unparsed `"var=value"` strings. This is faster, and sometimes good
#' enough.
#'
#' These functions throw a `zombie_process` error for zombie processes.
#'
#' @param p Process handle.
#' @return `ps_environ()` returns a named character vector (that has a
#' `Dlist` class, so it is printed nicely), `ps_environ_raw()` returns a
#' character vector.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' env <- ps_environ(p)
#' env[["R_HOME"]]
ps_environ <- function(p = ps_handle()) {
assert_ps_handle(p)
parse_envs(.Call(psll_environ, p))
}
#' @rdname ps_environ
#' @export
ps_environ_raw <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_environ, p)
}
#' Number of threads
#'
#' Throws a `zombie_process()` error for zombie processes.
#'
#' @param p Process handle.
#' @return Integer scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_num_threads(p)
ps_num_threads <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_num_threads, p)
}
#' CPU times of the process
#'
#' All times are measured in seconds:
#' * `user`: Amount of time that this process has been scheduled in user
#' mode.
#' * `system`: Amount of time that this process has been scheduled in
#' kernel mode
#' * `children_user`: On Linux, amount of time that this process's
#' waited-for children have been scheduled in user mode.
#' * `children_system`: On Linux, Amount of time that this process's
#' waited-for children have been scheduled in kernel mode.
#'
#' Throws a `zombie_process()` error for zombie processes.
#'
#' @param p Process handle.
#' @return Named real vector or length four: `user`, `system`,
#' `children_user`, `children_system`. The last two are `NA` on
#' non-Linux systems.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_cpu_times(p)
#' proc.time()
ps_cpu_times <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_cpu_times, p)
}
#' Memory usage information
#'
#' @details
#'
#' `ps_memory_info()` returns information about memory usage.
#'
#' It returns a named list. Portable fields:
#' * `rss`: "Resident Set Size", this is the non-swapped physical memory a
#' process has used (bytes). On UNIX it matches "top"‘s 'RES' column (see doc). On
#' Windows this is an alias for `wset` field and it matches "Memory"
#' column of `taskmgr.exe`.
#' * `vmem`: "Virtual Memory Size", this is the total amount of virtual
#' memory used by the process (bytes). On UNIX it matches "top"‘s 'VIRT' column
#' (see doc). On Windows this is an alias for the `pagefile` field and
#' it matches the "Working set (memory)" column of `taskmgr.exe`.
#'
#' Non-portable fields:
#' * `shared`: (Linux) memory that could be potentially shared with other
#' processes (bytes). This matches "top"‘s 'SHR' column (see doc).
#' * `text`: (Linux): aka 'TRS' (text resident set) the amount of memory
#' devoted to executable code (bytes). This matches "top"‘s 'CODE' column (see
#' doc).
#' * `data`: (Linux): aka 'DRS' (data resident set) the amount of physical
#' memory devoted to other than executable code (bytes). It matches "top"‘s
#' 'DATA' column (see doc).
#' * `lib`: (Linux): the memory used by shared libraries (bytes).
#' * `dirty`: (Linux): the amount of memory in dirty pages (bytes).
#' * `pfaults`: (macOS): number of page faults.
#' * `pageins`: (macOS): number of actual pageins.
#'
#' For the explanation of Windows fields see the
#' [PROCESS_MEMORY_COUNTERS_EX](https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex)
#' structure.
#'
#' `ps_memory_full_info()` returns all fields as `ps_memory_info()`, plus
#' additional information, but typically takes slightly longer to run, and
#' might not have access to some processes that `ps_memory_info()` can
#' query:
#'
#' * `uss`: Unique Set Size, this is the memory which is unique to a
#' process and which would be freed if the process was terminated right
#' now.
#' * `pss` (Linux only): Proportional Set Size, is the amount of memory
#' shared with other processes, accounted in a way that the amount is
#' divided evenly between the processes that share it. I.e. if a process
#' has 10 MBs all to itself and 10 MBs shared with another process its
#' PSS will be 15 MBs.
#' * `swap` (Linux only): amount of memory that has been swapped out to
#' disk.
#'
#' They both throw a `zombie_process()` error for zombie processes.
#'
#' @param p Process handle.
#' @return Named real vector.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' p
#' ps_memory_info(p)
#' ps_memory_full_info(p)
ps_memory_info <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_memory_info, p)
}
#' @export
#' @rdname ps_memory_info
ps_memory_full_info <- function(p = ps_handle()) {
assert_ps_handle(p)
type <- ps_os_type()
if (type[["LINUX"]]) {
match <- function(re) {
mt <- gregexpr(re, smaps, perl = TRUE)[[1]]
st <- substring(
smaps,
attr(mt, "capture.start"),
attr(mt, "capture.start") + attr(mt, "capture.length") - 1
)
sum(as.integer(st), na.rm = TRUE) * 1024
}
info <- ps_memory_info(p)
smaps <- .Call(ps__memory_maps, p)
info[["uss"]] <- match("\nPrivate.*:\\s+(\\d+)")
info[["pss"]] <- match("\nPss:\\s+(\\d+)")
info[["swap"]] <- match("\nSwap:\\s+(\\d+)")
info
} else if (type[["MACOS"]]) {
info <- ps_memory_info(p)
info[["uss"]] <- .Call(psll_memory_uss, p)
info
} else if (type[["WINDOWS"]]) {
info <- ps_memory_info(p)
info[["uss"]] <- .Call(psll_memory_uss, p)
info
}
}
#' Send signal to a process
#'
#' Send a signal to the process. Not implemented on Windows. See
#' [signals()] for the list of signals on the current platform.
#'
#' It checks if the process is still running, before sending the signal,
#' to avoid signalling the wrong process, because of pid reuse.
#'
#' @param p Process handle.
#' @param sig Signal number, see [signals()].
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()
#' px <- processx::process$new("sleep", "10")
#' p <- ps_handle(px$get_pid())
#' p
#' ps_send_signal(p, signals()$SIGINT)
#' p
#' ps_is_running(p)
#' px$get_exit_status()
ps_send_signal <- function(p = ps_handle(), sig) {
assert_ps_handle(p)
assert_signal(sig)
.Call(psll_send_signal, p, sig)
}
#' Suspend (stop) the process
#'
#' Suspend process execution with `SIGSTOP` preemptively checking
#' whether PID has been reused. On Windows this has the effect of
#' suspending all process threads.
#'
#' @param p Process handle.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()
#' px <- processx::process$new("sleep", "10")
#' p <- ps_handle(px$get_pid())
#' p
#' ps_suspend(p)
#' ps_status(p)
#' ps_resume(p)
#' ps_status(p)
#' ps_kill(p)
ps_suspend <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_suspend, p)
}
#' Resume (continue) a stopped process
#'
#' Resume process execution with SIGCONT preemptively checking
#' whether PID has been reused. On Windows this has the effect of resuming
#' all process threads.
#'
#' @param p Process handle.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()
#' px <- processx::process$new("sleep", "10")
#' p <- ps_handle(px$get_pid())
#' p
#' ps_suspend(p)
#' ps_status(p)
#' ps_resume(p)
#' ps_status(p)
#' ps_kill(p)
ps_resume <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_resume, p)
}
#' Terminate a Unix process
#'
#' Send a `SIGTERM` signal to the process. Not implemented on Windows.
#'
#' Checks if the process is still running, to work around pid reuse.
#'
#' @param p Process handle.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()
#' px <- processx::process$new("sleep", "10")
#' p <- ps_handle(px$get_pid())
#' p
#' ps_terminate(p)
#' p
#' ps_is_running(p)
#' px$get_exit_status()
ps_terminate <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_terminate, p)
}
#' Kill a process
#'
#' Kill the current process with SIGKILL preemptively checking
#' whether PID has been reused. On Windows it uses `TerminateProcess()`.
#'
#' @param p Process handle.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()
#' px <- processx::process$new("sleep", "10")
#' p <- ps_handle(px$get_pid())
#' p
#' ps_kill(p)
#' p
#' ps_is_running(p)
#' px$get_exit_status()
ps_kill <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_kill, p)
}
#' List of child processes (process objects) of the process. Note that
#' this typically requires enumerating all processes on the system, so
#' it is a costly operation.
#'
#' @param p Process handle.
#' @param recursive Whether to include the children of the children, etc.
#' @return List of `ps_handle` objects.
#'
#' @family process handle functions
#' @export
#' @importFrom utils head tail
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_parent(ps_handle())
#' ps_children(p)
ps_children <- function(p = ps_handle(), recursive = FALSE) {
assert_ps_handle(p)
assert_flag(recursive)
mypid <- ps_pid(p)
mytime <- ps_create_time(p)
map <- ps_ppid_map()
ret <- list()
if (!recursive) {
for (i in seq_len(nrow(map))) {
if (map$ppid[i] == mypid) {
tryCatch({
child <- ps_handle(map$pid[i])
if (mytime <= ps_create_time(child)) {
ret <- c(ret, child)
} },
no_such_process = function(e) NULL,
zombie_process = function(e) NULL)
}
}
} else {
seen <- integer()
stack <- mypid
while (length(stack)) {
pid <- tail(stack, 1)
stack <- head(stack, -1)
if (pid %in% seen) next # nocov (happens _very_ rarely)
seen <- c(seen, pid)
child_pids <- map[ map[,2] == pid, 1]
for (child_pid in child_pids) {
tryCatch({
child <- ps_handle(child_pid)
if (mytime <= ps_create_time(child)) {
ret <- c(ret, child)
stack <- c(stack, child_pid)
} },
no_such_process = function(e) NULL,
zombie_process = function(e) NULL)
}
}
}
## This will throw if p has finished
ps_ppid(p)
ret
}
#' Query the ancestry of a process
#'
#' Query the parent processes recursively, up to the first process.
#' (On some platforms, like Windows, the process tree is not a tree
#' and may contain loops, in which case `ps_descent()` only goes up
#' until the first repetition.)
#'
#' @param p Process handle.
#' @return A list of process handles, starting with `p`, each one
#' is the parent process of the previous one.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' ps_descent()
ps_descent <- function(p = ps_handle()) {
assert_ps_handle(p)
windows <- ps_os_type()[["WINDOWS"]]
branch <- list()
branch_pids <- integer()
current <- p
current_pid <- ps_pid(p)
if (windows) current_time <- ps_create_time(p)
while (TRUE) {
branch <- c(branch, list(current))
branch_pids <- c(branch_pids, current_pid)
parent <- fallback(ps_parent(current), NULL)
# Might fail on Windows, if the process does not exist
if (is.null(parent)) break;
# If the parent pid is the same, we stop.
# Also, Windows might have loops
parent_pid <- ps_pid(parent)
if (parent_pid %in% branch_pids) break;
# Need to check for pid reuse on Windows
if (windows) {
parent_time <- ps_create_time(parent)
if (current_time <= parent_time) break
current_time <- parent_time
}
current <- parent
current_pid <- parent_pid
}
branch
}
ps_ppid_map <- function() {
pids <- ps_pids()
processes <- not_null(lapply(pids, function(p) {
tryCatch(ps_handle(p), error = function(e) NULL) }))
pids <- map_int(processes, ps_pid)
ppids <- map_int(processes, function(p) fallback(ps_ppid(p), NA_integer_))
ok <- !is.na(ppids)
data_frame(
pid = pids[ok],
ppid = ppids[ok]
)
}
#' Number of open file descriptors
#'
#' Note that in some IDEs, e.g. RStudio or R.app on macOS, the IDE itself
#' opens files from other threads, in addition to the files opened from the
#' main R thread.
#'
#' For a zombie process it throws a `zombie_process` error.
#'
#' @param p Process handle.
#' @return Integer scalar.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' ps_num_fds(p)
#' f <- file(tmp <- tempfile(), "w")
#' ps_num_fds(p)
#' close(f)
#' unlink(tmp)
#' ps_num_fds(p)
ps_num_fds <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_num_fds, p)
}
#' Open files of a process
#'
#' Note that in some IDEs, e.g. RStudio or R.app on macOS, the IDE itself
#' opens files from other threads, in addition to the files opened from the
#' main R thread.
#'
#' For a zombie process it throws a `zombie_process` error.
#'
#' @param p Process handle.
#' @return Data frame with columns: `fd` and `path`. `fd` is numeric
#' file descriptor on POSIX systems, `NA` on Windows. `path` is an
#' absolute path to the file.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' ps_open_files(p)
#' f <- file(tmp <- tempfile(), "w")
#' ps_open_files(p)
#' close(f)
#' unlink(tmp)
#' ps_open_files(p)
ps_open_files <- function(p = ps_handle()) {
assert_ps_handle(p)
l <- not_null(.Call(psll_open_files, p))
d <- data_frame(
fd = vapply(l, "[[", integer(1), 2),
path = vapply(l, "[[", character(1), 1))
d
}
#' List network connections of a process
#'
#' For a zombie process it throws a `zombie_process` error.
#'
#' @param p Process handle.
#' @return Data frame, with columns:
#' * `fd`: integer file descriptor on POSIX systems, `NA` on Windows.
#' * `family`: Address family, string, typically `AF_UNIX`, `AF_INET` or
#' `AF_INET6`.
#' * `type`: Socket type, string, typically `SOCK_STREAM` (TCP) or
#' `SOCK_DGRAM` (UDP).
#' * `laddr`: Local address, string, `NA` for UNIX sockets.
#' * `lport`: Local port, integer, `NA` for UNIX sockets.
#' * `raddr`: Remote address, string, `NA` for UNIX sockets. This is
#' always `NA` for `AF_INET` sockets on Linux.
#' * `rport`: Remote port, integer, `NA` for UNIX sockets.
#' * `state`: Socket state, e.g. `CONN_ESTABLISHED`, etc. It is `NA`
#' for UNIX sockets.
#'
#' @family process handle functions
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
#' p <- ps_handle()
#' ps_connections(p)
#' sc <- socketConnection("httpbin.org", port = 80)
#' ps_connections(p)
#' close(sc)
#' ps_connections(p)
ps_connections <- function(p = ps_handle()) {
assert_ps_handle(p)
if (ps_os_type()[["LINUX"]]) return(psl_connections(p))
l <- not_null(.Call(psll_connections, p))
d <- data_frame(
fd = vapply(l, "[[", integer(1), 1),
family = match_names(ps_env$constants$address_families,
vapply(l, "[[", integer(1), 2)),
type = match_names(ps_env$constants$socket_types,
vapply(l, "[[", integer(1), 3)),
laddr = vapply(l, "[[", character(1), 4),
lport = vapply(l, "[[", integer(1), 5),
raddr = vapply(l, "[[", character(1), 6),
rport = vapply(l, "[[", integer(1), 7),
state = match_names(ps_env$constants$tcp_statuses,
vapply(l, "[[", integer(1), 8)))
d$laddr[d$laddr == ""] <- NA_character_
d$raddr[d$raddr == ""] <- NA_character_
d$lport[d$lport == 0] <- NA_integer_
d$rport[d$rport == 0] <- NA_integer_
d
}
#' Interrupt a process
#'
#' Sends `SIGINT` on POSIX, and 'CTRL+C' or 'CTRL+BREAK' on Windows.
#'
#' @param p Process handle.
#' @param ctrl_c On Windows, whether to send 'CTRL+C'. If `FALSE`, then
#' 'CTRL+BREAK' is sent. Ignored on non-Windows platforms.
#'
#' @family process handle functions
#' @export
ps_interrupt <- function(p = ps_handle(), ctrl_c = TRUE) {
assert_ps_handle(p)
assert_flag(ctrl_c)
if (ps_os_type()[["WINDOWS"]]) {
interrupt <- get_tool("interrupt")
.Call(psll_interrupt, p, ctrl_c, interrupt)
} else {
.Call(psll_interrupt, p, ctrl_c, NULL)
}
}
#' @return `ps_windows_nice_values()` return a character vector of possible
#' priority values on Windows.
#' @export
#' @rdname ps_get_nice
ps_windows_nice_values <- function() {
c("realtime",
"high",
"above_normal",
"normal",
"idle",
"below_normal")
}
#' Get or set the priority of a process
#'
#' `ps_get_nice()` returns the current priority, `ps_set_nice()` sets a
#' new priority, `ps_windows_nice_values()` list the possible priority
#' values on Windows.
#'
#' Priority values are different on Windows and Unix.
#'
#' On Unix, priority is an integer, which is maximum 20. 20 is the lowest
#' priority.
#'
#' ## Rules:
#' * On Windows you can only set the priority of the processes the current
#' user has `PROCESS_SET_INFORMATION` access rights to. This typically
#' means your own processes.
#' * On Unix you can only set the priority of the your own processes.
#' The superuser can set the priority of any process.
#' * On Unix you cannot set a higher priority, unless you are the superuser.
#' (I.e. you cannot set a lower number.)
#' * On Unix the default priority of a process is zero.
#'
#' @param p Process handle.
#' @return `ps_get_nice()` returns a string from
#' `ps_windows_nice_values()` on Windows. On Unix it returns an integer
#' smaller than or equal to 20.
#'
#' @export
ps_get_nice <- function(p = ps_handle()) {
assert_ps_handle(p)
code <- .Call(psll_get_nice, p)
if (ps_os_type()[["WINDOWS"]]) {
ps_windows_nice_values()[code]
} else {
code
}
}
#' @param value On Windows it must be a string, one of the values of
#' `ps_windows_nice_values()`. On Unix it is a priority value that is
#' smaller than or equal to 20.
#' @return `ps_set_nice()` return `NULL` invisibly.
#'
#' @export
#' @rdname ps_get_nice
ps_set_nice <- function(p = ps_handle(), value) {
assert_ps_handle(p)
assert_nice_value(value)
if (ps_os_type()[["POSIX"]]) {
value <- as.integer(value)
} else {
value <- match(value, ps_windows_nice_values())
}
invisible(.Call(psll_set_nice, p, value))
}
#' List the dynamically loaded libraries of a process
#'
#' Note: this function currently only works on Windows.
#' @param p Process handle.
#' @return Data frame with one column currently: `path`, the
#' absolute path to the loaded module or shared library. On Windows
#' the list includes the executable file itself.
#'
#' @export
#' @family process handle functions
#' @family shared library tools
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() && ps::ps_os_type()[["WINDOWS"]]
#' # The loaded DLLs of the current process
#' ps_shared_libs()
ps_shared_libs <- function(p = ps_handle()) {
assert_ps_handle(p)
if (!ps_os_type()[["WINDOWS"]]) {
stop("`ps_shared_libs()` is currently only supported on Windows")
}
l <- .Call(psll_dlls, p)
d <- data_frame(
path = map_chr(l, "[[", 1)
)
d
}
#' Query or set CPU affinity
#'
#' `ps_get_cpu_affinity()` queries the
#' [CPU affinity](https://www.linuxjournal.com/article/6799?page=0,0) of
#' a process. `ps_set_cpu_affinity()` sets the CPU affinity of a process.
#'
#' CPU affinity consists in telling the OS to run a process on a limited
#' set of CPUs only (on Linux cmdline, the `taskset` command is typically
#' used).
#'
#' These functions are only supported on Linux and Windows. They error on macOS.
#'
#' @param p Process handle.
#' @param affinity Integer vector of CPU numbers to restrict a process to.
#' CPU numbers start with zero, and they have to be smaller than the
#' number of (logical) CPUs, see [ps_cpu_count()].
#'
#' @return `ps_get_cpu_affinity()` returns an integer vector of CPU
#' numbers, starting with zero.
#'
#' `ps_set_cpu_affinity()` returns `NULL`, invisibly.
#'
#' @export
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() && ! ps::ps_os_type()[["MACOS"]]
#' # current
#' orig <- ps_get_cpu_affinity()
#' orig
#'
#' # restrict
#' ps_set_cpu_affinity(affinity = 0:0)
#' ps_get_cpu_affinity()
#'
#' # restore
#' ps_set_cpu_affinity(affinity = orig)
#' ps_get_cpu_affinity()
ps_get_cpu_affinity <- function(p = ps_handle()) {
assert_ps_handle(p)
type <- ps_os_type()
if (!type[["LINUX"]] && !type[["WINDOWS"]]) {
stop("`ps_cpu_affinity()` is only supported on Windows and Linux")
}
.Call(psll_get_cpu_aff, p)
}
#' @export
#' @rdname ps_get_cpu_affinity
ps_set_cpu_affinity <- function(p = ps_handle(), affinity) {
assert_ps_handle(p)
type <- ps_os_type()
if (!type[["LINUX"]] && !type[["WINDOWS"]]) {
stop("`ps_cpu_affinity()` is only supported on Windows and Linux")
}
# check affinity values
cnt <- ps_cpu_count()
stopifnot(is.integer(affinity), all(affinity < cnt))
invisible(.Call(psll_set_cpu_aff, p, affinity))
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.