04 Advanced SpaDES use

Alex M. Chubaty

Eliot J. B. McIntire

May 18 2026

1 Advanced SpaDES use

This vignette covers tools that become useful once you are building or maintaining a substantial SpaDES workflow: monitoring memory use across events, and statically checking that a module’s code agrees with its declared metadata.

1.1 Memory monitoring

profvis::profvis is great for digging into R internals, but it doesn’t fit the discrete-event pattern very well. Often the question is simpler: which event uses the most memory, and how much? That number sets how many parallel runs you can fit on a machine.

SpaDES.core ships a small background sampler. Turn it on by setting the option spades.memoryUseInterval to a sampling interval in seconds (e.g., 0.2). If future and future.callr are installed, each spades() call will then:

  1. spawn a future (parallel) R session that runs system('ps') once per interval, keeping only the row for the main R process;
  2. write that to a text file;
  3. when spades() returns, read the file into sim@.xData$.memoryUse$obj and delete the file;
  4. [memoryUse()] joins that table to [completed()] by timestamp, so each event is annotated with its memory use.
if (requireNamespace("future", quietly = TRUE) &&
    requireNamespace("future.callr", quietly = TRUE)) {
  options(spades.memoryUseInterval = 0.5)

  # sim <- simInit(...)
  # sim <- spades(sim)

  memoryUse(sim, max = TRUE)  # peak memory by event type (summarised across times)
  memoryUse(sim, max = FALSE) # peak memory for every individual event
}

[memoryUseThisSession()] returns the memory currently used by the running R session; useful as a quick sanity check between events.

1.2 Static code checking

simInit() parses your module’s source and statically checks that the code agrees with the metadata: every read of sim$x should have x listed in expectsInput() or createsOutput(), every parameter access (Par$y, P(sim)$y, params(sim)$mod$y) should match a defineParameter() entry, every doEvent.* function should return sim, and so on. Mismatches are reported as a structured table with file/line/column locations.

The checks are controlled by two options:

options(spades.moduleCodeChecks = TRUE)    # turn checking on/off
options(spades.codeCheckEngine = "v2")     # "v1" (legacy) or "v2" (new, structured)

You can also run the checks on a module on disk, without going through simInit(). This is handy while authoring or refactoring a module:

# returns a data.frame of findings; also prints a grouped report
findings <- codeCheckModule("path/to/myModule")

# subset by severity or rule id
findings[findings$severity == "warning", ]
findings[findings$id == "param_used_undeclared", ]

A finding has columns id, severity, module, where, name, fn, file, line, col, message, suggestion. The suggestion is a plain-language fix (e.g., “add defineParameter('coverThresh', ...) to parameters”). When simInit() runs the checks, the same data.frame is stashed at sim@.xData$.codeCheck[[moduleName]].

Typical findings the checker flags:

id what it catches
out_declared_unused output declared in metadata, never assigned in code
out_used_undeclared sim$x <- ... for x not in createsOutput()
in_declared_unused input declared, never read
in_used_undeclared sim$x read for x not in expectsInput()
param_declared_unused defineParameter("x", ...) but no Par$x in the code
param_used_undeclared Par$x / P(sim)$x for x not declared
unresolved_accessor sim[[expr]] or get(var, envir=envir(sim)) — can’t be checked statically
must_return_sim doEvent.* not ending with return(invisible(sim))
must_assign_to_sim scheduleEvent(...) or saveFiles(...) not assigned back to sim
module_named_object sim$<moduleName> <- ... (forbidden)
conflicting_fn_unqualified bare levels/scale/which.max (ambiguous with raster::)

See [codeCheckModule()] for details.

2 References