Resources

Barret Schloerke | Reactlog 2.0 Debugging the state of Shiny | RStudio (2019)

The revamped reactlog provides an updated visual display to traverse through the reactive behavior within your shiny application. Using live shiny applications, we will use reactlog’s directed dependency graph to find missing reactive dependencies in “working” applications and address suboptimal reactive coding patterns. Correcting these coding patterns will reduce the amount of calculations done by shiny and keep reactive objects from being created unnecessarily. VIEW MATERIALS http://github.com/schloerke/presentation-2019-01-18-reactlog About the Author Barret Schloerke I specialize in Large Data Visualization where I utilize the interactivity of a web browser, the fast iterations of the R programming language, and large data storage capacity of Hadoop

image: thumbnail.jpg

Transcript#

This transcript was generated automatically and may contain errors.

Hi everyone, I'm Barret Schloerke, I'm at RStudio on the Shiny team, and I help out with Shiny development, not necessarily directly with Shiny except for in this case, but help around the Shiny ecosystem a lot as well. GitHub and Twitter, at Schloerke, there's only one of me, I promise. I set all my parents up so they don't have the same names.

So let's get started. So situation. We're all here because of Shiny, so we built a Shiny application, yay, it's awesome. We add a new reactive feature to the application because that's why we use Shiny. Also awesome. But very common behavior is that that reactive output possibly does not update. And so we're a little sad and also confused.

So let's check the console because there's always information in the console, right? That's R, that's where all the traceback information is. No errors. Now we need to start panicking because we're in trouble. I hope by the end of this talk that that will not be a scary situation to be in.

Debugging reactive code

So we're going to talk about debugging that reactive code that caused that issue. Debugging reactive code, as that example just showed, is not easy. It's not a trivial task because you have working code. No errors are being produced. How can we explore what's going on because it's happening later? You have to know a lot of information for this reactive code. You have to know the R value, that's fair. You also have to know who depends on who, such as in the modules, can get messy very quickly. And you must do this over time. That is not easy. It's a lot of things to keep track of. Normally with R it's just linear.

Solution reactlog. If you're familiar with reactlog before, congratulations, you can be really excited. I'm not going to introduce the old setup. So what is reactlog? reactlog is a snapshot or log of all the reactive interactions within a Shiny application.

So just so that we're all on the same page, let's review this for just a quick second. So reactive programming, it's a paradigm concerned with propagation of change. You need to remember a lot of things. There's three types that we always talk about. We have our sources, such as input key A. We have conductors, such as a reactive. Where I create a new value, given that input key A. And then I have some end points. Such as output key B, which is one of my many render functions. In this case, render print. Or we could have an observe, where the propagation ends there at the observe statement.

If we were to kind of look at a dependency tree of this, it would look similar to this. Where we have an input, such as key A, going into value, which was then used in output key B, or even the observe statement. And we'll get into a more in depth look at that later.

reactlog features

So reactlog. We're gonna set up this dependency tree, and then we're gonna traverse backwards and forwards through this reactivity history. We're also going to be able to search for reactive elements. Because even though we all love our hello world applications, we're building some complex applications. So we need to search for that. Once we've searched for them, we want to filter. So we can filter down and make our large complex app a very small, interactive, tiny dependency tree. And then that way we can explore efficiently as to where our errors are coming from. And then before I show the demo on this, it's built on Cytoscape JS, which is a really nice JavaScript graph library, because that's what we're doing here.

So setup. To install. Currently it's on GitHub. So we'll do dev tools, install GitHub, RStudio reactlog. But it will be a natural dependency of Shiny in 1.3. So just wait a few months when that comes out on CRAN. And usage. Set an option for Shiny reactlog is true. From then on, it's just normal work. So we'll run our Shiny app. It'll start recording all of that history. And then finally we'll say, you know, command F3, which is the legacy reactlog key combination. Or control F3 if you're Windows.

And just to kind of make sure that we're recording everything, these are just a quick overview of everything that we are recording in reactlog. If you see some things that look familiar, great. If you see some new things, awesome. I hope you add them to your app at some point. But we are doing this just at surface level, with many more to come.

Demo: Pythagoras app

