Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

サーバのCPU使用率やメモリ使用率監視Webアプリ作成

先日、PythonでCPU使用率やメモリ資料率を監視するためのアプリを作成してみました。
elsammit-beginnerblg.hatenablog.com

今回は、こちらのアプリを改造してWebアプリを作成!!
これでネットワークが接続された環境であればどこでもサーバの監視・管理ができます!!



■環境

前回と同様にPythonのバージョンは、
Python 3.7.3
とします。

また、Webアプリ作成にあたり、サーバサイドはFlaskを用います。
Flaskについてはこちらにまとめておりますので、よろしかったらご参考にしてください。
elsammit-beginnerblg.hatenablog.com

■完成形

今回作成したアプリですがこちらの通りになります。
左側グラフにはCPU使用率 & メモリ使用率を表示させ、
右側グラフには使用しているメモリや空きメモリの容量について表示させています。
最後にグラフの下部にストレージの使用量をバー表示させました。
f:id:Elsammit:20210812230657g:plain

今回はこちらのアプリに関して、サーバサイド、フロントエンドに分けてまとめていきたいと思います!!

■Webアプリ(サーバサイド編)

ではまずはサーバサイドについてまとめていきたいと思います!!
コードはこちら!!

#!/usr/bin/env python
from importlib import import_module
import os
from flask import Flask, render_template, Response, url_for, request, redirect
import psutil
import numpy as np
import json
import csv
import math

app = Flask(__name__)

def allwed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.context_processor
def override_url_for():
    return dict(url_for=dated_url_for)

def dated_url_for(endpoint, **values):
    if endpoint == 'static':
        filename = values.get('filename', None)
        if filename:
            file_path = os.path.join(app.root_path,
                                     endpoint, filename)
            values['q'] = int(os.stat(file_path).st_mtime)
    return url_for(endpoint, **values)

x = 1
y = {0:[0,0]}
def GetResource():  #使用率取得用コールバック関数.
    global x
    mem = psutil.virtual_memory()
    cpu = psutil.cpu_percent(interval=0.1, percpu=False)    
    y[x] = [mem.percent,cpu]
    x += 1
    if x > 200:
        x = 0
        y.clear()

x2 = 1
y2 = {0:[0,0,0]}
def GetResource2():  #メモリ詳細情報取得用コールバック関数.
    global x2
    mem = psutil.virtual_memory()
    free = mem.free/1000000
    used = mem.used/1000000
    available = mem.available/1000000
    y2[x2] = [free, used, available]
    x2 += 1
    if x2 > 200:
        x2 = 0
        y2.clear()    
        

#メイン画面用html
@app.route('/')
def index():
    return render_template('index.html')

#CPU使用率 & メモリ使用率返却用
@app.route('/UseCheck', methods=["POST"])
def RespResource():
    GetResource()
    return Response(json.dumps(y), 200)

#メモリ使用率詳細返却用
@app.route('/CheckDetail', methods=["POST"])
def RespResource2():
    GetResource2()
    return Response(json.dumps(y2), 200)

#ストレージ容量返却用
@app.route('/StorageCheck', methods=["POST"])
def RespResource3():
    storage = psutil.disk_usage('/mnt/samba')
    jsn = {
        "persent":storage.percent,
        "total":math.floor(storage.total/1000000000),
        "used":math.floor(storage.used/1000000000)
        }
    return Response(json.dumps(jsn), 200)

if __name__ == '__main__':
    w_str = "No,memory persent(%),cpu persent(%) \n"
    with open("output.csv", mode='w') as f:
        f.write(w_str)
    app.run(host='0.0.0.0', threaded=True)

CPUやメモリ使用率の取得に関しては先日まとめたpsutilライブラリを用いて取得しています。
使い方やコードの書き方についてはこちらをご参考ください。
elsammit-beginnerblg.hatenablog.com

今回フロント・サーバ間はPOST通信を用いておjり、サーバからのresponseはjson形式のデータとしました。
json形式のデータはそれぞれ、

・CPU使用率 & メモリ使用率(左端グラフ用データ)

{
   '番号':['メモリ使用率','CPU使用率']
}

・使用しているメモリや空きメモリの容量(右端グラフ用データ)

{
   '番号':['空きメモリ','使用メモリ','availableメモリ']
}

・ストレージ容量

{
    "persent":使用率,
    "total":全体ストレージサイズ,
    "used":使用ストレージサイズ
}

といった形式としました。

リクエストが来た際に上記データ形式でresponseしています。

■Webアプリ(フロントエンド編)

