Tuesday, June 23, 2020

PCMON (Python code)

As my first post in this blog I shall be very imaginative and describe a PC-monitor software. I've also decided to call the said software PCMON which is also very imaginative and should not be confused with multitude of other software with the same name. The reason for this project is that I spend a lot of time in front of the computer and would like to know what it's currently doing. You know, knowledge is power and so on. There is of course a performance monitor in each and every modern operating system, but it has to be explicitly open unless you use Conky or Rainmeter or some Windows Widgets. None of these options really work while playing games or using any other software in full screen. This is why I want to have an external physical display to show such info.

I suppose a single most amazing piece of software that I have ever used is LCDSirReal. This piece of software was made for the Logitech G15 keyboard that had an LCD display embedded into the keyboard. Now the display by itself is quite useless even with the software provided by Logitech. LCDSirReal however was a game-changer. It was designed to provide support for showing at least the following info on the display: clock, date, CPU usage, memory usage, network usage, music player track info, Fraps fps info, TeamSpeak info, speaker configuration and whatnot. It's also amazingly small, it's very light resource wise and it had lots of different configurations for each feature. For the CPU usage it could be set to display the usage of up to two cores or just the "most utilised" and "least utilised" cores. However the keyboard is quite outdated and is not on sale anymore. Moreover the LCDSirReal is not opensource, so we can only borrow ideas from it.

I've been planning to implement this kind of physical display for a long time already (something like 5 years). Not really planning how to do it, just planning to start doing it.. In any case, this calls for a quick n dirty solution. I've decided to implement the required PC software using Python 2.7 (I personally don't care that it's outdated). I don't enjoy writing software in Python and hope to eventually rewrite this code in C++. Python has however the advantage of ready made packages that are easy to install. In this case we can use the psutil package. This package contains useful functions like cpu_percent (CPU usage), virtual_memory (memory usage), disk_usage, disk_io_counters and net_io_counters. As a bonus, this Python code should also work in Linux. That however is a topic for another day.

So how implement this in practice? First you have to install Python 2.7 and psutil. Psutil can be installed with the command python pip install psutil. Then you can import each of the needed functions with a command from psutil import cpu_percent,virtual_memory etc. After the import the functions can simply be used in the code. It's important to note that cpu_percent has an optional parameter to specify the measurement length. Measuring CPU utilisation with zero time interval does not make any sense and by default the function will measure the utilisation since the last function call. Since we are going to measure values continuously, specifying this time interval here is equivalent to adding a delay with the same interval. virtual_memory on the other hand returns many values, and we actually want to specify that we want the percent value. disk_usage has a mandatory path to a drive that needs to be monitored, 'C:\\' works for windows in this case.

Disk and net IO counters are a little bit more complicated. They measure cumulative values. So to get the net speed, you need to compare the new return value to the previous one and divide by the time interval between measurements. It's also good to note that these counters return values in bytes. Internet speeds on the other hand are usually measured in bits so you need to take that into account if you want to get the network usage as a percentage. Lastly we want to put this in an endless loop to read the values until the PC is shut down. Another thing we want to do at this point is to decide a format for the values that will be sent via a serial port to the display. I've decided to just use the following format: one or multiple characters, integer value and a newline ('\n') as termination. So for example "C75\n" would mean that the CPU is running at 75%. Here's an example for the project that I'm currently working on.

import serial
from psutil import cpu_percent,virtual_memory,net_io_counters

ser = serial.Serial('COM1', 9600, timeout=1)
previous_net_down_total = net_io_counters().bytes_recv
while True:
cpu_usage = cpu_percent(interval=1.0)
mem_usage = virtual_memory().percent
net_down_total = net_io_counters().bytes_recv
net_down = (net_down_total - previous_net_down_total) / 1.0
previous_net_down = net_io_counters().bytes_recv
text  = "C%d\n" % (cpu_usage)
text += "M%d\n" % (mem_usage)
text += "ND%d\n" % (net_down * 100 * 8 / (1 * 1024 * 1024 * 1024))
ser.write(text)
print "%s\r\n" % (text)

The example above scans the CPU, memory and network usage and prints them out as percentage in the format mentioned above. These values are also sent out via the serial port COM1, which is incidentally the only part of the code that is platform specific (AFAIK). Some other notes here. The cpu_percent function can return values per CPU for multi-core systems. This was not used in the example for simplicity. Three values chosen in the example are actually the ones that I'm going to use with the display that I will present in the next post. The display will be the 32x16 pixel Red/Green LED display from eBay.

In my opinion this could be considered as a finished product. The last thing to do is to make it start automatically with the PC and preferably in such a way that there is no visible terminal open. This can be easily done by using pythonw.exe. Adding a shortcut to the windows start menu under "startup" with the target "C:\Python27\pythonw.exe pcmon.py" where pcmon.py is the script we want to run. Additionally the "working directory" should be set to a folder where the script is located. Specifying the working directory is a good practice if we want to save some logs into the same folder as the Python script. Unfortunately pythonw seems to crash from time to time in this use-case (pyserial & psutil) and I currently have no idea why. In that case the only option is to simply use python.exe and set the window to be minimised at startup. This is slightly annoying and I know there are apps that could allow hiding this terminal window, but I haven't come up with anything simple yet. One option would be to use some simple app, or set up the script as a Windows service.

Although I would say that this is a finished product, it's not very nice.. There are some hard-coded values like the port and the time interval, there is no error checking for opening the serial port and there is also no check for when the port is forcefully closed. These are not a problem when this script is used for only one machine with one display and the display is attached to a real or a virtual serial port. However if the script is used for multiple machines, it would be nice to be able to choose the serial port with command line arguments. The last two problems on the other hand are very important if the device is sometimes disconnected or if it's connected via serial over a Bluetooth link. I've noticed with another project that after starting my PC, the Bluetooth serial link might take up to two minutes to open up. Additionally it seems that the Bluetooth connection is also sometimes lost. This happens regardless of the fact that the device that is connected to the PC via Bluetooth is less than one meter away from the PC and neither of these are moved...

ps. The PCMON name might be related to the fact that I was watching Futurama again at the moment I was designing this project. After imagining how Hermes Conrad would pronounce this name, it's quite difficult to un-imagine that.

No comments:

Post a Comment

Internet of Crosstrainers, part 2

Introduction As mentioned in the original post here , there were some issues in the described implementation. I had some difficulties to fin...