Fluent-Bit v0.12 リリース

Fluent-Bit v0.12 が2017/08にリリースされました。parser pluginが少しずつ充実してきました。

http://fluentbit.io/download/

Fluent-Bit とは?

Cで書かれた軽量なLog forwarder です。どんなソフトかは下記記事を参照して下さい。

nokute.hatenablog.com

おおまかなTopic

  • ナノ秒サポート
  • in_netif Plugin (送受信パケット数、パケットサイズ収集)
  • in_dummy Plugin (テスト用ダミーデータ生成)
  • in_systemd Plugin (Systemdのログ収集)
  • filter_record_modifier (fieldの追加、余計なfieldの除去)
  • filter_parser (任意のfieldをparseして展開)
  • out_kafka_rest Plugin (Kafka REST Proxyサーバへの出力)
  • parser が型変換をサポート

そのほかもいくつか変更点があります。詳しくは公式のリリースノートを参照してください。

Fluent Bit v0.12.0 - Release Notes

では、個別に追って見ましょう。

ナノ秒サポート

Fluent-bitもナノ秒単位でタイムスタンプを取り扱うようになりました。

$ fluent-bit -i mem -o stdout
Fluent-Bit v0.12.0
Copyright (C) Treasure Data

[2017/08/21 22:17:46] [ info] [engine] started
[0] mem.0: [1503321467.000308167, {"Mem.total"=>3920464, "Mem.used"=>3490564, "Mem.free"=>429900, "Swap.total"=>2064380, "Swap.used"=>0, "Swap.free"=>2064380}]
[1] mem.0: [1503321468.000348090, {"Mem.total"=>3920464, "Mem.used"=>3490596, "Mem.free"=>429868, "Swap.total"=>2064380, "Swap.used"=>0, "Swap.free"=>2064380}]
[2] mem.0: [1503321469.000500532, {"Mem.total"=>3920464, "Mem.used"=>3490596, "Mem.free"=>429868, "Swap.total"=>2064380, "Swap.used"=>0, "Swap.free"=>2064380}]

従来どおりに秒単位で転送する(Fluentd v0.12系やFluent-Bit v0.11以前 に送信する場合など)

Fluentd v0.12はナノ秒単位のデータを取り扱えません。Fluentd v0.12やFluent-Bit v0.11以前に送信する(Forwardする)場合は、out_forward pluginの設定値Time_as_Integer をtrueにして使ってください。

[INPUT]
    Name  mem
    Tag   mem

[OUTPUT]
    Name  forward
    Match *
    Host  localhost
    Time_as_Integer true

in_netif Plugin (送受信パケット数、パケットサイズ収集)

ネットワークパケット数やサイズを定期収集するプラグインです。/proc/net/devを定期的にparseし、送受信パケット数やデータサイズを収集します。 監視するInterface名(e.g. eth0, ens33)が必要になりますので、ifconfigなどで確認の上ご利用ください。なお、verbose を指定すると取れるだけの情報を収集します。

$ fluent-bit -i netif -p interface=eth0 -o stdout
Fluent-Bit v0.12.0
Copyright (C) Treasure Data

[2017/08/21 22:32:30] [ info] [engine] started
[0] netif.0: [1503322351.000838300, {"eth0.rx.bytes"=>361466756, "eth0.rx.packets"=>270147, "eth0.rx.errors"=>0, "eth0.tx.bytes"=>9581090, "eth0.tx.packets"=>90412, "eth0.tx.errors"=>0}]
[1] netif.0: [1503322352.000757569, {"eth0.rx.bytes"=>98, "eth0.rx.packets"=>1, "eth0.rx.errors"=>0, "eth0.tx.bytes"=>98, "eth0.tx.packets"=>1, "eth0.tx.errors"=>0}]
[2] netif.0: [1503322353.000829344, {"eth0.rx.bytes"=>98, "eth0.rx.packets"=>1, "eth0.rx.errors"=>0, "eth0.tx.bytes"=>98, "eth0.tx.packets"=>1, "eth0.tx.errors"=>0}]

設定ファイル形式の場合は、下記のような書式で設定します。

[INPUT]
    Name netif
    Tag  netif.all
    Interface eth0

in_dummy Plugin (テスト用ダミーデータ生成)

ダミーデータを生成するためのプラグインです。軽めの動作確認やバグ報告時の異常データ作成などに使えます。Fluentdのものを参考に移植しています。

下記のようにJSONでダミーデータを設定可能です。rateによって1秒間辺りに生成するイベント数を設定できます。

[INPUT]
    Name dummy
    Tag  dummy.data
    Dummy {"type":"test", "name":"john", "age":20}
    Rate 100

[OUTPUT]
    Name stdout
    Match *

コマンドラインだと下記のようになります。

$ fluent-bit -i dummy -t dummy.data -p 'dummy={"type":"test", "name":"john", "age":20}' -p rate=100 -o stdout
Fluent-Bit v0.12.0
Copyright (C) Treasure Data

[2017/08/28 22:05:12] [ info] [engine] started
[0] dummy.data: [1503925512.520811870, {"type"=>"test", "name"=>"john", "age"=>20}]
[1] dummy.data: [1503925512.530241262, {"type"=>"test", "name"=>"john", "age"=>20}]
[2] dummy.data: [1503925512.540228138, {"type"=>"test", "name"=>"john", "age"=>20}]

in_systemd Plugin (Systemdのログ収集)

(TODO)

Systemd · Fluent Bit v0.12 Documentation

filter_record_modifier (fieldの追加、余計なfieldの除去)

