├── 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 |
4 | CRAN check status R CMD check status Coverage Status 5 |
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 | --------------------------------------------------------------------------------