 Welcome, brave souls, to the last presentation, the last day, where you'd rather all be in bed waiting and preparing for the closing party. So, yes. So, we're going to have some fun. Don't worry if I go fast, I have lots of information that I'm very enthusiastic about, but don't worry, the slides are online, so they're all there to take a look at. So, what we're talking about today is attracting analytics from complex open-bedded builds. Oh, by the way, my name, oh, let me introduce myself. I'm David Reina. I'm a member of Wind River, a proud member of the Yachtoo project. And I've been working on the Maintainer for Toaster. I've been working with Open Source for many years, and I enjoy it very much, though. And the thesis of this presentation is the BitBig event system, together with the event database that comes with Toaster, which I maintain, can be used to generate and provide access to analytical data and provide new, unique toolset to solve difficult problems. And my goal is to introduce the concepts to enable people to take advantage of this stuff and the cultural opportunity to take advantage in your actual use cases and systems. So, yes, this is good started. The problems I'll be covering, the problem space for extracting and analyzing data, introducing the BitBig event system, doing a deep dive so I can actually demystify what events are all about and make them actually kind of a how-to, so I can kind of share my tribal knowledge of what I've learned about the event system. Do examples, practical examples of using events, creating events, consuming events, some gotchas to watch out for, a couple, guidelines, and some resources that you could follow on afterwards, links and information. And when I presented this idea to my coworkers, the first question was asked to me, my builds are working. Do I need this? No. You're in excellent shape. Congratulations. I'm happy. So, but if you need to do stuff like you're exploring new areas or you want to scale up to a build-bots system or build robots, we at Win River, I'm sure you guys too have, do thousands of builds every week, you might want to scale and understand what things are going on. So this is a toolset that you can play with. I also use it for debugging, BitBig, because I don't know all of BitBig. It's very complicated. So I've found ways to use events to kind of track down where things are happening. So the problem space, as I see it. Now, I'm sure you guys may have different problem spaces with analytics, but this is the problems that I'm looking at I'm trying to solve. The issues with time are coincidence sensitivity. That's overlapping builds, the order of things that are happening, the order of tasks and such. Transient data values, we'll show a couple examples of that. The transit UFOs, this is something that just plagues us. We get an error on one build every once a month. What's going on? We just don't know. It's not related to network. So we need to solve things around that. Issues with trends. Are the size of builds changing? Is the time taking changing? Is the cache missing changing? And the problem is scaling. You're doing one board, you do many boards, you do the multi-distro, the feature that just came out with the October project, where they're all been vetted. And the thing is, if the problem is the needle, where's your haystack that you can look for? So we need to accomplish, solving these kind of problems and addressing them is easy access to data. That data that has time and ordering to it, reliable interaction with BitBake. We don't want to instrument and start messing around with BitBake. Easy access to the data, ability to keep data long-term, to do long runs, not just a single build, not a couple builds, but to save a month of builds or two months of builds. So find the UFOs. And we want to keep BitBake pristine. We don't want to instrument if we don't have to, because that might change the environment. You might not do it right, introduce errors, everything about that. Problem space two. So there's well-known outputs from BitBake builds. There's our artifacts, which is your kernels, STKs. You've got your manifest, which has your licenses and image content. You've got your logs, probably the most important thing first off, particularly the error logs. You have your variables. You can examine what your variables are. And you know your dependencies. There's dependency trees and such. However, these only capture the final results, which you end up at the end of the build, unless you fail, of course, with an error. But they don't show you what happened in the meantime. They don't show the intermediate values. So it's hard. And it's also hard to correlate what happened between the log build logs that you get. They may be all successful, but maybe there's interaction that's messing stuff up. So the answer I propose is the event system that's already part of BitBake and the event database that we invented, and I think can be applied to this problem. Event system features built into BitBake. It's been there for years. There's a rich set of event types already existing that you can extract data from, more than 40. I'll show you some of those. It works built into Python XML or PC sockets. So it's already built part of, supported by Python itself. All the data marshals taking care of you. The data is safely moved from one domain to another. And it's very flexible. Events, as you'll see in a moment, are designed to be very flexible. They're not rigid. They're not enumerated. They're very, very flexible to your needs. Easy to attach custom handlers for your own data. And it's easy to add your own event types. I will show you an example of one of those at the end. And here's a quick overview of the many available event types. Now, how did I get this list? I did a grep for event fire throughout the code. And I also looked at the event queue that Toaster, for example, uses. There's a lot of events that we're covering. From build, start, stop, configuring, providers, the task queue, the scene, when your stuff is coming out of the cache, the set scene stuff, the metadata, the log records, commands, and a quicker exit. So you have a lot of hooks into the information that is going on in many different levels of the system. So... And the clients. Now, here's the thing that I did not realize when I first started, and you probably don't realize the other. You have been using events all this time. BitBake actually runs its own contacts and sends everything out of it through events. And they go to events to UIs, what they call. And the most famous is the one that you see every day, which is Knotty. This is the default one that has that very famous expanding and contracting tax list as you've been building your builds. So here's an event client. I didn't... yes? There's Knotty too? Oh, we're only... we're advanced Knotty. We've gotten to Knotty too. So some people say it's called Knotty, so they know what it means. I don't think you're being... So Toaster is one I've been working with. That is the event collector. We use it. This is... it has two parts to Toaster. We'll be covering not so much Toaster, but Toaster was designed to gather, build analytics, and also provide an environment to create projects. And that's where a lot of this event stuff and database came from. So this is the one I've been working with. It actually captures all the events coming out of BitBake builds and adds into their database, as we've seen. For complete, there's two other event clients. Dependency expression. This actually executes a BitBake command and gathers all the events related to dependencies, and then shows a GTK application that you can kind of browse through it. I didn't know it was there, but it's an event client. And end-curses, someone did an end-curses client. It just shows up a nice ASCII screen. Very simple, very straightforward. That captures the events and presents them. And these are just four of the ones. And actually, this is a very simple, easy model to extend. And I'll show you an example where I show you... You can create your own events thing and process the events your way. You want to see. So when you use BitBake, you're probably on the left-hand side, where you have BitBake, Naughty, 2, as your event client, and then to your user console. When I put on my toaster hat, when you put on your toaster hat, you get the middle column, where you use the toaster event client to capture the output from BitBake. It goes into the event database, which is SQL-based, and then people can then log on and use the toaster GUI server, which is HTML server, and it goes to your web client. So now I want to introduce, part of the purpose of this talk, is the third method. You don't need to use the GUI to take advantage of the database that you've gathered all your events into. You can write your own scripts. You can do CLI access to the data and manipulate, extract all the data you need. Now, there's many advantages to that. The GUI may not serve what you need. Your build machines may not want to support a GUI. You may want to do a lot of analysis. You may want to attach this to a CI system. We'll be talking about that. But this just opens the world to the command line interfaces to automated tools to start tracking at a distance what's going on in your builds through the central facility of the event database. And let's just say an example. And this is what I've been talking to a lot of people here at the conference. And I said to people, we got the information, just write an interface to it. And it's easy to actually attach this to continuous integration build system. Today, what you're doing is the left. You have the Canadi event client. Your continuous system will be calling pit-pick with. You're throwing away most of the data from the event system. You're getting the important stuff, the fact that it succeeded or failed, the fact that you got the artifacts, the fact that you got the logs. But you don't have all the stuff that happened while I was building stuff. You're throwing that away. So all you need to do, instead of using Canadi 2 as your event client, to use a toaster event client to gather the stuff, put it into the database that you keep, part of your content with your continuous build system. And then you have that information together with the builds. And you can look at that now. You can look at that later. You can now have a persistent capture of all the events that happened while it was going on. And it's very easy. You just walk one event client for the other. So easy. Why didn't we do that before? We should. So database instance is very easy to create when you come out of the box with toaster. You just start toaster up and use this SQLite. It's fast and easy. Small footprint. However, for large productions, particularly when you do multiple builds or if you're doing a CI system, you can want a production databases. Because SQL just doesn't cut it for that. So we recommend my SQL or my ADB and use that instead. So that can capture this. And that's more robust. It can capture all the information. And we actually have instructions how to do that to substitute the default SQLite with production system. And starting it, I just want to make it, it's really easy to substitute and do command line stuff. Normally you start your BitBake environment. You can source toaster. That will start the web server and start the client, the event client for it. And then you can start your Firefox and start using the interface, the web interface with toaster. But you don't have to do that. You can just stay command line. You can start your environment. You can source toaster and then just do BitBake. And you've already done what you need to do to get toaster database to capture the events. And what source toaster does, creates the event database if not present, also applies any schema updates. It starts with a client, but you can ignore that if you just want to do command line stuff. That's my point. And it sets up the command line environment to use toaster as the UI for BitBake. And the magic variable is bitbake underscore UI. So this is built into BitBake. So just to get an idea, some of the stuff that's already available as far as analytics from toaster is we already know how to capture tasks. You know how the time for all the tasks. You know the time for the recipes. We can look at CPU usage. And do you play with this? Try it out. This is just some of the stuff we have, disk.io. You can also look at the deep dive into the tasks and see the cache hits, the cache misses, the results of everything. So we already have a lot of stuff already presented in the toaster GUI, but you can do so much more. You can do direct access. And I wanted to touch on the idea of intermediate variables. As I said, when you're at the end of a build, you get the results. But with events, you get to see what happened as it was building. And here's an example of variables. The BitBake dashy, you see what the final values are. With analytics, you can capture what happened as it was doing. An example of BB files, we know what set it, what file, what line, and what it did for every single variable. So you have all of this history. So you know what happened. This is great for debugging. If someone's setting that variable in a way that you do not expect, you can find out who the culprit is. If you thought you set it and you didn't set it, you can find that your BB append or something did not apply. And then you'll know that it did not apply. So you can figure out why. That it happened. So that's just an example of many, one area of the intermediate values that you can pull out of this. So I just want to touch, I'll kind of go quickly through this. But I just want to share what I learned about how the event system works. The idea is that you can use this. When you start playing with events, you know where to find the stuff instead of spending the week or two that I spent trying to find out where all the stuff is. I will say the event system is documented. The event system is documented in code. So here's your guide into the how the event system works. It's like so many clever variables. I'll just say the event system is mostly for the internal people, but I think it really applies to everyone else. That's why we really want to share this information and make it easy to find where everything is. Event class, right there. And then creation examples, build.py. Event client registration, toaster and GUI. And Kanadi is two examples. Attaching event triggers to tasks. I give one example in toaster. That whole class, what it does, it attaches to events. And you said as an example how to do it yourself. Event handlers, I'll give you an example that toaster uses. So this is where the information is. Let's go in. The event class could not be simpler. Has one member at the base, which is the PID. That's all it requires. It's easy to extend. There's no enumeration. You just extend the class. Here I'll give you the example of the task class. There's a task base, which is, of course, derived from event.event. And that's the base stuff for information. And then you create that. And then you extend it with task start. The examples for task continue, task end, complete. Just extends the base class for task base. Could not be easier. Very, very simple stuff. And you can see some of the information that's captured into the event. The task name, task file, log file, the time it took. So just taking a look at these, you could find out the information that's already available in pass-through events. Event creation could not be easier. You just fire it. Create it either in line, or you can do it in a second example. I set it up in two steps, and fire it. Very easy. So in the first example, task start. The event class brings all the variables and creates the event on the fly, and you pass it on. The second example, you pass a dictionary to this one. The meta event is very simple to use. You just pass a dictionary to it, and you can pass any set of name value pairs that you wish to the outside world. Registration. What you do with your client is you just register for which events you like to listen to. Canadi 2. Register is for about 10 events. Toaster registers for about 35. And all you do is you get an event list as a list, and then just pass it when you start up. In this case, toaster starts a server, it runs a command, and one of the variables you can pass is the event list. Very, very simple. You just list the ones you want and go. It's easy to attach to events with your handlers. In this example, the toaster artifact dump data, it wants to know about certain things. Here's where you can fire it. I create an event, and this is where you attach to where you'd like it to do. In this case, we're doing post functions to populate SDK. In this case, we want to know, we want to capture what happens, the artifacts that come out of populate SDK. So you just find do populate SDK, attach a post function, which is this event handler is what we're doing here. And here's two examples. We're attaching it to SDK, and SDK underscore the extendable SDK. Could not be easier. There's a couple lines of code. Here's another example. Here's the add handler. You attach to events. You don't attach to a task or a class. You just attach to the event being produced. In this case, add handler is a built-in function that's part of the take. You just add a handler, and then you add define as event mask. In this case, I want my toaster artifacts for this case to trigger when the events BB run Q, Q task kips, Q task complete happens. So you can just attach to the event being created by anybody, or you can attach to before or after a specific task. Very simple. The event receive loop. Just example how it's easy to consume the events. This is example from toaster. All you do is you start your main. You start your event handler on your main loop. You wait in this case for an event because you want a timeout. You don't want to block waiting for an event, of course. So in this case, you wait up to a quarter of a second. If there's no events, you just continue. If there is event, you start handling it. And you can see it just if instance, is instance. That's how you enumerate through the events that have come through. Very, very simple stuff. Creating a custom command line event analytic tool. So I told people we got this SQL database. You can just write C like tools against it. And I said, I'll prove that you can be done. So I decided to write a tool. So I wrote a couple of tools. The source code is available. I give in the section. I wrote a couple tools, Python. So you can just grab and extend as you wish. And it's, by the way, in the header of the file, I tell you how you can find out the schema of the database and the ordering of the members of the database. So it's easy to find where stuff is. So if you look at that sample application, it's self-documenting about how to, one, create the database in the first place. How to query the database about the members and the structures. So everything is really easy. So here's a quick little example. This is a minimal Python script. I was surprised how easy it was to extract data out of the SQL database. You just a couple lines. You import SQLite3. You create a connection to the toaster.sqlite. That's the name of the database. You can pass parameters about where the database is. This is assumes it's right there. You get a cursor into the database, sqls. And you execute a select command. In this case, I'll just fetch the first member. I'm not going to do looping yet. And I'll just print it. And if you run this, I don't have to have the Python 3, but this little sample thing, it'll give you the first build. I also show you how you can select from the build. I get the name of the target that was built, and that's in a separate database. So I show you how you can use the value, the target value from the first, to then select into the target database. Oh, here we go. You got an example from a database, searching against the second database, and you can print both values. In this case, data build against the QMUX86, and there's all the pertinent information about the build, and there's the target that was run, which is data SDK CHRPath. So easy stuff. I was surprised how easy it was. So I decided let's write something more complicated. I decided to write an example. I've seen how Canadi 2 does all the output. Well, how does this stuff overlap? How do all the tasks overlap? How do the recipes overlap? Just how does that all work out? And this is not a deep problem, but this is what I wanted to write a tool against, just to kind of explore what I could do with it. And there actually are existing tools, like the PiBoot chart is an existing tool for doing this kind of analysis. My point was that this was easy to write. I wrote it in a day. And the idea is that you can write it in a day, and extend it to any kind of queries you'd like to put together to really analyze the data in a way you'd like, and also present the data in ways that might be interesting, or do correlations. But the idea was you don't have to be stuck with the tools that are provided. They're very powerful. But you can easily write your own. So here's my methodology. Select the build. Read the build's task list. Attach the apparent recipes to that from the recipe database. Reach tasks, find out all other tasks where executions were overlapping, because I have to start and stop time for every single task. And for each recipe, find all the other recipes whose execution is overlap. So I can not just get the overlaps for a task level. I can get overlaps for the full recipe level for all the tasks that are part of the recipe. I was just curious to see it. And the goals are to compute the histogram, because I like histograms. Review and understand the information that have no overlaps, and then compare and contrast to the tasks and recipes that do have overlaps. See the little overlaps. See if parallel stuff can be done to improve the build time. And the long-term goal, compare intermediate package failures. So I can start comparing against builds. I'll do that later. And this is the little command I wrote. There's a little quick little command set. You can start the build that you like to analyze. You can get a data histogram. You can look at the task recipes. Look at the events at the task level, the events at the recipe level. Look at the overlaps. You can graph the stuff, and I love GUIs. So I have HTML output also to show the output. And examples down below. So this is part of the program. And this is what I found out just from this simple analysis. The tasks for doing a simple core minimal build, 2,658 tasks, and the recipes were 254. Nothing too wild. But we have as many as 140 tasks to be able to run with all 24 available threads. Well, we have a 24 thread system, which I thought was pretty good. There were 621 tasks that ran solo. Zero recipes that ran solo. That means that no recipe ran by itself. Only tasks had that kind of atomic execution. There's one task, which I found out was Linux Yachto du Fetch, which overlaps with 983 other tasks. It takes a long time, it turns out, to download that darn kernel. So the second biggest one was Python 3 native. That also is very, very long. Orbelapse was 798. There were 69 recipes with orbelapse with 186 other recipes and all this kind of stuff. And how do I know this? I just printed a histogram of the data. Very easy with the application to do that. Very easy to pick out the information. And as I say, I love histograms. This is a histogram of overlapping tasks and recipe execution. There is a 614 that executed by itself in the top left corner. You can see down there's a straggler right down at the end. 981.23. One task, orbelapse with 983 other tasks. That was to download the kernel. And so by the histogram, I know what happened overall, where most of the stuff is at the bottom, but there are a lot of stragglers out at the end at orbelapse. And then comparing against how the recipes are all out. And there were 69 recipes at orbelapse with 180 others. And this is, I love graphs. I love HTML. This is what the color-wise output looks like. How tasks come in and come out. And just to kind of get a graphical feel of how things are going. The fact is it's very easy to program this. And the fact is that you don't have to just use built-in visualizers that come with toaster with BitPick. You can write your own very simply to present the information to your management, to the other people in your class, and to yourself. So custom event types, very simple. One of the simplest is using metadata event. Since all it does, it passes an arbitrary string name for the event name. And it passes an arbitrary dictionary list. So if you want to just insert a quick little event into the system that you would like to capture some specific information, use this one. Metadata event. Just randomly create a dictionary. Give it a name on the fly. In this case, my event. Meta event. Very clever name. And send it off. In the event handler, you just look for that instance. Compare the string name for the event type. And then you can start dumping the dictionary. Very simple to use. Now, here's an example. I want not Canadi or Canadi too. I want Canais. I want my own event receiver, my own event client, because I may want to be interested in specific events. I may want to be specific in correlating with other stuff that's happening outside the system or things that's going on. So I want my own. Easy to do. You just, in this case, what I did was I just cloned Canadi into Canais. I did a set to replace the word Naughty with the word nice. And I just added a little print statement in this place where just to prove that it was working, I just added a word to the print statement when you did not pass any Bitfake tasks. And if I run it, and then the command is easily, if you do a dash U, that resets the UI that you're going to use for Bitfake output. And so I just do Bitfake dash U, Canais, and lo and behold, it said nice, nothing to do. So, yes, I was able to attach my own event handler. Let's do something more interesting. The Canadi, Canais, doesn't happen to track dependency tree generation as one of the events. I would like to track that. So all I do is add that event to the event list then I populate the event handler. It turns out in Canadi they actually look for the instance of depth tree generate and do nothing. So I decided I'll instrument it. In this case, I'll just add a logger thing. I hit that event. So I could instrument it more, but I just captured this. And I run it with an actual target this time, actual thing to do. Bitfake U, Canais, and then quote native, and I hit the event. So it's very easy to extend. You just toss in the events you'd like to look for, your custom events, existing events into the event list. You call your own thing, you instrument it the way you'd like, and you can then do your own event processing. It opens the doors and stuff. Debugging Quentin and Data. This is how I use the event system to bug Bitfake. It's a huge system. I don't know how a lot of things work, how everything relate to each other. I had a bug where I was writing the system and I want to know when the SDK is generated, because then I can then gather that information, the name, the size, whatever, and put it into the event database for Toaster and show it off. Because we like to show you what results were from a build. It used to be that do populate SDK, or do populate SDK ext, when it ran, it would generate the file and your deployed directory, temp deploy, SDK, whatever. I stopped doing that in 2.2. What the heck happened? So I decided, I don't know, looking at the task, I can't see how it changed. So what I did was I decided I'm going to watch for that event, and I'm going to see when that file exists. So I just added this little line in logger. So I'm going to say, and when this event occurs, see if this file exists. So then I run my build in with the Toaster as the event client, and I find that, if I get a long list of events coming out of it, this is a long command line. I just put it out to file and then grep the file. That meta, the SDK file is deleted sometime between Deptree Generate and this meta event here, and I could find more context. I didn't keep it here, but it's deleted right before Deptree Generate, right after Deptree Generate is fired. And it's created not with do, with the SDK, but with estate build, populate SDK. That's when the file is created. So by doing this simple thing, I was able to find out when this file was created. It turns out that they changed the populate SDK. Instead of moving stuff to the deployed directory as part of its task, they deferred it to the cache, the estate cache stuff, and that's how I found where that thing was happening. So this is the thing about coincident data. This is just one small example, but you may have a lot of coincident data where you know something's happening. You don't know when. Generate the events, capture it, and you can then correlate exactly down to what event is happening. So that was fun. Got you some resources. The event system is very, very flexible. That can be very convenient. It can also be a problem if they change your mind about how things are created. So it's good to have unit tests on when you listen for events to make sure that events don't change on you and then you lose information. I discovered now all the events in BitBake are consistent. I was tracking on events from the native SDK in this example. And when it's a native thing, you usually get a prefix with virtual colon native or native SDK. It turns out that Bill gave it, but populate did not. There's two of the five tasks had that prefix, three did not. Little things like that. So in Toaster, we had to do some guessing. So if you find that you're trying to gate on events and you're not quite getting consistent data, look at the Toaster code. You probably have already solved that problem of having our code. We'll be fixing this in BitBake as we go along, but why torture yourself? Look at the Toaster code and find out how we solved it and you can just borrow that. Event database is buffered. This actually addresses one of the premises of the talk. How do we manage the thousands to millions of events that are coming? We buffer the data because we do not want to block BitBake. That is not smart. That's not nice to the Bill people. So we buffer it all. It also helps with the SQLite. We can batch the data instead of 1T2Cs. So that helps out BitBake. The consequence is that the Bill finishes, but we're still running for another minute or so. It's probably the database. So don't shut down your system. Don't kill your Python tasks. Better run to completion. So a benefit and a pause. The resources. There are some of the resources available. I've put the source code for that simple script. It's very simple. I grew it to the end of the lines because I like the HTML and filtering and everything. And we'll be presenting. I'll do some class exercises on this into the developer day on Friday tomorrow. We get to try some of this with your hands directly. So source code available. The toaster documentation is here. And we have a YouTube video about how to use toaster. So you can see kind of the data is coming out. Get an idea of what information is available to you. And toaster mailing list. Send us an email. Ask us about it. We'll help you out. Resources too. BitBake cheat sheet. This is a great sheet. It's put up by elinux.org about how to use BitBake. And some of this event stuff is captured there, I discovered. If you want to learn more about the event model, how it came about, we have a link there as we were designing it as we were going. And the original design information on toaster and the BitBake communication, how we tried to figure it out in the first place, there's a link there. I discovered all of this information. So I thought it was very pertinent. So I thought I'd share it with you. 15 minutes left. Questions and answers. Yes. In the back first. So the question is, what's the earliest version of the octoproject that supports this? Toaster's been around since 1.6, I believe. So we've had most of the event database since then. So it's been around for three years now. Something like that. It's gotten a lot more sophisticated since then. But yeah, it's been around for quite a while. But that's good. That shows it's been tested and used. And a second question. So the question is, what is the overhead for capturing events? Just a few percent. Not much. Yeah. So by buffering all the information, we can fill in all the IO gaps, use it up and what doesn't get done during the build then goes afterwards. So that you get your results, you get your build, you get your RFX as soon as possible because you don't want to hold stuff up. But yeah, it's just not very much. So the space does grow. But we can show you tools to manage and prune your information as you're going. And one of the questions I want to ask you is, what kind of data would you like to want? Because I want to gather more information, more analytics. And what use cases do you think you might want to solve that we could perhaps solve with this tool? If any of you guys have ideas, I'd gather that. Anyway, question. Oh yes, I am asking. Yeah, save yourself. So the question was, how do you track the package you care about? From completion, maybe it's delayed, maybe it's taking a long time, maybe you failed and you don't want to waste the rest of the time for the build and you don't want to wait until the end. I can give you a solution in five minutes. This is how I do it because it's a problem. I just attach the toaster, I start the toaster UI and I just build and I get this whole list of events and I cat it to a file. But instead of catting it, you can just capture it and start looking for a particular event name. So if you use this conice, conice one, and just watch for those events and watch for that name and then write out. If you watch for the log file to create, that's the ability to do coincident data. But you just watch for that particular recipe, set of recipes, and you can trigger on a certain event. If you know that something's going to fail, you can capture what's happening during that. In fact, if you use this tool today, I just showed you today and maybe we can try it later. And for that particular package, I can show you today what it overlapped in the time it took. So yes, that's a perfect problem that this can solve. More questions in the back. Great. So you're looking for advice or that's a use case. Yeah, very good. Can we talk after? I'd like to capture that and write that down. Any more questions? Well, anyway, thank you for coming for the last class, last day. Have some great fun, come talk, come play with Toaster, and let's have fun open source. Thank you.