filter_record_modifierを使うことでfieldの追加(Record)、削除(Remove_key, Whitelist_key)を行うことができるようになりました。 ユニークな値を付加したり、余計な情報の転送を抑制することで通信量やDiskの使用量を抑える用途に使えます。

Fieldを追加する(hostnameを追加する例)

RecordによってFieldを追加することが出来ます。 下記は環境変数を使ってkeyがhostnameなFieldを追加する例になります。

[INPUT]
    Name mem
    Tag  mem.local

[OUTPUT]
    Name  stdout
    Match *

[FILTER]
    Name record_modifier
    Match *
    Record hostname ${HOSTNAME}

コマンドラインでは下記のように書けます。

$ fluent-bit -i mem -o stdout -F record_modifier -p 'Record=hostname ${HOSTNAME}' -m '*'

出力例は下記のようになります。最後に"hostname"=>"localhost.localdomain"が追加されていることが分かるかと思います。

[0] mem.local: [1505701696.016712347, {"Mem.total"=>3920464, "Mem.used"=>3508872, "Mem.free"=>411592, "Swap.total"=>2064380, "Swap.used"=>0, "Swap.free"=>2064380, "hostname"=>"localhost.localdomain"}]

Fieldを削除する(memory, swapのused値を削除する)

削除については、Remove_key もしくは Whitelist_key で指定できます。 それぞれの違いは、

  • Remove_key: 指定されたものを削除する
  • Whitelist_key: 指定されたものを残す

点です。

では、memory情報からusedのFieldを削除する例について書いてみます。

[INPUT]
    Name mem
    Tag  mem.local

[OUTPUT]
    Name  stdout
    Match *

[FILTER]
    Name record_modifier
    Match *
    Remove_key Mem.used
    Remove_key Swap.used

もしくは[FILTER]の箇所を下記のようにWhitelist_keyを使って書くこともできます。

[FILTER]
    Name record_modifier
    Match *
    Whitelist_key Mem.total
    Whitelist_key Mem.free
    Whitelist_key Swap.total
    Whitelist_key Swap.free

これを実行すると、下記のようにMem.usedSwap.usedが削除されていることが分かります。

[0] mem.local: [1505702284.002455403, {"Mem.total"=>3920464, "Mem.free"=>423088, "Swap.total"=>2064380, "Swap.free"=>2064380}]

参考までにフィルタしない場合の出力例は下記です。

[0] mem.local: [1505702544.015796143, {"Mem.total"=>3920464, "Mem.used"=>3508940, "Mem.free"=>411524, "Swap.total"=>2064380, "Swap.used"=>0, "Swap.free"=>2064380}]

filter_parser (任意のfieldをparseして展開)

parser専用のfilter Pluginが加わりました。今まではin_tailなど一部のinput pluginしかparserをサポートしていませんでした。このfilter_parser を使うことで、任意のinput pluginが生成したFieldをparseすることができます。

下記はin_dummyで生成したFieldをparseする例になります。ここでは、"data":"100 0.5 true This is example" というFieldを生成しています。parse後は、{"INT"=>"100", "FLOAT"=>"0.5", "BOOL"=>"true", "STRING"=>"This is example"}というように変換してみましょう。

まず、下記のようにParsers file(parsers.conf)を用意します。dummy_testというparser名を定義し、正規表現を記述します。名前つきキャプチャを使用することで、展開後のkey名を定義することができます。

[PARSER]
    Name dummy_test
    Format regex
    Regex ^(?<INT>[^ ]+) (?<FLOAT>[^ ]+) (?<BOOL>[^ ]+) (?<STRING>.+)$

そしてconfig fileです。先ほどのParsers fileのパスをParsers_Fileとして記述する必要があります。

filter_parserの設定としては、Key_NameでparseするFieldのKey(ここではdata)を指定し、先ほど定義したparser名をParserによって指定します。

[SERVICE]
    Parsers_File /path/to/parsers.conf

[INPUT]
    Name dummy
    Tag  dummy.data
    Dummy {"data":"100 0.5 true This is example"}

[FILTER]
    Name parser
    Match dummy.*
    Key_Name data
    Parser dummy_test

[OUTPUT]
    Name stdout
    Match *

これを実行すると、下記のように"data":"100 0.5 true This is example"を各Key-Valueに展開することができます。デフォルトではStringとして展開されますが、後述する機能を使うことで、数値型などに変換することができます。

[0] dummy.data: [1499347993.001371317, {"INT"=>"100", "FLOAT"=>"0.5", "BOOL"=>"true", "STRING"=>"This is example"}]

out_kafka_rest Plugin (Kafka REST Proxyサーバへの出力)

kafkaについては詳しくないので公式ドキュメントを参照して下さい。

Kafka REST Proxy · Fluent Bit v0.12 Documentation

parser が型変換をサポート

parserによって展開された値はデフォルトでは文字列になります。Fluentdライクに型を指定することもできるようになりました。

下記では、"data":"100 0.5 true This is example"をparseして、integer, float, bool, 文字列に展開する例となります。 Parsers file にて、Types INT:integer FLOAT:float BOOL:boolのように、types <field_name_1>:<type_name_1> <field_name_2>:<type_name_2> .... と設定することで型を指定できます。 Fluentd とは異なり、,(コンマ)でなくスペース区切りな点に注意してください。

[PARSER]
    Name dummy_test
    Format regex
    Regex ^(?<INT>[^ ]+) (?<FLOAT>[^ ]+) (?<BOOL>[^ ]+) (?<STRING>.+)$
    Types INT:integer FLOAT:float BOOL:bool

