Let’s take this part by part and work our way through the whole stack. I’ll start with the monitored PC side, and then work my way through to the code that executes in the browser.
Monitored PC Code
The monitored PC’s code has the following tasks:
- Collect performance information
- Report performance information to the Pro Micro via USB
- Accept input from the Pro Micro via USB
The USB tasks are simplified by the Pro Micro’s USB support. We can use the USB serial port on the Pro Micro to send the performance info, and the keyboard interface makes accepting input a snap. So that simplifies these requirements down to “get current state of machine” and “write to a serial port.”
I decided to use Python for this, since it’s an easy language to write, I know it well, and there are tons of libraries for doing common tasks. It’s also cross platform, which means that although I’m writing this for Windows, porting it to a Linux or OSX system should be easy.
The two packages I pulled in are:
Here’s the critical loop of the code, but the whole thing is up on GitHub:
def run(self): # the first time, this does nothing psutil.cpu_percent() time.sleep(.5) report_end = bytearray(1) while(self.running): if(self.ser == None): self.reset_port() if(self.ser == None): time.sleep(5) continue report_dict = {} # get the timestamp report_dict['t'] = time.strftime("%Y-%m-%d %H:%M:%S") # Get CPU usage cpu_pct = psutil.cpu_percent(percpu=True) report_dict['c'] = [] for i in range(len(cpu_pct)): report_dict['c'].append(cpu_pct[i]) # Get memory usage mem = psutil.virtual_memory() report_dict['m'] = {'t':mem.total, 'a':mem.available} # Get disk usage diskparts = psutil.disk_partitions() report_dict['d'] = [] #print diskparts for part in diskparts: if os.name == 'nt': if 'cdrom' in part.opts or part.fstype == '': # skip optical drives on windows continue usage = psutil.disk_usage(part.mountpoint) report_dict['d'].append({'m':part.mountpoint, 't':usage.total, 'u':usage.used}) # Serialize to JSON report = json.dumps(report_dict) print report # Write to the serial port try: self.ser.write(report) self.ser.write(report_end) except serial.SerialException: print("Can't write to serial port, trying to reopen...") self.reset_port() time.sleep(5)
As you can see, most of the loop is fetching the current statistics from psutil’s functions, and then I create a Python dict out of these values. The dict has entries for the current time (‘t’), the current CPU usage, per core (‘c’), memory stats (‘m’), and disk utilization (‘d’).
Design Decision From there, I decided to convert this dict to text using JSON. JSON is a great text format for data like this, and the fact that it’s natively supported in JavaScript will save us some pain later when I want to parse it for the browser. With the data pre-formatted, I can just funnel it through the system without having to parse it again. That will save CPU time and keep the complexity of the later code to a minimum. JSON has a bit of overhead – though far less than XML – but in my testing it wasn’t significant enough to really matter.
One thing I’d like to add to this is a tray icon or something similar. Right now it just runs in a terminal/DOS prompt. There’s an example of making a Python script with a tray icon which is cross platform, over on Stack Exchange.