From 0047854ca2eb47c75b63b99169bcfad97b42d5b3 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:11:04 +0100 Subject: [PATCH 01/28] otel concept --- DESCRIPTION | 3 ++- R/daemon.R | 7 +++++++ R/daemons.R | 10 ++++++++++ R/mirai-package.R | 4 ++++ R/mirai.R | 9 ++++++++- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7453bb2d0..02cc41b50 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,7 +30,8 @@ Imports: nanonext (>= 1.7.0) Suggests: cli, - litedown + litedown, + otel Enhances: parallel, promises diff --git a/R/daemon.R b/R/daemon.R index e39fb4e80..21ee99a5a 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -209,6 +209,13 @@ eval_mirai <- function(._mirai_.) { withRestarts( withCallingHandlers( { + if (is_otel_tracing && !is.null(._mirai_.[["._otel_."]])) { + prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) + otel::start_local_active_span( + "mirai::daemon->eval", + options = list(parent = prtctx) + ) + } list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) eval(._mirai_.[["._expr_."]], envir = ._mirai_., enclos = .GlobalEnv) }, diff --git a/R/daemons.R b/R/daemons.R index 9a1dcadb1..16fa147ae 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -257,6 +257,9 @@ daemons <- function( if (signal) send_signal(envir) reap(envir[["sock"]]) + if (is_otel_tracing) { + envir[["otel_span"]][["end"]]() + } ..[[.compute]] <- NULL -> envir return(invisible(FALSE)) } @@ -292,6 +295,13 @@ daemons <- function( ) }) + if (is_otel_tracing) { + envir[["otel_span"]] <- otel::start_span( + "mirai::daemons", + attributes = otel::as_attributes(list(compute_profile = .compute)) + ) + } + invisible(`class<-`(TRUE, c("miraiDaemons", .compute))) } diff --git a/R/mirai-package.R b/R/mirai-package.R index 184a8153a..859650182 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -38,6 +38,7 @@ # tested implicitly .onLoad <- function(libname, pkgname) { + is_otel_tracing <<- requireNamespace("otel", quietly = TRUE) && otel::is_tracing_enabled() switch( Sys.info()[["sysname"]], Linux = { @@ -85,3 +86,6 @@ ), hash = TRUE ) + +is_otel_tracing <- FALSE +otel_tracer_name <- "org.r-lib.mirai" diff --git a/R/mirai.R b/R/mirai.R index 19d2b0ab9..3da95681b 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -164,7 +164,8 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) exists(as.character(expr), envir = parent.frame()) && is.language(.expr) ) .expr else expr, - ._globals_. = globals + ._globals_. = globals, + ._otel_. = NULL ) if (length(.args)) { if (is.environment(.args)) { @@ -177,6 +178,12 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) is.null(envir) && return(ephemeral_daemon(data, .timeout)) + if (is_otel_tracing) { + otel::local_active_span(envir[["otel_span"]]) + spn <- otel::start_local_active_span("mirai::mirai") + data[["._otel_."]] <- otel::pack_http_context() + } + request( .context(envir[["sock"]]), data, From f2f444b2e47e06cf49e30366898ebabe795c9c91 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:43:31 +0100 Subject: [PATCH 02/28] Include daemon on which mirai is evaluated as an attribute --- R/daemon.R | 2 ++ R/daemons.R | 4 ++-- R/mirai-package.R | 4 ++-- R/mirai.R | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 21ee99a5a..0a6937677 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -117,6 +117,7 @@ daemon <- function( task <- 1L timeout <- if (idletime > walltime) walltime else if (is.finite(idletime)) idletime maxtime <- if (is.finite(walltime)) mclock() + walltime else FALSE + `[[<-`(., "id", random(5)) if (dispatcher) { aio <- recv_aio(sock, mode = 1L, cv = cv) @@ -213,6 +214,7 @@ eval_mirai <- function(._mirai_.) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) otel::start_local_active_span( "mirai::daemon->eval", + attributes = otel::as_attributes(list(daemon = .[["id"]])), options = list(parent = prtctx) ) } diff --git a/R/daemons.R b/R/daemons.R index 16fa147ae..1df8253bf 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -257,7 +257,7 @@ daemons <- function( if (signal) send_signal(envir) reap(envir[["sock"]]) - if (is_otel_tracing) { + if (otel_tracing) { envir[["otel_span"]][["end"]]() } ..[[.compute]] <- NULL -> envir @@ -295,7 +295,7 @@ daemons <- function( ) }) - if (is_otel_tracing) { + if (otel_tracing) { envir[["otel_span"]] <- otel::start_span( "mirai::daemons", attributes = otel::as_attributes(list(compute_profile = .compute)) diff --git a/R/mirai-package.R b/R/mirai-package.R index 859650182..99ed04f32 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -38,7 +38,7 @@ # tested implicitly .onLoad <- function(libname, pkgname) { - is_otel_tracing <<- requireNamespace("otel", quietly = TRUE) && otel::is_tracing_enabled() + otel_tracing <<- requireNamespace("otel", quietly = TRUE) && otel::is_tracing_enabled() switch( Sys.info()[["sysname"]], Linux = { @@ -87,5 +87,5 @@ hash = TRUE ) -is_otel_tracing <- FALSE +otel_tracing <- FALSE otel_tracer_name <- "org.r-lib.mirai" diff --git a/R/mirai.R b/R/mirai.R index 3da95681b..08f86054c 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -178,7 +178,7 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) is.null(envir) && return(ephemeral_daemon(data, .timeout)) - if (is_otel_tracing) { + if (otel_tracing) { otel::local_active_span(envir[["otel_span"]]) spn <- otel::start_local_active_span("mirai::mirai") data[["._otel_."]] <- otel::pack_http_context() From b7456f7c90f971da4f81fcd8f5fb90348b2cc517 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:27:22 +0100 Subject: [PATCH 03/28] Enable across all daemons types --- R/daemon.R | 6 +++--- R/daemons.R | 17 ++++++++++------- R/launchers.R | 2 +- R/mirai.R | 18 ++++++++---------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 0a6937677..58884d730 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -117,7 +117,7 @@ daemon <- function( task <- 1L timeout <- if (idletime > walltime) walltime else if (is.finite(idletime)) idletime maxtime <- if (is.finite(walltime)) mclock() + walltime else FALSE - `[[<-`(., "id", random(5)) + if (otel_tracing) `[[<-`(., "id", random(10L)) if (dispatcher) { aio <- recv_aio(sock, mode = 1L, cv = cv) @@ -210,7 +210,8 @@ eval_mirai <- function(._mirai_.) { withRestarts( withCallingHandlers( { - if (is_otel_tracing && !is.null(._mirai_.[["._otel_."]])) { + list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) + if (otel_tracing && length(._mirai_.[["._otel_."]])) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) otel::start_local_active_span( "mirai::daemon->eval", @@ -218,7 +219,6 @@ eval_mirai <- function(._mirai_.) { options = list(parent = prtctx) ) } - list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) eval(._mirai_.[["._expr_."]], envir = ._mirai_., enclos = .GlobalEnv) }, error = handle_mirai_error, diff --git a/R/daemons.R b/R/daemons.R index 1df8253bf..652624f0d 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -257,9 +257,7 @@ daemons <- function( if (signal) send_signal(envir) reap(envir[["sock"]]) - if (otel_tracing) { - envir[["otel_span"]][["end"]]() - } + if (otel_tracing) .subset2(envir[["otel_span"]], "end")() ..[[.compute]] <- NULL -> envir return(invisible(FALSE)) } @@ -295,12 +293,17 @@ daemons <- function( ) }) - if (otel_tracing) { - envir[["otel_span"]] <- otel::start_span( + if (otel_tracing) `[[<-`( + envir, + "otel_span", + otel::start_span( "mirai::daemons", - attributes = otel::as_attributes(list(compute_profile = .compute)) + attributes = otel::as_attributes(list( + url = envir[["url"]], + compute_profile = .compute + )) ) - } + ) invisible(`class<-`(TRUE, c("miraiDaemons", .compute))) } diff --git a/R/launchers.R b/R/launchers.R index 71106e41b..2f518b9d5 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -459,7 +459,7 @@ host_url <- function(tls = FALSE, port = 0) { #' local_url <- function(tcp = FALSE, port = 0) { tcp && return(sprintf("tcp://127.0.0.1:%d", as.integer(port))) - sprintf("%s%s", .urlscheme, random(12L)) + sprintf("%s%s", .urlscheme, random(10L)) } #' @export diff --git a/R/mirai.R b/R/mirai.R index 08f86054c..24aac3d91 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -155,9 +155,8 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) } all(nzchar(gn)) || stop(._[["named_dots"]]) } - if (length(envir[["seed"]])) { - globals[[".Random.seed"]] <- next_stream(envir) - } + if (length(envir[["seed"]])) globals[[".Random.seed"]] <- next_stream(envir) + data <- list( ._expr_. = if ( is.symbol(expr) && @@ -165,8 +164,13 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) is.language(.expr) ) .expr else expr, ._globals_. = globals, - ._otel_. = NULL + ._otel_. = if (otel_tracing) { + if (length(envir)) otel::local_active_span(envir[["otel_span"]]) + spn <- otel::start_local_active_span("mirai::mirai") + otel::pack_http_context() + } ) + if (length(.args)) { if (is.environment(.args)) { .args <- as.list.environment(.args, all.names = TRUE) @@ -178,12 +182,6 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) is.null(envir) && return(ephemeral_daemon(data, .timeout)) - if (otel_tracing) { - otel::local_active_span(envir[["otel_span"]]) - spn <- otel::start_local_active_span("mirai::mirai") - data[["._otel_."]] <- otel::pack_http_context() - } - request( .context(envir[["sock"]]), data, From 05798307f8fbd05b37a2cd0fbceabca0c88e80e8 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:03:47 +0100 Subject: [PATCH 04/28] Add mirai_map() spans --- R/map.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/R/map.R b/R/map.R index d825c9e4e..ecf6abcde 100644 --- a/R/map.R +++ b/R/map.R @@ -157,6 +157,16 @@ mirai_map <- function(.x, .f, ..., .args = list(), .promise = NULL, .compute = N require_daemons(.compute = .compute, call = environment()) is.function(.f) || stop(sprintf(._[["function_required"]], typeof(.f))) + if (otel_tracing) { + if (is.null(.compute)) .compute <- .[["cp"]] + envir <- ..[[.compute]] + parent_span <- envir[["otel_span"]] + on.exit(`[[<-`(envir, "otel_span", parent_span)) + otel::local_active_span(parent_span) + spn <- otel::start_local_active_span("mirai::mirai_map") + `[[<-`(envir, "otel_span", spn) + } + dx <- dim(.x) vec <- if (is.null(dx)) { `names<-`( From f58b171fa7029f9a0ccfc5eea6f00c5d86fe7c48 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:06:50 +0100 Subject: [PATCH 05/28] Add daemon() span --- R/daemon.R | 13 +++++++++++-- R/daemons.R | 27 ++++++++++++++++----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 58884d730..1ad90fdc3 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -117,7 +117,16 @@ daemon <- function( task <- 1L timeout <- if (idletime > walltime) walltime else if (is.finite(idletime)) idletime maxtime <- if (is.finite(walltime)) mclock() + walltime else FALSE - if (otel_tracing) `[[<-`(., "id", random(10L)) + if (otel_tracing) { + `[[<-`( + ., + "otel_span", + otel::start_span( + "mirai::daemon", + attributes = otel::as_attributes(list(url = url)) + ) + ) + } if (dispatcher) { aio <- recv_aio(sock, mode = 1L, cv = cv) @@ -215,7 +224,7 @@ eval_mirai <- function(._mirai_.) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) otel::start_local_active_span( "mirai::daemon->eval", - attributes = otel::as_attributes(list(daemon = .[["id"]])), + attributes = otel::as_attributes(list(daemon = .[["otel_span"]]$get_context()$get_span_id())), options = list(parent = prtctx) ) } diff --git a/R/daemons.R b/R/daemons.R index 652624f0d..8d5fb628f 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -257,7 +257,7 @@ daemons <- function( if (signal) send_signal(envir) reap(envir[["sock"]]) - if (otel_tracing) .subset2(envir[["otel_span"]], "end")() + if (otel_tracing) envir[["otel_span"]]$end() ..[[.compute]] <- NULL -> envir return(invisible(FALSE)) } @@ -293,17 +293,22 @@ daemons <- function( ) }) - if (otel_tracing) `[[<-`( - envir, - "otel_span", - otel::start_span( - "mirai::daemons", - attributes = otel::as_attributes(list( - url = envir[["url"]], - compute_profile = .compute - )) + if (otel_tracing) { + envir <- ..[[.compute]] + `[[<-`( + envir, + "otel_span", + otel::start_span( + "mirai::daemons", + attributes = otel::as_attributes(list( + url = envir[["url"]], + n = envir[["n"]], + dispatcher = envir[["dispatcher"]], + compute_profile = .compute + )) + ) ) - ) + } invisible(`class<-`(TRUE, c("miraiDaemons", .compute))) } From e35a1dfea836254b023547c881f8f5237c6a56a7 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:20:53 +0100 Subject: [PATCH 06/28] Explicitly end daemon spans --- R/daemon.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/daemon.R b/R/daemon.R index 1ad90fdc3..1ce5ba6fc 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -179,6 +179,7 @@ daemon <- function( } } + if (otel_tracing) .[["otel_span"]]$end() if (!output) { sink(type = "message") sink() From d5f03a1fdcbc9f1fe94350ea794fd2929ba588cd Mon Sep 17 00:00:00 2001 From: Charlie Gao <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:55:46 +0100 Subject: [PATCH 07/28] Apply suggestion from @schloerke Co-authored-by: Barret Schloerke Signed-off-by: Charlie Gao <53399081+shikokuchuo@users.noreply.github.com> --- R/mirai.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/R/mirai.R b/R/mirai.R index 24aac3d91..e7fc7552f 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -165,7 +165,11 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) ) .expr else expr, ._globals_. = globals, ._otel_. = if (otel_tracing) { - if (length(envir)) otel::local_active_span(envir[["otel_span"]]) + if (!otel::get_active_span_context()$get_span_id()) { + # Nest the otel span within the mirai daemon iff no active span exists + # TODO: Make a link + otel::local_active_span(envir[["otel_span"]]) + } spn <- otel::start_local_active_span("mirai::mirai") otel::pack_http_context() } From de8389be4991844663a7e8aa1f2eeee3e7a26e64 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:21:57 +0100 Subject: [PATCH 08/28] Correct otel span nesting code --- R/mirai.R | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/R/mirai.R b/R/mirai.R index e7fc7552f..7fd08d50f 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -165,12 +165,14 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) ) .expr else expr, ._globals_. = globals, ._otel_. = if (otel_tracing) { - if (!otel::get_active_span_context()$get_span_id()) { - # Nest the otel span within the mirai daemon iff no active span exists - # TODO: Make a link + if (!otel::get_active_span_context()$is_valid() && length(envir)) { + # Nest the otel span within the mirai::daemons iff no active span exists otel::local_active_span(envir[["otel_span"]]) } - spn <- otel::start_local_active_span("mirai::mirai") + spn <- otel::start_local_active_span( + "mirai::mirai", + attributes = otel::as_attributes(list(compute_profile = .compute)) + ) otel::pack_http_context() } ) From de53879bd9d8545e0bc35fc0ced49a7f77970b79 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:31:26 +0100 Subject: [PATCH 09/28] Rebase --- R/daemons.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/daemons.R b/R/daemons.R index 8d5fb628f..c4c90767c 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -294,7 +294,6 @@ daemons <- function( }) if (otel_tracing) { - envir <- ..[[.compute]] `[[<-`( envir, "otel_span", From b3b50369c67c8038dd2cb30d1420f3315f078b14 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 19 Aug 2025 20:35:52 +0100 Subject: [PATCH 10/28] Simplify inheritance for mirai::mirai_map --- R/map.R | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/R/map.R b/R/map.R index ecf6abcde..419740878 100644 --- a/R/map.R +++ b/R/map.R @@ -159,12 +159,11 @@ mirai_map <- function(.x, .f, ..., .args = list(), .promise = NULL, .compute = N if (otel_tracing) { if (is.null(.compute)) .compute <- .[["cp"]] - envir <- ..[[.compute]] - parent_span <- envir[["otel_span"]] - on.exit(`[[<-`(envir, "otel_span", parent_span)) - otel::local_active_span(parent_span) - spn <- otel::start_local_active_span("mirai::mirai_map") - `[[<-`(envir, "otel_span", spn) + otel::local_active_span(..[[.compute]][["otel_span"]]) + spn <- otel::start_local_active_span( + "mirai::mirai_map", + attributes = otel::as_attributes(list(compute_profile = .compute)) + ) } dx <- dim(.x) From e9c5199320263098e58881a6fc23c90d79b636fc Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 19 Aug 2025 22:51:12 +0100 Subject: [PATCH 11/28] Simplify inheritance for mirai::daemon and mirai::daemon->eval --- R/daemon.R | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 1ce5ba6fc..868d9aca8 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -118,13 +118,9 @@ daemon <- function( timeout <- if (idletime > walltime) walltime else if (is.finite(idletime)) idletime maxtime <- if (is.finite(walltime)) mclock() + walltime else FALSE if (otel_tracing) { - `[[<-`( - ., - "otel_span", - otel::start_span( - "mirai::daemon", - attributes = otel::as_attributes(list(url = url)) - ) + spn <- otel::start_local_active_span( + "mirai::daemon", + attributes = otel::as_attributes(list(url = url)) ) } @@ -179,7 +175,6 @@ daemon <- function( } } - if (otel_tracing) .[["otel_span"]]$end() if (!output) { sink(type = "message") sink() @@ -222,10 +217,11 @@ eval_mirai <- function(._mirai_.) { { list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) if (otel_tracing && length(._mirai_.[["._otel_."]])) { + currid <- otel::get_active_span_context()$get_span_id() prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) - otel::start_local_active_span( + spn <- otel::start_local_active_span( "mirai::daemon->eval", - attributes = otel::as_attributes(list(daemon = .[["otel_span"]]$get_context()$get_span_id())), + attributes = otel::as_attributes(list(daemon = currid)), options = list(parent = prtctx) ) } From c4ce8f677e6e1eb922bafbe6eb70500672b908da Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 19 Aug 2025 22:53:38 +0100 Subject: [PATCH 12/28] Also nest mirai::mirai_map under active spans --- R/launchers.R | 2 +- R/map.R | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/R/launchers.R b/R/launchers.R index 2f518b9d5..71106e41b 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -459,7 +459,7 @@ host_url <- function(tls = FALSE, port = 0) { #' local_url <- function(tcp = FALSE, port = 0) { tcp && return(sprintf("tcp://127.0.0.1:%d", as.integer(port))) - sprintf("%s%s", .urlscheme, random(10L)) + sprintf("%s%s", .urlscheme, random(12L)) } #' @export diff --git a/R/map.R b/R/map.R index 419740878..a93f8744b 100644 --- a/R/map.R +++ b/R/map.R @@ -156,10 +156,12 @@ mirai_map <- function(.x, .f, ..., .args = list(), .promise = NULL, .compute = NULL) { require_daemons(.compute = .compute, call = environment()) is.function(.f) || stop(sprintf(._[["function_required"]], typeof(.f))) + if (is.null(.compute)) .compute <- .[["cp"]] if (otel_tracing) { - if (is.null(.compute)) .compute <- .[["cp"]] - otel::local_active_span(..[[.compute]][["otel_span"]]) + if (!otel::get_active_span_context()$is_valid()) { + otel::local_active_span(..[[.compute]][["otel_span"]]) + } spn <- otel::start_local_active_span( "mirai::mirai_map", attributes = otel::as_attributes(list(compute_profile = .compute)) From 70e017c70cb50c1bd743ad8456064f3ff62aaf6a Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:09:41 +0100 Subject: [PATCH 13/28] Better attribute labels --- R/daemon.R | 2 +- R/mirai.R | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 868d9aca8..b3f9a5155 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -221,7 +221,7 @@ eval_mirai <- function(._mirai_.) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) spn <- otel::start_local_active_span( "mirai::daemon->eval", - attributes = otel::as_attributes(list(daemon = currid)), + attributes = otel::as_attributes(list(daemon_id = currid)), options = list(parent = prtctx) ) } diff --git a/R/mirai.R b/R/mirai.R index 7fd08d50f..20738439a 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -142,8 +142,9 @@ #' mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) { missing(.expr) && stop(._[["missing_expression"]]) - envir <- compute_env(.compute) + if (is.null(.compute)) .compute <- .[["cp"]] + envir <- ..[[.compute]] expr <- substitute(.expr) globals <- list(...) length(globals) && { From 503a161937f59bf8a93179f46e83d1166825a44e Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:25:09 +0100 Subject: [PATCH 14/28] Set status for mirai::daemons span --- R/daemons.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/daemons.R b/R/daemons.R index c4c90767c..ee138dae5 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -257,7 +257,7 @@ daemons <- function( if (signal) send_signal(envir) reap(envir[["sock"]]) - if (otel_tracing) envir[["otel_span"]]$end() + if (otel_tracing) envir[["otel_span"]]$set_status("Ok")$end() ..[[.compute]] <- NULL -> envir return(invisible(FALSE)) } From 7518546dcb6e35b0c57816f8bd2033e92e2f1542 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:04:20 +0100 Subject: [PATCH 15/28] Add mirai id and enable spans only when daemons are used --- R/mirai.R | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/R/mirai.R b/R/mirai.R index 20738439a..ad4704af2 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -165,9 +165,8 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) is.language(.expr) ) .expr else expr, ._globals_. = globals, - ._otel_. = if (otel_tracing) { - if (!otel::get_active_span_context()$is_valid() && length(envir)) { - # Nest the otel span within the mirai::daemons iff no active span exists + ._otel_. = if (otel_tracing && length(envir)) { + if (!otel::get_active_span_context()$is_valid()) { otel::local_active_span(envir[["otel_span"]]) } spn <- otel::start_local_active_span( @@ -189,7 +188,7 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) is.null(envir) && return(ephemeral_daemon(data, .timeout)) - request( + req <- request( .context(envir[["sock"]]), data, send_mode = 1L, @@ -198,6 +197,8 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) cv = envir[["cv"]], id = envir[["dispatcher"]] ) + if (otel_tracing) spn$set_attribute("mirai_id", attr(req, "id")) + invisible(req) } #' Evaluate Everywhere @@ -609,7 +610,7 @@ ephemeral_daemon <- function(data, timeout) { stderr = FALSE, wait = FALSE ) - aio <- request( + req <- request( .context(sock), data, send_mode = 1L, @@ -617,8 +618,8 @@ ephemeral_daemon <- function(data, timeout) { timeout = timeout, cv = substitute() ) - `attr<-`(.subset2(aio, "aio"), "sock", sock) - invisible(aio) + `attr<-`(.subset2(req, "aio"), "sock", sock) + invisible(req) } deparse_safe <- function(x) { From 9b24a59bab6c29ca3aa59db5cbd8cb453e9aa32a Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:16:06 +0100 Subject: [PATCH 16/28] Add news item --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 4a6b0a01d..938dcefba 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ #### New Features +* Complete observability of mirai requests by emitting OpenTelemetry traces when tracing is enabled by the otelsdk package (#394). * Adds `info()` as an alternative to `status()` for retrieving more succinct information statistics, more convenient for programmatic use (thanks @wlandau, #410). * Adds `with_daemons()` and `local_daemons()` helper functions for using a particular compute profile. These work with daemons that are already set up unlike the existing `with.miraiDaemons()` method, which creates a new scope and tears it down when finished (#360). From 68277ca880100cdeb0fae46800553b0d75f1734f Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:42:55 +0100 Subject: [PATCH 17/28] Set status for mirai::daemon->eval result --- R/daemon.R | 10 ++++++++-- R/daemons.R | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index b3f9a5155..44151c324 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -207,9 +207,15 @@ daemon <- function( # internals -------------------------------------------------------------------- -handle_mirai_error <- function(cnd) invokeRestart("mirai_error", cnd, sys.calls()) +handle_mirai_error <- function(cnd) { + if (otel_tracing) dynGet("spn")$set_status("error") + invokeRestart("mirai_error", cnd, sys.calls()) +} -handle_mirai_interrupt <- function(cnd) invokeRestart("mirai_interrupt") +handle_mirai_interrupt <- function(cnd) { + if (otel_tracing) dynGet("spn")$set_status("unset") + invokeRestart("mirai_interrupt") +} eval_mirai <- function(._mirai_.) { withRestarts( diff --git a/R/daemons.R b/R/daemons.R index ee138dae5..e094409fe 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -257,7 +257,7 @@ daemons <- function( if (signal) send_signal(envir) reap(envir[["sock"]]) - if (otel_tracing) envir[["otel_span"]]$set_status("Ok")$end() + if (otel_tracing) envir[["otel_span"]]$set_status("ok")$end() ..[[.compute]] <- NULL -> envir return(invisible(FALSE)) } From 58ee98a874906732ef9d8d24680e4a6b47dcb66d Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:02:04 +0100 Subject: [PATCH 18/28] Upgrade to use links --- R/daemon.R | 3 +-- R/daemons.R | 2 +- R/map.R | 7 +++---- R/mirai.R | 10 ++++------ 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 44151c324..2bd326266 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -223,11 +223,10 @@ eval_mirai <- function(._mirai_.) { { list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) if (otel_tracing && length(._mirai_.[["._otel_."]])) { - currid <- otel::get_active_span_context()$get_span_id() prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) spn <- otel::start_local_active_span( "mirai::daemon->eval", - attributes = otel::as_attributes(list(daemon_id = currid)), + links = list(daemon = dynGet("spn")), options = list(parent = prtctx) ) } diff --git a/R/daemons.R b/R/daemons.R index e094409fe..8262bc430 100644 --- a/R/daemons.R +++ b/R/daemons.R @@ -302,7 +302,7 @@ daemons <- function( attributes = otel::as_attributes(list( url = envir[["url"]], n = envir[["n"]], - dispatcher = envir[["dispatcher"]], + dispatcher = if (is.null(envir[["dispatcher"]])) "false" else "true", compute_profile = .compute )) ) diff --git a/R/map.R b/R/map.R index a93f8744b..346934647 100644 --- a/R/map.R +++ b/R/map.R @@ -159,12 +159,11 @@ mirai_map <- function(.x, .f, ..., .args = list(), .promise = NULL, .compute = N if (is.null(.compute)) .compute <- .[["cp"]] if (otel_tracing) { - if (!otel::get_active_span_context()$is_valid()) { - otel::local_active_span(..[[.compute]][["otel_span"]]) - } + active <- otel::get_active_span_context()$is_valid() spn <- otel::start_local_active_span( "mirai::mirai_map", - attributes = otel::as_attributes(list(compute_profile = .compute)) + links = if (active) list(compute_profile = ..[[.compute]][["otel_span"]]), + options = if (!active) list(parent = ..[[.compute]][["otel_span"]]) ) } diff --git a/R/mirai.R b/R/mirai.R index ad4704af2..85d97d34b 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -142,9 +142,8 @@ #' mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) { missing(.expr) && stop(._[["missing_expression"]]) + envir <- compute_env(.compute) - if (is.null(.compute)) .compute <- .[["cp"]] - envir <- ..[[.compute]] expr <- substitute(.expr) globals <- list(...) length(globals) && { @@ -166,12 +165,11 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) ) .expr else expr, ._globals_. = globals, ._otel_. = if (otel_tracing && length(envir)) { - if (!otel::get_active_span_context()$is_valid()) { - otel::local_active_span(envir[["otel_span"]]) - } + active <- otel::get_active_span_context()$is_valid() spn <- otel::start_local_active_span( "mirai::mirai", - attributes = otel::as_attributes(list(compute_profile = .compute)) + links = if (active) list(compute_profile = envir[["otel_span"]]), + options = if (!active) list(parent = envir[["otel_span"]]) ) otel::pack_http_context() } From efa5c1ec5896996e955806b8ca5208381832a146 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:29:01 +0100 Subject: [PATCH 19/28] Add events to mirai::daemon span --- R/daemon.R | 6 ++++-- R/mirai.R | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 2bd326266..0577ff7a4 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -118,7 +118,7 @@ daemon <- function( timeout <- if (idletime > walltime) walltime else if (is.finite(idletime)) idletime maxtime <- if (is.finite(walltime)) mclock() + walltime else FALSE if (otel_tracing) { - spn <- otel::start_local_active_span( + dmn_spn <- otel::start_local_active_span( "mirai::daemon", attributes = otel::as_attributes(list(url = url)) ) @@ -224,11 +224,13 @@ eval_mirai <- function(._mirai_.) { list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) if (otel_tracing && length(._mirai_.[["._otel_."]])) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) + dmn_spn <- dynGet("dmn_spn") spn <- otel::start_local_active_span( "mirai::daemon->eval", - links = list(daemon = dynGet("spn")), + links = list(daemon = dmn_spn), options = list(parent = prtctx) ) + dmn_spn$add_event("eval started", attributes = list(span.id = spn$get_context()$get_span_id())) } eval(._mirai_.[["._expr_."]], envir = ._mirai_., enclos = .GlobalEnv) }, diff --git a/R/mirai.R b/R/mirai.R index 85d97d34b..c186106a6 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -195,7 +195,7 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) cv = envir[["cv"]], id = envir[["dispatcher"]] ) - if (otel_tracing) spn$set_attribute("mirai_id", attr(req, "id")) + if (otel_tracing) spn$set_attribute("mirai.id", attr(req, "id")) invisible(req) } From fa7ac08ed7adea8eb5bfe925e9496f8170c07994 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 21 Aug 2025 21:10:40 +0100 Subject: [PATCH 20/28] Eval start --- R/daemon.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/daemon.R b/R/daemon.R index 0577ff7a4..0db007c47 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -230,7 +230,7 @@ eval_mirai <- function(._mirai_.) { links = list(daemon = dmn_spn), options = list(parent = prtctx) ) - dmn_spn$add_event("eval started", attributes = list(span.id = spn$get_context()$get_span_id())) + dmn_spn$add_event("eval start", attributes = list(span.id = spn$get_context()$get_span_id())) } eval(._mirai_.[["._expr_."]], envir = ._mirai_., enclos = .GlobalEnv) }, From 02b4523ef0e16d96fc50572d84333a38ff949178 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Fri, 22 Aug 2025 11:10:17 +0100 Subject: [PATCH 21/28] Add span kinds --- R/daemon.R | 2 +- R/mirai.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 0db007c47..df4c9d6e8 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -228,7 +228,7 @@ eval_mirai <- function(._mirai_.) { spn <- otel::start_local_active_span( "mirai::daemon->eval", links = list(daemon = dmn_spn), - options = list(parent = prtctx) + options = list(kind = "server", parent = prtctx) ) dmn_spn$add_event("eval start", attributes = list(span.id = spn$get_context()$get_span_id())) } diff --git a/R/mirai.R b/R/mirai.R index c186106a6..18e050721 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -169,7 +169,7 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) spn <- otel::start_local_active_span( "mirai::mirai", links = if (active) list(compute_profile = envir[["otel_span"]]), - options = if (!active) list(parent = envir[["otel_span"]]) + options = list(kind = "client", parent = if (!active) envir[["otel_span"]]) ) otel::pack_http_context() } From e08688e3b9e152ce4282611f68253b5d7f412bba Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:01:31 +0100 Subject: [PATCH 22/28] Simplify and avoid long parent spans --- R/daemon.R | 1 - R/map.R | 4 +--- R/mirai.R | 5 ++--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index df4c9d6e8..68686722c 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -230,7 +230,6 @@ eval_mirai <- function(._mirai_.) { links = list(daemon = dmn_spn), options = list(kind = "server", parent = prtctx) ) - dmn_spn$add_event("eval start", attributes = list(span.id = spn$get_context()$get_span_id())) } eval(._mirai_.[["._expr_."]], envir = ._mirai_., enclos = .GlobalEnv) }, diff --git a/R/map.R b/R/map.R index 346934647..2004b5f6f 100644 --- a/R/map.R +++ b/R/map.R @@ -159,11 +159,9 @@ mirai_map <- function(.x, .f, ..., .args = list(), .promise = NULL, .compute = N if (is.null(.compute)) .compute <- .[["cp"]] if (otel_tracing) { - active <- otel::get_active_span_context()$is_valid() spn <- otel::start_local_active_span( "mirai::mirai_map", - links = if (active) list(compute_profile = ..[[.compute]][["otel_span"]]), - options = if (!active) list(parent = ..[[.compute]][["otel_span"]]) + links = list(compute_profile = ..[[.compute]][["otel_span"]]) ) } diff --git a/R/mirai.R b/R/mirai.R index 18e050721..f9dc3241b 100644 --- a/R/mirai.R +++ b/R/mirai.R @@ -165,11 +165,10 @@ mirai <- function(.expr, ..., .args = list(), .timeout = NULL, .compute = NULL) ) .expr else expr, ._globals_. = globals, ._otel_. = if (otel_tracing && length(envir)) { - active <- otel::get_active_span_context()$is_valid() spn <- otel::start_local_active_span( "mirai::mirai", - links = if (active) list(compute_profile = envir[["otel_span"]]), - options = list(kind = "client", parent = if (!active) envir[["otel_span"]]) + links = list(compute_profile = envir[["otel_span"]]), + options = list(kind = "client") ) otel::pack_http_context() } From e5f3219696fa1d8685dd609aebd9b8da636c37e8 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:19:01 +0100 Subject: [PATCH 23/28] Update for new otel interfaces --- R/daemon.R | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/R/daemon.R b/R/daemon.R index 68686722c..3b5da5cd3 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -118,7 +118,7 @@ daemon <- function( timeout <- if (idletime > walltime) walltime else if (is.finite(idletime)) idletime maxtime <- if (is.finite(walltime)) mclock() + walltime else FALSE if (otel_tracing) { - dmn_spn <- otel::start_local_active_span( + spn <- otel::start_local_active_span( "mirai::daemon", attributes = otel::as_attributes(list(url = url)) ) @@ -208,12 +208,12 @@ daemon <- function( # internals -------------------------------------------------------------------- handle_mirai_error <- function(cnd) { - if (otel_tracing) dynGet("spn")$set_status("error") + if (otel_tracing) otel::get_active_span()$set_status("error") invokeRestart("mirai_error", cnd, sys.calls()) } handle_mirai_interrupt <- function(cnd) { - if (otel_tracing) dynGet("spn")$set_status("unset") + if (otel_tracing) otel::get_active_span()$set_status("unset") invokeRestart("mirai_interrupt") } @@ -224,10 +224,9 @@ eval_mirai <- function(._mirai_.) { list2env(._mirai_.[["._globals_."]], envir = .GlobalEnv) if (otel_tracing && length(._mirai_.[["._otel_."]])) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) - dmn_spn <- dynGet("dmn_spn") spn <- otel::start_local_active_span( "mirai::daemon->eval", - links = list(daemon = dmn_spn), + links = list(daemon = dynGet("spn")), options = list(kind = "server", parent = prtctx) ) } From 5a1082117a862e14c23b97507d8c64fd7e41ff15 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:18:32 +0100 Subject: [PATCH 24/28] Add otel tests --- .github/workflows/test-coverage.yaml | 5 +++++ tests/tests.R | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 2eba28403..6c226f272 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -27,6 +27,11 @@ jobs: extra-packages: any::covr, any::xml2 needs: coverage + - name: Install additional packages + run: | + pak::pak(c("r-lib/otelsdk", "rlang")) + shell: Rscript {0} + - name: Test coverage run: | cov <- covr::package_coverage( diff --git a/tests/tests.R b/tests/tests.R index ab7bcf937..e5466f766 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -431,5 +431,25 @@ connection && Sys.getenv("NOT_CRAN") == "true" && { test_false(daemons_set("gpu")) test_identical(m, n) } +connection && requireNamespace("otel", quietly = TRUE) && Sys.getenv("NOT_CRAN") == "true" && { + record <- tryCatch( + otelsdk::with_otel_record({ + ns <- getNamespace("mirai") + unlockBinding("otel_tracing", ns) + ns[["otel_tracing"]] <- TRUE + lockBinding("otel_tracing", ns) + url <- local_url() + test_true(daemons(url = url, dispatcher = FALSE)) + mp <- mirai_map(1:3, rnorm) + m1 <- mirai(stop("error")) + m2 <- mirai(getNamespace("rlang")$interrupt()) + test_equal(daemon(url = url, maxtasks = 5L, dispatcher = FALSE), 3L) + test_true(is_error_value(m2[])) + test_false(daemons(0)) + }), + error = function(cnd) NULL + ) + length(record) && test_equal(length(record$traces), 13L) +} test_false(daemons(0)) Sys.sleep(1L) From 98cac72742a3e0c29eaf93c6016f5390499a94f4 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:50:02 +0100 Subject: [PATCH 25/28] Migrate from dynGet for all otel spans --- R/daemon.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/daemon.R b/R/daemon.R index 3b5da5cd3..068bee756 100644 --- a/R/daemon.R +++ b/R/daemon.R @@ -226,7 +226,7 @@ eval_mirai <- function(._mirai_.) { prtctx <- otel::extract_http_context(._mirai_.[["._otel_."]]) spn <- otel::start_local_active_span( "mirai::daemon->eval", - links = list(daemon = dynGet("spn")), + links = list(daemon = otel::get_active_span()), options = list(kind = "server", parent = prtctx) ) } From d13a068bc86125ba171eb9eb9b10afa2715eac15 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:25:34 +0100 Subject: [PATCH 26/28] Complete otel tests --- tests/tests.R | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/tests.R b/tests/tests.R index e5466f766..6694d789a 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -449,7 +449,42 @@ connection && requireNamespace("otel", quietly = TRUE) && Sys.getenv("NOT_CRAN") }), error = function(cnd) NULL ) - length(record) && test_equal(length(record$traces), 13L) + length(record) && { + test_equal(length(record$traces), 13L) + test_equal(record$traces[[1L]]$name, "mirai::mirai") + test_equal(record$traces[[1L]]$kind, "client") + test_equal(record$traces[[1L]]$status, "ok") + test_true(record$traces[[2L]]$attributes$mirai.id > record$traces[[1L]]$attributes$mirai.id) + test_equal(record$traces[[1L]]$parent, record$traces[[2L]]$parent) + test_equal(record$traces[[2L]]$links[[1L]]$span_id, record$traces[[3L]]$links[[1L]]$span_id) + test_equal(record$traces[[4L]]$name, "mirai::mirai_map") + test_equal(record$traces[[4L]]$kind, "internal") + test_equal(record$traces[[4L]]$links[[1L]]$span_id, record$traces[[3L]]$links[[1L]]$span_id) + test_equal(record$traces[[4L]]$span_id, record$traces[[2L]]$parent) + test_equal(record$traces[[5L]]$name, "mirai::mirai") + test_equal(record$traces[[5L]]$kind, "client") + test_equal(record$traces[[5L]]$status, "ok") + test_equal(record$traces[[5L]]$parent, record$traces[[4L]]$parent) + test_equal(record$traces[[7L]]$name, "mirai::daemon->eval") + test_equal(record$traces[[7L]]$kind, "server") + test_equal(record$traces[[7L]]$status, "ok") + test_equal(record$traces[[7L]]$parent, record$traces[[1L]]$span_id) + test_equal(record$traces[[10L]]$status, "error") + test_equal(record$traces[[12L]]$name, "mirai::daemon") + test_equal(record$traces[[12L]]$kind, "internal") + test_equal(record$traces[[12L]]$status, "ok") + test_equal(record$traces[[12L]]$span_id, record$traces[[7L]]$links[[1L]]$span_id) + test_equal(record$traces[[13L]]$name, "mirai::daemons") + test_equal(record$traces[[13L]]$kind, "internal") + test_equal(record$traces[[13L]]$status, "ok") + test_equal(record$traces[[13L]]$attributes$url, url) + test_equal(record$traces[[13L]]$attributes$n, 0L) + test_equal(record$traces[[13L]]$attributes$dispatcher, "false") + test_equal(record$traces[[13L]]$attributes$compute_profile, "default") + test_equal(record$traces[[13L]]$span_id, record$traces[[1L]]$links[[1L]]$span_id) + test_equal(record$traces[[13L]]$span_id, record$traces[[4L]]$links[[1L]]$span_id) + test_equal(record$traces[[13L]]$span_id, record$traces[[5L]]$links[[1L]]$span_id) + } } test_false(daemons(0)) Sys.sleep(1L) From e5ecc924c0f59aa641451312111daaba9823427b Mon Sep 17 00:00:00 2001 From: Charlie Gao <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:48:37 +0100 Subject: [PATCH 27/28] =?UTF-8?q?=E2=9C=A8Add=20OpenTelemetry=20vignette?= =?UTF-8?q?=E2=9C=A8=20(#423)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev/vignettes/_v07-opentelemetry.Rmd | 139 +++++++++++++++++++++++++++ dev/vignettes/precompile.R | 1 + vignettes/v07-opentelemetry.Rmd | 132 +++++++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 dev/vignettes/_v07-opentelemetry.Rmd create mode 100644 vignettes/v07-opentelemetry.Rmd diff --git a/dev/vignettes/_v07-opentelemetry.Rmd b/dev/vignettes/_v07-opentelemetry.Rmd new file mode 100644 index 000000000..2dd919aab --- /dev/null +++ b/dev/vignettes/_v07-opentelemetry.Rmd @@ -0,0 +1,139 @@ +--- +title: "OpenTelemetry" +vignette: > + %\VignetteIndexEntry{OpenTelemetry} + %\VignetteEngine{litedown::vignette} + %\VignetteEncoding{UTF-8} +--- + +```{r} +#| include: false +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + out.width = "100%" +) +``` + +### 1. Introduction + +mirai provides comprehensive OpenTelemetry (otel) tracing support for observing asynchronous operations and distributed computation. + +When the `otel` and `otelsdk` packages are installed and tracing is enabled, mirai automatically creates spans to track the lifecycle of daemon management, async operations, and task execution. + +This enables detailed monitoring of: + +- Task submission and completion times +- Daemon lifecycle and performance +- Error tracking and debugging +- Distributed tracing across network boundaries + +### 2. Automatic Tracing Setup + +Tracing is automatically enabled when: + +1. The `otel` and `otelsdk` packages are installed and available +2. OpenTelemetry tracing is configured and enabled (see ) + +No additional configuration is required - mirai will automatically detect the presence of OpenTelemetry and begin creating spans. + +### 3. Span Types and Hierarchy + +mirai creates several types of spans to represent different operations: + +#### 3.1 Core Span Types + +**`mirai::daemons`** - Root span for daemon management + +- **Kind**: `internal` +- **Attributes**: `url`, `n` (number of daemons), `dispatcher` (true/false), `compute_profile` +- **Lifecycle**: Created when `daemons()` is called, ended when daemons are reset + +**`mirai::daemon`** - Individual daemon process span + +- **Kind**: `internal` +- **Lifecycle**: Tracks the lifetime of a single daemon process + +**`mirai::mirai_map`** - Parallel map operation span + +- **Kind**: `internal` +- **Lifecycle**: Encompasses the entire map operation across multiple mirai tasks + +**`mirai::mirai`** - Client-side async task span + +- **Kind**: `client` +- **Attributes**: `mirai.id` (unique task identifier) +- **Lifecycle**: Created when `mirai()` is called, ended as soon as it returns + +**`mirai::daemon->eval`** - Server-side task evaluation span + +- **Kind**: `server` +- **Lifecycle**: Tracks for the duration of actual mirai evaluation on the daemon + +#### 3.2 Span Relationships and Context Propagation + +The spans form a distributed structure that traces the complete lifecycle of async operations: + +``` +mirai::daemons (compute profile - top level) + +mirai::daemon (daemon process 1 - top level) +mirai::daemon (daemon process 2 - top level) +mirai::daemon (daemon process N - top level) + +mirai::mirai_map (top level) +├── mirai::mirai (task 1) ──link→ mirai::daemons +├── mirai::mirai (task 2) ──link→ mirai::daemons +└── mirai::mirai (task N) ──link→ mirai::daemons + └── mirai::daemon->eval ──link→ mirai::daemon + +mirai::mirai (top level) ──link→ mirai::daemons +└── mirai::daemon->eval ──link→ mirai::daemon +``` + +**Context Propagation**: The context is automatically packaged with each `mirai()` call and extracted on the daemon side, enabling proper parent-child relationships across process boundaries. + +**Span Links**: Tasks are linked to their compute profile's `mirai::daemons` span on the client side, and to each `mirai::daemon` span on the server side, showing exactly where each evaluation happened. + +### 4. Status and Error Tracking + +`mirai::daemon->eval` spans automatically track the success or failure of operations: + +**Status Values**: + +- `'ok'` - Operation completed successfully +- `'error'` - Operation failed with an error +- `'unset'` - Operation was interrupted + +### 5. Monitoring and Observability + +The OpenTelemetry spans provide rich observability into mirai operations: + +**Performance Monitoring**: + +- Track task execution times from submission to completion +- Monitor daemon utilization and load balancing +- Identify bottlenecks in distributed computation + +**Error Analysis**: + +- Correlate errors with specific tasks and daemons +- Track error rates across different types of operations +- Debug issues in distributed environments + +**Distributed Tracing**: + +- Follow task execution across network boundaries +- Understand the complete lifecycle of async operations +- Correlate client-side requests with server-side execution + +### 6. Integration with Observability Platforms + +mirai's OpenTelemetry implementation works seamlessly with any OpenTelemetry-compatible observability platform, including: + +- Grafana +- Pydantic Logfire +- Jaeger +- Any of those listed at . + +The tracer name used by mirai is `"org.r-lib.mirai"`, making it easy to filter and identify mirai-related traces in your observability platform. diff --git a/dev/vignettes/precompile.R b/dev/vignettes/precompile.R index 36dad7dfc..c4d8543dc 100644 --- a/dev/vignettes/precompile.R +++ b/dev/vignettes/precompile.R @@ -6,3 +6,4 @@ knitr::knit("dev/vignettes/_v03-serialization.Rmd", "vignettes/v03-serialization knitr::knit("dev/vignettes/_v04-parallel.Rmd", "vignettes/v04-parallel.Rmd") knitr::knit("dev/vignettes/_v05-packages.Rmd", "vignettes/v05-packages.Rmd") knitr::knit("dev/vignettes/_v06-questions.Rmd", "vignettes/v06-questions.Rmd") +knitr::knit("dev/vignettes/_v07-opentelemetry.Rmd", "vignettes/v07-opentelemetry.Rmd") diff --git a/vignettes/v07-opentelemetry.Rmd b/vignettes/v07-opentelemetry.Rmd new file mode 100644 index 000000000..cadb4bfe9 --- /dev/null +++ b/vignettes/v07-opentelemetry.Rmd @@ -0,0 +1,132 @@ +--- +title: "OpenTelemetry" +vignette: > + %\VignetteIndexEntry{OpenTelemetry} + %\VignetteEngine{litedown::vignette} + %\VignetteEncoding{UTF-8} +--- + + + +### 1. Introduction + +mirai provides comprehensive OpenTelemetry (otel) tracing support for observing asynchronous operations and distributed computation. + +When the `otel` and `otelsdk` packages are installed and tracing is enabled, mirai automatically creates spans to track the lifecycle of daemon management, async operations, and task execution. + +This enables detailed monitoring of: + +- Task submission and completion times +- Daemon lifecycle and performance +- Error tracking and debugging +- Distributed tracing across network boundaries + +### 2. Automatic Tracing Setup + +Tracing is automatically enabled when: + +1. The `otel` and `otelsdk` packages are installed and available +2. OpenTelemetry tracing is configured and enabled (see ) + +No additional configuration is required - mirai will automatically detect the presence of OpenTelemetry and begin creating spans. + +### 3. Span Types and Hierarchy + +mirai creates several types of spans to represent different operations: + +#### 3.1 Core Span Types + +**`mirai::daemons`** - Root span for daemon management + +- **Kind**: `internal` +- **Attributes**: `url`, `n` (number of daemons), `dispatcher` (true/false), `compute_profile` +- **Lifecycle**: Created when `daemons()` is called, ended when daemons are reset + +**`mirai::daemon`** - Individual daemon process span + +- **Kind**: `internal` +- **Lifecycle**: Tracks the lifetime of a single daemon process + +**`mirai::mirai_map`** - Parallel map operation span + +- **Kind**: `internal` +- **Lifecycle**: Encompasses the entire map operation across multiple mirai tasks + +**`mirai::mirai`** - Client-side async task span + +- **Kind**: `client` +- **Attributes**: `mirai.id` (unique task identifier) +- **Lifecycle**: Created when `mirai()` is called, ended as soon as it returns + +**`mirai::daemon->eval`** - Server-side task evaluation span + +- **Kind**: `server` +- **Lifecycle**: Tracks for the duration of actual mirai evaluation on the daemon + +#### 3.2 Span Relationships and Context Propagation + +The spans form a distributed structure that traces the complete lifecycle of async operations: + +``` +mirai::daemons (compute profile - top level) + +mirai::daemon (daemon process 1 - top level) +mirai::daemon (daemon process 2 - top level) +mirai::daemon (daemon process N - top level) + +mirai::mirai_map (top level) +├── mirai::mirai (task 1) ──link→ mirai::daemons +├── mirai::mirai (task 2) ──link→ mirai::daemons +└── mirai::mirai (task N) ──link→ mirai::daemons + └── mirai::daemon->eval ──link→ mirai::daemon + +mirai::mirai (top level) ──link→ mirai::daemons +└── mirai::daemon->eval ──link→ mirai::daemon +``` + +**Context Propagation**: The context is automatically packaged with each `mirai()` call and extracted on the daemon side, enabling proper parent-child relationships across process boundaries. + +**Span Links**: Tasks are linked to their compute profile's `mirai::daemons` span on the client side, and to each `mirai::daemon` span on the server side, showing exactly where each evaluation happened. + +### 4. Status and Error Tracking + +`mirai::daemon->eval` spans automatically track the success or failure of operations: + +**Status Values**: + +- `'ok'` - Operation completed successfully +- `'error'` - Operation failed with an error +- `'unset'` - Operation was interrupted + +### 5. Monitoring and Observability + +The OpenTelemetry spans provide rich observability into mirai operations: + +**Performance Monitoring**: + +- Track task execution times from submission to completion +- Monitor daemon utilization and load balancing +- Identify bottlenecks in distributed computation + +**Error Analysis**: + +- Correlate errors with specific tasks and daemons +- Track error rates across different types of operations +- Debug issues in distributed environments + +**Distributed Tracing**: + +- Follow task execution across network boundaries +- Understand the complete lifecycle of async operations +- Correlate client-side requests with server-side execution + +### 6. Integration with Observability Platforms + +mirai's OpenTelemetry implementation works seamlessly with any OpenTelemetry-compatible observability platform, including: + +- Grafana +- Pydantic Logfire +- Jaeger +- Any of those listed at . + +The tracer name used by mirai is `"org.r-lib.mirai"`, making it easy to filter and identify mirai-related traces in your observability platform. From 72398985d90d51e8cc782349aca8ccecb463994b Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:04:30 +0100 Subject: [PATCH 28/28] Update package docs and reorg vignettes --- R/mirai-package.R | 7 +++++++ .../{_v07-opentelemetry.Rmd => _v05-opentelemetry.Rmd} | 0 dev/vignettes/{_v05-packages.Rmd => _v06-packages.Rmd} | 0 dev/vignettes/{_v06-questions.Rmd => _v07-questions.Rmd} | 0 dev/vignettes/precompile.R | 6 +++--- man/mirai-package.Rd | 9 +++++++++ .../{v07-opentelemetry.Rmd => v05-opentelemetry.Rmd} | 0 vignettes/{v05-packages.Rmd => v06-packages.Rmd} | 0 vignettes/{v06-questions.Rmd => v07-questions.Rmd} | 4 ++-- 9 files changed, 21 insertions(+), 5 deletions(-) rename dev/vignettes/{_v07-opentelemetry.Rmd => _v05-opentelemetry.Rmd} (100%) rename dev/vignettes/{_v05-packages.Rmd => _v06-packages.Rmd} (100%) rename dev/vignettes/{_v06-questions.Rmd => _v07-questions.Rmd} (100%) rename vignettes/{v07-opentelemetry.Rmd => v05-opentelemetry.Rmd} (100%) rename vignettes/{v05-packages.Rmd => v06-packages.Rmd} (100%) rename vignettes/{v06-questions.Rmd => v07-questions.Rmd} (98%) diff --git a/R/mirai-package.R b/R/mirai-package.R index 99ed04f32..f7d7cb297 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -21,6 +21,13 @@ #' This may be overriden, if desired, by specifying 'url' in the [daemons()] #' interface and launching daemons using [launch_local()]. #' +#' @section OpenTelemetry: +#' +#' mirai provides comprehensive OpenTelemetry tracing support for observing +#' asynchronous operations and distributed computation. Please refer to the +#' OpenTelemetry vignette for further details: +#' `vignette("v05-opentelemetry", package = "mirai")` +#' #' @section Reference Manual: #' #' `vignette("mirai", package = "mirai")` diff --git a/dev/vignettes/_v07-opentelemetry.Rmd b/dev/vignettes/_v05-opentelemetry.Rmd similarity index 100% rename from dev/vignettes/_v07-opentelemetry.Rmd rename to dev/vignettes/_v05-opentelemetry.Rmd diff --git a/dev/vignettes/_v05-packages.Rmd b/dev/vignettes/_v06-packages.Rmd similarity index 100% rename from dev/vignettes/_v05-packages.Rmd rename to dev/vignettes/_v06-packages.Rmd diff --git a/dev/vignettes/_v06-questions.Rmd b/dev/vignettes/_v07-questions.Rmd similarity index 100% rename from dev/vignettes/_v06-questions.Rmd rename to dev/vignettes/_v07-questions.Rmd diff --git a/dev/vignettes/precompile.R b/dev/vignettes/precompile.R index c4d8543dc..2a7e3ecb4 100644 --- a/dev/vignettes/precompile.R +++ b/dev/vignettes/precompile.R @@ -4,6 +4,6 @@ knitr::knit("dev/vignettes/_v01-map.Rmd", "vignettes/v01-map.Rmd") knitr::knit("dev/vignettes/_v02-promises.Rmd", "vignettes/v02-promises.Rmd") knitr::knit("dev/vignettes/_v03-serialization.Rmd", "vignettes/v03-serialization.Rmd") knitr::knit("dev/vignettes/_v04-parallel.Rmd", "vignettes/v04-parallel.Rmd") -knitr::knit("dev/vignettes/_v05-packages.Rmd", "vignettes/v05-packages.Rmd") -knitr::knit("dev/vignettes/_v06-questions.Rmd", "vignettes/v06-questions.Rmd") -knitr::knit("dev/vignettes/_v07-opentelemetry.Rmd", "vignettes/v07-opentelemetry.Rmd") +knitr::knit("dev/vignettes/_v05-opentelemetry.Rmd", "vignettes/v05-opentelemetry.Rmd") +knitr::knit("dev/vignettes/_v06-packages.Rmd", "vignettes/v06-packages.Rmd") +knitr::knit("dev/vignettes/_v07-questions.Rmd", "vignettes/v07-questions.Rmd") diff --git a/man/mirai-package.Rd b/man/mirai-package.Rd index cb04a61ea..e2c0e0dcd 100644 --- a/man/mirai-package.Rd +++ b/man/mirai-package.Rd @@ -28,6 +28,15 @@ This may be overriden, if desired, by specifying 'url' in the \code{\link[=daemo interface and launching daemons using \code{\link[=launch_local]{launch_local()}}. } +\section{OpenTelemetry}{ + + +mirai provides comprehensive OpenTelemetry tracing support for observing +asynchronous operations and distributed computation. Please refer to the +OpenTelemetry vignette for further details: +\code{vignette("v05-opentelemetry", package = "mirai")} +} + \section{Reference Manual}{ diff --git a/vignettes/v07-opentelemetry.Rmd b/vignettes/v05-opentelemetry.Rmd similarity index 100% rename from vignettes/v07-opentelemetry.Rmd rename to vignettes/v05-opentelemetry.Rmd diff --git a/vignettes/v05-packages.Rmd b/vignettes/v06-packages.Rmd similarity index 100% rename from vignettes/v05-packages.Rmd rename to vignettes/v06-packages.Rmd diff --git a/vignettes/v06-questions.Rmd b/vignettes/v07-questions.Rmd similarity index 98% rename from vignettes/v06-questions.Rmd rename to vignettes/v07-questions.Rmd index d4b545c11..18e3a5fc7 100644 --- a/vignettes/v06-questions.Rmd +++ b/vignettes/v07-questions.Rmd @@ -78,10 +78,10 @@ vec2 <- 4:6 # Returns different values: good mirai_map(list(vec, vec2), \(x) rnorm(x))[] #> [[1]] -#> [1] 0.38714685 0.09582403 0.85062845 +#> [1] 1.2170560 0.3514982 -0.5736225 #> #> [[2]] -#> [1] 0.3188942 0.2086956 0.5288199 +#> [1] -1.9380647 2.2063538 -0.1181435 # Set the seed in the function mirai_map(list(vec, vec2), \(x) {