Mangling Ruby with TracePoint
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 50 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/37478 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Producer | ||
Production Place | Miami Beach, Florida |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
Ruby Conference 201317 / 50
2
3
7
13
15
17
18
19
28
29
35
39
44
48
49
50
00:00
BitMetropolitan area networkInternet forumCASE <Informatik>TheoryGame controllerXMLComputer animation
00:23
BitGame controllerReal numberContext awarenessForm (programming)Meeting/Interview
00:47
Context awarenessFamilyWebsiteSeries (mathematics)Exception handlingQuicksortDifferent (Kate Ryan album)FreewareGrass (card game)Asynchronous Transfer ModeTheory of relativityTouchscreen
01:39
Set (mathematics)Point (geometry)Cartesian coordinate system
01:59
Different (Kate Ryan album)Demo (music)Cartesian coordinate systemEvent horizonVideo gameCycle (graph theory)Replication (computing)
02:22
Event horizonPoint (geometry)Social classBlock (periodic table)Multiplication signException handlingSystem callThread (computing)Error messageLine (geometry)TrailCartesian coordinate systemCASE <Informatik>CodeEvent horizonCausalityMereologyRewritingModule (mathematics)Letterpress printingHookingNumberSet (mathematics)BitGraph coloringPoint (geometry)Cycle (graph theory)MathematicsCodecVideo gameQuantum stateWritingComputer animation
05:41
Demo (music)Demo (music)MereologyCartesian coordinate systemTrailSocial class
06:02
Symbol tableComputer programmingComplete metric spaceBitSocial classDemo (music)System call
06:24
Decision tree learningComplete metric spaceBlock (periodic table)Instance (computer science)Cache (computing)Social classMultiplication signMathematicsPointer (computer programming)Letterpress printingObject (grammar)Instance (computer science)Event horizonTracing (software)BitPoint (geometry)InformationRight angleQuicksortSystem callBuilding
07:46
Normal (geometry)InformationForcing (mathematics)Object (grammar)System callFerry CorstenTracing (software)Block (periodic table)TouchscreenPoint (geometry)
08:19
Normal (geometry)Reverse engineeringLine (geometry)CountingTotal S.A.System callSocial classSymbol tableFerry CorstenTouchscreenFunction (mathematics)2 (number)Computer programmingInformationMultiplication sign
08:55
Social classObject (grammar)Symbol tableInheritance (object-oriented programming)System callTotal S.A.Object (grammar)Function (mathematics)InformationTouchscreenEndliche ModelltheorieComputer programmingBitCASE <Informatik>Letterpress printingPoint (geometry)Right angle
09:37
Cellular automatonFiber bundleCloud computingScripting languageGroup actionLine (geometry)TouchscreenSubject indexingCartesian coordinate systemVolumenvisualisierungComputer programmingUniform resource locatorProcess (computing)Multiplication signPhysical systemStandard deviationGame controllerGoodness of fitElectric generatorComputer virus
10:55
Game controllerComputer virusBootingComputer fileGroup actionBootingTouchscreenOpen setRoutingComputer programmingGame controllerCASE <Informatik>Network topologySampling (statistics)
11:21
Fiber bundleServer (computing)DemonWechselseitige InformationData structureDirectory serviceServer (computing)Web 2.0NumberGraphical user interfaceComputer animation
11:40
DemonInformationCloud computingTotal S.A.System callSymbol tableModule (mathematics)Social classHash functionCache (computing)String (computer science)Object (grammar)Computer fileProcess (computing)Context awarenessVolumenvisualisierungBoom (sailing)Multiplication signSystem callSocial classInformationQuicksortLine (geometry)NumberDemo (music)Point (geometry)BitGame controllerComputer programmingCodeLetterpress printingObject (grammar)Computer fileCartesian coordinate system2 (number)Personal identification numberFigurate numberLocal ringMetric systemModule (mathematics)Real numberDoubling the cubeQuantum state
14:46
Demo (music)BitProcess (computing)Java appletComputer animation
15:06
AbstractionInterface (computing)Java appletInterface (computing)Process (computing)
15:32
Computer programmingExecution unitVotingSocial classBuildingInterface (computing)AbstractionInformation managementIntelConvex hullBlogAbstractionSocial classInterface (computing)Different (Kate Ryan album)Type theoryGroup actionSheaf (mathematics)GodComputer animation
16:38
OvalInterface (computing)Social classLine (geometry)outputMultiplication signPoint (geometry)Interface (computing)Library (computing)AbstractionCASE <Informatik>Error messageCompilation albumAreaModule (mathematics)BootingProcess (computing)Right angleComputer animation
18:36
Interface (computing)Interface (computing)Social classLibrary (computing)Error messageRight angleCodeInterface (computing)Inclusion map
19:39
Instance (computer science)CASE <Informatik>Right angleAbstractionKey (cryptography)Game theorySocial classEntire functionInterface (computing)Data managementReal numberWordCodeModule (mathematics)Interface (computing)Line (geometry)Parameter (computer programming)BitService (economics)Impulse responsePoint (geometry)Factory (trading post)Instance (computer science)TouchscreenDemo (music)Object (grammar)Network topologyStandard errorNichtlineares GleichungssystemBlogError messageTracing (software)1 (number)Computer programmingExtension (kinesiology)HookingSystem callLoop (music)Block (periodic table)
23:28
Interface (computing)SurfaceComputer animationLecture/Conference
23:50
Meta elementSoftwareState of matterVideoconferencingEvent horizonComputer animation
Transcript: English(auto-generated)
00:17
Good, anyway, I'm Mark Bates, in case you couldn't tell by the side of the wall.
00:21
You probably all are a bit surprised that I actually am Mark Bates. You all thought I was this man, by my awesome stash, that I've been growing, see? You're all envious, I know. I've got to pick up a little mustache wax, it's getting a bit out of control. Actually I haven't shaved since I got here, so I look a little bit more like, like this man. Or at least I definitely looked like him last night, that's for certain.
00:43
So, real quick, like why am I growing a stash, this killer stash, that you see before you? It's for Movember. I know PJ and Katie talked about Movember yesterday in their keynote. So if you're not familiar with Movember, it's, you grow mustaches to raise awareness and money for men's health issues, like testicular cancer and prostate cancer, both
01:03
of which have affected my family greatly. So if you want to, I highly encourage you to donate to my Movember campaign at mo.markbates.com. In a related note, I am a voice behind a screencast site called medicast.tv, we do weekly screencast series on all sorts of different stuff, like Go and Ember and Angular and Ruby and Redis
01:22
and Postgres, and this month if you sign up, I'm donating a dollar to Movember as well, and to make it even better, use RubyConf 13 and you get the month for free. So it's kind of a win-win for everybody except for me, who has to pay the dollar. But it works out well, it's good. OK, so we're here to talk about mangling TracePoint.
01:43
At least that's what it says in your folders anyway. So what is TracePoint? TracePoint came out in Ruby 2, and it kind of superseded set-trace-funk, if anybody's ever used set-trace-funk before. And the whole purpose of TracePoint is to allow us to instrument our applications.
02:03
Right, it gives us all these nice little hooks into Ruby lifecycle events that we can use to figure out what is happening in our application, what things we're doing, what it's doing, some really fun stuff like that. And we're gonna use that here today. We're gonna do a couple different demos, and one of the first demos we're gonna build is actually instrumentation for an application.
02:23
Let's do a quick look at what kind of TracePoint looks like really quickly. This is from the documentation. I believe this was written by Zach Scott, who spoke the other day, and who's probably not here because he never attends any of my talks. The bastard. Probably at the bar. If you're looking for Zach, he's at the bar. Anyway, so this is what some TracePoint looks like.
02:40
We're gonna listen to the raise event. So every time that a raise, an exception gets raised in our application, this block of code up here is gonna get called. In this example, all it's gonna do is it's gonna print out the line number, the event, and the exception that gets raised. We enable the TracePoint, we do some silly math, and you can see that we get this nice little stuff printed out here.
03:01
So in essence, this is really what TracePoint is all about. You hook into an event, you give it a block of code, the block of code runs, and you can do really cool shit with it. So I said there's different events we can listen to. There's about eight or nine, I can't remember the exact number. The first one is, the first set is class and end. And class listens for class and module, the beginning of class and module definitions.
03:24
And end listens to the end of class and module definitions. And we'll use the end one a little bit when we do some really terrible things to Ruby that we shouldn't be doing. But it's really nice. But what the end doesn't do, it doesn't listen to method definition ending, so that's an important thing to note. And that's actually one of the things missing from the lifecycle hooks of TracePoint.
03:42
We can't listen to when a method definition ends. But we can get class and module methods, which is cool. Call returns C call and C return. I loop, I lump these together cause they all kind of behave the same. Call and C call listen to Ruby method calls that happen.
04:00
The call one, the call event listens for Ruby method calls. So anything that happens in the Ruby layer. And C call listens for anything that happens in the C part of Ruby. And the same thing returns. So return gets called whenever a method returns. So basically on every method. And C return whenever a method gets returned in C.
04:20
So put them together cause they're really useful. So if you're gonna instrument, you wanna listen to every method call in an entire application, you wanna use both call and C call. Which we'll do later. If you're just instrumenting your application, your application's probably written all in Ruby. So you just use call. Raise, we saw Raise earlier. One of the things I like about Raise is, think about the,
04:42
I was talking to, I think New Relic, one of the guys from New Relic earlier. Jason, you here? Jason, there he is. We talked about this over lunch. And I was saying you could rewrite something like Airbreak or Exceptional or any one of those error tracking using TracePoint new Raise and give it a block of code and then have it take that exception and post it to anywhere you want.
05:02
So in about like five lines of code, you could build a gem that listens to every exception in your entire application. Regardless of where it's called. Alright, that's pretty cool. That's some really cool stuff. Let's see. So bcall and breturn. These listen to block calls
05:22
and block returns. So whenever a block ends. There's no acall or areturn, in case you guys were wondering. Just doesn't exist, which is a shame. It'd be fun. Then there's thread begin and thread end, which do exactly what you think they do. They listen to the beginning of a thread when the thread gets spawned up. And then when a thread ends, you get thread end.
05:42
So now the fun part. We're gonna write a little demo here. We're gonna write a demo that's going to listen to a Ruby application and whenever a method gets called, I'm gonna keep track of it. So we can find out exactly how many method calls happen, which class they happen on, which methods get called in our application.
06:02
Alright, so here's a little quick example of what this is gonna look like. I'm gonna require my little tracer program and we'll build a tracer program in a second here. And I've got a nice food class because a demo wouldn't be complete without a food class. Um, I was gonna do a Fibonacci generator, but it was a bit too much. And I called foo.new.bar
06:21
and this is what dumps out from my tracer program. And we'll look at that in a little bit more in a second here. Um, so to do that, I'm gonna require, I'm gonna build a little trace collector and this is gonna hold the information about what we're building. Okay, and the things we want to capture are the class name, the method on that class that gets called, and the amount of times
06:41
it gets called. So build this little simple singleton here. It's gonna hold onto all the calls for me. Um, give us a nice little way to iterate over each of them. Um, build a nice little method missing because I hate calling dot instance on my singleton classes. Um, make that catch up here. And go a little faster, go a little faster.
07:01
By the way, don't you like my typing? I'm like, you're pretty good. So our collect method is our big method, right? Um, it's gonna take a trace point object that gets yielded up whenever one of these events happen. Right, and the first thing I need to know is the name of the class. So to do that, I can do tp.self. And I think this is a poorly named method, but what self returns when you call it on tp
07:22
is the object that the event occurred on. So in our case, foo. It's a little poorly named, um, but very useful, right? So now with that, we can get access to the class name. Um, I do a little math because nil class won't print out, obviously. We need to try to print it out. Um, and then finally, I'm calling the method id.
07:41
tp.methodId returns back the name of the method that gets called, and I increment it by one. So now we just need to create our tracers. So that's just, like I said, all it's doing is holding on to this information for us. Um, our tracers are even simpler. Right, we got two tracers. We want to listen to C calls. We want to listen to, you know, and the regular call.
08:01
We want both the Ruby and the C stack. Um, we want to trace both of those. So what happens when that happens, we're gonna create a tracer, and it's gonna just take that tracepoint object that gets returned to the block, and we're gonna send it to our collect on our trace collector. And then finally, we enable it. That's it. That's all we do.
08:20
Oh, here's an at exit. I'm not gonna explain the at exit, but it's just formatting and dumping out all the shit to the screen that we saw a second ago. Um, so it's just there for completeness so that you don't think it's a magic thing what's happening that tracepoint gives you. Um, so now when we run our example again, this time we'll let you look at the output just for a sec. I'll let you look at the output for a second.
08:43
Very simple little program. Go fubar. There we go. We get an output that looks like this. This isn't all the output. This is some of the output. All the output looks like this. All right, so we can see that in that program, I had 14 method calls
09:01
once I enabled the tracers. Now there's some information here I could've stripped out. Um, certainly the tracepoint stuff itself. If I really wanted to, I could strip that out. Uh, same thing with some of the object stuff where I'm just printing stuff out to the screen. Um, but we, so we, but, you know, it's like screw it. It's just a demo. Um, but you get the idea, right?
09:21
So we can find out some more information. So in our foo, if we look at our foo model here, we can say bar and initialize get called once, right? That's the, in this case, in our case, that's the information we're really interested in. We can do more than this. This is a very simple program. What if we built something just a little bit more complex? What if we instrumented this in a Rails application?
09:42
I'll just, I'll tell you what the Rails application is, and I'm gonna ask you guys to tell me how many method calls you think occur in this Rails application. So it's a hello world application. All right, I'm gonna build the application here. You'll see the entire application. It has one controller, hello controller. It has one index action, and it has one line inside that index action
10:02
that calls render text hello world. That's the entire program. I will hit hello world one time, and then close it out, and we'll see the instrumentation kick out all the method calls in the system. So my question is, how many people think it's under 10,000 method calls? One person.
10:21
How many think it's over 10,000? Okay, everybody, good. How many think it's over 20,000? Over 50,000. Over 100,000. Okay, over 200,000. I'll go crazy. Who thinks it's over 200,000? Okay, no one thinks it's over 200,000. Well, let's see what the answer actually is. The answer may surprise you.
10:41
So here we go. Rails new hello world. This is a standard Rails 4 application. I'm not doing anything funky. This is the entire process as you see it. Generating controller, hello. And, um, it should spit out some stuff. There we go. It spat out all its stuff. I open the controller. I'm defining that index action. All right, I'm gonna render the text,
11:01
hello world, to the screen. None of this should be surprising. Open up the routes file, add a route for it. In this case, the route route. All right. Nothing fancy here. Nothing up my sleeves. And finally, I'm gonna, at the boot RB file, I'm gonna add my tracer program. This is the same tracer program we just used to do that foo example. Nothing has changed here.
11:22
You can even see the directory structure I use for when I write my talks. There we go. And I save it. Now I'm gonna start the Rails server. As you can see, I'm using WebBrick. I'm not using any fancy web server here to inflate these numbers. I go to Chrome. I hit it. Hello world. Fantastic.
11:41
You see it hit once. Boom. Ran down. 122,538 method calls. Now I'm not trying to slam Rails here. I just, I just, I, I, that number blew my mind away.
12:01
I was like, I can't believe how many are there. And wait till you see some of the other numbers. So here are the top method, here are the top classes. I'll show you the top classes and the top method calls. It's kind of scary. So class has almost 26,000 method calls on it. Rails initializable initializer has 16 and a half thousand calls.
12:21
I don't know how many of you don't know what that class does. It seems a bit excessive for something that says initializer in it twice. That seems like a, a bit, a bit much. Now module and string and array, you expect to see a lot of method calls. Now notice, like, they're, like, they're, like, close, but, like, I mean, object only has 4,500 or fixed amount, right?
12:42
I'm, I'm, you make a lot of those in, in a Ruby program. So I expect to see a lot of method calls in there. But this one really does scare me a little bit. But let's look at the hello controller. Now, I've got another little quick question. So you saw I hit the, I hit the hello controller how many times? Once. How many times should initialize get called on that?
13:03
Come on. You would think once. How many times do you guys think it actually gets called? Oh, come on, throw out a number. 16 and a half thousand. That would be awesome. It actually gets called four times.
13:25
I'm not here to indict Rails. I'm just, just, I just think it's quite funny. You know, and if I really wanted to take this a step further, I was thinking about this last night in my hotel room, maybe I should take this demo a step further and use TracePoint to then pin, to dig into that and find out which files were calling
13:43
initialize four times on my hello controller. Right, so we could do that. I'm not going to cause that's just kind of beating a dead horse. Right, and this is not about figuring out why Rails is slow or why Rails calls these methods so many times or whatever. That's not the point. That just, I was just showing an example of a complex application here. But we could do that. We could use
14:01
TracePoint to do that. And you see we have the start of a program that is doing that for us. Right, of using this instrumentation that's built in, built right into Ruby in 2.0 and above to let us do that. And we could use this to build, you know, kind of, you know, benchmarking metrics. We can look at beginning and end of a method definition, beginning and end of a method call,
14:21
see how long it takes to return and then say, OK, these are all the methods that take over a second. These are all the methods that take over a certain amount of time. Print those out. We could gather all sorts of really incredible information with just a few lines of code. The largest amount of code I wrote in this example was the code to print it out at the end. You saw the rest of the code I wrote.
14:41
It was just a few lines to actually capture all this information. I promised you a little bit of mangling. I promised you I was gonna do something really terrible to Ruby. And I'm going to. I was not joking. I've got a really, this is, yeah, this is terrible. Anybody here like Java?
15:00
Aha! OK. There you go. Anybody like when you put Java and Ruby together? OK. And I don't mean JRuby. We're not gonna talk about JRuby. All right. So I came from a Java background. I did, I've been doing Ruby since 2005. Before that I was doing Java for years. And one of the things we had in Java were these terrible things called
15:21
abstract interfaces. You see where this is going, don't you? I have to say that. Please do not actually try this at home. I wrote a blog post a couple of years ago where I talk about this. I was bored
15:42
one day, as you do, and I said, geez, I wonder if you could do like abstract classes and interfaces in Ruby. And so I wrote this blog post kind of hypothesizing the different ways you could potentially do this. It was just a thought experiment, just kind of playing around with some stuff. And I got two different types of people responding to it. I got one group of people saying, oh my god, that's awesome,
16:01
you should write a gem. And then, and then other people was like, oh yeah, I also, I actually wrote the gem. Don't worry about it. They're like responding to each other. Like, I got your back. I wrote the gem for it. Don't. And another group of people who yelled at me, like, like were really violent with me on, on, on my comment section. Like, how dare you do this in Ruby. And if you want interfaces,
16:21
go use Java, you dick. And I was like, whoa. Like, that seems like overly hostile. Like, I'm just talking about interfaces. So I just wanted to make sure I got that out in advance, that I am not actually advocating this. But if you take it away and do something with it, it's all on you. So here's, here's an example of an interface in Java.
16:41
All right, we have a bicycle interface, and it says, anybody who implements me has to define these three methods. These three methods are required by me, by me the bicycle, to be considered a bicycle. All right. And then I have a class down here that implements that bicycle, the Acme bicycle here, and it has to define those three methods. If I don't define those
17:01
three methods, what happens is the JVM, when it's compiling, will kick up an error at compilation time and say, whoa, Acme bicycle, you're missing a method or two. You have to implement these things. And execution stops. Like, it won't even get past compilation. So I want to do that in Ruby. Right. I don't want it to happen that when I'm using a library,
17:21
it says, oh, you're missing a method. I want it to have happen at boot time. When Ruby starts actually looking at the method definition, it stops and says you're missing methods. That's, that's the goal here. I want to be able to do something like this. All right. I want to have an API interface, include an abstract interface module, define a couple abstract methods,
17:41
and then have another class down here in HTTP library, in this case, that should spit up errors as soon as Ruby parses this class definition and says you are missing shit. Now, one, one place you would try to do that is here, right, is the include line. You'd say, OK, when this is included, let me try
18:01
and figure out if HTTP library has these missing methods. Now here, this would work fine. This example, that would raise an error and you'd be, you'd be golden. However, in this case, if I try to do it here again, I am defining get and put. The problem is, at the include line, get and put have not been defined yet on
18:20
my class because it has not finished parsing the class definition, so it would stop execution and yell at me that you're missing these methods, and then you spend, you know, three hours trying to figure out what the fuck is going on. I've defined them. But they don't exist at that, that point in time there. So here's what we're gonna do. I'm gonna show you a little example of this here working.
18:41
I like doing that. I'm gonna require my interfaces library that I've built, and I'm gonna define the exact same code we just saw here, our API interface. Include the abstract interface. By the way, these robes are really hot. I don't know if anybody's worn one of these things yet. They are really, really hot, but I'm not wearing any pants.
19:02
Um, so I'm kinda stuck wearing it for the rest of the talk. Um, that's the thing with me. Apparently I've stopped wearing pants to talk, so I did a keynote in a half tuxedo this summer because I forgot my pants. Um, as you do. So anyway, so we got this, we got this great class here, and when we run it,
19:22
we get this. We get an error as soon as we try to run it that says you must implement the following methods get and put. That's what we're going for. That's the, that's the end goal here. That's the dream, right. Notice I wasn't trying to use the library. I was just defining it. Just defining my class, and this, this got called. So how do we build this? Oh, here's
19:43
here's it actually running successfully, just in case you don't believe me. So get and put. They're in there. I run it. It ends successfully. Nothing blows up. So that's the dream, right. That's the goal that we all want in our lives. So dude, it's actually pretty simple. Um, we build this abstract interface and it's got a couple
20:01
things on it. Nothing important. Uh, it has a standard error, a nice little not implemented error here, just so we can print it out to the screen. Um, I realized I was like, oh, I could have made this more of a boat. What happens when you start doing these demos, like these kinds of talks, you're like, oh, I could actually add this to it, and I could add that to it. And you miss the whole point of don't do it, not doing this. Um, but you could say, oh, this class
20:20
requires these methods and what have you. Um, but anyway, so I have an error there, and then I'm just calling the extent of the included hook on module. So when somebody includes the abstract interface into it, I add some class methods. Uh, and the only class method I add is the abstract method call we saw, which takes a splat of methods
20:41
and that's gonna just pass that off to an abstract method manager, which we haven't written yet. I'm big on singletons in case you haven't noticed. I even used manager because it's very Java-esque. I was gonna have a service and an impulse that generated the manager for me. In a factory as well, but I thought that was a bit too, a bit too much.
21:00
Um, so the abstract method manager takes, uh, has a method called add, and it's gonna take two arguments, the class that is requiring them, um, that is requiring these methods, so API interface in this example, and then the methods that it wants, get and put. Um, so to build that abstract method manager, this is even simpler than our trace collector earlier, which was a little bit more complex. This is even simpler.
21:22
Um, again, it's a singleton. We just have an adder for the methods, and the key is going to be the class name, uh, and the values are going to be those methods that we want to get and put, um, here. You can see that in our add method here. Should've sped this up a little more.
21:41
And there we go. Okay, that's our entire abstract method manager. The real fun obviously comes in our trace. So we're gonna find our trace point. It's gonna listen to the end keyword. So whenever the module or class gets defined, it's gonna run this code. As soon as it runs this code, first thing it's gonna do is it's gonna grab all the ancestors for that class
22:02
or module. Um, and then it's gonna delete itself, and the reason we delete itself from the ancestor tree is in the case of the API interface, right, I say here's some abstract methods for API interface, and then if I call end, if I didn't do that line, if I didn't do that third line of deleting myself, it's going to, when you see the rest of the program, it's going to look at the
22:21
it's gonna loop through the ancestor tree, see itself, look for abstract methods, gonna find some, it doesn't define them, and it's gonna blow out. So we have to take ourselves out of the equation through this. So anyway, once we take ourselves out, we're gonna have an array that's gonna hold on to any missing methods that we need, and at the end of our block here, we're gonna raise that error
22:41
if we have any missing methods. I'm gonna point them all out here. Um, but now we're, the real meat of it is actually looping through all the ancestors. So we loop through each of the ancestors, and then we ask the abstract method manager do you have any methods? If it has methods, we then use that tp dot self, I'm getting ahead of myself here. Here we go. We're gonna use that
23:01
tp dot self call again to ask the object, hey, on your instance methods, do you include these couple methods we need? Do you include get? Do you include put? And if not, we're gonna add them to that missing methods. That's it. That's all the code I wrote to make that work. That, that little bit of code, I mean this is really the bulk of the code, that and the last
23:21
last file. So what, maybe about two dozen lines of code, and I built abstract methods in Ruby. I built this using TracePoint. I think it's pretty cool. I think it's really fun. So there's a lot we can do with TracePoint, and I'm just kind of touching the surface of it here. I mean I could go into much further details,
23:41
much further examples, but really the docs say it all, and you can see that there's lots of fun things to do with instrumentation, with mangling Ruby, and other great stuff. And that is it. I told you it was gonna be a quick one. So thank you very much.