次にフロントサイド側です。
グラフ表示を行うにあたり、Chart.jsを用いました。
html文はこちらの通りになります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js"></script>
    <link rel="stylesheet" href="{{url_for('static', filename='css/style.css')}}">
    <title>Document</title>
</head>
<body>
    <h1>リソースチェックサイト</h1>
    <div id="graphArea">
        <p id="graphTitle">リソース使用率</p>
        <canvas id="myLineChart"></canvas>
    </div>
    <div id="graphArea">
        <p id="graphTitle">メモリ使用状況</p>
        <canvas id="myLineChart2"></canvas>
    </div>
    <div id="barArea">
        <label for="storageBar">ストレージ容量:</label>
        <progress id="storageBar" max="100" value="70"></progress>
        <div id="storageSize">****GB/****GB</div>
    </div>
    <script src="{{url_for('static', filename='js/script.js')}}"></script>
</body>
</html>

Chart.jsを用いるために、

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js"></script>

をコールしておりかつ、postリクエストを行うにあたりjQueryを用いたかったので

<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

をコールしています。

canvas上にChart.jsでのグラフ表示を行うため、

<canvas id="myLineChart"></canvas>
<canvas id="myLineChart2"></canvas>

を記載しています。

次にjavascriptです。
こちらは少し長いです。。

var labels =  [1,2,3,4,5,6,7,8,9,10,11,12];
var sample1 =  [1.9, 2.32, 1.52, 0.79, 1.37, 1.28, 1.92, 1.44, 2.58, -0.01, 0.71, 4.25];
var sample2 = [7.01, -2.15, -7.29, 1.71, 0.72, -4.83, 2.75, 4.11, 3.08, -2.45, 3.05, -3.93];
var sample21 =  [1.9, 2.32, 1.52, 0.79, 1.37, 1.28, 1.92, 1.44, 2.58, -0.01, 0.71, 4.25];
var sample22 = [7.01, -2.15, -7.29, 1.71, 0.72, -4.83, 2.75, 4.11, 3.08, -2.45, 3.05, -3.93];
var sample23 =  [5.9, 8.32, -1.52, 9.79, 10.37, -1.28, 10.92, 11.44, 12.58, -0.51, 1.71, -4.25];

var ctx = document.getElementById("myLineChart");
chart = new Chart(ctx, {
  type: 'line',
  data: {
    labels: labels,
    datasets: [{
      label: 'メモリ使用率',
      data: sample1,
      borderColor: 'rgba(60, 160, 220, 1)',
      backgroundColor: "rgba(0,0,0,0)"
    },{
      label: 'CPU使用率',
      data: sample2,
      borderColor: 'rgba(60, 190, 20, 1)',
      backgroundColor: "rgba(0,0,0,0)"
    }]
  },
  options: {
    animation: false,
    scales: {
      xAxes: [{
        scaleLabel: {
          display: true,
          labelString: '時間(秒)',   
          fontColor: "black", 
          fontSize: 16
        },
        ticks:{
          max:15,
          min:0,
          stepSize:1,
          fontColor: "black",
          fontSize: 14
        }
      }],
      yAxes: [{
        scaleLabel: {                  
          display: true,               
          labelString: '使用率(%)',   
          fontColor: "black",
          fontSize: 16     
        }
      }]
    }
  }
});

var ctx = document.getElementById("myLineChart2");
var chart2 = new Chart(ctx, {
  type: 'line',
  data: {
    labels: labels,
    datasets: [{
      label: 'メモリ-free',
      data: sample21,
      borderColor: 'rgba(60, 160, 220, 1)',
      backgroundColor: "rgba(0,0,0,0)"
    },{
      label: 'メモリ-used',
      data: sample22,
      borderColor: 'rgba(60, 190, 20, 1)',
      backgroundColor: "rgba(0,0,0,0)"
    },{
      label: 'メモリ-available',
      data: sample23,
      borderColor: 'rgba(160, 90, 120, 1)',
      backgroundColor: "rgba(0,0,0,0)"
    }]
  },
  options: {
    animation: false,
    scales: {
      xAxes: [{
        scaleLabel: {
          display: true,
          labelString: '時間(秒)',   
          fontColor: "black", 
          fontSize: 16
        },
        ticks:{
          max:15,
          min:0,
          stepSize:1,
          fontColor: "black",
          fontSize: 14
        }
      }],
      yAxes: [{
        scaleLabel: {                  
          display: true,               
          labelString: 'メモリ容量(MB)',   
          fontColor: "black",
          fontSize: 16     
        }
      }]
    }
  }
});

function RecieveData(){
    getResourceData();
    getResourceData2();
    getResourceData3();

    chart.data.datasets[0].data = sample1;
    chart.data.datasets[1].data = sample2;
    chart.update();

    chart2.data.datasets[0].data = sample21;
    chart2.data.datasets[1].data = sample22;
    chart2.data.datasets[2].data = sample23;
    chart2.update();
}

function setChartLabel(jsn, ChartBuf){
  if(Object.keys(jsn).length > 12){
    ChartBuf.options.scales.xAxes[0].ticks.max = Object.keys(jsn).length;
    ChartBuf.options.scales.xAxes[0].ticks.min = Object.keys(jsn).length - 12;
  }else{
    ChartBuf.options.scales.xAxes[0].ticks.max = 12;
    ChartBuf.options.scales.xAxes[0].ticks.min = 0;    
  }
}

function getResourceData(){
    $.ajax({
        url: '/UseCheck',
        type:'POST',
        dataType:"text",
        timeout:3000,
    }).done(function(data) {
        jsn = JSON.parse(data.toString());
        for(var i=0;i<Object.keys(jsn).length;i++){
            sample1[i] = jsn[i][0];
            sample2[i] = jsn[i][1];
            labels[i] = i;
        }
        setChartLabel(jsn, chart);
    }).fail(function() {
        console.log("error");
    })
}

function getResourceData2(){
    $.ajax({
        url: '/CheckDetail',
        type:'POST',
        dataType:"text",
        timeout:3000,
    }).done(function(data) {
        jsn = JSON.parse(data.toString());
        for(var i=0;i<Object.keys(jsn).length;i++){
            sample21[i] = jsn[i][0];
            sample22[i] = jsn[i][1];
            sample23[i] = jsn[i][1];
            labels[i] = i;
        }
        setChartLabel(jsn, chart2);
    }).fail(function() {
        console.log("error");
    })
}

function getResourceData3(){
  $.ajax({
      url: '/StorageCheck',
      type:'POST',
      dataType:"text",
      timeout:3000,
  }).done(function(data) {
      jsn = JSON.parse(data.toString());
      var bar = document.getElementById("storageBar");
      var storageSize = document.getElementById("storageSize");
      bar.value = jsn["persent"];
      storageSize.innerText = jsn["used"] + "GB/" + jsn["total"] + "GB";
  }).fail(function() {
      console.log("error");
  })
}

setInterval(RecieveData,1000);

やっていることは、RecieveData関数を1秒ごとにコールし、
RecieveData内でjQueryによるPostリクエストを実行しています。

function getResourceData(){
    $.ajax({
        url: '/UseCheck',
        type:'POST',
        dataType:"text",
        timeout:3000,
    }).done(function(data) {
        jsn = JSON.parse(data.toString());
        for(var i=0;i<Object.keys(jsn).length;i++){
            sample1[i] = jsn[i][0];
            sample2[i] = jsn[i][1];
            labels[i] = i;
        }
        setChartLabel(jsn, chart);
    }).fail(function() {
        console.log("error");
    })
}

function getResourceData2(){
    $.ajax({
        url: '/CheckDetail',
        type:'POST',
        dataType:"text",
        timeout:3000,
    }).done(function(data) {
        jsn = JSON.parse(data.toString());
        for(var i=0;i<Object.keys(jsn).length;i++){
            sample21[i] = jsn[i][0];
            sample22[i] = jsn[i][1];
            sample23[i] = jsn[i][1];
            labels[i] = i;
        }
        setChartLabel(jsn, chart2);
    }).fail(function() {
        console.log("error");
    })
}

function getResourceData3(){
  $.ajax({
      url: '/StorageCheck',
      type:'POST',
      dataType:"text",
      timeout:3000,
  }).done(function(data) {
      jsn = JSON.parse(data.toString());
      var bar = document.getElementById("storageBar");
      var storageSize = document.getElementById("storageSize");
      bar.value = jsn["persent"];
      storageSize.innerText = jsn["used"] + "GB/" + jsn["total"] + "GB";
  }).fail(function() {
      console.log("error");
  })
}

先ほどのサーバサイドで記載した通り、responseはjson形式となるので、
JSON.parseによりjsonを分割し適切な変数に格納しています。
本変数に格納したデータをChart.jsでグラフ化して表示しています。

グラフ範囲を大きくしすぎると見にくくなってしまうので、
範囲は12にし、本範囲を自動的に変化させられるようにしました。
範囲の自動変更に関してはこれからも使う技術な気がしているので、次回のブログにて細かく記載していきたいと思います!!

■最後に

ちょっと長くなってしまいました。
Webで公開したことにより、逐次見えるようになったのは少しありがたいかな?w
いったん自宅サーバに導入して使い勝手を見ていきたいと思います!!
使い勝手が悪いところは後で直そっと。