├── revdep
├── failures.md
├── data.sqlite
├── cran.md
├── run.R
├── README.md
└── problems.md
├── inst
├── extdata
│ ├── example.Rprofmem.out
│ └── broken.Rprofmem.out
└── WORDLIST
├── incl
├── readRprofmem.R
└── profmem.R
├── .Rinstignore
├── R
├── capableOfProfmem.R
├── utils.R
├── readRprofmem.R
├── Rprofmem-class.R
└── profmem.R
├── .gitignore
├── OVERVIEW.md
├── NAMESPACE
├── man
├── total.Rd
├── readRprofmem.Rd
└── profmem.Rd
├── Makefile
├── tests
├── profmem,error.R
├── capableOfProfmem.R
├── exceptions.R
├── profmem,nested.R
├── profmem.R
└── readRprofmem.R
├── DESCRIPTION
├── .Rbuildignore
├── CONTRIBUTING.md
├── .github
└── workflows
│ ├── test-coverage.yaml
│ └── R-CMD-check.yaml
├── NEWS.md
├── CONDUCT.md
├── vignettes
└── profmem.md.rsp
├── README.md
└── .make
└── Makefile
/revdep/failures.md:
--------------------------------------------------------------------------------
1 | *Wow, no problems at all. :)*
--------------------------------------------------------------------------------
/revdep/data.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HenrikBengtsson/profmem/HEAD/revdep/data.sqlite
--------------------------------------------------------------------------------
/inst/extdata/example.Rprofmem.out:
--------------------------------------------------------------------------------
1 | new page:
2 | 4048 :"integer"
3 | 80048 :"rnorm" "matrix"
4 | 2552 :"rnorm" "matrix"
5 | 80048 :"matrix"
6 |
--------------------------------------------------------------------------------
/incl/readRprofmem.R:
--------------------------------------------------------------------------------
1 | file <- system.file("extdata", "example.Rprofmem.out", package = "profmem")
2 |
3 | raw <- readRprofmem(file, as = "raw")
4 | cat(raw, sep = "\n")
5 |
6 | profmem <- readRprofmem(file, as = "Rprofmem")
7 | print(profmem)
8 |
--------------------------------------------------------------------------------
/inst/extdata/broken.Rprofmem.out:
--------------------------------------------------------------------------------
1 | 232 :
2 | 472 :
3 | 472 :
4 | 1064 :new page:1040 :"raw"
5 | 256 :"matrix"
6 | 536 :"matrix"
7 | 536 :"matrix"
8 | 1064 :"matrix"
9 | 840 :"rnorm" "matrix"
10 | 2544 :"rnorm" "matrix"
11 | 840 :"matrix"
12 |
--------------------------------------------------------------------------------
/revdep/cran.md:
--------------------------------------------------------------------------------
1 | ## revdepcheck results
2 |
3 | We checked 3 reverse dependencies (2 from CRAN + 1 from BioConductor), comparing R CMD check results across CRAN and dev versions of this package.
4 |
5 | * We saw 0 new problems
6 | * We failed to check 0 packages
7 |
8 |
--------------------------------------------------------------------------------
/.Rinstignore:
--------------------------------------------------------------------------------
1 | # Certain LaTeX files (e.g. bib, bst, sty) must be part of the build
2 | # such that they are available for R CMD check. These are excluded
3 | # from the install using .Rinstignore in the top-level directory
4 | # such as this one.
5 | doc/.*[.](bib|bst|sty)$
6 |
--------------------------------------------------------------------------------
/R/capableOfProfmem.R:
--------------------------------------------------------------------------------
1 | # A lookup-only-once version of capabilities("profmem")
2 | # Benchmarking shows it's 8-10 times faster this way.
3 | capableOfProfmem <- local({
4 | res <- NA
5 | function() {
6 | if (is.na(res)) {
7 | res <<- capabilities("profmem")
8 | }
9 | res
10 | }
11 | })
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rhistory
2 | *~
3 | **/*~
4 | .R
5 | .benchmark
6 | .check
7 | .devel
8 | .test
9 | *.o
10 | *.dll
11 | *.Rout
12 | .RData
13 | *.Rproj*
14 | *.swp
15 | .covr.rds
16 | .future
17 | .ghi
18 | .issues
19 | .make
20 | .local
21 | revdep/data.sqlite
22 | revdep/checks/*
23 | revdep/library/*
24 | docs/
25 |
--------------------------------------------------------------------------------
/revdep/run.R:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env Rscript
2 |
3 | precheck <- function() {
4 | ## WORKAROUND: Remove checked pkgs that use file links, which otherwise
5 | ## produce warnings which are promoted to errors by revdepcheck.
6 | unlink("revdep/checks/aroma.affymetrix", recursive = TRUE)
7 | }
8 |
9 | revdepcheck.extras::run()
10 |
--------------------------------------------------------------------------------
/OVERVIEW.md:
--------------------------------------------------------------------------------
1 | <%
2 | ## Reuse the future vignette
3 | md <- R.rsp::rstring(file="vignettes/profmem.md.rsp", postprocess=FALSE)
4 |
5 | ## Drop the header
6 | md <- unlist(strsplit(md, split="\n", fixed=TRUE))
7 | md <- md[-seq_len(grep("^## ", md)[1]-1)]
8 |
9 | ## Drop the footer
10 | md <- md[seq_len(grep("^---", md)[1]-1)]
11 |
12 | ## Output
13 | cat(md, sep="\n")
14 | %>
15 |
--------------------------------------------------------------------------------
/incl/profmem.R:
--------------------------------------------------------------------------------
1 | if (capabilities("profmem")) {
2 |
3 | ## Memory profile an R expression
4 | p <- profmem({
5 | x <- raw(1000)
6 | A <- matrix(rnorm(100), ncol = 10)
7 | })
8 |
9 | ## Display the results
10 | print(p)
11 |
12 | ## Total amount of memory allocation
13 | total(p)
14 |
15 | ## Allocations greater than 1 kB
16 | p2 <- subset(p, bytes > 1000)
17 | print(p2)
18 |
19 | ## The expression is evaluated in the calling environment
20 | str(x)
21 | str(A)
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | S3method(as.data.frame,Rprofmem)
4 | S3method(c,Rprofmem)
5 | S3method(print,Rprofmem)
6 | S3method(subset,Rprofmem)
7 | S3method(total,Rprofmem)
8 | export(profmem)
9 | export(profmem_begin)
10 | export(profmem_depth)
11 | export(profmem_end)
12 | export(profmem_resume)
13 | export(profmem_status)
14 | export(profmem_suspend)
15 | export(readRprofmem)
16 | export(total)
17 | importFrom(utils,Rprofmem)
18 | importFrom(utils,file_test)
19 |
--------------------------------------------------------------------------------
/R/utils.R:
--------------------------------------------------------------------------------
1 | stop_if_not <- function(...) {
2 | res <- list(...)
3 | n <- length(res)
4 | if (n == 0L) return()
5 |
6 | for (ii in 1L:n) {
7 | res_ii <- .subset2(res, ii)
8 | if (length(res_ii) != 1L || is.na(res_ii) || !res_ii) {
9 | mc <- match.call()
10 | call <- deparse(mc[[ii + 1]], width.cutoff = 60L)
11 | if (length(call) > 1L) call <- paste(call[1L], "...")
12 | stop(sQuote(call), " is not TRUE", call. = FALSE, domain = NA)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/man/total.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/Rprofmem-class.R
3 | \name{total}
4 | \alias{total}
5 | \alias{total.Rprofmem}
6 | \alias{subset.Rprofmem}
7 | \title{Total number of bytes allocated}
8 | \usage{
9 | total(x, ...)
10 | }
11 | \arguments{
12 | \item{x}{An \code{Rprofmem} object.}
13 |
14 | \item{...}{Not used.}
15 | }
16 | \value{
17 | A non-negative numeric.
18 | }
19 | \description{
20 | Total number of bytes allocated
21 | }
22 | \keyword{internal}
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | include .make/Makefile
2 |
3 | vignettes/future-1-overview.md.rsp: inst/vignettes-static/future-1-overview.md.rsp.rsp
4 | $(CD) $(@D); \
5 | $(R_SCRIPT) -e "R.rsp::rfile" ../$< --postprocess=FALSE
6 | $(RM) README.md
7 | $(MAKE) README.md
8 |
9 | vigs: vignettes/future-1-overview.md.rsp
10 |
11 | spelling:
12 | $(R_SCRIPT) -e "spelling::spell_check_package()"
13 | $(R_SCRIPT) -e "spelling::spell_check_files(c('NEWS.md', dir('vignettes', pattern='[.](md|rsp)$$', full.names=TRUE)), ignore=readLines('inst/WORDLIST', warn=FALSE))"
14 |
--------------------------------------------------------------------------------
/inst/WORDLIST:
--------------------------------------------------------------------------------
1 | AppVeyor
2 | CMD
3 | deallocated
4 | deallocations
5 | macOS
6 | microbenchmark
7 | neglectable
8 | pre
9 | Pre
10 | profiler
11 | Rprofmem
12 | allocVector
13 | Bengtsson
14 | Calloc
15 | cran
16 | expr
17 | gc
18 | github
19 | HenrikBengtsson
20 | https
21 | mc
22 | md
23 | na
24 | ncol
25 | newpage
26 | nrow
27 | oopts
28 | profmem
29 | readRprofmem
30 | rnorm
31 | Rprof
32 | rsp
33 | stringsAsFactors
34 | VignetteAuthor
35 | VignetteEngine
36 | VignetteIndexEntry
37 | VignetteKeyword
38 | VignetteTangle
39 | withCapture
40 | xBEEF
41 |
--------------------------------------------------------------------------------
/tests/profmem,error.R:
--------------------------------------------------------------------------------
1 | library("profmem")
2 |
3 | if (capabilities("profmem")) {
4 |
5 | ## Defaults to on_error = "ignore")
6 | p <- profmem({
7 | x <- raw(1000)
8 | stop("Woops!")
9 | })
10 | print(p)
11 |
12 |
13 | for (on_error in c("ignore", "warning", "error")) {
14 | message(sprintf("- profmem(..., on_error = \"%s\")", on_error))
15 | res <- tryCatch({
16 | profmem({
17 | x <- raw(1000)
18 | stop("Woops!")
19 | }, on_error = on_error)
20 | }, error = identity, warning = identity)
21 | if (on_error == "ignore") {
22 | stopifnot(inherits(res, "Rprofmem"))
23 | } else {
24 | stopifnot(inherits(res, on_error))
25 | }
26 | }
27 |
28 | } ## if (capabilities("profmem"))
29 |
--------------------------------------------------------------------------------
/tests/capableOfProfmem.R:
--------------------------------------------------------------------------------
1 | truth <- capabilities("profmem")
2 | print(truth)
3 |
4 | cache <- profmem:::capableOfProfmem()
5 | print(cache)
6 | stopifnot(identical(cache, truth))
7 |
8 | cache <- profmem:::capableOfProfmem()
9 | print(cache)
10 | stopifnot(identical(cache, truth))
11 |
12 | ## Fake calling profmem() when memory profiling is disabled
13 | ## and assert that profmem() throws an error
14 | f <- profmem:::capableOfProfmem
15 | environment(f)$res <- FALSE
16 | t <- profmem:::capableOfProfmem()
17 | print(t)
18 | stopifnot(identical(t, FALSE))
19 | res <- tryCatch({
20 | p <- profmem::profmem(x <- 1:1000)
21 | }, error = identity)
22 | stopifnot(inherits(res, "simpleError"))
23 | environment(f)$res <- cache ## Undo
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: profmem
2 | Version: 0.7.0-9000
3 | Title: Simple Memory Profiling for R
4 | Imports:
5 | utils
6 | Suggests:
7 | R.rsp,
8 | markdown,
9 | microbenchmark
10 | VignetteBuilder: R.rsp
11 | Authors@R: c(person("Henrik", "Bengtsson", role=c("aut", "cre", "cph"),
12 | email = "henrikb@braju.com"))
13 | Description: A simple and light-weight API for memory profiling of R expressions. The profiling is built on top of R's built-in memory profiler ('utils::Rprofmem()'), which records every memory allocation done by R (also native code).
14 | License: LGPL (>= 2.1)
15 | LazyLoad: TRUE
16 | URL: https://henrikbengtsson.github.io/profmem/, https://github.com/HenrikBengtsson/profmem
17 | BugReports: https://github.com/HenrikBengtsson/profmem/issues
18 | Encoding: UTF-8
19 | RoxygenNote: 7.3.2
20 | Roxygen: list(markdown = TRUE)
21 |
--------------------------------------------------------------------------------
/tests/exceptions.R:
--------------------------------------------------------------------------------
1 | library("profmem")
2 |
3 | profmem_stack <- profmem:::profmem_stack
4 |
5 | ## In case there are active profmem session in the current R session
6 | ## (shouldn't happen during 'R CMD check' though)
7 | while (profmem_stack("depth") > 0) profmem_stack("pop")
8 |
9 | ## Trying to pop from empty stack
10 | res <- tryCatch({
11 | p <- profmem_stack("pop")
12 | }, error = identity)
13 | print(res)
14 | stopifnot(inherits(res, "error"))
15 |
16 | ## Trying non-existing stack action
17 | res <- tryCatch({
18 | p <- profmem_stack("non-existing")
19 | }, error = identity)
20 | print(res)
21 | stopifnot(inherits(res, "error"))
22 |
23 |
24 | if (capabilities("profmem")) {
25 |
26 | ## Trying to end existing profmem session
27 | res <- tryCatch({
28 | p <- profmem_end()
29 | }, error = identity)
30 | print(res)
31 | stopifnot(inherits(res, "error"))
32 |
33 | } ## if (capabilities("profmem"))
34 |
--------------------------------------------------------------------------------
/tests/profmem,nested.R:
--------------------------------------------------------------------------------
1 | library("profmem")
2 |
3 | message("profmem() - nested ...")
4 |
5 | if (capabilities("profmem")) {
6 |
7 | p1 <- profmem({
8 | x <- integer(1000)
9 | p2 <- profmem({
10 | Y <- matrix(x, nrow = 100, ncol = 10)
11 | })
12 | z <- Y + (x + 1)
13 | })
14 | print(p1)
15 | print(p2)
16 |
17 | p1 <- profmem({
18 | x <- integer(1000)
19 | p2 <- profmem({
20 | Y <- matrix(x, nrow = 100, ncol = 10)
21 | })
22 | z <- Y + (x + 1)
23 | }, threshold = 500L)
24 | print(p1)
25 | print(p2)
26 |
27 | ## Cannot set a higher threshold than already active
28 | p1 <- profmem({ p2 <- profmem({ }, threshold = 1000L) })
29 |
30 | p1 <- profmem({
31 | ## Cannot set a higher threshold than already active
32 | res <- tryCatch({
33 | p2 <- profmem({ }, threshold = 1000L)
34 | }, warning = identify)
35 | stopifnot(inherits(res, "warning"))
36 | })
37 | print(p1)
38 | print(p2)
39 |
40 | }
41 |
42 | message("profmem() - nested ... DONE")
43 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | #----------------------------
2 | # Git and SVN related
3 | #----------------------------
4 | ^.svn
5 | ^.git
6 | ^.github
7 | ^.make
8 | INSTALL[.]md
9 | OVERVIEW[.]md
10 | README[.]md
11 | CONDUCT[.]md
12 | CONTRIBUTING[.]md
13 |
14 | #----------------------------
15 | # devtools
16 | #----------------------------
17 | ^revdep
18 |
19 | #----------------------------
20 | # Travis-CI et al.
21 | #----------------------------
22 | ^[.]travis[.]yml$
23 | ^travis-tool[.]sh$
24 | ^pkg-build[.]sh$
25 | ^appveyor[.]yml$
26 | ^covr-utils.R$
27 | ^[.]covr[.]R$
28 | ^[.]covr[.]rds$
29 |
30 | #----------------------------
31 | # R related
32 | #----------------------------
33 | Rplots.pdf$
34 | ^cran-comments[.].*$
35 | ^vignettes/.*[.](pdf|PDF)$
36 | ^vignettes/.*[.](r|R)$
37 | ^vignettes/[.]install_extras$
38 | ^Makefile$
39 | ^incl
40 | ^NAMESPACE,.*[.]txt$
41 | ^nohup.*$
42 | ^[.]R
43 | ^[.]benchmark
44 | ^[.]devel
45 | ^[.]test
46 | ^[.]check
47 | ^.*[.]tar[.]gz$
48 |
49 | #----------------------------
50 | # Package specific
51 | #----------------------------
52 | ^[.]BatchJobs[.]R$
53 | [.]future
54 |
55 |
56 | ^docs/
57 |
--------------------------------------------------------------------------------
/revdep/README.md:
--------------------------------------------------------------------------------
1 | # Platform
2 |
3 | |field |value |
4 | |:--------|:----------------------------|
5 | |version |R version 4.0.3 (2020-10-10) |
6 | |os |Ubuntu 18.04.5 LTS |
7 | |system |x86_64, linux-gnu |
8 | |ui |X11 |
9 | |language |en |
10 | |collate |en_US.UTF-8 |
11 | |ctype |en_US.UTF-8 |
12 | |tz |America/Los_Angeles |
13 | |date |2020-12-13 |
14 |
15 | # Dependencies
16 |
17 | |package |old |new |Δ |
18 | |:-------|:-----|:----------|:--|
19 | |profmem |0.5.0 |0.5.0-9000 |* |
20 |
21 | # Revdeps
22 |
23 | ## All (3)
24 |
25 | |package |version |error |warning |note |
26 | |:----------------------------------------------------|:-------|:-----|:-------|:----|
27 | |bench |1.1.1 | | | |
28 | |[DelayedMatrixStats](problems.md#delayedmatrixstats) |1.12.1 | | |1 |
29 | |[mashr](problems.md#mashr) |0.2.38 |1 | |1 |
30 |
31 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributing to the 'profmem' package
3 |
4 | This Git repository uses the [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model (the [`git flow`](https://github.com/petervanderdoes/gitflow-avh) extension is useful for this). The [`develop`](https://github.com/HenrikBengtsson/profmem/tree/develop) branch contains the latest contributions and other code that will appear in the next release, and the [`master`](https://github.com/HenrikBengtsson/profmem) branch contains the code of the latest release, which is exactly what is currently on [CRAN](https://cran.r-project.org/package=profmem).
5 |
6 | Contributing to this package is easy. Just send a [pull request](https://help.github.com/articles/using-pull-requests/). When you send your PR, make sure `develop` is the destination branch on the [profmem repository](https://github.com/HenrikBengtsson/profmem). Your PR should pass `R CMD check --as-cran`, which will also be checked by GitHub Actions and when the PR is submitted.
7 |
8 | We abide to the [Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/) of Contributor Covenant.
9 |
--------------------------------------------------------------------------------
/tests/profmem.R:
--------------------------------------------------------------------------------
1 | library("profmem")
2 |
3 | status <- profmem_status()
4 | message("status: ", status)
5 |
6 | depth <- profmem_depth()
7 | message("depth: ", depth)
8 |
9 | if (capabilities("profmem")) {
10 |
11 | p <- profmem({
12 | x <- raw(1000)
13 | A <- matrix(rnorm(100), ncol=10)
14 | status_2 <- profmem_status()
15 | depth_2 <- profmem_depth()
16 | message("depth_2: ", depth_2)
17 | stopifnot(depth2 == depth_+ 1)
18 | })
19 | print(p)
20 |
21 | data <- as.data.frame(p, stringsAsFactors = FALSE)
22 | print(data)
23 |
24 | t <- total(p)
25 | print(t)
26 | stopifnot(t == sum(data$bytes, na.rm=TRUE))
27 |
28 |
29 | foo <- function(n) {
30 | matrix(rnorm(n^2), ncol=n)
31 | }
32 |
33 | p <- profmem({
34 | A <- matrix(rnorm(100^2), ncol=100)
35 | B <- foo(100)
36 | })
37 | print(p)
38 |
39 | p2 <- subset(p, bytes >= 40000)
40 | print(p2)
41 |
42 | p <- profmem({
43 | A <- matrix(rnorm(100^2), ncol=100)
44 | B <- foo(100)
45 | }, threshold = 40000)
46 | d <- as.data.frame(p, stringsAsFactors = FALSE)
47 | print(d)
48 |
49 | p1 <- subset(p, !is.na(bytes))
50 | d1 <- as.data.frame(p1, stringsAsFactors = FALSE)
51 |
52 | p2 <- subset(p, bytes >= 40000)
53 | d2 <- as.data.frame(p2, stringsAsFactors = FALSE)
54 | print(d2)
55 | stopifnot(identical(d2, d1))
56 |
57 | print(dim(p))
58 | stopifnot(identical(dim(p), dim(d)))
59 |
60 | } ## if (capabilities("profmem"))
61 |
62 |
--------------------------------------------------------------------------------
/.github/workflows/test-coverage.yaml:
--------------------------------------------------------------------------------
1 | on: [push]
2 |
3 | name: test-coverage.yaml
4 |
5 | permissions: read-all
6 |
7 | jobs:
8 | test-coverage:
9 | runs-on: ubuntu-latest
10 | env:
11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - name: Assert CODECOV_TOKEN is set
17 | run: |
18 | if [[ -z "${{secrets.CODECOV_TOKEN}}" ]]; then
19 | >&2 echo "::error::ERROR: 'secrets.CODECOV_TOKEN' not set"
20 | exit 1
21 | fi
22 |
23 | - uses: r-lib/actions/setup-r@v2
24 | with:
25 | use-public-rspm: true
26 |
27 | - uses: r-lib/actions/setup-r-dependencies@v2
28 | with:
29 | extra-packages: any::covr, any::xml2
30 | needs: coverage
31 |
32 | - name: Test coverage
33 | run: |
34 | cov <- covr::package_coverage(
35 | quiet = FALSE,
36 | clean = FALSE,
37 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
38 | )
39 | print(cov)
40 | covr::to_cobertura(cov)
41 | shell: Rscript {0}
42 |
43 | - uses: codecov/codecov-action@v4
44 | with:
45 | fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }}
46 | file: ./cobertura.xml
47 | plugin: noop
48 | disable_search: true
49 | token: ${{ secrets.CODECOV_TOKEN }}
50 |
51 | - name: Upload test results
52 | if: failure()
53 | uses: actions/upload-artifact@v4
54 | with:
55 | name: coverage-test-failures
56 | path: ${{ runner.temp }}/package
57 |
--------------------------------------------------------------------------------
/man/readRprofmem.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/readRprofmem.R
3 | \name{readRprofmem}
4 | \alias{readRprofmem}
5 | \title{Read an Rprofmem log file}
6 | \usage{
7 | readRprofmem(pathname, as = c("Rprofmem", "fixed", "raw"), drop = 0L, ...)
8 | }
9 | \arguments{
10 | \item{pathname}{The Rprofmem log file to be read.}
11 |
12 | \item{as}{Specifies in what format data should be returned.
13 | If \code{"raw"}, the line content of the file is returned as is
14 | (as a character vector).
15 | If \code{"fixed"}, as \code{"raw"} but with missing newlines added to lines with empty
16 | stack calls that may be introduced in R (< 3.5.0) (see Ref. 1).
17 | If \code{"Rprofmem"}, the collected Rprofmem data is fully
18 | parsed into bytes and call stack information.}
19 |
20 | \item{drop}{Number of levels to drop from the top of the call stack.}
21 |
22 | \item{...}{Not used}
23 | }
24 | \value{
25 | An \code{Rprofmem} data.frame or a character vector (if \code{as} is \code{"raw"}
26 | or \code{"fixed"}).
27 | An \code{Rprofmem} data.frame has columns \code{what}, \code{bytes}, and \code{trace}, with:
28 | \itemize{
29 | \item \code{what}: (character) type of memory event;
30 | either \code{"alloc"} or \code{"new page"}
31 | \item \code{bytes}: (numeric) number of bytes allocated or \code{NA_real_}
32 | (when \code{what} is \code{"new page"})
33 | \item \code{trace}: (list of character vectors) zero or more function names
34 | }
35 | }
36 | \description{
37 | Reads and parses an Rprofmem log file that was created by
38 | \code{\link[utils:Rprofmem]{utils::Rprofmem()}}.
39 | }
40 | \examples{
41 | file <- system.file("extdata", "example.Rprofmem.out", package = "profmem")
42 |
43 | raw <- readRprofmem(file, as = "raw")
44 | cat(raw, sep = "\n")
45 |
46 | profmem <- readRprofmem(file, as = "Rprofmem")
47 | print(profmem)
48 | }
49 | \references{
50 | Ref. 1: \url{https://github.com/HenrikBengtsson/Wishlist-for-R/issues/25}
51 | }
52 |
--------------------------------------------------------------------------------
/tests/readRprofmem.R:
--------------------------------------------------------------------------------
1 | library("profmem")
2 |
3 | message("readRprofmem() ...")
4 |
5 | message(" - corrupt file")
6 |
7 | broken <- system.file("extdata", "broken.Rprofmem.out", package = "profmem")
8 |
9 | bfr <- readLines(broken)
10 | cat("readLines(broken):\n")
11 | print(bfr)
12 |
13 | raw <- readRprofmem(broken, as = "raw")
14 | cat("readRprofmem(broken, as = 'raw'):\n")
15 | print(raw)
16 | stopifnot(
17 | length(raw) == length(bfr),
18 | all(raw == bfr)
19 | )
20 |
21 | message("readRprofmem(broken, as = 'fixed'):\n")
22 | fixed <- readRprofmem(broken, as = "fixed")
23 | print(fixed)
24 | stopifnot(length(fixed) >= length(bfr))
25 |
26 | p <- readRprofmem(broken, as = "Rprofmem")
27 | cat("readRprofmem(broken, as = 'Rprofmem'):\n")
28 | print(p)
29 | str(p)
30 | stopifnot(nrow(p) == length(fixed))
31 |
32 |
33 | message(" - empty file")
34 |
35 | options(profmem.debug = TRUE)
36 |
37 | empty <- tempfile()
38 | writeLines(character(0L), con = empty)
39 |
40 | bfr <- readLines(empty)
41 | stopifnot(length(bfr) == 0)
42 |
43 | raw <- readRprofmem(empty, as = "raw")
44 | stopifnot(is.character(raw), length(raw) == 0)
45 |
46 | fixed <- readRprofmem(empty, as = "fixed")
47 | stopifnot(is.character(raw), length(raw) == 0)
48 |
49 | p <- readRprofmem(empty, as = "Rprofmem")
50 | stopifnot(nrow(p) == 0L)
51 |
52 | options(profmem.debug = FALSE)
53 |
54 |
55 | if (capabilities("profmem")) {
56 |
57 | live <- tempfile()
58 | Rprofmem(live)
59 | x <- raw(1000)
60 | A <- matrix(rnorm(100), ncol=10)
61 | Rprofmem()
62 |
63 | bfr <- readLines(live)
64 | cat("readLines(live):\n")
65 | print(bfr)
66 |
67 | raw <- readRprofmem(live, as = "raw")
68 | cat("readRprofmem(live, as = 'raw'):\n")
69 | print(raw)
70 | stopifnot(
71 | length(raw) == length(bfr),
72 | all(raw == bfr)
73 | )
74 |
75 | fixed <- readRprofmem(live, as = "fixed")
76 | cat("readRprofmem(live, as = 'fixed'):\n")
77 | print(fixed)
78 | stopifnot(length(fixed) >= length(bfr))
79 |
80 | p <- readRprofmem(live, as = "Rprofmem")
81 | cat("readRprofmem(live, as = 'Rprofmem'):\n")
82 | print(p)
83 | str(p)
84 | stopifnot(nrow(p) == length(fixed))
85 |
86 | } ## if (capabilities("profmem"))
87 |
88 | message("readRprofmem() ... DONE")
89 |
90 |
--------------------------------------------------------------------------------
/revdep/problems.md:
--------------------------------------------------------------------------------
1 | # DelayedMatrixStats
2 |
3 |
4 |
5 | * Version: 1.12.1
6 | * GitHub: https://github.com/PeteHaitch/DelayedMatrixStats
7 | * Source code: https://github.com/cran/DelayedMatrixStats
8 | * Date/Publication: 2020-11-24
9 | * Number of recursive dependencies: 80
10 |
11 | Run `revdep_details(, "DelayedMatrixStats")` for more info
12 |
13 |
14 |
15 | ## In both
16 |
17 | * checking dependencies in R code ... NOTE
18 | ```
19 | Namespaces in Imports field not imported from:
20 | ‘BiocParallel’ ‘HDF5Array’
21 | All declared Imports should be used.
22 | Unexported objects imported by ':::' calls:
23 | ‘DelayedArray:::.get_ans_type’
24 | ‘DelayedArray:::.reduce_array_dimensions’
25 | ‘DelayedArray:::RleArraySeed’ ‘DelayedArray:::get_Nindex_lengths’
26 | ‘DelayedArray:::set_dim’ ‘DelayedArray:::set_dimnames’
27 | ‘DelayedArray:::subset_by_Nindex’ ‘DelayedArray:::to_linear_index’
28 | See the note in ?`:::` about the use of this operator.
29 | ```
30 |
31 | # mashr
32 |
33 |
34 |
35 | * Version: 0.2.38
36 | * GitHub: https://github.com/stephenslab/mashr
37 | * Source code: https://github.com/cran/mashr
38 | * Date/Publication: 2020-06-19 05:50:11 UTC
39 | * Number of recursive dependencies: 84
40 |
41 | Run `revdep_details(, "mashr")` for more info
42 |
43 |
44 |
45 | ## In both
46 |
47 | * checking tests ...
48 | ```
49 | ERROR
50 | Running the tests in ‘tests/testthat.R’ failed.
51 | Last 13 lines of output:
52 | 4. ├─base::all.equal(...)
53 | 5. └─base::all.equal.numeric(...)
54 |
55 | ── Skipped tests ──────────────────────────────────────────────────────────────
56 | ● Cannot test it due to numerical differences between chol in armadillo and R ... (1)
57 | ● file.exists("estimate_null_cor.rds") is not TRUE (1)
58 | ● file.exists("estimate_null_cor_alpha.rds") is not TRUE (1)
59 |
60 | ══ testthat results ═══════════════════════════════════════════════════════════
61 | ERROR (test_sampling.R:10:3): Samples from the posterior look right
62 | ERROR (test_sampling.R:21:3): Samples from the posterior with linear transformation look right
63 |
64 | [ FAIL 2 | WARN 0 | SKIP 3 | PASS 127 ]
65 | Error: Test failures
66 | Execution halted
67 | ```
68 |
69 | * checking installed package size ... NOTE
70 | ```
71 | installed size is 6.8Mb
72 | sub-directories of 1Mb or more:
73 | libs 5.8Mb
74 | ```
75 |
76 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: R-CMD-check
4 |
5 | jobs:
6 | R-CMD-check:
7 | if: "! contains(github.event.head_commit.message, '[ci skip]')"
8 |
9 | timeout-minutes: 30
10 |
11 | runs-on: ${{ matrix.config.os }}
12 |
13 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) ${{ matrix.config.label }}
14 |
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | config:
19 | - {os: windows-latest, r: 'devel' }
20 | - {os: windows-latest, r: 'release' }
21 | # - {os: macOS-latest, r: 'devel' }
22 | - {os: macOS-latest, r: 'release' }
23 | # - {os: macOS-latest, r: 'oldrel' }
24 | - {os: ubuntu-latest, r: 'devel' }
25 | - {os: ubuntu-latest, r: 'release' }
26 | - {os: ubuntu-latest, r: 'oldrel' }
27 | - {os: ubuntu-latest, r: 'oldrel-1' }
28 | - {os: ubuntu-latest, r: 'oldrel-2' }
29 | - {os: ubuntu-latest, r: 'release' , language: ko, label: ko }
30 | - {os: ubuntu-latest, r: 'release' , language: zh_CN, label: zh_CN }
31 | - {os: ubuntu-latest, r: 'release' , language: zh_TW, label: zh_TW }
32 | env:
33 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
34 | R_KEEP_PKG_SOURCE: yes
35 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
36 | ## Test in other locale (optional)
37 | LANGUAGE: ${{ matrix.config.language }}
38 | ## R CMD check
39 | _R_CHECK_CRAN_INCOMING_: false
40 | _R_CHECK_MATRIX_DATA_: true
41 | _R_CHECK_SUGGESTS_ONLY_: true
42 | _R_CHECK_THINGS_IN_TEMP_DIR_: true
43 | ## R (>= 4.4.0) Note, no trailing underscore (sic!)
44 | _R_COMPARE_LANG_OBJECTS: eqonly
45 |
46 | steps:
47 | - uses: actions/checkout@v4
48 |
49 | - uses: r-lib/actions/setup-pandoc@v2
50 |
51 | - uses: r-lib/actions/setup-tinytex@v2
52 |
53 | - name: Install system dependencies (Linux)
54 | if: runner.os == 'Linux'
55 | run: sudo apt-get install -y tidy
56 |
57 | - uses: r-lib/actions/setup-r@v2
58 | with:
59 | r-version: ${{ matrix.config.r }}
60 | http-user-agent: ${{ matrix.config.http-user-agent }}
61 | use-public-rspm: true
62 |
63 | - uses: r-lib/actions/setup-r-dependencies@v2
64 | with:
65 | extra-packages: any::rcmdcheck
66 | needs: check
67 |
68 | - name: Install itself (to build vignettes)
69 | run: |
70 | install.packages(".", repos=NULL, type="source")
71 | shell: Rscript {0}
72 |
73 | - name: Session info
74 | run: |
75 | options(width = 100)
76 | capabilities()
77 | pkgs <- installed.packages()[, "Package"]
78 | sessioninfo::session_info(pkgs, include_base = TRUE)
79 | ## Verify LANGUAGE settings by generating a translatable error
80 | cat(sprintf("LANGUAGE=%s\n", sQuote(Sys.getenv("LANGUAGE"))))
81 | cat(sprintf("locales: %s\n", sQuote(Sys.getlocale())))
82 | tryCatch(log("a"), error = conditionMessage)
83 | shell: Rscript {0}
84 |
85 | - name: Check
86 | run: |
87 | rcmdcheck::rcmdcheck(
88 | args = c("--as-cran", if (.Platform$OS.type == "windows") "--no-manual"),
89 | error_on = "warning",
90 | check_dir = "check"
91 | )
92 | shell: Rscript {0}
93 |
94 | - name: Upload check results
95 | if: failure()
96 | uses: actions/upload-artifact@v4
97 | with:
98 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results
99 | path: check
100 |
--------------------------------------------------------------------------------
/R/readRprofmem.R:
--------------------------------------------------------------------------------
1 | #' Read an Rprofmem log file
2 | #'
3 | #' Reads and parses an Rprofmem log file that was created by
4 | #' [utils::Rprofmem()].
5 | #'
6 | #' @param pathname The Rprofmem log file to be read.
7 | #'
8 | #' @param as Specifies in what format data should be returned.
9 | #' If `"raw"`, the line content of the file is returned as is
10 | #' (as a character vector).
11 | #' If `"fixed"`, as `"raw"` but with missing newlines added to lines with empty
12 | #' stack calls that may be introduced in R (< 3.5.0) (see Ref. 1).
13 | #' If `"Rprofmem"`, the collected Rprofmem data is fully
14 | #' parsed into bytes and call stack information.
15 | #'
16 | #' @param drop Number of levels to drop from the top of the call stack.
17 | #'
18 | #' @param ... Not used
19 | #'
20 | #' @return An `Rprofmem` data.frame or a character vector (if `as` is `"raw"`
21 | #' or `"fixed"`).
22 | #' An `Rprofmem` data.frame has columns `what`, `bytes`, and `trace`, with:
23 | #'
24 | #' * `what`: (character) type of memory event;
25 | #' either `"alloc"` or `"new page"`
26 | #' * `bytes`: (numeric) number of bytes allocated or `NA_real_`
27 | #' (when `what` is `"new page"`)
28 | #' * `trace`: (list of character vectors) zero or more function names
29 | #'
30 | #' @example incl/readRprofmem.R
31 | #'
32 | #' @references
33 | #' Ref. 1: \url{https://github.com/HenrikBengtsson/Wishlist-for-R/issues/25}
34 | #'
35 | #' @export
36 | #' @importFrom utils file_test
37 | readRprofmem <- function(pathname, as = c("Rprofmem", "fixed", "raw"), drop = 0L, ...) {
38 | stop_if_not(file_test("-f", pathname))
39 | as <- match.arg(as)
40 | drop <- as.integer(drop)
41 | stop_if_not(length(drop) == 1, drop >= 0)
42 |
43 | ## Read raw
44 | bfr <- readLines(pathname, warn=FALSE)
45 | if (as == "raw") return(bfr)
46 |
47 |
48 | ## WORKAROUND: Add newlines for entries with empty call stacks
49 | ## https://github.com/HenrikBengtsson/Wishlist-for-R/issues/25
50 | pattern <- "^(new page|[0-9]+)[ ]?:(new page|[0-9]+)[ ]?:"
51 | while(any(grepl(pattern, bfr))) {
52 | bfr <- gsub(pattern, "\\1 :\n\\2 :", bfr)
53 | bfr <- unlist(strsplit(bfr, split="\n", fixed=TRUE))
54 | }
55 |
56 | if (as == "fixed") return(bfr)
57 |
58 | if (getOption("profmem.debug", FALSE)) print(bfr)
59 |
60 | ## Parse Rprofmem results
61 | pattern <- "^([0-9]+|new page)[ ]?:(.*)"
62 | res <- lapply(bfr, FUN=function(x) {
63 | bytes <- gsub(pattern, "\\1", x)
64 | what <- rep("alloc", times = length(x))
65 | idxs <- which(bytes == "new page")
66 | if (length(idxs) > 0) {
67 | what[idxs] <- "new page"
68 | bytes[idxs] <- "" # Will become NA below w/out warning
69 | }
70 | bytes <- as.numeric(bytes)
71 |
72 | trace <- gsub(pattern, "\\2", x)
73 | trace <- gsub('" "', '", "', trace, fixed=TRUE)
74 | trace <- sprintf("c(%s)", trace)
75 |
76 | trace <- eval(parse(text=trace), enclos = baseenv())
77 | trace <- trace[seq_len(max(0L, length(trace)-drop))]
78 |
79 | list(what = what, bytes = bytes, trace = trace)
80 | })
81 |
82 | if (length(res) == 0) {
83 | what <- character(0L)
84 | bytes <- numeric(0L)
85 | traces <- list()
86 | } else {
87 | what <- unlist(lapply(res, FUN=function(x) x$what), use.names=FALSE)
88 | bytes <- unlist(lapply(res, FUN=function(x) x$bytes), use.names=FALSE)
89 | traces <- lapply(res, FUN=function(x) x$trace)
90 | }
91 | res <- data.frame(what = what, bytes = bytes, stringsAsFactors = FALSE)
92 | res$trace <- traces
93 | bfr <- bytes <- traces <- NULL
94 |
95 | class(res) <- c("Rprofmem", class(res))
96 |
97 | ## Sanity check
98 | stop_if_not(all(c("what", "bytes", "trace") %in% names(res)))
99 |
100 | res
101 | } ## readRprofmem()
102 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # Version (development version)
2 |
3 | * ...
4 |
5 |
6 | # Version 0.7.0 [2025-05-02]
7 |
8 | ## New Features
9 |
10 | * `profmem()` gained argument `on_error` to control what should
11 | happen when there is a run-time error during profiling. Known
12 | actions are `"error"`, `"warning"`, and `"ignore"` (default).
13 |
14 | * `print()` for Rprofmem objects now report on any run-time errors
15 | that occurred while profiling the R expression.
16 |
17 | * `print()` for Rprofmem objects gained logical argument `calls' to
18 | control whether calls ("traces") are outputted.
19 |
20 |
21 | # Version 0.6.0 [2020-12-13]
22 |
23 | ## New Features
24 |
25 | * The defaults of arguments `expr` and `newpage` of `print()` for
26 | Rprofmem can now be set via options `profmem.print.expr` and
27 | `profmem.print.newpage`.
28 |
29 | ## Documentation
30 |
31 | * Added documentation on what an 'Rprofmem' data.frame contains.
32 |
33 | * Added example for `readRprofmem()`.
34 |
35 | ## Consistency
36 |
37 | * The `bytes` column returned by `readRprofmem()` would be of type
38 | integer when zero allocation events where read, whereas otherwise
39 | it would be of type numeric.
40 |
41 | * `print(x)` for Rprofmem now uses an explicit `stringsAsFactors =
42 | FALSE` when internally coercing `x` to a data frame.
43 |
44 |
45 | # Version 0.5.0 [2018-01-30]
46 |
47 | ## Significant Changes
48 |
49 | * Nested `profmem()` calls are now supported, such that inner
50 | `profmem()` calls will not affect outer ones. This allows, for
51 | instance, a package to use profmem internally without having to
52 | worry about profmem already being active when its functions are
53 | called.
54 |
55 | ## New Features
56 |
57 | * Added `profmem_begin()` and `profmem_end()` as an alternative to
58 | `profmem()`.
59 |
60 | * Added `profmem_suspend()` and `profmem_resume()` for suspending and
61 | resuming an active profmem session.
62 |
63 | * Added `profmem_status()` for checking whether profmem is inactive,
64 | active, or suspended.
65 |
66 | * Added `profmem_depth()` to get the current depth of nested profmem
67 | sessions.
68 |
69 | * `print()` for Rprofmem now reports on the threshold used, if known.
70 |
71 | * `print()` for Rprofmem gained arguments `expr = TRUE` and `newpage
72 | = FALSE`, controlling whether the profiled R expression and 'new
73 | page' entries should be displayed or not.
74 |
75 | * Added option `profmem.threshold` for controlling the default
76 | threshold level for `profmem()` and `profmem_begin()`.
77 |
78 | ## Documentation
79 |
80 | * Vignette: Updated the example in Section 'An example where memory
81 | profiling can make a difference' due to improvements in R (>=
82 | 3.4.0), which made the previous example no longer valid.
83 |
84 | * `help("profmem")` how provide much more details previously only
85 | available in the vignette.
86 |
87 | ## Bug Fixes
88 |
89 | * `readRprofmem()` failed to "workaround" another buggy `Rprofmem()`
90 | output.
91 |
92 |
93 | # Version 0.4.0 [2016-09-14]
94 |
95 | ## New Features
96 |
97 | * `profmem()` now returns a data.frame of class Rprofmem.
98 |
99 | * `profmem()` gained argument threshold.
100 |
101 | ## Code Quality
102 |
103 | * 100% test coverage.
104 |
105 |
106 | # Version 0.3.0 [2016-08-06]
107 |
108 | ## Documentation
109 |
110 | * Added vignette.
111 |
112 |
113 | # Version 0.2.1 [2016-07-11]
114 |
115 | ## New Features
116 |
117 | * Added `readRprofmem()`.
118 |
119 |
120 | # Version 0.2.0 [2016-06-05]
121 |
122 | ## New Features
123 |
124 | * Added `total()` to get the total number of bytes allocated.
125 |
126 | * `profmem()` returns also when evaluating the expression gives an
127 | error.
128 |
129 |
130 | # Version 0.1.0 [2016-06-04]
131 |
132 | ## Significant Changes
133 |
134 | * Added `profmem()`.
135 |
136 | * Package created.
137 |
--------------------------------------------------------------------------------
/R/Rprofmem-class.R:
--------------------------------------------------------------------------------
1 | #' Total number of bytes allocated
2 | #'
3 | #' @param x An `Rprofmem` object.
4 | #' @param ... Not used.
5 | #'
6 | #' @return A non-negative numeric.
7 | #'
8 | #' @aliases total.Rprofmem subset.Rprofmem
9 | #' @export
10 | #' @keywords internal
11 | total <- function(x, ...) UseMethod("total")
12 |
13 | #' @export
14 | total.Rprofmem <- function(x, ...) {
15 | sum(x$bytes, na.rm=TRUE)
16 | }
17 |
18 | #' @export
19 | c.Rprofmem <- function(...) {
20 | args <- list(...)
21 |
22 | what <- NULL
23 | bytes <- NULL
24 | trace <- NULL
25 | threshold <- NULL
26 |
27 | for (arg in args) {
28 | stop_if_not(inherits(arg, "Rprofmem"))
29 | what <- c(what, arg$what)
30 | bytes <- c(bytes, arg$bytes)
31 | trace <- c(trace, arg$trace)
32 | threshold <- c(threshold, attr(arg, "threshold"))
33 | }
34 | threshold <- max(threshold)
35 | stop_if_not(length(threshold) == 1, is.finite(threshold),
36 | is.integer(threshold), threshold >= 0L)
37 |
38 | res <- data.frame(what = what, bytes = bytes, stringsAsFactors = FALSE)
39 | res$trace <- trace
40 | if (threshold > 0L) {
41 | keep <- is.na(bytes) | (bytes >= threshold)
42 | stop_if_not(all(is.finite(keep)))
43 | res <- res[keep, ]
44 | }
45 | attr(res, "threshold") <- threshold
46 | class(res) <- c("Rprofmem", class(res))
47 | ## Sanity check
48 | stop_if_not(all(c("what", "bytes", "trace") %in% names(res)))
49 |
50 | res
51 | }
52 |
53 | #' @export
54 | subset.Rprofmem <- function(x, ...) {
55 | res <- NextMethod()
56 | attr(res, "expression") <- attr(x, "expression")
57 | attr(res, "threshold") <- attr(x, "threshold")
58 | res
59 | }
60 |
61 | #' @export
62 | as.data.frame.Rprofmem <- function(x, ...) {
63 | what <- x$what
64 | bytes <- x$bytes
65 | traces <- unlist(lapply(x$trace, FUN=function(x) {
66 | trace <- rev(x)
67 | hasName <- !grepl("^<[^>]*>$", trace)
68 | trace[hasName] <- sprintf("%s()", trace[hasName])
69 | paste(trace, collapse=" -> ")
70 | }))
71 |
72 | res <- data.frame(what = what, bytes = bytes, calls = traces,
73 | stringsAsFactors = FALSE)
74 |
75 | ## Preserve row names
76 | rownames(res) <- rownames(x)
77 |
78 | res
79 | } ## as.data.frame()
80 |
81 |
82 | #' @export
83 | print.Rprofmem <- function(x, expr = getOption("profmem.print.expr", TRUE), newpage = getOption("profmem.print.newpage", FALSE), calls = getOption("profmem.print.calls", TRUE), ...) {
84 | if (expr && "expression" %in% names(attributes(x))) {
85 | cat("Rprofmem memory profiling of:\n")
86 | print(attr(x, "expression"))
87 | cat("\n")
88 | }
89 |
90 | threshold <- attr(x, "threshold")
91 | if (is.null(threshold) || threshold == 0L) {
92 | cat("Memory allocations:\n")
93 | } else {
94 | cat(sprintf("Memory allocations (>= %g bytes):\n", threshold))
95 | }
96 |
97 | data <- as.data.frame(x, ..., stringsAsFactors = FALSE)
98 |
99 | if (!newpage) {
100 | drop <- which(x$what == "new page")
101 | if (length(drop) > 0) {
102 | cat(sprintf("Number of 'new page' entries not displayed: %d\n",
103 | length(drop)))
104 | data <- data[-drop, ]
105 | }
106 | }
107 |
108 | n <- nrow(data)
109 | total <- sum(data$bytes, na.rm=TRUE)
110 |
111 | ## Number of digits for indices
112 | widx <- floor(log10(n)+1)
113 |
114 | ## Number of digits for bytes
115 | wbytes <- floor(log10(total)+1)
116 |
117 | ## Report empty call stack as "internal"
118 | data$calls[!nzchar(data$calls)] <- ""
119 |
120 | data <- rbind(data, list(what = "", bytes = total, calls = ""))
121 | rownames(data)[n+1] <- "total"
122 |
123 | if (!calls) {
124 | data$calls <- NULL
125 | }
126 |
127 | print(data, ...)
128 |
129 | ## Any errors to report on?
130 | error <- attr(x, "error")
131 | if (!is.null(error)) {
132 | cat(sprintf("\nNote, an error occurred while evaluating the expression: %s\n", conditionMessage(error)))
133 | }
134 |
135 | invisible(x)
136 | } ## print()
137 |
--------------------------------------------------------------------------------
/man/profmem.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/profmem.R
3 | \name{profmem}
4 | \alias{profmem}
5 | \alias{profmem_begin}
6 | \alias{profmem_end}
7 | \alias{profmem_suspend}
8 | \alias{profmem_resume}
9 | \alias{profmem_status}
10 | \alias{profmem_depth}
11 | \title{Memory profiling R}
12 | \usage{
13 | profmem(
14 | expr,
15 | envir = parent.frame(),
16 | substitute = TRUE,
17 | threshold = getOption("profmem.threshold", 0L),
18 | on_error = c("ignore", "warning", "error")
19 | )
20 |
21 | profmem_begin(threshold = getOption("profmem.threshold", 0L))
22 |
23 | profmem_end()
24 |
25 | profmem_suspend()
26 |
27 | profmem_resume()
28 |
29 | profmem_status()
30 |
31 | profmem_depth()
32 | }
33 | \arguments{
34 | \item{expr}{An \R expression to be evaluated and profiled.}
35 |
36 | \item{envir}{The environment in which the expression should be evaluated.}
37 |
38 | \item{substitute}{Should \code{expr} be \code{\link[base:substitute]{base::substitute()}}:d or not.}
39 |
40 | \item{threshold}{The smallest memory allocation (in bytes) to log.}
41 |
42 | \item{on_error}{(character) Controls whether evaluation errors should
43 | signal an error (\code{"error"}), a warning (\verb{"warning"), or be ignored (}"ignore"`; default).}
44 | }
45 | \value{
46 | \code{profmem()} and \code{profmem_end()} returns the collected allocation
47 | data as an \code{Rprofmem} data.frame with additional attributes set.
48 | An \code{Rprofmem} data.frame has columns \code{what}, \code{bytes}, and \code{trace}, with:
49 | \itemize{
50 | \item \code{what}: (character) type of memory event;
51 | either \code{"alloc"} or \code{"new page"}
52 | \item \code{bytes}: (numeric) number of bytes allocated or \code{NA_real_}
53 | (when \code{what} is \code{"new page"})
54 | \item \code{trace}: (list of character vectors) zero or more function names
55 | }
56 |
57 | The attributes set are:
58 | \itemize{
59 | \item \code{threshold} : The threshold used (= argument \code{threshold})
60 | \item \code{expression}: The expression profiled (= argument \code{expr})
61 | \item \code{value} : The value of the evaluated expression
62 | (only set if there was no error)
63 | \item \code{error} : The error object in case the evaluation failed
64 | (only set if there was an error)
65 | }
66 |
67 | \code{profmem_begin()} returns (invisibly) the number of nested profmem
68 | session currently active.
69 |
70 | \code{profmem_suspend()} and \code{profmem_resume()} returns nothing.
71 |
72 | \code{profmem_status()} returns \code{"inactive"}, \code{"active"},
73 | or \code{"suspended"}.
74 |
75 | \code{promem_depth()} returns a non-negative integer.
76 | }
77 | \description{
78 | \code{profmem()} evaluates and memory profiles an \R expression.
79 |
80 | \code{profmem_begin()} starts the memory profiling of all the following \R
81 | evaluations until \code{profmem_end()} is called.
82 | }
83 | \details{
84 | In order for memory profiling to work, \R must have been \emph{built} with memory
85 | profiling enabled. Function
86 | \code{\link[base:capabilities]{base::capabilities("profmem")}} will
87 | return \code{TRUE} of it is enabled, otherwise \code{FALSE}.
88 | If memory profiling is \emph{not} supported, \code{profmem()} and \code{profmem_begin()}
89 | will produce an informative error. The pre-built \R binaries on
90 | \href{https://cran.r-project.org/}{CRAN} support memory profiling.
91 |
92 | What is logged? The \code{profmem()} function uses \code{\link[utils:Rprofmem]{utils::Rprofmem()}} for
93 | logging memory, which logs all memory \emph{allocations} that are done via the
94 | \R framework. Specifically, the logger is tied to \code{allocVector3()} part
95 | of \R's native API. This means that nearly all memory allocations done
96 | in \R are logged. \emph{Neither} memory deallocations \emph{nor} garbage collection
97 | events are logged. Furthermore, allocations done by non-\R native libraries
98 | or \R packages that use native code \code{Calloc() / Free()} for internal objects
99 | are also \emph{not} logged.
100 |
101 | Any memory events that would occur due to calling any of the \pkg{profmem}
102 | functions themselves will \emph{not} be logged and \emph{not} be part of the returned
103 | profile data (regardless whether memory profiling is active or not).
104 | This is intentional.
105 |
106 | If a profmem profiling is already active, \code{profmem()} and \code{profmem_begin()}
107 | performs an \emph{independent}, \emph{nested} profiling, which does not affect the
108 | already active one. When the active one completes, it will contain all
109 | memory events also collected by the nested profiling as if the nested one
110 | never occurred.
111 |
112 | Profiling gathered by \pkg{profmem} \emph{will} be corrupted if the code profiled
113 | calls \code{\link[utils:Rprofmem]{utils::Rprofmem()}}, with the exception of such calls done via the
114 | \pkg{profmem} package itself.
115 | }
116 | \examples{
117 | if (capabilities("profmem")) {
118 |
119 | ## Memory profile an R expression
120 | p <- profmem({
121 | x <- raw(1000)
122 | A <- matrix(rnorm(100), ncol = 10)
123 | })
124 |
125 | ## Display the results
126 | print(p)
127 |
128 | ## Total amount of memory allocation
129 | total(p)
130 |
131 | ## Allocations greater than 1 kB
132 | p2 <- subset(p, bytes > 1000)
133 | print(p2)
134 |
135 | ## The expression is evaluated in the calling environment
136 | str(x)
137 | str(A)
138 |
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, religion, or sexual identity
11 | and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the
27 | overall community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or
32 | advances of any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email
36 | address, without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the project lead.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
130 |
--------------------------------------------------------------------------------
/vignettes/profmem.md.rsp:
--------------------------------------------------------------------------------
1 | <%@meta language="R-vignette" content="--------------------------------
2 | %\VignetteIndexEntry{Simple Memory Profiling for R}
3 | %\VignetteAuthor{Henrik Bengtsson}
4 | %\VignetteKeyword{R}
5 | %\VignetteKeyword{package}
6 | %\VignetteKeyword{vignette}
7 | %\VignetteKeyword{memory}
8 | %\VignetteKeyword{profiling}
9 | %\VignetteKeyword{garbage collection}
10 | %\VignetteKeyword{data type}
11 | %\VignetteKeyword{coercion}
12 | %\VignetteEngine{R.rsp::rsp}
13 | %\VignetteTangle{FALSE}
14 | --------------------------------------------------------------------"%>
15 | <%
16 | library("R.utils")
17 |
18 | oopts <- options(
19 | "withCapture/newline" = FALSE,
20 | mc.cores = 2L,
21 | digits = 2L
22 | )
23 | set.seed(0xBEEF) ## Just to minimize diff when rebuilt
24 |
25 | ## WORKAROUND: Avoid spurious memory allocations that occurs
26 | ## occasional when running in a fresh R session
27 | options(profmem.threshold = 1000)
28 |
29 | ## Initiate the random number generator, because otherwise it will
30 | ## be done and captured during the first profiling below. It is not
31 | ## fully clear to me what the R internals behind this is. For instance,
32 | ## it is not enough to call `sample.int(n = 1)`. But calling rnorm(n = 1)
33 | ## seems to avoid the problem.
34 | rnorm(n=1)
35 | %>
36 |
37 | # Simple Memory Profiling in R
38 |
39 | ## Introduction
40 |
41 | The `profmem()` function of the [profmem] package provides an easy way to profile the memory usage of an R expression. It logs all memory allocations done in R. Profiling memory allocations is helpful when we, for instance, try to understand why a certain piece of R code consumes more memory than expected.
42 |
43 | The `profmem()` function builds upon existing memory profiling features available in R. It logs _every_ memory allocation done by plain R code as well as those done by native code such as C and Fortran. For each entry, it records the size (in bytes) and the name of the functions on the call stack.
44 | For example,
45 |
46 | <% if (capabilities('profmem')) { %>
47 | <% gc() %>
48 | ```r
49 | <%=withCapture({
50 | library("profmem")
51 | options(profmem.threshold = 2000)
52 |
53 | p <- profmem({
54 | x <- integer(1000)
55 | Y <- matrix(rnorm(n = 10000), nrow = 100)
56 | })
57 | p
58 | })%>
59 | ```
60 | <% p <- na.omit(p) %>
61 | From this, we find that <%= p$bytes[1] %> bytes are allocated for integer vector `x`, which is because each integer value occupies 4 bytes of memory. The additional 40 bytes are due to the internal data structure used for each variable R. The size of this allocation can also be confirmed by the value of `object.size(x)`.
62 | We also see that `rnorm()`, which is called via `matrix()`, allocates <%= p$bytes[2] %> bytes, which reflects the <%= length(Y) %> double values each occupying 8 bytes.
63 | Finally, the following entry reflects the memory allocation of <%= tail(p$bytes, 1) %> bytes done by `matrix()` itself.
64 | <% } else { %>
65 |
66 | **WARNING: This vignette was compiled with an R version that was built with memory profiling disabled, cf. `capabilities('profmem')`. Please redo!**
67 |
68 | <% } ## if (capabilities('profmem')) %>
69 |
70 |
71 | ## An example where memory profiling can make a difference
72 |
73 | Assume we want to set a 100-by-100 matrix with missing values except for element (1,1) that we assign to be zero. This can be done as:
74 | ```r
75 | <%=withCapture({
76 | x <- matrix(nrow = 100, ncol = 100)
77 | x[1, 1] <- 0
78 | x[1:3, 1:3]
79 | })%>
80 | ```
81 | This looks fairly innocent, but it turns out that it is very inefficient - both when it comes to memory and speed. The reason is that the default value used by `matrix()` is `NA`, which is of type _logical_. This means that initially `x` is a _logical_ matrix not a _numeric_ matrix. When we the assign the (1,1) element the value `0`, which is a _numeric_, the matrix first has to be coerced to _numeric_ internally and then the zero is assigned. Profiling the memory will reveal this;
82 |
83 | <% if (capabilities('profmem')) { %>
84 | <% gc() %>
85 |
86 | ```r
87 | <%=withCapture({
88 | p <- profmem({
89 | x <- matrix(nrow = 100, ncol = 100)
90 | x[1, 1] <- 0
91 | })
92 | print(p, expr = FALSE)
93 | })%>
94 | ```
95 | The first entry is for the logical matrix with 10,000 elements (= 4 \* 10,000 bytes + small header) that we allocate. The second entry reveals the coercion of this matrix to a numeric matrix (= 8 \* 10,000 elements + small header).
96 |
97 | To avoid this, we make sure to create a numeric matrix upfront as:
98 | ```r
99 | <%=withCapture({
100 | p <- profmem({
101 | x <- matrix(NA_real_, nrow = 100, ncol = 100)
102 | x[1, 1] <- 0
103 | })
104 | print(p, expr = FALSE)
105 | })%>
106 | ```
107 |
108 | Using the [microbenchmark] package, we can also quantify the extra overhead in processing time that is introduced due to the logical-to-numeric coercion;
109 | ```r
110 | <%=withCapture({
111 | library("microbenchmark")
112 | stats <- microbenchmark(
113 | bad = {
114 | x <- matrix(nrow = 100, ncol = 100)
115 | x[1, 1] <- 0
116 | },
117 | good = {
118 | x <- matrix(NA_real_, nrow = 100, ncol = 100)
119 | x[1, 1] <- 0
120 | },
121 | times = 100,
122 | unit = "ms"
123 | )
124 | stats
125 | })%>
126 | ```
127 | The inefficient approach is 1.5-2 times slower than the efficient one.
128 |
129 |
130 | The above illustrates the value of profiling your R code's memory usage and thanks to `profmem()` we can compare the amount of memory allocated of two alternative implementations. Being able to write memory-efficient R code becomes particularly important when working with large data sets, where an inefficient implementation may even prevent us from performing an analysis because we end up running out of memory. Moreover, each memory allocation will eventually have to be deallocated and in R this is done automatically by the garbage collector, which runs in the background and recovers any blocks of memory that are allocated but no longer in use. Garbage collection takes time and therefore slows down the overall processing in R even further.
131 |
132 | <% } else { %>
133 |
134 | **WARNING: This vignette was compiled with an R version that was built with memory profiling disabled, cf. `capabilities('profmem')`. Please redo!**
135 |
136 | <% } ## if (capabilities('profmem')) %>
137 |
138 |
139 | ## What is logged?
140 |
141 | The `profmem()` function uses the `utils::Rprofmem()` function for logging memory allocation events to a temporary file. The logged events are parsed and returned as an in-memory R object in a format that is convenient to work with. All memory allocations that are done via the native `allocVector3()` part of R's native API are logged, which means that nearly all memory allocations are logged. Any objects allocated this way are automatically deallocated by R's garbage collector at some point. Garbage collection events are _not_ logged by `profmem()`.
142 | Allocations _not_ logged are those done by non-R native libraries or R packages that use native code `Calloc() / Free()` for internal objects. Such objects are _not_ handled by the R garbage collector.
143 |
144 | ### Difference between `utils::Rprofmem()` and `utils::Rprof(memory.profiling = TRUE)`
145 | In addition to `utils::Rprofmem()`, R also provides `utils::Rprof(memory.profiling = TRUE)`. Despite the close similarity of their names, they use completely different approaches for profiling the memory usage. As explained above, the former logs _all individual_ (`allocVector3()`) memory allocation whereas the latter probes the _total_ memory usage of R at regular time intervals. If memory is allocated and deallocated between two such probing time points, `utils::Rprof(memory.profiling = TRUE)` will not log that memory whereas `utils::Rprofmem()` will pick it up. On the other hand, with `utils::Rprofmem()` it is not possible to quantify the total memory _usage_ at a given time because it only logs _allocations_ and does therefore not reflect deallocations done by the garbage collector.
146 |
147 |
148 | ## Requirements
149 |
150 | In order for `profmem()` to work, R must have been built with memory profiling enabled. If not, `profmem()` will produce an error with an informative message. To manually check whether an R binary was built with this enable or not, do:
151 | ```r
152 | <%=withCapture({
153 | capabilities('profmem')
154 | })%>
155 | ```
156 | The overhead of running an R installation with memory profiling enabled compared to one without is neglectable / non-measurable.
157 |
158 | Volunteers of the R Project provide and distribute pre-built binaries of the R software for all the major operating system via [CRAN]. [It has been confirmed](https://github.com/HenrikBengtsson/profmem/issues/2) that the R binaries for Windows, macOS (both by CRAN and by the AT&T Research Lab), and for Linux (\*) all have been built with memory profiling enabled. (\*) For Linux, this has been confirmed for the Debian/Ubuntu distribution but yet not for the other Linux distributions.
159 |
160 |
161 | In all other cases, to enable memory profiling, which is _only_ needed if `capabilities("profmem")` returns `FALSE`, R needs to be _configured_ and _built from source_ using:
162 | ```sh
163 | $ ./configure --enable-memory-profiling
164 | $ make
165 | ```
166 | For more information, please see the 'R Installation and Administration' documentation that comes with all R installations.
167 |
168 |
169 |
170 | [CRAN]: https://cran.r-project.org/
171 | [profmem]: https://cran.r-project.org/package=profmem
172 | [microbenchmark]: https://cran.r-project.org/package=microbenchmark
173 |
174 | ---
175 | Copyright Henrik Bengtsson, 2016-2018
176 |
177 | <%
178 | ## Clean up
179 | options(oopts)
180 | %>
181 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | # profmem: Simple Memory Profiling for R
8 |
9 | ## Introduction
10 |
11 | The `profmem()` function of the [profmem] package provides an easy way to profile the memory usage of an R expression. It logs all memory allocations done in R. Profiling memory allocations is helpful when we, for instance, try to understand why a certain piece of R code consumes more memory than expected.
12 |
13 | The `profmem()` function builds upon existing memory profiling features available in R. It logs _every_ memory allocation done by plain R code as well as those done by native code such as C and Fortran. For each entry, it records the size (in bytes) and the name of the functions on the call stack.
14 | For example,
15 |
16 | ```r
17 | > library("profmem")
18 | > options(profmem.threshold = 2000)
19 | > p <- profmem({
20 | + x <- integer(1000)
21 | + Y <- matrix(rnorm(n = 10000), nrow = 100)
22 | + })
23 | > p
24 | Rprofmem memory profiling of:
25 | {
26 | x <- integer(1000)
27 | Y <- matrix(rnorm(n = 10000), nrow = 100)
28 | }
29 | Memory allocations (>= 2000 bytes):
30 | what bytes calls
31 | 1 alloc 4048 integer()
32 | 2 alloc 80048 matrix() -> rnorm()
33 | 3 alloc 80048 matrix()
34 | total 164144
35 | ```
36 | From this, we find that 4048 bytes are allocated for integer vector `x`, which is because each integer value occupies 4 bytes of memory. The additional 40 bytes are due to the internal data structure used for each variable R. The size of this allocation can also be confirmed by the value of `object.size(x)`.
37 | We also see that `rnorm()`, which is called via `matrix()`, allocates 80048 bytes, which reflects the 10000 double values each occupying 8 bytes.
38 | Finally, the following entry reflects the memory allocation of 80048 bytes done by `matrix()` itself.
39 |
40 |
41 | ## An example where memory profiling can make a difference
42 |
43 | Assume we want to set a 100-by-100 matrix with missing values except for element (1,1) that we assign to be zero. This can be done as:
44 | ```r
45 | > x <- matrix(nrow = 100, ncol = 100)
46 | > x[1, 1] <- 0
47 | > x[1:3, 1:3]
48 | [,1] [,2] [,3]
49 | [1,] 0 NA NA
50 | [2,] NA NA NA
51 | [3,] NA NA NA
52 | ```
53 | This looks fairly innocent, but it turns out that it is very inefficient - both when it comes to memory and speed. The reason is that the default value used by `matrix()` is `NA`, which is of type _logical_. This means that initially `x` is a _logical_ matrix not a _numeric_ matrix. When we the assign the (1,1) element the value `0`, which is a _numeric_, the matrix first has to be coerced to _numeric_ internally and then the zero is assigned. Profiling the memory will reveal this;
54 |
55 |
56 | ```r
57 | > p <- profmem({
58 | + x <- matrix(nrow = 100, ncol = 100)
59 | + x[1, 1] <- 0
60 | + })
61 | > print(p, expr = FALSE)
62 | Memory allocations (>= 2000 bytes):
63 | what bytes calls
64 | 1 alloc 40048 matrix()
65 | 2 alloc 80048
66 | total 120096
67 | ```
68 | The first entry is for the logical matrix with 10,000 elements (= 4 \* 10,000 bytes + small header) that we allocate. The second entry reveals the coercion of this matrix to a numeric matrix (= 8 \* 10,000 elements + small header).
69 |
70 | To avoid this, we make sure to create a numeric matrix upfront as:
71 | ```r
72 | > p <- profmem({
73 | + x <- matrix(NA_real_, nrow = 100, ncol = 100)
74 | + x[1, 1] <- 0
75 | + })
76 | > print(p, expr = FALSE)
77 | Memory allocations (>= 2000 bytes):
78 | what bytes calls
79 | 1 alloc 80048 matrix()
80 | total 80048
81 | ```
82 |
83 | Using the [microbenchmark] package, we can also quantify the extra overhead in processing time that is introduced due to the logical-to-numeric coercion;
84 | ```r
85 | > library("microbenchmark")
86 | > stats <- microbenchmark(bad = {
87 | + x <- matrix(nrow = 100, ncol = 100)
88 | + x[1, 1] <- 0
89 | + }, good = {
90 | + x <- matrix(NA_real_, nrow = 100, ncol = 100)
91 | + x[1, 1] <- 0
92 | + }, times = 100, unit = "ms")
93 | > stats
94 | Unit: milliseconds
95 | expr min lq mean median uq max neval
96 | bad 0.022 0.024 0.025 0.025 0.026 0.045 100
97 | good 0.012 0.013 0.015 0.014 0.015 0.031 100
98 | ```
99 | The inefficient approach is 1.5-2 times slower than the efficient one.
100 |
101 |
102 | The above illustrates the value of profiling your R code's memory usage and thanks to `profmem()` we can compare the amount of memory allocated of two alternative implementations. Being able to write memory-efficient R code becomes particularly important when working with large data sets, where an inefficient implementation may even prevent us from performing an analysis because we end up running out of memory. Moreover, each memory allocation will eventually have to be deallocated and in R this is done automatically by the garbage collector, which runs in the background and recovers any blocks of memory that are allocated but no longer in use. Garbage collection takes time and therefore slows down the overall processing in R even further.
103 |
104 |
105 |
106 | ## What is logged?
107 |
108 | The `profmem()` function uses the `utils::Rprofmem()` function for logging memory allocation events to a temporary file. The logged events are parsed and returned as an in-memory R object in a format that is convenient to work with. All memory allocations that are done via the native `allocVector3()` part of R's native API are logged, which means that nearly all memory allocations are logged. Any objects allocated this way are automatically deallocated by R's garbage collector at some point. Garbage collection events are _not_ logged by `profmem()`.
109 | Allocations _not_ logged are those done by non-R native libraries or R packages that use native code `Calloc() / Free()` for internal objects. Such objects are _not_ handled by the R garbage collector.
110 |
111 | ### Difference between `utils::Rprofmem()` and `utils::Rprof(memory.profiling = TRUE)`
112 | In addition to `utils::Rprofmem()`, R also provides `utils::Rprof(memory.profiling = TRUE)`. Despite the close similarity of their names, they use completely different approaches for profiling the memory usage. As explained above, the former logs _all individual_ (`allocVector3()`) memory allocation whereas the latter probes the _total_ memory usage of R at regular time intervals. If memory is allocated and deallocated between two such probing time points, `utils::Rprof(memory.profiling = TRUE)` will not log that memory whereas `utils::Rprofmem()` will pick it up. On the other hand, with `utils::Rprofmem()` it is not possible to quantify the total memory _usage_ at a given time because it only logs _allocations_ and does therefore not reflect deallocations done by the garbage collector.
113 |
114 |
115 | ## Requirements
116 |
117 | In order for `profmem()` to work, R must have been built with memory profiling enabled. If not, `profmem()` will produce an error with an informative message. To manually check whether an R binary was built with this enable or not, do:
118 | ```r
119 | > capabilities("profmem")
120 | profmem
121 | TRUE
122 | ```
123 | The overhead of running an R installation with memory profiling enabled compared to one without is neglectable / non-measurable.
124 |
125 | Volunteers of the R Project provide and distribute pre-built binaries of the R software for all the major operating system via [CRAN]. [It has been confirmed](https://github.com/HenrikBengtsson/profmem/issues/2) that the R binaries for Windows, macOS (both by CRAN and by the AT&T Research Lab), and for Linux (\*) all have been built with memory profiling enabled. (\*) For Linux, this has been confirmed for the Debian/Ubuntu distribution but yet not for the other Linux distributions.
126 |
127 |
128 | In all other cases, to enable memory profiling, which is _only_ needed if `capabilities("profmem")` returns `FALSE`, R needs to be _configured_ and _built from source_ using:
129 | ```sh
130 | $ ./configure --enable-memory-profiling
131 | $ make
132 | ```
133 | For more information, please see the 'R Installation and Administration' documentation that comes with all R installations.
134 |
135 |
136 |
137 | [CRAN]: https://cran.r-project.org/
138 | [profmem]: https://cran.r-project.org/package=profmem
139 | [microbenchmark]: https://cran.r-project.org/package=microbenchmark
140 |
141 |
142 | ## Installation
143 | R package profmem is available on [CRAN](https://cran.r-project.org/package=profmem) and can be installed in R as:
144 | ```r
145 | install.packages("profmem")
146 | ```
147 |
148 |
149 | ### Pre-release version
150 |
151 | To install the pre-release version that is available in Git branch `develop` on GitHub, use:
152 | ```r
153 | remotes::install_github("HenrikBengtsson/profmem", ref="develop")
154 | ```
155 | This will install the package from source.
156 |
157 |
158 |
159 |
160 | ## Contributing
161 |
162 | To contribute to this package, please see [CONTRIBUTING.md](CONTRIBUTING.md).
163 |
164 |
--------------------------------------------------------------------------------
/R/profmem.R:
--------------------------------------------------------------------------------
1 | #' Memory profiling R
2 | #'
3 | #' `profmem()` evaluates and memory profiles an \R expression.
4 | #'
5 | #' @param expr An \R expression to be evaluated and profiled.
6 | #'
7 | #' @param envir The environment in which the expression should be evaluated.
8 | #'
9 | #' @param substitute Should `expr` be [base::substitute()]:d or not.
10 | #'
11 | #' @param threshold The smallest memory allocation (in bytes) to log.
12 | #'
13 | #' @param on_error (character) Controls whether evaluation errors should
14 | #' signal an error (`"error"`), a warning (`"warning"), or be ignored
15 | #' (`"ignore"`; default).
16 | #'
17 | #' @return `profmem()` and `profmem_end()` returns the collected allocation
18 | #' data as an `Rprofmem` data.frame with additional attributes set.
19 | #' An `Rprofmem` data.frame has columns `what`, `bytes`, and `trace`, with:
20 | #'
21 | #' * `what`: (character) type of memory event;
22 | #' either `"alloc"` or `"new page"`
23 | #' * `bytes`: (numeric) number of bytes allocated or `NA_real_`
24 | #' (when `what` is `"new page"`)
25 | #' * `trace`: (list of character vectors) zero or more function names
26 | #'
27 | #' The attributes set are:
28 | #'
29 | #' * `threshold` : The threshold used (= argument `threshold`)
30 | #' * `expression`: The expression profiled (= argument `expr`)
31 | #' * `value` : The value of the evaluated expression
32 | #' (only set if there was no error)
33 | #' * `error` : The error object in case the evaluation failed
34 | #' (only set if there was an error)
35 | #'
36 | #' @details
37 | #' In order for memory profiling to work, \R must have been _built_ with memory
38 | #' profiling enabled. Function
39 | #' \code{\link[base:capabilities]{base::capabilities("profmem")}} will
40 | #' return `TRUE` of it is enabled, otherwise `FALSE`.
41 | #' If memory profiling is _not_ supported, `profmem()` and `profmem_begin()`
42 | #' will produce an informative error. The pre-built \R binaries on
43 | #' [CRAN](https://cran.r-project.org/) support memory profiling.
44 | #'
45 | #' What is logged? The `profmem()` function uses [utils::Rprofmem()] for
46 | #' logging memory, which logs all memory _allocations_ that are done via the
47 | #' \R framework. Specifically, the logger is tied to `allocVector3()` part
48 | #' of \R's native API. This means that nearly all memory allocations done
49 | #' in \R are logged. _Neither_ memory deallocations _nor_ garbage collection
50 | #' events are logged. Furthermore, allocations done by non-\R native libraries
51 | #' or \R packages that use native code `Calloc() / Free()` for internal objects
52 | #' are also _not_ logged.
53 | #'
54 | #' Any memory events that would occur due to calling any of the \pkg{profmem}
55 | #' functions themselves will _not_ be logged and _not_ be part of the returned
56 | #' profile data (regardless whether memory profiling is active or not).
57 | #' This is intentional.
58 | #'
59 | #' If a profmem profiling is already active, `profmem()` and `profmem_begin()`
60 | #' performs an _independent_, _nested_ profiling, which does not affect the
61 | #' already active one. When the active one completes, it will contain all
62 | #' memory events also collected by the nested profiling as if the nested one
63 | #' never occurred.
64 | #'
65 | #' Profiling gathered by \pkg{profmem} _will_ be corrupted if the code profiled
66 | #' calls [utils::Rprofmem()], with the exception of such calls done via the
67 | #' \pkg{profmem} package itself.
68 | #'
69 | #'
70 | #' @example incl/profmem.R
71 | #'
72 | #' @export
73 | #' @importFrom utils Rprofmem
74 | profmem <- function(expr, envir = parent.frame(), substitute = TRUE, threshold = getOption("profmem.threshold", 0L), on_error = c("ignore", "warning", "error")) {
75 | if (substitute) expr <- substitute(expr)
76 | on_error <- match.arg(on_error, choices = c("ignore", "warning", "error"))
77 |
78 | profmem_begin(threshold = threshold)
79 |
80 | ## Profile memory
81 | error <- NULL
82 | value <- tryCatch({
83 | eval(expr, envir=envir, enclos = baseenv())
84 | }, error = function(ex) {
85 | error <<- ex
86 | NULL
87 | })
88 |
89 | pm <- profmem_end()
90 |
91 | if (on_error != "ignore") {
92 | action <- switch(on_error, error = stop, warning = warning)
93 | action(paste("profmem::profmem() detected a run-time error:",
94 | conditionMessage(error)))
95 | }
96 |
97 | ## Annotate
98 | attr(pm, "expression") <- expr
99 | attr(pm, "value") <- value
100 | attr(pm, "error") <- error
101 |
102 | pm
103 | } ## profmem()
104 |
105 |
106 | #' Memory profiling R
107 | #'
108 | #' `profmem_begin()` starts the memory profiling of all the following \R
109 | #' evaluations until `profmem_end()` is called.
110 | #'
111 | #' @return `profmem_begin()` returns (invisibly) the number of nested profmem
112 | #' session currently active.
113 | #'
114 | #' @rdname profmem
115 | #' @export
116 | profmem_begin <- function(threshold = getOption("profmem.threshold", 0L)) {
117 | ## Is memory profiling supported?
118 | if (!capableOfProfmem()) {
119 | msg <- "Profiling of memory allocations is not supported on this R system (capabilities('profmem') reports FALSE). See help('tracemem')."
120 | if (.Platform$OS.type == "unix") {
121 | msg <- paste(msg, "To enable memory profiling for R on Linux, R needs to be configured and built using './configure --enable-memory-profiling'.")
122 | }
123 | stop(msg)
124 | }
125 |
126 | threshold <- as.integer(threshold)
127 | stop_if_not(length(threshold) == 1, is.finite(threshold), threshold >= 0L)
128 |
129 | depth <- profmem_stack("depth")
130 | if (depth > 0) {
131 | threshold_parent <- profmem_stack("threshold")
132 | if (threshold > threshold_parent) {
133 | warning(sprintf("Nested profmem threshold (%d bytes) cannot be greater than the threshold (%d bytes) of active profmem sessions (n = %d). Will use the active threshold instead.", threshold, threshold_parent, depth))
134 | threshold <- threshold_parent
135 | }
136 | }
137 |
138 | profmem_suspend()
139 |
140 | ## Push new level
141 | depth <- profmem_stack("push", threshold = threshold)
142 |
143 | profmem_resume()
144 |
145 | invisible(depth)
146 | }
147 |
148 | #' @rdname profmem
149 | #' @export
150 | profmem_end <- function() {
151 | profmem_suspend()
152 |
153 | depth <- profmem_stack("depth")
154 | if (depth == 0) {
155 | stop("Did you forget to call profmem_begin()?")
156 | }
157 |
158 | data <- profmem_stack("pop")
159 |
160 | profmem_resume()
161 |
162 | data
163 | }
164 |
165 | #' `profmem_suspend()` suspends an active profiling until resumed by
166 | #' `profmem_resume()` or ended by `profmem_end()`.
167 | #' Calling `profmem_begin()` or `profmem()` will resume any suspended
168 | #' profiling. _Nested_ resuming and suspending is _not_ supported;
169 | #' it is a global state.
170 | #'
171 | #' @return `profmem_suspend()` and `profmem_resume()` returns nothing.
172 | #'
173 | #' @rdname profmem
174 | #' @importFrom utils Rprofmem
175 | #' @export
176 | profmem_suspend <- function() {
177 | ## Works regardless of active Rprofmem exists or not
178 | Rprofmem("")
179 |
180 | profmem_stack("suspend")
181 |
182 | ## Nothing more to do?
183 | if (profmem_stack("depth") == 0) return()
184 |
185 | ## Import current log
186 | drop <- length(sys.calls()) + 4L
187 | pathname <- profmem_pathname()
188 | data <- readRprofmem(pathname, drop = drop)
189 | attr(data, "threshold") <- profmem_stack("threshold")
190 | profmem_stack("append", data)
191 |
192 | invisible()
193 | }
194 |
195 | #' @rdname profmem
196 | #' @importFrom utils Rprofmem
197 | #' @export
198 | profmem_resume <- function() {
199 | threshold <- profmem_stack("threshold")
200 | pathname <- profmem_pathname()
201 | Rprofmem(filename = pathname, threshold = threshold)
202 | profmem_stack("resume")
203 | invisible()
204 | }
205 |
206 |
207 | #' `profmem_status()` checks whether there is an active profmem session
208 | #' or not, and whether it is suspended or not.
209 | #'
210 | #' @return `profmem_status()` returns `"inactive"`, `"active"`,
211 | #' or `"suspended"`.
212 | #'
213 | #' @rdname profmem
214 | #' @export
215 | profmem_status <- function() {
216 | profmem_stack("status")
217 | }
218 |
219 | #' `profmem_depth()` gets the number of nested / stacked profmem sessions.
220 | #'
221 | #' @return `promem_depth()` returns a non-negative integer.
222 | #'
223 | #' @rdname profmem
224 | #' @export
225 | profmem_depth <- function() {
226 | profmem_stack("depth")
227 | }
228 |
229 |
230 |
231 | profmem_pathname <- local({
232 | pathname <- NULL
233 | function() {
234 | if (!is.null(pathname)) return(pathname)
235 | pathname <<- tempfile(pattern = "profmem.", fileext = ".Rprofmem.out")
236 | pathname
237 | }
238 | })
239 |
240 | profmem_stack <- local({
241 | empty <- data.frame(what = NULL, bytes = NULL, trace = NULL, stringsAsFactors = FALSE)
242 | empty <- structure(empty, class = c("Rprofmem", "data.frame"), threshold = 0L)
243 |
244 | stack <- list()
245 | suspended <- FALSE
246 |
247 | function(action = c("status", "depth", "threshold", "push", "pop", "append", "suspend", "resume"),
248 | data = empty, threshold = 0L) {
249 | action <- match.arg(action)
250 |
251 | ## Status queries
252 | if (action == "status") {
253 | if (length(stack) == 0) return("inactive")
254 | if (suspended) return("suspended")
255 | return("active")
256 | } else if (action == "depth") {
257 | return(length(stack))
258 | } else if (action == "threshold") {
259 | if (length(stack) == 0) return(NA_integer_)
260 | threshold <- attr(stack[[1]], "threshold")
261 | return(threshold)
262 | }
263 |
264 | ## State changing
265 | if (action == "suspend") {
266 | suspended <<- TRUE
267 | ## WORKAROUND: If not, above won't happen /2018-01-29
268 | dummy <- suspended
269 | return(invisible(suspended))
270 | } else if (action == "resume") {
271 | suspended <<- FALSE
272 | ## WORKAROUND: If not, above won't happen /2018-01-29
273 | dummy <- suspended
274 | return(invisible(suspended))
275 | }
276 |
277 |
278 | ## Stack changing
279 | if (action == "push") {
280 | stop_if_not(inherits(data, "Rprofmem"),
281 | length(threshold) == 1, is.finite(threshold),
282 | is.integer(threshold), threshold >= 0L)
283 | attr(data, "threshold") <- threshold
284 | stack <<- c(stack, list(data))
285 | # message("PUSH: stack depth: ", length(stack))
286 | return(length(stack))
287 | } else if (action == "pop") {
288 | depth <- length(stack)
289 | if (depth == 0) stop("Cannot 'pop' - profmem stack is empty")
290 | value <- stack[[depth]]
291 | stack <<- stack[-depth]
292 | depth <- length(stack)
293 | if (depth >= 1) {
294 | tmp <- stack[[depth]]
295 | tmp <- if (is.null(tmp)) value else c(tmp, value)
296 | stack[[depth]] <<- tmp
297 | }
298 | # message("POP: stack depth: ", length(stack))
299 | return(value)
300 | } else if (action == "append") {
301 | depth <- length(stack)
302 | if (depth == 0) stop("Cannot 'append' - profmem stack is empty")
303 | stop_if_not(inherits(data, "Rprofmem"),
304 | length(threshold) == 1, is.finite(threshold),
305 | is.integer(threshold), threshold >= 0L)
306 | attr(data, "threshold") <- threshold
307 | value <- stack[[depth]]
308 | value <- if (is.null(value)) data else c(value, data)
309 | stack[[depth]] <<- value
310 | # message("APPEND: stack depth: ", length(stack))
311 | return(invisible(value))
312 | }
313 | }
314 | })
315 |
316 |
317 |
318 | ### TODO:
319 | ###
320 | ### profmem_add_string <- function(msg, ...) {
321 | ### pathname <- profmem_pathname()
322 | ### cat(msg, file = pathname, append = file_test("-f", pathname))
323 | ### }
324 | ###
325 | ### ## FIXME: This produces lots of extra memory allocations, which
326 | ### ## we don't want to inject.
327 | ### profmem_add_note <- function(..., timestamp = TRUE) {
328 | ### msg <- sprintf(...)
329 | ### if (timestamp) {
330 | ### msg <- sprintf("[%s] %s", format(Sys.time(), "%Y%m%d-%H%M%S"), msg)
331 | ### }
332 | ### msg <- sprintf("# %s\n", msg)
333 | ### profmem_add_string(msg)
334 | ### }
335 |
336 |
--------------------------------------------------------------------------------
/.make/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for R packages
2 |
3 | # CORE MACROS
4 | ifeq ($(OS), Windows_NT)
5 | CD=cd
6 | CURDIR=$(subst \,/,$(shell cmd.exe /C cd))
7 | FSEP=;
8 | else
9 | CD=cd -P "$(CURDIR)"; cd # This handles the case when CURDIR is a softlink
10 | FSEP=:
11 | endif
12 | CP=cp
13 | MAKE=make
14 | MV=mv
15 | RM=rm -f
16 | MKDIR=mkdir -p
17 | RMDIR=$(RM) -r
18 | GIT=git
19 |
20 | # PACKAGE MACROS
21 | PKG_VERSION := $(shell grep -i ^version DESCRIPTION | cut -d : -d \ -f 2)
22 | PKG_NAME := $(shell grep -i ^package DESCRIPTION | cut -d : -d \ -f 2)
23 | PKG_DIR := $(shell basename "$(CURDIR)")
24 | PKG_DIR := $(CURDIR)
25 | PKG_TARBALL := $(PKG_NAME)_$(PKG_VERSION).tar.gz
26 | PKG_ZIP := $(PKG_NAME)_$(PKG_VERSION).zip
27 | PKG_TGZ := $(PKG_NAME)_$(PKG_VERSION).tgz
28 |
29 | # FILE MACROS
30 | FILES_R := $(wildcard R/*.R)
31 | FILES_DATA := $(wildcard data/*)
32 | FILES_MAN := $(wildcard man/*.Rd)
33 | FILES_INCL := $(wildcard incl/*)
34 | FILES_INST := $(wildcard inst/* inst/*/* inst/*/*/* inst/*/*/*/*)
35 | FILES_VIGNETTES := $(wildcard vignettes/* vignettes/.install_extras)
36 | FILES_SRC := $(wildcard src/* src/*/* src/*/*/* src/*/*/*/* src/*/*/*/*/* src/*/*/*/*/*/* src/*/*/*/*/*/*/* src/*/*/*/*/*/*/*/*)
37 | FILES_SRC_C := $(wildcard src/*.c)
38 | FILES_SRC_H := $(wildcard src/*.h)
39 | FILES_TESTS := $(wildcard tests/*.R)
40 | FILES_NEWS := $(wildcard NEWS inst/NEWS)
41 | FILES_MISC := $(wildcard README.md)
42 | FILES_ROOT := DESCRIPTION NAMESPACE $(wildcard .Rbuildignore .Rinstignore)
43 | PKG_FILES := $(FILES_ROOT) $(FILES_NEWS) $(FILES_R) $(FILES_DATA) $(FILES_MAN) $(FILES_INST) $(FILES_VIGNETTES) $(FILES_SRC) $(FILES_TESTS) $(FILES_MISC)
44 | FILES_MAKEFILE := $(wildcard ../../Makefile)
45 |
46 | # Has vignettes in 'vignettes/' or 'inst/doc/'?
47 | DIR_VIGNS := $(wildcard vignettes inst/doc)
48 |
49 | # R MACROS
50 | R = R
51 | R_SCRIPT = Rscript
52 | R_HOME := $(shell $(R_SCRIPT) -e "cat(R.home())")
53 |
54 | ## R_USE_CRAN := $(shell $(R_SCRIPT) -e "cat(Sys.getenv('R_USE_CRAN', 'FALSE'))")
55 | R_NO_INIT := --no-init-file
56 | R_VERSION_STATUS := $(shell $(R_SCRIPT) -e "status <- tolower(R.version[['status']]); if (regexpr('unstable', status) != -1L) status <- 'devel'; cat(status)")
57 | R_VERSION_X_Y := $(shell $(R_SCRIPT) -e "cat(gsub('[.][0-9]+$$', '', getRversion()))")
58 | R_VERSION := $(shell $(R_SCRIPT) -e "cat(as.character(getRversion()))")
59 | R_VERSION_3_3_OR_NEWER := $(shell $(R_SCRIPT) -e "cat(getRversion() >= '3.3.0')")
60 | R_VERSION_FULL := $(R_VERSION)$(R_VERSION_STATUS)
61 | R_LIBS_USER_X := $(shell $(R_SCRIPT) -e "cat(.libPaths()[1])")
62 | R_INCLUDE := $(shell $(R_SCRIPT) -e "cat(R.home('include'))")
63 | R_OUTDIR := ../_R-$(R_VERSION_FULL)
64 | ## R_BUILD_OPTS :=
65 | ## R_BUILD_OPTS := $(R_BUILD_OPTS) --no-build-vignettes
66 | R_CHECK_OUTDIR := $(R_OUTDIR)/$(PKG_NAME).Rcheck
67 | _R_CHECK_CRAN_INCOMING_ = $(shell $(R_SCRIPT) -e "cat(Sys.getenv('_R_CHECK_CRAN_INCOMING_', 'FALSE'))")
68 | _R_CHECK_XREFS_REPOSITORIES_ = $(shell if test "$(_R_CHECK_CRAN_INCOMING_)" = "TRUE"; then echo ""; else echo "invalidURL"; fi)
69 | _R_CHECK_FULL_ = $(shell $(R_SCRIPT) -e "cat(Sys.getenv('_R_CHECK_FULL_', ''))")
70 | R_CHECK_OPTS = --as-cran --timings $(shell if test "$(_R_CHECK_USE_VALGRIND_)" = "TRUE"; then echo "--use-valgrind"; fi)
71 | R_RD4PDF = $(shell $(R_SCRIPT) -e "if (getRversion() < 3) cat('times,hyper')")
72 | R_CRAN_OUTDIR := $(R_OUTDIR)/$(PKG_NAME)_$(PKG_VERSION).CRAN
73 |
74 | HAS_ASPELL := $(shell $(R_SCRIPT) -e "cat(Sys.getenv('HAS_ASPELL', !inherits(try(aspell('DESCRIPTION', control=c('--master=en_US', '--add-extra-dicts=en_GB'), dictionaries='en_stats', program='aspell'), silent=TRUE), 'try-error')))")
75 |
76 | ## Git related
77 | GIT_BRANCH := $(shell $(GIT) symbolic-ref --short HEAD)
78 | GIT_BRANCH := $(subst /,-,$(GIT_BRANCH))
79 | GIT_COMMIT := $(shell $(GIT) log -1 --format="%h")
80 | R_LIBS_BRANCH := $(CURDIR)/.R/$(GIT_BRANCH)
81 |
82 |
83 | # Asserts proper Windows toolchain in R (>= 3.3.0)
84 | ifeq ($(OS), Windows_NT)
85 | ifeq ($(R_VERSION_3_3_OR_NEWER), TRUE)
86 | ifndef BINPREF
87 | $(error R (>= 3.3.0) on Windows: BINPREF not set)
88 | endif
89 | endif
90 | endif
91 |
92 |
93 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
94 | # Main
95 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
96 | all: build install check
97 |
98 |
99 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
100 | # Displays macros
101 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
102 | debug:
103 | @echo CURDIR=\'$(CURDIR)\'
104 | @echo R_HOME=\'$(R_HOME)\'
105 | @echo
106 | @echo PKG_DIR=\'$(PKG_DIR)\'
107 | @echo PKG_NAME=\'$(PKG_NAME)\'
108 | @echo PKG_VERSION=\'$(PKG_VERSION)\'
109 | @echo PKG_TARBALL=\'$(PKG_TARBALL)\'
110 | @echo
111 | @echo HAS_ASPELL=\'$(HAS_ASPELL)\'
112 | @echo
113 | @echo R=\'$(R)\'
114 | ## @echo R_USE_CRAN=\'$(R_USE_CRAN)\'
115 | @echo R_NO_INIT=\'$(R_NO_INIT)\'
116 | @echo R_SCRIPT=\'$(R_SCRIPT)\'
117 | @echo R_VERSION_X_Y=\'$(R_VERSION_X_Y)\'
118 | @echo R_VERSION=\'$(R_VERSION)\'
119 | @echo R_VERSION_STATUS=\'$(R_VERSION_STATUS)\'
120 | @echo R_VERSION_FULL=\'$(R_VERSION_FULL)\'
121 | @echo R_LIBS_USER_X=\'$(R_LIBS_USER_X)\'
122 | @echo R_INCLUDE=\'$(R_INCLUDE)\'
123 | @echo R_OUTDIR=\'$(R_OUTDIR)\'
124 | @echo
125 | @echo "Default packages:" $(shell $(R) --slave -e "cat(paste(getOption('defaultPackages'), collapse=', '))")
126 | @echo
127 | @echo R_BUILD_OPTS=\'$(R_BUILD_OPTS)\'
128 | @echo
129 | @echo R_CHECK_OUTDIR=\'$(R_CHECK_OUTDIR)\'
130 | @echo _R_CHECK_CRAN_INCOMING_=\'$(_R_CHECK_CRAN_INCOMING_)\'
131 | @echo _R_CHECK_XREFS_REPOSITORIES_=\'$(_R_CHECK_XREFS_REPOSITORIES_)\'
132 | @echo _R_CHECK_FULL_=\'$(_R_CHECK_FULL_)\'
133 | @echo R_CHECK_OPTS=\'$(R_CHECK_OPTS)\'
134 | @echo R_RD4PDF=\'$(R_RD4PDF)\'
135 | @echo
136 | @echo R_CRAN_OUTDIR=\'$(R_CRAN_OUTDIR)\'
137 | @echo R_VERSION_3_3_OR_NEWER=\'$(R_VERSION_3_3_OR_NEWER)\'
138 | @echo
139 |
140 |
141 | debug_full: debug
142 | @echo
143 | @echo FILES_ROOT=\'$(FILES_ROOT)\'
144 | @echo FILES_R=\'$(FILES_R)\'
145 | @echo FILES_DATA=\'$(FILES_DATA)\'
146 | @echo FILES_MAN=\'$(FILES_MAN)\'
147 | @echo FILES_INST=\'$(FILES_INST)\'
148 | @echo FILES_VIGNETTES=\'$(FILES_VIGNETTES)\'
149 | @echo FILES_SRC=\'$(FILES_SRC)\'
150 | @echo FILES_TESTS=\'$(FILES_TESTS)\'
151 | @echo FILES_INCL=\'$(FILES_INCL)\'
152 | @echo FILES_MISC=\'$(FILES_MISC)\'
153 | @echo
154 | @echo DIR_VIGNS=\'$(DIR_VIGNS)\'
155 | @echo dirname\(DIR_VIGNS\)=\'$(shell dirname $(DIR_VIGNS))\'
156 | @echo
157 | @echo GIT_BRANCH=\'$(GIT_BRANCH)\'
158 | @echo GIT_COMMIT=\'$(GIT_COMMIT)\'
159 | @echo R_LIBS_BRANCH=\'$(R_LIBS_BRANCH)\'
160 |
161 |
162 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
163 | # Update / install
164 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
165 | # Update existing packages
166 | update:
167 | $(R_SCRIPT) -e "try(update.packages(ask=FALSE)); source('http://bioconductor.org/biocLite.R'); biocLite(ask=FALSE);"
168 |
169 | # Install missing dependencies
170 | deps: DESCRIPTION
171 | $(MAKE) update
172 | $(R_SCRIPT) -e "x <- unlist(strsplit(read.dcf('DESCRIPTION',fields=c('Depends', 'Imports', 'Suggests')),',')); x <- gsub('([[:space:]]*|[(].*[)])', '', x); libs <- .libPaths()[file.access(.libPaths(), mode=2) == 0]; x <- unique(setdiff(x, c('R', rownames(installed.packages(lib.loc=libs))))); if (length(x) > 0) { try(install.packages(x)); x <- unique(setdiff(x, c('R', rownames(installed.packages(lib.loc=libs))))); source('http://bioconductor.org/biocLite.R'); biocLite(x); }"
173 |
174 | setup: update deps
175 | $(R_SCRIPT) -e "source('http://aroma-project.org/hbLite.R'); hbLite('R.oo')"
176 |
177 | ns:
178 | $(R_SCRIPT) -e "library('$(PKG_NAME)'); source('X:/devtools/NAMESPACE.R'); writeNamespaceSection('$(PKG_NAME)'); writeNamespaceImports('$(PKG_NAME)');"
179 |
180 |
181 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
182 | # Build source tarball
183 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
184 | $(R_OUTDIR)/$(PKG_TARBALL): $(PKG_FILES)
185 | $(MKDIR) $(R_OUTDIR)
186 | $(RM) $@
187 | $(CD) $(R_OUTDIR);\
188 | $(R) $(R_NO_INIT) CMD build $(R_BUILD_OPTS) $(PKG_DIR)
189 |
190 | build: $(R_OUTDIR)/$(PKG_TARBALL)
191 |
192 | build_force:
193 | $(RM) $(R_OUTDIR)/$(PKG_TARBALL)
194 | $(MAKE) install
195 |
196 | # Make sure the tarball is readable
197 | build_fix: $(R_OUTDIR)/$(PKG_TARBALL)
198 | ifeq ($(OS), Windows_NT)
199 | ifeq ($(USERNAME), hb)
200 | $(MKDIR) X:/tmp/$(R_VERSION_FULL)
201 | $(CP) -f $< X:/tmp/$(R_VERSION_FULL)/
202 | $(RM) $<
203 | $(MV) X:/tmp/$(R_VERSION_FULL)/$( $(PKG_NAME).Rcheck/.check.complete
259 |
260 | check: $(R_CHECK_OUTDIR)/.check.complete
261 |
262 | check_force:
263 | $(RM) -r $(R_CHECK_OUTDIR)
264 | $(MAKE) check
265 |
266 | clang:
267 | clang -c -pedantic -Wall -I$(R_INCLUDE) src/*.c
268 | $(RM) *.o
269 |
270 | clang-ubsan:
271 | clang -fsanitize=undefined -I$(R_INCLUDE) -c src/*.c
272 | $(RM) *.o
273 |
274 | valgrind_scan:
275 | grep -E "^==.*==[ ]+(at|by) 0x" $(R_CHECK_OUTDIR)/tests*/*.Rout | cat
276 | grep "^==.* ERROR SUMMARY:" $(R_CHECK_OUTDIR)/tests*/*.Rout | grep -v -F "ERROR SUMMARY: 0 errors" | cat
277 |
278 | valgrind:
279 | export _R_CHECK_USE_VALGRIND_=TRUE;\
280 | $(MAKE) check_force
281 | $(MAKE) valgrind_scan
282 |
283 | # Check the line width of incl/*.(R|Rex) files [max 100 chars in R devel]
284 | check_Rex:
285 | $(R_SCRIPT) -e "if (!file.exists('incl')) quit(status=0); setwd('incl/'); fs <- dir(pattern='[.](R|Rex)$$'); ns <- sapply(fs, function(f) max(nchar(readLines(f)))); ns <- ns[ns > 100]; print(ns); if (length(ns) > 0L) quit(status=1)"
286 |
287 |
288 | covr:
289 | $(R_SCRIPT) -e "c <- covr::package_coverage(quiet = FALSE); saveRDS(c, '.covr.rds'); print(c); covr::report(c)"
290 |
291 |
292 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
293 | # Install and build binaries
294 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
295 | $(R_OUTDIR)/$(PKG_ZIP): $(R_OUTDIR)/$(PKG_TARBALL) build_fix
296 | $(CD) $(R_OUTDIR);\
297 | $(R) --no-init-file CMD INSTALL --build --merge-multiarch $(PKG_TARBALL)
298 |
299 | binary: $(R_OUTDIR)/$(PKG_ZIP)
300 |
301 |
302 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
303 | # Build Rd help files from Rdoc comments
304 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
305 | rox:
306 | $(R_SCRIPT) -e "roxygen2::roxygenize()"
307 |
308 | Rd: check_Rex
309 | $(R_SCRIPT) -e "setwd('..'); Sys.setlocale(locale='C'); R.oo::compileRdoc('$(PKG_NAME)', path='$(PKG_DIR)')"
310 |
311 | %.Rd:
312 | $(R_SCRIPT) -e "setwd('..'); Sys.setlocale(locale='C'); R.oo::compileRdoc('$(PKG_NAME)', path='$(PKG_DIR)', '$*.R')"
313 |
314 | missing_Rd:
315 | $(R_SCRIPT) -e "x <- readLines('$(R_CHECK_OUTDIR)/00check.log'); from <- grep('Undocumented code objects:', x)+1; if (length(from) > 0L) { to <- grep('All user-level objects', x)-1; x <- x[from:to]; x <- gsub('^[ ]*', '', x); x <- gsub('[\']', '', x); cat(x, sep='\n', file='999.missingdocs.txt'); }"
316 |
317 | spell_Rd:
318 | $(R_SCRIPT) -e "f <- list.files('man', pattern='[.]Rd$$', full.names=TRUE); utils::aspell(f, filter='Rd')"
319 |
320 |
321 | spell_NEWS:
322 | $(R_SCRIPT) -e "utils::aspell('$(FILES_NEWS)')"
323 |
324 | spell:
325 | $(R_SCRIPT) -e "utils::aspell('DESCRIPTION', filter='dcf')"
326 |
327 |
328 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
329 | # Build package vignettes
330 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
331 | $(R_OUTDIR)/vigns: install
332 | $(MKDIR) $(R_OUTDIR)/vigns/$(shell dirname $(DIR_VIGNS))
333 | $(CP) DESCRIPTION $(R_OUTDIR)/vigns/
334 | $(CP) -r $(DIR_VIGNS) $(R_OUTDIR)/vigns/$(shell dirname $(DIR_VIGNS))
335 | $(CD) $(R_OUTDIR)/vigns;\
336 | $(R_SCRIPT) -e "v <- tools::buildVignettes(dir='.'); file.path(getwd(), v[['outputs']])"
337 |
338 | vignettes: $(R_OUTDIR)/vigns
339 |
340 |
341 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
342 | # Static code validation etc
343 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
344 | lint:
345 | $(R_SCRIPT) -e "library(lintr); lint_package(linters = with_defaults(commented_code_linter = NULL, closed_curly_linter = closed_curly_linter(allow_single_line = TRUE), open_curly_linter = open_curly_linter(allow_single_line = TRUE)))"
346 |
347 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
348 | # Run package tests
349 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
350 | $(R_OUTDIR)/tests/%.R: $(FILES_TESTS)
351 | $(RMDIR) $(R_OUTDIR)/tests
352 | $(MKDIR) $(R_OUTDIR)/tests
353 | $(CP) $? $(R_OUTDIR)/tests
354 |
355 | test_files: $(R_OUTDIR)/tests/*.R
356 |
357 | test: $(R_OUTDIR)/tests/%.R
358 | $(CD) $(R_OUTDIR)/tests;\
359 | $(R_SCRIPT) -e "for (f in list.files(pattern='[.]R$$')) { print(f); source(f, echo=TRUE) }"
360 |
361 | test_full: $(R_OUTDIR)/tests/%.R
362 | $(CD) $(R_OUTDIR)/tests;\
363 | export _R_CHECK_FULL_=TRUE;\
364 | $(R_SCRIPT) -e "for (f in list.files(pattern='[.]R$$')) { print(f); source(f, echo=TRUE) }"
365 |
366 |
367 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
368 | # Benchmarking
369 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
370 | R_branch:
371 | R_LIBS="$(R_LIBS_USER_X)$(FSEP)$(R_LIBS)" R_LIBS_USER="$(R_LIBS_BRANCH)" $(R)
372 |
373 | benchmark:
374 | $(MKDIR) .benchmark/$(PKG_VERSION);\
375 | $(CD) .benchmark/$(PKG_VERSION);\
376 | $(R_SCRIPT) -e "$(PKG_NAME):::benchmark('index')"
377 |
378 | benchmark_branch:
379 | $(MKDIR) .benchmark/$(PKG_VERSION)_$(GIT_BRANCH)_$(GIT_COMMIT);\
380 | $(CD) .benchmark/$(PKG_VERSION)_$(GIT_BRANCH)_$(GIT_COMMIT);\
381 | R_LIBS="$(R_LIBS_USER_X)$(FSEP)$(R_LIBS)" R_LIBS_USER="$(R_LIBS_BRANCH)" $(R_SCRIPT) -e "$(PKG_NAME):::benchmark('index')"
382 |
383 |
384 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
385 | # Miscellaneous development tools
386 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
387 | ## Fully expanded src/*.c files
388 | .devel/src/%: src/% $(FILES_SRC)
389 | $(MKDIR) .devel/src/;\
390 | gcc -I$(R_INCLUDE) -E $< | sed -e '/./b' -e :n -e 'N;s/\n$$//;tn' > .devel/$<
391 |
392 | .devel/src/all: $(FILES_SRC)
393 | for f in $(FILES_SRC_C); do\
394 | echo $$f;\
395 | $(MAKE) .devel/$$f;\
396 | done
397 |
398 |
399 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
400 | # Run extensive CRAN submission checks
401 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
402 | $(R_CRAN_OUTDIR)/$(PKG_TARBALL): $(R_OUTDIR)/$(PKG_TARBALL) build_fix
403 | $(MKDIR) $(R_CRAN_OUTDIR)
404 | $(CP) $(R_OUTDIR)/$(PKG_TARBALL) $(R_CRAN_OUTDIR)
405 |
406 | $(R_CRAN_OUTDIR)/$(PKG_NAME),EmailToCRAN.txt: $(R_CRAN_OUTDIR)/$(PKG_TARBALL)
407 | $(CD) $(R_CRAN_OUTDIR);\
408 | $(R_SCRIPT) -e "RCmdCheckTools::testPkgsToSubmit(delta=2/3)"
409 |
410 | cran_setup: $(R_CRAN_OUTDIR)/$(PKG_TARBALL)
411 | $(R_SCRIPT) -e "if (!nzchar(system.file(package='RCmdCheckTools'))) { source('http://aroma-project.org/hbLite.R'); hbLite('RCmdCheckTools', devel=TRUE); }"
412 |
413 | cran: cran_setup $(R_CRAN_OUTDIR)/$(PKG_NAME),EmailToCRAN.txt
414 |
415 |
416 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
417 | # Send to win-builder server
418 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
419 | WIN_BUILDER = win-builder.r-project.org
420 | win-builder-devel: $(R_OUTDIR)/$(PKG_TARBALL)
421 | curl -v -T $? ftp://anonymous@$(WIN_BUILDER)/R-devel/
422 |
423 | win-builder-release: $(R_OUTDIR)/$(PKG_TARBALL)
424 | curl -v -T $? ftp://anonymous@$(WIN_BUILDER)/R-release/
425 |
426 | win-builder: win-builder-devel win-builder-release
427 |
428 |
429 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
430 | # Local repositories
431 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
432 | ifeq ($(OS), Windows_NT)
433 | REPOS_PATH = T:/My\ Repositories/braju.com/R
434 | else
435 | REPOS_PATH = /tmp/hb/repositories/braju.com/R
436 | endif
437 | REPOS_SRC := $(REPOS_PATH)/src/contrib
438 |
439 | $(REPOS_SRC):
440 | $(MKDIR) "$@"
441 |
442 | $(REPOS_SRC)/$(PKG_TARBALL): $(R_OUTDIR)/$(PKG_TARBALL) $(REPOS_SRC)
443 | $(CP) $(R_OUTDIR)/$(PKG_TARBALL) $(REPOS_SRC)
444 |
445 | repos: $(REPOS_SRC)/$(PKG_TARBALL)
446 |
447 | Makefile: $(FILES_MAKEFILE)
448 | $(R_SCRIPT) -e "d <- 'Makefile'; s <- '../../Makefile'; if (file_test('-nt', s, d) && (regexpr('Makefile for R packages', readLines(s, n=1L)) != -1L)) file.copy(s, d, overwrite=TRUE)"
449 |
450 |
451 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
452 | # Refresh
453 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
454 | .make/Makefile:
455 | $(R_SCRIPT) -e "R.utils::downloadFile('https://raw.githubusercontent.com/HenrikBengtsson/r-package-files/master/.make/Makefile', path='.make/')"
456 |
457 | .make/.travis.yml.rsp:
458 | $(R_SCRIPT) -e "R.utils::downloadFile('https://raw.githubusercontent.com/HenrikBengtsson/r-package-files/master/.make/.travis.yml.rsp', path='.make/')"
459 |
460 | .make/appveyor.yml.rsp:
461 | $(R_SCRIPT) -e "R.utils::downloadFile('https://raw.githubusercontent.com/HenrikBengtsson/r-package-files/master/.make/appveyor.yml.rsp', path='.make/')"
462 |
463 | .make/%.md.rsp:
464 | $(R_SCRIPT) -e "R.utils::downloadFile('https://raw.githubusercontent.com/HenrikBengtsson/r-package-files/master/$@', path='.make/')"
465 |
466 | .covr.R:
467 | $(R_SCRIPT) -e "R.utils::downloadFile('https://raw.githubusercontent.com/HenrikBengtsson/covr-utils/master/.covr.R')"
468 |
469 | clean:
470 | $(RM) .make/.travis.yml.rsp .make/appveyor.yml.rsp .make/README.md.rsp .covr.R
471 | $(RM) covr-utils.R
472 |
473 | refresh: clean
474 | $(MAKE) --silent .make/.travis.yml.rsp
475 | $(MAKE) --silent .make/appveyor.yml.rsp
476 | $(MAKE) --silent .make/README.md.rsp
477 | $(MAKE) --silent .covr.R
478 | $(R_SCRIPT) -e "R.utils::downloadFile('https://raw.githubusercontent.com/HenrikBengtsson/r-package-files/master/.make/Makefile', path='.make/', skip=FALSE)"
479 |
480 |
481 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
482 | # GitHub, Travis CI, ...
483 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
484 | README.md: .make/README.md.rsp
485 | $(R_SCRIPT) -e "R.rsp::rfile('$<', postprocess=FALSE)"
486 |
487 | CONTRIBUTING.md: .make/CONTRIBUTING.md.rsp
488 | $(R_SCRIPT) -e "R.rsp::rfile('$<', postprocess=FALSE)"
489 |
490 | .travis.yml: .make/.travis.yml.rsp
491 | $(R_SCRIPT) -e "R.rsp::rfile('$<', postprocess=FALSE)"
492 |
493 | appveyor.yml: .make/appveyor.yml.rsp
494 | $(R_SCRIPT) -e "R.rsp::rfile('$<', postprocess=FALSE)"
495 |
496 | config: .travis.yml appveyor.yml README.md
497 |
498 | ## Include any other Makefile.*, if they exists
499 | INCLUDES := $(wildcard .make/Makefile.*)
500 | ifneq ($(strip $(INCLUDES)),)
501 | include $(INCLUDES)
502 | endif
503 |
504 |
--------------------------------------------------------------------------------