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

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

MENU

オフラインでjQueryやChart.jsを用いるために

先日、PythonのFlaskを用いてサーバのCPU使用率やメモリを可視化するWebアプリを作成してみました。
elsammit-beginnerblg.hatenablog.com

こちらを用いて監視作業を行っているのですが、
外部ネットワーク環境に接続せず、ローカル環境でも使用するケースが発生!!

先日のコードですと、jQueryやChart.jsが下記の通り
外部より取得・利用しているので外部ネットワークに接続していないと動かない!!

<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>

ということで、
今回はこちらのjQueryやChart.jsを外部ネットワークに接続しなくてもオフラインで使用する方法に関してまとめていきたいと思います。



■環境

今回動作させた環境ですがraspberryPi 4になります。
ただ、Linux OSであればこちらの手順は適用できるかと思います。

■準備

jQueryやChart.jsをオフラインで利用するには、
「ネットワーク経由で利用しているコードをローカルにダウンロード」
ことが必要になります。

jQueryやChart.jsをダウンロードするにあたり今回はnpmを用います。
各ライブラリ毎に専用のWebページがあるのでそちらから手動でダウンロードをするのもいいのですが、
面倒なのでパッケージ管理ツールであるnpmを用いることにしました。

npmをインストールするにはこちらを実行すればOK。

sudo apt install npm

jQueryやChart.jsをローカルへダウンロード

では早速npmでダウンロードしていきます。

まずはjQuery
こちらのコマンドを実行すればOKです。

npm install jquery --save

実行時にnode_modulesというフォルダが作成されかつ、
node_modules/jquery/dist配下にjquery.jsファイルが存在すればインストール成功です。

次にChart.jsです。
Chart.jsをこちらのように実行すると最新バージョンのChart.jsがインストールされます。

npm install chart.js

ダウンロードされたファイルを確認したのですが、、
Chart.bundle.jsがない!!

試しにChart.jsをもちいてみたのですが、

ChartBuf.options.scales.xAxes[0].ticks.max = 10;
ChartBuf.options.scales.xAxes[0].ticks.min = 0;    

といったコードを実行するとticksが存在しない!!と怒られてしまう。。

そこで、外部ネットワークと同様のバージョンに合わせるために、

npm install chart.js@2.8.0

で2.8.0のchart.jsをダウンロード。
すると、
node_modules/chart.js/dist配下にChart.bundle.jsが存在!!
無事に動かすことが出来ました。

■Flaskでダウンロードしたライブラリを用いてみる

先ほどの手順でnode_module配下にjQueryとChart.jsがダウンロードできました。
Flaskの場合はさらにパスを通す必要があります。
と言ってもすでに自作javascriptを利用するためにパスを通していたのでこちらのパスを用いることにしました。
Flask用のpythonコードと同階層にstaticフォルダを作成し、
自作javascriptと同様に

    <script src="{{url_for('static', filename='node_modules/jquery/dist/jquery.js')}}"></script>
    <script src="{{url_for('static', filename='node_modules/chart.js/dist/Chart.bundle.js')}}"></script>

といったコードをhtmlファイルに追記。
これでオフライン環境でもjQueryやChart.jsが用いることが出来るようになります。

一応htmlコードを抜粋して載せておきます。

<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 src="{{url_for('static', filename='node_modules/jquery/dist/jquery.js')}}"></script>
    <script src="{{url_for('static', filename='node_modules/chart.js/dist/Chart.bundle.js')}}"></script>
    
    <!--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')}}">
</head>

■最後に

今回はjQueryやChart.jsをオフライン環境で利用するための方法をまとめてみました。
Chart.jsはChart.bundle.jsファイルが見つからず結構苦労しました。。
まずはバージョンを確認しろ!!っていうことですね。。反省。。



pythonで複数枚の写真からスライド動画を作成

