ほわいとぼーど

ぷろぐらまのメモ帳

dstatをちょっとだけ改造する話

以前、『dstatをkibanaで可視化』の話を書いたのですが、 この時の環境で色々不足があって手を加えたり、あるいはその時に気になった部分を後で改良したり、という事があって、 前回の記事『fluentd-plugin-dstat_stdinぽいのを書いた話』も この時の1つです。

今回もその1つで、dstatでの出力内容に不足があったので追加してみた話です。
具体的にはdstat(0.7.2)のCPU関連の出力にstealを追加します。

dstatはpythonで書かれています。
apt-getで入れた場合、本体は /usr/bin/dstat にあります。
また、pluginの場合は/usr/share/dstat配下に個別ファイルで置かれる形です。

/usr/bin/dstat を見てみると、オプション項目毎にclassが切ってあって、 CPU関係の場合、class dstat_cpuになります。

class dstat_cpu(dstat):
    def __init__(self):
        self.nick = ( 'usr', 'sys', 'idl', 'wai', 'hiq', 'siq' )
        self.type = 'p'
        self.width = 3
        self.scale = 34
        self.open('/proc/stat')
        self.cols = 6
...略

class定義の部分から、/proc/statから情報を取得していて、 usr, sys, idl, wai, hiq, siq の6項目であることが想像できます。

/proc/statを見てみます。

cpu  370 0 441 313249 132 0 31 0 0 0
cpu0 370 0 441 313249 132 0 31 0 0 0
...略

この行の各数値を取得して加工して表示しています。
「cpu」がTotalで[cpu0」がcpu1個目、マルチコアなら更に行が増えます。

procのman page で/proc/statを見てみると、

①user
②nice
③system
④idle
⑤lowait
⑥irq
⑦softirq
⑧steal
⑨guest
⑩guest_nice

で、/usr/bin/dstatの実際に取得している部分を見ると

self.set2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]), long(l[5]), long(l[6]), long(l[7]) )

7番目まで使われているので、似たような形で8番目も取得するようにすればstealを表示できそうなことがわかります。

最終的に修正した差分は以下

vagrant@server:/usr/bin$ diff dstat updated__dstat
564c564
<         self.nick = ( 'usr', 'sys', 'idl', 'wai', 'hiq', 'siq' )
---
>         self.nick = ( 'usr', 'sys', 'idl', 'wai', 'hiq', 'siq', 'stl' )
569c569
<         self.cols = 6
---
>         self.cols = 7
574c574
<             if len(l) < 8 or l[0][0:3] != 'cpu': continue
---
>             if len(l) < 9 or l[0][0:3] != 'cpu': continue
609c609
<             if len(l) < 8: continue
---
>             if len(l) < 9: continue
612c612
<                     self.set2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]), long(l[5]), long(l[6]), long(l[7]) )
---
>                     self.set2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]), long(l[5]), long(l[6]), long(l[7]), long(l[8]) )
614c614
<             for i in range(6):
---
>             for i in range(7):

こんな感じで比較的簡単に修正することが可能です。
dstatがやっていることは大体が/proc配下のファイルを定期的に見て数値とってるだけなので、 慣れると色々好きな数値をとったりできそうです。 例えば自分は/proc/net/snmpUDPパケットの各値を取得するpluginを書いたりしました。

プルリクすればいいじゃないかと言われそうですが、githubのdstatレポジトリでは結構ソースが変更されていて、 stealの表示も既に入っています。逆にhiq, siqは別optionに追い出されていたり。 しかし0.7.2が出てから2年もたっていていつ更新されるかは不明な状況だったり。 悩ましいです。

あとはfluentd-plugin-mapのconfigurationの話が出来るようになると一通り・・・

fluentd-plugin-dstat_stdinぽいのを書いた話

