Kemp’s Blog

A technical blog about technical things

Plotting hardware stats via plotly

I recently signed up to try the beta of Plotly and decided to try it out by plotting the temperature, CPU load and RAM usage of a Raspberry Pi. Temperature, particularly, was of interest to me as I intend to stream video from the camera module, which makes heavy use of both the GPU and CPU. Plotly provide a Python API, which suits me perfectly.

Note that I use Python 2.7 and this code requires some modification to work correctly in Python 3.

Step 1: Getting the stats

I wasn’t intending to reinvent the wheel as far as getting the hardware stats goes, so I shamelessly stole code from here and here, as below.

from subprocess import PIPE, Popen
import psutil

def get_cpu_temp():
    tempFile = open("/sys/class/thermal/thermal_zone0/temp")
    cpu_temp = tempFile.read()
    tempFile.close()
    return cpu_temp / 1000.

def get_gpu_temp():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    return float(output[output.index('=') + 1:output.rindex("'")])

def get_ram_used():
    return psutil.phymem_usage().percent

def get_cpu_usage():
    return psutil.cpu_percent()

These functions do exactly what you’d expect, so I won’t go into any detail.

Step 2: Using the plotly API

To send the data to plotly, you first create a plotly object that provides a wrapper around their API, and then use methods of that object to add data or set the graph layout. The plotly object is created as follows:

USERNAME = 'username'
APIKEY   = 'api_key'
py = plotly.plotly(username_or_email=USERNAME, key=APIKEY)

You can find your API key by logging into plotly, clicking your username in the top right and clicking ‘Settings’. Alternatively, visit the Python API page and expand the installation instructions.

The two important methods in the plotly object are layout and plot. My script creates a layout specification and sends it to plotly as follows.

layout = {
    'title'       : 'Camera 1 hardware',
    'xaxis'       : {'title':'Time', 'type':'date'},
    'yaxis'       : {'title':'Temperature (deg. C)'},
    'yaxis2'      : {'title':'% usage', 'side':'right', 'overlaying':'y'},
    'showlegend'  : True,
    'legend'      : {'x' : 1, 'y' : 1},
}
py.layout(layout, filename=filename)

The x and y values for the legend are in the range 0 to 1, with (0, 0) being the bottom-left corner of the graph.

I also insert annotations at appropriate points using lines similar to the following (after which layout must be called again):

layout['annotations'] = [{
    'text'      : 'Camera enabled',
    'xref'      : 'x',
    'yref'      : 'y2',
    'x'         : calendar.timegm(curr_time.utctimetuple())*1000,
    'y'         : _cpu_load,
    'ax'        : -100,
    'ay'        : 10,
    'showarrow' : True,
    'arrowhead' : 7
}]

The ax and ay values control either the offset of the label or the length of the arrow, I’m not 100% clear on which but I would assume the latter, and they seem to be specified in pixels rather than axis units. Note that I convert my datetime object into a timestamp in milliseconds. While the plotly API supports datetime objects (and strings of the form ‘YYYY-MM-DD HH:MM:SS’) for datapoints, it currently doesn’t for annotations. I’ve been told that this is on their todo list though.

I send actual datapoints to plotly using the following:

cpu_temp = {'x': [curr_time], 'y': [get_cpu_temp()], 'type': 'scatter', 'mode': 'lines', 'name':'CPU temperature'}
gpu_temp = {'x': [curr_time], 'y': [get_gpu_temp()], 'type': 'scatter', 'mode': 'lines', 'name':'GPU temperature'}
ram_used = {'x': [curr_time], 'y': [get_ram_used()], 'type': 'scatter', 'mode': 'lines', 'name':'RAM usage', 'yaxis':'y2'}
cpu_load = {'x': [curr_time], 'y': [get_cpu_usage()], 'type': 'scatter', 'mode': 'lines', 'name':'CPU usage', 'yaxis':'y2'}

py.plot([cpu_temp, gpu_temp, ram_used, cpu_load], filename=filename, fileopt='extend')

There are two methods of passing data to plotly, one is to use individual lists of values such as

x1 = [1, 2, 3]
x2 = [1, 3, 5]
y1 = [1,3,6]
y2 = [6,3,1]
py.plot(x1, y1, x2, y2)

and the other is to use a list of dictionaries (one dictionary per trace) as I’ve done in my code. I use fileopt=’extend’ as I send the data for each trace one point at a time as it is gathered. The alternative would be to collect and buffer all the values and then send them all at once.

Putting all this together, along with some additional code to sample the values at the correct times, results in a graph looking like the following:

My blog doesn’t give the graph much space to play with, but you can also view it directly on the plotly website, where it looks significantly better. For those interested, the graph was embedded using:

<iframe height="510" id="igraph" scrolling="no" seamless="seamless"
src="https://plot.ly/~Kemp/17/600/500/" width="610"></iframe>

The width and height to display at is controlled directly in the URL and I’ve added an extra 10 pixels to the iframe size to prevent scrollbars from appearing. When I was looking at the temperature under load I, of course, graphed the Pi over a much longer period of time. I performed this shorter version of the test in order to be able to show the graph reasonably well in a small space.

You can find the complete source for my script here). This was a quick investigation to satisfy my curiosity about the hardware, so it’s not the most elegant script.

That’s all there is to it. Once you have the basics down, the plotly API seems very easy to use. You can do a lot more than what I’ve covered here, such as graphing data from uploaded files, processing the data directly on the website, and generating many more types of graph than just scatter/line plots. If you need to create graphs to share with other people I’d suggest giving them a try.