今回は複数の写真をつなぎ合わせ、スライドショーのような動画を作成していきます。
写真だけ眺めていてもちょっと味気なくなってきたのでスライドショーを作成することにしました!!
さらに、ただ写真がスライドショー形式で表示されても、、、なので、
作成したスライドショーに音楽も追加してみました!!

今後も何かで使えそうな技術なので備忘録として残しておこうと思います。



■環境準備

まずは今回使用する環境です。
言語はタイトルの通りpythonを持ちますが、バージョンは
Python 3.8.2
を用いました。
Python 3.7.3でも試しに動かしたのですが問題なく動きました。

今回動画作成にあたり用いたライブラリは、
"MoviePy"
です。

【MoviePyとは?】
カット、連結、タイトル挿入、ビデオ合成、ビデオ処理、エフェクト追加・作成などのビデオ編集をPythonで行うためのライブラリになります。
MoviePyはFFmpegを利用して画像・動画の編集を行っています。
FFmpegですが、MoviePyをインストールした際に自動でインストールされるようです。
WindowsmacOSLinuxでサポートされているようです。
さらに、サポート対象のPythonバージョンは3.6以降とのこと。
先ほど記載しました通り、私の環境は3.6以上になりますので問題なく動作することになります。


MoviePyのインストールですが、下記コマンドを実行すればOKです。

pip install moviepy
or 
pip3 install moviepy

インストール完了するまでに少し待ちますので、コーヒーでも飲みながら気長に待ってみてください。

これで環境のセットアップは完了です。

■複数枚の写真から動画を作成

では本題の複数枚の写真から動画を作成してみます。
早速ですが、コードはこちら。

import glob
from moviepy.editor import *
import os

def MakeImgToMovie():
    file_list = glob.glob(r'/写真までのパス/*.jpg')
    file_list.sort()
    clips = []
    for m in file_list:
        clip = ImageClip(m).set_duration('00:00:04')  #画像が表示され続ける時間(今回は4秒)
        clip = clip.crossfadein(0.5)                              #画像表示時のアニメーションとして0.5秒のフェードインを追加
        clip = clip.crossfadeout(0.5)                           #画像が消える時のアニメーションとして0.5秒のフェードアウトを追加
        clip = clip.resize(newsize=(Width,Height))    #動画化する際の画像サイズをリサイズ
        clips.append(clip)                                           
    
    concat_clip = concatenate_videoclips(clips, method="compose")
    concat_clip.write_videofile(r"作成先動画パス",fps=24)

まず、globライブラリを用いて複数写真(画像)をファイルリストとして読み出します。
合わせて、読み出し順が崩れないようにソートも実行。

file_list = glob.glob(r'/写真までのパス/*.jpg')
file_list.sort()

次にスライドショーにするために各画像のアニメーションや表示時間を設定していきます。

    for m in file_list:
        clip = ImageClip(m).set_duration('00:00:04')  #画像が表示され続ける時間(今回は4秒)
        clip = clip.crossfadein(0.5)                              #画像表示時のアニメーションとして0.5秒のフェードインを追加
        clip = clip.crossfadeout(0.5)                           #画像が消える時のアニメーションとして0.5秒のフェードアウトを追加
        clip = clip.resize(newsize=(Width,Height))    #動画化する際の画像サイズをリサイズ
        clips.append(clip)    

set_durationを用いて画像が表示され続ける時間の長さを調整。
crossfadein、crossfadeoutにより画像切り替え時に0.5秒のフェードイン・フェードアウトを追加。
そして、resize(newsize=(Width,Height))で動画化した時の画像サイズを指定画像サイズにリサイズ。

ここで、00:00:04や0.5、Width,Heightには好きな数値を入れてください。
ただし、これらの数値が大きいほど動画作成にかなりの時間を要することになるため注意が必要です。

最後に、

concat_clip = concatenate_videoclips(clips, method="compose")
concat_clip.write_videofile(r"作成先動画パス",fps=24)

にて、編集した画像群を動画として結合し動画化します。
write_videofileにて動画のfpsもセットできます(今回は24fpsに設定してみています)。