config file は filter_parserのものを使用してください。

これによって、下記のように出力されます。細かいですが、ダブルクォート(")が一部無くなっているのが分かるでしょうか。

[0] dummy.data: [1506081688.029663388, {"INT"=>100, "FLOAT"=>0.500000, "BOOL"=>true, "message"=>"This is example"}]

Fluent-Bit v0.11 リリース

Fluent-Bit v0.11 が2017/03にリリースされました。今回のリリースはかなりすごいですよ!

http://fluentbit.io/download/

おおまかなTopic

  • in_tail Plugin (ログをはじめとするファイルの各行を収集)
  • in_disk Plugin (ストレージへの Read/Write アクセスサイズを収集)
  • in_proc Plugin (特定プロセスのメモリ使用量やfd消費数を収集)
  • out_file Plugin (ファイルへの出力)
  • Filter Plugin
  • Parser
  • out_forward に Secure forward mode追加
  • in_memの書式がfree(1)っぽくなりました

あたりでしょうか。他にも色々と新機能がありますので詳しくは公式のリリースノートを参照ください。

Fluent Bit v0.11.0 - Release Notes

では、個別に追って見ましょう。

in_tail Plugin & Parser

本家 Fluentd でも利用頻度が高いと思われるin_tail Plugin、ついにFluent-bitに実装されました! 後述のParser機能と組み合わせることで、お手持ちのログを構造化しつつ転送できます。 ログローテーションや、アスタリスク指定による複数ファイルにも対応しています。

Parser にはOnigmoが、ログファイル内の位置情報を記憶しておくために、sqlite3が使われています。 本家Fluendにはまだまだ及びませんが、プロパティが充実していますので詳しくはドキュメントを参照してください。

Tail · Fluent Bit v0.11 Documentation

in_tail 応用例

Parserを使うことで、ログの各行を構造化することができます。これ非常に強力ですよ。 nginxやApacheのParserはデフォルトで入っていますので、下記のファイルを使いましょう。 おおまかにはParserファイルを指定し、そのファイルの中のどの正規表現を使うか更に指定する流れになります。

fluent-bit/parsers.conf at master · fluent/fluent-bit · GitHub

では、nginxのログをパースして構造化してみましょう。 サンプルのログは下記から取得してみました。

github.com

-R オプションでparsers.confを指定し、その中に書かれている定義を、in_tailの"parser"プロパティで指定します。 コマンドラインでのサンプルは下記です。下記のようにログ一行が構造化されている様子が分かるかと思います。

$  fluent-bit -i tail -p 'path=./nginx_logs' -p parser=nginx  -R ../conf/parsers.conf -o stdout
Fluent-Bit v0.11.0
Copyright (C) Treasure Data

[2017/04/04 23:32:12] [ info] [engine] started
[0] tail.0: [1431817532, {"remote"=>"93.180.71.3", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"304", "size"=>"0", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"}]
[1] tail.0: [1431817523, {"remote"=>"93.180.71.3", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"304", "size"=>"0", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"}]
[2] tail.0: [1431817524, {"remote"=>"80.91.33.133", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"304", "size"=>"0", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)"}]
[3] tail.0: [1431817534, {"remote"=>"217.168.17.5", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"200", "size"=>"490", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.10.3)"}]
[4] tail.0: [1431817509, {"remote"=>"217.168.17.5", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_2", "code"=>"200", "size"=>"490", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.10.3)"}]

設定ファイルは下記。適宜ファイルパスを置き換えてください。

[SERVICE]
    Parsers_File /path/to/parsers.conf

[INPUT]
    Name        tail
    Path        /path/to/nginx_logs
    
    # nginx is defined parsers.conf
    Parser      nginx

[OUTPUT]
    Name   stdout
    Match  *

in_disk Plugin

ストレージへのRead/Write アクセスサイズを定期収集するプラグインが追加されました。 内部的には/proc/diskstatsを定期的に読み出し、前回からの差分サイズを計算・収集しています。*1 デフォルトでは、/proc/diskstats全てのアクセスについて対象としていますが、 “dev_name” プロパティを指定することでアクセス対象を限定することが出来ます。(e.g. -p dev_name=sda3 のような感じです。)

$ fluent-bit -i disk -p dev_name=sda3 -o stdout
Fluent-Bit v0.11.0
Copyright (C) Treasure Data

[2017/01/28 16:58:16] [ info] [engine] started
[0] disk.0: [1485590297, {"read_size"=>0, "write_size"=>0}]
[1] disk.0: [1485590298, {"read_size"=>0, "write_size"=>0}]
[2] disk.0: [1485590299, {"read_size"=>0, "write_size"=>0}]
[3] disk.0: [1485590300, {"read_size"=>0, "write_size"=>11997184}]

設定ファイル形式の場合は、下記のような書式で設定します。

[INPUT]
    Name disk
    Tag  disk.all
    Dev_Name sda3

in_proc Plugin

特定プロセスのメモリ使用量等を収集するプラグインになります。例えば、プロセスのリーク監視に使えるかと思います。 mem.*というキーで、/proc/PID/statusの各種メモリ使用量を収集します。*2 各々の値の詳細はman procを参照してください。オープン中のfd個数も収集します。

$ fluent-bit -i proc -p proc_name=fluent-bit -o stdout
Fluent-Bit v0.11.0
Copyright (C) Treasure Data

[2017/01/30 21:44:56] [ info] [engine] started
[0] proc.0: [1485780297, {"alive"=>true, "proc_name"=>"fluent-bit", "pid"=>10964, "mem.VmPeak"=>14740000, "mem.VmSize"=>14740000, "mem.VmLck"=>0, "mem.VmHWM"=>1120000, "mem.VmRSS"=>1120000, "mem.VmData"=>2276000, "mem.VmStk"=>88000, "mem.VmExe"=>1768000, "mem.VmLib"=>2328000, "mem.VmPTE"=>68000, "mem.VmSwap"=>0, "fd"=>18}]
[1] proc.0: [1485780298, {"alive"=>true, "proc_name"=>"fluent-bit", "pid"=>10964, "mem.VmPeak"=>14740000, "mem.VmSize"=>14740000, "mem.VmLck"=>0, "mem.VmHWM"=>1148000, "mem.VmRSS"=>1148000, "mem.VmData"=>2276000, "mem.VmStk"=>88000, "mem.VmExe"=>1768000, "mem.VmLib"=>2328000, "mem.VmPTE"=>68000, "mem.VmSwap"=>0, "fd"=>18}]
[2] proc.0: [1485780299, {"alive"=>true, "proc_name"=>"fluent-bit", "pid"=>10964, "mem.VmPeak"=>14740000, "mem.VmSize"=>14740000, "mem.VmLck"=>0, "mem.VmHWM"=>1152000, "mem.VmRSS"=>1148000, "mem.VmData"=>2276000, "mem.VmStk"=>88000, "mem.VmExe"=>1768000, "mem.VmLib"=>2328000, "mem.VmPTE"=>68000, "mem.VmSwap"=>0, "fd"=>18}]
[3] proc.0: [1485780300, {"alive"=>true, "proc_name"=>"fluent-bit", "pid"=>10964, "mem.VmPeak"=>14740000, "mem.VmSize"=>14740000, "mem.VmLck"=>0, "mem.VmHWM"=>1152000, "mem.VmRSS"=>1148000, "mem.VmData"=>2276000, "mem.VmStk"=>88000, "mem.VmExe"=>1768000, "mem.VmLib"=>2328000, "mem.VmPTE"=>68000, "mem.VmSwap"=>0, "fd"=>18}]

out_file Plugin

収集したデータをファイルに吐き出すことができるプラグインです。現在はJSONフォーマットのみの出力になります。 出力先のファイル名は"path"プロパティで指定可能ですが、未指定の場合はタグ名でファイルを生成します。

$ fluent-bit -i mem -o file
Fluent-Bit v0.11.0
Copyright (C) Treasure Data

[2017/04/04 22:58:50] [ info] [engine] started
^C[engine] caught signal

mem.0というタグ名でファイルが出力されますので、catしてみます。

$ cat mem.0 
mem.0: [1491314331, {"Mem.total":1016044, "Mem.used":772140, "Mem.free":243904, "Swap.total":2064380, "Swap.used":149084, "Swap.free":1915296}]
mem.0: [1491314332, {"Mem.total":1016044, "Mem.used":772172, "Mem.free":243872, "Swap.total":2064380, "Swap.used":149084, "Swap.free":1915296}]
mem.0: [1491314333, {"Mem.total":1016044, "Mem.used":772172, "Mem.free":243872, "Swap.total":2064380, "Swap.used":149084, "Swap.free":1915296}]
mem.0: [1491314334, {"Mem.total":1016044, "Mem.used":772148, "Mem.free":243896, "Swap.total":2064380, "Swap.used":149084, "Swap.free":1915296}]

設定ファイルによる指定は下記のような感じです。

[OUTPUT]
    Name file
    Match *
    Path output.txt

Filter Plugin

Filter Pluginのサポートが入りました。v0.11ではfilter_stdout, filter_grep, filter_kubernetesが使用可能です。 余剰な情報をフィルタすることで、過度なログ転送を抑制することができ、送信先の負荷を減らすことができます。

filter_grep

正規表現を使い、マッチするログのみ送信(Regex)、マッチするログのみ抑制(Exclude)することができます。

設定ファイルの例は下記。in_tailの例に併せて404のログのみ出力してみましょう。

[SERVICE]
    Parsers_File /path/to/parsers.conf

[INPUT]
    Name        tail
    Path        /path/to/nginx_logs
    Parser      nginx

[FILTER]
    Name  grep
    Match *
    Regex code 404

[OUTPUT]
    Name   stdout
    Match  *

出力例は下記です。404なログのみ出力されていることがお分かり頂けるでしょうか。 逆に404のログだけ除外したい場合は、上記設定ファイルの"Regex"を"Exclude"と置き換えてみてください。

$ fluent-bit -c tail.conf 
Fluent-Bit v0.11.0
Copyright (C) Treasure Data

[2017/04/04 23:52:06] [ info] [engine] started
[0] tail.0: [1431817502, {"remote"=>"217.168.17.5", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_2", "code"=>"404", "size"=>"337", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.10.3)"}]
[1] tail.0: [1431817542, {"remote"=>"217.168.17.5", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"404", "size"=>"332", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.10.3)"}]
[2] tail.0: [1431817545, {"remote"=>"46.4.66.76", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"404", "size"=>"318", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (1.0.1ubuntu2)"}]
[3] tail.0: [1431817526, {"remote"=>"93.180.71.3", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"404", "size"=>"324", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"}]
[4] tail.0: [1431817538, {"remote"=>"37.26.93.214", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_2", "code"=>"404", "size"=>"319", "referer"=>"-", "agent"=>"Go 1.1 package http"}]
[5] tail.0: [1431817511, {"remote"=>"93.180.71.3", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/downloads/product_1", "code"=>"404", "size"=>"340", "referer"=>"-", "agent"=>"Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"}]

filter_kubernetes

すいません、kubernetesに疎いのでドキュメントに丸投げします。メタデータの付加をしてくれる?そうです。

当初は組み込みにフォーカスしていたFluent-bitですが、軽量コンテナ内にも居場所を見つけたようなので、そこ向けの機能かと思われます。

Kubernetes · Fluent Bit v0.11 Documentation

out_forward に Secure forward mode追加

従来は平文通信のみだったのですが、TSL/SSLによる暗号通信にも対応したようです。 手元では試せていませんが、ドキュメントに詳しい設定方法がありますので参照ください。 Fluentdと同様に、基本的にSecure forward modeで運用することが推奨されていくと思われます。

Forward · Fluent Bit v0.11 Documentation

余談ですが、v0.11ではOutput側のみサポートで、Input側(つまりin_forward)はSecure forward には対応してなさげです。

in_memの書式がfree(1)っぽくなりました

in_memの書式がfree(1)っぽくなり、併せてSwap量も出力するようになりました。なお、単位はKBです。

$ fluent-bit -i mem -t memory -o stdout -m '*'
Fluent-Bit v0.11.0
Copyright (C) Treasure Data

[2017/03/03 21:12:35] [ info] [engine] started
[0] memory: [1488543156, {"Mem.total"=>1016044, "Mem.used"=>841388, "Mem.free"=>174656, "Swap.total"=>2064380, "Swap.used"=>139888, "Swap.free"=>1924492}]
[1] memory: [1488543157, {"Mem.total"=>1016044, "Mem.used"=>841420, "Mem.free"=>174624, "Swap.total"=>2064380, "Swap.used"=>139888, "Swap.free"=>1924492}]
[2] memory: [1488543158, {"Mem.total"=>1016044, "Mem.used"=>841420, "Mem.free"=>174624, "Swap.total"=>2064380, "Swap.used"=>139888, "Swap.free"=>1924492}]
[3] memory: [1488543159, {"Mem.total"=>1016044, "Mem.used"=>841420, "Mem.free"=>174624, "Swap.total"=>2064380, "Swap.used"=>139888, "Swap.free"=>1924492}]

そのほか

他にもElasticSearch用のoutput PluginがLogstashフォーマットに対応したり、ネストしている出力にも対応したりしています。

余談

今回のリリースですが、なんとメインコミッタの@edsiperさんがプレゼン中にリリースしたとのこと! そんな技があったとは。

*1:procfsを使っているため、Linuxのみのサポートです。

*2:これも procfsを使っているため、Linuxのみのサポートです。

IoTや組み込み向けのデータコレクタ、FluentBitについて

FluentBitとは

IoT機器や組み込み機器を対象としたオープンソースのデータコレクタです。名前から分かる通り、Fluentdの兄弟アプリで、Treasure Dataのエンジニアが中心となって開発が進められています。 このソフトでは、例えば、

  • 定期的にCPU使用率を取得し、標準出力に出力する
  • カーネルログを取得し、外部のFluentdサーバに送信する
  • センサファイルから定期的に状態を読み出し、HTTP経由で外部に送信する

といったことが可能です。

名前はFluent-Bitだったり、Fluent Bitだったり、FluentBitだったり、様々な記載が見受けられます。

特徴

本家のFluentdと比べて次のような特徴があります。

  • C言語で実装されているため、Rubyが無い環境でも動作する
  • 軽量・高速(らしい)*1
  • C API経由で自前のアプリからデータを流せる/受け取れる
  • ビルドオプションで様々にカスタマイズ可能(Plugin削減やクロスコンパイルなど)

どう使うか

IoT機器のデータをFluentdに集める用途に使われるのではないでしょうか。in_libin_headでセンサデータを取得し、out_forwardで送信する使い方です。Fluentdに一旦送信できれば、強力で多彩なFluentdのOutput Pluginを使い、より多様な送信先にセンサデータを転送できるので。

Input Plugin

2016年9月現在、下記のプラグインがサポートされています。

  • in_cpu : CPU使用率
  • in_mem : メモリ使用量
  • in_kmsg : カーネルログバッファ(/dev/kmsg)
  • in_serial: シリアル入力
  • in_stdin: 標準入力
  • in_mqtt : MQTT
  • in_head : ファイル
  • in_xbee : xbee
  • in_forward : Fluentdプロトコル
  • in_random : ランダム値を生成
  • in_lib : C API経由でデータをPushするために使う

Output Plugin

2016年9月現在、下記のプラグインがサポートされています。

  • out_es : Elasticsearch
  • out_forward : Fluentdプロトコル
  • out_http : HTTP出力
  • out_null : データ破棄用
  • out_stdout : 標準出力
  • out_td : Treasure Data
  • out_nats : NATS用
  • out_lib : C API経由でデータを取得するために使う

使い方1: コマンドとして使う

後述のビルドを行うと、fluent-bitという実行ファイルが生成されます。使い方はシンプルで-iで入力元を、-oで出力先を指定し、実行するだけです。

例えば、CPU使用率を標準出力に出力する場合は、下記のようになります。

$ fluent-bit -i cpu -o stdout

Fluentdのように、ネットワーク経由の場合は下記のようにサーバ名やポートを指定できます。

$ fluent-bit -i cpu -o forward://127.0.0.1:12225

-cオプションを使うことでコンフィグファイルを食わせて実行させることもできます。confディレクトリにサンプルが含まれているので確認してみて下さい。

$ fluent-bit -c fluent-bit.conf

Fluentdとはフォーマットが異なりますが、下記のような内容になります。[INPUT][OUTPUT]はPluginごとに指定可能なパラメータが異なるので、詳しくは公式Documentを参照してください。

[SERVICE]
    Log_Level    info
    Flush        5

[INPUT]
    Name cpu
    Tag  cpu.local

[OUTPUT]
    Name  stdout
    Match **

使い方2: 共有ライブラリとして使う

FluentBitは共有ライブラリとしても提供されている為、他のアプリケーションからFluentBitに一度ログを送信し、FluentBitからFluentdに送信してもらう、といった使い方が可能です。 C APIからデータを入力/出力するには、それぞれin_libout_libプラグインを使用します。

下記のAPIが公開されています。

  • コンテキスト生成
  • プラグインの有効化
  • プラグイン設定
  • サービス開始
  • データをPushする
  • サービス終了
  • コンテキスト破棄

in_lib経由で自前のデータをPushし、out_libに登録したコールバック関数my_stdoutでデータを受け取り標準出力に出力するサンプルです。

#include <unistd.h>
#include <fluent-bit.h>
#include <msgpack.h>

/* out_lib用のコールバック関数 */
int my_stdout(void* data, size_t size)
{   
    printf("[%s]",__FUNCTION__);
    msgpack_object_print(stdout, *(msgpack_object*)data);
    printf("\n");

    /* User has to release the buffer. */
    free(data);

    return 0;
}

int main()
{
    int i;
    int n;
    char tmp[256];
    flb_ctx_t *ctx;
    flb_input_t *input;
    flb_output_t *output;

    /* Initialize library */
    ctx = flb_create(); /* コンテキスト生成 */
    if (!ctx) {
        exit(EXIT_FAILURE);
    }

    input = flb_input(ctx, "lib", NULL); /* Input Plugin in_libを有効化 */
    flb_input_set(input, "tag", "test", NULL); /* Input Plugin の設定を行う。可変引数の最後には番兵としてNULLを与える */

    output = flb_output(ctx, "lib", my_stdout);/* Output Plugin out_libを有効化。out_libは第三引数としてコールバック関数を与えられる */
    flb_output_set(output, "match", "test", NULL);/* Output Plugin の設定を行う。可変引数の最後には番兵としてNULLを与える */

    /* Start the background worker */
    flb_start(ctx);/* サービス開始 */

    /* Push some data */
    for (i = 0; i < 100; i++) {
        n = snprintf(tmp, sizeof(tmp) - 1,
                     "[%lu, {\"key\": \"val %i\"}]",
                     time(NULL), i);
        flb_lib_push(input, tmp, n); /* データPush */
    }

    flb_stop(ctx); /* サービス停止 */

    /* Release Resources */
    flb_destroy(ctx);/* コンテキスト破棄 */

    return 0;
}

他にもexamplesにC APIを使ったサンプルが複数ありますので、確認してみて下さい。

github.com

ビルド方法(基本)

ビルドするにはCMakeが必要ですので、予めインストールしておきましょう。

ソース取得、cmake、makeという流れでビルドできます。

masterはビルドエラーになる場合があるので、バージョンタグから引っ張ってくるか、公式サイトからtarballを取得した方が良いでしょう。

$ git clone https://github.com/fluent/fluent-bit.git
$ cd build
$ cmake ..
$ make

ビルド方法(応用)

CMake時に-Dオプションを付けることで、Pluginをそぎ落としてサイズ縮小を図ったり、デバッグビルドを試したりすることができます。

Pluginを減らして、ファイルサイズを小さくする

FluentBitは組み込みを意識しているため、余剰なPluginを削減し、ファイルサイズを抑えたくなりがちです。 下記はInput Pluginの一つ、in_cpuを削減する例になります。

$ cmake .. -DFLB_IN_CPU=No
$ make

テストを実行する

FluentBitはGoogleTestを使い、UnitTestを行うことができます。

$ cmake -DFLB_TESTS=yes ..
$ make
$ make test

ロスコンパイルする

公式にはArmadillo向けのクロスコンパイル用の設定ファイルが含まれていますが、CMakeのお作法に則れば、他のプラットフォームに向けてもクロスコンパイル可能です。

以下は、RaspberryPi 2向けにクロスコンパイルする方法です。

下記をCrossCompile.cmakeというファイルに保存し、適宜CMAKE_FIND_ROOT_PATHのパスを変更しておきます。

set (CMAKE_SYSTEM_NAME Linux )

set (CMAKE_FIND_ROOT_PATH /PATH/TO/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian )

set (CMAKE_C_COMPILER   ${CMAKE_FIND_ROOT_PATH}/bin/arm-linux-gnueabihf-gcc )
set (CMAKE_CXX_COMPILER ${CMAKE_FIND_ROOT_PATH}/bin/arm-linux-gnueabihf-g++ )

set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

CMake実行時に、-DCMAKE_TOOLCHAIN_FILEとして上記ファイルパスを指定しましょう。

$ cmake .. -DCMAKE_TOOLCHAIN_FILE=./CrossCompile.cmake  -DFLB_OUT_TD=No
$ make

成功すれば、下記のようにARM向けのバイナリができているはずです。

$ file bin/fluent-bit 
bin/fluent-bit: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, not stripped

リンク

*1:v0.8.5のリリースノートによると、500kレコード/秒を処理できるとのこと

freeし忘れた領域を教えてくれるmtrace

mtraceについてのメモ。

何ができるのか

mallocして、freeし忘れたと思われる領域をピックアップしてくれる。 ただし、GNU拡張機能なので、どこの環境でも使えるわけではない。

どうやるのか

3行のソース修正、デバッグオプションビルド、トレースしながら実行、解析

の順に行う。

1. ソース修正

下記三行を追加する。

  • mcheck.hをinclude。
  • トレース開始地点にmtrace();
  • トレース終了地点にmuntrace();

2. ビルド

-g オプションを付けてデバッグビルドするだけ。

$ gcc -g test.c

3. 実行

MALLOC_TRACE環境変数で、トレース結果の出力先を指定してから実行する。

$ MALLOC_TRACE=output.txt ./a.out

4. 解析

mtraceコマンドにトレース結果ファイルとトレース対象のバイナリを引数として与えて解析する。 mtraceコマンドは、CentOSの場合は、glibc-utilsに含まれている。

free忘れと思われるアドレス、サイズ、呼び元が出力される。 よそのライブラリ内部など、デバッグビルドしていない箇所が呼び元の場合、実行アドレスのみの表示になるようだ。

$ mtrace a.out output.txt

サンプル

下記サンプルコードでお試し。

#include <stdlib.h>
#include <mcheck.h>

int main(void){
  int i;
  char* p_str = NULL;

  mtrace();

  for(i=0; i<10; i++){
    p_str = (char*)malloc(sizeof(char)*4);/* LEAK */
  }

  muntrace();
  return 0;
}

これを-gオプションを付けてコンパイル

$ gcc -g test.c

トレース結果の出力先を指定して実行。

$ MALLOC_TRACE=./trace.txt ./a.out

mtraceコマンドで出力結果を解析。

下記のようにアドレス、サイズ、呼び元が出力される。

$ mtrace a.out trace.txt 

Memory not freed:
-----------------
           Address     Size     Caller
0x00000000020d4460      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d4480      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d44a0      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d44c0      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d44e0      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d4500      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d4520      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d4540      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d4560      0x4  at /home/.../mtrace_test/test.c:11
0x00000000020d4580      0x4  at /home/.../mtrace_test/test.c:11
$

ARMでメモリテスト Raspberry Pi2 でLinuxのearly_memtest

Linux Kernel 4.1からARMでもearly_memtest可能になった

メモリチェックソフトとしてはmemtest86が有名だけれど、このソフトのチェック処理はX86アセンブラで書かれており、ARM上では動作しない。

移植作業はホネが折れるがメモリテストはしておきたい。

と、そんな折、Linux Kernel 4.1からARMのmm初期化処理になにやらearly_memtestの文字が。

4.0まではx86限定?だったCONFIG_MEMTESTがARMでも使えるようになったっぽい。

ということでLinux Kernelのメモリテストを試してみる。

4.1のカーネルソースを取得

2015/07時点で、Raspberry Piの4.1カーネルは実験版のようだ。

Raspberry Pi2のカーネルソースは下記から取得して、4.1系のブランチをチェックアウトする。

raspberrypi/linux · GitHub

4.1系は、rpi-4.1.yというブランチで管理されていたので、下記コマンドでブランチを取得した。

$ git clone https://github.com/raspberrypi/linux.git
$ git checkout -b rpi-4.1.y origin/rpi-4.1.y

ツールチェインをチェックアウトしていない場合は、それもチェックアウトする。

$ git clone https://github.com/raspberrypi/tools.git

MEMTESTのconfigを有効にする

デフォルトのconfigをLoadしてからMEMTEST設定を有効化する。

$ make ARCH=arm bcm2709_defconfig
$ make ARCH=arm menuconfig

から、Kernel hackingを選択し、Memtestを選び、設定を有効化する。

f:id:nokute:20150724211410p:plain

カーネルのクロスコンパイル

Raspberry Pi 2 カーネル4.0.7のクロスコンパイルメモ - カッコ仮メモ置き

におおよそのことは書いてあるので、参考にしてコマンドをポチポチしていく。

下記でコンパイルして。

$ make ARCH=arm CROSS_COMPILE=../tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf- zImage modules dtbs
$ scripts/mkknlimg arch/arm/boot/zImage temp/kernel7.img

下記でtemp以下に必要なものを集める。

$ mkdir temp
$ mkdir temp/overlays
$ make ARCH=arm CROSS_COMPILE=../tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf- INSTALL_MOD_PATH=./temp  modules_install
$ cp arch/arm/boot/dts/*.dtb temp
$ cp arch/arm/boot/dts/overlays/*.dtb* temp/overlays
$ cp arch/arm/boot/dts/overlays/README temp/overlays
$ rm temp/lib/modules/4.1.2-v7+/source temp/lib/modules/4.1.2-v7+/build

インストール

上記のtempをscpなどでRaspberry Pi2に転送して、下記コマンドを実行する。

$ sudo cp temp/kernel7.img /boot/kernel7.img
$ sudo cp -r temp/lib /
$ sudo chown -R root /lib/modules
$ sudo chgrp -R root /lib/modules
$ sudo cp temp/*dtb /boot
$ sudo cp -r temp/overlays/ /boot

reboot後にunameでバージョンを確認する。

pi@raspberrypi ~ $ uname -a
Linux raspberrypi 4.1.2-v7+ #4 SMP PREEMPT Fri Jul 24 20:20:26 JST 2015 armv7l GNU/Linux

cmdline設定

menuconfigのhelpによると、cmdlineにてmemtest=という記述をして回数を指定する必要があるらしい。

/boot/cmdline.txtにmemtest=2を追記してみる。

pi@raspberrypi ~ $ sudo vi /boot/cmdline.txt
pi@raspberrypi ~ $ cat /boot/cmdline.txt
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait memtest=2

memtestの動作確認結果

dmesgに下記のような出力がなされていれば動いているはず。

memtestを回すようにcmdline設定すると、起動後もシングルコアのままになる問題があったが、ひとまずスルーする。

pi@raspberrypi ~ $ dmesg
<中略>
[    0.000000] early_memtest: # of tests: 2
[    0.000000]   0000000000 - 0000000100 pattern ffffffffffffffff
[    0.000000]   0000008000 - 0000008240 pattern ffffffffffffffff
[    0.000000]   000091f74c - 003a7f2000 pattern ffffffffffffffff
[    0.000000]   003a7ff000 - 003a7ffe48 pattern ffffffffffffffff
[    0.000000]   0000000000 - 0000000100 pattern 0000000000000000
[    0.000000]   0000008000 - 0000008240 pattern 0000000000000000
[    0.000000]   000091f74c - 003a7f2000 pattern 0000000000000000
[    0.000000]   003a7ff000 - 003a7ffe48 pattern 0000000000000000
<中略>

どのようなテストか

実装はmm/memtest.cにある。

予め決まったパターンで8byteごとに値を書きこみ、その後の読み出しで化けていないかをチェックしているようだ。 パターンは4bit周期で0x0から0xfを繰り返した16パターンと、ASCIIでyeah ;-)となる数字列の17パターン。

ループ回数ごとに17パターンのうちのどれかを実施するようで、非常にシンプルなテスト。

memtest86+ほどの検知力は期待しない方が良いかも?

NGな領域があった場合は

ソースによると、下記のような文言が出力されるようだ。

static void __init reserve_bad_mem(u64 pattern, phys_addr_t start_bad, phys_addr_t end_bad)
{
    printk(KERN_INFO "  %016llx bad mem addr %010llx - %010llx reserved\n",
           (unsigned long long) pattern,
           (unsigned long long) start_bad,
           (unsigned long long) end_bad);
    memblock_reserve(start_bad, end_bad - start_bad);
}

Raspberry Pi 2 カーネル4.0.7のクロスコンパイルメモ

(参考サイト)

Kernel Building - Raspberry Pi Documentation

RaspberryPi - Raspberry Piのカーネルをクロスコンパイルする - Qiita

Raspberry Pi2のカーネルの再構築とGW-450Dを使えるようにする。 - 石を売る

raspberry pi 2のカーネルソースとツール取得

githubから取得。git pullしないなら--depth 1 をつけた方が早く取得できると思う。

$ git clone https://github.com/raspberrypi/linux.git
$ git clone https://github.com/raspberrypi/tools.git

取得した時点で4.0.7だった。

pi@raspberrypi ~ $ uname -a
Linux raspberrypi 4.0.7-v7+ #3 SMP PREEMPT Wed Jul 8 23:31:48 JST 2015 armv7l GNU/Linux

環境構築 CentOS 6.6 x86_64

いろいろと怒られたので、zlib.i686とlibstdc++.i686yumで導入した。

$ sudo yum install zlib.i686 libstdc++.i686

ビルド

config設定

下記コマンドでデフォルトのコンフィグで.configが生成される。旧Raspberry Piの場合は引数が違うらしいので注意。

$ make ARCH=arm bcm2709_defconfig

カーネルカーネルモジュール、デバイスツリーのビルド

gitで取得したtoolsのパスをCROSS_COMPILE以降に適切に入力する。

64bit CentOSでトライしたものの、x64だとライブラリが不足していたので32bitツールチェインでコンパイルした。

$ make ARCH=arm CROSS_COMPILE=../tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf- zImage modules dtbs

コンパイルされたものを集める

適当なディレクトリを作っておいて、そちらにモジュールやらカーネルイメージやらを集める。 下記のコマンドだとtemp以下に集まる。

$ mkdir temp
$ mkdir temp/overlays
$ make ARCH=arm CROSS_COMPILE=../tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf- INSTALL_MOD_PATH=./temp  modules_install
$ scripts/mkknlimg arch/arm/boot/zImage temp/kernel7.img
$ cp arch/arm/boot/dts/*.dtb temp
$ cp arch/arm/boot/dts/overlays/*.dtb* temp/overlays
$ cp arch/arm/boot/dts/overlays/README temp/overlays

sourceとbuildのシンボリックリンクが出来ていたので、転送サイズを減らすためにも消しておく。

シンボリックリンクを削除するので、パスの最後に/を付けないように注意。

$ rm temp/lib/modules/4.0.7-v7+/source temp/lib/modules/4.0.7-v7+/build

インストール

Raspberry Pi2の場合、カーネルイメージは/boot/kernel7.imgらしい。7が付いていない方は旧Raspberry Pi用とのこと。

適当なディレクトリ下に集めた雑多なファイルをRaspberry Pi2にscpして、下記コマンドをRaspberry Pi上で実行する。

$ sudo cp temp/kernel7.img /boot/kernel7.img
$ sudo cp -r temp/lib /
$ sudo chown -R root /lib/modules
$ sudo chgrp -R root /lib/modules
$ sudo cp temp/*dtb /boot
$ sudo cp -r temp/overlays/ /boot

終わったら再起動する。失敗すると虹色画面で止まってしまうので、LED点灯が消えたら電源OFFして、SDカードの中身を修復してやりなおし。