Zabbix HTTP Agent in use
Monitor PythonAnywhere CPU usage
With the release of version 4.2 Zabbix gained a very useful tool for monitoring various APIs and web pages, namely — HTTP agent. Thanks to this addition you can make a HTTP call to any endpoint and save the response as item data. This differs greatly from existing Web checks as the user is in total control of the configuration. There are no automatically generated items, no associated graphs. Just the response and you.
As mentioned it is great for monitoring data served by APIs. To showcase this, I will show you how to monitor the usage of CPU on your PythonAnywhere account.
PythonAnywhere
As the name suggests, PythonAnywhere is a service that allows you to execute python code anywhere you are, given you have an internet connection and a modern browser. Apart from providing the ability to run web apps backed by various python frameworks, it allows you to run Jupyter notebooks and gives direct access to bash and python consoles. When running a Jupyter notebook or any of the consoles you use some of your daily CPU limit. After the limit is exhausted, you fall into what’s known as a tarpit, where processes get lower priority than those of other users and might even get killed off. It’s surprisingly easy to forget about closing all consoles, in which case you run the risk of using up all of your assigned CPU. Most likely you don’t want to check the dashboard every so often just to check the current usage. Thankfully there is a way to automate the process.
PythonAnywhere API
The ability to retrieve valuable information about the current usage was added to the PythonAnywhere API quite recently. According to the documentation a call to the corresponding endpoint will return the following response:
{
"daily_cpu_limit_seconds": <int>,
"next_reset_time": <isoformat>,
"daily_cpu_total_usage_seconds": <float>
}
It contains all the information we need. With this we can track the usage of CPU time and notify users about free resources.
Before we start making a Zabbix template let’s try creating a raw curl request, so we can get a hang of how the HTTP call should look like. For starters we need three things:
- Knowledge which PythonAnywhere system are we using: “classic” or eu. This will be reflected in the URL. For this article I will assume classic version
- The username of the PythonAnywhere account.
- User API token. It can be retrieved from account settings.
Once we have this information, we can create our first curl command. To make this work you need to assign values to bash variables beforehand.
curl -H “Authorization: Token ${token}” “https://www.pythonanywhere.com/api/v0/user/${username}/cpu/”
Please note that the trailing slash in the URL is mandatory. Executing this command will return a JSON just as the documentation described.
$ curl -H "Authorization: Token ${token}" "https://www.pythonanywhere.com/api/v0/user/${username}/cpu/" 2>/dev/null
{"daily_cpu_limit_seconds":2000,"daily_cpu_total_usage_seconds":1227.620719895,"next_reset_time":"2020-04-12T18:25:47"}
A small redirection to /dev/null clears the output. After this test we can reflect the call as a Zabbix HTTP Agent item.
Zabbix
The best practice when creating Zabbix items is to put them inside a template, even when it’s just one item. Additionally all values that may vary from host to host, should be put into a macro. This allows for greater reusability and faster reconfiguration. PythonAnywhere username and API token are a great example of this. In our template, the values will be kept in the following macros:
- {$PA_API_TOKEN}
- {$PA_USER}
Zabbix items
Inside of the template we’ll create one HTTP Agent item which will gather data from the API. Then we’ll leverage dependant items to slice the JSON response. Since we’ll be storing data in those dependant items we can have a very short history period on master item or just drop collected values altogether. The HTTP check will look like in the image bellow.
You may notice two macros being used in the item. One of them is for the username, the other one stores the API token. Thanks to this the template will be quite reusable. The other thing you should notice is the short history period. As mentioned earlier it is not required to store it for long, just enough to debug any issue that might occur. The usage of additional headers is required for authentication reasons.
After linking the template to a host, and realising the configuration cache, the item should start collecting some values. If everything went fine, we should have some JSONs in the history. Now we can create the dependant items to split the JSON into separate entities.
The easiest one is the usage of CPU — it’s just a float that can be stored ‘as is’. The CPU limit gives a little more room for additional configuration. As of Zabbix 4.4 it is possible to discard values if they didn’t change since the last check. This helps to save some disk space for data that actually matters.
The second dependant item is the CPU limit. It is tied closely with your account settings and more often than not it’s not something you change everyday. It should be more or less constant across time and doesn’t make interesting graphs. If for some reason you require this information, you can store the values as they come or choose the middle ground and discard values with a heartbeat.
The last dependant item is the time at which the limit will reset. It is presented in ISO format. This can be paired with some triggers and actions to send some notifications. For instance, you could get an e-mail or SMS to notify you that the CPU limit was renewed.
After creating all of the items, the template will look something like this.
Zabbix triggers
Once data is being gathered, it is time to put it to use. To do so, we will create two triggers. One to watch if the daily limit has been exceeded and one to check if the limit was renewed.
The first trigger is will be based on comparing two numbers, as you would guess, they are the current usage and the daily limit. As mentioned previously, whenever the usage will be greater than the limit, the account will be placed in the tarpit.
The second trigger will keep track of CPU limit renewal. This is fairly easy to implement. On every limit reset the returned timestamp changes. Zabbix provides means of checking this change in the form of diff trigger function. With such a trigger expression every time the limit is renewed (and thus the timestamp changes) a problem will be displayed.
Conclusion
In this article I presented how to monitor basic PythonAnywhere parameters via their API with the power of Zabbix HTTP Agent. As always you can check out the full template in my repo.
If you have any questions about this or similar topic, please leave them in the comments.