- はじめに -
この記事はXonsh Advent Calendar 2017 4日目の記事です。
Xonshの中にはxontribというメソッド群が存在します。
その中のmplhooksは、画像の描画をサポートしてくれます。
本記事では以下について記述します
- xontrib.mplhooksを利用したxonshコマンドプロンプト上へのMatplotlibグラフのインライン描画
- vm_statコマンドを利用したMac OS Xのメモリ状況の取得とPythonによるparse
- Xonshでメモリ状況のリアルタイム可視化
以下アジェンダです
- - はじめに -
- - Xonshのグラフインライン描画 -
- - vm_statコマンドでメモリ可視化 -
- - vm_statをMatplotlibでリアルタイム描画 -
- - MatplotlibのAnimationモジュールを使ってリアルタイム描画を実現する -
- - おわりに -
- Xonshのグラフインライン描画 -
Xonshでグラフは端末状にインライン描画する方法は、以下の2種類が容易されています。
- xontrib.mplhooks.display_figure_with_iterm2(fig)
- xontrib.mplhooks.show()
以下がソースコードです。
xontrib.mplhooks — xonsh 0.5.12.dev97 documentation
xonsh/mplhooks.py at master · xonsh/xonsh · GitHub
Pythonなのですぐ読めます。
display_figure_with_iterm2は、 iterm2_toolsを使ってiTerm2上にグラフを描画するようになっています。
showは、iterm2_toolsがimportできればdisplay_figure_with_iterm2で描画、iterm2_toolsが存在しない場合はplt.gcf()で現状プロット図を取得してきてRGB値をcolor stringにして描画する形になっています。
iTerm2でインライン描画
私はiTerm2利用者ですので以下でiterm2_toolsを導入しています。
pip install iterm2_tools
GitHub - asmeurer/iterm2-tools: iTerm2 tools for Python
以下エラーが出るのでXonshコンソールを再起動しておきます。
NameError: name 'display_image_bytes' is not defined
再起動後、Xonshコンソール上で以下を淡々と入力していきます。
もしくは適当にxshスクリプトにしてもOKです。
import matplotlib.pyplot as plt import xontrib.mplhooks import numpy as np x = np.linspace(-3, 3, 20) y = x ** 2 fig = plt.figure(figsize=(6, 4)) ax1 = fig.add_subplot(1, 1, 1) ax1.plot(x, y) xontrib.mplhooks.display_figure_with_iterm2(fig)
Terminal.app上のxonshでインライン描画
試しにMacにデフォルトで付随するTerminal.appでもXonsh上でインライン描画をしてみます。
上記iTerm2の時と同じGraphを作って以下。
xontrib.mplhooks.show()
iTerm2の描画は良いですね
itermplotでインライン描画
上記2つに加えてiTerm2にはインライン描画パッケージとして、itermplotというのがあります。
GitHub - daleroberts/itermplot: An awesome iTerm2 backend for Matplotlib, so you can plot directly in your terminal.
Animation supportもされており、インラインでリアルタイム表示ができそうです。
(といっても事前にGIF画像にしてiTermの表示機能に送る形ですが…)
導入はpipで、xonshを起動する前にMPLBACKENDにitermplotを指定しておくとplt.show()がインライン描画になります。
pip install itermplot export MPLBACKEND="module://itermplot"
また、後述しますがリアルタイム表示するにはImageMagickが必要なのでインストールしておきます。
brew install imagemagick
sampleスクリプトを動かしてみます。
import matplotlib.pyplot as plt plt.plot([1,2,3]) plt.show()
背景が黒い場合は以下で良さげな表示に
export ITERMPLOT=rv
- vm_statコマンドでメモリ可視化 -
Macでメモリ状況を出力するコマンドではfreeやtop、vm_statといった選択肢があります。
今回は適当にvm_statを選択します。
Xonshであれば以下コマンドどちらも結果を得ることができます
vm_stat $(vm_stat) # python string
vm_statコマンドの結果をparseしてdictにする関数を作って実行してみます。
def vms(): vm_dic = {} for i,x in enumerate($(vm_stat).split("\n")): row = x.split(":") if i>1 and len(row)==2: vm_dic[row[0]] = int(row[1].strip().replace(".",""))*4096/(1024.0 ** 3) return vm_dic
以下のようにGB単位でメモリ状況が出力されます。
$ vms() {'"Translation faults"': 2588.0195083618164, 'Anonymous pages': 7.266414642333984, 'Compressions': 54.13356018066406, 'Decompressions': 33.81982421875, 'File-backed pages': 2.6624984741210938, 'Pageins': 32.48748779296875, 'Pageouts': 0.26355743408203125, 'Pages active': 4.984306335449219, 'Pages copy-on-write': 82.98124694824219, 'Pages inactive': 4.944099426269531, 'Pages occupied by compressor': 3.5574417114257812, 'Pages purgeable': 0.14667129516601562, 'Pages purged': 1.3369598388671875, 'Pages reactivated': 22.909423828125, 'Pages speculative': 0.000507354736328125, 'Pages stored in compressor': 8.809425354003906, 'Pages throttled': 0.0, 'Pages wired down': 2.446277618408203, 'Pages zero filled': 1392.7644996643066, 'Swapins': 16.50379180908203, 'Swapouts': 17.286388397216797}
せっかくですのでwatchコマンドのように、メモリ状況をリアルタイムで表示し続けてみます。
import json while True: vm_dic = vms() $[clear] print(json.dumps(vm_dic, indent=4))
watch vm_statでよくない!?みたいな何かが出来上がりました。
vms functionをprintするスクリプトにして、watchもできます。
watchコマンドはデフォルトでsh -cにコマンドを飛ばすだけなので、--exec, -xで指定できるよう環境設定するか、以下のようにxonshを定期的に作るという酷いやつで対応できます。
# mac -> $ brew install watch watch "xonsh sample.xsh"
watch vm_statでよくない!?みたいな何かです。
- vm_statをMatplotlibでリアルタイム描画 -
上記2項目で作った関数を利用して、Matplotlibでvm_statの結果をリアルタイム描画してみます。
wired, active, inactive, speculative辺りがusedメモリに関連する項目ですのでそちらを可視化してみます。
(occupied by compressorも必要かな?)
スクリプトが適当です。
import time import xontrib.mplhooks import numpy as np import matplotlib.pyplot as plt plt.style.use('ggplot') def init_data(fig, id, title): x = np.zeros(100) y = np.zeros(100) subplot = fig.add_subplot(2,2,id) subplot.set_title(title) subplot.set_ylabel('GB') subplot.set_xlabel('time') li, = subplot.plot(x, y) return x, y, subplot, li def set_data(x,y,vm_dic,key): x = np.append(x, time.time()) x = np.delete(x, 0) y = np.append(y, vm_dic[key]) y = np.delete(y, 0) return x,y def update_fig(x, y, subplot, li): li.set_xdata(x) li.set_ydata(y) subplot.set_ylim(min(y), max(y)) subplot.set_xlim(min(x), max(x)) def watch_vms_windows(): fig = plt.figure() x,y,subplot,li = init_data(fig, 1, "Pages active") x2,y2,subplot2,li2 = init_data(fig, 2, "Pages inactive") x3,y3,subplot3,li3 = init_data(fig, 3, "Pages speculative") x4,y4,subplot4,li4 = init_data(fig, 4, "Pages wired down") count = 0 while True: vm_dic = vms() x,y = set_data(x, y, vm_dic, "Pages active") x2,y2 = set_data(x2, y2, vm_dic, "Pages inactive") x3,y3 = set_data(x3, y3, vm_dic, "Pages speculative") x4,y4 = set_data(x4, y4, vm_dic, "Pages wired down") if count > 100: update_fig(x, y, subplot, li) update_fig(x2, y2, subplot2, li2) update_fig(x3, y3, subplot3, li3) update_fig(x4, y4, subplot4, li4) plt.pause(.01) else: count += 1 print(count, end="\r")
以下参考にしています。
Arduino で測定したデータを Matplotlib でリアルタイムプロット | org-技術
100ループ回してGraph描画に十分なデータを集めたらGraph描画を開始するスクリプトです。
QuickTime Playerを利用して画面の動画を撮っているのでactiveがガンガン上昇しinactiveが下降、メモリ利用と廃棄が頻繁に行われている事がわかります。
$[clear] して display_figure_with_iterm2(fig) でインライン描画するスクリプトにする事も可能で、試しましたがdisplay_figure_with_iterm2では実行時描画になるため、思ったより綺麗に表示されませんでした。
- MatplotlibのAnimationモジュールを使ってリアルタイム描画を実現する -
Matplotlibにはpltで作られた配列をアニメーションにして表示するモジュールが存在する。
全てのGraphを事前に配列に取っておいて描画するArtistAnimation、動的に生成していくFuncAnimationの2つである。
animation module — Matplotlib 2.1.0 documentation
Animationはgifやmp4を生成するだけなので、これらを前述したitermplotに流してiTerm2にインライン描画する。
itermplotのカッケー背景はAnimationには対応してないので以下だけ設定しておく。
export MPLBACKEND="module://itermplot" export ITERMPLOT_FRAMES=100
適当に書いたコード
import time import xontrib.mplhooks import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation plt.style.use('ggplot') class VMS: def __init__(self, fig): self.fig = fig self.x, self.y, self.subplot, self.li = self.init_data(1, "Pages active") self.x2, self.y2, self.subplot2, self.li2 = self.init_data(2, "Pages inactive") self.x3, self.y3, self.subplot3, self.li3 = self.init_data(3, "Pages speculative") self.x4, self.y4, self.subplot4, self.li4 = self.init_data(4, "Pages wired down") self.c = 0 for i in range(100): self.plotsub(i) def init_data(self, id, title): x = np.zeros(100) y = np.zeros(100) subplot = self.fig.add_subplot(2,2,id) subplot.set_title(title) subplot.set_ylabel('GB') li, = subplot.plot(x, y) return x, y, subplot, li def vms(self): vm_dic = {} for i,x in enumerate($(vm_stat).split("\n")): row = x.split(":") if i>1 and len(row)==2: vm_dic[row[0]] = int(row[1].strip().replace(".",""))*4096/(1024.0 ** 3) return vm_dic def count(self): self.c += 1 def plotsub(self, frame): vm_dic = self.vms() self.count() # subplot1 self.x = np.append(self.x, self.c) self.x = np.delete(self.x, 0) self.y = np.append(self.y, vm_dic["Pages active"]) self.y = np.delete(self.y, 0) self.li.set_xdata(self.x) self.li.set_ydata(self.y) self.subplot.set_ylim(min(self.y), max(self.y)) self.subplot.set_xlim(min(self.x), max(self.x)) self.subplot.set_xticks(np.arange(min(self.x), max(self.x), 20)) # subplot2 self.x2 = np.append(self.x2, self.c) self.x2 = np.delete(self.x2, 0) self.y2 = np.append(self.y2, vm_dic["Pages inactive"]) self.y2 = np.delete(self.y2, 0) self.li2.set_xdata(self.x2) self.li2.set_ydata(self.y2) self.subplot2.set_ylim(min(self.y2), max(self.y2)) self.subplot2.set_xlim(min(self.x2), max(self.x2)) self.subplot2.set_xticks( np.arange(min(self.x2), max(self.x2), 20) ) # subplot3 self.x3 = np.append(self.x3, self.c) self.x3 = np.delete(self.x3, 0) self.y3 = np.append(self.y3, vm_dic["Pages speculative"]) self.y3 = np.delete(self.y3, 0) self.li3.set_xdata(self.x3) self.li3.set_ydata(self.y3) self.subplot3.set_ylim(min(self.y3), max(self.y3)) self.subplot3.set_xlim(min(self.x3), max(self.x3)) self.subplot3.set_xticks( np.arange(min(self.x3), max(self.x3), 20) ) # subplot4 self.x4 = np.append(self.x4, self.c) self.x4 = np.delete(self.x4, 0) self.y4 = np.append(self.y4, vm_dic["Pages wired down"]) self.y4 = np.delete(self.y4, 0) self.li4.set_xdata(self.x4) self.li4.set_ydata(self.y4) self.subplot4.set_ylim(min(self.y4), max(self.y4)) self.subplot4.set_xlim(min(self.x4), max(self.x4)) self.subplot4.set_xticks( np.arange(min(self.x4), max(self.x4), 20) ) fig = plt.figure(figsize=(8,8)) vms = VMS(fig) animation.FuncAnimation(vms.fig, vms.plotsub, blit=True) plt.show()
plt.show()がitermplotによって継承されてればインラインで見れる
これは動的に見れている訳ではなく、事前に100フレーム分をgif画像にしてbyteIOでiTerm2に送り込んでいるだけなのでリアルタイムの本質を見失っている状態である。
iTerm2に表示できるGIFのフレーム数を上手くコントロールできないとか課題は多いがまあできたという事で…