プロセスのメモリ使用量とCPU使用率などが取れる mackerel-plugin-linux-proc-stats をリリースしました
プロセスのメモリ使用量と CPU 使用率などのメトリクスが取れる、
mackerel-plugin-linux-proc-stats をリリースしました。
Installation
https://github.com/tkuchiki/mackerel-plugin-linux-proc-stats/releases
からバイナリをダウンロードして解凍してください。
Usage
$ ./mackerel-plugin-linux-proc-stats --help Usage of ./mackerel-plugin-linux-proc-stats: -follow-child-processes Follow child processes -metric-key-prefix string Metric key prefix -pid string PID -pidfile string PID file -tempfile string Temp file name -version Version
このプラグイン独自のオプションを説明すると、
pid
: PID を指定pidfile
: PIDファイルの pathfollow-child-processes
: 子プロセスまで集計するか
となっています。
follow-child-processes
について説明するために実行例を示します。
nginx の master と worker プロセスが各1つずつ起動しているとします。
その状態で master プロセスの情報だけ取得する場合は、以下のように実行します。
$ ./mackerel-plugin-linux-proc-stats -pidfile /var/run/nginx.pid nginx_process.cpu.usage 0.000000 1458808477 nginx_process.memory.vsize 107450368.000000 1458808477 nginx_process.memory.rss 3399680.000000 1458808477 nginx_process.num.running 0.000000 1458808477 nginx_process.num.processes 1.000000 1458808477 nginx_process.num.threads 1.000000 1458808477
master と worker プロセス両方の情報を合算して取得する場合は、以下のように実行します。
$ ./mackerel-plugin-linux-proc-stats -pidfile /var/run/nginx.pid -follow-child-processes nginx_process.num.running 0.000000 1458808496 nginx_process.num.processes 2.000000 1458808496 nginx_process.num.threads 2.000000 1458808496 nginx_process.cpu.usage 0.000095 1458808496 nginx_process.memory.vsize 215269376.000000 1458808496 nginx_process.memory.rss 7966720.000000 1458808496
子プロセスまでしか追わないので、孫プロセスは対象外です(実装するか検討中)。
メトリクスについて
このプラグインは、/proc/PID/stat
を parse しています。
num.running
: 3番目が R かどうかnum.processes
: 指定したプロセスのプロセス数(follow-child-processes
をつけた場合は子プロセス含む)num.threads
: 20番目の値cpu.usage
: 15, 16 , 17, 22番目の値 とプロセスの起動時間、uptime、getconf CLK_TCK
の値を使って計算memory.vsize
: 23番目の値memory.rss
: 24番目の値
num.running
については、実行時間が長い処理を行わない限りは R にならないので、0 の場合がほとんどです。
/proc/PID/stat
の例を載せておきます。
10440 (nginx) S 1 10440 10440 0 -1 4202816 71 0 0 0 0 0 0 0 20 0 1 0 364149088 107450368 830 18446744073709551615 1 1 0 0 0 0 0 1073746048 402745863 18446744073709551615 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0
IAM の Switch Role を捗らせる
AWS のクロスアカウントアクセスを利用すると、
IAM ユーザを AWS アカウントごとに作成しなくても良くなり管理が容易になるのですが、
Switch Role する アカウントが多いと、履歴が 5 件しかないのでアカウントとロールを再入力しなくてはならないことが増えてきます。
ロールを切り替えるユーザーアクセス権限の付与 を見ると、
とあります。
Alias は以下を例にすると Your_AWS_Account_ID
です(参考: AWS アカウント ID とその別名)。
https://signin.aws.amazon.com/switchrole?YourAccount=hogehoge&roleName=YourRole
にアクセスすると、
アカウントとロールが入力された状態でアクセスすることができます。
また、ロールの切り替え(AWS マネジメントコンソール) を見ると、
とあり、 displayName
を指定すると表示名も入力した状態にできます。
また、color=カラーコード
で色を指定することもできます。
指定できる色は左から順に、
- F2B0A9 (何も指定しなくてもこれになります)
- FBBF93
- FAD791
- B7CA9D
- 99BCE3
です(右端の黒は指定方法がわかりませんでした)。
color についてはドキュメントに書いていないようでしたので、
変更される可能性があります。
最後に、account, roleName, displayName, color を全部指定した場合の例を示します。
https://signin.aws.amazon.com/switchrole?account=YourAccount&roleName=YourRole&displayName=YourDisplayName&color=99BCE3
にアクセスすると、
すべて入力済みになります。
この URL をブックマークしておくと、各項目を入力しなおさなくても良くなるので便利です。
まとめ
Switch Role の URL の query string に、
- account = alias または account ID
- roleName = ロール名
- displayName = 表示名
- color = カラーコード
を指定すると、各項目を入力済みの状態にできるので便利です。
setusergroups の RPM spec ファイル
setusergroups については、作者の tokuhirom さんのブログを御覧ください。
supplementary groups をサポートする setuidgid であるところの setusergroups.c 書いた
supplementary groups をサポートした setuidgid が欲しいケースがある。
これを相談されたので RPM の spec ファイルを書きました。
以下を ~/rpmbuild/SPECS/setusergroups.spec
として保存して、
curl -L https://github.com/tokuhirom/setusergroups/archive/master.zip > ~/rpmbuild/SOURCES/master.zip rpmbuild -ba ~/rpmbuild/SPECS/setusergroups.spec
で RPM を作成できます。
autoconf
のバージョンが 2.68 以上でないと build できないので、
CentOS 6 で build する場合は base repo 以外のものを使う必要があります。
norikra-listener-zabbix 0.2.0 をリリースしました
norikra-listener-zabbix (rubygems) 0.2.0 をリリースしました。
変更点
group
の記述を変更
port
を廃止し、ZABBIX_SERVER:[PORT]
と記述するようになりました。
-- group ZABBIX(localhost:10051, zabbix host)
のように記述します。
IPv6 サポート
IPv6 に対応しました。
-- group ZABBIX([::1]:10051, zabbix host)
のように、[ ]
で IPアドレスをくくらないとエラーなります。
group
の prefix_item_key
を省略可能に
prefix の指定が必須ではなくなりました。
指定しない場合は、列名がそのままアイテムキーとして使用されます。
列の別名に .
を使いたい場合、$
を指定すると .
に置き換えてアイテムキーとして使用します。
なぜこのような仕様になっているかというと、
Identifiers cannot contain the "." (dot) character, i.e. "vol.price" is not a valid identifier for the rename syntax.
とあるように、.
が使えないので、Zabbix のアイテムキーとして使えない文字列かつ列の別名に使える記号として $
を採用しています(あまり美しくないですが...)。
例は以下のとおりです。
SELECT sum(foo) AS `bar$foo$sum`, avg(foo) AS `bar$foo$avg` FROM test_table.win:time_batch(1 min)
参考
Norikra の集計結果を直接 Zabbix に送る norikra-listener-zabbix をリリースしました
追記(2016/02/08 15:30):0.2.0 で group
の記述方式を変更しましたので、
norikra-listener-zabbix 0.2.0 をリリースしました をご確認ください。
Norikra から直接 Zabbix にデータを送る norikra-listener-zabbix (rubygems) をリリースしました。
Zabbix へデータを送信するコードのほとんどは、
fujiwara/fluent-plugin-zabbix を使わせていただきました。ありがとうございます。
Installation
gem install norikra-listener-zabbix
Norikra をインストールした gem で gem install すれば自動で有効になります。
Usage
Group を以下のように設定します。
ZABBIX(zabbix_server, zabbix_host, preifx_item_key, [port=10051])
- zabbix_server:
localhost
- zabbix_host:
test server
- prefix_item_key:
nginx
の場合は、以下のとおりです。
ZABBIX(localhost, test server, nginx)
prefix_item_key については、アイテムキーの prefix です。
port は Zabbix Server デフォルトの 10051 を使っている場合は省略できます。
利用例
秒間のリクエスト数を HTTP ステータスコードごとに集計する場合を考えます。
SQL
ステータスコードごとに COUNT
して秒間のデータにします。
SELECT COUNT(1, 200 <= status AND status <= 299) / 60 AS rate_2xx, COUNT(1, 300 <= status AND status <= 399) / 60 AS rate_3xx, COUNT(1, 400 <= status AND status <= 499) / 60 AS rate_4xx, COUNT(1, 500 <= status AND status <= 599) / 60 AS rate_5xx FROM nginx_status.win:time_batch(1 min)
Zabbix
アイテム
prefix_item_key とSQL の列名(rate_2xx
など) を .
で連結したアイテムを作成します。
prefix_item_key は nginx
、列名は rate_2xx
, rate_3xx
, rate_4xx
, rate_5xx
です。
作成するアイテムは以下のようになります。
- タイプ:
Zabbix トラッパー
- キー:
nginx.rate_2xx
,nginx.rate_3xx
,nginx.rate_4xx
,nginx.rate_5xx
- データ型:
数値 (浮動小数点)
1 つの SQL で複数のアイテムにデータを送信することができます。
グラフ
グラフは適宜作成してください。
2xx のデータしかないのであまり良くない例ですが、
それぞれのステータスコードのリクエストが送られてくれば集計されます。
その他
Zabbix 周りのコードはほとんど fujiwara/fluent-plugin-zabbix を使わせていただきましたが、
一部変更した部分があります。
また、実装する上でハマったところがあったのでそのことに触れておきます。
Zabbix 周りの改良点
Zabbix Server にデータを送信すると、
{ "response" => "success", "info" => "Processed 2 Failed 0 Total 2 Seconds spent 0.000103" }
のようなレスポンスが返って来ます。
このとき、info
の Failed が 1 以上だったり、Processed が 0 で全て Failed というケースでも response
は success になります。
Processed が 0 でなければ 1 つ以上データが送れていることになるので良いのですが、
全て Failed した場合は失敗にしたほうがわかり易いと思い、 warn
でログを出すようにしています
(実装している時、ログに何も出ないのにデータが送れていなくて理解するのに時間がかかりました)。
全て Failed になるケースは、以下の 2 つを確認しています。
- Zabbix ホスト名が間違っている
- アイテムキーが間違っている
split(",")
だけだと空白が含まれる
Norikra::Listener::Zabbix.initialize
の argument
を ,
で split するときに、
ZABBIX(zabbix_server, zabbix_host, preifx_item_key)
(わかりにくいですが ,
の後ろにスペースがあります)と書いて、
zabbix host とアイテムキーにスペースが含まれて正常に動作しない問題で1時間くらい嵌まりました...
そこで、スペースを気にしなくても良いように split したあとに strip しています。
def self.parse_argument(args) args.split(",").map(&:strip) end
Listener Plugin を書く方は、この処理をしておくと変なところで嵌らなくて良いのでおすすめです。
まとめ
Norikra Listener Plugin の norikra-listener-zabbix をリリースしました。
是非お使いください!
Consul Multiple Datacenters 検証結果 +α
この記事は、HashiCorp Advent Calendar 2015 17日目の記事です。
検証環境の Consul は、
$ consul --version Consul v0.6.0 Consul Protocol: 3 (Understands back to: 1)
です。
検証環境は v0.6.0
ですが、v0.6.0 に依存した話しはほとんどないと思います。
筆者は、Consul を Multiple Datacenters(以降、Multi-DC) で運用しております。
Multi-DC で Consul を導入する際に検証した結果を記します。
トピックは以下のとおりです。
- WAN
- DC をまたいで DNS を引く
- DC をまたいで KV のデータを取得する
- DC をまたいで KV にデータを入れる
- Consul Event は複数の DC に一斉に送れない
- DNS forward に Unbound を使う
- External Services
準備
Consul のバイナリを入手します。
$ curl -LO https://releases.hashicorp.com/consul/0.6.0/consul_0.6.0_linux_amd64.zip $ unzip consul_0.6.0_linux_amd64.zip
実験のために何台もサーバを立てるのは大変なので、
検証用に 1 台のサーバで consul を複数起動する - tkuchikiの日記 のような手順で、
1 台のサーバに Server x 3, Client x 1 のクラスタを 2セット作ります。
dc01
$ ./consul members Node Address Status Type Build Protocol DC consul01-dc01 10.0.2.15:8301 alive server 0.6.0 2 dc01 consul02-dc01 10.0.2.15:8311 alive server 0.6.0 2 dc01 consul03-dc01 10.0.2.15:8321 alive server 0.6.0 2 dc01 consul04-dc01 10.0.2.15:8331 alive client 0.6.0 2 dc01
$ curl http://127.0.0.1:8500/v1/status/leader "10.0.2.15:8300"
dc02
$ ./consul members -rpc-addr=127.0.0.1:18400 Node Address Status Type Build Protocol DC consul01-dc02 10.0.2.15:18301 alive server 0.6.0 2 dc02 consul02-dc02 10.0.2.15:18311 alive server 0.6.0 2 dc02 consul03-dc02 10.0.2.15:18321 alive server 0.6.0 2 dc02 consul04-dc02 10.0.2.15:18331 alive client 0.6.0 2 dc02
$ curl http://127.0.0.1:18500/v1/status/leader "10.0.2.15:18300"
WAN
WAN は、すべての DC を含めた Consul クラスタ全体のことです。
consul members -wan
は Multi-DC の Consul Server のみを返します。
$ ./consul members -wan Node Address Status Type Build Protocol DC consul01-dc01.dc01 10.0.2.15:8302 alive server 0.6.0 2 dc01 consul01-dc02.dc02 10.0.2.15:18302 alive server 0.6.0 2 dc02 consul02-dc01.dc01 10.0.2.15:8312 alive server 0.6.0 2 dc01 consul02-dc02.dc02 10.0.2.15:18312 alive server 0.6.0 2 dc02 consul03-dc01.dc01 10.0.2.15:8322 alive server 0.6.0 2 dc01 consul03-dc02.dc02 10.0.2.15:18322 alive server 0.6.0 2 dc02
client に向けて consul members -wan
を実行すると、
何も出力せず、status code 2 を返します。
$ ./consul members -rpc-addr=127.0.0.1:8430 -wan $ echo $? 2
DC をまたいで DNS を引く
dc01
の Consul に対して、dc02
の node を問い合わせます。
$ dig @127.0.0.1 -p 8630 consul04-dc02.node.dc02.consul A ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.2 <<>> @127.0.0.1 -p 8630 consul04-dc02.node.dc02.consul A ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19552 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;consul04-dc02.node.dc02.consul. IN A ;; ANSWER SECTION: consul04-dc02.node.dc02.consul. 60 IN A 10.0.2.15 ;; Query time: 1 msec ;; SERVER: 127.0.0.1#8630(127.0.0.1) ;; WHEN: Tue Dec 15 17:58:09 2015 ;; MSG SIZE rcvd: 94
A レコードが返ってきます。
DNS Interface - Consul by HashiCorp に書いてありますが、
<node>.node[.datacenter].<domain>
なので、
同一 DC にいる場合は、 [.datacenter]
を省略することができます。
$ dig @127.0.0.1 -p 8630 consul04-dc01.node.consul A ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.2 <<>> @127.0.0.1 -p 8630 consul04-dc01.node.consul A ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46271 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;consul04-dc01.node.consul. IN A ;; ANSWER SECTION: consul04-dc01.node.consul. 60 IN A 10.0.2.15 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#8630(127.0.0.1) ;; WHEN: Tue Dec 15 18:05:00 2015 ;; MSG SIZE rcvd: 84
DC をまたいで KV のデータを取得する
まず、通常の KV にデータを PUT して取り出す場合です。
dc01
に PUT します。
$ curl -X PUT -d "dc01-test" http://127.0.0.1:8500/v1/kv/dc01 true
$ curl http://127.0.0.1:8500/v1/kv/dc01?raw dc01-test $ curl http://127.0.0.1:8510/v1/kv/dc01?raw dc01-test $ curl http://127.0.0.1:8520/v1/kv/dc01?raw dc01-test $ curl http://127.0.0.1:8530/v1/kv/dc01?raw dc01-test
dc01
内で KV のデータが共有されていますね。
この状態で、dc02
の KV を参照すると、
$ curl http://127.0.0.1:18500/v1/kv/dc01?raw
データが入っていませんね。
これは、仕様なのでおかしい挙動ではありません。
追記: 2016-08-23 19:30
KV をまたいでデータを取得するためには、以下のように dc=
をつければ OK です。訂正致します。
curl -s 'data' http://127.0.0.1:18500/v1/kv/data?dc=dc02&raw"
ちなみに、consul-replicate というものがあるので、
これを使えば KV のデータをレプリケーションすることができます。
しかし、
- consul-replicate をどのサーバで動かすか
- consul-replicate の HA構成はどうするか
- consul-replicate のレプリケーションが一方向なので、DC 間での同期はどうするのか
といった問題があるので、使いどころが難しそうです。
DC をまたいで KV にデータを入れる
追記: 2016-08-23 19:30
curl -s -X PUT -d 'data' http://127.0.0.1:18500/v1/kv/data?dc=dc02"
のように、dc=
をつければ datacenter をまたいで KV にデータをいれることができました。
間違ってた情報を書いていたので、そちらは削除します。
Consul Event は複数の DC に一斉に送れない
consul event
には、-datacenter
というオプションがありますが、
指定できる DC は一つだけです。
複数の DC に Event を送りたい場合は、DC ごとに実行するようです。
Unbound で DNS forward する
Dnsmasq で Consul DNS に forward して内部 DNS に使っている方が多いと思いますが、
Unbound でも forward できます。
# unbound.conf server: access-control: 127.0.0.0/8 allow do-not-query-localhost: no forward-zone: name: "consul" forward-addr: 127.0.0.1@8600
Unbound の設定に明るくなくて、
デフォルトの設定に上記設定を追加したらうまく forward できませんでした...
設定が最低限すぎるので、適宜設定を追加してお使いください。
External Services
Consul Service は、Consul クラスタ内のサービスを監視しますが、
External Services を使うと、外部のサービスを監視することができます。
$ curl -X PUT -d '{"Datacenter": "dc01", "Node": "google", "Address": "www.google.com", "Service": {"Service": "search", "Port": 80}}' http://127.0.0.1:8500/v1/catalog/register true
External Services を使うためには、recursor
または recursors
で DNS サーバを指定しなくてはなりません。
consul01-dc01(port 8600) は recursor
を設定せず、
consul02-dc01(port 8610) は設定しているとします。
その場合、以下の様な実行結果になります。
$ dig @127.0.0.1 -p 8600 search.service.consul ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.2 <<>> @127.0.0.1 -p 8600 search.service.consul ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56306 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; WARNING: recursion requested but not available ;; QUESTION SECTION: ;search.service.consul. IN A ;; ANSWER SECTION: search.service.consul. 15 IN CNAME www.google.com. ;; Query time: 0 msec ;; SERVER: 127.0.0.1#8600(127.0.0.1) ;; WHEN: Tue Dec 15 19:41:34 2015 ;; MSG SIZE rcvd: 88
$ dig @127.0.0.1 -p 8610 search.service.consul ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.2 <<>> @127.0.0.1 -p 8610 search.service.consul ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26004 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;search.service.consul. IN A ;; ANSWER SECTION: search.service.consul. 15 IN CNAME www.google.com. www.google.com. 149 IN A 216.58.220.196 ;; Query time: 11 msec ;; SERVER: 127.0.0.1#8610(127.0.0.1) ;; WHEN: Tue Dec 15 19:42:01 2015 ;; MSG SIZE rcvd: 118
recursor
を設定した consul02-dc01 は、A レコードが返ってきます。
recursor
の反映は consul reload
ではできませんので、再起動する必要があります。
※追記
recursor を設定していない状態からの反映は consul reload
ではできませんが、
recursor を設定している状態からは consul reload
で変更を反映することができます。
まとめ
Consul の Multi-DC について検証した結果 +α を紹介しました。
使った設定ファイルは https://gist.github.com/tkuchiki/c4ea89df94c8e2a51b83 に置いてあります。
明日は、Multiple DNS recursors の対応ありがとうございます!(http://fstn.hateblo.jp/entry/2015/02/21/024400)
な、@foostan さんです!
mackerel-plugin-rack-stats を例にした mackerel agent plugin の作り方
この記事は、Mackerel Advent Calendar 2015 16日目の記事です。
mackerel-agent-plugins には様々な plugin がありますが、無い場合は自作する必要がありますよね。
ということで、mackerel-plugin-rack-stats を例に mackerel agent plugin の作り方を紹介します。
mackerel-plugin-rack-stats
raindrops の stats を収集する plugin です。
raindrops が Linux でしか動かないようですので、
必然的にこのプラグインも Linux でしか利用できません。
mackerel agent plugin の作り方
go-mackerel-plugin-helper を使うと簡単に実装することができます。
mackerel-agent-plugins の中には go-mackerel-plugin を使っているものもありますが、
1年近く更新されていないようですので、go-mackerel-plugin-helper を使うのが良さそうです。
それでは、順に説明していきます。
func main()
// mackerel-plugin-rack-stats package main import ( "flag" "fmt" ... mp "github.com/mackerelio/go-mackerel-plugin-helper" ) func main() { optAddress := flag.String("address", "http://localhost:8080", "URL or Unix Domain Socket") optPath := flag.String("path", "/_raindrops", "Path") optMetricKey := flag.String("metric-key", "", "Metric Key") optVersion := flag.Bool("version", false, "Version") optTempfile := flag.String("tempfile", "", "Temp file name") flag.Parse() if *optVersion { fmt.Println("0.2") os.Exit(0) } var rack RackStatsPlugin rack.Address = *optAddress rack.Path = *optPath rack.MetricKey = *optMetricKey helper := mp.NewMackerelPlugin(rack) if *optTempfile != "" { helper.Tempfile = *optTempfile } else { helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-rack-stats") } helper.Run() }
オプション解析に標準モジュールの flag を使っていますが、
機能が充実したライブラリを使用しても良いと思います。
以降は、処理ごとに分割して説明します。
MackerelPlugin
の生成
// mackerel-plugin-rack-stats var rack RackStatsPlugin rack.Address = *optAddress rack.Path = *optPath rack.MetricKey = *optMetricKey helper := mp.NewMackerelPlugin(rack)
go-mackerel-plugin-helper.NewMackerelPlugin
で MackerelPlugin
構造体を生成します。
MackerelPlugin
は Plugin
を変数に持っており、
Plugin
は FetchMetrics()
と GraphDefinition()
を実装する必要があります。
それぞれの定義は以下のとおりです。
// go-mackerel-plugin-helper type Plugin interface { FetchMetrics() (map[string]interface{}, error) GraphDefinition() map[string]Graphs } type MackerelPlugin struct { Plugin Tempfile string } func NewMackerelPlugin(plugin Plugin) MackerelPlugin { mp := MackerelPlugin{plugin, "/tmp/mackerel-plugin-default"} return mp }
FetchMetrics()
と GraphDefinition()
の説明は後ほど行います。
GraphDefinition()
の返り値である Graphs
と、
その引数である Metrics
の定義は以下のとおりです。
// go-mackerel-plugin-helper type Graphs struct { Label string `json:"label"` Unit string `json:"unit"` Metrics []Metrics `json:"metrics"` } type Metrics struct { Name string `json:"name"` Label string `json:"label"` Diff bool `json:"diff"` Type string `json:"type"` Stacked bool `json:"stacked"` Scale float64 `json:"scale"` }
Graphs
と Metrics
の詳細にも触れておきましょう。
Graphs
Label
赤枠で囲った memory
の部分の設定です。
Unit
float
, integer
, percentage
, bytes
, bytes/sec
, iops
のいずれかを指定します。
Metrics
Name
カスタムメトリック名です。
ワイルドカードとして、*
, #
を使うことができます。
Label
赤枠で囲った used
, buffers
などの設定です。
API仕様(v0) の /api/v0/graph-defs/create
に書いてありますが、
%1、%2.. のようにしてメトリック名の1つ目、2つ目.. のワイルドカードにマッチした部分を利用することができます。
とあるように、%N
を指定することもできます。
Diff
グラフを差分値で出力する設定です(デフォルト false
)。
Type
データの型です。
float64
, uint32
, uint64
のいずれかを指定します(デフォルト float64
)。
Stacked
積み重ねグラフにする設定です(デフォルト false
= 線グラフ)。
Scale
文字通り目盛りで、Mackerel に送る値を調整してくれます。
例えば、free -k
の実行結果を parse して、Mackerel に送る plugin があったとします。
free -k
の実行結果は単位が KB なので、Bytes にする場合 1024 を乗算する必要があります。
Scale
を 1024 とした場合、1024 を乗算して Mackerel にデータを送ってくれます。
Tempfile
if *optTempfile != "" { helper.Tempfile = *optTempfile } else { helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-rack-stats") }
差分値の出力に使います。
Tempfile
は差分値の出力以外では使っていなさそうでした。
mp := MackerelPlugin{plugin, "/tmp/mackerel-plugin-default"}
となっているので、
Tempfile
の設定をしないと/tmp/mackerel-plugin-default
に実行結果のJSONが出力されます。
plugin 実行
helper.Run()
でグラフの設定とメトリック収集処理を実行します。
他のプラグインを見ると、ほとんどが以下のような実装になっています。
if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" { helper.OutputDefinitions() } else { helper.OutputValues() }
しかし、MackerelPlugin.Run()
は内部で同じことを行っているため、
Run()
を使うのが良いでしょう。
なぜ、2015-12-16 現在、ほとんどのプラグインで使われていないかのかというと、
Run()
が実装されたのが da2aa407cda88bca4a1159f32bfdcee078ad20fc のコミットで、
2015-12-03 に実装されたばかりだからですね。
mackerel-agent が、環境変数 MACKEREL_AGENT_PLUGIN_META
をつけて実行した場合は、
OutputDefinitions()
が実行され、グラフ定義の json が出力されます。
この時、GraphDefinition()
が実行されます。
環境変数がついていない場合は、OutputValues()
が実行され、
メトリックが出力されます。
こちらは、FetchMetrics()
と GraphDefinition()
の両方が実行されます。
FetchMetrics() (map[string]interface{}, error)
メトリックの収集処理です。
mackerel-plugin-rack-stats では、 GET /_raindrops
のレスポンスを parse して map[string]interface{}
を生成します。
細かい処理は省略しますが、map[string]interface{}
は以下のようになっています。
map[string]interface{ "active": N.nnnnnn, "queued": N.nnnnnn, "calling": N.nnnnnn, "writing": N.nnnnnn, }
このとき、map[string]interface{}
のキーが、
GraphDefinition()
の map[string](mp.Graphs)
の Metrics.Name
と
対応している必要があります(この例では、active
, queued
, calling
, writing
)。
GraphDefinition() map[string]Graphs
func (u RackStatsPlugin) GraphDefinition() map[string](mp.Graphs) { scheme, path, err := parseAddress(u.Address) if err != nil { log.Fatal(err) } var label string if u.MetricKey == "" { switch scheme { case "http": _, port, _ := net.SplitHostPort(path) u.MetricKey = port label = fmt.Sprintf("Rack Port %s Stats", port) case "unix": u.MetricKey = strings.Replace(strings.Replace(path, "/", "_", -1), ".", "_", -1) label = fmt.Sprintf("Rack %s Stats", path) } } else { label = fmt.Sprintf("Rack %s Stats", u.MetricKey) } return map[string](mp.Graphs){ fmt.Sprintf("%s.rack.stats", u.MetricKey): mp.Graphs{ Label: label, Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "queued", Label: "Queued", Diff: false}, mp.Metrics{Name: "active", Label: "Active", Diff: false}, mp.Metrics{Name: "writing", Label: "Writing", Diff: false}, mp.Metrics{Name: "calling", Label: "Calling", Diff: false}, }, }, } }
1 ホストに複数設定できるように、map[string](mp.Graphs)
のキーを変更できるようになっているため処理が複雑になっていますが、
スルーして return している map[string](mp.Graphs)
に注目してください。
わかりやすくするために、fmt.Sprintf
で生成しているキーや label
を展開した状態の map[string](mp.Graphs)
を以下に示します。
map[string](mp.Graphs){ "8080.rack.stats": mp.Graphs{ Label: "Rack Port 8080 Stats", Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "active", Label: "Active", Diff: false}, mp.Metrics{Name: "queued", Label: "Queued", Diff: false}, mp.Metrics{Name: "calling", Label: "Calling", Diff: false}, mp.Metrics{Name: "writing", Label: "Writing", Diff: false}, }, }, }
前述のとおり、Metrics.Name
の値が、
FetchMetrics()
が返す map[string]interface{}
のキーと
対応している必要があります(この例では、active
, queued
, calling
, writing
)。
Metrics.Name
にワイルドカードを使う場合
Metrics.Name
にワイルドカードとして、*
と #
を使うことができます。
ワイルドカードを使う場合、GraphDefinition()
の map[string](mp.Graphs)
のキーと
FetchMetrics()
が返す map[string]interface{}
のキーを
連結したものにしなくてはなりません。
map[string](mp.Graphs)
のキーが 8080.rack.stats
だった場合、
map[string]interface{}
は
// FetchMetrics() map[string]interface{ "8080.rack.stats.active": N.nnnnnn, "8080.rack.stats.queued": N.nnnnnn, "8080.rack.stats.calling": N.nnnnnn, "8080.rack.stats.writing": N.nnnnnn, }
となります。
ワイルドカードを使うと、GraphDefinition()
の map[string](mp.Graphs)
を
以下のように書き換えることができます。
// GraphDefinition() return map[string](mp.Graphs){ "8080.rack.stats" : mp.Graphs{ Label: "Rack Port 8080 Stats", Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "*", Label: "%1", Diff: false}, }, }, }
前述したとおり、ワイルドカードにマッチした部分を Label でつかうことができます。
map[string](mp.Graphs)
のキー にワイルドカードを使う場合
ワイルドカードは、map[string](mp.Graphs)
のキーにも使うことができます。
こちらは、グラフの凡例をグループ化する場合に使います。
mackerel-plugin-rack-stats には当てはまりませんが、
FetchMetrics()
が以下のようなデータを返すとし、
// FetchMetrics() map[string]interface{ "rack.foo.stats.active": 10.000000, "rack.foo.stats.queued": 10.000000, "rack.foo.stats.calling": 10.000000, "rack.foo.stats.writing": 10.000000, "rack.hoge.stats.active": 5.000000, "rack.hoge.stats.queued": 5.000000, "rack.hoge.stats.calling": 5.000000, "rack.hoge.stats.writing": 5.000000, }
GraphDefinition()
の map[string](mp.Graphs)
が以下のような実装になっているとします。
// GraphDefinition() map[string](mp.Graphs){ "rack.#.stats": mp.Graphs{ Label: "Rack test Stats", Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "active", Label: "Active", Diff: false}, mp.Metrics{Name: "queued", Label: "Queued", Diff: false}, mp.Metrics{Name: "calling", Label: "Calling", Diff: false}, mp.Metrics{Name: "writing", Label: "Writing", Diff: false}, }, }, }
その場合、グラフは
となります。
foo
と hoge
でグループ化されていますね。
API仕様にも書いてありますが、
またグラフ定義のメトリック名にはワイルドカード(* または #)を 2つのドット(.)の間、または最後のドット(.)の後ろに単独で使用することができます。
とあるので、#.rack.stats
のようには定義できないようです。
ちなみに、#
と *
の使い分けについては、
ワイルドカード # を使った場合はメトリック名の# にマッチした部分でグラフの凡例がグループ化されます。
とありますし、色々なプラグインを見たところ、map[string](mp.Graphs)
のキーは #
、
Metrics.Name
では *
が使われていましたので、慣例に従うと良いと思います。
またワイルドカード # は一つまでしか使えません。メトリック名全体は ^custom(.[-a-zA-Z0-9_]+|[*#])+ のようになります。
という制約もありますのでご注意ください。
以上が mackerel agent plugin の作り方でした。
長々と説明しましたが、
- 自作の
Plugin
構造体 Plugin
構造体にFetchMetrics() (map[string]interface{}, error)
Plugin
構造体にGraphDefinition() map[string]Graphs
を実装するだけですのでお手軽ですね!
mackerel-plugin-rack-stats の使い方
mackerel-plugin-rack-stats の説明があまりできていなかったので軽く紹介します。
-address
で指定した Port または Unix Domain Socket に、
-path
で指定した(デフォルト /_raindrops
) に GET リクエストを送ります。
1ホストに Rack server が複数起動している場合にも対応できるように、
-metric-key
を指定すると、別のメトリックとして登録することができます。
-metric-key
を指定しない場合は、
PORT.rack.stats
や _path_to_unicorn_sock.rack.stats
のようなメトリック名になります。
config の例は以下のとおりです。
[plugin.metrics.rack_stats] command = "/path/to/mackerel-plugin-rack-stats -address=unix:/path/to/unicorn.sock"
[plugin.metrics.rack_stats] command = "/path/to/mackerel-plugin-rack-stats -address=http://localhost:8080"
他にも Rack の stats を収集する gem がありましたら教えて下さい...
まとめ
mackerel-plugin-rack-stats を例に mackerel agent plugin の作り方を紹介しました。
go-mackerel-plugin-helper を使うと簡単ですので、皆さんも実装してみてください!
17日目の担当は、@shiozaki さんです!