├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R ├── compat-zeallot.R ├── file_contents.R ├── highlight.R ├── parse.R ├── pause.R ├── profvis-package.R ├── profvis.R ├── shiny_module.R ├── utils.R ├── version_jquery.R └── zzz.R ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── cran-comments.md ├── inst ├── htmlwidgets │ ├── lib │ │ ├── d3 │ │ │ ├── LICENSE │ │ │ └── d3.min.js │ │ ├── highlight │ │ │ ├── LICENSE │ │ │ ├── highlight.min.js │ │ │ └── textmate.css │ │ ├── jquery │ │ │ ├── AUTHORS.txt │ │ │ └── jquery.min.js │ │ └── profvis │ │ │ ├── profvis.css │ │ │ ├── profvis.js │ │ │ └── scroll.js │ ├── profvis.js │ └── profvis.yaml └── shinymodule │ └── draggable-helper.js ├── man ├── parse_rprof.Rd ├── pause.Rd ├── print.profvis.Rd ├── profvis-package.Rd ├── profvis.Rd ├── profvisOutput.Rd └── profvis_ui.Rd ├── profvis.Rproj ├── revdep ├── .gitignore ├── README.md ├── cran.md ├── failures.md └── problems.md ├── src ├── pause.c └── profvis-init.c ├── tests ├── test-all.R └── testthat │ ├── _snaps │ ├── pause.md │ └── profvis.md │ ├── helper-profvis.R │ ├── test-parse-sort1.R │ ├── test-parse.R │ ├── test-parse.prof │ ├── test-pause.R │ ├── test-profvis.R │ └── test-utils.R ├── tools └── update_jquery.R └── vignettes ├── .gitignore ├── articles ├── .gitignore ├── cmpfun-profile1.rds ├── cmpfun-profile2.rds ├── examples.Rmd ├── faq.Rmd ├── ggplot2.rds ├── profvis-rpubs.png ├── profvis-shiny-demo.gif ├── rstudio-code-tools-menu.png ├── rstudio-profile-menu-selected-lines.png ├── rstudio-profile-menu-start-profiling.png ├── rstudio-profile-view.png ├── rstudio.Rmd └── shinyapp.rds └── profvis.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | \.sublime-project$ 4 | \.sublime-workspace$ 5 | ^.*\.o$ 6 | ^\.travis\.yml$ 7 | ^CONTRIBUTING.md$ 8 | ^docs$ 9 | ^revdep$ 10 | ^cran-comments\.md$ 11 | ^scripts$ 12 | ^\.github$ 13 | ^codecov\.yml$ 14 | ^_pkgdown\.yml$ 15 | ^pkgdown$ 16 | ^LICENSE\.md$ 17 | ^vignettes/articles$ 18 | ^CRAN-SUBMISSION$ 19 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | 13 | name: R-CMD-check 14 | 15 | permissions: read-all 16 | 17 | jobs: 18 | R-CMD-check: 19 | runs-on: ${{ matrix.config.os }} 20 | 21 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | config: 27 | - {os: macos-latest, r: 'release'} 28 | 29 | - {os: windows-latest, r: 'release'} 30 | # use 4.1 to check with rtools40's older compiler 31 | - {os: windows-latest, r: '4.1'} 32 | 33 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 34 | - {os: ubuntu-latest, r: 'release'} 35 | - {os: ubuntu-latest, r: 'oldrel-1'} 36 | - {os: ubuntu-latest, r: 'oldrel-2'} 37 | - {os: ubuntu-latest, r: 'oldrel-3'} 38 | - {os: ubuntu-latest, r: 'oldrel-4'} 39 | 40 | env: 41 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 42 | R_KEEP_PKG_SOURCE: yes 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | 47 | - uses: r-lib/actions/setup-pandoc@v2 48 | 49 | - uses: r-lib/actions/setup-r@v2 50 | with: 51 | r-version: ${{ matrix.config.r }} 52 | http-user-agent: ${{ matrix.config.http-user-agent }} 53 | use-public-rspm: true 54 | 55 | - uses: r-lib/actions/setup-r-dependencies@v2 56 | with: 57 | extra-packages: any::rcmdcheck 58 | needs: check 59 | 60 | - uses: r-lib/actions/check-r-package@v2 61 | with: 62 | upload-snapshots: true 63 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 64 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | permissions: read-all 15 | 16 | jobs: 17 | pkgdown: 18 | runs-on: ubuntu-latest 19 | # Only restrict concurrency for non-PR jobs 20 | concurrency: 21 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | permissions: 25 | contents: write 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - uses: r-lib/actions/setup-pandoc@v2 30 | 31 | - uses: r-lib/actions/setup-r@v2 32 | with: 33 | use-public-rspm: true 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | extra-packages: any::pkgdown, local::. 38 | needs: website 39 | 40 | - name: Build site 41 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 42 | shell: Rscript {0} 43 | 44 | - name: Deploy to GitHub pages 🚀 45 | if: github.event_name != 'pull_request' 46 | uses: JamesIves/github-pages-deploy-action@v4.5.0 47 | with: 48 | clean: false 49 | branch: gh-pages 50 | folder: docs 51 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: Commands 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/pr-fetch@v2 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - uses: r-lib/actions/setup-r@v2 26 | with: 27 | use-public-rspm: true 28 | 29 | - uses: r-lib/actions/setup-r-dependencies@v2 30 | with: 31 | extra-packages: any::roxygen2 32 | needs: pr-document 33 | 34 | - name: Document 35 | run: roxygen2::roxygenise() 36 | shell: Rscript {0} 37 | 38 | - name: commit 39 | run: | 40 | git config --local user.name "$GITHUB_ACTOR" 41 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 42 | git add man/\* NAMESPACE 43 | git commit -m 'Document' 44 | 45 | - uses: r-lib/actions/pr-push@v2 46 | with: 47 | repo-token: ${{ secrets.GITHUB_TOKEN }} 48 | 49 | style: 50 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 51 | name: style 52 | runs-on: ubuntu-latest 53 | env: 54 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 55 | steps: 56 | - uses: actions/checkout@v4 57 | 58 | - uses: r-lib/actions/pr-fetch@v2 59 | with: 60 | repo-token: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | - uses: r-lib/actions/setup-r@v2 63 | 64 | - name: Install dependencies 65 | run: install.packages("styler") 66 | shell: Rscript {0} 67 | 68 | - name: Style 69 | run: styler::style_pkg() 70 | shell: Rscript {0} 71 | 72 | - name: commit 73 | run: | 74 | git config --local user.name "$GITHUB_ACTOR" 75 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 76 | git add \*.R 77 | git commit -m 'Style' 78 | 79 | - uses: r-lib/actions/pr-push@v2 80 | with: 81 | repo-token: ${{ secrets.GITHUB_TOKEN }} 82 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | test-coverage: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: r-lib/actions/setup-r@v2 23 | with: 24 | use-public-rspm: true 25 | 26 | - uses: r-lib/actions/setup-r-dependencies@v2 27 | with: 28 | extra-packages: any::covr, any::xml2 29 | needs: coverage 30 | 31 | - name: Test coverage 32 | run: | 33 | cov <- covr::package_coverage( 34 | quiet = FALSE, 35 | clean = FALSE, 36 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 37 | ) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v4 42 | with: 43 | fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} 44 | file: ./cobertura.xml 45 | plugin: noop 46 | disable_search: true 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | 49 | - name: Show testthat output 50 | if: always() 51 | run: | 52 | ## -------------------------------------------------------------------- 53 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 54 | shell: bash 55 | 56 | - name: Upload test results 57 | if: failure() 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: coverage-test-failures 61 | path: ${{ runner.temp }}/package 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | *.sublime-project 5 | *.sublime-workspace 6 | inst/doc 7 | src/*.o 8 | src/*.so 9 | docs 10 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: profvis 2 | Title: Interactive Visualizations for Profiling R Code 3 | Version: 0.4.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@posit.co", role = c("aut", "cre")), 6 | person("Winston", "Chang", role = "aut"), 7 | person("Javier", "Luraschi", role = "aut"), 8 | person("Timothy", "Mastny", role = "aut"), 9 | person("Posit Software, PBC", role = c("cph", "fnd")), 10 | person(, "jQuery Foundation", role = "cph", 11 | comment = "jQuery library"), 12 | person(, "jQuery contributors", role = c("ctb", "cph"), 13 | comment = "jQuery library; authors listed in inst/htmlwidgets/lib/jquery/AUTHORS.txt"), 14 | person("Mike", "Bostock", role = c("ctb", "cph"), 15 | comment = "D3 library"), 16 | person(, "D3 contributors", role = "ctb", 17 | comment = "D3 library"), 18 | person("Ivan", "Sagalaev", role = c("ctb", "cph"), 19 | comment = "highlight.js library") 20 | ) 21 | Description: Interactive visualizations for profiling R code. 22 | License: MIT + file LICENSE 23 | URL: https://profvis.r-lib.org, https://github.com/r-lib/profvis 24 | BugReports: https://github.com/r-lib/profvis/issues 25 | Depends: 26 | R (>= 4.0) 27 | Imports: 28 | htmlwidgets (>= 0.3.2), 29 | rlang (>= 1.1.0), 30 | vctrs 31 | Suggests: 32 | htmltools, 33 | knitr, 34 | rmarkdown, 35 | shiny, 36 | testthat (>= 3.0.0) 37 | VignetteBuilder: 38 | knitr 39 | Config/Needs/website: tidyverse/tidytemplate, rmarkdown 40 | Config/testthat/edition: 3 41 | Encoding: UTF-8 42 | Roxygen: list(markdown = TRUE) 43 | RoxygenNote: 7.3.2 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: profvis authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 profvis authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,profvis) 4 | export(parse_rprof) 5 | export(pause) 6 | export(profvis) 7 | export(profvisOutput) 8 | export(profvis_server) 9 | export(profvis_ui) 10 | export(renderProfvis) 11 | import(htmlwidgets) 12 | import(rlang) 13 | importFrom(utils,Rprof) 14 | useDynLib(profvis, .registration = TRUE, .fixes = "c_") 15 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # profvis (development version) 2 | 3 | # profvis 0.4.0 4 | 5 | * profvis now requires R 4.0.0. The bundled version of jQuery has been upgraded 6 | to 3.7.1 (@hedsnz, #139) and the bundled `highlight.js` has been updated to 7 | the 11.10.0 (#140). It no longer longer requires purrr or stringr, and no 8 | longer suggests ggplot2, devtools, knitr, or rmarkdown. 9 | 10 | * `provis()` uses a new technique for trimming uninteresting frames from the 11 | stack (#130). This requires a new evaluation model where the code you supply 12 | to `profvis()` is turned into the body of a zero-argument anonymous function 13 | that is then called by profvis. This subtly changes the semantics of 14 | evaluation, but the primary effect is that if you create variables inside of 15 | the profiled code they will no longer be available outside of it. 16 | 17 | * `profvis()` now uses elapsed time where possible (#72). 18 | 19 | * `profvis()` now uses doubles instead of integers (#114). 20 | 21 | * The CSS for profvis code is scoped so that it does not affect other blocks of 22 | code, such as those from RMarkdown or Quarto (@wch, #140). 23 | 24 | profvis 0.3.8 25 | ============================= 26 | 27 | * `print()` gains an `aggregate` argument. Use `print(profvis(f()), aggregate = TRUE)` to aggregate frames by name in the flamegraph. This makes it easier to see the big picture (#115). Set the `profvis.aggregate` global option to `TRUE` to change the default. 28 | 29 | * For C function declarations that take no parameters, added `void` parameter. 30 | 31 | profvis 0.3.7 32 | ============= 33 | 34 | * Resolved [#102](https://github.com/r-lib/profvis/issues/102):" Added `simplify` argument. When `TRUE` (the default), the profiles are simplified using the new `filter.callframes` argument of R 4.0. This argument has no effect on older R versions. ([#118](https://github.com/r-lib/profvis/pull/118)) 35 | 36 | * Fixed [#111](https://github.com/r-lib/profvis/issues/111): auto-scrolling to lines of code did not work in some browsers. ([#113](https://github.com/r-lib/profvis/pull/113)) 37 | 38 | profvis 0.3.6 39 | ============= 40 | 41 | * Added a profvis Shiny module, for starting/stopping the profiler during the execution of a Shiny application. This can be helpful if you don't want to profile the entire execution of an app, only a certain operation. To install the profvis module into your app, add `profvis_ui("profvis")` to your UI, and `callModule(profvis_server, "profvis")` to your server function. 42 | 43 | * Exported `parse_rprof` function. 44 | 45 | profvis 0.3.5 46 | ============= 47 | 48 | * Fixed problem with development build of R where source refs are turned on by default (reported by Tomas Kalibera). 49 | 50 | profvis 0.3.4 51 | ============= 52 | 53 | * Fixed [#77](https://github.com/r-lib/profvis/issues/77): The contents of `` are now always listed first. 54 | 55 | * Addressed [#85](https://github.com/r-lib/profvis/issues/85): The `pause()` function is now implemented in C, which reduces the amount of data generated. 56 | 57 | * Fixed [#86](https://github.com/r-lib/profvis/issues/86): In the data pane, toggling between horizontal/vertical view caused the flame graph to render on top of the tree view. 58 | 59 | * Fixed [#84](https://github.com/r-lib/profvis/issues/84): In the data pane, leaf nodes (representing top-most calls on the stack) were not displayed. 60 | 61 | * Addressed [#82](https://github.com/r-lib/profvis/issues/82): In the data pane, if a node has exactly one child, that child will automatically be expanded. This makes it more efficient to explore the data. ([#83](https://github.com/r-lib/profvis/pull/83)) 62 | 63 | * Fixed [#50](https://github.com/r-lib/profvis/issues/50): In the data pane, function calls were shown in reverse order. 64 | 65 | 66 | profvis 0.3.3 67 | ============= 68 | 69 | * Fixed [#68](https://github.com/r-lib/profvis/issues/68): profvis threw an error when a package was installed using `devtools::install_github(args = "--with-keep.source")`. 70 | 71 | * Fix bug where, when loading a profile that didn't contain memory data, profvis would throw an error. [#66](https://github.com/r-lib/profvis/pull/66) 72 | 73 | * Fixed [#73](https://github.com/r-lib/profvis/issues/73): profvis would throw an error if used on code sourced from a remote URL. 74 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/R/aaa.R -------------------------------------------------------------------------------- /R/compat-zeallot.R: -------------------------------------------------------------------------------- 1 | # nocov start --- compat-zeallot --- 2020-11-23 2 | 3 | # This drop-in file implements a simple version of zeallot::`%<-%`. 4 | # Please find the most recent version in rlang's repository. 5 | 6 | 7 | `%<-%` <- function(lhs, value) { 8 | lhs <- substitute(lhs) 9 | env <- caller_env() 10 | 11 | if (!is_call(lhs, "c")) { 12 | abort("The left-hand side of `%<-%` must be a call to `c()`.") 13 | } 14 | 15 | vars <- as.list(lhs[-1]) 16 | 17 | if (length(value) != length(vars)) { 18 | abort("The left- and right-hand sides of `%<-%` must be the same length.") 19 | } 20 | 21 | for (i in seq_along(vars)) { 22 | var <- vars[[i]] 23 | if (!is_symbol(var)) { 24 | abort(paste0("Element ", i, " of the left-hand side of `%<-%` must be a symbol.")) 25 | } 26 | 27 | env[[as_string(var)]] <- value[[i]] 28 | } 29 | 30 | invisible(value) 31 | } 32 | 33 | 34 | # nocov end 35 | -------------------------------------------------------------------------------- /R/file_contents.R: -------------------------------------------------------------------------------- 1 | get_file_contents <- function(filenames, expr_source) { 2 | filenames <- filenames[filenames != ""] 3 | names(filenames) <- filenames 4 | 5 | srcfile_cache <- build_srcfile_cache() 6 | srcfile_cache[[""]] <- expr_source 7 | 8 | file_contents <- lapply(filenames, function(filename) { 9 | fetch_cached(filename, srcfile_cache) 10 | }) 11 | 12 | # If there's an entry, put it first. 13 | if ("" %in% names(file_contents)) { 14 | expr_idx <- (names(file_contents) == "") 15 | file_contents <- c(file_contents[expr_idx], file_contents[!expr_idx]) 16 | } 17 | 18 | drop_nulls(file_contents) 19 | } 20 | 21 | # Fetch a file from the cache, if present. If not already present, read the file 22 | # from disk and add it to the cache. 23 | fetch_cached <- function(filename, srcfile_cache) { 24 | # If in the cache, simply return it 25 | if (!is.null(srcfile_cache[[filename]])) { 26 | return(srcfile_cache[[filename]]) 27 | } 28 | 29 | # Exit if file doesn't exist locally. In some cases (e.g. a URL like 30 | # "http://xyz.com/" ) the `file()` call below can return a filehandle even 31 | # when the file is not local, and then it will error when `readChar()` is 32 | # called on the file. See https://github.com/r-lib/profvis/issues/73 33 | if (!file.exists(filename)) 34 | return(NULL) 35 | 36 | # If not in the cache, try to read the file 37 | filehandle <- tryCatch( 38 | file(filename, 'rb'), 39 | error = function(e) NULL, 40 | warning = function(e) NULL 41 | ) 42 | # If we can't read file, give up 43 | if (is.null(filehandle)) { 44 | return(NULL) 45 | } 46 | on.exit( close(filehandle) ) 47 | 48 | # Add it to the cache 49 | srcfile_cache[[filename]] <- readChar(filename, file.info(filename)$size, 50 | useBytes = TRUE) 51 | srcfile_cache[[filename]] 52 | } 53 | 54 | build_srcfile_cache <- function(pkgs = loadedNamespaces()) { 55 | srcfile_cache <- new.env(parent = emptyenv()) 56 | 57 | lapply(pkgs, function(pkg) { 58 | srcrefs <- get_pkg_srcrefs(pkg) 59 | if (length(srcrefs) > 0) 60 | list2env(srcrefs, srcfile_cache) 61 | }) 62 | 63 | srcfile_cache 64 | } 65 | 66 | 67 | # Given a namespace, try to extract source code. It does this by looking at 68 | # functions in the namespace and getting the appropriate attributes. This 69 | # returns a named list with all sources for a package. 70 | get_pkg_srcrefs <- function(pkg) { 71 | ns_env <- asNamespace(pkg) 72 | 73 | # Given a char vector with contents of an entire package, split out all 74 | # files into separate entries in a list. 75 | full_src_to_file_contents <- function(src) { 76 | # Before R 2.5.0, the first line looked like this: 77 | # .packageName <- "R6" 78 | # As of 2.5.0, that line was dropped. If that line is present, remove it. 79 | if (grepl("^\\.packageName <-", src[1])) { 80 | src <- src[-1] 81 | } 82 | 83 | # Lines which contain filenames. Have a format like: 84 | # "#line 1 \"/tmp/Rtmp6W0MLC/R.INSTALL1a531f3beb59/ggplot2/R/aaa-.r\"" 85 | filename_idx <- grep('^#line 1 "', src) 86 | filename_lines <- src[filename_idx] 87 | filenames <- sub('^#line 1 "(.*)"$', '\\1', filename_lines) 88 | 89 | # Starting and ending indices for the content of each file 90 | start_idx <- filename_idx + 1 91 | end_idx <- c(filename_idx[-1] - 1, length(src)) 92 | 93 | file_contents <- mapply(start_idx, end_idx, SIMPLIFY = FALSE, 94 | FUN = function(start, end) { 95 | content <- src[seq(start, end)] 96 | paste(content, collapse = "\n") 97 | } 98 | ) 99 | 100 | names(file_contents) <- filenames 101 | file_contents 102 | } 103 | 104 | # Get all objects in package. Need to filter out S4 mangled names (.__T__) 105 | ns_names <- grep("^\\.__[TC]__", ls(ns_env, all.names = TRUE), value = TRUE, 106 | invert = TRUE, fixed = FALSE) 107 | 108 | files <- list() 109 | 110 | for (name in ns_names) { 111 | x <- ns_env[[name]] 112 | if (is.function(x)) { 113 | srcref <- utils::getSrcref(x) 114 | 115 | # If any function lacks source refs, then no functions in the package will 116 | # have them. Quit early to save time. 117 | if (is.null(srcref)) 118 | break 119 | 120 | # There are two possible formats for source refs. If the file was 121 | # loaded with `source()` (as with `devtools::load_all()`), the lines 122 | # will just be the cotents of that one file. If the file was from a 123 | # package that was built and installed the normal way, it will contain 124 | # the all sources for the entire package. 125 | srcfile <- attr(srcref, "srcfile", exact = TRUE) 126 | 127 | if (!is.null(srcfile$lines)) { 128 | # Was loaded with `source(). If we don't already have the source for 129 | # this file, save them and keep going. 130 | if (is.null(files[[srcfile$filename]])) { 131 | files[[srcfile$filename]] <- paste(srcfile$lines, collapse = "\n") 132 | } 133 | 134 | } else if (!is.null(srcfile$original$lines)) { 135 | # Was from a built package and therefore contains source for all 136 | # files in the package. We can extract source code for all files and 137 | # return. 138 | files <- full_src_to_file_contents(srcfile$original$lines) 139 | break 140 | 141 | } else { 142 | # Shouldn't get here -- if so, this is an unexpected configuration. 143 | stop("Unexpected format for source refs.") 144 | } 145 | } 146 | } 147 | 148 | files 149 | } 150 | -------------------------------------------------------------------------------- /R/highlight.R: -------------------------------------------------------------------------------- 1 | # A list of regexes to highlight on the flamegraph. The name of each sublist is 2 | # the CSS class to give to cells whose labels match the regex listed. For each 3 | # of these names, there should be a corresponding CSS class in profvis.css. 4 | highlightPatterns <- function() { 5 | list( 6 | output = list("^output\\$"), 7 | gc = list("^$"), 8 | stacktrace = list("^\\.\\.stacktraceo(n|ff)\\.\\.$") 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /R/parse.R: -------------------------------------------------------------------------------- 1 | #' Parse Rprof output file for use with profvis 2 | #' 3 | #' @param path Path to the [Rprof()] output file. 4 | #' @param expr_source If any source refs in the profiling output have an empty 5 | #' filename, that means they refer to code executed at the R console. This 6 | #' code can be captured and passed (as a string) as the `expr_source` 7 | #' argument. 8 | #' @keywords internal 9 | #' @export 10 | parse_rprof <- function(path = "Rprof.out", expr_source = NULL) { 11 | parse_rprof_lines(readLines(path), expr_source = expr_source) 12 | } 13 | parse_rprof_lines <- function(lines, expr_source = NULL) { 14 | stopifnot(is_character(lines)) 15 | 16 | if (length(lines) < 2) { 17 | stop("No parsing data available. Maybe your function was too fast?") 18 | } 19 | 20 | # Parse header, including interval (in ms) 21 | opts <- strsplit(lines[[1]], ": ", fixed = TRUE)[[1]] 22 | interval <- as.numeric(strsplit(opts[length(opts)], "=", fixed = TRUE)[[1]][2]) / 1e3 23 | lines <- lines[-1] 24 | 25 | # Separate file labels and profiling data 26 | is_label <- grepl("^#", lines) 27 | 28 | label_lines <- lines[is_label] 29 | label_pieces <- split_in_half(label_lines, ": ", fixed = TRUE) 30 | labels <- data.frame( 31 | label = as.numeric(sub("^#File ", "", label_pieces[, 1])), 32 | path = label_pieces[, 2], 33 | stringsAsFactors = FALSE 34 | ) 35 | 36 | # Parse profiling data ----------------- 37 | prof_lines <- lines[!is_label] 38 | 39 | prof_data <- sub(' +$', '', prof_lines) 40 | 41 | # Memory profiles start with ':' 42 | has_memory <- length(prof_data) > 0 && substr(prof_data[[1]], 1, 1) == ":" 43 | 44 | # Extract memory data from ':m1:m2:m3:d:"c1" "c2" "c3"', and remove the memory 45 | # stuff from the prof_data strings. 46 | if (has_memory) { 47 | mem_data <- gsub("^:(\\d+:\\d+:\\d+:\\d+):.*", "\\1", prof_data) 48 | mem_data <- strsplit(mem_data, ":", fixed = TRUE) 49 | prof_data <- zap_mem_prefix(prof_data) 50 | } else { 51 | mem_data <- rep(NA_character_, length(prof_data)) 52 | } 53 | 54 | # Convert frames with srcrefs from: 55 | # "foo" 2#8 56 | # to 57 | # "foo",2#8 58 | prof_data <- gsub('" (\\d+#\\d+)', '",\\1', prof_data) 59 | # But if the line starts with a , it shouldn't be joined like that. 60 | # Convert: 61 | # ,1#7 "foo" 62 | # back to 63 | # 1#7 "foo" 64 | prof_data <- gsub('^"",', '"" ', prof_data) 65 | 66 | # # Split by ' ' for call stack 67 | # prof_data <- strsplit(prof_data, " ") 68 | # 69 | # prof_data <- lapply(prof_data, function(s) { 70 | # if (identical(s, "")) character(0) 71 | # else s 72 | # }) 73 | 74 | # Parse each line into a separate data frame 75 | prof_data <- mapply(prof_data, mem_data, seq_along(prof_data), FUN = function(line, mem, time) { 76 | memalloc <- 0 77 | if (has_memory) { 78 | # See memory allocation on r-sources (memory.c): 79 | # https://github.com/wch/r-source/blob/tags/R-3-0-0/src/main/memory.c#L1845 80 | # Memory is defined as: small:big:nodes:dupes. Originally, we tracked 81 | # mem[1:3] to include 'nodes' which track expression allocations. 82 | # However, the 3rd parameter is internal to the R execution engine since 83 | # it tracks all expression references and can yield information that's 84 | # confusing to users. For instance, profiling profvis::pause(1) can yield 85 | # several hundred MB due to busy waits of pause that trigger significant 86 | # creation of expressions that is not enterily useful to the end user. 87 | memalloc <- sum(as.numeric(mem[1:2])) / 1024 ^ 2 88 | 89 | # get_current_mem provides the results as either R_SmallVallocSize or R_LargeVallocSize 90 | # which are internal untis of allocation. 91 | # https://github.com/wch/r-source/blob/tags/R-3-0-0/src/main/memory.c#L2291. 92 | # 93 | # R_SmallVallocSize maps to alloc_size; alloc_size is assigned from size, which depending on 94 | # the type gets calculated with a macro, for instance, using FLOAT2VEC. 95 | # https://github.com/wch/r-source/blob/tags/R-3-0-0/src/main/memory.c#L2374 96 | # 97 | # FLOAT2VEC and similar functions always divide by sizeof(VECREC). 98 | # https://github.com/wch/r-source/blob/tags/R-3-0-0/src/include/Defn.h#L400 99 | # 100 | # VECREC is defined as follows: 101 | # typedef struct { 102 | # union { 103 | # SEXP backpointer; 104 | # double align; 105 | # } u; 106 | # } VECREC, *VECP; 107 | # 108 | # SEXP is defined as typedef struct SEXPREC { ... } SEXPREC, *SEXP; 109 | # Therefore, SEXP being a pointer if of variable length across different platforms. 110 | # https://svn.r-project.org/R/trunk/src/include/Rinternals.h 111 | # 112 | # On the other hand, align is always a double of 64 bits for both, 64 and 32bit platforms. 113 | # 114 | # Therefore, this results needs to be multiplied by 8 bytes. 115 | memalloc <- memalloc * 8 116 | } 117 | 118 | # Replace empty strings with character(0); otherwise causes incorrect output 119 | # later. 120 | if (identical(line, "")) 121 | line <- character(0) 122 | 123 | labels <- scan(text = line, what = character(0), quiet = TRUE) 124 | 125 | # If an element in `labels` is just a bare srcref without label, it doesn't 126 | # actually refer to a function call on the call stack -- instead, it just 127 | # means that the line of code is being evaluated. This can happen in either 128 | # of the first 2 elements in `labels`, because it could be "3#19", or it 129 | # could be " 3#19" -- the doesn't count as a real label. 130 | # 131 | # Note how the first lineprof() call differs from the ones in the loop: 132 | # https://github.com/wch/r-source/blob/be7197f/src/main/eval.c#L228-L244 In 133 | # this case, we'll use NA as the label for now, and later insert the line of 134 | # source code. 135 | bare_srcref_idx <- grep("^\\d+#\\d+$", labels[1:2]) 136 | 137 | # If found the bare srcref, insert an NA before it. 138 | if (length(bare_srcref_idx) > 0) { 139 | after_idx <- seq.int(bare_srcref_idx, length(labels)) 140 | labels <- c(labels[-after_idx], NA_character_, labels[after_idx]) 141 | } 142 | 143 | # Extract the srcrefs. These have the form ",3#12", or "3#12" if it was the 144 | # first item on the line. 145 | ref_idx <- grep('^,?\\d+#\\d+$', labels) 146 | 147 | # The number of calls on the stack 148 | n_calls <- length(labels) - length(ref_idx) 149 | 150 | # Create char vector with srcref strings, of form "3#12" or ",3#12". 151 | ref_strs <- rep(NA_character_, n_calls) 152 | ref_strs[ref_idx - seq_along(ref_idx)] <- labels[ref_idx] 153 | 154 | # Remove srcref text from `labels`. Make sure length is >0 because if length 155 | # is 0, labels[-integer(0)] will drop all entries. 156 | if (length(ref_idx) > 0) 157 | labels <- labels[-ref_idx] 158 | 159 | # Get file and line numbers 160 | ref_strs <- sub('^,', '', ref_strs) 161 | filenum <- as.numeric(sub('#.*', '', ref_strs)) 162 | linenum <- as.numeric(sub('.*#', '', ref_strs)) 163 | 164 | nrows <- length(labels) 165 | # Return what is essentially a data frame, but in list format because R is 166 | # slow at creating data frames here, and slow at rbinding them later. Doing 167 | # it with lists is about 4-5x faster than with data frames. 168 | list( 169 | time = rep(time, nrows), 170 | depth = if (nrows == 0) integer(0) else seq(nrows, 1), 171 | label = labels, 172 | filenum = filenum, 173 | linenum = linenum, 174 | # Using numeric(0) for memalloc can be slightly erroneous because memory 175 | # could have been allocated here due to stuff that happened in the part of 176 | # the stack that got trimmed off earlier. But there's another way to 177 | # represent memory usage because it's not associated with a line or 178 | # function label, only a time stamp, and profvis doesn't record memory 179 | # usage by time alone -- it must be associated with a function call and 180 | # optionally, a line of code. 181 | memalloc = rep(memalloc, nrows) 182 | ) 183 | }, SIMPLIFY = FALSE, USE.NAMES = FALSE) 184 | 185 | 186 | extract_vector <- function(x, name) { 187 | vecs <- lapply(x, `[[`, name) 188 | do.call(c, vecs) 189 | } 190 | 191 | # Bind all the pseudo data-frames together, into a real data frame. 192 | prof_data <- data.frame( 193 | time = extract_vector(prof_data, "time"), 194 | depth = extract_vector(prof_data, "depth"), 195 | label = extract_vector(prof_data, "label"), 196 | filenum = extract_vector(prof_data, "filenum"), 197 | linenum = extract_vector(prof_data, "linenum"), 198 | memalloc = extract_vector(prof_data, "memalloc"), 199 | stringsAsFactors = FALSE 200 | ) 201 | 202 | # Compute memory changes 203 | prof_data$meminc <- append(0, diff(prof_data$memalloc)) 204 | 205 | # Add filenames 206 | prof_data$filename <- labels$path[prof_data$filenum] 207 | 208 | # Get code file contents --------------------------- 209 | filenames <- unique(prof_data$filename) 210 | # Drop NA 211 | filenames <- filenames[!is.na(filenames)] 212 | 213 | 214 | file_contents <- get_file_contents(filenames, expr_source) 215 | 216 | # Trim filenames to make output a bit easier to interpret 217 | prof_data$filename <- trim_filenames(prof_data$filename) 218 | normpaths <- normalizePath(names(file_contents), winslash = "/", mustWork = FALSE) 219 | # Workaround for different behavior of normalizePath on Windows. Need to convert 220 | # "C:/path/to/file/" back to just "". 221 | if (.Platform$OS.type == "windows") { 222 | normpaths <- sub(file.path(getwd(), ""), "", normpaths, fixed = TRUE) 223 | } 224 | names(file_contents) <- trim_filenames(names(file_contents)) 225 | 226 | # Remove srcref info from the prof_data in cases where no file is present. 227 | no_file_idx <- !(prof_data$filename %in% names(file_contents)) 228 | prof_data$filename[no_file_idx] <- NA 229 | prof_data$filenum[no_file_idx] <- NA 230 | prof_data$linenum[no_file_idx] <- NA 231 | 232 | # Because we removed srcrefs when no file is present, there can be cases where 233 | # the label is NA and we couldn't read the file. This is when the profiler 234 | # output is like '1#2 "foo" "bar"' -- when the first item is a ref that 235 | # points to a file we couldn't read. We need to remove these NAs because we 236 | # don't have any useful information about them. 237 | prof_data <- prof_data[!(is.na(prof_data$label) & no_file_idx), ] 238 | 239 | # Add labels for where there's a srcref but no function on the call stack. 240 | # This can happen for frames at the top level. 241 | prof_data <- insert_code_line_labels(prof_data, file_contents) 242 | 243 | # Convert file_contents to a format suitable for client 244 | file_contents <- mapply(names(file_contents), file_contents, normpaths, 245 | FUN = function(filename, content, normpath) { 246 | list(filename = filename, content = content, normpath = normpath) 247 | }, SIMPLIFY = FALSE, USE.NAMES = FALSE) 248 | 249 | list( 250 | prof = prof_data, 251 | interval = interval, 252 | files = file_contents 253 | ) 254 | } 255 | 256 | zap_mem_prefix <- function(lines) { 257 | gsub("^:\\d+:\\d+:\\d+:\\d+:", "\\1", lines) 258 | } 259 | zap_file_labels <- function(lines) { 260 | lines[!grepl("^#", lines)] 261 | } 262 | zap_srcref <- function(lines) { 263 | gsub(" \\d+#\\d+", "", lines) 264 | } 265 | zap_meta_data <- function(lines) { 266 | lines <- zap_file_labels(lines) 267 | lines <- zap_mem_prefix(lines) 268 | lines 269 | } 270 | zap_header <- function(lines) { 271 | lines <- lines[-1] 272 | lines <- zap_file_labels(lines) 273 | lines 274 | } 275 | 276 | # For any rows where label is NA and there's a srcref, insert the line of code 277 | # as the label. 278 | insert_code_line_labels <- function(prof_data, file_contents) { 279 | file_label_contents <- lapply(file_contents, function(content) { 280 | content <- strsplit(content, "\n", fixed = TRUE)[[1]] 281 | sub("^ +", "", content) 282 | }) 283 | 284 | # Indices where a filename is present and the label is NA 285 | filename_idx <- !is.na(prof_data$filename) & is.na(prof_data$label) 286 | 287 | # Get the labels 288 | labels <- mapply( 289 | prof_data$filename[filename_idx], 290 | prof_data$linenum[filename_idx], 291 | FUN = function(filename, linenum) { 292 | if (filename == "") 293 | return("") 294 | file_label_contents[[filename]][linenum] 295 | }, SIMPLIFY = FALSE) 296 | labels <- unlist(labels, use.names = FALSE) 297 | # Insert the labels at appropriate indices 298 | prof_data$label[filename_idx] <- labels 299 | 300 | prof_data 301 | } 302 | 303 | 304 | trim_filenames <- function(filenames) { 305 | # Strip off current working directory from filenames 306 | filenames <- sub(getwd(), "", filenames, fixed = TRUE) 307 | 308 | # Replace /xxx/yyy/package/R/zzz.R with package/R/zzz.R, and same for inst/. 309 | filenames <- sub("^.*?([^/]+/(R|inst)/.*\\.R$)", "\\1", filenames, ignore.case = TRUE) 310 | 311 | filenames 312 | } 313 | 314 | # The profile data is sorted by time by default. To sort it 315 | # alphabetically we create a data frame of stacks where columns 316 | # represent stack depth and rows represent samples. 317 | # `vctrs::vec_order()` then gives us the sorting key we need. 318 | prof_sort <- function(prof) { 319 | # Split profile data frame by `time`. Each split corresponds to a 320 | # single line of the original Rprof output, i.e. a single sampled 321 | # stack. 322 | prof_split <- vctrs::vec_split(prof, prof$time)$val 323 | 324 | max_depth <- max(vapply(prof_split, nrow, integer(1))) 325 | n_samples <- length(prof_split) 326 | 327 | # Extract labels (function names for the stack frames) and pad with NAs 328 | # so we can easily make a data frame 329 | pad <- function(x) x[seq_len(max_depth)] 330 | stacks <- lapply(prof_split, function(x) pad(rev(x$label))) 331 | stacks <- as.data.frame(do.call(rbind, stacks)) 332 | 333 | # Reorder the profile data according to the sort key of the 334 | # transposed stacks 335 | key <- vctrs::vec_order(stacks) 336 | stacks <- vctrs::vec_slice(stacks, key) 337 | prof_split <- prof_split[key] 338 | 339 | # Now that stacks are in alphabetical order we sort them again by 340 | # contiguous run 341 | runs <- lapply(stacks, function(stack) { 342 | times <- vctrs::vec_unrep(stack)$times 343 | rep(rev(order(times)), times) 344 | }) 345 | runs <- vctrs::data_frame(!!!runs, .name_repair = "minimal") 346 | prof_split <- prof_split[vctrs::vec_order(runs)] 347 | 348 | # Assign an increasing `time` sequence in each split 349 | prof_split <- Map(seq_len(n_samples), prof_split, f = function(n, split) { 350 | split$time <- n 351 | split 352 | }) 353 | 354 | # Put the sorted splits back together 355 | vctrs::vec_rbind(!!!prof_split) 356 | } 357 | -------------------------------------------------------------------------------- /R/pause.R: -------------------------------------------------------------------------------- 1 | #' Pause an R process 2 | #' 3 | #' This function pauses an R process for some amount of time. It differs from 4 | #' [Sys.sleep()] in that time spent in `pause` will show up in 5 | #' profiler data. Another difference is that `pause` uses up 100\% of a CPU, 6 | #' whereas `Sys.sleep` does not. 7 | #' 8 | #' @examples 9 | #' # Wait for 0.5 seconds 10 | #' pause(0.5) 11 | #' 12 | #' @param seconds Number of seconds to pause. 13 | #' @useDynLib profvis, .registration = TRUE, .fixes = "c_" 14 | #' @export 15 | pause <- function(seconds) { 16 | if (is.integer(seconds)) { 17 | seconds <- as.numeric(seconds) 18 | } 19 | .Call(c_profvis_pause, seconds) 20 | } 21 | 22 | # This guarantees that (1) `pause()` is always compiled, even on 23 | # `load_all()` and (2) it doesn't include source references. This in 24 | # turn ensures consistent profile output: if the function is not 25 | # compiled and doesn't contain srcrefs, `.Call()` is never included in 26 | # the profiles, even when `line.profiling` is set. 27 | on_load_pause <- function() { 28 | pause <<- utils::removeSource(pause) 29 | pause <<- compiler::cmpfun(pause) 30 | } 31 | -------------------------------------------------------------------------------- /R/profvis-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | #' @import rlang 6 | ## usethis namespace: end 7 | NULL 8 | -------------------------------------------------------------------------------- /R/profvis.R: -------------------------------------------------------------------------------- 1 | #' Profile an R expression and visualize profiling data 2 | #' 3 | #' This function will run an R expression with profiling, and then return an 4 | #' htmlwidget for interactively exploring the profiling data. 5 | #' 6 | #' An alternate way to use `profvis` is to separately capture the profiling 7 | #' data to a file using [Rprof()], and then pass the path to the 8 | #' corresponding data file as the `prof_input` argument to 9 | #' `profvis()`. 10 | #' 11 | #' @param expr Expression to profile. The expression will be turned into the 12 | #' body of a zero-argument anonymous function which is then called repeatedly 13 | #' as needed. This means that if you create variables inside of `expr` they 14 | #' will not be available outside of it. 15 | #' 16 | #' The expression is repeatedly evaluated until `Rprof()` produces 17 | #' an output. It can _be_ a quosure injected with [rlang::inject()] but 18 | #' it cannot _contain_ injected quosures. 19 | #' 20 | #' Not compatible with `prof_input`. 21 | #' @param interval Interval for profiling samples, in seconds. Values less than 22 | #' 0.005 (5 ms) will probably not result in accurate timings 23 | #' @param prof_output Name of an Rprof output file or directory in which to save 24 | #' profiling data. If `NULL` (the default), a temporary file will be used 25 | #' and automatically removed when the function exits. For a directory, a 26 | #' random filename is used. 27 | #' 28 | #' @param prof_input The path to an [Rprof()] data file. Not 29 | #' compatible with `expr` or `prof_output`. 30 | #' @param timing The type of timing to use. Either `"elapsed"` (the 31 | #' default) for wall clock time, or `"cpu"` for CPU time. Wall clock time 32 | #' includes time spent waiting for other processes (e.g. waiting for a 33 | #' web page to download) so is generally more useful. 34 | #' 35 | #' If `NULL`, the default, will use elapsed time where possible, i.e. 36 | #' on Windows or on R 4.4.0 or greater. 37 | #' @param width Width of the htmlwidget. 38 | #' @param height Height of the htmlwidget 39 | #' @param split Orientation of the split bar: either `"h"` (the default) for 40 | #' horizontal or `"v"` for vertical. 41 | #' @param torture Triggers garbage collection after every `torture` memory 42 | #' allocation call. 43 | #' 44 | #' Note that memory allocation is only approximate due to the nature of the 45 | #' sampling profiler and garbage collection: when garbage collection triggers, 46 | #' memory allocations will be attributed to different lines of code. Using 47 | #' `torture = steps` helps prevent this, by making R trigger garbage 48 | #' collection after every `torture` memory allocation step. 49 | #' @param simplify Whether to simplify the profiles by removing 50 | #' intervening frames caused by lazy evaluation. Equivalent to the 51 | #' `filter.callframes` argument to [Rprof()]. 52 | #' @param rerun If `TRUE`, `Rprof()` is run again with `expr` until a 53 | #' profile is actually produced. This is useful for the cases where 54 | #' `expr` returns too quickly, before R had time to sample a 55 | #' profile. Can also be a string containing a regexp to match 56 | #' profiles. In this case, `profvis()` reruns `expr` until the 57 | #' regexp matches the modal value of the profile stacks. 58 | #' 59 | #' @seealso [print.profvis()] for printing options. 60 | #' @seealso [Rprof()] for more information about how the profiling 61 | #' data is collected. 62 | #' 63 | #' @examples 64 | #' # Only run these examples in interactive R sessions 65 | #' if (interactive()) { 66 | #' 67 | #' # Profile some code 68 | #' profvis({ 69 | #' dat <- data.frame( 70 | #' x = rnorm(5e4), 71 | #' y = rnorm(5e4) 72 | #' ) 73 | #' 74 | #' plot(x ~ y, data = dat) 75 | #' m <- lm(x ~ y, data = dat) 76 | #' abline(m, col = "red") 77 | #' }) 78 | #' 79 | #' 80 | #' # Save a profile to an HTML file 81 | #' p <- profvis({ 82 | #' dat <- data.frame( 83 | #' x = rnorm(5e4), 84 | #' y = rnorm(5e4) 85 | #' ) 86 | #' 87 | #' plot(x ~ y, data = dat) 88 | #' m <- lm(x ~ y, data = dat) 89 | #' abline(m, col = "red") 90 | #' }) 91 | #' htmlwidgets::saveWidget(p, "profile.html") 92 | #' 93 | #' # Can open in browser from R 94 | #' browseURL("profile.html") 95 | #' 96 | #' } 97 | #' @import htmlwidgets 98 | #' @importFrom utils Rprof 99 | #' @export 100 | profvis <- function(expr = NULL, 101 | interval = 0.01, 102 | prof_output = NULL, 103 | prof_input = NULL, 104 | timing = NULL, 105 | width = NULL, 106 | height = NULL, 107 | split = c("h", "v"), 108 | torture = 0, 109 | simplify = TRUE, 110 | rerun = FALSE) { 111 | check_exclusive(expr, prof_input) 112 | split <- match.arg(split) 113 | c(expr_q, env) %<-% enquo0_list(expr) 114 | 115 | 116 | if (interval < 0.005) { 117 | message("Intervals smaller than ~5ms will probably not result in accurate timings.") 118 | } 119 | 120 | if (is.null(timing)) { 121 | if (has_event() || Sys.info()[["sysname"]] == "Windows") { 122 | timing <- "elapsed" 123 | } else { 124 | timing <- "cpu" 125 | } 126 | } else { 127 | timing <- arg_match(timing, c("elapsed", "cpu")) 128 | } 129 | 130 | if (!is.null(expr_q)) { 131 | # Change the srcfile to add "" as the filename. Code executed from the 132 | # console will have "" here, and code executed in a knitr code block will 133 | # have "". This value is used by the profiler as the filename listed 134 | # in the profiler output. We need to do this to distinguish code that was 135 | # run in the profvis({}) code block from code that was run outside of it. 136 | # See https://github.com/r-lib/profvis/issues/57 137 | attr(expr_q, "srcfile")$filename <- "" 138 | 139 | # Keep original expression source code 140 | expr_source <- attr(expr_q, "wholeSrcref", exact = TRUE) 141 | expr_source <- attr(expr_source, "srcfile", exact = TRUE)$lines 142 | # Usually, $lines is a single string, but sometimes it can be split up into a 143 | # vector. Make sure it's a single string. 144 | expr_source <- paste(expr_source, collapse = "\n") 145 | 146 | prof_extension <- getOption("profvis.prof_extension", default = ".prof") 147 | 148 | if (is.null(prof_output) && !is.null(getOption("profvis.prof_output"))) 149 | prof_output <- getOption("profvis.prof_output") 150 | 151 | remove_on_exit <- FALSE 152 | if (is.null(prof_output)) { 153 | prof_output <- tempfile(fileext = prof_extension) 154 | remove_on_exit <- TRUE 155 | } 156 | else { 157 | if (dir.exists(prof_output)) 158 | prof_output <- tempfile(fileext = prof_extension, tmpdir = prof_output) 159 | } 160 | 161 | gc() 162 | 163 | if (!identical(torture, 0)) { 164 | gctorture2(step = torture) 165 | on.exit(gctorture2(step = 0), add = TRUE) 166 | } 167 | 168 | rprof_args <- drop_nulls(list( 169 | interval = interval, 170 | line.profiling = TRUE, 171 | gc.profiling = TRUE, 172 | memory.profiling = TRUE, 173 | event = if (has_event()) timing, 174 | filter.callframes = simplify 175 | )) 176 | 177 | if (remove_on_exit) { 178 | on.exit(unlink(prof_output), add = TRUE) 179 | } 180 | 181 | # We call the quoted expression directly inside a function to make it 182 | # easy to detect in both raw and simplified stack traces. The simplified 183 | # case is particularly tricky because evaluating a promise fails to create 184 | # a call on the trailing edges of the tree returned by simplification 185 | `__profvis_execute__` <- new_function(list(), expr_q, env) 186 | 187 | repeat { 188 | inject(Rprof(prof_output, !!!rprof_args)) 189 | cnd <- with_profvis_handlers(`__profvis_execute__`()) 190 | Rprof(NULL) 191 | 192 | lines <- readLines(prof_output) 193 | if (!is.null(cnd)) { 194 | break 195 | } 196 | if (prof_matches(zap_header(lines), rerun)) { 197 | break 198 | } 199 | } 200 | 201 | lines <- gsub('"__profvis_execute__".*$', "", lines) 202 | } else { 203 | # If we got here, we were provided a prof_input file instead of expr 204 | expr_source <- NULL 205 | prof_output <- prof_input 206 | lines <- readLines(prof_output) 207 | } 208 | 209 | message <- parse_rprof_lines(lines, expr_source) 210 | message$prof_output <- prof_output 211 | 212 | # Patterns to highlight on flamegraph 213 | message$highlight <- highlightPatterns() 214 | 215 | message$split <- split 216 | 217 | htmlwidgets::createWidget( 218 | name = 'profvis', 219 | list(message = message), 220 | width = width, 221 | height = height, 222 | package = 'profvis', 223 | sizingPolicy = htmlwidgets::sizingPolicy( 224 | padding = 0, 225 | browser.fill = TRUE, 226 | viewer.suppress = TRUE, 227 | knitr.defaultWidth = "100%", 228 | knitr.defaultHeight = "600px", 229 | knitr.figure = FALSE 230 | ) 231 | ) 232 | } 233 | 234 | prof_matches <- function(lines, rerun) { 235 | if (is_bool(rerun)) { 236 | !rerun || length(lines) > 0 237 | } else if (is_string(rerun)) { 238 | mode <- modal_value0(zap_meta_data(lines)) 239 | !is_null(mode) && grepl(rerun, mode) 240 | } else { 241 | abort("`rerun` must be logical or a character value.") 242 | } 243 | } 244 | 245 | with_profvis_handlers <- function(expr) { 246 | tryCatch({ 247 | expr 248 | NULL 249 | }, 250 | error = function(cnd) { 251 | message("profvis: code exited with error:\n", cnd$message, "\n") 252 | cnd 253 | }, 254 | interrupt = function(cnd) { 255 | message("profvis: interrupt received.") 256 | cnd 257 | }) 258 | } 259 | 260 | #' Print a profvis object 261 | #' 262 | #' @inheritParams profvis 263 | #' @param x The object to print. 264 | #' @param ... Further arguments to passed on to other print methods. 265 | #' @param aggregate If `TRUE`, the profiled stacks are aggregated by 266 | #' name. This makes it easier to see the big picture. Set your own 267 | #' global default for this argument with `options(profvis.aggregate 268 | #' = )`. 269 | #' @export 270 | print.profvis <- function(x, 271 | ..., 272 | width = NULL, 273 | height = NULL, 274 | split = NULL, 275 | aggregate = NULL) { 276 | 277 | if (!is.null(split)) { 278 | split <- arg_match(split, c("h", "v")) 279 | x$x$message$split <- split 280 | } 281 | if (!is.null(width)) x$width <- width 282 | if (!is.null(height)) x$height <- height 283 | 284 | aggregate <- aggregate %||% getOption("profvis.aggregate") %||% FALSE 285 | if (aggregate) { 286 | x$x$message$prof <- prof_sort(x$x$message$prof) 287 | } 288 | 289 | f <- getOption("profvis.print") 290 | if (is.function(f)) { 291 | f(x, ...) 292 | } else { 293 | NextMethod() 294 | } 295 | } 296 | 297 | #' Widget output and renders functions for use in Shiny 298 | #' 299 | #' @param outputId Output variable for profile visualization. 300 | #' 301 | #' @inheritParams profvis 302 | #' @export 303 | profvisOutput <- function(outputId, width = '100%', height = '600px'){ 304 | shinyWidgetOutput(outputId, 'profvis', width, height, package = 'profvis') 305 | } 306 | 307 | #' @param expr An expression that returns a profvis object. 308 | #' @param env The environment in which to evaluate `expr`. 309 | #' @param quoted Is `expr` a quoted expression (with [quote()])? 310 | #' @export 311 | #' @rdname profvisOutput 312 | renderProfvis <- function(expr, env = parent.frame(), quoted = FALSE) { 313 | if (!quoted) { expr <- substitute(expr) } # force quoted 314 | shinyRenderWidget(expr, profvisOutput, env, quoted = TRUE) 315 | } 316 | 317 | 318 | has_event <- function() { 319 | getRversion() >= "4.4.0" 320 | } 321 | -------------------------------------------------------------------------------- /R/shiny_module.R: -------------------------------------------------------------------------------- 1 | #' profvis UI for Shiny Apps 2 | #' 3 | #' Use this Shiny module to inject profvis controls into your Shiny app. The 4 | #' profvis Shiny module injects UI that can be used to start and stop profiling, 5 | #' and either view the results in the profvis UI or download the raw .Rprof 6 | #' data. It is highly recommended that this be used for testing and debugging 7 | #' only, and not included in production apps! 8 | #' 9 | #' The usual way to use profvis with Shiny is to simply call 10 | #' `profvis(shiny::runApp())`, but this may not always be possible or desirable: 11 | #' first, if you only want to profile a particular interaction in the Shiny app 12 | #' and not capture all the calculations involved in starting up the app and 13 | #' getting it into the correct state; and second, if you're trying to profile an 14 | #' application that's been deployed to a server. 15 | #' 16 | #' For more details on how to invoke Shiny modules, see [this 17 | #' article](https://shiny.rstudio.com/articles/modules.html). 18 | #' 19 | #' @param id Output id from `profvis_server`. 20 | #' 21 | #' @examples 22 | #' # In order to avoid "Hit to see next plot" prompts, 23 | #' # run this example with `example(profvis_ui, ask=FALSE)` 24 | #' 25 | #' if(interactive()) { 26 | #' library(shiny) 27 | #' shinyApp( 28 | #' fluidPage( 29 | #' plotOutput("plot"), 30 | #' actionButton("new", "New plot"), 31 | #' profvis_ui("profiler") 32 | #' ), 33 | #' function(input, output, session) { 34 | #' callModule(profvis_server, "profiler") 35 | #' 36 | #' output$plot <- renderPlot({ 37 | #' input$new 38 | #' boxplot(mpg ~ cyl, data = mtcars) 39 | #' }) 40 | #' } 41 | #' ) 42 | #' } 43 | #' 44 | #' @export 45 | profvis_ui <- function(id) { 46 | if (!requireNamespace("shiny", quietly = TRUE)) { 47 | stop('profvis_ui requires the shiny package.') 48 | } 49 | ns <- shiny::NS(id) 50 | 51 | style <- htmltools::css( 52 | padding = "6px", 53 | white_space = "nowrap", 54 | top = "-1px", 55 | border_top_left_radius = "0", 56 | border_top_right_radius = "0", 57 | box_shadow = "none", 58 | z_index = 9000 59 | ) 60 | 61 | shiny::tagList( 62 | htmltools::tags$style( 63 | ".profvis-module-container:empty() { visibility: hidden; }" 64 | ), 65 | shiny::fixedPanel( 66 | top = 0, left = -1, width = "auto", height = "auto", 67 | class = "profvis-module-container well", style = style, draggable = TRUE, 68 | 69 | shiny::uiOutput(ns("button_group"), class = "btn-group") 70 | ), 71 | # TODO: Make htmlDependency 72 | shiny::singleton(shiny::includeScript(system.file("shinymodule/draggable-helper.js", package = "profvis"))) 73 | ) 74 | } 75 | 76 | #' @param input,output,session Arguments provided by 77 | #' [shiny::callModule()]. 78 | #' @param dir Output directory to save Rprof files. 79 | #' 80 | #' @rdname profvis_ui 81 | #' @export 82 | profvis_server <- function(input, output, session, dir = ".") { 83 | if (!requireNamespace("shiny", quietly = TRUE)) { 84 | stop('profvis_server requires the shiny package.') 85 | } 86 | # Whether we're currently profiling 87 | profiling <- shiny::reactiveVal(FALSE) 88 | # The current/most recent profile 89 | current_profile <- shiny::reactiveVal(NULL) 90 | 91 | profiles <- function() { 92 | dir(dir, pattern = "\\.Rprof$") 93 | } 94 | 95 | shiny::setBookmarkExclude(c("start_rprof", "browse", "dl_rprof", "dl_profvis", "download")) 96 | 97 | shiny::observeEvent(input$start_rprof, { 98 | if (!profiling()) { 99 | proffile <- file.path(dir, strftime(Sys.time(), "%Y-%m-%d_%H-%M-%S.Rprof")) 100 | Rprof(proffile, 101 | interval = 0.01, line.profiling = TRUE, 102 | gc.profiling = TRUE, memory.profiling = TRUE) 103 | current_profile(proffile) 104 | profiling(TRUE) 105 | } 106 | }) 107 | 108 | output$button_group <- shiny::renderUI({ 109 | profiling() 110 | 111 | ns <- session$ns 112 | 113 | shiny::isolate({ 114 | browseBtn <- shiny::actionButton(class = "btn-xs", ns("browse"), NULL, shiny::icon("list-ul")) 115 | 116 | if (!profiling()) { 117 | htmltools::tagList( 118 | shiny::actionButton(class = "btn-xs", ns("start_rprof"), "Start profiling", shiny::icon("play")), 119 | if (length(profiles()) > 0) browseBtn 120 | ) 121 | } else { 122 | # Register a URL for the "Stop Recording" button to go to. 123 | # Requesting this URL will stop the current profiling session, update 124 | # the profiling() reactiveVal, and return a new profvis. 125 | url <- session$registerDataObj("stop_profvis_module", list(), function(data, req) { 126 | shiny::isolate({ 127 | Rprof(NULL) 128 | profiling(FALSE) 129 | 130 | # profiling(FALSE) should cause a flushReact, but doesn't. This 131 | # invalidateLater is a hack to force one (since it's inside an 132 | # isolate, it otherwise has no effect). 133 | shiny::invalidateLater(50) 134 | 135 | if (is.null(current_profile())) { 136 | stop("Invalid state detected") 137 | } 138 | 139 | # Create a profvis and save it to a self-contained temp .html file 140 | p <- profvis(prof_input = current_profile()) 141 | outfile <- tempfile("profvis", fileext = ".html") 142 | htmlwidgets::saveWidget(p, outfile) 143 | 144 | # Return as HTML. Since owned=TRUE, httpuv will take care of deleting 145 | # the temp file when it's done streaming it to the client. 146 | list( 147 | status = 200L, 148 | headers = list( 149 | "Content-Type" = "text/html;charset=utf-8" 150 | ), 151 | body = list( 152 | file = outfile, 153 | owned = TRUE 154 | ) 155 | ) 156 | }) 157 | }) 158 | 159 | htmltools::tagList( 160 | htmltools::tags$a(class = "btn btn-default btn-xs", target = "_blank", href = url, shiny::icon("stop"), "Stop profiling") 161 | ) 162 | } 163 | }) 164 | }) 165 | 166 | shiny::observeEvent(input$browse, { 167 | ns <- session$ns 168 | 169 | shiny::showModal(shiny::modalDialog( 170 | shiny::uiOutput(ns("download_select")), 171 | shiny::downloadButton(ns("dl_rprof"), "Download as Rprof", class = "btn-xs"), 172 | shiny::downloadButton(ns("dl_profvis"), "Download as profvis", class = "btn-xs") 173 | )) 174 | }) 175 | 176 | shiny::onSessionEnded(function() { 177 | # Make sure we stop profiling when session exits 178 | Rprof(NULL) 179 | }) 180 | 181 | output$download_select <- shiny::renderUI({ 182 | shiny::req(!profiling(), cancelOutput = TRUE) 183 | ns <- session$ns 184 | shiny::selectInput(ns("download"), "Select profile to download", 185 | choices = sort(profiles(), decreasing = TRUE) 186 | ) 187 | }) 188 | 189 | # Validate input$download so we don't just let the user download whatever 190 | # file they want from the server. 191 | download <- shiny::reactive({ 192 | dl <- input$download 193 | shiny::validate(shiny::need(isTRUE(dl %in% profiles()), "Illegal download or not found")) 194 | dl 195 | }) 196 | 197 | output$dl_rprof <- shiny::downloadHandler( 198 | filename = function() { 199 | file.path(dir, download()) 200 | }, 201 | content = function(file) { 202 | file.copy( 203 | file.path(dir, download()), 204 | file 205 | ) 206 | } 207 | ) 208 | 209 | output$dl_profvis <- shiny::downloadHandler( 210 | filename = function() { 211 | file.path(dir, sub("Rprof$", "html", download())) 212 | }, 213 | content = function(file) { 214 | p <- profvis(prof_input = download()) 215 | htmlwidgets::saveWidget(p, file) 216 | } 217 | ) 218 | } 219 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | # Drop NULLs from a list 2 | drop_nulls <- function(x) { 3 | x[!vapply(x, is.null, logical(1))] 4 | } 5 | 6 | # Everything above this function in the stack will be hidden by default in the 7 | # flamegraph. 8 | ..stacktraceoff.. <- function(x) x 9 | 10 | modal_value0 <- function(x) { 11 | if (!length(x)) { 12 | return(NULL) 13 | } 14 | 15 | if (is.list(x)) { 16 | abort("Can't use `modal_value()` with lists.") 17 | } 18 | self_split <- unname(split(x, x)) 19 | 20 | lens <- lengths(self_split) 21 | max_locs <- which(lens == max(lens)) 22 | 23 | if (length(max_locs) != 1) { 24 | return(NULL) 25 | } 26 | 27 | modal <- self_split[[max_locs]] 28 | modal[[1]] 29 | } 30 | modal_value <- function(x) { 31 | modal_value0(x) %||% abort("Expected modal value.") 32 | } 33 | 34 | enquo0_list <- function(arg) { 35 | quo <- inject(enquo0(!!substitute(arg)), caller_env()) 36 | 37 | # Warn if there are any embedded quosures as these are not supported 38 | quo_squash(quo, warn = TRUE) 39 | 40 | list( 41 | expr = quo_get_expr(quo), 42 | env = quo_get_env(quo) 43 | ) 44 | } 45 | 46 | 47 | split_in_half <- function(x, pattern, fixed = FALSE, perl = FALSE) { 48 | pos <- regexpr(pattern, x, fixed = fixed, perl = perl) 49 | 50 | start <- as.vector(pos) - 1 51 | length <- attr(pos, "match.length") 52 | 53 | no_match <- !is.na(pos) & pos == -1L 54 | length[no_match] <- 0 55 | start[no_match] <- nchar(x)[no_match] 56 | 57 | cbind( 58 | substr(x, 1, start), 59 | substr(x, start + length + 1, nchar(x)) 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /R/version_jquery.R: -------------------------------------------------------------------------------- 1 | # Generated by R/updatejQuery.R; do not edit by hand 2 | version_jquery <- "3.7.1" 3 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(...) { 2 | on_load_pause() 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | profvis 2 | ======= 3 | 4 | 5 | [![R-CMD-check](https://github.com/r-lib/profvis/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/profvis/actions/workflows/R-CMD-check.yaml) 6 | [![Codecov test coverage](https://codecov.io/gh/r-lib/profvis/graph/badge.svg)](https://app.codecov.io/gh/r-lib/profvis) 7 | 8 | 9 | profvis is a tool for visualizing code profiling data from R. It creates a web page which provides a graphical interface for exploring the data. 10 | 11 | 12 | ## Installation 13 | 14 | ```R 15 | install.packages("profvis") 16 | ``` 17 | 18 | ## Example 19 | 20 | To run code with profiling, wrap the expression in `profvis()`. By default, this will result in the interactive profile visualizer opening in a web browser. 21 | 22 | ```R 23 | library(profvis) 24 | 25 | f <- function() { 26 | pause(0.1) 27 | g() 28 | h() 29 | } 30 | g <- function() { 31 | pause(0.1) 32 | h() 33 | } 34 | h <- function() { 35 | pause(0.1) 36 | } 37 | 38 | profvis(f()) 39 | ``` 40 | 41 | 42 | The `profvis()` call returns an [htmlwidget](http://www.htmlwidgets.org/), which by default when printed opens a web browser. If you wish to save the object, it won't open the browser at first, but you can view it later by typing the variable name at the console, or calling `print()` on it. 43 | 44 | ```R 45 | p <- profvis(f()) 46 | 47 | # View it with: 48 | p 49 | # or print(p) 50 | ``` 51 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://profvis.r-lib.org 2 | 3 | template: 4 | package: tidytemplate 5 | bootstrap: 5 6 | 7 | includes: 8 | in_header: | 9 | 10 | 11 | development: 12 | mode: auto 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Comments 2 | 3 | ## R CMD check results 4 | 5 | 0 errors | 0 warnings | 0 notes 6 | 7 | ## revdepcheck results 8 | 9 | We checked 8 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. 10 | 11 | * We saw 1 new problems 12 | * We failed to check 0 packages 13 | 14 | Issues with CRAN packages are summarised below. 15 | 16 | ### New problems 17 | (This reports the first line of each new failure) 18 | 19 | * tidyft 20 | Patch submitted at https://github.com/hope-data-science/tidyft/pull/4 21 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/d3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2015, Michael Bostock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Michael Bostock may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/highlight/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Ivan Sagalaev 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of highlight.js nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/highlight/highlight.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Highlight.js v11.10.0 (git: 366a8bd012) 3 | (c) 2006-2024 Josh Goebel and other contributors 4 | License: BSD-3-Clause 5 | */ 6 | var hljs=function(){"use strict";function e(t){ 7 | return t instanceof Map?t.clear=t.delete=t.set=()=>{ 8 | throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{ 9 | throw Error("set is read-only") 10 | }),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{ 11 | const i=t[n],s=typeof i;"object"!==s&&"function"!==s||Object.isFrozen(i)||e(i) 12 | })),t}class t{constructor(e){ 13 | void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} 14 | ignoreMatch(){this.isMatchIgnored=!0}}function n(e){ 15 | return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") 16 | }function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] 17 | ;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.scope 18 | ;class o{constructor(e,t){ 19 | this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ 20 | this.buffer+=n(e)}openNode(e){if(!s(e))return;const t=((e,{prefix:t})=>{ 21 | if(e.startsWith("language:"))return e.replace("language:","language-") 22 | ;if(e.includes(".")){const n=e.split(".") 23 | ;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") 24 | }return`${t}${e}`})(e.scope,{prefix:this.classPrefix});this.span(t)} 25 | closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ 26 | this.buffer+=``}}const r=(e={})=>{const t={children:[]} 27 | ;return Object.assign(t,e),t};class a{constructor(){ 28 | this.rootNode=r(),this.stack=[this.rootNode]}get top(){ 29 | return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ 30 | this.top.children.push(e)}openNode(e){const t=r({scope:e}) 31 | ;this.add(t),this.stack.push(t)}closeNode(){ 32 | if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ 33 | for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} 34 | walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ 35 | return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), 36 | t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ 37 | "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ 38 | a._collapse(e)})))}}class c extends a{constructor(e){super(),this.options=e} 39 | addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ 40 | this.closeNode()}__addSublanguage(e,t){const n=e.root 41 | ;t&&(n.scope="language:"+t),this.add(n)}toHTML(){ 42 | return new o(this,this.options).value()}finalize(){ 43 | return this.closeAllNodes(),!0}}function l(e){ 44 | return e?"string"==typeof e?e:e.source:null}function g(e){return h("(?=",e,")")} 45 | function u(e){return h("(?:",e,")*")}function d(e){return h("(?:",e,")?")} 46 | function h(...e){return e.map((e=>l(e))).join("")}function f(...e){const t=(e=>{ 47 | const t=e[e.length-1] 48 | ;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} 49 | })(e);return"("+(t.capture?"":"?:")+e.map((e=>l(e))).join("|")+")"} 50 | function p(e){return RegExp(e.toString()+"|").exec("").length-1} 51 | const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ 52 | ;function m(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n 53 | ;let i=l(e),s="";for(;i.length>0;){const e=b.exec(i);if(!e){s+=i;break} 54 | s+=i.substring(0,e.index), 55 | i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0], 56 | "("===e[0]&&n++)}return s})).map((e=>`(${e})`)).join(t)} 57 | const E="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",y="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_="\\b(0b[01]+)",O={ 58 | begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'", 59 | illegal:"\\n",contains:[O]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", 60 | contains:[O]},N=(e,t,n={})=>{const s=i({scope:"comment",begin:e,end:t, 61 | contains:[]},n);s.contains.push({scope:"doctag", 62 | begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", 63 | end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) 64 | ;const o=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) 65 | ;return s.contains.push({begin:h(/[ ]+/,"(",o,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s 66 | },S=N("//","$"),M=N("/\\*","\\*/"),R=N("#","$");var j=Object.freeze({ 67 | __proto__:null,APOS_STRING_MODE:v,BACKSLASH_ESCAPE:O,BINARY_NUMBER_MODE:{ 68 | scope:"number",begin:_,relevance:0},BINARY_NUMBER_RE:_,COMMENT:N, 69 | C_BLOCK_COMMENT_MODE:M,C_LINE_COMMENT_MODE:S,C_NUMBER_MODE:{scope:"number", 70 | begin:y,relevance:0},C_NUMBER_RE:y,END_SAME_AS_BEGIN:e=>Object.assign(e,{ 71 | "on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ 72 | t.data._beginMatch!==e[1]&&t.ignoreMatch()}}),HASH_COMMENT_MODE:R,IDENT_RE:E, 73 | MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+x,relevance:0}, 74 | NUMBER_MODE:{scope:"number",begin:w,relevance:0},NUMBER_RE:w, 75 | PHRASAL_WORDS_MODE:{ 76 | begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ 77 | },QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, 78 | end:/\/[gimuy]*/,contains:[O,{begin:/\[/,end:/\]/,relevance:0,contains:[O]}]}, 79 | RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", 80 | SHEBANG:(e={})=>{const t=/^#![ ]*\// 81 | ;return e.binary&&(e.begin=h(t,/.*\b/,e.binary,/\b.*/)),i({scope:"meta",begin:t, 82 | end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, 83 | TITLE_MODE:{scope:"title",begin:E,relevance:0},UNDERSCORE_IDENT_RE:x, 84 | UNDERSCORE_TITLE_MODE:{scope:"title",begin:x,relevance:0}});function A(e,t){ 85 | "."===e.input[e.index-1]&&t.ignoreMatch()}function I(e,t){ 86 | void 0!==e.className&&(e.scope=e.className,delete e.className)}function T(e,t){ 87 | t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", 88 | e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, 89 | void 0===e.relevance&&(e.relevance=0))}function L(e,t){ 90 | Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function B(e,t){ 91 | if(e.match){ 92 | if(e.begin||e.end)throw Error("begin & end are not supported with match") 93 | ;e.begin=e.match,delete e.match}}function P(e,t){ 94 | void 0===e.relevance&&(e.relevance=1)}const D=(e,t)=>{if(!e.beforeMatch)return 95 | ;if(e.starts)throw Error("beforeMatch cannot be used with starts") 96 | ;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] 97 | })),e.keywords=n.keywords,e.begin=h(n.beforeMatch,g(n.begin)),e.starts={ 98 | relevance:0,contains:[Object.assign(n,{endsParent:!0})] 99 | },e.relevance=0,delete n.beforeMatch 100 | },H=["of","and","for","in","not","or","if","then","parent","list","value"],C="keyword" 101 | ;function $(e,t,n=C){const i=Object.create(null) 102 | ;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{ 103 | Object.assign(i,$(e[n],t,n))})),i;function s(e,n){ 104 | t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") 105 | ;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ 106 | return t?Number(t):(e=>H.includes(e.toLowerCase()))(e)?0:1}const z={},W=e=>{ 107 | console.error(e)},X=(e,...t)=>{console.log("WARN: "+e,...t)},G=(e,t)=>{ 108 | z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) 109 | },K=Error();function F(e,t,{key:n}){let i=0;const s=e[n],o={},r={} 110 | ;for(let e=1;e<=t.length;e++)r[e+i]=s[e],o[e+i]=!0,i+=p(t[e-1]) 111 | ;e[n]=r,e[n]._emit=o,e[n]._multi=!0}function Z(e){(e=>{ 112 | e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, 113 | delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ 114 | _wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope 115 | }),(e=>{if(Array.isArray(e.begin)){ 116 | if(e.skip||e.excludeBegin||e.returnBegin)throw W("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), 117 | K 118 | ;if("object"!=typeof e.beginScope||null===e.beginScope)throw W("beginScope must be object"), 119 | K;F(e,e.begin,{key:"beginScope"}),e.begin=m(e.begin,{joinWith:""})}})(e),(e=>{ 120 | if(Array.isArray(e.end)){ 121 | if(e.skip||e.excludeEnd||e.returnEnd)throw W("skip, excludeEnd, returnEnd not compatible with endScope: {}"), 122 | K 123 | ;if("object"!=typeof e.endScope||null===e.endScope)throw W("endScope must be object"), 124 | K;F(e,e.end,{key:"endScope"}),e.end=m(e.end,{joinWith:""})}})(e)}function V(e){ 125 | function t(t,n){ 126 | return RegExp(l(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) 127 | }class n{constructor(){ 128 | this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} 129 | addRule(e,t){ 130 | t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), 131 | this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) 132 | ;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(m(e,{joinWith:"|" 133 | }),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex 134 | ;const t=this.matcherRe.exec(e);if(!t)return null 135 | ;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] 136 | ;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){ 137 | this.rules=[],this.multiRegexes=[], 138 | this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ 139 | if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n 140 | ;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), 141 | t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ 142 | return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ 143 | this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ 144 | const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex 145 | ;let n=t.exec(e) 146 | ;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ 147 | const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} 148 | return n&&(this.regexIndex+=n.position+1, 149 | this.regexIndex===this.count&&this.considerAll()),n}} 150 | if(e.compilerExtensions||(e.compilerExtensions=[]), 151 | e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") 152 | ;return e.classNameAliases=i(e.classNameAliases||{}),function n(o,r){const a=o 153 | ;if(o.isCompiled)return a 154 | ;[I,B,Z,D].forEach((e=>e(o,r))),e.compilerExtensions.forEach((e=>e(o,r))), 155 | o.__beforeBegin=null,[T,L,P].forEach((e=>e(o,r))),o.isCompiled=!0;let c=null 156 | ;return"object"==typeof o.keywords&&o.keywords.$pattern&&(o.keywords=Object.assign({},o.keywords), 157 | c=o.keywords.$pattern, 158 | delete o.keywords.$pattern),c=c||/\w+/,o.keywords&&(o.keywords=$(o.keywords,e.case_insensitive)), 159 | a.keywordPatternRe=t(c,!0), 160 | r&&(o.begin||(o.begin=/\B|\b/),a.beginRe=t(a.begin),o.end||o.endsWithParent||(o.end=/\B|\b/), 161 | o.end&&(a.endRe=t(a.end)), 162 | a.terminatorEnd=l(a.end)||"",o.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+r.terminatorEnd)), 163 | o.illegal&&(a.illegalRe=t(o.illegal)), 164 | o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>i(e,{ 165 | variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?i(e,{ 166 | starts:e.starts?i(e.starts):null 167 | }):Object.isFrozen(e)?i(e):e))("self"===e?o:e)))),o.contains.forEach((e=>{n(e,a) 168 | })),o.starts&&n(o.starts,r),a.matcher=(e=>{const t=new s 169 | ;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" 170 | }))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" 171 | }),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ 172 | return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ 173 | constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} 174 | const Y=n,Q=i,ee=Symbol("nomatch"),te=n=>{ 175 | const i=Object.create(null),s=Object.create(null),o=[];let r=!0 176 | ;const a="Could not find the language '{}', did you forget to load/include a language module?",l={ 177 | disableAutodetect:!0,name:"Plain text",contains:[]};let p={ 178 | ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, 179 | languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", 180 | cssSelector:"pre code",languages:null,__emitter:c};function b(e){ 181 | return p.noHighlightRe.test(e)}function m(e,t,n){let i="",s="" 182 | ;"object"==typeof t?(i=e, 183 | n=t.ignoreIllegals,s=t.language):(G("10.7.0","highlight(lang, code, ...args) has been deprecated."), 184 | G("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), 185 | s=e,i=t),void 0===n&&(n=!0);const o={code:i,language:s};N("before:highlight",o) 186 | ;const r=o.result?o.result:E(o.language,o.code,n) 187 | ;return r.code=o.code,N("after:highlight",r),r}function E(e,n,s,o){ 188 | const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(R) 189 | ;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(R),n="" 190 | ;for(;t;){n+=R.substring(e,t.index) 191 | ;const s=_.case_insensitive?t[0].toLowerCase():t[0],o=(i=s,N.keywords[i]);if(o){ 192 | const[e,i]=o 193 | ;if(M.addText(n),n="",c[s]=(c[s]||0)+1,c[s]<=7&&(j+=i),e.startsWith("_"))n+=t[0];else{ 194 | const n=_.classNameAliases[e]||e;u(t[0],n)}}else n+=t[0] 195 | ;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(R)}var i 196 | ;n+=R.substring(e),M.addText(n)}function g(){null!=N.subLanguage?(()=>{ 197 | if(""===R)return;let e=null;if("string"==typeof N.subLanguage){ 198 | if(!i[N.subLanguage])return void M.addText(R) 199 | ;e=E(N.subLanguage,R,!0,S[N.subLanguage]),S[N.subLanguage]=e._top 200 | }else e=x(R,N.subLanguage.length?N.subLanguage:null) 201 | ;N.relevance>0&&(j+=e.relevance),M.__addSublanguage(e._emitter,e.language) 202 | })():l(),R=""}function u(e,t){ 203 | ""!==e&&(M.startScope(t),M.addText(e),M.endScope())}function d(e,t){let n=1 204 | ;const i=t.length-1;for(;n<=i;){if(!e._emit[n]){n++;continue} 205 | const i=_.classNameAliases[e[n]]||e[n],s=t[n];i?u(s,i):(R=s,l(),R=""),n++}} 206 | function h(e,t){ 207 | return e.scope&&"string"==typeof e.scope&&M.openNode(_.classNameAliases[e.scope]||e.scope), 208 | e.beginScope&&(e.beginScope._wrap?(u(R,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), 209 | R=""):e.beginScope._multi&&(d(e.beginScope,t),R="")),N=Object.create(e,{parent:{ 210 | value:N}}),N}function f(e,n,i){let s=((e,t)=>{const n=e&&e.exec(t) 211 | ;return n&&0===n.index})(e.endRe,i);if(s){if(e["on:end"]){const i=new t(e) 212 | ;e["on:end"](n,i),i.isMatchIgnored&&(s=!1)}if(s){ 213 | for(;e.endsParent&&e.parent;)e=e.parent;return e}} 214 | if(e.endsWithParent)return f(e.parent,n,i)}function b(e){ 215 | return 0===N.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function m(e){ 216 | const t=e[0],i=n.substring(e.index),s=f(N,e,i);if(!s)return ee;const o=N 217 | ;N.endScope&&N.endScope._wrap?(g(), 218 | u(t,N.endScope._wrap)):N.endScope&&N.endScope._multi?(g(), 219 | d(N.endScope,e)):o.skip?R+=t:(o.returnEnd||o.excludeEnd||(R+=t), 220 | g(),o.excludeEnd&&(R=t));do{ 221 | N.scope&&M.closeNode(),N.skip||N.subLanguage||(j+=N.relevance),N=N.parent 222 | }while(N!==s.parent);return s.starts&&h(s.starts,e),o.returnEnd?0:t.length} 223 | let w={};function y(i,o){const a=o&&o[0];if(R+=i,null==a)return g(),0 224 | ;if("begin"===w.type&&"end"===o.type&&w.index===o.index&&""===a){ 225 | if(R+=n.slice(o.index,o.index+1),!r){const t=Error(`0 width match regex (${e})`) 226 | ;throw t.languageName=e,t.badRule=w.rule,t}return 1} 227 | if(w=o,"begin"===o.type)return(e=>{ 228 | const n=e[0],i=e.rule,s=new t(i),o=[i.__beforeBegin,i["on:begin"]] 229 | ;for(const t of o)if(t&&(t(e,s),s.isMatchIgnored))return b(n) 230 | ;return i.skip?R+=n:(i.excludeBegin&&(R+=n), 231 | g(),i.returnBegin||i.excludeBegin||(R=n)),h(i,e),i.returnBegin?0:n.length})(o) 232 | ;if("illegal"===o.type&&!s){ 233 | const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') 234 | ;throw e.mode=N,e}if("end"===o.type){const e=m(o);if(e!==ee)return e} 235 | if("illegal"===o.type&&""===a)return 1 236 | ;if(I>1e5&&I>3*o.index)throw Error("potential infinite loop, way more iterations than matches") 237 | ;return R+=a,a.length}const _=O(e) 238 | ;if(!_)throw W(a.replace("{}",e)),Error('Unknown language: "'+e+'"') 239 | ;const v=V(_);let k="",N=o||v;const S={},M=new p.__emitter(p);(()=>{const e=[] 240 | ;for(let t=N;t!==_;t=t.parent)t.scope&&e.unshift(t.scope) 241 | ;e.forEach((e=>M.openNode(e)))})();let R="",j=0,A=0,I=0,T=!1;try{ 242 | if(_.__emitTokens)_.__emitTokens(n,M);else{for(N.matcher.considerAll();;){ 243 | I++,T?T=!1:N.matcher.considerAll(),N.matcher.lastIndex=A 244 | ;const e=N.matcher.exec(n);if(!e)break;const t=y(n.substring(A,e.index),e) 245 | ;A=e.index+t}y(n.substring(A))}return M.finalize(),k=M.toHTML(),{language:e, 246 | value:k,relevance:j,illegal:!1,_emitter:M,_top:N}}catch(t){ 247 | if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n), 248 | illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A, 249 | context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:k},_emitter:M};if(r)return{ 250 | language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:N} 251 | ;throw t}}function x(e,t){t=t||p.languages||Object.keys(i);const n=(e=>{ 252 | const t={value:Y(e),illegal:!1,relevance:0,_top:l,_emitter:new p.__emitter(p)} 253 | ;return t._emitter.addText(e),t})(e),s=t.filter(O).filter(k).map((t=>E(t,e,!1))) 254 | ;s.unshift(n);const o=s.sort(((e,t)=>{ 255 | if(e.relevance!==t.relevance)return t.relevance-e.relevance 256 | ;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 257 | ;if(O(t.language).supersetOf===e.language)return-1}return 0})),[r,a]=o,c=r 258 | ;return c.secondBest=a,c}function w(e){let t=null;const n=(e=>{ 259 | let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" 260 | ;const n=p.languageDetectRe.exec(t);if(n){const t=O(n[1]) 261 | ;return t||(X(a.replace("{}",n[1])), 262 | X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} 263 | return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return 264 | ;if(N("before:highlightElement",{el:e,language:n 265 | }),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) 266 | ;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), 267 | console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), 268 | console.warn("The element with unescaped HTML:"), 269 | console.warn(e)),p.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) 270 | ;t=e;const i=t.textContent,o=n?m(i,{language:n,ignoreIllegals:!0}):x(i) 271 | ;e.innerHTML=o.value,e.dataset.highlighted="yes",((e,t,n)=>{const i=t&&s[t]||n 272 | ;e.classList.add("hljs"),e.classList.add("language-"+i) 273 | })(e,n,o.language),e.result={language:o.language,re:o.relevance, 274 | relevance:o.relevance},o.secondBest&&(e.secondBest={ 275 | language:o.secondBest.language,relevance:o.secondBest.relevance 276 | }),N("after:highlightElement",{el:e,result:o,text:i})}let y=!1;function _(){ 277 | "loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(w):y=!0 278 | }function O(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]} 279 | function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ 280 | s[e.toLowerCase()]=t}))}function k(e){const t=O(e) 281 | ;return t&&!t.disableAutodetect}function N(e,t){const n=e;o.forEach((e=>{ 282 | e[n]&&e[n](t)}))} 283 | "undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ 284 | y&&_()}),!1),Object.assign(n,{highlight:m,highlightAuto:x,highlightAll:_, 285 | highlightElement:w, 286 | highlightBlock:e=>(G("10.7.0","highlightBlock will be removed entirely in v12.0"), 287 | G("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{p=Q(p,e)}, 288 | initHighlighting:()=>{ 289 | _(),G("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, 290 | initHighlightingOnLoad:()=>{ 291 | _(),G("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") 292 | },registerLanguage:(e,t)=>{let s=null;try{s=t(n)}catch(t){ 293 | if(W("Language definition for '{}' could not be registered.".replace("{}",e)), 294 | !r)throw t;W(t),s=l} 295 | s.name||(s.name=e),i[e]=s,s.rawDefinition=t.bind(null,n),s.aliases&&v(s.aliases,{ 296 | languageName:e})},unregisterLanguage:e=>{delete i[e] 297 | ;for(const t of Object.keys(s))s[t]===e&&delete s[t]}, 298 | listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, 299 | autoDetection:k,inherit:Q,addPlugin:e=>{(e=>{ 300 | e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ 301 | e["before:highlightBlock"](Object.assign({block:t.el},t)) 302 | }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ 303 | e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),o.push(e)}, 304 | removePlugin:e=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}),n.debugMode=()=>{ 305 | r=!1},n.safeMode=()=>{r=!0},n.versionString="11.10.0",n.regex={concat:h, 306 | lookahead:g,either:f,optional:d,anyNumberOfTimes:u} 307 | ;for(const t in j)"object"==typeof j[t]&&e(j[t]);return Object.assign(n,j),n 308 | },ne=te({});return ne.newInstance=()=>te({}),ne}() 309 | ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `r` grammar compiled for Highlight.js 11.10.0 */ 310 | (()=>{var e=(()=>{"use strict";return e=>{ 311 | const a=e.regex,n=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,i=a.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),s=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,t=a.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/) 312 | ;return{name:"R",keywords:{$pattern:n, 313 | keyword:"function if in break next repeat else for while", 314 | literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10", 315 | built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm" 316 | },contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/, 317 | starts:{end:a.lookahead(a.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)), 318 | endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{ 319 | scope:"variable",variants:[{match:n},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0 320 | }]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}] 321 | }),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE], 322 | variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/ 323 | }),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/ 324 | }),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/ 325 | }),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/ 326 | }),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/ 327 | }),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"', 328 | relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{ 329 | 1:"operator",2:"number"},match:[s,i]},{scope:{1:"operator",2:"number"}, 330 | match:[/%[^%]*%/,i]},{scope:{1:"punctuation",2:"number"},match:[t,i]},{scope:{ 331 | 2:"number"},match:[/[^a-zA-Z0-9._]|^/,i]}]},{scope:{3:"operator"}, 332 | match:[n,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:s},{ 333 | match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:t},{begin:"`",end:"`", 334 | contains:[{begin:/\\./}]}]}}})();hljs.registerLanguage("r",e)})(); -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/highlight/textmate.css: -------------------------------------------------------------------------------- 1 | .profvis-code span.hljs-operator, 2 | .profvis-code span.hljs-paren { 3 | color: rgb(104, 118, 135) 4 | } 5 | 6 | .profvis-code span.hljs-literal { 7 | color: rgb(88, 72, 246) 8 | } 9 | 10 | .profvis-code span.hljs-number { 11 | color: rgb(0, 0, 205); 12 | } 13 | 14 | .profvis-code span.hljs-comment { 15 | color: rgb(76, 136, 107); 16 | } 17 | 18 | .profvis-code span.hljs-keyword { 19 | color: rgb(0, 0, 255); 20 | } 21 | 22 | .profvis-code span.hljs-identifier { 23 | color: rgb(0, 0, 0); 24 | } 25 | 26 | .profvis-code span.hljs-string { 27 | color: rgb(3, 106, 7); 28 | } 29 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/jquery/AUTHORS.txt: -------------------------------------------------------------------------------- 1 | John Resig 2 | Gilles van den Hoven 3 | Michael Geary 4 | Stefan Petre 5 | Yehuda Katz 6 | Corey Jewett 7 | Klaus Hartl 8 | Franck Marcia 9 | Jörn Zaefferer 10 | Paul Bakaus 11 | Brandon Aaron 12 | Mike Alsup 13 | Dave Methvin 14 | Ed Engelhardt 15 | Sean Catchpole 16 | Paul Mclanahan 17 | David Serduke 18 | Richard D. Worth 19 | Scott González 20 | Ariel Flesler 21 | Cheah Chu Yeow 22 | Andrew Chalkley 23 | Fabio Buffoni 24 | Stefan Bauckmeier  25 | Jon Evans 26 | TJ Holowaychuk 27 | Riccardo De Agostini 28 | Michael Bensoussan 29 | Louis-Rémi Babé 30 | Robert Katić 31 | Damian Janowski 32 | Anton Kovalyov 33 | Dušan B. Jovanovic 34 | Earle Castledine 35 | Rich Dougherty 36 | Kim Dalsgaard 37 | Andrea Giammarchi 38 | Fabian Jakobs 39 | Mark Gibson 40 | Karl Swedberg 41 | Justin Meyer 42 | Ben Alman 43 | James Padolsey 44 | David Petersen 45 | Batiste Bieler 46 | Jake Archibald 47 | Alexander Farkas 48 | Filipe Fortes 49 | Rick Waldron 50 | Neeraj Singh 51 | Paul Irish 52 | Iraê Carvalho 53 | Matt Curry 54 | Michael Monteleone 55 | Noah Sloan 56 | Tom Viner 57 | J. Ryan Stinnett 58 | Douglas Neiner 59 | Adam J. Sontag 60 | Heungsub Lee 61 | Dave Reed 62 | Carl Fürstenberg 63 | Jacob Wright 64 | Ralph Whitbeck 65 | unknown 66 | temp01 67 | Colin Snover 68 | Jared Grippe 69 | Ryan W Tenney 70 | Alex Sexton 71 | Pinhook 72 | Ron Otten 73 | Jephte Clain 74 | Anton Matzneller 75 | Dan Heberden 76 | Henri Wiechers 77 | Russell Holbrook 78 | Julian Aubourg 79 | Gianni Alessandro Chiappetta 80 | Scott Jehl 81 | James Burke 82 | Jonas Pfenniger 83 | Xavi Ramirez 84 | Sylvester Keil 85 | Brandon Sterne 86 | Mathias Bynens 87 | Lee Carpenter 88 | Timmy Willison <4timmywil@gmail.com> 89 | Corey Frang 90 | Digitalxero 91 | David Murdoch 92 | Josh Varner 93 | Charles McNulty 94 | Jordan Boesch 95 | Jess Thrysoee 96 | Michael Murray 97 | Alexis Abril 98 | Rob Morgan 99 | John Firebaugh 100 | Sam Bisbee 101 | Gilmore Davidson 102 | Brian Brennan 103 | Xavier Montillet 104 | Daniel Pihlstrom 105 | Sahab Yazdani 106 | avaly 107 | Scott Hughes 108 | Mike Sherov 109 | Greg Hazel 110 | Schalk Neethling 111 | Denis Knauf 112 | Timo Tijhof 113 | Steen Nielsen 114 | Anton Ryzhov 115 | Shi Chuan 116 | Matt Mueller 117 | Berker Peksag 118 | Toby Brain 119 | Justin 120 | Daniel Herman 121 | Oleg Gaidarenko 122 | Rock Hymas 123 | Richard Gibson 124 | Rafaël Blais Masson 125 | cmc3cn <59194618@qq.com> 126 | Joe Presbrey 127 | Sindre Sorhus 128 | Arne de Bree 129 | Vladislav Zarakovsky 130 | Andrew E Monat 131 | Oskari 132 | Joao Henrique de Andrade Bruni 133 | tsinha 134 | Dominik D. Geyer 135 | Matt Farmer 136 | Trey Hunner 137 | Jason Moon 138 | Jeffery To 139 | Kris Borchers 140 | Vladimir Zhuravlev 141 | Jacob Thornton 142 | Chad Killingsworth 143 | Vitya Muhachev 144 | Nowres Rafid 145 | David Benjamin 146 | Alan Plum 147 | Uri Gilad 148 | Chris Faulkner 149 | Marcel Greter 150 | Elijah Manor 151 | Daniel Chatfield 152 | Daniel Gálvez 153 | Nikita Govorov 154 | Wesley Walser 155 | Mike Pennisi 156 | Matthias Jäggli 157 | Devin Cooper 158 | Markus Staab 159 | Dave Riddle 160 | Callum Macrae 161 | Jonathan Sampson 162 | Benjamin Truyman 163 | Jay Merrifield 164 | James Huston 165 | Sai Lung Wong 166 | Erick Ruiz de Chávez 167 | David Bonner 168 | Allen J Schmidt Jr 169 | Akintayo Akinwunmi 170 | MORGAN 171 | Ismail Khair 172 | Carl Danley 173 | Mike Petrovich 174 | Greg Lavallee 175 | Tom H Fuertes 176 | Roland Eckl 177 | Yiming He 178 | David Fox 179 | Bennett Sorbo 180 | Paul Ramos 181 | Rod Vagg 182 | Sebastian Burkhard 183 | Zachary Adam Kaplan 184 | Adam Coulombe 185 | nanto_vi 186 | nanto 187 | Danil Somsikov 188 | Ryunosuke SATO 189 | Diego Tres 190 | Jean Boussier 191 | Andrew Plummer 192 | Mark Raddatz 193 | Pascal Borreli 194 | Isaac Z. Schlueter 195 | Karl Sieburg 196 | Nguyen Phuc Lam 197 | Dmitry Gusev 198 | Steven Benner 199 | Li Xudong 200 | Michał Gołębiowski-Owczarek 201 | Renato Oliveira dos Santos 202 | Frederic Junod 203 | Tom H Fuertes 204 | Mitch Foley 205 | ros3cin 206 | Kyle Robinson Young 207 | John Paul 208 | Jason Bedard 209 | Chris Talkington 210 | Eddie Monge 211 | Terry Jones 212 | Jason Merino 213 | Dan Burzo 214 | Jeremy Dunck 215 | Chris Price 216 | Guy Bedford 217 | njhamann 218 | Goare Mao 219 | Amey Sakhadeo 220 | Mike Sidorov 221 | Anthony Ryan 222 | Lihan Li 223 | George Kats 224 | Dongseok Paeng 225 | Ronny Springer 226 | Ilya Kantor 227 | Marian Sollmann 228 | Chris Antaki 229 | David Hong 230 | Jakob Stoeck 231 | Christopher Jones 232 | Forbes Lindesay 233 | S. Andrew Sheppard 234 | Leonardo Balter 235 | Rodrigo Rosenfeld Rosas 236 | Daniel Husar 237 | Philip Jägenstedt 238 | John Hoven 239 | Roman Reiß 240 | Benjy Cui 241 | Christian Kosmowski 242 | David Corbacho 243 | Liang Peng 244 | TJ VanToll 245 | Aurelio De Rosa 246 | Senya Pugach 247 | Dan Hart 248 | Nazar Mokrynskyi 249 | Benjamin Tan 250 | Amit Merchant 251 | Jason Bedard 252 | Veaceslav Grimalschi 253 | Richard McDaniel 254 | Arthur Verschaeve 255 | Shivaji Varma 256 | Ben Toews 257 | Bin Xin 258 | Neftaly Hernandez 259 | T.J. Crowder 260 | Nicolas HENRY 261 | Frederic Hemberger 262 | Victor Homyakov 263 | Aditya Raghavan 264 | Anne-Gaelle Colom 265 | Leonardo Braga 266 | George Mauer 267 | Stephen Edgar 268 | Thomas Tortorini 269 | Jörn Wagner 270 | Jon Hester 271 | Colin Frick 272 | Winston Howes 273 | Alexander O'Mara 274 | Chris Rebert 275 | Bastian Buchholz 276 | Mu Haibao 277 | Calvin Metcalf 278 | Arthur Stolyar 279 | Gabriel Schulhof 280 | Gilad Peleg 281 | Julian Alexander Murillo 282 | Kevin Kirsche 283 | Martin Naumann 284 | Yongwoo Jeon 285 | John-David Dalton 286 | Marek Lewandowski 287 | Bruno Pérel 288 | Daniel Nill 289 | Reed Loden 290 | Sean Henderson 291 | Gary Ye 292 | Richard Kraaijenhagen 293 | Connor Atherton 294 | Christian Grete 295 | Tom von Clef 296 | Liza Ramo 297 | Joelle Fleurantin 298 | Steve Mao 299 | Jon Dufresne 300 | Jae Sung Park 301 | Josh Soref 302 | Saptak Sengupta 303 | Henry Wong 304 | Jun Sun 305 | Martijn W. van der Lee 306 | Devin Wilson 307 | Damian Senn 308 | Zack Hall 309 | Vitaliy Terziev 310 | Todor Prikumov 311 | Bernhard M. Wiedemann 312 | Jha Naman 313 | Alexander Lisianoi 314 | William Robinet 315 | Joe Trumbull 316 | Alexander K 317 | Ralin Chimev 318 | Felipe Sateler 319 | Christophe Tafani-Dereeper 320 | Manoj Kumar 321 | David Broder-Rodgers 322 | Alex Louden 323 | Alex Padilla 324 | karan-96 325 | 南漂一卒 326 | Erik Lax 327 | Boom Lee 328 | Andreas Solleder 329 | Pierre Spring 330 | Shashanka Nataraj 331 | CDAGaming 332 | Matan Kotler-Berkowitz <205matan@gmail.com> 333 | Jordan Beland 334 | Henry Zhu 335 | Nilton Cesar 336 | basil.belokon 337 | Andrey Meshkov 338 | tmybr11 339 | Luis Emilio Velasco Sanchez 340 | Ed S 341 | Bert Zhang 342 | Sébastien Règne 343 | wartmanm <3869625+wartmanm@users.noreply.github.com> 344 | Siddharth Dungarwal 345 | abnud1 346 | Andrei Fangli 347 | Marja Hölttä 348 | buddh4 349 | Hoang 350 | Wonseop Kim 351 | Pat O'Callaghan 352 | JuanMa Ruiz 353 | Ahmed.S.ElAfifi 354 | Christian Oliff 355 | Christian Wenz 356 | Sean Robinson 357 | Jonathan 358 | Pierre Grimaud 359 | Beatriz Rezener 360 | Natalia Sroka <37873210+natipo@users.noreply.github.com> 361 | Wonhyoung Park 362 | Dallas Fraser 363 | fecore1 <89127124+fecore1@users.noreply.github.com> 364 | ygj6 <7699524+ygj6@users.noreply.github.com> 365 | Simon Legner 366 | Vladimir Sitnikov 367 | Anders Kaseorg 368 | Alex 369 | Timo Tijhof 370 | Gabriela Gutierrez 371 | Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> 372 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/profvis/profvis.css: -------------------------------------------------------------------------------- 1 | .profvis { 2 | position: relative; 3 | } 4 | 5 | .profvis * { 6 | /* Need to disable box-sizing:border-box if enabled from other CSS (like 7 | Bootstrap) */ 8 | -webkit-box-sizing: content-box; 9 | -moz-box-sizing: content-box; 10 | box-sizing: content-box; 11 | } 12 | 13 | .profvis-footer .info-label { 14 | cursor: default; 15 | } 16 | 17 | .profvis-panel1 { 18 | left: 0px; 19 | top: 23px; 20 | position: absolute; 21 | } 22 | 23 | .profvis-panel1-vertical { 24 | bottom: 0px; 25 | width: 500px; 26 | margin-bottom: 20px; 27 | } 28 | 29 | .profvis-panel1-horizontal { 30 | height: 378px; 31 | right: 0px; 32 | } 33 | 34 | .profvis-panel2 { 35 | position: absolute; 36 | right: 0px; 37 | bottom: 0px; 38 | } 39 | 40 | .profvis-panel2-vertical { 41 | top: 23px; 42 | left: 508px; 43 | margin-bottom: 20px; 44 | } 45 | 46 | .profvis-panel2-horizontal { 47 | left: 0px; 48 | top: 408px; 49 | margin-bottom: 20px; 50 | } 51 | 52 | .profvis-splitbar { 53 | position: absolute; 54 | background-color: rgb(224, 224, 224); 55 | border-color: #ddd; 56 | border-style: solid; 57 | background-repeat: no-repeat; 58 | background-position: center; 59 | } 60 | 61 | .profvis-splitbar-vertical { 62 | cursor: col-resize; 63 | left: 500px; 64 | width: 8px; 65 | top: 23px; 66 | bottom: 0px; 67 | border-width: 0 1px; 68 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAHCAYAAADNufepAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAADdJREFUeNpMi7ENADAIwwz/H8TEhMQz/JAupWqmyHGI7JoZIrscxMbBbhX+MMbTpG+x/UgCgzMA+a4RzyZ2yIIAAAAASUVORK5CYII='); 69 | } 70 | 71 | .profvis-splitbar-horizontal { 72 | cursor: row-resize; 73 | top: 400px; 74 | height: 8px; 75 | left: 0px; 76 | right: 0px; 77 | border-width: 0px 0; 78 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAADCAYAAABfwxXFAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAADZJREFUeNpi3LHv+BEGBgYGBob/EIqBkQEGGN+8ecOADfz//5+B5cylW0cgqjF1AgAAAP//AwCtfA4GqIXcdgAAAABJRU5ErkJggg=='); 79 | } 80 | 81 | .profvis-status-bar { 82 | position: absolute; 83 | padding: 0px 0px; 84 | top: 0; 85 | left: 0; 86 | right: 0; 87 | height: 22px; 88 | line-height: 18px; 89 | border-bottom: 1px solid rgb(196, 201, 204); 90 | background-color: rgb(248, 249, 248); 91 | color: #444; 92 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 93 | font-size: 11px; 94 | -webkit-user-select: none; 95 | -moz-user-select: none; 96 | -ms-user-select: none; 97 | user-select: none; 98 | overflow: hidden; 99 | } 100 | 101 | .profvis-footer { 102 | position: absolute; 103 | padding: 0px 0px; 104 | bottom: 0px; 105 | left: 0; 106 | right: 0; 107 | height: 19px; 108 | line-height: 18px; 109 | border-top: 1px solid rgb(196, 201, 204); 110 | background-color: rgb(248, 249, 248); 111 | color: #444; 112 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 113 | font-size: 11px; 114 | -webkit-user-select: none; 115 | -moz-user-select: none; 116 | -ms-user-select: none; 117 | user-select: none; 118 | overflow: hidden; 119 | } 120 | 121 | .profvis-status-bar .info-block { 122 | display: inline-block; 123 | vertical-align: top; 124 | width: 140px; 125 | padding: 2px 11px 2px 11px; 126 | } 127 | 128 | .profvis-footer .info-block { 129 | display: inline-block; 130 | vertical-align: top; 131 | padding: 1px 11px 1px 11px; 132 | } 133 | 134 | .profvis-footer .info-block-right { 135 | display: inline-block; 136 | vertical-align: top; 137 | padding: 1px 11px 1px 11px; 138 | float: right; 139 | } 140 | 141 | .profvis-status-bar .result-block { 142 | width: auto; 143 | cursor: pointer; 144 | } 145 | 146 | .profvis-status-bar .result-block-active { 147 | width: auto; 148 | background: rgb(227, 229, 230); 149 | } 150 | 151 | .profvis-status-bar .spacing-block { 152 | display: inline-block; 153 | width: 25px; 154 | } 155 | 156 | .profvis-status-bar .separator-block { 157 | display: inline-block; 158 | width: 1px; 159 | text-align: center; 160 | } 161 | 162 | .profvis-status-bar .separator-block .separator-image { 163 | width: 2px; 164 | height: 26px; 165 | margin-top: -2px; 166 | } 167 | 168 | .profvis-status-bar .options-button { 169 | float: right; 170 | color: #444; 171 | text-decoration: none; 172 | cursor: pointer; 173 | padding: 2px 11px 2px 11px; 174 | } 175 | 176 | 177 | .profvis-options-panel { 178 | float: left; 179 | position: absolute; 180 | right: 0; 181 | top: 21px; 182 | padding: 3px 6px; 183 | border: 1px solid #999; 184 | background-color: #fff; 185 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 186 | font-size: 11px; 187 | line-height: 170%; 188 | -webkit-user-select: none; 189 | -moz-user-select: none; 190 | -ms-user-select: none; 191 | user-select: none; 192 | cursor: pointer; 193 | } 194 | 195 | .profvis-code { 196 | position: absolute; 197 | top: 0px; 198 | left: 0; 199 | bottom: 0; 200 | right: 0; 201 | overflow-y: auto; 202 | border: 0px solid #ddd; 203 | } 204 | 205 | .profvis-flamegraph { 206 | position: absolute; 207 | right: 0; 208 | top: 0; 209 | bottom: 0; 210 | left: 0; 211 | overflow: hidden; 212 | border: 0px solid #ddd; 213 | background: rgb(249, 249, 250); 214 | } 215 | 216 | table.profvis-table { 217 | border-collapse: collapse; 218 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 219 | font-size: 11px; 220 | width: 100%; 221 | text-align: left; 222 | cursor: default; 223 | } 224 | 225 | table.profvis-table th { 226 | background-color: rgb(249, 249, 250); 227 | width: 14%; 228 | } 229 | 230 | table.profvis-table th.spacing { 231 | width: 20px; 232 | } 233 | 234 | table.profvis-table th.filename { 235 | font-family: monospace; 236 | padding-left: 10px; 237 | width: auto; 238 | } 239 | 240 | table.profvis-table th.percent { 241 | text-align: center; 242 | } 243 | 244 | table.profvis-table tr { 245 | border-top: 1px solid transparent; 246 | border-bottom: 1px solid transparent; 247 | vertical-align: top; 248 | } 249 | 250 | /* Need to use td for locked cells, so that the border color overrides the 251 | border color from the row above. */ 252 | table.profvis-table tr.locked > td { 253 | border-top: 1px solid #444; 254 | border-bottom: 1px solid #444; 255 | } 256 | 257 | table.profvis-table tr > td { 258 | padding-top: 0; 259 | padding-bottom: 0; 260 | } 261 | 262 | table.profvis-table tr.active { 263 | background-color: #fdb; 264 | } 265 | 266 | table.profvis-table .linenum { 267 | color: #aaa; 268 | padding: 0 15px; 269 | font-family: monospace; 270 | width: 25px; 271 | } 272 | 273 | /* For unselectable and uncopyable text elements in table */ 274 | table.profvis-table [data-pseudo-content]::before { 275 | content: attr(data-pseudo-content); 276 | } 277 | 278 | table.profvis-table .code { 279 | white-space: pre-wrap; 280 | margin: 0; 281 | min-height: 1.25em; 282 | line-height: 1.25; 283 | width: auto; 284 | font-family: monospace; 285 | background: transparent; 286 | } 287 | 288 | 289 | table.profvis-table .time, table.profvis-table .percent { 290 | padding: 0 5px; 291 | text-align: right; 292 | 293 | min-width: 2em; 294 | max-width: 2em; 295 | overflow: hidden; 296 | 297 | padding-right: 10px; 298 | } 299 | 300 | table.profvis-table .memory { 301 | padding-right: 5px; 302 | } 303 | 304 | table.profvis-table th.time { 305 | text-align: center; 306 | } 307 | 308 | table.profvis-table .memory { 309 | padding: 0 5px; 310 | text-align: right; 311 | 312 | min-width: 2em; 313 | max-width: 4em; 314 | overflow: hidden; 315 | 316 | padding-right: 10px; 317 | } 318 | 319 | table.profvis-table .memory-right { 320 | text-align: left; 321 | } 322 | 323 | table.profvis-table th.memory { 324 | text-align: center; 325 | } 326 | 327 | table.profvis-table .timebar-cell { 328 | padding-left: 0; 329 | border-left: 1px solid #444; 330 | min-width: 3em; 331 | width: 3em; 332 | } 333 | 334 | table.profvis-table .timebar-cell > .timebar { 335 | background-color: #5A5A5A; 336 | border-radius: 0px 2px 2px 0px; 337 | line-height: 15px; 338 | } 339 | 340 | table.profvis-table .membar-left-cell { 341 | padding-left: 0; 342 | padding-right: 0; 343 | border-left: 0px solid black; 344 | min-width: 0.5em; 345 | width: 0.5em; 346 | } 347 | 348 | table.profvis-table .membar-left-cell > .membar { 349 | background-color: #A7A7A7; 350 | float: right; 351 | border-radius: 2px 0px 0px 2px; 352 | line-height: 15px; 353 | } 354 | 355 | table.profvis-table .membar-right-cell { 356 | padding-left: 0; 357 | border-left: 1px solid black; 358 | min-width: 1em; 359 | width: 1em; 360 | } 361 | 362 | table.profvis-table .membar-right-cell > .membar { 363 | background-color: #5A5A5A; 364 | border-radius: 0px 2px 2px 0px; 365 | line-height: 15px; 366 | } 367 | 368 | .profvis-flamegraph .background { 369 | fill: rgb(249, 249, 250); 370 | } 371 | 372 | .profvis-flamegraph .cell .rect { 373 | stroke: #000; 374 | stroke-width: 0.25px; 375 | fill: #fff; 376 | } 377 | 378 | .profvis-flamegraph .cell.active .rect { 379 | stroke-width: 0.75px; 380 | fill: #ddd; 381 | } 382 | 383 | .profvis-flamegraph .cell.highlighted .rect { 384 | fill: #ffc; 385 | } 386 | 387 | .profvis-flamegraph .cell.highlighted.active .rect { 388 | fill: #fdb; 389 | } 390 | 391 | .profvis-flamegraph .cell.output .rect { 392 | fill: #eef; 393 | } 394 | 395 | .profvis-flamegraph .cell.gc .rect { 396 | fill: #ccc; 397 | } 398 | 399 | .profvis-flamegraph .cell.stacktrace .rect { 400 | fill: #eee; 401 | } 402 | .profvis-flamegraph .cell.stacktrace .profvis-label { 403 | fill: #666; 404 | } 405 | 406 | .profvis-flamegraph .cell.locked { 407 | font-weight: bold; 408 | } 409 | 410 | .profvis-flamegraph .cell.locked .rect { 411 | stroke-width: 2px; 412 | } 413 | 414 | .profvis-flamegraph .cell .profvis-label { 415 | font-family: monospace; 416 | font-size: 11px; 417 | cursor: default; 418 | } 419 | 420 | 421 | .profvis-flamegraph .axis text { 422 | font: 10px sans-serif; 423 | } 424 | 425 | .profvis-flamegraph .axis path, 426 | .profvis-flamegraph .axis line { 427 | fill: none; 428 | stroke: #000; 429 | shape-rendering: crispEdges; 430 | } 431 | 432 | 433 | .profvis-flamegraph .profvis-tooltip rect { 434 | fill: rgb(249, 249, 250); 435 | stroke: #000; 436 | opacity: 0.75; 437 | stroke-opacity: 0.75; 438 | stroke-width: 0.5; 439 | } 440 | 441 | .profvis-flamegraph .profvis-tooltip text { 442 | text-anchor: middle; 443 | font-family: monospace; 444 | font-size: 11px; 445 | } 446 | 447 | .profvis-infobox { 448 | position: absolute; 449 | left: 8px; 450 | top: 8px; 451 | opacity: 0.8; 452 | border-radius: 3px; 453 | color: #f8f8f8; 454 | background-color: #333; 455 | padding: 5px 10px; 456 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 457 | font-size: 11px; 458 | line-height: 100%; 459 | pointer-events: none; 460 | min-width: 280px; 461 | } 462 | 463 | .profvis-infobox table { 464 | border-collapse: separate; 465 | border-spacing: 2px; 466 | } 467 | 468 | 469 | .profvis-infobox table td { 470 | padding: 1px; 471 | } 472 | 473 | .profvis-infobox .infobox-title { 474 | font-weight: bold; 475 | } 476 | 477 | .profvis-message { 478 | width: 100%; 479 | height: 100%; 480 | display: -ms-flexbox; 481 | display: -webkit-flex; 482 | display: flex; 483 | -ms-flex-pack: center; 484 | -webkit-justify-content: center; 485 | justify-content: center; 486 | -ms-flex-align: center; 487 | -webkit-align-items: center; 488 | align-items: center; 489 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 490 | font-size: 11px; 491 | } 492 | 493 | .profvis-message div { 494 | color: #444; 495 | height: 25%; 496 | } 497 | 498 | .profvis-treetable { 499 | top: 0px; 500 | bottom: 0px; 501 | left: 0px; 502 | right: 0px; 503 | position: absolute; 504 | margin-top: 23px; 505 | margin-bottom: 21px; 506 | margin-right: 0px; 507 | overflow: hidden; 508 | font: 10px sans-serif; 509 | color: #161616; 510 | font-family: "Lucida Sans", "DejaVu Sans", "Lucida Grande", "Segoe UI", Verdana, Helvetica, sans-serif; 511 | font-size: 11px; 512 | overflow-y: auto; 513 | } 514 | 515 | .profvis-treetable .results { 516 | width: 100%; 517 | table-layout: fixed; 518 | } 519 | 520 | .profvis-treetable th { 521 | font-weight: normal; 522 | height: 18px; 523 | background-color: #F8F9F8; 524 | border-bottom: solid 1px #E4E4E4; 525 | border-right: solid 1px #E4E4E4; 526 | padding-left: 3px; 527 | padding-right: 3px; 528 | } 529 | 530 | .profvis-treetable td { 531 | height: 18px; 532 | border-bottom: solid 1px #E4E4E4; 533 | border-right: solid 1px #E4E4E4; 534 | padding-left: 3px; 535 | padding-right: 3px; 536 | overflow: hidden; 537 | text-overflow: ellipsis; 538 | } 539 | 540 | .profvis-treetable .action { 541 | width: 18px; 542 | } 543 | 544 | .profvis-treetable .memory { 545 | width: 100px; 546 | text-align: right; 547 | } 548 | 549 | .profvis-treetable th { 550 | cursor: default; 551 | -webkit-user-select: none; 552 | -moz-user-select: none; 553 | -ms-user-select: none; 554 | user-select: none; 555 | overflow: hidden; 556 | } 557 | 558 | .profvis-treetable th.memory { 559 | text-align: center; 560 | } 561 | 562 | .profvis-treetable .time { 563 | width: 100px; 564 | text-align: right; 565 | } 566 | 567 | .profvis-treetable th.time { 568 | text-align: center; 569 | } 570 | 571 | .profvis-treetable .count { 572 | text-align: right; 573 | } 574 | 575 | .profvis-treetable th.count { 576 | text-align: center; 577 | } 578 | 579 | .profvis-treetable th.code-label { 580 | text-align: left; 581 | padding-left: 5px; 582 | } 583 | 584 | .profvis-treetable td.label-pointer { 585 | cursor: pointer; 586 | } 587 | 588 | .profvis-treetable .label-text { 589 | display: inline-block; 590 | -webkit-user-select: none; 591 | -khtml-user-select: none; 592 | -moz-user-select: none; 593 | -ms-user-select: none; 594 | user-select: none; 595 | } 596 | 597 | .profvis-treetable .path { 598 | text-align: left; 599 | } 600 | 601 | .profvis-treetable .treetable-expand > div { 602 | display: inline-block; 603 | width: 15px; 604 | } 605 | 606 | .profvis-treetable .treetable-expand > div > div{ 607 | width: 0px; 608 | height: 0px; 609 | margin-left: 3px; 610 | margin-right: 6px; 611 | border-top: 4px solid transparent; 612 | border-left: 6px solid #161616; 613 | border-bottom: 4px solid transparent; 614 | } 615 | 616 | .profvis-treetable .treetable-collapse > div { 617 | display: inline-block; 618 | width: 15px; 619 | } 620 | 621 | .profvis-treetable .treetable-collapse > div > div { 622 | width: 0px; 623 | height: 0px; 624 | margin-left: 3px; 625 | margin-right: 6px; 626 | border-right: 4px solid transparent; 627 | border-top: 6px solid #161616; 628 | border-left: 4px solid transparent; 629 | display: inline-block; 630 | } 631 | 632 | .profvis-treetable .time-info { 633 | padding: 0px; 634 | padding-right: 2px; 635 | text-align: right; 636 | } 637 | 638 | .profvis-treetable .timebar { 639 | width: 0px; 640 | height: 14px; 641 | background-color: #5A5A5A; 642 | float: left; 643 | border-radius: 2px 2px 2px 2px; 644 | padding-top: 0px; 645 | margin-top: 1px; 646 | } 647 | 648 | .profvis-treetable .timecell { 649 | padding-top: 2px; 650 | padding-right: 3px; 651 | } 652 | 653 | .profvis-treetable .memory-info { 654 | padding-left: 0px; 655 | padding-right: 2px; 656 | text-align: right; 657 | } 658 | 659 | .profvis-treetable .memory-info-right { 660 | padding-left: 2px; 661 | padding-right: 0px; 662 | text-align: left; 663 | } 664 | 665 | .profvis-treetable .memory-leftbar-wrapper { 666 | float: left; 667 | height: 14px; 668 | width: 5px; 669 | } 670 | 671 | .profvis-treetable .memory-leftbar { 672 | width: 2px; 673 | height: 14px; 674 | background-color: #A7A7A7; 675 | float: right; 676 | border-radius: 1px 0px 0px 1px; 677 | } 678 | 679 | .profvis-treetable .memory-rightbar { 680 | width: 2px; 681 | float: left; 682 | height: 14px; 683 | background-color: #5A5A5A; 684 | border-radius: 0px 1px 1px 0px; 685 | } 686 | 687 | .profvis-treetable .memory-cell { 688 | } 689 | 690 | .profvis-treetable .memory-bar-container { 691 | margin-right: 3px; 692 | } 693 | 694 | .profvis-treetable .time-bar-container { 695 | margin-right: 3px; 696 | } 697 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/profvis/scroll.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Polyfill for scrollIntoViewIfNeeded() 4 | // From https://gist.github.com/jocki84/6ffafd003387179a988e 5 | // License: ISC 6 | if (!Element.prototype.scrollIntoViewIfNeeded) { 7 | Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { 8 | "use strict"; 9 | 10 | function makeRange(start, length) { 11 | return {"start": start, "length": length, "end": start + length}; 12 | } 13 | 14 | function coverRange(inner, outer) { 15 | if ( 16 | false === centerIfNeeded || 17 | (outer.start < inner.end && inner.start < outer.end) 18 | ) { 19 | return Math.max( 20 | inner.end - outer.length, 21 | Math.min(outer.start, inner.start) 22 | ); 23 | } 24 | return (inner.start + inner.end - outer.length) / 2; 25 | } 26 | 27 | function makePoint(x, y) { 28 | return { 29 | "x": x, 30 | "y": y, 31 | "translate": function translate(dX, dY) { 32 | return makePoint(x + dX, y + dY); 33 | } 34 | }; 35 | } 36 | 37 | function absolute(elem, pt) { 38 | while (elem) { 39 | pt = pt.translate(elem.offsetLeft, elem.offsetTop); 40 | elem = elem.offsetParent; 41 | } 42 | return pt; 43 | } 44 | 45 | var target = absolute(this, makePoint(0, 0)), 46 | extent = makePoint(this.offsetWidth, this.offsetHeight), 47 | elem = this.parentNode, 48 | origin; 49 | 50 | while (elem instanceof HTMLElement) { 51 | // Apply desired scroll amount. 52 | origin = absolute(elem, makePoint(elem.clientLeft, elem.clientTop)); 53 | elem.scrollLeft = coverRange( 54 | makeRange(target.x - origin.x, extent.x), 55 | makeRange(elem.scrollLeft, elem.clientWidth) 56 | ); 57 | elem.scrollTop = coverRange( 58 | makeRange(target.y - origin.y, extent.y), 59 | makeRange(elem.scrollTop, elem.clientHeight) 60 | ); 61 | 62 | // Determine actual scroll amount by reading back scroll properties. 63 | target = target.translate(-elem.scrollLeft, -elem.scrollTop); 64 | elem = elem.parentNode; 65 | } 66 | }; 67 | } 68 | 69 | })() -------------------------------------------------------------------------------- /inst/htmlwidgets/profvis.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'profvis', 4 | 5 | type: 'output', 6 | 7 | initialize: function(el, width, height) { 8 | 9 | return { 10 | // TODO: add instance fields as required 11 | }; 12 | 13 | }, 14 | 15 | renderValue: function(el, x, instance) { 16 | profvis.render(el, x.message); 17 | }, 18 | 19 | resize: function(el, width, height, instance) { 20 | 21 | } 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /inst/htmlwidgets/profvis.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: jquery 3 | version: 3.7.1 4 | src: "htmlwidgets/lib/jquery" 5 | script: 6 | - jquery.min.js 7 | - name: d3 8 | version: 3.5.6 9 | src: "htmlwidgets/lib/d3" 10 | script: 11 | - d3.min.js 12 | - name: profvis 13 | version: 0.3.6.9000 14 | src: "htmlwidgets/lib/profvis" 15 | script: 16 | - profvis.js 17 | - scroll.js 18 | stylesheet: profvis.css 19 | - name: highlight 20 | version: 11.10.0 21 | src: "htmlwidgets/lib/highlight" 22 | script: highlight.min.js 23 | stylesheet: textmate.css 24 | -------------------------------------------------------------------------------- /inst/shinymodule/draggable-helper.js: -------------------------------------------------------------------------------- 1 | // Delay this layout code for 100ms so the DOM has time to layout. 2 | // We've initialize the profvis-module-container to left:-200px so there's no 3 | // flash of unstyled content anyway. 4 | setTimeout(function() { 5 | var profvis_container = $(".profvis-module-container"); 6 | 7 | // Constrain draggability to x-axis 8 | profvis_container.draggable("option", "axis", "x"); 9 | }, 100); 10 | -------------------------------------------------------------------------------- /man/parse_rprof.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parse.R 3 | \name{parse_rprof} 4 | \alias{parse_rprof} 5 | \title{Parse Rprof output file for use with profvis} 6 | \usage{ 7 | parse_rprof(path = "Rprof.out", expr_source = NULL) 8 | } 9 | \arguments{ 10 | \item{path}{Path to the \code{\link[=Rprof]{Rprof()}} output file.} 11 | 12 | \item{expr_source}{If any source refs in the profiling output have an empty 13 | filename, that means they refer to code executed at the R console. This 14 | code can be captured and passed (as a string) as the \code{expr_source} 15 | argument.} 16 | } 17 | \description{ 18 | Parse Rprof output file for use with profvis 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/pause.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pause.R 3 | \name{pause} 4 | \alias{pause} 5 | \title{Pause an R process} 6 | \usage{ 7 | pause(seconds) 8 | } 9 | \arguments{ 10 | \item{seconds}{Number of seconds to pause.} 11 | } 12 | \description{ 13 | This function pauses an R process for some amount of time. It differs from 14 | \code{\link[=Sys.sleep]{Sys.sleep()}} in that time spent in \code{pause} will show up in 15 | profiler data. Another difference is that \code{pause} uses up 100\\% of a CPU, 16 | whereas \code{Sys.sleep} does not. 17 | } 18 | \examples{ 19 | # Wait for 0.5 seconds 20 | pause(0.5) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /man/print.profvis.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/profvis.R 3 | \name{print.profvis} 4 | \alias{print.profvis} 5 | \title{Print a profvis object} 6 | \usage{ 7 | \method{print}{profvis}(x, ..., width = NULL, height = NULL, split = NULL, aggregate = NULL) 8 | } 9 | \arguments{ 10 | \item{x}{The object to print.} 11 | 12 | \item{...}{Further arguments to passed on to other print methods.} 13 | 14 | \item{width}{Width of the htmlwidget.} 15 | 16 | \item{height}{Height of the htmlwidget} 17 | 18 | \item{split}{Orientation of the split bar: either \code{"h"} (the default) for 19 | horizontal or \code{"v"} for vertical.} 20 | 21 | \item{aggregate}{If \code{TRUE}, the profiled stacks are aggregated by 22 | name. This makes it easier to see the big picture. Set your own 23 | global default for this argument with \code{options(profvis.aggregate = )}.} 24 | } 25 | \description{ 26 | Print a profvis object 27 | } 28 | -------------------------------------------------------------------------------- /man/profvis-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/profvis-package.R 3 | \docType{package} 4 | \name{profvis-package} 5 | \alias{profvis-package} 6 | \title{profvis: Interactive Visualizations for Profiling R Code} 7 | \description{ 8 | Interactive visualizations for profiling R code. 9 | } 10 | \seealso{ 11 | Useful links: 12 | \itemize{ 13 | \item \url{https://profvis.r-lib.org} 14 | \item \url{https://github.com/r-lib/profvis} 15 | \item Report bugs at \url{https://github.com/r-lib/profvis/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Hadley Wickham \email{hadley@posit.co} 21 | 22 | Authors: 23 | \itemize{ 24 | \item Winston Chang 25 | \item Javier Luraschi 26 | \item Timothy Mastny 27 | } 28 | 29 | Other contributors: 30 | \itemize{ 31 | \item Posit Software, PBC [copyright holder, funder] 32 | \item jQuery Foundation (jQuery library) [copyright holder] 33 | \item jQuery contributors (jQuery library; authors listed in inst/htmlwidgets/lib/jquery/AUTHORS.txt) [contributor, copyright holder] 34 | \item Mike Bostock (D3 library) [contributor, copyright holder] 35 | \item D3 contributors (D3 library) [contributor] 36 | \item Ivan Sagalaev (highlight.js library) [contributor, copyright holder] 37 | } 38 | 39 | } 40 | \keyword{internal} 41 | -------------------------------------------------------------------------------- /man/profvis.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/profvis.R 3 | \name{profvis} 4 | \alias{profvis} 5 | \title{Profile an R expression and visualize profiling data} 6 | \usage{ 7 | profvis( 8 | expr = NULL, 9 | interval = 0.01, 10 | prof_output = NULL, 11 | prof_input = NULL, 12 | timing = NULL, 13 | width = NULL, 14 | height = NULL, 15 | split = c("h", "v"), 16 | torture = 0, 17 | simplify = TRUE, 18 | rerun = FALSE 19 | ) 20 | } 21 | \arguments{ 22 | \item{expr}{Expression to profile. The expression will be turned into the 23 | body of a zero-argument anonymous function which is then called repeatedly 24 | as needed. This means that if you create variables inside of \code{expr} they 25 | will not be available outside of it. 26 | 27 | The expression is repeatedly evaluated until \code{Rprof()} produces 28 | an output. It can \emph{be} a quosure injected with \code{\link[rlang:inject]{rlang::inject()}} but 29 | it cannot \emph{contain} injected quosures. 30 | 31 | Not compatible with \code{prof_input}.} 32 | 33 | \item{interval}{Interval for profiling samples, in seconds. Values less than 34 | 0.005 (5 ms) will probably not result in accurate timings} 35 | 36 | \item{prof_output}{Name of an Rprof output file or directory in which to save 37 | profiling data. If \code{NULL} (the default), a temporary file will be used 38 | and automatically removed when the function exits. For a directory, a 39 | random filename is used.} 40 | 41 | \item{prof_input}{The path to an \code{\link[=Rprof]{Rprof()}} data file. Not 42 | compatible with \code{expr} or \code{prof_output}.} 43 | 44 | \item{timing}{The type of timing to use. Either \code{"elapsed"} (the 45 | default) for wall clock time, or \code{"cpu"} for CPU time. Wall clock time 46 | includes time spent waiting for other processes (e.g. waiting for a 47 | web page to download) so is generally more useful. 48 | 49 | If \code{NULL}, the default, will use elapsed time where possible, i.e. 50 | on Windows or on R 4.4.0 or greater.} 51 | 52 | \item{width}{Width of the htmlwidget.} 53 | 54 | \item{height}{Height of the htmlwidget} 55 | 56 | \item{split}{Orientation of the split bar: either \code{"h"} (the default) for 57 | horizontal or \code{"v"} for vertical.} 58 | 59 | \item{torture}{Triggers garbage collection after every \code{torture} memory 60 | allocation call. 61 | 62 | Note that memory allocation is only approximate due to the nature of the 63 | sampling profiler and garbage collection: when garbage collection triggers, 64 | memory allocations will be attributed to different lines of code. Using 65 | \code{torture = steps} helps prevent this, by making R trigger garbage 66 | collection after every \code{torture} memory allocation step.} 67 | 68 | \item{simplify}{Whether to simplify the profiles by removing 69 | intervening frames caused by lazy evaluation. Equivalent to the 70 | \code{filter.callframes} argument to \code{\link[=Rprof]{Rprof()}}.} 71 | 72 | \item{rerun}{If \code{TRUE}, \code{Rprof()} is run again with \code{expr} until a 73 | profile is actually produced. This is useful for the cases where 74 | \code{expr} returns too quickly, before R had time to sample a 75 | profile. Can also be a string containing a regexp to match 76 | profiles. In this case, \code{profvis()} reruns \code{expr} until the 77 | regexp matches the modal value of the profile stacks.} 78 | } 79 | \description{ 80 | This function will run an R expression with profiling, and then return an 81 | htmlwidget for interactively exploring the profiling data. 82 | } 83 | \details{ 84 | An alternate way to use \code{profvis} is to separately capture the profiling 85 | data to a file using \code{\link[=Rprof]{Rprof()}}, and then pass the path to the 86 | corresponding data file as the \code{prof_input} argument to 87 | \code{profvis()}. 88 | } 89 | \examples{ 90 | # Only run these examples in interactive R sessions 91 | if (interactive()) { 92 | 93 | # Profile some code 94 | profvis({ 95 | dat <- data.frame( 96 | x = rnorm(5e4), 97 | y = rnorm(5e4) 98 | ) 99 | 100 | plot(x ~ y, data = dat) 101 | m <- lm(x ~ y, data = dat) 102 | abline(m, col = "red") 103 | }) 104 | 105 | 106 | # Save a profile to an HTML file 107 | p <- profvis({ 108 | dat <- data.frame( 109 | x = rnorm(5e4), 110 | y = rnorm(5e4) 111 | ) 112 | 113 | plot(x ~ y, data = dat) 114 | m <- lm(x ~ y, data = dat) 115 | abline(m, col = "red") 116 | }) 117 | htmlwidgets::saveWidget(p, "profile.html") 118 | 119 | # Can open in browser from R 120 | browseURL("profile.html") 121 | 122 | } 123 | } 124 | \seealso{ 125 | \code{\link[=print.profvis]{print.profvis()}} for printing options. 126 | 127 | \code{\link[=Rprof]{Rprof()}} for more information about how the profiling 128 | data is collected. 129 | } 130 | -------------------------------------------------------------------------------- /man/profvisOutput.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/profvis.R 3 | \name{profvisOutput} 4 | \alias{profvisOutput} 5 | \alias{renderProfvis} 6 | \title{Widget output and renders functions for use in Shiny} 7 | \usage{ 8 | profvisOutput(outputId, width = "100\%", height = "600px") 9 | 10 | renderProfvis(expr, env = parent.frame(), quoted = FALSE) 11 | } 12 | \arguments{ 13 | \item{outputId}{Output variable for profile visualization.} 14 | 15 | \item{width}{Width of the htmlwidget.} 16 | 17 | \item{height}{Height of the htmlwidget} 18 | 19 | \item{expr}{An expression that returns a profvis object.} 20 | 21 | \item{env}{The environment in which to evaluate \code{expr}.} 22 | 23 | \item{quoted}{Is \code{expr} a quoted expression (with \code{\link[=quote]{quote()}})?} 24 | } 25 | \description{ 26 | Widget output and renders functions for use in Shiny 27 | } 28 | -------------------------------------------------------------------------------- /man/profvis_ui.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shiny_module.R 3 | \name{profvis_ui} 4 | \alias{profvis_ui} 5 | \alias{profvis_server} 6 | \title{profvis UI for Shiny Apps} 7 | \usage{ 8 | profvis_ui(id) 9 | 10 | profvis_server(input, output, session, dir = ".") 11 | } 12 | \arguments{ 13 | \item{id}{Output id from \code{profvis_server}.} 14 | 15 | \item{input, output, session}{Arguments provided by 16 | \code{\link[shiny:callModule]{shiny::callModule()}}.} 17 | 18 | \item{dir}{Output directory to save Rprof files.} 19 | } 20 | \description{ 21 | Use this Shiny module to inject profvis controls into your Shiny app. The 22 | profvis Shiny module injects UI that can be used to start and stop profiling, 23 | and either view the results in the profvis UI or download the raw .Rprof 24 | data. It is highly recommended that this be used for testing and debugging 25 | only, and not included in production apps! 26 | } 27 | \details{ 28 | The usual way to use profvis with Shiny is to simply call 29 | \code{profvis(shiny::runApp())}, but this may not always be possible or desirable: 30 | first, if you only want to profile a particular interaction in the Shiny app 31 | and not capture all the calculations involved in starting up the app and 32 | getting it into the correct state; and second, if you're trying to profile an 33 | application that's been deployed to a server. 34 | 35 | For more details on how to invoke Shiny modules, see \href{https://shiny.rstudio.com/articles/modules.html}{this article}. 36 | } 37 | \examples{ 38 | # In order to avoid "Hit to see next plot" prompts, 39 | # run this example with `example(profvis_ui, ask=FALSE)` 40 | 41 | if(interactive()) { 42 | library(shiny) 43 | shinyApp( 44 | fluidPage( 45 | plotOutput("plot"), 46 | actionButton("new", "New plot"), 47 | profvis_ui("profiler") 48 | ), 49 | function(input, output, session) { 50 | callModule(profvis_server, "profiler") 51 | 52 | output$plot <- renderPlot({ 53 | input$new 54 | boxplot(mpg ~ cyl, data = mtcars) 55 | }) 56 | } 57 | ) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /profvis.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | library 3 | checks.noindex 4 | library.noindex 5 | data.sqlite 6 | *.html 7 | cloud.noindex 8 | -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Revdeps 2 | 3 | ## New problems (1) 4 | 5 | |package |version |error |warning |note | 6 | |:-------|:-------|:------|:-------|:--------| 7 | |[tidyft](problems.md#tidyft)|0.5.7 |__+1__ | |1 __+1__ | 8 | 9 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 8 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. 4 | 5 | * We saw 1 new problems 6 | * We failed to check 0 packages 7 | 8 | Issues with CRAN packages are summarised below. 9 | 10 | ### New problems 11 | (This reports the first line of each new failure) 12 | 13 | * tidyft 14 | checking running R code from vignettes ... ERROR 15 | checking re-building of vignette outputs ... NOTE 16 | 17 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | # tidyft 2 | 3 |
4 | 5 | * Version: 0.5.7 6 | * GitHub: https://github.com/hope-data-science/tidyft 7 | * Source code: https://github.com/cran/tidyft 8 | * Date/Publication: 2023-01-08 14:40:01 UTC 9 | * Number of recursive dependencies: 47 10 | 11 | Run `revdepcheck::cloud_details(, "tidyft")` for more info 12 | 13 |
14 | 15 | ## Newly broken 16 | 17 | * checking running R code from vignettes ... ERROR 18 | ``` 19 | Errors in running code in vignettes: 20 | when running code in ‘Introduction.Rmd’ 21 | ... 22 | 23 | > profvis({ 24 | + res1 = ft %>% select_fst(Species, Sepal.Length, Sepal.Width, 25 | + Petal.Length) %>% dplyr::select(-Petal.Length) %>% dplyr::re .... [TRUNCATED] 26 | 27 | > setequal(res1, res2) 28 | 29 | When sourcing ‘Introduction.R’: 30 | Error: object 'res1' not found 31 | Execution halted 32 | 33 | ‘Introduction.Rmd’ using ‘UTF-8’... failed 34 | ``` 35 | 36 | * checking re-building of vignette outputs ... NOTE 37 | ``` 38 | Error(s) in re-building vignettes: 39 | ... 40 | --- re-building ‘Introduction.Rmd’ using rmarkdown 41 | 42 | Quitting from lines 113-183 [unnamed-chunk-5] (Introduction.Rmd) 43 | Error: processing vignette 'Introduction.Rmd' failed with diagnostics: 44 | object 'res1' not found 45 | --- failed re-building ‘Introduction.Rmd’ 46 | 47 | SUMMARY: processing the following file failed: 48 | ‘Introduction.Rmd’ 49 | 50 | Error: Vignette re-building failed. 51 | Execution halted 52 | ``` 53 | 54 | ## In both 55 | 56 | * checking Rd cross-references ... NOTE 57 | ``` 58 | Packages unavailable to check Rd xrefs: ‘tidyfst’, ‘tidyr’, ‘fastDummies’ 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /src/pause.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifdef _WIN32 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | double get_time_ms(void) { 11 | #ifdef _WIN32 12 | LARGE_INTEGER time_var, frequency; 13 | QueryPerformanceCounter(&time_var); 14 | QueryPerformanceFrequency(&frequency); 15 | 16 | return (double)time_var.QuadPart / (double)frequency.QuadPart; 17 | 18 | #else 19 | struct timeval tv; 20 | 21 | gettimeofday(&tv, NULL); 22 | return (double)tv.tv_sec + (double)tv.tv_usec / 1000000; 23 | #endif 24 | } 25 | 26 | SEXP profvis_pause (SEXP seconds) { 27 | if (TYPEOF(seconds) != REALSXP || Rf_length(seconds) != 1) 28 | Rf_error("`seconds` must be a single number."); 29 | 30 | double start = get_time_ms(); 31 | double sec = Rf_asReal(seconds); 32 | 33 | while(get_time_ms() - start < sec) { 34 | R_CheckUserInterrupt(); 35 | } 36 | 37 | return R_NilValue; 38 | } 39 | -------------------------------------------------------------------------------- /src/profvis-init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | extern SEXP profvis_pause (SEXP seconds); 6 | 7 | static const R_CallMethodDef callMethods[] = { 8 | { "profvis_pause", (DL_FUNC) &profvis_pause, 1 }, 9 | { NULL, NULL, 0 } 10 | }; 11 | 12 | void R_init_profvis(DllInfo *dll) { 13 | R_registerRoutines(dll, NULL, callMethods, NULL, NULL); 14 | R_useDynamicSymbols(dll, FALSE); 15 | R_forceSymbols(dll, TRUE); 16 | } 17 | -------------------------------------------------------------------------------- /tests/test-all.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(profvis) 3 | 4 | test_check("profvis") 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/pause.md: -------------------------------------------------------------------------------- 1 | # checks its inputs 2 | 3 | Code 4 | pause(c(1, 2)) 5 | Condition 6 | Error in `pause()`: 7 | ! `seconds` must be a single number. 8 | Code 9 | pause("a") 10 | Condition 11 | Error in `pause()`: 12 | ! `seconds` must be a single number. 13 | 14 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/profvis.md: -------------------------------------------------------------------------------- 1 | # expr and prof_input are mutually exclusive 2 | 3 | Code 4 | profvis(expr = f(), prof_input = "foo.R") 5 | Condition 6 | Error in `profvis()`: 7 | ! Exactly one of `expr` or `prof_input` must be supplied. 8 | 9 | # can capture profile of code with error 10 | 11 | Code 12 | out <- profvis(f(), rerun = "pause") 13 | Message 14 | profvis: code exited with error: 15 | error 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/testthat/helper-profvis.R: -------------------------------------------------------------------------------- 1 | TEST_PAUSE_TIME <- 0.050 2 | 3 | call_stacks <- function(x) { 4 | prof <- x$x$message$prof 5 | stacks <- split(prof$label, prof$time) 6 | vapply(stacks, paste, "", collapse = " ") 7 | } 8 | 9 | modal_call <- function(x) { 10 | modal_value0(call_stacks(x)) 11 | } 12 | 13 | profile_calls <- function(x) { 14 | prof <- x$x$message$prof 15 | stacks <- split(prof$label, prof$time) 16 | vapply(stacks, paste, "", collapse = " ") 17 | } 18 | 19 | profile_mode <- function(x) { 20 | modal_value0(profile_calls(x)) 21 | } 22 | -------------------------------------------------------------------------------- /tests/testthat/test-parse-sort1.R: -------------------------------------------------------------------------------- 1 | 2 | structure(list(time = c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 3L, 3L, 3 | 3L, 3L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 5L, 6L, 6L, 6L, 6L, 7L, 7L, 4 | 7L, 7L, 8L, 8L, 8L, 8L, 9L, 9L, 9L, 9L, 10L, 10L, 10L, 11L, 11L, 5 | 11L, 12L, 12L, 12L, 13L, 13L, 13L, 14L, 14L, 14L, 14L, 15L, 15L, 6 | 15L, 15L, 16L, 16L, 16L, 16L, 17L, 17L, 17L, 17L, 18L, 18L, 18L, 7 | 18L, 19L, 19L, 19L, 19L, 20L, 20L, 20L, 20L, 21L, 21L, 21L, 21L, 8 | 22L, 22L, 22L, 23L, 23L, 23L, 24L, 24L, 24L, 25L, 25L, 25L, 26L, 9 | 26L, 26L, 27L, 27L, 27L, 27L, 28L, 28L, 28L, 28L, 29L, 29L, 29L, 10 | 29L, 30L, 30L, 30L, 30L, 31L, 31L, 31L, 31L, 32L, 32L, 32L, 32L, 11 | 33L, 33L, 33L, 33L, 34L, 34L, 34L, 34L, 35L, 35L, 35L, 36L, 36L, 12 | 36L, 37L, 37L, 37L, 38L, 38L, 38L, 39L, 39L, 39L), depth = c(4L, 13 | 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 14 | 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 15 | 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 4L, 16 | 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 17 | 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 3L, 18 | 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 4L, 3L, 19 | 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 20 | 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L, 3L, 2L, 21 | 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L), label = c("pause", 22 | "foo", "f", "root", "pause", "foo", "f", "root", "pause", "foo", 23 | "f", "root", "pause", "foo", "f", "root", "pause", "bar", "f", 24 | "root", "pause", "bar", "f", "root", "pause", "bar", "f", "root", 25 | "pause", "bar", "f", "root", "pause", "bar", "f", "root", "pause", 26 | "foo", "root", "pause", "foo", "root", "pause", "foo", "root", 27 | "pause", "foo", "root", "pause", "foo", "f", "root", "pause", 28 | "foo", "f", "root", "pause", "foo", "f", "root", "pause", "foo", 29 | "f", "root", "pause", "bar", "f", "root", "pause", "bar", "f", 30 | "root", "pause", "bar", "f", "root", "pause", "bar", "f", "root", 31 | "pause", "foo", "root", "pause", "foo", "root", "pause", "foo", 32 | "root", "pause", "foo", "root", "pause", "foo", "root", "pause", 33 | "foo", "f", "root", "pause", "foo", "f", "root", "pause", "foo", 34 | "f", "root", "pause", "foo", "f", "root", "pause", "bar", "f", 35 | "root", "pause", "bar", "f", "root", "pause", "bar", "f", "root", 36 | "pause", "bar", "f", "root", "pause", "foo", "root", "pause", 37 | "foo", "root", "pause", "foo", "root", "pause", "foo", "root", 38 | "pause", "foo", "root"), filenum = c(NA, NA, NA, 2L, NA, NA, 39 | NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, 40 | NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, 41 | 2L, NA, NA, 2L, NA, NA, 2L, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, 42 | NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, 43 | NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, 2L, NA, NA, 2L, 44 | NA, NA, 2L, NA, NA, 2L, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 45 | 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, NA, 46 | 2L, NA, NA, NA, 2L, NA, NA, NA, 2L, NA, NA, 2L, NA, NA, 2L, NA, 47 | NA, 2L, NA, NA, 2L, NA, NA, 2L), linenum = c(NA, NA, NA, 222L, 48 | NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, 49 | NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, 50 | NA, NA, NA, 222L, NA, NA, 222L, NA, NA, 222L, NA, NA, 222L, NA, 51 | NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, 52 | NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, 53 | NA, 222L, NA, NA, NA, 222L, NA, NA, 222L, NA, NA, 222L, NA, NA, 54 | 222L, NA, NA, 222L, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 55 | 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, 56 | NA, NA, 222L, NA, NA, NA, 222L, NA, NA, NA, 222L, NA, NA, 222L, 57 | NA, NA, 222L, NA, NA, 222L, NA, NA, 222L, NA, NA, 222L), memalloc = c(20.5919799804688, 58 | 20.5919799804688, 20.5919799804688, 20.5919799804688, 20.5919799804688, 59 | 20.5919799804688, 20.5919799804688, 20.5919799804688, 20.5919799804688, 60 | 20.5919799804688, 20.5919799804688, 20.5919799804688, 20.5919799804688, 61 | 20.5919799804688, 20.5919799804688, 20.5919799804688, 20.6181106567383, 62 | 20.6181106567383, 20.6181106567383, 20.6181106567383, 20.6181106567383, 63 | 20.6181106567383, 20.6181106567383, 20.6181106567383, 20.6181106567383, 64 | 20.6181106567383, 20.6181106567383, 20.6181106567383, 20.6181106567383, 65 | 20.6181106567383, 20.6181106567383, 20.6181106567383, 20.6181106567383, 66 | 20.6181106567383, 20.6181106567383, 20.6181106567383, 20.6346893310547, 67 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6346893310547, 68 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6346893310547, 69 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6346893310547, 70 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6346893310547, 71 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6346893310547, 72 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6346893310547, 73 | 20.6346893310547, 20.6346893310547, 20.6346893310547, 20.6512680053711, 74 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 75 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 76 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 77 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 78 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 79 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 80 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 81 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 82 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 83 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 84 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 85 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 86 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 87 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 88 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 89 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 90 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 91 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 92 | 20.6512680053711, 20.6512680053711, 20.6512680053711, 20.6512680053711, 93 | 20.6512680053711), meminc = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94 | 0, 0, 0, 0, 0, 0.0261306762695312, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0165786743164062, 0, 0, 0, 0, 96 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97 | 0, 0, 0.0165786743164062, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101 | 0, 0), filename = c(NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", 102 | NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, 103 | NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", 104 | NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, 105 | "/R/profvis.R", NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", 106 | NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, NA, 107 | "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", 108 | NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, 109 | NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", 110 | NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", 111 | NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, NA, 112 | "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", 113 | NA, NA, NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, 114 | NA, "/R/profvis.R", NA, NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", 115 | NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", NA, NA, "/R/profvis.R", 116 | NA, NA, "/R/profvis.R")), row.names = c(NA, 142L), class = "data.frame") 117 | -------------------------------------------------------------------------------- /tests/testthat/test-parse.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("parsing prof files", { 3 | # The three lines in this file have three different kinds of call stacks. 4 | # They start with: 5 | # "test space" 1#1 6 | # 1#2 "test" 1#1 7 | # "" 1#2 "test" 1#1 8 | # "" "test" 1#1 9 | # ":" 10 | # 11 | # (The last line is empty) 12 | p <- parse_rprof(test_path("test-parse.prof"), expr_source = "line 1\nline 2") 13 | 14 | expect_identical(p$prof$time, c(1L, 2L, 2L, 3L, 3L, 3L, 4L, 4L, 5L)) 15 | expect_identical(p$prof$depth, c(1L, 2L, 1L, 3L, 2L, 1L, 2L, 1L, 1L)) 16 | expect_identical(p$prof$label, c("test space", "line 2", "test", "", "line 2", "test", "", "test", ":")) 17 | expect_identical(p$prof$filenum, c(1, 1, 1, NA, 1, 1, NA, 1, NA)) 18 | expect_identical(p$prof$linenum, c(1, 2, 1, NA, 2, 1, NA, 1, NA)) 19 | # Memory sizes in the test-parse.prof file were chosen to create these values 20 | expect_identical(p$prof$memalloc, c(136, 152, 152, 168, 168, 168, 152, 152, 152)) 21 | expect_identical(p$prof$meminc, c(0, 16, 0, 16, 0, 0, -16, 0, 0)) 22 | expect_identical(p$prof$filename, c("", "", "", NA, "", "", NA, "", NA)) 23 | 24 | expect_identical(p$interval, 10) 25 | expect_identical(p$files, 26 | list(list(filename = "", content = "line 1\nline 2", normpath = "")) 27 | ) 28 | }) 29 | 30 | test_that("can sort profiles alphabetically (#115)", { 31 | prof <- eval(parse_expr(file(test_path("test-parse-sort1.R")))) 32 | 33 | sorted <- prof_sort(prof) 34 | 35 | split <- vctrs::vec_split(sorted$label, sorted$time) 36 | runs <- vctrs::vec_unrep(split$val)$key 37 | 38 | expect_equal( 39 | runs, 40 | list( 41 | c("pause", "foo", "f", "root"), 42 | c("pause", "bar", "f", "root"), 43 | c("pause", "foo", "root") 44 | ) 45 | ) 46 | 47 | # Regenerate `prof` 48 | if (FALSE) { 49 | root <- function() { 50 | for (. in 1:3) { 51 | f(TRUE) 52 | f(FALSE) 53 | foo() 54 | } 55 | } 56 | f <- function(x) if (x) foo() else bar() 57 | foo <- function() pause(0.05) 58 | bar <- function() pause(0.05) 59 | 60 | prof <- profvis(root())$x$message$prof 61 | } 62 | }) 63 | -------------------------------------------------------------------------------- /tests/testthat/test-parse.prof: -------------------------------------------------------------------------------- 1 | memory profiling: GC profiling: line profiling: sample.interval=10000 2 | #File 1: 3 | :1048576:16777216:10000000:100:"test space" 1#1 4 | :2097152:17825792:10000000:100:1#2 "test" 1#1 5 | :3145728:18874368:10000000:100:"" 1#2 "test" 1#1 6 | :2097152:17825792:10000000:100:"" "test" 1#1 7 | :2097152:17825792:10000000:100:":" 8 | :2097152:17825792:10000000:100: 9 | -------------------------------------------------------------------------------- /tests/testthat/test-pause.R: -------------------------------------------------------------------------------- 1 | test_that("pause takes expected time", { 2 | time <- system.time(pause(0.2))[[3]] 3 | # system.time is a little inaccurate so allow 10% padding 4 | expect_lt(abs(time - 0.2) / 0.2, 0.1) 5 | }) 6 | 7 | test_that("works with integers", { 8 | expect_no_error(pause(0L)) 9 | }) 10 | 11 | test_that("pause has no srcrefs", { 12 | expect_equal(attr(pause, "srcref"), NULL) 13 | }) 14 | 15 | test_that("checks its inputs", { 16 | expect_snapshot(error = TRUE, { 17 | pause(c(1, 2)) 18 | pause("a") 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/testthat/test-profvis.R: -------------------------------------------------------------------------------- 1 | test_that("irrelevant stack trimmed from function calls (#123)", { 2 | skip_on_cran() 3 | skip_on_covr() 4 | 5 | f <- function() pause(TEST_PAUSE_TIME) 6 | g <- function() f() 7 | 8 | out <- profvis(g(), simplify = TRUE, rerun = "pause") 9 | expect_equal(profile_mode(out), "pause f g") 10 | 11 | out <- profvis(g(), simplify = FALSE, rerun = "pause") 12 | expect_equal(profile_mode(out), "pause f g") 13 | }) 14 | 15 | test_that("irrelevant stack trimmed from inlined code (#130)", { 16 | skip_on_cran() 17 | skip_on_covr() 18 | 19 | out <- profvis(for (i in 1:1e4) rnorm(100), simplify = TRUE, rerun = "rnorm") 20 | expect_equal(profile_mode(out), "rnorm") 21 | 22 | out <- profvis(for (i in 1:1e4) rnorm(100), simplify = FALSE, rerun = "rnorm") 23 | expect_equal(profile_mode(out), "rnorm") 24 | }) 25 | 26 | test_that("strips stack above profvis", { 27 | skip_on_cran() 28 | skip_on_covr() 29 | 30 | f <- function() pause(TEST_PAUSE_TIME) 31 | profvis_wrap <- function(...) profvis(...) 32 | 33 | out <- profvis_wrap(f(), simplify = TRUE, rerun = "pause") 34 | expect_equal(profile_mode(out), "pause f") 35 | 36 | out <- profvis_wrap(f(), simplify = FALSE, rerun = "pause") 37 | expect_equal(profile_mode(out), "pause f") 38 | }) 39 | 40 | test_that("defaults to elapsed timing", { 41 | skip_on_cran() 42 | skip_on_covr() 43 | skip_if_not(has_event()) 44 | 45 | f <- function() Sys.sleep(TEST_PAUSE_TIME) 46 | 47 | out <- profvis(f(), rerun = "Sys.sleep") 48 | expect_equal(profile_mode(out), "Sys.sleep f") 49 | }) 50 | 51 | test_that("expr and prof_input are mutually exclusive", { 52 | expect_snapshot(profvis(expr = f(), prof_input = "foo.R"), error = TRUE) 53 | }) 54 | 55 | test_that("can capture profile of code with error", { 56 | skip_on_cran() 57 | skip_on_covr() 58 | 59 | f <- function() { 60 | pause(TEST_PAUSE_TIME) 61 | stop("error") 62 | } 63 | expect_snapshot(out <- profvis(f(), rerun = "pause")) 64 | expect_equal(profile_mode(out), "pause f") 65 | }) 66 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("split_in_half handles common cases", { 2 | out <- split_in_half(c("a-b", "a---b", "ab", "", NA), "-+", perl = TRUE) 3 | expect_equal(out, cbind( 4 | c("a", "a", "ab", "", NA), 5 | c("b", "b", "", "", NA) 6 | )) 7 | }) 8 | -------------------------------------------------------------------------------- /tools/update_jquery.R: -------------------------------------------------------------------------------- 1 | # Script to update the version of jQuery bundled 2 | # in projvis, at inst/htmlwidgets/lib/jquery. 3 | # Based on simplified version of script at 4 | # https://github.com/rstudio/shiny/blob/main/tools/updatejQuery.R 5 | 6 | version <- "3.7.1" 7 | extension <- ".min.js" 8 | 9 | jq_cdn_download <- function(version, extension) { 10 | download.file( 11 | url = file.path( 12 | "https://code.jquery.com", 13 | paste0("jquery-", version, extension) 14 | ), 15 | destfile = file.path( 16 | "inst", "htmlwidgets", "lib", "jquery", 17 | paste0("jquery", extension) 18 | ) 19 | ) 20 | } 21 | 22 | jq_cdn_download(version, extension) 23 | 24 | download.file( 25 | url = sprintf("https://raw.githubusercontent.com/jquery/jquery/%s/AUTHORS.txt", version), 26 | destfile = file.path("inst", "htmlwidgets", "lib", "jquery", "AUTHORS.txt") 27 | ) 28 | 29 | writeLines(con = "R/version_jquery.R", c( 30 | "# Generated by R/updatejQuery.R; do not edit by hand", 31 | sprintf('version_jquery <- "%s"', version) 32 | )) 33 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | 4 | /.quarto/ 5 | -------------------------------------------------------------------------------- /vignettes/articles/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | -------------------------------------------------------------------------------- /vignettes/articles/cmpfun-profile1.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/cmpfun-profile1.rds -------------------------------------------------------------------------------- /vignettes/articles/cmpfun-profile2.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/cmpfun-profile2.rds -------------------------------------------------------------------------------- /vignettes/articles/examples.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Examples" 3 | --- 4 | 5 | ```{r echo = FALSE} 6 | library(profvis) 7 | library(knitr) 8 | 9 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 10 | 11 | # Make output a little less tall by default 12 | registerS3method("knit_print", "htmlwidget", function(x, ...) { 13 | # Get the chunk height 14 | height <- knitr::opts_current$get("height") 15 | if (length(height) > 0 && height != FALSE) 16 | x$height <- height 17 | else 18 | x$height <- "450px" 19 | 20 | htmlwidgets:::knit_print.htmlwidget(x, ...) 21 | }) 22 | ``` 23 | 24 | 25 | Below are some examples of profvis in use. Keep in mind that R's sampling profiler is non-deterministic, and that the code in these examples is run and profiled when this knitr document is executed, so the numeric timing values may not exactly match the text. 26 | 27 | 28 | ### Example 1 29 | 30 | In this first example, we'll work with a data frame that has 151 columns. One of the columns contains an ID, and the other 150 columns contain numeric values. What we will do is, for each numeric column, take the mean and subtract it from the column, so that the new mean value of the column is zero. 31 | 32 | ```{r ex1} 33 | # Generate data 34 | times <- 4e5 35 | cols <- 150 36 | data <- as.data.frame(x = matrix(rnorm(times * cols, mean = 5), ncol = cols)) 37 | data <- cbind(id = paste0("g", seq_len(times)), data) 38 | 39 | profvis({ 40 | data1 <- data # Store in another variable for this run 41 | 42 | # Get column means 43 | means <- apply(data1[, names(data1) != "id"], 2, mean) 44 | 45 | # Subtract mean from each column 46 | for (i in seq_along(means)) { 47 | data1[, names(data1) != "id"][, i] <- data1[, names(data1) != "id"][, i] - means[i] 48 | } 49 | }) 50 | ``` 51 | 52 | Most of the time is spent in the `apply` call, so that's the best candidate for a first pass at optimization. We can also see that the `apply` results in a lot of memory being allocated and deallocated. Memory "churning" is often a cause for performance problems. 53 | 54 | Looking at the flame graph, we can see that `apply` calls `as.matrix` and `aperm`. These two functions convert the data frame to a matrix and transpose it -- so even before we've done any useful computations, we've spent a large amount of time transforming the data. 55 | 56 | We could try to speed this up in a number of ways. One possibility is that we could simply leave the data in matrix form (instead of putting it in a data frame in line 4). That would remove the need for the `as.matrix` call, but it would still require `aperm` to transpose the data. It would also lose the connection of each row to the `id` column, which is undesirable. In any case, using `apply` over columns looks like it will be expensive because of the call to `aperm`. 57 | 58 | An obvious alternative is to use the `colMeans` function. But there's also another possibility. Data frames are implemented as lists of vectors, where each column is one vector, so we could use `lapply` or `vapply` to apply the `mean` function over each column. Let's compare the speed of these four different ways of getting column means. 59 | 60 | ```{r ex1_apply} 61 | profvis({ 62 | data1 <- data 63 | # Four different ways of getting column means 64 | means <- apply(data1[, names(data1) != "id"], 2, mean) 65 | means <- colMeans(data1[, names(data1) != "id"]) 66 | means <- lapply(data1[, names(data1) != "id"], mean) 67 | means <- vapply(data1[, names(data1) != "id"], mean, numeric(1)) 68 | }) 69 | ``` 70 | 71 | `colMeans` is about 3x faster than using `apply` with `mean`, but it looks like it's still using `as.matrix`, which takes a significant amount of time. `lapply`/`vapply`are faster yet -- about 10x faster than `apply`. `lapply` returns the values in a list, while `vapply` returns the values in a numeric vector, which is the form that we want, so it looks like `vapply` is the way to go for this part. 72 | 73 | (If you want finer-grained comparisons of code performance, you can use the [microbenchmark](https://cran.r-project.org/web/packages/microbenchmark/index.html) package. There's more information about microbenchmark in the [profiling](http://adv-r.had.co.nz/Profiling.html#code-organisation) chapter of Hadley Wickham's book, *Advanced R*.) 74 | 75 | You can also see that the faster methods also result in less memory allocation and deallocation. This is not a coincidence -- allocating and deallocating memory can be expensive. 76 | 77 | Let's take the original code and replace `apply` with `vapply`: 78 | 79 | ```{r ex1_for} 80 | profvis({ 81 | data1 <- data 82 | means <- vapply(data1[, names(data1) != "id"], mean, numeric(1)) 83 | 84 | for (i in seq_along(means)) { 85 | data1[, names(data1) != "id"][, i] <- data1[, names(data1) != "id"][, i] - means[i] 86 | } 87 | }) 88 | ``` 89 | 90 | Our code is about 3x faster than the original version. Most of the time is now spent on line 6, and the majority of that is in the `[<-.data.frame` function. This is usually called with syntax `x[i, j] <- y`, which is equivalent to `` `[<-`(x, i, j, y)``. In addition to being slow, the code is ugly: on each side of the assignment operator we're indexing into `data1` twice with `[`. 91 | 92 | In this case, it's useful to take a step back and think about the broader problem. We want to normalize each column. Couldn't we we apply a function over the columns that does both steps, taking the mean and subtracting it? Because a data frame is a list, and we want to assign a list of values into the data frame, we'll need to use `lapply`. 93 | 94 | ```{r ex1_norm} 95 | profvis({ 96 | data1 <- data 97 | 98 | # Given a column, normalize values and return them 99 | col_norm <- function(col) { 100 | col - mean(col) 101 | } 102 | 103 | # Apply the normalizer function over all columns except id 104 | data1[, names(data1) != "id"] <- lapply(data1[, names(data1) != "id"], col_norm) 105 | }) 106 | ``` 107 | 108 | Now we have code that's not only about 6x faster than our original -- it's shorter and more elegant as well. Not bad! The profiler data helped us to identify performance bottlenecks, and understanding of the underlying data structures allowed us to approach the problem in a more efficient way. 109 | 110 | Could we further optimize the code? It seems unlikely, given that all the time is spent in functions that are implemented in C (`mean` and `-`). That doesn't necessarily mean that there's no room for improvement, but this is a good place to move on to the next example. 111 | 112 | 113 | ### Example 2 114 | 115 | This example addresses some more advanced issues. This time, it will be hard to directly see the causes of slowness, but we will be able to see some of their side-effects, most notably the side-effects from large amounts of memory allocation. 116 | 117 | Suppose you have a data frame that contains a column for which you'd like to take a cumulative sum (and you don't know about R's built-in `cumsum` function). Here's one way to do it: 118 | 119 | ```{r ex2} 120 | profvis({ 121 | data <- data.frame(value = runif(5e4)) 122 | 123 | data$sum[1] <- data$value[1] 124 | for (i in seq(2, nrow(data))) { 125 | data$sum[i] <- data$sum[i-1] + data$value[i] 126 | } 127 | }) 128 | ``` 129 | 130 | This takes about 2.5 seconds to calculate the cumulative sum of 50,000 items. That's pretty slow for a computer program. Looking at the profvis visualization, we can see a number of notable features: 131 | 132 | Almost all the time is spent in one line of code, line 6. Although this is just one line of code, many different functions that are called on that line. 133 | 134 | That line also results in a large amount of memory being allocated and deallocated, which is somewhat suprising: just looking at the code, that line *appears* to just modify the data in-place, but that's not actually what's happening internally. 135 | 136 | In the flame graph, you'll see that some of the flame graph blocks have the label `$`, which means that those samples were spent in the `$` function for indexing into an object (in R, the expression `x$y` is equivalent to `` `$`(x, "y")``). 137 | 138 | Because `$` is a *generic* function, it calls the corresponding *method* for the object, in this case `$.data.frame`. This function in turn calls `[[`, which calls `[[.data.frame`. (Zoom in to see this more clearly.) 139 | 140 | Other flame graph cells have the label `$<-`. The usual syntax for calling this function is `x$y <- z`; this is equivalent to `` `$<-`(x, "y", z)``. (Assignment with indexing, as in `x$y[i] <- z` is actually a bit [more complicated](http://adv-r.had.co.nz/Functions.html#replacement-functions), and it turns out that this is the cause of the excessive memory allocation and deallocation.) 141 | 142 | Finally, many of the flame graph cells contain the entire expression from line 6. This can mean one of two things: 143 | 144 | 1. R is currently evaluating the expression but is not inside another function call. 145 | 1. R is in another function, but that function does not show up on the stack. (A number of R's internal functions do not show up in the profiling data. See [more](#why-do-some-function-calls-not-show-in-the-profiler) about this in the FAQ.) 146 | 147 | This profiling data tells us that much of the time is spent in `$` and `$<-`. Maybe avoiding these functions entirely will speed things up. To do that, instead of operating on data frame columns, we can operate on temporary vectors. As it turns out, writing a function that takes a vector as input and returns a vector as output is not only convenient; it provides a natural way of creating temporary variables so that we can avoid calling `$` and `$<-` in a loop. 148 | 149 | ```{r ex2_csum} 150 | x <- runif(5e5) 151 | 152 | profvis({ 153 | csum <- function(x) { 154 | if (length(x) < 2) return(x) 155 | 156 | sum <- x[1] 157 | for (i in seq(2, length(x))) { 158 | sum[i] <- sum[i-1] + x[i] 159 | } 160 | sum 161 | } 162 | x$sum <- csum(x) 163 | }) 164 | ``` 165 | 166 | Using this `csum` function, it takes about 100 ms for 10x as much data, which is about 250x as fast as before. 167 | 168 | It may appear that no functions are called from line 7, but that's not quite true: that line also calls `[`, `[<-`, `-`, and `+`. 169 | 170 | * The `[` and `[<-` functions don't appear in the flame graph. They are internal R functions which contain C code to handle indexing into atomic vectors, and are not dispatched to methods. (Contrast this with the first version of the code, where `$` was dispatched to `$.data.frame`). 171 | * The `-` and `+` functions can show up in a flame graph, but they are very fast so the sampling profiler may or may not happen to take a sample when they're on the call stack. 172 | 173 | The code panel shows that there is still a large amount of memory being allocated in the `csum` function. In the flame graph. you probably have noticed the gray blocks labeled ``. These represent times where R is doing *garbage collection* -- that is, when it is freeing (deallocating) chunks of memory that were allocated but no longer needed. If R is spending a lot of time freeing memory, that suggests that R is also spending a lot of time allocating memory. This is another common source of slowness in R code. 174 | 175 | ### Example 3 - Profiling a Shiny Application 176 | 177 | In addition to R code, you can also profile [Shiny](http://shiny.rstudio.com) applications. To do this, simply execute the `runApp()` command inside of `profvis`. For instance, we can run one of shiny's built-in examples using the `runExample` command (which is a wrapper for `runApp`). 178 | 179 | ```{r ex3,eval=FALSE, echo=FALSE} 180 | #This code block is what we run, it must be manually run each time 181 | library(shiny) 182 | p<-profvis({ 183 | runExample(example = "06_tabsets", display.mode = "normal") 184 | }) 185 | saveRDS(p, "shinyapp.rds") 186 | 187 | ``` 188 | 189 | 190 | ```{r ex3_tabsets,eval=FALSE} 191 | library(shiny) 192 | profvis({ 193 | runExample(example = "06_tabsets", display.mode = "normal") 194 | }) 195 | ``` 196 | 197 | 198 | Your Shiny application will launch, and after interacting and closing the app a profile will be generated. 199 | 200 | ![](profvis-shiny-demo.gif) 201 | 202 | ```{r ex3_readRDS,echo=FALSE} 203 | # This block loads the data from the first block and shows the correct output 204 | # for the readers. 205 | readRDS("shinyapp.rds") 206 | 207 | ``` 208 | 209 | The profile for Shiny applications automatically hides functions that Shiny runs behind the scenes. More information is available in the [FAQ](http://rstudio.github.io/profvis/faq.html#why-does-the-flame-graph-hide-some-function-calls-for-shiny-apps). The profile automatically colors outputs in blue. In this example, you can identify each time `output$plot` was called to re-create the plot. 210 | 211 | Profiling a Shiny application is particularly helpful for understanding reactive dependencies. For more information, see this [video](https://rstudio.com/resources/webinars/how-to-profile-and-optimize-code-using-profvis-a-new-package-for-exploring-profiling-data/). 212 | -------------------------------------------------------------------------------- /vignettes/articles/faq.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Frequently asked questions" 3 | --- 4 | 5 | ```{r echo = FALSE} 6 | library(profvis) 7 | library(knitr) 8 | 9 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 10 | 11 | # Make output a little less tall by default 12 | registerS3method("knit_print", "htmlwidget", function(x, ...) { 13 | # Get the chunk height 14 | height <- knitr::opts_current$get("height") 15 | if (length(height) > 0 && height != FALSE) 16 | x$height <- height 17 | else 18 | x$height <- "450px" 19 | 20 | htmlwidgets:::knit_print.htmlwidget(x, ...) 21 | }) 22 | ``` 23 | 24 | 25 | ## Why do some function calls not show in the profiler? 26 | 27 | As noted earlier, some of R's built-in functions don't show in the profvis flame graph. These include functions like `<-`, `[`, and `$`. Although these functions can occupy a lot of time, they don't show on the call stack. (In one of the examples above, `$` does show on the call stack, but this is because it was dispatched to `$.data.frame`, as opposed to R's internal C code, which is used for indexing into lists.) 28 | 29 | In some cases the side-effects of these functions can be seen in the flamegraph. As we saw in the example above, using these functions in a loop led to many memory allocations, which led to garbage collections, or `` blocks in the flame graph. 30 | 31 | 32 | ## How do I share a profvis visualization? 33 | 34 | Right now the easiest way to do this is to run `profvis` in RStudio, and publish to [RPubs](http://rpubs.com/). 35 | 36 | Once the profile shows up in the RStudio IDE, click on the **Publish** button to send it to RPubs. You can see an example [here](http://rpubs.com/wch/178493). If you don't already have an account on RPubs, you'll need to set one up. 37 | 38 | You can also click on the save (disk) icon. This will save the profvis visualization to an .Rprofvis file. This file can be opened by RStudio, or if you rename it to have an .html extension, it can be opened in a web browser. 39 | 40 | ![Publishing to RPubs](profvis-rpubs.png) 41 | 42 | Another way to publish a profvis visualization is to save the HTML output file using `htmlwidgets::saveWidget`, and put that on any web hosting service: 43 | 44 | ```{r eval=FALSE} 45 | p <- profvis({ 46 | # Interesting code here 47 | }) 48 | 49 | htmlwidgets::saveWidget(p, "profile.html") 50 | ``` 51 | 52 | It's also possible to put a profvis visualization in a knitr document. At this time, some CSS workarounds are needed needed for them to display properly. You can look at the source of [this website](https://github.com/r-lib/profvis/tree/gh-pages/) to see the workarounds. 53 | 54 | 55 | ## What does `` mean? 56 | 57 | It's not uncommon for R code to contain *anonymous* functions -- that is, functions that aren't named. These show up as `` in the profiling data collected from `Rprof`. 58 | 59 | In the code below there is a function, `make_adder`, that returns a function. We'll invoke the returned function in two ways. 60 | 61 | First, we'll run `make_adder(1)(10)`. The call `make_adder(1)` returns a function, which is invoked immediately (without being saved in a variable), and shows up as `` in the flame graph. 62 | 63 | Next, we'll call `make_adder(2)` but this time, we'll save the result in a variable, `adder2`. Then we'll call `adder2(10)`. When we do it this way, the profiler records that the function label is `adder2`. 64 | 65 | ```{r, height="400px"} 66 | profvis({ 67 | make_adder <- function(n) { 68 | function(x) { 69 | pause(0.25) # Wait for a moment so that this shows in the profiler 70 | x + n 71 | } 72 | } 73 | 74 | # Called with no intermediate variable, it shows as "" 75 | make_adder(1)(10) 76 | 77 | # With the function saved in a variable, it shows as "adder2" 78 | adder2 <- make_adder(2) 79 | adder2(10) 80 | }) 81 | ``` 82 | 83 | Those are equivalent to `` `::`(package, function)`` and `` `$`(x, "fun")``, respectively. These calls return anonymous functions, and so R's internal profiling code labels these as ``. If you want labels in the profiler to have a different label, you can assign the value to a temporary variable (like `adder2` above), and then invoke that. 84 | 85 | Finally, if a function is passed to `lapply`, it will be show up as `FUN` in the flame graph. If we inspect the source code for `lapply`, it's clear why: when a function is passed to `lapply`, the name used for the function inside of `lapply` is `FUN`. 86 | 87 | ```{r} 88 | lapply 89 | ``` 90 | 91 | 92 | ## What does `cmpfun` mean? 93 | 94 | The first time we run `profvis` on a function in a clean 3.4.0 or greater R session, we'll see `compiler:::tryCmpfun`. For example, 95 | 96 | ```{r eval=FALSE, echo=FALSE} 97 | # This code block is what we actually run. It must be run manually each time 98 | # this is updated. 99 | # The next block is what we show to the readers. 100 | p <- profvis::profvis({ 101 | data <- data.frame(value = runif(5e4)) 102 | 103 | csum <- function(x) { 104 | if (length(x) < 2) return(x) 105 | 106 | sum <- x[1] 107 | for (i in seq(2, length(x))) { 108 | sum[i] <- sum[i-1] + x[i] 109 | } 110 | sum 111 | } 112 | data$sum <- csum(data$value) 113 | }) 114 | p$x$message$split <- "v" 115 | 116 | saveRDS(p, "cmpfun-profile1.rds") 117 | 118 | p <- profvis::profvis({ 119 | data <- data.frame(value = runif(5e4)) 120 | 121 | csum <- function(x) { 122 | if (length(x) < 2) return(x) 123 | 124 | sum <- x[1] 125 | for (i in seq(2, length(x))) { 126 | sum[i] <- sum[i-1] + x[i] 127 | } 128 | sum 129 | } 130 | data$sum <- csum(data$value) 131 | }) 132 | 133 | p$x$message$split <- "v" 134 | 135 | saveRDS(p, "cmpfun-profile2.rds") 136 | ``` 137 | ```{r eval = FALSE} 138 | profvis({ 139 | data <- data.frame(value = runif(5e4)) 140 | 141 | csum <- function(x) { 142 | if (length(x) < 2) return(x) 143 | 144 | sum <- x[1] 145 | for (i in seq(2, length(x))) { 146 | sum[i] <- sum[i-1] + x[i] 147 | } 148 | sum 149 | } 150 | data$sum <- csum(data$value) 151 | }) 152 | ``` 153 | ```{r echo=FALSE} 154 | readRDS("cmpfun-profile1.rds") 155 | ``` 156 | 157 | R attempts to compile functions when they are first ran to byte code. On subsequent function calls, instead of reinterpreting the body of the function, R executes the saved and compiled byte code. Typically, this results in faster execution times on later function calls. For example, let's profile `csum` a second time in the same R session: 158 | 159 | ```{r echo=FALSE} 160 | readRDS("cmpfun-profile2.rds") 161 | ``` 162 | 163 | Now the flame graph shows that the function is no longer being compiled. And after compiling, `csum` is about 40 ms faster. 164 | 165 | ## How do I get code from an R package to show in the code panel? 166 | 167 | In typical use, only code written by the user is shown in the code panel. (This is code for which *source references* are available.) Yellow blocks in the flame graph have corresponding lines of code in the code panel, and when moused over, the line of code will be highlighted. White blocks in the flame graph don't have corresponding lines in the code panel. In most cases, the calls represented by the white blocks are to functions that are in base R and other packages. 168 | 169 | profvis can also show code that's inside an R package. To do this, source refs for the package code must be available. There are two general ways to do this: you can install the package with source refs, or you can use `devtools::load_all()` to load a package from sources on disk. 170 | 171 | 172 | ### Installing with source refs 173 | 174 | There are many ways to install a package with source refs. Here are some examples of installing ggplot2: 175 | 176 | * From CRAN: 177 | ```{r eval=FALSE} 178 | ## First, restart R ## 179 | install.packages("ggplot2", type="source", INSTALL_opts="--with-keep.source") 180 | ``` 181 | 182 | * From an RStudio package project on local disk: Go to **Build** -> **Configure Build Tools** -> **Build Tools** -> **Build and Reload -- R CMD INSTALL additional options**, and add `--with-keep.source`. Then run **Build** -> **Build and Reload**. 183 | 184 | * From sources on disk with devtools: 185 | ```{r eval=FALSE} 186 | ## First, restart R ## 187 | # Assuming sources are in a subdirectory ggplot2/ 188 | devtools::install("ggplot2", keep_source = TRUE) 189 | ``` 190 | 191 | * From sources on disk using the command line: 192 | ```{sh eval=FALSE} 193 | R CMD INSTALL --with-keep.source ggplot2/ 194 | ``` 195 | 196 | * From sources on Github: 197 | ```{r eval=FALSE} 198 | ## First, restart R ## 199 | remotes::install_github("hadley/ggplot2", INSTALL_opts="--with-keep.source") 200 | ``` 201 | 202 | 203 | ### Loading packages with source refs (without installing) 204 | 205 | * Instead of installing an in-development package, you can simply load it from source using devtools. 206 | ```{r eval=FALSE} 207 | # Assuming sources are in a subdirectory ggplot2/ 208 | devtools::load_all("ggplot2") 209 | ``` 210 | 211 | Once a package is loaded or installed with source refs, profvis visualizations will display source code for that package. For example, the visualization below has yellow blocks for both user code and for code in ggplot2, and it contains ggplot2 code in the code panel: 212 | 213 | ```{r echo=FALSE, eval=FALSE} 214 | # This code block is what we actually run. It must be run manually each time 215 | # this is updated. 216 | # The next block is what we show to the readers. 217 | # This also generates the livedemo.html file. 218 | 219 | install.packages("ggplot2", type="source", INSTALL_opts="--with-keep.source") 220 | library(ggplot2) 221 | 222 | p <- profvis({ 223 | g <- ggplot(diamonds, aes(carat, price)) + geom_point(size = 1, alpha = 0.2) 224 | print(g) 225 | }) 226 | 227 | saveRDS(p, "ggplot2.rds") 228 | p$x$message$split <- "v" 229 | htmlwidgets::saveWidget(p, "livedemo.html") 230 | ``` 231 | 232 | ```{r eval=FALSE} 233 | library(ggplot2) 234 | profvis({ 235 | g <- ggplot(diamonds, aes(carat, price)) + geom_point(size = 1, alpha = 0.2) 236 | print(g) 237 | }) 238 | ``` 239 | 240 | ```{r echo=FALSE, height="800px"} 241 | # This block loads the data from the first block and shows the correct output 242 | # for the readers. 243 | readRDS("ggplot2.rds") 244 | ``` 245 | 246 | 247 | ## Can I profile code without calling `profvis()`? 248 | 249 | Yes. There are two ways to do it. 250 | 251 | If you are in RStudio, you can select Profile->Start Profiling, run your code, and then Profile->Stop Profiling. When you stop the profiling, the profvis viewer will come up. 252 | 253 | Another way is to start and stop the R profiler manually, then have profvis read in the recorded profiling data. To profile your code, run: 254 | 255 | ```{r eval=FALSE} 256 | # Start profiler 257 | Rprof("data.Rprof", interval = 0.01, line.profiling = TRUE, 258 | gc.profiling = TRUE, memory.profiling = TRUE) 259 | 260 | ## Run your code here 261 | 262 | # Stop profiler 263 | Rprof(NULL) 264 | ``` 265 | 266 | Then you can load the data into profvis: 267 | 268 | ```{r eval=FALSE} 269 | profvis(prof_input = "data.Rprof") 270 | ``` 271 | 272 | This technique can also be used to profile just one section of your code. 273 | 274 | 275 | ## Why does the flame graph hide some function calls for Shiny apps? 276 | 277 | When profiling [Shiny](http://shiny.rstudio.com/) applications, the profvis flame graph will hide many function calls by default. They're hidden because they aren't particularly informative for optimizing code, and they add visual complexity. This feature requires Shiny 0.13.0 or greater. 278 | 279 | If you want to see these hidden blocks, uncheck **Options** -> **Hide internal function calls**: 280 | 281 | ```{r echo=FALSE, eval=FALSE} 282 | # This code block is what we actually run. It must be run manually each time 283 | # this is updated. 284 | # The next block is what we show to the readers. 285 | library(shiny) 286 | p <- profvis({ 287 | runExample("06_tabsets", display.mode = "normal") 288 | }) 289 | p$x$message$split <- "v" 290 | 291 | saveRDS(p, "shinyapp.rds") 292 | ``` 293 | 294 | ```{r eval=FALSE} 295 | library(shiny) 296 | profvis({ 297 | # After this app has started, interact with it a bit, then quit 298 | runExample("06_tabsets", display.mode = "normal") 299 | }) 300 | ``` 301 | 302 | ```{r echo=FALSE, height="700px"} 303 | # This block loads the data from the first block and shows the correct output 304 | # for the readers. 305 | readRDS("shinyapp.rds") 306 | ``` 307 | 308 | To make the hiding work, Shiny has special functions called `..stacktraceon..` and `..stacktraceoff..`. profvis goes up the stack, and when it sees a `..stacktraceoff..`, it will hide all function calls until it sees a corresponding `..stacktraceon..`. If there are nukltiple `..stacktraceoff..` calls in the stack, it requires an equal number of `..stacktraceon..` calls before it starts displaying function calls again. 309 | 310 | 311 | ## Can I profile just part of a Shiny application? 312 | 313 | Sometimes it it useful to profile just part of a Shiny application, instead of the whole thing from start to finish. 314 | 315 | If you are in RStudio, you can start your application, then select Profile->Start Profiling, interact with your application, and then select Profile->Stop Profiling. When you stop the profiling, the profvis viewer will come up. 316 | 317 | Profivs also provides a [Shiny Module](http://shiny.rstudio.com/articles/modules.html) to initiate the profiling, and provides a UI to start, stop, view, and download profvis sessions. This is done with `profvis::profvis_server` and `profvis::profvis_ui`. 318 | 319 | For example, here's a small app that uses the module: 320 | 321 | ```{r eval=FALSE} 322 | library(shiny) 323 | library(ggplot2) 324 | library(profvis) 325 | shinyApp( 326 | fluidPage( 327 | plotOutput("plot"), 328 | actionButton("new", "New plot"), 329 | profvis_ui("profiler") 330 | ), 331 | function(input, output, session) { 332 | callModule(profvis_server, "profiler") 333 | 334 | output$plot <- renderPlot({ 335 | input$new 336 | ggplot(diamonds, aes(carat, price)) + geom_point() 337 | }) 338 | } 339 | ) 340 | ``` 341 | 342 | In the server function, `callModule(profvis_server, "profiler")` sets up the profvis session, and in the UI `profvis_ui("profiler")` sets up a basic interface to start, stop, view, and download profvis sessions. 343 | 344 | You can create your own `profvis_server` and `profvis_ui` functions by calling `Rprof()` to start and stop profiling (as described in [this answer](#can-i-profile-code-without-using-profvis)), and trigger it with an `actionButton`. For example, you could put this in your UI: 345 | 346 | ```{r eval=FALSE} 347 | radioButtons("profile", "Profiling", c("off", "on")) 348 | ``` 349 | 350 | And put this in your server function: 351 | 352 | ```{r eval=FALSE} 353 | observe({ 354 | if (identical(input$profile, "off")) { 355 | Rprof(NULL) 356 | } else if (identical(input$profile, "on")){ 357 | Rprof(strftime(Sys.time(), "%Y-%m-%d-%H-%M-%S.Rprof"), 358 | interval = 0.01, line.profiling = TRUE, 359 | gc.profiling = TRUE, memory.profiling = TRUE) 360 | } 361 | }) 362 | ``` 363 | 364 | 365 | It will add radio buttons to turn profiling on and off. Turn it on, then interact with your app, then turn it off. There will be a file with a name corresponding to the start time. You can view the profiler output with profvis, with something like this: 366 | 367 | ```{r eval=FALSE} 368 | profvis(prof_input = "2018-08-07-12-22-35.Rprof") 369 | ``` 370 | 371 | 372 | ## Can I profile a Shiny application running on a remote server? 373 | 374 | Yes. One option is to include the profvis Shiny Module desribed in the [previous question](#-can-i-profile-just-part-of-a-shiny-application?). 375 | 376 | You can also set it up manually. The main idea is to start and stop profiling (as described in [this answer](#can-i-profile-code-without-using-profvis)). At the top of your app.R or server.R, you can add the following: 377 | 378 | ```{r eval=FALSE} 379 | Rprof(strftime(Sys.time(), "%Y-%m-%d-%H-%M-%S.Rprof"), 380 | interval = 0.01, line.profiling = TRUE, 381 | gc.profiling = TRUE, memory.profiling = TRUE) 382 | 383 | onStop(function() { 384 | Rprof(NULL) 385 | }) 386 | ``` 387 | 388 | This will start profiling when the app starts, and stop when it exits. 389 | 390 | ## Why does `Sys.sleep()` not show up in profiler data? 391 | 392 | The R profiler doesn't provide any data when R makes a system call. If, for example, you call `Sys.sleep(5)`, the R process will pause for 5 seconds, but you probably won't see any instances of `Sys.sleep` in the profvis visualization -- it won't even take any horizontal space. For these examples, we've used the `pause` function instead, which is part of the profvis package. It's similar to `Sys.sleep`, except that it does show up in the profiling data. For example: 393 | 394 | ```{r, height="350px"} 395 | profvis({ 396 | # Does not show in the flame graph 397 | Sys.sleep(0.25) 398 | 399 | # Does show in the flame graph 400 | pause(0.25) 401 | }) 402 | ``` 403 | 404 | Calls to external programs and libraries also may not show up in the profiling data. If you call functions from a package to fetch data from external sources, keep in mind that time spent in those functions may not show in the profiler. 405 | 406 | 407 | ## Why is the call stack reversed for some of my code? 408 | 409 | One of the unusual features of R as a programming language is that it has *lazy evaluation* of function arguments. If you pass an expression to a function, that expression won't be evaluated until it's actually used somewhere in that function. 410 | 411 | The result of this is that sometimes the stack can look like it's in the wrong order. In this example below, we call `times_10` and `times_10_lazy`. They both call `times_5()` and `times_2()`, but the "regular" version uses an intermediate variable `y`, while the lazy version nests the calls, with `times_2(times_5(x))`. 412 | 413 | ```{r} 414 | profvis({ 415 | times_5 <- function(x) { 416 | pause(0.5) 417 | x * 5 418 | } 419 | 420 | times_2 <- function(x) { 421 | pause(0.2) 422 | x * 2 423 | } 424 | 425 | times_10 <- function(x) { 426 | y <- times_5(x) 427 | times_2(y) 428 | } 429 | 430 | times_10_lazy <- function(x) { 431 | times_2(times_5(x)) 432 | } 433 | 434 | times_10(10) 435 | times_10_lazy(10) 436 | }) 437 | ``` 438 | 439 | In most programming languages, the flame graph would look the same for both: the `times_10` (or `times_10_lazy`) block would be on the bottom, with `times_5` and `times_2` side-by-side on the next level up on the stack. 440 | 441 | With lazy evaluation, when the `times_10_lazy` function calls `times_2(times_5(x))`, the `times_2` function receives a *promise* with the unevaluated expression `times_5(x)`, and evaluates it only when it reaches line 9, `x * 2` (the expression gets evaluated in the correct context, so there's no naming collision of the `x` variable). 442 | 443 | It's not only the call stack that has a surprising order with `times_10_lazy` -- the temporal order the simulated work we're doing in the function (represented by the `pause` blocks) is different. The `times_2` and `times_5` functions pause for 0.2 and 0.5 seconds, respectively. Those pauses occur in opposite order in `times_10` and `times_10_lazy`. 444 | 445 | Keep in mind that lazy evaluation may result in counterintuitive results in the flame graph. If you want to avoid some of the possible confusion from lazy evaluation, you can use intermediate variables to force the evaluation of arguments at specific locations in your code, as we did in `times_10`. 446 | 447 | 448 | ## Why does profvis tell me the the wrong line is taking time? 449 | 450 | In some cases, multi-line expressions will report that the first line of the expression is the one that takes all the time. In the example below, there are two `for` loops: one with curly braces, and one without. In the loop with curly braces, it reports that line 3, containing the `pause` is the one that takes all the time. In the loop without curly braces, it reports that line 6, containing `for`, is the one that takes all the time, even though the time is really spent on line 7, with the `pause`. 451 | 452 | ```{r, height="350px"} 453 | profvis({ 454 | for (i in 1:3) { 455 | pause(0.1) 456 | } 457 | 458 | for (i in 1:3) 459 | pause(0.1) 460 | 461 | }) 462 | ``` 463 | 464 | For code that contains multi-line expressions like these, using curly braces will allow the profiler to identify the correct line where code is running. 465 | 466 | 467 | ## How do I interpret memory profiling information? 468 | 469 | The memory profiling information can be somewhat tricky to interpret, for two reasons. The first reason is that, compared to call stack information, memory usage information is collected with different temporal characteristics: call stack information is recorded instantaneously at each sample, while memory information is recorded between each sample. 470 | 471 | The second reason that is that memory deallocations happen somewhat randomly, and may happen long after the point where the memory was no longer needed. The deallocations occur in garbage collection (``) events. 472 | 473 | For these reasons, it might look like a particular line of code (or function call in the flame graph) is responsible for memory allocation or deallocation, when in reality the memory use is due to a previous line of code. 474 | 475 | If a section of code results in a large amount of allocation and deallocation, it means that it's "churning" through memory and using a large amonut of temporary memory storage. This can be seen in Example 1 above. In these cases, it may be possible to optimize the code so that it doesn't use as much temporary memory. 476 | 477 | If a section of code results in a large amount of allocation but does not have a large amount of deallocation, then it means the memory is not being released. This could be because the code genuinely requires that extra memory, but it could also be a sign of a memory leak. 478 | 479 | 480 | ## How do I split the panes vertically instead of horizontally? 481 | 482 | The profvis examples in this document have a vertical split, but by default, profvis visualizations have a horizontal split. To switch directions, you can check or uncheck **Options -> Split horizontally**. 483 | 484 | To change the split direction when the visualization opens, use `split="v"`: 485 | 486 | ```{r eval=FALSE} 487 | profvis({ 488 | # Code here 489 | }, split = "v") 490 | 491 | 492 | # Also possible to control the split when calling print() 493 | p <- profvis({ 494 | # Code here 495 | }) 496 | print(p, split = "v") 497 | ``` 498 | 499 | 500 | ## How do I get source code to show with `Rscript`? 501 | 502 | If you run profvis from a script, the source code won't show in the source panel. This is because source refs are not recorded by default when R is run non-interactively. To make it work, use `options(keep.source=TRUE)`. For example: 503 | 504 | 505 | ``` 506 | Rscript -e "options(keep.source=TRUE); p <- profvis::profvis({ profvis::pause(0.2) }); htmlwidgets::saveWidget(p, 'test.html')" 507 | ``` 508 | 509 | ## What are some other resources for profiling R code? 510 | 511 | Base R comes with the `Rprof` function (it's what `profvis` calls to collect profiling data) as well as the `summaryRprof` function for getting a human-readable summary of the profiling data collected by `Rprof`. 512 | 513 | Luke Tierney and Riad Jarjour have authored the [proftools](https://cran.r-project.org/web/packages/proftools/index.html) package, which provides many more tools for summarizing profiling data, including hot paths, call summaries, and call graphs. Proftools can also be used to generate a number of visualizations, including call graphs, flame graphs, and callee tree maps. 514 | -------------------------------------------------------------------------------- /vignettes/articles/ggplot2.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/ggplot2.rds -------------------------------------------------------------------------------- /vignettes/articles/profvis-rpubs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/profvis-rpubs.png -------------------------------------------------------------------------------- /vignettes/articles/profvis-shiny-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/profvis-shiny-demo.gif -------------------------------------------------------------------------------- /vignettes/articles/rstudio-code-tools-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/rstudio-code-tools-menu.png -------------------------------------------------------------------------------- /vignettes/articles/rstudio-profile-menu-selected-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/rstudio-profile-menu-selected-lines.png -------------------------------------------------------------------------------- /vignettes/articles/rstudio-profile-menu-start-profiling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/rstudio-profile-menu-start-profiling.png -------------------------------------------------------------------------------- /vignettes/articles/rstudio-profile-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/rstudio-profile-view.png -------------------------------------------------------------------------------- /vignettes/articles/rstudio.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "RStudio IDE integration" 3 | --- 4 | 5 | ```{r echo = FALSE} 6 | library(profvis) 7 | 8 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 9 | ``` 10 | 11 | 12 | The [RStudio IDE](https://www.rstudio.com/products/rstudio/download/) includes integrated support for profiling with profvis. 13 | 14 | ## Profiling code 15 | 16 | There are a number of ways to start and stop the profiler. 17 | 18 | * From the **Profile** menu, you can start and stop the profiler. 19 | ![](rstudio-profile-menu-start-profiling.png) 20 | 21 | * From the **Profile** menu, you can run a selected block of code with profiling. 22 | ![](rstudio-profile-menu-selected-lines.png) 23 | 24 | * From the editor pane's code tool menu, you can run a selected block of code with profiling: 25 | ![](rstudio-code-tools-menu.png) 26 | 27 | Finally, it's also possible to use profvis by calling the `profvis()` function, as demonstrated in the rest of the documentation on this site. 28 | 29 | 30 | ## Exploring profiles in RStudio 31 | 32 | Profiles will open in a new tab in the RStudio IDE. 33 | 34 | ![](rstudio-profile-view.png) 35 | 36 | When viewed in RStudio, these profiles have some extra features: 37 | 38 | * **Opening sources:** If there are source refs for code that was executed, or if you ran code by using `source()` or the **Source** menu in RStudio code editor, then double-clicking on a line of code in the profile viewer will open that file in the editor. This won't be available if the code was run from the console, and is shown as being in ****. If you want to be able to view code in a package, see [this FAQ](faq.html#how-do-i-get-code-from-an-r-package-to-show-in-the-code-panel). 39 | 40 | * **Opening and saving profiles:** Profiles can be saved for sharing or viewing in the future. Profile files have extension `.Rprofvis`. If you wish to share a profile for viewing in a web browser, you can simply rename the file to have an `.html` extension. 41 | 42 | * **Profiling Shiny applications:** Entire Shiny applications can be profiled by starting the profiler before launching the application. To capture just a part of a Shiny session, you can start and stop the profiler while the application is running. 43 | 44 | * **Publishing profiles:** Profiles can be shared on RPubs by clicking on the **Publish** button. See [this FAQ](http://rstudio.github.io/profvis/faq.html#how-do-i-share-a-profvis-visualization) for more information. 45 | -------------------------------------------------------------------------------- /vignettes/articles/shinyapp.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/profvis/dec7381a90aac83e534a1e9f1c85a8874989862b/vignettes/articles/shinyapp.rds -------------------------------------------------------------------------------- /vignettes/profvis.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting started with profvis" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Getting started with profvis} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r} 11 | #| include: false 12 | library(profvis) 13 | library(knitr) 14 | 15 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 16 | 17 | # Make output a little less tall by default 18 | registerS3method("knit_print", "htmlwidget", function(x, ...) { 19 | # Get the chunk height 20 | height <- knitr::opts_current$get("height") 21 | if (length(height) > 0 && height != FALSE) 22 | x$height <- height 23 | else 24 | x$height <- "450px" 25 | 26 | htmlwidgets:::knit_print.htmlwidget(x, ...) 27 | }) 28 | ``` 29 | 30 | ## Introduction 31 | 32 | [profvis](https://github.com/r-lib/profvis) is a tool for helping you to understand how R spends its time. It provides a interactive graphical interface for visualizing data from `Rprof()`, R's built-in tool for collecting profiling data. 33 | 34 | Most R users have had times where we've wanted our code to run faster. However, it's not always clear how to accomplish this. A common approach is to rely on intuition, and on wisdom from the broader R community about speeding up R code. One drawback to this is it can lead to a focus on optimizing things that actually take a small proportion of the overall running time. 35 | 36 | Suppose you make a loop run 5 times faster. That sounds like a huge improvement, but if that loop only takes 10% of the total time, it's still only a 8% speedup overall. Another drawback is that, although many of the commonly-held beliefs are true (for example, preallocating memory can speed things up), some are not (e.g., that `*apply` functions are inherently faster than `for` loops). This can lead us to spend time doing "optimizations" that don't really help. To make slow code faster, we need accurate information about what is making our code slow. 37 | 38 | ## Getting started 39 | 40 | Below is an example of profvis in use. The code generates a random 50,000 row data set, draws a plot, then fits a linear model, and draws a line for the model. (The plot isn't displayed in this document, though.) If you copy and paste this code into your R console, it'll open a window with the same profvis interface that you see in this HTML document. 41 | 42 | ```{r abline, fig.show="hide"} 43 | library(profvis) 44 | 45 | profvis({ 46 | df <- data.frame(x = rnorm(5e5), y = rnorm(5e5)) 47 | 48 | plot(y ~ x, data = df) 49 | m <- lm(y ~ x, data = df) 50 | abline(m, col = "red") 51 | }) 52 | ``` 53 | 54 | On top is the code, and on the bottom is a flame graph. In the flame graph, the horizontal direction represents time in milliseconds, and the vertical direction represents the call stack. Looking at the bottom-most items on the stack, most of the time, about 2 seconds, is spent in `plot`, and then a much smaller amount of time is spent in `lm`, and almost no time at all is spent in `abline` -- it doesn't even show up on the flame graph. 55 | 56 | Traveling up the stack, `plot` called `plot.formula`, which called `do.call`, and so on. Going up a few more levels, we can see that `plot.default` called a number of functions: first `deparse`, and later, `plot.xy`. Similarly, `lm` calls a number of different functions. 57 | 58 | On top, we can see the amount of time spent on each line of code. This tells us, unsurprisingly, that most of the time is spent on the line with `plot`, and a little bit is spent on the line with `lm`. 59 | 60 | The code panel also shows memory allocation and deallocation. Interpreting this information can be a little tricky, because it does not necessarily reflect memory allocated and deallcated *at* that line of code. The sampling profiler records information about memory allocations that happen between the previous sample and the current one. This means that the allocation/deallocation values on that line may have actually occurred in a previous line of code. 61 | 62 | ### Navigating profvis 63 | 64 | profvis is interactive. You can try the following: 65 | 66 | * As you mouse over the flame graph, information about each block will show in the info box. 67 | * Yellow flame graph blocks have corresponding lines of code on the left. (White blocks represent code where profvis doesn't have the source code -- for example, in base R and in R packages. But see [this FAQ](#how-do-i-get-code-from-an-r-package-to-show-in-the-code-panel) if you want package code to show up in the code panel.) If you mouse over a yellow block, the corresponding line of code will be highlighted. Note that the highlighted line of code is where the yellow function is called *from*, not the content of that function. 68 | * If you mouse over a line of code, all flame graph blocks that were called from that line will be highlighted. 69 | * Click on a block or line of code to lock the current highlighting. Click on the background, or again on that same item to unlock the highlighting. Click on another item to lock on that item. 70 | * Use the mouse scroll wheel or trackpad's scroll gesture to zoom in or out in the x direction. 71 | * Click and drag on the flame graph to pan up, down, left, right. 72 | * Double-click on the background to zoom the x axis to its original extent. 73 | * Double-click on a flamegraph block to zoom the x axis the width of that block. 74 | 75 | Each *block* in the flame graph represents a call to a function, or possibly multiple calls to the same function. The width of the block is proportional to the amount of time spent in that function. When a function calls another function, another block is added on top of it in the flame graph. 76 | 77 | The profiling data has some limitations: some internal R functions don't show up in the flame graph, and it offers no insight into code that's implemented in languages other than R (e.g. C, C++, or Fortran). 78 | 79 | ### The data view 80 | 81 | In addition to the flame graph view, profvis provides a *data view*, which can be seen by clicking on the Data tab. It provides a top-down tabular view of the profile. Click the **code** column to expand the call stack under investigation and the following columns to reason about resource allocation: 82 | 83 | * Memory: Memory allocated or deallocated (for negative numbers) for a given call stack. This is represented in megabytes and aggregated over all the call stacks over the code in the given row. 84 | * Time: Time spent in milliseconds. This field is also aggregated over all the call stacks executed over the code in the given row. 85 | 86 | ## How profiling data is collected 87 | 88 | profvis uses data collected by `Rprof()`, which is part of the base R distribution. At each time interval (`profvis()` uses a default interval of 10ms), the profiler stops the R interpreter, looks at the current function call stack, and records it to a file. Because it works by *sampling*, the result isn't deterministic. Each time you profile your code, the result will be slightly different. 89 | --------------------------------------------------------------------------------