■MoviePyは動画生成が遅い?

ではコードもでき、画像も準備出来たので早速実行!!
してみたのですが、、どうにも処理速度が遅い。。
FFmpegを用いていることので、
write_videofile関数の引数にcodecを指定してハードウェアエンコードも試したのですが結果は変わらず。。

調べてみたところ、どうやらMoviePyライブラリ内のwrite_videofile関数に問題があるようで、
実行後、リソース開放が完了するまでの間待ち続けてしまうとのこと。

少しでも早くするためには画像サイズや画像表示時間を短くすることで、
実際の画像処理にかかる時間を縮めるないしは使用するフレーム数を減らす必要があるようです。
まぁ、ハイスペックPCであれば短くなるのでしょうが。。

■動画に音楽を付与する

では先ほど作成した動画に音楽を付与してみます。
コードはこちら。

def AddAudioToMovie():
    clip = VideoFileClip(r"動画パス")
    audioclip = AudioFileClip(r"音楽パス")
    new_videoclip = clip.set_audio(audioclip)
    new_videoclip.write_videofile(r"合成した動画保存先")

解説することがないくらいですが、、
まず、

    clip = VideoFileClip(r"動画パス")
    audioclip = AudioFileClip(r"音楽パス")

で合成させたい動画と音楽をVideoFileClip、AudioFileClipで読み出します。

そして、

new_videoclip = clip.set_audio(audioclip)

にて動画と音声を結合。

最後に、

new_videoclip.write_videofile(r"合成した動画保存先")

で保存先に動画を保存します。

■作成してみた

では、先ほどのコードで作成した動画の一部を下記に載せます。
f:id:Elsammit:20210829152937g:plain
確かにスライドショーのように動画が切り替わっていることが分かるかと思います。

■最後に

ライブラリを使用すること簡単に複数写真からスライドショーのような動画を作成してみました。
作ってみた感想としては、簡単に作れるけど凝ったことを使用とすると難しかったり手間がかかるので商用のビデオツールはやはり優秀だな、と感じたことです。
う~~ん。
やはり編集ソフト持っていたほうが良いかな。。
まぁ、作るの好きだし苦ではないんですがw



pythonでグラフ付きエクセルファイルを自動出力

最近取得したcsvファイルからexcelファイルにコピーしてグラフ化して報告することが多くなってきた。
面倒なので自動化出来ないのかな??と思い調べてみたところpythonを用いれば結構簡単にexcelにデータの書き込みとエクセル出力できることを発見!!
早速実施してみると、本当に簡単!!
今回はこちらの方法を備忘録としてまとめていきます!!



■環境構築

今回用いるpythonのバージョンですが、
Python 3.7.3
です。
といっても、3.8系や3.6系でも試してみたのですが同じ方法で実施できました。

pythonexcelファイルに出力するにあたり、
openpyxl
を用います。

【openpyxltとは】
openpyxlはExcelの読み書きをpythonで行うためのモジュールになり、
・特定のセルに読み書きを行う
・シートを追加する
・グラフを追加する
などの処理が行えます。
※参考:https://rightcode.co.jp/blog/information-technology/python-openpyxl-excel-automatic-operation

openpyxlを利用するにあたり、下記でライブラリのインストールを行ってください。

pip install openpyxl

or

pip3 install openpyxl

これで準備完了です。
環境構築は簡単かつすぐに行えます。

■openpyxlで各セルにデータを書き込む

ではまずデータを書き込む方法に関してまとめていきます。

前提条件としてdataX, dataY変数を用いるのですが、
各データは下記とします。

dataX = ["index",1,2,3,4,5]
dataY = [
     ["data1",10,5,30,50,80],
     ["data2",100,5,30,10,100]
    ]

では全体のコードですが、下記となります。

