diff --git a/.devcontainer/Dockerfile_rstudio b/.devcontainer/Dockerfile_rstudio index 5e223a2..421d5b2 100644 --- a/.devcontainer/Dockerfile_rstudio +++ b/.devcontainer/Dockerfile_rstudio @@ -1,5 +1,10 @@ FROM ghcr.io/rocker-org/devcontainer/tidyverse:4.3 +ENV QUARTO_VERSION=prerelease + +# install preview version of quarto +RUN /rocker_scripts/install_quarto.sh + # key dependencies for utilities RUN apt-get update -qq \ && export DEBIAN_FRONTEND=noninteractive \ @@ -23,3 +28,4 @@ RUN apt-get update -qq \ # install R package dependencies RUN install2.r renv httpgd languageserver shiny rmarkdown markdown countdown fontawesome profvis golem rsconnect devtools + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4c0c0f3..2620efc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,7 @@ "features": { "ghcr.io/rocker-org/devcontainer-features/pandoc:1": {}, "ghcr.io/rocker-org/devcontainer-features/quarto-cli:1": { - "version": "1.3.433" + "version": "1.4.352" }, "ghcr.io/meaningful-ooo/devcontainer-features/fish:1": {}, "ghcr.io/guiyomh/features/vim:0": {} diff --git a/R/shinycannon.R b/R/shinycannon.R new file mode 100644 index 0000000..86f4ee9 --- /dev/null +++ b/R/shinycannon.R @@ -0,0 +1,54 @@ +#' Run the shinycannon utility +#' +#' @param shinycannon_path Path to the Shinycannon JAR file. +#' @param recording_file Path to recording file +#' @param app_url URL of the Shiny application to interact with +#' @param output_dir Path to directory to store session logs in for this +#' test run +#' @param workers Number of workers to simulate. Default is 1. +#' @param loaded_duration_minutes Number of minutes to continue simulating +#' sessions in each worker after all workers have completed one session. +#' Can be fractional. Default is 5. +#' @param overwrite_output Delete the output directory before starting, +#' if it exists already. Default is TRUE. +#' @param debug_log Produce a debug.log in the output directory. File can get +#' very large. Default is FALSE. +#' @param log_level Log level. Possible values include warn, info, error, +#' debug. Default is warn. +#' +#' @return exit code from `sys::exec_wait()` +shinycannon <- function( + shinycannon_path, + recording_file, + app_url, + output_dir, + workers = 1, + loaded_duration_minutes = 5, + overwrite_output = TRUE, + debug_log = FALSE, + log_level = "warn") { + + # assemble command-line arguments + cli_args <- c( + "-jar", + shinycannon_path, + recording_file, + app_url, + "--workers", + workers, + "--loaded-duration-minutes", + loaded_duration_minutes, + "--output-dir", + output_dir, + "--log-level", + log_level + ) + + if (debug_log) cli_args <- c(cli_args, "--debug-log") + if (overwrite_output) cli_args <- c(cli_args, "--overwrite-output") + + sys::exec_wait( + cmd = "java", + args = cli_args + ) +} \ No newline at end of file diff --git a/R/test_dependencies.R b/R/test_dependencies.R index 202ca3c..de2ea85 100644 --- a/R/test_dependencies.R +++ b/R/test_dependencies.R @@ -25,18 +25,25 @@ library(sys) # define paths and constants -app_link <- "https://rsc.training.rstudio.com/bricktest/" +app_url <- "https://rsc.training.rstudio.com/bricktest/" recording_file <- "R/recording.log" shinyloadtest::record_session( app_link, - output_file = "R/recording.log", + output_file = recording_file, connect_api_key = Sys.getenv("RSCONNECT_KEY") ) # use the exec_wait function from sys shinycannon_path <- "utils/shinycannon-1.1.3-dd43f6b.jar" +shinycannon( + shinycannon_path, + recording_file, + app_url, + output_dir = "R/run1" +) + # baseline run exec_wait( cmd = "java", @@ -55,6 +62,18 @@ exec_wait( ) ) +df <- load_runs("Run 1" = "R/run1") + +shinyloadtest_report( + df, + output = "R/report_test.html" +) + +# experiment with creating single plots +dur_plot <- slt_session_duration(df) +plotly::ggplotly(dur_plot) + + # comparison run (3 workers) exec_wait( cmd = "java", @@ -104,8 +123,8 @@ shinyloadtest_report(df, "R/report1.html") library(connectapi) client <- connect( - server = paste0("https://", Sys.getenv("RSCONNECT_SERVER")), - api_key = Sys.getenv("RSCONNECT_KEY") + server = paste0("https://", Sys.getenv("CONNECT_SERVER")), + api_key = Sys.getenv("CONNECT_API_KEY") ) content_df <- get_content(client) diff --git a/_extensions/emilhvitfeldt/codewindow/_extension.yml b/_extensions/emilhvitfeldt/codewindow/_extension.yml new file mode 100644 index 0000000..322dd04 --- /dev/null +++ b/_extensions/emilhvitfeldt/codewindow/_extension.yml @@ -0,0 +1,12 @@ +title: codewindow +author: Emil Hvitfeldt +version: 1.1.0 +quarto-required: ">=1.4.0" +contributes: + revealjs-plugins: + - name: RevealCodewindow + script: + - codewindow.js + stylesheet: + - codewindow.css + diff --git a/_extensions/emilhvitfeldt/codewindow/codewindow.css b/_extensions/emilhvitfeldt/codewindow/codewindow.css new file mode 100644 index 0000000..0ebd16b --- /dev/null +++ b/_extensions/emilhvitfeldt/codewindow/codewindow.css @@ -0,0 +1,48 @@ +/* TODO: CSS for plugin */ + +.codewindow { + overflow-wrap: anywhere; + margin-bottom: 36px; + font-size: 0.45em; + font-family: monospace; + background: white; + border-radius: 10px; + box-shadow: #26394d 0px 20px 30px -10px; +} + +.codewindow .header { + display: flex; + background: #e2e8f0; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + padding-left: 16px; + gap: 16px; + height: 30px; +} + +.codewindow .header .file { + margin-top: 5px; + padding-left: 20px; + padding-right: 20px; + display: flex; + background: white; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + height: 90%; + align-items: center; + gap: 4px; +} + +.codewindow .textarea { + padding: 21px; +} + +.codewindow .textarea .sourceCode { + background: unset; + border: unset; + font-size: unset; +} + +.codewindow .textarea pre { + font-size: unset; +} diff --git a/_extensions/emilhvitfeldt/codewindow/codewindow.js b/_extensions/emilhvitfeldt/codewindow/codewindow.js new file mode 100644 index 0000000..f565150 --- /dev/null +++ b/_extensions/emilhvitfeldt/codewindow/codewindow.js @@ -0,0 +1,112 @@ +window.RevealCodewindow = function () { + return { + id: "RevealCodewindow", + init: function(deck) { + initCodewindow(deck); + } + }; +}; + + +const initCodewindow = function(Reveal) { + + const svg_buttons = `` + + const svg_sass = `` + + const svg_r = `` + + const svg_python = `` + + const svg_html = `` + + const svg_css = `` + + const svg_js = `` + + const svg_quarto = `` + + const svg_julia = `` + + window.addEventListener( 'ready', function(event) { + + var content; + var new_content; + var empty_file; + + // Remove configured margin of the presentation + var codewindows = document.getElementsByClassName("codewindow"); + + for (var i = 0; i < codewindows.length; i++) { + content = codewindows[i]; + + empty_file = true; + + new_content = document.createElement("div"); + new_content.classList.add("codewindow"); + + header = document.createElement("div"); + header.classList.add("header"); + header.innerHTML = svg_buttons; + + file = document.createElement("div"); + + file.classList.add("file"); + if (content.classList.contains("sass")) { + file.innerHTML += svg_sass; + empty_file = false; + } + if (content.classList.contains("r")) { + file.innerHTML += svg_r; + empty_file = false; + } + if (content.classList.contains("python")) { + file.innerHTML += svg_python; + empty_file = false; + } + if (content.classList.contains("html")) { + file.innerHTML += svg_html; + empty_file = false; + } + if (content.classList.contains("css")) { + file.innerHTML += svg_css; + empty_file = false; + } + if (content.classList.contains("js")) { + file.innerHTML += svg_js; + empty_file = false; + } + if (content.classList.contains("quarto")) { + file.innerHTML += svg_quarto; + empty_file = false; + } + if (content.classList.contains("julia")) { + file.innerHTML += svg_julia; + empty_file = false; + } + + file_name = content.querySelector("p"); + if (file_name !== null) { + file.innerHTML += file_name.innerText; + empty_file = false; + } + + textarea = document.createElement("div"); + textarea.classList.add("textarea"); + textarea.appendChild(content.querySelector("div")); + + if (!empty_file) { + header.appendChild(file); + } + + new_content.appendChild(header); + new_content.appendChild(textarea); + + codewindows[i].innerHTML = new_content.innerHTML; + + if (content.attributes.width !== undefined) { + codewindows[i].style.width = content.attributes.width.value; + } + } + }); +}; \ No newline at end of file diff --git a/_extensions/emilhvitfeldt/highlightword/_extension.yml b/_extensions/emilhvitfeldt/highlightword/_extension.yml new file mode 100644 index 0000000..73f92a4 --- /dev/null +++ b/_extensions/emilhvitfeldt/highlightword/_extension.yml @@ -0,0 +1,9 @@ +title: highlightword +author: Emil Hvitfeldt +version: 1.0.0 +quarto-required: ">=1.4.0" +contributes: + revealjs-plugins: + - name: RevealHighlightword + script: + - highlightword.js diff --git a/_extensions/emilhvitfeldt/highlightword/highlightword.js b/_extensions/emilhvitfeldt/highlightword/highlightword.js new file mode 100644 index 0000000..73f6e72 --- /dev/null +++ b/_extensions/emilhvitfeldt/highlightword/highlightword.js @@ -0,0 +1,81 @@ +window.RevealHighlightword= function () { + return { + id: "RevealCodewindow", + init: function(deck) { + initCodewindow(deck); + } + }; +}; + +function replaceOccurrence(string, regex, n, replace) { + var i = 0; + return string.replace(regex, function(match) { + i+=1; + if(i===n) return replace; + return match; + }); +} + +const highlight_apply = function(fragment) { + if (fragment.classList.contains("highlightword")) { + var chunk_id = 0 + if (fragment.dataset.chunk !== undefined) { + chunk_id = fragment.dataset.chunk - 1; + } + var chunk = Reveal.getCurrentSlide().querySelectorAll("code.sourceCode")[chunk_id] + + word = fragment.dataset.word; + if (word === undefined) { + return + } + + replacement = document.createElement("span"); + replacement.innerText = word + replacement.style.cssText = fragment.style.cssText; + + var number = 1 + if (fragment.dataset.number !== undefined) { + number = Number(fragment.dataset.number); + } + + let t = 0; + chunk.innerHTML = chunk.innerHTML.replaceAll( + word, + match => ++t === number ? replacement.outerHTML : match + ); + } +} + +const highlight_reverse = function(fragment) { + if (fragment.classList.contains("highlightword")) { + var chunk_id = 0 + if (fragment.dataset.chunk !== undefined) { + chunk_id = fragment.dataset.chunk - 1; + } + var chunk = Reveal.getCurrentSlide().querySelectorAll("code.sourceCode")[chunk_id] + + word = fragment.dataset.word; + if (word === undefined) { + return + } + + replacement = document.createElement("span"); + replacement.innerText = word + replacement.style.cssText = fragment.style.cssText; + + let t = 0; + chunk.innerHTML = chunk.innerHTML.replace( + replacement.outerHTML, word + ); + } +} + +const initCodewindow = function(window) { + window.on( 'fragmentshown', event => { + event.fragments.forEach(highlight_apply); + }); + + window.on( 'fragmenthidden', event => { + event.fragments.forEach(highlight_reverse); + }); +}; \ No newline at end of file diff --git a/_freeze/materials/d1-9001-loadtesting/index/execute-results/html.json b/_freeze/materials/d1-9001-loadtesting/index/execute-results/html.json new file mode 100644 index 0000000..1c4315c --- /dev/null +++ b/_freeze/materials/d1-9001-loadtesting/index/execute-results/html.json @@ -0,0 +1,20 @@ +{ + "hash": "86308c1c83aa589f5231cb9463fc2773", + "result": { + "markdown": "---\ntitle: \"Load Testing\"\nsubtitle: \"posit::conf(2023)
Shiny in Production: Tools & Techniques\"\nauthor: \"TBD\"\nfooter: \"[{{< var workshop_short_url >}}]({{< var workshop_full_url >}})\"\nformat: \n revealjs:\n theme: [default, ../slides.scss] # moon= teal bg | dark\n scrollable: true\n incremental: false\n slide-number: c/t # c/t | c | h/v | h.v\n slide-tone: false #true\n code-line-numbers: true\n history: false\nrevealjs-plugins:\n - codewindow\n---\n\n\n## One to Many\n\n:::: {.columns}\n\n::: {.column width=\"50%\"}\n![single-user](assets/img/lego_single_user_computer.png)\n:::\n\n::: {.column width=\"50%\"}\n![crowd](assets/img/lego_crowd.jpg)\n:::\n\n::::\n\n## Optimization Loop Method\n\n![](assets/img/loop.svg)\n\n::: footer\n[rstudio.github.io/shinyloadtest/articles/case-study-scaling.html](https://rstudio.github.io/shinyloadtest/articles/case-study-scaling.html)\n:::\n\n## Double the (Load-Testing) Fun\n\n#### `{shinyloadtest}`\n\n* Record the events of a Shiny application session\n* Process and analyze metrics associated with application runs\n\n#### `shinycannon`\n\n* Command-line utility to launch multiple application sessions and collect event-based metrics\n* Cross-platform (built with Kotlin)\n\n## Hosting Requirements\n\n* Application deployed to a server supporting [SockJS](https://github.com/sockjs/sockjs-client) (Posit Connect, Shiny Server)\n* If authentication required for app on Posit Connect, you'll need an API key\n\n## Recording a Session\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(shinyloadtest)\nrecord_session(\"https://my-lego-app.me\", output_file = \"recording.log\")\n```\n:::\n\n\n## Recorder Tips\n\n* Emulate a real-world usage of your application\n* Avoid rapid clicks / selections of your inputs\n\n## The log\n\n```\n# version: 1\n# target_url: https://my-lego-app.me/\n# target_type: RStudio Server Connect\n# rscApiKeyRequired: false\n{\"type\":\"REQ_HOME\",\"begin\":\"2023-09-07T13:03:39.042Z\",\"end\":\"2023-09-07T13:03:40.138Z\",\"status\":200,\"url\":\"/\"}\n{\"type\":\"REQ_GET\",\"begin\":\"2023-09-07T13:03:40.332Z\",\"end\":\"2023-09-07T13:03:40.502Z\",\"status\":200,\"url\":\"/_w_${WORKER}/shiny-sass-1.7.5/shiny-sass.css\"}\n{\"type\":\"REQ_GET\",\"begin\":\"2023-09-07T13:03:40.517Z\",\"end\":\"2023-09-07T13:03:40.678Z\",\"status\":200,\"url\":\"/_w_${WORKER}/bslib-grid-styles-0.5.1/grid.css\"}\n{\"type\":\"REQ_GET\",\"begin\":\"2023-09-07T13:03:40.692Z\",\"end\":\"2023-09-07T13:03:40.886Z\",\"status\":200,\"url\":\"/_w_${WORKER}/bootstrap-5.2.2/bootstrap.min.css\"}\n...\n...\n{\"type\":\"WS_SEND\",\"begin\":\"2023-09-07T13:04:17.805Z\",\"message\":\"[\\\"D#0|m|{\\\\\\\"method\\\\\\\":\\\\\\\"update\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"n_parts_display\\\\\\\":\\\\\\\"20\\\\\\\"}}\\\"]\"}\n{\"type\":\"WS_CLOSE\",\"begin\":\"2023-09-07T13:04:35.561Z\"}\n```\n\n. . .\n\n![](assets/img/confused_minifig.jpg){.absolute top=50 right=250 width=\"500px\" height=\"500px\"}\n\n## Loading the (shiny) cannon\n\n::: {.codewindow}\nbash\n\n::: {.cell}\n\n```{.bash .cell-code}\njava -jar shinycannon-1.1.3-dd43f6b.jar \\\n recording.log \\\n https://my-lego-app.me \\\n --loaded-duration-minutes 2 \\\n --workers 1 \\\n --output-dir run1\n```\n:::\n\n:::\n\n. . .\n\n::: {.codewindow .r}\nrun_shinycannon.R\n\n::: {.cell}\n\n```{.r .cell-code}\nsource(file.path(here::here(), \"R\", \"shinycannon.R\"))\n\nshinycannon(\n \"shinycannon-1.1.3-dd43f6b.jar\",\n \"recording.log\",\n \"https://my-lego-app.me\",\n loaded_duration_minutes = 2,\n workers = 1,\n output_dir = \"run1\"\n)\n```\n:::\n\n:::\n\n## Metrics\n\n\n::: {.cell}\n\n:::\n\n\n::: {.codewindow .r}\nanalyze_recording.R\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(shinyloadtest)\ndf <- load_runs(\"Run 1\" = \"run1\")\n```\n:::\n\n:::\n\n\n::: {.smaller-output}\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 138 × 13\n run user_id session_id iteration input_line_number event start end time\n \n 1 Run 1 0 0 0 5 REQ_H… 0 0.951 0.951\n 2 Run 1 0 0 0 6 REQ_G… 0.952 1.27 0.320\n 3 Run 1 0 0 0 7 REQ_G… 1.27 1.54 0.271\n 4 Run 1 0 0 0 8 REQ_G… 1.54 1.85 0.308\n 5 Run 1 0 0 0 9 REQ_G… 1.85 2.19 0.334\n 6 Run 1 0 0 0 10 REQ_G… 2.19 2.44 0.253\n 7 Run 1 0 0 0 11 REQ_G… 2.44 2.86 0.422\n 8 Run 1 0 0 0 12 REQ_G… 2.86 3.12 0.257\n 9 Run 1 0 0 0 13 REQ_G… 3.12 3.37 0.252\n10 Run 1 0 0 0 14 REQ_G… 3.37 3.62 0.253\n# ℹ 128 more rows\n# ℹ 4 more variables: concurrency , maintenance , label ,\n# json \n```\n\n\n:::\n:::\n\n:::\n\n## But Wait .. There's More\n\n::: {.codewindow .r}\ngen_report.R\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(shinyloadtest)\ndf <- load_runs(\"Run 1\" = \"run1\")\nshinyloadtest_report(df, output = \"report_test.html\")\n```\n:::\n\n:::\n\n::: {.callout-warning title=\"TODO\"}\nAdd screenshot of HTML report with hyperlink\n:::\n\n# ``{=html} Code-Along {background-color=\"#17395c\"}\n\nCode-Along 1: Record and analyze a baseline application session", + "supporting": [ + "index_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-after-body": [ + "\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/site_libs/revealjs/dist/theme/quarto.css b/_freeze/site_libs/revealjs/dist/theme/quarto.css index dd9eded..d9282d5 100644 --- a/_freeze/site_libs/revealjs/dist/theme/quarto.css +++ b/_freeze/site_libs/revealjs/dist/theme/quarto.css @@ -1,4 +1,4 @@ -@import"./fonts/source-sans-pro/source-sans-pro.css";.v-center-container{display:flex;justify-content:center;align-items:center;height:90%}:root{--r-background-color: #fff;--r-main-font: Source Sans Pro, Helvetica, sans-serif;--r-main-font-size: 40px;--r-main-color: #222;--r-block-margin: 12px;--r-heading-margin: 0 0 12px 0;--r-heading-font: Source Sans Pro, Helvetica, sans-serif;--r-heading-color: #222;--r-heading-line-height: 1.2;--r-heading-letter-spacing: normal;--r-heading-text-transform: none;--r-heading-text-shadow: none;--r-heading-font-weight: 600;--r-heading1-text-shadow: none;--r-heading1-size: 2.5em;--r-heading2-size: 1.6em;--r-heading3-size: 1.3em;--r-heading4-size: 1em;--r-code-font: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;--r-link-color: #2a76dd;--r-link-color-dark: #1a53a1;--r-link-color-hover: #5692e4;--r-selection-background-color: #98bdef;--r-selection-color: #fff}.reveal-viewport{background:#fff;background-color:var(--r-background-color)}.reveal{font-family:var(--r-main-font);font-size:var(--r-main-font-size);font-weight:normal;color:var(--r-main-color)}.reveal ::selection{color:var(--r-selection-color);background:var(--r-selection-background-color);text-shadow:none}.reveal ::-moz-selection{color:var(--r-selection-color);background:var(--r-selection-background-color);text-shadow:none}.reveal .slides section,.reveal .slides section>section{line-height:1.3;font-weight:inherit}.reveal h1,.reveal h2,.reveal h3,.reveal h4,.reveal h5,.reveal h6{margin:var(--r-heading-margin);color:var(--r-heading-color);font-family:var(--r-heading-font);font-weight:var(--r-heading-font-weight);line-height:var(--r-heading-line-height);letter-spacing:var(--r-heading-letter-spacing);text-transform:var(--r-heading-text-transform);text-shadow:var(--r-heading-text-shadow);word-wrap:break-word}.reveal h1{font-size:var(--r-heading1-size)}.reveal h2{font-size:var(--r-heading2-size)}.reveal h3{font-size:var(--r-heading3-size)}.reveal h4{font-size:var(--r-heading4-size)}.reveal h1{text-shadow:var(--r-heading1-text-shadow)}.reveal p{margin:var(--r-block-margin) 0;line-height:1.3}.reveal h1:last-child,.reveal h2:last-child,.reveal h3:last-child,.reveal h4:last-child,.reveal h5:last-child,.reveal h6:last-child{margin-bottom:0}.reveal img,.reveal video,.reveal iframe{max-width:95%;max-height:95%}.reveal strong,.reveal b{font-weight:bold}.reveal em{font-style:italic}.reveal ol,.reveal dl,.reveal ul{display:inline-block;text-align:left;margin:0 0 0 1em}.reveal ol{list-style-type:decimal}.reveal ul{list-style-type:disc}.reveal ul ul{list-style-type:square}.reveal ul ul ul{list-style-type:circle}.reveal ul ul,.reveal ul ol,.reveal ol ol,.reveal ol ul{display:block;margin-left:40px}.reveal dt{font-weight:bold}.reveal dd{margin-left:40px}.reveal blockquote{display:block;position:relative;width:70%;margin:var(--r-block-margin) auto;padding:5px;font-style:italic;background:rgba(255,255,255,.05);box-shadow:0px 0px 2px rgba(0,0,0,.2)}.reveal blockquote p:first-child,.reveal blockquote p:last-child{display:inline-block}.reveal q{font-style:italic}.reveal pre{display:block;position:relative;width:90%;margin:var(--r-block-margin) auto;text-align:left;font-size:.55em;font-family:var(--r-code-font);line-height:1.2em;word-wrap:break-word;box-shadow:0px 5px 15px rgba(0,0,0,.15)}.reveal code{font-family:var(--r-code-font);text-transform:none;tab-size:2}.reveal pre code{display:block;padding:5px;overflow:auto;max-height:400px;word-wrap:normal}.reveal .code-wrapper{white-space:normal}.reveal .code-wrapper code{white-space:pre}.reveal table{margin:auto;border-collapse:collapse;border-spacing:0}.reveal table th{font-weight:bold}.reveal table th,.reveal table td{text-align:left;padding:.2em .5em .2em .5em;border-bottom:1px solid}.reveal table th[align=center],.reveal table td[align=center]{text-align:center}.reveal table th[align=right],.reveal table td[align=right]{text-align:right}.reveal table tbody tr:last-child th,.reveal table tbody tr:last-child td{border-bottom:none}.reveal sup{vertical-align:super;font-size:smaller}.reveal sub{vertical-align:sub;font-size:smaller}.reveal small{display:inline-block;font-size:.6em;line-height:1.2em;vertical-align:top}.reveal small *{vertical-align:top}.reveal img{margin:var(--r-block-margin) 0}.reveal a{color:var(--r-link-color);text-decoration:none;transition:color .15s ease}.reveal a:hover{color:var(--r-link-color-hover);text-shadow:none;border:none}.reveal .roll span:after{color:#fff;background:var(--r-link-color-dark)}.reveal .r-frame{border:4px solid var(--r-main-color);box-shadow:0 0 10px rgba(0,0,0,.15)}.reveal a .r-frame{transition:all .15s linear}.reveal a:hover .r-frame{border-color:var(--r-link-color);box-shadow:0 0 20px rgba(0,0,0,.55)}.reveal .controls{color:var(--r-link-color)}.reveal .progress{background:rgba(0,0,0,.2);color:var(--r-link-color)}@media print{.backgrounds{background-color:var(--r-background-color)}}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6f6f6f}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! +@import"./fonts/source-sans-pro/source-sans-pro.css";.v-center-container{display:flex;justify-content:center;align-items:center;height:90%}:root{--r-background-color: #fff;--r-main-font: Source Sans Pro, Helvetica, sans-serif;--r-main-font-size: 40px;--r-main-color: #222;--r-block-margin: 12px;--r-heading-margin: 0 0 12px 0;--r-heading-font: Source Sans Pro, Helvetica, sans-serif;--r-heading-color: #222;--r-heading-line-height: 1.2;--r-heading-letter-spacing: normal;--r-heading-text-transform: none;--r-heading-text-shadow: none;--r-heading-font-weight: 600;--r-heading1-text-shadow: none;--r-heading1-size: 2.5em;--r-heading2-size: 1.6em;--r-heading3-size: 1.3em;--r-heading4-size: 1em;--r-code-font: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;--r-link-color: #2a76dd;--r-link-color-dark: #1a53a1;--r-link-color-hover: #5692e4;--r-selection-background-color: #98bdef;--r-selection-color: #fff}.reveal-viewport{background:#fff;background-color:var(--r-background-color)}.reveal{font-family:var(--r-main-font);font-size:var(--r-main-font-size);font-weight:normal;color:var(--r-main-color)}.reveal ::selection{color:var(--r-selection-color);background:var(--r-selection-background-color);text-shadow:none}.reveal ::-moz-selection{color:var(--r-selection-color);background:var(--r-selection-background-color);text-shadow:none}.reveal .slides section,.reveal .slides section>section{line-height:1.3;font-weight:inherit}.reveal h1,.reveal h2,.reveal h3,.reveal h4,.reveal h5,.reveal h6{margin:var(--r-heading-margin);color:var(--r-heading-color);font-family:var(--r-heading-font);font-weight:var(--r-heading-font-weight);line-height:var(--r-heading-line-height);letter-spacing:var(--r-heading-letter-spacing);text-transform:var(--r-heading-text-transform);text-shadow:var(--r-heading-text-shadow);word-wrap:break-word}.reveal h1{font-size:var(--r-heading1-size)}.reveal h2{font-size:var(--r-heading2-size)}.reveal h3{font-size:var(--r-heading3-size)}.reveal h4{font-size:var(--r-heading4-size)}.reveal h1{text-shadow:var(--r-heading1-text-shadow)}.reveal p{margin:var(--r-block-margin) 0;line-height:1.3}.reveal h1:last-child,.reveal h2:last-child,.reveal h3:last-child,.reveal h4:last-child,.reveal h5:last-child,.reveal h6:last-child{margin-bottom:0}.reveal img,.reveal video,.reveal iframe{max-width:95%;max-height:95%}.reveal strong,.reveal b{font-weight:bold}.reveal em{font-style:italic}.reveal ol,.reveal dl,.reveal ul{display:inline-block;text-align:left;margin:0 0 0 1em}.reveal ol{list-style-type:decimal}.reveal ul{list-style-type:disc}.reveal ul ul{list-style-type:square}.reveal ul ul ul{list-style-type:circle}.reveal ul ul,.reveal ul ol,.reveal ol ol,.reveal ol ul{display:block;margin-left:40px}.reveal dt{font-weight:bold}.reveal dd{margin-left:40px}.reveal blockquote{display:block;position:relative;width:70%;margin:var(--r-block-margin) auto;padding:5px;font-style:italic;background:rgba(255,255,255,.05);box-shadow:0px 0px 2px rgba(0,0,0,.2)}.reveal blockquote p:first-child,.reveal blockquote p:last-child{display:inline-block}.reveal q{font-style:italic}.reveal pre{display:block;position:relative;width:90%;margin:var(--r-block-margin) auto;text-align:left;font-size:.55em;font-family:var(--r-code-font);line-height:1.2em;word-wrap:break-word;box-shadow:0px 5px 15px rgba(0,0,0,.15)}.reveal code{font-family:var(--r-code-font);text-transform:none;tab-size:2}.reveal pre code{display:block;padding:5px;overflow:auto;max-height:400px;word-wrap:normal}.reveal .code-wrapper{white-space:normal}.reveal .code-wrapper code{white-space:pre}.reveal table{margin:auto;border-collapse:collapse;border-spacing:0}.reveal table th{font-weight:bold}.reveal table th,.reveal table td{text-align:left;padding:.2em .5em .2em .5em;border-bottom:1px solid}.reveal table th[align=center],.reveal table td[align=center]{text-align:center}.reveal table th[align=right],.reveal table td[align=right]{text-align:right}.reveal table tbody tr:last-child th,.reveal table tbody tr:last-child td{border-bottom:none}.reveal sup{vertical-align:super;font-size:smaller}.reveal sub{vertical-align:sub;font-size:smaller}.reveal small{display:inline-block;font-size:.6em;line-height:1.2em;vertical-align:top}.reveal small *{vertical-align:top}.reveal img{margin:var(--r-block-margin) 0}.reveal a{color:var(--r-link-color);text-decoration:none;transition:color .15s ease}.reveal a:hover{color:var(--r-link-color-hover);text-shadow:none;border:none}.reveal .roll span:after{color:#fff;background:var(--r-link-color-dark)}.reveal .r-frame{border:4px solid var(--r-main-color);box-shadow:0 0 10px rgba(0,0,0,.15)}.reveal a .r-frame{transition:all .15s linear}.reveal a:hover .r-frame{border-color:var(--r-link-color);box-shadow:0 0 20px rgba(0,0,0,.55)}.reveal .controls{color:var(--r-link-color)}.reveal .progress{background:rgba(0,0,0,.2);color:var(--r-link-color)}@media print{.backgrounds{background-color:var(--r-background-color)}}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}.quarto-figure>figure>div.cell-annotation,.quarto-figure>figure>div code{text-align:left}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6f6f6f}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}a{text-underline-offset:3px}div.ansi-escaped-output{font-family:monospace;display:block}/*! * * ansi colors from IPython notebook's * diff --git a/_freeze/site_libs/revealjs/plugin/reveal-codewindow/codewindow.css b/_freeze/site_libs/revealjs/plugin/reveal-codewindow/codewindow.css new file mode 100644 index 0000000..0ebd16b --- /dev/null +++ b/_freeze/site_libs/revealjs/plugin/reveal-codewindow/codewindow.css @@ -0,0 +1,48 @@ +/* TODO: CSS for plugin */ + +.codewindow { + overflow-wrap: anywhere; + margin-bottom: 36px; + font-size: 0.45em; + font-family: monospace; + background: white; + border-radius: 10px; + box-shadow: #26394d 0px 20px 30px -10px; +} + +.codewindow .header { + display: flex; + background: #e2e8f0; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + padding-left: 16px; + gap: 16px; + height: 30px; +} + +.codewindow .header .file { + margin-top: 5px; + padding-left: 20px; + padding-right: 20px; + display: flex; + background: white; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + height: 90%; + align-items: center; + gap: 4px; +} + +.codewindow .textarea { + padding: 21px; +} + +.codewindow .textarea .sourceCode { + background: unset; + border: unset; + font-size: unset; +} + +.codewindow .textarea pre { + font-size: unset; +} diff --git a/_freeze/site_libs/revealjs/plugin/reveal-codewindow/codewindow.js b/_freeze/site_libs/revealjs/plugin/reveal-codewindow/codewindow.js new file mode 100644 index 0000000..f565150 --- /dev/null +++ b/_freeze/site_libs/revealjs/plugin/reveal-codewindow/codewindow.js @@ -0,0 +1,112 @@ +window.RevealCodewindow = function () { + return { + id: "RevealCodewindow", + init: function(deck) { + initCodewindow(deck); + } + }; +}; + + +const initCodewindow = function(Reveal) { + + const svg_buttons = `` + + const svg_sass = `` + + const svg_r = `` + + const svg_python = `` + + const svg_html = `` + + const svg_css = `` + + const svg_js = `` + + const svg_quarto = `` + + const svg_julia = `` + + window.addEventListener( 'ready', function(event) { + + var content; + var new_content; + var empty_file; + + // Remove configured margin of the presentation + var codewindows = document.getElementsByClassName("codewindow"); + + for (var i = 0; i < codewindows.length; i++) { + content = codewindows[i]; + + empty_file = true; + + new_content = document.createElement("div"); + new_content.classList.add("codewindow"); + + header = document.createElement("div"); + header.classList.add("header"); + header.innerHTML = svg_buttons; + + file = document.createElement("div"); + + file.classList.add("file"); + if (content.classList.contains("sass")) { + file.innerHTML += svg_sass; + empty_file = false; + } + if (content.classList.contains("r")) { + file.innerHTML += svg_r; + empty_file = false; + } + if (content.classList.contains("python")) { + file.innerHTML += svg_python; + empty_file = false; + } + if (content.classList.contains("html")) { + file.innerHTML += svg_html; + empty_file = false; + } + if (content.classList.contains("css")) { + file.innerHTML += svg_css; + empty_file = false; + } + if (content.classList.contains("js")) { + file.innerHTML += svg_js; + empty_file = false; + } + if (content.classList.contains("quarto")) { + file.innerHTML += svg_quarto; + empty_file = false; + } + if (content.classList.contains("julia")) { + file.innerHTML += svg_julia; + empty_file = false; + } + + file_name = content.querySelector("p"); + if (file_name !== null) { + file.innerHTML += file_name.innerText; + empty_file = false; + } + + textarea = document.createElement("div"); + textarea.classList.add("textarea"); + textarea.appendChild(content.querySelector("div")); + + if (!empty_file) { + header.appendChild(file); + } + + new_content.appendChild(header); + new_content.appendChild(textarea); + + codewindows[i].innerHTML = new_content.innerHTML; + + if (content.attributes.width !== undefined) { + codewindows[i].style.width = content.attributes.width.value; + } + } + }); +}; \ No newline at end of file diff --git a/_quarto.yml b/_quarto.yml index b9e79ce..84feb38 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -25,6 +25,7 @@ format: #css: styles.css toc: true link-external-newwindow: true + code-line-numbers: true execute: freeze: auto diff --git a/materials/d1-9001-loadtesting/assets/img/confused_minifig.jpg b/materials/d1-9001-loadtesting/assets/img/confused_minifig.jpg new file mode 100644 index 0000000..117f982 Binary files /dev/null and b/materials/d1-9001-loadtesting/assets/img/confused_minifig.jpg differ diff --git a/materials/d1-9001-loadtesting/assets/img/lego_crowd.jpg b/materials/d1-9001-loadtesting/assets/img/lego_crowd.jpg new file mode 100644 index 0000000..2a4c1fe Binary files /dev/null and b/materials/d1-9001-loadtesting/assets/img/lego_crowd.jpg differ diff --git a/materials/d1-9001-loadtesting/assets/img/lego_single_user_computer.png b/materials/d1-9001-loadtesting/assets/img/lego_single_user_computer.png new file mode 100644 index 0000000..33fd32c Binary files /dev/null and b/materials/d1-9001-loadtesting/assets/img/lego_single_user_computer.png differ diff --git a/materials/d1-9001-loadtesting/assets/img/loop.svg b/materials/d1-9001-loadtesting/assets/img/loop.svg new file mode 100644 index 0000000..8b9e3fe --- /dev/null +++ b/materials/d1-9001-loadtesting/assets/img/loop.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4. Optimize + 1. Benchmark + 3. Recommend + 2. Analyze + Done! + + \ No newline at end of file diff --git a/materials/d1-9001-loadtesting/index.qmd b/materials/d1-9001-loadtesting/index.qmd new file mode 100644 index 0000000..d52e969 --- /dev/null +++ b/materials/d1-9001-loadtesting/index.qmd @@ -0,0 +1,179 @@ +--- +title: "Load Testing" +subtitle: "posit::conf(2023)
Shiny in Production: Tools & Techniques" +author: "TBD" +footer: "[{{< var workshop_short_url >}}]({{< var workshop_full_url >}})" +format: + revealjs: + theme: [default, ../slides.scss] # moon= teal bg | dark + scrollable: true + incremental: false + slide-number: c/t # c/t | c | h/v | h.v + slide-tone: false #true + code-line-numbers: true + history: false +revealjs-plugins: + - codewindow +--- + +## One to Many + +:::: {.columns} + +::: {.column width="50%"} +![single-user](assets/img/lego_single_user_computer.png) +::: + +::: {.column width="50%"} +![crowd](assets/img/lego_crowd.jpg) +::: + +:::: + +## Optimization Loop Method + +![](assets/img/loop.svg) + +::: footer +[rstudio.github.io/shinyloadtest/articles/case-study-scaling.html](https://rstudio.github.io/shinyloadtest/articles/case-study-scaling.html) +::: + +## Double the (Load-Testing) Fun + +#### `{shinyloadtest}` + +* Record the events of a Shiny application session +* Process and analyze metrics associated with application runs + +#### `shinycannon` + +* Command-line utility to launch multiple application sessions and collect event-based metrics +* Cross-platform (built with Kotlin) + +## Hosting Requirements + +* Application deployed to a server supporting [SockJS](https://github.com/sockjs/sockjs-client) (Posit Connect, Shiny Server) +* If authentication required for app on Posit Connect, you'll need an API key + +## Recording a Session + +```{r} +#| eval: false +#| echo: true + +library(shinyloadtest) +record_session("https://my-lego-app.me", output_file = "recording.log") +``` + +## Recorder Tips + +* Emulate a real-world usage of your application +* Avoid rapid clicks / selections of your inputs + +## The log + +``` +# version: 1 +# target_url: https://my-lego-app.me/ +# target_type: RStudio Server Connect +# rscApiKeyRequired: false +{"type":"REQ_HOME","begin":"2023-09-07T13:03:39.042Z","end":"2023-09-07T13:03:40.138Z","status":200,"url":"/"} +{"type":"REQ_GET","begin":"2023-09-07T13:03:40.332Z","end":"2023-09-07T13:03:40.502Z","status":200,"url":"/_w_${WORKER}/shiny-sass-1.7.5/shiny-sass.css"} +{"type":"REQ_GET","begin":"2023-09-07T13:03:40.517Z","end":"2023-09-07T13:03:40.678Z","status":200,"url":"/_w_${WORKER}/bslib-grid-styles-0.5.1/grid.css"} +{"type":"REQ_GET","begin":"2023-09-07T13:03:40.692Z","end":"2023-09-07T13:03:40.886Z","status":200,"url":"/_w_${WORKER}/bootstrap-5.2.2/bootstrap.min.css"} +... +... +{"type":"WS_SEND","begin":"2023-09-07T13:04:17.805Z","message":"[\"D#0|m|{\\\"method\\\":\\\"update\\\",\\\"data\\\":{\\\"n_parts_display\\\":\\\"20\\\"}}\"]"} +{"type":"WS_CLOSE","begin":"2023-09-07T13:04:35.561Z"} +``` + +. . . + +![](assets/img/confused_minifig.jpg){.absolute top=50 right=250 width="500px" height="500px"} + +## Loading the (shiny) cannon + +::: {.codewindow} +bash +```{bash} +#| eval: false +#| echo: true +java -jar shinycannon-1.1.3-dd43f6b.jar \ + recording.log \ + https://my-lego-app.me \ + --loaded-duration-minutes 2 \ + --workers 1 \ + --output-dir run1 +``` +::: + +. . . + +::: {.codewindow .r} +run_shinycannon.R +```{r} +#| eval: false +#| echo: true + +source(file.path(here::here(), "R", "shinycannon.R")) + +shinycannon( + "shinycannon-1.1.3-dd43f6b.jar", + "recording.log", + "https://my-lego-app.me", + loaded_duration_minutes = 2, + workers = 1, + output_dir = "run1" +) +``` +::: + +## Metrics + +```{css echo=FALSE} +.smaller-output{ + font-size: 80%; +} +``` + +::: {.codewindow .r} +analyze_recording.R +```{r} +#| eval: false +#| echo: true +library(shinyloadtest) +df <- load_runs("Run 1" = "run1") +``` +::: + + +::: {.smaller-output} +```{r} +#| eval: true +#| echo: false + +shinyloadtest::load_runs("Run 1" = file.path(here::here(), "R", "run1")) + +``` +::: + +## But Wait .. There's More + +::: {.codewindow .r} +gen_report.R +```{r} +#| eval: false +#| echo: true +library(shinyloadtest) +df <- load_runs("Run 1" = "run1") +shinyloadtest_report(df, output = "report_test.html") +``` +::: + +::: {.callout-warning title="TODO"} +Add screenshot of HTML report with hyperlink +::: + +# `r fontawesome::fa("people-carry", "white")` Code-Along {background-color="#17395c"} + +Code-Along 1: Record and analyze a baseline application session \ No newline at end of file diff --git a/units/d1-9001-loadtesting.qmd b/units/d1-9001-loadtesting.qmd new file mode 100644 index 0000000..17a0c2d --- /dev/null +++ b/units/d1-9001-loadtesting.qmd @@ -0,0 +1,32 @@ +--- +title: "Load Testing" +subtitle: "TBD" +author: "Eric Nantz & Michael Thomas" +date: "2023-09-18" +listing: + - id: exercises + contents: + - ../materials/d1-9001-loadtesting/ex-*.qmd + type: table + fields: [subtitle, title] + field-display-names: + subtitle: "Exercise" + sort: [filename] + sort-ui: false + filter-ui: false + image-placeholder: assets/img/placeholder.png +tbl-colwidths: [5,20,75] +--- + +## Slides + +::: callout-warning +These slides are under construction and will be finalized prior to the workshop date. +::: + +[View slides in full screen](../materials/d1-9001-loadtesting/index.html) + +```{=html} + +``` +