Okay. So fun stuff. Let's start debugging some Shiny applications with reactlog. So Pythagoras. I'm gonna do a demo application with Pythagoras theorem. Because if I were to explain that I'm gonna calculate C, we already know how this app should work. So we have our input A. We have an input B. We calculate some A squared, B squared, maybe a C squared, and then a C. And now we have our answer to be displayed with a render method. So we can already kind of build this horizontal graph in our minds as to what this should look like.

So let's take a quick look at the app. So we have my server function here, where I have input A goes to A squared, B to B squared. And reactive A squared plus B squared is C squared. And C. We're good. Then I have some two outputs. Output C text and C verbatim. Should be good. So let's run this app. I have my options set up at the top of the script.

And let's change to the other well-known triangle, where B is 5. And C updates value. Great. And then let's change this to A is 12. So now we have C is 13. Great. Let's check out the reactlog for this.

So command FN or F3. And this is what we get. So this will default load to where the first time that Shiny is idle. It's waiting for the user to do some interactions. So we have our A value, our B value, calculates A squared, B squared to go into C. Or C squared to C. And then we have our two outputs. As we step through, the first thing that I did was I invalidated B. I set it to the value 5.

So as B gets changed, you can see the input value is changed here. And then invalidation will propagate all the way down. So now all the B squared, C squared, C, and everything has been invalidated. So we'll back out of our invalidation process. And then now we can start our calculation process. This is where the reactivity code is being triggered. So the C text is wanting to calculate. It needs C, right? And so now we're starting to go back up our tree. I already have A squared. So we can just pull that value. We're good. Calculate and down we go. Same with C verbatim. But since we already have C calculated, no need to duplicate that. And that is your typical shiny interaction. It's working how we want.

Demo: broken Pythagoras app

So broken Pythagoras. Now let's switch this model up, because we know what to expect in our model. And let's switch to that one. In this case, I was a little sloppy when I was creating my application. It looks good on the surface, but, you know, I may have some bugs coming into my code.

So let's kill the app there. And run app. So here let's do the same interaction. I change it to 5. Well, C print didn't change, but C did. So that's odd. And then let's change our A value to 12. And C changed again, but C print didn't. So now we have some odd behavior.

So let's explore what's going on with that. So this is not the same graph that we saw prior. There's some ghost edges, some missing edges there, and also we have no connectivity to C verbatim. So we can double check that as well. So as I stepped through saying that I updated B, nothing happened. Actually A was the next reactive element, because B in the app is not connected. In this example, input A is calculating B squared. And so that is now a bug that I have found.

If I wanted to, I could change it quickly here and go back to B. And if I change now B to 5, C should update. Maybe I didn't save. But it would do the same thing in there. Oh, I didn't use B squared. That was the deal. Sorry, I have more bugs in the code. I found more.

So actually what happened, B changed, right? That's what I did there. But there was an isolate call, a rogue isolate call. So it did not propagate through. And that is one of the trip ups, as I just experienced as well, that you can have with reactivity. So we could remove that isolate call and it would be fixed. There's many more things that we can do. But for presentation time, I'm going to continue on.

But there was an isolate call, a rogue isolate call. So it did not propagate through. And that is one of the trip ups, as I just experienced as well, that you can have with reactivity.

Searching and filtering in reactlog

So let's show off some searching within reactlog. Joe yesterday demoed his crayon whales application. It's a very standard application, but it's also a very dense application. There's a lot of reactivity going on underneath the hood. So if we want to run this, we just need to set our flag again. And then we can say run GitHub, his crayon whales application on the synchronous branch. We'll just make it easy for us.

So we'll download the repo. Open up the application. And click through as Joe would click through. It's downloading the data, parsing the data right now. And displaying. Perfect. So we have our crayon whales. Let's open up the other tab. Whales by hour. And let's I'm marking a time point right now. We got the notification. Good. Let's change the top end downloaders. And then we have a detail view. So I'm just stepping through as Joe did at a much faster pace.

So let's double check that reactivity. This is a very dense application. It's not that a big application either, but it gets dense very quickly. One of the views that I remember seeing was the detail view. Here where we have three bits of information. So I can look at that reactivity. That detail view is this guy right here is the important part. Because he has three bits of numbers and also a plot that is produced. So what we can do, even though this is quite small, if I double click, I'll then filter down to just that parent tree. So I get all the descendants and all the ancestors of that particular node.