def DataToXmlFiles(dataX, dataY, outfile):
    wb = Workbook()
    ws = wb.active

    for var in range(len(dataX)):
        ws.cell(row=var+1,column=1).value = dataX[var]
        for i in range(2):
            ws.cell(row=var+1, column=i+2).value = float(dataY[i][var])
    wb.save(outfile)

まず、

    wb = Workbook()
    ws = wb.active

にてセルの書き込むための新規シートを定義します。

後は、

ws.cell(row=var+1,column=1).value = values

といった形でrow,columnで挿入したいセル値を与えてvalueにセットしたい値や文字列を与えるだけです。
row、columnは左上から1,2,3~~になります。
今回先ほどのdataXをexcelシートのA列、dataYをB列、C列にセットしたかったので、

    for var in range(len(dataX)):
        ws.cell(row=var+1,column=1).value = dataX[var]
        for i in range(2):
            ws.cell(row=var+1, column=i+2).value = float(dataY[i][var])

といったような制御としました。
valueにセットするのは数値、文字列どちらでもセット可能です。
意識せずともセットすることが可能になっています。

最後に、

wb.save(outfile)

でエクセルファイルとして保存します。

■openpyxlでグラフを作成する

では次に先ほどセットしたセル値から自動でグラフを作成していきます。
コードはこちら。

def DataToXmlFiles(dataX, dataY, outfile):
    wb = Workbook()
    ws = wb.active

    for var in range(len(dataX)):
        ws.cell(row=var+1,column=1).value = dataX[var]
        for i in range(2):
            ws.cell(row=var+1, column=i+2).value = float(dataY[i][var])
    wb.save(outfile)

    #==========ここから追記==========
    values = Reference(ws, min_col=2, min_row=1, max_col=3, max_row=len(dataX))
    categories = Reference(ws, min_col=1, min_row=2, max_col=1, max_row=len(dataX))

    chart = LineChart()

    chart.add_data(values, titles_from_data=True)
    chart.set_categories(categories)

    ws.add_chart(chart,"E1")
    #==========ここまで追記==========
    wb.save(outfile)

追加したコードは下記になります。

    values = Reference(ws, min_col=2, min_row=1, max_col=3, max_row=len(dataX))
    categories = Reference(ws, min_col=1, min_row=2, max_col=1, max_row=len(dataX))

    chart = LineChart()

    chart.add_data(values, titles_from_data=True)
    chart.set_categories(categories)

    ws.add_chart(chart,"E1")

まず、

 values = Reference(ws, min_col=2, min_row=1, max_col=3, max_row=len(dataX))

にてグラフとして作成グラフの各種(y列)をセットしていきます。
min_colやmin_rowには最小の行列値、
max_col、max_rowには最大の行列値をセットします。
要するに、
min_col ~ max_colで指定した範囲の列かつ、
min_row ~ max_rowで指定した範囲の行
に関してグラフを作成しますよ。
ということです。
今回はB、C列かつ5行になりますので、
min_col = 2 (Bから)、max_col=3(C列まで)
min_row = 1(1行目から)、max_row=len(dataX)(dataX要素数まで)
と定義しました。

categories = Reference(ws, min_col=1, min_row=2, max_col=1, max_row=len(dataX))

はX軸として指定する範囲になります。
指定の仕方は先ほどと同様なので割愛します。

後は、どんなグラフを用いるのかを

chart = LineChart()

で指定した後、

    chart.add_data(values, titles_from_data=True)
    chart.set_categories(categories)

にてシートにグラフをセットします。
ここで、
titles_from_data=True
を指定しますと、valuesで指定したデータの先頭1行目を系列名として用いることが出来ます。
逆に系列名は不要な場合には、

chart.legend = None

とすればOKです。

最後に、

ws.add_chart(chart,"E1")

にてグラフをシート上にセット。
セットする値は第2引数にセットすることが出来、今回は"E1"の位置にしました。

■実行結果

こちらのコードを実行すると下記のようなエクセルファイルが出力されます。
f:id:Elsammit:20210826231044p:plain

問題なくグラフ化とデータが書き込んでエクセル出力できました。

