Commits

Josh VanderLinden committed 8943f5d

Starting to use ExpressJS to make life a little easier

Comments (0)

Files changed (2)

  <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
     <title>Disk Utilization</title>
-    <link href="layout.css" rel="stylesheet" type="text/css">
+    <link href="/static/layout.css" rel="stylesheet" type="text/css">
     <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->
-    <script language="javascript" type="text/javascript" src="jquery.js"></script>
-    <script language="javascript" type="text/javascript" src="jquery.flot.js"></script>
+    <script language="javascript" type="text/javascript" src="/static/jquery.js"></script>
+    <script language="javascript" type="text/javascript" src="/static/jquery.flot.js"></script>
  </head>
     <body>
     <h1>Disk Utilization</h1>
 
 <script type="text/javascript">
 var options = {
+  clickable: true,
+  hoverable: true,
   lines: { show: true },
-  points: { show: true },
-  xaxis: { tickDecimals: 0, tickSize: 1 }
+  //points: { show: true },
+  xaxis: {
+    mode: 'time',
+    minTickSize: [1, 'second'],
+    tickSize: [20, 'second'],
+    //timeformat: '%H:%M:%S'
+  }
 };
-var MODE = 'util';
+var data = [];
+var last_stamp = null;
+var max_size = 120;
 
 var columns = new Array('rrqm_s', 'wrqm_s', 'r_s', 'w_s', 'rkb_s', 'wkb_s', 'avgrq_sz', 'avgqu_sz', 'await', 'r_await', 'w_await', 'svctm', 'util');
 
 $(function () {
-  var placeholder = $("#placeholder");
   setInterval(fetchData, 1000);
 
+  display_mode = $('#id_display_mode');
   for (var i = 0; i < columns.length; i++) {
     col = columns[i];
     sel = col == 'util' ? ' selected="selected"' : '';
-    $('#id_display_mode').append('<option' + sel +'>' + columns[i] + '</option>');
+    display_mode.append('<option' + sel +'>' + columns[i] + '</option>');
   }
+  display_mode.change(function () { data = []; last_stamp = null; });
 });
 