If I wanted to do it a different way, I can search for detail, and I have some extra arguments, detail, download, and now I'm back to that one app. So that makes it very quick, even if you have 2,000 reactives, you're not going to be overloaded. I've experienced one app that has over 2,000 reactive elements on start. And it's open source, so it's great to test against. Works great.

Things to remember and future ideas

So some things to remember for the reactive coding. Any outputs that are not visible are not calculated. This is on purpose. This is a direct design decision by Shiny, so you may see some output elements that just hang out invalidated forever. They're not on display. So you may need to open that second tab or do something else with that.

If new reactive elements are created for every user interaction, such as a click or an input value change, you've probably coded what's referred to as an anti-solution. Joe talks about this in his Shiny DevCon presentation. And what it will do with reactlog is it will cause the reactlog to explode. It'll get very big because more reactives will be done for every user interaction. So if this happens, it's okay. Your Shiny app isn't exploding on memory, I promise. There's garbage collection going on, but the graph will be quite big right now. And then reactlog is not a performance debugger. It's a reactivity debugger. If you want performance debugging or inspection, please use Profvis. It's very good at that and it will tell you very quickly where to change your code.

And then last word of caution. Do not keep reactlog enabled when deploying to production. Your memory will be very mad at you when we're all done.

Do not keep reactlog enabled when deploying to production. Your memory will be very mad at you when we're all done.

So future ideas. I want to visually differentiate user sessions. Right now everything is all displayed as one graph. You may have independent graphs show up, which is just fine, but it's nice to maybe silo off a user so that it's very visual as to what's happening at one time. We also want to display the values of the outputs and reactive values. Currently we only have it for the input values, but that's how a standard reactlog is working. So that will come up in the next version of reactlog.

We also want to do expandable and collapsible groups. This is one of my favorite ideas about this project. Things such as render plot. Render plot actually inserts like four or five different reactive values for every render plot function. It's not something that you did. It's something that the render plot function did. So we should be able to collapse that down, just say bundle it up to render plot. I don't care how it works. Just gloss over that for me. This would also help for things like modules. If you know your module is solid, you could collapse it down to a single group and then we'd be good to go.

Also I'd like to remove objects from the reactlog graph. So if you happen to have coded an anti-solution, that's fine. But if the other elements have been garbage collected, we should remove them so that they don't present a possible state that's kind of confusing. It's not there on your machine, so I don't think we should have to worry about it anymore.

And then finally, I'd like to create a super app of reactlog and Profvis that will both analyze the performance or time aspect as well as the reactivity and the state of your Shiny application in one tool. Similar to Profvis, as if we step through on reactlog, it would highlight the code in your reactivity where it's being done. That's a super app and a dream. So we'll see.

The GitHub repo is done at RStudio slash reactlog. Don't forget to enable it before you run your Shiny application. For just tips on Shiny reactivity, there's the link to the RStudio articles and slides can be found on Bitly there. And then apparently everything's good with you guys, but do you have any questions? Thank you.

Q&A

Do you have record capability or versioning? Because this would be really fantastic when you put out an app and you know the reactivity's right, to have it versioned as well as the step through basically recorded. And then when somebody makes a change, it breaks something, you can see the difference.

Yes. So, I don't have a comparison tool set up for that. But the way that we've made the page to show up is that it is just a single web page that's savable. You can just download that as is and then you can compare the two Chrome pages after you even shut down your computer and you'll be just fine there. That way we can keep like a working Pythagoras up, right?

Does it have or could you add source code annotations? Like which file, which line something was defined in? It's currently being exported from Shiny. It's a feature that we do want to do. So it is in the workings.

Things like render UI, insert UI and stuff that changes HTML, is there a plan to be able to log that? Does that mean that they're changing the HTML or? Like, there's render UI in the script and I'm creating a new object. And the user's creating a new object. Is there a way to track that the HTML is changing?

I would have to defer that you would understand the method being run. I can tell you that when it's being changed, because the output value will be set with render UI, you have your output dollar, my area will be updated, so I can tell you when it's being set. That's where I would like to have that grouped module set up where I can say this is running through the render UI method right now. But currently it will just show some temporary variables that are used.