■最後に

今回は作業自動化のためにデータをエクセルに掃き出しかつグラフの作成まで自動化するためのコードを作成しました。
これはかなり便利なのでもう少しツールを使い込んでいこうと思います!!
どうやらパワーポイントも作れそうなのでこっちも調べてみようかな??



Chart.js で横軸を動的に指定して範囲を変更していく

今回はChart.jsで横軸のスケールで自動で動かす方法についてまとめておきたいと思います。
前回のブログの最後に後でまとめていきます。と記載した内容です。
elsammit-beginnerblg.hatenablog.com



■Chart.jsで横軸範囲を指定する

まずはChart.jsで横軸を指定する方法に関してまとめていきます。
Chart.jsで横軸を指定するには、optionタグのticksを指定すればoKです。
例えば、

var ctx = document.getElementById("myLineChart3");
var chart = new Chart(ctx, {
  type: 'line',
  data: {
    labels: [1,2,3,4,5,6,7,8,9,10,11],
    datasets: [{
      label: 'CPU使用率',
      data: [10,20,50,30,0,15,20,10,80,12,32],
      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:8,
          min:3,
          stepSize:1,
          fontColor: "black",
          fontSize: 14
        }
      }],
    }
  }
});

というようにコードを作成すると、
f:id:Elsammit:20210820223218p:plain
といったように、グラフに与えている横軸データは、

[1,2,3,4,5,6,7,8,9,10,11]

と11項目あるにも関わらず、

max:8,
min:3

と指定しているためにグラフの範囲が3~8になっています。
こちらのmax, minの値を変更させればグラフの範囲を自由に変化させることができます。
ここで、

max:20,
min:3

といったように横軸データを超えて範囲を指定した場合には、データの最大値が末端になります。
f:id:Elsammit:20210820223953p:plain

逆に、

max:3,
min:8

とした場合はどうなるのでしょうか??
答えは、こちらの図のようにminで指定した値が一区間だけ表示されるようです。
f:id:Elsammit:20210820223953p:plain

■横軸を動的に動かしてみる

先ほどまでで、ticks内のmax,minを指定すれば横軸の範囲を指定すればよいことがわかりました。
では、本題の横軸の範囲を動的に指定して横軸を動かしてみたいと思います。
完成形はこんな感じになります。
f:id:Elsammit:20210812230657g:plain

コード全体は前回のブログをご覧になっていただくとして、
elsammit-beginnerblg.hatenablog.com

コードを抜き出すとこちらになります。

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;    
  }
}

setChartLabelの引数はjsnとChartBufです。
ChartBufには、new Chartで生成したChartオブジェクトになります。

jsnですがリスト型でサーバからくるデータ群が格納されています。
こちらのデータは時間により格納されていきます。
データが100も1000もあるとすべて表示できないので、
データが追加された時に本関数がコールされ、
maxを最大データ数、minを最大データ数-12と12個のデータに限定しています。

データ追加時に12個の範囲に限定してminの値をコントロールしているので、
動的に動いたようになる。ということです。
※1個追加するとmaxの値がmax+1されminの値がmin+1になるので右に動いているように領域が変化するのですね。

■最後に

今回はChart.jsで横軸の指定の仕方と動的に横軸の範囲を変化させてグラフを見やすくする方法についてまとめました。
自宅で使用してみているのですが、もう少し使いやすくしたいアイデアがあるのでもう少しいじってみます!!
いいものができたらまた紹介していきたいと思います!!


サーバの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
いったん自宅サーバに導入して使い勝手を見ていきたいと思います!!
使い勝手が悪いところは後で直そっと。



Pythonでリソース管理を行ってみる

今回はCPU使用率やメモリ使用率などのチェックを行うためのコードに挑戦してみたいと思います!!
目指すは、サーバー上のリソースを監視できるWebアプリの作成です!!

今回はPythonでどのようにリソースを監視すればよいのか?
方法をまとめておこうと思います!!



■環境

