CPU Utilization Graph

Posted on
  • I've often wanted to know the CPU utilization of my home server to help debugging or just seeing general usage, I'm aware of the Pico CPU Monitor but that uses USB, seeing as though it's a server I wanted something with WiFi. Oh and fancy WebSockets.

    First of all, we need some software on the server to broadcast its utilization. I chose WebSocketd, it takes any regular command line script and broadcasts it over WebSockets. Just remember to add it to your $PATH so you can use it from anywhere. I thought this was easier than making Node script to do essentially the same thing, this project is about Espruino after all. I have this run in a screen at startup on my server:

    websocketd --port=9231 /usr/bin/vmstat -n 1
    

    Now for the Espruino portion, while I had originally planned to run this on an Original Espruino I ended up using an ESP8266 I'll explain why later. The Espruino has to connect to the server, parse the information then display it on a graph. The information from the WebSocket comes in a format structured for columns on a command line using multiple spaces to separate it. Since Espruino doesn't support RegEx yet I had to use some fidgeting with indexOf to get the data into a usable format, specifically the idle time of the CPU. I then created a simple graph library to convert the CPU utilization into a graph and plot it. I used an SSD1306 128*64 OLED, here's the code and how it looks:

    require("Font8x12").add(Graphics);
    I2C1.setup({scl:NodeMCU.D4,sda:NodeMCU.D­3});
    var Wifi = require("Wifi");
    var Graph = require("http://dev.mrtimcakes.com/espru­ino/graph.js");
    var CPU = new Graph(90, 48);
    var WebSocket = require("ws");
    var ws = new WebSocket("<YOUR SERVER HERE>",{port: 9231, origin: 'http://' + Wifi.getIP().ip});
    var column = ["r", "b", "swpd", "free", "buff", "cache", "si", "so", "bi", "bo", "in", "cs", "us", "sy", "id", "wa", "st"];
    var lastUpdate = 0;
    
    ws.on('message', function(e) {
      while(e.indexOf('  ')!=-1)e=e.replace('  ',' ');
      var raw = e.trim().split(" ");
      var data = {};
      for (var i = 0; i < column.length; i++) {
        data[column[i]] = parseInt(raw[i]);
      }
    
      CPU.update(100 - data.id);
      lastUpdate = getTime();
    });
    
    function draw(){
      g.clear();
      g.setFont8x12(); 
      g.drawString("CPU", 7.5, 27);
      CPU.drawGraph(g, 33, 5.5);
      g.setFontBitmap();
      g.drawString("0", 29, 48);
      g.drawString("100", 21, 6);
      g.drawString("0", 33, 55);
      g.drawString("90", 115, 55);
      g.drawString("Seconds", 64, 55);
      
      if( (getTime() - lastUpdate) > 1.5){
        g.drawString("!", 0, 0);
      }
      
      g.flip();
    }
    
    var g = require("SSD1306").connect(I2C1, function(){
      draw();
      setInterval(draw,1000);
    });
    

    2 Attachments

    • IMG_20170625_041359.jpg
    • IMG_20170625_041518.jpg
  • And here is Graph.js

    /* Copyright (c) 2017 MrTimcakes. See the file LICENSE for copying permission. */
    /*
    Live graphs that update from right to left
    
    var Graph = require("graph");
    var myGraph = new Graph(90, 48);
    myGraph.initData([0,1,2,3,4,5,10,15,20,2­5,30]);
    
    function draw(){
      g.clear();
      myGraph.drawGraph(g, 5, 5);
      g.flip();
    }
    */
    
    /* Create new graph, datapoints to keep (width), height, range of data from 0 */
    function Graph(historyLength, height = 64, range = 100) {
      this.history = new Int8Array(historyLength);
      this.height = height;
      this.range = range;
    }
    
    Graph.prototype.setHeight = function(height) {
      this.height = height;
    };
    
    Graph.prototype.setRange = function(range) {
      this.range = range;
    };
    
    /* Cycle all history values, add new ones to the end*/
    Graph.prototype.update = function(data) {
      for (var i=1;i<this.history.length;i++){
        this.history[i-1] = this.history[i];
      }
      this.history[this.history.length-1] = data;
    };
    
    /* Replace end of history with the array given */
    Graph.prototype.initData = function(data) {
      if(Array.isArray(data)){
        for (var i=0;i<data.length;i++){
          this.history[this.history.length-i] = data[data.length-i];
        }
      }
    };
    
    /* Draw axis, the convert each value to a y and plot */
    Graph.prototype.drawGraph = function(g, x, y) {
      g.drawLine(x, y, x, y+this.height);
      g.drawLine(x, y+this.height, x+this.history.length, y+this.height);
    
      g.moveTo(x, (y - (this.height/this.range)*this.history[0]­) + this.height);
      for (i=1;i<this.history.length;i++){
        g.lineTo(x+i, (y - (this.height/this.range)*this.history[i]­) + this.height);
      }
    };
    
    exports = Graph;
    
    
  • Ah, and about using an ESP over the Orginal Espruino. Originally I wanted to use an ILI9341 LCD and display multiple graphs at the same time, this is why I designed the Graph library to create new instances. However, I couldn't get the display to update fast enough. Perhaps I'll try to improve that again at some point, probably something to do with compiled code for the STM32, any advice for that is welcome. In the end seeing as though I was going to use an OLED anyway it was easier to just use a NodeMCU.

  • That's great! Thanks for posting it up!

    I really like websocketd - it seems like a really neat idea.

    Gettin the ILI9341 fast enough can be tricky - paletted mode can help sometimes (as you can build the whole image in RAM) but even then the updates aren't that fast. You do seem pretty limited by SPI - sending a whole screenful is 150kb, so even running SPI flat out at 10MHz it's still going to take over 1/10 of a second - and Espruino won't really manage that while doing the palette as well

  • Yeah, is 10MHz the fastest SPI clock? The Adafruit ILI9341 and TFT_eSPI Arduino libraries run 36MHz for the STM32F1 and 40MHz for the ESP

  • No, you can push it out faster - however I'd be a bit worried about trying to push 36MHz reliably over jumper wires?

    It's likely it'll be the pallet code that's slowing it down rather than SPI (well, a mix). However, if you weren't using the palleted code it'll certainly be the display driver that's slowing it down since it's in JavaScript.

    If you were willing to do a bit of poking in C it'd be pretty easy to write your own native SPI driver though. There's already a branch for an OLED display: https://github.com/espruino/Espruino/com­pare/DO003-oled

    You could strip that down and just send the commands from:
    https://github.com/espruino/EspruinoDocs­/blob/master/devices/ILI9341.js

    in it instead... That one uses software SPI but it'd still be pretty speedy

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

CPU Utilization Graph

Posted by Avatar for MrTimcakes @MrTimcakes

Actions