index.html 9.65 KB
<!DOCTYPE html>
<html>
<head>
  <title>{{title}}</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.2.1/Chart.bundle.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js"></script>
  <style>
    * {
      font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
    }

    h1 {
      font-size: 3em;
      color: #222222;
      margin: 0;
    }

    h5 {
      margin: 0;
      color: #888888;
    }

    p {
      font-size: 0.7em;
      color: #888888;
    }

    span {
      cursor: pointer;
      font-size: 10px;
      margin-left: 5px;
      border: 1px solid #DDD;
      padding: 3px 10px 4px 10px;
    }

    canvas {
      width: 400px;
      height: 100px;
    }

    .active {
      background: #eeeeee;
    }

    .stats-column {
      flex: 0 0 200px;
    }

    .container {
      display: flex;
      flex-direction: row;
      margin-top: 20px;
      height: 100px;
    }

    .chart-container {
      width: 400px;
      height: 100px;
    }

    .footer {
      position: fixed;
      margin: auto;
      text-align: center;
      left: 0;
      right: 0;
      bottom: 0;
    }

    .span-controls {
      float: right;
    }
  </style>
</head>
<body>
<div style="width: 600px; margin: auto">
  <div class="header">
    <b>{{title}}</b>
    <div id="span-controls" class="span-controls">
    </div>
  </div>
  <div class="container">
    <div class="stats-column">
      <h5>CPU Usage</h5>
      <h1 id="cpuStat">- %</h1>
    </div>
    <div class="chart-container">
      <canvas id="cpuChart" width="400" height="100"></canvas>
    </div>
  </div>
  <div class="container">
    <div class="stats-column">
      <h5>Memory Usage</h5>
      <h1 id="memStat">- %</h1>
    </div>
    <div class="chart-container">
      <canvas id="memChart" width="200" height="100"></canvas>
    </div>
  </div>
  <div class="container">
    <div class="stats-column">
      <h5>One Minute Load Avg</h5>
      <h1 id="loadStat">-</h1>
    </div>
    <div class="chart-container">
      <canvas id="loadChart" width="200" height="100"></canvas>
    </div>
  </div>
  <div class="container">
    <div class="stats-column">
      <h5>Response Time</h5>
      <h1 id="responseTimeStat">-</h1>
    </div>
    <div class="chart-container">
      <canvas id="responseTimeChart" width="200" height="100"></canvas>
    </div>
  </div>
  <div class="container">
    <div class="stats-column">
      <h5>Requests per Second</h5>
      <h1 id="rpsStat">-</h1>
    </div>
    <div class="chart-container">
      <canvas id="rpsChart" width="200" height="100"></canvas>
    </div>
  </div>
  <div class="footer">
    <p>Made with &#9829; with Socket.io & Chart.js</p>
  </div>
