We're experimenting with mirroring technical articles written by Best Practical engineers. This article is about a component of Jifty, our web framework.
Jifty's RequestInspector plugin provides you with debugging information for HTTP requests. You can examine the SQL queries that were issued in servicing the request, inspect a Devel::NYTProf profile of the request, and more. The information is presented in an (admittedly ugly) admin panel. Here's what our Changelogger's request inspector page looks like:
If we click into one of the requests, we can get an overview of that request.
We can inspect in detail a particular part of a request with another click.
The frontend is good enough. It gets the job done. But what I am really interested in, and what I had far more fun creating, is the backend that powers this.
RequestInspector is a Jifty plugin. Its job is to manage all of the other plugins that inspect requests (such as SQLQueries, DumpRequestResponse, NYTProf, and Gladiator). RequestInspector informs the plugins of interesting events such as: a new request is beginning, that request finished.
Each plugin produces various kinds of data. That data is completely opaque to RequestInspector. The data can be an object, a number, a code reference, or whatever else. All RequestInspector does is keep track of that data, handing the data to the plugin when it needs to do something with the data.
When a new request begins, RequestInspector calls the inspect_before_request method on each plugin. When the request ends, inspect_after_request is called on each plugin. The interesting bit is that the opaque data is threaded between the return values and parameters of these methods. The return value of inspect_before_request is passed to that plugin's inspect_after_request. inspect_after_request's return value is stored for later rendering. (In what amounts to a global variable — sorry!)
When the user wants to see a request, RequestInspector calls the inspect_render_summary method on each plugin. When the user wants to see the detailed analysis by a particular plugin for a particular request, RequestInspector calls the inspect_render_analysis method on that plugin. It is completely up to the plugin to decide what to render. Both methods receive the data it returned from inspect_after_request for that request.
You might wonder why this design is better than, say, a specific data format to which each plugin must adhere.
It is unclear what such a data format would look like. I don't think a single data format could usefully encompass the different kinds of data that these plugins produce. While SQLQueries's data is complex data structure with a lot of information, NYTProf's is basically just a random number used to track the on-disk profile directory.
The format would certainly evolve over time. This means that plugins would have to change as well just to keep up with RequestInspector. With my design, plugins really only need to change when they want to support newer RequestInspector features.
It's simpler for plugins to let them dictate how they want to store whatever they are tracking. It's simpler for the RequestInspector core too, since it is free from having to examine the plugins' data.
Finally, the RequestInspector plugin can get arbitrarily complex and the plugins do not have to care about it. The RequestInspector already lets you filter out URLs you know you will not want to inspect. The individual request inspector plugins do not have to implement that functionality.
In the future, I plan to add a feature to RequestInspector that lets you see which requests took the most amount of database time, or leaked the most amount of memory, etc. All the feature will require of plugins is that they implement an optional inspect_compare_requests method. The method will receive that plugin's data from two requests and returns -1, 0, or 1. So trivial that I should just do it now.. :)
Basically, RequestInspector leverages the Principle of Least Knowledge to keep things simple, easy, and flexible. Shriram Krishnamurthi's Moby Scheme Compiler talk was particularly influential on this design. He described a similar (though simpler) system that he uses to make programming fun and interesting for middle school kids.