冒頭やタイトルで記載した通り、Pythonを用いていきます。
Pythonのバージョンは、

Python 3.7.3

です。

Pythonでリソースチェックする方法

PythonにはCPU使用率やメモリ使用率などのリソースを取得するライブラリとして、
psutil
が用意されております。

公式ドキュメントには下記が記載されております。

psutilは、pythonでの実行中のプロセス及びシステム使用率に関する情報を取得するためのクロスプラットフォームライブラリになります。
システムのモニタリングやプロファイリング、プロセスリソース制限や実行プロセス管理などに役立つ。
psutil (python system and process utilities) is a cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network, sensors) in Python. It is useful mainly for system monitoring, profiling, limiting process resources and the management of running processes. It implements many functionalities offered by UNIX command line tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. psutil currently supports the following platforms:

インストールするためには、

pip install psutil

を実行すればOKです。

■psutilでシステム使用率をチェックしてみる

ではpsutilでCPU使用率やメモリ使用率をチェックしてみたいと思います。
コードはこちら。

import psutil

mem = psutil.virtual_memory()
print(mem.percent)
print(mem.free)
print(mem.used)
cpu = psutil.cpu_percent(interval=1)
print(cpu)

とてもシンプルですねw。

mem = psutil.virtual_memory()

によりmem変数にメモリ関係のデータが格納されます。
mem変数をprintしてみると、

svmem(total=8418025472, available=1307623424, percent=84.5, used=7110402048, free=1307623424)

といったようにトータルメモリやメモリ使用率、使用/未使用メモリ量が格納されています。
各データを抽出したい場合には、

print(mem.percent)
print(mem.free)
print(mem.used)

というようにすればOKです。

さらにCPU使用率は、

cpu = psutil.cpu_percent(interval=1)

とすればOKです。
intervalですが、CPU使用率を算出するまでの時間を示しており、こちらが短くすることですぐに出力が得られるのですが、
精度が落ちてしまいます。
逆に長くすれば精度は増加するようです。
トレードオフですね。。
こちらのintervalですが、0.1などの小数点も指定できます。

psutilはCPU使用率やメモリ使用率以外にもネットワークやディスクについても確認できます。
下記に使い方が載っているので是非ご参考ください。
https://githubja.com/giampaolo/psutil

■グラフ化してみる

psutilを用いることでリソースが出力されることが分かりました。
しかし、数値のみだと増減や変化率がわかりにくい!!
ということで、こちらの結果をグラフとして出力させてみました。

グラフ出力にあたり、matplotlibをもちいました。
コードはこちら。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
import psutil
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk

fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)
xlim = [0,100]
x = []
mem = []
cpu = []

def plotFunc(data):
    mem = psutil.virtual_memory()
    mem.append(mem.percent)
    cpu.append(psutil.cpu_percent(interval=0.1, percpu=False))
    x.append(len(x))

    ax1.plot(x,mem, color='blue')
    ax1.set_xlim(xlim[0],xlim[1])

    ax2.plot(x,cpu, color='red')
    ax2.set_xlim(xlim[0],xlim[1])
    
    if len(x) > 2000:
        mem.clear()
        cpu.clear()
        x.clear()
        xlim[0] = 0
        xlim[1] = 100
        ax1.cla()
        ax2.cla()
    elif len(x) > 100:
        xlim[0]+=1
        xlim[1]+=1

def Domatplot():
    ani = animation.FuncAnimation(fig, plotFunc, interval=100)
    plt.show()

Domatplot()

今回定期的に使用率の見える化をしたかったので、グラフを動的に変更させました。
グラフを動的に変更させるにあたり、matplotlibにはFuncAnimation関数が用意されていますのでこちらを利用しました。

FuncAnimationの引数ですが、
第1引数:Figure
第2引数:グラフを各インターバル毎に作成sるうコールバック関数
第3引数:コールバック関数をコールして画面を更新するインターバル
になります。

コールバック関数ですが、
plotFunc(data)
にあたります。

