This is the second topic of the “Black Box” series, bringing us to Python. For people interested in part one — Generated Code — please click here. In this post I’ll go through how to take a simple Python script and instrument it using the AppMon Native ADK. I will also shed some light on the most common pitfall when instrumenting Python scripts and how you might be able to work around it.

Let’s start with the example script. The code below is the complete script and while it isn’t doing much it will be able to show how to start the agent, how to create new PurePaths, how to capture function timings and arguments and more.

The handleRequest function is the entry point of the script and it prints out whatever argument you pass to it. It could be visualized as a web request handler, which is executed once with every web request. The handleRequest function calls the executeQuery function three times with different arguments, simulating a function executing database queries.

If we execute the above script, we get the following result printed out.

To instrument the above script we would first have to import the Dynatrace library by calling import dynatrace at the top of the script. The Dynatrace library is a Python wrapper which contains all the code needed for Python to communicate with the native ADK. The library is tested with Python 2.7 and 3.6 and can be downloaded from here.

The next step is to initialize the agent using dynatrace.init(). The initialization only needs to be executed once per process, so normally you would call that during the startup of the script. The init function will set up the connection to the AppMon collector and start monitoring the process. There is no need to call the uninitialized function at the end of the script, as the uninitialize function is registered by atexit, and will therefore be called automatically.

Within the Dynatrace library there are a set of default values on line 19 to 24.

If you need to change any of these values, for example the name of the agent, you can either change the default value within the Dynatrace library, or pass it as an argument to the init function.
dynatrace.init(agentName=“MyAgent”)

Once the agent is injected we will also need to add instrumentation into the code, for example to start PurePaths and capture functions. The two functions we will use for this are start_purepath and sensor. Both functions are contexts, so there is no need to call a function such as exit or end_purepath.

The start_purepath and sensor functions will not need to be passed information about the monitored function such as the function name, line number, file name and the arguments of the function. That is handled within the Dynatrace library using inspect. If you don’t want to use the automatically captured information you can also pass your own values. For example, if you would like to have a sensor with the name “mySensor” and the captured arguments “hello” and “world”, the function would look like this:
dynatrace.sensor(method=“mySensor”, params_to_capture=[“hello”, “world”])

This is how the script will look after we added the four lines.

By executing this script we receive the following PurePath in AppMon.

As you can see the instrumentation has automatically captured the function names, arguments, CPU/IO breakdown and name of the file. By looking at the details of the top node we can see that it also recorded the line number and full path to the file.

One important note regarding the instrumentation is what happens when your application uses forks to spawn several processes. The Native ADK registers one agent per process, meaning that if you create a new process for each request you would have to initialize the agent on every request. As this does not scale well you should tweak the forking to spawn a new process only once every 10.000 or so requests (depending on the load).

Within the Dynatrace library there are already functions created for linking the Python PurePath with PurePaths from other instrumented applications. The library is clear text and doesn’t require a separate compiler, so you can easily change the code if you need additional functions from the Native ADK.

Break open the black box and get in control of your applications, even if they are created in Python!