Linux/sarをグラフ化その3(tableタグでグラフを描く

グラフをHTMLのtableタグで描く例(2017-03-14)



gnuplotもない、Excelもない、だがリソースを監視・分析しろ*1
という過酷な環境で戦うあなたへ贈るこのページです。


以下、HTMLとJavaScriptのみでグラフ描画します。
どういう風になるかは、こちら
実物を見て頂くのが早いかと。
なお、グラフにマウスを近づけることで、時刻と値が表示されます。*2

PCでご覧になった方は、比較的すんなり表示できたと思います。
スマートフォンの方は、かなり時間がかかったでしょう。
これは JavaScript でグラフをドット1つずつ描いているからで、ブラウザの性能に左右されます。*3
この例では sar で10分おき1日分のデータになりますが、
描画速度はデータの量次第です・・・

Shellの使い方

この shellは cgi を想定していますが、shell単体でも問題なく機能します。
最後に書いてある
「printf '%s' "$body"」 がHTMLファイルを作る部分なので、
「printf '%s' "$body" > graph.html」にして shellを実行し、
作成された graph.html をWebブラウザで開けばOKです。

それ以外で、更新しなければならない箇所は2つ、

  • 冒頭の、sar と awk の値を保持するところ
  • 後半の、それらの値を使う document.addEventListener

最初は、SARFILE行だけ書き換えて、感覚をつかんでください。
ここに「sar -o ファイル」等で作成したファイルを指定すれば、
あとはよしなに、CPU、メモリ、ロードアベレージを出力します。
雰囲気が分かったら、あとは既存の例を参考に改変してみてください。

以下、sar から グラフ付きHTMLファイルを描くShellです。

グラフを作るshell

#!/bin/sh

export LANG=C
#----------------------------------------------------------
# 値を変更する箇所は 2箇所
# (参照:cpu_all_title, cpu_all_x, cpu_all_y)
# - sar と awk の値を保持するところ、
# - それらの値を使う document.addEventListener
#
# CGIにせずにファイル取得のみであれば、リダイレクトすればOK.
# printf '%s' "$body"  >>  sample.html
#----------------------------------------------------------
# sar.log の場所を指定
SARFILE=`ls -t1 /var/log/sa/sa* | tail -1`
SARINFO=`basename $SARFILE`

cpu_all_title="cpu [useage %]"
cpu_all_x=`sar -f $SARFILE -u |\
            egrep -vi '^$|average|CPU' |\
            grep " all " |\
            awk '{ printf("\"%s\",", $1); }' |\
            sed -e 's/,$//'`
cpu_all_y=`sar -f $SARFILE -u |\
            egrep -vi '^$|average|CPU' |\
            grep " all " |\
            awk '{ printf("%.2f,", 100 - $8); }' |\
            sed -e 's/,$//'`

mem_use_title="memory [memused %]"
mem_use_x=`sar -f $SARFILE -r |\
            egrep -vi '^$|average|memused|Linux' |\
            awk '{ printf("\"%s\",", $1); }' |\
            sed -e 's/,$//'`
mem_use_y=`sar -f $SARFILE -r |\
            egrep -vi '^$|average|memused|Linux' |\
            awk '{ printf("%.2f,", $4); }' |\
            sed -e 's/,$//'`

load_1m_title="load average 1min [ldavg-1]"
load_1m_x=`sar -f $SARFILE -q |\
            egrep -vi '^$|average|ldavg|Linux' |\
            awk '{ printf("\"%s\",", $1); }' |\
            sed -e 's/,$//'`
load_1m_y=`sar -f $SARFILE -q |\
            egrep -vi '^$|average|ldavg' |\
            awk '{ printf("%.2f,", $4); }' |\
            sed -e 's/,$//'`

rxp_eth0_title="dev eth0 [rxpck/s]"
rxk_eth0_title="dev eth0 [rxkB/s]"
rxp_eth0_x=`sar -f $SARFILE -n DEV |\
            egrep -vi '^$|average|IFACE' |\
            grep eth0 |\
            awk '{ printf("\"%s\",", $1); }' |\
            sed -e 's/,$//'`
rxp_eth0_y=`sar -f $SARFILE -n DEV |\
            egrep -vi '^$|average|IFACE' |\
            grep eth0 |\
            awk '{ printf("%.2f,", $3); }' |\
            sed -e 's/,$//'`
rxk_eth0_y=`sar -f $SARFILE -n DEV |\
            egrep -vi '^$|average|IFACE' |\
            grep eth0 |\
            awk '{ printf("%.2f,", $5); }' |\
            sed -e 's/,$//'`

#---(sarの変数への格納 ここまで)-------------------------------------------------------

body=`cat <<EOS
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
    <meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
    <meta http-equiv="content-style-type" content="text/css" />
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title> sar graph </title>

  <style type="text/css">
  <!--
    body {
      background-color:black;
      color: #eeeeee;
      margin-left:2%;
      margin-right:2%;
      font-size:100%;
    }
    table {
      // clear gaps, and fix size, even if the screen of the Web browser is scaled.
      padding:0px; border: none; margin:0px;
      border-collapse: collapse;
      height:auto;
      background-color:white;
    }
    td {
      // dot graphic
      // if graph input is not enough, set large value width and height px.
      padding:0px;
      width: 1px;
      height: 1px;
    }
    td.left {
      // if change width, see makeTable() and update <table width>
      padding:0px; margin:0px;
      font-size: 2px;
      background-color: gray;
      width: 55px;
    }
    td.bottom {
      padding:0px; margin:0px;
      font-size: 2px;
      background-color: gray;
    }
    td.end {
      padding:0px; margin:0px;
      background-color:black;
    }
    div#HintDiv {
      visibility: hidden;
      position: fixed;
      font-size: 100%;
      padding: 1em;
      color: black;
      background-color: white;
      opacity: 0.8;
    }
    h2, h3 {
      border-bottom:  3px solid #448888;
      border-top:     1px solid #448888;
      border-left:    4px solid #224444;
      border-right:   8px solid #224444;
      color: #aacccc;
      width: 80%;
      background-color:#88aaaa;
      margin:0px 0px 2px 0px;
    }
  -->
  </style>

  <script type="text/javascript">
<!--


var HintTimer=null;
var HintMesg = "";
function addHintListener(index){
  var hint_obj = document.getElementById(index);
  if(hint_obj == null || hint_obj == undefined){
      return;
  }

  hint_obj.addEventListener("mouseover", function(e) {
      if(HintTimer != null){ return; }
      var obj = document.getElementById("HintDiv");
      obj.innerHTML = hint_obj.class;
      obj.style.top = e.clientY + "px";
      obj.style.left = e.clientX + "px";
      obj.style.visibility = "visible";
  }, false);

  hint_obj.addEventListener("mouseout", function(e) {
      if(HintTimer != null){ return; }
      var obj = document.getElementById("HintDiv");
      obj.innerHTML = "";
      obj.style.visibility = "hidden";
  }, false);

  hint_obj.addEventListener("touchend", function(e) {
      var obj = document.getElementById("HintDiv");
      if(HintMesg == "" || HintMesg != hint_obj.class){
          obj.style.top = e.changedTouches[0].pageY  + "px";
          obj.style.left = e.changedTouches[0].pageX + "px";
          obj.style.position = "absolute";
          obj.innerHTML = HintMesg = hint_obj.class;
          obj.style.visibility = "visible";
          clearInterval(HintTimer);
          HintTimer = setInterval(function(e) {
              if(HintMesg != ""){
                  obj.innerHTML = HintMesg = "";
                  obj.style.visibility = "hidden";
              }
              if(HintTimer != null){
                  clearInterval(HintTimer);
                  HintTimer = null;
              }
          }, 7000);
      }
      else {
          obj.innerHTML = HintMesg = "";
          obj.style.visibility = "hidden";
          if(HintTimer != null){
              clearInterval(HintTimer);
              HintTimer = null;
          }
      }
  }, false);
}

function makeTable(header, index, x_range, y_range, x_min, x_max, y_min, y_max){
  var string = "<h3>" + header + "</h3>";
     string += "<table id=\"" + index + "\" width=" + (x_range + 55) + "px >";
  var pre = Math.floor(y_range / 2);
  for(var y=0; y < y_range; y++){
    string += "<tr>";
    if(y == 0){
      string += "<td rowspan=" + pre
              + " class=left valign=top align=right>" + y_max + "</td>";
    }
    else if(y == pre){
      string += "<td rowspan=" + (y_range - pre)
              + " class=left valign=bottom align=right>" + y_min + "</td>";
    }
    for(var x=0; x < x_range; x++){
      string += "<td id=\"" + index + "_" + x + "_" + y + "\" ";
      if(y==pre){
        string += "style=background-color:#dddddd;";
      }
      string += " />"
    }
    string += "</tr>";
  }
  pre = Math.floor(x_range / 2);
  string += "<tr><td class=bottom />";
  string += "<td colspan=" + pre
          + " class=bottom valign=bottom align=left>" + x_min + "</td>";
  string += "<td colspan=" + (x_range - pre + 1)
          + " class=bottom valign=bottom align=right>" + x_max + "</td>";
  string += "</tr></table>";

  document.body.innerHTML += string;
}

function setTable(index, x_plot, y_plot, comment){
  var td_id = document.getElementById(index + "_" + x_plot + "_" + y_plot);
  if(td_id == null || td_id == undefined){
      return;
  }
  td_id.style.backgroundColor = "#99dd99";
  td_id.class = comment;

  addHintListener(index + "_" + x_plot + "_" + y_plot);
}

function initGraph(header, index, x_array, y_array, type){
  var y_pixel=100;
  var y_top=100;
  var y_bottom=0;
  if(type == null || type != "percent"){
    for(var i=0; i<y_array.length; i++){
      if(i==0){ y_bottom=y_array[i]; y_top=y_array[i]; }
      if(y_top<=y_array[i]){ y_top=y_array[i]; }
      if(y_bottom>=y_array[i]){ y_bottom=y_array[i]; }
    }
  }
  makeTable(header, index, y_array.length, y_pixel, x_array[0], x_array[x_array.length-1], y_bottom, y_top);

}

function setGraph(header, index, x_array, y_array, type){
  // same action with initGraph, but do again,
  // because of I want to addListener after make All Graphs.
  var y_pixel=100;
  var y_top=100;
  var y_bottom=0;
  if(type == null || type != "percent"){
    for(var i=0; i<y_array.length; i++){
      if(i==0){ y_bottom=y_array[i]; y_top=y_array[i]; }
      if(y_top<=y_array[i]){ y_top=y_array[i]; }
      if(y_bottom>=y_array[i]){ y_bottom=y_array[i]; }
    }
  }
  var y_dot = (y_top - y_bottom) / y_pixel;
  if(y_dot <= 0){
    return;
  }
  for(var i=0; i<y_array.length; i++){
    var value = y_pixel - Math.round((y_array[i] - y_bottom) / y_dot);
    for(var n=y_pixel; n>=value; n--){
      setTable(index, i, n, x_array[i] + "<br>" + y_array[i]);
    }
  }
}

//---ここに必要な変数を追加or削除する---------------------------------------------------
document.addEventListener("DOMContentLoaded", function(){
  // Title,  any_ID, Array_for_X, Array_for_Y, Type[percent|null]
  initGraph("$cpu_all_title", "cpu", cpu_all_x, cpu_all_y, "percent");
  initGraph("$mem_use_title", "mem", mem_use_x, mem_use_y, "percent");
  initGraph("$load_1m_title", "ld", load_1m_x, load_1m_y, null);
  initGraph("$rxp_eth0_title", "rxp", rxp_eth0_x, rxp_eth0_y, null);
  initGraph("$rxk_eth0_title", "rxk", rxp_eth0_x, rxk_eth0_y, null);

  setGraph("$cpu_all_title", "cpu", cpu_all_x, cpu_all_y, "percent");
  setGraph("$mem_use_title", "mem", mem_use_x, mem_use_y, "percent");
  setGraph("$load_1m_title", "ld", load_1m_x, load_1m_y, null);
  setGraph("$rxp_eth0_title", "rxp", rxp_eth0_x, rxp_eth0_y, null);
  setGraph("$rxk_eth0_title", "rxk", rxp_eth0_x, rxk_eth0_y, null);

}, false);
var cpu_all_x = [$cpu_all_x];
var cpu_all_y = [$cpu_all_y];
var mem_use_x = [$mem_use_x];
var mem_use_y = [$mem_use_y];
var load_1m_x = [$load_1m_x];
var load_1m_y = [$load_1m_y];
var rxp_eth0_x = [$rxp_eth0_x];
var rxp_eth0_y = [$rxp_eth0_y];
var rxk_eth0_y = [$rxk_eth0_y];
//---(変数の追加 or 削除 ここまで)---------------------------------------------------


-->
    </script>
</head>
<body>
  <div id=HintDiv>now loading</div>
  <h3>$SARINFO</h3>
</body>
</html>
EOS
`

printf "Content-type: text/html; charset=UTF-8\r\n"
printf "Content-Length: %d\r\n"  `expr length "$body"`
printf "\r\n"
printf '%s' "$body"
printf "\r\n"

*1 テレビも無ぇ、ラジオも無ぇ、助けも無ぇ、だがデッドラインは無くならない、オラこんなプロジェクト嫌だ~・・・
*2 CANVAS?HTML5?はて?まだ食べたことありません?
*3 つまり、TABLEタグで描いたドット絵です。1マスを1x1ピクセルにして、背景色を塗ってます。