plotFunc(data)ではまず、

mem = psutil.virtual_memory()
    mem.append(mem.percent)
    cpu.append(psutil.cpu_percent(interval=0.1, percpu=False))
    x.append(len(x))

にてCPU使用率やメモリ使用率をリストに格納します。

そして、

    ax1.plot(x,mem, color='blue')
    ax1.set_xlim(xlim[0],xlim[1])

    ax2.plot(x,cpu, color='red')
    ax2.set_xlim(xlim[0],xlim[1])

にてグラフ上に先ほど格納したリスト型変数内のデータをプロットします。
これでコールされる毎にグラフの更新が行われます。

最後に、

    if len(x) > 2000:
        mem.clear()
        cpu.clear()
        x.clear()
        xlim[0] = 0
        xlim[1] = 100
        ax1.cla()
        ax2.cla()
    elif len(x) > 100:
        xlim[0]+=1
        xlim[1]+=1

ですが、あまりリストに変数を格納しておくことを嫌ったため2000データ格納された際にはリセットするようにしました。

 elif len(x) > 100:
        xlim[0]+=1
        xlim[1]+=1

はグラフの横軸を変化させ見やすくするためにセットしております。


こちらのコードを実行するとこちらのような結果となります。
f:id:Elsammit:20210808150518g:plain
グラフの上段がメモリ使用率、
下段がCPU使用率になります。
グラフ化することで数値より変化が分かりやすくなりましたね。

■最後に

今回はリソース管理ために数値出力やグラフによる変化をモニタリングしてみました。
次回はこちらをWebアプリとすることでどこからでも監視・モニタリングできるようにしてみたいと思います。
がんばるぞ~~



React for文内でonClickを定義する

約1年前、Reactでモグラたたきを作成しました。
elsammit-beginnerblg.hatenablog.com

久しぶりにこちらのコードを見たのですが、、、
ちょっと汚い、、いやちょっとではない!!かなりだ!!
ということでコード修正・整理を行いました。

コードを修正していたところ、モグラたたきのマップ作成のコードがこんな感じになっていました。。。
ちょっとこれは。。

            <table>
                <tbody>
                    <tr>
                        <td><img id="Mas1" src={shibafu} alt="green" onClick={() => this.onClick("Mas1" )} /></td>
                        <td><img id="Mas2" src={shibafu} alt="green" onClick={() => this.onClick("Mas2" )} /></td>
                        <td><img id="Mas3" src={shibafu} alt="green"  onClick={() => this.onClick("Mas3" )} /></td>
                        <td><img id="Mas4" src={shibafu} alt="green"  onClick={() => this.onClick("Mas4" )} /></td>
                        <td><img id="Mas5" src={shibafu} alt="green"  onClick={() => this.onClick("Mas5" )} /></td>
                    </tr>
                    <tr>
                        <td><img id="Mas6" src={shibafu} alt="green"  onClick={() => this.onClick("Mas6" )} /></td>
                        <td><img id="Mas7" src={shibafu} alt="green"  onClick={() => this.onClick("Mas7" )} /></td>
                        <td><img id="Mas8" src={shibafu} alt="green"  onClick={() => this.onClick("Mas8" )} /></td>
                        <td><img id="Mas9" src={shibafu} alt="green"  onClick={() => this.onClick("Mas9" )} /></td>
                        <td><img id="Mas10" src={shibafu} alt="green"  onClick={() => this.onClick("Mas10" )} /></td>
                    </tr>
                    <tr>
                        <td><img id="Mas11" src={shibafu} alt="green"  onClick={() => this.onClick("Mas11" )} /></td>
                        <td><img id="Mas12" src={shibafu} alt="green"  onClick={() => this.onClick("Mas12" )} /></td>
                        <td><img id="Mas13" src={shibafu} alt="green"  onClick={() => this.onClick("Mas13" )} /></td>
                        <td><img id="Mas14" src={shibafu} alt="green"  onClick={() => this.onClick("Mas14" )} /></td>
                        <td><img id="Mas15" src={shibafu} alt="green"  onClick={() => this.onClick("Mas15" )} /></td>
                    </tr>
                    <tr>
                        <td><img id="Mas16" src={shibafu} alt="green"  onClick={() => this.onClick("Mas16" )} /></td>
                        <td><img id="Mas17" src={shibafu} alt="green"  onClick={() => this.onClick("Mas17" )} /></td>
                        <td><img id="Mas18" src={shibafu} alt="green"  onClick={() => this.onClick("Mas18" )} /></td>
                        <td><img id="Mas19" src={shibafu} alt="green"  onClick={() => this.onClick("Mas19" )} /></td>
                        <td><img id="Mas20" src={shibafu} alt="green"  onClick={() => this.onClick("Mas20" )} /></td>
                    </tr>
                    <tr>
                        <td><img id="Mas21" src={shibafu} alt="green"  onClick={() => this.onClick("Mas21" )} /></td>
                        <td><img id="Mas22" src={shibafu} alt="green"  onClick={() => this.onClick("Mas22" )} /></td>
                        <td><img id="Mas23" src={shibafu} alt="green"  onClick={() => this.onClick("Mas23" )} /></td>
                        <td><img id="Mas24" src={shibafu} alt="green"  onClick={() => this.onClick("Mas24" )} /></td>
                        <td><img id="Mas25" src={shibafu} alt="green"  onClick={() => this.onClick("Mas25" )} /></td>
                    </tr>
                </tbody>
            </table>