</div>
<script>
  Chart.defaults.global.defaultFontSize = 8;
  Chart.defaults.global.animation.duration = 500;
  Chart.defaults.global.legend.display = false;
  Chart.defaults.global.elements.line.backgroundColor = "rgba(0,0,0,0)";
  Chart.defaults.global.elements.line.borderColor = "rgba(0,0,0,0.9)";
  Chart.defaults.global.elements.line.borderWidth = 2;

  var socket = io(location.protocol + '//' + location.hostname + ':' + location.port);
  var defaultSpan = 0;
  var spans = [];

  var defaultDataset = {
    label: '',
    data: [],
    lineTension: 0.2,
    pointRadius: 0
  };

  var defaultOptions = {
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }],
      xAxes: [{
        type: 'time',
        time: {
          unitStepSize: 30
        },
        gridLines: {
          display: false
        }
      }]
    },
    tooltips: {
      enabled: false
    },
    responsive: true,
    maintainAspectRatio: false,
    animation: false
  };

  var createChart = function (ctx, dataset) {
    return new Chart(ctx, {
      type: 'line',
      data: {
        labels: [],
        datasets: dataset
      },
      options: defaultOptions
    });
  };

  var addTimestamp = function(point) {
    return point.timestamp;
  };

  var cpuDataset = [Object.create(defaultDataset)];
  var memDataset = [Object.create(defaultDataset)];
  var loadDataset = [Object.create(defaultDataset)];
  var responseTimeDataset = [Object.create(defaultDataset)];
  var rpsDataset = [Object.create(defaultDataset)];

  var cpuStat = document.getElementById('cpuStat');
  var memStat = document.getElementById('memStat');
  var loadStat = document.getElementById('loadStat');
  var responseTimeStat = document.getElementById('responseTimeStat');
  var rpsStat = document.getElementById('rpsStat');

  var cpuChartCtx = document.getElementById("cpuChart");
  var memChartCtx = document.getElementById("memChart");
  var loadChartCtx = document.getElementById("loadChart");
  var responseTimeChartCtx = document.getElementById("responseTimeChart");
  var rpsChartCtx = document.getElementById("rpsChart");

  var cpuChart = createChart(cpuChartCtx, cpuDataset);
  var memChart = createChart(memChartCtx, memDataset);
  var loadChart = createChart(loadChartCtx, loadDataset);
  var responseTimeChart = createChart(responseTimeChartCtx, responseTimeDataset);
  var rpsChart = createChart(rpsChartCtx, rpsDataset);

  var charts = [cpuChart, memChart, loadChart, responseTimeChart, rpsChart];

  var onSpanChange = function (e) {
    e.target.classList.add('active');
    defaultSpan = parseInt(e.target.id);

    var otherSpans = document.getElementsByTagName('span');
    for (var i = 0; i < otherSpans.length; i++) {
      if (otherSpans[i] !== e.target) otherSpans[i].classList.remove('active');
    }

    socket.emit('change');
  };

  socket.on('start', function (data) {
    // Remove last element of Array because it contains malformed responses data.
    // To keep consistency we also remove os data.
    data[defaultSpan].responses.pop();
    data[defaultSpan].os.pop();

    cpuStat.textContent = data[defaultSpan].os[data[defaultSpan].os.length - 1].cpu.toFixed(1) + '%';
    cpuChart.data.datasets[0].data = data[defaultSpan].os.map(function (point) {
      return point.cpu;
    });
    cpuChart.data.labels = data[defaultSpan].os.map(addTimestamp);

    memStat.textContent = data[defaultSpan].os[data[defaultSpan].os.length - 1].memory.toFixed(1) + 'MB';
    memChart.data.datasets[0].data = data[defaultSpan].os.map(function (point) {
      return point.memory;
    });
    memChart.data.labels = data[defaultSpan].os.map(addTimestamp);

    loadStat.textContent = data[defaultSpan].os[data[defaultSpan].os.length - 1].load[defaultSpan].toFixed(2);
    loadChart.data.datasets[0].data = data[defaultSpan].os.map(function (point) {
      return point.load[0];
    });
    loadChart.data.labels = data[defaultSpan].os.map(addTimestamp);

    responseTimeStat.textContent = data[defaultSpan].responses[data[defaultSpan].responses.length - 1].mean.toFixed(2) + 'ms';
    responseTimeChart.data.datasets[0].data = data[defaultSpan].responses.map(function (point) {
      return point.mean;
    });
    responseTimeChart.data.labels = data[defaultSpan].responses.map(addTimestamp);

    if (data[defaultSpan].responses.length >= 2) {
      var deltaTime = data[defaultSpan].responses[data[defaultSpan].responses.length - 1].timestamp - data[defaultSpan].responses[data[defaultSpan].responses.length - 2].timestamp;
      rpsStat.textContent = (data[defaultSpan].responses[data[defaultSpan].responses.length - 1].count / deltaTime * 1000).toFixed(2);
      rpsChart.data.datasets[0].data = data[defaultSpan].responses.map(function (point) {
        return point.count / deltaTime * 1000;
      });
      rpsChart.data.labels = data[defaultSpan].responses.map(addTimestamp);
    }

    charts.forEach(function(chart) {
      chart.update();
    });

    var spanControls = document.getElementById('span-controls');
    if (data.length !== spans.length) {
      data.forEach(function (span, index) {
        spans.push({
          retention: span.retention,
          interval: span.interval
        });

        var spanNode = document.createElement('span');
        var textNode = document.createTextNode((span.retention * span.interval) / 60 + "M");
        spanNode.appendChild(textNode);
        spanNode.setAttribute('id', index);
        spanNode.onclick = onSpanChange;
        spanControls.appendChild(spanNode);
      });
      document.getElementsByTagName('span')[0].classList.add('active');
    }
  });

  socket.on('stats', function (data) {
    if (data.retention === spans[defaultSpan].retention && data.interval === spans[defaultSpan].interval) {
      cpuStat.textContent = data.os.cpu.toFixed(1) + '%';
      cpuChart.data.datasets[0].data.push(data.os.cpu);
      cpuChart.data.labels.push(data.os.timestamp);

      memStat.textContent = data.os.memory.toFixed(1) + 'MB';
      memChart.data.datasets[0].data.push(data.os.memory);
      memChart.data.labels.push(data.os.timestamp);

      loadStat.textContent = data.os.load[0].toFixed(2);
      loadChart.data.datasets[0].data.push(data.os.load[0]);
      loadChart.data.labels.push(data.os.timestamp);

      responseTimeStat.textContent = data.responses.mean.toFixed(2) + 'ms';
      responseTimeChart.data.datasets[0].data.push(data.responses.mean);
      responseTimeChart.data.labels.push(data.responses.timestamp);

      var deltaTime = data.responses.timestamp - rpsChart.data.labels[rpsChart.data.labels.length - 1];
      rpsStat.textContent = (data.responses.count / deltaTime * 1000).toFixed(2);
      rpsChart.data.datasets[0].data.push(data.responses.count / deltaTime * 1000);
      rpsChart.data.labels.push(data.responses.timestamp);

      charts.forEach(function (chart) {
        if (spans[defaultSpan].retention < chart.data.labels.length) {
          chart.data.datasets[0].data.shift();
          chart.data.labels.shift();
        }

        chart.update();
      });
    }
  });
</script>
</body>
</html>