概要
業務でEC2のインスタンスサイズの選定を行う機会がありました。
選定にあたって、開発環境のインスタンスに負荷をかけ、その負荷状況から本番のインスタンスで許容したいアクセス数にはどのくらいのリソースが必要かを見積もりとして出す、ということをしてみました。
その過程でインスタンスの各種統計情報をローカルに落として手元でプロットしてみたので備忘録として記しておきます。
方針としては、
- インスタンスにSSH接続
-
vmstat
にて統計情報を取得 - 上記をローカルのファイルに追記
- ローカルでファイルをwatchしておいて、更新をプロットに反映
という手順で行ってみました。
EC2ならcloudwatch logs agent使えばいいのでh 聞こえませんね。
統計の取集
まずssh経由でインスタンスの統計情報を取得し、ローカルに落としてくる必要があります。
と言ってもコマンド一つで済むのが楽なところですね。
今回はCPU使用率だけ取れれば十分だったので、vmstat
の出力結果にタイムスタンプを付与し、CSV形式に加工するにとどめました。
実際のコマンドは以下のようになりました。
ssh (user@)host "vmstat -n 1 | awk 'BEGIN{OFS=\",\"} { \$1=\$1; print strftime(\"%s\") , \$0; system(\"\") }; '" > /tmp/vmstat.log
これで1秒ごとにインスタンスの統計情報がローカルのファイルに追記されます。
vmstat
の頻度がミリ秒単位で指定できなそうなので、1秒以上の頻度で収集したい場合は別のコマンドか、do while
等でうまいことしてあげる必要がありそうです。
出力の加工にあたってawk
コマンドを利用しましたが、こいつがなかなか癖のあるやつで。。
こちらの記事が詳しく掘られてらっしゃいますね。
https://zariganitosh.hatenablog.jp/entry/20131209/minimum_awk
awk 'BEGIN{OFS=","} { $1=$1; print strftime("%s") , $0; system("") };
まずコマンドの先頭でOFS(output field separator)をカンマ区切りに変換していますが、その後のメインパートで何かしらのフィールド操作がないと有効にならないらしく、$1=$1
でOFSの指定を有効化しています。
また、awk
コマンドは通常だとバッファがコマンド終了まで出力されないらしく、リアルタイムに出力を出したい場合は、明示的にバッファを吐き出してあげる必要があります。
awk
ではsystem("")
を呼び出すことでそれまでのバッファがflushされるようです。
このテクニックはドキュメントにも記載があります。
https://www.gnu.org/software/gawk/manual/html_node/I_002fO-Functions.html#index-buffers-2
統計のプロット
そしていよいよ収集した情報のプロットです。正直上記ができていれば後はもうなんでもいいと思うのですが、今回はpyqtgraphによるリアルタイムプロットを使ってみました。
pythonのグラフの描写用ライブラリといえば、一番有名なのは間違いなくmatplotlibだと思うのですが、欠点の一つにスピードの遅さがあります。pyqtgraphはリアルタイムプロットにおいて分があるようです。
ファイルの監視
まずローカルに落としたファイルに追記があるかを別スレッドで定期的に確認させます。
追記があった場合、プロット対象のデータをそれぞれの配列にappend
します。
今回はmemory free,cpu usage,cpu io waitをプロットしてみました。
import time
import threading
class AwkClass:
def handle_line(line: str):
pass
class VmstatAwkClass(AwkClass):
count: int = 0
def __init__(self) -> None:
super().__init__()
self.timestamps = []
self.memory_frees = []
self.cpu_uses = []
self.cpu_was = []
def handle_line(self, line: str):
self.count += 1
# ヘッダ分はスキップする
if self.count <= 2:
return
if line.endswith("\n"):
line = line[:-1]
columns = list(map(lambda s: float(s), line.split(",")))
# timestamp r b swpd free buff cache si so bi bo in cs us sy id wa st
timestamp, memory_free, cpu_us, cpu_wa = columns[0], columns[4], columns[13], columns[16]
self.timestamps.append(timestamp)
self.memory_frees.append(memory_free)
self.cpu_uses.append(cpu_us)
self.cpu_was.append(cpu_wa)
class Watcher:
def __init__(self, file_path: str, awk: AwkClass):
self.file_path = file_path
self.aws = awk
def activate(self):
self.fs = open(self.file_path)
self.flag = True
thread = threading.Thread(target=self._watch)
thread.start()
def _watch(self):
while self.flag:
line = self.fs.readline()
if not line:
time.sleep(0.1)
continue
self._handle_line(line)
def _handle_line(self, line: str):
self.aws.handle_line(line)
if __name__ == "__main__":
file_path = "/tmp/vmstat.log"
vmstat_awk = VmstatAwkClass()
watcher = Watcher(file_path=file_path, awk=vmstat_awk)
watcher.activate()
ファイルのプロット
あとは上記で作った配列を定期的に出力するプロット用のwidgetを作って起動すれば完了です。
まず利用するライブラリをinstallします。
pip3 install pyqtgraph PyQt5
from PyQt5 import QtWidgets, QtCore
import pyqtgraph as pg
import sys
from filewatch import Watcher, VmstatAwkClass
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, windowTitle: str, awk: VmstatAwkClass, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.awk = awk
timestamp, mem_free, cpu_us, cpu_wa = self.__get_current_array()
self.graphWidget = pg.GraphicsLayoutWidget(
show=True, title=windowTitle)
self.graphWidget.setBackground('w')
pen = pg.mkPen(color=(255, 0, 0))
self.memory_free_plot = self.graphWidget.addPlot(
title="memory free").plot(timestamp, mem_free, pen=pen)
self.cpu_us_plot = self.graphWidget.addPlot(
title="cpu us").plot(timestamp, cpu_us, pen=pen)
self.cpu_wa_plot = self.graphWidget.addPlot(
title="cpu wa").plot(timestamp, cpu_wa, pen=pen)
self.timer = QtCore.QTimer()
self.timer.setInterval(50)
self.timer.timeout.connect(self.update_plot_data)
self.timer.start()
def __get_current_array(self):
return self.awk.timestamps[-100:], self.awk.memory_frees[-100:], self.awk.cpu_uses[-100:], self.awk.cpu_was[-100:]
def update_plot_data(self):
timestamp, mem_free, cpu_us, cpu_wa = self.__get_current_array()
self.memory_free_plot.setData(timestamp, mem_free)
self.cpu_us_plot.setData(timestamp, cpu_us)
self.cpu_wa_plot.setData(timestamp, cpu_wa)
if __name__ == "__main__":
awk = VmstatAwkClass()
watcher = Watcher(f"/tmp/vmstat.log", awk)
watcher.activate()
app = QtWidgets.QApplication(sys.argv)
w = MainWindow(f"vmstat instance", awk)
w.show()
sys.exit(app.exec_())
各項目のデータを最大100個表示するようにしました。
横軸はタイムスタンプなので時系列になっています。
こいつを実行してやるとめでたくリアルタイムにプロットされます。(ディスプレイがちっちゃいので少し窮屈なグラフになってしまいましたが、、、)
python3 plot.py
あとは色々収集項目を変えたり、プロットの形式を変えたりすればいい感じの資料が作れそうですね。最後まで読んでいただきましてありがとうございます。