Exposing your Applications Guts using IronPython
Application guts (or indeed anyone’s guts) isn’t typically on ones “list of things to see”. Quite often though, when presented with some perplexing behaviour on live, you end up wishing that you’d added a key piece of logging code to get you to the point where you had just enough visibility to be able to solve the problem.
As it turns out, if you’re writing a .NET application, there isn’t a tremendous amount of difference between a Debug and a Release build. That’s because, of course, the compiler isn’t spitting out real machine code, but rather MSIL, and if any kind of optimisation ever happens, it happens at JIT time. At the end of the day, if you’re talking .NET, the difference between being able to debug your application boils down to the availability of some .pdb’s and an INI file … that’s it.
Never-the-less, you may not actually have a copy of Visual Studio or WinDbg with Son-of-Strike installed on the machine that you’re interested in poking around your badly behaving live application with. That’s why you’d be particularly interested in employing the services of a worthy logging framework like Log4Net or the logging block from Entlib at the outset of your enterprise development stint. Just increase your log level to “debug” and you’re a-for-away … right?
There are a couple of issues with this:
- Typically, you need to restart your application or service to get the higher logging level into effect—maybe you have a situation where you don’t want to do that; you just want to view state?
- What if, even on debug level, you’re not emitting the detail that you need? At the end of the day, you’re at the mercy of the vigilance of the developer who wrote the debug entries—they may just not be enough.
How about bundling a little back-door into your app? You could:
- Get at it using something available on any PC, namely telnet;
- Do anything that you would be able to do using code, only dynamically.
Enter PythonServer, available on github. Let’s take it for a run using the (very) simple sample application:
static void Main()
{
Console.WriteLine("Starting...");
var theFibber = new Fibber();
var pythonServer = new TheLimberLambda.Utils.PythonServer(2323,
new [] { new NameBinding("fibber", theFibber) });
pythonServer.Start();
Console.ReadLine();
}
SimpleSample fires up a console, instantiates an instance of Fibber and starts an instance of PythonServer, listening on port 2323.
Fibber is just an implementation of IEnumerable<int> that spits out Fibonacci terms, but the key point is that it was instantiated in and lives inside the SimpleSample process. We keep SimpleSample from exiting by waiting for input on the console.
Telnet’ing into localhost on port 2323 gives us an interactive Python command-line, so legal Python will execute as expected:
The real kicker here though is that we have access to our SimpleSample process, and anything that we decided to publish is available to us (in SimpleSample’s case, that would include our instance of Fibber). Since Fibber implements IEnumerable, we can benefit from IronPython’s automatic recognition of anything IEnumerable as a Python iterator:
Here we’re using the itertools package that comes with Python (or in our case, IronPython) to grab the first 10 items of the Fibonacci series.
Because we’re referencing a single instance of Fibber, and because the state of “where we are” in the series is maintained, we can telnet in from a difference spot, and ask for the next two items:
Thus, we have a Python interface into potentially any .NET application.
Name Binding
Now, how did the name “fibber” become available to us, you may ask? The key is the IEnumerable<NameBinding> that we passed to the PythonServer constructor. At some point we need to provide some translation between the python namespace and the object instances of interest. Presently, PythonServer does this using a dead simple string-to-reference map provided up-front.
Going under the bonnet and taking a squiz at the code that gets executed when a connection is made to the server, we notice the introduction of a ScriptScope instance:
private void InitialiseScriptRuntime(Socket socket)
{
_ScriptRuntime.IO.SetOutput(new SocketConverserStream(socket), Encoding.ASCII);
_ScriptRuntime.IO.SetErrorOutput(new SocketConverserStream(socket), Encoding.ASCII);
_ScriptScope = _ScriptRuntime.CreateScope("py");
}
… and binding names is just a matter of setting ScriptScope variables, thusly:
private void BindScriptScopeNames(IEnumerable<NameBinding> nameBindings)
{
foreach (var binding in nameBindings)
_ScriptScope.SetVariable(binding.Name, binding.Target);
}
I will admit that PythonServer has a way to go, and could do with a whole bunch of things, including:
- A solid security model. At the moment PythonServer should really only be used in controlled environments since of course there is no authentication (or encryption) to speak of—SSH should fit nicely here;
- Integration of the name binding interface into your favourite IoC container.
As a start though, this provides a great means of getting into your process in a relatively hassle-free way.
IoC containers mostly work on type level, but the name bindings has to be done on object/instance level. After all it’s particular variables that you are most interested in monitoring. How do you envisage the IoC integration?
Also if IoC is configuration driven, then you’ll have to cycle your process anyway, to make your configuration changes active. How would you resolve this?
In a scripting language environment where all the objects live in some sort of global container this makes a lot of sense – you don’t even need to have name bindings in such a setup. Unfortunately in .NET this is not the case. And unless you explicitly expose the gut pieces you would be interested to look from the start you are in trouble. There is no good way of saying “give me variable at the state it is now” unless you somehow collected/referenced this variable beforehand for the purpose of exposing it via your backdoor. And then you have all sort of problems with garbage not being collected (weak references, anyone?) memory leaks, and concurrency issues if you modify any values on the fly and locking issues if any synchronization is involved.
Imagine and asp.net application where all the state is quire short-lived (as long as a request is being processed) what then?
Overall it is an interesting idea, but I’d love to hear how the problems above can be addressed to make the idea practical.