fluentdのpluginを書いてみたかったのもあって、 fluentd-plugin-dstat_stdinぽいのを書いてみました。
fluentdはオレオレpluginをgemにしないでも実行できる仕組みがあって、更にtd-agentはそれを非常に簡単に利用できます。
具体的には/etc/td-agent/pluginにrbファイルを置くだけ。
ちょっとだけカスタマイズしたい時とかに非常に有用な仕組みだと思う。

解決したかったこと

■ fluent-plugin-dstat のIOが気になる
fluentd-plugin-dstatはdstatのCSV出力機能を利用し、temporaryに出力したCSVをreadして実現しています。 この仕組みが高IOな環境において負荷を助長するのでは?と思ったのがキッカケです。 EBSを利用している、というのもあるかも。既に性能が不足していた可能性も高い。
とはいえ、何かしら性能計測を行って比較してみたわけではありません。あしからず。

■ 30分毎にプロセス再起動でグラフに角が生える
外部CSVファイルを利用するせいかわからないけど30分たつとうまく動かなくなる問題があるらしく、 その解決のために30分毎にプロセス再起動をかけている。これがグラフに角として現れる。
原因がわかってるのでそれほど致命的ではないかもしれないけど見た目に影響するのはちょっと残念。

■ plugin書きたかった。
これに尽きる。

そこで、fluentd-plugin-jstatをパクった参考にして、標準出力を利用した形でどうなるか書いて試してみ・・・ たかったのですが元々利用していた環境が今は使えず、残念ながら性能試験的なことは行えていません。
一応、30分以上でも再起動無しに継続して実行は出来ているので2番目のIssueは解決しています。
Disk-writeとMemory使用系も下がってる(当たり前?)みたいなのですが、影響度合いを測ってないのでなんとも。
今回はplugin書くのを試してみたかったということで・・・。

注意点、違い

fluent-plugin-dstat本家はrewrite-tag-nameが使えるようなのだが自分のはまだ対応していません。
/etc/td-agent/pluginに置いた時のrequireをどうすればいいのかよくわからず一旦諦めました。

それから本家はの項目名は"total cpu usage"のようになるが、こちらは"total-cpu-usage"となる。 どちらも受け取ったものをそのまま出しているだけなのだが、 じゃぁ何が違うのかというとCSV出力と標準出力で表現が異なるため。ご了承ください。

その他気づき

パクッた真似した時にClass名とかもちゃんと変更しておかないと本家と同時に起動したときにパラメータが重複してるとか言われる。 当たり前!気を付けよう!