流石に微妙なので修正することにしました!!
修正するにあたり、onClickに引数を渡す方法が分からず少し苦労したので備忘録で残しておこうと思います!!



■Reactでのfor文の利用

まずはおさらいを兼ねてReactでfor文を用いる方法を載せておきます。
例えば、5×5のマスを作成するコードはこちら。

MakeMap = () =>{
    var List = [];
    for(var i=1;i<=5;i++){
          var buf = [];
          for(var j=1;j<=5;j++){
             var num = (i-1)*5 + j;
             var str = "Mas" + num;
             buf.push(
                    <td>{str}</td>
             );
         }
         List.push(<tr>{buf}</tr>);
    }
    return List;
}
render(){
    return(<div>
        {this.MakeMap()}
    </div>);
}

こちらのコードを実行すると下記のような5×5のマスが作成されます。
f:id:Elsammit:20210805223602p:plain

先ほどの羅列したコードよりもよっぽどきれいに整理されています。

■for文内でのonClick定義

では本題のfor文内でのonClickの定義です。
onClickを定義するだけであれば問題ないのですが、引数を与えるとなると先ほどの、

onClick={() => this.onClick("Mas1" )} 

といった形では実行できませんでした。

ではどうするのか?
onClick関数に対してbindを用いればOKです。
コードはこちら。

onClick={this.onClick.bind(this,str)}

strには任意の変数を入れることが出来ます。

もし複数引数を与えたい場合には第3、第4、、、と引数を与えればOKです。

■先ほどの羅列したコードを置き換えてみる
では先ほどのコードを置き換えてみます。
コードはこちら。
MakeMap = () =>{
var List = ;
for(var i=1;i<=5;i++){
var buf =
;
for(var j=1;j<=5;j++){
var num = (i-1)*5 + j;
var str = "Mas" + num;
buf.push(
green
);
}
List.push({buf});
}
return List;
}
render(){
return(


{this.MakeMap()}
);
}
|

すっきり!!
しかもマス数の増減も簡単にできるようになりました。

全体のコードはこちらに格納しておりますので是非。
https://github.com/Elsammit/Moguratataki

■最後

今回は過去のコードを直していた時の備忘録をまとめてみました。
そういえば、ブログを始めてから1年以上経過したんだな。。
結構続けられている気がします。
これからの有益?無益?な情報を載せていきたいと思いますのでよろしくお願いします!!