+function updateDataSet(new_data) {
+  var match_found = false;
+  for (s in data) {
+    series = data[s];
+    if (series.label == new_data.label) {
+      match_found = true;
+      data[s].data = series.data.concat(new_data.data);
+
+      // make sure we only have a certain amount of data
+      while (data[s].data.length > max_size) data[s].data.shift();
+      return;
+    }
+  }
+
+  if (!match_found) data.push(new_data);
+}
+
 function fetchData() {
   function onDataReceived(series) {
     // we get all the data in one go, if we only got partial
     // data, we could merge it with what we already got
-    $.plot($("#placeholder"), series, options);
+    series.map(updateDataSet);
+
+    last_stamp = +new Date();
+    $.plot($("#placeholder"), data, options);
   }
 
+  params = {};
+  if (last_stamp !== null)  params = {since: last_stamp};
+
   $.ajax({
     // usually, we'll just call the same URL, a script
     // connected to a database, but in this case we only
     // have static example files so we need to modify the
     // URL
-    url: "/" + $('#id_display_mode').val(),
+    url: "/data/" + $('#id_display_mode').val(),
+    data: params,
     method: 'GET',
     dataType: 'json',
     success: onDataReceived
 var sys = require('sys'),
     spawn = require('child_process').spawn,
-    app = require('http').createServer(handler),
+    //app = require('http').createServer(handler),
+    express = require('express'),
     url = require('url'),
     fs = require('fs'),
     all_disks = {},
-    columns = new Array('rrqm_s', 'wrqm_s', 'r_s', 'w_s', 'rkb_s', 'wkb_s', 'avgrq_sz', 'avgqu_sz', 'await', 'r_await', 'w_await', 'svctm', 'util');
-
-function handler(req, res) {
-  var path = url.parse(req.url).pathname;
-  switch (path) {
-    case '/':
-      console.log('Serving homepage');
-      getFile('iostat.html', res);
-      break;
-    default:
-      col = path.slice(1);
-      if (columns.indexOf(col) != -1) {
-        getDiskInfo(col, res);
-      } else {
-        console.log('Got a request for', path);
-        getFile(path, res);
-      }
-  }
-}
+    columns = [];
 
-function getFile(filename, res) {
-  fs.readFile(__dirname + '/' + filename, function (err, data) {
-    if (err) {
-      res.writeHead(500);
-      return res.end('Error loading ' + filename);
-    }
+var app = express.createServer();
+app.configure(function () {
+  app.use(express.errorHandler({dumpExceptions: true, showStack: true}));
+});
 
-    res.writeHead(200);
-    res.end(data);
-  });
-}
+app.get('/', function(req, res) {
+  getFile('iostat.html', res);
+});
+
+app.use('/static', express.static(__dirname));
+
+app.get('/data/:col', function(req, res) {
+  if (columns.indexOf(req.params.col) != -1) {
+    since = parseInt(req.param('since', null));
+    getDiskInfo(req.params.col, since, res);
+  }
+});
 
-function getDiskInfo(col, res) {
+function getDiskInfo(col, since, res) {
   results = new Array();
   for (name in all_disks) {
     disk = all_disks[name];
-    points = new Array();
-    size = disk.data[col].items.length;
-    for (history = -1 * size; history < 0; history++) {
-      points.push([history, disk.data[col].items[history + size]]);
+    data = disk.data[col].items;
+    if (since !== null) {
+      data = data.filter(function (value) {
+        return value[0] >= since;
+      });
     }
 
     results.push({
       label: name,
-      data: points
+      data: data
     });
   }
   res.writeHead(200);
   this.data = {}
   for (var i = 0; i < columns.length; i++) {
     var col = columns[i];
-    this.data[col] = new Queue(20);
+    this.data[col] = new Queue(120);
   }
 }
 Disk.prototype.parse_data = function (data) {
+  var now = Math.round(+new Date());
   for (var i = 0; i < columns.length; i++) {
     var col = columns[i];
-    this.data[col].push(parseFloat(data[i]));
+    this.data[col].push([now, parseFloat(data[i])]);
   }
 }
 
+function splitColumns(line) { return line.replace(/\s+/g, ' ').split(' '); }
+function parseColumn(col) { return col.replace('/', '_').replace(/[^\w\-]/g, ''); }
+
 function getIOStats() {
-  iostat = spawn("iostat", ['-dx', '1'], {setsid: true, encoding: 'ascii'});
+  /**
+   * Executes the iostat command and regularly parses the output.  Data are
+   * queued up for later use.
+   **/
+
+  iostat = spawn("iostat", ['-dx', '-p', '1'], {setsid: true, encoding: 'ascii'});
   iostat.stdout.on('data', function (data) {
+    // coerce the buffer to a string
     stdout = '' + data;
-    disks = stdout.match(/^\w+(\s+\d+.\d+){13}/gm);
-    if (!disks) {
-      return;
+
+    // make sure we have column names on the first run
+    if (columns.length == 0) {
+      raw_columns = stdout.match(/^Device:(\s+[\w\/%\-]+)+/gm);
+      if (!raw_columns) return;
+
+      raw_columns = raw_columns.map(splitColumns)[0];
+      raw_columns.shift();
+      columns = raw_columns.map(parseColumn);
     }
 
+    // now try to parse disk information
+    disks = stdout.match(/^\w+(\s+\d+.\d+){13}/gm);
+    if (!disks) return;
+
     disks.forEach(function (disk) {
-      data = disk.replace(/\s+/g, ' ').split(' ');
-      //console.log(data);
+      data = splitColumns(disk);
       name = data.shift();
       if (!(name in all_disks)) {
         all_disks[name] = new Disk(name);
       }
       all_disks[name].parse_data(data);
-      //console.log(name + ' utilization:', all_disks[name].data.util.items);
     });
   });
 }
 
-app.listen(1337);
+app.listen(8000);
 getIOStats();