Skip to content

Commit f46b6e2

Browse files
authored
Merge pull request #201 from dansmith01/main
2 parents 8730d4a + e1f4f6d commit f46b6e2

File tree

10 files changed

+138
-7
lines changed

10 files changed

+138
-7
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: ps
22
Title: List, Query, Manipulate System Processes
3-
Version: 1.9.1.9000
3+
Version: 1.9.1.9001
44
Authors@R: c(
55
person("Jay", "Loden", role = "aut"),
66
person("Dave", "Daeschler", role = "aut"),

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export(ps_set_nice)
5656
export(ps_shared_lib_users)
5757
export(ps_shared_libs)
5858
export(ps_status)
59+
export(ps_string)
5960
export(ps_suspend)
6061
export(ps_system_cpu_times)
6162
export(ps_system_memory)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ps (development version)
22

3+
* New `ps_string()` for uniquely identifying a process.
4+
35
# ps 1.9.1
46

57
* ps now builds correctly on Alpine Linux (3.19) on R 4.5.0.

R/low-level.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
#' Create a process handle
44
#'
5-
#' @param pid Process id. Integer scalar. `NULL` means the current R
6-
#' process.
5+
#' @param pid A process id (integer scalar) or process string (from
6+
#' `ps_string()`). `NULL` means the current R process.
77
#' @param time Start time of the process. Usually `NULL` and ps will query
88
#' the start time.
99
#' @return `ps_handle()` returns a process handle (class `ps_handle`).
@@ -16,6 +16,7 @@
1616

1717
ps_handle <- function(pid = NULL, time = NULL) {
1818
if (!is.null(pid)) pid <- assert_pid(pid)
19+
if (is.character(pid)) return(ps__str_decode(pid))
1920
if (!is.null(time)) assert_time(time)
2021
.Call(psll_handle, pid, time)
2122
}

R/string.R

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
#' Encode a `ps_handle` as a short string
3+
#'
4+
#' A convenient format for passing between processes, naming semaphores, or
5+
#' using as a directory/file name. Will always be 14 alphanumeric characters,
6+
#' with the first character guarantied to be a letter. Encodes the pid and
7+
#' creation time for a process.
8+
#'
9+
#' @param p Process handle.
10+
#'
11+
#' @return A process string (scalar character), that can be passed to
12+
#' `ps_handle()` in place of a pid.
13+
#'
14+
#' @export
15+
#' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check()
16+
#' (p <- ps_handle())
17+
#' (str <- ps_string(p))
18+
#' ps_handle(pid = str)
19+
20+
ps_string <- function (p = ps_handle()) {
21+
assert_ps_handle(p)
22+
ps__str_encode(ps_pid(p), ps_create_time(p))
23+
}
24+
25+
26+
ps__str_encode <- function (process_id, time) {
27+
28+
whole_secs <- as.integer(time)
29+
micro_secs <- as.numeric(time) %% 1 * 1000000
30+
31+
# Assumptions:
32+
# time between Jan 1st 1970 and Dec 5th 3769.
33+
# max time precision = 1/1,000,000 of a second.
34+
# pid <= 7,311,615 (current std max = 4,194,304).
35+
36+
# Note: micro_secs has three extra unused bits
37+
38+
map <- c(letters, LETTERS, 0:9)
39+
40+
paste(collapse = '', map[1 + c(
41+
floor(process_id / 52 ^ (3:0)) %% 52,
42+
floor(whole_secs / 62 ^ (5:0)) %% 62,
43+
floor(micro_secs / 62 ^ (3:0)) %% 62 )])
44+
}
45+
46+
47+
ps__str_decode <- function (str) {
48+
49+
map <- structure(0:61, names = c(letters, LETTERS, 0:9))
50+
val <- map[strsplit(str, '', fixed = TRUE)[[1]]]
51+
52+
process_id <- sum(val[01:04] * 52 ^ (3:0))
53+
whole_secs <- sum(val[05:10] * 62 ^ (5:0))
54+
micro_secs <- sum(val[11:14] * 62 ^ (3:0))
55+
56+
time <- whole_secs + (micro_secs / 1000000)
57+
time <- as.POSIXct(time, tz = 'GMT', origin = '1970-01-01')
58+
59+
# Allow fuzzy-matching the time by +/- 2 microseconds
60+
tryCatch(
61+
expr = {
62+
p <- ps_handle(pid = process_id)
63+
stopifnot(abs(ps_create_time(p) - time) < 2/1000000)
64+
p
65+
},
66+
error = function (e) {
67+
ps_handle(pid = process_id, time = time)
68+
}
69+
)
70+
}

R/utils.R

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,13 @@ assert_pid <- function(x) {
135135
as.integer(x) == x) {
136136
return(as.integer(x))
137137
}
138-
stop(ps__invalid_argument(match.call()$x,
139-
" is not a process id (integer scalar)"))
138+
if (is.character(x) && length(x) == 1 && !is.na(x) &&
139+
grepl("^[A-Za-z]{4}[A-Za-z0-9]{10}$", x)) {
140+
return(x)
141+
}
142+
stop(ps__invalid_argument(
143+
match.call()$x,
144+
" is not a process id (integer scalar) or process string (from `ps_string()`)"))
140145
}
141146

142147
assert_grace <- function(x) {

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,6 @@ reference:
9696
- title: Utility functions
9797
contents:
9898
- ps_is_supported
99+
- ps_string
99100
- signals
100101
- errno

man/ps_handle.Rd

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ps_string.Rd

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-common.R

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@ test_that("print", {
1717
expect_output(print(ps), format_regexp())
1818
})
1919

20+
test_that("string", {
21+
ps <- ps_handle()
22+
23+
# Values satisfy encoding assumptions
24+
expect_true(all(ps_pids() < 52^4))
25+
expect_true(Sys.time() < 62^6 * .99)
26+
expect_identical(nchar(format(ps_create_time(), "%OS8")), 9L)
27+
28+
# Roundtrip through ps_string
29+
str <- expect_silent(ps_string(ps))
30+
ps2 <- expect_silent(ps_handle(str))
31+
32+
# Got the same process back
33+
expect_true(ps_is_running(ps2))
34+
expect_identical(ps_pid(ps), ps_pid(ps2))
35+
expect_identical(ps_ppid(ps), ps_ppid(ps2))
36+
37+
# Invalid process
38+
str <- ps__str_encode(ps_pid(ps), ps_create_time(ps) + 1)
39+
ps3 <- expect_silent(ps_handle(str))
40+
expect_false(ps_is_running(ps3))
41+
})
42+
2043
test_that("pid", {
2144
## Argument check
2245
expect_error(ps_pid(123), class = "invalid_argument")

0 commit comments

Comments
 (0)