名前がstdinなのかstdoutなのかっていう。gemじゃないからまだ大丈夫(震え声

あと今回からmarkdown記法に変えてみた。書き方研究中

JMeterでハマッた話

JMeterでショボい理由で無駄にハマッたので自戒をこめて書く。
普段はWindows上からJMeter使ってたのだけど
テスト対象の都合でLinuxから実施したかったので
Windows上でテスト計画を作成し動作確認後、
保存したJMXファイルをLinux上にもっていって実行する。

vagrant@jmeter:~/work$ /usr/local/apache-jmeter/bin/jmeter -n -t postdata.jmx 
Creating summariser <summary>
Created the tree successfully using postdata.jmx
Error in NonGUIDriver java.lang.RuntimeException: Could not find the TestPlan class!

エラーメッセージでググるも決定打は見つからず半日浪費・・・。

結論から言うと親要素が無い不完全なJMXファイルを作成していたから。
回避するには、別名を付けてテスト計画を保存したい場合には、
・ファイルメニューの「テスト計画に名前を付けて保存」を使う。
・テスト計画を選択した状態で「別名で保存...」ボタンを押す。

どうしてハマッたか一応説明言い訳。

f:id:a3no:20140505050759j:plain

JMeterGUIの左上の画像。
初期状態では「テスト計画」と「ワークベンチ」が並んでいて、
「テスト計画」の下に画像のようにツリー上にコンポーネントを配置します。
そして出来たら実行したり保存したりします。

画像を見るとおり保存ボタンぽいのが2つあって、
左が「テスト計画を保存」、右が「別名で保存...」です。
一番最初や動作確認がとれるまでは「テスト計画を保存」を利用して、
ちょっと条件を変えたので別名で保存したい時に「別名で保存...」を使いそうですね。

しかしココに実は罠が合って、「別名で保存...」は私が思っているのと違ったという話。
ボタンと同じコマンドはファイルメニューからも実行できて、見ると、

f:id:a3no:20140505050802j:plain

新たに「テスト計画に名前を付けて保存」が見つかりました。
では、「別名で保存...」は何を保存するのか。
答えは英語表記にしてみるとわかりました。

f:id:a3no:20140505050804j:plain

「Save Selection As...」

選択したものを別名で保存する、なので
最初の画像のように子要素を選択した状態で「別名で保存...」すると
部分的な保存になってしまうというオチでした。

dstatをkibanaで可視化+3.0.0milestone5新機能

開発中の負荷試験をモニタリングしたくてdstat+Kibanaで組んでみました。
通常モニタリングは専用のツール(muninとか)で行うと思いますが、
今回はfluentdとKibanaの組み合わせにdstatを絡めてみます。

監視対象にはdstatとtd-agent、fluent-plugin-dstatを入れます。
インストール方法や細かい設定は割愛。
今回はdstat -ams 5相当を実施する設定とします。

fluent-plugin-dstatが出力する内容(1レコード)は以下になります。

2014-02-12T07:25:18+00:00	dstat	{
	"hostname":"large0000.example.com",
	"dstat":{
		"total cpu usage":{"usr":"0.400","sys":"0.500","idl":"99.0","wai":"0.100","hiq":"0.0","siq":"0.0"},
		"dsk/total":{"read":"0.0","writ":"18022.400"},
		"net/total":{"recv":"8389.600","send":"192767.200"},
		"paging":{"in":"0.0","out":"0.0"},
		"system":{"int":"170.200","csw":"119.600"},
		"memory usage":{"used":"562212864.0","buff":"394461184.0","cach":"6345080832.0","free":"510849024.0"},
		"swap":{"used":"0.0","free":"0.0"}
	}
}

改行は私が適宜入れてるので本来1行です。
dstat pluginはhostnameというパラメータを追加してくれるので
fluentdのタグを駆使してデータに格納したりする必要が無いです、便利。
3段目の"dstat"以下が実際にdstatで出力される内容に相当します。


モニタリング可視サーバ側にはtd-agentとElasticsearch、Kibanaを入れます。
使用したfluent-pluginはmap、elasticsearchです。
fluentdの設定は以下、

<source>
  type        forward
</source>

<match dstat>
  type copy
  <store>
    type map
    tag  "map.dstat.cpu-usr"
    time time
    record {"value" => record["dstat"]["total cpu usage"]["usr"], "stat" => "cpu-usr", "hostname" => record["hostname"]}
  </store>
  <store>
    type map
    tag  "map.dstat.dsk-read"
    time time
    record {"value" => record["dstat"]["dsk/total"]["read"], "stat" => "dsk-read", "hostname" => record["hostname"]}
  </store>
</match>

<match map.dstat.*>
  type elasticsearch
  type_name       dstat
  host            localhost
  port            9200
  logstash_format true
  logstash_prefix dstat
  flush_interval  3s
</match>

ここで重要なのはmapの部分になります。
"total cpu usage"とか"memory usage"のような同じグループの内容を同一グラフに表示したくなると思いますが、
dstatの結果を1レコードのまま格納してしまうとkibanaでは複合グラフにできません。
そこで1レコードをパラメータ数分だけ分割して同時刻の複数レコードに変換しています。

timestamp hostname:host a:1 b:2 c:3

timestamp hostname:host stat:a value:1
timestamp hostname:host stat:b value:2
timestamp hostname:host stat:c value:3

前述のfluentの設定はサンプルなのでmap2つしか書いてませんが、
実際にはElasticsearchに取り込みたいパラメータ分copyしてmapを書く必要があります。
今回のdstat設定であれば20パラメータですね。
map pluginにはmultiという1行で複数レコード作れる設定方法もあるのですが、
折り返して書くことが出来ず1行が長くなるので敢えてパラメータ毎に分けてます。
recordディレクティブを複数書くことが出来たらなーと思ったりはする。



これで準備は整いました。
ここからはKibanaの説明に移るのですが、
つい先日、Elasticsearchの1.0.0と併せてKibanaも3.0.0milestone5になっていますので、
一部、新機能の説明もしてみようと思います。
ではKibanaの画面を見てみます。

f:id:a3no:20140216130956j:plain

Histogramでvalueフィールドの平均値を表示しています。
"total cpu usage"と"memory usage"をそれぞれ1つのグラフにしてみました。

Elasticsearchには複数hostのdstatのデータを登録してあるのですが、
上記のグラフでは実は1台分だけにフィルタリングしてあります。
自在にフィルタリングを作成する機能もmilestone5の新機能です。

f:id:a3no:20140216130958j:plain

"FILTERING"一覧の右に出ている+を押すと、
こんな感じで入力画面が出るので、1台を特定可能なqueryを指定しています。



ここまでは1台のリソースを詳細にモニタリングしたい場合でした。
でも複数台を同時に見たい場合も当然ありますよね。
そこでmileston5の新機能であるtopNを使います。

f:id:a3no:20140216130959j:plain

query窓の●の部分をクリックして表示される画面で最初の選択肢をluceneからtopNに変更。
Fieldの所に組み合わせたいパラメータを入れます。今回はhostnameを指定。
Countは上位何個を表示したいかです。

f:id:a3no:20140216131000j:plain

hostname毎にグラフが描画されました。
指定fieldをterms facetした結果を掛け合わせてグラフを作ってくれます。
並び順を変えたり表示する対象を細かく指定したりは難しいのですが、
非常に使い道がある機能です。
例えば「アクセスを国別に」とか、「売り上げを製品毎に」とかしたいときに、
わざわざqueryで1国ずつ、1製品ずつ入力しなくても良くなるわけです。


新しい機能でsparklinesというのがあります。

f:id:a3no:20140216145834j:plain
f:id:a3no:20140216131002j:plain

query設定はHistogramの時と一緒(topN併用)です。
複数のものを形(要するに傾向)でざっと見たい場合に便利。
例えば前出のHistogramではsmall0001-0003はたいした負荷じゃないために
0付近でつぶれてしまっているのですが、
実際には何となく変動していることが見えたりします。(形が他と異なる3つ)
サービスを跨いだアクセス数とか、異なるインスタンスサイズのメモリ動向とかを
まとめて表示して傾向の変化を判断するのに使えると思います。
詳細はもっと別のグラフで見としても、対象数が増えてきた時に便利そう。


もう一つ、新しい機能terms_statsmode

f:id:a3no:20140216131003j:plain
f:id:a3no:20140216131004j:plain

terms facetをcountだけじゃなくてaggregationvalueとれるようにしたもの?
設定は色々できそうなんですがまだ把握してないので1例だけ。
色々な量を項目別に比較できます。
モニタリングというよりはデータ解析向きかも。

他にもまだ機能があったり細かく色々進化していたりしますが、今回はこんなところで。
もっと役立つユースケースとか出せるといいのですが、
まだ全然使いこなせてませんね。修行が足りない・・・

fabricをeasy_installでインストールする

前回@shiumachiさんの記事@ymotongpooさんの記事を読んだにも関わらず、
挫折してしまったと書いたのですが、
冷静に読み返すと@shiumachiさんはvirtualenvの話などしておらず、
easy_installを5分で使えるようになるということだったので
そのとおりにしてみたところ、あっさり問題なくfabric 1.8.1が手に入った。

sudo apt-get install python-distribute
sudo easy_install fabric
sudo easy_install jinja2

これだけ。前回書いたパスの設定とかも不要。

本日、elasticsearch-curatorを入れるのにpipが必要だったので
再度読み返したところサックリ解決したというもの。
お騒がせしましたという感じ。

一方、elasticsearch-curatorはインストールは出来たが、
実行時にバージョンチェックでエラーとなっており、
こちらは時間のある時に調べなければならない。
1つうまくいっても別なものがうまくいかないものです。

fabricでProxyを解決したりメモ

fabricのroleで試行錯誤した話もそうですが最近Fabricを少し触ってるのでその辺りのメモ。


①proxy経由でfabricする

"bundle installしたらプロキシに阻まれて失敗したので会社辞めます。"が話題になりましたが、、
ご多分にもれず私の現在の職場環境もProxyがかまされていて、
「Vagrantとか使う上ではまるProxyの話」とか、
Elasticsearchのplugin入れるのにjohtaniさんのブログ参考にしたりとか、
日々格闘しているわけですが、fabricももちろん例外ではありません。

fabricの場合、sshを使うのでssh_configを利用します。
ssh_configをfabricから使うには環境変数env.use_ssh_config=Trueにします。
、、、が!、env.use_ssh_configが使えるのversion 1.4からでした。
手元で試していたUbuntu12.04preciseではapt-get install fabricするとversion 1.3.2が入ります。
いきなり挫折してしまいました。

@shiumachiさんの記事@ymotongpooさんの記事を読んだにも関わらず、
virtualenvやらpipやらを入れる辺りで挫折してしまい(うぅ、、、) 追記:解決しました。
最終的にkuchitamaさんの記事を見て

wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py
python get-pip.py

で何とかpipが入り、fabricも最新version?の1.8を入れることが出来ました。
ちなみにpipはapt-getとパスが異なる(/usr/bin/pip⇔/usr/local/bin/pip)みたいなのでパスを通す必要があります。
fabricも/usr/local/bin/fabに入りますね。

あとはssh_configにproxycommand使ってproxy指定すればいいのですが、
今度はコレにはまりました。
要はproxycommandを適用する部分のソースをtypoしているみたいです。
現時点ではプルリクがまだ取り込まれていないようなので、 (1.8.2でなおったみたいです
Issueを見ながら/usr/local/lib/python2.7/dist-packages/fabric/netowrk.pyの中を修正、

-        sock = ssh.proxycommand(proxy_command)
+        sock = ssh.ProxyCommand(proxy_command)

これでproxy経由でfabricできるようになりました。
ちなみにproxycommand使えるのはfabric 1.5からみたいです。


ssh_config使用時のdisconnectバグ?
何故かたまに実行自体は終わってるのに制御が戻ってこない時があります。
どこかでIssueを見た気がするのですが今は忘れてしまいました。
ssh_config使用時のdisconnectの部分に問題があるようなのですが、
これは@parallelの時は起きない事に気づいたので、
最終コマンドにechoみたいな何でもないコマンドを入れて@parallel実行するという方法でしのぎ中。
完全なるバッドノウハウですね・・・


③パスワード入力

sshする時にenv.passwordを設定しないと実行時にプロンプトで聞かれることになりますが
(pty=Falseだと聞かれないかも??)
@parallelしていると順番保障できない旨のメッセージが出ます。

Fatal error: Needed to prompt for a connection or sudo password (host: host.example.com), but input would be ambiguous in parallel mode

そこで、自分でパスワード入力を受けて内部で設定することにします。
というかしないとsudoの度にパス入力必要ですね。
promptを使えば入力を受け付けれますが、これだとパスワードが画面上で丸見えなので、
pythongetpassを使いました。

import getpass

def _login():
    prompt('Enter the user: ', 'login_user')
    env.user = env.login_user
    env.password = getpass('Enter the password: ')

上記のメソッドをenv設定するメソッドの中で呼んでます。
が、結局、今のところ自分しか使わないプログラムだったのでenv.passwordしました。。。
開発時ということで後で戻すはず、多分。



他にも細々色々あった気がするがとりあえず。
・色つけるメソッドがあることに気づいたり
・fabric分割するならfabfileってディレクトリ作って、、、みたいなのよく見るけど、
 普通にファイル分割して単純にimportするだけでも使えた
とか。


逆に課題という意味では、
順番を制御したい時には前回の記事の最後の@parallelのようなことができず、
各ノード向けのメソッドを作らなきゃならないのが冗長で悩んでいる。

あと、UbuntuにPPAというレポジトリがあることを今日知ったので、
これを試したらapt-getでもいけたのかもしれないとか。まだ試してませんが。

まだまだ使いこなすには程遠いですね。

第3回elasticsearch勉強会 [2014/02/07(Fri.)]に参加してきました

http://elasticsearch.doorkeeper.jp/events/7491

メモって即UP満々な気で行って英語で挫折して何もメモらなかったので、
記憶を頼りに感想を書きます。


①「Geohashing with Elasticsearch」Florian Schilling, Elasticsearch Inc,

GeoHashという単語を始めて知ったほどに情弱でしたが、
場所検索をどうやって行っているか、ということは何となくわかったような。
マイクトラブルはありましたが楽しく話してもらえたので
また何か話に来てもらえるといいですね。
どれだけ聞き取れたか、、、という意味では皆無に近いかも。

②「AWSで構築するsharding」株式会社イプロス 外山 寛さん @toyama0919
http://toyama0919.bitbucket.org/elasticsearch.html#/

実際にProductionで使ってみて、という話。
相野谷さんもですが、AWS上での事例が出てくるのありがたいですね。
ec2pluginを使えば簡単とのこと。
Indexingにlogstash formatを使うかどうかという議論があって、
routingの特性も含めてIndexは1つにした方が良いのでは?という話でした。
データは100GBということでしたが、データ量がもっと多くなった場合にどうなるかですね。
pixivさんのsolrの事例で高速化のためにIndex分割する話があったので、
いずれ再Indexing必要になるのなら最初はlogstash formatで
後で適宜統合、、とか妄想したけど私は検索扱った事無いので妄想止まり

③「テスト駆動検索のススメ」株式会社じげん 多田 雅斗さん @tady_jp

検索でもTDDしような話
検索の仕様を最初からちゃんと設計できるのか?みたいなのもありますが、
仕様変わったら再Indexingになる可能性を考えても、
少なくとも現状を明示して検証できるのは非常に有効そうです。
検索も色々種類あるんだなーってわかって面白かったです。

④「MySQLユーザ視点での、小さく始めるElasticsearch」株式会社リブセンス 吉田 健太郎さん @yoshi_ken

MySQLからElasticsearchにデータレプリケーションするYamabiko中心の話。
検索用なんかでデータを入れるときに、
最初から分岐して平行で入れてる(絵がそうだけかもしれないが)例をよく見る気がするけど、
YamabikoだとMySQLに入った結果から連携する形になるので、
運用面でどういう違いがあるのかなーとか思ったり。
思っただけどまり。
単純にサービスにもよるのかもしれないですが。

⑤「nodeJS+DynamoDB+Elasticsearchで全社基盤を作った話」株式会社リクルートテクノロジーズ 相野谷 直樹さん @naokiainoya

スマホアプリに各種条件でPUSH通知するための全社基盤を作った話。
条件検索でPUSH対象のデバイスを一覧で返す。使い方が割りと独特。
ただこれってGeoHash検索に通じるような所が実はある気がする。
スキーマレスな部分を多様な条件登録に使っているということでした。
いろんな種類のユーザを全社共通でもっているリクルートさんならでは。
scanというAPIは正直知らなかった情弱です。
MultiAZでAutoScalingしているってのは興味深いです。
メインはあくまでDynamoなのでということでしたが、
今後、障害対策や運用系の知見が出てくるといいなぁと思いました。
あとキュー使うのもポイントなのかなーと思ったりも。


自分はKibanaとしてしか使ってないので今回はかなり検索メインでしたが、
クラスタ運用面なんかは実例が多数出てくると参考になりますね。
第4回は4/21だそうです。